• 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 "lib/stdcompat/bit.h"
17 #include "pw_allocator/block/basic.h"
18 #include "pw_allocator/hardening.h"
19 #include "pw_bytes/span.h"
20 
21 namespace pw::allocator {
22 namespace internal {
23 
24 // Trivial base class for trait support.
25 struct ContiguousBase {};
26 
27 }  // namespace internal
28 
29 /// Mix-in for blocks that collectively represent a contiguous region of memory.
30 ///
31 /// Contiguous blocks can be split into smaller blocks and merged when adjacent.
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 `BasicBlock`, and
37 /// provide the following symbols:
38 ///
39 /// - size_t PrevOuterSizeUnchecked() const
40 ///   - Returns the outer size of the previous block, if any, or zero.  Must be
41 ///     multiple of `kAlignment`.
42 /// - bool IsLastUnchecked() const
43 ///   - Returns whether this block is the last block.
44 template <typename Derived>
45 class ContiguousBlock : public internal::ContiguousBase {
46  protected:
ContiguousBlock()47   constexpr explicit ContiguousBlock() {
48     // Assert within a function, since `Derived` is not complete when this type
49     // is defined.
50     static_assert(
51         is_block_v<Derived>,
52         "Types derived from ContiguousBlock must also derive from BasicBlock");
53   }
54 
55  public:
56   /// @returns the block immediately before this one, or null if this is the
57   /// first block.
58   constexpr Derived* Prev() const;
59 
60   /// @returns the block immediately after this one, or null if this is the last
61   /// block.
62   constexpr Derived* Next() const;
63 
64  protected:
65   /// Split a block into two smaller blocks.
66   ///
67   /// This method splits a block into a leading block of the given
68   /// `new_inner_size` and a trailing block, and returns the trailing space as a
69   /// new block.
70   ///
71   /// @pre The block must not be in use.
72   /// @pre The block must have enough usable space for the requested size.
73   /// @pre The space remaining after a split can hold a new block.
74   constexpr Derived* DoSplitFirst(size_t new_inner_size);
75 
76   /// Split a block into two smaller blocks.
77   ///
78   /// This method splits a block into a leading block and a trailing block of
79   /// the given `new_inner_size`, and returns the trailing space is returned as
80   /// a new block.
81   ///
82   /// @pre The block must not be in use.
83   /// @pre The block must have enough usable space for the requested size.
84   /// @pre The space remaining after a split can hold a new block.
85   constexpr Derived* DoSplitLast(size_t new_inner_size);
86 
87   /// Merges this block with next block.
88   ///
89   /// This method is static in order to consume and replace the given block
90   /// pointer with a pointer to the new, larger block.
91   ///
92   /// @pre The blocks must not be in use.
93   constexpr void DoMergeNext();
94 
95   /// Performs the ContiguousBlock invariant checks.
96   constexpr bool DoCheckInvariants(bool strict) const;
97 
98  private:
derived()99   constexpr Derived* derived() { return static_cast<Derived*>(this); }
derived()100   constexpr const Derived* derived() const {
101     return static_cast<const Derived*>(this);
102   }
103 
104   /// @copydoc Prev
105   constexpr Derived* PrevUnchecked() const;
106 
107   /// @copydoc Next
108   constexpr Derived* NextUnchecked() const;
109 
110   /// Split a block into two smaller blocks.
111   ///
112   /// This method is static in order to consume and replace the given block
113   /// pointer with a pointer to the new, smaller block with an inner size of
114   /// `new_inner_size` The remaining space will be returned as a new block.
115   ///
116   /// @pre The block must not be in use.
117   /// @pre The block must have enough usable space for the requested size.
118   /// @pre The space remaining after a split can hold a new block.
119   static constexpr Derived* Split(Derived*& block, size_t new_inner_size);
120 
121   /// Consumes the block and returns as a span of bytes.
122   static constexpr ByteSpan AsBytes(Derived*&& block);
123 
124   // PoisonableBlock calls DoSplitFirst, DoSplitLast, and DoMergeNext
125   template <typename>
126   friend class PoisonableBlock;
127 };
128 
129 /// Trait type that allow interrogating a block as to whether it is contiguous.
130 template <typename BlockType>
131 struct is_contiguous : std::is_base_of<internal::ContiguousBase, BlockType> {};
132 
133 /// Helper variable template for `is_contiguous<BlockType>::value`.
134 template <typename BlockType>
135 constexpr bool is_contiguous_v = is_contiguous<BlockType>::value;
136 
137 namespace internal {
138 
139 /// Functions to crash with an error message describing which block invariant
140 /// has been violated. These functions are implemented independent of any
141 /// template parameters to allow them to use `PW_CHECK`.
142 
143 /// Crashes with an error message about the next block being misaligned if
144 /// `next_is_aligned` is false.
145 void CheckNextMisaligned(const void* block,
146                          const void* next,
147                          bool next_is_aligned);
148 
149 /// Crashes with an error message about the next block being corrupted if
150 /// `next_prev_matches` is false.
151 void CheckNextPrevMismatched(const void* block,
152                              const void* next,
153                              const void* next_prev,
154                              bool next_prev_matches);
155 
156 /// Crashes with an error message about the previous block being misaligned if
157 /// `prev_is_aligned` is false.
158 void CheckPrevMisaligned(const void* block,
159                          const void* prev,
160                          bool prev_is_aligned);
161 
162 /// Crashes with an error message about the previous block being corrupted if
163 /// `prev_next_matches` is false.
164 void CheckPrevNextMismatched(const void* block,
165                              const void* prev,
166                              const void* prev_next,
167                              bool prev_next_matches);
168 
169 }  // namespace internal
170 
171 // Template method implementations.
172 
173 template <typename Derived>
Prev()174 constexpr Derived* ContiguousBlock<Derived>::Prev() const {
175   if constexpr (Hardening::kIncludesDebugChecks) {
176     derived()->CheckInvariants();
177   }
178   return PrevUnchecked();
179 }
180 
181 template <typename Derived>
PrevUnchecked()182 constexpr Derived* ContiguousBlock<Derived>::PrevUnchecked() const {
183   size_t prev_outer_size = derived()->PrevOuterSizeUnchecked();
184   if (prev_outer_size == 0) {
185     return nullptr;
186   }
187   auto addr = cpp20::bit_cast<uintptr_t>(this);
188   Hardening::Decrement(addr, prev_outer_size);
189   return std::launder(reinterpret_cast<Derived*>(addr));
190 }
191 
192 template <typename Derived>
Next()193 constexpr Derived* ContiguousBlock<Derived>::Next() const {
194   if constexpr (Hardening::kIncludesDebugChecks) {
195     derived()->CheckInvariants();
196   }
197   return NextUnchecked();
198 }
199 
200 template <typename Derived>
NextUnchecked()201 constexpr Derived* ContiguousBlock<Derived>::NextUnchecked() const {
202   if (derived()->IsLastUnchecked()) {
203     return nullptr;
204   }
205   size_t outer_size = derived()->OuterSizeUnchecked();
206   auto addr = cpp20::bit_cast<uintptr_t>(this);
207   Hardening::Increment(addr, outer_size);
208   return std::launder(reinterpret_cast<Derived*>(addr));
209 }
210 
211 template <typename Derived>
DoSplitFirst(size_t new_inner_size)212 constexpr Derived* ContiguousBlock<Derived>::DoSplitFirst(
213     size_t new_inner_size) {
214   Derived* next = derived()->Next();
215   size_t new_outer_size = Derived::kBlockOverhead + new_inner_size;
216   ByteSpan bytes(derived()->UsableSpace(), derived()->InnerSize());
217   bytes = bytes.subspan(new_inner_size);
218   auto* trailing = Derived::AsBlock(bytes);
219   derived()->SetNext(new_outer_size, trailing);
220   trailing->SetNext(bytes.size(), next);
221   return trailing;
222 }
223 
224 template <typename Derived>
DoSplitLast(size_t new_inner_size)225 constexpr Derived* ContiguousBlock<Derived>::DoSplitLast(
226     size_t new_inner_size) {
227   size_t new_outer_size = Derived::kBlockOverhead + new_inner_size;
228   return DoSplitFirst(derived()->InnerSize() - new_outer_size);
229 }
230 
231 template <typename Derived>
DoMergeNext()232 constexpr void ContiguousBlock<Derived>::DoMergeNext() {
233   Derived* next = derived()->Next();
234   if (next != nullptr) {
235     size_t outer_size = derived()->OuterSize() + next->OuterSize();
236     derived()->SetNext(outer_size, next->Next());
237   }
238 }
239 
240 template <typename Derived>
DoCheckInvariants(bool strict)241 constexpr bool ContiguousBlock<Derived>::DoCheckInvariants(bool strict) const {
242   bool valid = true;
243 
244   Derived* next = derived()->NextUnchecked();
245   if (next != nullptr) {
246     valid &= (cpp20::bit_cast<uintptr_t>(next) % Derived::kAlignment) == 0;
247     if constexpr (Hardening::kIncludesDebugChecks) {
248       internal::CheckNextMisaligned(this, next, valid || !strict);
249     }
250 
251     Derived* next_prev = next->PrevUnchecked();
252     valid &= this == next_prev;
253     if constexpr (Hardening::kIncludesDebugChecks) {
254       internal::CheckNextPrevMismatched(
255           this, next, next_prev, valid || !strict);
256     }
257   }
258 
259   Derived* prev = derived()->PrevUnchecked();
260   if (prev != nullptr) {
261     valid &= (cpp20::bit_cast<uintptr_t>(prev) % Derived::kAlignment) == 0;
262     if constexpr (Hardening::kIncludesDebugChecks) {
263       internal::CheckPrevMisaligned(this, prev, valid || !strict);
264     }
265 
266     Derived* prev_next = prev->NextUnchecked();
267     valid &= this == prev_next;
268     if constexpr (Hardening::kIncludesDebugChecks) {
269       internal::CheckPrevNextMismatched(
270           this, prev, prev_next, valid || !strict);
271     }
272   }
273 
274   return valid;
275 }
276 
277 }  // namespace pw::allocator
278