Files
AILang/examples/tail_recur_owned_param_no_leak_pin.ail
T
Brummel 1be6e49b4f test(codegen): RED pin for owned-param leak in tail-recursion (#63)
An owned heap ADT param threaded through a tail-recursive fn whose
base-case arm neither forwards nor consumes it is never RC-dec'd, so
it leaks one slab per recursion step. Series-free minimal repro
(a plain `Box` ADT, no Series/RawBuf), so the pin guards the GENERAL
memory-model invariant rather than the Series surface that first
surfaced it (examples/series_sma.ail, live=8).

The pin builds the fixture with --alloc=rc, runs it under
AILANG_RC_STATS=1, and asserts live == 0 (allocs == frees). RED today:
`allocs=6 frees=4 live=2`. The fixture passes the replacement ctor
directly into the tail call (not let-bound) to isolate leg 1 — the
owned-param drop omission — from the second, distinct leg the
debugger found (a `let`-wrapped tail call also defeats the
scrutinee-husk shallow-dec gate; that leg is what pushes box_ctrl to
live=4 and series_sma to live=8, and gets its own pin once leg 1 is
green).

Cause (debugger, for the GREEN side): the fn-return Own-param dec in
crates/ailang-codegen/src/lib.rs (~1444) is gated behind
`!block_terminated`, so it fires only on the fall-through ret path;
crates/ailang-codegen/src/match_lower.rs (~805) drops only pattern
binders, never the fn's owned params. A tail-call-terminated match arm
thus has no drop site for an owned param with consume_count==0 that is
not forwarded into the tail call.

refs #63
2026-06-02 13:31:54 +02:00

18 lines
691 B
Plaintext

(module tail_recur_owned_param_no_leak_pin
(data Box (ctor B (con Int)))
(data IntList (ctor INil) (ctor ICons (con Int) (con IntList)))
(fn run
(type (fn-type (params (own (con Box)) (own (con IntList))) (ret (own (con Unit))) (effects IO)))
(params b input)
(body
(match input
(case (pat-ctor INil) (do io/print_str "done\n"))
(case (pat-ctor ICons v rest)
(tail-app run (term-ctor Box B v) rest)))))
(fn main
(type (fn-type (params) (ret (own (con Unit))) (effects IO)))
(params)
(body
(let b (term-ctor Box B 0)
(app run b (term-ctor IntList ICons 1 (term-ctor IntList ICons 2 (term-ctor IntList INil))))))))