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