Serene Runtime 1.0.0
C runtime for the Serene programming language
Loading...
Searching...
No Matches
default.c
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 library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser 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 library 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 Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#include <stdlib.h>
20
22#include "serene/utils.h"
23
24#include <assert.h>
25#include <inttypes.h>
26#include <string.h>
27
28#if defined(_WIN32)
29#include <windows.h>
30#elif defined(__unix__) || defined(__APPLE__)
31#include <unistd.h>
32#elif defined(__wasm__)
33#define WASM_PAGE_SIZE 65536U
34#endif
35
36#if defined(MM_DEBUG)
37#define MM_LOG(FMT, ...) DBG("MM", FMT __VA_OPT__(, ) __VA_ARGS__)
38#else
39#define MM_LOG(FMT, ...)
40#endif
41
42// -----------------------------------------------------------------------------
43// Helper functions
44// -----------------------------------------------------------------------------
45static inline bool is_block_id_free(const srn_mm_t *mm, uint16_t bit) {
46 PANIC_IF(bit >= 256, "Bit position is out of block_bitmap boundaries.");
47
48 // divide by 64, which 64-bit word
49 uint64_t index = bit >> 6U;
50 // remainder, which bit in that word
51 unsigned offset = bit & 63U;
52
53 return ((mm->block_bitmap[index] >> offset) & 1ULL) == 0;
54}
55
56static inline void allocated_block_id(srn_mm_t *mm, uint16_t bit) {
57 PANIC_IF(bit >= 256, "Bit position is out of block_bitmap boundaries.");
58
59 // divide by 64, which 64-bit word
60 uint64_t index = bit >> 6U;
61 // remainder, which bit in that word
62 unsigned offset = bit & 63U;
63
64 mm->block_bitmap[index] |= (1ULL << offset);
65}
66
67static inline void deallocated_block_id(srn_mm_t *mm, uint16_t bit) {
68 PANIC_IF(bit >= 256, "Bit position is out of block_bitmap boundaries.");
69
70 uint64_t index = bit >> 6U;
71 unsigned offset = bit & 63U;
72 uint64_t mask = ~(1ULL << offset);
73 mm->block_bitmap[index] &= mask;
74}
75
76static inline int find_a_free_block_id(const srn_mm_t *mm) {
77#if defined(__GNUC__) || defined(__clang__)
78 for (int i = 0; i < 4; i++) {
79 uint64_t word = mm->block_bitmap[i];
80
81 if (~word) {
82 // invert, then find first 1 bit
83 int bit = __builtin_ctzll(~word);
84 return (i * 64) + bit;
85 }
86 }
87 return -1;
88#else
89 // Fallback
90 for (int i = 0; i < 4; i++) {
91 uint64_t word = mm->block_bitmap[i];
92 if (word != ~0ULL) {
93 uint64_t mask = ~word;
94 int bit = 0;
95 while ((mask & 1ULL) == 0) {
96 mask >>= 1;
97 bit++;
98 }
99 return (i * 64) + bit;
100 }
101 }
102 return -1;
103#endif
104}
105
106/// An abstraction over ID->Block operation
107static inline srn_block_t *get_block(const srn_mm_t *mm, srn_block_id_t block_id) {
108 if (block_id < mm->block_count) {
109 return mm->blocks[block_id];
110 }
111 return nullptr;
112}
113
114/// Just a proxy functions to the standard stdlib malloc/free
115/// later on if we decided to use mimalloc or something or even
116/// our own malloc, we can plug them in like this
117static void *stdlib_allocator(size_t size, size_t alignment) {
118 PANIC_IF(size % alignment != 0, "'size' should be a multiple of alignment");
119
120#if defined(_WIN32)
121 return _aligned_malloc(size, alignment);
122#else
123 // C11 standard. It stills bothers me why microsoft is not doing it.
124 return aligned_alloc(alignment, size);
125#endif
126}
127
128static void stdlib_releaser(void *ptr) { free(ptr); }
129
131 .allocate = &stdlib_allocator,
132 .release = &stdlib_releaser,
133};
134
135// ---------------------------------------------------------------------------
136// Generic allocations (currently a thin wrapper over stdlib malloc/realloc
137// /free). Block based machinery above is untouched; these are for callers
138// that need a plain "give me N bytes, here you go" allocator.
139// ---------------------------------------------------------------------------
140
141void *srn_mm_allocate(srn_mm_t *mm, size_t size) {
142 UNUSED(mm);
143 return malloc(size);
144}
145
146void *srn_mm_reallocate(srn_mm_t *mm, void *ptr, size_t new_size) {
147 UNUSED(mm);
148 return realloc(ptr, new_size);
149}
150
151void srn_mm_free(srn_mm_t *mm, void *ptr) {
152 UNUSED(mm);
153 free(ptr);
154}
155
156static inline size_t closest_multiple(size_t x, size_t m) {
157 size_t half = m / 2;
158 return (x + half) / m * m;
159}
160
161/// Return the size of the payload section of the block
162static inline size_t block_capacity(const srn_block_t *block) {
163 return block->size - offsetof(srn_block_t, base);
164}
165
166static inline void init_block(srn_mm_t *mm, srn_block_t *block) {
167 PANIC_IF_NULL(mm);
168 block->next = nullptr;
169 block->size = mm->block_size;
170 block->offset = 0;
171 srn_spinlock_init(&block->lock);
172 srn_spinlock_unlock(&block->lock);
173}
174
175/// Allocate a block worth of memory using the memory provider. This is an
176/// internal function and it will NOT allocate block_id nor track the block
177/// via the memory manager by default.
178static inline void *alloc_block_internal(srn_mm_t *mm) {
179 PANIC_IF_NULL(mm);
180 srn_block_t *block =
182 PANIC_IF_NULL(block);
183 init_block(mm, block);
184 return block;
185}
186
187static inline void destroy_chain(srn_mm_t *mm, srn_block_id_t root_id) {
188
189 if (is_block_id_free(mm, root_id)) {
190 return;
191 }
192 srn_block_t *block = get_block(mm, root_id);
193
195 while (block != nullptr) {
196 srn_block_t *tmp = block;
197 block = tmp->next;
198
199 mm->provider->release(tmp);
200 }
201 deallocated_block_id(mm, root_id);
203}
204
205/// This is the main allocation logic that allocates the space in the given
206/// block. Other public functions have to use this fuction to operate.
207static inline void *alloc_in_block(srn_mm_t *mm, srn_block_t *root_block, size_t size,
208 size_t alignment) {
209
210 if (size > block_capacity(root_block)) {
211 // Since all the blocks in a block chain have the same size,
212 // if user is asking for an allocation larger than the block size,
213 // there is no way we can find it in this chain, so a larger block
214 // size is required
215 TODO("We need to allocate a larger block");
216 }
217 MM_LOG("Allocating %ld with %ld alignment", size, alignment);
218
219 MM_LOG("Root block: %p", (void *)root_block);
220 srn_block_t *target_block = root_block;
221 MM_LOG("Walking the block chain");
222 for (;;) {
223 srn_spinlock_t *lock = &target_block->lock;
224 srn_spinlock_lock(lock);
225
226 MM_LOG("Next block: %p", (void *)target_block);
227 MM_LOG("Base: %p | Offset: 0x%lx | Space: %ld | Total size: %ld", (void *)&target_block->base,
228 target_block->offset, block_remaining(target_block), target_block->size);
229
230 size_t base_offset = target_block->offset;
231 size_t aligned_offset = (base_offset + (alignment - 1)) & ~(alignment - 1);
232 size_t capacity = block_capacity(target_block);
233
234 MM_LOG("Calculated aligned offset 0x%lx", aligned_offset);
235
236 if (aligned_offset + size > capacity) {
237 MM_LOG("We can't allocate in this block");
238 if (target_block->next != nullptr) {
240 // let's try the next block in the chain
241 target_block = target_block->next;
242 continue;
243 }
244 // We need to create a new block for this chain.
245 MM_LOG("We have to allocate a new block");
246
248 init_block(mm, b);
249 target_block->next = b;
250 target_block = b;
252 MM_LOG("New block: %p", (void *)target_block);
253 continue;
254 }
255 MM_LOG("We have found enough space");
256
257 void *ptr = target_block->base + aligned_offset;
258 target_block->offset = aligned_offset + size;
259 MM_LOG("Allocated 0x%lx bytes from %p to 0x%lx", target_block->offset, ptr,
260 (uintptr_t)ptr + (uintptr_t)target_block->offset);
261
263 return ptr;
264 }
265}
266
267// -----------------------------------------------------------------------------
268// Public API
269// -----------------------------------------------------------------------------
271#if defined(_WIN32)
272 SYSTEM_INFO si;
273 GetSystemInfo(&si);
274 return (size_t)si.dwPageSize;
275#elif defined(__unix__) || defined(__APPLE__)
276 long sz = sysconf(_SC_PAGESIZE);
277 return (sz > 0) ? (size_t)sz : FALLBACK_PAGE_SIZE;
278#elif defined(__wasm__)
279 return WASM_PAGE_SIZE;
280#else
281 return FALLBACK_PAGE_SIZE;
282#endif
283}
284
286 const size_t ps = srn_mm_get_os_page_size();
288}
289
291 return get_block(mm, block_id);
292}
293
295 srn_mm_t *mm = malloc(sizeof(srn_mm_t));
296 PANIC_IF_NULL(mm);
297
299 memset(mm->block_bitmap, 0, sizeof(mm->block_bitmap));
300
301 mm->block_count = 0;
303 memset((void *)mm->blocks, 0, sizeof(mm->blocks));
304
305 PANIC_IF(mm->block_size <= sizeof(srn_block_t),
306 "Wrong block size. Modify DESIRED_BLOCK_SIZE to a larger number");
307
309
310 srn_block_t *immortal =
312 PANIC_IF_NULL(immortal);
313 init_block(mm, immortal);
314 mm->immortal_block = immortal;
315
316#if SERENE_DEBUG
317 mm->stats.allocated_pages = 0;
318 mm->stats.total_allocations = 0;
319 mm->stats.total_os_allocations = 0;
320 mm->stats.total_blocks = 0;
321#endif
322 return mm;
323}
324
326 PANIC_IF_NULL(mm);
327 assert(mm->provider != nullptr);
328
329 for (size_t i = 0; i < mm->block_count; i++) {
330 if (mm->blocks[i] != nullptr) {
331 destroy_chain(mm, i);
332 }
333 }
334
335 srn_block_t *block = mm->immortal_block;
336
337 while (block != nullptr) {
338 srn_block_t *tmp = block;
339 block = tmp->next;
340
341 mm->provider->release(tmp);
342 }
343
344 mm->provider->release(mm);
345}
346
348 size_t alignment) {
349 MM_LOG("Allocating %ld bytes with 16 bytes alignment in block: %zu", size, block_id);
350 srn_block_t *block = get_block(mm, block_id);
351 PANIC_IF_NULL(block);
352
353 return alloc_in_block(mm, block, size, alignment);
354}
355
356void *srn_mm_immortal_allocate_aligned(srn_mm_t *mm, size_t size, size_t alignment) {
357 return alloc_in_block(mm, mm->immortal_block, size, alignment);
358}
359
361
363
365 PANIC_IF(mm->block_count >= MAX_NUMBER_OF_BLOCKS - 1, "Out of memory");
366
369 int index = find_a_free_block_id(mm);
370 PANIC_IF(index == -1, "Couldn't allocate a block id. This is a bug!");
371
372 init_block(mm, block);
373 mm->block_count++;
374 PANIC_IF(!is_block_id_free(mm, (uint16_t)index),
375 "Miscalculated the block id. It is not free. This is a bug!");
376 allocated_block_id(mm, (uint16_t)index);
377 mm->blocks[(srn_block_id_t)index] = block;
378
380
381#if SERENE_DEBUG
382 mm->stats.total_blocks++;
383 mm->stats.total_os_allocations++;
384#endif
385 return (srn_block_id_t)index;
386}
387
389 PANIC_IF_NULL(mm);
390 destroy_chain(mm, id);
391}
392
393#if SERENE_DEBUG
394void srn_mm_print_block_summary(srn_mm_t *mm, srn_block_id_t id) {
395 srn_block_t *block = get_block(mm, id);
396 PANIC_IF_NULL(block);
397
398 printf("Block: %zu@%lx" PRIXPTR "\n", id, (uintptr_t)block);
399 printf("======================================================\n");
400 printf("Next:\t");
401 if (block->next == nullptr) {
402 printf("X\n");
403 } else {
404 printf("%lx" PRIXPTR "\n", (uintptr_t)block->next);
405 }
406}
407
408void srn_mm_print_blocks_summary(srn_mm_t *mm) {
409 for (size_t d = 0; d <= mm->block_count; d++) {
410 srn_mm_print_block_summary(mm, d);
411 }
412}
413#endif
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:388
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:347
static void destroy_chain(srn_mm_t *mm, srn_block_id_t root_id)
Definition default.c:187
static void * alloc_block_internal(srn_mm_t *mm)
Allocate a block worth of memory using the memory provider.
Definition default.c:178
static void stdlib_releaser(void *ptr)
Definition default.c:128
#define MM_LOG(FMT,...)
Definition default.c:39
static srn_block_t * get_block(const srn_mm_t *mm, srn_block_id_t block_id)
An abstraction over ID->Block operation.
Definition default.c:107
static void init_block(srn_mm_t *mm, srn_block_t *block)
Definition default.c:166
static srn_memory_provider_t stdlib_provider
Definition default.c:130
static void deallocated_block_id(srn_mm_t *mm, uint16_t bit)
Definition default.c:67
void srn_lock_memory_manager(srn_mm_t *mm)
Locks the memory manager.
Definition default.c:362
static int find_a_free_block_id(const srn_mm_t *mm)
Definition default.c:76
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:290
void * srn_mm_reallocate(srn_mm_t *mm, void *ptr, size_t new_size)
Definition default.c:146
void * srn_mm_immortal_allocate_aligned(srn_mm_t *mm, size_t size, size_t alignment)
Allocate memory on the importal block which will never gets freed.
Definition default.c:356
void srn_mm_free(srn_mm_t *mm, void *ptr)
Release a pointer previously returned by srn_mm_allocate or srn_mm_reallocate.
Definition default.c:151
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:364
void * srn_mm_allocate(srn_mm_t *mm, size_t size)
Generic allocations that do not participate in the block based pools.
Definition default.c:141
size_t srn_mm_get_os_page_size(void)
Retutrns the OS page size.
Definition default.c:270
static void allocated_block_id(srn_mm_t *mm, uint16_t bit)
Definition default.c:56
srn_mm_t * srn_mm_init()
Initialize the memory manager, this function will panic on error.
Definition default.c:294
void srn_mm_shutdown(srn_mm_t *mm)
Shut down the memory manager and release the resources.
Definition default.c:325
static size_t block_capacity(const srn_block_t *block)
Return the size of the payload section of the block.
Definition default.c:162
size_t srn_mm_get_block_size(void)
Calculates and return the block size based on our desired size and the OS page size.
Definition default.c:285
static void * alloc_in_block(srn_mm_t *mm, srn_block_t *root_block, size_t size, size_t alignment)
This is the main allocation logic that allocates the space in the given block.
Definition default.c:207
static void * stdlib_allocator(size_t size, size_t alignment)
Just a proxy functions to the standard stdlib malloc/free later on if we decided to use mimalloc or s...
Definition default.c:117
static size_t closest_multiple(size_t x, size_t m)
Definition default.c:156
void srn_unlock_memory_manager(srn_mm_t *mm)
Unocks the memory manager.
Definition default.c:360
static bool is_block_id_free(const srn_mm_t *mm, uint16_t bit)
Definition default.c:45
#define DESIRED_BLOCK_SIZE
This is the value that we want for our blocks but we want it to be a multiple of the page size on the...
Definition interface.h:53
#define FALLBACK_PAGE_SIZE
Definition interface.h:36
#define MAX_NUMBER_OF_BLOCKS
TODO(lxsameer): Since we want to move fast, at this stage a static array of blocks is enough for us,...
Definition interface.h:44
#define DEFAULT_BLOCK_ALIGNMENT
We strictly use 16 bytes alignment for blocks.
Definition interface.h:47
size_t offset
Offset from the base.
Definition interface.h:91
srn_spinlock_t lock
This lock will protect only the block level operations and NOT the memory manager.
Definition interface.h:79
size_t size
This is the TOTAL size of the block, header + payloud.
Definition interface.h:88
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:83
This interface is here to abstract over the allocator.
Definition interface.h:69
void *(* allocate)(size_t size, size_t alignment)
Definition interface.h:70
void(* release)(void *p)
Definition interface.h:71
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:112
srn_block_t * blocks[MAX_NUMBER_OF_BLOCKS]
Definition interface.h:129
srn_spinlock_t lock
This spinlock is here to protect the srn_mm_t when allocating/deallocating new blocks.
Definition interface.h:119
srn_block_t * immortal_block
Immortal block is a chain of blocks which will never die.
Definition interface.h:134
size_t block_size
Definition interface.h:127
size_t block_count
Definition interface.h:128
uint64_t block_bitmap[4]
This is a 256bit bitmap we treat it as a whole.
Definition interface.h:126
srn_memory_provider_t * provider
An abstraction over a memory provider like the malloc/free pair.
Definition interface.h:122
#define PANIC_IF_NULL(ptr)
Definition utils.h:64
static void srn_spinlock_lock(srn_spinlock_t *lock)
Definition utils.h:286
#define PANIC_IF(cond, msg)
Definition utils.h:57
static void srn_spinlock_unlock(srn_spinlock_t *lock)
Definition utils.h:277
#define UNUSED(x)
Definition utils.h:43
static void srn_spinlock_init(srn_spinlock_t *lock)
Definition utils.h:281
#define TODO(msg)
Definition utils.h:52