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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user