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
/* 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.
*
* 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;
/* 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 ;
void ;
void ;
void ;
void ;
void ;
void ;
void ;
void ;
void ;
void ;
int ;
int ;
void ;
_Noreturn void ;
/* 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 *