Files
AILang/examples/rc_match_arm_partial_drop_leak.ailx
T
Brummel caefcf996a codegen rc: tag-conditional partial-drop helper closes 3 carve-outs
Three sibling sites previously fell back to shallow `ailang_rc_dec`
when a binder had non-empty `moved_slots` and a dynamic runtime ctor
tag: Iter B Own-param dec at fn-return (lib.rs), Iter A arm-close
pattern-binder dec (match_lower.rs), and emit_inlined_partial_drop's
non-Ctor branch (drop.rs). Shallow freed the outer cell but did not
walk the unmoved ptr fields — silent leak.

Adds `emit_partial_drop_fn_for_type`: a per-ADT helper structurally
parallel to `emit_drop_fn_for_type` but taking an i64 moved-slots
bitmask. Each ptr-field's dec is gated on the corresponding mask
bit; the outer box is dec'd at join. Emitted alongside the per-type
drop fn for every ADT under --alloc=rc.

Three call sites rewritten to resolve the partial_drop symbol from
the binder's static type and build the mask from `moved_slots`.

Three RED-then-GREEN fixtures (rc_own_param_/rc_match_arm_/
rc_app_let_partial_drop_leak) pin the carve-outs in regression
coverage; live cell counts go from 3/1/3 (pre-fix) to 0/0/0.
e2e count: 66 → 69.

The pre-existing `alloc_rc_own_param_dec_at_fn_return` IR-shape
assertion is widened to accept `partial_drop_<m>_<T>(%arg_xs,
i64 mask)` as a third valid drop shape (the canonical fu2 case).

Closes the JOURNAL queue's only remaining iter; the "wait for
organic fixture" stance from the 18g tidy is retracted — known
leaks are bugs, CLAUDE.md's TDD rule covers them autonomously.
2026-05-08 16:25:40 +02:00

72 lines
2.2 KiB
Plaintext

; Iter 18g.tidy.fu2 RED-test fixture 2/3: dynamic-tag partial-drop
; carve-out at outer-arm-close pattern-binder dec
; (Iter A / match_lower.rs:839).
;
; Shape:
; - `Pair` carries two `Cell` fields; `Cell` carries two `Wrap`
; fields. (Same types as fixture 1.)
; - `use_first` takes `(own Pair)` and the OUTER match binds both
; slots: `MkPair a b`.
; - The arm body INNER-matches `a` as `MkCell w1 _` — slot 0 of
; a is bound, slot 1 is wildcarded.
; - Inner arm-close drops `w1` properly via `drop_<m>_Wrap`.
; Inner match adds slot 0 to `moved_slots[a]`.
; - Outer arm-close drops `a` and `b`. `a` has `moves={0}`
; (non-empty) and `consume_count==0` (only Borrow use as
; scrutinee of inner match) → Iter A's dynamic-tag carve-out
; fires: shallow `ailang_rc_dec(a)`. a's outer Cell is freed,
; but slot 1 (the unbound MkWrap(2) cell) leaks.
; - `b` has `moves={}` and `consume_count==0` → routes through
; `drop_<m>_Cell(b)` which cascades through both Wraps. No
; leak from b.
; - At fn return, `p`'s `moves={0,1}` (both slots bound) → Iter
; B carve-out fires too, but with all slots moved, the shallow
; dec is correct (no additional leak from p).
;
; Pre-fix: live = 1 (the unbound MkWrap(2) cell in slot 1 of a).
; Post-fix: live = 0.
(module rc_match_arm_partial_drop_leak
(data Wrap
(ctor MkWrap (con Int)))
(data Cell
(ctor MkCell (con Wrap) (con Wrap)))
(data Pair
(ctor MkPair (con Cell) (con Cell)))
(fn build_pair
(type
(fn-type
(params (con Int))
(ret (own (con Pair)))))
(params n)
(body
(term-ctor Pair MkPair
(term-ctor Cell MkCell
(term-ctor Wrap MkWrap n)
(term-ctor Wrap MkWrap 2))
(term-ctor Cell MkCell
(term-ctor Wrap MkWrap 3)
(term-ctor Wrap MkWrap 4)))))
(fn use_first
(type
(fn-type
(params (own (con Pair)))
(ret (con Int))))
(params p)
(body
(match p
(case (pat-ctor MkPair a b)
(match a
(case (pat-ctor MkCell w1 _) 1))))))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(do io/print_int (app use_first (app build_pair 1))))))