C/C++ API Reference
Loading...
Searching...
No Matches
block_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
18#include "pw_allocator/allocator.h"
19#include "pw_allocator/block/basic.h"
20#include "pw_allocator/block/iterable.h"
21#include "pw_allocator/block/poisonable.h"
22#include "pw_allocator/block/result.h"
23#include "pw_allocator/block/with_layout.h"
24#include "pw_allocator/capability.h"
25#include "pw_allocator/config.h"
26#include "pw_allocator/fragmentation.h"
27#include "pw_allocator/hardening.h"
28#include "pw_assert/assert.h"
29#include "pw_bytes/span.h"
30#include "pw_result/result.h"
31#include "pw_status/status.h"
32
33namespace pw::allocator {
34namespace internal {
35
46 public:
47 // Not copyable or movable.
49 GenericBlockAllocator& operator=(const GenericBlockAllocator&) = delete;
51 GenericBlockAllocator& operator=(GenericBlockAllocator&&) = delete;
52
53 protected:
54 template <typename BlockType>
55 static constexpr Capabilities GetCapabilities() {
56 Capabilities common = kImplementsGetUsableLayout |
57 kImplementsGetAllocatedLayout |
58 kImplementsGetCapacity | kImplementsRecognizes;
59 if constexpr (has_layout_v<BlockType>) {
60 return common | kImplementsGetRequestedLayout;
61 } else {
62 return common;
63 }
64 }
65
66 constexpr explicit GenericBlockAllocator(Capabilities capabilities)
67 : Allocator(capabilities) {}
68
74 [[noreturn]] static void CrashOnAllocated(const void* allocated);
75
78 [[noreturn]] static void CrashOnOutOfRange(const void* freed);
79
81 [[noreturn]] static void CrashOnDoubleFree(const void* freed);
82};
83
84} // namespace internal
85
86namespace test {
87
88// Forward declaration for friending.
89template <typename, size_t>
91
92} // namespace test
93
95
105template <typename BlockType_>
107 private:
109
110 public:
111 using BlockType = BlockType_;
112 using Range = typename BlockType::Range;
113
114 static constexpr Capabilities kCapabilities =
115 Base::GetCapabilities<BlockType>();
116 static constexpr size_t kPoisonInterval = PW_ALLOCATOR_BLOCK_POISON_INTERVAL;
117
118 ~BlockAllocator() override;
119
121 Range blocks() const;
122
130 void Init(ByteSpan region);
131
153
156
157 protected:
158 constexpr explicit BlockAllocator() : Base(kCapabilities) {}
159
167 void Init(BlockType* begin);
168
180 template <typename Ptr>
181 internal::copy_const_ptr_t<Ptr, BlockType*> FromUsableSpace(Ptr ptr) const;
182
187 virtual void DeallocateBlock(BlockType*&& block);
188
189 private:
190 using BlockResultPrev = internal::GenericBlockResult::Prev;
191 using BlockResultNext = internal::GenericBlockResult::Next;
192
193 // Let unit tests call internal methods in order to "preallocate" blocks..
194 template <typename, size_t>
195 friend class test::BlockAllocatorTest;
196
198 void* DoAllocate(Layout layout) override;
199
201 void DoDeallocate(void* ptr) override;
202
204 void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
205
207 bool DoResize(void* ptr, size_t new_size) override;
208
210 size_t DoGetAllocated() const override { return allocated_; }
211
213 Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override;
214
216 virtual size_t DoGetMaxAllocatable() = 0;
217
227
234 virtual void ReserveBlock(BlockType&) {}
235
242 virtual void RecycleBlock(BlockType&) {}
243
256 virtual void Flush() {}
257
259 static bool PrevIsFree(const BlockType* block) {
260 auto* prev = block->Prev();
261 return prev != nullptr && prev->IsFree();
262 }
263
265 static bool NextIsFree(const BlockType* block) {
266 auto* next = block->Next();
267 return next != nullptr && next->IsFree();
268 }
269
272 void UpdateLast(BlockType* block);
273
274 // Represents the range of blocks for this allocator.
275 size_t capacity_ = 0;
276 size_t allocated_ = 0;
277 BlockType* first_ = nullptr;
278 BlockType* last_ = nullptr;
279 uint16_t unpoisoned_ = 0;
280};
281
283
284// Template method implementations
285
286template <typename BlockType>
287BlockAllocator<BlockType>::~BlockAllocator() {
288 if constexpr (Hardening::kIncludesRobustChecks) {
289 for (auto* block : blocks()) {
290 if (!block->IsFree()) {
291 CrashOnAllocated(block);
292 }
293 }
294 }
295}
296
297template <typename BlockType>
298typename BlockAllocator<BlockType>::Range BlockAllocator<BlockType>::blocks()
299 const {
300 return Range(first_);
301}
302
303template <typename BlockType>
305 Result<BlockType*> result = BlockType::Init(region);
306 Init(*result);
307}
308
309template <typename BlockType>
310void BlockAllocator<BlockType>::Init(BlockType* begin) {
311 if constexpr (Hardening::kIncludesRobustChecks) {
312 PW_ASSERT(begin != nullptr);
313 PW_ASSERT(begin->Prev() == nullptr);
314 }
315 first_ = begin;
316 for (auto* block : blocks()) {
317 last_ = block;
318 capacity_ += block->OuterSize();
319 if (block->IsFree()) {
320 RecycleBlock(*block);
321 }
322 }
323}
324
325template <typename BlockType>
327 if (capacity_ == 0) {
328 // Not initialized.
329 return nullptr;
330 }
331
332 if constexpr (Hardening::kIncludesDebugChecks) {
333 PW_ASSERT(last_->Next() == nullptr);
334 }
335 auto result = ChooseBlock(layout);
336 if (!result.ok()) {
337 // No valid block for request.
338 return nullptr;
339 }
340 BlockType* block = result.block();
341 allocated_ += block->OuterSize();
342 switch (result.prev()) {
343 case BlockResultPrev::kSplitNew:
344 // New free blocks may be created when allocating.
345 RecycleBlock(*(block->Prev()));
346 break;
347 case BlockResultPrev::kResizedLarger:
348 // Extra bytes may be appended to the previous block.
349 allocated_ += result.size();
350 break;
351 case BlockResultPrev::kUnchanged:
352 case BlockResultPrev::kResizedSmaller:
353 break;
354 }
355 if (result.next() == BlockResultNext::kSplitNew) {
356 RecycleBlock(*(block->Next()));
357 }
358
359 UpdateLast(block);
360 if constexpr (Hardening::kIncludesDebugChecks) {
361 PW_ASSERT(block <= last_);
362 }
363
364 return block->UsableSpace();
365}
366
367template <typename BlockType>
369 BlockType* block = FromUsableSpace(ptr);
370 if (block->IsFree()) {
371 if constexpr (Hardening::kIncludesBasicChecks) {
372 CrashOnDoubleFree(block);
373 } else {
374 return;
375 }
376 }
377 DeallocateBlock(std::move(block));
378}
379
380template <typename BlockType>
382 // Neighboring blocks may be merged when freeing.
383 if (auto* prev = block->Prev(); prev != nullptr && prev->IsFree()) {
384 ReserveBlock(*prev);
385 }
386 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
387 ReserveBlock(*next);
388 }
389
390 // Free the block and merge it with its neighbors, if possible.
391 allocated_ -= block->OuterSize();
392 auto free_result = BlockType::Free(std::move(block));
393 block = free_result.block();
394 UpdateLast(block);
395
396 if (free_result.prev() == BlockResultPrev::kResizedSmaller) {
397 // Bytes were reclaimed from the previous block.
398 allocated_ -= free_result.size();
399 }
400
401 if constexpr (is_poisonable_v<BlockType> && kPoisonInterval != 0) {
402 ++unpoisoned_;
403 if (unpoisoned_ >= kPoisonInterval) {
404 block->Poison();
405 unpoisoned_ = 0;
406 }
407 }
408
409 RecycleBlock(*block);
410}
411
412template <typename BlockType>
413bool BlockAllocator<BlockType>::DoResize(void* ptr, size_t new_size) {
414 BlockType* block = FromUsableSpace(ptr);
415
416 // Neighboring blocks may be merged when resizing.
417 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
418 ReserveBlock(*next);
419 }
420
421 size_t old_size = block->OuterSize();
422 if (!block->Resize(new_size).ok()) {
423 return false;
424 }
425 allocated_ -= old_size;
426 allocated_ += block->OuterSize();
427 UpdateLast(block);
428
429 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
430 RecycleBlock(*next);
431 }
432
433 return true;
434}
435
436template <typename BlockType>
438 const void* ptr) const {
439 // Handle types not related to a block first.
440 if (info_type == InfoType::kCapacity) {
441 return Layout(capacity_);
442 }
443 // Get a block from the given pointer.
444 if (ptr < first_->UsableSpace() || last_->UsableSpace() < ptr) {
445 return Status::NotFound();
446 }
447 const auto* block = BlockType::FromUsableSpace(ptr);
448 if (!block->IsValid()) {
449 return Status::DataLoss();
450 }
451 if (block->IsFree()) {
453 }
454 if constexpr (kCapabilities.has(kImplementsGetRequestedLayout)) {
455 if (info_type == InfoType::kRequestedLayoutOf) {
456 return block->RequestedLayout();
457 }
458 }
459 switch (info_type) {
460 case InfoType::kUsableLayoutOf:
461 return Layout(block->InnerSize(), BlockType::kAlignment);
462 case InfoType::kAllocatedLayoutOf:
463 return Layout(block->OuterSize(), BlockType::kAlignment);
464 case InfoType::kRecognizes:
465 return Layout();
466 case InfoType::kCapacity:
467 case InfoType::kRequestedLayoutOf:
468 default:
469 return Status::Unimplemented();
470 }
471}
472
473template <typename BlockType>
475 Fragmentation fragmentation;
476 for (auto block : blocks()) {
477 if (block->IsFree()) {
478 fragmentation.AddFragment(block->InnerSize() / BlockType::kAlignment);
479 }
480 }
481 return fragmentation;
482}
483
484template <typename BlockType>
485template <typename Ptr>
486internal::copy_const_ptr_t<Ptr, BlockType*>
488 if (ptr < first_->UsableSpace() || last_->UsableSpace() < ptr) {
489 if constexpr (Hardening::kIncludesBasicChecks) {
490 CrashOnOutOfRange(ptr);
491 }
492 return nullptr;
493 }
494 auto* block = BlockType::FromUsableSpace(ptr);
495 if (!block->IsValid()) {
496 if constexpr (Hardening::kIncludesBasicChecks) {
497 block->CheckInvariants();
498 }
499 return nullptr;
500 }
501 return block;
502}
503
504template <typename BlockType>
505void BlockAllocator<BlockType>::UpdateLast(BlockType* block) {
506 BlockType* next = block->Next();
507 if (next == nullptr) {
508 last_ = block;
509 } else if (next->Next() == nullptr) {
510 last_ = next;
511 }
512}
513
514} // namespace pw::allocator
Definition: allocator.h:36
constexpr Allocator()=default
TODO(b/326509341): Remove when downstream consumers migrate.
Definition: result.h:143
static constexpr Status DataLoss()
Definition: status.h:316
static constexpr Status Unimplemented()
Definition: status.h:280
static constexpr Status FailedPrecondition()
Definition: status.h:243
static constexpr Status NotFound()
Definition: status.h:190
Definition: block_allocator.h:106
void * DoAllocate(Layout layout) override
Definition: block_allocator.h:326
size_t DoGetAllocated() const override
Definition: block_allocator.h:210
Fragmentation MeasureFragmentation() const
Returns fragmentation information for the block allocator's memory region.
Definition: block_allocator.h:474
virtual size_t DoGetMaxAllocatable()=0
internal::copy_const_ptr_t< Ptr, BlockType * > FromUsableSpace(Ptr ptr) const
Definition: block_allocator.h:487
virtual BlockResult< BlockType > ChooseBlock(Layout layout)=0
virtual void ReserveBlock(BlockType &)
Definition: block_allocator.h:234
void DoDeallocate(void *ptr) override
Definition: block_allocator.h:368
virtual void DeallocateBlock(BlockType *&&block)
Definition: block_allocator.h:381
bool DoResize(void *ptr, size_t new_size) override
Definition: block_allocator.h:413
void Init(ByteSpan region)
Definition: block_allocator.h:304
void DoDeallocate(void *ptr, Layout) override
Definition: block_allocator.h:204
void Init(BlockType *begin)
Definition: block_allocator.h:310
virtual void Flush()
Definition: block_allocator.h:256
size_t GetMaxAllocatable()
Definition: block_allocator.h:152
virtual void RecycleBlock(BlockType &)
Definition: block_allocator.h:242
Range blocks() const
Returns a Range of blocks tracking the memory of this allocator.
Definition: block_allocator.h:298
Result< Layout > DoGetInfo(InfoType info_type, const void *ptr) const override
Definition: block_allocator.h:437
Definition: result.h:116
Definition: capability.h:65
Definition: detailed_block.h:88
Definition: layout.h:58
Definition: block_allocator.h:45
static void CrashOnOutOfRange(const void *freed)
static void CrashOnDoubleFree(const void *freed)
Crashes with an informational message that a given block was freed twice.
static void CrashOnAllocated(const void *allocated)
Definition: block_allocator.h:90
#define PW_ALLOCATOR_BLOCK_POISON_INTERVAL
Definition: config.h:30
Definition: fragmentation.h:46