blob: 4fea9ce117839faa88fff9d3d07bb84ec494b231 [file] [log] [blame]
/*
* Copyright (c) 2018-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef PAS_TRY_REALLOCATE_H
#define PAS_TRY_REALLOCATE_H
#include "pas_bitfit_directory.h"
#include "pas_deallocate.h"
#include "pas_large_map.h"
#include "pas_reallocate_free_mode.h"
#include "pas_reallocate_heap_teleport_rule.h"
#include "pas_try_allocate.h"
#include "pas_try_allocate_array.h"
#include "pas_try_allocate_intrinsic.h"
#include "pas_try_allocate_primitive.h"
PAS_BEGIN_EXTERN_C;
typedef pas_typed_allocation_result
(*pas_try_reallocate_allocate_callback)(pas_heap* heap,
size_t new_count,
void* arg);
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_allocate_for_reallocate_and_copy(
pas_heap* source_heap,
pas_heap* target_heap,
void* old_ptr,
size_t old_size,
size_t new_count,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_try_reallocate_allocate_callback allocate_callback,
void* allocate_callback_arg)
{
static const bool verbose = false;
pas_typed_allocation_result result;
/* If heaps are based on some rigorous notion of type, then we should disallow heap
teleporting since that's just type confusion.
But if heaps are based on something sloppy like allocation site then we have no choice
but to allow heap teleporting.
We could have made this part of the heap_config, but making it a separate parameter means
we could hypothetically have a heap_config that allows both. */
switch (teleport_rule) {
case pas_reallocate_allow_heap_teleport:
/* The only danger here is that new_count and old_count were in
different units. But that should not matter:
- We only care about the old size, not the old count. We get a
size from the allocator.
- We end up computing the new size using the new count and the
new heap.
Then we take the min of those two. No big deal if the old count
and new count had different units. */
break;
case pas_reallocate_disallow_heap_teleport: {
if (source_heap != target_heap) {
pas_reallocation_did_fail(
"Attempting to teleport heaps",
source_heap, target_heap, old_ptr, old_size, new_count);
}
break;
} }
result = allocate_callback(target_heap, new_count, allocate_callback_arg);
if (result.ptr) {
if (verbose)
pas_log("result.ptr = %p\n", result.ptr);
/* FIXME: We need to actually be rounding down the old size to our
type size. To do that we'd want the typed allocation result to
give us the type size. For primitive allocations, it would
statically give us 1.
Also: this only matters for thingy because of fat pointers,
and there it might not even matter that much since fat pointers
are aligned (I think). */
size_t copy_size = PAS_MIN(result.size, old_size);
memcpy(result.ptr, old_ptr, copy_size);
}
return result;
}
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_table_segregated_case(pas_page_base* page_base,
uintptr_t begin,
pas_heap* heap,
size_t new_count,
pas_segregated_page_config segregated_config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode,
pas_try_reallocate_allocate_callback allocate_callback,
void* allocate_callback_arg)
{
size_t old_size;
pas_typed_allocation_result result;
pas_heap* old_heap;
pas_segregated_page* page;
page = pas_page_base_get_segregated(page_base);
switch (teleport_rule) {
case pas_reallocate_allow_heap_teleport:
old_size = pas_segregated_page_get_object_size_for_address_in_page(
page, begin, segregated_config);
old_heap = NULL;
break;
case pas_reallocate_disallow_heap_teleport: {
pas_segregated_size_directory* directory;
directory = pas_segregated_page_get_directory_for_address_in_page(
page, begin, segregated_config);
old_size = directory->object_size;
old_heap = pas_heap_for_segregated_heap(directory->heap);
break;
} }
result = pas_try_allocate_for_reallocate_and_copy(
old_heap, heap, (void*)begin, old_size, new_count, teleport_rule,
allocate_callback, allocate_callback_arg);
if (result.ptr || free_mode == pas_reallocate_free_always)
pas_deallocate_known_segregated((void*)begin, segregated_config);
return result;
}
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_table_bitfit_case(pas_page_base* page_base,
uintptr_t begin,
pas_heap* heap,
size_t new_count,
pas_bitfit_page_config bitfit_config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode,
pas_try_reallocate_allocate_callback allocate_callback,
void* allocate_callback_arg)
{
size_t old_size;
pas_typed_allocation_result result;
pas_bitfit_page* page;
pas_heap* old_heap;
page = pas_page_base_get_bitfit(page_base);
old_size = bitfit_config.specialized_page_get_allocation_size_with_page(page, begin);
switch (teleport_rule) {
case pas_reallocate_allow_heap_teleport:
old_heap = NULL;
break;
case pas_reallocate_disallow_heap_teleport:
old_heap = pas_heap_for_segregated_heap(
pas_compact_bitfit_directory_ptr_load_non_null(
&pas_compact_atomic_bitfit_view_ptr_load_non_null(
&page->owner)->directory)->heap);
break;
}
result = pas_try_allocate_for_reallocate_and_copy(
old_heap, heap, (void*)begin, old_size, new_count, teleport_rule,
allocate_callback, allocate_callback_arg);
if (result.ptr || free_mode == pas_reallocate_free_always)
bitfit_config.specialized_page_deallocate_with_page(page, begin);
return result;
}
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate(void* old_ptr,
pas_heap* heap,
size_t new_count,
pas_heap_config config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode,
pas_try_reallocate_allocate_callback allocate_callback,
void* allocate_callback_arg)
{
uintptr_t begin;
begin = (uintptr_t)old_ptr;
switch (config.fast_megapage_kind_func(begin)) {
case pas_small_segregated_fast_megapage_kind: {
size_t old_size;
pas_typed_allocation_result result;
pas_heap* old_heap;
switch (teleport_rule) {
case pas_reallocate_allow_heap_teleport:
old_size = pas_segregated_page_get_object_size_for_address_and_page_config(
begin, config.small_segregated_config);
old_heap = NULL;
break;
case pas_reallocate_disallow_heap_teleport: {
pas_segregated_size_directory* directory;
directory = pas_segregated_page_get_directory_for_address_and_page_config(
begin, config.small_segregated_config);
old_size = directory->object_size;
old_heap = pas_heap_for_segregated_heap(directory->heap);
break;
} }
result = pas_try_allocate_for_reallocate_and_copy(
old_heap, heap, old_ptr, old_size, new_count, teleport_rule,
allocate_callback, allocate_callback_arg);
if (result.ptr || free_mode == pas_reallocate_free_always)
pas_deallocate_known_segregated(old_ptr, config.small_segregated_config);
return result;
}
case pas_small_bitfit_fast_megapage_kind: {
size_t old_size;
pas_typed_allocation_result result;
pas_bitfit_page* page;
pas_heap* old_heap;
page = pas_bitfit_page_for_address_and_page_config(begin, config.small_bitfit_config);
old_size = config.small_bitfit_config.specialized_page_get_allocation_size_with_page(
page, begin);
switch (teleport_rule) {
case pas_reallocate_allow_heap_teleport:
old_heap = NULL;
break;
case pas_reallocate_disallow_heap_teleport:
old_heap = pas_heap_for_segregated_heap(
pas_compact_bitfit_directory_ptr_load_non_null(
&pas_compact_atomic_bitfit_view_ptr_load_non_null(
&page->owner)->directory)->heap);
break;
}
result = pas_try_allocate_for_reallocate_and_copy(
old_heap, heap, old_ptr, old_size, new_count, teleport_rule,
allocate_callback, allocate_callback_arg);
if (result.ptr || free_mode == pas_reallocate_free_always)
config.small_bitfit_config.specialized_page_deallocate_with_page(page, begin);
return result;
}
case pas_not_a_fast_megapage_kind: {
pas_heap* source_heap;
size_t old_size;
pas_large_map_entry entry;
pas_typed_allocation_result result;
pas_page_base* page_base;
page_base = config.page_header_func(begin);
if (page_base) {
switch (pas_page_base_get_kind(page_base)) {
case pas_small_segregated_page_kind:
PAS_ASSERT(!config.small_segregated_is_in_megapage);
return pas_try_reallocate_table_segregated_case(
page_base, begin, heap, new_count, config.small_segregated_config,
teleport_rule, free_mode, allocate_callback, allocate_callback_arg);
case pas_small_bitfit_page_kind:
PAS_ASSERT(!config.small_bitfit_is_in_megapage);
return pas_try_reallocate_table_bitfit_case(
page_base, begin, heap, new_count, config.small_bitfit_config,
teleport_rule, free_mode, allocate_callback, allocate_callback_arg);
case pas_medium_segregated_page_kind:
return pas_try_reallocate_table_segregated_case(
page_base, begin, heap, new_count, config.medium_segregated_config,
teleport_rule, free_mode, allocate_callback, allocate_callback_arg);
case pas_medium_bitfit_page_kind:
return pas_try_reallocate_table_bitfit_case(
page_base, begin, heap, new_count, config.medium_bitfit_config,
teleport_rule, free_mode, allocate_callback, allocate_callback_arg);
case pas_marge_bitfit_page_kind:
return pas_try_reallocate_table_bitfit_case(
page_base, begin, heap, new_count, config.marge_bitfit_config,
teleport_rule, free_mode, allocate_callback, allocate_callback_arg);
}
PAS_ASSERT(!"Wrong page kind");
return pas_typed_allocation_result_create_empty();
}
if (!begin)
return allocate_callback(heap, new_count, allocate_callback_arg);
if (PAS_UNLIKELY(pas_debug_heap_is_enabled(config.kind))) {
void* raw_result;
size_t size;
bool did_overflow;
PAS_ASSERT(free_mode == pas_reallocate_free_if_successful);
did_overflow = __builtin_mul_overflow(new_count, config.get_type_size(heap->type), &size);
PAS_ASSERT(!did_overflow);
raw_result = pas_debug_heap_realloc(old_ptr, size);
result = pas_typed_allocation_result_create_empty();
if (raw_result) {
result.type = heap->type;
result.ptr = raw_result;
result.size = size;
result.did_succeed = true;
}
return result;
}
pas_heap_lock_lock();
entry = pas_large_map_find(begin);
if (pas_large_map_entry_is_empty(entry)) {
pas_reallocation_did_fail(
"Source object not allocated",
NULL, heap, old_ptr, 0, new_count);
}
PAS_ASSERT(entry.begin == begin);
PAS_ASSERT(entry.end > begin);
PAS_ASSERT(entry.heap);
old_size = entry.end - begin;
source_heap = pas_heap_for_large_heap(entry.heap);
pas_heap_lock_unlock();
result = pas_try_allocate_for_reallocate_and_copy(
source_heap, heap, old_ptr, old_size, new_count, teleport_rule,
allocate_callback, allocate_callback_arg);
if (result.ptr || free_mode == pas_reallocate_free_always)
pas_deallocate_known_large(old_ptr, config.config_ptr);
return result;
} }
PAS_ASSERT(!"Should never be reached");
}
typedef struct {
pas_try_allocate_intrinsic_for_realloc try_allocate_intrinsic;
} pas_try_reallocate_intrinsic_allocate_data;
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_intrinsic_allocate_callback(
pas_heap* heap,
size_t new_count,
void* arg)
{
pas_try_reallocate_intrinsic_allocate_data* data;
PAS_UNUSED_PARAM(heap);
data = (pas_try_reallocate_intrinsic_allocate_data*)arg;
return pas_typed_allocation_result_create_with_intrinsic_allocation_result(
data->try_allocate_intrinsic(new_count),
NULL,
new_count);
}
static PAS_ALWAYS_INLINE pas_allocation_result
pas_try_reallocate_intrinsic(
void* old_ptr,
pas_heap* heap,
size_t new_size,
pas_heap_config config,
pas_try_allocate_intrinsic_for_realloc try_allocate_intrinsic,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode)
{
pas_try_reallocate_intrinsic_allocate_data data;
data.try_allocate_intrinsic = try_allocate_intrinsic;
return pas_typed_allocation_result_as_intrinsic_allocation_result(
pas_try_reallocate(
old_ptr,
heap,
new_size,
config,
teleport_rule,
free_mode,
pas_try_reallocate_intrinsic_allocate_callback,
&data));
}
typedef struct {
pas_try_allocate try_allocate;
pas_heap_ref* heap_ref;
} pas_try_reallocate_single_allocate_data;
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_single_allocate_callback(
pas_heap* heap,
size_t new_count,
void* arg)
{
pas_try_reallocate_single_allocate_data* data;
PAS_UNUSED_PARAM(heap);
data = (pas_try_reallocate_single_allocate_data*)arg;
PAS_ASSERT(new_count == 1);
return data->try_allocate(data->heap_ref);
}
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_single(
void* old_ptr,
pas_heap_ref* heap_ref,
pas_heap_config config,
pas_try_allocate try_allocate,
pas_heap_runtime_config* runtime_config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode)
{
pas_try_reallocate_single_allocate_data data;
data.heap_ref = heap_ref;
data.try_allocate = try_allocate;
return pas_try_reallocate(
old_ptr,
pas_ensure_heap(heap_ref,
pas_normal_heap_ref_kind,
config.config_ptr,
runtime_config),
1,
config,
teleport_rule,
free_mode,
pas_try_reallocate_single_allocate_callback,
&data);
}
typedef struct {
pas_try_allocate_array_for_realloc try_allocate_array;
pas_heap_ref* heap_ref;
} pas_try_reallocate_array_allocate_data;
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_array_allocate_callback(
pas_heap* heap,
size_t new_count,
void* arg)
{
pas_try_reallocate_array_allocate_data* data;
pas_typed_allocation_result result;
data = (pas_try_reallocate_array_allocate_data*)arg;
result = data->try_allocate_array(data->heap_ref, heap, new_count);
return result;
}
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_array(
void* old_ptr,
pas_heap_ref* heap_ref,
size_t new_count,
pas_heap_config config,
pas_try_allocate_array_for_realloc try_allocate_array,
pas_heap_runtime_config* runtime_config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode)
{
pas_try_reallocate_array_allocate_data data;
data.try_allocate_array = try_allocate_array;
data.heap_ref = heap_ref;
return pas_try_reallocate(
old_ptr,
pas_ensure_heap(heap_ref,
pas_normal_heap_ref_kind,
config.config_ptr,
runtime_config),
new_count,
config,
teleport_rule,
free_mode,
pas_try_reallocate_array_allocate_callback,
&data);
}
typedef struct {
pas_primitive_heap_ref* heap_ref;
pas_try_allocate_primitive_for_realloc try_allocate_primitive;
} pas_try_reallocate_primitive_allocate_data;
static PAS_ALWAYS_INLINE pas_typed_allocation_result
pas_try_reallocate_primitive_allocate_callback(
pas_heap* heap,
size_t new_count,
void* arg)
{
static const bool verbose = false;
pas_try_reallocate_primitive_allocate_data* data;
pas_allocation_result result;
PAS_UNUSED_PARAM(heap);
data = (pas_try_reallocate_primitive_allocate_data*)arg;
result = data->try_allocate_primitive(data->heap_ref, new_count);
if (verbose)
pas_log("in realloc - result.begin = %p\n", (void*)result.begin);
return pas_typed_allocation_result_create_with_intrinsic_allocation_result(
result,
NULL,
new_count);
}
static PAS_ALWAYS_INLINE pas_allocation_result
pas_try_reallocate_primitive(
void* old_ptr,
pas_primitive_heap_ref* heap_ref,
size_t new_size,
pas_heap_config config,
pas_try_allocate_primitive_for_realloc try_allocate_primitive,
pas_heap_runtime_config* runtime_config,
pas_reallocate_heap_teleport_rule teleport_rule,
pas_reallocate_free_mode free_mode)
{
pas_try_reallocate_primitive_allocate_data data;
data.heap_ref = heap_ref;
data.try_allocate_primitive = try_allocate_primitive;
return pas_typed_allocation_result_as_intrinsic_allocation_result(
pas_try_reallocate(
old_ptr,
pas_ensure_heap(&heap_ref->base,
pas_primitive_heap_ref_kind,
config.config_ptr,
runtime_config),
new_size,
config,
teleport_rule,
free_mode,
pas_try_reallocate_primitive_allocate_callback,
&data));
}
PAS_END_EXTERN_C;
#endif /* PAS_TRY_REALLOCATE_H */