1 | //===-- Unittests for freelist_heap ---------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "src/__support/CPP/span.h" |
10 | #include "src/__support/freelist_heap.h" |
11 | #include "src/__support/macros/config.h" |
12 | #include "src/string/memcmp.h" |
13 | #include "src/string/memcpy.h" |
14 | #include "test/UnitTest/Test.h" |
15 | |
16 | asm(R"( |
17 | .globl _end, __llvm_libc_heap_limit |
18 | |
19 | .bss |
20 | _end: |
21 | .fill 1024 |
22 | __llvm_libc_heap_limit: |
23 | )" ); |
24 | |
25 | using LIBC_NAMESPACE::Block; |
26 | using LIBC_NAMESPACE::freelist_heap; |
27 | using LIBC_NAMESPACE::FreeListHeap; |
28 | using LIBC_NAMESPACE::FreeListHeapBuffer; |
29 | using LIBC_NAMESPACE::cpp::byte; |
30 | using LIBC_NAMESPACE::cpp::span; |
31 | |
32 | // Similar to `LlvmLibcBlockTest` in block_test.cpp, we'd like to run the same |
33 | // tests independently for different parameters. In this case, we'd like to test |
34 | // functionality for a `FreeListHeap` and the global `freelist_heap` which was |
35 | // constinit'd. Functionally, it should operate the same if the FreeListHeap |
36 | // were initialized locally at runtime or at compile-time. |
37 | // |
38 | // Note that calls to `allocate` for each test case here don't always explicitly |
39 | // `free` them afterwards, so when testing the global allocator, allocations |
40 | // made in tests leak and aren't free'd. This is fine for the purposes of this |
41 | // test file. |
42 | #define TEST_FOR_EACH_ALLOCATOR(TestCase, BufferSize) \ |
43 | class LlvmLibcFreeListHeapTest##TestCase \ |
44 | : public LIBC_NAMESPACE::testing::Test { \ |
45 | public: \ |
46 | FreeListHeapBuffer<BufferSize> fake_global_buffer; \ |
47 | void SetUp() override { \ |
48 | freelist_heap = \ |
49 | new (&fake_global_buffer) FreeListHeapBuffer<BufferSize>; \ |
50 | } \ |
51 | void RunTest(FreeListHeap &allocator, [[maybe_unused]] size_t N); \ |
52 | }; \ |
53 | TEST_F(LlvmLibcFreeListHeapTest##TestCase, TestCase) { \ |
54 | byte buf[BufferSize] = {byte(0)}; \ |
55 | FreeListHeap allocator(buf); \ |
56 | RunTest(allocator, BufferSize); \ |
57 | RunTest(*freelist_heap, freelist_heap->region().size()); \ |
58 | } \ |
59 | void LlvmLibcFreeListHeapTest##TestCase::RunTest(FreeListHeap &allocator, \ |
60 | size_t N) |
61 | |
62 | TEST_FOR_EACH_ALLOCATOR(CanAllocate, 2048) { |
63 | constexpr size_t ALLOC_SIZE = 512; |
64 | |
65 | void *ptr = allocator.allocate(ALLOC_SIZE); |
66 | |
67 | ASSERT_NE(ptr, static_cast<void *>(nullptr)); |
68 | } |
69 | |
70 | TEST_FOR_EACH_ALLOCATOR(AllocationsDontOverlap, 2048) { |
71 | constexpr size_t ALLOC_SIZE = 512; |
72 | |
73 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
74 | void *ptr2 = allocator.allocate(ALLOC_SIZE); |
75 | |
76 | ASSERT_NE(ptr1, static_cast<void *>(nullptr)); |
77 | ASSERT_NE(ptr2, static_cast<void *>(nullptr)); |
78 | |
79 | uintptr_t ptr1_start = reinterpret_cast<uintptr_t>(ptr1); |
80 | uintptr_t ptr1_end = ptr1_start + ALLOC_SIZE; |
81 | uintptr_t ptr2_start = reinterpret_cast<uintptr_t>(ptr2); |
82 | |
83 | EXPECT_GT(ptr2_start, ptr1_end); |
84 | } |
85 | |
86 | TEST_FOR_EACH_ALLOCATOR(CanFreeAndRealloc, 2048) { |
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 | constexpr size_t ALLOC_SIZE = 512; |
90 | |
91 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
92 | allocator.free(ptr1); |
93 | void *ptr2 = allocator.allocate(ALLOC_SIZE); |
94 | |
95 | EXPECT_EQ(ptr1, ptr2); |
96 | } |
97 | |
98 | TEST_FOR_EACH_ALLOCATOR(ReturnsNullWhenAllocationTooLarge, 2048) { |
99 | EXPECT_EQ(allocator.allocate(N), static_cast<void *>(nullptr)); |
100 | } |
101 | |
102 | // NOTE: This doesn't use TEST_FOR_EACH_ALLOCATOR because the first `allocate` |
103 | // here will likely actually return a nullptr since the same global allocator |
104 | // is used for other test cases and we don't explicitly free them. |
105 | TEST(LlvmLibcFreeListHeap, ReturnsNullWhenFull) { |
106 | constexpr size_t N = 2048; |
107 | byte buf[N]; |
108 | |
109 | FreeListHeap allocator(buf); |
110 | |
111 | bool went_null = false; |
112 | for (size_t i = 0; i < N; i++) { |
113 | if (!allocator.allocate(1)) { |
114 | went_null = true; |
115 | break; |
116 | } |
117 | } |
118 | EXPECT_TRUE(went_null); |
119 | EXPECT_EQ(allocator.allocate(1), static_cast<void *>(nullptr)); |
120 | } |
121 | |
122 | TEST_FOR_EACH_ALLOCATOR(ReturnedPointersAreAligned, 2048) { |
123 | void *ptr1 = allocator.allocate(1); |
124 | |
125 | uintptr_t ptr1_start = reinterpret_cast<uintptr_t>(ptr1); |
126 | EXPECT_EQ(ptr1_start % alignof(max_align_t), static_cast<size_t>(0)); |
127 | |
128 | void *ptr2 = allocator.allocate(1); |
129 | uintptr_t ptr2_start = reinterpret_cast<uintptr_t>(ptr2); |
130 | |
131 | EXPECT_EQ(ptr2_start % alignof(max_align_t), static_cast<size_t>(0)); |
132 | } |
133 | |
134 | TEST_FOR_EACH_ALLOCATOR(CanRealloc, 2048) { |
135 | constexpr size_t ALLOC_SIZE = 512; |
136 | constexpr size_t kNewAllocSize = 768; |
137 | |
138 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
139 | void *ptr2 = allocator.realloc(ptr1, kNewAllocSize); |
140 | |
141 | ASSERT_NE(ptr1, static_cast<void *>(nullptr)); |
142 | ASSERT_NE(ptr2, static_cast<void *>(nullptr)); |
143 | } |
144 | |
145 | TEST_FOR_EACH_ALLOCATOR(ReallocHasSameContent, 2048) { |
146 | constexpr size_t ALLOC_SIZE = sizeof(int); |
147 | constexpr size_t kNewAllocSize = sizeof(int) * 2; |
148 | // Data inside the allocated block. |
149 | byte data1[ALLOC_SIZE]; |
150 | // Data inside the reallocated block. |
151 | byte data2[ALLOC_SIZE]; |
152 | |
153 | int *ptr1 = reinterpret_cast<int *>(allocator.allocate(ALLOC_SIZE)); |
154 | *ptr1 = 42; |
155 | LIBC_NAMESPACE::memcpy(data1, ptr1, ALLOC_SIZE); |
156 | int *ptr2 = reinterpret_cast<int *>(allocator.realloc(ptr1, kNewAllocSize)); |
157 | LIBC_NAMESPACE::memcpy(data2, ptr2, ALLOC_SIZE); |
158 | |
159 | ASSERT_NE(ptr1, static_cast<int *>(nullptr)); |
160 | ASSERT_NE(ptr2, static_cast<int *>(nullptr)); |
161 | // Verify that data inside the allocated and reallocated chunks are the same. |
162 | EXPECT_EQ(LIBC_NAMESPACE::memcmp(data1, data2, ALLOC_SIZE), 0); |
163 | } |
164 | |
165 | TEST_FOR_EACH_ALLOCATOR(ReturnsNullReallocFreedPointer, 2048) { |
166 | constexpr size_t ALLOC_SIZE = 512; |
167 | constexpr size_t kNewAllocSize = 256; |
168 | |
169 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
170 | allocator.free(ptr1); |
171 | void *ptr2 = allocator.realloc(ptr1, kNewAllocSize); |
172 | |
173 | EXPECT_EQ(static_cast<void *>(nullptr), ptr2); |
174 | } |
175 | |
176 | TEST_FOR_EACH_ALLOCATOR(ReallocSmallerSize, 2048) { |
177 | constexpr size_t ALLOC_SIZE = 512; |
178 | constexpr size_t kNewAllocSize = 256; |
179 | |
180 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
181 | void *ptr2 = allocator.realloc(ptr1, kNewAllocSize); |
182 | |
183 | // For smaller sizes, realloc will not shrink the block. |
184 | EXPECT_EQ(ptr1, ptr2); |
185 | } |
186 | |
187 | TEST_FOR_EACH_ALLOCATOR(ReallocTooLarge, 2048) { |
188 | constexpr size_t ALLOC_SIZE = 512; |
189 | size_t kNewAllocSize = N * 2; // Large enough to fail. |
190 | |
191 | void *ptr1 = allocator.allocate(ALLOC_SIZE); |
192 | void *ptr2 = allocator.realloc(ptr1, kNewAllocSize); |
193 | |
194 | // realloc() will not invalidate the original pointer if realloc() fails |
195 | EXPECT_NE(static_cast<void *>(nullptr), ptr1); |
196 | EXPECT_EQ(static_cast<void *>(nullptr), ptr2); |
197 | } |
198 | |
199 | TEST_FOR_EACH_ALLOCATOR(CanCalloc, 2048) { |
200 | constexpr size_t ALLOC_SIZE = 128; |
201 | constexpr size_t NUM = 4; |
202 | constexpr int size = NUM * ALLOC_SIZE; |
203 | constexpr byte zero{0}; |
204 | |
205 | byte *ptr1 = reinterpret_cast<byte *>(allocator.calloc(NUM, ALLOC_SIZE)); |
206 | |
207 | // calloc'd content is zero. |
208 | for (int i = 0; i < size; i++) { |
209 | EXPECT_EQ(ptr1[i], zero); |
210 | } |
211 | } |
212 | |
213 | TEST_FOR_EACH_ALLOCATOR(CanCallocWeirdSize, 2048) { |
214 | constexpr size_t ALLOC_SIZE = 143; |
215 | constexpr size_t NUM = 3; |
216 | constexpr int size = NUM * ALLOC_SIZE; |
217 | constexpr byte zero{0}; |
218 | |
219 | byte *ptr1 = reinterpret_cast<byte *>(allocator.calloc(NUM, ALLOC_SIZE)); |
220 | |
221 | // calloc'd content is zero. |
222 | for (int i = 0; i < size; i++) { |
223 | EXPECT_EQ(ptr1[i], zero); |
224 | } |
225 | } |
226 | |
227 | TEST_FOR_EACH_ALLOCATOR(CallocTooLarge, 2048) { |
228 | size_t ALLOC_SIZE = N + 1; |
229 | EXPECT_EQ(allocator.calloc(1, ALLOC_SIZE), static_cast<void *>(nullptr)); |
230 | } |
231 | |
232 | TEST_FOR_EACH_ALLOCATOR(AllocateZero, 2048) { |
233 | void *ptr = allocator.allocate(0); |
234 | ASSERT_EQ(ptr, static_cast<void *>(nullptr)); |
235 | } |
236 | |
237 | TEST_FOR_EACH_ALLOCATOR(AlignedAlloc, 2048) { |
238 | constexpr size_t ALIGNMENTS[] = {1, 2, 4, 8, 16, 32, 64, 128, 256}; |
239 | constexpr size_t SIZE_SCALES[] = {1, 2, 3, 4, 5}; |
240 | |
241 | for (size_t alignment : ALIGNMENTS) { |
242 | for (size_t scale : SIZE_SCALES) { |
243 | size_t size = alignment * scale; |
244 | void *ptr = allocator.aligned_allocate(alignment, size); |
245 | EXPECT_NE(ptr, static_cast<void *>(nullptr)); |
246 | EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignment, size_t(0)); |
247 | allocator.free(ptr); |
248 | } |
249 | } |
250 | } |
251 | |
252 | // This test is not part of the TEST_FOR_EACH_ALLOCATOR since we want to |
253 | // explicitly ensure that the buffer can still return aligned allocations even |
254 | // if the underlying buffer is unaligned. This is so we can check that we can |
255 | // still get aligned allocations even if the underlying buffer is not aligned to |
256 | // the alignments we request. |
257 | TEST(LlvmLibcFreeListHeap, AlignedAllocUnalignedBuffer) { |
258 | byte buf[4096] = {byte(0)}; |
259 | |
260 | // Ensure the underlying buffer is poorly aligned. |
261 | FreeListHeap allocator(span<byte>(buf).subspan(1)); |
262 | |
263 | constexpr size_t ALIGNMENTS[] = {1, 2, 4, 8, 16, 32, 64, 128, 256}; |
264 | constexpr size_t SIZE_SCALES[] = {1, 2, 3, 4, 5}; |
265 | |
266 | for (size_t alignment : ALIGNMENTS) { |
267 | for (size_t scale : SIZE_SCALES) { |
268 | size_t size = alignment * scale; |
269 | void *ptr = allocator.aligned_allocate(alignment, size); |
270 | EXPECT_NE(ptr, static_cast<void *>(nullptr)); |
271 | EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignment, size_t(0)); |
272 | allocator.free(ptr); |
273 | } |
274 | } |
275 | } |
276 | |
277 | TEST_FOR_EACH_ALLOCATOR(InvalidAlignedAllocAlignment, 2048) { |
278 | // Must be a power of 2. |
279 | constexpr size_t ALIGNMENTS[] = {4, 8, 16, 32, 64, 128, 256}; |
280 | for (size_t alignment : ALIGNMENTS) { |
281 | void *ptr = allocator.aligned_allocate(alignment - 1, alignment - 1); |
282 | EXPECT_EQ(ptr, static_cast<void *>(nullptr)); |
283 | } |
284 | |
285 | // Size must be a multiple of alignment |
286 | for (size_t alignment : ALIGNMENTS) { |
287 | void *ptr = allocator.aligned_allocate(alignment, alignment + 1); |
288 | EXPECT_EQ(ptr, static_cast<void *>(nullptr)); |
289 | } |
290 | |
291 | // Don't accept zero size. |
292 | void *ptr = allocator.aligned_allocate(1, 0); |
293 | EXPECT_EQ(ptr, static_cast<void *>(nullptr)); |
294 | |
295 | // Don't accept zero alignment. |
296 | ptr = allocator.aligned_allocate(0, 8); |
297 | EXPECT_EQ(ptr, static_cast<void *>(nullptr)); |
298 | } |
299 | |