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.
This commit is contained in:
@@ -821,24 +821,28 @@ impl<'a> Emitter<'a> {
|
||||
" call void @{drop_call}(ptr {b_ssa})\n"
|
||||
));
|
||||
} else {
|
||||
// Iter 18d.4 debt: pattern-binder with
|
||||
// statically-recorded moved slots requires
|
||||
// tag-conditional partial-drop emission. The
|
||||
// 18d.3 `emit_inlined_partial_drop` helper
|
||||
// assumes a static ctor (it's keyed against a
|
||||
// `Term::Ctor` node), but a pattern-bound
|
||||
// binder's runtime tag is dynamic. Until a
|
||||
// dynamic-tag-aware partial-drop lands, fall
|
||||
// back to a shallow `ailang_rc_dec` of the
|
||||
// outer cell. The non-moved fields of the
|
||||
// active ctor (if any) leak under this path —
|
||||
// the canonical 18d.4 fixtures avoid this
|
||||
// case by construction (the regression `t`
|
||||
// is never re-scrutinised inside the arm,
|
||||
// so its `moved_slots` entry is empty).
|
||||
self.body.push_str(&format!(
|
||||
" call void @ailang_rc_dec(ptr {b_ssa})\n"
|
||||
));
|
||||
// Iter 18g.tidy.fu2: pattern-binder with
|
||||
// statically-recorded moved slots routes
|
||||
// through the tag-conditional helper
|
||||
// `partial_drop_<owner>_<T>(b, mask)`. The
|
||||
// binder's runtime tag is dynamic but its
|
||||
// static type (`b_ail`) selects the per-type
|
||||
// helper, which dispatches on the runtime tag
|
||||
// and dec's only the unmoved fields. Closes
|
||||
// the 18d.4 carve-out: previously the shallow
|
||||
// `ailang_rc_dec` leaked the unmoved fields of
|
||||
// whichever ctor `b` happened to be at runtime.
|
||||
let sym = self.partial_drop_symbol_for_type(b_ail);
|
||||
let mask = Self::build_moved_mask(&moves);
|
||||
if let (Some(sym), Some(mask)) = (sym, mask) {
|
||||
self.body.push_str(&format!(
|
||||
" call void @{sym}(ptr {b_ssa}, i64 {mask})\n"
|
||||
));
|
||||
} else {
|
||||
self.body.push_str(&format!(
|
||||
" call void @ailang_rc_dec(ptr {b_ssa})\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user