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 <[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
19#include "serene/rt/engine.h"
20
21#include "serene/rt/context.h"
22#include "serene/rt/fiber.h"
25#include "serene/rt/reactor.h"
26#include "serene/utils.h"
27
28#ifdef SRN_WITH_SERENE
29# include "serene/jit/jit.h"
30# include "serene/rt/core.h"
31# include "serene/rt/errors.h"
32# include "serene/rt/keywords.h"
33# include "serene/rt/strings.h"
34#endif
35
36#include <assert.h>
37#include <signal.h>
38#include <string.h>
39
40#define XXH_INLINE_ALL
41#define XXH_STATIC_LINKING_ONLY
42#define XXH_ENABLE_AUTOVECTORIZE
43// Disable invocation of <stdlib.h> functions, notably malloc() and free()
44#define XXH_NO_STDLIB
45// We use xxhash 32bit only, so no need for these
46#define XXH_NO_XXH3
47#define XXH_NO_LONG_LONG
48#define XXH_NO_STREAM
49#define XXH_IMPLEMENTATION
50#include "third_party/xxhash.h"
51
52#if defined(__linux__) || defined(__GLIBC__)
53# include <sys/random.h>
54#elif defined(__APPLE__)
55# include <stdlib.h>
56#elif defined(_WIN32)
57# define NOMINMAX
58# include <bcrypt.h>
59# include <windows.h>
60# pragma comment(lib, "bcrypt.lib")
61#endif
62
63// Allocate a random Seed. This function has to be run every
64// time we allocate a new context
66 // A genuinely random zero is legal output from every source below, so a
67 // zero is retried rather than treated as a failure. Two zeros in a row
68 // from a working source is a 2^-64 event, so a tiny bound suffices.
69 for (int attempt = 0; attempt < 8; attempt++) {
70 srn_seed_t s = 0;
71
72#if defined(__linux__)
73 auto res = getrandom(&s, sizeof(s), 0);
74 if (res < 0) {
75 PANIC("Can't get a random number");
76 }
77#elif defined(__APPLE__)
78 arc4random_buf(&s, sizeof(s));
79#elif defined(_WIN32)
80 NTSTATUS st = BCryptGenRandom(nullptr, (PUCHAR)&s, sizeof(s), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
81 if (st != 0) {
82 PANIC("Couldn't allocate a seed");
83 }
84#else
85 // fallback: /dev/urandom
86 FILE *f = fopen("/dev/urandom", "rb");
88 size_t got = fread(&s, sizeof(s), 1, f);
89 UNUSED(fclose(f));
90 PANIC_IF(got != 1, "Couldn't read a seed from /dev/urandom");
91#endif
92
93 if (s != 0) {
94 return s;
95 }
96 }
97
98 PANIC("Couldn't generate a nonzero seed");
99}
100
102 PANIC_IF_NULL(mm);
104
105 if (engine == nullptr) {
106 return nullptr;
107 }
108
109 // A null `config` means "use the defaults"; otherwise copy the caller's
110 // knobs. Either way the engine owns its copy, so the caller's `config` need
111 // not outlive this call. Validating the copy makes a bad knob fail loudly
112 // here, before anything is sized or bounded by it.
113 engine->config = (config != nullptr) ? *config : SRN_CONFIG_DEFAULTS;
114 srn_config_validate(&engine->config);
115
116 engine->seed = srn_generate_seed();
117 // Just reserving 0..100 for any unpredicted needs
118 engine->mm = mm;
119
120#if !defined(_WIN32)
121 // A fiber writing to a peer that has closed its end would otherwise raise
122 // SIGPIPE and take the whole process down. The reactor's WRITE path uses
123 // write(), which cannot pass MSG_NOSIGNAL, so suppress SIGPIPE process-wide
124 // and let the operation fail with -EPIPE instead. Idempotent across engines.
125 // Something to have a look at:
126 // https://stackoverflow.com/questions/8369506/why-does-sigpipe-exist
127 // Also since we're ignoring sigpipe here permanently, we can safely ignore
128 // the returning previous signal handler by the signal function call
129 UNUSED(signal(SIGPIPE, SIG_IGN));
130#endif
131
132#ifdef SRN_WITH_SERENE
133 engine->jit = srn_jit_make(mm);
134#endif
135 engine->scheduler = srn_sched_init(engine);
136 engine->reactor = srn_reactor_init(engine);
137
138#ifdef SRN_WITH_SERENE
139 // Both registries outlive every user context, so they are pinned to a
140 // context the engine itself owns. Every trie node an insert creates lands
141 // in this context's block chain, which is released only at engine
142 // shutdown.
143 engine->root_context = srn_context_make(engine);
144 engine->namespaces = hmap_empty(engine->root_context);
145 srn_spinlock_init(&engine->ns_lock);
146 engine->keywords = hmap_empty(engine->root_context);
147 srn_spinlock_init(&engine->keywords_lock);
148#endif
149 atomic_init(&engine->object_id_counter, ENGINE_FIRST_OBJECT_ID);
150
151 return engine;
152}
153
155 PANIC_IF_NULL(engine);
156
159
160#ifdef SRN_WITH_SERENE
161 srn_jit_shutdown(engine->jit);
162 srn_context_release(engine->root_context);
163#endif
164}
165
166srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len) {
167 // NULL pointers are only valid if the length is zero
168 size_t length = (data == nullptr) ? 0 : len;
169 return XXH32(data, length, engine->seed);
170}
171
173 PANIC_IF_NULL(engine);
174 return atomic_fetch_add_explicit(&engine->object_id_counter, 1, memory_order_relaxed);
175}
176
177#ifdef SRN_WITH_SERENE
179srn_engine_intern_keyword(srn_context_t *ctx, srn_metadata_t *metadata, const char *name) {
180 PANIC_IF_NULL(ctx);
181 PANIC_IF_NULL(name);
182
183 srn_engine_t *engine = ctx->engine;
184 srn_mm_t *mm = engine->mm;
185 // Cap the read at the configured string-length limit so a malformed
186 // unbounded `name` does not produce an oversized allocation.
187 const size_t max_len = engine->config.limits.string_max_len;
188 size_t name_len = strnlen(name, max_len);
189
190 srn_spinlock_lock(&engine->keywords_lock);
191
192 // Fast path, return any existing entry under the same name.
193 hmap_key_t lookup = {.data = (void *)name, .len = name_len};
194 void *found = hmap_lookup(&engine->keywords, &lookup, nullptr);
195 if (found != nullptr) {
196 srn_spinlock_unlock(&engine->keywords_lock);
197 return (srn_value_t *)found;
198 }
199
200 if (name_len == max_len) {
201 srn_spinlock_unlock(&engine->keywords_lock);
202 return srn_errors_make_error(
203 ctx, metadata, STRING_LENGTH_LIMIT_EXCEEDED, "Keyword name exceeds the string length limit"
204 );
205 }
206
207 // Interned keywords must outlive any individual context, so the name
208 // buffer, the keyword payload, and the value wrapper live in the engine's
209 // immortal memory. The table key and the trie nodes are handled by the map
210 // itself, which copies and allocates them in its pinned context.
211
212 // Name string.
213 size_t name_alloc_size = sizeof(srn_string_t) + name_len + 1;
214 srn_string_t *name_str =
215 srn_mm_immortal_allocate_aligned(mm, name_alloc_size, alignof(srn_string_t));
216 name_str->len = name_len;
217 name_str->size = name_len + 1;
218 memcpy(name_str->buffer, name, name_len);
219 name_str->buffer[name_len] = '\0';
220
221 // Keyword payload.
223 kw->name = name_str;
224
225 // Value wrapper.
227 v->type = VKeyword;
228 v->metadata = metadata;
229 v->as.keyword = kw;
230
231 hmap_key_t k = {.data = (void *)name_str->buffer, .len = name_str->len};
232 engine->keywords = hmap_insert(&engine->keywords, &k, (void *)v);
233
234 srn_spinlock_unlock(&engine->keywords_lock);
235 return v;
236}
237#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
SRN_SEED_TYPE srn_seed_t
Definition context.h:45
@ VKeyword
Definition core.h:125
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_engine_shutdown(srn_engine_t *engine)
Definition engine.c:154
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:101
srn_hash_t srn_hash(const srn_engine_t *engine, const void *data, size_t len)
Definition engine.c:166
static srn_seed_t srn_generate_seed()
Definition engine.c:65
srn_object_id_t srn_allocate_object_id(srn_engine_t *engine)
Definition engine.c:172
uint64_t srn_object_id_t
Definition engine.h:42
#define ENGINE_FIRST_OBJECT_ID
Start allocating IDs from 100, earlier IDs are reserved.
Definition engine.h:44
Error handling for the runtime.
@ STRING_LENGTH_LIMIT_EXCEEDED
Definition errors.h:83
AI Generated (🤦) Fiber subsystem overview.
hmap_t hmap_insert(const hmap_t *hmap, hmap_key_t *k, void *v)
Insert the given key k with the value v in the given hash hmap and return the new map.
Definition hashmap.c:661
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
void * hmap_lookup(const hmap_t *hmap, const hmap_key_t *k, void *default_value)
Lookup the given k in the given hmap and return the value if it's been found.
Definition hashmap.c:665
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:183
int srn_jit_shutdown(srn_jit_t *jit)
Definition jit.c:74
srn_jit_t * srn_jit_make(srn_mm_t *mm)
Definition jit.c:43
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:327
srn_scheduler_t * srn_sched_init(srn_engine_t *engine)
Definition scheduler.c:257
Note: For key equality we use the memcpy function.
Definition hashmap.h:66
Every runtime knob, in one place.
srn_limits_config_t limits
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:51
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
Definition engine.h:62
_Atomic srn_object_id_t object_id_counter
An unsigned counter to allocate object ids atomically.
Definition engine.h:57
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:75
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:86
srn_mm_t * mm
Memory manager.
Definition engine.h:65
srn_reactor_t * reactor
The I/O reactor, that is in charge of handling everything I/O.
Definition engine.h:78
A keyword, just a name.
Definition keywords.h:29
srn_string_t * name
Definition keywords.h:30
size_t string_max_len
Largest string accepted, in bytes.
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:107
size_t size
Size of the buffer.
Definition strings.h:62
uint8_t buffer[]
The buffer that holds the WTF8 sequence.
Definition strings.h:66
size_t len
length of the WTF-8 sequence in bytes
Definition strings.h:64
srn_metadata_t * metadata
Definition core.h:133
union srn_value_t::@033047061046230251001111174367071167226300135003 as
IMPORTANT NOTE: The size of this union should never be larger than a word.
srn_value_tag_t type
Definition core.h:132
srn_keyword_t * keyword
Definition core.h:146
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
static void srn_spinlock_lock(srn_spinlock_t *lock)
Definition utils.h:285
#define PANIC_IF(cond, msg)
Definition utils.h:59
static void srn_spinlock_unlock(srn_spinlock_t *lock)
Definition utils.h:276
#define UNUSED(x)
Definition utils.h:45
static void srn_spinlock_init(srn_spinlock_t *lock)
Definition utils.h:280
#define PANIC(msg)
Definition utils.h:53