source: scrub iter-code / Decision-N residue from inline comments
Follow-up to bcd4181: the remaining ~530 inline `//` and `///`
comments still carrying opaque shorthand are now reformulated to
their content phrases. The only surviving `iter-<code>` reference
in source is the literal filename
`docs/journals/2026-05-13-iter-mq.3.md` (a real journal file).
Sweep covered:
- `// Iter X.Y: <text>` prefixes (Iter 13a / 14a / 14e / 15g-aux /
16b.x / 16d / 16e / 18b / 18c.x / 18d.x / 18e / 18g.x / 19a /
19a.1 / 19b / 20a / 20f / 22-floats.x / 22b.x / 22c / 23.x /
24.1 / cli-diag-human / hs.x / str-concat / etc.) — fully
removed; the descriptive text that followed each prefix stays.
- `// (Decision N)` and `per Decision N` and `Decision N axis 3` —
replaced with the content phrase plus the relevant contract
file (`design/contracts/tail-calls.md` for Decision 8,
`design/contracts/memory-model.md` for Decision 10,
`design/contracts/typeclasses.md` and `design/models/typeclasses.md`
for Decision 11, `design/contracts/authoring-surface.md` for
Decision 6, "the transitional dual-allocator" for Decision 9,
"Effect prose" for Decision 3).
- `// mq.X / mq.X (Task N) / mq.X journal / mq.X invariant` ->
"the canonical-class-form rule / Class-class repurpose /
method-dispatch-refactor journal / canonical-class-form
invariant".
- `// ct.X / ct.X (canonical-type-names) / ct.1.5a + ctt.2 /
ct.2 Task N` -> "the canonical-form rule for type references /
the canonical-form normalisation step / canonical-type-lookup
refactor".
- `// eob.X` -> "heap-Str-ABI" / "the Str carve-out".
- `// rpe.X` -> "the per-type-print-op retirement".
- `// post-mq.X / Pre-ct.X / pre-mq.X` -> "post-canonical-class-form" /
"Pre-canonical-type-form" etc.
- `/// Iter X regression: / /// Iter X.Y: / /// Iter A arm-close /
/// Iter ct.4 (...): / /// Iter rpe.1 ...` -> descriptive
phrases.
The journal filename in `crates/ailang-core/src/workspace.rs:573`
stays verbatim because it points at an actual file under
`docs/journals/`.
Tests: full `cargo test --workspace` green (80/80 test-result blocks
clean, no FAILED line). design_index_pin 5/5 + docs_honesty_pin 5/5
gating tests pass.
This commit is contained in:
@@ -32,7 +32,7 @@ use super::synth::llvm_type;
|
||||
use super::{AllocStrategy, Emitter, FnSig, Result};
|
||||
|
||||
impl<'a> Emitter<'a> {
|
||||
/// Iter 18c.4: emit a `void @drop_<module>_<TypeName>(ptr %p)`
|
||||
/// emit a `void @drop_<module>_<TypeName>(ptr %p)`
|
||||
/// function that decrements the refcount of every pointer-typed
|
||||
/// field of every ctor, then frees the outer box.
|
||||
///
|
||||
@@ -160,7 +160,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&out);
|
||||
}
|
||||
|
||||
/// Iter 18e: emit `drop_<m>_<T>` for a `(drop-iterative)` type.
|
||||
/// emit `drop_<m>_<T>` for a `(drop-iterative)` type.
|
||||
/// Replaces the recursive cascade in [`Self::emit_drop_fn_for_type`]
|
||||
/// with an iterative-with-explicit-worklist body so cells of
|
||||
/// arbitrary chain depth can free without consuming proportional
|
||||
@@ -328,7 +328,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&out);
|
||||
}
|
||||
|
||||
/// Iter 18e helper: is `fty` the same ADT as `td_name` in the
|
||||
/// is `fty` the same ADT as `td_name` in the
|
||||
/// current module? Used by the iterative-drop body to decide
|
||||
/// "push to worklist" (same type) vs. "call its drop fn directly"
|
||||
/// (different type).
|
||||
@@ -365,7 +365,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18c.4: pick the drop-fn symbol to call for a single
|
||||
/// pick the drop-fn symbol to call for a single
|
||||
/// pointer-typed field. Routes ADT fields to their own
|
||||
/// `drop_<owner>_<T>` symbol so the recursion cascades through
|
||||
/// recursive types (List, Tree). Falls back to `ailang_rc_dec`
|
||||
@@ -430,7 +430,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18c.3: predicate the `Term::Let` lowering uses to decide
|
||||
/// predicate the `Term::Let` lowering uses to decide
|
||||
/// whether a let-binder owns a fresh RC-heap allocation that
|
||||
/// codegen should `dec` at scope close.
|
||||
///
|
||||
@@ -443,7 +443,7 @@ impl<'a> Emitter<'a> {
|
||||
/// non-escaping ones become stack `alloca`s and must NOT be
|
||||
/// `dec`'d (they are freed by LLVM at fn return).
|
||||
///
|
||||
/// Iter 18g.2 widens this to include `Term::App` whose callee's
|
||||
/// The widened input set includes `Term::App` whose callee's
|
||||
/// fn-type carries `ret_mode == Own`. The mode contract states that
|
||||
/// the callee hands the returned cell's ownership to the caller's
|
||||
/// frame; the let-scope close is the right place for the caller's
|
||||
@@ -464,7 +464,7 @@ impl<'a> Emitter<'a> {
|
||||
!self.non_escape.contains(&term_ptr)
|
||||
}
|
||||
Term::App { callee, .. } => {
|
||||
// Iter 18g.2: a call whose callee carries
|
||||
// a call whose callee carries
|
||||
// `ret_mode == Own` hands a fresh heap allocation to
|
||||
// the caller's frame. Trackable. `Borrow` and
|
||||
// `Implicit` ret-modes do not carry that signal —
|
||||
@@ -479,7 +479,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18g.2: lookup helper for a callee's `ret_mode`. Returns
|
||||
/// lookup helper for a callee's `ret_mode`. Returns
|
||||
/// `Some(mode)` when the callee resolves to a fn-typed term;
|
||||
/// `None` for shapes whose type is not a `Type::Fn` (typechecker
|
||||
/// would already have rejected an App on a non-fn, but the helper
|
||||
@@ -494,7 +494,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18c.4: pick the drop-fn symbol to call at the close of a
|
||||
/// pick the drop-fn symbol to call at the close of a
|
||||
/// trackable `Term::Let` scope. For a `Term::Ctor` binder the
|
||||
/// symbol is `drop_<owner>_<TypeName>` — derived from the ctor's
|
||||
/// `type_name` (which already encodes the owning module via the
|
||||
@@ -524,7 +524,7 @@ impl<'a> Emitter<'a> {
|
||||
.get(val_ssa)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "ailang_rc_dec".to_string()),
|
||||
// Iter 18g.2: an Own-returning call hands a freshly heap-
|
||||
// an Own-returning call hands a freshly heap-
|
||||
// allocated cell whose static type is the callee's
|
||||
// `ret`. Resolve the per-type drop fn from that ret-type
|
||||
// so the cascade walks the cell's pointer-typed children
|
||||
@@ -559,7 +559,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18g.tidy.fu2: tag-conditional partial-drop helper.
|
||||
/// tag-conditional partial-drop helper.
|
||||
/// Emits `void @partial_drop_<m>_<T>(ptr %p, i64 %mask)` —
|
||||
/// structurally parallel to [`Self::emit_drop_fn_for_type`] but
|
||||
/// gates each ptr-field dec on a bit of `%mask`. If bit j of the
|
||||
@@ -699,7 +699,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&out);
|
||||
}
|
||||
|
||||
/// Iter 18g.tidy.fu2: resolve the `partial_drop_<owner>_<T>`
|
||||
/// resolve the `partial_drop_<owner>_<T>`
|
||||
/// symbol for a type, parallel to the existing per-type drop
|
||||
/// dispatch in `field_drop_call`. Returns `None` for non-ADT
|
||||
/// types (Str, fn-typed, type-vars) — callers fall back to
|
||||
@@ -728,7 +728,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 18g.tidy.fu2: build the i64 moved-slots bitmask for a
|
||||
/// build the i64 moved-slots bitmask for a
|
||||
/// `partial_drop_<m>_<T>` call. Bit j is set iff slot j is in
|
||||
/// `moved`. We reject slot indices ≥ 64 with `None` — no
|
||||
/// language-level ADT has that many fields, but the cap is
|
||||
@@ -747,7 +747,7 @@ impl<'a> Emitter<'a> {
|
||||
Some(mask)
|
||||
}
|
||||
|
||||
/// Iter 18d.3: emit a partial-drop sequence inline at a let-close
|
||||
/// emit a partial-drop sequence inline at a let-close
|
||||
/// site whose binder has moved-out pattern slots. Replaces the
|
||||
/// uniform `drop_<m>_<T>(ptr)` call: load each pointer-typed
|
||||
/// field whose slot index is NOT in `moved`, dispatch through
|
||||
@@ -757,11 +757,11 @@ impl<'a> Emitter<'a> {
|
||||
/// transferred to a pattern-bound binder that owns the dec
|
||||
/// for them.
|
||||
///
|
||||
/// Iter 18g.2 widened the input set to `Term::App` (Own-
|
||||
/// returning call). The static per-field emission below is keyed
|
||||
/// The widened input set includes `Term::App` (Own-returning
|
||||
/// call). The static per-field emission below is keyed
|
||||
/// against a `Term::Ctor`'s known ctor; a `Term::App` binder
|
||||
/// whose body pattern-matches it has a *dynamic* runtime tag.
|
||||
/// Iter 18g.tidy.fu2 closes that path: instead of falling back
|
||||
/// instead of falling back
|
||||
/// to shallow `ailang_rc_dec` (which leaked the unmoved fields),
|
||||
/// we route through the tag-conditional helper
|
||||
/// [`Self::emit_partial_drop_fn_for_type`], which dispatches on
|
||||
@@ -775,7 +775,7 @@ impl<'a> Emitter<'a> {
|
||||
let (type_name, ctor_name) = match value {
|
||||
Term::Ctor { type_name, ctor, .. } => (type_name.as_str(), ctor.as_str()),
|
||||
_ => {
|
||||
// Iter 18g.tidy.fu2: dynamic-tag partial-drop via the
|
||||
// dynamic-tag partial-drop via the
|
||||
// per-type helper. `value` is `Term::App` (Own-
|
||||
// returning) — the binder's static type is the App's
|
||||
// ret type, recovered through `synth_arg_type` /
|
||||
@@ -836,7 +836,7 @@ impl<'a> Emitter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Iter 18c.4: build the IR text for a closure env's drop fn.
|
||||
/// build the IR text for a closure env's drop fn.
|
||||
/// Layout: 8 bytes per capture, in declaration order.
|
||||
/// For each pointer-typed capture, emit a load + drop call;
|
||||
/// finally `ailang_rc_dec` the env block.
|
||||
@@ -878,7 +878,7 @@ impl<'a> Emitter<'a> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Iter 18c.4: build the IR text for a closure pair's drop fn.
|
||||
/// build the IR text for a closure pair's drop fn.
|
||||
/// Layout: { ptr thunk, ptr env } — env at offset 8.
|
||||
/// Loads env, calls the env drop, then decs the pair box.
|
||||
pub(crate) fn build_pair_drop_fn(
|
||||
|
||||
@@ -174,18 +174,18 @@ fn walk(t: &Term, out: &mut NonEscapeSet) {
|
||||
walk(body, out);
|
||||
}
|
||||
Term::LetRec { body, in_term, .. } => {
|
||||
// Iter 16b.x: LetRec is normally eliminated before codegen.
|
||||
// LetRec is normally eliminated before codegen.
|
||||
// Still walk for completeness.
|
||||
walk(body, out);
|
||||
walk(in_term, out);
|
||||
}
|
||||
Term::Clone { value } => {
|
||||
// Iter 18c.1: clone is identity at IR level — only walk
|
||||
// clone is identity at IR level — only walk
|
||||
// sub-terms looking for Let-allocation candidates.
|
||||
walk(value, out);
|
||||
}
|
||||
Term::ReuseAs { source, body } => {
|
||||
// Iter 18d.1: identity at IR level (codegen lowers `body`,
|
||||
// identity at IR level (codegen lowers `body`,
|
||||
// ignores `source`). Walk both children for completeness.
|
||||
walk(source, out);
|
||||
walk(body, out);
|
||||
@@ -361,13 +361,13 @@ fn escapes(t: &Term, tainted: &BTreeSet<String>, in_tail: bool) -> bool {
|
||||
true
|
||||
}
|
||||
Term::Clone { value } => {
|
||||
// Iter 18c.1: clone is identity. The wrapper's escape
|
||||
// clone is identity. The wrapper's escape
|
||||
// verdict is exactly that of its inner term, threaded
|
||||
// through with the same `in_tail` context.
|
||||
escapes(value, tainted, in_tail)
|
||||
}
|
||||
Term::ReuseAs { source, body } => {
|
||||
// Iter 18d.1: identity at IR level. The whole expression's
|
||||
// identity at IR level. The whole expression's
|
||||
// value is `body`, so its escape verdict is the body's
|
||||
// (threaded through `in_tail`). The `source` is evaluated
|
||||
// but its value is discarded; it cannot escape via the
|
||||
@@ -490,11 +490,11 @@ fn collect_free_vars(t: &Term, bound: &mut BTreeSet<String>, out: &mut BTreeSet<
|
||||
collect_free_vars(in_term, bound, out);
|
||||
}
|
||||
Term::Clone { value } => {
|
||||
// Iter 18c.1: clone is identity for free-var collection.
|
||||
// clone is identity for free-var collection.
|
||||
collect_free_vars(value, bound, out);
|
||||
}
|
||||
Term::ReuseAs { source, body } => {
|
||||
// Iter 18d.1: free vars are the union of source and body.
|
||||
// free vars are the union of source and body.
|
||||
collect_free_vars(source, bound, out);
|
||||
collect_free_vars(body, bound, out);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ use super::synth::{fn_sig_from_type, llvm_type};
|
||||
use super::{AllocStrategy, CodegenError, Emitter, FnSig, Result};
|
||||
|
||||
impl<'a> Emitter<'a> {
|
||||
/// Iter 8b: lower a `Term::Lam`. Walks the body to find captures
|
||||
/// lower a `Term::Lam`. Walks the body to find captures
|
||||
/// (free vars relative to the enclosing scope, minus builtins and
|
||||
/// top-level fns), allocates a heap env for the captures, lifts the
|
||||
/// body to a top-level thunk fn `@ail_<m>_<def>_lam<id>`, and
|
||||
@@ -122,17 +122,17 @@ impl<'a> Emitter<'a> {
|
||||
let saved_terminated = self.block_terminated;
|
||||
self.block_terminated = false;
|
||||
let saved_sigs = std::mem::take(&mut self.ssa_fn_sigs);
|
||||
// Iter 17a: a lambda thunk is its own fn frame for escape
|
||||
// a lambda thunk is its own fn frame for escape
|
||||
// analysis. Save the outer fn's non-escape set and recompute
|
||||
// for the lambda body. Restored at the end alongside the rest
|
||||
// of the saved emitter state.
|
||||
let saved_non_escape = std::mem::take(&mut self.non_escape);
|
||||
self.non_escape = escape::analyze_fn_body(lam_body);
|
||||
// Iter 18d.3: a lambda thunk is its own fn frame for move
|
||||
// a lambda thunk is its own fn frame for move
|
||||
// tracking — the outer fn's binders are not in scope inside
|
||||
// the thunk body (only its captures + params). Save and reset.
|
||||
let saved_moved = std::mem::take(&mut self.moved_slots);
|
||||
// Iter mut.3: a lambda thunk is its own fn frame for the
|
||||
// a lambda thunk is its own fn frame for the
|
||||
// mut-var bookkeeping introduced in iter mut.3 — the outer
|
||||
// fn's loop binders are not in scope inside the thunk (a
|
||||
// lambda body cannot reference enclosing loop binders; the
|
||||
@@ -150,7 +150,7 @@ impl<'a> Emitter<'a> {
|
||||
let saved_loop_frames = std::mem::take(&mut self.loop_frames);
|
||||
let saved_pending_allocas = std::mem::take(&mut self.pending_entry_allocas);
|
||||
let saved_entry_marker = self.entry_block_end_marker.take();
|
||||
// Iter 18d.4 fix: a lambda thunk is its own fn frame for
|
||||
// A lambda thunk is its own fn frame for
|
||||
// param-mode lookup. The outer fn's params are not in scope
|
||||
// inside the thunk; the thunk's own params are pushed below
|
||||
// and (currently) carry no mode annotation, so they default
|
||||
@@ -269,7 +269,7 @@ impl<'a> Emitter<'a> {
|
||||
// 3. Emit allocation + capture filling + closure-pair packing
|
||||
// in the OUTER body. Captures use 8 bytes each; closure-pair
|
||||
// is 16 bytes.
|
||||
// Iter 17a: env and closure-pair share the Lam term's escape
|
||||
// env and closure-pair share the Lam term's escape
|
||||
// status — they have parallel lifetimes. If the closure pair
|
||||
// does not escape (only used as a callee in the outer body),
|
||||
// both can be `alloca`'d. The escape-analysis lookup runs
|
||||
@@ -334,7 +334,7 @@ impl<'a> Emitter<'a> {
|
||||
};
|
||||
self.ssa_fn_sigs.insert(clos.clone(), fs);
|
||||
|
||||
// Iter 18c.4: emit per-pair drop fns for heap-allocated
|
||||
// emit per-pair drop fns for heap-allocated
|
||||
// closures under `--alloc=rc`. `lam_local` closures live on
|
||||
// the stack and need no drop fn — LLVM `alloca` reclaims the
|
||||
// pair on fn return. For heap closures we emit two symbols:
|
||||
@@ -365,7 +365,7 @@ impl<'a> Emitter<'a> {
|
||||
Ok((clos, "ptr".into()))
|
||||
}
|
||||
|
||||
/// Iter 8b: walk a Term collecting free-variable names that should
|
||||
/// walk a Term collecting free-variable names that should
|
||||
/// be captured by an enclosing lambda. Builtins and top-level fns
|
||||
/// are excluded — they're globally accessible. Qualified names
|
||||
/// (`prefix.def`) are excluded too.
|
||||
@@ -461,16 +461,16 @@ impl<'a> Emitter<'a> {
|
||||
Self::collect_captures(rhs, bound, captures, captures_set, builtins, top_level);
|
||||
}
|
||||
Term::LetRec { .. } => {
|
||||
// Iter 16b.1: eliminated by desugar before codegen.
|
||||
// eliminated by desugar before codegen.
|
||||
unreachable!("Term::LetRec eliminated by desugar")
|
||||
}
|
||||
Term::Clone { value } => {
|
||||
// Iter 18c.1: clone is identity for capture analysis —
|
||||
// clone is identity for capture analysis —
|
||||
// free vars of `(clone X)` are exactly the free vars of `X`.
|
||||
Self::collect_captures(value, bound, captures, captures_set, builtins, top_level);
|
||||
}
|
||||
Term::ReuseAs { source, body } => {
|
||||
// Iter 18d.1: free vars are the union of source and body.
|
||||
// free vars are the union of source and body.
|
||||
Self::collect_captures(source, bound, captures, captures_set, builtins, top_level);
|
||||
Self::collect_captures(body, bound, captures, captures_set, builtins, top_level);
|
||||
}
|
||||
|
||||
+147
-147
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ impl<'a> Emitter<'a> {
|
||||
/// i1 and i8 fields also occupy a full 8-byte slot — the typed
|
||||
/// load/store instructions write/read only the required size.
|
||||
///
|
||||
/// Iter 17a: `term_ptr` is the pointer-as-usize of the lowered
|
||||
/// `term_ptr` is the pointer-as-usize of the lowered
|
||||
/// `Term::Ctor` node. If escape analysis flagged this site as
|
||||
/// non-escaping (i.e., the value cannot live past the current fn
|
||||
/// frame), allocation lowers to LLVM `alloca` instead of
|
||||
@@ -52,13 +52,13 @@ impl<'a> Emitter<'a> {
|
||||
"ctor `{type_name}/{ctor_name}` arity"
|
||||
)));
|
||||
}
|
||||
// Iter 13b: for parameterised ADTs the precomputed `cref.fields`
|
||||
// for parameterised ADTs the precomputed `cref.fields`
|
||||
// is meaningless because field types reference rigid type vars.
|
||||
// Derive a per-use-site substitution from the arg types and
|
||||
// re-lower each field type. Monomorphic ADTs hit the fast path
|
||||
// (no var-set, substitution is empty, ail_fields lower exactly
|
||||
// like cref.fields).
|
||||
// Iter 15b: for cross-module ctors, qualify any local type-cons
|
||||
// for cross-module ctors, qualify any local type-cons
|
||||
// in `cref.ail_fields` (symmetric to the term-ctor synth fix).
|
||||
let qualified_ail_fields: Vec<Type> = if type_name.matches('.').count() == 1 {
|
||||
let (prefix, _) = type_name.split_once('.').expect("checked");
|
||||
@@ -108,7 +108,7 @@ impl<'a> Emitter<'a> {
|
||||
// design/contracts/frozen-value-layout.md. size = 8 + n*8, tag@0, fields@8+i*8.
|
||||
let size_bytes = 8 + (compiled.len() * 8) as i64;
|
||||
let p = self.fresh_ssa();
|
||||
// Iter 17a: pick allocator based on escape analysis. `alloca`
|
||||
// pick allocator based on escape analysis. `alloca`
|
||||
// for non-escaping (stack-allocated, freed on fn return);
|
||||
// `@GC_malloc` for everything else.
|
||||
if self.non_escape.contains(&term_ptr) {
|
||||
@@ -139,7 +139,7 @@ impl<'a> Emitter<'a> {
|
||||
Ok((p, "ptr".into()))
|
||||
}
|
||||
|
||||
/// Iter 18d.2: lower `Term::ReuseAs { source, body = Term::Ctor }`
|
||||
/// lower `Term::ReuseAs { source, body = Term::Ctor }`
|
||||
/// under `--alloc=rc` as a runtime refcount-1 dispatch.
|
||||
///
|
||||
/// IR shape (Lean 4 / Roc lineage):
|
||||
@@ -192,7 +192,7 @@ impl<'a> Emitter<'a> {
|
||||
"reuse-as source type must be ptr, got {src_ty}"
|
||||
)));
|
||||
}
|
||||
// Iter 18d.3: the source binder name keys into `moved_slots`
|
||||
// the source binder name keys into `moved_slots`
|
||||
// so the reuse arm can skip dec'ing fields that earlier
|
||||
// pattern-extracted out of the source. Linearity already
|
||||
// ensures `source` is `Term::Var` here; the var-resolution
|
||||
@@ -323,7 +323,7 @@ impl<'a> Emitter<'a> {
|
||||
|
||||
// 5. Reuse arm: overwrite tag and field slots in place.
|
||||
//
|
||||
// Iter 18d.3: per-field dec is now safe — moved-out slots
|
||||
// per-field dec is now safe — moved-out slots
|
||||
// are skipped via the codegen `moved_slots` side table;
|
||||
// non-moved slots are dec'd via `field_drop_call`'s null-
|
||||
// guarded drop fns BEFORE the new field values overwrite
|
||||
@@ -344,7 +344,7 @@ impl<'a> Emitter<'a> {
|
||||
// pointer-typed fields are dec'd via `field_drop_call`,
|
||||
// closing the 18d.2 leak.
|
||||
self.start_block(&reuse_lbl);
|
||||
// Iter 18d.3: dec non-moved pointer-typed slots before the
|
||||
// dec non-moved pointer-typed slots before the
|
||||
// overwrite. The dec fns are null-guarded, so an empty slot
|
||||
// is a no-op. Clone the move set out of the side table so
|
||||
// the loop below can mutably borrow `self` for SSA allocation.
|
||||
@@ -449,7 +449,7 @@ impl<'a> Emitter<'a> {
|
||||
"match on non-ADT scrutinee (got {s_ty}); MVP supports only ADTs"
|
||||
)));
|
||||
}
|
||||
// Iter 18d.3: move tracking key — the bare-Var binder name of
|
||||
// move tracking key — the bare-Var binder name of
|
||||
// the scrutinee, if it has one. A complex expression scrutinee
|
||||
// (e.g. the result of `(map_inc xs)` matched directly) has no
|
||||
// binder we can key off; moves through such matches are
|
||||
@@ -486,7 +486,7 @@ impl<'a> Emitter<'a> {
|
||||
open_var = Some(name.clone());
|
||||
}
|
||||
Pattern::Ctor { ctor, fields } => {
|
||||
// Iter 15a: lookup falls back to imported modules when
|
||||
// lookup falls back to imported modules when
|
||||
// a bare ctor name doesn't resolve locally.
|
||||
let cref = self.lookup_ctor_in_pattern(ctor)?;
|
||||
let bindings: Vec<Option<String>> = fields
|
||||
@@ -530,7 +530,7 @@ impl<'a> Emitter<'a> {
|
||||
|
||||
for (i, (cref, arm, bindings)) in ctor_arms.iter().enumerate() {
|
||||
self.start_block(&arm_labels[i]);
|
||||
// Iter 13b: derive substitution for parameterised ADTs from
|
||||
// derive substitution for parameterised ADTs from
|
||||
// the scrutinee's concrete type-args. Map `cref.type_vars[i]`
|
||||
// → `s_ail.args[i]`. For monomorphic ADTs the mapping is
|
||||
// empty and substitution is a no-op. The substituted AILang
|
||||
@@ -559,7 +559,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
};
|
||||
// Load fields and bind as locals.
|
||||
// Iter 15b: when the scrutinee's ADT lives in another module,
|
||||
// when the scrutinee's ADT lives in another module,
|
||||
// `cref.ail_fields[idx]` carries the field type written in
|
||||
// the owner's local namespace. Qualify it before substituting
|
||||
// — symmetric to the term-ctor and pat-ctor fixes in
|
||||
@@ -571,13 +571,13 @@ impl<'a> Emitter<'a> {
|
||||
_ => None,
|
||||
};
|
||||
let mut pushed = 0usize;
|
||||
// Iter 18d.3: collect the binder names this arm pushes so
|
||||
// collect the binder names this arm pushes so
|
||||
// we can drop their `moved_slots` entries when the arm
|
||||
// body closes (these binders are themselves freshly named
|
||||
// here — their move tracking starts empty and ends at arm
|
||||
// body close).
|
||||
//
|
||||
// Iter 18d.4: the metadata is widened to (name, SSA,
|
||||
// the metadata is widened to (name, SSA,
|
||||
// llvm-type, AILang-type) so that the arm-close drop
|
||||
// emission can route the call through the binder's
|
||||
// per-type drop symbol without re-walking `self.locals`.
|
||||
@@ -608,7 +608,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&format!(
|
||||
" {v} = load {fty}, ptr {addr}, align 8\n"
|
||||
));
|
||||
// Iter 18d.3: a non-wildcard, pointer-typed slot
|
||||
// a non-wildcard, pointer-typed slot
|
||||
// bound here is treated as moved out of the
|
||||
// scrutinee binder. The source slot is NOT
|
||||
// mutated; codegen records the move statically so
|
||||
@@ -632,9 +632,9 @@ impl<'a> Emitter<'a> {
|
||||
pushed += 1;
|
||||
}
|
||||
}
|
||||
// Iter 18d.4 fix + 18g.1: scrutinee ownership signal,
|
||||
// hoisted here so both the pre-tail-call shallow-dec
|
||||
// (Iter 18g.1, below) and the arm-close pattern-binder
|
||||
// Scrutinee ownership signal, hoisted here so both the
|
||||
// pre-tail-call shallow-dec (below) and the arm-close
|
||||
// pattern-binder
|
||||
// dec (Iter A, further below) consult the same gate.
|
||||
//
|
||||
// If the scrutinee resolves to a fn-param whose mode is
|
||||
@@ -657,7 +657,7 @@ impl<'a> Emitter<'a> {
|
||||
},
|
||||
None => true,
|
||||
};
|
||||
// Iter 18g.1: pre-tail-call shallow-dec for moved-from
|
||||
// pre-tail-call shallow-dec for moved-from
|
||||
// scrutinee outer cell. Iter A (arm-close pattern-binder
|
||||
// dec, below) cannot fire when `arm.body` is a tail call:
|
||||
// `lower_term` emits `musttail call ... ret` and sets
|
||||
@@ -739,7 +739,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
let (val, vty) = self.lower_term(&arm.body)?;
|
||||
// Iter 18d.4: arm-close pattern-binder dec. Symmetric to
|
||||
// arm-close pattern-binder dec. Symmetric to
|
||||
// 18c.3/18c.4's `Term::Let`-scope-close drop emission, but
|
||||
// fired at the lexical close of a match arm. For each
|
||||
// pattern-bound binder this arm pushed, emit a drop call
|
||||
@@ -763,11 +763,11 @@ impl<'a> Emitter<'a> {
|
||||
// through the tail slot. Now the arm itself dec's `t`,
|
||||
// freeing the 4-cell tail.
|
||||
//
|
||||
// Iter 18d.4 fix (regression
|
||||
// `alloc_rc_pattern_bind_in_implicit_fn_does_not_dec_borrowed_children`):
|
||||
// Iter A is symmetric to Iter B (Own-param dec at fn
|
||||
// return) and must share Iter B's mode gate. If the
|
||||
// scrutinee resolves to a fn-param whose mode is
|
||||
// Regression
|
||||
// `alloc_rc_pattern_bind_in_implicit_fn_does_not_dec_borrowed_children`:
|
||||
// arm-close pattern-binder dec is symmetric to Own-param
|
||||
// dec at fn return and must share the same mode gate. If
|
||||
// the scrutinee resolves to a fn-param whose mode is
|
||||
// `Borrow` or `Implicit`, the caller still holds a
|
||||
// reference to the heap value the pattern-binders were
|
||||
// loaded from. Dec'ing those binders fragments the
|
||||
@@ -823,7 +823,7 @@ impl<'a> Emitter<'a> {
|
||||
" call void @{drop_call}(ptr {b_ssa})\n"
|
||||
));
|
||||
} else {
|
||||
// Iter 18g.tidy.fu2: pattern-binder with
|
||||
// pattern-binder with
|
||||
// statically-recorded moved slots routes
|
||||
// through the tag-conditional helper
|
||||
// `partial_drop_<owner>_<T>(b, mask)`. The
|
||||
@@ -852,13 +852,13 @@ impl<'a> Emitter<'a> {
|
||||
for _ in 0..pushed {
|
||||
self.locals.pop();
|
||||
}
|
||||
// Iter 18d.3: arm-bound binders go out of scope at arm
|
||||
// arm-bound binders go out of scope at arm
|
||||
// body close. Drop their move-tracking entries (any moves
|
||||
// recorded against `h`/`t` belonged to this arm only).
|
||||
for (bname, _, _, _) in &arm_pushed_binders {
|
||||
self.moved_slots.remove(bname);
|
||||
}
|
||||
// Iter 14e: if the arm body lowered to a `musttail call` +
|
||||
// if the arm body lowered to a `musttail call` +
|
||||
// `ret`, the block is already terminated. Skip the
|
||||
// fall-through `br` and exclude this arm from the join phi.
|
||||
if !self.block_terminated {
|
||||
@@ -912,7 +912,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
|
||||
// join
|
||||
// Iter 14e: if every arm tail-called and terminated its own
|
||||
// if every arm tail-called and terminated its own
|
||||
// block, no predecessor branches into the join. Mark the whole
|
||||
// match as block-terminated and emit no join body — the
|
||||
// surrounding context (top-level fn body, seq rhs, etc.) checks
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::{CodegenError, Result};
|
||||
|
||||
/// Iter 12b: derive a name → concrete-type substitution from the
|
||||
/// derive a name → concrete-type substitution from the
|
||||
/// declared params of a `Forall` body and the actual arg types at a
|
||||
/// call site. Walks both sides in parallel; whenever a `Type::Var`
|
||||
/// (rigid name) appears on the params side, binds it to the
|
||||
@@ -45,7 +45,7 @@ pub(crate) fn derive_substitution(
|
||||
// through return-type unification, but at the call site we only
|
||||
// see args; if needed, callers can extend this with expected-ret
|
||||
// info.
|
||||
// Iter 15a: a forall var that the args couldn't pin (e.g.
|
||||
// a forall var that the args couldn't pin (e.g.
|
||||
// `is_none(Nothing) : forall a. (Maybe a) -> Bool` — `a` is
|
||||
// genuinely unobservable from the args alone) defaults to `Unit`.
|
||||
// The specialised body must not actually read an `a`-typed value,
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn unify_for_subst(
|
||||
vars: &BTreeSet<&str>,
|
||||
subst: &mut BTreeMap<String, Type>,
|
||||
) -> Result<()> {
|
||||
// Iter 14a fix, extended in 15g-aux: a `$u`-prefixed var is a
|
||||
// A `$u`-prefixed var is a
|
||||
// synth-only wildcard produced by `synth_arg_type` for nullary
|
||||
// ctors of a parameterised ADT (e.g. `Nil : List<$u>`). It
|
||||
// carries no real constraint — accept without binding so a
|
||||
@@ -104,7 +104,7 @@ pub(crate) fn unify_for_subst(
|
||||
match (param, arg) {
|
||||
(Type::Var { name }, _) if vars.contains(name.as_str()) => {
|
||||
if let Some(prev) = subst.get(name).cloned() {
|
||||
// Iter 15b: the previously-bound type may be more
|
||||
// the previously-bound type may be more
|
||||
// concrete than `arg` (e.g. `prev = List<Int>` from a
|
||||
// sibling binding, `arg = List<$u>` from a synth-
|
||||
// wildcard nullary ctor). Use recursive unification
|
||||
@@ -149,7 +149,7 @@ pub(crate) fn unify_for_subst(
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 15a: rewrites bare `Type::Con` references that resolve against
|
||||
/// rewrites bare `Type::Con` references that resolve against
|
||||
/// `owner_local_types` into qualified `module.Type` form. Mirrors
|
||||
/// `ailang_check::qualify_local_types`. Used when the codegen pulls a
|
||||
/// polymorphic fn signature across the import boundary; without this
|
||||
@@ -204,7 +204,7 @@ pub(crate) fn qualify_local_types_codegen(
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 12b: substitute rigid type vars in `t` according to `subst`.
|
||||
/// substitute rigid type vars in `t` according to `subst`.
|
||||
/// Used to specialise the type of a polymorphic def for a given
|
||||
/// instantiation.
|
||||
pub(crate) fn apply_subst_to_type(t: &Type, subst: &BTreeMap<String, Type>) -> Type {
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) fn llvm_type(t: &Type) -> Result<String> {
|
||||
// at the LLVM level. The actual signature travels via the
|
||||
// emitter's `ssa_fn_sigs` sidetable.
|
||||
Type::Fn { .. } => Ok("ptr".into()),
|
||||
// Iter 13b: an unresolved rigid `Type::Var` reaching codegen is
|
||||
// an unresolved rigid `Type::Var` reaching codegen is
|
||||
// a substitution bug. Earlier this silently lowered as `ptr`
|
||||
// (via the ADT fallback) and produced garbage IR; failing loudly
|
||||
// here surfaces the bug in the test suite.
|
||||
@@ -56,11 +56,11 @@ pub(crate) fn fn_sig_from_type(t: &Type) -> Option<FnSig> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Iter 12b: AILang type of a builtin operator. Used by
|
||||
/// AILang type of a builtin operator. Used by
|
||||
/// `synth_arg_type` for arg-type inference at polymorphic call sites.
|
||||
/// Mirrors what the typechecker installs in its env via `builtins`.
|
||||
pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
// Iter 22-floats.3: same widening as `crates/ailang-check/src/
|
||||
// same widening as `crates/ailang-check/src/
|
||||
// builtins.rs` — `+`/`-`/`*`/`/` and `!=`/`<`/`<=`/`>`/`>=` are
|
||||
// polymorphic. `%` stays monomorphic-Int. Codegen lowering for
|
||||
// these ops still goes through `builtin_binop` (Int-only) in
|
||||
@@ -104,7 +104,7 @@ pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
"+" | "-" | "*" | "/" => poly_a_a_to_a(),
|
||||
"%" => int_int_int(),
|
||||
"!=" | "<" | "<=" | ">" | ">=" => poly_a_a_to_bool(),
|
||||
// Iter 16e: `==` is polymorphic — `forall a. (a, a) -> Bool`.
|
||||
// `==` is polymorphic — `forall a. (a, a) -> Bool`.
|
||||
// The mono pipeline asks `synth_arg_type` for the actual arg
|
||||
// types at the call site; `lower_app` then dispatches to the
|
||||
// right LLVM instruction (icmp eq i64 / i1, @strcmp, or
|
||||
@@ -130,14 +130,14 @@ pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
// Iter 16d: `__unreachable__` is the polymorphic bottom value
|
||||
// `__unreachable__` is the polymorphic bottom value
|
||||
// (`forall a. a`). Mirrors the typechecker's `builtins::install`.
|
||||
"__unreachable__" => Type::Forall {
|
||||
vars: vec!["a".into()],
|
||||
constraints: vec![],
|
||||
body: Box::new(Type::Var { name: "a".into() }),
|
||||
},
|
||||
// Iter 22-floats.3: Float-conversion and inspection builtins.
|
||||
// Float-conversion and inspection builtins.
|
||||
// Same lockstep with `crates/ailang-check/src/builtins.rs`.
|
||||
"neg" => Type::Forall {
|
||||
vars: vec!["a".into()],
|
||||
@@ -199,7 +199,7 @@ pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
// Iter 22-floats.3: Float bit-pattern constants. Bare values,
|
||||
// Float bit-pattern constants. Bare values,
|
||||
// not fns — referenced via `Term::Var`. Mirrors the
|
||||
// typechecker's `builtins::install`. Codegen emits LLVM hex-
|
||||
// float literals at the use site in iter 4.
|
||||
@@ -208,7 +208,7 @@ pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Iter 12b: AILang return type of a built-in effect op. The op's
|
||||
/// AILang return type of a built-in effect op. The op's
|
||||
/// param signature is irrelevant here since we only consume the ret.
|
||||
pub(crate) fn builtin_effect_op_ret(op: &str) -> Option<Type> {
|
||||
Some(match op {
|
||||
|
||||
@@ -30,7 +30,7 @@ fn lower_eq_primitives_smoke() -> String {
|
||||
lower_workspace(&ws).expect("lower")
|
||||
}
|
||||
|
||||
/// Iter 23.2: integration with the auto-loaded prelude. Compiling a
|
||||
/// integration with the auto-loaded prelude. Compiling a
|
||||
/// workspace that calls `eq` on an Int pair must produce a
|
||||
/// mono-synthesised `@ail_prelude_eq__Int` whose body lowers to
|
||||
/// `icmp eq i64`. Full pipeline: end-of-typecheck → monomorphise →
|
||||
@@ -48,7 +48,7 @@ fn eq_int_call_produces_icmp_i64_mono_fn() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 23.2: same shape as `eq_int_call_produces_icmp_i64_mono_fn`
|
||||
/// same shape as `eq_int_call_produces_icmp_i64_mono_fn`
|
||||
/// for Bool. Asserts the mono-synthesised `@ail_prelude_eq__Bool`
|
||||
/// lowers to `icmp eq i1`.
|
||||
#[test]
|
||||
@@ -64,7 +64,7 @@ fn eq_bool_call_produces_icmp_i1_mono_fn() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 23.2: Str's mono-symbol body is hand-rolled by the
|
||||
/// Str's mono-symbol body is hand-rolled by the
|
||||
/// `try_emit_primitive_instance_body` intercept. Asserts the intercept
|
||||
/// fires for the prelude-loaded `eq__Str` synthesised against a real
|
||||
/// user fixture that calls `eq` on Str.
|
||||
|
||||
Reference in New Issue
Block a user