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/bucket_allocator.h"
16
17 #include "pw_allocator/allocator.h"
18 #include "pw_allocator/block_allocator_testing.h"
19 #include "pw_allocator/bucket_block_allocator.h"
20 #include "pw_unit_test/framework.h"
21
22 namespace {
23
24 // Test fixtures.
25
26 constexpr size_t kMinChunkSize = 64;
27 constexpr size_t kNumBuckets = 4;
28
29 using ::pw::allocator::Layout;
30 using ::pw::allocator::test::BlockAllocatorTest;
31 using ::pw::allocator::test::Preallocation;
32 using BlockType = ::pw::allocator::BucketBlock<uint16_t>;
33 using BucketAllocator =
34 ::pw::allocator::BucketAllocator<BlockType, kMinChunkSize, kNumBuckets>;
35
36 class BucketAllocatorTest : public BlockAllocatorTest<BucketAllocator> {
37 public:
BucketAllocatorTest()38 BucketAllocatorTest() : BlockAllocatorTest(allocator_) {}
39
40 private:
41 BucketAllocator allocator_;
42 };
43
44 // Unit tests.
45
TEST_F(BucketAllocatorTest,AutomaticallyInit)46 TEST_F(BucketAllocatorTest, AutomaticallyInit) {
47 BucketAllocator allocator(GetBytes());
48 AutomaticallyInit(allocator);
49 }
50
TEST_F(BucketAllocatorTest,ExplicitlyInit)51 TEST_F(BucketAllocatorTest, ExplicitlyInit) {
52 BucketAllocator allocator;
53 ExplicitlyInit(allocator);
54 }
55
TEST_F(BucketAllocatorTest,GetCapacity)56 TEST_F(BucketAllocatorTest, GetCapacity) { GetCapacity(); }
57
TEST_F(BucketAllocatorTest,AllocateLarge)58 TEST_F(BucketAllocatorTest, AllocateLarge) { AllocateLarge(); }
59
TEST_F(BucketAllocatorTest,AllocateSmall)60 TEST_F(BucketAllocatorTest, AllocateSmall) { AllocateSmall(); }
61
TEST_F(BucketAllocatorTest,AllocateLargeAlignment)62 TEST_F(BucketAllocatorTest, AllocateLargeAlignment) {
63 AllocateLargeAlignment();
64 }
65
TEST_F(BucketAllocatorTest,AllocateAlignmentFailure)66 TEST_F(BucketAllocatorTest, AllocateAlignmentFailure) {
67 AllocateAlignmentFailure();
68 }
69
TEST_F(BucketAllocatorTest,AllocatesFromCompatibleBucket)70 TEST_F(BucketAllocatorTest, AllocatesFromCompatibleBucket) {
71 // Bucket sizes are: [ 64, 128, 256 ]
72 // Start with everything allocated in order to recycle blocks into buckets.
73 auto& allocator = GetAllocator({
74 {63 + BlockType::kBlockOverhead, Preallocation::kUsed},
75 {kSmallerOuterSize, Preallocation::kUsed},
76 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
77 {kSmallerOuterSize, Preallocation::kUsed},
78 {255 + BlockType::kBlockOverhead, Preallocation::kUsed},
79 {kSmallerOuterSize, Preallocation::kUsed},
80 {257 + BlockType::kBlockOverhead, Preallocation::kUsed},
81 {Preallocation::kSizeRemaining, Preallocation::kUsed},
82 });
83
84 // Deallocate to fill buckets.
85 void* bucket0_ptr = Fetch(0);
86 Store(0, nullptr);
87 allocator.Deallocate(bucket0_ptr);
88
89 void* bucket1_ptr = Fetch(2);
90 Store(2, nullptr);
91 allocator.Deallocate(bucket1_ptr);
92
93 void* bucket2_ptr = Fetch(4);
94 Store(4, nullptr);
95 allocator.Deallocate(bucket2_ptr);
96
97 // Bucket 3 is the implicit, unbounded bucket.
98 void* bucket3_ptr = Fetch(6);
99 Store(6, nullptr);
100 allocator.Deallocate(bucket3_ptr);
101
102 // Allocate in a different order. The correct bucket should be picked for each
103 // allocation
104
105 // The allocation from bucket 2 splits off a leading block.
106 Store(4, allocator.Allocate(Layout(129, 1)));
107 auto* block2 = BlockType::FromUsableSpace(bucket2_ptr);
108 EXPECT_TRUE(block2->Next()->IsFree());
109 EXPECT_EQ(Fetch(4), block2->UsableSpace());
110
111 // This allocation exactly matches the max inner size of bucket 1.
112 Store(2, allocator.Allocate(Layout(128, 1)));
113 EXPECT_EQ(Fetch(2), bucket1_ptr);
114
115 // 129 should start with bucket 2, then use bucket 3 since 2 is empty.
116 // The allocation from bucket 3 splits off a leading block.
117 auto* block3 = BlockType::FromUsableSpace(bucket3_ptr);
118 Store(6, allocator.Allocate(Layout(129, 1)));
119 EXPECT_TRUE(block3->Next()->IsFree());
120 EXPECT_EQ(Fetch(6), block3->UsableSpace());
121
122 // The allocation from bucket 0 splits off a leading block.
123 auto* block0 = BlockType::FromUsableSpace(bucket0_ptr);
124 Store(0, allocator.Allocate(Layout(32, 1)));
125 EXPECT_TRUE(block0->Next()->IsFree());
126 EXPECT_EQ(Fetch(0), block0->UsableSpace());
127 }
128
TEST_F(BucketAllocatorTest,UnusedPortionIsRecycled)129 TEST_F(BucketAllocatorTest, UnusedPortionIsRecycled) {
130 auto& allocator = GetAllocator({
131 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
132 {Preallocation::kSizeRemaining, Preallocation::kUsed},
133 });
134
135 // Deallocate to fill buckets.
136 allocator.Deallocate(Fetch(0));
137 Store(0, nullptr);
138
139 Store(2, allocator.Allocate(Layout(65, 1)));
140 ASSERT_NE(Fetch(2), nullptr);
141
142 // The remainder should be recycled to a smaller bucket.
143 Store(3, allocator.Allocate(Layout(32, 1)));
144 ASSERT_NE(Fetch(3), nullptr);
145 }
146
TEST_F(BucketAllocatorTest,ExhaustBucket)147 TEST_F(BucketAllocatorTest, ExhaustBucket) {
148 auto& allocator = GetAllocator({
149 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
150 {kSmallerOuterSize, Preallocation::kUsed},
151 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
152 {kSmallerOuterSize, Preallocation::kUsed},
153 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
154 {Preallocation::kSizeRemaining, Preallocation::kUsed},
155 });
156
157 // Deallocate to fill buckets.
158 allocator.Deallocate(Fetch(0));
159 Store(0, nullptr);
160 allocator.Deallocate(Fetch(2));
161 Store(2, nullptr);
162 allocator.Deallocate(Fetch(4));
163 Store(4, nullptr);
164
165 void* ptr0 = allocator.Allocate(Layout(65, 1));
166 EXPECT_NE(ptr0, nullptr);
167 Store(0, ptr0);
168
169 void* ptr2 = allocator.Allocate(Layout(65, 1));
170 EXPECT_NE(ptr2, nullptr);
171 Store(2, ptr2);
172
173 void* ptr4 = allocator.Allocate(Layout(65, 1));
174 EXPECT_NE(ptr4, nullptr);
175 Store(4, ptr4);
176
177 EXPECT_EQ(allocator.Allocate(Layout(65, 1)), nullptr);
178 }
179
TEST_F(BucketAllocatorTest,DeallocateNull)180 TEST_F(BucketAllocatorTest, DeallocateNull) { DeallocateNull(); }
181
TEST_F(BucketAllocatorTest,DeallocateShuffled)182 TEST_F(BucketAllocatorTest, DeallocateShuffled) { DeallocateShuffled(); }
183
TEST_F(BucketAllocatorTest,IterateOverBlocks)184 TEST_F(BucketAllocatorTest, IterateOverBlocks) { IterateOverBlocks(); }
185
TEST_F(BucketAllocatorTest,ResizeNull)186 TEST_F(BucketAllocatorTest, ResizeNull) { ResizeNull(); }
187
TEST_F(BucketAllocatorTest,ResizeLargeSame)188 TEST_F(BucketAllocatorTest, ResizeLargeSame) { ResizeLargeSame(); }
189
TEST_F(BucketAllocatorTest,ResizeLargeSmaller)190 TEST_F(BucketAllocatorTest, ResizeLargeSmaller) { ResizeLargeSmaller(); }
191
TEST_F(BucketAllocatorTest,ResizeLargeLarger)192 TEST_F(BucketAllocatorTest, ResizeLargeLarger) { ResizeLargeLarger(); }
193
TEST_F(BucketAllocatorTest,ResizeLargeLargerFailure)194 TEST_F(BucketAllocatorTest, ResizeLargeLargerFailure) {
195 ResizeLargeLargerFailure();
196 }
197
TEST_F(BucketAllocatorTest,ResizeSmallSame)198 TEST_F(BucketAllocatorTest, ResizeSmallSame) { ResizeSmallSame(); }
199
TEST_F(BucketAllocatorTest,ResizeSmallSmaller)200 TEST_F(BucketAllocatorTest, ResizeSmallSmaller) { ResizeSmallSmaller(); }
201
TEST_F(BucketAllocatorTest,ResizeSmallLarger)202 TEST_F(BucketAllocatorTest, ResizeSmallLarger) { ResizeSmallLarger(); }
203
TEST_F(BucketAllocatorTest,ResizeSmallLargerFailure)204 TEST_F(BucketAllocatorTest, ResizeSmallLargerFailure) {
205 ResizeSmallLargerFailure();
206 }
207
TEST_F(BucketAllocatorTest,MeasureFragmentation)208 TEST_F(BucketAllocatorTest, MeasureFragmentation) { MeasureFragmentation(); }
209
TEST_F(BucketAllocatorTest,PoisonPeriodically)210 TEST_F(BucketAllocatorTest, PoisonPeriodically) { PoisonPeriodically(); }
211
212 // TODO(b/376730645): Remove this test when the legacy alias is deprecated.
213 using BucketBlockAllocator = ::pw::allocator::BucketBlockAllocator<uint16_t>;
214
215 class BucketBlockAllocatorTest
216 : public BlockAllocatorTest<BucketBlockAllocator> {
217 public:
BucketBlockAllocatorTest()218 BucketBlockAllocatorTest() : BlockAllocatorTest(allocator_) {}
219
220 private:
221 BucketBlockAllocator allocator_;
222 };
223
TEST_F(BucketBlockAllocatorTest,AllocatesFromCompatibleBucket)224 TEST_F(BucketBlockAllocatorTest, AllocatesFromCompatibleBucket) {
225 // Bucket sizes are: [ 64, 128, 256 ]
226 // Start with everything allocated in order to recycle blocks into buckets.
227 auto& allocator = GetAllocator({
228 {63 + BlockType::kBlockOverhead, Preallocation::kUsed},
229 {kSmallerOuterSize, Preallocation::kUsed},
230 {128 + BlockType::kBlockOverhead, Preallocation::kUsed},
231 {kSmallerOuterSize, Preallocation::kUsed},
232 {255 + BlockType::kBlockOverhead, Preallocation::kUsed},
233 {kSmallerOuterSize, Preallocation::kUsed},
234 {257 + BlockType::kBlockOverhead, Preallocation::kUsed},
235 {Preallocation::kSizeRemaining, Preallocation::kUsed},
236 });
237
238 // Deallocate to fill buckets.
239 void* bucket0_ptr = Fetch(0);
240 Store(0, nullptr);
241 allocator.Deallocate(bucket0_ptr);
242
243 void* bucket1_ptr = Fetch(2);
244 Store(2, nullptr);
245 allocator.Deallocate(bucket1_ptr);
246
247 void* bucket2_ptr = Fetch(4);
248 Store(4, nullptr);
249 allocator.Deallocate(bucket2_ptr);
250
251 // Bucket 3 is the implicit, unbounded bucket.
252 void* bucket3_ptr = Fetch(6);
253 Store(6, nullptr);
254 allocator.Deallocate(bucket3_ptr);
255
256 // Allocate in a different order. The correct bucket should be picked for each
257 // allocation
258
259 // The allocation from bucket 2 splits off a leading block.
260 Store(4, allocator.Allocate(Layout(129, 1)));
261 auto* block2 = BlockType::FromUsableSpace(bucket2_ptr);
262 EXPECT_TRUE(block2->Next()->IsFree());
263 EXPECT_EQ(Fetch(4), block2->UsableSpace());
264
265 // This allocation exactly matches the max inner size of bucket 1.
266 Store(2, allocator.Allocate(Layout(128, 1)));
267 EXPECT_EQ(Fetch(2), bucket1_ptr);
268
269 // 129 should start with bucket 2, then use bucket 3 since 2 is empty.
270 // The allocation from bucket 3 splits off a leading block.
271 auto* block3 = BlockType::FromUsableSpace(bucket3_ptr);
272 Store(6, allocator.Allocate(Layout(129, 1)));
273 EXPECT_TRUE(block3->Next()->IsFree());
274 EXPECT_EQ(Fetch(6), block3->UsableSpace());
275
276 // The allocation from bucket 0 splits off a leading block.
277 auto* block0 = BlockType::FromUsableSpace(bucket0_ptr);
278 Store(0, allocator.Allocate(Layout(32, 1)));
279 EXPECT_TRUE(block0->Next()->IsFree());
280 EXPECT_EQ(Fetch(0), block0->UsableSpace());
281 }
282
283 } // namespace
284