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