Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
41e693a
coroutine: preserve yield-from and exhausted state
youknowone Aug 8, 2026
37dfc23
copy: reject types without constructors
youknowone Aug 8, 2026
bb63f8f
async-for: chain invalid anext awaitables
youknowone Aug 8, 2026
47204b0
coroutine: report unawaited finalizer errors
youknowone Aug 8, 2026
9be3e5f
str: port CPython sizeof reporting
youknowone Aug 8, 2026
859aa63
int: port CPython size metadata
youknowone Aug 8, 2026
7d29c63
type: expose CPython layout metadata
youknowone Aug 8, 2026
a9a8ce9
bytearray: port CPython allocation metadata
youknowone Aug 8, 2026
c08027d
list: port CPython allocation metadata
youknowone Aug 8, 2026
a97bdeb
jit: reject null callable resume images
youknowone Aug 8, 2026
ee1785d
weakref: expose CPython layout metadata
youknowone Aug 8, 2026
c5ee4a9
jit: route guard exceptions to trailing catches
youknowone Aug 8, 2026
a68fb4b
generator: clear fresh frames through gi_frame
youknowone Aug 8, 2026
2559973
generator: release consumed close exceptions
youknowone Aug 8, 2026
fbb793b
jit: preserve generic sequence iterator exhaustion
youknowone Aug 8, 2026
74d6276
io: make opened descriptors non-inheritable
youknowone Aug 8, 2026
39e1ec8
exceptions: describe unraisable user finalizers
youknowone Aug 8, 2026
4dbc271
function: inherit builtins from the caller frame
youknowone Aug 8, 2026
624973a
exceptions: port BlockingIOError written slot
youknowone Aug 8, 2026
3a535a4
socket: alias timeout to builtin TimeoutError
youknowone Aug 8, 2026
25707ae
jit: preserve LOAD_GLOBAL null in guard snapshots
youknowone Aug 9, 2026
dedc639
baseobjspace, executioncontext, function: reformat six rustfmt-reflow…
youknowone Aug 9, 2026
ef7dda1
builtins: port mixed numeric lists and fix JIT regressions
youknowone Aug 9, 2026
cf40e9e
jit: repair entry bridge and carrier regressions
youknowone Aug 9, 2026
bfa00bc
jit: fix blackhole catches and void call results
youknowone Aug 9, 2026
8650fc5
tests: scope POSIX-only parity fixtures
youknowone Aug 9, 2026
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
15 changes: 15 additions & 0 deletions extra_tests/test_itertools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,18 @@ def test_islice_maxint():
def test_islice_largeint():
slic = itertools.islice(itertools.count(), 1, 10, sys.maxsize - 20)
assert len(list(slic)) == 1


def test_generic_sequence_iterator_exhaustion_after_jit():
class Sequence:
def __init__(self, values):
self.values = values

def __getitem__(self, index):
return self.values[index]

total = 0
for _ in range(20):
for value in Sequence(range(1000)):
total += value
assert total == 9_990_000
4 changes: 2 additions & 2 deletions majit/majit-backend-dynasm/src/aarch64/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2215,9 +2215,9 @@ impl<'a> AssemblerARM64<'a> {
// opref_to_slot stores ABSOLUTE jitframe slots (user position +
// JITFRAME_FIXED_SIZE) so slot_offset(slot) gives the correct byte
// offset without further adjustment.
for iarg in inputargs {
for (position, iarg) in inputargs.iter().enumerate() {
self.opref_to_slot
.insert(iarg.opref(), JITFRAME_FIXED_SIZE + iarg.index as usize);
.insert(iarg.opref(), JITFRAME_FIXED_SIZE + position);
Comment on lines +2218 to +2220

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== InputArg and FrameLoc definitions =="
rg -n 'struct InputArg' -A 15 --type=rust
rg -n 'struct FrameLoc' -A 12 --type=rust
echo "== RegAlloc::prepare_bridge: how are bridge input-arg frame positions assigned? =="
rg -n 'fn prepare_bridge' -A 50 --type=rust
echo "== Does ra.longevity cover every input arg with a current_frame_loc? =="
rg -n 'current_frame_loc' -B3 -A10 --type=rust

Repository: youknowone/pyre

Length of output: 193


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== repo files matching assembler.rs =="
fd -a 'assembler\.rs$' . | sed 's#^\./##'
echo "== git status/stat =="
git status --short
git diff --stat
echo "== search symbols broadly =="
rg -n 'struct InputArg|struct FrameLoc|fn prepare_bridge|current_frame_loc|setup_input_state|bridge_input_locs|rebuild_faillocs_from_descr|JITFRAME_FIXED_SIZE|opref_to_slot|lifetimes_iter' -S .

Repository: youknowone/pyre

Length of output: 33064


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== majit aarch64 setup_input_state & _assemble =="
sed -n '1330,1490p' majit/majit-backend-dynasm/src/aarch64/assembler.rs | cat -n
sed -n '2155,2240p' majit/majit-backend-dynasm/src/aarch64/assembler.rs | cat -n
sed -n '2010,2060p' majit/majit-backend-dynasm/src/aarch64/assembler.rs | cat -n
echo "== majit aarch64 regalloc prepare_bridge, current_frame_loc, longevity =="
sed -n '80,95p' majit/majit-backend-dynasm/src/regalloc.rs | cat -n
sed -n '1876,1930p' majit/majit-backend-dynasm/src/regalloc.rs | cat -n
sed -n '286,305p' majit/majit-backend-dynasm/src/regalloc.rs | cat -n
sed -n '620,655p' majit/majit-backend-dynasm/src/regalloc.rs | cat -n
echo "== x86 parity for same path =="
sed -n '2840,2865p' majit/majit-backend-dynasm/src/x86/assembler.rs | cat -n
sed -n '1648,1670p' majit/majit-backend-dynasm/src/x86/assembler.rs | cat -n
echo "== rust code parser probe =="
python3 - <<'PY'
from pathlib import Path
for p in ['majit/majit-backend-dynasm/src/aarch64/assembler.rs','majit/majit-backend-dynasm/src/x86/assembler.rs']:
    text=Path(p).read_text()
    for needle in ['fn setup_input_state(&mut self, inputargs: &[InputArg])', 'fn _assemble(&mut self', 'if self.bridge_input_locs.is_some()']:
        idx=text.find(needle)
        print(p, needle, 'FOUND' if idx!=-1 else 'MISSING', idx)
PY

Repository: youknowone/pyre

Length of output: 25397


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== regalloc binding methods =="
sed -n '1,220p' majit/majit-backend-dynasm/src/regalloc.rs | cat -n
echo "== rpython aarch64 comparable path =="
python3 - <<'PY'
from pathlib import Path
for p in [
    'rpython/jit/backend/aarch64/assembler.py',
    'rpython/jit/backend/aarch64/regalloc.py',
    'majit/majit-backend-dynasm/src/aarch64/assembler.rs',
    'majit/majit-backend-dynasm/src/regalloc.rs'
]:
    s=Path(p).read_text()
    print(p, 'setup_input_state', s.find('def setup_input_state')),
    print(p, '_assemble bridge sequential loop', s.find('for position iarg in enumerate(inputargs)')),
    print(p, 'lifetimes_iter', s.find('lifetimes_iter')),
PY

Repository: youknowone/pyre

Length of output: 10045


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== rpython dynasm backends containing setup_input_state / assemble_bridge =="
rg -n 'def setup_input_state|def assemble_bridge|bridge_input_locs|rebuild_faillocs_from_descr|for position, iarg in enumerate\(inputargs\)' rpython/jit/backend -S
echo "== rpython x86 assembler relevant sections =="
rg -n 'def setup_input_state|def assemble_bridge|bridge_input_locs|for position, iarg in enumerate\(inputargs\)|def prepare_bridge' rpython/jit/backend/x86/assembler.py -A 20 -B 5
echo "== rpython x86 regalloc prepare_binding update bindings =="
rg -n 'def prepare_bridge|def .*(binding|bindings)|current_frame_loc|hint_frame_pos|_update_bindings' rpython/jit/backend/x86/regalloc.py -A 25 -B 10 | sed -n '1,220p'

Repository: youknowone/pyre

Length of output: 7439


Keep bridge input frame slots from bridge input arg mapping.

The unconditional enumerate() loop writes JITFRAME_FIXED_SIZE + position for every bridge input arg, overwriting the bridge-specific positions set by setup_input_state. The subsequent current_frame_loc loop only updates locations that the regalloc FrameManager actually assigned, so inputs that came from a bridge frame arg can be left mapped by sequential index. Restore or carry forward the original bridge input arg slots before this loop, or apply regalloc frame updates without overwriting inputs that never received a current_frame_loc binding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-backend-dynasm/src/aarch64/assembler.rs` around lines 2218 -
2220, Update the input-argument mapping around setup_input_state and the
enumerate loop so bridge input arguments retain their bridge-specific frame
slots. Do not unconditionally assign JITFRAME_FIXED_SIZE + position for every
iarg; preserve existing opref_to_slot entries and only apply sequential slots to
arguments without a bridge mapping, while still allowing current_frame_loc
updates for regalloc-assigned inputs.

}
// Also sync any frame allocations from regalloc's FrameManager.
for (&opref, lifetime) in ra.longevity.lifetimes_iter() {
Expand Down
4 changes: 2 additions & 2 deletions majit/majit-backend-dynasm/src/x86/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2848,9 +2848,9 @@ impl<'a> Assembler386<'a> {
// opref_to_slot stores ABSOLUTE jitframe slots (user position +
// JITFRAME_FIXED_SIZE) so slot_offset(slot) gives the correct byte
// offset without further adjustment.
for iarg in inputargs {
for (position, iarg) in inputargs.iter().enumerate() {
self.opref_to_slot
.insert(iarg.opref(), JITFRAME_FIXED_SIZE + iarg.index as usize);
.insert(iarg.opref(), JITFRAME_FIXED_SIZE + position);
}
// Also sync any frame allocations from regalloc's FrameManager.
for (&opref, lifetime) in ra.longevity.lifetimes_iter() {
Expand Down
104 changes: 104 additions & 0 deletions majit/majit-metainterp/src/blackhole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,17 @@ impl BlackholeInterpreter {
if opcode == self.op_catch_exception {
return self.route_to_catch(position, exc_value);
}
// A guard resume coordinate may name the successor block's entry
// `-live-`, before that block mirrors the virtualizable and reaches
// the raising operation's trailing `-live-`. The flattener emits the
// `catch_exception` immediately after that trailing marker
// (`flatten.py:206-217`). Walk forward to the first such marker and
// accept only its immediately-following catch; crossing any other
// operation after it means this exception belongs to no handler at
// the resumed call site.
if let Some(catch_pos) = self.find_catch_after_resume_live(resume_live_pos) {
return self.route_to_catch(catch_pos, exc_value);
}
// Backward case (after-residual-call guard): pyre resumes the post-call
// `GUARD_NO_EXCEPTION` at the next opcode's `-live-`
// (`pc_map[fallthrough_pc]`, jitcode_dispatch.rs / capture_resumedata),
Expand Down Expand Up @@ -1365,6 +1376,48 @@ impl BlackholeInterpreter {
None
}

fn find_catch_after_resume_live(&self, resume_live_pos: usize) -> Option<usize> {
let code = &self.jitcode.code;
let startpoints = self.jitcode.startpoints.as_ref()?;
let mut points: Vec<usize> = startpoints
.iter()
.copied()
.filter(|&q| q > resume_live_pos)
.collect();
points.sort_unstable();
let mut crossed_trailing_live = false;
for q in points {
let op = code[q];
if op == self.op_catch_exception {
return crossed_trailing_live.then_some(q);
Comment on lines +1389 to +1390

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop before catches belonging to the next operation

When a GUARD_NO_EXCEPTION for an operation outside a try fails and resumes at the first opcode of a following try, this forward scan ignores every operation until the next live marker and then accepts that following operation's catch. The blackhole consequently routes the earlier exception into a handler that does not cover the raising operation, so compiled execution can swallow an exception that the interpreter propagates; restrict this path to the known successor-sync operations or encode the originating catch explicitly.

AGENTS.md reference: AGENTS.md:L14-L19

Useful? React with 👍 / 👎.

}
if crossed_trailing_live {
return None;
}
if op == self.op_live {
crossed_trailing_live = true;
continue;
}
if !matches!(
op,
majit_translate::insns::BC_SETFIELD_VABLE_I
| majit_translate::insns::BC_SETFIELD_VABLE_R
| majit_translate::insns::BC_SETFIELD_VABLE_F
| majit_translate::insns::BC_SETARRAYITEM_VABLE_I
| majit_translate::insns::BC_SETARRAYITEM_VABLE_R
| majit_translate::insns::BC_SETARRAYITEM_VABLE_F
) {
// Only the codewriter's successor-block virtualizable mirror
// stores may separate the guard resume coordinate from the
// raising operation's trailing live marker. Crossing an
// arbitrary operation would attach its exception to the next
// operation's handler and silently swallow it.
return None;
}
}
None
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// blackhole.py:424-439 handle_rvmprof_enter.
pub fn handle_rvmprof_enter(&mut self) {
let code = &self.jitcode.code;
Expand Down Expand Up @@ -4284,6 +4337,57 @@ mod tests {
assert_eq!(bh.return_type, BhReturnType::Int);
}

#[test]
fn test_guard_exception_resume_finds_catch_after_successor_sync() {
let mut asm = majit_translate::codewriter::assembler::Assembler::new();
let mut b = JitCodeBuilder::default();
b.load_const_r_value(0, 1);
b.load_const_i_value(0, 2);
let resume_pc = b.current_pos();
b.live(&mut asm, &[], &[], &[]);
// The normal-flow successor mirrors virtualizable state before
// the can-raise block's trailing live/catch pair.
b.vable_setfield_int_with_base(0, 0, 0);
b.live(&mut asm, &[], &[], &[]);
let handler_lbl = b.new_label();
b.catch_exception(handler_lbl);
b.load_const_i_value(2, 99);
b.int_return(2);
b.mark_label(handler_lbl);
let handler_pc = b.current_pos();
b.load_const_i_value(2, 42);
b.int_return(2);
let jitcode = b.finish();

let mut builder = super::build_inline_call_only_bh_builder();
let mut bh = builder.acquire_interp();
bh.setposition(std::sync::Arc::new(jitcode), resume_pc);

assert!(bh.handle_exception_in_frame(0xCAFE_F00D));
assert_eq!(bh.position, handler_pc);
}

#[test]
fn test_guard_exception_resume_does_not_cross_another_operation() {
let mut asm = majit_translate::codewriter::assembler::Assembler::new();
let mut b = JitCodeBuilder::default();
let resume_pc = b.current_pos();
b.live(&mut asm, &[], &[], &[]);
b.load_const_i_value(0, 1);
b.live(&mut asm, &[], &[], &[]);
let handler_lbl = b.new_label();
b.catch_exception(handler_lbl);
b.mark_label(handler_lbl);
b.int_return(0);
let jitcode = b.finish();

let mut builder = super::build_inline_call_only_bh_builder();
let mut bh = builder.acquire_interp();
bh.setposition(std::sync::Arc::new(jitcode), resume_pc);

assert!(!bh.handle_exception_in_frame(0xCAFE_F00D));
}

thread_local! {
/// Address of the interpreter's `exception_last_value` slot, read
/// back by [`probe_exception_slot_at_record_time`].
Expand Down
Loading
Loading