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