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 <[email protected]>
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
21
22#include "base.h"
23#include "serene/utils.h"
24
25#define MM_TESTS(X) \
26 X("mm::allocation", test_mm_allocation), X("mm::block_allocation", test_mm_allocation_in_block), \
27 X("mm::multiblock_allocation", test_mm_allocation_multiblock), \
28 X("mm::block_bitmap", test_mm_block_bitmap), \
29 X("mm::block_size_properties", test_mm_block_size_properties), \
30 X("mm::aligned_block_allocation", test_mm_aligned_block_allocation), \
31 X("mm::immortal_multiblock", test_mm_immortal_multiblock), \
32 X("mm::get_block_out_of_range", test_mm_get_block_out_of_range), \
33 X("mm::block_release_reuse", test_mm_block_release_reuse), \
34 X("mm::get_block_after_release", test_mm_get_block_after_release), \
35 X("mm::over_aligned_allocation", test_mm_over_aligned_allocation)
36
37#if defined(MM_DEBUG)
38# define MM_TEST_LOG(...) DBG("MM_TEST", __VA_ARGS__)
39#else
40# define MM_TEST_LOG(...)
41#endif
42
43typedef struct mm_dummy {
44 char foo[200];
45 char bar[200];
47
48typedef struct mm_64k {
49 char foo[0xffff];
51
52static inline size_t padding(size_t size, size_t align) {
53 return (align - (size & (align - 1U))) & (align - 1);
54}
55
56static void test_mm_allocation() {
57 srn_mm_t *mm = srn_mm_init(nullptr);
59
61
62 for (int i = 0; i < 200; i++) {
63 d->foo[i] = 'A';
64 d->bar[i] = 'B';
65 }
66
67 // DBG_HEX(mm->immortal_block, 600);
68
70}
71
73 srn_mm_t *mm = srn_mm_init(nullptr);
75
78
79 for (int i = 0; i < 200; i++) {
80 d->foo[i] = 'A';
81 d->bar[i] = 'B';
82 }
83
84 srn_block_t *block = srn_mm_get_block(mm, b);
85
86 TEST_CHECK(block != nullptr);
87 // DBG_HEX(block, 600);
88
90}
91
93 srn_mm_t *mm = srn_mm_init(nullptr);
95
97 MM_TEST_LOG("Allocated block 'b' with id %zu", b);
98 MM_TEST_LOG("Allocating 'd1'");
100
101 for (int i = 0; i < 0xffff; i++) {
102 d1->foo[i] = 'A';
103 }
104 MM_TEST_LOG("Allocating 'd2'");
106 for (int i = 0; i < 0xffff; i++) {
107 d2->foo[i] = 'B';
108 }
109
110 MM_TEST_LOG("Allocating 'd3'");
111 // this should end up on the root block
113 for (int i = 0; i < 200; i++) {
114 d3->foo[i] = 'C';
115 d3->bar[i] = 'D';
116 }
117
118 srn_block_t *block = srn_mm_get_block(mm, b);
119
120 TEST_CHECK(block != nullptr);
121 TEST_CHECK(block->next != nullptr);
122 TEST_CHECK(block->next->next == nullptr);
123 TEST_CHECK((uintptr_t)&block->next->base == (uintptr_t)d2);
124
126 (uintptr_t)&block->base + 0xffff + padding(sizeof(mm_64k), alignof(mm_64k)) == (uintptr_t)d3
127 );
128
129 /* DBG_HEX(block, 600); */
130 /* DBG_HEX(((char *)(uintptr_t)&block->base + (uintptr_t)0xfff0), 600); */
131
132 /* DBG_HEX(block->next, 600); */
133
134 srn_mm_shutdown(mm);
135}
136
137static void test_mm_block_bitmap(void) {
138 srn_mm_t *mm = srn_mm_init(nullptr);
139 ASSERT_NOT_NULL(mm);
140
141 // Initially all bits must be 0 (no user blocks allocated)
142 for (int i = 0; i < 4; i++) {
143 TEST_CHECK(mm->block_bitmap[i] == 0);
144 }
145
149
150 MM_TEST_LOG("Allocated block ids: %zu, %zu, %zu", (size_t)b0, (size_t)b1, (size_t)b2);
151
152 TEST_CHECK(b0 == 0);
153 TEST_CHECK(b1 == 1);
154 TEST_CHECK(b2 == 2);
155
156 uint64_t word0 = mm->block_bitmap[0];
157
158 // Bits 0,1,2 must be set
159 TEST_CHECK((word0 & 0x7ULL) == 0x7ULL);
160 // Other bits in the first word are untouched
161 // and other words should still be zero
162 TEST_CHECK(mm->block_bitmap[1] == 0);
163 TEST_CHECK(mm->block_bitmap[2] == 0);
164 TEST_CHECK(mm->block_bitmap[3] == 0);
165
166 srn_mm_shutdown(mm);
167}
168
170 size_t page_size = srn_mm_get_os_page_size();
171
172 TEST_CHECK(page_size > 0);
173 // The block size is a power of two derived from the magnitude, so it is a
174 // whole number of pages on this platform by construction.
177 );
179}
180
182 srn_mm_t *mm = srn_mm_init(nullptr);
183 ASSERT_NOT_NULL(mm);
184
186 MM_TEST_LOG("Allocated block id for aligned test: %zu", (size_t)b);
187
188 size_t alignment = 32;
189 size_t size = 13;
190
191 void *p1 = srn_mm_allocate_in_block_aligned(mm, b, size, alignment);
192 void *p2 = srn_mm_allocate_in_block_aligned(mm, b, size, alignment);
193
194 MM_TEST_LOG("Aligned allocations: p1=%p, p2=%p", p1, p2);
195
196 TEST_CHECK(p1 != NULL);
197 TEST_CHECK(p2 != NULL);
198 // The requested alignment is honored, which implies the default block
199 // alignment as well.
200 TEST_CHECK(((uintptr_t)p1 % alignment) == 0);
201 TEST_CHECK(((uintptr_t)p2 % alignment) == 0);
202
203 // Quick sanity, both allocations must belong to the same root block
204 srn_block_t *block = srn_mm_get_block(mm, b);
205 TEST_CHECK(block != nullptr);
206
207 uintptr_t base = (uintptr_t)&block->base;
208 uintptr_t end = base + (block->size - offsetof(srn_block_t, base));
209
210 TEST_CHECK((uintptr_t)p1 >= base && (uintptr_t)p1 + size <= end);
211 TEST_CHECK((uintptr_t)p2 >= base && (uintptr_t)p2 + size <= end);
212
213 srn_mm_shutdown(mm);
214}
215
217 srn_mm_t *mm = srn_mm_init(nullptr);
218 ASSERT_NOT_NULL(mm);
219
220 // Same trick as test_mm_allocation_multiblock but on immortal_block
221 MM_TEST_LOG("Allocating mm_64k instances in immortal block");
222
226
227 ASSERT_NOT_NULL(i1);
228 ASSERT_NOT_NULL(i2);
229 ASSERT_NOT_NULL(i3);
230
231 for (int i = 0; i < 0xffff; i++) {
232 i1->foo[i] = 'X';
233 i2->foo[i] = 'Y';
234 i3->foo[i] = 'Z';
235 }
236
237 srn_block_t *root = mm->immortal_block;
238 TEST_CHECK(root != nullptr);
239 TEST_CHECK(root->next != nullptr);
240
241 MM_TEST_LOG("immortal root = %p, next = %p", (void *)root, (void *)root->next);
242
243 srn_mm_shutdown(mm);
244}
245
246/// Block ids are reused after release, so a release must also retire every
247/// piece of allocation bookkeeping. These cycles exceed
248/// MAX_NUMBER_OF_BLOCKS; only live blocks may count against the limit.
250 srn_mm_t *mm = srn_mm_init(nullptr);
251 ASSERT_NOT_NULL(mm);
252
253 for (size_t i = 0; i < MAX_NUMBER_OF_BLOCKS + 44; i++) {
255 void *p = srn_mm_allocate_in_block_aligned(mm, b, 64, 16);
256 TEST_CHECK(p != nullptr);
258 }
259
260 srn_mm_shutdown(mm);
261}
262
263/// A released id must not resolve to a block anymore. Handing back the stale
264/// pointer lets callers bump allocate into freed memory.
266 srn_mm_t *mm = srn_mm_init(nullptr);
267 ASSERT_NOT_NULL(mm);
268
270 TEST_CHECK(srn_mm_get_block(mm, b) != nullptr);
271
273 TEST_CHECK(srn_mm_get_block(mm, b) == nullptr);
274
275 srn_mm_shutdown(mm);
276}
277
278/// Alignments above DEFAULT_BLOCK_ALIGNMENT must be honored on the returned
279/// pointer. Aligning the bump offset alone leaves the result at
280/// base modulo the requested alignment, and base is only guaranteed to be
281/// 16 byte aligned.
283 srn_mm_t *mm = srn_mm_init(nullptr);
284 ASSERT_NOT_NULL(mm);
285
287
288 // Skew the bump offset first so the aligned request cannot start at zero.
289 void *skew = srn_mm_allocate_in_block_aligned(mm, b, 1, 1);
290 TEST_CHECK(skew != nullptr);
291
292 for (size_t alignment = 32; alignment <= 256; alignment *= 2) {
293 void *p = srn_mm_allocate_in_block_aligned(mm, b, 24, alignment);
294 TEST_CHECK(p != nullptr);
295 TEST_MSG("alignment %zu, remainder %zu", alignment, (size_t)((uintptr_t)p % alignment));
296 TEST_CHECK(((uintptr_t)p % alignment) == 0);
297 }
298
299 srn_mm_shutdown(mm);
300}
301
303 srn_mm_t *mm = srn_mm_init(nullptr);
304 ASSERT_NOT_NULL(mm);
305
307 MM_TEST_LOG("Allocated block id: %zu", (size_t)b);
308
309 srn_block_t *block = srn_mm_get_block(mm, b);
310 TEST_CHECK(block != nullptr);
311
312 // This ID is guaranteed to be out of range
313 srn_block_id_t invalid = b + 100;
314 srn_block_t *null_block = srn_mm_get_block(mm, invalid);
315 TEST_CHECK(null_block == nullptr);
316
317 srn_mm_shutdown(mm);
318}
#define TEST_CHECK(cond)
Definition acutest.h:95
#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:451
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:396
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:318
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:426
size_t srn_mm_get_os_page_size(void)
Retutrns the OS page size.
Definition default.c:303
void srn_mm_shutdown(srn_mm_t *mm)
Shut down the memory manager and release the resources.
Definition default.c:374
srn_mm_t * srn_mm_init(const srn_configuration_t *config)
Initialize the memory manager, this function will panic on error.
Definition default.c:322
#define srn_mm_immortal_allocate(mm, T)
Definition interface.h:183
#define srn_mm_allocate_in_block(mm, id, T)
Definition interface.h:180
#define MAX_NUMBER_OF_BLOCKS
array of blocks is enough for us, we can tweak the size as we see fit.
Definition interface.h:50
static void test_mm_allocation()
Definition mm_tests.h:56
static void test_mm_immortal_multiblock(void)
Definition mm_tests.h:216
static void test_mm_block_bitmap(void)
Definition mm_tests.h:137
static void test_mm_allocation_in_block()
Definition mm_tests.h:72
static void test_mm_block_size_properties(void)
Definition mm_tests.h:169
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:249
static void test_mm_aligned_block_allocation(void)
Definition mm_tests.h:181
static void test_mm_over_aligned_allocation(void)
Alignments above DEFAULT_BLOCK_ALIGNMENT must be honored on the returned pointer.
Definition mm_tests.h:282
static void test_mm_get_block_after_release(void)
A released id must not resolve to a block anymore.
Definition mm_tests.h:265
static size_t padding(size_t size, size_t align)
Definition mm_tests.h:52
#define MM_TEST_LOG(...)
Definition mm_tests.h:40
static void test_mm_allocation_multiblock()
Definition mm_tests.h:92
static void test_mm_get_block_out_of_range(void)
Definition mm_tests.h:302
char foo[0xffff]
Definition mm_tests.h:49
char foo[200]
Definition mm_tests.h:44
char bar[200]
Definition mm_tests.h:45
uint8_t base[]
Where the data area starts.
Definition interface.h:89
size_t size
This is the TOTAL size of the block, header + payloud.
Definition interface.h:82
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:77
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:107
srn_block_t * immortal_block
Immortal block is a chain of blocks which will never die.
Definition interface.h:139
uint64_t block_bitmap[4]
This is a 256bit bitmap we treat it as a whole.
Definition interface.h:121