xref: /aosp_15_r20/external/pigweed/pw_allocator/bucket_allocator_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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