mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
289 lines
11 KiB
C++
289 lines
11 KiB
C++
// Copyright (c) 2018 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.
|
|
|
|
#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_PAGE_H_
|
|
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_PAGE_H_
|
|
|
|
#include "base/allocator/partition_allocator/partition_alloc_constants.h"
|
|
#include "base/allocator/partition_allocator/partition_bucket.h"
|
|
#include "base/allocator/partition_allocator/partition_cookie.h"
|
|
#include "base/allocator/partition_allocator/partition_freelist_entry.h"
|
|
|
|
namespace base {
|
|
namespace internal {
|
|
|
|
struct PartitionRootBase;
|
|
|
|
// Some notes on page states. A page can be in one of four major states:
|
|
// 1) Active.
|
|
// 2) Full.
|
|
// 3) Empty.
|
|
// 4) Decommitted.
|
|
// An active page has available free slots. A full page has no free slots. An
|
|
// empty page has no free slots, and a decommitted page is an empty page that
|
|
// had its backing memory released back to the system.
|
|
// There are two linked lists tracking the pages. The "active page" list is an
|
|
// approximation of a list of active pages. It is an approximation because
|
|
// full, empty and decommitted pages may briefly be present in the list until
|
|
// we next do a scan over it.
|
|
// The "empty page" list is an accurate list of pages which are either empty
|
|
// or decommitted.
|
|
//
|
|
// The significant page transitions are:
|
|
// - free() will detect when a full page has a slot free()'d and immediately
|
|
// return the page to the head of the active list.
|
|
// - free() will detect when a page is fully emptied. It _may_ add it to the
|
|
// empty list or it _may_ leave it on the active list until a future list scan.
|
|
// - malloc() _may_ scan the active page list in order to fulfil the request.
|
|
// If it does this, full, empty and decommitted pages encountered will be
|
|
// booted out of the active list. If there are no suitable active pages found,
|
|
// an empty or decommitted page (if one exists) will be pulled from the empty
|
|
// list on to the active list.
|
|
//
|
|
// TODO(ajwong): Evaluate if this should be named PartitionSlotSpanMetadata or
|
|
// similar. If so, all uses of the term "page" in comments, member variables,
|
|
// local variables, and documentation that refer to this concept should be
|
|
// updated.
|
|
struct PartitionPage {
|
|
PartitionFreelistEntry* freelist_head;
|
|
PartitionPage* next_page;
|
|
PartitionBucket* bucket;
|
|
// Deliberately signed, 0 for empty or decommitted page, -n for full pages:
|
|
int16_t num_allocated_slots;
|
|
uint16_t num_unprovisioned_slots;
|
|
uint16_t page_offset;
|
|
int16_t empty_cache_index; // -1 if not in the empty cache.
|
|
|
|
// Public API
|
|
|
|
// Note the matching Alloc() functions are in PartitionPage.
|
|
BASE_EXPORT NOINLINE void FreeSlowPath();
|
|
ALWAYS_INLINE void Free(void* ptr);
|
|
|
|
void Decommit(PartitionRootBase* root);
|
|
void DecommitIfPossible(PartitionRootBase* root);
|
|
|
|
// Pointer manipulation functions. These must be static as the input |page|
|
|
// pointer may be the result of an offset calculation and therefore cannot
|
|
// be trusted. The objective of these functions is to sanitize this input.
|
|
ALWAYS_INLINE static void* ToPointer(const PartitionPage* page);
|
|
ALWAYS_INLINE static PartitionPage* FromPointerNoAlignmentCheck(void* ptr);
|
|
ALWAYS_INLINE static PartitionPage* FromPointer(void* ptr);
|
|
|
|
ALWAYS_INLINE const size_t* get_raw_size_ptr() const;
|
|
ALWAYS_INLINE size_t* get_raw_size_ptr() {
|
|
return const_cast<size_t*>(
|
|
const_cast<const PartitionPage*>(this)->get_raw_size_ptr());
|
|
}
|
|
|
|
ALWAYS_INLINE size_t get_raw_size() const;
|
|
ALWAYS_INLINE void set_raw_size(size_t size);
|
|
|
|
ALWAYS_INLINE void Reset();
|
|
|
|
// TODO(ajwong): Can this be made private? https://crbug.com/787153
|
|
BASE_EXPORT static PartitionPage* get_sentinel_page();
|
|
|
|
// Page State accessors.
|
|
// Note that it's only valid to call these functions on pages found on one of
|
|
// the page lists. Specifically, you can't call these functions on full pages
|
|
// that were detached from the active list.
|
|
//
|
|
// This restriction provides the flexibity for some of the status fields to
|
|
// be repurposed when a page is taken off a list. See the negation of
|
|
// |num_allocated_slots| when a full page is removed from the active list
|
|
// for an example of such repurposing.
|
|
ALWAYS_INLINE bool is_active() const;
|
|
ALWAYS_INLINE bool is_full() const;
|
|
ALWAYS_INLINE bool is_empty() const;
|
|
ALWAYS_INLINE bool is_decommitted() const;
|
|
|
|
private:
|
|
// g_sentinel_page is used as a sentinel to indicate that there is no page
|
|
// in the active page list. We can use nullptr, but in that case we need
|
|
// to add a null-check branch to the hot allocation path. We want to avoid
|
|
// that.
|
|
//
|
|
// Note, this declaration is kept in the header as opposed to an anonymous
|
|
// namespace so the getter can be fully inlined.
|
|
static PartitionPage sentinel_page_;
|
|
};
|
|
static_assert(sizeof(PartitionPage) <= kPageMetadataSize,
|
|
"PartitionPage must be able to fit in a metadata slot");
|
|
|
|
ALWAYS_INLINE char* PartitionSuperPageToMetadataArea(char* ptr) {
|
|
uintptr_t pointer_as_uint = reinterpret_cast<uintptr_t>(ptr);
|
|
DCHECK(!(pointer_as_uint & kSuperPageOffsetMask));
|
|
// The metadata area is exactly one system page (the guard page) into the
|
|
// super page.
|
|
return reinterpret_cast<char*>(pointer_as_uint + kSystemPageSize);
|
|
}
|
|
|
|
ALWAYS_INLINE PartitionPage* PartitionPage::FromPointerNoAlignmentCheck(
|
|
void* ptr) {
|
|
uintptr_t pointer_as_uint = reinterpret_cast<uintptr_t>(ptr);
|
|
char* super_page_ptr =
|
|
reinterpret_cast<char*>(pointer_as_uint & kSuperPageBaseMask);
|
|
uintptr_t partition_page_index =
|
|
(pointer_as_uint & kSuperPageOffsetMask) >> kPartitionPageShift;
|
|
// Index 0 is invalid because it is the metadata and guard area and
|
|
// the last index is invalid because it is a guard page.
|
|
DCHECK(partition_page_index);
|
|
DCHECK(partition_page_index < kNumPartitionPagesPerSuperPage - 1);
|
|
PartitionPage* page = reinterpret_cast<PartitionPage*>(
|
|
PartitionSuperPageToMetadataArea(super_page_ptr) +
|
|
(partition_page_index << kPageMetadataShift));
|
|
// Partition pages in the same slot span can share the same page object.
|
|
// Adjust for that.
|
|
size_t delta = page->page_offset << kPageMetadataShift;
|
|
page =
|
|
reinterpret_cast<PartitionPage*>(reinterpret_cast<char*>(page) - delta);
|
|
return page;
|
|
}
|
|
|
|
// Resturns start of the slot span for the PartitionPage.
|
|
ALWAYS_INLINE void* PartitionPage::ToPointer(const PartitionPage* page) {
|
|
uintptr_t pointer_as_uint = reinterpret_cast<uintptr_t>(page);
|
|
|
|
uintptr_t super_page_offset = (pointer_as_uint & kSuperPageOffsetMask);
|
|
|
|
// A valid |page| must be past the first guard System page and within
|
|
// the following metadata region.
|
|
DCHECK(super_page_offset > kSystemPageSize);
|
|
// Must be less than total metadata region.
|
|
DCHECK(super_page_offset < kSystemPageSize + (kNumPartitionPagesPerSuperPage *
|
|
kPageMetadataSize));
|
|
uintptr_t partition_page_index =
|
|
(super_page_offset - kSystemPageSize) >> kPageMetadataShift;
|
|
// Index 0 is invalid because it is the superpage extent metadata and the
|
|
// last index is invalid because the whole PartitionPage is set as guard
|
|
// pages for the metadata region.
|
|
DCHECK(partition_page_index);
|
|
DCHECK(partition_page_index < kNumPartitionPagesPerSuperPage - 1);
|
|
uintptr_t super_page_base = (pointer_as_uint & kSuperPageBaseMask);
|
|
void* ret = reinterpret_cast<void*>(
|
|
super_page_base + (partition_page_index << kPartitionPageShift));
|
|
return ret;
|
|
}
|
|
|
|
ALWAYS_INLINE PartitionPage* PartitionPage::FromPointer(void* ptr) {
|
|
PartitionPage* page = PartitionPage::FromPointerNoAlignmentCheck(ptr);
|
|
// Checks that the pointer is a multiple of bucket size.
|
|
DCHECK(!((reinterpret_cast<uintptr_t>(ptr) -
|
|
reinterpret_cast<uintptr_t>(PartitionPage::ToPointer(page))) %
|
|
page->bucket->slot_size));
|
|
return page;
|
|
}
|
|
|
|
ALWAYS_INLINE const size_t* PartitionPage::get_raw_size_ptr() const {
|
|
// For single-slot buckets which span more than one partition page, we
|
|
// have some spare metadata space to store the raw allocation size. We
|
|
// can use this to report better statistics.
|
|
if (bucket->slot_size <= kMaxSystemPagesPerSlotSpan * kSystemPageSize)
|
|
return nullptr;
|
|
|
|
DCHECK((bucket->slot_size % kSystemPageSize) == 0);
|
|
DCHECK(bucket->is_direct_mapped() || bucket->get_slots_per_span() == 1);
|
|
|
|
const PartitionPage* the_next_page = this + 1;
|
|
return reinterpret_cast<const size_t*>(&the_next_page->freelist_head);
|
|
}
|
|
|
|
ALWAYS_INLINE size_t PartitionPage::get_raw_size() const {
|
|
const size_t* ptr = get_raw_size_ptr();
|
|
if (UNLIKELY(ptr != nullptr))
|
|
return *ptr;
|
|
return 0;
|
|
}
|
|
|
|
ALWAYS_INLINE void PartitionPage::Free(void* ptr) {
|
|
// If these asserts fire, you probably corrupted memory.
|
|
#if DCHECK_IS_ON()
|
|
size_t slot_size = this->bucket->slot_size;
|
|
size_t raw_size = get_raw_size();
|
|
if (raw_size)
|
|
slot_size = raw_size;
|
|
PartitionCookieCheckValue(ptr);
|
|
PartitionCookieCheckValue(reinterpret_cast<char*>(ptr) + slot_size -
|
|
kCookieSize);
|
|
memset(ptr, kFreedByte, slot_size);
|
|
#endif
|
|
DCHECK(this->num_allocated_slots);
|
|
// TODO(palmer): See if we can afford to make this a CHECK.
|
|
// FIX FIX FIX
|
|
// DCHECK(!freelist_head || PartitionRootBase::IsValidPage(
|
|
// PartitionPage::FromPointer(freelist_head)));
|
|
CHECK(ptr != freelist_head); // Catches an immediate double free.
|
|
// Look for double free one level deeper in debug.
|
|
DCHECK(!freelist_head || ptr != internal::PartitionFreelistEntry::Transform(
|
|
freelist_head->next));
|
|
internal::PartitionFreelistEntry* entry =
|
|
static_cast<internal::PartitionFreelistEntry*>(ptr);
|
|
entry->next = internal::PartitionFreelistEntry::Transform(freelist_head);
|
|
freelist_head = entry;
|
|
--this->num_allocated_slots;
|
|
if (UNLIKELY(this->num_allocated_slots <= 0)) {
|
|
FreeSlowPath();
|
|
} else {
|
|
// All single-slot allocations must go through the slow path to
|
|
// correctly update the size metadata.
|
|
DCHECK(get_raw_size() == 0);
|
|
}
|
|
}
|
|
|
|
ALWAYS_INLINE bool PartitionPage::is_active() const {
|
|
DCHECK(this != get_sentinel_page());
|
|
DCHECK(!page_offset);
|
|
return (num_allocated_slots > 0 &&
|
|
(freelist_head || num_unprovisioned_slots));
|
|
}
|
|
|
|
ALWAYS_INLINE bool PartitionPage::is_full() const {
|
|
DCHECK(this != get_sentinel_page());
|
|
DCHECK(!page_offset);
|
|
bool ret = (num_allocated_slots == bucket->get_slots_per_span());
|
|
if (ret) {
|
|
DCHECK(!freelist_head);
|
|
DCHECK(!num_unprovisioned_slots);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ALWAYS_INLINE bool PartitionPage::is_empty() const {
|
|
DCHECK(this != get_sentinel_page());
|
|
DCHECK(!page_offset);
|
|
return (!num_allocated_slots && freelist_head);
|
|
}
|
|
|
|
ALWAYS_INLINE bool PartitionPage::is_decommitted() const {
|
|
DCHECK(this != get_sentinel_page());
|
|
DCHECK(!page_offset);
|
|
bool ret = (!num_allocated_slots && !freelist_head);
|
|
if (ret) {
|
|
DCHECK(!num_unprovisioned_slots);
|
|
DCHECK(empty_cache_index == -1);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ALWAYS_INLINE void PartitionPage::set_raw_size(size_t size) {
|
|
size_t* raw_size_ptr = get_raw_size_ptr();
|
|
if (UNLIKELY(raw_size_ptr != nullptr))
|
|
*raw_size_ptr = size;
|
|
}
|
|
|
|
ALWAYS_INLINE void PartitionPage::Reset() {
|
|
DCHECK(this->is_decommitted());
|
|
|
|
num_unprovisioned_slots = bucket->get_slots_per_span();
|
|
DCHECK(num_unprovisioned_slots);
|
|
|
|
next_page = nullptr;
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace base
|
|
|
|
#endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_PAGE_H_
|