Skip to content

Commit 15e2b2e

Browse files
committed
Add do_not_emit_bytecode for constant folding dead code elimination
Walk dead branches to consume sub_tables without emitting bytecode, matching CPython's BEGIN_DO_NOT_EMIT_BYTECODE pattern. Fixes freeze compilation errors for files containing scope-creating definitions inside dead branches (e.g. if False: def foo(): ...).
1 parent ed87ab6 commit 15e2b2e

File tree

3 files changed

+87
-28
lines changed

3 files changed

+87
-28
lines changed

crates/codegen/src/compile.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ struct Compiler {
154154
/// True when compiling in "single" (interactive) mode.
155155
/// Expression statements at module scope emit CALL_INTRINSIC_1(Print).
156156
interactive: bool,
157+
/// Counter for dead-code elimination during constant folding.
158+
/// When > 0, the compiler walks AST (consuming sub_tables) but emits no bytecode.
159+
/// Mirrors CPython's `c_do_not_emit_bytecode`.
160+
do_not_emit_bytecode: u32,
157161
}
158162

159163
#[derive(Clone, Copy)]
@@ -465,6 +469,7 @@ impl Compiler {
465469
opts,
466470
in_annotation: false,
467471
interactive: false,
472+
do_not_emit_bytecode: 0,
468473
}
469474
}
470475

@@ -5186,30 +5191,37 @@ impl Compiler {
51865191
) -> CompileResult<()> {
51875192
let constant = Self::expr_constant(test);
51885193

5189-
// If the test is constant false, skip the body entirely
5194+
// If the test is constant false, walk the body (consuming sub_tables)
5195+
// but don't emit bytecode
51905196
if constant == Some(false) {
5191-
// The NOP is emitted by CPython to keep line number info
51925197
self.emit_nop();
5198+
self.do_not_emit_bytecode += 1;
5199+
self.compile_statements(body)?;
5200+
self.do_not_emit_bytecode -= 1;
51935201
// Compile the elif/else chain (if any)
51945202
match elif_else_clauses {
5195-
[] => {} // nothing to do
5203+
[] => {}
51965204
[first, rest @ ..] => {
51975205
if let Some(elif_test) = &first.test {
5198-
// elif: recursively compile with elif as the new if
51995206
self.compile_if(elif_test, &first.body, rest)?;
52005207
} else {
5201-
// else: compile the else body directly
52025208
self.compile_statements(&first.body)?;
52035209
}
52045210
}
52055211
}
52065212
return Ok(());
52075213
}
52085214

5209-
// If the test is constant true, compile body directly
5215+
// If the test is constant true, compile body directly,
5216+
// but walk elif/else without emitting
52105217
if constant == Some(true) {
52115218
self.emit_nop();
52125219
self.compile_statements(body)?;
5220+
self.do_not_emit_bytecode += 1;
5221+
for clause in elif_else_clauses {
5222+
self.compile_statements(&clause.body)?;
5223+
}
5224+
self.do_not_emit_bytecode -= 1;
52135225
return Ok(());
52145226
}
52155227

@@ -5264,9 +5276,13 @@ impl Compiler {
52645276

52655277
let constant = Self::expr_constant(test);
52665278

5267-
// while False: body → skip body, compile orelse only
5279+
// while False: body → walk body (consuming sub_tables) but don't emit,
5280+
// then compile orelse
52685281
if constant == Some(false) {
52695282
self.emit_nop();
5283+
self.do_not_emit_bytecode += 1;
5284+
self.compile_statements(body)?;
5285+
self.do_not_emit_bytecode -= 1;
52705286
self.compile_statements(orelse)?;
52715287
self.leave_conditional_block();
52725288
return Ok(());
@@ -8719,6 +8735,9 @@ impl Compiler {
87198735

87208736
// Low level helper functions:
87218737
fn _emit<I: Into<AnyInstruction>>(&mut self, instr: I, arg: OpArg, target: BlockIdx) {
8738+
if self.do_not_emit_bytecode > 0 {
8739+
return;
8740+
}
87228741
let range = self.current_source_range;
87238742
let source = self.source_file.to_source_code();
87248743
let location = source.source_location(range.start(), PositionEncoding::Utf8);
@@ -8970,6 +8989,10 @@ impl Compiler {
89708989
range: ruff_text_size::TextRange,
89718990
is_break: bool,
89728991
) -> CompileResult<()> {
8992+
if self.do_not_emit_bytecode > 0 {
8993+
return Ok(());
8994+
}
8995+
89738996
// unwind_fblock_stack
89748997
// We need to unwind fblocks and compile cleanup code. For FinallyTry blocks,
89758998
// we need to compile the finally body inline, but we must temporarily pop

crates/codegen/src/ir.rs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -729,8 +729,10 @@ impl CodeInfo {
729729
}
730730
let (const_idx, _) = self.metadata.consts.insert_full(result_const);
731731
// Replace first instruction with LOAD_CONST result
732-
block.instructions[i].instr =
733-
Instruction::LoadConst { consti: Arg::marker() }.into();
732+
block.instructions[i].instr = Instruction::LoadConst {
733+
consti: Arg::marker(),
734+
}
735+
.into();
734736
block.instructions[i].arg = OpArg::new(const_idx as u32);
735737
// NOP out the second and third instructions
736738
let loc = block.instructions[i].location;
@@ -743,17 +745,18 @@ impl CodeInfo {
743745
block.instructions[i + 2].end_location = end_loc;
744746
// Don't advance - check if the result can be folded again
745747
// (e.g., 2 ** 31 - 1)
746-
if i > 0 {
747-
i -= 1; // re-check with previous instruction
748-
}
748+
i = i.saturating_sub(1); // re-check with previous instruction
749749
} else {
750750
i += 1;
751751
}
752752
}
753753
}
754754
}
755755

756-
fn get_const_value_from(metadata: &CodeUnitMetadata, info: &InstructionInfo) -> Option<ConstantData> {
756+
fn get_const_value_from(
757+
metadata: &CodeUnitMetadata,
758+
info: &InstructionInfo,
759+
) -> Option<ConstantData> {
757760
match info.instr.real() {
758761
Some(Instruction::LoadConst { .. }) => {
759762
let idx = u32::from(info.arg) as usize;
@@ -769,7 +772,11 @@ impl CodeInfo {
769772
}
770773
}
771774

772-
fn eval_binop(left: &ConstantData, right: &ConstantData, op: oparg::BinaryOperator) -> Option<ConstantData> {
775+
fn eval_binop(
776+
left: &ConstantData,
777+
right: &ConstantData,
778+
op: oparg::BinaryOperator,
779+
) -> Option<ConstantData> {
773780
use oparg::BinaryOperator as BinOp;
774781
match (left, right) {
775782
(ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) => {
@@ -778,21 +785,29 @@ impl CodeInfo {
778785
BinOp::Subtract => l - r,
779786
BinOp::Multiply => l * r,
780787
BinOp::FloorDivide => {
781-
if r.is_zero() { return None; }
788+
if r.is_zero() {
789+
return None;
790+
}
782791
l / r
783792
}
784793
BinOp::Remainder => {
785-
if r.is_zero() { return None; }
794+
if r.is_zero() {
795+
return None;
796+
}
786797
l % r
787798
}
788799
BinOp::Power => {
789800
let exp: u32 = r.try_into().ok()?;
790-
if exp > 128 { return None; } // prevent huge results
801+
if exp > 128 {
802+
return None;
803+
} // prevent huge results
791804
num_traits::pow::pow(l.clone(), exp as usize)
792805
}
793806
BinOp::Lshift => {
794807
let shift: u32 = r.try_into().ok()?;
795-
if shift > 128 { return None; }
808+
if shift > 128 {
809+
return None;
810+
}
796811
l << (shift as usize)
797812
}
798813
BinOp::Rshift => {
@@ -812,21 +827,29 @@ impl CodeInfo {
812827
BinOp::Subtract => l - r,
813828
BinOp::Multiply => l * r,
814829
BinOp::TrueDivide => {
815-
if *r == 0.0 { return None; }
830+
if *r == 0.0 {
831+
return None;
832+
}
816833
l / r
817834
}
818835
BinOp::FloorDivide => {
819-
if *r == 0.0 { return None; }
836+
if *r == 0.0 {
837+
return None;
838+
}
820839
(l / r).floor()
821840
}
822841
BinOp::Remainder => {
823-
if *r == 0.0 { return None; }
842+
if *r == 0.0 {
843+
return None;
844+
}
824845
l % r
825846
}
826847
BinOp::Power => l.powf(*r),
827848
_ => return None,
828849
};
829-
if !result.is_finite() { return None; }
850+
if !result.is_finite() {
851+
return None;
852+
}
830853
Some(ConstantData::Float { value: result })
831854
}
832855
// Int op Float or Float op Int → Float
@@ -847,16 +870,26 @@ impl CodeInfo {
847870
)
848871
}
849872
// String concatenation and repetition
850-
(ConstantData::Str { value: l }, ConstantData::Str { value: r }) if matches!(op, BinOp::Add) => {
873+
(ConstantData::Str { value: l }, ConstantData::Str { value: r })
874+
if matches!(op, BinOp::Add) =>
875+
{
851876
let mut result = l.to_string();
852877
result.push_str(&r.to_string());
853-
Some(ConstantData::Str { value: result.into() })
878+
Some(ConstantData::Str {
879+
value: result.into(),
880+
})
854881
}
855-
(ConstantData::Str { value: s }, ConstantData::Integer { value: n }) if matches!(op, BinOp::Multiply) => {
882+
(ConstantData::Str { value: s }, ConstantData::Integer { value: n })
883+
if matches!(op, BinOp::Multiply) =>
884+
{
856885
let n: usize = n.try_into().ok()?;
857-
if n > 4096 { return None; }
886+
if n > 4096 {
887+
return None;
888+
}
858889
let result = s.to_string().repeat(n);
859-
Some(ConstantData::Str { value: result.into() })
890+
Some(ConstantData::Str {
891+
value: result.into(),
892+
})
860893
}
861894
_ => None,
862895
}

crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap

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

0 commit comments

Comments
 (0)