Visitar URL original
Align nested code object bytecode parity by youknowone · Pull Request #7942 · RustPython/RustPython · GitHub
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
Next Next commit
Align nested code object bytecode parity
  • Loading branch information
youknowone committed May 21, 2026
commit 2c932fa57e69fc414e1691055abeb34c232ff6d3
115 changes: 95 additions & 20 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,20 @@ impl Compiler {
})
}

fn statements_end_with_try_except_scope_exit_handlers(body: &[ast::Stmt]) -> bool {
body.last().is_some_and(|stmt| match stmt {
ast::Stmt::Try(ast::StmtTry {
handlers,
finalbody,
is_star: false,
..
}) if !handlers.is_empty() && finalbody.is_empty() => {
Self::handlers_end_with_scope_exit(handlers)
}
_ => false,
})
}

fn statements_end_with_successor_join(body: &[ast::Stmt]) -> bool {
body.last().is_some_and(|stmt| match stmt {
ast::Stmt::If(ast::StmtIf {
Expand Down Expand Up @@ -1348,6 +1362,13 @@ impl Compiler {
.is_some_and(Self::statement_ends_with_direct_break)
}

fn handlers_end_with_scope_exit(handlers: &[ast::ExceptHandler]) -> bool {
handlers.iter().all(|handler| {
let ast::ExceptHandler::ExceptHandler(handler) = handler;
Self::statements_end_with_scope_exit(&handler.body)
})
}

fn statement_ends_with_direct_break(stmt: &ast::Stmt) -> bool {
match stmt {
ast::Stmt::Break(_) => true,
Expand Down Expand Up @@ -2875,29 +2896,42 @@ impl Compiler {
.map(|table| table.typ);
}

// Check if this is a global class/function
// Check if this is a global class/function.
// CPython compiler_set_qualname() only applies this GLOBAL_EXPLICIT
// shortcut to function, async-function, and class scopes. Annotation
// scopes, including type-alias value scopes, still inherit the parent
// function's .<locals> qualname.
let mut force_global = false;
if stack_size > self.symbol_table_stack.len() {
// We might be in a situation where symbol table isn't pushed yet
// In this case, check the parent symbol table
if let Some(parent_table) = self.symbol_table_stack.last()
&& let Some(symbol) = parent_table.lookup(&current_obj_name)
&& symbol.scope == SymbolScope::GlobalExplicit
{
force_global = true;
}
} else if let Some(_current_table) = self.symbol_table_stack.last() {
// Mangle the name if necessary (for private names in classes)
let mangled_name = self.mangle(&current_obj_name);

// Look up in parent symbol table to check scope
if self.symbol_table_stack.len() >= 2 {
let parent_table = &self.symbol_table_stack[self.symbol_table_stack.len() - 2];
if let Some(symbol) = parent_table.lookup(&mangled_name)
let current_scope = self
.code_stack
.last()
.map(|code| self.symbol_table_stack[code.symbol_table_index].typ);
if matches!(
current_scope,
Some(CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Class)
) {
if stack_size > self.symbol_table_stack.len() {
// We might be in a situation where symbol table isn't pushed yet
// In this case, check the parent symbol table
if let Some(parent_table) = self.symbol_table_stack.last()
&& let Some(symbol) = parent_table.lookup(&current_obj_name)
&& symbol.scope == SymbolScope::GlobalExplicit
{
force_global = true;
}
} else if let Some(_current_table) = self.symbol_table_stack.last() {
// Mangle the name if necessary (for private names in classes)
let mangled_name = self.mangle(&current_obj_name);

// Look up in parent symbol table to check scope
if self.symbol_table_stack.len() >= 2 {
let parent_table = &self.symbol_table_stack[self.symbol_table_stack.len() - 2];
if let Some(symbol) = parent_table.lookup(&mangled_name)
&& symbol.scope == SymbolScope::GlobalExplicit
{
force_global = true;
}
}
}
}

Expand Down Expand Up @@ -4400,9 +4434,10 @@ impl Compiler {
in_try_except_body && self.ctx.func == FunctionContext::AsyncFunction;
let preserve_finally_exit_empty_label = (self.fallthrough_has_statement_successor
|| preserve_async_try_except_finally_scope_exit)
&& (!handlers.is_empty()
&& ((!handlers.is_empty() && Self::handlers_end_with_scope_exit(handlers))
|| Self::statements_are_single_with(finalbody)
|| Self::statements_contain_for_with_conditional_body(finalbody)
|| Self::statements_end_with_conditional(finalbody)
|| (preserve_async_try_except_finally_scope_exit
&& Self::statements_contain_conditional_scope_exit(finalbody)));
let preserve_finally_exit_empty_barrier = preserve_async_try_except_finally_scope_exit
Expand Down Expand Up @@ -7457,7 +7492,7 @@ impl Compiler {
let preserve_while_true_tail_break_successor_load_fast =
!is_async && self.statements_end_with_while_true_tail_direct_break(body)?;
let preserve_try_except_tail_successor_load_fast =
!is_async && Self::statements_end_with_try_except_no_finally(body);
!is_async && Self::statements_end_with_try_except_scope_exit_handlers(body);
let preserve_try_finally_with_finalbody_successor_load_fast =
Self::statements_end_with_try_finally_finalbody_with(body);
let materialize_async_with_outer_cleanup_target_nop = is_async
Expand Down Expand Up @@ -14043,6 +14078,46 @@ def f(sys, os, file):
}
}

#[test]
fn test_for_try_except_break_keeps_cpython_if_layout() {
let code = compile_exec(
"\
def f(support, func, value):
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
try:
if func() == value:
break
except NotImplementedError:
break
sink(value)
",
);
let f = find_code(&code, "f").expect("missing f code");
let ops = f
.instructions
.iter()
.map(|unit| unit.op)
.filter(|op| !matches!(op, Instruction::Cache))
.collect::<Vec<_>>();
let cond = ops
.iter()
.position(|op| matches!(op, Instruction::PopJumpIfFalse { .. }))
.expect("missing CPython-style false jump for if/break");
assert!(
matches!(
ops.get(cond..cond + 5),
Some([
Instruction::PopJumpIfFalse { .. },
Instruction::NotTaken,
Instruction::PopTop,
Instruction::JumpForward { .. },
Instruction::JumpBackward { .. },
])
),
"CPython codegen_if() keeps the break cleanup in the true-body fallthrough before the loop backedge, got ops={ops:?}"
);
}

#[test]
fn test_handler_resume_after_nested_try_keeps_successor_load_fast_strong() {
let code = compile_exec(
Expand Down
30 changes: 30 additions & 0 deletions crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7684,6 +7684,27 @@ fn is_pop_top_jump_block(block: &Block) -> bool {
&& second.target != BlockIdx::NULL
}

fn is_for_break_cleanup_block(blocks: &[Block], block_idx: BlockIdx) -> bool {
if block_idx == BlockIdx::NULL {
return false;
}
let mut real_instrs = blocks[block_idx.idx()]
.instructions
.iter()
.filter(|info| !matches!(info.instr.real(), Some(Instruction::Nop)));
let Some(first) = real_instrs.next() else {
return false;
};
let Some(second) = real_instrs.next() else {
return false;
};
real_instrs.next().is_none()
&& matches!(first.instr.real(), Some(Instruction::PopTop))
&& second.instr.is_unconditional_jump()
&& second.target != BlockIdx::NULL
&& !comes_before(blocks, second.target, block_idx)
}

fn is_scope_exit_block(block: &Block) -> bool {
block
.instructions
Expand Down Expand Up @@ -8601,6 +8622,13 @@ fn reorder_conditional_chain_and_jump_back_blocks(blocks: &mut Vec<Block>) {
.instructions
.first()
.is_some_and(|info| matches!(info.lineno_override, Some(line) if line < 0));
if is_generic_false_path_reorder
&& jump_targets_for_iter(blocks, jump_block)
&& is_for_break_cleanup_block(blocks, chain_start)
{
current = next;
continue;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
let after_jump_is_adjacent_scope_exit =
after_jump != BlockIdx::NULL && is_pop_top_exit_like_block(&blocks[after_jump.idx()]);
if !is_generic_false_path_reorder
Expand Down Expand Up @@ -8804,6 +8832,8 @@ fn reorder_conditional_scope_exit_and_jump_back_blocks(
|| mismatched_protection
|| !is_scope_exit_block(&blocks[exit_block.idx()])
|| !is_jump_back_only_block(blocks, jump_block)
|| (jump_targets_for_iter(blocks, jump_block)
&& is_for_break_cleanup_block(blocks, exit_block))
|| (!allow_for_iter_jump_targets
&& is_explicit_continue_to_for_iter(blocks, jump_block))
&& blocks[exit_block.idx()].instructions.iter().any(|info| {
Expand Down