From 84a2161b74549e85afff030f3dc1d1d5dd2ed1fe Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Wed, 5 Aug 2026 09:18:20 +0900 Subject: [PATCH] jit: delete the exc_edge_bridge_enabled gate and the call-site exception prologue it guarded The gate returned a constant `true`, so `call_jit.rs`'s `if !exc_edge_bridge_enabled()` prologue was unreachable: it read the backend exception cells and emitted a snapshot-less GUARD_EXCEPTION through `emit_exception_bridge_prologue`. The bridge walker emits the whole SAVE_EXC_CLASS / SAVE_EXCEPTION / RESTORE_EXCEPTION / GUARD_EXCEPTION sequence at the bridge-entry frame state, where the guard carries a snapshot, so the two routing conjuncts and the gate go with it. `emit_exception_bridge_prologue` stays as the `_prepare_exception_resumption` + `prepare_resume_from_failure` counterpart, with its doc noting it is off the live path and why. The reason the gate existed is recorded where the read that caused it lives, in `bridge_subwalk`'s `exc_edge_class`. check.py cranelift 377/377, dynasm 377/377, wasm 373/373. Assisted-by: Claude --- majit/majit-metainterp/src/pyjitpl.rs | 5 ++ .../src/jitcode_dispatch/bridge_subwalk.rs | 13 +++-- .../src/jitcode_dispatch/mod.rs | 25 ---------- pyre/pyre-jit/src/call_jit.rs | 50 +++---------------- 4 files changed, 22 insertions(+), 71 deletions(-) diff --git a/majit/majit-metainterp/src/pyjitpl.rs b/majit/majit-metainterp/src/pyjitpl.rs index 03bf5c81052..2442388a7e7 100644 --- a/majit/majit-metainterp/src/pyjitpl.rs +++ b/majit/majit-metainterp/src/pyjitpl.rs @@ -12856,6 +12856,11 @@ impl MetaInterp { /// matching RPython's `_prepare_exception_resumption` (phase 1, /// trace start) and `prepare_resume_from_failure` (phase 2, /// after resume ops). + /// + /// Not on the live path: the bridge walker emits this sequence itself at + /// the bridge-entry frame state, where the GUARD_EXCEPTION it ends with + /// carries a snapshot. The guard emitted here carries none, so it could + /// only ever serve a trace that gets discarded. pub fn emit_exception_bridge_prologue(&mut self, exc_class: i64, exc_value: i64) { let Some(ref mut ctx) = self.tracing else { return; diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs index 0c0b034b8f3..ea743d95fcf 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs @@ -313,8 +313,7 @@ pub fn dispatch_via_miframe( // walk MUST resume at an `except` handler — falling through to the // no-exception continuation would record the return of the NULL raised-call // result (`Finish(NULL)` → "call failed"). - let exc_edge_precondition = exc_edge_bridge_enabled() - && trace_ctx.is_bridge_trace + let exc_edge_precondition = trace_ctx.is_bridge_trace && trace_ctx.bridge_source_is_exception_guard() && !sym.last_exc_box().is_none() && !sym.last_exc_value().is_null(); @@ -334,7 +333,7 @@ pub fn dispatch_via_miframe( // Routed by `call_jit` with no `catch_exception` in this frame at all, // which the routing precondition above is supposed to exclude. Abort // BEFORE any recording so the guard failure resumes via the blackhole, - // exactly as when the flag is off. + // exactly as an unrouted one does. return Err(DispatchError::ExcEdgeNoInFrameCatch { pc: position }); } let exc_edge_concrete = sym.last_exc_value(); @@ -344,7 +343,13 @@ pub fn dispatch_via_miframe( // One machine word, so read it at pointer width: an i64 read on a // 32-bit target pulls the adjacent header word into the high half, and // the guard then compares against a class value the pending-exception - // cell (`jit_exc_raise`, pointer-width) can never hold. + // cell (`jit_exc_raise`, pointer-width) can never hold. That read is + // why exception-edge routing was once wasm-off: the guard it emitted + // could not pass, so every raising iteration deopted one chain link + // deeper and `guard_failures` tracked the iteration count instead of + // converging. A backend that skips SAVE_EXCEPTION / SAVE_EXC_CLASS / + // RESTORE_EXCEPTION rather than lowering them fails the same way, one + // step later: the handler then reads a null caught exception. unsafe { *(exc_edge_concrete as *const usize) as i64 } } else { 0 diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs index 441ffefa1e4..4ac8e6f99a5 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs @@ -3139,31 +3139,6 @@ pub(crate) fn try_catch_exception_at(code: &[u8], position: usize) -> Option 202 -/// guard failures against dynasm's 201, `inline_subwalk_property_mutates` -/// 23748 -> 613 against 611, `handler_reraise_second_exc` and -/// `named_reraise_sibling_hot` exactly on dynasm's 804 and 1712, and -/// `loops_aborted` 1 -> 0 on all of them. -pub fn exc_edge_bridge_enabled() -> bool { - true -} - /// `PYRE_CARRIER_EXC_RESUME=1` enables the multi-frame (carrier) exception /// resume: seed the grabbed guard exception onto the bridge sym and route the /// inlined callee's carrier sub-walk into its own `catch_exception` handler diff --git a/pyre/pyre-jit/src/call_jit.rs b/pyre/pyre-jit/src/call_jit.rs index 038913dc83a..ae6395e846b 100644 --- a/pyre/pyre-jit/src/call_jit.rs +++ b/pyre/pyre-jit/src/call_jit.rs @@ -3264,50 +3264,18 @@ pub fn trace_and_compile_from_bridge( } return BridgeResolution::ResumeBlackhole; } - // RPython pyjitpl.py:3101 _prepare_exception_resumption + - // pyjitpl.py:3132 prepare_resume_from_failure parity: - // For exception guard bridges (GUARD_EXCEPTION / GUARD_NO_EXCEPTION), - // emit SAVE_EXC_CLASS + SAVE_EXCEPTION at trace start, then - // RESTORE_EXCEPTION before the guard. The exception class/value - // are read from the TLS exception state set by Cranelift codegen. + // `_prepare_exception_resumption` (pyjitpl.py:3101) + + // `prepare_resume_from_failure` (pyjitpl.py:3132) parity: for exception + // guard bridges (GUARD_EXCEPTION / GUARD_NO_EXCEPTION), SAVE_EXC_CLASS + + // SAVE_EXCEPTION at trace start, then RESTORE_EXCEPTION before the guard. + // The walker owns that whole sequence — it emits it at the bridge-entry + // frame state, which is where the guard can capture resume data — so this + // site only consumes the flag. let last_bridge_is_exception_guard = { let (driver, _) = crate::eval::driver_pair(); driver.last_bridge_is_exception_guard }; if last_bridge_is_exception_guard { - // The walker emits the whole exception - // resumption sequence (SAVE_EXC_CLASS/SAVE_EXCEPTION/RESTORE_EXCEPTION + - // a snapshotted GUARD_EXCEPTION) at the bridge-entry frame state, where - // the guard can capture resume data. The legacy call-site prologue - // below emits a snapshot-less GUARD_EXCEPTION and is only reached on the - // declined path (which discards the trace), so skip it when routing is - // enabled and let the walker own the sequence. - if !pyre_jit_trace::jitcode_dispatch::exc_edge_bridge_enabled() { - #[cfg(feature = "cranelift")] - let exc_class = majit_backend_cranelift::jit_exc_class_raw(); - #[cfg(not(feature = "cranelift"))] - let exc_class: i64 = 0; - #[cfg(feature = "cranelift")] - let exc_value = majit_backend_cranelift::jit_exc_value_raw(); - #[cfg(not(feature = "cranelift"))] - let exc_value: i64 = 0; - if exc_class != 0 { - // RPython pyjitpl.py:3125-3126 + 3138: - // SAVE_EXC_CLASS, SAVE_EXCEPTION, RESTORE_EXCEPTION - { - let (driver, _) = crate::eval::driver_pair(); - driver - .meta_interp_mut() - .emit_exception_bridge_prologue(exc_class, exc_value); - } - if majit_metainterp::majit_log_enabled() { - eprintln!( - "[jit][bridge-exc] exception guard bridge: class={:#x} value={:#x}", - exc_class, exc_value - ); - } - } - } let (driver, _) = crate::eval::driver_pair(); driver.last_bridge_is_exception_guard = false; } @@ -3451,9 +3419,7 @@ pub fn trace_and_compile_from_bridge( && resume_coords .first() .is_some_and(|&(outer_w_code, _)| outer_w_code == frame.pycode as usize); - let route_exc_edge = caught_in_frame - && (!is_multiframe_resume || unwind_to_live_frame) - && pyre_jit_trace::jitcode_dispatch::exc_edge_bridge_enabled(); + let route_exc_edge = caught_in_frame && (!is_multiframe_resume || unwind_to_live_frame); // The levels this route is about to throw away: `resume_coords` minus the // live frame, which keeps its own recorder at the handler entry. Published // unconditionally on the routing path — an empty slice for the single-frame