Iter 18e: (drop-iterative) annotation + worklist allocator

Closes the 18-arc's stack-recursion limit. Recursive drop
cascades from 18c.4 overflow on long ADT chains (Linux's 8 MB
default stack maxes out around 1M cells of List). The new
opt-in (drop-iterative) annotation on a Def::Type switches the
synthesised drop_<m>_<T> body from recursive to iterative-with-
explicit-worklist for that type.

Schema:
- TypeDef.drop_iterative: bool. Default false; serde-skip
  when false so existing fixtures' canonical JSON hashes stay
  stable.
- Form-A: (drop-iterative) clause inside (data T ...).

Worklist runtime (4 new ABI symbols in runtime/rc.c):
- ailang_drop_worklist_new(initial_capacity)
- ailang_drop_worklist_push(wl, ptr)
- ailang_drop_worklist_pop(wl) -> ptr
- ailang_drop_worklist_free(wl)

Heap stretchy buffer, doubling on overflow, null-filtering on
push. Lean 4 / Roc precedent documented in the runtime; the
slot-repurposing strategy was considered and rejected because
not every box has a free pointer-typed slot to thread the
worklist through (Cons head is i64, slot 1 is ptr but it's
the field we're following — no free slot).

Codegen (emit_iterative_drop_fn_for_type): for a
drop_iterative type, drop_<m>_<T>(ptr %p) emits a worklist
loop. Fields of the SAME annotated type push onto the
worklist (mono-typed); fields of DIFFERENT types call their
own drop fn directly (recursive on those, but only if THEY
are themselves recursive — i.e. one level of cascade jump
maximum). Mono-typed-worklist is sound for the deep-self-
recursion case the iter targets (List of List of T just
needs the spine flattened).

Tests:
- examples/rc_drop_iterative_long_list — 1M-cell List of Int
  with (drop-iterative) annotation.
- alloc_rc_drop_iterative_handles_million_cell_list E2E —
  builds + runs under --alloc=rc, asserts clean exit. With
  annotation: exits 0. Without annotation (control): SIGSEGV
  at exit code 139 (verified by hand). Worklist is load-
  bearing.
- iter18e_drop_iterative_emits_worklist_body_no_self_recursion
  IR-shape: worklist body has br to loop_head AND no direct
  recursive call into drop_<m>_<T>.
- iter18e_no_annotation_keeps_recursive_drop_body — control:
  unannotated variant still emits the 18c.4 recursive shape.
- 3 surface parse-tests for the annotation round-trip.

Test deltas: e2e 58 -> 61 (+3), surface 18 -> 21 (+3). All
other buckets unchanged. cargo test --workspace green.

Known debt (deliberate):
- Mono-typed worklist: cross-type drop-iterative fields call
  the other type's drop fn directly. A heterogeneous
  worklist would be more general but adds tag tracking
  complexity for a case (drop-iterative T containing
  drop-iterative T') that's narrower than the deep-self-
  recursion target. Documented in
  emit_iterative_drop_fn_for_type's doc.
- Closure / Type::Var / Type::Forall fields fall back to
  shallow ailang_rc_dec via field_drop_call — same as the
  recursive variant.
- Dynamic-tag partial-drop fallback (head_or_zero epilogue
  shallow dec when moved_slots non-empty) — out of scope per
  brief.
This commit is contained in:
2026-05-08 12:53:09 +02:00
parent 5e401a3520
commit ce6ab8ee44
12 changed files with 763 additions and 2 deletions
+110 -1
View File
@@ -364,6 +364,7 @@ impl<'a> Parser<'a> {
}
let mut doc: Option<String> = None;
let mut ctors: Vec<Ctor> = Vec::new();
let mut drop_iterative = false;
loop {
match self.peek_head_ident() {
Some("doc") => {
@@ -373,12 +374,42 @@ impl<'a> Parser<'a> {
Some("ctor") => {
ctors.push(self.parse_ctor()?);
}
Some("drop-iterative") => {
// Iter 18e: `(drop-iterative)` opt-in annotation.
// Takes no arguments — it is a flag. A second
// `(drop-iterative)` clause is a parse error
// (rejected here so that the JSON schema's
// `drop_iterative: bool` round-trips unambiguously).
self.expect_lparen("drop-iterative-attr")?;
self.expect_keyword("drop-iterative")?;
if !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "data-def",
message:
"drop-iterative takes no arguments; expected `)`"
.into(),
pos,
});
}
self.expect_rparen("drop-iterative-attr")?;
if drop_iterative {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "data-def",
message: "duplicate `drop-iterative` attribute"
.into(),
pos,
});
}
drop_iterative = true;
}
Some(other) => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "data-def",
message: format!(
"unknown data attribute `{other}`; expected `doc` or `ctor`"
"unknown data attribute `{other}`; expected `doc`, `ctor`, or `drop-iterative`"
),
pos,
});
@@ -392,6 +423,7 @@ impl<'a> Parser<'a> {
vars,
ctors,
doc,
drop_iterative,
})
}
@@ -1483,6 +1515,83 @@ mod tests {
assert_eq!(term_to_form_a(&parsed), printed);
}
/// Iter 18e: `(data T (drop-iterative))` parses with
/// `drop_iterative = true` AND round-trips through the printer
/// back to the same canonical surface form.
#[test]
fn parses_drop_iterative_annotation_on_data_decl() {
use crate::print::print;
let src = r#"
(module m
(data Tree
(vars a)
(ctor Leaf)
(ctor Node a (con Tree a) (con Tree a))
(drop-iterative)))
"#;
let m = parse(src).expect("parse should succeed");
match &m.defs[0] {
Def::Type(td) => {
assert_eq!(td.name, "Tree");
assert!(td.drop_iterative, "drop_iterative flag must be set");
}
_ => panic!("expected Def::Type"),
}
// Round-trip through the printer.
let printed = print(&m);
let m2 = parse(&printed).expect("re-parse should succeed");
match &m2.defs[0] {
Def::Type(td) => {
assert!(td.drop_iterative, "drop_iterative must survive print/parse");
}
_ => panic!("expected Def::Type"),
}
}
/// Iter 18e: `(data T)` with no `(drop-iterative)` clause parses
/// with `drop_iterative = false`. Default state must be the
/// pre-18e shape so legacy fixtures' canonical bytes are stable.
#[test]
fn parses_data_without_drop_iterative_defaults_to_false() {
let m = parse(
r#"
(module m
(data Tree
(vars a)
(ctor Leaf)
(ctor Node a (con Tree a) (con Tree a))))
"#,
)
.expect("parse should succeed");
match &m.defs[0] {
Def::Type(td) => {
assert!(!td.drop_iterative, "drop_iterative must default to false");
}
_ => panic!("expected Def::Type"),
}
}
/// Iter 18e: `(drop-iterative ...arg...)` is rejected — the
/// annotation is a flag with no payload.
#[test]
fn rejects_drop_iterative_with_arguments() {
let err = parse(
r#"
(module m
(data T
(ctor MkT)
(drop-iterative oops)))
"#,
)
.err()
.expect("parse should fail");
let msg = format!("{err}");
assert!(
msg.contains("drop-iterative takes no arguments"),
"diagnostic should explain drop-iterative's shape, got: {msg}"
);
}
/// Iter 16b.1: minimal `(let-rec ...)` round-trips through the
/// parser into a `Term::LetRec` whose `name`, `params`, `body` and
/// `in_term` line up with the source.