1 // Copyright (c) 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_BUCKET_H_ 6 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_BUCKET_H_ 7 8 #include <stddef.h> 9 #include <stdint.h> 10 11 #include "base/allocator/partition_allocator/partition_alloc_check.h" 12 #include "base/allocator/partition_allocator/partition_alloc_constants.h" 13 #include "base/allocator/partition_allocator/partition_alloc_forward.h" 14 #include "base/base_export.h" 15 #include "base/check.h" 16 #include "base/compiler_specific.h" 17 #include "base/thread_annotations.h" 18 19 namespace base { 20 namespace internal { 21 22 template <bool thread_safe> 23 struct PartitionBucket { 24 // Accessed most in hot path => goes first. 25 SlotSpanMetadata<thread_safe>* active_slot_spans_head; 26 27 SlotSpanMetadata<thread_safe>* empty_slot_spans_head; 28 SlotSpanMetadata<thread_safe>* decommitted_slot_spans_head; 29 uint32_t slot_size; 30 uint32_t num_system_pages_per_slot_span : 8; 31 uint32_t num_full_slot_spans : 24; 32 33 // `slot_size_reciprocal` is used to improve the performance of 34 // `GetSlotOffset`. It is computed as `(1 / size) * (2 ** M)` where M is 35 // chosen to provide the desired accuracy. As a result, we can replace a slow 36 // integer division (or modulo) operation with a pair of multiplication and a 37 // bit shift, i.e. `value / size` becomes `(value * size_reciprocal) >> M`. 38 uint64_t slot_size_reciprocal; 39 40 // This is `M` from the formula above. For accurate results, both `value` and 41 // `size`, which are bound by `kMaxBucketed` for our purposes, must be less 42 // than `2 ** (M / 2)`. On the other hand, the result of the expression 43 // `3 * M / 2` must be less than 64, otherwise integer overflow can occur. 44 static constexpr uint64_t kReciprocalShift = 42; 45 static constexpr uint64_t kReciprocalMask = (1ull << kReciprocalShift) - 1; 46 static_assert( 47 kMaxBucketed < (1 << (kReciprocalShift / 2)), 48 "GetSlotOffset may produce an incorrect result when kMaxBucketed is too " 49 "large."); 50 51 // Public API. 52 void Init(uint32_t new_slot_size); 53 54 // Sets |is_already_zeroed| to true if the allocation was satisfied by 55 // requesting (a) new page(s) from the operating system, or false otherwise. 56 // This enables an optimization for when callers use |PartitionAllocZeroFill|: 57 // there is no need to call memset on fresh pages; the OS has already zeroed 58 // them. (See |PartitionRoot::AllocFromBucket|.) 59 // 60 // Note the matching Free() functions are in SlotSpanMetadata. 61 BASE_EXPORT NOINLINE void* SlowPathAlloc(PartitionRoot<thread_safe>* root, 62 int flags, 63 size_t raw_size, 64 bool* is_already_zeroed) 65 EXCLUSIVE_LOCKS_REQUIRED(root->lock_); 66 is_direct_mappedPartitionBucket67 ALWAYS_INLINE bool is_direct_mapped() const { 68 return !num_system_pages_per_slot_span; 69 } get_bytes_per_spanPartitionBucket70 ALWAYS_INLINE size_t get_bytes_per_span() const { 71 // TODO(ajwong): Change to CheckedMul. https://crbug.com/787153 72 // https://crbug.com/680657 73 return num_system_pages_per_slot_span * SystemPageSize(); 74 } get_slots_per_spanPartitionBucket75 ALWAYS_INLINE uint16_t get_slots_per_span() const { 76 // TODO(ajwong): Change to CheckedMul. https://crbug.com/787153 77 // https://crbug.com/680657 78 return static_cast<uint16_t>(get_bytes_per_span() / slot_size); 79 } 80 // Returns a natural number of partition pages (calculated by 81 // get_system_pages_per_slot_span()) to allocate from the current 82 // super page when the bucket runs out of slots. get_pages_per_slot_spanPartitionBucket83 ALWAYS_INLINE uint16_t get_pages_per_slot_span() const { 84 // Rounds up to nearest multiple of NumSystemPagesPerPartitionPage(). 85 return (num_system_pages_per_slot_span + 86 (NumSystemPagesPerPartitionPage() - 1)) / 87 NumSystemPagesPerPartitionPage(); 88 } 89 get_direct_map_sizePartitionBucket90 static ALWAYS_INLINE size_t get_direct_map_size(size_t size) { 91 // Caller must check that the size is not above the MaxDirectMapped() 92 // limit before calling. This also guards against integer overflow in the 93 // calculation here. 94 PA_DCHECK(size <= MaxDirectMapped()); 95 return (size + SystemPageOffsetMask()) & SystemPageBaseMask(); 96 } 97 98 // This helper function scans a bucket's active slot span list for a suitable 99 // new active slot span. When it finds a suitable new active slot span (one 100 // that has free slots and is not empty), it is set as the new active slot 101 // span. If there is no suitable new active slot span, the current active slot 102 // span is set to SlotSpanMetadata::get_sentinel_slot_span(). As potential 103 // slot spans are scanned, they are tidied up according to their state. Empty 104 // slot spans are swept on to the empty list, decommitted slot spans on to the 105 // decommitted list and full slot spans are unlinked from any list. 106 // 107 // This is where the guts of the bucket maintenance is done! 108 bool SetNewActiveSlotSpan(); 109 110 // Returns an offset within an allocation slot. GetSlotOffsetPartitionBucket111 ALWAYS_INLINE size_t GetSlotOffset(size_t offset_in_slot_span) { 112 // Knowing that slots are tightly packed in a slot span, calculate an offset 113 // using an equivalent of a modulo operation. 114 115 // See the static assertion for `kReciprocalShift` above. 116 PA_DCHECK(offset_in_slot_span <= kMaxBucketed); 117 PA_DCHECK(slot_size <= kMaxBucketed); 118 119 // Calculate `decimal_part{offset_in_slot / size} * (2 ** M)` first. 120 uint64_t offset_in_slot = 121 (offset_in_slot_span * slot_size_reciprocal) & kReciprocalMask; 122 123 // (decimal_part * size) * (2 ** M) == offset_in_slot_span % size * (2 ** M) 124 // Divide by `2 ** M` using a bit shift. 125 offset_in_slot = (offset_in_slot * slot_size) >> kReciprocalShift; 126 PA_DCHECK(offset_in_slot_span % slot_size == offset_in_slot); 127 128 return static_cast<size_t>(offset_in_slot); 129 } 130 131 // Returns a slot number starting from the beginning of the slot span. GetSlotNumberPartitionBucket132 ALWAYS_INLINE size_t GetSlotNumber(size_t offset_in_slot_span) { 133 // See the static assertion for `kReciprocalShift` above. 134 PA_DCHECK(offset_in_slot_span <= kMaxBucketed); 135 PA_DCHECK(slot_size <= kMaxBucketed); 136 137 const size_t offset_in_slot = 138 ((offset_in_slot_span * slot_size_reciprocal) >> kReciprocalShift); 139 PA_DCHECK(offset_in_slot_span / slot_size == offset_in_slot); 140 141 return offset_in_slot; 142 } 143 144 private: 145 static NOINLINE void OnFull(); 146 147 // Returns the number of system pages in a slot span. 148 // 149 // The calculation attempts to find the best number of system pages to 150 // allocate for the given slot_size to minimize wasted space. It uses a 151 // heuristic that looks at number of bytes wasted after the last slot and 152 // attempts to account for the PTE usage of each system page. 153 uint8_t get_system_pages_per_slot_span(); 154 155 // Allocates a new slot span with size |num_partition_pages| from the 156 // current extent. Metadata within this slot span will be uninitialized. 157 // Returns nullptr on error. 158 ALWAYS_INLINE void* AllocNewSlotSpan(PartitionRoot<thread_safe>* root, 159 int flags, 160 uint16_t num_partition_pages, 161 size_t committed_size) 162 EXCLUSIVE_LOCKS_REQUIRED(root->lock_); 163 164 // Each bucket allocates a slot span when it runs out of slots. 165 // A slot span's size is equal to get_pages_per_slot_span() number of 166 // partition pages. This function initializes all PartitionPage within the 167 // span to point to the first PartitionPage which holds all the metadata 168 // for the span (in PartitionPage::SlotSpanMetadata) and registers this bucket 169 // as the owner of the span. It does NOT put the slots into the bucket's 170 // freelist. 171 ALWAYS_INLINE void InitializeSlotSpan( 172 SlotSpanMetadata<thread_safe>* slot_span); 173 174 // Allocates one slot from the given |slot_span| and then adds the remainder 175 // to the current bucket. If the |slot_span| was freshly allocated, it must 176 // have been passed through InitializeSlotSpan() first. 177 ALWAYS_INLINE char* AllocAndFillFreelist( 178 SlotSpanMetadata<thread_safe>* slot_span); 179 }; 180 181 } // namespace internal 182 } // namespace base 183 184 #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_BUCKET_H_ 185