#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ROOT_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ROOT_H_
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include "base/allocator/partition_allocator/address_pool_manager_types.h"
#include "base/allocator/partition_allocator/allocation_guard.h"
#include "base/allocator/partition_allocator/chromecast_buildflags.h"
#include "base/allocator/partition_allocator/freeslot_bitmap.h"
#include "base/allocator/partition_allocator/page_allocator.h"
#include "base/allocator/partition_allocator/partition_address_space.h"
#include "base/allocator/partition_allocator/partition_alloc-inl.h"
#include "base/allocator/partition_allocator/partition_alloc_base/bits.h"
#include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
#include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
#include "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h"
#include "base/allocator/partition_allocator/partition_alloc_base/thread_annotations.h"
#include "base/allocator/partition_allocator/partition_alloc_base/time/time.h"
#include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
#include "base/allocator/partition_allocator/partition_alloc_check.h"
#include "base/allocator/partition_allocator/partition_alloc_config.h"
#include "base/allocator/partition_allocator/partition_alloc_constants.h"
#include "base/allocator/partition_allocator/partition_alloc_forward.h"
#include "base/allocator/partition_allocator/partition_alloc_hooks.h"
#include "base/allocator/partition_allocator/partition_alloc_notreached.h"
#include "base/allocator/partition_allocator/partition_bucket_lookup.h"
#include "base/allocator/partition_allocator/partition_cookie.h"
#include "base/allocator/partition_allocator/partition_direct_map_extent.h"
#include "base/allocator/partition_allocator/partition_freelist_entry.h"
#include "base/allocator/partition_allocator/partition_lock.h"
#include "base/allocator/partition_allocator/partition_oom.h"
#include "base/allocator/partition_allocator/partition_page.h"
#include "base/allocator/partition_allocator/partition_ref_count.h"
#include "base/allocator/partition_allocator/pkey.h"
#include "base/allocator/partition_allocator/reservation_offset_table.h"
#include "base/allocator/partition_allocator/tagging.h"
#include "base/allocator/partition_allocator/thread_cache.h"
#include "build/build_config.h"
#if BUILDFLAG(USE_STARSCAN)
#include "base/allocator/partition_allocator/starscan/pcscan.h"
#endif
#define CHECK_MAX_SIZE_OR_RETURN_NULLPTR(size, flags) \
if (size > partition_alloc::internal::MaxDirectMapped()) { \
if (flags & AllocFlags::kReturnNull) { \
return nullptr; \
} \
PA_CHECK(false); \
}
namespace partition_alloc::internal {
static constexpr size_t kAllocInfoSize = 1 << 24;
struct AllocInfo {
std::atomic<size_t> index{0};
struct {
uintptr_t addr;
size_t size;
} allocs[kAllocInfoSize] = {};
};
#if BUILDFLAG(RECORD_ALLOC_INFO)
extern AllocInfo g_allocs;
void RecordAllocOrFree(uintptr_t addr, size_t size);
#endif
}
namespace partition_alloc {
namespace internal {
#if BUILDFLAG(PA_DCHECK_IS_ON)
PA_COMPONENT_EXPORT(PARTITION_ALLOC)
void DCheckIfManagedByPartitionAllocBRPPool(uintptr_t address);
#else
PA_ALWAYS_INLINE void DCheckIfManagedByPartitionAllocBRPPool(
uintptr_t address) {}
#endif
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
class PartitionRootEnumerator;
#endif
}
struct PurgeFlags {
enum : int {
kDecommitEmptySlotSpans = 1 << 0,
kDiscardUnusedSystemPages = 1 << 1,
kAggressiveReclaim = 1 << 2,
};
};
struct PartitionOptions {
enum class AlignedAlloc : uint8_t {
kDisallowed,
kAllowed,
};
enum class ThreadCache : uint8_t {
kDisabled,
kEnabled,
};
enum class Quarantine : uint8_t {
kDisallowed,
kAllowed,
};
enum class Cookie : uint8_t {
kDisallowed,
kAllowed,
};
enum class BackupRefPtr : uint8_t {
kDisabled,
kEnabled,
};
enum class BackupRefPtrZapping : uint8_t {
kDisabled,
kEnabled,
};
enum class AddDummyRefCount : uint8_t {
kDisabled,
kEnabled,
};
enum class UseConfigurablePool : uint8_t {
kNo,
kIfAvailable,
};
constexpr PartitionOptions(
AlignedAlloc aligned_alloc,
ThreadCache thread_cache,
Quarantine quarantine,
Cookie cookie,
BackupRefPtr backup_ref_ptr,
BackupRefPtrZapping backup_ref_ptr_zapping,
UseConfigurablePool use_configurable_pool,
AddDummyRefCount add_dummy_ref_count = AddDummyRefCount::kDisabled
#if BUILDFLAG(ENABLE_PKEYS)
,
int pkey = internal::kDefaultPkey
#endif
)
: aligned_alloc(aligned_alloc),
thread_cache(thread_cache),
quarantine(quarantine),
cookie(cookie),
backup_ref_ptr(backup_ref_ptr),
backup_ref_ptr_zapping(backup_ref_ptr_zapping),
use_configurable_pool(use_configurable_pool)
#if BUILDFLAG(ENABLE_PKEYS)
,
pkey(pkey)
#endif
{
}
AlignedAlloc aligned_alloc;
ThreadCache thread_cache;
Quarantine quarantine;
Cookie cookie;
BackupRefPtr backup_ref_ptr;
BackupRefPtrZapping backup_ref_ptr_zapping;
UseConfigurablePool use_configurable_pool;
AddDummyRefCount add_dummy_ref_count = AddDummyRefCount::kDisabled;
#if BUILDFLAG(ENABLE_PKEYS)
int pkey;
#endif
};
template <bool thread_safe>
struct PA_ALIGNAS(64) PA_COMPONENT_EXPORT(PARTITION_ALLOC) PartitionRoot {
using SlotSpan = internal::SlotSpanMetadata<thread_safe>;
using Page = internal::PartitionPage<thread_safe>;
using Bucket = internal::PartitionBucket<thread_safe>;
using FreeListEntry = internal::PartitionFreelistEntry;
using SuperPageExtentEntry =
internal::PartitionSuperPageExtentEntry<thread_safe>;
using DirectMapExtent = internal::PartitionDirectMapExtent<thread_safe>;
#if BUILDFLAG(USE_STARSCAN)
using PCScan = internal::PCScan;
#endif
enum class QuarantineMode : uint8_t {
kAlwaysDisabled,
kDisabledByDefault,
kEnabled,
};
enum class ScanMode : uint8_t {
kDisabled,
kEnabled,
};
enum class BucketDistribution : uint8_t { kDefault, kDenser };
struct Flags {
QuarantineMode quarantine_mode;
ScanMode scan_mode;
BucketDistribution bucket_distribution = BucketDistribution::kDefault;
bool with_thread_cache = false;
bool allow_aligned_alloc;
bool allow_cookie;
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
bool brp_enabled_;
bool brp_zapping_enabled_;
#if PA_CONFIG(ENABLE_MAC11_MALLOC_SIZE_HACK)
bool mac11_malloc_size_hack_enabled_ = false;
#endif
#endif
bool use_configurable_pool;
#if BUILDFLAG(ENABLE_PKEYS)
int pkey;
#endif
#if PA_CONFIG(EXTRAS_REQUIRED)
uint32_t extras_size;
uint32_t extras_offset;
#else
static inline constexpr uint32_t extras_size = 0;
static inline constexpr uint32_t extras_offset = 0;
#endif
};
union {
Flags flags;
uint8_t one_cacheline[internal::kPartitionCachelineSize];
};
static_assert(thread_safe, "Only the thread-safe root is supported.");
::partition_alloc::internal::Lock lock_;
Bucket buckets[internal::kNumBuckets] = {};
Bucket sentinel_bucket{};
bool initialized = false;
std::atomic<size_t> total_size_of_committed_pages{0};
std::atomic<size_t> max_size_of_committed_pages{0};
std::atomic<size_t> total_size_of_super_pages{0};
std::atomic<size_t> total_size_of_direct_mapped_pages{0};
size_t total_size_of_allocated_bytes PA_GUARDED_BY(lock_) = 0;
size_t max_size_of_allocated_bytes PA_GUARDED_BY(lock_) = 0;
std::atomic<uint64_t> syscall_count{};
std::atomic<uint64_t> syscall_total_time_ns{};
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
std::atomic<size_t> total_size_of_brp_quarantined_bytes{0};
std::atomic<size_t> total_count_of_brp_quarantined_slots{0};
std::atomic<size_t> cumulative_size_of_brp_quarantined_bytes{0};
std::atomic<size_t> cumulative_count_of_brp_quarantined_slots{0};
#endif
size_t empty_slot_spans_dirty_bytes PA_GUARDED_BY(lock_) = 0;
int max_empty_slot_spans_dirty_bytes_shift = 3;
uintptr_t next_super_page = 0;
uintptr_t next_partition_page = 0;
uintptr_t next_partition_page_end = 0;
SuperPageExtentEntry* current_extent = nullptr;
SuperPageExtentEntry* first_extent = nullptr;
DirectMapExtent* direct_map_list PA_GUARDED_BY(lock_) = nullptr;
SlotSpan*
global_empty_slot_span_ring[internal::kMaxFreeableSpans] PA_GUARDED_BY(
lock_) = {};
int16_t global_empty_slot_span_ring_index PA_GUARDED_BY(lock_) = 0;
int16_t global_empty_slot_span_ring_size PA_GUARDED_BY(lock_) =
internal::kDefaultEmptySlotSpanRingSize;
uintptr_t inverted_self = 0;
std::atomic<int> thread_caches_being_constructed_{0};
bool quarantine_always_for_testing = false;
PartitionRoot()
: flags{QuarantineMode::kAlwaysDisabled, ScanMode::kDisabled} {}
explicit PartitionRoot(PartitionOptions opts) : flags() { Init(opts); }
~PartitionRoot();
void DestructForTesting();
#if PA_CONFIG(ENABLE_MAC11_MALLOC_SIZE_HACK)
void EnableMac11MallocSizeHackForTesting();
#endif
void Init(PartitionOptions);
void EnableThreadCacheIfSupported();
PA_ALWAYS_INLINE static bool IsValidSlotSpan(SlotSpan* slot_span);
PA_ALWAYS_INLINE static PartitionRoot* FromSlotSpan(SlotSpan* slot_span);
PA_ALWAYS_INLINE static PartitionRoot* FromFirstSuperPage(
uintptr_t super_page);
PA_ALWAYS_INLINE static PartitionRoot* FromAddrInFirstSuperpage(
uintptr_t address);
PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len,
size_t raw_size)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void IncreaseCommittedPages(size_t len);
PA_ALWAYS_INLINE void DecreaseCommittedPages(size_t len);
PA_ALWAYS_INLINE void DecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void RecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE bool TryRecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition)
PA_LOCKS_EXCLUDED(lock_);
[[noreturn]] PA_NOINLINE void OutOfMemory(size_t size);
PA_ALWAYS_INLINE void* AlignedAllocWithFlags(unsigned int flags,
size_t alignment,
size_t requested_size);
PA_NOINLINE PA_MALLOC_FN void* Alloc(size_t requested_size,
const char* type_name) PA_MALLOC_ALIGNED;
PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocWithFlags(unsigned int flags,
size_t requested_size,
const char* type_name)
PA_MALLOC_ALIGNED;
PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocWithFlagsInternal(
unsigned int flags,
size_t requested_size,
size_t slot_span_alignment,
const char* type_name) PA_MALLOC_ALIGNED;
PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocWithFlagsNoHooks(
unsigned int flags,
size_t requested_size,
size_t slot_span_alignment) PA_MALLOC_ALIGNED;
PA_NOINLINE void* Realloc(void* ptr,
size_t newize,
const char* type_name) PA_MALLOC_ALIGNED;
PA_NOINLINE void* TryRealloc(void* ptr,
size_t new_size,
const char* type_name) PA_MALLOC_ALIGNED;
PA_NOINLINE void* ReallocWithFlags(unsigned int flags,
void* ptr,
size_t new_size,
const char* type_name) PA_MALLOC_ALIGNED;
PA_NOINLINE static void Free(void* object);
PA_ALWAYS_INLINE static void FreeWithFlags(unsigned int flags, void* object);
PA_ALWAYS_INLINE static void FreeNoHooks(void* object);
PA_ALWAYS_INLINE void FreeNoHooksImmediate(void* object,
SlotSpan* slot_span,
uintptr_t slot_start);
PA_ALWAYS_INLINE static size_t GetUsableSize(void* ptr);
PA_ALWAYS_INLINE static size_t GetUsableSizeWithMac11MallocSizeHack(
void* ptr);
PA_ALWAYS_INLINE PageAccessibilityConfiguration GetPageAccessibility() const;
PA_ALWAYS_INLINE PageAccessibilityConfiguration
PageAccessibilityWithPkeyIfEnabled(
PageAccessibilityConfiguration::Permissions) const;
PA_ALWAYS_INLINE size_t
AllocationCapacityFromSlotStart(uintptr_t slot_start) const;
PA_ALWAYS_INLINE size_t
AllocationCapacityFromRequestedSize(size_t size) const;
PA_ALWAYS_INLINE bool IsMemoryTaggingEnabled() const;
void PurgeMemory(int flags);
void ShrinkEmptySlotSpansRing(size_t limit)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
void EnableLargeEmptySlotSpanRing() {
::partition_alloc::internal::ScopedGuard locker{lock_};
global_empty_slot_span_ring_size = internal::kMaxFreeableSpans;
}
void DumpStats(const char* partition_name,
bool is_light_dump,
PartitionStatsDumper* partition_stats_dumper);
static void DeleteForTesting(PartitionRoot* partition_root);
void ResetForTesting(bool allow_leaks);
void ResetBookkeepingForTesting();
PA_ALWAYS_INLINE BucketDistribution GetBucketDistribution() const {
return flags.bucket_distribution;
}
static uint16_t SizeToBucketIndex(size_t size,
BucketDistribution bucket_distribution);
PA_ALWAYS_INLINE void FreeInSlotSpan(uintptr_t slot_start,
SlotSpan* slot_span)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void RawFree(uintptr_t slot_start);
PA_ALWAYS_INLINE void RawFree(uintptr_t slot_start, SlotSpan* slot_span)
PA_LOCKS_EXCLUDED(lock_);
PA_ALWAYS_INLINE void RawFreeBatch(FreeListEntry* head,
FreeListEntry* tail,
size_t size,
SlotSpan* slot_span)
PA_LOCKS_EXCLUDED(lock_);
PA_ALWAYS_INLINE void RawFreeWithThreadCache(uintptr_t slot_start,
SlotSpan* slot_span);
void SwitchToDenserBucketDistribution() {
flags.bucket_distribution = BucketDistribution::kDenser;
}
void ResetBucketDistributionForTesting() {
flags.bucket_distribution = BucketDistribution::kDefault;
}
ThreadCache* thread_cache_for_testing() const {
return flags.with_thread_cache ? ThreadCache::Get() : nullptr;
}
size_t get_total_size_of_committed_pages() const {
return total_size_of_committed_pages.load(std::memory_order_relaxed);
}
size_t get_max_size_of_committed_pages() const {
return max_size_of_committed_pages.load(std::memory_order_relaxed);
}
size_t get_total_size_of_allocated_bytes() const {
return PA_TS_UNCHECKED_READ(total_size_of_allocated_bytes);
}
size_t get_max_size_of_allocated_bytes() const {
return PA_TS_UNCHECKED_READ(max_size_of_allocated_bytes);
}
internal::pool_handle ChoosePool() const {
#if BUILDFLAG(HAS_64_BIT_POINTERS)
if (flags.use_configurable_pool) {
PA_DCHECK(IsConfigurablePoolAvailable());
return internal::kConfigurablePoolHandle;
}
#endif
#if BUILDFLAG(ENABLE_PKEYS)
if (flags.pkey != internal::kDefaultPkey) {
return internal::kPkeyPoolHandle;
}
#endif
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
return brp_enabled() ? internal::kBRPPoolHandle
: internal::kRegularPoolHandle;
#else
return internal::kRegularPoolHandle;
#endif
}
PA_ALWAYS_INLINE bool IsQuarantineAllowed() const {
return flags.quarantine_mode != QuarantineMode::kAlwaysDisabled;
}
PA_ALWAYS_INLINE bool IsQuarantineEnabled() const {
return flags.quarantine_mode == QuarantineMode::kEnabled;
}
PA_ALWAYS_INLINE bool ShouldQuarantine(void* object) const {
if (PA_UNLIKELY(flags.quarantine_mode != QuarantineMode::kEnabled)) {
return false;
}
#if PA_CONFIG(HAS_MEMORY_TAGGING)
if (PA_UNLIKELY(quarantine_always_for_testing)) {
return true;
}
if (PA_LIKELY(IsMemoryTaggingEnabled())) {
return internal::HasOverflowTag(object);
}
return true;
#else
return true;
#endif
}
PA_ALWAYS_INLINE void SetQuarantineAlwaysForTesting(bool value) {
quarantine_always_for_testing = value;
}
PA_ALWAYS_INLINE bool IsScanEnabled() const {
PA_DCHECK(flags.scan_mode != ScanMode::kEnabled || IsQuarantineEnabled());
return flags.scan_mode == ScanMode::kEnabled;
}
PA_ALWAYS_INLINE static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
GetDirectMapMetadataAndGuardPagesSize() {
return 2 * internal::PartitionPageSize();
}
PA_ALWAYS_INLINE static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
GetDirectMapSlotSize(size_t raw_size) {
PA_DCHECK(raw_size <= internal::MaxDirectMapped());
return partition_alloc::internal::base::bits::AlignUp(
raw_size, internal::SystemPageSize());
}
PA_ALWAYS_INLINE static size_t GetDirectMapReservationSize(
size_t padded_raw_size) {
PA_DCHECK(padded_raw_size <= internal::MaxDirectMapped());
return partition_alloc::internal::base::bits::AlignUp(
padded_raw_size + GetDirectMapMetadataAndGuardPagesSize(),
internal::DirectMapAllocationGranularity());
}
PA_ALWAYS_INLINE size_t AdjustSize0IfNeeded(size_t size) const {
if (PA_UNLIKELY(size == 0)) {
return 1;
}
return size;
}
PA_ALWAYS_INLINE size_t AdjustSizeForExtrasAdd(size_t size) const {
size = AdjustSize0IfNeeded(size);
PA_DCHECK(size + flags.extras_size >= size);
return size + flags.extras_size;
}
PA_ALWAYS_INLINE size_t AdjustSizeForExtrasSubtract(size_t size) const {
return size - flags.extras_size;
}
PA_ALWAYS_INLINE uintptr_t SlotStartToObjectAddr(uintptr_t slot_start) const {
return slot_start + flags.extras_offset;
}
PA_ALWAYS_INLINE void* SlotStartToObject(uintptr_t slot_start) const {
return internal::TagAddr(SlotStartToObjectAddr(slot_start));
}
PA_ALWAYS_INLINE uintptr_t ObjectToSlotStart(void* object) const {
return UntagPtr(object) - flags.extras_offset;
}
bool brp_enabled() const {
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
return flags.brp_enabled_;
#else
return false;
#endif
}
bool brp_zapping_enabled() const {
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
return flags.brp_zapping_enabled_;
#else
return false;
#endif
}
PA_ALWAYS_INLINE bool uses_configurable_pool() const {
return flags.use_configurable_pool;
}
void UncapEmptySlotSpanMemoryForTesting() {
max_empty_slot_spans_dirty_bytes_shift = 0;
}
static void EnableSortActiveSlotSpans();
private:
static inline bool sort_active_slot_spans_ = false;
PA_ALWAYS_INLINE const Bucket& PA_NO_SANITIZE("undefined")
bucket_at(size_t i) const {
PA_DCHECK(i <= internal::kNumBuckets);
return buckets[i];
}
PA_ALWAYS_INLINE bool IsDirectMappedBucket(Bucket* bucket) const {
bool ret = !(bucket >= this->buckets && bucket <= &this->sentinel_bucket);
PA_DCHECK(ret == bucket->is_direct_mapped());
return ret;
}
PA_ALWAYS_INLINE uintptr_t RawAlloc(Bucket* bucket,
unsigned int flags,
size_t raw_size,
size_t slot_span_alignment,
size_t* usable_size,
bool* is_already_zeroed);
PA_ALWAYS_INLINE uintptr_t AllocFromBucket(Bucket* bucket,
unsigned int flags,
size_t raw_size,
size_t slot_span_alignment,
size_t* usable_size,
bool* is_already_zeroed)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
bool TryReallocInPlaceForNormalBuckets(void* object,
SlotSpan* slot_span,
size_t new_size);
bool TryReallocInPlaceForDirectMap(
internal::SlotSpanMetadata<thread_safe>* slot_span,
size_t requested_size) PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
void DecommitEmptySlotSpans() PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void RawFreeLocked(uintptr_t slot_start)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
ThreadCache* MaybeInitThreadCache();
PA_ALWAYS_INLINE ThreadCache* GetOrCreateThreadCache();
PA_ALWAYS_INLINE ThreadCache* GetThreadCache();
#if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
static internal::Lock& GetEnumeratorLock();
PartitionRoot* PA_GUARDED_BY(GetEnumeratorLock()) next_root = nullptr;
PartitionRoot* PA_GUARDED_BY(GetEnumeratorLock()) prev_root = nullptr;
friend class internal::PartitionRootEnumerator;
#endif
friend class ThreadCache;
};
namespace internal {
class ScopedSyscallTimer {
public:
#if PA_CONFIG(COUNT_SYSCALL_TIME)
explicit ScopedSyscallTimer(PartitionRoot<>* root)
: root_(root), tick_(base::TimeTicks::Now()) {}
~ScopedSyscallTimer() {
root_->syscall_count.fetch_add(1, std::memory_order_relaxed);
int64_t elapsed_nanos = (base::TimeTicks::Now() - tick_).InNanoseconds();
if (elapsed_nanos > 0) {
root_->syscall_total_time_ns.fetch_add(
static_cast<uint64_t>(elapsed_nanos), std::memory_order_relaxed);
}
}
private:
PartitionRoot<>* root_;
const base::TimeTicks tick_;
#else
explicit ScopedSyscallTimer(PartitionRoot<>* root) {
root->syscall_count.fetch_add(1, std::memory_order_relaxed);
}
#endif
};
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
PA_ALWAYS_INLINE uintptr_t
PartitionAllocGetDirectMapSlotStartInBRPPool(uintptr_t address) {
PA_DCHECK(IsManagedByPartitionAllocBRPPool(address));
#if BUILDFLAG(HAS_64_BIT_POINTERS)
uintptr_t offset = OffsetInBRPPool(address);
uintptr_t reservation_start =
GetDirectMapReservationStart(address, kBRPPoolHandle, offset);
#else
uintptr_t reservation_start = GetDirectMapReservationStart(address);
#endif
if (!reservation_start) {
return 0;
}
auto* first_page = PartitionPage<ThreadSafe>::FromAddr(reservation_start +
PartitionPageSize());
auto* page = first_page + first_page->slot_span_metadata_offset;
PA_DCHECK(page->is_valid);
PA_DCHECK(!page->slot_span_metadata_offset);
auto* slot_span = &page->slot_span_metadata;
uintptr_t slot_start =
SlotSpanMetadata<ThreadSafe>::ToSlotSpanStart(slot_span);
#if BUILDFLAG(PA_DCHECK_IS_ON)
auto* metadata =
PartitionDirectMapMetadata<ThreadSafe>::FromSlotSpan(slot_span);
size_t padding_for_alignment =
metadata->direct_map_extent.padding_for_alignment;
PA_DCHECK(padding_for_alignment ==
static_cast<size_t>(page - first_page) * PartitionPageSize());
PA_DCHECK(slot_start ==
reservation_start + PartitionPageSize() + padding_for_alignment);
#endif
return slot_start;
}
PA_ALWAYS_INLINE uintptr_t
PartitionAllocGetSlotStartInBRPPool(uintptr_t address) {
address -= kPartitionPastAllocationAdjustment;
PA_DCHECK(IsManagedByNormalBucketsOrDirectMap(address));
DCheckIfManagedByPartitionAllocBRPPool(address);
uintptr_t directmap_slot_start =
PartitionAllocGetDirectMapSlotStartInBRPPool(address);
if (PA_UNLIKELY(directmap_slot_start)) {
return directmap_slot_start;
}
auto* slot_span = SlotSpanMetadata<ThreadSafe>::FromAddr(address);
auto* root = PartitionRoot<ThreadSafe>::FromSlotSpan(slot_span);
PA_DCHECK(root->brp_enabled());
uintptr_t slot_span_start =
SlotSpanMetadata<ThreadSafe>::ToSlotSpanStart(slot_span);
size_t offset_in_slot_span = address - slot_span_start;
auto* bucket = slot_span->bucket;
return slot_span_start +
bucket->slot_size * bucket->GetSlotNumber(offset_in_slot_span);
}
enum class PtrPosWithinAlloc {
kInBounds,
#if BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR)
kAllocEnd,
#endif
kFarOOB
};
PA_COMPONENT_EXPORT(PARTITION_ALLOC)
PtrPosWithinAlloc IsPtrWithinSameAlloc(uintptr_t orig_address,
uintptr_t test_address,
size_t type_size);
PA_ALWAYS_INLINE void PartitionAllocFreeForRefCounting(uintptr_t slot_start) {
PA_DCHECK(!PartitionRefCountPointer(slot_start)->IsAlive());
auto* slot_span = SlotSpanMetadata<ThreadSafe>::FromSlotStart(slot_start);
auto* root = PartitionRoot<ThreadSafe>::FromSlotSpan(slot_span);
PA_DCHECK(root->brp_enabled());
#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
auto hook = PartitionAllocHooks::GetQuarantineOverrideHook();
if (PA_LIKELY(!hook)) {
unsigned char* object =
static_cast<unsigned char*>(root->SlotStartToObject(slot_start));
for (size_t i = 0; i < slot_span->GetUsableSize(root); ++i) {
PA_DCHECK(object[i] == kQuarantinedByte);
}
}
DebugMemset(SlotStartAddr2Ptr(slot_start), kFreedByte,
slot_span->GetUtilizedSlotSize()
#if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
- sizeof(PartitionRefCount)
#endif
);
#endif
root->total_size_of_brp_quarantined_bytes.fetch_sub(
slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
root->total_count_of_brp_quarantined_slots.fetch_sub(
1, std::memory_order_relaxed);
root->RawFreeWithThreadCache(slot_start, slot_span);
}
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE uintptr_t
PartitionRoot<thread_safe>::AllocFromBucket(Bucket* bucket,
unsigned int flags,
size_t raw_size,
size_t slot_span_alignment,
size_t* usable_size,
bool* is_already_zeroed) {
PA_DCHECK((slot_span_alignment >= internal::PartitionPageSize()) &&
internal::base::bits::IsPowerOfTwo(slot_span_alignment));
#if defined(OHOS_ENABLE_POINTER_HARDENED)
SlotSpan* slot_span = (SlotSpan*)EncodeBucket((void*)bucket->active_slot_spans_head);
#else
SlotSpan* slot_span = bucket->active_slot_spans_head;
#endif
PA_DCHECK(slot_span);
PA_DCHECK(!slot_span->marked_full);
uintptr_t slot_start =
internal::SlotStartPtr2Addr(slot_span->get_freelist_head());
if (PA_LIKELY(slot_span_alignment <= internal::PartitionPageSize() &&
slot_start)) {
*is_already_zeroed = false;
*usable_size = AdjustSizeForExtrasSubtract(bucket->slot_size);
PA_DCHECK(*usable_size == slot_span->GetUsableSize(this));
PA_DCHECK(IsValidSlotSpan(slot_span));
PA_DCHECK(!slot_span->CanStoreRawSize());
PA_DCHECK(!slot_span->bucket->is_direct_mapped());
void* entry = slot_span->PopForAlloc(bucket->slot_size);
PA_DCHECK(internal::SlotStartPtr2Addr(entry) == slot_start);
PA_DCHECK(slot_span->bucket == bucket);
} else {
slot_start = bucket->SlowPathAlloc(this, flags, raw_size,
slot_span_alignment, is_already_zeroed);
if (PA_UNLIKELY(!slot_start)) {
return 0;
}
slot_span = SlotSpan::FromSlotStart(slot_start);
PA_DCHECK(IsValidSlotSpan(slot_span));
PA_DCHECK((slot_span->bucket == bucket) ||
(slot_span->bucket->is_direct_mapped() &&
(bucket == &sentinel_bucket)));
*usable_size = slot_span->GetUsableSize(this);
}
PA_DCHECK(slot_span->GetUtilizedSlotSize() <= slot_span->bucket->slot_size);
IncreaseTotalSizeOfAllocatedBytes(
slot_start, slot_span->GetSlotSizeForBookkeeping(), raw_size);
#if BUILDFLAG(USE_FREESLOT_BITMAP)
if (!slot_span->bucket->is_direct_mapped()) {
internal::FreeSlotBitmapMarkSlotAsUsed(slot_start);
}
#endif
return slot_start;
}
template <bool thread_safe>
PA_NOINLINE void PartitionRoot<thread_safe>::Free(void* object) {
return FreeWithFlags(0, object);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeWithFlags(
unsigned int flags,
void* object) {
PA_DCHECK(flags < FreeFlags::kLastFlag << 1);
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
if (!(flags & FreeFlags::kNoMemoryToolOverride)) {
free(object);
return;
}
#endif
if (PA_UNLIKELY(!object)) {
return;
}
if (PartitionAllocHooks::AreHooksEnabled()) {
PartitionAllocHooks::FreeObserverHookIfEnabled(object);
if (PartitionAllocHooks::FreeOverrideHookIfEnabled(object)) {
return;
}
}
FreeNoHooks(object);
}
template <bool thread_safe>
PA_ALWAYS_INLINE bool PartitionRoot<thread_safe>::IsMemoryTaggingEnabled()
const {
#if PA_CONFIG(HAS_MEMORY_TAGGING)
return !flags.use_configurable_pool;
#else
return false;
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeNoHooks(void* object) {
if (PA_UNLIKELY(!object)) {
return;
}
PA_PREFETCH(object);
uintptr_t object_addr = internal::ObjectPtr2Addr(object);
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
(BUILDFLAG(IS_ANDROID) && !BUILDFLAG(PA_IS_CAST_ANDROID))
PA_CHECK(IsManagedByPartitionAlloc(object_addr));
#endif
auto* root = FromAddrInFirstSuperpage(object_addr);
SlotSpan* slot_span = SlotSpan::FromObject(object);
PA_DCHECK(FromSlotSpan(slot_span) == root);
uintptr_t slot_start = root->ObjectToSlotStart(object);
PA_DCHECK(slot_span == SlotSpan::FromSlotStart(slot_start));
#if PA_CONFIG(HAS_MEMORY_TAGGING)
if (PA_LIKELY(root->IsMemoryTaggingEnabled())) {
const size_t slot_size = slot_span->bucket->slot_size;
if (PA_LIKELY(slot_size <= internal::kMaxMemoryTaggingSize)) {
internal::TagMemoryRangeIncrement(internal::TagAddr(slot_start),
slot_size);
object = internal::TagPtr(object);
}
}
#else
PA_PREFETCH(slot_span);
#endif
#if BUILDFLAG(USE_STARSCAN)
if (PA_UNLIKELY(root->ShouldQuarantine(object))) {
PCScan::JoinScanIfNeeded();
if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
PCScan::MoveToQuarantine(object, slot_span->GetUsableSize(root),
slot_start, slot_span->bucket->slot_size);
return;
}
}
#endif
root->FreeNoHooksImmediate(object, slot_span, slot_start);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeNoHooksImmediate(
void* object,
SlotSpan* slot_span,
uintptr_t slot_start) {
PA_DCHECK(object);
PA_DCHECK(slot_span);
PA_DCHECK(IsValidSlotSpan(slot_span));
PA_DCHECK(slot_start);
#if BUILDFLAG(PA_DCHECK_IS_ON)
if (flags.allow_cookie) {
internal::PartitionCookieCheckValue(static_cast<unsigned char*>(object) +
slot_span->GetUsableSize(this));
}
#endif
#if BUILDFLAG(USE_STARSCAN)
if (PA_UNLIKELY(IsQuarantineEnabled())) {
if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
internal::StateBitmapFromAddr(slot_start)->Free(slot_start);
}
}
#endif
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (brp_enabled()) {
auto* ref_count = internal::PartitionRefCountPointer(slot_start);
if (PA_UNLIKELY(!ref_count->IsAliveWithNoKnownRefs() &&
brp_zapping_enabled())) {
auto usable_size = slot_span->GetUsableSize(this);
auto hook = PartitionAllocHooks::GetQuarantineOverrideHook();
if (PA_UNLIKELY(hook)) {
hook(object, usable_size);
} else {
internal::SecureMemset(object, internal::kQuarantinedByte, usable_size);
}
}
if (PA_UNLIKELY(!(ref_count->ReleaseFromAllocator()))) {
total_size_of_brp_quarantined_bytes.fetch_add(
slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
total_count_of_brp_quarantined_slots.fetch_add(1,
std::memory_order_relaxed);
cumulative_size_of_brp_quarantined_bytes.fetch_add(
slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
cumulative_count_of_brp_quarantined_slots.fetch_add(
1, std::memory_order_relaxed);
return;
}
}
#endif
#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
internal::DebugMemset(internal::SlotStartAddr2Ptr(slot_start),
internal::kFreedByte,
slot_span->GetUtilizedSlotSize()
#if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
- sizeof(internal::PartitionRefCount)
#endif
);
#elif PA_CONFIG(ZERO_RANDOMLY_ON_FREE)
if (PA_UNLIKELY(internal::RandomPeriod()) &&
!IsDirectMappedBucket(slot_span->bucket)) {
internal::SecureMemset(internal::SlotStartAddr2Ptr(slot_start), 0,
slot_span->GetUtilizedSlotSize()
#if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
- sizeof(internal::PartitionRefCount)
#endif
);
}
#endif
RawFreeWithThreadCache(slot_start, slot_span);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeInSlotSpan(
uintptr_t slot_start,
SlotSpan* slot_span) {
DecreaseTotalSizeOfAllocatedBytes(slot_start,
slot_span->GetSlotSizeForBookkeeping());
#if BUILDFLAG(USE_FREESLOT_BITMAP)
if (!slot_span->bucket->is_direct_mapped()) {
internal::FreeSlotBitmapMarkSlotAsFree(slot_start);
}
#endif
return slot_span->Free(slot_start);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFree(
uintptr_t slot_start) {
SlotSpan* slot_span = SlotSpan::FromSlotStart(slot_start);
RawFree(slot_start, slot_span);
}
#if PA_CONFIG(IS_NONCLANG_MSVC)
#pragma optimize("", off)
#endif
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFree(uintptr_t slot_start,
SlotSpan* slot_span) {
*static_cast<volatile uintptr_t*>(internal::SlotStartAddr2Ptr(slot_start)) =
0;
#if !(PA_CONFIG(IS_NONCLANG_MSVC))
__asm__ __volatile__("" : : "r"(slot_start) : "memory");
#endif
::partition_alloc::internal::ScopedGuard guard{lock_};
FreeInSlotSpan(slot_start, slot_span);
}
#if PA_CONFIG(IS_NONCLANG_MSVC)
#pragma optimize("", on)
#endif
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFreeBatch(
FreeListEntry* head,
FreeListEntry* tail,
size_t size,
SlotSpan* slot_span) {
PA_DCHECK(head);
PA_DCHECK(tail);
PA_DCHECK(size > 0);
PA_DCHECK(slot_span);
PA_DCHECK(IsValidSlotSpan(slot_span));
::partition_alloc::internal::ScopedGuard guard{lock_};
DecreaseTotalSizeOfAllocatedBytes(
0u, slot_span->GetSlotSizeForBookkeeping() * size);
slot_span->AppendFreeList(head, tail, size);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFreeWithThreadCache(
uintptr_t slot_start,
SlotSpan* slot_span) {
ThreadCache* thread_cache = GetThreadCache();
if (PA_LIKELY(ThreadCache::IsValid(thread_cache) &&
!IsDirectMappedBucket(slot_span->bucket))) {
size_t bucket_index =
static_cast<size_t>(slot_span->bucket - this->buckets);
size_t slot_size;
if (PA_LIKELY(thread_cache->MaybePutInCache(slot_start, bucket_index,
&slot_size))) {
PA_DCHECK(!slot_span->CanStoreRawSize());
size_t usable_size = AdjustSizeForExtrasSubtract(slot_size);
PA_DCHECK(usable_size == slot_span->GetUsableSize(this));
thread_cache->RecordDeallocation(usable_size);
return;
}
}
if (PA_LIKELY(ThreadCache::IsValid(thread_cache))) {
size_t usable_size = slot_span->GetUsableSize(this);
thread_cache->RecordDeallocation(usable_size);
}
RawFree(slot_start, slot_span);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFreeLocked(
uintptr_t slot_start) {
SlotSpan* slot_span = SlotSpan::FromSlotStart(slot_start);
PA_DCHECK(!IsDirectMappedBucket(slot_span->bucket));
FreeInSlotSpan(slot_start, slot_span);
}
template <bool thread_safe>
PA_ALWAYS_INLINE bool PartitionRoot<thread_safe>::IsValidSlotSpan(
SlotSpan* slot_span) {
PartitionRoot* root = FromSlotSpan(slot_span);
return root->inverted_self == ~reinterpret_cast<uintptr_t>(root);
}
template <bool thread_safe>
PA_ALWAYS_INLINE PartitionRoot<thread_safe>*
PartitionRoot<thread_safe>::FromSlotSpan(SlotSpan* slot_span) {
auto* extent_entry = reinterpret_cast<SuperPageExtentEntry*>(
reinterpret_cast<uintptr_t>(slot_span) & internal::SystemPageBaseMask());
#if defined(OHOS_ENABLE_POINTER_HARDENED)
PartitionRoot* real_root = (PartitionRoot*)EncodeRoot((void*)(extent_entry->root));
return real_root;
#else
return extent_entry->root;
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE PartitionRoot<thread_safe>*
PartitionRoot<thread_safe>::FromFirstSuperPage(uintptr_t super_page) {
PA_DCHECK(internal::IsReservationStart(super_page));
auto* extent_entry =
internal::PartitionSuperPageToExtent<thread_safe>(super_page);
#if defined(OHOS_ENABLE_POINTER_HARDENED)
PartitionRoot* root = (PartitionRoot*)EncodeRoot((void*)(extent_entry->root));
#else
PartitionRoot* root = extent_entry->root;
#endif
PA_DCHECK(root->inverted_self == ~reinterpret_cast<uintptr_t>(root));
return root;
}
template <bool thread_safe>
PA_ALWAYS_INLINE PartitionRoot<thread_safe>*
PartitionRoot<thread_safe>::FromAddrInFirstSuperpage(uintptr_t address) {
uintptr_t super_page = address & internal::kSuperPageBaseMask;
PA_DCHECK(internal::IsReservationStart(super_page));
return FromFirstSuperPage(super_page);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void
PartitionRoot<thread_safe>::IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len,
size_t raw_size) {
total_size_of_allocated_bytes += len;
max_size_of_allocated_bytes =
std::max(max_size_of_allocated_bytes, total_size_of_allocated_bytes);
#if BUILDFLAG(RECORD_ALLOC_INFO)
partition_alloc::internal::RecordAllocOrFree(addr | 0x01, raw_size);
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE void
PartitionRoot<thread_safe>::DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len) {
PA_DCHECK(total_size_of_allocated_bytes >= len);
total_size_of_allocated_bytes -= len;
#if BUILDFLAG(RECORD_ALLOC_INFO)
partition_alloc::internal::RecordAllocOrFree(addr | 0x00, len);
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::IncreaseCommittedPages(
size_t len) {
const auto old_total =
total_size_of_committed_pages.fetch_add(len, std::memory_order_relaxed);
const auto new_total = old_total + len;
size_t expected = max_size_of_committed_pages.load(std::memory_order_relaxed);
size_t desired;
do {
desired = std::max(expected, new_total);
} while (!max_size_of_committed_pages.compare_exchange_weak(
expected, desired, std::memory_order_relaxed, std::memory_order_relaxed));
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::DecreaseCommittedPages(
size_t len) {
total_size_of_committed_pages.fetch_sub(len, std::memory_order_relaxed);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::DecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
internal::ScopedSyscallTimer timer{this};
DecommitSystemPages(address, length, accessibility_disposition);
DecreaseCommittedPages(length);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
internal::ScopedSyscallTimer timer{this};
bool ok = TryRecommitSystemPages(address, length, GetPageAccessibility(),
accessibility_disposition);
if (PA_UNLIKELY(!ok)) {
DecommitEmptySlotSpans();
RecommitSystemPages(address, length, GetPageAccessibility(),
accessibility_disposition);
}
IncreaseCommittedPages(length);
}
template <bool thread_safe>
PA_ALWAYS_INLINE bool PartitionRoot<thread_safe>::TryRecommitSystemPagesForData(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
internal::ScopedSyscallTimer timer{this};
bool ok = TryRecommitSystemPages(address, length, GetPageAccessibility(),
accessibility_disposition);
if (PA_UNLIKELY(!ok)) {
{
::partition_alloc::internal::ScopedGuard guard(lock_);
DecommitEmptySlotSpans();
}
ok = TryRecommitSystemPages(address, length, GetPageAccessibility(),
accessibility_disposition);
}
if (ok) {
IncreaseCommittedPages(length);
}
return ok;
}
template <bool thread_safe>
PA_ALWAYS_INLINE size_t PartitionRoot<thread_safe>::GetUsableSize(void* ptr) {
if (!ptr) {
return 0;
}
auto* slot_span = SlotSpan::FromObjectInnerPtr(ptr);
auto* root = FromSlotSpan(slot_span);
return slot_span->GetUsableSize(root);
}
template <bool thread_safe>
PA_ALWAYS_INLINE size_t
PartitionRoot<thread_safe>::GetUsableSizeWithMac11MallocSizeHack(void* ptr) {
if (!ptr) {
return 0;
}
auto* slot_span = SlotSpan::FromObjectInnerPtr(ptr);
auto* root = FromSlotSpan(slot_span);
size_t usable_size = slot_span->GetUsableSize(root);
#if PA_CONFIG(ENABLE_MAC11_MALLOC_SIZE_HACK)
if (PA_UNLIKELY(root->flags.mac11_malloc_size_hack_enabled_ &&
usable_size == internal::kMac11MallocSizeHackUsableSize)) {
uintptr_t slot_start =
internal::PartitionAllocGetSlotStartInBRPPool(UntagPtr(ptr));
auto* ref_count = internal::PartitionRefCountPointer(slot_start);
if (ref_count->NeedsMac11MallocSizeHack()) {
return internal::kMac11MallocSizeHackRequestedSize;
}
}
#endif
return usable_size;
}
template <bool thread_safe>
PA_ALWAYS_INLINE PageAccessibilityConfiguration
PartitionRoot<thread_safe>::GetPageAccessibility() const {
PageAccessibilityConfiguration::Permissions permissions =
PageAccessibilityConfiguration::kReadWrite;
#if PA_CONFIG(HAS_MEMORY_TAGGING)
if (IsMemoryTaggingEnabled()) {
permissions = PageAccessibilityConfiguration::kReadWriteTagged;
}
#endif
#if BUILDFLAG(ENABLE_PKEYS)
return PageAccessibilityConfiguration(permissions, flags.pkey);
#else
return PageAccessibilityConfiguration(permissions);
#endif
}
template <bool thread_safe>
PA_ALWAYS_INLINE PageAccessibilityConfiguration
PartitionRoot<thread_safe>::PageAccessibilityWithPkeyIfEnabled(
PageAccessibilityConfiguration::Permissions permissions) const {
#if BUILDFLAG(ENABLE_PKEYS)
return PageAccessibilityConfiguration(permissions, flags.pkey);
#endif
return PageAccessibilityConfiguration(permissions);
}
template <bool thread_safe>
PA_ALWAYS_INLINE size_t
PartitionRoot<thread_safe>::AllocationCapacityFromSlotStart(
uintptr_t slot_start) const {
auto* slot_span = SlotSpan::FromSlotStart(slot_start);
return AdjustSizeForExtrasSubtract(slot_span->bucket->slot_size);
}
template <bool thread_safe>
PA_ALWAYS_INLINE uint16_t PartitionRoot<thread_safe>::SizeToBucketIndex(
size_t size,
BucketDistribution bucket_distribution) {
switch (bucket_distribution) {
case BucketDistribution::kDefault:
return internal::BucketIndexLookup::GetIndexForDefaultBuckets(size);
case BucketDistribution::kDenser:
return internal::BucketIndexLookup::GetIndexForDenserBuckets(size);
}
}
template <bool thread_safe>
PA_ALWAYS_INLINE void* PartitionRoot<thread_safe>::AllocWithFlags(
unsigned int flags,
size_t requested_size,
const char* type_name) {
return AllocWithFlagsInternal(flags, requested_size,
internal::PartitionPageSize(), type_name);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void* PartitionRoot<thread_safe>::AllocWithFlagsInternal(
unsigned int flags,
size_t requested_size,
size_t slot_span_alignment,
const char* type_name) {
PA_DCHECK(
(slot_span_alignment >= internal::PartitionPageSize()) &&
partition_alloc::internal::base::bits::IsPowerOfTwo(slot_span_alignment));
PA_DCHECK(flags < AllocFlags::kLastFlag << 1);
PA_DCHECK((flags & AllocFlags::kNoHooks) == 0);
PA_DCHECK(initialized);
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
if (!(flags & AllocFlags::kNoMemoryToolOverride)) {
CHECK_MAX_SIZE_OR_RETURN_NULLPTR(requested_size, flags);
const bool zero_fill = flags & AllocFlags::kZeroFill;
void* result =
zero_fill ? calloc(1, requested_size) : malloc(requested_size);
PA_CHECK(result || flags & AllocFlags::kReturnNull);
return result;
}
#endif
void* object = nullptr;
const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
if (PA_UNLIKELY(hooks_enabled)) {
if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(
&object, flags, requested_size, type_name)) {
PartitionAllocHooks::AllocationObserverHookIfEnabled(
object, requested_size, type_name);
return object;
}
}
object = AllocWithFlagsNoHooks(flags, requested_size, slot_span_alignment);
if (PA_UNLIKELY(hooks_enabled)) {
PartitionAllocHooks::AllocationObserverHookIfEnabled(object, requested_size,
type_name);
}
return object;
}
template <bool thread_safe>
PA_ALWAYS_INLINE void* PartitionRoot<thread_safe>::AllocWithFlagsNoHooks(
unsigned int flags,
size_t requested_size,
size_t slot_span_alignment) {
PA_DCHECK(
(slot_span_alignment >= internal::PartitionPageSize()) &&
partition_alloc::internal::base::bits::IsPowerOfTwo(slot_span_alignment));
size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
PA_CHECK(raw_size >= requested_size);
uint16_t bucket_index =
SizeToBucketIndex(raw_size, this->GetBucketDistribution());
size_t usable_size;
bool is_already_zeroed = false;
uintptr_t slot_start = 0;
size_t slot_size;
#if BUILDFLAG(USE_STARSCAN)
const bool is_quarantine_enabled = IsQuarantineEnabled();
if (PA_UNLIKELY(is_quarantine_enabled)) {
PCScan::JoinScanIfNeeded();
}
#endif
auto* thread_cache = GetOrCreateThreadCache();
if (PA_LIKELY(ThreadCache::IsValid(thread_cache) &&
slot_span_alignment <= internal::PartitionPageSize())) {
slot_start = thread_cache->GetFromCache(bucket_index, &slot_size);
if (PA_LIKELY(slot_start)) {
usable_size = AdjustSizeForExtrasSubtract(slot_size);
#if BUILDFLAG(PA_DCHECK_IS_ON)
SlotSpan* slot_span = SlotSpan::FromSlotStart(slot_start);
PA_DCHECK(IsValidSlotSpan(slot_span));
PA_DCHECK(slot_span->bucket == &bucket_at(bucket_index));
PA_DCHECK(slot_span->bucket->slot_size == slot_size);
PA_DCHECK(usable_size == slot_span->GetUsableSize(this));
PA_DCHECK(!slot_span->CanStoreRawSize());
PA_DCHECK(!slot_span->bucket->is_direct_mapped());
#endif
} else {
slot_start =
RawAlloc(buckets + bucket_index, flags, raw_size, slot_span_alignment,
&usable_size, &is_already_zeroed);
}
} else {
slot_start =
RawAlloc(buckets + bucket_index, flags, raw_size, slot_span_alignment,
&usable_size, &is_already_zeroed);
}
if (PA_UNLIKELY(!slot_start)) {
return nullptr;
}
if (PA_LIKELY(ThreadCache::IsValid(thread_cache))) {
thread_cache->RecordAllocation(usable_size);
}
void* object = SlotStartToObject(slot_start);
#if BUILDFLAG(PA_DCHECK_IS_ON)
if (this->flags.allow_cookie) {
internal::PartitionCookieWriteValue(static_cast<unsigned char*>(object) +
usable_size);
}
#endif
bool zero_fill = flags & AllocFlags::kZeroFill;
if (PA_LIKELY(!zero_fill)) {
#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
internal::DebugMemset(object, internal::kUninitializedByte, usable_size);
#endif
} else if (!is_already_zeroed) {
memset(object, 0, usable_size);
}
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (brp_enabled()) {
bool needs_mac11_malloc_size_hack = false;
#if PA_CONFIG(ENABLE_MAC11_MALLOC_SIZE_HACK)
if (PA_UNLIKELY(this->flags.mac11_malloc_size_hack_enabled_ &&
requested_size ==
internal::kMac11MallocSizeHackRequestedSize)) {
needs_mac11_malloc_size_hack = true;
}
#endif
auto* ref_count = new (internal::PartitionRefCountPointer(slot_start))
internal::PartitionRefCount(needs_mac11_malloc_size_hack);
#if PA_CONFIG(REF_COUNT_STORE_REQUESTED_SIZE)
ref_count->SetRequestedSize(requested_size);
#else
(void)ref_count;
#endif
}
#endif
#if BUILDFLAG(USE_STARSCAN)
if (PA_UNLIKELY(is_quarantine_enabled)) {
if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
internal::StateBitmapFromAddr(slot_start)->Allocate(slot_start);
}
}
#endif
return object;
}
template <bool thread_safe>
PA_ALWAYS_INLINE uintptr_t
PartitionRoot<thread_safe>::RawAlloc(Bucket* bucket,
unsigned int flags,
size_t raw_size,
size_t slot_span_alignment,
size_t* usable_size,
bool* is_already_zeroed) {
::partition_alloc::internal::ScopedGuard guard{lock_};
return AllocFromBucket(bucket, flags, raw_size, slot_span_alignment,
usable_size, is_already_zeroed);
}
template <bool thread_safe>
PA_ALWAYS_INLINE void* PartitionRoot<thread_safe>::AlignedAllocWithFlags(
unsigned int flags,
size_t alignment,
size_t requested_size) {
PA_DCHECK(this->flags.allow_aligned_alloc);
PA_DCHECK(!this->flags.extras_offset);
PA_CHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(alignment));
PA_CHECK(alignment <= internal::kMaxSupportedAlignment);
size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
size_t adjusted_size = requested_size;
if (alignment <= internal::PartitionPageSize()) {
if (PA_UNLIKELY(raw_size < alignment)) {
raw_size = alignment;
} else {
raw_size =
static_cast<size_t>(1)
<< (int{sizeof(size_t) * 8} -
partition_alloc::internal::base::bits::CountLeadingZeroBits(
raw_size - 1));
}
PA_DCHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(raw_size));
adjusted_size = AdjustSizeForExtrasSubtract(raw_size);
if (PA_UNLIKELY(adjusted_size < requested_size)) {
if (flags & AllocFlags::kReturnNull) {
return nullptr;
}
internal::PartitionExcessiveAllocationSize(requested_size);
PA_NOTREACHED();
}
}
size_t slot_span_alignment =
std::max(alignment, internal::PartitionPageSize());
bool no_hooks = flags & AllocFlags::kNoHooks;
void* object =
no_hooks
? AllocWithFlagsNoHooks(0, adjusted_size, slot_span_alignment)
: AllocWithFlagsInternal(0, adjusted_size, slot_span_alignment, "");
PA_CHECK(!(reinterpret_cast<uintptr_t>(object) & (alignment - 1)));
return object;
}
template <bool thread_safe>
PA_NOINLINE void* PartitionRoot<thread_safe>::Alloc(size_t requested_size,
const char* type_name) {
return AllocWithFlags(0, requested_size, type_name);
}
template <bool thread_safe>
PA_NOINLINE void* PartitionRoot<thread_safe>::Realloc(void* ptr,
size_t new_size,
const char* type_name) {
return ReallocWithFlags(0, ptr, new_size, type_name);
}
template <bool thread_safe>
PA_NOINLINE void* PartitionRoot<thread_safe>::TryRealloc(
void* ptr,
size_t new_size,
const char* type_name) {
return ReallocWithFlags(AllocFlags::kReturnNull, ptr, new_size, type_name);
}
template <bool thread_safe>
PA_ALWAYS_INLINE size_t
PartitionRoot<thread_safe>::AllocationCapacityFromRequestedSize(
size_t size) const {
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
return size;
#else
PA_DCHECK(PartitionRoot<thread_safe>::initialized);
size = AdjustSizeForExtrasAdd(size);
auto& bucket = bucket_at(SizeToBucketIndex(size, GetBucketDistribution()));
PA_DCHECK(!bucket.slot_size || bucket.slot_size >= size);
PA_DCHECK(!(bucket.slot_size % internal::kSmallestBucket));
if (PA_LIKELY(!bucket.is_direct_mapped())) {
size = bucket.slot_size;
} else if (size > internal::MaxDirectMapped()) {
} else {
size = GetDirectMapSlotSize(size);
}
size = AdjustSizeForExtrasSubtract(size);
return size;
#endif
}
template <bool thread_safe>
ThreadCache* PartitionRoot<thread_safe>::GetOrCreateThreadCache() {
ThreadCache* thread_cache = nullptr;
if (PA_LIKELY(flags.with_thread_cache)) {
thread_cache = ThreadCache::Get();
if (PA_UNLIKELY(!ThreadCache::IsValid(thread_cache))) {
thread_cache = MaybeInitThreadCache();
}
}
return thread_cache;
}
template <bool thread_safe>
ThreadCache* PartitionRoot<thread_safe>::GetThreadCache() {
return PA_LIKELY(flags.with_thread_cache) ? ThreadCache::Get() : nullptr;
}
using ThreadSafePartitionRoot = PartitionRoot<internal::ThreadSafe>;
static_assert(offsetof(ThreadSafePartitionRoot, lock_) ==
internal::kPartitionCachelineSize,
"Padding is incorrect");
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
using ::partition_alloc::internal::PartitionAllocGetSlotStartInBRPPool;
#endif
}
#endif