Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c09c475
Align CFG cleanup bytecode with CPython
youknowone Apr 28, 2026
fe64200
remove test
youknowone Apr 30, 2026
94e2ae3
Align bytecode CFG cleanup with CPython
youknowone May 5, 2026
a743ffc
fix
youknowone May 5, 2026
c998628
Align named-except borrow deopts with CPython
youknowone May 5, 2026
68610b9
Remove duplicated Lib test codegen cases
youknowone May 5, 2026
c82c43f
Cache CFG layout predecessors during NOP cleanup
youknowone May 6, 2026
cd9a711
Address bytecode parity review feedback
youknowone May 7, 2026
15a88df
Align nested with cleanup bytecode
youknowone May 7, 2026
4bd27be
Align conditional raise loop backedge ordering
youknowone May 7, 2026
9b257b1
Align percent format optimization with CPython preprocess
youknowone May 7, 2026
8fbe367
Preserve shared finally reraises in CFG cleanup
youknowone May 7, 2026
3ccda96
Align CFG cleanup with CPython finally layout
youknowone May 7, 2026
2aa954f
Align loop CFG anchors with CPython
youknowone May 7, 2026
742ff9b
Preserve loop false-path CFG bodies
youknowone May 7, 2026
6cfa6f2
Align protected store-subscript CFG bytecode
youknowone May 7, 2026
cc59c46
Align borrow deopts with CPython CFG
youknowone May 7, 2026
891e972
Align delete-loop CFG with CPython
youknowone May 7, 2026
19b7427
Align CFG inlining with CPython jumps
youknowone May 7, 2026
d734c06
Align protected CFG jump threading
youknowone May 7, 2026
0e2b226
Narrow handler resume borrow deopt
youknowone May 7, 2026
3bb0430
Preserve branch-local implicit continue targets
youknowone May 7, 2026
55ec30d
Avoid duplicating boolop continue backedges
youknowone May 7, 2026
95c4492
Preserve same-line assert message borrows
youknowone May 7, 2026
8ce6677
Handle nested handler update tail borrows
youknowone May 7, 2026
d37b510
Align terminal handler borrow deopts with CPython CFG
youknowone May 7, 2026
ac086bf
Refine borrow deopts for reraise handler continuations
youknowone May 7, 2026
644d3e3
Refine try-else terminal handler borrow deopts
youknowone May 7, 2026
7adc9f4
Refine protected tail borrow parity
youknowone May 7, 2026
33ec4a6
Refine exception borrow deopt parity
youknowone May 7, 2026
de00801
Align while loop CFG layout with CPython
youknowone May 7, 2026
309b936
Align loop backedge CFG with CPython
youknowone May 7, 2026
f3b8ba1
Handle multi-block scope-exit CFG segments
youknowone May 7, 2026
aa2ca06
Preserve CPython-normalized call-body CFG
youknowone May 7, 2026
cea61f2
Preserve CPython empty if-end return anchor
youknowone May 7, 2026
8be2a54
Match CPython borrow CFG boundaries
youknowone May 7, 2026
f155436
Match CPython tuple unpack constant folding
youknowone May 7, 2026
85c3367
Match CPython implicit continue CFG layout
youknowone May 7, 2026
bb7be66
Keep implicit continue CFG targets in layout
youknowone May 7, 2026
058bc6d
Align try-except end label location with CPython
youknowone May 7, 2026
e4eb97c
Remove folded operand NOPs before line propagation
youknowone May 8, 2026
b022111
Align no-location return exit handling
youknowone May 8, 2026
5e4c096
Align nested protected import bytecode
youknowone May 8, 2026
f7d8240
Align conditional loop backedge layout
youknowone May 8, 2026
5001df7
Align protected loop exit duplication
youknowone May 8, 2026
6b604df
Preserve protected jump-back duplicates
youknowone May 8, 2026
83f109e
Align loop call-body backedge layout
youknowone May 8, 2026
3e11e1f
Align future annotation setup ordering
youknowone May 8, 2026
88ec625
Align named-except cleanup and borrow parity
youknowone May 8, 2026
e24a9b7
Align redundant jump removal with CPython
youknowone May 8, 2026
7f1376b
Preserve finally cleanup jump NOPs
youknowone May 8, 2026
6f95452
Align finally cleanup CFG with CPython
youknowone May 8, 2026
1d078e3
Fix finally cleanup CFG regression
youknowone May 8, 2026
85c70ca
Align async cleanup CFG marker handling
youknowone May 8, 2026
606e816
Preserve CPython continue CFG layout before conditional bodies
youknowone May 8, 2026
b340ca8
Refine protected CFG bytecode parity
youknowone May 8, 2026
330c44c
Align protected CFG cleanup layout
youknowone May 8, 2026
9c737eb
Align CFG cleanup and peephole parity
youknowone May 8, 2026
d35c13d
Align nested loop conditional CFG layout
youknowone May 8, 2026
bba340b
Align named expression comprehension scope
youknowone May 8, 2026
ea757cf
Align CFG cleanup with CPython line markers
youknowone May 8, 2026
555e8fc
Align CFG line marker cleanup with CPython
youknowone May 8, 2026
8abe32a
Align loop CFG fallthrough with CPython
youknowone May 8, 2026
05d96e4
Align conditional CFG fallthrough cases with CPython
youknowone May 8, 2026
a891818
Align protected CFG layout with CPython
youknowone May 8, 2026
24dde16
Align CFG cleanup and type-param calls with CPython
youknowone May 8, 2026
93e8bc4
Align annotation and super call bytecode parity
youknowone May 8, 2026
1b414fe
Align finally reraise tail inlining
youknowone May 8, 2026
7268983
Align protected loop CFG cleanup
youknowone May 8, 2026
a24a791
Align loop CFG bytecode layout with CPython
youknowone May 9, 2026
0941b7a
Align nested loop jump-back layout
youknowone May 9, 2026
448e1f2
Align conditional jump threading with CPython
youknowone May 9, 2026
a448fb6
fix
youknowone May 9, 2026
b3b8329
Skip test_stack_overflow under -u cpu
youknowone May 11, 2026
7790e54
Fix CI lint and clippy errors
youknowone May 12, 2026
19bf583
Speed up CFG cleanup for large compile inputs
youknowone May 12, 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
Prev Previous commit
Next Next commit
Address bytecode parity review feedback
  • Loading branch information
youknowone committed May 11, 2026
commit cd9a71146d85f771f35edd2c50553bed457ea2ae
71 changes: 71 additions & 0 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13686,6 +13686,77 @@ def g2(x):
);
}

#[test]
fn test_high_index_parameter_stays_initialized_in_fast_scan() {
let params = (0..65)
.map(|idx| format!("p{idx}"))
.collect::<Vec<_>>()
.join(", ");
let code = compile_exec(&format!(
"\
def f({params}):
return p64
"
));
let f = find_code(&code, "f").expect("missing f code");

assert!(
f.instructions.iter().any(|unit| matches!(
unit.op,
Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
if f.varnames
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
== "p64"
)),
"expected high-index parameter p64 to use LOAD_FAST, got ops={:?}",
f.instructions
.iter()
.map(|unit| unit.op)
.collect::<Vec<_>>()
);
assert!(
!f.instructions.iter().any(|unit| matches!(
unit.op,
Instruction::LoadFastCheck { var_num }
if f.varnames
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
== "p64"
)),
"high-index parameter p64 should not use LOAD_FAST_CHECK before deletion"
);
}

#[test]
fn test_deleted_high_index_parameter_uses_load_fast_check() {
let params = (0..65)
.map(|idx| format!("p{idx}"))
.collect::<Vec<_>>()
.join(", ");
let code = compile_exec(&format!(
"\
def f({params}):
del p64
return p64
"
));
let f = find_code(&code, "f").expect("missing f code");

assert!(
f.instructions.iter().any(|unit| matches!(
unit.op,
Instruction::LoadFastCheck { var_num }
if f.varnames
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
== "p64"
)),
"expected deleted high-index parameter p64 to use LOAD_FAST_CHECK, got ops={:?}",
f.instructions
.iter()
.map(|unit| unit.op)
.collect::<Vec<_>>()
);
}

#[test]
fn test_assert_without_message_raises_class_directly() {
let code = compile_exec(
Expand Down
182 changes: 148 additions & 34 deletions crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9227,10 +9227,22 @@ impl CodeInfo {
fn fast_scan_many_locals(
&mut self,
nlocals: usize,
nparams: usize,
merged_cell_local: &impl Fn(usize) -> Option<usize>,
) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const PARAM_INITIALIZED: usize = usize::MAX;

debug_assert!(nlocals > 64);
let mut states = vec![0usize; nlocals - 64];
let high_params = nparams.saturating_sub(64).min(states.len());
for state in states.iter_mut().take(high_params) {
*state = PARAM_INITIALIZED;
}

let is_known = |idx: usize, state: usize, blocknum: usize| {
state == blocknum || (idx < nparams && state == PARAM_INITIALIZED)
};

let mut blocknum = 0usize;
let mut current = BlockIdx(0);
while current != BlockIdx::NULL {
Expand Down Expand Up @@ -9316,7 +9328,7 @@ impl CodeInfo {
states[store_idx - 64] = blocknum;
}
if load_idx >= 64 && load_idx < nlocals {
if states[load_idx - 64] != blocknum {
if !is_known(load_idx, states[load_idx - 64], blocknum) {
let mut first = info;
first.instr = Instruction::StoreFast {
var_num: Arg::marker(),
Expand All @@ -9334,19 +9346,17 @@ impl CodeInfo {
} else {
new_instructions.push(info);
}
states[load_idx - 64] = blocknum;
} else {
new_instructions.push(info);
}
}
Some(Instruction::LoadFast { var_num }) => {
let idx = usize::from(var_num.get(info.arg));
if idx >= 64 && idx < nlocals {
if states[idx - 64] != blocknum {
info.instr = Opcode::LoadFastCheck.into();
changed = true;
}
if idx >= 64 && idx < nlocals && !is_known(idx, states[idx - 64], blocknum)
{
info.instr = Opcode::LoadFastCheck.into();
states[idx - 64] = blocknum;
changed = true;
}
new_instructions.push(info);
}
Expand All @@ -9355,16 +9365,15 @@ impl CodeInfo {
let (idx1, idx2) = packed.indexes();
let idx1 = usize::from(idx1);
let idx2 = usize::from(idx2);
let needs_check_1 =
idx1 >= 64 && idx1 < nlocals && states[idx1 - 64] != blocknum;
if idx1 >= 64 && idx1 < nlocals {
let needs_check_1 = idx1 >= 64
&& idx1 < nlocals
&& !is_known(idx1, states[idx1 - 64], blocknum);
if needs_check_1 {
states[idx1 - 64] = blocknum;
}
let needs_check_2 =
idx2 >= 64 && idx2 < nlocals && states[idx2 - 64] != blocknum;
if idx2 >= 64 && idx2 < nlocals {
states[idx2 - 64] = blocknum;
}
let needs_check_2 = idx2 >= 64
&& idx2 < nlocals
&& !is_known(idx2, states[idx2 - 64], blocknum);

if needs_check_1 || needs_check_2 {
let mut first = info;
Expand All @@ -9387,6 +9396,9 @@ impl CodeInfo {
new_instructions.push(first);
new_instructions.push(second);
changed = true;
if needs_check_2 {
states[idx2 - 64] = blocknum;
}
} else {
new_instructions.push(info);
}
Expand Down Expand Up @@ -9434,7 +9446,7 @@ impl CodeInfo {
nparams = nparams.min(nlocals);

if nlocals > 64 {
self.fast_scan_many_locals(nlocals, &merged_cell_local);
self.fast_scan_many_locals(nlocals, nparams, &merged_cell_local);
nlocals = 64;
}

Expand Down Expand Up @@ -10617,20 +10629,39 @@ fn jump_threading_unconditional(blocks: &mut [Block]) {
jump_threading_impl(blocks, false);
}

fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool {
let Some(cond_idx) = trailing_conditional_jump_index(block) else {
return false;
};
fn short_circuit_stub_conditional(block: &Block) -> Option<Instruction> {
let cond_idx = trailing_conditional_jump_index(block)?;
if cond_idx < 2 {
return None;
}
let [first, second, ..] = block.instructions.as_slice() else {
return false;
return None;
};
if !matches!(first.instr.real(), Some(Instruction::Copy { i }) if i.get(first.arg) == 1)
|| !matches!(second.instr.real(), Some(Instruction::ToBool))
{
return false;
return None;
}

let only_markers_between = block.instructions[2..cond_idx].iter().all(|info| {
matches!(
info.instr.real(),
None | Some(Instruction::Nop | Instruction::NotTaken)
)
});
if !only_markers_between {
return None;
}

block.instructions[cond_idx].instr.real()
}

fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool {
let Some(conditional) = short_circuit_stub_conditional(block) else {
return false;
};
matches!(
(source.real(), block.instructions[cond_idx].instr.real()),
(source.real(), Some(conditional)),
(
Some(Instruction::PopJumpIfFalse { .. }),
Some(Instruction::PopJumpIfTrue { .. })
Expand All @@ -10642,17 +10673,9 @@ fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool
}

fn same_short_circuit_target(block: &Block, source: AnyInstruction) -> Option<BlockIdx> {
let cond_idx = trailing_conditional_jump_index(block)?;
let [first, second, ..] = block.instructions.as_slice() else {
return None;
};
if !matches!(first.instr.real(), Some(Instruction::Copy { i }) if i.get(first.arg) == 1)
|| !matches!(second.instr.real(), Some(Instruction::ToBool))
{
return None;
}
let conditional = short_circuit_stub_conditional(block)?;
matches!(
(source.real(), block.instructions[cond_idx].instr.real()),
(source.real(), Some(conditional)),
(
Some(Instruction::PopJumpIfFalse { .. }),
Some(Instruction::PopJumpIfFalse { .. })
Expand All @@ -10661,7 +10684,7 @@ fn same_short_circuit_target(block: &Block, source: AnyInstruction) -> Option<Bl
Some(Instruction::PopJumpIfTrue { .. })
)
)
.then_some(block.instructions[cond_idx].target)
.then_some(block.instructions[trailing_conditional_jump_index(block)?].target)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[derive(Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -14290,3 +14313,94 @@ pub(crate) fn fixup_deref_opargs(blocks: &mut [Block], cellfixedoffsets: &[u32])
}
}
}

#[cfg(test)]
mod tests {
use super::*;

fn instruction_info(instr: Instruction, arg: u32, target: BlockIdx) -> InstructionInfo {
InstructionInfo {
instr: instr.into(),
arg: OpArg::new(arg),
target,
location: SourceLocation::default(),
end_location: SourceLocation::default(),
except_handler: None,
folded_from_nonliteral_expr: false,
lineno_override: None,
cache_entries: 0,
preserve_redundant_jump_as_nop: false,
remove_no_location_nop: false,
preserve_block_start_no_location_nop: false,
}
}

#[test]
fn short_circuit_stub_allows_only_marker_instructions_before_jump() {
let final_target = BlockIdx(7);
let block = Block {
instructions: vec![
instruction_info(Instruction::Copy { i: Arg::marker() }, 1, BlockIdx::NULL),
instruction_info(Instruction::ToBool, 0, BlockIdx::NULL),
instruction_info(Instruction::Nop, 0, BlockIdx::NULL),
instruction_info(Instruction::NotTaken, 0, BlockIdx::NULL),
instruction_info(
Instruction::PopJumpIfFalse {
delta: Arg::marker(),
},
0,
final_target,
),
],
..Block::default()
};

assert_eq!(
same_short_circuit_target(
&block,
Instruction::PopJumpIfFalse {
delta: Arg::marker(),
}
.into(),
),
Some(final_target)
);
}

#[test]
fn short_circuit_stub_rejects_real_instruction_before_jump() {
let block = Block {
instructions: vec![
instruction_info(Instruction::Copy { i: Arg::marker() }, 1, BlockIdx::NULL),
instruction_info(Instruction::ToBool, 0, BlockIdx::NULL),
instruction_info(Instruction::PopTop, 0, BlockIdx::NULL),
instruction_info(
Instruction::PopJumpIfFalse {
delta: Arg::marker(),
},
0,
BlockIdx(7),
),
],
..Block::default()
};

assert_eq!(
same_short_circuit_target(
&block,
Instruction::PopJumpIfFalse {
delta: Arg::marker(),
}
.into(),
),
None
);
assert!(!opposite_short_circuit_target(
&block,
Instruction::PopJumpIfTrue {
delta: Arg::marker(),
}
.into()
));
}
}