• 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 <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