blob: f07c587b165cb525d19d325bfb4b6a387c2de6f9 [file] [log] [blame]
/*
* Copyright (c) 2020-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.
*/
#include "pas_config.h"
#if LIBPAS_ENABLED
#include "pas_bitfit_allocator.h"
#include "pas_bitfit_allocator_inlines.h"
#include "pas_bitfit_directory.h"
#include "pas_bitfit_size_class.h"
#include "pas_bitfit_view_inlines.h"
#include "pas_epoch.h"
#include "pas_physical_memory_transaction.h"
bool pas_bitfit_allocator_commit_view(pas_bitfit_view* view,
pas_local_allocator* local,
pas_bitfit_page_config* config,
pas_lock_hold_mode commit_lock_hold_mode)
{
static const bool verbose = false;
pas_bitfit_directory* directory;
directory = pas_compact_bitfit_directory_ptr_load(&view->directory);
/* We're almost certainly gonna commit a page, so let's just get this out of the way. We need to
release the ownership lock to do this. But, we can't do it at all if we already hold the commit
lock -- and if we do, then we've already executed some kind of take. */
if (!commit_lock_hold_mode) {
pas_lock_unlock(&view->ownership_lock);
pas_physical_page_sharing_pool_take_for_page_config(
config->base.page_size, &config->base, pas_lock_is_not_held, NULL, 0);
pas_lock_lock(&view->ownership_lock);
}
if (verbose)
pas_log("committing bitfit view!\n");
for (;;) {
pas_lock* commit_lock;
bool has_page_boundary;
if (PAS_LIKELY(view->is_owned)) {
if (verbose)
pas_log("already owned.\n");
PAS_ASSERT(view->page_boundary);
return true;
}
/* Checking the presence of page boundary while holding the ownership lock ensures that if
there are two concurrent commits on a page that is not yet allocated, then one of them
will only proceed to the commit after the other has already finished. This avoids
recommit and reconstruction of a page that is already constructed. */
has_page_boundary = !!view->page_boundary;
pas_lock_unlock(&view->ownership_lock);
if (!has_page_boundary) {
pas_physical_memory_transaction transaction;
bool did_succeed;
bool did_already_have_boundary;
if (verbose)
pas_log("need to allocate new page.\n");
pas_physical_memory_transaction_construct(&transaction);
did_succeed = false;
did_already_have_boundary = false;
for (;;) {
PAS_ASSERT(!did_succeed);
PAS_ASSERT(!did_already_have_boundary);
pas_physical_memory_transaction_begin(&transaction);
pas_heap_lock_lock();
pas_lock_lock(&view->ownership_lock);
if (view->page_boundary) {
bool end_result;
pas_heap_lock_unlock();
end_result = pas_physical_memory_transaction_end(&transaction);
PAS_ASSERT(end_result);
did_already_have_boundary = true;
break;
}
PAS_ASSERT(!view->is_owned);
if (verbose) {
pas_log("really allocating new page.\n");
pas_log("Giving view %p with config %s a page.\n",
view, pas_bitfit_page_config_kind_get_string(config->kind));
}
view->page_boundary = config->base.page_allocator(
pas_segregated_view_get_size_directory(local->view)->heap, &transaction);
did_succeed = !!view->page_boundary;
if (verbose)
pas_log("page_boundary = %p, did_succeed = %d\n", view->page_boundary, did_succeed);
if (did_succeed)
config->base.create_page_header(view->page_boundary, pas_lock_is_held);
pas_heap_lock_unlock();
if (pas_physical_memory_transaction_end(&transaction))
break;
PAS_ASSERT(!did_succeed);
pas_lock_unlock(&view->ownership_lock);
}
if (did_already_have_boundary)
continue;
if (did_succeed) {
view->is_owned = true;
pas_bitfit_page_construct(
pas_bitfit_page_for_boundary_unchecked(view->page_boundary, *config),
view, config);
}
return did_succeed;
}
commit_lock = &view->commit_lock;
pas_lock_lock_conditionally(commit_lock, commit_lock_hold_mode);
if (view->is_owned) {
pas_lock_unlock_conditionally(commit_lock, commit_lock_hold_mode);
pas_lock_lock(&view->ownership_lock);
continue;
}
if (verbose)
pas_log("need to commit existing page.\n");
/* If the view is not owned and we hold the commit lock then there is no way for it to
become owned because anyone who notices that bit being zero will release the ownership
lock and grab the commit lock.
Also, there's no virtual page stealing. So, if a view ever has a page then it will still
have it. */
pas_compiler_fence();
PAS_ASSERT(view->page_boundary);
PAS_ASSERT(!view->is_owned);
pas_page_malloc_commit(
view->page_boundary, config->base.page_size);
config->base.create_page_header(view->page_boundary, pas_lock_is_not_held);
pas_lock_lock(&view->ownership_lock);
PAS_ASSERT(!view->is_owned);
/* We shoud still think that the page is there but not owned! See comment above. */
view->is_owned = true;
pas_bitfit_page_construct(
pas_bitfit_page_for_boundary_unchecked(view->page_boundary, *config), view, config);
pas_lock_unlock_conditionally(commit_lock, commit_lock_hold_mode);
if (PAS_DEBUG_SPECTRUM_USE_FOR_COMMIT) {
pas_heap_lock_lock();
pas_debug_spectrum_add(
directory, pas_bitfit_directory_dump_for_spectrum, config->base.page_size);
pas_heap_lock_unlock();
}
return true;
}
}
pas_bitfit_view* pas_bitfit_allocator_finish_failing(pas_bitfit_allocator* allocator,
pas_bitfit_view* view,
size_t size,
size_t alignment,
size_t largest_available,
pas_bitfit_page_config* config)
{
pas_bitfit_directory* directory;
pas_bitfit_size_class* size_class;
pas_bitfit_view* new_view;
unsigned view_index;
PAS_UNUSED_PARAM(alignment);
pas_lock_assert_held(&view->ownership_lock);
size_class = allocator->size_class;
directory = pas_compact_bitfit_directory_ptr_load_non_null(&size_class->directory);
PAS_ASSERT(pas_compact_bitfit_directory_ptr_load_non_null(&view->directory) == directory);
view_index = view->index;
/* If we're still on the view that the allocator was on and we found that this view no longer
has enough max_free for our size class, then tell everyone about this and bail.
NOTE: There are some aspects of this that we could do even if the view was displaced. Like,
we could update the max_free. */
if (view == allocator->view && largest_available < size_class->size) {
unsigned index;
pas_bitfit_size_class* current_size_class;
pas_versioned_field first_free_value;
pas_bitfit_allocator_reset(allocator);
PAS_ASSERT(view->page_boundary);
pas_bitfit_page_for_boundary(view->page_boundary, *config)->did_note_max_free = false;
index = view_index;
pas_bitfit_directory_set_processed_max_free(
directory, index, largest_available >> config->base.min_align_shift,
"processing on finish_failing");
PAS_TESTING_ASSERT(largest_available < size);
PAS_TESTING_ASSERT(largest_available < size_class->size);
current_size_class = size_class;
do {
/* The induction hypothesis is that current_size_class was too big for
largest_available. */
current_size_class = pas_compact_atomic_bitfit_size_class_ptr_load(
&current_size_class->next_smaller);
} while (current_size_class && largest_available < current_size_class->size);
if (current_size_class) {
PAS_TESTING_ASSERT(largest_available >= current_size_class->size);
do {
first_free_value = pas_versioned_field_read(&current_size_class->first_free);
if (first_free_value.value > index) {
if (!pas_versioned_field_try_write(&current_size_class->first_free,
first_free_value,
index))
continue;
}
current_size_class = pas_compact_atomic_bitfit_size_class_ptr_load(
&current_size_class->next_smaller);
} while (current_size_class);
}
pas_lock_unlock(&view->ownership_lock);
return NULL;
}
pas_lock_unlock(&view->ownership_lock);
/* Try to continue searching for memory in the size class, but without altering its cursor.
This is an unusual degenerate case that happens only for memalign. */
PAS_ASSERT((unsigned)size == size);
new_view = pas_bitfit_directory_get_first_free_view(
directory, view_index + 1, (unsigned)size, config).view;
PAS_ASSERT(new_view);
return new_view;
}
#endif /* LIBPAS_ENABLED */