Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Use relative jump offsets and fix bytecode layout
- Convert jump arguments from absolute to relative offsets
  in frame.rs, monitoring.rs, and stack_analysis
- Add jump_relative_forward/backward helpers to ExecutingFrame
- Resolve pseudo jump instructions before offset fixpoint loop
- Emit NOP for break, continue, pass to match line-tracing
- Fix async for: emit EndAsyncFor with correct target, add NotTaken
- Fix comprehension if-cleanup to use separate block
- Fix super() source range for multi-line calls
- Fix NOP removal to preserve line-marker NOPs
- Fix InstrumentedLine cache skipping after re-dispatch
- Match InstrumentedResume/YieldValue in yield_from_target
- Remove CALL_FUNCTION_EX cache entry from opcode.py
- Remove resolved expectedFailure markers
  • Loading branch information
youknowone committed Mar 1, 2026
commit f68f7ef2afa60f15959277b7dce1c4a535a82326
3 changes: 0 additions & 3 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@
"counter": 1,
"func_version": 2,
},
"CALL_FUNCTION_EX": {
"counter": 1,
},
"STORE_SUBSCR": {
"counter": 1,
},
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2288,7 +2288,6 @@ def last_item(iterable):
self.assertEqual(14, instructions[6].offset)
self.assertEqual(8, instructions[6].start_offset)

@unittest.expectedFailure # TODO: RUSTPYTHON; no inline caches
def test_cache_offset_and_end_offset(self):
code = bytes([
opcode.opmap["LOAD_GLOBAL"], 0x01,
Expand Down Expand Up @@ -2587,7 +2586,6 @@ def f():
with contextlib.redirect_stderr(io.StringIO()):
_ = self.invoke_dis('--unknown')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_show_cache(self):
# test 'python -m dis -C/--show-caches'
source = 'print()'
Expand Down
3 changes: 0 additions & 3 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1671,7 +1671,6 @@ def func():
('return', 'func', None),
('line', 'get_events', 11)])

@unittest.expectedFailure # TODO: RUSTPYTHON; - bytecode layout differs from CPython
def test_while_offset_consistency(self):

def foo(n=0):
Expand All @@ -1689,7 +1688,6 @@ def foo(n=0):
in_loop,
exit_loop])

@unittest.expectedFailure # TODO: RUSTPYTHON; - bytecode layout differs from CPython
def test_async_for(self):

def func():
Expand Down Expand Up @@ -2125,7 +2123,6 @@ def test_get_local_events_uninitialized(self):

class TestRegressions(MonitoringTestBase, unittest.TestCase):

@unittest.expectedFailure # TODO: RUSTPYTHON; + inner
def test_105162(self):
caught = None

Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,6 @@ def f():
self.check_jump_targets(f)
self.check_lnotab(f)

@unittest.expectedFailure # TODO: RUSTPYTHON; KeyError: 44
def test_elim_jump_to_uncond_jump3(self):
# Intentionally use two-line expressions to test issue37213.
# POP_JUMP_IF_FALSE to POP_JUMP_IF_FALSE --> POP_JUMP_IF_FALSE to non-jump
Expand Down
98 changes: 74 additions & 24 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ impl Compiler {
if matches!(info.fb_type, FBlockType::AsyncWith) {
emit!(self, Instruction::GetAwaitable { arg: 2 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}

// Pop the __exit__ result
Expand Down Expand Up @@ -2371,12 +2371,16 @@ impl Compiler {
}
}
ast::Stmt::Break(_) => {
// Match CPython line-tracing behavior (codegen_break emits NOP).
emit!(self, Instruction::Nop);
// Unwind fblock stack until we find a loop, emitting cleanup for each fblock
self.compile_break_continue(statement.range(), true)?;
let dead = self.new_block();
self.switch_to_block(dead);
}
ast::Stmt::Continue(_) => {
// Match CPython line-tracing behavior (codegen_continue emits NOP).
emit!(self, Instruction::Nop);
// Unwind fblock stack until we find a loop, emitting cleanup for each fblock
self.compile_break_continue(statement.range(), false)?;
let dead = self.new_block();
Expand Down Expand Up @@ -2449,7 +2453,8 @@ impl Compiler {
}
}
ast::Stmt::Pass(_) => {
// No need to emit any code here :)
// Match CPython line-tracing behavior (Pass_kind emits NOP).
emit!(self, Instruction::Nop);
}
ast::Stmt::TypeAlias(ast::StmtTypeAlias {
name,
Expand Down Expand Up @@ -4942,7 +4947,7 @@ impl Compiler {
emit!(self, Instruction::Call { nargs: 0 }); // [bound_aexit, awaitable]
emit!(self, Instruction::GetAwaitable { arg: 1 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
} else {
// Load __exit__ and __enter__, then call __enter__
emit!(
Expand Down Expand Up @@ -5025,7 +5030,7 @@ impl Compiler {
if is_async {
emit!(self, Instruction::GetAwaitable { arg: 2 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}
emit!(self, Instruction::PopTop); // Pop __exit__ result
emit!(
Expand Down Expand Up @@ -5063,7 +5068,7 @@ impl Compiler {
if is_async {
emit!(self, Instruction::GetAwaitable { arg: 2 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}

// TO_BOOL + POP_JUMP_IF_TRUE: check if exception is suppressed
Expand Down Expand Up @@ -5137,6 +5142,7 @@ impl Compiler {
let for_block = self.new_block();
let else_block = self.new_block();
let after_block = self.new_block();
let mut end_async_for_target = BlockIdx::NULL;

// The thing iterated:
self.compile_expression(iter)?;
Expand All @@ -5156,9 +5162,10 @@ impl Compiler {
emit!(self, PseudoInstruction::SetupFinally { target: else_block });
emit!(self, Instruction::GetANext);
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
end_async_for_target = self.compile_yield_from_sequence(true)?;
// POP_BLOCK for SETUP_FINALLY - only GetANext/yield_from are protected
emit!(self, PseudoInstruction::PopBlock);
emit!(self, Instruction::NotTaken);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove Instruction::NotTaken from async-iteration control flow.

This NOT_TAKEN is tied to the SEND-driven async iteration path, not a boolean POP_JUMP_IF_* conditional jump. It can distort branch-monitoring semantics.

Suggested fix
-            emit!(self, Instruction::NotTaken);

Based on learnings: In RustPython's codegen ir.rs, is_conditional_jump should only match POP_JUMP_IF_*; ForIter and Send should not be included because NOT_TAKEN is for boolean-test conditional jumps.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emit!(self, Instruction::NotTaken);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` at line 5168, The emit!(self,
Instruction::NotTaken) call is incorrectly added on the async-iteration/SEND
path; remove that emission so async-iteration (e.g., Send/ForIter handling in
compile.rs) does not produce NOT_TAKEN. Update the related logic so only boolean
conditional jumps emit NOT_TAKEN—ensure is_conditional_jump in ir.rs matches
only POP_JUMP_IF_* (and does not include ForIter or Send), and delete the
emit!(..., Instruction::NotTaken) from the async-iteration branch (the code that
handles Send/ForIter) so branch-monitoring semantics remain correct.


// Success block for __anext__
self.compile_store(target)?;
Expand Down Expand Up @@ -5192,7 +5199,7 @@ impl Compiler {
let saved_range = self.current_source_range;
self.set_source_range(iter.range());
if is_async {
emit!(self, Instruction::EndAsyncFor);
self.emit_end_async_for(end_async_for_target);
} else {
emit!(self, Instruction::EndFor);
emit!(self, Instruction::PopIter);
Expand Down Expand Up @@ -6786,7 +6793,7 @@ impl Compiler {
/// CLEANUP_THROW
/// exit:
/// END_SEND
fn compile_yield_from_sequence(&mut self, is_await: bool) -> CompileResult<()> {
fn compile_yield_from_sequence(&mut self, is_await: bool) -> CompileResult<BlockIdx> {
let send_block = self.new_block();
let fail_block = self.new_block();
let exit_block = self.new_block();
Expand Down Expand Up @@ -6842,7 +6849,7 @@ impl Compiler {
self.switch_to_block(exit_block);
emit!(self, Instruction::EndSend);

Ok(())
Ok(send_block)
}

fn compile_expression(&mut self, expression: &ast::Expr) -> CompileResult<()> {
Expand Down Expand Up @@ -6897,7 +6904,11 @@ impl Compiler {
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
// super().attr or super(cls, self).attr optimization
// Stack: [global_super, class, self] → LOAD_SUPER_ATTR → [attr]
// Set source range to super() call for arg-loading instructions
let super_range = value.range();
self.set_source_range(super_range);
self.load_args_for_super(&super_type)?;
self.set_source_range(super_range);
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
Expand Down Expand Up @@ -6983,7 +6994,7 @@ impl Compiler {
self.compile_expression(value)?;
emit!(self, Instruction::GetAwaitable { arg: 0 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}
ast::Expr::YieldFrom(ast::ExprYieldFrom { value, .. }) => {
match self.ctx.func {
Expand All @@ -6999,7 +7010,7 @@ impl Compiler {
self.compile_expression(value)?;
emit!(self, Instruction::GetYieldFromIter);
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(false)?;
let _ = self.compile_yield_from_sequence(false)?;
}
ast::Expr::Name(ast::ExprName { id, .. }) => self.load_name(id.as_str())?,
ast::Expr::Lambda(ast::ExprLambda {
Expand Down Expand Up @@ -7354,7 +7365,11 @@ impl Compiler {
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
// super().method() or super(cls, self).method() optimization
// Stack: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
// Set source range to the super() call for LOAD_GLOBAL/LOAD_DEREF/etc.
let super_range = value.range();
self.set_source_range(super_range);
self.load_args_for_super(&super_type)?;
self.set_source_range(super_range);
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
Expand All @@ -7364,7 +7379,11 @@ impl Compiler {
self.emit_load_zero_super_method(idx);
}
}
self.codegen_call_helper(0, args, call_range)?;
// NOP for line tracking at .method( line
self.set_source_range(attr.range());
emit!(self, Instruction::Nop);
// CALL at .method( line (not the full expression line)
self.codegen_call_helper(0, args, attr.range())?;
} else {
// Normal method call: compile object, then LOAD_ATTR with method flag
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
Expand Down Expand Up @@ -7654,6 +7673,7 @@ impl Compiler {
let mut loop_labels = vec![];
for generator in generators {
let loop_block = self.new_block();
let if_cleanup_block = self.new_block();
let after_block = self.new_block();

if loop_labels.is_empty() {
Expand All @@ -7671,8 +7691,8 @@ impl Compiler {
}
}

loop_labels.push((loop_block, after_block, generator.is_async));
self.switch_to_block(loop_block);
let mut end_async_for_target = BlockIdx::NULL;
if generator.is_async {
emit!(
self,
Expand All @@ -7687,7 +7707,7 @@ impl Compiler {
after_block,
)?;
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
end_async_for_target = self.compile_yield_from_sequence(true)?;
// POP_BLOCK before store: only __anext__/yield_from are
// protected by SetupFinally targeting END_ASYNC_FOR.
emit!(self, PseudoInstruction::PopBlock);
Expand All @@ -7702,23 +7722,35 @@ impl Compiler {
);
self.compile_store(&generator.target)?;
}
loop_labels.push((
loop_block,
if_cleanup_block,
after_block,
generator.is_async,
end_async_for_target,
));

// Now evaluate the ifs:
for if_condition in &generator.ifs {
self.compile_jump_if(if_condition, false, loop_block)?
self.compile_jump_if(if_condition, false, if_cleanup_block)?
}
}

compile_element(self)?;

for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() {
for (loop_block, if_cleanup_block, after_block, is_async, end_async_for_target) in
loop_labels.iter().rev().copied()
{
emit!(self, PseudoInstruction::Jump { target: loop_block });

self.switch_to_block(if_cleanup_block);
emit!(self, PseudoInstruction::Jump { target: loop_block });

self.switch_to_block(after_block);
if is_async {
// EndAsyncFor pops both the exception and the aiter
// (handler depth is before GetANext, so aiter is at handler depth)
emit!(self, Instruction::EndAsyncFor);
self.emit_end_async_for(end_async_for_target);
} else {
// END_FOR + POP_ITER pattern (CPython 3.14)
emit!(self, Instruction::EndFor);
Expand Down Expand Up @@ -7756,7 +7788,7 @@ impl Compiler {
if is_async_list_set_dict_comprehension {
emit!(self, Instruction::GetAwaitable { arg: 0 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}

Ok(())
Expand Down Expand Up @@ -7867,6 +7899,7 @@ impl Compiler {
let mut loop_labels = vec![];
for (i, generator) in generators.iter().enumerate() {
let loop_block = self.new_block();
let if_cleanup_block = self.new_block();
let after_block = self.new_block();

if i > 0 {
Expand All @@ -7879,13 +7912,13 @@ impl Compiler {
}
}

loop_labels.push((loop_block, after_block, generator.is_async));
self.switch_to_block(loop_block);
let mut end_async_for_target = BlockIdx::NULL;

if generator.is_async {
emit!(self, Instruction::GetANext);
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
end_async_for_target = self.compile_yield_from_sequence(true)?;
self.compile_store(&generator.target)?;
} else {
emit!(
Expand All @@ -7896,22 +7929,35 @@ impl Compiler {
);
self.compile_store(&generator.target)?;
}
loop_labels.push((
loop_block,
if_cleanup_block,
after_block,
generator.is_async,
end_async_for_target,
));

// Evaluate the if conditions
for if_condition in &generator.ifs {
self.compile_jump_if(if_condition, false, loop_block)?;
self.compile_jump_if(if_condition, false, if_cleanup_block)?;
}
}

// Step 6: Compile the element expression and append to collection
compile_element(self)?;

// Step 7: Close all loops
for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() {
for (loop_block, if_cleanup_block, after_block, is_async, end_async_for_target) in
loop_labels.iter().rev().copied()
{
emit!(self, PseudoInstruction::Jump { target: loop_block });

self.switch_to_block(if_cleanup_block);
emit!(self, PseudoInstruction::Jump { target: loop_block });

self.switch_to_block(after_block);
if is_async {
emit!(self, Instruction::EndAsyncFor);
self.emit_end_async_for(end_async_for_target);
// Pop the iterator
emit!(self, Instruction::PopTop);
} else {
Expand Down Expand Up @@ -8048,6 +8094,10 @@ impl Compiler {
emit!(self, Instruction::ReturnValue)
}

fn emit_end_async_for(&mut self, send_target: BlockIdx) {
self._emit(Instruction::EndAsyncFor, OpArg::NULL, send_target);
}

/// Emit LOAD_ATTR for attribute access (method=false).
/// Encodes: (name_idx << 1) | 0
fn emit_load_attr(&mut self, name_idx: u32) {
Expand Down Expand Up @@ -8260,7 +8310,7 @@ impl Compiler {
if is_async {
emit!(self, Instruction::GetAwaitable { arg: 2 });
self.emit_load_const(ConstantData::None);
self.compile_yield_from_sequence(true)?;
let _ = self.compile_yield_from_sequence(true)?;
}

emit!(self, Instruction::PopTop);
Expand Down
Loading