6bc0b501cb
First of two iterations delivering spec 0060's mir.3 row (plan docs/plans/0117-mir.3a-consume-count-relocation.md). The per-binder consume_count — the only thing codegen read from its second infer_module_with_cross run — now lives on a new binder-keyed MirDef.consume: BTreeMap<String,u32>, filled once by lower_to_mir on the post-mono body. Codegen builds its existing (def,binder)->consume_count lookup from those maps and the three scope-close drop sites (own-param, let-binder, match-arm) read it; the codegen uniqueness run, its UniquenessTable field, and the import are deleted. mir.3 is split into two iterations (recorded in spec 0060): the named deletion depends ONLY on consume_count. All three drop sites read consume_count from self.uniqueness, while the modes they also gate on (param_modes for the own-param dec, scrutinee_is_owned for the match dec) come from the type, not the uniqueness pass (UniquenessInfo carries no mode). So mir.3a relocates consume_count and ships the deletion; mir.3b (next) fills MArg.mode/MTerm::Let.mode and switches emit_call's anon-temp gate onto MArg.mode. The split halves the change at the codebase's highest-risk surface (RC/drop correctness, the raw-buf leak saga) and makes each half independently verifiable against the live=0 leak pins. Design: consume_count lands on MirDef (binder-keyed), NOT on MArg. The drop sites key by (def, binder_name) — a per-binder aggregate — while the spec-sketched MArg.consume_count is per-arg-position and never reaches them; the per-def map matches the UniquenessTable's own keying and all three binder kinds (param/let/pattern) uniformly. MArg.consume_count stays a mir.1 default. Source = run check's infer_module_with_cross inside lower_to_mir post-mono (the synth_pure single-engine-re-walked pattern); retaining a check-time result is blocked (check's uniqueness is pre-mono and runs on-demand-from-codegen, never in check_workspace, so it would miss the mono specialisations). cross_module_types (= codegen's module_def_ail_types) is rebuilt in elaborate_workspace from the post-mono workspace. Safety property held: this is a PURE RELOCATION of an identical computation — infer_module_with_cross ran on the post-mono module in codegen and now runs on the same post-mono module in lower_to_mir, so drop placement is byte-identical. cross_module_types matches module_def_ail_types exactly (both insert f.name->f.ty over post-mono Def::Fn), confirmed by the leak pins staying green. Verification (orchestrator, post-implement inspect): diff matches the plan; codegen grep-clean for infer_module_with_cross + UniquenessTable (now ailang-check-internal); cargo build --workspace clean (no errors, no unused warnings — module_def_ail_types survives for emit_call's anon-temp param_modes gate); cargo test --workspace 701 passed / 0 failed / 3 ignored (+1 = the new producer pin mirdef_consume_is_populated_for_let_binder; no #[ignore] added). The live=0 drop-correctness witnesses all green: alloc_rc_loop_valued_rawbuf_binder_drops_at_scope_close (#43/#47), alloc_rc_heap_loop_binder_replaced_by_recur_does_not_leak, alloc_rc_print_int_does_not_leak_show_result_str. #49 stays #[ignore] (mir.4); #51/#53 build guards green.
201 lines
7.2 KiB
Rust
201 lines
7.2 KiB
Rust
//! Typed mid-level IR — the single artefact that crosses the
|
|
//! `check` → `codegen` boundary. Produced by
|
|
//! `ailang_check::lower_to_mir` from a monomorphised, typechecked
|
|
//! `Workspace`; consumed by `ailang_codegen`. Every node carries the
|
|
//! facts codegen used to re-derive: its type (`ty`), the resolved
|
|
//! callee (`Callee`, filled mir.2), parameter modes / consume counts
|
|
//! (`Mode` / `consume_count`, filled mir.3), and string representation
|
|
//! (`StrRep`, filled mir.4). MIR is never authored, never hashed,
|
|
//! never round-tripped — it is a compiler-internal derived form.
|
|
|
|
use ailang_core::ast::{Literal, Module, Pattern, Type};
|
|
use std::collections::BTreeMap;
|
|
|
|
/// A whole monomorphised program, lowered to MIR.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MirWorkspace {
|
|
pub entry: String,
|
|
pub modules: BTreeMap<String, MirModule>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MirModule {
|
|
pub name: String,
|
|
/// The post-mono AST module. codegen reads structural data
|
|
/// (Def::Type / Def::Const / ctor layouts / intrinsic-fn markers /
|
|
/// imports) from here; the typed fn bodies come from `defs`. This
|
|
/// keeps `MirWorkspace` the single artefact codegen consumes —
|
|
/// structural reads were never a re-derivation, so carrying the AST
|
|
/// alongside the typed bodies does not reintroduce one.
|
|
pub ast: Module,
|
|
pub defs: Vec<MirDef>,
|
|
/// Typed bodies of the module's non-literal `Def::Const`s. A const
|
|
/// is inlined at every `Var` reference site (codegen reads the
|
|
/// body from here, keyed by name); literal consts emit a global
|
|
/// instead and are not carried. Same role as `defs` for fns — the
|
|
/// body is the checker-typed `MTerm` so codegen re-derives no
|
|
/// type to lower it.
|
|
pub consts: Vec<MirConst>,
|
|
}
|
|
|
|
/// One lowered non-literal const. `body` is the typed `MTerm` codegen
|
|
/// inlines at each reference site.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MirConst {
|
|
pub name: String,
|
|
pub ty: Type,
|
|
pub body: MTerm,
|
|
}
|
|
|
|
/// One lowered top-level fn. `sig` is the declared signature; `body`
|
|
/// is the lowered fn body with `ty` on every node.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MirDef {
|
|
pub name: String,
|
|
pub sig: Type,
|
|
pub params: Vec<String>,
|
|
pub body: MTerm,
|
|
/// Per-binder consume count for this def's body, keyed by binder
|
|
/// name (let-binders, params, pattern-binders). `0` means the
|
|
/// binder is exclusively borrowed/unused in its scope and codegen
|
|
/// must emit the scope-close `dec` under `--alloc=rc`. Filled by
|
|
/// `lower_to_mir` from `ailang_check::uniqueness::infer_module_with_cross`
|
|
/// (mir.3a) — the single uniqueness engine, run once on the
|
|
/// post-mono body. Codegen reads it instead of re-running the pass.
|
|
pub consume: BTreeMap<String, u32>,
|
|
}
|
|
|
|
/// One MIR node — structural counterpart of `ast::Term`, plus the
|
|
/// `Str` split. `ty` is the checker-proved type at this node.
|
|
#[derive(Debug, Clone)]
|
|
pub enum MTerm {
|
|
Lit { lit: Literal, ty: Type },
|
|
/// String literal — the `Str`-representation carrier. `rep` is
|
|
/// `Static` for every literal at mir.1; mir.4 flips loop-carried
|
|
/// seeds to `Heap`. Split out of `Lit` so the rep annotation has a
|
|
/// home (spec §"Str split").
|
|
Str { lit: String, rep: StrRep },
|
|
Var { name: String, ty: Type },
|
|
/// `callee` is `Indirect(<lowered callee>)` at mir.1; mir.2
|
|
/// resolves static targets to `Callee::Static`.
|
|
App { callee: Callee, args: Vec<MArg>, tail: bool, ty: Type },
|
|
Let { name: String, mode: Mode, init: Box<MTerm>, body: Box<MTerm>, ty: Type },
|
|
LetRec {
|
|
name: String,
|
|
sig: Type,
|
|
params: Vec<String>,
|
|
body: Box<MTerm>,
|
|
in_term: Box<MTerm>,
|
|
ty: Type,
|
|
},
|
|
If { cond: Box<MTerm>, then: Box<MTerm>, else_: Box<MTerm>, ty: Type },
|
|
Do { op: String, args: Vec<MArg>, tail: bool, ty: Type },
|
|
Ctor { type_name: String, ctor: String, args: Vec<MArg>, ty: Type },
|
|
Match { scrutinee: Box<MTerm>, arms: Vec<MArm>, ty: Type },
|
|
Lam {
|
|
params: Vec<String>,
|
|
param_tys: Vec<Type>,
|
|
ret_ty: Type,
|
|
effects: Vec<String>,
|
|
body: Box<MTerm>,
|
|
ty: Type,
|
|
},
|
|
Seq { lhs: Box<MTerm>, rhs: Box<MTerm>, ty: Type },
|
|
Clone { value: Box<MTerm>, ty: Type },
|
|
ReuseAs { source: Box<MTerm>, body: Box<MTerm>, ty: Type },
|
|
Loop { binders: Vec<MLoopBinder>, body: Box<MTerm>, ty: Type },
|
|
Recur { args: Vec<MArg>, ty: Type },
|
|
/// `elem` is `None` at mir.1; mir.5 carries the element type.
|
|
New { type_name: String, elem: Option<Type>, args: Vec<MNewArg>, ty: Type },
|
|
Intrinsic { ty: Type },
|
|
}
|
|
|
|
/// A match arm. Patterns stay structural (`ast::Pattern`) — they carry
|
|
/// no type annotation the boundary re-derives.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MArm {
|
|
pub pat: Pattern,
|
|
pub body: MTerm,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MLoopBinder {
|
|
pub name: String,
|
|
pub ty: Type,
|
|
pub init: MTerm,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum MNewArg {
|
|
Type(Type),
|
|
Value(MTerm),
|
|
}
|
|
|
|
/// A call/ctor/recur argument with its mode and consume count. Both
|
|
/// fields are mir.1 defaults (`Owned` / `1`); mir.3 fills them.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MArg {
|
|
pub term: MTerm,
|
|
pub mode: Mode,
|
|
pub consume_count: u32,
|
|
}
|
|
|
|
/// A resolved App callee. `Static`/`Builtin` are filled by mir.2's
|
|
/// `lower_to_mir::classify_callee`, which mirrors check's `synth`
|
|
/// `Term::Var` ladder; `Indirect` is a genuinely dynamic callee
|
|
/// (fn-typed local/param, closure value, or a name shadowed by a
|
|
/// local). `sig` is the callee's fn-type — the lossless replacement
|
|
/// for the sub-term's `ty()` that codegen's drop path reads for the
|
|
/// callee `ret_mode`.
|
|
#[derive(Debug, Clone)]
|
|
pub enum Callee {
|
|
/// A statically-resolved user/prelude fn: codegen routes it to
|
|
/// `emit_call(module, fn_name, …)` with `module` pre-resolved
|
|
/// (replacing codegen's `type_home_module` ladder).
|
|
Static { module: String, fn_name: String, sig: Type },
|
|
/// An operator / intrinsic builtin (`+`, `not`, `str_concat`, …):
|
|
/// codegen lowers it inline by `name` (opcode selection), never
|
|
/// via `emit_call`.
|
|
Builtin { name: String, sig: Type },
|
|
/// A dynamic callee — lower the boxed term to a fn-pointer.
|
|
Indirect(Box<MTerm>),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum StrRep {
|
|
Heap,
|
|
Static,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Mode {
|
|
Owned,
|
|
Borrow,
|
|
}
|
|
|
|
impl MTerm {
|
|
/// The checker-proved type at this node. `Str` is always `Str`.
|
|
pub fn ty(&self) -> Type {
|
|
match self {
|
|
MTerm::Lit { ty, .. }
|
|
| MTerm::Var { ty, .. }
|
|
| MTerm::App { ty, .. }
|
|
| MTerm::Let { ty, .. }
|
|
| MTerm::LetRec { ty, .. }
|
|
| MTerm::If { ty, .. }
|
|
| MTerm::Do { ty, .. }
|
|
| MTerm::Ctor { ty, .. }
|
|
| MTerm::Match { ty, .. }
|
|
| MTerm::Lam { ty, .. }
|
|
| MTerm::Seq { ty, .. }
|
|
| MTerm::Clone { ty, .. }
|
|
| MTerm::ReuseAs { ty, .. }
|
|
| MTerm::Loop { ty, .. }
|
|
| MTerm::Recur { ty, .. }
|
|
| MTerm::New { ty, .. }
|
|
| MTerm::Intrinsic { ty } => ty.clone(),
|
|
MTerm::Str { .. } => Type::str_(),
|
|
}
|
|
}
|
|
}
|