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.
61 ///
62 /// @pre The block must be in use.
63 constexpr Layout RequestedLayout() const;
64
65 protected:
66 /// @copydoc AllocatableBlock::AllocFirst
67 static constexpr BlockResult<Derived> DoAllocFirst(Derived*&& block,
68 Layout layout);
69
70 /// @copydoc AllocatableBlock::AllocLast
71 static constexpr BlockResult<Derived> DoAllocLast(Derived*&& block,
72 Layout layout);
73
74 /// @copydoc AllocatableBlock::Resize
75 constexpr BlockResult<Derived> DoResize(size_t new_inner_size,
76 bool shifted = false);
77
78 /// @copydoc AllocatableBlock::Free
79 static constexpr BlockResult<Derived> DoFree(Derived*&& block);
80
81 private:
82 using BlockResultPrev = internal::GenericBlockResult::Prev;
83
derived()84 constexpr Derived* derived() { return static_cast<Derived*>(this); }
derived()85 constexpr const Derived* derived() const {
86 return static_cast<const Derived*>(this);
87 }
88 };
89
90 /// Trait type that allow interrogating a block as to whether it records the
91 /// requested layout.
92 template <typename BlockType>
93 struct has_layout : std::is_base_of<internal::BaseWithLayout, BlockType> {};
94
95 /// Helper variable template for `has_layout<BlockType>::value`.
96 template <typename BlockType>
97 constexpr bool has_layout_v = has_layout<BlockType>::value;
98
99 // Template method implementations.
100
101 template <typename Derived>
RequestedLayout()102 constexpr Layout BlockWithLayout<Derived>::RequestedLayout() const {
103 if constexpr (Hardening::kIncludesDebugChecks) {
104 derived()->CheckInvariants();
105 }
106 if constexpr (Hardening::kIncludesRobustChecks) {
107 PW_ASSERT(!derived()->IsFree());
108 }
109 return Layout(derived()->RequestedSize(), derived()->RequestedAlignment());
110 }
111
112 template <typename Derived>
DoAllocFirst(Derived * && block,Layout layout)113 constexpr BlockResult<Derived> BlockWithLayout<Derived>::DoAllocFirst(
114 Derived*&& block, Layout layout) {
115 auto result = AlignableBlock<Derived>::DoAllocFirst(std::move(block), layout);
116 if (!result.ok()) {
117 return result;
118 }
119 block = result.block();
120 block->SetRequestedSize(layout.size());
121 block->SetRequestedAlignment(layout.alignment());
122 return result;
123 }
124
125 template <typename Derived>
DoAllocLast(Derived * && block,Layout layout)126 constexpr BlockResult<Derived> BlockWithLayout<Derived>::DoAllocLast(
127 Derived*&& block, Layout layout) {
128 auto result = AlignableBlock<Derived>::DoAllocLast(std::move(block), layout);
129 if (!result.ok()) {
130 return result;
131 }
132 block = result.block();
133 block->SetRequestedSize(layout.size());
134 block->SetRequestedAlignment(layout.alignment());
135 return result;
136 }
137
138 template <typename Derived>
DoResize(size_t new_inner_size,bool shifted)139 constexpr BlockResult<Derived> BlockWithLayout<Derived>::DoResize(
140 size_t new_inner_size, bool shifted) {
141 size_t old_size = derived()->RequestedSize();
142 auto result =
143 derived()->AllocatableBlock<Derived>::DoResize(new_inner_size, shifted);
144 if (result.ok() && !shifted) {
145 derived()->SetRequestedSize(new_inner_size);
146 } else {
147 derived()->SetRequestedSize(old_size);
148 }
149 return result;
150 }
151
152 template <typename Derived>
DoFree(Derived * && block)153 constexpr BlockResult<Derived> BlockWithLayout<Derived>::DoFree(
154 Derived*&& block) {
155 auto result = AllocatableBlock<Derived>::DoFree(std::move(block));
156 if (!result.ok()) {
157 return result;
158 }
159 block = result.block();
160 Derived* prev = block->Prev();
161 if (prev == nullptr) {
162 return result;
163 }
164 size_t prev_size = prev->RequestedSize();
165 if (prev->InnerSize() - prev_size < Derived::kAlignment) {
166 return result;
167 }
168 // Reclaim bytes that were shifted to prev when the block allocated.
169 size_t old_prev_size = prev->OuterSize();
170 prev->DoResize(prev_size, true).IgnoreUnlessStrict();
171 return BlockResult(prev->Next(),
172 BlockResultPrev::kResizedSmaller,
173 old_prev_size - prev->OuterSize());
174 }
175
176 } // namespace pw::allocator
177