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:
@@ -800,6 +800,16 @@ impl<'a> Emitter<'a> {
|
||||
} else {
|
||||
self.emit_drop_fn_for_type(td);
|
||||
}
|
||||
// Iter 18g.tidy.fu2: tag-conditional partial-drop
|
||||
// helper alongside the recursive/iterative drop fn.
|
||||
// Used at carve-out sites where a binder's runtime
|
||||
// tag is dynamic and `moved_slots` is a strict
|
||||
// subset of its ptr fields. Same shape regardless
|
||||
// of `(drop-iterative)` (the helper is single-shot
|
||||
// on the binder; field cascades go through their
|
||||
// own drop fns which themselves choose recursive
|
||||
// vs iterative).
|
||||
self.emit_partial_drop_fn_for_type(td);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1085,14 +1095,30 @@ impl<'a> Emitter<'a> {
|
||||
" call void @{drop_call}(ptr {p_ssa})\n"
|
||||
));
|
||||
} else {
|
||||
// Iter 18d.4 debt: dynamic-tag partial-drop —
|
||||
// see the symmetric arm-close branch in
|
||||
// `lower_match` for the rationale. Falls back
|
||||
// to shallow `ailang_rc_dec` of the outer
|
||||
// cell.
|
||||
self.body.push_str(&format!(
|
||||
" call void @ailang_rc_dec(ptr {p_ssa})\n"
|
||||
));
|
||||
// Iter 18g.tidy.fu2: dynamic-tag partial-drop
|
||||
// via the per-type helper. The param's runtime
|
||||
// tag is dynamic but its static type is known
|
||||
// (`pty_ail`), so we route through
|
||||
// `partial_drop_<owner>_<T>(p, mask)` which
|
||||
// dispatches on the runtime tag and dec's only
|
||||
// the unmoved fields. The fallback to shallow
|
||||
// `ailang_rc_dec` only fires for non-ADT param
|
||||
// types (Str, fn-typed, vars) — none of which
|
||||
// can populate `moved_slots` in practice, so
|
||||
// the fallback is dead under the typechecker.
|
||||
let sym = Self::partial_drop_symbol_for_type(
|
||||
self, pty_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 {p_ssa}, i64 {mask})\n"
|
||||
));
|
||||
} else {
|
||||
self.body.push_str(&format!(
|
||||
" call void @ailang_rc_dec(ptr {p_ssa})\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user