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
15 #include "pw_allocator/first_fit.h"
16
17 #include <cstdint>
18
19 #include "pw_allocator/block/detailed_block.h"
20 #include "pw_allocator/block_allocator_testing.h"
21 #include "pw_allocator/buffer.h"
22 #include "pw_allocator/dual_first_fit_block_allocator.h"
23 #include "pw_allocator/first_fit_block_allocator.h"
24 #include "pw_allocator/last_fit_block_allocator.h"
25 #include "pw_unit_test/framework.h"
26
27 namespace {
28
29 using ::pw::allocator::Layout;
30 using ::pw::allocator::test::Preallocation;
31 using BlockType = ::pw::allocator::FirstFitBlock<uint16_t>;
32 using FirstFitAllocator = ::pw::allocator::FirstFitAllocator<BlockType>;
33 using ::pw::allocator::test::BlockAllocatorTest;
34
35 // Minimum size of a "large" allocation; allocation less than this size are
36 // considered "small" when using the "dual first fit" strategy.
37 static constexpr size_t kThreshold =
38 BlockAllocatorTest<FirstFitAllocator>::kSmallInnerSize * 2;
39
40 class FirstFitAllocatorTest : public BlockAllocatorTest<FirstFitAllocator> {
41 public:
FirstFitAllocatorTest()42 FirstFitAllocatorTest() : BlockAllocatorTest(allocator_) {}
43
44 private:
45 FirstFitAllocator allocator_;
46 };
47
TEST_F(FirstFitAllocatorTest,AutomaticallyInit)48 TEST_F(FirstFitAllocatorTest, AutomaticallyInit) {
49 FirstFitAllocator allocator(GetBytes());
50 AutomaticallyInit(allocator);
51 }
52
TEST_F(FirstFitAllocatorTest,ExplicitlyInit)53 TEST_F(FirstFitAllocatorTest, ExplicitlyInit) {
54 FirstFitAllocator allocator;
55 ExplicitlyInit(allocator);
56 }
57
TEST_F(FirstFitAllocatorTest,GetCapacity)58 TEST_F(FirstFitAllocatorTest, GetCapacity) { GetCapacity(); }
59
TEST_F(FirstFitAllocatorTest,AllocateLarge)60 TEST_F(FirstFitAllocatorTest, AllocateLarge) { AllocateLarge(); }
61
TEST_F(FirstFitAllocatorTest,AllocateSmall)62 TEST_F(FirstFitAllocatorTest, AllocateSmall) { AllocateSmall(); }
63
TEST_F(FirstFitAllocatorTest,AllocateLargeAlignment)64 TEST_F(FirstFitAllocatorTest, AllocateLargeAlignment) {
65 AllocateLargeAlignment();
66 }
67
TEST_F(FirstFitAllocatorTest,AllocateAlignmentFailure)68 TEST_F(FirstFitAllocatorTest, AllocateAlignmentFailure) {
69 AllocateAlignmentFailure();
70 }
71
TEST_F(FirstFitAllocatorTest,AllocatesZeroThreshold)72 TEST_F(FirstFitAllocatorTest, AllocatesZeroThreshold) {
73 FirstFitAllocator& allocator = GetAllocator({
74 {kSmallOuterSize, Preallocation::kFree},
75 {kSmallerOuterSize, Preallocation::kUsed},
76 {kSmallOuterSize, Preallocation::kFree},
77 {kSmallerOuterSize, Preallocation::kUsed},
78 {kLargeOuterSize, Preallocation::kFree},
79 {Preallocation::kSizeRemaining, Preallocation::kUsed},
80 });
81
82 Store(0, allocator.Allocate(Layout(kSmallInnerSize, 1)));
83 EXPECT_EQ(NextAfter(0), Fetch(1));
84 Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1)));
85 EXPECT_EQ(NextAfter(3), Fetch(4));
86 EXPECT_EQ(NextAfter(4), Fetch(5));
87 }
88
TEST_F(FirstFitAllocatorTest,AllocatesMaxThreshold)89 TEST_F(FirstFitAllocatorTest, AllocatesMaxThreshold) {
90 FirstFitAllocator& allocator = GetAllocator({
91 {kLargeOuterSize, Preallocation::kFree},
92 {kSmallerOuterSize, Preallocation::kUsed},
93 {kSmallOuterSize, Preallocation::kFree},
94 {kSmallerOuterSize, Preallocation::kUsed},
95 {kSmallOuterSize, Preallocation::kFree},
96 {Preallocation::kSizeRemaining, Preallocation::kUsed},
97 });
98 allocator.set_threshold(std::numeric_limits<size_t>::max());
99
100 Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1)));
101 EXPECT_EQ(NextAfter(0), Fetch(1));
102 Store(4, allocator.Allocate(Layout(kSmallInnerSize, 1)));
103 EXPECT_EQ(NextAfter(3), Fetch(4));
104 EXPECT_EQ(NextAfter(4), Fetch(5));
105 }
106
TEST_F(FirstFitAllocatorTest,AllocatesUsingThreshold)107 TEST_F(FirstFitAllocatorTest, AllocatesUsingThreshold) {
108 FirstFitAllocator& allocator = GetAllocator({
109 {kLargeOuterSize, Preallocation::kFree},
110 {kSmallerOuterSize, Preallocation::kUsed},
111 {kSmallOuterSize, Preallocation::kFree},
112 {Preallocation::kSizeRemaining, Preallocation::kUsed},
113 {kLargeOuterSize, Preallocation::kFree},
114 {kSmallerOuterSize, Preallocation::kUsed},
115 {kSmallOuterSize, Preallocation::kFree},
116 });
117 allocator.set_threshold(kThreshold);
118
119 Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1)));
120 EXPECT_EQ(NextAfter(0), Fetch(1));
121 Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1)));
122 EXPECT_EQ(NextAfter(3), Fetch(4));
123 EXPECT_EQ(NextAfter(4), Fetch(5));
124 Store(6, allocator.Allocate(Layout(kSmallInnerSize, 1)));
125 EXPECT_EQ(NextAfter(5), Fetch(6));
126 EXPECT_EQ(NextAfter(6), Fetch(7));
127 Store(2, allocator.Allocate(Layout(kSmallInnerSize, 1)));
128 EXPECT_EQ(NextAfter(1), Fetch(2));
129 EXPECT_EQ(NextAfter(2), Fetch(3));
130 }
131
TEST_F(FirstFitAllocatorTest,DeallocateNull)132 TEST_F(FirstFitAllocatorTest, DeallocateNull) { DeallocateNull(); }
133
TEST_F(FirstFitAllocatorTest,DeallocateShuffled)134 TEST_F(FirstFitAllocatorTest, DeallocateShuffled) { DeallocateShuffled(); }
135
TEST_F(FirstFitAllocatorTest,IterateOverBlocks)136 TEST_F(FirstFitAllocatorTest, IterateOverBlocks) { IterateOverBlocks(); }
137
TEST_F(FirstFitAllocatorTest,ResizeNull)138 TEST_F(FirstFitAllocatorTest, ResizeNull) { ResizeNull(); }
139
TEST_F(FirstFitAllocatorTest,ResizeLargeSame)140 TEST_F(FirstFitAllocatorTest, ResizeLargeSame) { ResizeLargeSame(); }
141
TEST_F(FirstFitAllocatorTest,ResizeLargeSmaller)142 TEST_F(FirstFitAllocatorTest, ResizeLargeSmaller) { ResizeLargeSmaller(); }
143
TEST_F(FirstFitAllocatorTest,ResizeLargeLarger)144 TEST_F(FirstFitAllocatorTest, ResizeLargeLarger) { ResizeLargeLarger(); }
145
TEST_F(FirstFitAllocatorTest,ResizeLargeLargerFailure)146 TEST_F(FirstFitAllocatorTest, ResizeLargeLargerFailure) {
147 ResizeLargeLargerFailure();
148 }
149
TEST_F(FirstFitAllocatorTest,ResizeSmallSame)150 TEST_F(FirstFitAllocatorTest, ResizeSmallSame) { ResizeSmallSame(); }
151
TEST_F(FirstFitAllocatorTest,ResizeSmallSmaller)152 TEST_F(FirstFitAllocatorTest, ResizeSmallSmaller) { ResizeSmallSmaller(); }
153
TEST_F(FirstFitAllocatorTest,ResizeSmallLarger)154 TEST_F(FirstFitAllocatorTest, ResizeSmallLarger) { ResizeSmallLarger(); }
155
TEST_F(FirstFitAllocatorTest,ResizeSmallLargerFailure)156 TEST_F(FirstFitAllocatorTest, ResizeSmallLargerFailure) {
157 ResizeSmallLargerFailure();
158 }
159
TEST_F(FirstFitAllocatorTest,ResizeLargeSmallerAcrossThreshold)160 TEST_F(FirstFitAllocatorTest, ResizeLargeSmallerAcrossThreshold) {
161 FirstFitAllocator& allocator = GetAllocator({
162 {kThreshold * 2, Preallocation::kUsed},
163 {Preallocation::kSizeRemaining, Preallocation::kFree},
164 });
165 allocator.set_threshold(kThreshold);
166
167 // Shrinking succeeds, and the pointer is unchanged even though it is now
168 // below the threshold.
169 size_t new_size = kThreshold / 2;
170 ASSERT_TRUE(allocator.Resize(Fetch(0), new_size));
171 UseMemory(Fetch(0), kThreshold / 2);
172 }
173
TEST_F(FirstFitAllocatorTest,ResizeSmallLargerAcrossThreshold)174 TEST_F(FirstFitAllocatorTest, ResizeSmallLargerAcrossThreshold) {
175 FirstFitAllocator& allocator = GetAllocator({
176 {Preallocation::kSizeRemaining, Preallocation::kUsed},
177 {kThreshold / 2, Preallocation::kUsed},
178 {kThreshold * 2, Preallocation::kFree},
179 });
180 allocator.set_threshold(kThreshold);
181
182 // Growing succeeds, and the pointer is unchanged even though it is now
183 // above the threshold.
184 size_t new_size = kThreshold * 2;
185 ASSERT_TRUE(allocator.Resize(Fetch(1), new_size));
186 UseMemory(Fetch(1), kThreshold * 2);
187 }
188
TEST_F(FirstFitAllocatorTest,MeasureFragmentation)189 TEST_F(FirstFitAllocatorTest, MeasureFragmentation) { MeasureFragmentation(); }
190
TEST_F(FirstFitAllocatorTest,PoisonPeriodically)191 TEST_F(FirstFitAllocatorTest, PoisonPeriodically) { PoisonPeriodically(); }
192
193 // TODO(b/376730645): Remove this test when the legacy alias is deprecated.
194 using DualFirstFitBlockAllocator =
195 ::pw::allocator::DualFirstFitBlockAllocator<uint16_t>;
196 class DualFirstFitBlockAllocatorTest
197 : public BlockAllocatorTest<DualFirstFitBlockAllocator> {
198 public:
DualFirstFitBlockAllocatorTest()199 DualFirstFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {}
200
201 private:
202 DualFirstFitBlockAllocator allocator_;
203 };
TEST_F(DualFirstFitBlockAllocatorTest,AllocatesUsingThreshold)204 TEST_F(DualFirstFitBlockAllocatorTest, AllocatesUsingThreshold) {
205 auto& allocator = GetAllocator({
206 {kLargeOuterSize, Preallocation::kFree},
207 {kSmallerOuterSize, Preallocation::kUsed},
208 {kSmallOuterSize, Preallocation::kFree},
209 {Preallocation::kSizeRemaining, Preallocation::kUsed},
210 {kLargeOuterSize, Preallocation::kFree},
211 {kSmallerOuterSize, Preallocation::kUsed},
212 {kSmallOuterSize, Preallocation::kFree},
213 });
214 allocator.set_threshold(kThreshold);
215
216 Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1)));
217 EXPECT_EQ(NextAfter(0), Fetch(1));
218 Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1)));
219 EXPECT_EQ(NextAfter(3), Fetch(4));
220 EXPECT_EQ(NextAfter(4), Fetch(5));
221 Store(6, allocator.Allocate(Layout(kSmallInnerSize, 1)));
222 EXPECT_EQ(NextAfter(5), Fetch(6));
223 EXPECT_EQ(NextAfter(6), Fetch(7));
224 Store(2, allocator.Allocate(Layout(kSmallInnerSize, 1)));
225 EXPECT_EQ(NextAfter(1), Fetch(2));
226 EXPECT_EQ(NextAfter(2), Fetch(3));
227 }
228
229 // TODO(b/376730645): Remove this test when the legacy alias is deprecated.
230 using FirstFitBlockAllocator =
231 ::pw::allocator::FirstFitBlockAllocator<uint16_t>;
232 class FirstFitBlockAllocatorTest
233 : public BlockAllocatorTest<FirstFitBlockAllocator> {
234 public:
FirstFitBlockAllocatorTest()235 FirstFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {}
236
237 private:
238 FirstFitBlockAllocator allocator_;
239 };
TEST_F(FirstFitBlockAllocatorTest,AllocatesFirstCompatible)240 TEST_F(FirstFitBlockAllocatorTest, AllocatesFirstCompatible) {
241 auto& allocator = GetAllocator({
242 {kSmallOuterSize, Preallocation::kFree},
243 {kSmallerOuterSize, Preallocation::kUsed},
244 {kSmallOuterSize, Preallocation::kFree},
245 {kSmallerOuterSize, Preallocation::kUsed},
246 {kLargeOuterSize, Preallocation::kFree},
247 {Preallocation::kSizeRemaining, Preallocation::kUsed},
248 });
249
250 Store(0, allocator.Allocate(Layout(kSmallInnerSize, 1)));
251 EXPECT_EQ(NextAfter(0), Fetch(1));
252 Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1)));
253 EXPECT_EQ(NextAfter(3), Fetch(4));
254 EXPECT_EQ(NextAfter(4), Fetch(5));
255 }
256
257 // TODO(b/376730645): Remove this test when the legacy alias is deprecated.
258 using LastFitBlockAllocator = ::pw::allocator::LastFitBlockAllocator<uint16_t>;
259 class LastFitBlockAllocatorTest
260 : public BlockAllocatorTest<LastFitBlockAllocator> {
261 public:
LastFitBlockAllocatorTest()262 LastFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {}
263
264 private:
265 LastFitBlockAllocator allocator_;
266 };
TEST_F(LastFitBlockAllocatorTest,AllocatesLastCompatible)267 TEST_F(LastFitBlockAllocatorTest, AllocatesLastCompatible) {
268 auto& allocator = GetAllocator({
269 {kLargeOuterSize, Preallocation::kFree},
270 {kSmallerOuterSize, Preallocation::kUsed},
271 {kSmallOuterSize, Preallocation::kFree},
272 {kSmallerOuterSize, Preallocation::kUsed},
273 {kSmallOuterSize, Preallocation::kFree},
274 {Preallocation::kSizeRemaining, Preallocation::kUsed},
275 });
276
277 Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1)));
278 EXPECT_EQ(NextAfter(0), Fetch(1));
279 Store(4, allocator.Allocate(Layout(kSmallInnerSize, 1)));
280 EXPECT_EQ(NextAfter(3), Fetch(4));
281 EXPECT_EQ(NextAfter(4), Fetch(5));
282 }
283
284 } // namespace
285