This spec clarifies how top-level declarations — fn, const, type, effect, trait, impl — are threaded through the codegen environment, with particular attention to the two declarations that emit runtime artefacts: fn and const.
The codegen environment is a ctx record threaded through gen_decl for each top-level declaration in source order. The two fields that hold name → index bindings are:
| Field | Purpose |
|---|---|
|
Maps both function names AND constant names to a signed integer. Positive indices point into |
|
The WASM globals table — one entry per |
The signed-int sentinel trick is internal: every ExprVar lookup in fn bodies must consult func_indices and dispatch on the sign to emit either a call or a global.get.
Mechanism in lib/codegen.ml:
-
The function body is generated against a fresh context derived from
ctx(gen_fn_body), producing a list of WASM instructions and any lambda-funcs created during generation. -
A new entry is appended to
ctx.funcswith a fresh indexfunc_idx = List.length ctx.funcs. -
func_indicesgains(fd.fd_name.name, func_idx). -
If
Publicor matches a game-hook export name (init,update,draw, …), anExportFuncexport is added.
ExprVar lookup against a TopFn: List.assoc name func_indices returns a non-negative func_idx; the call site emits Call func_idx.
Mechanism in lib/codegen.ml (see the | TopConst tc → case in gen_decl):
-
The initialiser expression is compiled against the current ctx via
gen_expr. The result must be a constant expression — a singleI32Const(or analogue). Non-constant initialisers are not yet supported and will fail at WASM validation. -
A new entry is appended to
ctx.globals:{ g_type = I32; g_mutable = false; g_init = init_code } -
The const’s name is registered in
func_indiceswith a negative index:(tc.tc_name.name, -(global_idx + 1)). This sentinel encoding lets ExprVar lookup dispatch on the sign without a separateconst_indicestable.
ExprVar lookup against a TopConst: List.assoc name func_indices returns a negative index i; the call site decodes global_idx = -i - 1 and emits GlobalGet global_idx.
Top-level declarations are processed in source order. A fn body that references a const defined later in the file currently fails at codegen with Codegen.UnboundVariable, because func_indices only contains entries for declarations already processed. This forward-reference limitation is a known constraint.
Recommended source ordering:
-
Top-level types (
type) -
Top-level effects (
effect) -
Top-level constants (
const) -
Top-level traits / impls
-
Top-level functions
This ordering matches how the typechecker resolves dependencies and avoids forward-reference failures in codegen.
Cross-module imports (use OtherModule::{thing}) are handled by two passes:
-
WASM and WasmGC targets —
Codegen.gen_importswalksprog.prog_imports, loads each referenced module viaModule_loader, and emits one(import "<modpath>" "<fn>" (func …))entry per imported function. The imported name lands infunc_indiceswith a positive index pointing into the imports section. -
All other codegens (Rust, JS, Lua, OCaml, Julia, C, WGSL, Faust, ONNX, Bash, Nickel, ReScript, LLVM, Verilog, Gleam, CUDA, Metal, OpenCL, MLIR, Why3, Lean, SPIR-V) —
Module_loader.flatten_importsprepends imported publicTopFndeclarations into the importer’sprog_decls(deduplicating against local fn names). The non-WASM codegens then see the imports as if they were locally-defined and emit them inline.
Imported TopConst`s are not currently threaded by either path. This is a known gap; only `TopFn flows across module boundaries today.
Each non-WASM codegen has its own gen_decl (or equivalent) and may or may not implement TopConst. The expected behaviour, target-by-target:
| Target | TopConst handling |
|---|---|
|
Emits |
|
Emits |
|
Emits |
Other backends |
If unimplemented, |
When auditing or adding a backend, the env-population rule is the same: register the name and emit a target-appropriate definition before any fn body that might reference it.
-
#73 —
Codegen.UnboundVariablefor top-levelconstbindings: typechecker accepts the reference, codegen ExprVar lookup fails. Implementation status varies by target; verify against the currentgen_declfor the target in question.