mir.5 is the typed-MIR milestone's closing iteration. Its CODE half had
already converged before the iteration began, so mir.5 ships no code —
it ratifies into the design/ ledger the boundary the code already holds.
Verified at iteration entry (empirically, not from the spec sketch):
- all four named re-derivers grep-clean in codegen: synth_with_extras,
synth_arg_type, type_home_module, the second infer_module_with_cross;
- lower_workspace takes &MirWorkspace (codegen consumes MIR);
- MTerm::New is unreachable!() — raw-buf.4 desugars Term::New to
(app T.new …) before codegen, so there is no element-type
re-derivation left to relocate;
- #51 / #53 (the element-type / new-T codegen crashes) are closed;
their residue was fixed by ee4107c / 420f75f plus the New-desugar,
not by a separate raw-buf patch track.
Ledger work (the mir.5 deliverable):
- NEW design/contracts/0018-check-codegen-boundary.md: the invariant
"codegen re-derives nothing; MIR is total over what check proved;
a codegen arm that recomputes a fact instead of reading MIR is
drift." Ratifier: lower_to_mir_ty.rs::callee_classification_builtin_and_static.
- 0013-typeclasses invariant 2 retracted: codegen no longer re-resolves
cross-module names via an import_map fallback; lower_to_mir::classify_callee
resolves the reference once into Callee::Static and codegen consumes it.
- 0003-pipeline.md: the "lower to MIR" line names the real stage
(elaborate_workspace → MirWorkspace → lower_workspace) and a new
paragraph states the boundary, cross-referencing 0018.
- INDEX.md: boundary contract row added; qualified-xref re-pointed at
lower_to_mir + 0018.
- codegen_import_map_fallback_pin.rs doc-comment made honest — it pins
the post-mono AST precondition classify_callee relies on, not a
codegen-side resolution that no longer exists. Assertions unchanged;
the test stays green.
- spec 0060 gains a mir.5 refinement note recording the early code
convergence.
Acceptance criteria 1-7 of docs/specs/0060-typed-mir.md are all met.
Full workspace suite green (exit 0, 0 failed, 2 ignored); 708 passed
carried from mir.4 (no test added or removed).
Not done here (deliberately): the milestone #7 (raw-buf) subsumption
note is an external Gitea tracker write; the /boss auto-mode classifier
declined it as an unauthorised external write and it is surfaced to the
user rather than worked around. The end-to-end milestone fieldtest
remains the deliberate manual close-gate before the tracker milestone
is marked done.
refs #51 #53
3.2 KiB
Check→codegen boundary
Check→codegen boundary
check produces a single elaborated, typed artefact — a
MirWorkspace — that is total
over every fact codegen needs. Codegen consumes that artefact and
re-derives nothing: it performs no type synthesis, no callee
resolution, no uniqueness inference. Every decision codegen makes reads
a field MIR already carries; there is no recompute-or-guess fallback
path.
The producer is
ailang-check::lower_to_mir,
driven by elaborate_workspace; the consumer is
ailang-codegen::lower_workspace,
whose public entry points take &MirWorkspace. The build path is
Workspace → elaborate_workspace → MirWorkspace → lower_workspace → LLVM IR
lower_to_mir re-enters check's own canonical synth on the post-mono
AST, so the MIR each node carries is the same judgement check proved,
not a parallel re-derivation. This is the source of the totality
guarantee: a fact in MIR is a fact check ratified.
What MIR carries (the totality)
MIR is structurally complete over all 17 Term variants
(ailang-core/src/ast.rs), and
each node carries the annotation classes codegen formerly re-derived:
tyon every node — the synthesised type. Codegen reads layout, drop shape, and mangling off it.Calleeon everyApp—Static { module, fn_name, sig }/Builtin { name, sig }/Indirect. The static target is resolved inlower_to_mir; codegen reads the identity, it does not walk a callee ladder.Mode+consume— per-arg modes onMArgand the per-binderconsume_countonMirDef.consume. Drop placement reads these; the modes/linearity contract is memory-model.StrRepon everyStrliteral —Static(constexpr GEP into rodata) /Heap(promoted viastr_clone). The producer decides the representation; codegen emits what the rep says (see str-abi).
What codegen no longer holds
The re-derivers that made codegen a second, weaker type pass are gone
and stay grep-clean: synth_with_extras, synth_arg_type,
type_home_module, and codegen's own infer_module_with_cross run.
Cross-module name resolution in particular is resolved once, in
lower_to_mir, into Callee::Static — codegen does not re-resolve it
(the retraction recorded in typeclasses, §
"Cross-module references in synthesised bodies", invariant 2).
Failure mode this contract names
A codegen arm that recomputes a fact instead of reading it off MIR is drift, even when it computes the right answer — it reintroduces the weaker parallel pass this boundary exists to delete, and the next post-mono inconsistency it silently tolerates is a latent miscompile. The architect agent walks codegen for type synthesis, callee-ladder walks, and uniqueness inference during drift review; any reappearance is a regression against this contract, not a local style choice.
The pipeline view of where this stage sits is pipeline.