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
28 changes: 21 additions & 7 deletions majit/majit-metainterp/src/blackhole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,11 +1151,16 @@ impl BlackholeInterpreter {
/// Locate the `catch_exception` op that belongs to the just-executed
/// opcode whose post-call guard resumed at `resume_live_pos` (the next
/// opcode's `-live-`). Scans op boundaries (the jitcode's `startpoints`)
/// strictly before `resume_live_pos`, newest first, and stops at the
/// first `-live-` — that is the call's own post-call `-live-`, so the only
/// `catch_exception` that can match sits inside this opcode's expansion.
/// Returns `None` (propagate) when the opcode raised outside any
/// try-block (no `catch_exception` was emitted for it).
/// strictly before `resume_live_pos`, newest first. The caught can-raise
/// op's expansion is `[op, -live-, catch_exception, -live-(block entry),
/// vable stores…]` (the guessexception split closes the block at the op's
/// trailing `-live-`, so the successor block opens with its own `-live-`
/// before the moved stores). The scan therefore hops over exactly one
/// `-live-` — the successor's block-entry marker — and accepts the
/// `catch_exception` only when it sits immediately before it; any other
/// op there is the raising op itself (no catch emitted), and a second
/// `-live-` means the raising op sits outside any in-frame try.
/// Returns `None` (propagate) in those cases.
fn find_catch_before_resume_live(&self, resume_live_pos: usize) -> Option<usize> {
let code = &self.jitcode.code;
let startpoints = self.jitcode.startpoints.as_ref()?;
Expand All @@ -1165,14 +1170,23 @@ impl BlackholeInterpreter {
.filter(|&q| q < resume_live_pos)
.collect();
points.sort_unstable_by(|a, b| b.cmp(a));
let mut crossed_block_entry_live = false;
for q in points {
let op = code[q];
if op == self.op_catch_exception {
return Some(q);
}
if op == self.op_live {
// The call's own post-call `-live-`: bound the scan here so a
// preceding opcode's catch can never be mis-selected.
if crossed_block_entry_live {
// Second `-live-`: the raising op has no catch.
return None;
}
crossed_block_entry_live = true;
continue;
}
if crossed_block_entry_live {
// The op immediately before the block-entry `-live-` is not a
// `catch_exception` — it is the raising op itself (uncaught).
return None;
}
}
Expand Down
26 changes: 20 additions & 6 deletions pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2638,10 +2638,15 @@ pub(crate) fn find_catch_for_exc_resume(code: &[u8], resume_live_pos: usize) ->
/// for the walker. An after-residual-call exception guard resumes at the
/// no-exception fallthrough `-live-` (the next opcode after the call); the
/// `catch_exception/L` that belongs to the just-executed raising op sits BEHIND
/// that resume `-live-` (between the call's own post-call `-live-` and the next
/// op), so there is no forward catch to route to. Scan op boundaries backward
/// from `resume_live_pos`, newest first, bounded by the first `live/` (the
/// call's own post-call `-live-`), so only THIS opcode's catch can match.
/// that resume `-live-`. The caught can-raise op's expansion is `[op, -live-,
/// catch_exception, -live-(block entry), vable stores…]` (the guessexception
/// split closes the block at the op's trailing `-live-`, so the successor block
/// opens with its own `-live-` before the moved stores). Scan op boundaries
/// backward from `resume_live_pos`, newest first, hopping over exactly one
/// `-live-` (the successor's block-entry marker); accept the `catch_exception`
/// only when it sits immediately before it — any other op there is the raising
/// op itself (uncaught), and a second `-live-` means the raising op sits
/// outside any in-frame try.
/// Returns the handler target (2-byte LE label after `catch_exception/L`), or
/// `None` when the raising op sits outside any in-frame try (propagate).
pub(crate) fn find_catch_before_resume_live(code: &[u8], resume_live_pos: usize) -> Option<usize> {
Expand All @@ -2650,6 +2655,7 @@ pub(crate) fn find_catch_before_resume_live(code: &[u8], resume_live_pos: usize)
.filter(|&pc| pc < resume_live_pos)
.collect();
pcs.sort_unstable_by(|a, b| b.cmp(a));
let mut crossed_block_entry_live = false;
for pc in pcs {
let op = decode_op_at(code, pc)?;
if op.key == "catch_exception/L" {
Expand All @@ -2658,8 +2664,16 @@ pub(crate) fn find_catch_before_resume_live(code: &[u8], resume_live_pos: usize)
return Some(lo | (hi << 8));
}
if op.key == "live/" {
// The call's own post-call `-live-`: bound the scan so a preceding
// opcode's catch can never be mis-selected.
if crossed_block_entry_live {
// Second `-live-`: the raising op has no catch.
return None;
}
crossed_block_entry_live = true;
continue;
}
if crossed_block_entry_live {
// The op immediately before the block-entry `-live-` is not a
// `catch_exception` — it is the raising op itself (uncaught).
return None;
}
}
Expand Down
26 changes: 16 additions & 10 deletions pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,17 +1447,23 @@ pub(crate) fn compute_inline_caller_frame<Sym: WalkSym>(
// `result_color_at_pc` (top-of-stack color at the return pc), not the flat
// `stack_slot_color_map` — the result is not a live Variable here, so it
// carries no pcdep entry.
let result_color = match resume_marker_jit_pc {
Some(marker) => unsafe { &(*caller_sym.jitcode()).payload }
.result_color_trivia_for_jitcode_pc(marker)
.filter(|&color| color != u16::MAX)
.map(|color| color as usize),
// Marker-miss: the after-residual result-color twin keys the same
// fallthrough coordinate by JitCode byte offset, retiring the py read.
None => unsafe { &(*caller_sym.jitcode()).payload }
.result_color_after_residual_for_jitcode_pc(call_jit_pc)
let result_color = {
let payload = unsafe { &(*caller_sym.jitcode()).payload };
// The trivia twin resolves the return marker only while that marker
// doubles as the fallthrough PC's own resume marker (a pc_map
// block-head). A CALL whose fallthrough carries its own per-PC
// marker leaves the trailing marker un-keyed there — the
// after-residual twin keys the same fallthrough coordinate by the
// CALL's byte offset and stays exact in both shapes.
resume_marker_jit_pc
.and_then(|marker| payload.result_color_trivia_for_jitcode_pc(marker))
.filter(|&color| color != u16::MAX)
.map(|color| color as usize),
.or_else(|| {
payload
.result_color_after_residual_for_jitcode_pc(call_jit_pc)
.filter(|&color| color != u16::MAX)
})
.map(|color| color as usize)
}
.ok_or(InlineCallerFrameDecline::Unavailable)?;
// Null the not-yet-produced result slot, build the box list, then restore
Expand Down
44 changes: 29 additions & 15 deletions pyre/pyre-jit-trace/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ pub struct LiveVars {
/// RPython JitCode treats stack as registers with liveness.
/// For Python bytecodes, stack depth determines which slots are live.
stack_depth_at: Vec<usize>,
/// Memoized per-Python-PC `u16` depth table (`stack_depth_at`
/// truncated to `n`, `usize::MAX` sentinel → 0, saturated to
/// `u16::MAX`). Built once on first `depth_at_py_pc` call: the
/// resume/handback paths query it repeatedly per compiled code
/// object, and `LiveVars` is immutable after construction and
/// cached per code pointer by `liveness_for`.
depth_u16: std::sync::OnceLock<Vec<u16>>,
}

impl LiveVars {
Expand All @@ -57,6 +64,7 @@ impl LiveVars {
defined_bits: Vec::new(),
words_per_pc: 0,
stack_depth_at: Vec::new(),
depth_u16: std::sync::OnceLock::new(),
};
}
let nlocals = code.varnames.len().max(1);
Expand Down Expand Up @@ -422,6 +430,7 @@ impl LiveVars {
defined_bits,
words_per_pc,
stack_depth_at,
depth_u16: std::sync::OnceLock::new(),
}
}

Expand Down Expand Up @@ -486,21 +495,26 @@ impl LiveVars {
/// guards or be the resume target. `usize` values exceeding
/// `u16::MAX` saturate; CPython's max stack depth fits comfortably
/// inside `u16` for realistic bytecode.
pub fn depth_at_py_pc(&self) -> Vec<u16> {
// `stack_depth_at` has length `n + 1` (entry + each instr's
// post-state); the metadata table indexes per Python PC, so
// truncate to `n`.
let n = self.stack_depth_at.len().saturating_sub(1);
self.stack_depth_at[..n]
.iter()
.map(|&d| {
if d == usize::MAX {
0
} else {
u16::try_from(d).unwrap_or(u16::MAX)
}
})
.collect()
pub fn depth_at_py_pc(&self) -> &[u16] {
// Built once and memoized: `liveness_for` caches this `LiveVars`
// per code pointer, and the resume/handback callers query the
// table repeatedly per code object.
self.depth_u16.get_or_init(|| {
// `stack_depth_at` has length `n + 1` (entry + each instr's
// post-state); the metadata table indexes per Python PC, so
// truncate to `n`.
let n = self.stack_depth_at.len().saturating_sub(1);
self.stack_depth_at[..n]
.iter()
.map(|&d| {
if d == usize::MAX {
0
} else {
u16::try_from(d).unwrap_or(u16::MAX)
}
})
.collect()
})
}
}

Expand Down
Loading
Loading