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#include <optional>
18
19#include "pw_allocator/allocator.h"
20#include "pw_allocator/block/basic.h"
21#include "pw_allocator/block/iterable.h"
22#include "pw_allocator/block/poisonable.h"
23#include "pw_allocator/block/result.h"
24#include "pw_allocator/block/with_layout.h"
25#include "pw_allocator/capability.h"
26#include "pw_allocator/config.h"
27#include "pw_allocator/fragmentation.h"
28#include "pw_allocator/hardening.h"
29#include "pw_assert/assert.h"
30#include "pw_bytes/span.h"
31#include "pw_result/result.h"
32#include "pw_status/status.h"
33
34namespace pw::allocator {
35namespace internal {
36
47 public:
48 // Not copyable or movable.
50 GenericBlockAllocator& operator=(const GenericBlockAllocator&) = delete;
52 GenericBlockAllocator& operator=(GenericBlockAllocator&&) = delete;
53
54 protected:
55 template <typename BlockType>
56 static constexpr Capabilities GetCapabilities();
57
58 constexpr explicit GenericBlockAllocator(Capabilities capabilities)
59 : pw::Allocator(capabilities) {}
60
66 [[noreturn]] static void CrashOnAllocated(const void* allocated);
67
70 [[noreturn]] static void CrashOnOutOfRange(const void* freed);
71
73 [[noreturn]] static void CrashOnDoubleFree(const void* freed);
74};
75
76} // namespace internal
77
78namespace test {
79
80// Forward declaration for friending.
81template <typename, size_t>
83
84} // namespace test
85
87
97template <typename BlockType_>
99 private:
101
102 public:
103 using BlockType = BlockType_;
104 using Range = typename BlockType::Range;
105
106 static constexpr Capabilities kCapabilities =
107 Base::GetCapabilities<BlockType>();
108 static constexpr size_t kPoisonInterval = PW_ALLOCATOR_BLOCK_POISON_INTERVAL;
109
110 ~BlockAllocator() override;
111
113 Range blocks() const { return Range(first_); }
114
122 void Init(ByteSpan region);
123
145
146 protected:
147 constexpr explicit BlockAllocator() : Base(kCapabilities) {}
148
150 void* DoAllocate(Layout layout) override;
151
153 void DoDeallocate(void* ptr) override;
154
156 bool DoResize(void* ptr, size_t new_size) override;
157
159 size_t DoGetAllocated() const override { return allocated_; }
160
162 std::optional<Fragmentation> DoMeasureFragmentation() const override;
163
165 Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override;
166
174 void Init(BlockType* begin);
175
187 template <typename Ptr>
188 internal::copy_const_ptr_t<Ptr, BlockType*> FromUsableSpace(Ptr ptr) const;
189
194 virtual void DeallocateBlock(BlockType*&& block);
195
196 private:
199
200 // Let unit tests call internal methods in order to "preallocate" blocks..
201 template <typename, size_t>
202 friend class test::BlockAllocatorTest;
203
205 virtual size_t DoGetMaxAllocatable() = 0;
206
215
222 virtual void ReserveBlock([[maybe_unused]] BlockType& block) {}
223
230 virtual void RecycleBlock([[maybe_unused]] BlockType& block) {}
231
244 virtual void Flush() {}
245
247 static bool PrevIsFree(const BlockType* block) {
248 auto* prev = block->Prev();
249 return prev != nullptr && prev->IsFree();
250 }
251
253 static bool NextIsFree(const BlockType* block) {
254 auto* next = block->Next();
255 return next != nullptr && next->IsFree();
256 }
257
260 void UpdateLast(BlockType* block);
261
262 // Represents the range of blocks for this allocator.
263 size_t capacity_ = 0;
264 size_t allocated_ = 0;
265 BlockType* first_ = nullptr;
266 BlockType* last_ = nullptr;
267 uint16_t unpoisoned_ = 0;
268};
269
271
272// Template method implementations
273
274namespace internal {
275
276template <typename BlockType>
277constexpr Capabilities GenericBlockAllocator::GetCapabilities() {
278 Capabilities common = kImplementsGetUsableLayout |
279 kImplementsGetAllocatedLayout | kImplementsGetCapacity |
280 kImplementsRecognizes;
281 if constexpr (has_layout_v<BlockType>) {
282 return common | kImplementsGetRequestedLayout;
283 } else {
284 return common;
285 }
286}
287
288} // namespace internal
289
290template <typename BlockType>
291BlockAllocator<BlockType>::~BlockAllocator() {
292 if constexpr (Hardening::kIncludesRobustChecks) {
293 for (auto* block : blocks()) {
294 if (!block->IsFree()) {
295 CrashOnAllocated(block);
296 }
297 }
298 }
299}
300
301template <typename BlockType>
303 Result<BlockType*> result = BlockType::Init(region);
304 PW_ASSERT(result.ok());
305 Init(*result);
306}
307
308template <typename BlockType>
309void BlockAllocator<BlockType>::Init(BlockType* begin) {
310 if constexpr (Hardening::kIncludesRobustChecks) {
311 PW_ASSERT(begin != nullptr);
312 PW_ASSERT(begin->Prev() == nullptr);
313 }
314 first_ = begin;
315 for (auto* block : blocks()) {
316 last_ = block;
317 capacity_ += block->OuterSize();
318 if (block->IsFree()) {
319 RecycleBlock(*block);
320 }
321 }
322}
323
324template <typename BlockType>
326 if (capacity_ == 0) {
327 // Not initialized.
328 return nullptr;
329 }
330
331 if constexpr (Hardening::kIncludesDebugChecks) {
332 PW_ASSERT(last_->Next() == nullptr);
333 }
334 auto result = ChooseBlock(layout);
335 if (!result.ok()) {
336 // No valid block for request.
337 return nullptr;
338 }
339 BlockType* block = result.block();
340 allocated_ += block->OuterSize();
341 switch (result.prev()) {
342 case BlockResultPrev::kSplitNew:
343 // New free blocks may be created when allocating.
344 RecycleBlock(*(block->Prev()));
345 break;
346 case BlockResultPrev::kResizedLarger:
347 // Extra bytes may be appended to the previous block.
348 allocated_ += result.size();
349 break;
350 case BlockResultPrev::kUnchanged:
351 case BlockResultPrev::kResizedSmaller:
352 break;
353 }
354 if (result.next() == BlockResultNext::kSplitNew) {
355 RecycleBlock(*(block->Next()));
356 }
357
358 UpdateLast(block);
359 if constexpr (Hardening::kIncludesDebugChecks) {
360 PW_ASSERT(block <= last_);
361 }
362
363 return block->UsableSpace();
364}
365
366template <typename BlockType>
368 BlockType* block = FromUsableSpace(ptr);
369 if (block->IsFree()) {
370 if constexpr (Hardening::kIncludesBasicChecks) {
371 CrashOnDoubleFree(block);
372 } else {
373 return;
374 }
375 }
376 DeallocateBlock(std::move(block));
377}
378
379template <typename BlockType>
381 // Neighboring blocks may be merged when freeing.
382 if (auto* prev = block->Prev(); prev != nullptr && prev->IsFree()) {
383 ReserveBlock(*prev);
384 }
385 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
386 ReserveBlock(*next);
387 }
388
389 // Free the block and merge it with its neighbors, if possible.
390 allocated_ -= block->OuterSize();
391 auto free_result = BlockType::Free(std::move(block));
392 block = free_result.block();
393 UpdateLast(block);
394
395 if (free_result.prev() == BlockResultPrev::kResizedSmaller) {
396 // Bytes were reclaimed from the previous block.
397 allocated_ -= free_result.size();
398 }
399
400 if constexpr (is_poisonable_v<BlockType> && kPoisonInterval != 0) {
401 ++unpoisoned_;
402 if (unpoisoned_ >= kPoisonInterval) {
403 block->Poison();
404 unpoisoned_ = 0;
405 }
406 }
407
408 RecycleBlock(*block);
409}
410
411template <typename BlockType>
412bool BlockAllocator<BlockType>::DoResize(void* ptr, size_t new_size) {
413 BlockType* block = FromUsableSpace(ptr);
414
415 // Neighboring blocks may be merged when resizing.
416 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
417 ReserveBlock(*next);
418 }
419
420 size_t old_size = block->OuterSize();
421 if (!block->Resize(new_size).ok()) {
422 return false;
423 }
424 allocated_ -= old_size;
425 allocated_ += block->OuterSize();
426 UpdateLast(block);
427
428 if (auto* next = block->Next(); next != nullptr && next->IsFree()) {
429 RecycleBlock(*next);
430 }
431
432 return true;
433}
434
435template <typename BlockType>
437 const void* ptr) const {
438 // Handle types not related to a block first.
439 if (info_type == InfoType::kCapacity) {
440 return Layout(capacity_);
441 }
442 // Get a block from the given pointer.
443 if (ptr < first_->UsableSpace() || last_->UsableSpace() < ptr) {
444 return Status::NotFound();
445 }
446 const auto* block = BlockType::FromUsableSpace(ptr);
447 if (!block->IsValid()) {
448 return Status::DataLoss();
449 }
450 if (block->IsFree()) {
452 }
453 if constexpr (kCapabilities.has(kImplementsGetRequestedLayout)) {
454 if (info_type == InfoType::kRequestedLayoutOf) {
455 return block->RequestedLayout();
456 }
457 }
458 switch (info_type) {
459 case InfoType::kUsableLayoutOf:
460 return Layout(block->InnerSize(), BlockType::kAlignment);
461 case InfoType::kAllocatedLayoutOf:
462 return Layout(block->OuterSize(), BlockType::kAlignment);
463 case InfoType::kRecognizes:
464 return Layout();
465 case InfoType::kCapacity:
466 case InfoType::kRequestedLayoutOf:
467 default:
468 return Status::Unimplemented();
469 }
470}
471
472template <typename BlockType>
474 const {
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:42
InfoType
Definition: deallocator.h:173
Definition: result.h:145
constexpr bool ok() const
Definition: result.h:451
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:98
void * DoAllocate(Layout layout) override
Definition: block_allocator.h:325
Range blocks() const
Returns a Range of blocks tracking the memory of this allocator.
Definition: block_allocator.h:113
virtual void RecycleBlock(BlockType &block)
Definition: block_allocator.h:230
size_t DoGetAllocated() const override
Definition: block_allocator.h:159
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
std::optional< Fragmentation > DoMeasureFragmentation() const override
Returns fragmentation information for the allocator's memory region.
Definition: block_allocator.h:473
void DoDeallocate(void *ptr) override
Definition: block_allocator.h:367
virtual void DeallocateBlock(BlockType *&&block)
Definition: block_allocator.h:380
bool DoResize(void *ptr, size_t new_size) override
Definition: block_allocator.h:412
void Init(ByteSpan region)
Definition: block_allocator.h:302
void Init(BlockType *begin)
Definition: block_allocator.h:309
virtual void Flush()
Definition: block_allocator.h:244
size_t GetMaxAllocatable()
Definition: block_allocator.h:144
virtual void ReserveBlock(BlockType &block)
Definition: block_allocator.h:222
Result< Layout > DoGetInfo(InfoType info_type, const void *ptr) const override
Definition: block_allocator.h:436
Definition: result.h:106
Definition: capability.h:65
Definition: detailed_block.h:88
Definition: layout.h:64
Definition: block_allocator.h:46
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:82
#define PW_ALLOCATOR_BLOCK_POISON_INTERVAL
Definition: config.h:30
Definition: fragmentation.h:47
void AddFragment(size_t inner_size)
Includes a region of free memory in the fragmentation calculation.