Skip to content

Commit 11152d4

Browse files
morealclaude
andcommitted
Add compile_bool_op_inner and optimize nested opposite-operator BoolOps to avoid redundant __bool__ calls
When a nested BoolOp has the opposite operator (e.g., `And` inside `Or`), the inner BoolOp's short-circuit exits are redirected to skip the outer BoolOp's redundant truth test. This avoids calling `__bool__()` twice on the same value (e.g., `Test() and False or False` previously called `Test().__bool__()` twice instead of once). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6b46052 commit 11152d4

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

crates/codegen/src/compile.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6616,6 +6616,44 @@ impl Compiler {
66166616
let (last_value, values) = values.split_last().unwrap();
66176617

66186618
for value in values {
6619+
// Optimization: when a non-last value is a BoolOp with the opposite
6620+
// operator, redirect its short-circuit exits to skip the outer's
6621+
// redundant __bool__ test (jump threading).
6622+
if let ast::Expr::BoolOp(ast::ExprBoolOp {
6623+
op: inner_op,
6624+
values: inner_values,
6625+
..
6626+
}) = value
6627+
{
6628+
if inner_op != op {
6629+
let pop_block = self.new_block();
6630+
self.compile_bool_op_inner(inner_op, inner_values, Some(pop_block))?;
6631+
// Test the inner result for the outer BoolOp
6632+
emit!(self, Instruction::Copy { index: 1_u32 });
6633+
match op {
6634+
ast::BoolOp::And => {
6635+
emit!(
6636+
self,
6637+
Instruction::PopJumpIfFalse {
6638+
target: after_block,
6639+
}
6640+
);
6641+
}
6642+
ast::BoolOp::Or => {
6643+
emit!(
6644+
self,
6645+
Instruction::PopJumpIfTrue {
6646+
target: after_block,
6647+
}
6648+
);
6649+
}
6650+
}
6651+
self.switch_to_block(pop_block);
6652+
emit!(self, Instruction::PopTop);
6653+
continue;
6654+
}
6655+
}
6656+
66196657
self.compile_expression(value)?;
66206658

66216659
emit!(self, Instruction::Copy { index: 1_u32 });
@@ -6647,6 +6685,54 @@ impl Compiler {
66476685
Ok(())
66486686
}
66496687

6688+
/// Compile a boolean operation as an expression, with an optional
6689+
/// short-circuit target override. When `short_circuit_target` is `Some`,
6690+
/// the short-circuit jumps go to that block instead of the default
6691+
/// `after_block`, enabling jump threading to avoid redundant `__bool__` calls.
6692+
fn compile_bool_op_inner(
6693+
&mut self,
6694+
op: &ast::BoolOp,
6695+
values: &[ast::Expr],
6696+
short_circuit_target: Option<BlockIdx>,
6697+
) -> CompileResult<()> {
6698+
let after_block = self.new_block();
6699+
6700+
let (last_value, values) = values.split_last().unwrap();
6701+
6702+
let target = short_circuit_target.unwrap_or(after_block);
6703+
6704+
for value in values {
6705+
self.compile_expression(value)?;
6706+
6707+
emit!(self, Instruction::Copy { index: 1_u32 });
6708+
match op {
6709+
ast::BoolOp::And => {
6710+
emit!(
6711+
self,
6712+
Instruction::PopJumpIfFalse {
6713+
target,
6714+
}
6715+
);
6716+
}
6717+
ast::BoolOp::Or => {
6718+
emit!(
6719+
self,
6720+
Instruction::PopJumpIfTrue {
6721+
target,
6722+
}
6723+
);
6724+
}
6725+
}
6726+
6727+
emit!(self, Instruction::PopTop);
6728+
}
6729+
6730+
// If all values did not qualify, take the value of the last value:
6731+
self.compile_expression(last_value)?;
6732+
self.switch_to_block(after_block);
6733+
Ok(())
6734+
}
6735+
66506736
fn compile_dict(&mut self, items: &[ast::DictItem]) -> CompileResult<()> {
66516737
let has_unpacking = items.iter().any(|item| item.key.is_none());
66526738

0 commit comments

Comments
 (0)