• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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