xref: /aosp_15_r20/external/pigweed/pw_allocator/block/public/pw_allocator/block/with_layout.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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/alignable.h"
19 #include "pw_allocator/block/result.h"
20 #include "pw_allocator/layout.h"
21 #include "pw_assert/assert.h"
22 
23 namespace pw::allocator {
24 namespace internal {
25 
26 // Trivial base class for trait support.
27 struct BaseWithLayout {};
28 
29 }  // namespace internal
30 
31 /// Mix-in for blocks that can retrieve the layout used to allocate them.
32 ///
33 /// Block mix-ins are stateless and trivially constructible. See `BasicBlock`
34 /// for details on how mix-ins can be combined to implement blocks.
35 ///
36 /// This mix-in requires its derived type also derive from `AlignableBlock`
37 /// and provide the following symbols:
38 ///
39 /// - size_t RequestedSize() const
40 ///   - Returns the size of the original layout
41 /// - size_t RequestedAlignment() const
42 ///   - Returns the alignment of the original layout
43 /// - void SetRequestedSize(size_t)
44 ///   - Records the size of the original layout
45 /// - void SetRequestedAlignment(size_t)
46 ///   - Records the alignment from the original layout
47 template <typename Derived>
48 class BlockWithLayout : public internal::BaseWithLayout {
49  protected:
BlockWithLayout()50   constexpr explicit BlockWithLayout() {
51     // Assert within a function, since `Derived` is not complete when this type
52     // is defined.
53     static_assert(is_alignable_v<Derived>,
54                   "Types derived from BlockWithLayout must also derive from "
55                   "AlignableBlock");
56   }
57 
58  public:
59   /// @returns The memory layout that was requested using AllocFirst, AllocLast,
60   /// or Resize, or FAILED_PRECONDITION if the block is free.
61   Result<Layout> RequestedLayout() const;
62 
63  protected:
64   /// @copydoc AllocatableBlock::AllocFirst
65   static BlockResult<Derived> DoAllocFirst(Derived*&& block, Layout layout);
66 
67   /// @copydoc AllocatableBlock::AllocLast
68   static BlockResult<Derived> DoAllocLast(Derived*&& block, Layout layout);
69 
70   /// @copydoc AllocatableBlock::Resize
71   BlockResult<Derived> DoResize(size_t new_inner_size, bool shifted = false);
72 
73   /// @copydoc AllocatableBlock::Free
74   static BlockResult<Derived> DoFree(Derived*&& block);
75 
76  private:
77   using BlockResultPrev = internal::GenericBlockResult::Prev;
78 
derived()79   constexpr Derived* derived() { return static_cast<Derived*>(this); }
derived()80   constexpr const Derived* derived() const {
81     return static_cast<const Derived*>(this);
82   }
83 };
84 
85 /// Trait type that allow interrogating a block as to whether it records the
86 /// requested layout.
87 template <typename BlockType>
88 struct has_layout : std::is_base_of<internal::BaseWithLayout, BlockType> {};
89 
90 /// Helper variable template for `has_layout<BlockType>::value`.
91 template <typename BlockType>
92 inline constexpr bool has_layout_v = has_layout<BlockType>::value;
93 
94 // Template method implementations.
95 
96 template <typename Derived>
RequestedLayout()97 Result<Layout> BlockWithLayout<Derived>::RequestedLayout() const {
98   derived()->CheckInvariantsIfStrict();
99   if (derived()->IsFree()) {
100     return Status::FailedPrecondition();
101   }
102   return Layout(derived()->RequestedSize(), derived()->RequestedAlignment());
103 }
104 
105 template <typename Derived>
DoAllocFirst(Derived * && block,Layout layout)106 BlockResult<Derived> BlockWithLayout<Derived>::DoAllocFirst(Derived*&& block,
107                                                             Layout layout) {
108   auto result = AlignableBlock<Derived>::DoAllocFirst(std::move(block), layout);
109   if (!result.ok()) {
110     return result;
111   }
112   block = result.block();
113   block->SetRequestedSize(layout.size());
114   block->SetRequestedAlignment(layout.alignment());
115   return result;
116 }
117 
118 template <typename Derived>
DoAllocLast(Derived * && block,Layout layout)119 BlockResult<Derived> BlockWithLayout<Derived>::DoAllocLast(Derived*&& block,
120                                                            Layout layout) {
121   auto result = AlignableBlock<Derived>::DoAllocLast(std::move(block), layout);
122   if (!result.ok()) {
123     return result;
124   }
125   block = result.block();
126   block->SetRequestedSize(layout.size());
127   block->SetRequestedAlignment(layout.alignment());
128   return result;
129 }
130 
131 template <typename Derived>
DoResize(size_t new_inner_size,bool shifted)132 BlockResult<Derived> BlockWithLayout<Derived>::DoResize(size_t new_inner_size,
133                                                         bool shifted) {
134   size_t old_size = derived()->RequestedSize();
135   auto result =
136       derived()->AllocatableBlock<Derived>::DoResize(new_inner_size, shifted);
137   if (result.ok() && !shifted) {
138     derived()->SetRequestedSize(new_inner_size);
139   } else {
140     derived()->SetRequestedSize(old_size);
141   }
142   return result;
143 }
144 
145 template <typename Derived>
DoFree(Derived * && block)146 BlockResult<Derived> BlockWithLayout<Derived>::DoFree(Derived*&& block) {
147   auto result = AllocatableBlock<Derived>::DoFree(std::move(block));
148   if (!result.ok()) {
149     return result;
150   }
151   block = result.block();
152   Derived* prev = block->Prev();
153   if (prev == nullptr) {
154     return result;
155   }
156   size_t prev_size = prev->RequestedSize();
157   if (prev->InnerSize() - prev_size < Derived::kAlignment) {
158     return result;
159   }
160   // Reclaim bytes that were shifted to prev when the block allocated.
161   size_t old_prev_size = prev->OuterSize();
162   prev->DoResize(prev_size, true).IgnoreUnlessStrict();
163   return BlockResult(prev->Next(),
164                      BlockResultPrev::kResizedSmaller,
165                      old_prev_size - prev->OuterSize());
166 }
167 
168 }  // namespace pw::allocator
169