#include "partition_alloc/slot_start.h"
#include "partition_alloc/partition_root.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include "partition_alloc/bucket_lookup.h"
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/in_slot_metadata.h"
#include "partition_alloc/oom.h"
#include "partition_alloc/page_allocator.h"
#include "partition_alloc/partition_address_space.h"
#include "partition_alloc/partition_alloc-inl.h"
#include "partition_alloc/partition_alloc_base/bits.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_base/component_export.h"
#include "partition_alloc/partition_alloc_base/thread_annotations.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "partition_alloc/partition_bucket.h"
#include "partition_alloc/partition_cookie.h"
#include "partition_alloc/partition_oom.h"
#include "partition_alloc/partition_page.h"
#include "partition_alloc/reservation_offset_table.h"
#include "partition_alloc/spinning_mutex.h"
#include "partition_alloc/tagging.h"
#include "partition_alloc/thread_isolation/thread_isolation.h"
#if PA_BUILDFLAG(IS_MAC)
#include "partition_alloc/partition_alloc_base/mac/mac_util.h"
#endif
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
#include "partition_alloc/address_pool_manager_bitmap.h"
#endif
#if PA_BUILDFLAG(IS_WIN)
#include <windows.h>
#include "wow64apiset.h"
#endif
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_CHROMEOS)
#include <pthread.h>
#endif
namespace partition_alloc::internal {
#if PA_BUILDFLAG(RECORD_ALLOC_INFO)
AllocInfo g_allocs = {};
void RecordAllocOrFree(uintptr_t addr, size_t size) {
g_allocs.allocs[g_allocs.index.fetch_add(1, std::memory_order_relaxed) %
kAllocInfoSize] = {addr, size};
}
#endif
}
namespace partition_alloc {
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
namespace {
internal::Lock g_root_enumerator_lock;
}
internal::Lock& PartitionRoot::GetEnumeratorLock() {
return g_root_enumerator_lock;
}
namespace internal {
class PartitionRootEnumerator {
public:
template <typename T>
using EnumerateCallback = void (*)(PartitionRoot* root, T param);
enum EnumerateOrder {
kNormal,
kReverse,
};
static PartitionRootEnumerator& Instance() {
static PartitionRootEnumerator instance;
return instance;
}
template <typename T>
void Enumerate(EnumerateCallback<T> callback,
T param,
EnumerateOrder order) PA_NO_THREAD_SAFETY_ANALYSIS {
if (order == kNormal) {
PartitionRoot* root;
for (root = Head(partition_roots_); root != nullptr;
root = root->next_root) {
callback(root, param);
}
} else {
PA_DCHECK(order == kReverse);
PartitionRoot* root;
for (root = Tail(partition_roots_); root != nullptr;
root = root->prev_root) {
callback(root, param);
}
}
}
void Register(PartitionRoot* root) {
internal::ScopedGuard guard(PartitionRoot::GetEnumeratorLock());
root->next_root = partition_roots_;
root->prev_root = nullptr;
if (partition_roots_) {
partition_roots_->prev_root = root;
}
partition_roots_ = root;
}
void Unregister(PartitionRoot* root) {
internal::ScopedGuard guard(PartitionRoot::GetEnumeratorLock());
PartitionRoot* prev = root->prev_root;
PartitionRoot* next = root->next_root;
if (prev) {
PA_DCHECK(prev->next_root == root);
prev->next_root = next;
} else {
PA_DCHECK(partition_roots_ == root);
partition_roots_ = next;
}
if (next) {
PA_DCHECK(next->prev_root == root);
next->prev_root = prev;
}
root->next_root = nullptr;
root->prev_root = nullptr;
}
private:
constexpr PartitionRootEnumerator() = default;
PartitionRoot* Head(PartitionRoot* roots) { return roots; }
PartitionRoot* Tail(PartitionRoot* roots) PA_NO_THREAD_SAFETY_ANALYSIS {
if (!roots) {
return nullptr;
}
PartitionRoot* node = roots;
for (; node->next_root != nullptr; node = node->next_root)
;
return node;
}
PartitionRoot* partition_roots_
PA_GUARDED_BY(PartitionRoot::GetEnumeratorLock()) = nullptr;
};
}
#endif
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_CONFIG(HAS_ATFORK_HANDLER)
namespace {
void LockRoot(PartitionRoot* root, bool) PA_NO_THREAD_SAFETY_ANALYSIS {
PA_DCHECK(root);
internal::PartitionRootLock(root).Acquire();
}
template <typename T>
void UnlockOrReinit(T& lock, bool in_child) PA_NO_THREAD_SAFETY_ANALYSIS {
if (in_child) {
lock.Reinit();
} else {
lock.Release();
}
}
void UnlockOrReinitRoot(PartitionRoot* root,
bool in_child) PA_NO_THREAD_SAFETY_ANALYSIS {
UnlockOrReinit(internal::PartitionRootLock(root), in_child);
}
} #endif
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
namespace {
#if PA_CONFIG(HAS_ATFORK_HANDLER)
void BeforeForkInParent() PA_NO_THREAD_SAFETY_ANALYSIS {
g_root_enumerator_lock.Acquire();
internal::PartitionRootEnumerator::Instance().Enumerate(
LockRoot, false,
internal::PartitionRootEnumerator::EnumerateOrder::kNormal);
ThreadCacheRegistry::GetLock().Acquire();
}
void ReleaseLocks(bool in_child) PA_NO_THREAD_SAFETY_ANALYSIS {
UnlockOrReinit(ThreadCacheRegistry::GetLock(), in_child);
internal::PartitionRootEnumerator::Instance().Enumerate(
UnlockOrReinitRoot, in_child,
internal::PartitionRootEnumerator::EnumerateOrder::kReverse);
UnlockOrReinit(g_root_enumerator_lock, in_child);
}
void AfterForkInParent() {
ReleaseLocks( false);
}
void AfterForkInChild() {
ReleaseLocks( true);
ThreadCacheRegistry::Instance().ForcePurgeAllThreadAfterForkUnsafe();
}
#endif
std::atomic<bool> g_global_init_called;
void PartitionAllocMallocInitOnce() {
bool expected = false;
if (!g_global_init_called.compare_exchange_strong(expected, true)) {
return;
}
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_CHROMEOS)
int err =
pthread_atfork(BeforeForkInParent, AfterForkInParent, AfterForkInChild);
PA_CHECK(err == 0);
#endif }
}
#if PA_BUILDFLAG(IS_APPLE)
void PartitionAllocMallocHookOnBeforeForkInParent() {
BeforeForkInParent();
}
void PartitionAllocMallocHookOnAfterForkInParent() {
AfterForkInParent();
}
void PartitionAllocMallocHookOnAfterForkInChild() {
AfterForkInChild();
}
#endif
#endif
namespace internal {
namespace {
constexpr size_t kMaxPurgeableSlotsPerSystemPage = 64;
constexpr size_t kConservativeMaxPurgeableSlotsPerSystemPage = 2;
PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
MinPurgeableSlotSize() {
return SystemPageSize() / kMaxPurgeableSlotsPerSystemPage;
}
PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
MinConservativePurgeableSlotSize() {
return SystemPageSize() / kConservativeMaxPurgeableSlotsPerSystemPage;
}
}
PA_NOPROFILE
static size_t PartitionPurgeSlotSpan(PartitionRoot* root,
internal::SlotSpanMetadata* slot_span,
bool accounting_only)
PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(root)) {
const internal::PartitionBucket* bucket = slot_span->bucket;
size_t slot_size = bucket->slot_size;
if (slot_size < MinPurgeableSlotSize() || !slot_span->num_allocated_slots) {
return 0;
}
size_t bucket_num_slots = bucket->get_slots_per_span();
size_t discardable_bytes = 0;
if (slot_span->CanStoreRawSize()) {
uint32_t utilized_slot_size = static_cast<uint32_t>(
RoundUpToSystemPage(slot_span->GetUtilizedSlotSize()));
discardable_bytes = bucket->slot_size - utilized_slot_size;
if (discardable_bytes && !accounting_only) {
SlotSpanStart slot_span_start =
internal::SlotSpanMetadata::ToSlotSpanStart(slot_span, root);
uintptr_t committed_data_end =
slot_span_start.value() + utilized_slot_size;
ScopedSyscallTimer timer{root};
DiscardSystemPages(committed_data_end, discardable_bytes);
}
return discardable_bytes;
}
#if defined(PAGE_ALLOCATOR_CONSTANTS_ARE_CONSTEXPR)
constexpr size_t kMaxSlotCount =
(PartitionPageSize() * kMaxPartitionPagesPerRegularSlotSpan) /
MinPurgeableSlotSize();
#elif PA_BUILDFLAG(IS_APPLE) || \
defined(PARTITION_ALLOCATOR_CONSTANTS_POSIX_NONCONST_PAGE_SIZE)
constexpr size_t kMaxSlotCount =
4 * kMaxPurgeableSlotsPerSystemPage *
internal::kMaxPartitionPagesPerRegularSlotSpan;
PA_CHECK(kMaxSlotCount == (PartitionPageSize() *
internal::kMaxPartitionPagesPerRegularSlotSpan) /
MinPurgeableSlotSize());
#endif
PA_DCHECK(bucket_num_slots <= kMaxSlotCount);
PA_DCHECK(slot_span->num_unprovisioned_slots < bucket_num_slots);
size_t num_provisioned_slots =
bucket_num_slots - slot_span->num_unprovisioned_slots;
std::array<char, kMaxSlotCount> slot_usage{};
#if !PA_BUILDFLAG(IS_WIN)
size_t last_slot = static_cast<size_t>(-1);
#endif
std::fill_n(slot_usage.begin(), num_provisioned_slots, 1);
SlotSpanStart slot_span_start =
internal::SlotSpanMetadata::ToSlotSpanStart(slot_span, root);
for (FreelistEntry* entry = slot_span->get_freelist_head(); entry;
entry = entry->GetNext(slot_size)) {
size_t slot_number = bucket->GetSlotNumber(
slot_span_start.offset(SlotStart::Unchecked(entry).Untag().value()));
PA_DCHECK(slot_number < num_provisioned_slots);
slot_usage[slot_number] = 0;
#if !PA_BUILDFLAG(IS_WIN)
if (entry->IsEncodedNextPtrZero()) {
last_slot = slot_number;
}
#endif
}
size_t truncated_slots = 0;
while (!slot_usage[num_provisioned_slots - 1]) {
truncated_slots++;
num_provisioned_slots--;
PA_DCHECK(num_provisioned_slots);
}
size_t unprovisioned_bytes = 0;
uintptr_t begin_addr =
slot_span_start.value() + (num_provisioned_slots * slot_size);
uintptr_t end_addr = begin_addr + (slot_size * truncated_slots);
if (truncated_slots) {
uintptr_t rounded_up_truncatation_begin_addr =
RoundUpToSystemPage(begin_addr);
while (begin_addr + slot_size <= rounded_up_truncatation_begin_addr) {
begin_addr += slot_size;
PA_DCHECK(truncated_slots);
--truncated_slots;
++num_provisioned_slots;
}
begin_addr = rounded_up_truncatation_begin_addr;
end_addr = RoundUpToSystemPage(end_addr);
PA_DCHECK(end_addr <=
slot_span_start.value() + bucket->get_bytes_per_span());
if (begin_addr < end_addr) {
unprovisioned_bytes = end_addr - begin_addr;
discardable_bytes += unprovisioned_bytes;
}
}
if (!accounting_only) {
auto straighten_mode =
PartitionRoot::GetStraightenLargerSlotSpanFreeListsMode();
bool straighten =
straighten_mode == StraightenLargerSlotSpanFreeListsMode::kAlways ||
(straighten_mode ==
StraightenLargerSlotSpanFreeListsMode::kOnlyWhenUnprovisioning &&
unprovisioned_bytes);
PA_DCHECK((unprovisioned_bytes > 0) == (truncated_slots > 0));
size_t new_unprovisioned_slots =
truncated_slots + slot_span->num_unprovisioned_slots;
PA_DCHECK(new_unprovisioned_slots <= bucket->get_slots_per_span());
slot_span->num_unprovisioned_slots = new_unprovisioned_slots;
size_t num_new_freelist_entries = 0;
internal::FreelistEntry* back = nullptr;
if (straighten) {
for (size_t slot_index = 0; slot_index < num_provisioned_slots;
++slot_index) {
if (slot_usage[slot_index]) {
continue;
}
auto* entry = slot_span_start.GetNthSlotStart(slot_index, slot_size)
.Tag()
.ToObject<FreelistEntry>();
if (num_new_freelist_entries) {
back->SetNext(entry);
} else {
slot_span->SetFreelistHead(entry);
}
back = entry;
num_new_freelist_entries++;
}
} else if (unprovisioned_bytes) {
uintptr_t first_unprovisioned_slot =
slot_span_start.value() + (num_provisioned_slots * slot_size);
bool skipped = false;
for (FreelistEntry* entry = slot_span->get_freelist_head(); entry;
entry = entry->GetNext(slot_size)) {
uintptr_t entry_addr = SlotStart::Unchecked(entry).Untag().value();
if (entry_addr >= first_unprovisioned_slot) {
skipped = true;
continue;
}
if (skipped) {
if (num_new_freelist_entries) {
back->SetNext(entry);
} else {
slot_span->SetFreelistHead(entry);
}
skipped = false;
}
back = entry;
num_new_freelist_entries++;
}
}
if (straighten || unprovisioned_bytes) {
if (num_new_freelist_entries) {
PA_DCHECK(back);
FreelistEntry::EmplaceAndInitNull(back);
#if !PA_BUILDFLAG(IS_WIN)
last_slot = bucket->GetSlotNumber(
slot_span_start.offset(SlotStart::Unchecked(back).Untag().value()));
#endif
} else {
PA_DCHECK(!back);
slot_span->SetFreelistHead(nullptr);
}
PA_DCHECK(num_new_freelist_entries ==
num_provisioned_slots - slot_span->num_allocated_slots);
}
if (unprovisioned_bytes) {
if (!kUseLazyCommit) {
ScopedSyscallTimer timer{root};
DiscardSystemPages(begin_addr, unprovisioned_bytes);
} else {
root->DecommitSystemPagesForData(
begin_addr, unprovisioned_bytes,
PageAccessibilityDisposition::kAllowKeepForPerf);
}
}
}
if (slot_size < SystemPageSize()) {
return discardable_bytes;
}
for (size_t i = 0; i < num_provisioned_slots; ++i) {
if (slot_usage[i]) {
continue;
}
begin_addr = slot_span_start.GetNthSlotStart(i, slot_size).value();
end_addr = begin_addr + slot_size;
bool can_discard_free_list_pointer = false;
#if !PA_BUILDFLAG(IS_WIN)
if (i != last_slot) {
begin_addr += sizeof(internal::FreelistEntry);
} else {
can_discard_free_list_pointer = true;
}
#else
begin_addr += sizeof(internal::FreelistEntry);
#endif
uintptr_t rounded_up_begin_addr = RoundUpToSystemPage(begin_addr);
uintptr_t rounded_down_begin_addr = RoundDownToSystemPage(begin_addr);
end_addr = RoundDownToSystemPage(end_addr);
PA_DCHECK(rounded_up_begin_addr <= end_addr);
if (rounded_down_begin_addr < rounded_up_begin_addr && i != 0 &&
!slot_usage[i - 1] && can_discard_free_list_pointer) {
begin_addr = rounded_down_begin_addr;
} else {
begin_addr = rounded_up_begin_addr;
}
if (begin_addr < end_addr) {
size_t partial_slot_bytes = end_addr - begin_addr;
discardable_bytes += partial_slot_bytes;
if (!accounting_only) {
ScopedSyscallTimer timer{root};
DiscardSystemPages(begin_addr, partial_slot_bytes);
}
}
}
return discardable_bytes;
}
PA_NOPROFILE
static void PartitionPurgeBucket(PartitionRoot* root,
internal::PartitionBucket* bucket)
PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(root)) {
if (bucket->active_slot_spans_head !=
internal::SlotSpanMetadata::get_sentinel_slot_span()) {
for (internal::SlotSpanMetadata* slot_span = bucket->active_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(slot_span !=
internal::SlotSpanMetadata::get_sentinel_slot_span());
PartitionPurgeSlotSpan(root, slot_span, false);
}
}
}
static void PartitionDumpSlotSpanStats(PartitionBucketMemoryStats* stats_out,
PartitionRoot* root,
internal::SlotSpanMetadata* slot_span,
bool populate_discardable_bytes)
PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(root)) {
uint16_t bucket_num_slots = slot_span->bucket->get_slots_per_span();
if (slot_span->is_decommitted()) {
++stats_out->num_decommitted_slot_spans;
return;
}
if (populate_discardable_bytes) {
stats_out->discardable_bytes +=
PartitionPurgeSlotSpan(root, slot_span, true);
}
if (slot_span->CanStoreRawSize()) {
stats_out->active_bytes += static_cast<uint32_t>(slot_span->GetRawSize());
} else {
stats_out->active_bytes +=
(slot_span->num_allocated_slots * stats_out->bucket_slot_size);
}
stats_out->active_count += slot_span->num_allocated_slots;
size_t slot_span_bytes_resident = RoundUpToSystemPage(
(bucket_num_slots - slot_span->num_unprovisioned_slots) *
stats_out->bucket_slot_size);
stats_out->resident_bytes += slot_span_bytes_resident;
if (slot_span->is_empty()) {
stats_out->decommittable_bytes += slot_span_bytes_resident;
++stats_out->num_empty_slot_spans;
} else if (slot_span->is_full()) {
++stats_out->num_full_slot_spans;
} else {
PA_DCHECK(slot_span->is_active());
++stats_out->num_active_slot_spans;
}
}
static void PartitionDumpBucketStats(PartitionBucketMemoryStats* stats_out,
PartitionRoot* root,
const internal::PartitionBucket* bucket,
bool populate_discardable_bytes)
PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(root)) {
PA_DCHECK(!bucket->is_direct_mapped());
stats_out->is_valid = false;
if (bucket->active_slot_spans_head ==
internal::SlotSpanMetadata::get_sentinel_slot_span() &&
!bucket->empty_slot_spans_head && !bucket->decommitted_slot_spans_head &&
!bucket->num_full_slot_spans) {
return;
}
PA_UNSAFE_TODO(memset(stats_out, '\0', sizeof(*stats_out)));
stats_out->is_valid = true;
stats_out->is_direct_map = false;
stats_out->num_full_slot_spans =
static_cast<size_t>(bucket->num_full_slot_spans);
stats_out->bucket_slot_size = bucket->slot_size;
uint16_t bucket_num_slots = bucket->get_slots_per_span();
size_t bucket_useful_storage = stats_out->bucket_slot_size * bucket_num_slots;
stats_out->allocated_slot_span_size = bucket->get_bytes_per_span();
stats_out->active_bytes = bucket->num_full_slot_spans * bucket_useful_storage;
stats_out->active_count = bucket->num_full_slot_spans * bucket_num_slots;
stats_out->resident_bytes =
bucket->num_full_slot_spans * stats_out->allocated_slot_span_size;
for (internal::SlotSpanMetadata* slot_span = bucket->empty_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(slot_span->is_empty() || slot_span->is_decommitted());
PartitionDumpSlotSpanStats(stats_out, root, slot_span,
populate_discardable_bytes);
}
for (internal::SlotSpanMetadata* slot_span =
bucket->decommitted_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(slot_span->is_decommitted());
PartitionDumpSlotSpanStats(stats_out, root, slot_span,
populate_discardable_bytes);
}
if (bucket->active_slot_spans_head !=
internal::SlotSpanMetadata::get_sentinel_slot_span()) {
for (internal::SlotSpanMetadata* slot_span = bucket->active_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(slot_span !=
internal::SlotSpanMetadata::get_sentinel_slot_span());
PartitionDumpSlotSpanStats(stats_out, root, slot_span,
populate_discardable_bytes);
}
}
}
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
void DCheckIfManagedByPartitionAllocBRPPool(uintptr_t address) {
PA_DCHECK(IsManagedByPartitionAllocBRPPool(address));
}
#endif
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
void PartitionAllocThreadIsolationInit(ThreadIsolationOption thread_isolation) {
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
ThreadIsolationSettings::settings.enabled = true;
#endif
PartitionAddressSpace::InitThreadIsolatedPool(thread_isolation);
WriteProtectThreadIsolatedGlobals(thread_isolation);
}
#endif
}
[[noreturn]] PA_NOINLINE void PartitionRoot::OutOfMemory(size_t size) {
const size_t virtual_address_space_size =
total_size_of_super_pages_.load(std::memory_order_relaxed) +
total_size_of_direct_mapped_pages_.load(std::memory_order_relaxed);
#if !PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)
const size_t uncommitted_size =
virtual_address_space_size -
total_size_of_committed_pages_.load(std::memory_order_relaxed);
if (uncommitted_size > internal::kReasonableSizeOfUnusedPages) {
internal::PartitionOutOfMemoryWithLotsOfUncommitedPages(size);
}
#if PA_BUILDFLAG(IS_WIN)
BOOL is_wow_64 = FALSE;
IsWow64Process(GetCurrentProcess(), &is_wow_64);
const size_t kReasonableVirtualSize = (is_wow_64 ? 2800 : 1024) * 1024 * 1024;
PA_DEBUG_DATA_ON_STACK("iswow64", static_cast<size_t>(is_wow_64));
#else
constexpr size_t kReasonableVirtualSize =
(1024 + 512) * 1024 * 1024;
#endif
if (virtual_address_space_size > kReasonableVirtualSize) {
internal::PartitionOutOfMemoryWithLargeVirtualSize(
virtual_address_space_size);
}
#endif
PA_DEBUG_DATA_ON_STACK("va_size", virtual_address_space_size);
PA_DEBUG_DATA_ON_STACK("alloc", get_total_size_of_allocated_bytes());
PA_DEBUG_DATA_ON_STACK("commit", get_total_size_of_committed_pages());
PA_DEBUG_DATA_ON_STACK("size", size);
if (internal::g_oom_handling_function) {
(*internal::g_oom_handling_function)(size);
}
OOM_CRASH(size);
}
void PartitionRoot::DecommitEmptySlotSpans() {
ShrinkEmptySlotSpansRing(0);
PA_DCHECK(empty_slot_spans_dirty_bytes_ == 0);
}
void PartitionRoot::DecommitEmptySlotSpansForTesting() {
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
DecommitEmptySlotSpans();
}
void PartitionRoot::DestructForTesting()
PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this)) {
PA_CHECK(!settings_.with_thread_cache);
auto pool_handle = ChoosePool();
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (pool_handle == internal::kThreadIsolatedPoolHandle) {
return;
}
PA_DCHECK(pool_handle < internal::kNumPools);
#else
PA_DCHECK(pool_handle <= internal::kNumPools);
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
std::ptrdiff_t metadata_offset = MetadataOffset();
bool must_decommit_metadata = MetadataOffset() & internal::kSuperPageBaseMask;
#endif
{
auto* curr = first_extent_;
while (curr != nullptr) {
auto* next = curr->next;
uintptr_t super_page = SuperPagesBeginFromExtent(curr);
size_t num_super_pages = curr->number_of_consecutive_super_pages;
size_t size = internal::kSuperPageSize * num_super_pages;
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
internal::AddressPoolManager::GetInstance().MarkUnused(pool_handle,
super_page, size);
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
if (must_decommit_metadata) {
uintptr_t metadata_start = internal::PartitionSuperPageToMetadataPage(
super_page, metadata_offset);
for (size_t index = 0; index < num_super_pages; ++index) {
DecommitAndZeroSystemPages(metadata_start, SystemPageSize(),
PageTag::kPartitionAlloc);
metadata_start += internal::kSuperPageSize;
}
}
#endif internal::AddressPoolManager::GetInstance().UnreserveAndDecommit(
pool_handle, super_page, size);
curr = next;
}
first_extent_ = current_extent_ = nullptr;
}
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
{
auto* curr = direct_map_list_;
while (curr != nullptr) {
auto* next = curr->next_extent;
uintptr_t reservation_start = internal::base::bits::AlignDown(
internal::PartitionMetadataPageToSuperPage(
reinterpret_cast<uintptr_t>(curr), metadata_offset),
internal::kSuperPageAlignment);
size_t reservation_size = curr->reservation_size;
GetReservationOffsetTable().SetNotAllocatedTag(reservation_start,
reservation_size);
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
internal::AddressPoolManager::GetInstance().MarkUnused(
pool_handle, reservation_start, reservation_size);
#endif
if (must_decommit_metadata) {
uintptr_t metadata_start = internal::PartitionSuperPageToMetadataPage(
reservation_start, metadata_offset);
DecommitAndZeroSystemPages(metadata_start, SystemPageSize(),
PageTag::kPartitionAlloc);
}
internal::AddressPoolManager::GetInstance().UnreserveAndDecommit(
pool_handle, reservation_start, reservation_size);
curr = next;
}
direct_map_list_ = nullptr;
}
#endif }
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && \
!PA_BUILDFLAG(HAS_64_BIT_POINTERS)
namespace {
std::atomic<bool> g_reserve_brp_guard_region_called;
void ReserveBackupRefPtrGuardRegionIfNeeded() {
bool expected = false;
if (!g_reserve_brp_guard_region_called.compare_exchange_strong(expected,
true)) {
return;
}
size_t alignment = internal::PageAllocationGranularity();
uintptr_t requested_address;
PA_UNSAFE_TODO(memset(&requested_address, internal::kQuarantinedByte,
sizeof(requested_address)));
requested_address = RoundDownToPageAllocationGranularity(requested_address);
for (size_t i = 0; i < 4; ++i) {
[[maybe_unused]] uintptr_t allocated_address =
AllocPages(requested_address, alignment, alignment,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
requested_address += alignment;
}
}
} #endif
void PartitionRoot::Init(PartitionOptions opts) {
{
#if PA_BUILDFLAG(IS_APPLE)
PA_CHECK((internal::SystemPageSize() == (size_t{1} << 12)) ||
(internal::SystemPageSize() == (size_t{1} << 14)));
#elif PA_BUILDFLAG(IS_LINUX) && PA_BUILDFLAG(PA_ARCH_CPU_ARM64)
PA_CHECK((internal::SystemPageSize() == (size_t{1} << 12)) ||
(internal::SystemPageSize() == (size_t{1} << 14)) ||
(internal::SystemPageSize() == (size_t{1} << 16)));
#endif
::partition_alloc::internal::ScopedGuard guard{lock_};
if (initialized_) {
return;
}
#if PA_BUILDFLAG(HAS_64_BIT_POINTERS)
internal::PartitionAddressSpace::Init();
#endif
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && \
!PA_BUILDFLAG(HAS_64_BIT_POINTERS)
ReserveBackupRefPtrGuardRegionIfNeeded();
#endif
#if PA_BUILDFLAG(HAS_64_BIT_POINTERS)
if (opts.use_configurable_pool == PartitionOptions::kAllowed &&
IsConfigurablePoolAvailable()) {
PA_CHECK(opts.backup_ref_ptr == PartitionOptions::kDisabled);
PA_CHECK(settings_.pool_handle == internal::kNullPoolHandle);
settings_.pool_handle = internal::kConfigurablePoolHandle;
}
#endif settings_.eventually_zero_freed_memory =
opts.eventually_zero_freed_memory == PartitionOptions::kEnabled;
scheduler_loop_quarantine_.Configure(
scheduler_loop_quarantine_root_,
opts.scheduler_loop_quarantine_global_config);
scheduler_loop_quarantine_for_advanced_memory_safety_checks_.Configure(
scheduler_loop_quarantine_root_,
opts.scheduler_loop_quarantine_for_advanced_memory_safety_checks_config);
settings_.scheduler_loop_quarantine_thread_local_config =
opts.scheduler_loop_quarantine_thread_local_config;
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
settings_.memory_tagging_enabled_ =
opts.memory_tagging.enabled == PartitionOptions::kEnabled;
PA_CHECK(!settings_.memory_tagging_enabled_ ||
settings_.pool_handle != internal::kConfigurablePoolHandle);
settings_.use_random_memory_tagging_ =
opts.memory_tagging.random_memory_tagging == PartitionOptions::kEnabled;
settings_.memory_tagging_reporting_mode_ =
opts.memory_tagging.reporting_mode;
#endif
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
settings_.thread_isolation = opts.thread_isolation;
if (opts.thread_isolation.enabled) {
PA_CHECK(opts.backup_ref_ptr == PartitionOptions::kDisabled);
PA_CHECK(settings_.pool_handle == internal::kNullPoolHandle);
settings_.pool_handle = internal::kThreadIsolatedPoolHandle;
internal::PartitionAllocThreadIsolationInit(settings_.thread_isolation);
}
#endif
#if PA_CONFIG(EXTRAS_REQUIRED)
settings_.extras_size = 0;
if (settings_.use_cookie) {
settings_.extras_size += internal::kPartitionCookieSizeAdjustment;
}
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
settings_.brp_enabled_ = opts.backup_ref_ptr == PartitionOptions::kEnabled;
if (opts.backup_ref_ptr == PartitionOptions::kEnabled) {
settings_.in_slot_metadata_size = internal::kInSlotMetadataSizeAdjustment;
settings_.extras_size += internal::kInSlotMetadataSizeAdjustment;
settings_.extras_size += opts.backup_ref_ptr_extra_extras_size;
PA_CHECK(settings_.pool_handle == internal::kNullPoolHandle);
settings_.pool_handle = internal::kBRPPoolHandle;
}
#endif #endif
#if !PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
PA_CHECK(opts.backup_ref_ptr == PartitionOptions::kDisabled);
#endif
if (settings_.pool_handle == internal::kNullPoolHandle) {
settings_.pool_handle = internal::kRegularPoolHandle;
}
#if PA_BUILDFLAG(HAS_64_BIT_POINTERS)
settings_.offset_lookup =
internal::PartitionAddressSpace::GetOffsetLookup(settings_.pool_handle);
#endif settings_.reservation_offset_table =
internal::ReservationOffsetTable::Get(settings_.pool_handle);
PA_UNSAFE_TODO(memset(&sentinel_bucket_, 0, sizeof(sentinel_bucket_)));
sentinel_bucket_.active_slot_spans_head =
internal::SlotSpanMetadata::get_sentinel_slot_span_non_const();
inverted_self_ = ~reinterpret_cast<uintptr_t>(this);
for (size_t bucket_index = 0; bucket_index < BucketIndexLookup::kNumBuckets;
++bucket_index) {
const size_t slot_size = BucketIndexLookup::GetBucketSize(bucket_index);
PA_UNSAFE_TODO(buckets_[bucket_index]).Init(slot_size);
}
#if !PA_CONFIG(THREAD_CACHE_SUPPORTED)
settings_.with_thread_cache = false;
#else
ThreadCache::EnsureThreadSpecificDataInitialized();
settings_.with_thread_cache =
(opts.thread_cache == PartitionOptions::kEnabled);
settings_.thread_cache_index = opts.thread_cache_index;
if (settings_.with_thread_cache) {
PA_CHECK(opts.thread_cache_index < internal::kMaxThreadCacheIndex);
ThreadCache::Init(this);
}
#endif
#if PA_BUILDFLAG(USE_PARTITION_COOKIE)
settings_.use_cookie =
opts.use_cookie_if_supported == PartitionOptions::kEnabled;
#endif
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
internal::PartitionRootEnumerator::Instance().Register(this);
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
settings_.metadata_offset_ =
internal::GetMetadataOffset(settings_.pool_handle);
#endif
settings_.enable_free_with_size =
(opts.free_with_size == PartitionOptions::kEnabled);
settings_.enable_strict_free_size_check =
(opts.strict_free_size_check == PartitionOptions::kEnabled);
initialized_ = true;
}
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
PartitionAllocMallocInitOnce();
#endif
}
PartitionRoot::Settings::Settings() = default;
PartitionRoot::PartitionRoot()
: scheduler_loop_quarantine_root_(*this),
scheduler_loop_quarantine_(this),
scheduler_loop_quarantine_for_advanced_memory_safety_checks_(this) {}
PartitionRoot::PartitionRoot(PartitionOptions opts)
: scheduler_loop_quarantine_root_(*this),
scheduler_loop_quarantine_(this),
scheduler_loop_quarantine_for_advanced_memory_safety_checks_(this) {
Init(opts);
}
PartitionRoot::~PartitionRoot() {
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
PA_CHECK(!settings_.with_thread_cache)
<< "Must not destroy a partition with a thread cache";
#endif
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
if (initialized_) {
internal::PartitionRootEnumerator::Instance().Unregister(this);
}
#endif }
void PartitionRoot::EnableThreadCacheIfSupported() {
#if PA_CONFIG(THREAD_CACHE_SUPPORTED)
::partition_alloc::internal::ScopedGuard guard{lock_};
PA_CHECK(!settings_.with_thread_cache);
{
::partition_alloc::internal::ScopedGuard construction_guard{
thread_cache_construction_lock_};
ThreadCache::Init(this);
ThreadCache::Create(this, settings_.thread_cache_index);
}
settings_.with_thread_cache = true;
#endif }
bool PartitionRoot::TryReallocInPlaceForDirectMap(
internal::SlotSpanMetadata* slot_span,
size_t requested_size) {
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
PA_DCHECK(slot_span->bucket->is_direct_mapped());
PA_DCHECK(GetReservationOffsetTable().IsManagedByDirectMap(
internal::PartitionMetadataPageToSuperPage(
reinterpret_cast<uintptr_t>(slot_span), MetadataOffset())));
#endif
size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
auto* extent = DirectMapExtent::FromSlotSpanMetadata(slot_span);
size_t current_reservation_size = extent->reservation_size;
size_t new_reservation_size = GetDirectMapReservationSize(raw_size);
if (new_reservation_size > current_reservation_size) {
return false;
}
if ((new_reservation_size >> internal::SystemPageShift()) * 5 <
(current_reservation_size >> internal::SystemPageShift()) * 4) {
return false;
}
size_t new_slot_size = GetDirectMapSlotSize(raw_size);
if (new_slot_size < internal::kMinDirectMappedDownsize) {
return false;
}
size_t current_slot_size = slot_span->bucket->slot_size;
size_t current_usable_size = GetSlotUsableSize(slot_span);
internal::SlotSpanStart slot_span_start =
internal::SlotSpanMetadata::ToSlotSpanStart(slot_span, this);
size_t available_reservation_size =
current_reservation_size - extent->padding_for_alignment -
PartitionRoot::GetDirectMapMetadataAndGuardPagesSize();
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
uintptr_t reservation_start =
slot_span_start.value() & internal::kSuperPageBaseMask;
PA_DCHECK(GetReservationOffsetTable().IsReservationStart(reservation_start));
PA_DCHECK(slot_span_start.value() + available_reservation_size ==
reservation_start + current_reservation_size -
GetDirectMapMetadataAndGuardPagesSize() +
internal::PartitionPageSize());
#endif
PA_DCHECK(new_slot_size > internal::kMaxMemoryTaggingSize);
if (new_slot_size == current_slot_size) {
} else if (new_slot_size < current_slot_size) {
size_t decommit_size = current_slot_size - new_slot_size;
DecommitSystemPagesForData(slot_span_start.value() + new_slot_size,
decommit_size,
PageAccessibilityDisposition::kRequireUpdate);
} else if (new_slot_size <= available_reservation_size) {
size_t recommit_slot_size_growth = new_slot_size - current_slot_size;
RecommitSystemPagesForData(
slot_span_start.value() + current_slot_size, recommit_slot_size_growth,
PageAccessibilityDisposition::kRequireUpdate, false);
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
PA_UNSAFE_TODO(memset(
reinterpret_cast<void*>(slot_span_start.value() + current_slot_size),
internal::kUninitializedByte, recommit_slot_size_growth));
#endif
} else {
return false;
}
DecreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span),
slot_span->bucket->slot_size);
slot_span->SetRawSize(raw_size);
slot_span->bucket->slot_size = new_slot_size;
IncreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span),
slot_span->bucket->slot_size, raw_size);
auto* thread_cache = GetOrCreateThreadCache();
if (ThreadCache::IsValid(thread_cache)) {
thread_cache->RecordDeallocation(current_usable_size);
thread_cache->RecordAllocation(GetSlotUsableSize(slot_span));
}
#if PA_BUILDFLAG(USE_PARTITION_COOKIE)
if (settings_.use_cookie) {
auto* object = slot_span_start.AsSlotStart().Tag().ToObject();
internal::PartitionCookieWriteValue(
PA_UNSAFE_TODO(object + GetSlotUsableSize(slot_span)));
}
#endif
return true;
}
bool PartitionRoot::TryReallocInPlaceForNormalBuckets(
void* object,
internal::SlotSpanMetadata* slot_span,
size_t new_size) {
auto slot_start = internal::SlotStart::Unchecked(object).Untag();
PA_DCHECK(
GetReservationOffsetTable().IsManagedByNormalBuckets(slot_start.value()));
if (AllocationCapacityFromRequestedSize(new_size) !=
AllocationCapacityFromSlotStart(slot_start)) {
return false;
}
size_t current_usable_size = GetSlotUsableSize(slot_span);
if (slot_span->CanStoreRawSize()) {
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && PA_BUILDFLAG(DCHECKS_ARE_ON)
internal::InSlotMetadata* old_ref_count = nullptr;
if (brp_enabled()) [[likely]] {
old_ref_count = InSlotMetadataPointerFromSlotStartAndSize(
internal::UntaggedSlotStart(slot_start),
slot_span->bucket->slot_size);
}
#endif size_t new_raw_size = AdjustSizeForExtrasAdd(new_size);
slot_span->SetRawSize(new_raw_size);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && PA_BUILDFLAG(DCHECKS_ARE_ON)
if (brp_enabled()) [[likely]] {
internal::InSlotMetadata* new_ref_count =
InSlotMetadataPointerFromSlotStartAndSize(
internal::UntaggedSlotStart(slot_start),
slot_span->bucket->slot_size);
PA_DCHECK(new_ref_count == old_ref_count);
}
#endif #if PA_BUILDFLAG(USE_PARTITION_COOKIE)
if (settings_.use_cookie) {
internal::PartitionCookieWriteValue(PA_UNSAFE_TODO(
static_cast<unsigned char*>(object) + GetSlotUsableSize(slot_span)));
}
#endif }
ThreadCache* thread_cache = GetOrCreateThreadCache();
if (ThreadCache::IsValid(thread_cache)) [[likely]] {
thread_cache->RecordDeallocation(current_usable_size);
thread_cache->RecordAllocation(GetSlotUsableSize(slot_span));
}
return object;
}
void PartitionRoot::PurgeMemory(int flags, PurgeState& purge_state) {
uint16_t& purge_generation = purge_state.generation;
uint16_t& purge_next_bucket_index = purge_state.next_bucket_index;
auto start = now_maybe_overridden_for_testing_();
{
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
if (flags & PurgeFlags::kDecommitEmptySlotSpans) {
DecommitEmptySlotSpans();
if (flags & PurgeFlags::kLimitDuration &&
(now_maybe_overridden_for_testing_() - start > kMaxPurgeDuration)) {
return;
}
}
}
if (flags & PurgeFlags::kDiscardUnusedSystemPages) {
size_t min_bucket_size_to_purge =
internal::MinConservativePurgeableSlotSize();
if (!(flags & PurgeFlags::kLimitDuration) || !purge_generation) {
min_bucket_size_to_purge = internal::MinPurgeableSlotSize();
}
for (unsigned int bucket_index = purge_next_bucket_index;
bucket_index < BucketIndexLookup::kNumBuckets; bucket_index++) {
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
Bucket& bucket = PA_UNSAFE_TODO(buckets_[bucket_index]);
if (bucket.slot_size >= min_bucket_size_to_purge) {
internal::PartitionPurgeBucket(this, &bucket);
} else {
if (sort_smaller_slot_span_free_lists_) {
bucket.SortSmallerSlotSpanFreeLists(this);
}
}
bucket.MaintainActiveList();
if (sort_active_slot_spans_) {
bucket.SortActiveSlotSpans();
}
if (flags & PurgeFlags::kLimitDuration &&
(now_maybe_overridden_for_testing_() - start > kMaxPurgeDuration)) {
purge_next_bucket_index =
(bucket_index + 1) % BucketIndexLookup::kNumBuckets;
return;
}
}
purge_next_bucket_index = 0;
purge_generation = (purge_generation + 1) % 16;
}
}
void PartitionRoot::ShrinkEmptySlotSpansRing(size_t limit) {
int16_t index = global_empty_slot_span_ring_index_;
int16_t starting_index = index;
while (empty_slot_spans_dirty_bytes_ > limit) {
internal::SlotSpanMetadata* slot_span =
PA_UNSAFE_TODO(global_empty_slot_span_ring_[index]);
if (slot_span) {
slot_span->DecommitIfPossible(this);
PA_UNSAFE_TODO(PA_DCHECK(!global_empty_slot_span_ring_[index]));
}
index += 1;
if (index == internal::kMaxEmptySlotSpanRingSize) {
index = 0;
}
if (index == starting_index) {
PA_DCHECK(empty_slot_spans_dirty_bytes_ == 0);
break;
}
}
}
void PartitionRoot::DumpStats(const char* partition_name,
bool is_light_dump,
bool populate_discardable_bytes,
PartitionStatsDumper* dumper) {
static const size_t kMaxReportableDirectMaps = 4096;
std::unique_ptr<uint32_t[]> direct_map_lengths;
if (!is_light_dump) {
direct_map_lengths =
std::unique_ptr<uint32_t[]>(new uint32_t[kMaxReportableDirectMaps]);
}
PartitionBucketMemoryStats bucket_stats[BucketIndexLookup::kNumBuckets];
size_t num_direct_mapped_allocations = 0;
PartitionMemoryStats stats = {};
stats.syscall_count = syscall_count_.load(std::memory_order_relaxed);
stats.syscall_total_time_ns =
syscall_total_time_ns_.load(std::memory_order_relaxed);
{
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
PA_DCHECK(total_size_of_allocated_bytes_ <= max_size_of_allocated_bytes_);
stats.total_mmapped_bytes =
total_size_of_super_pages_.load(std::memory_order_relaxed) +
total_size_of_direct_mapped_pages_.load(std::memory_order_relaxed);
stats.total_committed_bytes =
total_size_of_committed_pages_.load(std::memory_order_relaxed);
stats.max_committed_bytes =
max_size_of_committed_pages_.load(std::memory_order_relaxed);
stats.total_allocated_bytes = total_size_of_allocated_bytes_;
stats.max_allocated_bytes = max_size_of_allocated_bytes_;
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
stats.total_brp_quarantined_bytes =
total_size_of_brp_quarantined_bytes.load(std::memory_order_relaxed);
stats.total_brp_quarantined_count =
total_count_of_brp_quarantined_slots_.load(std::memory_order_relaxed);
stats.cumulative_brp_quarantined_bytes =
cumulative_size_of_brp_quarantined_bytes_.load(
std::memory_order_relaxed);
stats.cumulative_brp_quarantined_count =
cumulative_count_of_brp_quarantined_slots_.load(
std::memory_order_relaxed);
#endif
size_t direct_mapped_allocations_total_size = 0;
for (size_t i = 0; i < BucketIndexLookup::kNumBuckets; ++i) {
const Bucket* bucket = &bucket_at(i);
if (!bucket->is_valid()) {
PA_UNSAFE_TODO(bucket_stats[i]).is_valid = false;
} else {
internal::PartitionDumpBucketStats(&PA_UNSAFE_TODO(bucket_stats[i]),
this, bucket,
populate_discardable_bytes);
}
if (PA_UNSAFE_TODO(bucket_stats[i]).is_valid) {
stats.total_resident_bytes +=
PA_UNSAFE_TODO(bucket_stats[i]).resident_bytes;
stats.total_active_bytes +=
PA_UNSAFE_TODO(bucket_stats[i]).active_bytes;
stats.total_active_count +=
PA_UNSAFE_TODO(bucket_stats[i]).active_count;
stats.total_decommittable_bytes +=
PA_UNSAFE_TODO(bucket_stats[i]).decommittable_bytes;
stats.total_discardable_bytes +=
PA_UNSAFE_TODO(bucket_stats[i]).discardable_bytes;
}
}
for (const DirectMapExtent* extent = direct_map_list_;
extent && num_direct_mapped_allocations < kMaxReportableDirectMaps;
extent = extent->next_extent, ++num_direct_mapped_allocations) {
PA_DCHECK(!extent->next_extent ||
extent->next_extent->prev_extent == extent);
size_t slot_size = extent->bucket->slot_size;
direct_mapped_allocations_total_size += slot_size;
if (is_light_dump) {
continue;
}
PA_UNSAFE_TODO(direct_map_lengths[num_direct_mapped_allocations]) =
slot_size;
}
stats.total_resident_bytes += direct_mapped_allocations_total_size;
stats.total_active_bytes += direct_mapped_allocations_total_size;
stats.total_active_count += num_direct_mapped_allocations;
stats.has_thread_cache = settings_.with_thread_cache;
if (stats.has_thread_cache) {
ThreadCacheRegistry::Instance().DumpStats(
true, &stats.current_thread_cache_stats,
settings_.thread_cache_index);
ThreadCacheRegistry::Instance().DumpStats(
false, &stats.all_thread_caches_stats, settings_.thread_cache_index);
}
stats.has_scheduler_loop_quarantine =
settings_.scheduler_loop_quarantine_thread_local_config
.enable_quarantine;
if (stats.has_scheduler_loop_quarantine) {
PA_UNSAFE_TODO(memset(
reinterpret_cast<void*>(&stats.scheduler_loop_quarantine_stats_total),
0, sizeof(SchedulerLoopQuarantineStats)));
scheduler_loop_quarantine_root_.AccumulateStats(
stats.scheduler_loop_quarantine_stats_total);
}
}
if (!is_light_dump) {
for (auto& stat : bucket_stats) {
if (stat.is_valid) {
dumper->PartitionsDumpBucketStats(partition_name, &stat);
}
}
for (size_t i = 0; i < num_direct_mapped_allocations; ++i) {
uint32_t size = PA_UNSAFE_TODO(direct_map_lengths[i]);
PartitionBucketMemoryStats mapped_stats = {};
mapped_stats.is_valid = true;
mapped_stats.is_direct_map = true;
mapped_stats.num_full_slot_spans = 1;
mapped_stats.allocated_slot_span_size = size;
mapped_stats.bucket_slot_size = size;
mapped_stats.active_bytes = size;
mapped_stats.active_count = 1;
mapped_stats.resident_bytes = size;
dumper->PartitionsDumpBucketStats(partition_name, &mapped_stats);
}
}
dumper->PartitionDumpTotals(partition_name, &stats);
}
void PartitionRoot::DeleteForTesting(PartitionRoot* partition_root) {
if (partition_root->settings_.with_thread_cache) {
ThreadCache::SwapForTesting(nullptr,
partition_root->settings_.thread_cache_index);
partition_root->settings_.with_thread_cache = false;
}
{
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(partition_root)};
partition_root->DestructForTesting(); }
delete partition_root;
}
void PartitionRoot::ResetForTesting(bool allow_leaks) {
if (settings_.with_thread_cache) {
ThreadCache::SwapForTesting(nullptr, settings_.thread_cache_index);
settings_.with_thread_cache = false;
}
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
if (!allow_leaks) {
unsigned num_allocated_slots = 0;
for (Bucket& bucket : buckets_) {
if (bucket.active_slot_spans_head !=
internal::SlotSpanMetadata::get_sentinel_slot_span()) {
for (const internal::SlotSpanMetadata* slot_span =
bucket.active_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
num_allocated_slots += slot_span->num_allocated_slots;
}
}
if (bucket.num_full_slot_spans) {
num_allocated_slots +=
bucket.num_full_slot_spans * bucket.get_slots_per_span();
}
}
PA_DCHECK(num_allocated_slots == 0);
PA_DCHECK(!direct_map_list_);
}
#endif
DestructForTesting();
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
if (initialized_) {
internal::PartitionRootEnumerator::Instance().Unregister(this);
}
#endif
for (Bucket& bucket : buckets_) {
bucket.active_slot_spans_head =
internal::SlotSpanMetadata::get_sentinel_slot_span_non_const();
bucket.empty_slot_spans_head = nullptr;
bucket.decommitted_slot_spans_head = nullptr;
bucket.num_full_slot_spans = 0;
}
next_super_page_ = 0;
next_partition_page_ = 0;
next_partition_page_end_ = 0;
current_extent_ = nullptr;
first_extent_ = nullptr;
direct_map_list_ = nullptr;
for (auto*& entity : global_empty_slot_span_ring_) {
entity = nullptr;
}
global_empty_slot_span_ring_index_ = 0;
global_empty_slot_span_ring_size_ = internal::kDefaultEmptySlotSpanRingSize;
initialized_ = false;
}
void PartitionRoot::ResetBookkeepingForTesting() {
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
max_size_of_allocated_bytes_.store(
total_size_of_allocated_bytes_.load(std::memory_order_relaxed),
std::memory_order_relaxed);
max_size_of_committed_pages_.store(
total_size_of_committed_pages_.load(std::memory_order_relaxed),
std::memory_order_relaxed);
}
void PartitionRoot::SetGlobalEmptySlotSpanRingIndexForTesting(int16_t index) {
::partition_alloc::internal::ScopedGuard guard{
internal::PartitionRootLock(this)};
global_empty_slot_span_ring_index_ = index;
}
ThreadCache* PartitionRoot::MaybeInitThreadCache() {
if (ThreadCache::IsTombstone()) {
return nullptr;
}
if (!thread_cache_construction_lock_.TryAcquire()) {
return nullptr;
}
auto* tcache = ThreadCache::Create(this, settings_.thread_cache_index);
thread_cache_construction_lock_.Release();
return tcache;
}
ThreadCache* PartitionRoot::ForceInitThreadCache() {
if (ThreadCache::IsTombstone()) {
return nullptr;
}
::partition_alloc::internal::ScopedGuard construction_guard{
thread_cache_construction_lock_};
auto* tcache = ThreadCache::Create(this, settings_.thread_cache_index);
return tcache;
}
void PartitionRoot::SetStraightenLargerSlotSpanFreeListsMode(
StraightenLargerSlotSpanFreeListsMode new_value) {
straighten_larger_slot_span_free_lists_ = new_value;
}
void PartitionRoot::SetSortSmallerSlotSpanFreeListsEnabled(bool new_value) {
sort_smaller_slot_span_free_lists_ = new_value;
}
void PartitionRoot::SetSortActiveSlotSpansEnabled(bool new_value) {
sort_active_slot_spans_ = new_value;
}
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
PA_NOINLINE void PartitionRoot::QuarantineForBrp(
const internal::SlotSpanMetadata* slot_span,
internal::SlotStart slot_start) {
auto usable_size = GetSlotUsableSize(slot_span);
auto hook = PartitionAllocHooks::GetQuarantineOverrideHook();
if (hook) [[unlikely]] {
hook(slot_start.ToObject(), usable_size);
} else {
internal::SecureMemset(slot_start.ToObject(), internal::kQuarantinedByte,
usable_size);
}
}
#endif
void PartitionRoot::CheckMetadataIntegrity(const void* ptr) {
uintptr_t address = internal::ObjectInnerPtr2Addr(ptr);
if (!IsManagedByPartitionAlloc(address)) {
return;
}
const internal::ReservationOffsetTable& reservation_offset =
internal::ReservationOffsetTable::Get(address);
if (reservation_offset.IsManagedByDirectMap(address)) {
return;
}
PA_CHECK(reservation_offset.IsManagedByNormalBuckets(address));
auto* root = FromAddrInFirstSuperpage(address);
SlotSpanMetadata* slot_span = SlotSpanMetadata::FromAddr(address, root);
PA_CHECK(PartitionRoot::FromSlotSpanMetadata(slot_span) == root);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) || \
PA_BUILDFLAG(USE_PARTITION_COOKIE)
internal::SlotSpanStart slot_span_start =
SlotSpanMetadata::ToSlotSpanStart(slot_span, root);
size_t offset_in_slot_span = slot_span_start.offset(address);
auto* bucket = slot_span->bucket;
internal::UntaggedSlotStart untagged_slot_start =
slot_span_start.GetNthSlotStart(
bucket->GetSlotNumber(offset_in_slot_span), bucket->slot_size);
#endif
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (root->brp_enabled()) {
auto* in_slot_metadata = InSlotMetadataPointerFromSlotStartAndSize(
untagged_slot_start, slot_span->bucket->slot_size);
in_slot_metadata->EnsureAlive(untagged_slot_start, slot_span);
}
#endif
#if PA_BUILDFLAG(USE_PARTITION_COOKIE)
if (root->settings_.use_cookie) {
const size_t usable_size = root->GetSlotUsableSize(slot_span);
uintptr_t cookie_address = untagged_slot_start.value() + usable_size;
internal::PartitionCookieCheckValue(
static_cast<const unsigned char*>(internal::TagAddr(cookie_address)),
usable_size);
}
#endif }
#define EXPORT_TEMPLATE \
template PA_EXPORT_TEMPLATE_DEFINE(PA_COMPONENT_EXPORT(PARTITION_ALLOC))
EXPORT_TEMPLATE void* PartitionRoot::Alloc<AllocFlags::kNone>(size_t,
const char*);
EXPORT_TEMPLATE void* PartitionRoot::Alloc<AllocFlags::kReturnNull>(
size_t,
const char*);
EXPORT_TEMPLATE void*
PartitionRoot::Realloc<AllocFlags::kNone, FreeFlags::kNone>(void*,
size_t,
const char*);
EXPORT_TEMPLATE void*
PartitionRoot::Realloc<AllocFlags::kReturnNull, FreeFlags::kNone>(void*,
size_t,
const char*);
EXPORT_TEMPLATE void* PartitionRoot::AlignedAlloc<AllocFlags::kNone>(size_t,
size_t);
#undef EXPORT_TEMPLATE
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
#endif
static_assert(offsetof(PartitionRoot, sentinel_bucket_) ==
offsetof(PartitionRoot, buckets_) +
BucketIndexLookup::kNumBuckets *
sizeof(PartitionRoot::Bucket),
"sentinel_bucket_ must be just after the regular buckets_.");
static_assert(
offsetof(PartitionRoot, lock_) >= internal::kPartitionCachelineSize,
"The lock should not be on the same cacheline as the read-mostly flags");
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}