The runtime beneath both execution paths
Assembly can add two integers by itself. It cannot grow a Luce list, keep a class alive, copy a worker graph, or report a source-aware trap. Those jobs live in the native runtime shared by compiled programs and the test interpreter.
If an operation depends only on scalar registers, LLVM can often inline it. If it needs Luce’s heap, object table, host capabilities, or error state, generated assembly calls the Zig runtime.
Where the runtime sits
luce_rt_* C exportsIf code generation implemented its own list rules, the test interpreter could quietly become a different language. If only the interpreter had a rule, tests could pass for behavior users never run. Both paths therefore call the same runtime operations.
The runtime modules
heap.zig + value.zig24-byte values, object rows, generations, strong counts, weak handles, allocation, and trap state.
containers.zigLists, maps, arrays, builders, element ownership, bounds, and immutable roots.
text.zigUTF-8 and bytes storage, inline text, validation, conversions, and scalar operations.
operators.zigChecked arithmetic, comparison, numeric conversion, parsing, formatting, and math primitives.
Files, sockets, and graphicsRuntime-owned resource handles installed from optional host service channels.
workers.zigPrivate runtimes, graph transfer, thread nursery, effect lock, task result, and join.
trace.zigTrap state, bounded call frames, stable locations, and one completed report.
exports.zigThe C ABI that generated objects call and effect metadata verifies.
The 24-byte value passed around the runtime
Runtime operations exchange a C-layout 24-byte Value. A tag selects the view: scalar bits, inline or outside text, an object handle, absence, a function pair, or another defined representation. Zig callers use a tagged view(); generated code uses constants whose layout is asserted against the Zig struct.
Handles make moving storage safe
A program does not hold raw pointers to collection buffers or class storage. It holds an object-table handle with an index and generation. A list may reallocate its buffer without invalidating every alias; a stale weak handle cannot match a reused row; the runtime can count and inspect all live objects at the end of a run.
Failure at the runtime boundary
A fallible C export returns a status and writes results through out-pointers. A trap is recorded in the runtime once, then propagated through generated frames. Allocation exhaustion is a separate run status rather than a catchable language trap. This keeps language failure, program violations, and machine exhaustion distinguishable.
The library stands below the compiler
No runtime file imports a compiler stage. Shared enum vocabulary—trap codes, error codes, operators, service failures—lives under support/, below both. A library linked into every artifact must be understandable and buildable without pulling the front end into the program.