Skip to content

JIT: no bridge for a guard failure inside an inlined branchy callee (depth-1); #577 is dormant behind the p2_drain default #584

Description

@youknowone

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 120299120000 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions