1 // Copyright 2020 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/freelist_heap.h"
16
17 #include "lib/stdcompat/bit.h"
18 #include "pw_allocator/block/testing.h"
19 #include "pw_bytes/alignment.h"
20 #include "pw_unit_test/framework.h"
21
22 namespace {
23
24 // Test fixtures.
25
26 using ::pw::allocator::FreeListHeapBuffer;
27 using ::pw::allocator::test::GetAlignedOffsetAfter;
28
29 class FreeListHeapBufferTest : public ::testing::Test {
30 protected:
31 using BlockType = ::pw::allocator::DetailedBlock<>;
32
33 static constexpr size_t kN = 2048;
34
35 alignas(BlockType::kAlignment) std::array<std::byte, kN> buffer_;
36 };
37
38 // Unit tests.
39
TEST_F(FreeListHeapBufferTest,CanAllocate)40 TEST_F(FreeListHeapBufferTest, CanAllocate) {
41 FreeListHeapBuffer allocator(buffer_);
42
43 void* ptr = allocator.Allocate(kN / 4);
44 ASSERT_NE(ptr, nullptr);
45
46 // The returned memory should be within the allocator's memory...
47 EXPECT_GE(ptr, buffer_.data());
48 EXPECT_LT(ptr, buffer_.data() + buffer_.size());
49
50 // ...and should be usable.
51 std::memset(ptr, 0xff, kN / 4);
52
53 // All pointers must be freed before the allocator goes out of scope.
54 allocator.Free(ptr);
55 }
56
TEST_F(FreeListHeapBufferTest,AllocationsDontOverlap)57 TEST_F(FreeListHeapBufferTest, AllocationsDontOverlap) {
58 FreeListHeapBuffer allocator(buffer_);
59
60 void* ptr1 = allocator.Allocate(kN / 4);
61 ASSERT_NE(ptr1, nullptr);
62 uintptr_t ptr1_start = cpp20::bit_cast<uintptr_t>(ptr1);
63 uintptr_t ptr1_end = ptr1_start + (kN / 4);
64
65 void* ptr2 = allocator.Allocate(kN / 4);
66 ASSERT_NE(ptr2, nullptr);
67 uintptr_t ptr2_start = cpp20::bit_cast<uintptr_t>(ptr2);
68 uintptr_t ptr2_end = ptr2_start + (kN / 4);
69
70 if (ptr1 < ptr2) {
71 EXPECT_LT(ptr1_end, ptr2_start);
72 } else {
73 EXPECT_LT(ptr2_end, ptr1_start);
74 }
75
76 // All pointers must be freed before the allocator goes out of scope.
77 allocator.Free(ptr1);
78 allocator.Free(ptr2);
79 }
80
TEST_F(FreeListHeapBufferTest,CanFreeAndRealloc)81 TEST_F(FreeListHeapBufferTest, CanFreeAndRealloc) {
82 FreeListHeapBuffer allocator(buffer_);
83
84 void* ptr1 = allocator.Allocate(kN / 4);
85 allocator.Free(ptr1);
86
87 // There's not really a nice way to test that Free works, apart from to try
88 // and get that value back again.
89 void* ptr2 = allocator.Allocate(kN / 4);
90
91 EXPECT_EQ(ptr1, ptr2);
92
93 // All pointers must be freed before the allocator goes out of scope.
94 allocator.Free(ptr2);
95 }
96
TEST_F(FreeListHeapBufferTest,ReturnsNullWhenAllocationTooLarge)97 TEST_F(FreeListHeapBufferTest, ReturnsNullWhenAllocationTooLarge) {
98 FreeListHeapBuffer allocator(buffer_);
99
100 EXPECT_EQ(allocator.Allocate(kN), nullptr);
101 }
102
TEST_F(FreeListHeapBufferTest,ReturnsNullWhenFull)103 TEST_F(FreeListHeapBufferTest, ReturnsNullWhenFull) {
104 size_t offset = GetAlignedOffsetAfter(
105 buffer_.data(), alignof(std::max_align_t), BlockType::kBlockOverhead);
106 auto buffer = pw::ByteSpan(buffer_).subspan(offset);
107 size_t inner_size = buffer.size() - BlockType::kBlockOverhead;
108
109 FreeListHeapBuffer allocator(buffer);
110 void* ptr1 = allocator.Allocate(inner_size);
111 ASSERT_NE(ptr1, nullptr);
112
113 void* ptr2 = allocator.Allocate(1);
114 EXPECT_EQ(ptr2, nullptr);
115
116 // All pointers must be freed before the allocator goes out of scope.
117 allocator.Free(ptr1);
118 }
119
TEST_F(FreeListHeapBufferTest,ReturnedPointersAreAligned)120 TEST_F(FreeListHeapBufferTest, ReturnedPointersAreAligned) {
121 FreeListHeapBuffer allocator(buffer_);
122
123 void* ptr1 = allocator.Allocate(1);
124
125 // Should be aligned to native pointer alignment
126 uintptr_t ptr1_start = cpp20::bit_cast<uintptr_t>(ptr1);
127 size_t alignment = alignof(void*);
128
129 EXPECT_EQ(ptr1_start % alignment, static_cast<size_t>(0));
130
131 void* ptr2 = allocator.Allocate(1);
132 uintptr_t ptr2_start = cpp20::bit_cast<uintptr_t>(ptr2);
133
134 EXPECT_EQ(ptr2_start % alignment, static_cast<size_t>(0));
135
136 // All pointers must be freed before the allocator goes out of scope.
137 allocator.Free(ptr1);
138 allocator.Free(ptr2);
139 }
140
TEST_F(FreeListHeapBufferTest,CanRealloc)141 TEST_F(FreeListHeapBufferTest, CanRealloc) {
142 FreeListHeapBuffer allocator(buffer_);
143
144 void* ptr1 = allocator.Allocate(kN / 4);
145 ASSERT_NE(ptr1, nullptr);
146
147 void* ptr2 = allocator.Realloc(ptr1, (kN * 3) / 8);
148 ASSERT_NE(ptr2, nullptr);
149
150 // All pointers must be freed before the allocator goes out of scope.
151 allocator.Free(ptr2);
152 }
153
TEST_F(FreeListHeapBufferTest,ReallocHasSameContent)154 TEST_F(FreeListHeapBufferTest, ReallocHasSameContent) {
155 FreeListHeapBuffer allocator(buffer_);
156
157 size_t val1 = 42;
158 void* ptr1 = allocator.Allocate(sizeof(size_t));
159 ASSERT_NE(ptr1, nullptr);
160 std::memcpy(ptr1, &val1, sizeof(size_t));
161
162 size_t val2;
163 void* ptr2 = allocator.Realloc(ptr1, sizeof(size_t) * 2);
164 ASSERT_NE(ptr2, nullptr);
165 std::memcpy(&val2, ptr2, sizeof(size_t));
166
167 // Verify that data inside the allocated and reallocated block are the same.
168 EXPECT_EQ(val1, val2);
169
170 // All pointers must be freed before the allocator goes out of scope.
171 allocator.Free(ptr2);
172 }
173
TEST_F(FreeListHeapBufferTest,ReallocSmallerSize)174 TEST_F(FreeListHeapBufferTest, ReallocSmallerSize) {
175 FreeListHeapBuffer allocator(buffer_);
176
177 void* ptr1 = allocator.Allocate(kN / 4);
178 ASSERT_NE(ptr1, nullptr);
179
180 // For smaller sizes, Realloc will not shrink the block.
181 void* ptr2 = allocator.Realloc(ptr1, kN / 8);
182 EXPECT_EQ(ptr1, ptr2);
183
184 // All pointers must be freed before the allocator goes out of scope.
185 allocator.Free(ptr2);
186 }
187
TEST_F(FreeListHeapBufferTest,ReallocTooLarge)188 TEST_F(FreeListHeapBufferTest, ReallocTooLarge) {
189 FreeListHeapBuffer allocator(buffer_);
190
191 void* ptr1 = allocator.Allocate(kN / 4);
192 ASSERT_NE(ptr1, nullptr);
193
194 // Realloc() will not invalidate the original pointer if it fails
195 void* ptr2 = allocator.Realloc(ptr1, kN * 2);
196 EXPECT_EQ(ptr2, nullptr);
197
198 // All pointers must be freed before the allocator goes out of scope.
199 allocator.Free(ptr1);
200 }
201
TEST_F(FreeListHeapBufferTest,CanCalloc)202 TEST_F(FreeListHeapBufferTest, CanCalloc) {
203 constexpr size_t kNum = 4;
204 constexpr size_t kSize = 128;
205 constexpr std::array<std::byte, kNum * kSize> kZero = {std::byte(0)};
206
207 FreeListHeapBuffer allocator(buffer_);
208
209 void* ptr1 = allocator.Calloc(kNum, kSize);
210 ASSERT_NE(ptr1, nullptr);
211
212 // Calloc'd content is zero.
213 EXPECT_EQ(std::memcmp(ptr1, kZero.data(), kZero.size()), 0);
214
215 // All pointers must be freed before the allocator goes out of scope.
216 allocator.Free(ptr1);
217 }
218
TEST_F(FreeListHeapBufferTest,CanCallocWeirdSize)219 TEST_F(FreeListHeapBufferTest, CanCallocWeirdSize) {
220 constexpr size_t kNum = 4;
221 constexpr size_t kSize = 128;
222 constexpr std::array<std::byte, kNum * kSize> kZero = {std::byte(0)};
223
224 FreeListHeapBuffer allocator(buffer_);
225
226 void* ptr1 = allocator.Calloc(kNum, kSize);
227 ASSERT_NE(ptr1, nullptr);
228
229 // Calloc'd content is zero.
230 EXPECT_EQ(std::memcmp(ptr1, kZero.data(), kZero.size()), 0);
231
232 // All pointers must be freed before the allocator goes out of scope.
233 allocator.Free(ptr1);
234 }
235
TEST_F(FreeListHeapBufferTest,CallocTooLarge)236 TEST_F(FreeListHeapBufferTest, CallocTooLarge) {
237 FreeListHeapBuffer allocator(buffer_);
238
239 void* ptr1 = allocator.Calloc(1, kN + 1);
240 EXPECT_EQ(ptr1, nullptr);
241
242 // All pointers must be freed before the allocator goes out of scope.
243 allocator.Free(ptr1);
244 }
245
246 } // namespace
247