Constant folding: fold LOAD_CONST/LOAD_SMALL_INT + LOAD_CONST/LOAD_SMALL_INT + BINARY_OP into a single LOAD_CONST when the result is computable at compile time. = fold_binops_on_constants in CPython flowgraph.c
(&mut self)
| 708 | /// into a single LOAD_CONST when the result is computable at compile time. |
| 709 | /// = fold_binops_on_constants in CPython flowgraph.c |
| 710 | fn fold_binop_constants(&mut self) { |
| 711 | use oparg::BinaryOperator as BinOp; |
| 712 | |
| 713 | for block in &mut self.blocks { |
| 714 | let mut i = 0; |
| 715 | while i + 2 < block.instructions.len() { |
| 716 | // Check pattern: LOAD_CONST/LOAD_SMALL_INT, LOAD_CONST/LOAD_SMALL_INT, BINARY_OP |
| 717 | let Some(Instruction::BinaryOp { .. }) = block.instructions[i + 2].instr.real() |
| 718 | else { |
| 719 | i += 1; |
| 720 | continue; |
| 721 | }; |
| 722 | |
| 723 | let op_raw = u32::from(block.instructions[i + 2].arg); |
| 724 | let Ok(op) = BinOp::try_from(op_raw) else { |
| 725 | i += 1; |
| 726 | continue; |
| 727 | }; |
| 728 | |
| 729 | let left = Self::get_const_value_from(&self.metadata, &block.instructions[i]); |
| 730 | let right = Self::get_const_value_from(&self.metadata, &block.instructions[i + 1]); |
| 731 | |
| 732 | let (Some(left_val), Some(right_val)) = (left, right) else { |
| 733 | i += 1; |
| 734 | continue; |
| 735 | }; |
| 736 | |
| 737 | let result = Self::eval_binop(&left_val, &right_val, op); |
| 738 | |
| 739 | if let Some(result_const) = result { |
| 740 | // Check result size limit (CPython limits to 4096 bytes) |
| 741 | if Self::const_too_big(&result_const) { |
| 742 | i += 1; |
| 743 | continue; |
| 744 | } |
| 745 | let (const_idx, _) = self.metadata.consts.insert_full(result_const); |
| 746 | // Replace first instruction with LOAD_CONST result |
| 747 | block.instructions[i].instr = Instruction::LoadConst { |
| 748 | consti: Arg::marker(), |
| 749 | } |
| 750 | .into(); |
| 751 | block.instructions[i].arg = OpArg::new(const_idx as u32); |
| 752 | // NOP out the second and third instructions |
| 753 | let loc = block.instructions[i].location; |
| 754 | let end_loc = block.instructions[i].end_location; |
| 755 | block.instructions[i + 1].instr = Instruction::Nop.into(); |
| 756 | block.instructions[i + 1].location = loc; |
| 757 | block.instructions[i + 1].end_location = end_loc; |
| 758 | block.instructions[i + 2].instr = Instruction::Nop.into(); |
| 759 | block.instructions[i + 2].location = loc; |
| 760 | block.instructions[i + 2].end_location = end_loc; |
| 761 | // Don't advance - check if the result can be folded again |
| 762 | // (e.g., 2 ** 31 - 1) |
| 763 | i = i.saturating_sub(1); // re-check with previous instruction |
| 764 | } else { |
| 765 | i += 1; |
| 766 | } |
| 767 | } |
no test coverage detected