Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
engine.c
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
19#include "serene/rt/engine.h"
20
21#include "serene/rt/context.h"
22#include "serene/rt/fiber.h"
26#include "serene/rt/reactor.h"
28#include "serene/utils.h"
29
30#ifdef SRN_WITH_SERENE
31# include "serene/jit/jit.h"
32# include "serene/rt/core.h"
33# include "serene/rt/errors.h"
34# include "serene/rt/keywords.h"
35# include "serene/rt/strings.h"
36#endif
37
38#include <assert.h>
39#include <signal.h>
40#include <string.h>
41
42#define XXH_INLINE_ALL
43#define XXH_STATIC_LINKING_ONLY
44#define XXH_ENABLE_AUTOVECTORIZE
45// Disable invocation of <stdlib.h> functions, notably malloc() and free()
46#define XXH_NO_STDLIB
47// We use xxhash 32bit only, so no need for these
48#define XXH_NO_XXH3
49#define XXH_NO_LONG_LONG
50#define XXH_NO_STREAM
51#define XXH_IMPLEMENTATION
52#include "third_party/xxhash.h"
53
54#if defined(__linux__) || defined(__GLIBC__)
55# include <sys/random.h>
56#elif defined(__APPLE__)
57# include <stdlib.h>
58#elif defined(_WIN32)
59# define NOMINMAX
60# include <bcrypt.h>
61# include <windows.h>
62# pragma comment(lib, "bcrypt.lib")
63#endif
64
65// Allocate a random Seed. This function has to be run every
66// time we allocate a new context
68 // A genuinely random zero is legal output from every source below, so a
69 // zero is retried rather than treated as a failure. Two zeros in a row
70 // from a working source is a 2^-64 event, so a tiny bound suffices.
71 for (int attempt = 0; attempt < 8; attempt++) {
72 srn_seed_t s = 0;
73
74#if defined(__linux__)
75 auto res = getrandom(&s, sizeof(s), 0);
76 if (res < 0) {
77 PANIC("Can't get a random number");
78 }
79#elif defined(__APPLE__)
80 arc4random_buf(&s, sizeof(s));
81#elif defined(_WIN32)
82 NTSTATUS st = BCryptGenRandom(nullptr, (PUCHAR)&s, sizeof(s), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
83 if (st != 0) {
84 PANIC("Couldn't allocate a seed");
85 }
86#else
87 // fallback: /dev/urandom
88 FILE *f = fopen("/dev/urandom", "rb");
90 size_t got = fread(&s, sizeof(s), 1, f);
91 UNUSED(fclose(f));
92 PANIC_IF(got != 1, "Couldn't read a seed from /dev/urandom");
93#endif
94
95 if (s != 0) {
96 return s;
97 }
98 }
99
100 PANIC("Couldn't generate a nonzero seed");
101}
102
104 PANIC_IF_NULL(mm);
106
107 if (engine == nullptr) {
108 return nullptr;
109 }
110
111 // A null `config` means "use the defaults"; otherwise copy the caller's
112 // knobs. Either way the engine owns its copy, so the caller's `config` need
113 // not outlive this call. Validating the copy makes a bad knob fail loudly
114 // here, before anything is sized or bounded by it.
115 engine->config = (config != nullptr) ? *config : SRN_CONFIG_DEFAULTS;
116 srn_config_validate(&engine->config);
117
118 engine->seed = srn_generate_seed();
119 // Just reserving 0..100 for any unpredicted needs
120 engine->mm = mm;
121
122#if !defined(_WIN32)
123 // A fiber writing to a peer that has closed its end would otherwise raise
124 // SIGPIPE and take the whole process down. The reactor's WRITE path uses
125 // write(), which cannot pass MSG_NOSIGNAL, so suppress SIGPIPE process-wide
126 // and let the operation fail with -EPIPE instead. Idempotent across engines.
127 // Something to have a look at:
128 // https://stackoverflow.com/questions/8369506/why-does-sigpipe-exist
129 // Also since we're ignoring sigpipe here permanently, we can safely ignore
130 // the returning previous signal handler by the signal function call
131 UNUSED(signal(SIGPIPE, SIG_IGN));
132#endif
133
134#ifdef SRN_WITH_SERENE
135 // engine->jit = srn_jit_create(mm, nullptr);
136 // PANIC_IF_NULL(engine->jit);
137#endif
138 engine->scheduler = srn_sched_init(engine);
139 engine->reactor = srn_reactor_init(engine);
140
141#ifdef SRN_WITH_SERENE
142 // Both registries outlive every user context, so they are pinned to a
143 // context the engine itself owns. Every trie node an insert creates lands
144 // in this context's block chain, which is released only at engine
145 // shutdown.
146 engine->root_context = srn_context_make(engine);
147
148 // Each registry head is an immutable, heap allocated map that readers load
149 // atomically without a lock. Writers replace the head under the matching
150 // mutex, so the mutexes live in the same context as the heads. Nothing
151 // else can see the engine yet, so the atomics are initialized instead of stored.
152 hmap_t *namespaces = ALLOC_OR_PANIC(engine->root_context, hmap_t);
153 *namespaces = hmap_empty(engine->root_context);
154 atomic_init(&engine->namespaces, namespaces);
155
156 engine->ns_lock = ALLOC_OR_PANIC(engine->root_context, srn_fiber_mutex_t);
157 srn_fiber_mutex_init(engine->ns_lock);
158
159 hmap_t *keywords = ALLOC_OR_PANIC(engine->root_context, hmap_t);
160 *keywords = hmap_empty(engine->root_context);
161 atomic_init(&engine->keywords, keywords);
162
163 engine->keywords_lock = ALLOC_OR_PANIC(engine->root_context, srn_fiber_mutex_t);
164 srn_fiber_mutex_init(engine->keywords_lock);
165#endif
166 atomic_init(&engine->object_id_counter, ENGINE_FIRST_OBJECT_ID);
167
168 // TODO(lxsameer): Should we create a sub context out of root?
169 engine->source_manager = srn_sm_init(engine->root_context);
170
171 return engine;
172}
173
175 PANIC_IF_NULL(engine);
176
179 srn_sm_shutdown(engine->source_manager);
180
181#ifdef SRN_WITH_SERENE
182 // srn_jit_destroy(engine->jit);
183 srn_context_release(engine->root_context);
184#endif
185}
186
187srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len) {
188 // NULL pointers are only valid if the length is zero
189 size_t length = (data == nullptr) ? 0 : len;
190 return XXH32(data, length, engine->seed);
191}
192
194 PANIC_IF_NULL(engine);
195 return atomic_fetch_add_explicit(&engine->object_id_counter, 1, memory_order_relaxed);
196}
197
198/* #ifdef SRN_WITH_SERENE */
199/* srn_value_t * */
200/* srn_engine_intern_keyword(srn_context_t *ctx, srn_metadata_t *metadata, const char *name) { */
201/* PANIC_IF_NULL(ctx); */
202/* PANIC_IF_NULL(name); */
203
204/* srn_engine_t *engine = ctx->engine; */
205/* srn_mm_t *mm = engine->mm; */
206/* // Cap the read at the configured string-length limit so a malformed */
207/* // unbounded `name` does not produce an oversized allocation. */
208/* const size_t max_len = engine->config.limits.string_max_len; */
209/* size_t name_len = strnlen(name, max_len); */
210
211/* // A read that hit the cap was truncated, so it must not reach the table. */
212/* // Its prefix could otherwise match a shorter keyword and hand back the wrong */
213/* // value. */
214/* if (name_len == max_len) { */
215/* return srn_errors_make_error( */
216/* ctx, metadata, STRING_LENGTH_LIMIT_EXCEEDED, "Keyword name exceeds the string length limit"
217 */
218/* ); */
219/* } */
220
221/* hmap_key_t k = {.data = (void *)name, .len = name_len}; */
222
223/* // Every published head is immutable, so readers only need an acquire load */
224/* // and never take the lock. */
225/* hmap_t *keywords = atomic_load_explicit(&engine->keywords, memory_order_acquire); */
226/* void *found = hmap_lookup(keywords, &k, nullptr); */
227/* if (found != nullptr) { */
228/* return (srn_value_t *)found; */
229/* } */
230
231/* srn_value_t *v = nullptr; */
232
233/* // Writers are serialized by `keywords_lock`. Two interners racing on the */
234/* // same new name would otherwise both miss and produce two distinct */
235/* // keywords, and keyword equality is pointer identity. */
236/* WITH_LOCK(engine->keywords_lock, { */
237/* // Another interner may have added the name between the unlocked lookup */
238/* // and taking the lock, so the lookup is repeated here. */
239/* keywords = atomic_load_explicit(&engine->keywords, memory_order_relaxed); */
240/* v = hmap_lookup(keywords, &k, nullptr); */
241
242/* if (v == nullptr) { */
243/* // Interned keywords must outlive any individual context, so the name */
244/* // buffer, the keyword payload, and the value wrapper live in the */
245/* // engine's immortal memory. The table key and the trie nodes are */
246/* // handled by the map itself, which copies and allocates them in its */
247/* // pinned context. */
248
249/* // Name string. */
250/* size_t name_alloc_size = sizeof(srn_string_t) + name_len + 1; */
251/* srn_string_t *name_str = */
252/* srn_mm_immortal_allocate_aligned(mm, name_alloc_size, alignof(srn_string_t)); */
253/* name_str->len = name_len; */
254/* name_str->size = name_len + 1; */
255/* memcpy(name_str->buffer, name, name_len); */
256/* name_str->buffer[name_len] = '\0'; */
257
258/* // Keyword payload. */
259/* srn_keyword_t *kw = srn_mm_immortal_allocate(mm, srn_keyword_t); */
260/* kw->name = name_str; */
261
262/* // Value wrapper. */
263/* v = srn_mm_immortal_allocate(mm, srn_value_t); */
264/* v->type = VKeyword; */
265/* v->metadata = metadata; */
266/* v->as.keyword = kw; */
267
268/* // The old head stays valid for readers that already loaded it, so the */
269/* // new version gets its own allocation. */
270/* hmap_t *next = ALLOC_OR_PANIC(engine->root_context, hmap_t); */
271/* *next = hmap_insert(keywords, &k, (void *)v); */
272/* atomic_store_explicit(&engine->keywords, next, memory_order_release); */
273/* } */
274/* }); */
275
276/* return v; */
277/* } */
278/* #endif */
void srn_config_validate(const srn_configuration_t *config)
A configuration with every field set to its default.
#define SRN_CONFIG_DEFAULTS
srn_context_t * srn_context_make(srn_engine_t *engine)
Make an empty context, by allocating a new memory block.
Definition context.c:39
int srn_context_release(srn_context_t *ctx)
Definition context.c:64
SRN_HASH_TYPE srn_hash_t
Definition context.h:44
#define ALLOC_OR_PANIC(ctx, T)
Definition context.h:98
SRN_SEED_TYPE srn_seed_t
Definition context.h:45
void srn_engine_shutdown(srn_engine_t *engine)
Definition engine.c:174
srn_engine_t * srn_engine_make(srn_mm_t *mm, const srn_configuration_t *config)
Create the engine over mm, copying config (the runtime's knobs) into it.
Definition engine.c:103
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:187
static srn_seed_t srn_generate_seed()
Definition engine.c:67
srn_object_id_t srn_allocate_object_id(srn_engine_t *engine)
Definition engine.c:193
uint64_t srn_object_id_t
Definition engine.h:45
#define ENGINE_FIRST_OBJECT_ID
Start allocating IDs from 100, earlier IDs are reserved.
Definition engine.h:47
Error handling for the runtime.
AI Generated (🤦) Fiber subsystem overview.
XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32(const void *input, size_t length, XXH32_hash_t seed)
Calculates the 32-bit hash of input using xxHash32.
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
This is an implementation of Compressed Hash-Array Mapped Prefix-tree, which is a bit-partitioned,...
#define srn_mm_immortal_allocate(mm, T)
Definition interface.h:188
void srn_fiber_mutex_init(srn_fiber_mutex_t *m)
Initialise m to the unlocked state.
Definition mutex.c:76
A fiber blocking mutex.
void srn_reactor_shutdown(srn_reactor_t *reactor)
Tear the reactor down, stop and join the reactor thread and release its channels.
Definition reactor.c:66
srn_reactor_t * srn_reactor_init(srn_engine_t *engine)
Allocate the IO reactor from the engine.
Definition reactor.c:34
Reactor overview.
void srn_sched_shutdown(srn_scheduler_t *sched)
The one stop tear down of the fiber subsystem, should be called once srn_sched_run has returned.
Definition scheduler.c:333
srn_scheduler_t * srn_sched_init(srn_engine_t *engine)
Definition scheduler.c:263
void srn_sm_shutdown(srn_sm_t *sm)
Shutdown the source manager freeing all the buffers.
srn_sm_t * srn_sm_init(srn_context_t *ctx)
Initialize the source manager, as an immortal data structure.
Source manager is what that read the source files and own the buffer that contains the data.
const size_t len
Every runtime knob, in one place.
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:54
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
Definition engine.h:65
_Atomic srn_object_id_t object_id_counter
An unsigned counter to allocate object ids atomically.
Definition engine.h:60
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:78
srn_seed_t seed
We use the seed for hashing and the value will be generated at random for each new context.
Definition engine.h:89
srn_mm_t * mm
Memory manager.
Definition engine.h:68
srn_reactor_t * reactor
The I/O reactor, that is in charge of handling everything I/O.
Definition engine.h:81
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:112
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define UNUSED(x)
Definition utils.h:45
#define PANIC(msg)
Definition utils.h:53