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: a throw whose value is CONSTRUCTED stays on the generic path (7.99 vs 0.95 ns) — needs a GC-safety argument, not just admission #9232
Follow-up to #9210 / #9230. Recording a measured 8.4× opportunity together with the reason I did not take it, so the next person starts from the hazard rather than rediscovering it.
Every CONSTRUCTED operand lands on exactly 7.99 — one shared generic path, and the Error-ness is irrelevant (a bare string concat behaves identically). Node shows the same cliff, so we are at parity here rather than behind; the prize is that 0.95 is achievable and node cannot reach it.
Why it is not just an admission tweak
#9185 admitted exactly these shapes and produced a silent wrong answer. #9230 fixed the writeback half of that, so the accumulator staleness is no longer the blocker. The remaining question is one #9185 never asked:
Block::contains_gc_unsafe_call (crates/perry-codegen/src/block.rs:299) exempts a block ending in unreachable:
That is the codebase sanctioning "control never returns, so a pointer the collector moved is never dereferenced" — the reasoning #9185 quoted. But it covers only the TERMINATING block. A constructed operand is lowered into blocks that PRECEDE it and end in ordinary branches, so the exemption does not reach them, and those blocks contain the allocating call.
The packed-f64 versioned loop does not run that scan at all (it is used by the class-field loop at loops.rs:4669 and :4167), so its safety rests entirely on the admission predicates. That is why the wrong answer got through: nothing downstream was going to catch it.
What a fix has to establish
Not "is the throw block safe" — that part is already argued — but: the fast clone caches the array data pointer, and an allocation between the cache and the unwind can trigger a moving collection. Someone has to show either that the cached pointer is dead from the allocation onward (plausible: the loop never resumes, and #9230's flush already stores the accumulators before the throw), or that it is properly rooted across it.
I did not attempt this. It is the [[cc-8770]] bug class — a bare pointer held across an allocation — which took eighteen sessions to root-cause the last time, and 3.4% against node is not the reason to re-enter it at the end of a long session. It is worth a design pass on its own terms, where the payoff is 8.4× and beating node on a very common validation shape rather than merely matching it.
PERRY_PACKED_LOOP_TRACE=1 (#9204) names the rejecting gate if you pick this up.
Activity
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 (…