A hot loop that inlines a callee whose body branches compiles no bridge for the rare arm. Every crossing deopts to the blackhole, so the loop runs ~285x slower than it does when a bridge exists.
Probe:
def helper(i):
if i % 7 == 0:
return i * 2
return i + 1
def main():
s = 0; i = 0
while i < 3000000:
s = s + helper(i)
i = i + 1
print(s) # 5142858428570
main()
Measured on 6552f3cd55, both backends, machine idle:
| config |
bridges |
aborts |
guard failures |
time |
default (p2_drain ON) |
0 |
2142 |
428544 |
11.43s |
PYRE_P2_DRAIN=0 |
1 |
1 |
201 |
0.04s |
Output is correct in both; this is a performance gap, not a wrong answer.
Why it is not just a default flip
pyre-jit-trace/src/trace.rs states the framestack-walk cross-frame bridge is "correctness-buggy for a branchy inlined-callee continuation", kept behind PYRE_P2_DRAIN=0 pending the orthodox multi-frame reconstruction. #538 made the drain the default, and the drain is a blackhole safety floor — it discards the trace rather than compiling.
The gating is load-bearing. With the drain off, #538’s own regression guards miscompile on both backends:
| config |
synth/inline_multiframe_module_branch_deopt (ref 7215477652) |
synth/inline_multiframe_drain_journaled_store (ref 7215477652 120000) |
probe bridge |
| default |
OK |
OK |
none |
PYRE_P2_DRAIN=0 |
7215474836 |
7215474836 |
yes |
PYRE_P2_COMPILE=1 |
OK |
120299 ≠ 120000 |
none, stops at RunPerfnWalkNone |
No configuration is both correct and fast. PYRE_P2_COMPILE=1 over-counts a journaled store by 299 — a committed effect re-applied on resume, the same family as the trace-abort double-execution hazard.
Relationship to #577 and #343
Acceptance test
pyre/bench/bridge_branchy_callee_regression.py (committed, deliberately not wired into check.py — it fails today and would turn the suite red). It times a branchy-callee loop against a straight-line-callee loop in the same process, so the ratio is immune to machine load, and fails above 20x. It currently measures ~2000x on dynasm and ~4000x on cranelift. Wire it into check.py via run_selfcheck when this closes.
Note that check.py cannot otherwise see this class of gap: output stays correct and the loop finishes well inside every wall-clock gate.
— filed by Claude
A hot loop that inlines a callee whose body branches compiles no bridge for the rare arm. Every crossing deopts to the blackhole, so the loop runs ~285x slower than it does when a bridge exists.
Probe:
Measured on
6552f3cd55, both backends, machine idle:p2_drainON)PYRE_P2_DRAIN=0Output is correct in both; this is a performance gap, not a wrong answer.
Why it is not just a default flip
pyre-jit-trace/src/trace.rsstates the framestack-walk cross-frame bridge is "correctness-buggy for a branchy inlined-callee continuation", kept behindPYRE_P2_DRAIN=0pending the orthodox multi-frame reconstruction. #538 made the drain the default, and the drain is a blackhole safety floor — it discards the trace rather than compiling.The gating is load-bearing. With the drain off, #538’s own regression guards miscompile on both backends:
synth/inline_multiframe_module_branch_deopt(ref7215477652)synth/inline_multiframe_drain_journaled_store(ref7215477652 120000)PYRE_P2_DRAIN=0PYRE_P2_COMPILE=1120299≠120000RunPerfnWalkNoneNo configuration is both correct and fast.
PYRE_P2_COMPILE=1over-counts a journaled store by 299 — a committed effect re-applied on resume, the same family as the trace-abort double-execution hazard.Relationship to #577 and #343
main— see the correction comment there. It does not fix the defect; the code is kept for whoever lands the reconstruction.n_recipes=1), so it is related but not the same scope.trace.rsnames JIT: recover deep-unroll — depth≥2 middle-frame virtual PyFrame rematerialization (walker-arch rework) #343 as the pending fix for the framestack path; whether depth-1 needs the full JIT: recover deep-unroll — depth≥2 middle-frame virtual PyFrame rematerialization (walker-arch rework) #343 rework or a bounded fix is an open question.Acceptance test
pyre/bench/bridge_branchy_callee_regression.py(committed, deliberately not wired intocheck.py— it fails today and would turn the suite red). It times a branchy-callee loop against a straight-line-callee loop in the same process, so the ratio is immune to machine load, and fails above 20x. It currently measures ~2000x on dynasm and ~4000x on cranelift. Wire it intocheck.pyviarun_selfcheckwhen this closes.Note that
check.pycannot otherwise see this class of gap: output stays correct and the loop finishes well inside every wall-clock gate.— filed by Claude