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