Found while building the #311 repro. select_with_stack's If/Else/End lowering tracks the operand stack textually through both arms with no checkpoint at If, no restore at Else, and no reconciliation move at End. The two arms' result registers agree only when both arms happen to consume identical temp counts.
Failing shape (i64-heavy then-arm vs trivial else-arm — the widths diverge the temp counter):
(if (result i32)
(i32.eq (i32.wrap_i64 (i64.and (local.get $r) (i64.const 255))) (i32.const 1))
(then (i32.wrap_i64 (i64.shr_u (local.get $r) (i64.const 32))))
(else (i32.const 0xDEAD)))
Observed codegen (v0.11.35 + #311 fix): then-arm leaves its result in r2, else-arm materializes 0xDEAD into r3, and the End/return path reads r3 unconditionally — the then-path returns garbage. Repro: take scripts/repro/u64_unpack.wat, replace the select with the if-form above; check_call(3,4) = 0 instead of 8.
Why nothing caught it before: the frozen fixtures' if/else sites are register-symmetric; gale's production seams use select (IT-blocks). The class is latent in any module where loom emits branching conditionals with asymmetric arms.
Fix sketch: checkpoint the vstack + temp counter at If; at Else, pop the then-result (remember its register R_then) and restore the checkpoint; at End-with-result, emit mov R_then, R_else as the else-arm's last instruction and push R_then. Needs the block result arity from the If's blocktype (decoder currently drops it — same threading pattern as #311's result types).
Severity: silent wrong-code (same class as #311) but no known production hit yet. Should ride the queue ahead of perf work.
Found while building the #311 repro.
select_with_stack's If/Else/End lowering tracks the operand stack textually through both arms with no checkpoint atIf, no restore atElse, and no reconciliation move atEnd. The two arms' result registers agree only when both arms happen to consume identical temp counts.Failing shape (i64-heavy then-arm vs trivial else-arm — the widths diverge the temp counter):
Observed codegen (v0.11.35 + #311 fix): then-arm leaves its result in
r2, else-arm materializes0xDEADintor3, and the End/return path readsr3unconditionally — the then-path returns garbage. Repro: takescripts/repro/u64_unpack.wat, replace theselectwith the if-form above;check_call(3,4)= 0 instead of 8.Why nothing caught it before: the frozen fixtures' if/else sites are register-symmetric; gale's production seams use
select(IT-blocks). The class is latent in any module where loom emits branching conditionals with asymmetric arms.Fix sketch: checkpoint the vstack + temp counter at
If; atElse, pop the then-result (remember its register R_then) and restore the checkpoint; atEnd-with-result, emitmov R_then, R_elseas the else-arm's last instruction and push R_then. Needs the block result arity from the If's blocktype (decoder currently drops it — same threading pattern as #311's result types).Severity: silent wrong-code (same class as #311) but no known production hit yet. Should ride the queue ahead of perf work.