Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
interface.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 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#pragma once
19
20#include <stddef.h>
21#include <stdint.h>
22#include <stdio.h>
23
25#include "serene/rt/trace.h"
26#include "serene/utils.h"
27
28/** @file
29
30 Notes:
31 - Never give out any pointer to intermediate blocks in a block chain to
32 user.
33 - Always lock the memory manager when operating only on `srn_mm_t`.
34 - Chain locks live in the manager, keyed by block id. An allocation holds
35 the chain's lock for the whole walk; a release holds the manager lock
36 and then the chain lock before freeing, so an allocation racing a
37 release either completes first or resolves the id to nothing.
38 - To the user a chain of blocks are just one block, so deallocation
39 happens on the chain level not per block
40 - It's users responsibility to copy the data between different chains.
41*/
42
43#define MM_TRACEPOINT(...) SRN_TRACEPOINT_WITH_GROUP(mm __VA_OPT__(, ) __VA_ARGS__)
44
45#define FALLBACK_PAGE_SIZE 4096U
46
47// TODO(lxsameer): Since we want to move fast, at this stage a static
48/// array of blocks is enough for us, we can tweak the size as we see
49/// fit. But we need to change this for the final stage. We should
50/// be able to dynamically expand the array of blocks.
51/// Notes. Due to my laziness, if you ever change this value, you
52/// need to change the popcount functions for the block bitmap as well.
53#define MAX_NUMBER_OF_BLOCKS 256U
54
55/// We strictly use 16 bytes alignment for blocks.
56#define DEFAULT_BLOCK_ALIGNMENT 16U
57
58typedef struct srn_mm_t srn_mm_t;
59
60/// The block id is effectively just an index in the blocks array in `srn_mm_t`.
61/// NOTE: We use the value `SIZE_MAX` to indicate NO BLOCK ID. For example, when
62/// embedding a `srn_block_id_t` field in a data structure. If the value
63/// of the field is `SIZE_MAX` ((size_t) -1), it indicates that no block id
64/// is present.
65typedef size_t srn_block_id_t;
66#define SRN_BLOCK_NO_ID SIZE_MAX
67
68/// This interface is here to abstract over the allocator. For instance,
69/// malloc/free can be a page provider. This will let us switch to other
70/// implementation later on. Eventually we might end up coming up with our
71/// own version of malloc/free.
72typedef struct srn_memory_provider_t {
73 void *(*allocate)(size_t size, size_t alignment);
74 void (*release)(void *p);
76
77typedef struct srn_block_t {
78 /// when the block does not have space to allocate a request, we will
79 /// allocate a new block and point to it here, a simple link list.
81
82 /// This is the TOTAL size of the block, header + payloud. Basically the same
83 /// as `srn_mm_t.block_size`. We have a copy here just for a few cases that
84 /// one might use a block outside of the memory manager.
85 size_t size;
86
87 /// Offset from the base
88 size_t offset;
89 /// Where the data area starts. Blocks carry no lock of their own; the
90 /// owning chain's lock lives in `srn_mm_t.chain_locks`, outside the memory
91 /// being freed on release.
92 alignas(DEFAULT_BLOCK_ALIGNMENT) uint8_t base[];
94
95_Static_assert(offsetof(srn_block_t, base) % 16 == 0, "srn_block_t::base must be 16-byte aligned");
96
97#if SERENE_DEBUG
98typedef struct srn_allocation_stats_t {
99 size_t total_allocations;
100 size_t total_os_allocations;
101 size_t allocated_pages;
102 size_t total_blocks;
103} srn_allocation_stats_t;
104#endif
105
106/// Main memory manager structure that will own all the allocated blocks and
107/// data. In every instance of the compiler there should be only one instance
108/// of this. It should be created via `srn_mm_init` and destroyed
109/// via `srn_shutdown_memory_manager`.
110typedef struct srn_mm_t {
111#if SERENE_DEBUG
112 srn_allocation_stats_t stats;
113#endif
114 /// This spinlock is here to protect the srn_mm_t when allocating/deallocating
115 /// new blocks. It is NOT used to protect blocks themselves. For block level
116 /// operations we use a block level spinlock
118
119 /// An abstraction over a memory provider like the malloc/free pair.
121 /// This is a 256bit bitmap we treat it as a whole.
122 /// 1 means that bit position in the blocks array
123 /// is occupied for allocation. otherwise it is free.
124 uint64_t block_bitmap[4];
126 /// Number of live chains. Incremented on block allocation and decremented
127 /// on release, both under `lock`.
130
131 /// One lock per chain, keyed by block id. Held for the whole allocation
132 /// walk and by release before freeing the chain. Living here rather than
133 /// inside the blocks keeps the lock valid while the chain is being freed.
135
136 /// The immortal chain has no block id, so it gets its own chain lock.
138
139 /// Immortal block is a chain of blocks which will never die. We will
140 /// free the chain at exit. it is there to allocate objects that will
141 /// be around for the duration of the program.
144
145/**
146 * Retutrns the OS page size
147 */
148size_t srn_mm_get_os_page_size(void);
149/**
150 * Allocate a new block in the memory manager and return its ID.
151 * The client code can use the ID to allocate memory on the block
152 * and when it's done, just use the same ID to release the block
153 */
154[[nodiscard]] [[gnu::nonnull(1)]]
156
157/**
158 * Release the given block id and free the memory for later allocations.
159 */
160[[gnu::nonnull(1)]]
162
163/**
164 * Return the block object associated by the given `block_id`
165 */
166[[gnu::nonnull(1)]]
168
169/**
170 * Allocate memory on a block with the given `block_id`.
171 */
172[[nodiscard]] [[gnu::nonnull(1)]]
174 srn_mm_t *mm, srn_block_id_t block_id, size_t size, size_t alignment
175);
176
177/**
178 * Allocate memory on the importal block which will never gets freed.
179 */
180[[nodiscard]] [[gnu::nonnull(1)]]
181void *srn_mm_immortal_allocate_aligned(srn_mm_t *mm, size_t size, size_t alignment);
182
183#define srn_mm_allocate_in_block(mm, id, T) \
184 (T *)srn_mm_allocate_in_block_aligned(mm, id, sizeof(T), alignof(T))
185
186#define srn_mm_immortal_allocate(mm, T) \
187 (T *)srn_mm_immortal_allocate_aligned(mm, sizeof(T), alignof(T))
188
189/**
190 * Initialize the memory manager, this function will panic on error. `config`
191 * provides the knobs the manager reads at init (`mm.block_size_magnitude`,
192 * the block size is `1 << magnitude`); a null `config` means "use the
193 * defaults". The config is read only during the call, so the caller may pass
194 * a stack value and reuse it for `srn_engine_make`.
195 */
197
198/**
199 * Shut down the memory manager and release the resources. Will panic on error.
200 * Technically it should be the final piece of clean up that we call.
201 * Note: Shutdown is not thread safe at has to execute on the main thread.
202 */
203[[gnu::nonnull(1)]]
204void srn_mm_shutdown(srn_mm_t *mm);
205
206/**
207 * Unocks the memory manager.
208 */
209[[gnu::nonnull(1)]]
211
212/**
213 * Locks the memory manager. We have to lock the memory manager when allocating
214 * blocks.
215 * TODO(lxsameer): Do we need to support thread local blocks?
216 */
217[[gnu::nonnull(1)]]
219
220// ---------------------------------------------------------------------------
221// Manual allocation
222// ---------------------------------------------------------------------------
223
224/**
225 * Generic allocations that do not participate in the block based pools.
226 * Equivalent to malloc/realloc/free. Routed through the memory
227 * manager so the backend can later be swapped without touching callers.
228 * `mm` is reserved for future per manager routing and is currently
229 * unused inside the implementation.
230 */
231[[nodiscard]] [[gnu::nonnull(1)]]
232void *srn_mm_malloc(srn_mm_t *mm, size_t size);
233
234[[nodiscard]] [[gnu::nonnull(1)]]
235void *srn_mm_reallocate(srn_mm_t *mm, void *ptr, size_t new_size);
236
237/**
238 * Release a pointer previously returned by srn_mm_malloc or
239 * srn_mm_reallocate. `ptr` may be nullptr, in which case the call is a
240 * no-op.
241 */
242[[gnu::nonnull(1)]]
243void srn_mm_free(srn_mm_t *mm, void *ptr);
244
245#if SERENE_DEBUG
246void srn_mm_print_block_summary(srn_mm_t *mm, srn_block_id_t id);
247void srn_mm_print_blocks_summary(srn_mm_t *mm);
248#endif
The single place that holds every runtime knob.
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:463
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:406
void srn_lock_memory_manager(srn_mm_t *mm)
Locks the memory manager.
Definition default.c:436
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:328
void * srn_mm_reallocate(srn_mm_t *mm, void *ptr, size_t new_size)
Definition default.c:162
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:426
void srn_mm_free(srn_mm_t *mm, void *ptr)
Release a pointer previously returned by srn_mm_malloc or srn_mm_reallocate.
Definition default.c:169
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:438
size_t srn_mm_get_os_page_size(void)
Retutrns the OS page size.
Definition default.c:313
void srn_mm_shutdown(srn_mm_t *mm)
Shut down the memory manager and release the resources.
Definition default.c:384
srn_mm_t * srn_mm_init(const srn_configuration_t *config)
Initialize the memory manager, this function will panic on error.
Definition default.c:332
#define MAX_NUMBER_OF_BLOCKS
array of blocks is enough for us, we can tweak the size as we see fit.
Definition interface.h:53
void * srn_mm_malloc(srn_mm_t *mm, size_t size)
Generic allocations that do not participate in the block based pools.
Definition default.c:155
#define DEFAULT_BLOCK_ALIGNMENT
We strictly use 16 bytes alignment for blocks.
Definition interface.h:56
void srn_unlock_memory_manager(srn_mm_t *mm)
Unocks the memory manager.
Definition default.c:434
uint8_t base[]
Where the data area starts.
Definition interface.h:92
size_t offset
Offset from the base.
Definition interface.h:88
size_t size
This is the TOTAL size of the block, header + payloud.
Definition interface.h:85
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:80
Every runtime knob, in one place.
This interface is here to abstract over the allocator.
Definition interface.h:72
void(* release)(void *p)
Definition interface.h:74
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:110
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:117
srn_block_t * immortal_block
Immortal block is a chain of blocks which will never die.
Definition interface.h:142
size_t block_size
Definition interface.h:125
size_t block_count
Number of live chains.
Definition interface.h:128
srn_spinlock_t chain_locks[MAX_NUMBER_OF_BLOCKS]
One lock per chain, keyed by block id.
Definition interface.h:134
uint64_t block_bitmap[4]
This is a 256bit bitmap we treat it as a whole.
Definition interface.h:124
srn_memory_provider_t * provider
An abstraction over a memory provider like the malloc/free pair.
Definition interface.h:120
srn_spinlock_t immortal_lock
The immortal chain has no block id, so it gets its own chain lock.
Definition interface.h:137
Platform neutral static tracepoints.