Serene Runtime 1.0.0
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
24#include "serene/utils.h"
25
26/// @file
27/// Notes:
28/// - Never give out any pointer to intermediate blocks in a block chain to
29/// user.
30/// - Always lock the memory manager when operating only on `srn_mm_t`.
31/// - Always lock the block when operating on it
32/// - Never lock the memory manager to operate on a block in it.
33/// - To the user a chain of blocks are just one block, so deallocation
34/// happens on the chain level not per block
35/// - It's users responsibility to copy the data between different chains.
36#define FALLBACK_PAGE_SIZE 4096U
37
38/// TODO(lxsameer): Since we want to move fast, at this stage a static
39/// array of blocks is enough for us, we can tweak the size as we see
40/// fit. But we need to change this for the final stage. We should
41/// be able to dynamically expand the array of blocks.
42/// Notes: Due to my laziness, if you ever change this value, you
43/// need to change the popcount functions for the block bitmap as well.
44#define MAX_NUMBER_OF_BLOCKS 256U
45
46/// We strictly use 16 bytes alignment for blocks.
47#define DEFAULT_BLOCK_ALIGNMENT 16U
48
49/// This is the value that we want for our blocks but we want it to be
50/// a multiple of the page size on the platform as well. So there is no
51/// guarantee that we get exactly this number.
52/// 128Kib
53#define DESIRED_BLOCK_SIZE 131072
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 /// This lock will protect only the block level operations and NOT
76 /// the memory manager. We lock only the root block when operating on
77 /// block chains. Intermediate blocks considered as part of the root
78 /// block.
80
81 /// when the block does not have space to allocate a request, we will
82 /// allocate a new block and point to it here, a simple link list.
84
85 /// This is the TOTAL size of the block, header + payloud. Basically the same
86 /// as `srn_mm_t.block_size`. We have a copy here just for a few cases that
87 /// one might use a block outside of the memory manager.
88 size_t size;
89
90 /// Offset from the base
91 size_t offset;
92 /// Where the data area starts
93 uint8_t base[];
95
96_Static_assert(offsetof(srn_block_t, base) % 16 == 0,
97 "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];
130
131 /// Immortal block is a chain of blocks which will never die. We will
132 /// free the chain at exit. it is there to allocate objects that will
133 /// be around for the duration of the program.
136
137/// Retutrns the OS page size
138size_t srn_mm_get_os_page_size(void);
139/// Calculates and return the block size based on our desired size
140/// and the OS page size. Basically the ruturn value will be a number
141/// multiple of the OS page size and close or equal to our desired size
142size_t srn_mm_get_block_size(void);
143
144/// Allocate a new block in the memory manager and return its ID.
145/// The client code can use the ID to allocate memory on the block
146/// and when it's done, just use the same ID to release the block
147[[nodiscard]] [[gnu::nonnull(1)]] srn_block_id_t
149
150/// Release the given block id and free the memory for later allocations.
151[[gnu::nonnull(1)]] void srn_mm_release_block(srn_mm_t *mm, srn_block_id_t id);
152
153/// Return the block object associated by the given `block_id`
154[[gnu::nonnull(1)]] srn_block_t *srn_mm_get_block(srn_mm_t *mm,
155 srn_block_id_t block_id);
156
157/// Allocate memory on a block with the given `block_id`.
158[[nodiscard]] [[gnu::nonnull(1)]] void *
160 size_t size, size_t alignment);
161
162/// Allocate memory on the importal block which will never gets freed.
163[[nodiscard]] [[gnu::nonnull(1)]] void *
164srn_mm_immortal_allocate_aligned(srn_mm_t *mm, size_t size, size_t alignment);
165
166#define srn_mm_allocate_in_block(mm, id, T) \
167 (T *)srn_mm_allocate_in_block_aligned(mm, id, sizeof(T), alignof(T))
168
169#define srn_mm_immortal_allocate(mm, T) \
170 (T *)srn_mm_immortal_allocate_aligned(mm, sizeof(T), alignof(T))
171
172/// Initialize the memory manager, this function will panic on error
173srn_mm_t *srn_mm_init(void);
174
175/// Shut down the memory manager and release the resources. Will panic on error.
176/// Technically it should be the final piece of clean up that we call.
177/// Note: Shutdown is not thread safe at has to execute on the main thread.
178[[gnu::nonnull(1)]] void srn_mm_shutdown(srn_mm_t *mm);
179
180/// Unocks the memory manager.
181[[gnu::nonnull(1)]] void srn_unlock_memory_manager(srn_mm_t *mm);
182
183/// Locks the memory manager. We have to lock the memory manager when allocating
184/// blocks.
185/// TODO(lxsameer): Do we need to support thread local blocks?
186[[gnu::nonnull(1)]] void srn_lock_memory_manager(srn_mm_t *mm);
187
188// ---------------------------------------------------------------------------
189// Manual allocation
190// ---------------------------------------------------------------------------
191
192/// Generic allocations that do not participate in the block based pools.
193/// Equivalent to malloc/realloc/free. Routed through the memory
194/// manager so the backend can later be swapped without touching callers.
195/// `mm` is reserved for future per manager routing and is currently
196/// unused inside the implementation.
197[[nodiscard]] [[gnu::nonnull(1)]] void *srn_mm_allocate(srn_mm_t *mm,
198 size_t size);
199
200[[nodiscard]] [[gnu::nonnull(1)]] void *
201srn_mm_reallocate(srn_mm_t *mm, void *ptr, size_t new_size);
202
203/// Release a pointer previously returned by srn_mm_allocate or
204/// srn_mm_reallocate. `ptr` may be nullptr, in which case the call is a
205/// no-op.
206[[gnu::nonnull(1)]] void srn_mm_free(srn_mm_t *mm, void *ptr);
207
208#if SERENE_DEBUG
209void srn_mm_print_block_summary(srn_mm_t *mm, srn_block_id_t id);
210void srn_mm_print_blocks_summary(srn_mm_t *mm);
211#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
void srn_lock_memory_manager(srn_mm_t *mm)
Locks the memory manager.
Definition default.c:362
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
srn_mm_t * srn_mm_init(void)
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
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
#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
void srn_unlock_memory_manager(srn_mm_t *mm)
Unocks the memory manager.
Definition default.c:360
uint8_t base[]
Where the data area starts.
Definition interface.h:93
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(* 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