• 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 <cstddef>
17 
18 #include "pw_allocator/block/contiguous.h"
19 #include "pw_allocator/block/result.h"
20 #include "pw_allocator/hardening.h"
21 #include "pw_allocator/layout.h"
22 #include "pw_bytes/alignment.h"
23 #include "pw_status/status.h"
24 #include "pw_status/status_with_size.h"
25 
26 namespace pw::allocator {
27 namespace internal {
28 
29 // Trivial base class for trait support.
30 struct AllocatableBase {};
31 
32 }  // namespace internal
33 
34 /// Mix-in for blocks that can be allocated and freed.
35 ///
36 /// Block mix-ins are stateless and trivially constructible. See `BasicBlock`
37 /// for details on how mix-ins can be combined to implement blocks.
38 ///
39 /// This mix-in requires its derived type also derive from `ContiguousBlock`
40 /// and provide the following symbols:
41 ///
42 /// - size_t kMinOuterSize
43 ///   - Size of the smallest block that can be allocated.
44 /// - bool IsFreeUnchecked() const
45 ///   - Returns whether the block is free or in use.
46 /// - void SetFree(bool)
47 ///   - Sets whether the block is free or in use.
48 template <typename Derived>
49 class AllocatableBlock : public internal::AllocatableBase {
50  protected:
AllocatableBlock()51   constexpr explicit AllocatableBlock() {
52     // Assert within a function, since `Derived` is not complete when this type
53     // is defined.
54     static_assert(is_contiguous_v<Derived>,
55                   "Types derived from AllocatableBlock must also derive from "
56                   "ContiguousBlock");
57   }
58 
59  public:
60   /// @returns whether this block is free or is in use.
61   constexpr bool IsFree() const;
62 
63   /// Indicates whether the block is in use is free.
64   ///
65   /// This method will eventually be deprecated. Prefer `IsFree`.
Used()66   constexpr bool Used() const { return !IsFree(); }
67 
68   /// Checks if a block could be split from the block.
69   ///
70   /// On error, this method will return the same status as `AllocFirst` or
71   /// `AllocLast` without performing any modifications.
72   ///
73   /// @pre The block must not be in use.
74   ///
75   /// @returns @rst
76   ///
77   /// .. pw-status-codes::
78   ///
79   ///    OK: Returns the number of bytes to shift this block in order to align
80   ///    its usable space.
81   ///
82   ///    FAILED_PRECONDITION: This block is in use and cannot be split.
83   ///
84   ///    RESOURCE_EXHAUSTED: The available space is insufficient to fulfill the
85   ///    request. This may be due to a large requested size, or insufficient
86   ///    remaining space to fulfill the requested alignment create a valid
87   ///    leading block, and/or create a valid trailing block.
88   ///
89   /// @endrst
90   constexpr StatusWithSize CanAlloc(Layout layout) const;
91 
92   /// Splits an aligned block from the start of the block, and marks it as used.
93   ///
94   /// If successful, `block` will be replaced by a block that has an inner
95   /// size of at least `inner_size`, and whose starting address is aligned to an
96   /// `alignment` boundary. If unsuccessful, `block` will be unmodified.
97   ///
98   /// This method is static in order to consume the given block pointer.
99   /// On success, a pointer to the new, smaller block is returned. In total, up
100   /// to two additional blocks may be created: one to pad the returned block to
101   /// an alignment boundary and one for the trailing space. On error, the
102   /// original pointer is returned.
103   ///
104   /// For larger alignments, the `AllocLast` method is generally preferable to
105   /// this method, as this method may create an additional fragments both before
106   /// and after the returned block in order to align the usable space.
107   ///
108   /// @pre The block must not be in use.
109   ///
110   /// @returns @rst
111   ///
112   /// .. pw-status-codes::
113   ///
114   ///    OK: The split completed successfully. The `BlockAllocType` indicates
115   ///    how extra memory was distributed to other blocks.
116   ///
117   ///    FAILED_PRECONDITION: This block is in use and cannot be split.
118   ///
119   ///    RESOURCE_EXHAUSTED: The available space is insufficient to fulfill the
120   ///    request. This may be due to a large requested size, or insufficient
121   ///    remaining space to fulfill the requested alignment create a valid
122   ///    leading block, and/or create a valid trailing block.
123   ///
124   /// @endrst
125   static constexpr BlockResult<Derived> AllocFirst(Derived*&& block,
126                                                    Layout layout);
127 
128   /// Splits an aligned block from the end of the block, and marks it as used.
129   ///
130   /// If successful, `block` will be replaced by a block that has an inner
131   /// size of at least `inner_size`, and whose starting address is aligned to an
132   /// `alignment` boundary. If unsuccessful, `block` will be unmodified.
133   ///
134   /// This method is static in order to consume the given block pointer.
135   /// On success, a pointer to the new, smaller block is returned. In total, up
136   /// to two additional blocks may be created: one to pad the returned block to
137   /// an alignment boundary and one for the trailing space. On error, the
138   /// original pointer is returned.
139   ///
140   /// @pre The block must not be in use.
141   ///
142   /// @returns @rst
143   ///
144   /// .. pw-status-codes::
145   ///
146   ///    OK: The split completed successfully. The `BlockAllocType` indicates
147   ///    how extra memory was distributed to other blocks.
148   ///
149   ///    FAILED_PRECONDITION: This block is in use and cannot be split.
150   ///
151   ///    RESOURCE_EXHAUSTED: The available space is insufficient to fulfill the
152   ///    request. This may be due to a large requested size, or insufficient
153   ///    remaining space to fulfill the requested alignment create a valid
154   ///    leading block, and/or create a valid trailing block.
155   ///
156   /// @endrst
157   static constexpr BlockResult<Derived> AllocLast(Derived*&& block,
158                                                   Layout layout);
159 
160   /// Grows or shrinks the block.
161   ///
162   /// If successful, `block` may be merged with the block after it in order to
163   /// provide additional memory (when growing) or to merge released memory (when
164   /// shrinking). If unsuccessful, `block` will be unmodified.
165   ///
166   /// Note: Resizing may modify the block following this one if it is free.
167   /// Allocators that track free blocks based on their size must be prepared to
168   /// handle this size change.
169   ///
170   /// @pre The block must be in use.
171   ///
172   /// @returns @rst
173   ///
174   /// .. pw-status-codes::
175   ///
176   ///    OK: The resize completed successfully.
177   ///
178   ///    FAILED_PRECONDITION: This block is not in use.
179   ///
180   ///    RESOURCE_EXHAUSTED: The available space is insufficient to fulfill the
181   ///    request. This may be due to a large requested size, or insufficient
182   ///    remaining space to fulfill the requested alignment create a valid
183   ///    leading block, and/or create a valid trailing block.
184   ///
185   /// @endrst
186   constexpr BlockResult<Derived> Resize(size_t new_inner_size);
187 
188   /// Marks the block as free.
189   ///
190   /// This method is static in order to consume the given block pointer. It
191   /// returns a pointer to a freed block that is the result of merging the given
192   /// block with either or both of its neighbors, if they were free.
193   ///
194   /// Note: Freeing may modify the adjacent blocks if they are free.
195   /// Allocators that track free blocks must be prepared to handle this merge.
196   static constexpr BlockResult<Derived> Free(Derived*&& block);
197 
198  protected:
199   /// @copydoc CanAlloc
200   constexpr StatusWithSize DoCanAlloc(Layout layout) const;
201 
202   /// @copydoc AllocFirst
203   static constexpr BlockResult<Derived> DoAllocFirst(Derived*&& block,
204                                                      Layout layout);
205 
206   /// @copydoc AllocLast
207   static constexpr BlockResult<Derived> DoAllocLast(Derived*&& block,
208                                                     Layout layout);
209 
210   /// @copydoc Resize
211   constexpr BlockResult<Derived> DoResize(size_t new_inner_size,
212                                           bool shifted = false);
213 
214   /// @copydoc Free
215   static constexpr BlockResult<Derived> DoFree(Derived*&& block);
216 
217  private:
218   using BlockResultPrev = internal::GenericBlockResult::Prev;
219   using BlockResultNext = internal::GenericBlockResult::Next;
220 
derived()221   constexpr Derived* derived() { return static_cast<Derived*>(this); }
derived()222   constexpr const Derived* derived() const {
223     return static_cast<const Derived*>(this);
224   }
225 
226   // AlignableBlock calls DoCanAlloc, DoAllocLast, DoResize
227   template <typename>
228   friend class AlignableBlock;
229 
230   // BlockWithLayout calls DoFree, DoResize
231   template <typename>
232   friend class BlockWithLayout;
233 };
234 
235 /// Trait type that allows interrogating a block as to whether it is
236 /// allocatable.
237 template <typename BlockType>
238 struct is_allocatable : std::is_base_of<internal::AllocatableBase, BlockType> {
239 };
240 
241 /// Helper variable template for `is_allocatable<BlockType>::value`.
242 template <typename BlockType>
243 constexpr bool is_allocatable_v = is_allocatable<BlockType>::value;
244 
245 // Template method implementations.
246 
247 template <typename Derived>
IsFree()248 constexpr bool AllocatableBlock<Derived>::IsFree() const {
249   if constexpr (Hardening::kIncludesDebugChecks) {
250     derived()->CheckInvariants();
251   }
252   return derived()->IsFreeUnchecked();
253 }
254 
255 template <typename Derived>
CanAlloc(Layout layout)256 constexpr StatusWithSize AllocatableBlock<Derived>::CanAlloc(
257     Layout layout) const {
258   if constexpr (Hardening::kIncludesDebugChecks) {
259     derived()->CheckInvariants();
260   }
261   return derived()->DoCanAlloc(layout);
262 }
263 
264 template <typename Derived>
DoCanAlloc(Layout layout)265 constexpr StatusWithSize AllocatableBlock<Derived>::DoCanAlloc(
266     Layout layout) const {
267   if (!derived()->IsFree()) {
268     return StatusWithSize::FailedPrecondition();
269   }
270   if (layout.size() == 0) {
271     return StatusWithSize::InvalidArgument();
272   }
273   size_t extra = derived()->InnerSize();
274   size_t new_inner_size = AlignUp(layout.size(), Derived::kAlignment);
275   if (PW_SUB_OVERFLOW(extra, new_inner_size, &extra)) {
276     return StatusWithSize::ResourceExhausted();
277   }
278   return StatusWithSize(extra);
279 }
280 
281 template <typename Derived>
AllocFirst(Derived * && block,Layout layout)282 constexpr BlockResult<Derived> AllocatableBlock<Derived>::AllocFirst(
283     Derived*&& block, Layout layout) {
284   if (block == nullptr || layout.size() == 0) {
285     return BlockResult(block, Status::InvalidArgument());
286   }
287   if constexpr (Hardening::kIncludesRobustChecks) {
288     block->CheckInvariants();
289   }
290   if (!block->IsFree()) {
291     return BlockResult(block, Status::FailedPrecondition());
292   }
293   return Derived::DoAllocFirst(std::move(block), layout);
294 }
295 
296 template <typename Derived>
DoAllocFirst(Derived * && block,Layout layout)297 constexpr BlockResult<Derived> AllocatableBlock<Derived>::DoAllocFirst(
298     Derived*&& block, Layout layout) {
299   size_t size = AlignUp(layout.size(), Derived::kAlignment);
300   layout = Layout(size, layout.alignment());
301   StatusWithSize can_alloc = block->DoCanAlloc(layout);
302   if (!can_alloc.ok()) {
303     return BlockResult(block, can_alloc.status());
304   }
305   size_t extra = can_alloc.size();
306   BlockResult result(block);
307   if (extra >= Derived::kMinOuterSize) {
308     // Split the large padding off the back.
309     block->DoSplitFirst(block->InnerSize() - extra);
310     result = BlockResult(block, BlockResultNext::kSplitNew);
311   }
312   block->SetFree(false);
313   return result;
314 }
315 
316 template <typename Derived>
AllocLast(Derived * && block,Layout layout)317 constexpr BlockResult<Derived> AllocatableBlock<Derived>::AllocLast(
318     Derived*&& block, Layout layout) {
319   if (block == nullptr || layout.size() == 0) {
320     return BlockResult(block, Status::InvalidArgument());
321   }
322   if constexpr (Hardening::kIncludesRobustChecks) {
323     block->CheckInvariants();
324   }
325   if (!block->IsFree()) {
326     return BlockResult(block, Status::FailedPrecondition());
327   }
328   return Derived::DoAllocLast(std::move(block), layout);
329 }
330 
331 template <typename Derived>
DoAllocLast(Derived * && block,Layout layout)332 constexpr BlockResult<Derived> AllocatableBlock<Derived>::DoAllocLast(
333     Derived*&& block, Layout layout) {
334   size_t size = AlignUp(layout.size(), Derived::kAlignment);
335   layout = Layout(size, layout.alignment());
336   StatusWithSize can_alloc = block->DoCanAlloc(layout);
337   if (!can_alloc.ok()) {
338     return BlockResult(block, can_alloc.status());
339   }
340   size_t extra = can_alloc.size();
341   BlockResult result(block);
342   Derived* prev = block->Prev();
343   if (extra >= Derived::kMinOuterSize) {
344     // Split the large padding off the front.
345     block = block->DoSplitLast(layout.size());
346     prev = block->Prev();
347     result = BlockResult(block, BlockResultPrev::kSplitNew);
348 
349   } else if (extra != 0 && prev != nullptr) {
350     // The small amount of padding can be appended to the previous block.
351     prev->DoResize(prev->InnerSize() + extra, true).IgnoreUnlessStrict();
352     block = prev->Next();
353     result = BlockResult(block, BlockResultPrev::kResizedLarger, extra);
354   }
355   block->SetFree(false);
356   return result;
357 }
358 
359 template <typename Derived>
Resize(size_t new_inner_size)360 constexpr BlockResult<Derived> AllocatableBlock<Derived>::Resize(
361     size_t new_inner_size) {
362   if constexpr (Hardening::kIncludesRobustChecks) {
363     derived()->CheckInvariants();
364   }
365   if (derived()->IsFree()) {
366     return BlockResult(derived(), Status::FailedPrecondition());
367   }
368   return derived()->DoResize(new_inner_size, /* shifted: */ false);
369 }
370 
371 template <typename Derived>
DoResize(size_t new_inner_size,bool)372 constexpr BlockResult<Derived> AllocatableBlock<Derived>::DoResize(
373     size_t new_inner_size, bool) {
374   size_t old_inner_size = derived()->InnerSize();
375   new_inner_size = AlignUp(new_inner_size, Derived::kAlignment);
376   if (old_inner_size == new_inner_size) {
377     return BlockResult(derived());
378   }
379 
380   // Treat the block as free and try to combine it with the next block. At most
381   // one free block is expected to follow this block.
382   derived()->SetFree(true);
383   Derived* next = derived()->Next();
384   BlockResult result(derived());
385   if (next != nullptr && next->IsFree()) {
386     derived()->DoMergeNext();
387     result = BlockResult(derived(), BlockResultNext::kMerged);
388   }
389   size_t merged_inner_size = derived()->InnerSize();
390   if (merged_inner_size < new_inner_size) {
391     // The merged block is too small for the resized block. Restore the original
392     // blocks as needed.
393     if (merged_inner_size != old_inner_size) {
394       derived()->DoSplitFirst(old_inner_size);
395     }
396     derived()->SetFree(false);
397     return BlockResult(derived(), Status::ResourceExhausted());
398   }
399   if (new_inner_size + Derived::kMinOuterSize <= merged_inner_size) {
400     // There is enough room after the resized block for another trailing block.
401     derived()->DoSplitFirst(new_inner_size);
402     result = result.next() == BlockResultNext::kMerged
403                  ? BlockResult(derived(), BlockResultNext::kResized)
404                  : BlockResult(derived(), BlockResultNext::kSplitNew);
405   }
406   derived()->SetFree(false);
407   return result;
408 }
409 
410 template <typename Derived>
Free(Derived * && block)411 constexpr BlockResult<Derived> AllocatableBlock<Derived>::Free(
412     Derived*&& block) {
413   if (block == nullptr) {
414     return BlockResult(block, Status::InvalidArgument());
415   }
416   if constexpr (Hardening::kIncludesRobustChecks) {
417     block->CheckInvariants();
418   }
419   return Derived::DoFree(std::move(block));
420 }
421 
422 template <typename Derived>
DoFree(Derived * && block)423 constexpr BlockResult<Derived> AllocatableBlock<Derived>::DoFree(
424     Derived*&& block) {
425   block->SetFree(true);
426   BlockResult result(block);
427 
428   // Try to merge the previous block with this one.
429   Derived* prev = block->Prev();
430   if (prev != nullptr && prev->IsFree()) {
431     prev->DoMergeNext();
432     block = prev;
433     result = BlockResult(block, BlockResultNext::kMerged);
434   }
435 
436   // Try to merge this block with the next one.
437   Derived* next = block->Next();
438   if (next != nullptr && next->IsFree()) {
439     block->DoMergeNext();
440     result = BlockResult(block, BlockResultNext::kMerged);
441   }
442 
443   if constexpr (Hardening::kIncludesDebugChecks) {
444     block->CheckInvariants();
445   }
446   return result;
447 }
448 
449 }  // namespace pw::allocator
450