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