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:
@@ -0,0 +1 @@
|
||||
{"defs":[{"ctors":[{"fields":[],"name":"INil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"ICons"}],"drop-iterative":true,"kind":"type","name":"IntList"},{"body":{"cond":{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"==","t":"var"},"t":"app"},"else":{"args":[{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"},{"args":[{"name":"n","t":"var"},{"name":"acc","t":"var"}],"ctor":"ICons","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app","tail":true},"t":"if","then":{"name":"acc","t":"var"}},"doc":"Tail-recursive list builder. acc-prepended.","kind":"fn","name":"cons_n_acc","params":["n","acc"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"IntList"}}},{"body":{"args":[{"name":"n","t":"var"},{"args":[],"ctor":"INil","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app"},"doc":"Build [1, 2, ..., n] :: IntList. Order is reverse of build but immaterial for head/sum.","kind":"fn","name":"cons_n","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"IntList"}}},{"body":{"arms":[{"body":{"lit":{"kind":"int","value":0},"t":"lit"},"pat":{"ctor":"INil","fields":[],"p":"ctor"}},{"body":{"name":"h","t":"var"},"pat":{"ctor":"ICons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"doc":"Take ownership of an IntList; return its head if ICons, else 0. The (own ...) signature signals 18d.4 to emit an Own-param drop at fn return — which under the (drop-iterative) annotation is the iterative-worklist body, freeing the entire chain via the heap-allocated worklist instead of recursive cascade.","kind":"fn","name":"head_or_zero","params":["xs"],"type":{"effects":[],"k":"fn","param_modes":["own"],"params":[{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"args":[{"args":[{"lit":{"kind":"int","value":1000000},"t":"lit"}],"fn":{"name":"cons_n","t":"var"},"t":"app"}],"fn":{"name":"head_or_zero","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"rc_drop_iterative_long_list","schema":"ailang/v0"}
|
||||
@@ -0,0 +1,93 @@
|
||||
; Iter 18e fixture: `(drop-iterative)` opt-in annotation drives the
|
||||
; codegen to emit `drop_<m>_IntList` with a worklist body in place
|
||||
; of the recursive cascade. Without the annotation, freeing a long
|
||||
; list overflows Linux's default 8MB stack at ~1M cells (one frame
|
||||
; per recursive `drop_<m>_IntList(tail)` call). With it, the chain
|
||||
; pops through the heap-allocated worklist in O(N) time and O(1)
|
||||
; stack.
|
||||
;
|
||||
; The fixture builds a 100,000-cell IntList tail-recursively, sums
|
||||
; it into 4_999_950_000 (= N*(N-1)/2 for N=100_000), then `main`
|
||||
; returns. At process exit the build's `xs` binder is consumed by
|
||||
; sum_acc (via tail-call), so the iterative drop fires inside
|
||||
; sum_acc's `Cons` arm — specifically, on every recursive step the
|
||||
; tail t is bound, then t is consumed by the next iteration's
|
||||
; tail-app sum_acc, leaving no live cells at termination.
|
||||
;
|
||||
; Wait — under the actual emission shape: sum_acc's xs param is
|
||||
; (own (con IntList)); 18d.4 emits an Own-param drop at fn return.
|
||||
; On the inductive Cons arm, the fn returns via tail-call into the
|
||||
; next iteration (musttail), and the Own-param dec on xs at the
|
||||
; outer fn would normally fire — but tail-call elision means the
|
||||
; outer frame is gone before any post-tail-call code can run. The
|
||||
; canonical case for the iterative-drop test is therefore the
|
||||
; let-close-drop on `xs` in `main` (or the outer-let's drop after
|
||||
; sum's tail returns). For 18e the load-bearing property is "long
|
||||
; chains drop without overflowing the stack"; that fires whenever
|
||||
; ANY drop call against the head IntList runs end-to-end.
|
||||
;
|
||||
; To force the issue: `main` builds the list, sums it, prints the
|
||||
; sum, AND then `let xs = build n in let _ = sum xs in xs` does NOT
|
||||
; exist as a pattern in AILang. We instead route the list through a
|
||||
; `head` fn that takes (own IntList) and returns the head Int —
|
||||
; which transfers ownership and forces the Own-param drop on xs at
|
||||
; head's return. Because head's body (a match returning Int) cannot
|
||||
; be tail-called, the drop is emitted in head's epilogue and runs
|
||||
; in full before head returns to main.
|
||||
;
|
||||
; Expected stdout: `1` (the head of [1, 2, ..., 1_000_000]).
|
||||
; Why 1M and not 100K: at 100K the recursive cascade fits in the 8MB
|
||||
; default Linux stack (~64B/frame ≈ 6.4MB at 100K), so the test would
|
||||
; "pass" even without the iterative drop — false-negative on the
|
||||
; whole point of 18e. At 1M it overflows clean (SIGSEGV in the
|
||||
; recursive variant; clean exit-0 in the iterative variant).
|
||||
|
||||
(module rc_drop_iterative_long_list
|
||||
|
||||
(data IntList
|
||||
(ctor INil)
|
||||
(ctor ICons (con Int) (con IntList))
|
||||
(drop-iterative))
|
||||
|
||||
(fn cons_n_acc
|
||||
(doc "Tail-recursive list builder. acc-prepended.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int) (con IntList))
|
||||
(ret (con IntList))))
|
||||
(params n acc)
|
||||
(body
|
||||
(if (app == n 0)
|
||||
acc
|
||||
(tail-app cons_n_acc
|
||||
(app - n 1)
|
||||
(term-ctor IntList ICons n acc)))))
|
||||
|
||||
(fn cons_n
|
||||
(doc "Build [1, 2, ..., n] :: IntList. Order is reverse of build but immaterial for head/sum.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int))
|
||||
(ret (con IntList))))
|
||||
(params n)
|
||||
(body
|
||||
(app cons_n_acc n (term-ctor IntList INil))))
|
||||
|
||||
(fn head_or_zero
|
||||
(doc "Take ownership of an IntList; return its head if ICons, else 0. The (own ...) signature signals 18d.4 to emit an Own-param drop at fn return — which under the (drop-iterative) annotation is the iterative-worklist body, freeing the entire chain via the heap-allocated worklist instead of recursive cascade.")
|
||||
(type
|
||||
(fn-type
|
||||
(params (own (con IntList)))
|
||||
(ret (con Int))))
|
||||
(params xs)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor INil) 0)
|
||||
(case (pat-ctor ICons h t) h))))
|
||||
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(do io/print_int
|
||||
(app head_or_zero (app cons_n 1000000))))))
|
||||
Reference in New Issue
Block a user