| /* |
| * 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_LOCAL_ALLOCATOR_INLINES_H |
| #define PAS_LOCAL_ALLOCATOR_INLINES_H |
| |
| #include "pas_allocator_counts.h" |
| #include "pas_bitfit_allocator_inlines.h" |
| #include "pas_bitfit_directory.h" |
| #include "pas_bitfit_size_class.h" |
| #include "pas_config.h" |
| #include "pas_debug_heap.h" |
| #include "pas_epoch.h" |
| #include "pas_full_alloc_bits_inlines.h" |
| #include "pas_scavenger.h" |
| #include "pas_segregated_exclusive_view_inlines.h" |
| #include "pas_segregated_size_directory_inlines.h" |
| #include "pas_segregated_heap.h" |
| #include "pas_segregated_page_inlines.h" |
| #include "pas_segregated_partial_view_inlines.h" |
| #include "pas_segregated_shared_page_directory.h" |
| #include "pas_segregated_shared_view_inlines.h" |
| #include "pas_segregated_size_directory_inlines.h" |
| #include "pas_segregated_view_allocator_inlines.h" |
| #include "pas_size_thunk.h" |
| #include "pas_thread_local_cache.h" |
| #include "pas_thread_local_cache_node.h" |
| |
| PAS_BEGIN_EXTERN_C; |
| |
| static inline void pas_local_allocator_reset_impl(pas_local_allocator* allocator, |
| pas_segregated_size_directory* directory, |
| pas_segregated_page_config_kind kind) |
| { |
| allocator->page_ish = 0; |
| pas_compiler_fence(); |
| allocator->current_word_is_valid = false; |
| allocator->payload_end = 0; |
| allocator->remaining = 0; |
| allocator->current_offset = 0; |
| allocator->end_offset = 0; |
| allocator->current_word = 0; |
| allocator->view = pas_segregated_size_directory_as_view(directory); |
| allocator->config_kind = pas_local_allocator_config_kind_create_normal(kind); |
| } |
| |
| static inline void pas_local_allocator_set_up_bump(pas_local_allocator* allocator, |
| uintptr_t page_boundary, |
| uintptr_t begin, |
| uintptr_t end) |
| { |
| PAS_TESTING_ASSERT(end); |
| allocator->payload_end = end; |
| allocator->remaining = (unsigned)(end - begin); |
| allocator->current_offset = 0; |
| allocator->end_offset = 0; |
| allocator->current_word = 0; |
| pas_compiler_fence(); |
| allocator->page_ish = page_boundary; |
| } |
| |
| typedef struct { |
| unsigned free; |
| unsigned object_size; |
| pas_page_granule_use_count* use_counts; |
| uintptr_t base_offset; |
| uintptr_t shift; |
| uintptr_t page_size; |
| uintptr_t granule_size; |
| } pas_local_allocator_scan_bits_to_set_up_use_counts_data; |
| |
| static PAS_ALWAYS_INLINE unsigned pas_local_allocator_scan_bits_to_set_up_use_counts_bits_source( |
| size_t word_index, void* arg) |
| { |
| pas_local_allocator_scan_bits_to_set_up_use_counts_data* data; |
| |
| data = (pas_local_allocator_scan_bits_to_set_up_use_counts_data*)arg; |
| |
| PAS_ASSERT(!word_index); |
| |
| return data->free; |
| } |
| |
| static PAS_ALWAYS_INLINE bool pas_local_allocator_scan_bits_to_set_up_use_counts_bit_callback( |
| pas_found_bit_index index, void* arg) |
| { |
| pas_local_allocator_scan_bits_to_set_up_use_counts_data* data; |
| uintptr_t offset; |
| |
| data = (pas_local_allocator_scan_bits_to_set_up_use_counts_data*)arg; |
| |
| offset = data->base_offset + (index.index << data->shift); |
| |
| pas_page_granule_increment_uses_for_range( |
| data->use_counts, offset, offset + data->object_size, data->page_size, data->granule_size); |
| |
| return true; |
| } |
| |
| static PAS_ALWAYS_INLINE void pas_local_allocator_scan_bits_to_set_up_free_bits( |
| pas_local_allocator* allocator, |
| pas_segregated_size_directory* directory, |
| pas_segregated_page* page, |
| uintptr_t page_boundary, |
| unsigned current_offset, |
| unsigned end_offset, |
| pas_segregated_view_kind view_kind, |
| pas_full_alloc_bits full_alloc_bits, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| unsigned* alloc_bits; |
| size_t index; |
| unsigned num_denullified_words; |
| pas_local_allocator_scan_bits_to_set_up_use_counts_data data; |
| unsigned object_size; |
| |
| #if PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY |
| unsigned num_total_objects; |
| unsigned num_taken_objects; |
| #endif /* PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY */ |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| PAS_UNUSED_PARAM(directory); |
| |
| object_size = allocator->object_size; |
| alloc_bits = page->alloc_bits; |
| |
| num_denullified_words = 0; |
| |
| /* Get the compiler to not suffer an anxiety attack. */ |
| data.free = 0; |
| data.object_size = object_size; |
| data.use_counts = NULL; |
| data.base_offset = 0; |
| data.shift = page_config.base.min_align_shift; |
| data.page_size = page_config.base.page_size; |
| data.granule_size = page_config.base.granule_size; |
| |
| if (verbose) { |
| pas_log("%p, %s: Setting up alloc bits in range %zu...%zu\n", |
| allocator, |
| pas_local_allocator_config_kind_get_string(allocator->config_kind), |
| full_alloc_bits.word_index_begin, |
| full_alloc_bits.word_index_end); |
| } |
| |
| if (page_config.base.page_size > page_config.base.granule_size) |
| data.use_counts = pas_segregated_page_get_granule_use_counts(page, page_config); |
| |
| allocator->current_offset = current_offset; |
| allocator->end_offset = end_offset; |
| |
| /* Need to make sure that the allocator's bits are cleared out before we set the page_ish and start |
| setting them for real so that the enumerator can make sense of the page. |
| |
| NOTE: an alternative is to just have the loop set the end_offset repeatedly. Maybe that would be |
| faster? */ |
| if (verbose) |
| pas_log("Allocator %p zeroing bits.\n", allocator); |
| pas_zero_memory( |
| allocator->bits + (full_alloc_bits.word_index_begin >> 1), |
| sizeof(unsigned) * (((full_alloc_bits.word_index_end + 1) & ~1u) - |
| (full_alloc_bits.word_index_begin & ~1u))); |
| |
| pas_compiler_fence(); |
| |
| PAS_TESTING_ASSERT(!allocator->current_word_is_valid); |
| |
| allocator->page_ish = page_boundary + |
| pas_page_base_object_offset_from_page_boundary_at_index( |
| PAS_BITVECTOR_BIT_INDEX64(current_offset), |
| page_config.base); |
| |
| pas_compiler_fence(); |
| |
| #if PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY |
| num_total_objects = 0; |
| num_taken_objects = 0; |
| #endif /* PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY */ |
| |
| for (index = full_alloc_bits.word_index_begin; index < full_alloc_bits.word_index_end; ++index) { |
| unsigned full; |
| unsigned alloc; |
| unsigned free; |
| |
| full = full_alloc_bits.bits[index]; |
| alloc = alloc_bits[index]; |
| free = full & ~alloc; |
| ((unsigned*)allocator->bits)[index] = free; |
| |
| #if PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY |
| num_total_objects += __builtin_popcount(full); |
| num_taken_objects += __builtin_popcount(free); |
| #endif /* PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY */ |
| |
| pas_compiler_fence(); |
| alloc_bits[index] = alloc | full; |
| switch (view_kind) { |
| case pas_segregated_exclusive_view_kind: |
| break; |
| |
| case pas_segregated_partial_view_kind: |
| if (!alloc && full) |
| num_denullified_words++; |
| |
| if (page_config.base.page_size > page_config.base.granule_size) { |
| data.free = free; |
| data.base_offset = pas_page_base_object_offset_from_page_boundary_at_index( |
| (unsigned)PAS_BITVECTOR_BIT_INDEX(index), page_config.base); |
| |
| if (verbose) { |
| pas_log("Dealing with use counts at index = %zu, base_offset = %zu, free = %x\n", |
| index, |
| data.base_offset, |
| data.free); |
| } |
| |
| pas_bitvector_for_each_set_bit( |
| pas_local_allocator_scan_bits_to_set_up_use_counts_bits_source, |
| 0, 1, |
| pas_local_allocator_scan_bits_to_set_up_use_counts_bit_callback, |
| &data); |
| } |
| break; |
| |
| default: |
| PAS_ASSERT(!"Should not be reached"); |
| break; |
| } |
| |
| if (verbose) { |
| printf("index = %zu, full = %x, alloc = %x, free = %x\n", |
| index, full, alloc, ((unsigned*)allocator->bits)[index]); |
| } |
| } |
| |
| #if PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY |
| PAS_ASSERT(num_taken_objects <= num_total_objects); |
| |
| pas_local_allocator_refill_efficiency_lock_lock(); |
| pas_local_allocator_refill_efficiency_sum += (double)num_taken_objects / (double)num_total_objects; |
| pas_local_allocator_refill_efficiency_n++; |
| pas_local_allocator_refill_efficiency_lock_unlock(); |
| #endif /* PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY */ |
| |
| if (page_config.use_reversed_current_word) { |
| PAS_ASSERT(page_config.variant == pas_small_segregated_page_config_variant); |
| allocator->current_word = pas_reverse64(allocator->bits[current_offset]); |
| if (verbose) { |
| pas_log("Allocator %p using word %llx, reversed to %llx.\n", |
| allocator, allocator->bits[current_offset], allocator->current_word); |
| } |
| } else if (page_config.variant == pas_small_segregated_page_config_variant) |
| allocator->current_word = allocator->bits[current_offset]; |
| else |
| allocator->current_word = 0; |
| |
| pas_compiler_fence(); |
| |
| allocator->current_word_is_valid = true; |
| |
| switch (view_kind) { |
| case pas_segregated_exclusive_view_kind: |
| page->num_non_empty_words = |
| pas_segregated_size_directory_data_ptr_load_non_null( |
| &directory->data)->full_num_non_empty_words; |
| break; |
| |
| case pas_segregated_partial_view_kind: |
| page->num_non_empty_words += num_denullified_words; |
| break; |
| |
| default: |
| PAS_ASSERT(!"Should not be reached"); |
| break; |
| } |
| } |
| |
| static PAS_ALWAYS_INLINE void |
| pas_local_allocator_set_up_free_bits(pas_local_allocator* allocator, |
| pas_segregated_view_kind view_kind, |
| void* view, |
| pas_segregated_size_directory* directory, |
| pas_segregated_page* page, |
| uintptr_t page_boundary, |
| pas_segregated_page_config page_config) |
| { |
| pas_full_alloc_bits full_alloc_bits; |
| pas_segregated_view partial_view_as_view; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| PAS_ASSERT(view_kind == pas_segregated_exclusive_view_kind |
| || view_kind == pas_segregated_partial_view_kind); |
| |
| allocator->payload_end = 0; |
| allocator->remaining = 0; |
| |
| if (view_kind == pas_segregated_exclusive_view_kind) { |
| unsigned begin_offset; |
| unsigned end_offset; |
| |
| if (page_config.base.page_size > page_config.base.granule_size) |
| pas_segregated_exclusive_view_install_full_use_counts((pas_segregated_exclusive_view*)view); |
| |
| begin_offset = pas_segregated_page_offset_from_page_boundary_to_first_object( |
| page, directory, page_config); |
| end_offset = pas_segregated_size_directory_data_ptr_load_non_null( |
| &directory->data)->offset_from_page_boundary_to_end_of_last_object; |
| |
| begin_offset = PAS_BITVECTOR_WORD64_INDEX( |
| (unsigned)pas_page_base_index_of_object_at_offset_from_page_boundary( |
| begin_offset, page_config.base)); |
| end_offset = PAS_BITVECTOR_WORD64_INDEX( |
| (unsigned)pas_page_base_index_of_object_at_offset_from_page_boundary( |
| end_offset, page_config.base) - 1) + 1; |
| |
| pas_local_allocator_scan_bits_to_set_up_free_bits( |
| allocator, directory, page, page_boundary, begin_offset, end_offset, |
| pas_segregated_exclusive_view_kind, |
| pas_full_alloc_bits_create_for_exclusive(directory, page_config), page_config); |
| return; |
| } |
| |
| partial_view_as_view = pas_segregated_partial_view_as_view((pas_segregated_partial_view*)view); |
| |
| full_alloc_bits = pas_full_alloc_bits_create_for_partial(partial_view_as_view); |
| |
| allocator->view = partial_view_as_view; |
| |
| pas_local_allocator_scan_bits_to_set_up_free_bits( |
| allocator, directory, page, page_boundary, |
| full_alloc_bits.word_index_begin >> 1, |
| (full_alloc_bits.word_index_end + 1) >> 1, |
| pas_segregated_partial_view_kind, |
| full_alloc_bits, page_config); |
| } |
| |
| static PAS_ALWAYS_INLINE void |
| pas_local_allocator_make_bump( |
| pas_local_allocator* allocator, |
| uintptr_t page_boundary, |
| uintptr_t begin, uintptr_t end, |
| pas_segregated_page_config page_config) |
| { |
| PAS_ASSERT(page_config.base.is_enabled); |
| |
| pas_local_allocator_set_up_bump(allocator, page_boundary, begin, end); |
| } |
| |
| static PAS_ALWAYS_INLINE void |
| pas_local_allocator_prepare_to_allocate( |
| pas_local_allocator* allocator, |
| pas_segregated_view_kind view_kind, |
| void* view, |
| pas_segregated_page* page, |
| pas_segregated_size_directory* directory, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| uintptr_t page_boundary; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| PAS_ASSERT(view_kind == pas_segregated_exclusive_view_kind |
| || view_kind == pas_segregated_partial_view_kind); |
| |
| if (!pas_segregated_page_config_is_utility(page_config)) |
| pas_lock_testing_assert_held(page->lock_ptr); |
| |
| if (verbose) |
| pas_log("Preparing to allocate in view %p, page %p\n", view, page); |
| |
| page_boundary = (uintptr_t)pas_segregated_page_boundary(page, page_config); |
| |
| if (view_kind == pas_segregated_exclusive_view_kind && !page->num_non_empty_words) { |
| uintptr_t payload_begin; |
| uintptr_t payload_end; |
| pas_segregated_size_directory_data* data; |
| |
| if (verbose) |
| pas_log("Refilling with bump.\n"); |
| |
| if (page_config.base.page_size > page_config.base.granule_size) |
| pas_segregated_exclusive_view_install_full_use_counts((pas_segregated_exclusive_view*)view); |
| |
| data = pas_segregated_size_directory_data_ptr_load_non_null(&directory->data); |
| |
| payload_begin = page_boundary + data->offset_from_page_boundary_to_first_object; |
| payload_end = page_boundary + data->offset_from_page_boundary_to_end_of_last_object; |
| |
| page->num_non_empty_words = data->full_num_non_empty_words; |
| |
| pas_local_allocator_make_bump( |
| allocator, page_boundary, payload_begin, payload_end, page_config); |
| |
| pas_compiler_fence(); |
| |
| memcpy(page->alloc_bits, pas_compact_tagged_unsigned_ptr_load_non_null(&data->full_alloc_bits), |
| pas_segregated_page_config_num_alloc_bytes(page_config)); |
| |
| #if PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY |
| pas_local_allocator_refill_efficiency_lock_lock(); |
| pas_local_allocator_refill_efficiency_sum++; |
| pas_local_allocator_refill_efficiency_n++; |
| pas_local_allocator_refill_efficiency_lock_unlock(); |
| #endif /* PAS_LOCAL_ALLOCATOR_MEASURE_REFILL_EFFICIENCY */ |
| |
| return; |
| } |
| |
| if (verbose) |
| pas_log("Refilling with %p using free_bits\n", page); |
| pas_local_allocator_set_up_free_bits( |
| allocator, |
| view_kind, |
| view, |
| directory, |
| page, |
| page_boundary, |
| page_config); |
| } |
| |
| typedef enum { |
| pas_local_allocator_primordial_bump_return_first_allocation, |
| pas_local_allocator_primordial_bump_stash_whole_allocation |
| } pas_local_allocator_primordial_bump_allocation_mode; |
| |
| /* Call holding the page lock and maybe the commit lock. But definitely the page lock. */ |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_set_up_primordial_bump( |
| pas_local_allocator* allocator, |
| pas_segregated_partial_view* view, |
| pas_segregated_shared_handle* handle, |
| pas_segregated_page* page, |
| pas_lock** held_lock, |
| pas_shared_view_computed_bump_result bump_result, |
| pas_local_allocator_primordial_bump_allocation_mode mode, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| uintptr_t page_boundary; |
| unsigned object_size; |
| unsigned current_offset; |
| unsigned begin_offset; |
| unsigned end_offset; |
| unsigned* bits; |
| unsigned* alloc_bits; |
| pas_page_granule_use_count* use_counts; |
| |
| PAS_UNUSED_PARAM(held_lock); |
| PAS_ASSERT(page_config.base.is_enabled); |
| |
| page_boundary = allocator->page_ish; |
| object_size = allocator->object_size; |
| begin_offset = bump_result.old_bump; |
| end_offset = bump_result.end_bump; |
| |
| bits = (unsigned*)allocator->bits; |
| alloc_bits = page->alloc_bits; |
| |
| if (page_config.base.page_size > page_config.base.granule_size) |
| use_counts = pas_segregated_page_get_granule_use_counts(page, page_config); |
| else |
| use_counts = NULL; /* Prevent the compiler from having an episode. */ |
| |
| if (verbose) |
| pas_log("Allocator %p setting up primordial bump.\n", allocator); |
| allocator->payload_end = page_boundary + end_offset; |
| |
| switch (mode) { |
| case pas_local_allocator_primordial_bump_return_first_allocation: |
| allocator->remaining = end_offset - begin_offset - object_size; |
| break; |
| |
| case pas_local_allocator_primordial_bump_stash_whole_allocation: |
| allocator->remaining = end_offset - begin_offset; |
| break; |
| } |
| |
| pas_compiler_fence(); |
| |
| for (current_offset = begin_offset; current_offset < end_offset; current_offset += object_size) { |
| size_t index; |
| size_t word_index; |
| unsigned word; |
| |
| index = pas_page_base_index_of_object_at_offset_from_page_boundary( |
| current_offset, page_config.base); |
| |
| word_index = PAS_BITVECTOR_WORD_INDEX(index); |
| |
| pas_bitvector_set_in_word(bits + word_index, index, true); |
| |
| word = alloc_bits[word_index]; |
| if (!word) { |
| if (page_config.sharing_shift == PAS_BITVECTOR_WORD_SHIFT) { |
| PAS_ASSERT(pas_compact_atomic_segregated_partial_view_ptr_is_null( |
| handle->partial_views + word_index)); |
| pas_compact_atomic_segregated_partial_view_ptr_store( |
| handle->partial_views + word_index, view); |
| } |
| |
| page->num_non_empty_words++; |
| } |
| if (page_config.sharing_shift != PAS_BITVECTOR_WORD_SHIFT) { |
| pas_compact_atomic_segregated_partial_view_ptr* ptr; |
| pas_segregated_partial_view* old_view; |
| |
| ptr = pas_segregated_shared_handle_partial_view_ptr_for_index( |
| handle, index, page_config); |
| old_view = pas_compact_atomic_segregated_partial_view_ptr_load(ptr); |
| PAS_ASSERT(!old_view || old_view == view); |
| pas_compact_atomic_segregated_partial_view_ptr_store(ptr, view); |
| } |
| pas_bitvector_set_in_word(&word, index, true); |
| alloc_bits[word_index] = word; |
| |
| if (page_config.base.page_size > page_config.base.granule_size) { |
| pas_page_granule_increment_uses_for_range( |
| use_counts, current_offset, current_offset + object_size, |
| page_config.base.page_size, page_config.base.granule_size); |
| } |
| } |
| |
| switch (mode) { |
| case pas_local_allocator_primordial_bump_return_first_allocation: |
| return pas_allocation_result_create_success(page_boundary + begin_offset); |
| |
| case pas_local_allocator_primordial_bump_stash_whole_allocation: |
| return pas_allocation_result_create_failure(); |
| } |
| |
| PAS_ASSERT(!"Should not be reached"); |
| return pas_allocation_result_create_failure(); |
| } |
| |
| static PAS_ALWAYS_INLINE bool |
| pas_local_allocator_start_allocating_in_primordial_partial_view( |
| pas_local_allocator* allocator, |
| pas_segregated_partial_view* view, |
| pas_segregated_size_directory* size_directory, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| unsigned size; |
| unsigned alignment; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| PAS_ASSERT(!pas_compact_segregated_shared_view_ptr_load(&view->shared_view)); |
| |
| size = allocator->object_size; |
| alignment = (unsigned)pas_local_allocator_alignment(allocator); |
| |
| for (;;) { |
| pas_segregated_shared_view* shared_view; |
| pas_segregated_shared_handle* handle; |
| pas_segregated_page* page; |
| pas_shared_view_computed_bump_result bump_result; |
| pas_segregated_heap* heap; |
| pas_segregated_shared_page_directory* shared_page_directory; |
| pas_lock* held_lock; |
| |
| heap = size_directory->heap; |
| shared_page_directory = page_config.shared_page_directory_selector(heap, size_directory); |
| |
| shared_view = pas_segregated_shared_page_directory_find_first_eligible( |
| shared_page_directory, size, alignment, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config)); |
| |
| PAS_ASSERT(shared_view); |
| |
| pas_lock_lock_conditionally( |
| &shared_view->commit_lock, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config)); |
| |
| handle = pas_segregated_shared_view_commit_page_if_necessary( |
| shared_view, heap, shared_page_directory, view, page_config); |
| if (!handle) { |
| pas_lock_unlock_conditionally( |
| &shared_view->commit_lock, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config)); |
| return false; |
| } |
| |
| page = pas_segregated_page_for_boundary(handle->page_boundary, page_config); |
| |
| held_lock = NULL; |
| pas_segregated_page_switch_lock(page, &held_lock, page_config); |
| |
| bump_result = pas_segregated_shared_view_bump(shared_view, size, alignment, page_config); |
| |
| if (verbose) { |
| pas_log("bump_result = %u, %u, %u, %u.\n", |
| bump_result.old_bump, |
| bump_result.new_bump, |
| bump_result.end_bump, |
| bump_result.num_objects); |
| } |
| |
| if (!bump_result.num_objects) { |
| pas_lock_switch(&held_lock, NULL); |
| pas_lock_unlock_conditionally( |
| &shared_view->commit_lock, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config)); |
| continue; |
| } |
| |
| pas_segregated_partial_view_set_is_in_use_for_allocation(view, shared_view, handle); |
| |
| if (page_config.base.page_size > page_config.base.granule_size) { |
| pas_segregated_page_commit_fully( |
| page, &held_lock, pas_commit_fully_holding_page_and_commit_locks); |
| } |
| |
| pas_compact_segregated_shared_view_ptr_store(&view->shared_view, shared_view); |
| |
| /* Make sure that if the enumerator sees a nonzero page_ish, it will know that the allocator is |
| in primordial mode. */ |
| allocator->config_kind = pas_local_allocator_config_kind_create_primordial_partial( |
| pas_local_allocator_config_kind_get_segregated_page_config_kind(allocator->config_kind)); |
| pas_compiler_fence(); |
| |
| allocator->page_ish = (uintptr_t)pas_segregated_page_boundary(page, page_config); |
| |
| pas_zero_memory(allocator->bits, pas_segregated_page_config_num_alloc_bytes(page_config)); |
| |
| /* Doing this helps heap introspection but isn't otherwise necessary. */ |
| pas_compact_tagged_unsigned_ptr_store(&view->alloc_bits, (unsigned*)allocator->bits); |
| PAS_ASSERT(!view->alloc_bits_offset); |
| PAS_ASSERT((uint8_t)pas_segregated_page_config_num_alloc_words(page_config) |
| == pas_segregated_page_config_num_alloc_words(page_config)); |
| view->alloc_bits_size = (uint8_t)pas_segregated_page_config_num_alloc_words(page_config); |
| |
| pas_local_allocator_set_up_primordial_bump( |
| allocator, view, handle, page, &held_lock, bump_result, |
| pas_local_allocator_primordial_bump_stash_whole_allocation, |
| page_config); |
| |
| allocator->view = pas_segregated_partial_view_as_view_non_null(view); |
| |
| view->is_attached_to_shared_handle = true; |
| |
| pas_lock_switch(&held_lock, NULL); |
| pas_lock_unlock_conditionally( |
| &shared_view->commit_lock, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config)); |
| |
| return true; |
| } |
| } |
| |
| static PAS_ALWAYS_INLINE void |
| pas_local_allocator_bless_primordial_partial_view_before_stopping( |
| pas_local_allocator* allocator, |
| pas_segregated_page* page, |
| pas_lock_hold_mode heap_lock_hold_mode, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| /* We're sitting on a still-ineligible partial view. Nobody else can do things to it. We need |
| to grab the heap lock in order to allocate the full alloc bits. This is called with the |
| page lock held, which is great, since we mess with eligibility data structures. */ |
| |
| pas_segregated_partial_view* view; |
| bool has_first_non_zero_word_index; |
| size_t first_non_zero_word_index; |
| size_t last_non_zero_word_index; |
| size_t word_index; |
| unsigned* bits; |
| size_t alloc_bits_size; |
| size_t alloc_bits_offset; |
| unsigned* alloc_bits; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| PAS_ASSERT(pas_local_allocator_config_kind_is_primordial_partial(allocator->config_kind)); |
| |
| has_first_non_zero_word_index = false; |
| bits = (unsigned*)allocator->bits; |
| |
| first_non_zero_word_index = SIZE_MAX; |
| last_non_zero_word_index = SIZE_MAX; |
| |
| view = pas_segregated_view_get_partial(allocator->view); |
| |
| if (verbose) |
| pas_log("Blessing view %p.\n", view); |
| |
| for (word_index = 0; |
| word_index < pas_segregated_page_config_num_alloc_words(page_config); |
| word_index++) { |
| unsigned full_alloc_word; |
| bool should_be_eligible; |
| |
| full_alloc_word = bits[word_index]; |
| |
| if (!full_alloc_word) |
| continue; |
| |
| if (verbose) |
| pas_log("Nonzero full alloc word at index = %zu\n", word_index); |
| |
| /* We go into this loop with the partial view appearing eligibile to deallocations. So, |
| something may have been freed. If it was, let folks know. Note that the only reason why |
| we have a special case for enable_empty_word_eligibility_optimization is that this |
| simplifies the logic in pas_segregated_view_should_be_eligible(). It would only be a tiny |
| behavioral change - slightly better for space, slightly worse for time - to get rid of |
| this special case, in which case we'd have to edit should_be_eligible(). */ |
| if (page_config.enable_empty_word_eligibility_optimization) |
| should_be_eligible = !!(full_alloc_word && !page->alloc_bits[word_index]); |
| else |
| should_be_eligible = !!(full_alloc_word & ~page->alloc_bits[word_index]); |
| if (should_be_eligible) { |
| view->eligibility_notification_has_been_deferred = true; |
| view->eligibility_has_been_noted = true; |
| } |
| |
| if (!has_first_non_zero_word_index) { |
| first_non_zero_word_index = word_index; |
| has_first_non_zero_word_index = true; |
| } |
| last_non_zero_word_index = word_index; |
| } |
| |
| PAS_ASSERT(has_first_non_zero_word_index); |
| |
| alloc_bits_size = last_non_zero_word_index + 1 - first_non_zero_word_index; |
| alloc_bits_offset = first_non_zero_word_index; |
| |
| PAS_ASSERT(alloc_bits_size); |
| |
| PAS_ASSERT((uint8_t)alloc_bits_size == alloc_bits_size); |
| view->alloc_bits_size = (uint8_t)alloc_bits_size; |
| |
| PAS_ASSERT((uint8_t)alloc_bits_offset == alloc_bits_offset); |
| view->alloc_bits_offset = (uint8_t)alloc_bits_offset; |
| |
| if (alloc_bits_size == 1) |
| alloc_bits = &view->inline_alloc_bits - alloc_bits_offset; |
| else { |
| /* We hold the page lock. Lock ordering says that we cannot acquire the heap lock when |
| we are holding the page lock. |
| |
| Luckily, this is a fine place to drop the page lock. |
| |
| Therefore, we try-lock the heap lock. It's always safe to do that. Usually it will just |
| succeed. But if it fails, we will drop the page lock and then acquire both of them. */ |
| if (!pas_heap_lock_try_lock_conditionally(heap_lock_hold_mode)) { |
| pas_segregated_page_unlock(page, page_config); |
| pas_heap_lock_lock(); |
| pas_segregated_page_lock(page, page_config); |
| } |
| |
| alloc_bits = (unsigned*)pas_immortal_heap_allocate_with_manual_alignment( |
| alloc_bits_size * sizeof(unsigned), |
| sizeof(unsigned), |
| "pas_segregated_partial_view/alloc_bits", |
| pas_object_allocation) - alloc_bits_offset; |
| |
| pas_heap_lock_unlock_conditionally(heap_lock_hold_mode); |
| } |
| |
| memcpy(alloc_bits + alloc_bits_offset, |
| bits + alloc_bits_offset, |
| alloc_bits_size * sizeof(unsigned)); |
| |
| pas_store_store_fence(); |
| |
| pas_compact_tagged_unsigned_ptr_store(&view->alloc_bits, alloc_bits); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_in_primordial_partial_view( |
| pas_local_allocator* allocator, |
| pas_segregated_page_config page_config) |
| { |
| pas_segregated_page* page; |
| pas_segregated_partial_view* partial_view; |
| pas_segregated_shared_view* shared_view; |
| pas_shared_view_computed_bump_result bump_result; |
| pas_allocation_result result; |
| pas_lock* held_lock; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| |
| page = pas_segregated_page_for_boundary((void*)allocator->page_ish, page_config); |
| partial_view = pas_segregated_view_get_partial(allocator->view); |
| shared_view = pas_compact_segregated_shared_view_ptr_load_non_null(&partial_view->shared_view); |
| |
| held_lock = NULL; |
| pas_segregated_page_switch_lock(page, &held_lock, page_config); |
| |
| bump_result = pas_segregated_shared_view_bump( |
| shared_view, |
| allocator->object_size, |
| (unsigned)pas_local_allocator_alignment(allocator), |
| page_config); |
| |
| if (!bump_result.num_objects) { |
| pas_local_allocator_bless_primordial_partial_view_before_stopping( |
| allocator, page, |
| pas_segregated_page_config_heap_lock_hold_mode(page_config), |
| page_config); |
| pas_segregated_page_unlock(page, page_config); |
| return pas_allocation_result_create_failure(); |
| } |
| |
| result = pas_local_allocator_set_up_primordial_bump( |
| allocator, |
| partial_view, |
| pas_unwrap_shared_handle(shared_view->shared_handle_or_page_boundary, page_config), |
| page, |
| &held_lock, |
| bump_result, |
| pas_local_allocator_primordial_bump_return_first_allocation, |
| page_config); |
| |
| pas_lock_switch(&held_lock, NULL); |
| |
| return result; |
| } |
| |
| PAS_API bool pas_local_allocator_refill_with_bitfit( |
| pas_local_allocator* allocator, |
| pas_allocator_counts* counts); |
| |
| PAS_API void pas_local_allocator_finish_refill_with_bitfit( |
| pas_local_allocator* allocator, |
| pas_segregated_size_directory* size_directory); |
| |
| static PAS_ALWAYS_INLINE bool |
| pas_local_allocator_refill_with_known_config( |
| pas_local_allocator* allocator, |
| pas_allocator_counts* counts, |
| pas_segregated_page_config page_config, |
| bool skip_bitfit) |
| { |
| static const bool verbose = false; |
| |
| pas_segregated_view new_view; |
| pas_segregated_view old_view; |
| pas_lock* held_lock; |
| pas_segregated_page* old_page; |
| pas_segregated_page* new_page; |
| pas_segregated_size_directory* size_directory; |
| pas_segregated_directory* directory; |
| pas_thread_local_cache_node* cache_node; |
| bool did_initialize_bitfit; |
| pas_segregated_exclusive_view* exclusive; |
| pas_segregated_partial_view* partial; |
| pas_segregated_shared_view* shared; |
| bool enable_segregated; |
| pas_thread_local_cache* cache; |
| |
| PAS_UNUSED_PARAM(counts); |
| |
| enable_segregated = page_config.kind != pas_segregated_page_config_kind_null; |
| |
| PAS_ASSERT(page_config.base.is_enabled || !enable_segregated); |
| |
| if (skip_bitfit) |
| PAS_ASSERT(enable_segregated); |
| |
| size_directory = pas_segregated_view_get_size_directory(allocator->view); |
| directory = &size_directory->base; |
| |
| if (verbose) { |
| pas_log("Refilling allocator = %p with size = %u and mode = %s\n", |
| allocator, |
| allocator->object_size, |
| pas_segregated_page_config_kind_get_string(directory->page_config_kind)); |
| } |
| |
| if (enable_segregated) |
| PAS_ASSERT(!pas_local_allocator_has_bitfit(allocator)); |
| |
| if (!skip_bitfit && pas_local_allocator_has_bitfit(allocator)) { |
| pas_bitfit_allocator* bitfit; |
| |
| bitfit = pas_local_allocator_get_bitfit(allocator); |
| |
| pas_bitfit_allocator_assert_reset(bitfit); |
| } |
| |
| if (!allocator->scavenger_data.dirty && verbose) |
| pas_log("Using allocator %p\n", allocator); |
| pas_local_allocator_scavenger_data_did_use_for_allocation(&allocator->scavenger_data); |
| |
| if (pas_scavenger_did_create_eligible()) { |
| if (pas_segregated_page_config_heap_lock_hold_mode(page_config) == pas_lock_is_not_held) |
| pas_scavenger_notify_eligibility_if_needed(); |
| else |
| pas_heap_lock_assert_held(); |
| } |
| |
| /* This will get set if we are sitting on a page. */ |
| old_view = NULL; |
| old_page = NULL; |
| |
| if (enable_segregated && allocator->page_ish) { |
| old_page = pas_segregated_page_for_boundary( |
| (void*)pas_local_allocator_page_boundary(allocator, page_config), page_config); |
| old_view = old_page->owner; |
| if (!pas_segregated_view_is_some_exclusive(old_view)) { |
| PAS_ASSERT(pas_segregated_view_is_shared_handle(old_view)); |
| old_view = allocator->view; |
| PAS_ASSERT(pas_segregated_view_is_partial(old_view)); |
| } |
| } |
| |
| PAS_TESTING_ASSERT(!allocator->remaining); /* If we did have a bump region we should have |
| exhausted it by now. */ |
| PAS_TESTING_ASSERT(allocator->current_offset == allocator->end_offset); /* We should have used |
| up all of the alloc |
| bits if that's what |
| we were doing. */ |
| |
| pas_local_allocator_reset_impl(allocator, size_directory, page_config.kind); |
| |
| #if PAS_ENABLE_TESTING |
| if (counts) |
| counts->slow_refills++; |
| #endif /* PAS_ENABLE_TESTING */ |
| |
| cache = NULL; |
| cache_node = NULL; |
| |
| if (!pas_segregated_page_config_is_utility(page_config)) { |
| cache = pas_thread_local_cache_try_get(); |
| |
| if (cache) { |
| cache_node = cache->node; |
| |
| /* Doing this here has some special properties. For example, it doesn't prevent fast reuse |
| of the page we just finished allocating out of. */ |
| pas_thread_local_cache_stop_local_allocators_if_necessary( |
| cache, allocator, pas_lock_is_not_held); |
| } |
| } |
| |
| if (verbose) { |
| pas_log("old_view = %p.\n", old_view); |
| if (old_view) { |
| pas_log(" index = %zu, first_eligible = %zu.\n", |
| pas_segregated_view_get_index(old_view), |
| pas_segregated_directory_get_first_eligible( |
| &pas_segregated_view_get_size_directory(old_view)->base).value); |
| } |
| } |
| |
| did_initialize_bitfit = false; |
| |
| if (verbose) |
| pas_log("allocating in size_directory = %p\n", size_directory); |
| |
| held_lock = NULL; |
| |
| if (old_view) { |
| pas_segregated_view_kind kind; |
| |
| PAS_ASSERT(enable_segregated); |
| |
| kind = pas_segregated_view_get_kind(old_view); |
| |
| switch (kind) { |
| case pas_segregated_exclusive_view_kind: { |
| new_view = old_view; |
| new_page = old_page; |
| |
| exclusive = (pas_segregated_exclusive_view*)pas_segregated_view_get_ptr(new_view); |
| |
| if (verbose) |
| pas_log("Restarting on same page as before.\n"); |
| pas_segregated_page_switch_lock_and_rebias_while_ineligible( |
| new_page, &held_lock, cache_node, page_config); |
| new_page->owner = pas_segregated_view_as_ineligible(new_view); |
| new_page->eligibility_notification_has_been_deferred = false; |
| goto prepare_exclusive; |
| } |
| |
| case pas_segregated_partial_view_kind: { |
| partial = (pas_segregated_partial_view*)pas_segregated_view_get_ptr(old_view); |
| if (partial->eligibility_has_been_noted) { |
| new_view = old_view; |
| new_page = old_page; |
| pas_segregated_page_switch_lock(new_page, &held_lock, page_config); |
| partial->eligibility_has_been_noted = false; |
| partial->eligibility_notification_has_been_deferred = false; |
| goto prepare_partial; |
| } |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| if (verbose) |
| pas_log("Finding a different page.\n"); |
| |
| new_view = NULL; |
| new_page = NULL; |
| |
| if (enable_segregated) { |
| bool did_get_view; |
| |
| did_get_view = false; |
| |
| if (page_config.enable_view_cache && cache) { |
| pas_local_allocator_result view_cache_result; |
| |
| PAS_ASSERT(!pas_segregated_page_config_is_utility(page_config)); |
| |
| view_cache_result = pas_thread_local_cache_get_local_allocator( |
| cache, size_directory->view_cache_index, pas_lock_is_not_held); |
| cache = NULL; /* The cache could have been resized, so we don't want to use this pointer |
| anymore. */ |
| if (view_cache_result.did_succeed) { |
| pas_local_view_cache* view_cache; |
| |
| view_cache = (pas_local_view_cache*)view_cache_result.allocator; |
| |
| PAS_TESTING_ASSERT(!view_cache->scavenger_data.is_in_use); |
| |
| view_cache->scavenger_data.is_in_use = true; |
| pas_compiler_fence(); |
| |
| pas_local_allocator_scavenger_data_did_use_for_allocation(&view_cache->scavenger_data); |
| |
| if (!pas_local_view_cache_is_empty(view_cache)) { |
| new_view = pas_segregated_exclusive_view_as_view(pas_local_view_cache_pop(view_cache)); |
| |
| PAS_TESTING_ASSERT(pas_segregated_view_is_some_exclusive(new_view)); |
| PAS_TESTING_ASSERT(pas_segregated_view_is_owned(new_view)); |
| PAS_TESTING_ASSERT(!pas_segregated_view_is_eligible(new_view)); |
| PAS_TESTING_ASSERT(pas_segregated_view_get_page(new_view)->is_in_use_for_allocation); |
| |
| if (verbose) |
| pas_log("%p, a=%p, vc=%p: Got from cache (%u).\n", cache_node, allocator, view_cache, allocator->object_size); |
| |
| did_get_view = true; |
| } |
| |
| pas_compiler_fence(); |
| view_cache->scavenger_data.is_in_use = false; |
| } |
| } |
| |
| if (!did_get_view) { |
| new_view = pas_segregated_size_directory_take_first_eligible(size_directory); |
| if (verbose) |
| pas_log("Got a new view %p\n", new_view); |
| |
| PAS_TESTING_ASSERT(!pas_segregated_view_is_eligible(new_view)); |
| if (pas_segregated_view_is_some_exclusive(new_view)) { |
| PAS_TESTING_ASSERT( |
| !pas_segregated_view_is_owned(new_view) |
| || !pas_segregated_view_get_page(new_view)->is_in_use_for_allocation); |
| } |
| |
| if (new_view) { |
| if (verbose) |
| pas_log("%p, %p: Got from directory (%u).\n", cache_node, allocator, allocator->object_size); |
| new_view = pas_segregated_view_will_start_allocating(new_view, page_config); |
| } |
| if (verbose) { |
| pas_log("And after will_start got a new view %p, page = %p, boundary = %p\n", |
| new_view, |
| pas_segregated_view_get_page(new_view), |
| pas_segregated_view_get_page_boundary(new_view)); |
| } |
| } |
| } else { |
| pas_local_allocator_finish_refill_with_bitfit(allocator, size_directory); |
| if (verbose) |
| pas_log("initialized bitfit!\n"); |
| did_initialize_bitfit = true; |
| } |
| |
| /* At this point we need to get the page. We can't do that for primordial partial views, |
| though. */ |
| |
| if (old_view) { |
| if (verbose) |
| pas_log("Getting rid of page %p\n", old_page); |
| PAS_ASSERT(enable_segregated); |
| pas_segregated_page_switch_lock(old_page, |
| &held_lock, |
| page_config); |
| pas_segregated_view_did_stop_allocating(old_view, old_page, page_config); |
| if (verbose) |
| pas_log("Done getting rid of page %p\n", old_page); |
| } |
| |
| if (!new_view) { |
| pas_lock_switch(&held_lock, NULL); |
| if (verbose) |
| pas_log("No new page but did_initialize_bitfit = %d.\n", did_initialize_bitfit); |
| return did_initialize_bitfit; |
| } |
| |
| PAS_ASSERT(enable_segregated); |
| |
| if (pas_segregated_view_is_some_exclusive(new_view)) { |
| exclusive = pas_segregated_view_get_exclusive(new_view); |
| new_page = pas_segregated_page_for_boundary(exclusive->page_boundary, page_config); |
| |
| pas_segregated_page_switch_lock_and_rebias_while_ineligible( |
| new_page, &held_lock, cache_node, page_config); |
| |
| pas_segregated_exclusive_view_did_start_allocating( |
| exclusive, new_view, size_directory, new_page, &held_lock, page_config); |
| goto prepare_exclusive; |
| } |
| |
| PAS_TESTING_ASSERT(pas_segregated_view_is_partial(new_view)); |
| |
| partial = pas_segregated_view_get_partial(new_view); |
| shared = pas_compact_segregated_shared_view_ptr_load(&partial->shared_view); |
| |
| if (!shared) { |
| pas_lock_switch(&held_lock, NULL); |
| if (verbose) |
| pas_log("Going the primordial route.\n"); |
| |
| return page_config.specialized_local_allocator_start_allocating_in_primordial_partial_view( |
| allocator, pas_segregated_view_get_partial(new_view), size_directory); |
| } |
| |
| new_page = pas_segregated_page_for_boundary( |
| pas_shared_handle_or_page_boundary_get_page_boundary( |
| shared->shared_handle_or_page_boundary, page_config), |
| page_config); |
| |
| pas_segregated_page_switch_lock(new_page, &held_lock, page_config); |
| |
| pas_segregated_partial_view_did_start_allocating(partial, shared, page_config); |
| goto prepare_partial; |
| |
| prepare_exclusive: |
| pas_local_allocator_prepare_to_allocate( |
| allocator, pas_segregated_exclusive_view_kind, exclusive, new_page, size_directory, |
| page_config); |
| goto done; |
| |
| prepare_partial: |
| pas_local_allocator_prepare_to_allocate( |
| allocator, pas_segregated_partial_view_kind, partial, new_page, size_directory, |
| page_config); |
| |
| done: |
| pas_lock_switch(&held_lock, NULL); |
| |
| PAS_TESTING_ASSERT(allocator->page_ish); |
| PAS_TESTING_ASSERT(allocator->payload_end || allocator->current_offset < allocator->end_offset); |
| if (pas_local_allocator_config_kind_is_primordial_partial(allocator->config_kind)) { |
| PAS_TESTING_ASSERT(allocator->current_offset == allocator->end_offset); |
| PAS_TESTING_ASSERT(allocator->remaining); /* We must have left behind at least one object for the |
| fast path to take. */ |
| } |
| |
| return true; |
| } |
| |
| typedef struct { |
| pas_full_alloc_bits full_alloc_bits; |
| pas_local_allocator* allocator; |
| pas_segregated_page_config page_config; |
| uintptr_t page_boundary; |
| pas_segregated_page* page; |
| } pas_local_allocator_return_memory_to_page_set_bit_data; |
| |
| static PAS_ALWAYS_INLINE unsigned |
| pas_local_allocator_return_memory_to_page_bits_source(size_t word_index, |
| void* arg) |
| { |
| pas_local_allocator_return_memory_to_page_set_bit_data* data; |
| |
| data = (pas_local_allocator_return_memory_to_page_set_bit_data*)arg; |
| |
| return data->full_alloc_bits.bits[word_index] & ((unsigned*)data->allocator->bits)[word_index]; |
| } |
| |
| static PAS_ALWAYS_INLINE bool |
| pas_local_allocator_return_memory_to_page_set_bit_callback(pas_found_bit_index index, |
| void* arg) |
| { |
| pas_local_allocator_return_memory_to_page_set_bit_data* data; |
| uintptr_t object; |
| |
| data = (pas_local_allocator_return_memory_to_page_set_bit_data*)arg; |
| |
| object = data->page_boundary + pas_page_base_object_offset_from_page_boundary_at_index( |
| (unsigned)index.index, data->page_config.base); |
| |
| pas_segregated_page_deallocate_with_page( |
| data->page, object, pas_segregated_deallocation_direct_mode, NULL, data->page_config); |
| |
| return true; |
| } |
| |
| static PAS_ALWAYS_INLINE void |
| pas_local_allocator_return_memory_to_page(pas_local_allocator* allocator, |
| pas_segregated_view view, |
| pas_segregated_page* page, |
| pas_segregated_size_directory* directory, |
| pas_lock_hold_mode heap_lock_hold_mode, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| uintptr_t begin; |
| uintptr_t end; |
| uintptr_t object; |
| size_t object_size; |
| pas_full_alloc_bits full_alloc_bits; |
| pas_local_allocator_return_memory_to_page_set_bit_data data; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| |
| if (!pas_segregated_page_config_is_utility(page_config)) |
| pas_lock_assert_held(page->lock_ptr); |
| |
| if (verbose) { |
| pas_log("Returning memory to page from %p, %s.\n", |
| allocator, |
| pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| |
| if (pas_local_allocator_config_kind_is_primordial_partial(allocator->config_kind)) { |
| if (verbose) |
| pas_log("Blessing primordial.\n"); |
| pas_local_allocator_bless_primordial_partial_view_before_stopping( |
| allocator, page, heap_lock_hold_mode, page_config); |
| } |
| |
| object_size = allocator->object_size; |
| |
| if (allocator->remaining) { |
| begin = allocator->payload_end - allocator->remaining; |
| end = allocator->payload_end; |
| |
| for (object = begin; object < end; object += object_size) { |
| pas_segregated_page_deallocate_with_page( |
| page, object, pas_segregated_deallocation_direct_mode, NULL, page_config); |
| } |
| } |
| |
| if (allocator->current_offset == allocator->end_offset) |
| return; |
| |
| if (page_config.use_reversed_current_word) { |
| PAS_ASSERT(page_config.variant == pas_small_segregated_page_config_variant); |
| allocator->bits[allocator->current_offset] = pas_reverse64(allocator->current_word); |
| } else if (page_config.variant == pas_small_segregated_page_config_variant) |
| allocator->bits[allocator->current_offset] = allocator->current_word; |
| |
| PAS_ASSERT(!pas_local_allocator_config_kind_is_primordial_partial(allocator->config_kind)); |
| |
| full_alloc_bits = |
| pas_full_alloc_bits_create_for_view_and_directory(view, directory, page_config); |
| |
| if (verbose) { |
| pas_log("Full alloc bits have range %zu...%zu\n", |
| full_alloc_bits.word_index_begin, |
| full_alloc_bits.word_index_end); |
| } |
| |
| data.allocator = allocator; |
| data.full_alloc_bits = full_alloc_bits; |
| data.page_config = page_config; |
| data.page_boundary = pas_local_allocator_page_boundary(allocator, page_config); |
| data.page = page; |
| pas_bitvector_for_each_set_bit( |
| pas_local_allocator_return_memory_to_page_bits_source, |
| full_alloc_bits.word_index_begin, full_alloc_bits.word_index_end, |
| pas_local_allocator_return_memory_to_page_set_bit_callback, |
| &data); |
| } |
| |
| /* This returns either: |
| |
| - A success. Then you're done! |
| |
| - A failure. That means you need to refill and try again. */ |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_with_free_bits( |
| pas_local_allocator* allocator, |
| pas_segregated_page_config page_config) |
| { |
| static const bool verbose = false; |
| |
| uintptr_t found_bit_index; |
| uint64_t current_word; |
| uintptr_t result; |
| unsigned current_offset; |
| uintptr_t page_ish; |
| |
| bool use_current_word; |
| bool use_reversed_current_word; |
| |
| PAS_ASSERT(page_config.base.is_enabled); |
| |
| use_current_word = page_config.variant == pas_small_segregated_page_config_variant; |
| |
| use_reversed_current_word = page_config.use_reversed_current_word; |
| |
| if (use_reversed_current_word) |
| PAS_ASSERT(use_current_word); |
| |
| current_offset = 0; /* Tell the compiler to chill. */ |
| |
| if (use_current_word) { |
| /* This is the path we want to take. We use it for small objects. The point here is that |
| the small object path doesn't have to check if the local allocator is in small object |
| mode - it just tries to allocate using current_word. |
| |
| If the allocator turns out to be a medium allocator, then this will fail because medium |
| allocators always set current_word to zero, and we will cascade into the slow path that |
| runs the actual medium allocator. */ |
| current_word = allocator->current_word; |
| } else { |
| PAS_TESTING_ASSERT(!allocator->current_word); |
| |
| current_offset = allocator->current_offset; |
| |
| if (PAS_UNLIKELY(current_offset >= allocator->end_offset)) |
| return pas_allocation_result_create_failure(); |
| |
| current_word = allocator->bits[current_offset]; |
| } |
| |
| page_ish = allocator->page_ish; |
| |
| if (PAS_UNLIKELY(!current_word)) { |
| unsigned end_offset; |
| |
| end_offset = allocator->end_offset; |
| |
| if (use_current_word) { |
| if (allocator->config_kind |
| != pas_local_allocator_config_kind_create_normal(page_config.kind)) |
| return pas_allocation_result_create_failure(); |
| |
| current_offset = allocator->current_offset; |
| if (current_offset >= end_offset) |
| return pas_allocation_result_create_failure(); |
| |
| PAS_TESTING_ASSERT(current_offset < PAS_BITVECTOR_NUM_WORDS64(page_config.num_alloc_bits)); |
| |
| if (verbose) |
| pas_log("Zeroing %p\n", allocator->bits + current_offset); |
| allocator->bits[current_offset] = 0; |
| } |
| |
| do { |
| ++current_offset; |
| page_ish += 64u << page_config.base.min_align_shift; |
| if (current_offset >= end_offset) { |
| PAS_TESTING_ASSERT(current_offset == end_offset || current_offset == end_offset + 1); |
| allocator->current_offset = end_offset; |
| return pas_allocation_result_create_failure(); |
| } |
| current_word = allocator->bits[current_offset]; |
| } while (!current_word); |
| |
| if (use_reversed_current_word) { |
| uint64_t reversed_current_word; |
| reversed_current_word = pas_reverse64(current_word); |
| if (verbose) |
| pas_log("Original word = %llx, reversed = %llx\n", current_word, reversed_current_word); |
| current_word = reversed_current_word; |
| } |
| |
| allocator->current_offset = current_offset; |
| allocator->page_ish = page_ish; |
| } |
| |
| if (use_reversed_current_word) { |
| if (verbose) |
| pas_log("current_word = %llx\n", current_word); |
| found_bit_index = (uintptr_t)__builtin_clzll(current_word); |
| if (verbose) |
| pas_log("found_bit_index = %lu\n", found_bit_index); |
| current_word &= ~(0x8000000000000000llu >> found_bit_index); |
| if (verbose) |
| pas_log("new current_word = %llx\n", current_word); |
| } else { |
| found_bit_index = (uintptr_t)__builtin_ctzll(current_word); |
| current_word &= ~PAS_BITVECTOR_BIT_MASK64(found_bit_index); |
| } |
| if (use_current_word) |
| allocator->current_word = current_word; |
| else |
| allocator->bits[current_offset] = current_word; |
| result = page_ish + (found_bit_index << page_config.base.min_align_shift); |
| |
| if (verbose) { |
| pas_log( |
| "%p(%p): Returning result using free bits: %p.\n", |
| allocator, |
| pas_segregated_view_get_size_directory(allocator->view), |
| (void*)result); |
| } |
| |
| return pas_allocation_result_create_success(result); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_inline_cases(pas_local_allocator* allocator, |
| pas_heap_config config) |
| { |
| static const bool verbose = false; |
| |
| unsigned remaining; |
| unsigned object_size; |
| |
| if (!config.small_segregated_config.base.is_enabled |
| && !config.medium_segregated_config.base.is_enabled) |
| return pas_allocation_result_create_failure(); |
| |
| remaining = allocator->remaining; |
| object_size = allocator->object_size; |
| |
| if (remaining) { |
| uintptr_t result; |
| |
| if (verbose) |
| pas_log("payload_end = %p\n", allocator->payload_end); |
| |
| PAS_TESTING_ASSERT(allocator->payload_end); |
| PAS_TESTING_ASSERT(remaining - object_size < allocator->remaining); |
| |
| /* This is the fastest fast path for allocation. It will happen if: |
| |
| - We found a totally empty page. |
| |
| - We are in alloc_bits_in_page refill mode. Note that this refill mode is the slower of |
| the two, and we use it only for bootstrapping. That's because although it uses this |
| very fast fast path, it has a terrible slow path. */ |
| allocator->remaining = remaining - object_size; |
| |
| result = allocator->payload_end - remaining; |
| |
| if (verbose) |
| pas_log("Returning bump allocation %p.\n", (void*)result); |
| |
| return pas_allocation_result_create_success(result); |
| } |
| |
| if (config.small_segregated_config.base.is_enabled) { |
| /* This is the way to the second-fastest fast path. We use it a lot. */ |
| return pas_local_allocator_try_allocate_with_free_bits( |
| allocator, config.small_segregated_config); |
| } |
| |
| return pas_allocation_result_create_failure(); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_small_segregated_slow_impl( |
| pas_local_allocator* allocator, |
| pas_heap_config config, |
| pas_allocator_counts* counts) |
| { |
| PAS_ASSERT(!pas_debug_heap_is_enabled(config.kind)); |
| |
| for (;;) { |
| pas_allocation_result result; |
| bool skip_bitfit; |
| bool refill_result; |
| |
| PAS_ASSERT(config.small_segregated_config.base.is_enabled); |
| PAS_TESTING_ASSERT(allocator->config_kind == pas_local_allocator_config_kind_create_normal( |
| config.small_segregated_config.kind)); |
| |
| /* It's a *bit* gross that we're inlining this here. But, some profiling implied that not |
| inlining this made it twice as expensive. It's just one of those things: if code size is ever |
| an issue, then we should ponder turning this back into an outline call. */ |
| skip_bitfit = true; |
| refill_result = pas_local_allocator_refill_with_known_config( |
| allocator, counts, config.small_segregated_config, skip_bitfit); |
| |
| if (!refill_result) |
| return pas_allocation_result_create_failure(); |
| |
| result = pas_local_allocator_try_allocate_inline_cases(allocator, config); |
| if (result.did_succeed) |
| return result; |
| } |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_small_segregated_slow( |
| pas_local_allocator* allocator, |
| pas_heap_config config, |
| pas_allocator_counts* counts, |
| pas_allocation_result_filter result_filter) |
| { |
| pas_allocation_result result; |
| |
| result = pas_local_allocator_try_allocate_small_segregated_slow_impl(allocator, config, counts); |
| |
| pas_compiler_fence(); |
| allocator->scavenger_data.is_in_use = false; |
| |
| return result_filter(result); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_fast_path_allocation_result |
| pas_local_allocator_try_allocate_out_of_line_cases( |
| pas_local_allocator* allocator, |
| size_t size, |
| size_t alignment, |
| pas_heap_config config) |
| { |
| static const bool verbose = false; |
| |
| pas_local_allocator_config_kind our_kind; |
| our_kind = allocator->config_kind; |
| |
| if (verbose) { |
| pas_log("try_allocate_out_of_line_cases with kind = %s, size = %zu, alignment = %zu.\n", |
| pas_local_allocator_config_kind_get_string(our_kind), size, alignment); |
| } |
| |
| if (config.small_bitfit_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_bitfit( |
| config.small_bitfit_config.kind)) { |
| return config.small_bitfit_config.specialized_allocator_try_allocate( |
| pas_local_allocator_get_bitfit(allocator), allocator, |
| size, alignment); |
| } |
| |
| if (config.medium_segregated_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_normal( |
| config.medium_segregated_config.kind)) { |
| return pas_fast_path_allocation_result_from_allocation_result( |
| config.specialized_local_allocator_try_allocate_medium_segregated_with_free_bits(allocator), |
| pas_fast_path_allocation_result_need_slow); |
| } |
| |
| if (config.medium_bitfit_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_bitfit( |
| config.medium_bitfit_config.kind)) { |
| return config.medium_bitfit_config.specialized_allocator_try_allocate( |
| pas_local_allocator_get_bitfit(allocator), allocator, |
| size, alignment); |
| } |
| |
| if (config.small_segregated_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_primordial_partial( |
| config.small_segregated_config.kind)) { |
| return pas_fast_path_allocation_result_from_allocation_result( |
| config.small_segregated_config |
| .specialized_local_allocator_try_allocate_in_primordial_partial_view(allocator), |
| pas_fast_path_allocation_result_need_slow); |
| } |
| |
| if (config.medium_segregated_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_primordial_partial( |
| config.medium_segregated_config.kind)) { |
| return pas_fast_path_allocation_result_from_allocation_result( |
| config.medium_segregated_config |
| .specialized_local_allocator_try_allocate_in_primordial_partial_view(allocator), |
| pas_fast_path_allocation_result_need_slow); |
| } |
| |
| if (config.marge_bitfit_config.base.is_enabled && |
| our_kind == pas_local_allocator_config_kind_create_bitfit( |
| config.marge_bitfit_config.kind)) { |
| return config.marge_bitfit_config.specialized_allocator_try_allocate( |
| pas_local_allocator_get_bitfit(allocator), allocator, size, alignment); |
| } |
| |
| return pas_fast_path_allocation_result_create_need_slow(); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_slow_impl(pas_local_allocator* allocator, |
| size_t size, |
| size_t alignment, |
| pas_heap_config config, |
| pas_allocator_counts* counts) |
| { |
| static const bool verbose = false; |
| |
| if (verbose) { |
| pas_log("Called try_allocate_slow with kind = %s\n", |
| pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| |
| PAS_ASSERT(!pas_debug_heap_is_enabled(config.kind)); |
| |
| for (;;) { |
| pas_fast_path_allocation_result fast_result; |
| pas_allocation_result result; |
| pas_segregated_page_config* page_config; |
| |
| fast_result = pas_local_allocator_try_allocate_out_of_line_cases( |
| allocator, size, alignment, config); |
| if (fast_result.kind != pas_fast_path_allocation_result_need_slow) |
| return pas_fast_path_allocation_result_to_allocation_result(fast_result); |
| |
| if (verbose) { |
| pas_log("Refilling from try_allocate_slow with kind = %s\n", |
| pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| |
| /* The config kind may be bitfit, but refilling is specialized on segregated page config. */ |
| page_config = pas_segregated_view_get_page_config(allocator->view); |
| if (page_config) |
| page_config->specialized_local_allocator_refill(allocator, counts); |
| else |
| pas_local_allocator_refill_with_bitfit(allocator, counts); |
| |
| if (!allocator->page_ish && !pas_local_allocator_has_bitfit(allocator)) |
| return pas_allocation_result_create_failure(); |
| |
| result = config.specialized_local_allocator_try_allocate_inline_cases(allocator); |
| if (result.did_succeed) |
| return result; |
| |
| if (verbose) { |
| pas_log("Relooping in try_allocate_slow with kind = %s\n", |
| pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| } |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_slow(pas_local_allocator* allocator, |
| size_t size, |
| size_t alignment, |
| pas_heap_config config, |
| pas_allocator_counts* counts, |
| pas_allocation_result_filter result_filter) |
| { |
| pas_allocation_result result; |
| |
| result = pas_local_allocator_try_allocate_slow_impl( |
| allocator, size, alignment, config, counts); |
| |
| pas_compiler_fence(); |
| allocator->scavenger_data.is_in_use = false; |
| |
| return result_filter(result); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate_inline_only(pas_local_allocator* allocator, |
| pas_heap_config config, |
| pas_allocation_result_filter result_filter) |
| { |
| static const bool verbose = false; |
| pas_allocation_result result; |
| |
| if (verbose) { |
| pas_log("Allocator %p (%s) allocating inline only.\n", |
| allocator, pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| |
| allocator->scavenger_data.is_in_use = true; |
| pas_compiler_fence(); |
| |
| result = pas_local_allocator_try_allocate_inline_cases(allocator, config); |
| if (result.did_succeed) { |
| pas_compiler_fence(); |
| allocator->scavenger_data.is_in_use = false; |
| |
| if (verbose) { |
| pas_log("Allocator %p (%s) allocated %p (inline cases).\n", |
| allocator, pas_local_allocator_config_kind_get_string(allocator->config_kind), |
| (void*)result.begin); |
| } |
| |
| return result_filter( |
| pas_allocation_result_create_success_with_zero_mode(result.begin, result.zero_mode)); |
| } |
| |
| pas_compiler_fence(); |
| allocator->scavenger_data.is_in_use = false; |
| |
| /* NOTE: It's intentional that we only apply the result filter on the success case. */ |
| return pas_allocation_result_create_failure(); |
| } |
| |
| static PAS_ALWAYS_INLINE pas_allocation_result |
| pas_local_allocator_try_allocate(pas_local_allocator* allocator, |
| pas_size_thunk size_thunk, |
| void* size_thunk_arg, |
| size_t alignment, |
| pas_heap_config config, |
| pas_allocator_counts* counts, |
| pas_allocation_result_filter result_filter) |
| { |
| static const bool verbose = false; |
| pas_allocation_result result; |
| |
| PAS_TESTING_ASSERT(!allocator->scavenger_data.is_in_use); |
| |
| if (verbose) { |
| pas_log("Allocator %p (%s) allocating size = %zu, alignment = %zu.\n", |
| allocator, pas_local_allocator_config_kind_get_string(allocator->config_kind), |
| size_thunk(size_thunk_arg), alignment); |
| } |
| |
| allocator->scavenger_data.is_in_use = true; |
| pas_compiler_fence(); |
| |
| result = pas_local_allocator_try_allocate_inline_cases(allocator, config); |
| if (result.did_succeed) { |
| pas_compiler_fence(); |
| allocator->scavenger_data.is_in_use = false; |
| |
| if (verbose) { |
| pas_log("Allocator %p (%s) allocated %p (inline cases).\n", |
| allocator, pas_local_allocator_config_kind_get_string(allocator->config_kind), |
| (void*)result.begin); |
| } |
| |
| return result_filter( |
| pas_allocation_result_create_success_with_zero_mode(result.begin, result.zero_mode)); |
| } |
| |
| if (PAS_UNLIKELY(pas_debug_heap_is_enabled(config.kind))) |
| return pas_debug_heap_allocate(size_thunk(size_thunk_arg), alignment); |
| |
| if (config.small_segregated_config.base.is_enabled && |
| allocator->config_kind == pas_local_allocator_config_kind_create_normal( |
| config.small_segregated_config.kind)) { |
| pas_compiler_fence(); |
| |
| if (verbose) { |
| pas_log("Allocator %p (%s) allocating using small segregated.\n", |
| allocator, pas_local_allocator_config_kind_get_string(allocator->config_kind)); |
| } |
| |
| result = config.specialized_local_allocator_try_allocate_small_segregated_slow( |
| allocator, counts, result_filter); |
| if (verbose) |
| pas_log("in small segregated slow return - result.begin = %p\n", (void*)result.begin); |
| return result; |
| } |
| |
| result = config.specialized_local_allocator_try_allocate_slow( |
| allocator, size_thunk(size_thunk_arg), alignment, counts, result_filter); |
| if (verbose) |
| pas_log("in generic return - result.begin = %p\n", (void*)result.begin); |
| return result; |
| } |
| |
| PAS_END_EXTERN_C; |
| |
| #endif /* PAS_LOCAL_ALLOCATOR_INLINES_H */ |
| |