@@ -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
1065110682fn 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