fix: 18g.1 — pre-tail-call shallow-dec for moved-from scrutinee outer cell
18d.4 Iter A (arm-close pattern-binder dec) and Iter B (Own-param
dec at fn return) both fire AFTER the arm body lowers; for a tail-
call arm body, lower_term emits 'musttail call ... ret' and sets
block_terminated=true, so neither seam fires. The result: the
scrutinee's outer cell (whose ptr fields were all moved into the
tail-call's args via 18d.3 moved_slots) leaks one cell per
recursion step.
Surfaced by 18f.2's tail-latency bench: explicit-mode RC peaked
at the same 511 MB RSS as implicit-mode RC despite full mode
annotations — the per-op IntList chains were never being freed.
Bencher diagnosed the tail-call drop-elision; this iter implements
the fix.
Fix shape: in lower_match, BEFORE lower_term(arm.body), emit
'call void @ailang_rc_dec(ptr <scrutinee_ssa>)' iff:
- alloc=Rc,
- arm.body is structurally Term::App{tail:true} or
Term::Do{tail:true},
- scrutinee_is_owned (existing 18d.4 Iter A gate, hoisted),
- every ptr-typed slot in this ctor's pattern is in
moved_slots[scrutinee] (i.e. all ptr children are moved into
binders that own them downstream — a Wild-bound ptr would
still hold a live ref that the shallow free would strand).
Shallow rc_dec, not field_drop_call: the per-type drop fn would
re-walk ptr fields, dec'ing values now owned by the downstream
frame — exactly the bug 18d.3's moved_slots was set up to prevent.
Hoists scrutinee_is_owned to a single declaration shared by both
the new 18g.1 emission and 18d.4 Iter A.
Red: alloc_rc_explicit_mode_tail_sum_does_not_leak_outer_cells
on examples/rc_tail_sum_explicit_leak — 100 LCons cells leaked
pre-fix, 0 post-fix. Verified end-to-end on
bench_latency_explicit: pre-fix live=11068575,
post-fix live=1048575 (= the persistent tree cache, separate
issue at main-scope-close).
Test infrastructure: build_and_run_with_rc_stats helper +
AILANG_RC_STATS=1 env-var for the runtime atexit summary.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"defs":[{"ctors":[{"fields":[],"name":"LNil"},{"fields":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"name":"LCons"}],"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":[{"args":[{"name":"n","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"},{"name":"acc","t":"var"}],"ctor":"LCons","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app","tail":true},"t":"if","then":{"name":"acc","t":"var"}},"kind":"fn","name":"cons_n_acc","params":["n","acc"],"type":{"effects":[],"k":"fn","param_modes":["implicit","own"],"params":[{"k":"con","name":"Int"},{"k":"con","name":"IntList"}],"ret":{"k":"con","name":"IntList"},"ret_mode":"own"}},{"body":{"args":[{"name":"n","t":"var"},{"args":[],"ctor":"LNil","t":"ctor","type":"IntList"}],"fn":{"name":"cons_n_acc","t":"var"},"t":"app"},"kind":"fn","name":"cons_n","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"IntList"},"ret_mode":"own"}},{"body":{"arms":[{"body":{"name":"acc","t":"var"},"pat":{"ctor":"LNil","fields":[],"p":"ctor"}},{"body":{"args":[{"name":"t","t":"var"},{"args":[{"name":"acc","t":"var"},{"name":"h","t":"var"}],"fn":{"name":"+","t":"var"},"t":"app"}],"fn":{"name":"sum_acc","t":"var"},"t":"app","tail":true},"pat":{"ctor":"LCons","fields":[{"name":"h","p":"var"},{"name":"t","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"xs","t":"var"},"t":"match"},"kind":"fn","name":"sum_acc","params":["xs","acc"],"type":{"effects":[],"k":"fn","param_modes":["own","implicit"],"params":[{"k":"con","name":"IntList"},{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"name":"xs","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"sum_acc","t":"var"},"t":"app"},"kind":"fn","name":"sum_list","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":100},"t":"lit"}],"fn":{"name":"cons_n","t":"var"},"t":"app"}],"fn":{"name":"sum_list","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_tail_sum_explicit_leak","schema":"ailang/v0"}
|
||||
@@ -0,0 +1,69 @@
|
||||
; Iter 18g.1 RED-test fixture: minimal tail-recursive list-sum
|
||||
; under explicit-mode + (drop-iterative). Demonstrates the leak
|
||||
; surfaced by the 18f.2 latency bench: the LCons outer cell, whose
|
||||
; only ptr field `t` is moved into the tail-app of `sum_acc`, has
|
||||
; no drop site and leaks once per element.
|
||||
;
|
||||
; Built and run with `AILANG_RC_STATS=1` under `--alloc=rc`. The
|
||||
; stderr summary should show `allocs == frees + small_const`,
|
||||
; where `small_const` covers the Tree-shaped drop-frames (zero
|
||||
; here — the program returns Int, no live ADTs at exit).
|
||||
;
|
||||
; Pre-fix: `live = N` (one outer LCons cell per consumed element).
|
||||
; Post-fix: `live = 0`.
|
||||
|
||||
(module rc_tail_sum_explicit_leak
|
||||
|
||||
(data IntList
|
||||
(ctor LNil)
|
||||
(ctor LCons (con Int) (con IntList))
|
||||
(drop-iterative))
|
||||
|
||||
(fn cons_n_acc
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int) (own (con IntList)))
|
||||
(ret (own (con IntList)))))
|
||||
(params n acc)
|
||||
(body
|
||||
(if (app == n 0)
|
||||
acc
|
||||
(tail-app cons_n_acc
|
||||
(app - n 1)
|
||||
(term-ctor IntList LCons (app - n 1) acc)))))
|
||||
|
||||
(fn cons_n
|
||||
(type
|
||||
(fn-type
|
||||
(params (con Int))
|
||||
(ret (own (con IntList)))))
|
||||
(params n)
|
||||
(body
|
||||
(app cons_n_acc n (term-ctor IntList LNil))))
|
||||
|
||||
(fn sum_acc
|
||||
(type
|
||||
(fn-type
|
||||
(params (own (con IntList)) (con Int))
|
||||
(ret (con Int))))
|
||||
(params xs acc)
|
||||
(body
|
||||
(match xs
|
||||
(case (pat-ctor LNil) acc)
|
||||
(case (pat-ctor LCons h t)
|
||||
(tail-app sum_acc t (app + acc h))))))
|
||||
|
||||
(fn sum_list
|
||||
(type
|
||||
(fn-type
|
||||
(params (own (con IntList)))
|
||||
(ret (con Int))))
|
||||
(params xs)
|
||||
(body
|
||||
(app sum_acc xs 0)))
|
||||
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(do io/print_int (app sum_list (app cons_n 100))))))
|
||||
Reference in New Issue
Block a user