You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Packed-loop clone does not flush loop-carried locals on an unwind edge (regression from #9185) #9210
#9185 admitted Stmt::Throw to the packed-f64 versioned loop. That is a silent wrong answer on main today, and I am reverting the admission in the linked PR. This issue records what would be needed to re-admit it.
The defect
break, continue and return leave the fast clone through normal CFG edges, and those edges flush the loop-carried locals back to their frame slots. An unwind edge does not. So any local the loop wrote reads back stale after the throw:
The 0.5.1220 release (pre-#9185) also gives 780, confirming the regression.
Why the existing tests passed
#9185 shipped two taken-throw tests and both are blind to this. throwPre reads the thrown value and an untouched parameter; throwValue throws s itself. Both observe the clone's live SSA value, which is correct — never the frame slot left behind. The closure row above is correct for the same underlying reason from the other side: a captured accumulator is boxed rather than register-promoted, so there is no promoted copy to lose.
The general rule this is the second instance of: an abrupt-exit optimisation is only tested by an exit that is actually taken AND a subsequent read of something the loop wrote.#9154's labeled-break bug had the identical shape.
To re-admit
Emit the loop-carried writeback at the throw site rather than relying on the exit block, i.e. flush promoted locals immediately before the throw is emitted. The admission predicate is not the missing piece; the flush is. Worth confirming the same edge is handled for any other construct that can leave a clone without passing through its exit block.
Fixed in #9230. The flush is emitted at the throw site via flush_packed_accumulator_locals, covering both the DOUBLE-alloca (+=) and i32-slot (c++) accumulator representations. Measured 7.99 → 0.95 ns/op on the throw shape against node's 1.10, and the regression tests were confirmed to fail with the flush disabled, so they guard the fix rather than sit beside it.
Activity
proggeramlug commentedon Aug 30, 2026
Fixed in #9230. The flush is emitted at the throw site via
flush_packed_accumulator_locals, covering both theDOUBLE-alloca (+=) and i32-slot (c++) accumulator representations. Measured 7.99 → 0.95 ns/op on the throw shape against node's 1.10, and the regression tests were confirmed to fail with the flush disabled, so they guard the fix rather than sit beside it.fix(codegen): a taken throw out of a packed loop no longer loses loop…
fix(codegen): a taken throw out of a packed loop no longer loses loop…
perf(codegen): flush packed accumulators at the throw site, restoring…
perf(codegen): flush packed accumulators at the throw site, restoring…
perf(codegen): flush packed accumulators at the throw site, restoring…
perf(codegen): constructed throw operands keep the packed fast path (…
perf(codegen): constructed throw operands keep the packed fast path (…
perf(codegen): constructed throw operands keep the packed fast path (…