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:
2026-05-20 09:58:04 +02:00
parent bcd41810f4
commit ac4d545570
43 changed files with 1004 additions and 1000 deletions
+20 -20
View File
@@ -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(
+7 -7
View File
@@ -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);
}
+11 -11
View File
@@ -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);
}
File diff suppressed because it is too large Load Diff
+29 -29
View File
@@ -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
+6 -6
View File
@@ -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 {
+8 -8
View File
@@ -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 {