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 "pw_allocator/allocator.h"
17 #include "pw_allocator/block.h"
18 #include "pw_allocator/capability.h"
19 #include "pw_allocator/fragmentation.h"
20 #include "pw_bytes/span.h"
21 #include "pw_result/result.h"
22 #include "pw_status/status.h"
23
24 namespace pw::allocator {
25 namespace internal {
26
27 /// Block-independent base class of ``BlockAllocator``.
28 ///
29 /// This class contains static methods which do not depend on the template
30 /// parameters of ``BlockAllocator`` that are used to determine block/ type.
31 /// This allows the methods to be defined in a separate source file and use
32 /// macros that cannot be used in headers, e.g. PW_CHECK.
33 ///
34 /// This class should not be used directly. Instead, use ``BlockAllocator`` or
35 /// one of its specializations.
36 class GenericBlockAllocator : public Allocator {
37 public:
38 static constexpr Capabilities kCapabilities =
39 kImplementsGetRequestedLayout | kImplementsGetUsableLayout |
40 kImplementsGetAllocatedLayout | kImplementsGetCapacity |
41 kImplementsRecognizes;
42
43 // Not copyable or movable.
44 GenericBlockAllocator(const GenericBlockAllocator&) = delete;
45 GenericBlockAllocator& operator=(const GenericBlockAllocator&) = delete;
46 GenericBlockAllocator(GenericBlockAllocator&&) = delete;
47 GenericBlockAllocator& operator=(GenericBlockAllocator&&) = delete;
48
49 protected:
GenericBlockAllocator()50 constexpr GenericBlockAllocator() : Allocator(kCapabilities) {}
51
52 /// Crashes with an informational method that the given block is allocated.
53 ///
54 /// This method is meant to be called by ``SplitFreeListAllocator``s
55 /// destructor. There must not be any outstanding allocations from an
56 /// when it is destroyed.
57 static void CrashOnAllocated(void* allocated);
58 };
59
60 } // namespace internal
61
62 /// A memory allocator that uses a list of blocks.
63 ///
64 /// This class does not implement `ChooseBlock` and cannot be used directly.
65 /// Instead, use one of its specializations.
66 ///
67 /// NOTE!! Do NOT use memory returned from this allocator as the backing for
68 /// another allocator. If this is done, the `Query` method may incorrectly
69 /// think pointers returned by that allocator were created by this one, and
70 /// report that this allocator can de/reallocate them.
71 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
72 class BlockAllocator : public internal::GenericBlockAllocator {
73 public:
74 using BlockType = Block<OffsetType, kAlign, kPoisonInterval != 0>;
75 using Range = typename BlockType::Range;
76
77 /// Constexpr constructor. Callers must explicitly call `Init`.
BlockAllocator()78 constexpr BlockAllocator() : internal::GenericBlockAllocator() {}
79
80 /// Non-constexpr constructor that automatically calls `Init`.
81 ///
82 /// @param[in] region Region of memory to use when satisfying allocation
83 /// requests. The region MUST be large enough to fit an
84 /// aligned block with overhead. It MUST NOT be larger
85 /// than what is addressable by `OffsetType`.
BlockAllocator(ByteSpan region)86 explicit BlockAllocator(ByteSpan region) : BlockAllocator() { Init(region); }
87
~BlockAllocator()88 ~BlockAllocator() override { Reset(); }
89
90 /// Returns a ``Range`` of blocks tracking the memory of this allocator.
91 Range blocks() const;
92
93 /// Sets the memory region to be used by this allocator.
94 ///
95 /// This method will instantiate an initial block using the memory region.
96 ///
97 /// @param[in] region Region of memory to use when satisfying allocation
98 /// requests. The region MUST be large enough to fit an
99 /// aligned block with overhead. It MUST NOT be larger
100 /// than what is addressable by `OffsetType`.
101 void Init(ByteSpan region);
102
103 /// Sets the blocks to be used by this allocator.
104 ///
105 /// This method will use the sequence blocks as-is, which must be valid. The
106 /// sequence extends to a block marked "last".
107 ///
108 /// @param[in] begin The first block for this allocator.
Init(BlockType * begin)109 void Init(BlockType* begin) { return Init(begin, nullptr); }
110
111 /// Sets the blocks to be used by this allocator.
112 ///
113 /// This method will use the sequence blocks as-is, which must be valid.
114 ///
115 /// @param[in] begin The first block for this allocator.
116 /// @param[in] end The last block for this allocator. May be null, in
117 /// which case the sequence ends with the first block
118 /// marked "last".
119 virtual void Init(BlockType* begin, BlockType* end);
120
121 /// Returns fragmentation information for the block allocator's memory region.
122 Fragmentation MeasureFragmentation() const;
123
124 /// Resets the allocator to an uninitialized state.
125 ///
126 /// At the time of the call, there MUST NOT be any outstanding allocated
127 /// blocks from this allocator.
128 void Reset();
129
130 protected:
131 using ReverseRange = typename BlockType::ReverseRange;
132
133 /// Returns a ``ReverseRange`` of blocks tracking the memory of this
134 /// allocator.
135 ReverseRange rblocks();
136
137 private:
138 /// @copydoc Allocator::Allocate
139 void* DoAllocate(Layout layout) override;
140
141 /// @copydoc Allocator::Deallocate
142 void DoDeallocate(void* ptr) override;
143
144 /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr,Layout)145 void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
146
147 /// @copydoc Allocator::Resize
148 bool DoResize(void* ptr, size_t new_size) override;
149
150 /// @copydoc Deallocator::GetInfo
151 Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override;
152
153 /// Selects a free block to allocate from.
154 ///
155 /// This method represents the allocator-specific strategy of choosing which
156 /// block should be used to satisfy allocation requests.
157 ///
158 /// @param layout Same as ``Allocator::Allocate``.
159 virtual BlockType* ChooseBlock(Layout layout) = 0;
160
161 /// Makes this block available for allocation again.
162 ///
163 /// This method is called when a block is deallocated. By default, it does
164 /// nothing.
165 ///
166 /// @param block The block beind deallocated.
167 virtual void RecycleBlock(BlockType* block);
168
169 /// Returns the block associated with a pointer.
170 ///
171 /// If the given pointer is to this allocator's memory region, but not to a
172 /// valid block, the memory is corrupted and this method will crash to assist
173 /// in uncovering the underlying bug.
174 ///
175 /// @param ptr Pointer to an allocated block's usable space.
176 ///
177 /// @returns @rst
178 ///
179 /// .. pw-status-codes::
180 ///
181 /// OK: Result contains a pointer to the block.
182 ///
183 /// OUT_OF_RANGE: Given pointer is outside the allocator's memory.
184 ///
185 /// @endrst
186 template <typename PtrType,
187 typename BlockPtrType = std::conditional_t<
188 std::is_const_v<std::remove_pointer_t<PtrType>>,
189 const BlockType*,
190 BlockType*>>
191 Result<BlockPtrType> FromUsableSpace(PtrType ptr) const;
192
193 /// Ensures the pointer to the last block is correct after the given block is
194 /// allocated or freed.
195 void UpdateLast(BlockType* block);
196
197 // Represents the range of blocks for this allocator.
198 size_t capacity_ = 0;
199 BlockType* first_ = nullptr;
200 BlockType* last_ = nullptr;
201 uint16_t unpoisoned_ = 0;
202 };
203
204 // Template method implementations
205
206 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
207 typename BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Range
blocks()208 BlockAllocator<OffsetType, kPoisonInterval, kAlign>::blocks() const {
209 return Range(first_);
210 }
211
212 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
213 typename BlockAllocator<OffsetType, kPoisonInterval, kAlign>::ReverseRange
rblocks()214 BlockAllocator<OffsetType, kPoisonInterval, kAlign>::rblocks() {
215 while (last_ != nullptr && !last_->Last()) {
216 last_ = last_->Next();
217 }
218 return ReverseRange(last_);
219 }
220
221 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Init(ByteSpan region)222 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Init(
223 ByteSpan region) {
224 Result<BlockType*> result = BlockType::Init(region);
225 Init(*result, nullptr);
226 }
227
228 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Init(BlockType * begin,BlockType * end)229 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Init(BlockType* begin,
230 BlockType* end) {
231 PW_ASSERT(begin != nullptr);
232 if (end == nullptr) {
233 end = begin;
234 while (!end->Last()) {
235 end = end->Next();
236 }
237 } else {
238 PW_ASSERT(begin < end);
239 end->MarkLast();
240 }
241 first_ = begin;
242 last_ = end;
243 for (const auto& block : blocks()) {
244 capacity_ += block->OuterSize();
245 }
246 }
247
248 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Reset()249 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Reset() {
250 for (auto* block : blocks()) {
251 if (block->Used()) {
252 CrashOnAllocated(block);
253 }
254 }
255 }
256
257 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
DoAllocate(Layout layout)258 void* BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoAllocate(
259 Layout layout) {
260 BlockType* block = ChooseBlock(layout);
261 if (block == nullptr) {
262 return nullptr;
263 }
264 UpdateLast(block);
265 return block->UsableSpace();
266 }
267
268 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
DoDeallocate(void * ptr)269 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoDeallocate(
270 void* ptr) {
271 auto result = FromUsableSpace(ptr);
272 if (!result.ok()) {
273 return;
274 }
275 BlockType* block = *result;
276
277 // Free the block and merge it with its neighbors, if possible.
278 BlockType::Free(block);
279 UpdateLast(block);
280
281 if constexpr (kPoisonInterval != 0) {
282 ++unpoisoned_;
283 if (unpoisoned_ >= kPoisonInterval) {
284 block->Poison();
285 unpoisoned_ = 0;
286 }
287 }
288 RecycleBlock(block);
289 }
290
291 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
DoResize(void * ptr,size_t new_size)292 bool BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoResize(
293 void* ptr, size_t new_size) {
294 auto result = FromUsableSpace(ptr);
295 if (!result.ok()) {
296 return false;
297 }
298 BlockType* block = *result;
299
300 if (!BlockType::Resize(block, new_size).ok()) {
301 return false;
302 }
303 UpdateLast(block);
304 return true;
305 }
306
307 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
DoGetInfo(InfoType info_type,const void * ptr)308 Result<Layout> BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoGetInfo(
309 InfoType info_type, const void* ptr) const {
310 // Handle types not related to a block first.
311 if (info_type == InfoType::kCapacity) {
312 return Layout(capacity_, kAlign);
313 }
314 // Get a block from the given pointer.
315 auto result = FromUsableSpace(ptr);
316 if (!result.ok()) {
317 return Status::NotFound();
318 }
319 const BlockType* block = result.value();
320 if (!block->Used()) {
321 return Status::FailedPrecondition();
322 }
323 switch (info_type) {
324 case InfoType::kRequestedLayoutOf:
325 return Layout(block->RequestedSize(), block->Alignment());
326 case InfoType::kUsableLayoutOf:
327 return Layout(block->InnerSize(), block->Alignment());
328 case InfoType::kAllocatedLayoutOf:
329 return Layout(block->OuterSize(), block->Alignment());
330 case InfoType::kRecognizes:
331 return Layout();
332 case InfoType::kCapacity:
333 default:
334 return Status::Unimplemented();
335 }
336 }
337
338 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
339 Fragmentation
MeasureFragmentation()340 BlockAllocator<OffsetType, kPoisonInterval, kAlign>::MeasureFragmentation()
341 const {
342 Fragmentation fragmentation;
343 for (auto block : blocks()) {
344 if (!block->Used()) {
345 fragmentation.AddFragment(block->InnerSize() / BlockType::kAlignment);
346 }
347 }
348 return fragmentation;
349 }
350
351 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
RecycleBlock(BlockType *)352 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::RecycleBlock(
353 BlockType*) {}
354
355 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
356 template <typename PtrType, typename BlockPtrType>
357 Result<BlockPtrType>
FromUsableSpace(PtrType ptr)358 BlockAllocator<OffsetType, kPoisonInterval, kAlign>::FromUsableSpace(
359 PtrType ptr) const {
360 if (ptr < first_->UsableSpace() || last_->UsableSpace() < ptr) {
361 return Status::OutOfRange();
362 }
363 BlockPtrType block = BlockType::FromUsableSpace(ptr);
364 block->CrashIfInvalid();
365 return block;
366 }
367
368 template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
UpdateLast(BlockType * block)369 void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::UpdateLast(
370 BlockType* block) {
371 if (block->Last()) {
372 last_ = block;
373 } else if (block->Next()->Last()) {
374 last_ = block->Next();
375 }
376 }
377
378 } // namespace pw::allocator
379