blob: 782c827f3c09a6952f1d67e7e5cf49fe626c36df [file] [log] [blame]
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/allocator/partition_allocator/partition_root.h"
#include "base/allocator/partition_allocator/oom.h"
#include "base/allocator/partition_allocator/partition_alloc_check.h"
#include "base/allocator/partition_allocator/partition_bucket.h"
#include "base/allocator/partition_allocator/partition_cookie.h"
#include "base/allocator/partition_allocator/partition_oom.h"
#include "base/allocator/partition_allocator/pcscan.h"
#include "build/build_config.h"
namespace base {
namespace internal {
template <bool thread_safe>
static size_t PartitionPurgeSlotSpan(
internal::SlotSpanMetadata<thread_safe>* slot_span,
bool discard) {
const internal::PartitionBucket<thread_safe>* bucket = slot_span->bucket;
size_t slot_size = bucket->slot_size;
if (slot_size < SystemPageSize() || !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 used_bytes =
static_cast<uint32_t>(RoundUpToSystemPage(slot_span->GetRawSize()));
discardable_bytes = bucket->slot_size - used_bytes;
if (discardable_bytes && discard) {
char* ptr = reinterpret_cast<char*>(
internal::SlotSpanMetadata<thread_safe>::ToPointer(slot_span));
ptr += used_bytes;
DiscardSystemPages(ptr, discardable_bytes);
}
return discardable_bytes;
}
#if defined(PAGE_ALLOCATOR_CONSTANTS_ARE_CONSTEXPR)
constexpr size_t kMaxSlotCount =
(PartitionPageSize() * kMaxPartitionPagesPerSlotSpan) / SystemPageSize();
#elif defined(OS_APPLE)
// It's better for slot_usage to be stack-allocated and fixed-size, which
// demands that its size be constexpr. On OS_APPLE, PartitionPageSize() is
// always SystemPageSize() << 2, so regardless of what the run time page size
// is, kMaxSlotCount can always be simplified to this expression.
constexpr size_t kMaxSlotCount = 4 * kMaxPartitionPagesPerSlotSpan;
PA_CHECK(kMaxSlotCount ==
(PartitionPageSize() * kMaxPartitionPagesPerSlotSpan) /
SystemPageSize());
#endif
PA_DCHECK(bucket_num_slots <= kMaxSlotCount);
PA_DCHECK(slot_span->num_unprovisioned_slots < bucket_num_slots);
size_t num_slots = bucket_num_slots - slot_span->num_unprovisioned_slots;
char slot_usage[kMaxSlotCount];
#if !defined(OS_WIN)
// The last freelist entry should not be discarded when using OS_WIN.
// DiscardVirtualMemory makes the contents of discarded memory undefined.
size_t last_slot = static_cast<size_t>(-1);
#endif
memset(slot_usage, 1, num_slots);
char* ptr = reinterpret_cast<char*>(
internal::SlotSpanMetadata<thread_safe>::ToPointer(slot_span));
// First, walk the freelist for this slot span and make a bitmap of which
// slots are not in use.
for (internal::PartitionFreelistEntry* entry = slot_span->freelist_head;
entry;
/**/) {
size_t slot_index = (reinterpret_cast<char*>(entry) - ptr) / slot_size;
PA_DCHECK(slot_index < num_slots);
slot_usage[slot_index] = 0;
entry = entry->GetNext();
#if !defined(OS_WIN)
// If we have a slot where the masked freelist entry is 0, we can actually
// discard that freelist entry because touching a discarded page is
// guaranteed to return original content or 0. (Note that this optimization
// won't fire on big-endian machines because the masking function is
// negation.)
if (!internal::PartitionFreelistEntry::Encode(entry))
last_slot = slot_index;
#endif
}
// If the slot(s) at the end of the slot span are not in used, we can truncate
// them entirely and rewrite the freelist.
size_t truncated_slots = 0;
while (!slot_usage[num_slots - 1]) {
truncated_slots++;
num_slots--;
PA_DCHECK(num_slots);
}
// First, do the work of calculating the discardable bytes. Don't actually
// discard anything unless the discard flag was passed in.
if (truncated_slots) {
size_t unprovisioned_bytes = 0;
char* begin_ptr = ptr + (num_slots * slot_size);
char* end_ptr = begin_ptr + (slot_size * truncated_slots);
begin_ptr = reinterpret_cast<char*>(
RoundUpToSystemPage(reinterpret_cast<size_t>(begin_ptr)));
// We round the end pointer here up and not down because we're at the end of
// a slot span, so we "own" all the way up the page boundary.
end_ptr = reinterpret_cast<char*>(
RoundUpToSystemPage(reinterpret_cast<size_t>(end_ptr)));
PA_DCHECK(end_ptr <= ptr + bucket->get_bytes_per_span());
if (begin_ptr < end_ptr) {
unprovisioned_bytes = end_ptr - begin_ptr;
discardable_bytes += unprovisioned_bytes;
}
if (unprovisioned_bytes && discard) {
PA_DCHECK(truncated_slots > 0);
size_t num_new_entries = 0;
slot_span->num_unprovisioned_slots +=
static_cast<uint16_t>(truncated_slots);
// Rewrite the freelist.
internal::PartitionFreelistEntry* head = nullptr;
internal::PartitionFreelistEntry* back = head;
for (size_t slot_index = 0; slot_index < num_slots; ++slot_index) {
if (slot_usage[slot_index])
continue;
auto* entry = reinterpret_cast<internal::PartitionFreelistEntry*>(
ptr + (slot_size * slot_index));
if (!head) {
head = entry;
back = entry;
} else {
back->SetNext(entry);
back = entry;
}
num_new_entries++;
#if !defined(OS_WIN)
last_slot = slot_index;
#endif
}
slot_span->SetFreelistHead(head);
if (back)
back->SetNext(nullptr);
PA_DCHECK(num_new_entries == num_slots - slot_span->num_allocated_slots);
// Discard the memory.
DiscardSystemPages(begin_ptr, unprovisioned_bytes);
}
}
// Next, walk the slots and for any not in use, consider where the system page
// boundaries occur. We can release any system pages back to the system as
// long as we don't interfere with a freelist pointer or an adjacent slot.
for (size_t i = 0; i < num_slots; ++i) {
if (slot_usage[i])
continue;
// The first address we can safely discard is just after the freelist
// pointer. There's one quirk: if the freelist pointer is actually nullptr,
// we can discard that pointer value too.
char* begin_ptr = ptr + (i * slot_size);
char* end_ptr = begin_ptr + slot_size;
#if !defined(OS_WIN)
if (i != last_slot)
begin_ptr += sizeof(internal::PartitionFreelistEntry);
#else
begin_ptr += sizeof(internal::PartitionFreelistEntry);
#endif
begin_ptr = reinterpret_cast<char*>(
RoundUpToSystemPage(reinterpret_cast<size_t>(begin_ptr)));
end_ptr = reinterpret_cast<char*>(
RoundDownToSystemPage(reinterpret_cast<size_t>(end_ptr)));
if (begin_ptr < end_ptr) {
size_t partial_slot_bytes = end_ptr - begin_ptr;
discardable_bytes += partial_slot_bytes;
if (discard)
DiscardSystemPages(begin_ptr, partial_slot_bytes);
}
}
return discardable_bytes;
}
template <bool thread_safe>
static void PartitionPurgeBucket(
internal::PartitionBucket<thread_safe>* bucket) {
if (bucket->active_slot_spans_head !=
internal::SlotSpanMetadata<thread_safe>::get_sentinel_slot_span()) {
for (internal::SlotSpanMetadata<thread_safe>* slot_span =
bucket->active_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(
slot_span !=
internal::SlotSpanMetadata<thread_safe>::get_sentinel_slot_span());
PartitionPurgeSlotSpan(slot_span, true);
}
}
}
template <bool thread_safe>
static void PartitionDumpSlotSpanStats(
PartitionBucketMemoryStats* stats_out,
internal::SlotSpanMetadata<thread_safe>* slot_span) {
uint16_t bucket_num_slots = slot_span->bucket->get_slots_per_span();
if (slot_span->is_decommitted()) {
++stats_out->num_decommitted_slot_spans;
return;
}
stats_out->discardable_bytes += PartitionPurgeSlotSpan(slot_span, false);
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);
}
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;
}
}
template <bool thread_safe>
static void PartitionDumpBucketStats(
PartitionBucketMemoryStats* stats_out,
const internal::PartitionBucket<thread_safe>* bucket) {
PA_DCHECK(!bucket->is_direct_mapped());
stats_out->is_valid = false;
// If the active slot span list is empty (==
// internal::SlotSpanMetadata::get_sentinel_slot_span()), the bucket might
// still need to be reported if it has a list of empty, decommitted or full
// slot spans.
if (bucket->active_slot_spans_head ==
internal::SlotSpanMetadata<thread_safe>::get_sentinel_slot_span() &&
!bucket->empty_slot_spans_head && !bucket->decommitted_slot_spans_head &&
!bucket->num_full_slot_spans)
return;
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->resident_bytes =
bucket->num_full_slot_spans * stats_out->allocated_slot_span_size;
for (internal::SlotSpanMetadata<thread_safe>* 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, slot_span);
}
for (internal::SlotSpanMetadata<thread_safe>* 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, slot_span);
}
if (bucket->active_slot_spans_head !=
internal::SlotSpanMetadata<thread_safe>::get_sentinel_slot_span()) {
for (internal::SlotSpanMetadata<thread_safe>* slot_span =
bucket->active_slot_spans_head;
slot_span; slot_span = slot_span->next_slot_span) {
PA_DCHECK(
slot_span !=
internal::SlotSpanMetadata<thread_safe>::get_sentinel_slot_span());
PartitionDumpSlotSpanStats(stats_out, slot_span);
}
}
}
template <bool thread_safe>
void InitBucketIndexLookup(PartitionRoot<thread_safe>* root) {
uint16_t* bucket_index_ptr =
&PartitionRoot<thread_safe>::bucket_index_lookup[0];
uint16_t bucket_index = 0;
const uint16_t sentinel_bucket_index = kNumBuckets;
for (uint16_t order = 0; order <= kBitsPerSizeT; ++order) {
for (uint16_t j = 0; j < kNumBucketsPerOrder; ++j) {
if (order < kMinBucketedOrder) {
// Use the bucket of the finest granularity for malloc(0) etc.
*bucket_index_ptr++ = 0;
} else if (order > kMaxBucketedOrder) {
*bucket_index_ptr++ = sentinel_bucket_index;
} else {
uint16_t valid_bucket_index = bucket_index;
while (root->buckets[valid_bucket_index].slot_size % kSmallestBucket)
valid_bucket_index++;
*bucket_index_ptr++ = valid_bucket_index;
bucket_index++;
}
}
}
PA_DCHECK(bucket_index == kNumBuckets);
PA_DCHECK(bucket_index_ptr ==
PartitionRoot<thread_safe>::bucket_index_lookup +
((kBitsPerSizeT + 1) * kNumBucketsPerOrder));
// And there's one last bucket lookup that will be hit for e.g. malloc(-1),
// which tries to overflow to a non-existent order.
*bucket_index_ptr = sentinel_bucket_index;
}
} // namespace internal
// TODO(lizeb): Consider making this constexpr. Without C++17 std::array, this
// requires some template magic, only do it if it helps performance.
template <bool thread_safe>
uint16_t PartitionRoot<thread_safe>::bucket_index_lookup[((kBitsPerSizeT + 1) *
kNumBucketsPerOrder) +
1];
template <bool thread_safe>
NOINLINE void PartitionRoot<thread_safe>::OutOfMemory(size_t size) {
#if !defined(ARCH_CPU_64_BITS)
// Check whether this OOM is due to a lot of super pages that are allocated
// but not committed, probably due to http://crbug.com/421387.
//
if (total_size_of_super_pages.load(std::memory_order_relaxed) +
total_size_of_direct_mapped_pages.load(std::memory_order_relaxed) -
total_size_of_committed_pages.load(std::memory_order_relaxed) >
kReasonableSizeOfUnusedPages) {
internal::PartitionOutOfMemoryWithLotsOfUncommitedPages(size);
}
#endif
if (internal::g_oom_handling_function)
(*internal::g_oom_handling_function)(size);
OOM_CRASH(size);
}
template <bool thread_safe>
void PartitionRoot<thread_safe>::DecommitEmptySlotSpans() {
for (SlotSpan*& slot_span : global_empty_slot_span_ring) {
if (slot_span)
slot_span->DecommitIfPossible(this);
slot_span = nullptr;
}
}
template <bool thread_safe>
void PartitionRoot<thread_safe>::Init(PartitionOptions opts) {
ScopedGuard guard{lock_};
if (initialized)
return;
#if defined(PA_HAS_64_BITS_POINTERS)
// Reserve address space for partition alloc.
if (features::IsPartitionAllocGigaCageEnabled())
internal::PartitionAddressSpace::Init();
#endif
// If alignment needs to be enforced, disallow adding cookies and/or tags at
// the beginning of the slot.
allow_extras = (opts.alignment != PartitionOptions::Alignment::kAlignedAlloc);
size_t size = 0, offset = 0;
if (allow_extras) {
size += internal::kPartitionTagSizeAdjustment;
size += internal::kPartitionCookieSizeAdjustment;
size += internal::kPartitionRefCountSizeAdjustment;
offset += internal::kPartitionTagOffsetAdjustment;
offset += internal::kPartitionCookieOffsetAdjustment;
offset += internal::kPartitionRefCountOffsetAdjustment;
}
extras_size = static_cast<uint32_t>(size);
extras_offset = static_cast<uint32_t>(offset);
scannable = (opts.pcscan != PartitionOptions::PCScan::kAlwaysDisabled);
// Concurrent freeing in PCScan can only safely work on thread-safe
// partitions.
if (thread_safe &&
opts.pcscan == PartitionOptions::PCScan::kForcedEnabledForTesting)
pcscan.emplace(this);
// We mark the sentinel slot span as free to make sure it is skipped by our
// logic to find a new active slot span.
memset(&sentinel_bucket, 0, sizeof(sentinel_bucket));
sentinel_bucket.active_slot_spans_head = SlotSpan::get_sentinel_slot_span();
// This is a "magic" value so we can test if a root pointer is valid.
inverted_self = ~reinterpret_cast<uintptr_t>(this);
// Set up the actual usable buckets first.
// Note that typical values (i.e. min allocation size of 8) will result in
// pseudo buckets (size==9 etc. or more generally, size is not a multiple
// of the smallest allocation granularity).
// We avoid them in the bucket lookup map, but we tolerate them to keep the
// code simpler and the structures more generic.
size_t i, j;
size_t current_size = kSmallestBucket;
size_t current_increment = kSmallestBucket >> kNumBucketsPerOrderBits;
Bucket* bucket = &buckets[0];
for (i = 0; i < kNumBucketedOrders; ++i) {
for (j = 0; j < kNumBucketsPerOrder; ++j) {
bucket->Init(current_size);
// Disable pseudo buckets so that touching them faults.
if (current_size % kSmallestBucket)
bucket->active_slot_spans_head = nullptr;
current_size += current_increment;
++bucket;
}
current_increment <<= 1;
}
PA_DCHECK(current_size == 1 << kMaxBucketedOrder);
PA_DCHECK(bucket == &buckets[0] + kNumBuckets);
// Then set up the fast size -> bucket lookup table. We call this multiple
// times, even though the indices are shared between all PartitionRoots, but
// this operation is idempotent, so there is no harm.
internal::InitBucketIndexLookup(this);
#if !defined(PA_THREAD_CACHE_SUPPORTED)
// TLS in ThreadCache not supported on other OSes.
with_thread_cache = false;
#else
with_thread_cache =
(opts.thread_cache == PartitionOptions::ThreadCache::kEnabled);
if (with_thread_cache)
internal::ThreadCache::Init(this);
#endif // !defined(PA_THREAD_CACHE_SUPPORTED)
initialized = true;
}
template <bool thread_safe>
PartitionRoot<thread_safe>::~PartitionRoot() = default;
template <bool thread_safe>
bool PartitionRoot<thread_safe>::ReallocDirectMappedInPlace(
internal::SlotSpanMetadata<thread_safe>* slot_span,
size_t requested_size) {
PA_DCHECK(slot_span->bucket->is_direct_mapped());
size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
// Note that the new size isn't a bucketed size; this function is called
// whenever we're reallocating a direct mapped allocation.
size_t new_slot_size = GetDirectMapSlotSize(raw_size);
if (new_slot_size < kMinDirectMappedDownsize)
return false;
// bucket->slot_size is the current size of the allocation.
size_t current_slot_size = slot_span->bucket->slot_size;
char* char_ptr = static_cast<char*>(SlotSpan::ToPointer(slot_span));
if (new_slot_size == current_slot_size) {
// No need to move any memory around, but update size and cookie below.
// That's because raw_size may have changed.
} else if (new_slot_size < current_slot_size) {
size_t current_map_size =
DirectMapExtent::FromSlotSpan(slot_span)->map_size;
size_t new_map_size = GetDirectMapReservedSize(raw_size) -
GetDirectMapMetadataAndGuardPagesSize();
// Don't reallocate in-place if new map size would be less than 80 % of the
// current map size, to avoid holding on to too much unused address space.
if ((new_map_size / SystemPageSize()) * 5 <
(current_map_size / SystemPageSize()) * 4)
return false;
// Shrink by decommitting unneeded pages and making them inaccessible.
size_t decommit_size = current_slot_size - new_slot_size;
DecommitSystemPages(char_ptr + new_slot_size, decommit_size);
SetSystemPagesAccess(char_ptr + new_slot_size, decommit_size,
PageInaccessible);
} else if (new_slot_size <=
DirectMapExtent::FromSlotSpan(slot_span)->map_size) {
// Grow within the actually allocated memory. Just need to make the
// pages accessible again.
size_t recommit_slot_size_growth = new_slot_size - current_slot_size;
SetSystemPagesAccess(char_ptr + current_slot_size,
recommit_slot_size_growth, PageReadWrite);
RecommitSystemPages(char_ptr + current_slot_size,
recommit_slot_size_growth);
#if DCHECK_IS_ON()
memset(char_ptr + current_slot_size, kUninitializedByte,
recommit_slot_size_growth);
#endif
} else {
// We can't perform the realloc in-place.
// TODO: support this too when possible.
return false;
}
#if DCHECK_IS_ON()
// Write a new trailing cookie.
if (allow_extras) {
internal::PartitionCookieWriteValue(char_ptr + raw_size -
internal::kCookieSize);
}
#endif
slot_span->SetRawSize(raw_size);
slot_span->bucket->slot_size = new_slot_size;
return true;
}
template <bool thread_safe>
void* PartitionRoot<thread_safe>::ReallocFlags(int flags,
void* ptr,
size_t new_size,
const char* type_name) {
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
CHECK_MAX_SIZE_OR_RETURN_NULLPTR(new_size, flags);
void* result = realloc(ptr, new_size);
PA_CHECK(result || flags & PartitionAllocReturnNull);
return result;
#else
bool no_hooks = flags & PartitionAllocNoHooks;
if (UNLIKELY(!ptr)) {
return no_hooks ? AllocFlagsNoHooks(flags, new_size)
: AllocFlags(flags, new_size, type_name);
}
if (UNLIKELY(!new_size)) {
Free(ptr);
return nullptr;
}
if (new_size > MaxDirectMapped()) {
if (flags & PartitionAllocReturnNull)
return nullptr;
internal::PartitionExcessiveAllocationSize(new_size);
}
const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
bool overridden = false;
size_t actual_old_size;
if (UNLIKELY(!no_hooks && hooks_enabled)) {
overridden = PartitionAllocHooks::ReallocOverrideHookIfEnabled(
&actual_old_size, ptr);
}
if (LIKELY(!overridden)) {
auto* slot_span =
SlotSpan::FromPointer(AdjustPointerForExtrasSubtract(ptr));
bool success = false;
{
internal::ScopedGuard<thread_safe> guard{lock_};
// TODO(palmer): See if we can afford to make this a CHECK.
PA_DCHECK(IsValidSlotSpan(slot_span));
if (UNLIKELY(slot_span->bucket->is_direct_mapped())) {
// We may be able to perform the realloc in place by changing the
// accessibility of memory pages and, if reducing the size, decommitting
// them.
success = ReallocDirectMappedInPlace(slot_span, new_size);
}
}
if (success) {
if (UNLIKELY(!no_hooks && hooks_enabled)) {
PartitionAllocHooks::ReallocObserverHookIfEnabled(ptr, ptr, new_size,
type_name);
}
return ptr;
}
const size_t actual_new_size = ActualSize(new_size);
actual_old_size = GetSize(ptr);
// TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the
// new size is a significant percentage smaller. We could do the same if we
// determine it is a win.
if (actual_new_size == actual_old_size) {
// Trying to allocate a block of size |new_size| would give us a block of
// the same size as the one we've already got, so re-use the allocation
// after updating statistics (and cookies, if present).
if (slot_span->CanStoreRawSize()) {
size_t new_raw_size = AdjustSizeForExtrasAdd(new_size);
slot_span->SetRawSize(new_raw_size);
#if DCHECK_IS_ON()
// Write a new trailing cookie only when it is possible to keep track
// raw size (otherwise we wouldn't know where to look for it later).
if (allow_extras) {
internal::PartitionCookieWriteValue(static_cast<char*>(ptr) +
new_size);
}
#endif
}
return ptr;
}
}
// This realloc cannot be resized in-place. Sadness.
void* ret = no_hooks ? AllocFlagsNoHooks(flags, new_size)
: AllocFlags(flags, new_size, type_name);
if (!ret) {
if (flags & PartitionAllocReturnNull)
return nullptr;
internal::PartitionExcessiveAllocationSize(new_size);
}
size_t copy_size = actual_old_size;
if (new_size < copy_size)
copy_size = new_size;
memcpy(ret, ptr, copy_size);
Free(ptr);
return ret;
#endif
}
template <bool thread_safe>
void PartitionRoot<thread_safe>::PurgeMemory(int flags) {
// TODO(chromium:1129751): Change to LIKELY once PCScan is enabled by default.
if (UNLIKELY(pcscan) && (flags & PartitionPurgeForceAllFreed)) {
pcscan->PerformScanIfNeeded(
internal::PCScan<thread_safe>::InvocationMode::kBlocking);
}
{
ScopedGuard guard{lock_};
if (flags & PartitionPurgeDecommitEmptySlotSpans)
DecommitEmptySlotSpans();
if (flags & PartitionPurgeDiscardUnusedSystemPages) {
for (size_t i = 0; i < kNumBuckets; ++i) {
Bucket* bucket = &buckets[i];
if (bucket->slot_size >= SystemPageSize())
internal::PartitionPurgeBucket(bucket);
}
}
}
if (with_thread_cache)
internal::ThreadCacheRegistry::Instance().PurgeAll();
}
template <bool thread_safe>
void PartitionRoot<thread_safe>::DumpStats(const char* partition_name,
bool is_light_dump,
PartitionStatsDumper* dumper) {
static const size_t kMaxReportableDirectMaps = 4096;
// Allocate on the heap rather than on the stack to avoid stack overflow
// skirmishes (on Windows, in particular). Allocate before locking below,
// otherwise when PartitionAlloc is malloc() we get reentrancy issues. This
// inflates reported values a bit for detailed dumps though, by 16kiB.
std::unique_ptr<uint32_t[]> direct_map_lengths = nullptr;
if (!is_light_dump) {
direct_map_lengths =
std::unique_ptr<uint32_t[]>(new uint32_t[kMaxReportableDirectMaps]);
}
PartitionBucketMemoryStats bucket_stats[kNumBuckets];
size_t num_direct_mapped_allocations = 0;
PartitionMemoryStats stats = {0};
// Collect data with the lock held, cannot allocate or call third-party code
// below.
{
ScopedGuard guard{lock_};
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);
size_t direct_mapped_allocations_total_size = 0;
for (size_t i = 0; i < kNumBuckets; ++i) {
const Bucket* bucket = &buckets[i];
// Don't report the pseudo buckets that the generic allocator sets up in
// order to preserve a fast size->bucket map (see
// PartitionRoot::Init() for details).
if (!bucket->active_slot_spans_head)
bucket_stats[i].is_valid = false;
else
internal::PartitionDumpBucketStats(&bucket_stats[i], bucket);
if (bucket_stats[i].is_valid) {
stats.total_resident_bytes += bucket_stats[i].resident_bytes;
stats.total_active_bytes += bucket_stats[i].active_bytes;
stats.total_decommittable_bytes += bucket_stats[i].decommittable_bytes;
stats.total_discardable_bytes += bucket_stats[i].discardable_bytes;
}
}
for (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;
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.has_thread_cache = !is_light_dump && with_thread_cache;
if (stats.has_thread_cache) {
internal::ThreadCacheRegistry::Instance().DumpStats(
true, &stats.current_thread_cache_stats);
internal::ThreadCacheRegistry::Instance().DumpStats(
false, &stats.all_thread_caches_stats);
}
}
// Do not hold the lock when calling |dumper|, as it may allocate.
if (!is_light_dump) {
for (size_t i = 0; i < kNumBuckets; ++i) {
if (bucket_stats[i].is_valid)
dumper->PartitionsDumpBucketStats(partition_name, &bucket_stats[i]);
}
for (size_t i = 0; i < num_direct_mapped_allocations; ++i) {
uint32_t size = 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.resident_bytes = size;
dumper->PartitionsDumpBucketStats(partition_name, &mapped_stats);
}
}
dumper->PartitionDumpTotals(partition_name, &stats);
}
template struct BASE_EXPORT PartitionRoot<internal::ThreadSafe>;
template struct BASE_EXPORT PartitionRoot<internal::NotThreadSafe>;
static_assert(sizeof(PartitionRoot<internal::ThreadSafe>) ==
sizeof(PartitionRoot<internal::NotThreadSafe>),
"Layouts should match");
static_assert(offsetof(PartitionRoot<internal::ThreadSafe>, buckets) ==
offsetof(PartitionRoot<internal::NotThreadSafe>, buckets),
"Layouts should match");
static_assert(offsetof(PartitionRoot<internal::ThreadSafe>, sentinel_bucket) ==
offsetof(PartitionRoot<internal::ThreadSafe>, buckets) +
kNumBuckets *
sizeof(PartitionRoot<internal::ThreadSafe>::Bucket),
"sentinel_bucket must be just after the regular buckets.");
} // namespace base