Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions pyre/extra_tests/parity_tests/pickletools_optimize_bridge_abort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""A bridge walk that adopts a blackhole terminal must keep the frame's result.

``pickletools.optimize`` ends in a single ``return out.getvalue()``. Under the
JIT, the guard-failure bridge walk over its opcode loop can stop inside the
reconstructed callee, adopt the blackhole chain it has already driven to a
return, and then abort the trace. The adopted terminal carries that frame's
``DoneWithThisFrame`` value, which is the function's return value; dropping it
leaves the caller with neither a result nor a replay, so ``optimize`` returns
``None`` and ``pickle.loads`` raises ``EOFError: Ran out of input``.

Two rounds over the varying protocol/width matrix are required: the trace is
compiled during the first round and the guard fails in the second, and a fixed
input never reaches the failing shape.
"""

import pickle
import pickletools
import sys

for _round in range(2):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
n = sys.maxsize
while n:
for expected in (-n, n):
data = pickletools.optimize(pickle.dumps(expected, proto))
assert data is not None, (
f"optimize() returned None (proto={proto} n={expected})"
)
got = pickle.loads(data)
assert got == expected, (
f"round-trip gave {got!r}, want {expected!r} "
f"(proto={proto} n={expected})"
)
n = n >> 1

print("OK")
11 changes: 11 additions & 0 deletions pyre/pyre-jit-trace/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2048,6 +2048,17 @@ fn drive_bridge_carrier_walk<Sym: WalkSym>(
// A declined adopt leaves everything to the rollback below, which is the
// pre-existing behaviour.
let live_root_addr = sym.live_vable_frame_addr();
// Clear a stash the sub-walk left BEFORE adopting, not after. An adopted
// terminal stores the frame's `DoneWithThisFrame*` result here
// (`try_adopt_blackhole`), and that result IS this drain's answer: the
// guard's caller takes it as the bridge resolution
// (`fbw_finish_concrete_take` in `call_jit.rs`). Clearing after the adopt
// dropped it, and the drop is silent — the adopt also commits the walk-end
// state, so the caller neither finds a concrete nor replays through the
// blackhole, and the frame's return value reaches Python as `None`.
// Clearing first keeps the same protection against a stale stash while
// leaving whatever the adopt installs intact.
crate::jitcode_dispatch::fbw_finish_payload_reset();
let adopted = crate::jitcode_dispatch::fbw_executed_effect_count() != effects_at_entry
&& try_adopt_blackhole(ctx, cf_addr, live_root_addr, WalkEndCommitLeg::CarrierAbort);
if crate::jitcode_dispatch::fbw_debug_abort_enabled() {
Expand Down
Loading