1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/* code_abi.h — the native module ABI.
*
* A native module is a `.so` compiled from any language that can produce a
* C-ABI shared library. It must:
*
* 1. `#include` this header (or reproduce it exactly — the layout below,
* once a module is compiled against it, is a wire format: changing it
* silently breaks every module built against the old one).
* 2. Export `uint32_t code_module_abi_version(void)`, returning
* `CODE_ABI_VERSION`.
* 3. Export `void code_module_dispatch(CodeValue *out, const CodeValue
* *particle)`, doing its own `_class`-field dispatch exactly like
* `runtime.c`'s `code_core_dispatch` — see that function for the
* pattern to copy.
* 4. Export `void code_release(CodeValue *v)`, so the host can free
* whatever `code_module_dispatch` allocated for its result. The
* simplest way to get a correct one is to `#include "runtime.c"`
* itself (its constructors/refcounting are exactly what a handler
* needs to build a result) — see `tests/native_modules/` for an
* example.
* 5. *Optionally* export `const CodeVarList *code_module_vars(void)`,
* returning the module's exported values (constants) — see
* `CodeVarList` below. A module that does not export it simply has no
* exported variables; `link "x.so" as x` then binds `x` to an empty
* object. This is optional (not a required symbol) so that a Phase 1
* module — handlers only — keeps working unchanged, and so the ABI
* version does not have to bump for it.
* 6. *Optionally* export `void code_module_set_inbound(void *queue,
* CodeEmitFn emit)`. The host calls it once at link time to hand the
* module a queue and the function that pushes onto it; the module
* keeps both and may call `emit(queue, &particle)` to speak *first*,
* rather than only answering a dispatch. Optional for the same reason
* as `code_module_vars`: a module that never initiates simply doesn't
* export it, and the ABI version doesn't move.
* 7. *Optionally* export `void code_module_inbound_reply(const CodeValue
* *particle, const CodeValue *result)`. After the host dispatches a
* particle this module pushed, it calls this with the particle it
* pushed and whatever the program's handler returned — `CODE_NULL` when
* no handler matched. That is how a push gets an *answer*: a module
* that asks the program a question (an HTTP request needing a response)
* gets one back, without the program having to emit anything.
*
* Both pointers are the host's and are valid only for the duration of
* the call: read what you need and copy it out. Optional, and additive,
* for the same reason as the two above — a module that only announces
* things does not export it, and the ABI version does not move.
*
* Correlation is the module's own business. Nothing identifies *which*
* push is being answered beyond the particle handed back, so a module
* with more than one outstanding push has to carry its own key in the
* particle it pushed and read it back here.
*
* Why `emit` is a function *pointer* the host supplies, rather than a
* `code_emit_inbound` a module could call directly: a `.so` carries its own
* copy of this runtime (see below), so a direct call would push onto the
* module's own queue, which the host never reads. The pointer is the host's.
* A queued particle is deep-copied into the host's heap by that function,
* the same boundary rule a dispatch result follows, so the module may
* release its own copy the moment `emit` returns.
*
* Queued particles are dispatched to the *program's* handlers — a `.code`
* `ClassName { ... } => { ... }` — not back into the module. That is what
* makes an event loop expressible: the module supplies events, the program
* decides what they mean.
*
* Why a module needs its own `code_release`, not the host's: values never
* cross this boundary by shared ownership. Whatever a module allocates for
* its result is deep-copied into the host's own heap immediately after the
* call (see `code_native_dispatch` in runtime.c) using the host's own
* refcount bookkeeping, and then the module's copy of `code_release` frees
* what the module itself allocated. Each side's allocator only ever frees
* blocks it itself allocated — that's what keeps `CODE_CHECK_LEAKS`
* meaningful on both sides of a dlopen boundary, where two copies of this
* runtime have entirely separate static state.
*
* Loading is dlopen/dlsym-based, in both `code run` and a `code build`
* binary — never `cc`-time static linking. Every module exports the same
* three symbol names, and that's fine: dlsym resolves within one module's
* own handle, so two linked modules never collide, no matter how many
* handlers each defines internally.
*
* A fatal error inside a module (its own `code_runtime_error`, a segfault,
* anything that isn't a normal return) takes down the *host* process —
* `code run` included, not just a `code build` binary. Unlike `core`
* (`code_core_dispatch`), which the interpreter has its own independent
* Rust implementation of specifically so a bad handler call there is a
* clean `Result::Err`, a native module is real native code the interpreter
* runs in-process via dlopen — there is no reimplementation to fall back
* on, and no sandboxing here (out of scope for Phase 1). This is the same
* tradeoff any native-extension mechanism makes (a Python C extension can
* just as easily crash the interpreter that loaded it); it is not
* considered a bug, and `docs/todo/native-module-linking.md`'s fixture
* suite works around it by never provoking a module's fatal path in-process.
*/
typedef enum CodeTag;
typedef struct CodeValue CodeValue;
/* What `code_module_set_inbound` hands a module: the host's own pusher.
* `queue` is opaque to the module — it only ever passes it straight back. */
typedef void ;
/* `code_module_inbound_reply` — see the numbered list above. Declared as a
* type here because the host stores one per module; a module writes the
* function itself. */
typedef void ;
/* How many pushed particles a module may have outstanding before the oldest
* starts being dropped. Bounded on purpose: a module that pushes faster than
* the program drains must cost bounded memory, not unbounded. */
/* A module's exported variables (constants) — what `code_module_vars`
* returns. `names` and `values` are parallel arrays of `count` entries:
* `values[i]` is the value exported under `names[i]`. `values` is strided at
* `CODE_VALUE_SLOT_SIZE` bytes (address it through a `slot_at`-style helper,
* never `[]`), exactly like a `CodeValue`'s own `items` buffer.
*
* The module owns all of this memory — the names, the value buffer, and
* everything the values point into — and it must stay valid for the module's
* whole lifetime (the host reads it once at `link` time and deep-copies each
* value out, the same way it treats a `code_module_dispatch` result). The
* host never frees any of it; it only ever calls the module's own
* `code_release` on a *copy* it made. */
typedef struct CodeVarList CodeVarList;
/* ---- `.a` static modules — a different, simpler contract -----------------
*
* Everything above is the `.so` contract: a module `dlopen`s in as a
* self-contained unit with its own copy of the runtime, so values crossing
* the boundary need a deep copy and the module needs its own `code_release`.
*
* A `.a` module is linked straight into the same binary as the host by `cc`
* (`code build` only — there is no `dlopen` for a static archive, so `code
* run` refuses to link one at all). That means there is only ever one copy
* of the runtime in the final program — the host's — so a `.a` module does
* NOT bring its own copy of it. It builds its results by calling the host's
* own constructors directly, declared `extern` below, and needs no
* `code_release` of its own: nothing it builds is ever a separate
* allocation to free.
*
* The only names a `.a` module must still choose carefully are the ones it
* defines itself — `code_module_dispatch` and `code_module_abi_version`
* (required), `code_module_vars` (optional) — because unlike `.so` handles,
* every `.a` linked into one program shares a single flat symbol table.
* Convention: pick a prefix unique among every `.a` your program will ever
* link alongside, and name them `<prefix>_code_module_dispatch`,
* `<prefix>_code_module_abi_version`, `<prefix>_code_module_vars`. `code
* build` finds them by running `nm` on the archive at link time (see
* `loader.rs`), so `link "libfoo.a" as m` needs no syntax to name the
* prefix — it just has to be unique. */
void ;
/* Borrows `s` — the value keeps the pointer rather than the bytes, so `s`
* must outlive it. Correct for a string literal, wrong for anything built at
* runtime; use `code_str_owned` for those. */
void ;
/* Copies `s`'s bytes into a heap-owned string. What a handler returning a
* message built into a stack buffer must use — handing that buffer to
* `code_str` leaves a dangling read the moment the handler returns. */
void ;
void ;
void ;
void ;
void ;
void ;
void ;
void ;
int ;
int ;
/* Builds `Exception { source, message, innerException }` — how a module
* reports that it could not do the work. `inner` may be NULL. A module may
* never end the application, so this replaces `code_runtime_error` for
* everything a module can encounter; see docs/todo/errors-as-particles.md.
* `source` and `message` are both copied. */
void ;
_Noreturn void ;
/* ---- The invariant this list holds -------------------------------------
*
* **Nothing declared above can fail.** No function here sets `code_failed`,
* runtime.c's failure flag, and that is a property of the list rather than a
* coincidence: the flag is read only by the *host's* generated code, and a
* `.so` module carries its own copy of this runtime, so a failure raised
* inside a module would set the module's flag and go nowhere. Silently. A
* fallible entry in this header is therefore not a sharp edge to document,
* it is a hole. `tests/module_abi_cannot_fail.rs` enforces it.
*
* Four functions were removed for exactly that reason, none of them renamed
* and none replaced:
*
* - `code_bool_value`, `code_assert` (2026-08-28, phase 3) — the compiler's
* own: one checks an `and`/`or` operand, the other is the `assert`
* statement. Neither was ever called by a module.
* - `code_field`, `code_index` (2026-08-28) — these looked module-facing,
* and the language needs them to fail: `"abc".length` is an error, which
* the README states as a rule. A module needs the opposite, a total
* accessor, and one function cannot be both. Modules read fields by
* walking `keys`/`items`, which are right there in `CodeValue`;
* `code-native` does exactly that in `find_field`/`field`/`index`, in
* plain Rust with no call back into this ABI.
*
* What a module does when it cannot do its work is return
* `code_make_exception`. It may never end the application. */
/* Addresses slot `index` of a `CODE_VALUE_SLOT_SIZE`-strided buffer — the
* same convention `items`/`values` buffers use throughout this header.
* `static inline` rather than declared `extern`: it's pure pointer
* arithmetic with no state, so a header-only copy in each translation unit
* that includes this file (host and every `.a` module alike) is simpler
* than giving it one more externally-linked name to keep unique. */
static inline CodeValue *