Compiler design / 17. Backend lowering
Lowerers translate validated HIR into target code without reopening source or rewriting language meaning.
One HIR operation may become a JavaScript expression in one partition and a Wasm instruction sequence in another. The meaning stays Moth. The spelling becomes target-native. Same source meaning, different host climates.
Lowerers translate validated inputs. They are not second language frontends.
A lowerer receives explicit records:
ValidatedMemoryPlan for this target/profile physical variant, including its already-settled planned affine-responsibility transitions, retained-edge obligation transitions and discharge operationsA lowerer does not:
If a lowerer needs a fact, an earlier owner should have recorded it.
The ValidatedMemoryPlan is the memory half of that rule. For this physical variant, it arrives with its physical decisions already settled: selected memory layout per allocation family, region and declared-region placement, planned affine-responsibility transitions, retained-edge obligation transitions and discharge operations, cleanup and destruction plans, and REC representation decisions. The lowerer encodes those decisions. It does not infer them from borrow facts, recompute a count delta or choose a cleanup site. Borrow facts and validated lifetime topology remain validation context and assertion material only.
Because the plan is built after target partition and target validation, it is scoped to one physical variant. The same source function can be lowered GC-native in a variant targeting a host collector and collector-free with REC-capable handles in a variant targeting full memory control. Those variants share the same validated topology and the same source semantics; only the physical plan differs.
For the walkthrough, target-labelled start, greet and template work arrive ready. The JavaScript path might own page orchestration. Neutral string work may sit in either partition depending on assignment.
Diagram placeholder: Selected target sends one HIR input to two target paths
Caption: exact frontend boundary simplified. Question: what stays shared?
The JS backend speaks host values and functions. It leans on the host GC for reachability. Runtime helpers implement language operations the surface syntax made look primitive: checked numerics, certain casts, template runtime pieces, error plumbing.
greet in JavaScript form is ordinary host code that preserves Moth contracts. A more permissive JS primitive must not silently weaken checked arithmetic, finite-Float rules or error propagation.
Accepted Wasm design prefers a structured path:
Beginner picture:
Old dispatcher-loop LIR stories, moth_start bootstrap myths, i64 Int bridges as the final story and per-module memory as the default are removed final-design paths. Teach them only as historical "do not rebuild this" notes, not as the architecture.
Diagram placeholder: A derived backend view feeds structured blocks and ABI operations
Caption: accepted Wasm design, simplified records. Question: where does target structure enter?
Accepted HTML mixed-target design wants page-local shared Wasm runtime and memory rather than a scatter of isolated memories per module as the end state. Page-local shared Wasm runtime/memory applies to Moth-linked variants only. Imported WIT components use component-private memory and value conversion.
JavaScript companion facades and wrappers call Wasm exports through explicit handles and layout rules. The bridge is data, not guesswork.
Diagram placeholder: JavaScript companion calls a Wasm export through explicit handles and layout rules
Caption: teaching boundary. Question: how do targets communicate?
Binding-backed calls lower through demand-driven glue. The binder's semantic interface already existed upstream. The lowerer maps stable external ids to imports, helpers or native operations.
At a binding-backed boundary, the closed external-boundary classification and the ValidatedMemoryPlan supply the ownership and obligation behaviour for this physical variant. The lowerer encodes that settled result rather than rereading borrow facts to decide whether a call transfers or discharges responsibility. Borrow and last-use facts and validated lifetime topology remain upstream inputs and validation context, not a source for new physical decisions. Where the plan selected a GC-native representation, planned ownership-specific operations may be absent in the target encoding; where it selected affine, inferred-region, declared-region or REC handling, the lowerer emits exactly what the plan says. Every backend still requires the same validation, and optimisation must preserve observable behaviour and the same accepted program set.
CompilerError laneUser-facing target failures preferably die in validation, not mid-emission.
Alternative: give each backend a source interpreter or separate frontend.
Choice: one validated semantic HIR plus target-owned lowering.
Benefit: source behaviour remains shared across JavaScript and Wasm.
Cost: each backend needs a faithful ABI and runtime implementation.
html_project_backend_wasm_final_implementation_plan.md.You leave with JavaScript, Wasm, wrappers, runtime modules and asset records as data. Next: how the HTML builder assembles and owns those outputs on disk, ending at <p>Hello, Priya!</p>.