C/C++ API Reference
Loading...
Searching...
No Matches
dl_allocator.h
1// Copyright 2024 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#pragma once
15
16#include <cstddef>
17#include <limits>
18
19#include "pw_allocator/block/detailed_block.h"
20#include "pw_allocator/block_allocator.h"
21#include "pw_allocator/bucket/fast_sorted.h"
22#include "pw_allocator/bucket/unordered.h"
23#include "pw_allocator/config.h"
24
25namespace pw::allocator {
26
28
30template <typename OffsetType>
32
46template <typename BlockType = DlBlock<uintptr_t>>
47class DlAllocator : public BlockAllocator<BlockType> {
48 private:
53
54 static constexpr size_t kMinSize = 8;
55
56 // Cache free blocks with sizes up to 80 bytes.
57 static constexpr size_t kNumFastBins = 10;
58
59 // The number of small bins must be a power of two.
60 static constexpr size_t kNumSmallBins = 64;
61 static_assert((kNumSmallBins & (kNumSmallBins - 1)) == 0);
62
63 // The number of large bins is the sum of all powers of two smaller than the
64 // number of small bins.
65 static constexpr size_t kNumLargeBins = kNumSmallBins - 1;
66
67 // Bit maps are implemented as an array of `uintptr_t`s.
68 static constexpr size_t kBitmapBits = std::numeric_limits<uintptr_t>::digits;
69 static constexpr size_t kNumBitmaps =
70 AlignUp(kNumSmallBins + kNumLargeBins, kBitmapBits) / kBitmapBits;
71
72 public:
74 constexpr DlAllocator();
75
81 DlAllocator(ByteSpan region) : DlAllocator() { Base::Init(region); }
82
83 ~DlAllocator() override { Flush(); }
84
85 private:
87 size_t DoGetMaxAllocatable() override;
88
90 BlockResult<BlockType> ChooseBlock(Layout layout) override;
91
93 void ReserveBlock(BlockType& block) override;
94
96 void RecycleBlock(BlockType& block) override;
97
99 void Flush() override { ReleaseFastBins(); }
100
102 void DeallocateBlock(BlockType*&& block) override;
103
110 size_t GetIndex(size_t size);
111
119 void ReleaseFastBins();
120
125 bool FindNextAvailable(size_t& index);
126
129 void UpdateBitmaps(size_t index, bool empty);
130
131 std::array<FastBin, kNumFastBins> fast_bins_;
132 std::array<SmallBin, kNumSmallBins> small_bins_;
133 std::array<LargeBin, kNumLargeBins> large_bins_;
134 std::array<uintptr_t, kNumBitmaps> bitmaps_;
135};
136
138
139// Template method implementations.
140
141template <typename BlockType>
143 size_t index = 0;
144 size_t size = 0;
145 size_t bin_size = kMinSize;
146 size_t bins_in_round = kNumSmallBins;
147 while (bins_in_round > 1) {
148 for (size_t i = 0; i < bins_in_round; ++i) {
149 size += bin_size;
150 if (index < kNumFastBins) {
151 fast_bins_[index].set_max_inner_size(size);
152 }
153 if (index < kNumSmallBins) {
154 small_bins_[index].set_max_inner_size(size);
155 } else {
156 large_bins_[index - kNumSmallBins].set_max_inner_size(size);
157 }
158 ++index;
159 }
160 bin_size *= 8;
161 bins_in_round /= 2;
162 }
163 bitmaps_.fill(0);
164}
165
166template <typename BlockType>
168 ReleaseFastBins();
169 size_t bitmap_index = bitmaps_.size() - 1;
170 uintptr_t bitmap = bitmaps_[bitmap_index];
171 while (bitmap_index != 0 && bitmap == 0) {
172 --bitmap_index;
173 bitmap = bitmaps_[bitmap_index];
174 }
175 if (bitmap == 0) {
176 // No free blocks.
177 return 0;
178 }
179 size_t bitmap_offset = kBitmapBits - internal::CountLZero(bitmap) - 1;
180 size_t index = bitmap_index * kBitmapBits + bitmap_offset;
181 const BlockType* largest =
182 index < kNumSmallBins ? small_bins_[index].FindLargest()
183 : large_bins_[index - kNumSmallBins].FindLargest();
184 return largest == nullptr ? 0 : largest->InnerSize();
185}
186
187template <typename BlockType>
189 layout = Layout(std::max(layout.size(), kMinSize), layout.alignment());
190 size_t index = GetIndex(layout.size());
191
192 if (index < kNumSmallBins) {
193 // First, check if there's a chunk available in the fast bucket.
194 if (index < kNumFastBins) {
195 FastBin& fast_bin = fast_bins_[index];
196 BlockType* block = fast_bin.RemoveCompatible(layout);
197 if (block != nullptr) {
198 return BlockResult<BlockType>(block);
199 }
200 }
201 // If the corresponding small bucket is empty, release the fast bins to
202 // consolidate chunks and maybe get the requested size.
203 if (small_bins_[index].empty()) {
204 ReleaseFastBins();
205 }
206 } else {
207 // Always release fast bins on large requests.
208 ReleaseFastBins();
209 }
210
211 // Check the small, fixed-size buckets.
212 for (; FindNextAvailable(index) && index < kNumSmallBins; ++index) {
213 SmallBin& small_bin = small_bins_[index];
214 BlockType* block = small_bin.RemoveCompatible(layout);
215 if (block != nullptr) {
216 UpdateBitmaps(index, small_bin.empty());
217 return BlockType::AllocFirst(std::move(block), layout);
218 }
219 }
220
221 // Check the larger, sorted buckets.
222 for (; FindNextAvailable(index); ++index) {
223 LargeBin& large_bin = large_bins_[index - kNumSmallBins];
224 BlockType* block = large_bin.RemoveCompatible(layout);
225 if (block != nullptr) {
226 UpdateBitmaps(index, large_bin.empty());
227 return BlockType::AllocFirst(std::move(block), layout);
228 }
229 }
230
231 // No sufficiently large block found.
232 return BlockResult<BlockType>(nullptr, Status::NotFound());
233}
234
235template <typename BlockType>
237 size_t index = GetIndex(block.InnerSize());
238 if (index < kNumSmallBins) {
239 SmallBin& small_bin = small_bins_[index];
240 std::ignore = small_bin.Remove(block);
241 UpdateBitmaps(index, small_bin.empty());
242 } else {
243 LargeBin& large_bin = large_bins_[index - kNumSmallBins];
244 std::ignore = large_bin.Remove(block);
245 UpdateBitmaps(index, large_bin.empty());
246 }
247}
248
249template <typename BlockType>
251 size_t index = GetIndex(block.InnerSize());
252 if (index < kNumSmallBins) {
253 std::ignore = small_bins_[index].Add(block);
254 } else {
255 std::ignore = large_bins_[index - kNumSmallBins].Add(block);
256 }
257 UpdateBitmaps(index, false);
258}
259
260template <typename BlockType>
262 // Add small blocks to the fast bins without actually freeing them.
263 size_t index = block->InnerSize() / kMinSize;
264 if (index < kNumFastBins) {
265 std::ignore = fast_bins_[index].Add(*block);
266 } else {
267 Base::DeallocateBlock(std::move(block));
268 }
269}
270
271template <typename BlockType>
272size_t DlAllocator<BlockType>::GetIndex(size_t size) {
273 size_t index = 0;
274 size_t bin_size = kMinSize;
275 size_t bins_in_round = kNumSmallBins;
276 size = AlignUp(size, kMinSize) - kMinSize;
277 while (bins_in_round > 1) {
278 size_t round_size = bin_size * bins_in_round;
279 if (size < round_size) {
280 return index + (size / bin_size);
281 }
282 size -= round_size;
283 index += bins_in_round;
284 bin_size *= 8;
285 bins_in_round /= 2;
286 }
287 return index;
288}
289
290template <typename BlockType>
291void DlAllocator<BlockType>::ReleaseFastBins() {
292 for (auto& fast_bin : fast_bins_) {
293 while (!fast_bin.empty()) {
294 BlockType* block = fast_bin.RemoveAny();
295 if constexpr (Hardening::kIncludesDebugChecks) {
296 PW_ASSERT(block != nullptr);
297 }
298 Base::DeallocateBlock(std::move(block));
299 }
300 }
301}
302
303template <typename BlockType>
304bool DlAllocator<BlockType>::FindNextAvailable(size_t& index) {
305 size_t bitmap_index = index / kBitmapBits;
306 size_t bitmap_offset = index % kBitmapBits;
307 uintptr_t bitmap = bitmaps_[bitmap_index] & (~uintptr_t(0) << bitmap_offset);
308 while (bitmap == 0) {
309 ++bitmap_index;
310 if (bitmap_index >= kNumBitmaps) {
311 return false;
312 }
313 bitmap = bitmaps_[bitmap_index];
314 }
315 auto bitmap_log2 = internal::CountRZero(bitmap);
316 index = (bitmap_index * kBitmapBits) + bitmap_log2;
317 return true;
318}
319
320template <typename BlockType>
321void DlAllocator<BlockType>::UpdateBitmaps(size_t index, bool empty) {
322 size_t bitmap_index = index / kBitmapBits;
323 size_t bitmap_offset = index % kBitmapBits;
324 uintptr_t bitmap = uintptr_t(1) << bitmap_offset;
325 if (empty) {
326 bitmaps_[bitmap_index] &= ~bitmap;
327 } else {
328 bitmaps_[bitmap_index] |= bitmap;
329 }
330}
331
332} // namespace pw::allocator
Definition: block_allocator.h:106
void Init(ByteSpan region)
Definition: block_allocator.h:310
Definition: result.h:116
Definition: detailed_block.h:88
Definition: dl_allocator.h:47
void RecycleBlock(BlockType &block) override
Definition: dl_allocator.h:250
void DeallocateBlock(BlockType *&&block) override
Definition: dl_allocator.h:261
BlockResult< BlockType > ChooseBlock(Layout layout) override
Definition: dl_allocator.h:188
void Flush() override
Definition: dl_allocator.h:99
void ReserveBlock(BlockType &block) override
Definition: dl_allocator.h:236
DlAllocator(ByteSpan region)
Definition: dl_allocator.h:81
constexpr DlAllocator()
Constexpr constructor. Callers must explicitly call Init.
Definition: dl_allocator.h:142
size_t DoGetMaxAllocatable() override
Definition: dl_allocator.h:167
Definition: fast_sorted.h:65
Definition: layout.h:58
Definition: unordered.h:44
bool Remove(BlockType &block)
Definition: base.h:118
constexpr bool empty() const
Returns whether this buckets contains any free blocks.
Definition: base.h:70
BlockType * RemoveCompatible(Layout layout)
Definition: base.h:129
constexpr U CountRZero(T t)
Definition: base.h:224
constexpr U CountLZero(T t)
Definition: base.h:233
SmallBlock BlockType
Default block type to use for tests.
Definition: size_report.h:32
constexpr size_t AlignUp(uintptr_t value, size_t alignment)
Returns the value rounded up to the nearest multiple of alignment.
Definition: alignment.h:52