Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
mm_tests.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <lxsameer@lxsameer.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18#pragma once
19
20#include <signal.h>
21#include <stddef.h>
22#include <sys/wait.h>
23#include <unistd.h>
24
26
27#include "base.h"
28#include "serene/utils.h"
29
30#define MM_TESTS(X) \
31 X("mm::allocation", test_mm_allocation), X("mm::block_allocation", test_mm_allocation_in_block), \
32 X("mm::multiblock_allocation", test_mm_allocation_multiblock), \
33 X("mm::block_bitmap", test_mm_block_bitmap), \
34 X("mm::block_size_properties", test_mm_block_size_properties), \
35 X("mm::aligned_block_allocation", test_mm_aligned_block_allocation), \
36 X("mm::immortal_multiblock", test_mm_immortal_multiblock), \
37 X("mm::get_block_out_of_range", test_mm_get_block_out_of_range), \
38 X("mm::block_release_reuse", test_mm_block_release_reuse), \
39 X("mm::get_block_after_release", test_mm_get_block_after_release), \
40 X("mm::over_aligned_allocation", test_mm_over_aligned_allocation), \
41 X("mm::over_aligned_request_near_capacity", test_mm_over_aligned_request_near_capacity)
42
43#if defined(MM_DEBUG)
44# define MM_TEST_LOG(...) DBG("MM_TEST", __VA_ARGS__)
45#else
46# define MM_TEST_LOG(...)
47#endif
48
49typedef struct mm_dummy {
50 char foo[200];
51 char bar[200];
53
54typedef struct mm_64k {
55 char foo[0xffff];
57
58static inline size_t padding(size_t size, size_t align) {
59 return (align - (size & (align - 1U))) & (align - 1);
60}
61
62static void test_mm_allocation() {
63 srn_mm_t *mm = srn_mm_init(nullptr);
65
67
68 for (int i = 0; i < 200; i++) {
69 d->foo[i] = 'A';
70 d->bar[i] = 'B';
71 }
72
73 // DBG_HEX(mm->immortal_block, 600);
74
76}
77
79 srn_mm_t *mm = srn_mm_init(nullptr);
81
84
85 for (int i = 0; i < 200; i++) {
86 d->foo[i] = 'A';
87 d->bar[i] = 'B';
88 }
89
90 srn_block_t *block = srn_mm_get_block(mm, b);
91
92 TEST_CHECK(block != nullptr);
93 // DBG_HEX(block, 600);
94
96}
97
99 srn_mm_t *mm = srn_mm_init(nullptr);
100 ASSERT_NOT_NULL(mm);
101
103 MM_TEST_LOG("Allocated block 'b' with id %zu", b);
104 MM_TEST_LOG("Allocating 'd1'");
106
107 for (int i = 0; i < 0xffff; i++) {
108 d1->foo[i] = 'A';
109 }
110 MM_TEST_LOG("Allocating 'd2'");
112 for (int i = 0; i < 0xffff; i++) {
113 d2->foo[i] = 'B';
114 }
115
116 MM_TEST_LOG("Allocating 'd3'");
117 // this should end up on the root block
119 for (int i = 0; i < 200; i++) {
120 d3->foo[i] = 'C';
121 d3->bar[i] = 'D';
122 }
123
124 srn_block_t *block = srn_mm_get_block(mm, b);
125
126 TEST_CHECK(block != nullptr);
127 TEST_CHECK(block->next != nullptr);
128 TEST_CHECK(block->next->next == nullptr);
129 TEST_CHECK((uintptr_t)&block->next->base == (uintptr_t)d2);
130
132 (uintptr_t)&block->base + 0xffff + padding(sizeof(mm_64k), alignof(mm_64k)) == (uintptr_t)d3
133 );
134
135 /* DBG_HEX(block, 600); */
136 /* DBG_HEX(((char *)(uintptr_t)&block->base + (uintptr_t)0xfff0), 600); */
137
138 /* DBG_HEX(block->next, 600); */
139
140 srn_mm_shutdown(mm);
141}
142
143static void test_mm_block_bitmap(void) {
144 srn_mm_t *mm = srn_mm_init(nullptr);
145 ASSERT_NOT_NULL(mm);
146
147 // Initially all bits must be 0 (no user blocks allocated)
148 for (int i = 0; i < 4; i++) {
149 TEST_CHECK(mm->block_bitmap[i] == 0);
150 }
151
155
156 MM_TEST_LOG("Allocated block ids: %zu, %zu, %zu", (size_t)b0, (size_t)b1, (size_t)b2);
157
158 TEST_CHECK(b0 == 0);
159 TEST_CHECK(b1 == 1);
160 TEST_CHECK(b2 == 2);
161
162 uint64_t word0 = mm->block_bitmap[0];
163
164 // Bits 0,1,2 must be set
165 TEST_CHECK((word0 & 0x7ULL) == 0x7ULL);
166 // Other bits in the first word are untouched
167 // and other words should still be zero
168 TEST_CHECK(mm->block_bitmap[1] == 0);
169 TEST_CHECK(mm->block_bitmap[2] == 0);
170 TEST_CHECK(mm->block_bitmap[3] == 0);
171
172 srn_mm_shutdown(mm);
173}
174
176 size_t page_size = srn_mm_get_os_page_size();
177
178 TEST_CHECK(page_size > 0);
179 // The block size is a power of two derived from the magnitude, so it is a
180 // whole number of pages on this platform by construction.
183 );
185}
186
188 srn_mm_t *mm = srn_mm_init(nullptr);
189 ASSERT_NOT_NULL(mm);
190
192 MM_TEST_LOG("Allocated block id for aligned test: %zu", (size_t)b);
193
194 size_t alignment = 32;
195 size_t size = 13;
196
197 void *p1 = srn_mm_allocate_in_block_aligned(mm, b, size, alignment);
198 void *p2 = srn_mm_allocate_in_block_aligned(mm, b, size, alignment);
199
200 MM_TEST_LOG("Aligned allocations: p1=%p, p2=%p", p1, p2);
201
202 TEST_CHECK(p1 != NULL);
203 TEST_CHECK(p2 != NULL);
204 // The requested alignment is honored, which implies the default block
205 // alignment as well.
206 TEST_CHECK(((uintptr_t)p1 % alignment) == 0);
207 TEST_CHECK(((uintptr_t)p2 % alignment) == 0);
208
209 // Quick sanity, both allocations must belong to the same root block
210 srn_block_t *block = srn_mm_get_block(mm, b);
211 TEST_CHECK(block != nullptr);
212
213 uintptr_t base = (uintptr_t)&block->base;
214 uintptr_t end = base + (block->size - offsetof(srn_block_t, base));
215
216 TEST_CHECK((uintptr_t)p1 >= base && (uintptr_t)p1 + size <= end);
217 TEST_CHECK((uintptr_t)p2 >= base && (uintptr_t)p2 + size <= end);
218
219 srn_mm_shutdown(mm);
220}
221
223 srn_mm_t *mm = srn_mm_init(nullptr);
224 ASSERT_NOT_NULL(mm);
225
226 // Same trick as test_mm_allocation_multiblock but on immortal_block
227 MM_TEST_LOG("Allocating mm_64k instances in immortal block");
228
232
233 ASSERT_NOT_NULL(i1);
234 ASSERT_NOT_NULL(i2);
235 ASSERT_NOT_NULL(i3);
236
237 for (int i = 0; i < 0xffff; i++) {
238 i1->foo[i] = 'X';
239 i2->foo[i] = 'Y';
240 i3->foo[i] = 'Z';
241 }
242
243 srn_block_t *root = mm->immortal_block;
244 TEST_CHECK(root != nullptr);
245 TEST_CHECK(root->next != nullptr);
246
247 MM_TEST_LOG("immortal root = %p, next = %p", (void *)root, (void *)root->next);
248
249 srn_mm_shutdown(mm);
250}
251
252/// Block ids are reused after release, so a release must also retire every
253/// piece of allocation bookkeeping. These cycles exceed
254/// MAX_NUMBER_OF_BLOCKS; only live blocks may count against the limit.
256 srn_mm_t *mm = srn_mm_init(nullptr);
257 ASSERT_NOT_NULL(mm);
258
259 for (size_t i = 0; i < MAX_NUMBER_OF_BLOCKS + 44; i++) {
261 void *p = srn_mm_allocate_in_block_aligned(mm, b, 64, 16);
262 TEST_CHECK(p != nullptr);
264 }
265
266 srn_mm_shutdown(mm);
267}
268
269/// A released id must not resolve to a block anymore. Handing back the stale
270/// pointer lets callers bump allocate into freed memory.
272 srn_mm_t *mm = srn_mm_init(nullptr);
273 ASSERT_NOT_NULL(mm);
274
276 TEST_CHECK(srn_mm_get_block(mm, b) != nullptr);
277
279 TEST_CHECK(srn_mm_get_block(mm, b) == nullptr);
280
281 srn_mm_shutdown(mm);
282}
283
284/// Alignments above DEFAULT_BLOCK_ALIGNMENT must be honored on the returned
285/// pointer. Aligning the bump offset alone leaves the result at
286/// base modulo the requested alignment, and base is only guaranteed to be
287/// 16 byte aligned.
289 srn_mm_t *mm = srn_mm_init(nullptr);
290 ASSERT_NOT_NULL(mm);
291
293
294 // Skew the bump offset first so the aligned request cannot start at zero.
295 void *skew = srn_mm_allocate_in_block_aligned(mm, b, 1, 1);
296 TEST_CHECK(skew != nullptr);
297
298 for (size_t alignment = 32; alignment <= 256; alignment *= 2) {
299 void *p = srn_mm_allocate_in_block_aligned(mm, b, 24, alignment);
300 TEST_CHECK(p != nullptr);
301 TEST_MSG("alignment %zu, remainder %zu", alignment, (size_t)((uintptr_t)p % alignment));
302 TEST_CHECK(((uintptr_t)p % alignment) == 0);
303 }
304
305 srn_mm_shutdown(mm);
306}
307
308// A request that fits a block only if the base happens to be aligned better
309// than DEFAULT_BLOCK_ALIGNMENT cannot be served by growing the chain, every
310// new block has the same base guarantee. It must be refused up front rather
311// than link blocks until either memory runs out or a block happens to land on
312// a better aligned base. The request runs in a child with an alarm. A refusal
313// ends the child with the panic's signal, a runaway chain ends it with SIGALRM
314// or, when a lucky base cuts the walk short, with a normal exit.
316 pid_t pid = fork();
317 TEST_ASSERT(pid >= 0);
318
319 if (pid == 0) {
320 alarm(2);
321 srn_mm_t *mm = srn_mm_init(nullptr);
323 srn_block_t *block = srn_mm_get_block(mm, b);
324 size_t capacity = block->size - offsetof(srn_block_t, base);
325
326 // The base only promises DEFAULT_BLOCK_ALIGNMENT, but the provider may
327 // hand out something better aligned by chance, so pick the smallest
328 // alignment this base actually misses. Every block of the chain comes
329 // from the same provider with the same layout, so a fresh one misses it
330 // too.
331 size_t alignment = 2 * DEFAULT_BLOCK_ALIGNMENT;
332 while (alignment <= 4096 && ((uintptr_t)block->base % alignment) == 0) {
333 alignment *= 2;
334 }
335 if (alignment > 4096) {
336 // Nothing to show against a page aligned base, count it as refused.
337 raise(SIGABRT);
338 }
339
340 (void)srn_mm_allocate_in_block_aligned(mm, b, capacity - 8, alignment);
341 _exit(0);
342 }
343
344 int status = 0;
345 TEST_ASSERT(waitpid(pid, &status, 0) == pid);
346 TEST_CHECK(WIFSIGNALED(status));
347 TEST_CHECK_(WTERMSIG(status) != SIGALRM, "the chain grew until the alarm fired");
348}
349
351 srn_mm_t *mm = srn_mm_init(nullptr);
352 ASSERT_NOT_NULL(mm);
353
355 MM_TEST_LOG("Allocated block id: %zu", (size_t)b);
356
357 srn_block_t *block = srn_mm_get_block(mm, b);
358 TEST_CHECK(block != nullptr);
359
360 // This ID is guaranteed to be out of range
361 srn_block_id_t invalid = b + 100;
362 srn_block_t *null_block = srn_mm_get_block(mm, invalid);
363 TEST_CHECK(null_block == nullptr);
364
365 srn_mm_shutdown(mm);
366}
#define TEST_CHECK(cond)
Definition acutest.h:95
#define TEST_CHECK_(cond,...)
Definition acutest.h:94
#define TEST_ASSERT(cond)
Definition acutest.h:117
#define TEST_MSG(...)
Definition acutest.h:223
#define ASSERT_NOT_NULL(x)
Definition base.h:30
#define SRN_CONFIG_DEFAULT_BLOCK_SIZE
The default block size in bytes, derived from the magnitude.
#define SRN_CONFIG_DEFAULT_BLOCK_SIZE_MAGNITUDE
Magnitude of one memory-manager block.
size_t srn_block_id_t
The block id is effectively just an index in the blocks array in srn_mm_t.
Definition context.h:38
void srn_mm_release_block(srn_mm_t *mm, srn_block_id_t id)
Release the given block id and free the memory for later allocations.
Definition default.c:468
void * srn_mm_allocate_in_block_aligned(srn_mm_t *mm, srn_block_id_t block_id, size_t size, size_t alignment)
Allocate memory on a block with the given block_id.
Definition default.c:411
srn_block_t * srn_mm_get_block(srn_mm_t *mm, srn_block_id_t block_id)
Return the block object associated by the given block_id.
Definition default.c:333
srn_block_id_t srn_mm_allocate_block(srn_mm_t *mm)
Allocate a new block in the memory manager and return its ID.
Definition default.c:443
size_t srn_mm_get_os_page_size(void)
Retutrns the OS page size.
Definition default.c:318
void srn_mm_shutdown(srn_mm_t *mm)
Shut down the memory manager and release the resources.
Definition default.c:389
srn_mm_t * srn_mm_init(const srn_configuration_t *config)
Initialize the memory manager, this function will panic on error.
Definition default.c:337
#define srn_mm_immortal_allocate(mm, T)
Definition interface.h:188
#define srn_mm_allocate_in_block(mm, id, T)
Definition interface.h:185
#define MAX_NUMBER_OF_BLOCKS
array of blocks is enough for us, we can tweak the size as we see fit.
Definition interface.h:55
#define DEFAULT_BLOCK_ALIGNMENT
We strictly use 16 bytes alignment for blocks.
Definition interface.h:58
static void test_mm_allocation()
Definition mm_tests.h:62
static void test_mm_immortal_multiblock(void)
Definition mm_tests.h:222
static void test_mm_block_bitmap(void)
Definition mm_tests.h:143
static void test_mm_allocation_in_block()
Definition mm_tests.h:78
static void test_mm_block_size_properties(void)
Definition mm_tests.h:175
static void test_mm_block_release_reuse(void)
Block ids are reused after release, so a release must also retire every piece of allocation bookkeepi...
Definition mm_tests.h:255
static void test_mm_aligned_block_allocation(void)
Definition mm_tests.h:187
static void test_mm_over_aligned_allocation(void)
Alignments above DEFAULT_BLOCK_ALIGNMENT must be honored on the returned pointer.
Definition mm_tests.h:288
static void test_mm_get_block_after_release(void)
A released id must not resolve to a block anymore.
Definition mm_tests.h:271
static size_t padding(size_t size, size_t align)
Definition mm_tests.h:58
#define MM_TEST_LOG(...)
Definition mm_tests.h:46
static void test_mm_over_aligned_request_near_capacity(void)
Definition mm_tests.h:315
static void test_mm_allocation_multiblock()
Definition mm_tests.h:98
static void test_mm_get_block_out_of_range(void)
Definition mm_tests.h:350
char foo[0xffff]
Definition mm_tests.h:55
char foo[200]
Definition mm_tests.h:50
char bar[200]
Definition mm_tests.h:51
uint8_t base[]
Where the data area starts.
Definition interface.h:94
size_t size
This is the TOTAL size of the block, header + payloud.
Definition interface.h:87
struct srn_block_t * next
when the block does not have space to allocate a request, we will allocate a new block and point to i...
Definition interface.h:82
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:112
srn_block_t * immortal_block
Immortal block is a chain of blocks which will never die.
Definition interface.h:144
uint64_t block_bitmap[4]
This is a 256bit bitmap we treat it as a whole.
Definition interface.h:126