Shen source is compiled through a fully in-repo pipeline. shen->kl (our own full-arity Shen→KLambda compiler) feeds kmacros, normalize-term (KLambda expansion, A-normal form, debruijn indices), zinc-c (KLambda → ZINC bytecode), and compile-zinc (→ canonical s-expressions), landing in nat->csexp and finally the C VM.
Shen source → kmacros → normalize-term → debruijn → zinc-c → compile-zinc → nat->csexp → C VM
There are two separate global tables. The C VM native global_table[] (populated by parse_bundle from the bundle and by init_globals) is what raw C bytecode [global X] reaches. The metacircular interp's Shen global-table (an assoc list in interp.shen) is how the interp resolves [global G] via lookup-global.
A runtime-loaded closure (shen.foo) lives in the interp's Shen global-table (namespace 2) — NOT its own C global_table[] entry. To call it, drive it through the metacircular interp: eval-kl, toplevel-interp, or a bundled closure that resolves names via lookup-global.
Call sites split into two kinds. Static call sites — code produced by the compiler, type-safe by construction — need no runtime type check and no safe wrapper; zinc-c special-cases primitive? heads to emit [prim F], a direct primitive dispatch that bypasses the global table. Dynamic call sites — boundaries, higher-order use, untyped input — route through the Shen safe wrappers (safe.X) so a type error becomes a catchable simple-error.
The C primitives have no runtime type guards. Type validation is owned entirely by the Shen safe-wrapper layer. This is safe only for a type-safe bundle — the canonical globals.csexp is the reduced self-contained interpreter, which never passes bad types.
A 2 MB nursery (pages marked space==3) is the allocation fast lane; the full-copy collect() is the (rare) old-gen collector and compacts old gen. Typed headers drive a tag-dispatch scavenger; roots are precise-only via the shadow stack + typed walkers — no conservative C-stack scan. A write barrier at address-> vector writes keeps old-gen→nursery references correct.
The same collector runs under WebAssembly in the playground — the wasm build swaps the 4 GB mmap reservation for an aligned_alloc heap via a #ifdef __wasm__ shim, and a 5000-eval GC stress probe stays clean.