fix: let-alias-aware mode propagation through Term::Let
Closes the carve-out shared by 18d.4 Iter A and 18g.1's pre-tail-
call seam: a let-binder whose value is Term::Var aliasing a non-
Own fn-param now inherits the param's mode in current_param_modes
for the duration of the let body. Without this, (let a t (match
a ...)) where t is Implicit / Borrow defeated scrutinee_is_owned
(default for non-fn-param scrutinees was 'owned'), and the arm-
close drop fired on pattern-binders whose underlying memory
belongs to the caller — refcount underflow / SIGSEGV on the next
recursion.
Implementation: Term::Let lowering checks if value is Term::Var
referencing a name in current_param_modes; if so, inserts that
mode under the let-binder's name for the body and restores on
let-close (push/pop pattern, symmetric with locals).
Red:
alloc_rc_let_alias_of_implicit_param_does_not_dec_borrowed_children
on examples/rc_let_alias_implicit_param. Pre-fix: SIGSEGV / RC
underflow on the second iteration of loop. Post-fix: matches
alloc=gc ('0').
The Borrow-ret-mode case is already covered by the language-design
rule (typechecker rejects 'consume-while-borrowed' on the
borrow-aliasing shape); the Implicit-mode case is what the carve-
out actually surfaces. Both are now closed at codegen-time.
Two of the three known debts from the 18g tidy now resolved
(stat-of-N earlier in the session, let-alias here). The remaining
debt — tag-conditional partial-drop helper for the dynamic-tag
carve-outs in lower_match and emit_inlined_partial_drop — is the
substantively larger piece and stays queued.
This commit is contained in:
@@ -2201,3 +2201,41 @@ fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() {
|
||||
means the unjustified dec triggered the underflow guard."
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 18g tidy follow-up: let-alias-aware mode propagation.
|
||||
///
|
||||
/// 18d.4 Iter A's fix gated arm-close pattern-binder dec on the
|
||||
/// scrutinee's `current_param_modes` lookup, but only fn-params
|
||||
/// register there. A let-binder whose value is `Term::Var` aliasing
|
||||
/// a non-Own fn-param defeats the gate: the lookup misses, the
|
||||
/// default treats it as owned, and the arm-close drop fires on
|
||||
/// pattern-binders whose underlying memory is still owned by the
|
||||
/// caller. The fixture's shape:
|
||||
///
|
||||
/// (fn pin_aliased (params t) ; t: Implicit-mode (default)
|
||||
/// (let a t ; a aliases t
|
||||
/// (match a ; matched-on alias
|
||||
/// (case (TLeaf) 0)
|
||||
/// (case (TNode v l r) 1))))
|
||||
///
|
||||
/// Pre-fix on a recursive caller passing the same heap value: the
|
||||
/// arm-close drop fragments the caller's tree, the recursive call
|
||||
/// re-loads now-freed cells, and the binary aborts (SIGSEGV / RC
|
||||
/// underflow).
|
||||
///
|
||||
/// Post-fix: `current_param_modes` propagates through `Term::Let
|
||||
/// { value: Term::Var(p), ... }` for the duration of the let body,
|
||||
/// so a let-aliased Implicit / Borrow scrutinee correctly skips
|
||||
/// the arm-close drop. Output matches `--alloc=gc` (`0`).
|
||||
#[test]
|
||||
fn alloc_rc_let_alias_of_implicit_param_does_not_dec_borrowed_children() {
|
||||
let example = "rc_let_alias_implicit_param.ail.json";
|
||||
let stdout_gc = build_and_run_with_alloc(example, "gc");
|
||||
let stdout_rc = build_and_run_with_alloc(example, "rc");
|
||||
assert_eq!(stdout_gc.trim(), "0");
|
||||
assert_eq!(stdout_rc.trim(), "0");
|
||||
assert_eq!(
|
||||
stdout_gc, stdout_rc,
|
||||
"alloc=rc must match alloc=gc on rc_let_alias_implicit_param"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user