a378dad0aa
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
70 lines
3.2 KiB
Markdown
70 lines
3.2 KiB
Markdown
# Check→codegen boundary
|
|
|
|
## Check→codegen boundary
|
|
|
|
`check` produces a single elaborated, typed artefact — a
|
|
[`MirWorkspace`](../../crates/ailang-mir/src/lib.rs) — 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`](../../crates/ailang-check/src/lower_to_mir.rs),
|
|
driven by `elaborate_workspace`; the consumer is
|
|
[`ailang-codegen::lower_workspace`](../../crates/ailang-codegen/src/lib.rs),
|
|
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`](../../crates/ailang-core/src/ast.rs)), and
|
|
each node carries the annotation classes codegen formerly re-derived:
|
|
|
|
- **`ty`** on every node — the synthesised type. Codegen reads layout,
|
|
drop shape, and mangling off it.
|
|
- **`Callee`** on every `App` — `Static { module, fn_name, sig }` /
|
|
`Builtin { name, sig }` / `Indirect`. The static target is resolved
|
|
in `lower_to_mir`; codegen reads the identity, it does not walk a
|
|
callee ladder.
|
|
- **`Mode` + `consume`** — per-arg modes on `MArg` and the per-binder
|
|
`consume_count` on `MirDef.consume`. Drop placement reads these; the
|
|
modes/linearity contract is [memory-model](0008-memory-model.md).
|
|
- **`StrRep`** on every `Str` literal — `Static` (constexpr GEP into
|
|
rodata) / `Heap` (promoted via `str_clone`). The producer decides the
|
|
representation; codegen emits what the rep says (see
|
|
[str-abi](0011-str-abi.md)).
|
|
|
|
### 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](0013-typeclasses.md), §
|
|
"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](../models/0003-pipeline.md).
|