Skip to content

Commit 904bdaf

Browse files
committed
Duplicate return-None epilogue for fall-through blocks
When the last block in a code object is exactly LOAD_CONST None + RETURN_VALUE (the implicit return), duplicate these instructions into blocks that would otherwise fall through to it. This matches CPython 3.14's behavior of giving each code path its own explicit return instruction.
1 parent c9230b8 commit 904bdaf

File tree

4 files changed

+89
-20
lines changed

4 files changed

+89
-20
lines changed

crates/codegen/src/ir.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ impl CodeInfo {
207207
label_exception_targets(&mut self.blocks);
208208
push_cold_blocks_to_end(&mut self.blocks);
209209
normalize_jumps(&mut self.blocks);
210+
duplicate_end_returns(&mut self.blocks);
210211
self.optimize_load_global_push_null();
211212

212213
let max_stackdepth = self.max_stackdepth()?;
@@ -1632,6 +1633,68 @@ fn normalize_jumps(blocks: &mut [Block]) {
16321633
}
16331634
}
16341635

1636+
/// Duplicate `LOAD_CONST None + RETURN_VALUE` for blocks that fall through
1637+
/// to the final return block. Matches CPython's behavior of ensuring every
1638+
/// code path that reaches the end of a function/module has its own explicit
1639+
/// return instruction.
1640+
fn duplicate_end_returns(blocks: &mut [Block]) {
1641+
// Walk the block chain to find the last block
1642+
let mut last_block = BlockIdx(0);
1643+
let mut current = BlockIdx(0);
1644+
while current != BlockIdx::NULL {
1645+
last_block = current;
1646+
current = blocks[current.idx()].next;
1647+
}
1648+
1649+
// Check if the last block ends with LOAD_CONST + RETURN_VALUE (the implicit return)
1650+
let last_insts = &blocks[last_block.idx()].instructions;
1651+
// Only apply when the last block is EXACTLY a return-None epilogue
1652+
let is_return_block = last_insts.len() == 2
1653+
&& matches!(
1654+
last_insts[0].instr,
1655+
AnyInstruction::Real(Instruction::LoadConst { .. })
1656+
)
1657+
&& matches!(
1658+
last_insts[1].instr,
1659+
AnyInstruction::Real(Instruction::ReturnValue)
1660+
);
1661+
if !is_return_block {
1662+
return;
1663+
}
1664+
1665+
// Get the return instructions to clone
1666+
let return_insts: Vec<InstructionInfo> = last_insts[last_insts.len() - 2..].to_vec();
1667+
1668+
// Find non-cold blocks that fall through to the last block
1669+
let mut blocks_to_fix = Vec::new();
1670+
current = BlockIdx(0);
1671+
while current != BlockIdx::NULL {
1672+
let block = &blocks[current.idx()];
1673+
if current != last_block
1674+
&& block.next == last_block
1675+
&& !block.cold
1676+
&& !block.except_handler
1677+
{
1678+
let has_fallthrough = block
1679+
.instructions
1680+
.last()
1681+
.map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump())
1682+
.unwrap_or(true);
1683+
if has_fallthrough {
1684+
blocks_to_fix.push(current);
1685+
}
1686+
}
1687+
current = blocks[current.idx()].next;
1688+
}
1689+
1690+
// Duplicate the return instructions at the end of fall-through blocks
1691+
for block_idx in blocks_to_fix {
1692+
blocks[block_idx.idx()]
1693+
.instructions
1694+
.extend_from_slice(&return_insts);
1695+
}
1696+
}
1697+
16351698
/// Label exception targets: walk CFG with except stack, set per-instruction
16361699
/// handler info and block preserve_lasti flag. Converts POP_BLOCK to NOP.
16371700
/// flowgraph.c label_exception_targets + push_except_block

crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap

Lines changed: 12 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)