Skip to content

Commit 597c759

Browse files
committed
Address bytecode parity review feedback
1 parent 8189045 commit 597c759

2 files changed

Lines changed: 219 additions & 34 deletions

File tree

crates/codegen/src/compile.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13759,6 +13759,77 @@ def g2(x):
1375913759
);
1376013760
}
1376113761

13762+
#[test]
13763+
fn test_high_index_parameter_stays_initialized_in_fast_scan() {
13764+
let params = (0..65)
13765+
.map(|idx| format!("p{idx}"))
13766+
.collect::<Vec<_>>()
13767+
.join(", ");
13768+
let code = compile_exec(&format!(
13769+
"\
13770+
def f({params}):
13771+
return p64
13772+
"
13773+
));
13774+
let f = find_code(&code, "f").expect("missing f code");
13775+
13776+
assert!(
13777+
f.instructions.iter().any(|unit| matches!(
13778+
unit.op,
13779+
Instruction::LoadFast { var_num } | Instruction::LoadFastBorrow { var_num }
13780+
if f.varnames
13781+
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
13782+
== "p64"
13783+
)),
13784+
"expected high-index parameter p64 to use LOAD_FAST, got ops={:?}",
13785+
f.instructions
13786+
.iter()
13787+
.map(|unit| unit.op)
13788+
.collect::<Vec<_>>()
13789+
);
13790+
assert!(
13791+
!f.instructions.iter().any(|unit| matches!(
13792+
unit.op,
13793+
Instruction::LoadFastCheck { var_num }
13794+
if f.varnames
13795+
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
13796+
== "p64"
13797+
)),
13798+
"high-index parameter p64 should not use LOAD_FAST_CHECK before deletion"
13799+
);
13800+
}
13801+
13802+
#[test]
13803+
fn test_deleted_high_index_parameter_uses_load_fast_check() {
13804+
let params = (0..65)
13805+
.map(|idx| format!("p{idx}"))
13806+
.collect::<Vec<_>>()
13807+
.join(", ");
13808+
let code = compile_exec(&format!(
13809+
"\
13810+
def f({params}):
13811+
del p64
13812+
return p64
13813+
"
13814+
));
13815+
let f = find_code(&code, "f").expect("missing f code");
13816+
13817+
assert!(
13818+
f.instructions.iter().any(|unit| matches!(
13819+
unit.op,
13820+
Instruction::LoadFastCheck { var_num }
13821+
if f.varnames
13822+
[usize::from(var_num.get(OpArg::new(u32::from(u8::from(unit.arg)))))]
13823+
== "p64"
13824+
)),
13825+
"expected deleted high-index parameter p64 to use LOAD_FAST_CHECK, got ops={:?}",
13826+
f.instructions
13827+
.iter()
13828+
.map(|unit| unit.op)
13829+
.collect::<Vec<_>>()
13830+
);
13831+
}
13832+
1376213833
#[test]
1376313834
fn test_assert_without_message_raises_class_directly() {
1376413835
let code = compile_exec(

crates/codegen/src/ir.rs

Lines changed: 148 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9229,10 +9229,22 @@ impl CodeInfo {
92299229
fn fast_scan_many_locals(
92309230
&mut self,
92319231
nlocals: usize,
9232+
nparams: usize,
92329233
merged_cell_local: &impl Fn(usize) -> Option<usize>,
92339234
) {
9235+
const PARAM_INITIALIZED: usize = usize::MAX;
9236+
92349237
debug_assert!(nlocals > 64);
92359238
let mut states = vec![0usize; nlocals - 64];
9239+
let high_params = nparams.saturating_sub(64).min(states.len());
9240+
for state in states.iter_mut().take(high_params) {
9241+
*state = PARAM_INITIALIZED;
9242+
}
9243+
9244+
let is_known = |idx: usize, state: usize, blocknum: usize| {
9245+
state == blocknum || (idx < nparams && state == PARAM_INITIALIZED)
9246+
};
9247+
92369248
let mut blocknum = 0usize;
92379249
let mut current = BlockIdx(0);
92389250
while current != BlockIdx::NULL {
@@ -9318,7 +9330,7 @@ impl CodeInfo {
93189330
states[store_idx - 64] = blocknum;
93199331
}
93209332
if load_idx >= 64 && load_idx < nlocals {
9321-
if states[load_idx - 64] != blocknum {
9333+
if !is_known(load_idx, states[load_idx - 64], blocknum) {
93229334
let mut first = info;
93239335
first.instr = Instruction::StoreFast {
93249336
var_num: Arg::marker(),
@@ -9336,19 +9348,17 @@ impl CodeInfo {
93369348
} else {
93379349
new_instructions.push(info);
93389350
}
9339-
states[load_idx - 64] = blocknum;
93409351
} else {
93419352
new_instructions.push(info);
93429353
}
93439354
}
93449355
Some(Instruction::LoadFast { var_num }) => {
93459356
let idx = usize::from(var_num.get(info.arg));
9346-
if idx >= 64 && idx < nlocals {
9347-
if states[idx - 64] != blocknum {
9348-
info.instr = Opcode::LoadFastCheck.into();
9349-
changed = true;
9350-
}
9357+
if idx >= 64 && idx < nlocals && !is_known(idx, states[idx - 64], blocknum)
9358+
{
9359+
info.instr = Opcode::LoadFastCheck.into();
93519360
states[idx - 64] = blocknum;
9361+
changed = true;
93529362
}
93539363
new_instructions.push(info);
93549364
}
@@ -9357,16 +9367,15 @@ impl CodeInfo {
93579367
let (idx1, idx2) = packed.indexes();
93589368
let idx1 = usize::from(idx1);
93599369
let idx2 = usize::from(idx2);
9360-
let needs_check_1 =
9361-
idx1 >= 64 && idx1 < nlocals && states[idx1 - 64] != blocknum;
9362-
if idx1 >= 64 && idx1 < nlocals {
9370+
let needs_check_1 = idx1 >= 64
9371+
&& idx1 < nlocals
9372+
&& !is_known(idx1, states[idx1 - 64], blocknum);
9373+
if needs_check_1 {
93639374
states[idx1 - 64] = blocknum;
93649375
}
9365-
let needs_check_2 =
9366-
idx2 >= 64 && idx2 < nlocals && states[idx2 - 64] != blocknum;
9367-
if idx2 >= 64 && idx2 < nlocals {
9368-
states[idx2 - 64] = blocknum;
9369-
}
9376+
let needs_check_2 = idx2 >= 64
9377+
&& idx2 < nlocals
9378+
&& !is_known(idx2, states[idx2 - 64], blocknum);
93709379

93719380
if needs_check_1 || needs_check_2 {
93729381
let mut first = info;
@@ -9389,6 +9398,9 @@ impl CodeInfo {
93899398
new_instructions.push(first);
93909399
new_instructions.push(second);
93919400
changed = true;
9401+
if needs_check_2 {
9402+
states[idx2 - 64] = blocknum;
9403+
}
93929404
} else {
93939405
new_instructions.push(info);
93949406
}
@@ -9436,7 +9448,7 @@ impl CodeInfo {
94369448
nparams = nparams.min(nlocals);
94379449

94389450
if nlocals > 64 {
9439-
self.fast_scan_many_locals(nlocals, &merged_cell_local);
9451+
self.fast_scan_many_locals(nlocals, nparams, &merged_cell_local);
94409452
nlocals = 64;
94419453
}
94429454

@@ -10624,20 +10636,39 @@ fn jump_threading_unconditional(blocks: &mut [Block]) {
1062410636
jump_threading_impl(blocks, false);
1062510637
}
1062610638

10627-
fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool {
10628-
let Some(cond_idx) = trailing_conditional_jump_index(block) else {
10629-
return false;
10630-
};
10639+
fn short_circuit_stub_conditional(block: &Block) -> Option<Instruction> {
10640+
let cond_idx = trailing_conditional_jump_index(block)?;
10641+
if cond_idx < 2 {
10642+
return None;
10643+
}
1063110644
let [first, second, ..] = block.instructions.as_slice() else {
10632-
return false;
10645+
return None;
1063310646
};
1063410647
if !matches!(first.instr.real(), Some(Instruction::Copy { i }) if i.get(first.arg) == 1)
1063510648
|| !matches!(second.instr.real(), Some(Instruction::ToBool))
1063610649
{
10637-
return false;
10650+
return None;
1063810651
}
10652+
10653+
let only_markers_between = block.instructions[2..cond_idx].iter().all(|info| {
10654+
matches!(
10655+
info.instr.real(),
10656+
None | Some(Instruction::Nop | Instruction::NotTaken)
10657+
)
10658+
});
10659+
if !only_markers_between {
10660+
return None;
10661+
}
10662+
10663+
block.instructions[cond_idx].instr.real()
10664+
}
10665+
10666+
fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool {
10667+
let Some(conditional) = short_circuit_stub_conditional(block) else {
10668+
return false;
10669+
};
1063910670
matches!(
10640-
(source.real(), block.instructions[cond_idx].instr.real()),
10671+
(source.real(), Some(conditional)),
1064110672
(
1064210673
Some(Instruction::PopJumpIfFalse { .. }),
1064310674
Some(Instruction::PopJumpIfTrue { .. })
@@ -10649,17 +10680,9 @@ fn opposite_short_circuit_target(block: &Block, source: AnyInstruction) -> bool
1064910680
}
1065010681

1065110682
fn same_short_circuit_target(block: &Block, source: AnyInstruction) -> Option<BlockIdx> {
10652-
let cond_idx = trailing_conditional_jump_index(block)?;
10653-
let [first, second, ..] = block.instructions.as_slice() else {
10654-
return None;
10655-
};
10656-
if !matches!(first.instr.real(), Some(Instruction::Copy { i }) if i.get(first.arg) == 1)
10657-
|| !matches!(second.instr.real(), Some(Instruction::ToBool))
10658-
{
10659-
return None;
10660-
}
10683+
let conditional = short_circuit_stub_conditional(block)?;
1066110684
matches!(
10662-
(source.real(), block.instructions[cond_idx].instr.real()),
10685+
(source.real(), Some(conditional)),
1066310686
(
1066410687
Some(Instruction::PopJumpIfFalse { .. }),
1066510688
Some(Instruction::PopJumpIfFalse { .. })
@@ -10668,7 +10691,7 @@ fn same_short_circuit_target(block: &Block, source: AnyInstruction) -> Option<Bl
1066810691
Some(Instruction::PopJumpIfTrue { .. })
1066910692
)
1067010693
)
10671-
.then_some(block.instructions[cond_idx].target)
10694+
.then_some(block.instructions[trailing_conditional_jump_index(block)?].target)
1067210695
}
1067310696

1067410697
#[derive(Clone, Copy, PartialEq, Eq)]
@@ -14300,3 +14323,94 @@ pub(crate) fn fixup_deref_opargs(blocks: &mut [Block], cellfixedoffsets: &[u32])
1430014323
}
1430114324
}
1430214325
}
14326+
14327+
#[cfg(test)]
14328+
mod tests {
14329+
use super::*;
14330+
14331+
fn instruction_info(instr: Instruction, arg: u32, target: BlockIdx) -> InstructionInfo {
14332+
InstructionInfo {
14333+
instr: instr.into(),
14334+
arg: OpArg::new(arg),
14335+
target,
14336+
location: SourceLocation::default(),
14337+
end_location: SourceLocation::default(),
14338+
except_handler: None,
14339+
folded_from_nonliteral_expr: false,
14340+
lineno_override: None,
14341+
cache_entries: 0,
14342+
preserve_redundant_jump_as_nop: false,
14343+
remove_no_location_nop: false,
14344+
preserve_block_start_no_location_nop: false,
14345+
}
14346+
}
14347+
14348+
#[test]
14349+
fn short_circuit_stub_allows_only_marker_instructions_before_jump() {
14350+
let final_target = BlockIdx(7);
14351+
let block = Block {
14352+
instructions: vec![
14353+
instruction_info(Instruction::Copy { i: Arg::marker() }, 1, BlockIdx::NULL),
14354+
instruction_info(Instruction::ToBool, 0, BlockIdx::NULL),
14355+
instruction_info(Instruction::Nop, 0, BlockIdx::NULL),
14356+
instruction_info(Instruction::NotTaken, 0, BlockIdx::NULL),
14357+
instruction_info(
14358+
Instruction::PopJumpIfFalse {
14359+
delta: Arg::marker(),
14360+
},
14361+
0,
14362+
final_target,
14363+
),
14364+
],
14365+
..Block::default()
14366+
};
14367+
14368+
assert_eq!(
14369+
same_short_circuit_target(
14370+
&block,
14371+
Instruction::PopJumpIfFalse {
14372+
delta: Arg::marker(),
14373+
}
14374+
.into(),
14375+
),
14376+
Some(final_target)
14377+
);
14378+
}
14379+
14380+
#[test]
14381+
fn short_circuit_stub_rejects_real_instruction_before_jump() {
14382+
let block = Block {
14383+
instructions: vec![
14384+
instruction_info(Instruction::Copy { i: Arg::marker() }, 1, BlockIdx::NULL),
14385+
instruction_info(Instruction::ToBool, 0, BlockIdx::NULL),
14386+
instruction_info(Instruction::PopTop, 0, BlockIdx::NULL),
14387+
instruction_info(
14388+
Instruction::PopJumpIfFalse {
14389+
delta: Arg::marker(),
14390+
},
14391+
0,
14392+
BlockIdx(7),
14393+
),
14394+
],
14395+
..Block::default()
14396+
};
14397+
14398+
assert_eq!(
14399+
same_short_circuit_target(
14400+
&block,
14401+
Instruction::PopJumpIfFalse {
14402+
delta: Arg::marker(),
14403+
}
14404+
.into(),
14405+
),
14406+
None
14407+
);
14408+
assert!(!opposite_short_circuit_target(
14409+
&block,
14410+
Instruction::PopJumpIfTrue {
14411+
delta: Arg::marker(),
14412+
}
14413+
.into()
14414+
));
14415+
}
14416+
}

0 commit comments

Comments
 (0)