fix(codegen): drop a let-bound owned loop result at scope close
An owned heap value whose `let`-binding value is a `(loop ...)` result, afterwards only borrow-read (or unused), was never dropped at scope close — a memory leak (AILANG_RC_STATS live=1). Newly observable once #47 made fill-loops build: rawbuf_2_running_max leaked one buffer per run. Root cause: is_rc_heap_allocated — the predicate the let-lowering uses to decide a scope-close `dec` — matched only Term::Ctor, Term::Lam and Own-returning Term::App. A Term::Loop-valued binder fell through to false, so it was never registered for drop. (Sibling of the #47 synth gap: the Term::Loop arm under-handled in yet another pass.) Fix: add a Term::Loop arm that tracks the binder iff the loop's static result type lowers to llvm `ptr` (boxed/heap) AND is not Str — and widen drop_symbol_for_binder's App arm to cover Term::Loop so it resolves the per-type drop symbol. Two load-bearing safety gates: - the `ptr` gate excludes unboxed primitives (Int/Bool/Float/Unit), so an Int-returning loop is not handed to a drop fn; - Str is excluded because a loop can return a *static* Str (a seed literal threaded unchanged) and ailang_rc_dec on a static-Str pointer is UB; an Own-App can never return a static Str, which is why the App arm may track Str but the Loop arm must not. The loop's seed binders are consumed (moved in), so nothing else tracks the result; linearity guarantees no alias, so the new drop is single. Verified leak-clean and double-free-free across the fieldtest RawBuf set, the consumed case, the Int-gate case, and the escape case (a fn returning a loop-built owned RawBuf to its caller). The separate per-iteration leak of superseded heap loop-binder values (e.g. a Str accumulator threaded through recur) is a distinct, broader pre-existing bug, filed separately — not addressed here. RED-first: raw_buf_loop_no_leak_pin.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
(module raw_buf_loop_no_leak_pin
|
||||
(fn main
|
||||
(doc "RED-pin fixture for the loop-valued let-binder scope-close drop gap. Under --alloc=rc, an owned `(RawBuf Int)` whose let-value is a `(loop ...)` result and which is afterwards only borrow-read (RawBuf.get) is NEVER dropped at scope close. `is_rc_heap_allocated` (crates/ailang-codegen/src/drop.rs) has no `Term::Loop` arm, so the `Term::Let` lowering never flags the binder `filled` as trackable and emits no drop. AILANG_RC_STATS=1 reports `allocs=2 frees=1 live=1` (the RawBuf slab leaks). Compare examples/raw_buf_drop_min.ail (direct `(new ...)` value, no loop) which is dropped correctly. Expected post-fix: `allocs == frees && live == 0`.")
|
||||
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||
(params)
|
||||
(body
|
||||
(let buf (new RawBuf (con Int) 4)
|
||||
(let filled (loop (b (con RawBuf (con Int)) buf) (i (con Int) 0)
|
||||
(if (app ge i 4) b (recur (app RawBuf.set b i i) (app + i 1))))
|
||||
(app print (app RawBuf.get filled 0)))))))
|
||||
Reference in New Issue
Block a user