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
5 changes: 5 additions & 0 deletions majit/majit-metainterp/src/pyjitpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12856,6 +12856,11 @@ impl<M: Clone> MetaInterp<M> {
/// 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;
Expand Down
13 changes: 9 additions & 4 deletions pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,7 @@ pub fn dispatch_via_miframe<Sym: WalkSym>(
// 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();
Expand All @@ -334,7 +333,7 @@ pub fn dispatch_via_miframe<Sym: WalkSym>(
// 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();
Expand All @@ -344,7 +343,13 @@ pub fn dispatch_via_miframe<Sym: WalkSym>(
// 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
Expand Down
25 changes: 0 additions & 25 deletions pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3139,31 +3139,6 @@ pub(crate) fn try_catch_exception_at(code: &[u8], position: usize) -> Option<usi
}
}

/// Exception-edge bridge: route an exception-guard bridge
/// (GUARD_NO_EXCEPTION / GUARD_EXCEPTION) resume to the in-frame `except`
/// handler instead of declining to the blackhole (`call_jit.rs` pending-exc
/// decline). Every backend runs it.
///
/// It was off on wasm because the bridge it produced deopted again on its own
/// entry GUARD_EXCEPTION, so the guard failure just moved one chain link deeper
/// on every raising iteration and the chain grew one link per `trace_eagerness`
/// cycle without bound (47 / 97 / 197 bridges at 10k / 20k / 40k iterations of
/// `type_name_surrogate_reject`, with `guard_failures` byte-identical to the
/// declining arm). Two pointer-width reads were the cause: the expected class
/// was read as an i64 out of the exception's one-word `typeptr`, so on a 32-bit
/// target it carried the adjacent header word in its high half and could never
/// equal the pending-exception cell, which `jit_exc_raise` publishes at pointer
/// width. With both reads narrowed and the wasm backend's SAVE_EXCEPTION /
/// SAVE_EXC_CLASS / RESTORE_EXCEPTION lowered instead of skipped, the wasm
/// counters land on the native ones: `type_name_surrogate_reject` 9464 -> 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
Expand Down
50 changes: 8 additions & 42 deletions pyre/pyre-jit/src/call_jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Loading