2222//! | `x == x` | `1` |
2323//! | `x != x` | `0` |
2424
25- use super :: utils:: instr_dest;
26- use crate :: ir:: { BinOp , IrFunction , IrInstr , IrValue , VarId } ;
27- use std:: collections:: HashMap ;
25+ use crate :: {
26+ ir:: { BinOp , IrFunction , IrInstr , IrValue , VarId } ,
27+ optimizer:: utils:: build_global_const_map,
28+ } ;
2829
2930// ── Public entry point ────────────────────────────────────────────────────────
3031
32+ /// Eliminates algebraic identities and annihilators until reaching a fixed point.
33+ ///
34+ /// Runs multiple passes because simplifications from one pass can expose new
35+ /// optimization opportunities. For example, `(x * 1) + 0` requires two passes:
36+ /// first `x * 1 → x`, then `x + 0 → x`.
3137pub fn eliminate ( func : & mut IrFunction ) {
38+ // Run passes until reaching a fixed point (no more changes)
39+ loop {
40+ if !eliminate_once ( func) {
41+ break ;
42+ }
43+ }
44+ }
45+
46+ fn eliminate_once ( func : & mut IrFunction ) -> bool {
3247 let global_consts = build_global_const_map ( func) ;
48+ let mut changed = false ;
3349
3450 for block in & mut func. blocks {
3551 let mut local_consts = global_consts. clone ( ) ;
3652
3753 for instr in & mut block. instructions {
38- // Track constants defined in this block.
54+ // Track constants defined in this block. We walk forward and update
55+ // local_consts as we encounter Const instructions, so that later BinOp
56+ // instructions in the same block can see newly-defined constants.
57+ // (global_consts only captures constants with a single def across the
58+ // entire function, not block-local defs discovered during forward walk.)
3959 if let IrInstr :: Const { dest, value } = instr {
4060 local_consts. insert ( * dest, * value) ;
4161 continue ;
@@ -50,6 +70,7 @@ pub fn eliminate(func: &mut IrFunction) {
5070 if lhs == rhs {
5171 if let Some ( replacement) = same_operand_rule ( op, dest, lhs) {
5272 * instr = replacement;
73+ changed = true ;
5374 if let IrInstr :: Const { dest, value } = instr {
5475 local_consts. insert ( * dest, * value) ;
5576 }
@@ -62,35 +83,15 @@ pub fn eliminate(func: &mut IrFunction) {
6283
6384 if let Some ( replacement) = constant_operand_rule ( op, dest, lhs, rhs, lhs_val, rhs_val) {
6485 * instr = replacement;
86+ changed = true ;
6587 if let IrInstr :: Const { dest, value } = instr {
6688 local_consts. insert ( * dest, * value) ;
6789 }
6890 }
6991 }
7092 }
71- }
72-
73- // ── Global constant map ───────────────────────────────────────────────────────
74-
75- fn build_global_const_map ( func : & IrFunction ) -> HashMap < VarId , IrValue > {
76- let mut total_defs: HashMap < VarId , usize > = HashMap :: new ( ) ;
77- let mut const_defs: HashMap < VarId , IrValue > = HashMap :: new ( ) ;
78-
79- for block in & func. blocks {
80- for instr in & block. instructions {
81- if let Some ( dest) = instr_dest ( instr) {
82- * total_defs. entry ( dest) . or_insert ( 0 ) += 1 ;
83- if let IrInstr :: Const { dest, value } = instr {
84- const_defs. insert ( * dest, * value) ;
85- }
86- }
87- }
88- }
8993
90- const_defs
91- . into_iter ( )
92- . filter ( |( v, _) | total_defs. get ( v) . copied ( ) . unwrap_or ( 0 ) == 1 )
93- . collect ( )
94+ changed
9495}
9596
9697// ── Same-operand rules ────────────────────────────────────────────────────────
@@ -108,32 +109,34 @@ fn same_operand_rule(op: BinOp, dest: VarId, operand: VarId) -> Option<IrInstr>
108109 } ) ,
109110
110111 // x == x → 1 (integers only; floats have NaN)
111- BinOp :: I32Eq | BinOp :: I32LeS | BinOp :: I32LeU | BinOp :: I32GeS | BinOp :: I32GeU => {
112- Some ( IrInstr :: Const {
113- dest,
114- value : IrValue :: I32 ( 1 ) ,
115- } )
116- }
117- BinOp :: I64Eq | BinOp :: I64LeS | BinOp :: I64LeU | BinOp :: I64GeS | BinOp :: I64GeU => {
118- Some ( IrInstr :: Const {
119- dest,
120- value : IrValue :: I32 ( 1 ) ,
121- } )
122- }
112+ BinOp :: I32Eq
113+ | BinOp :: I32LeS
114+ | BinOp :: I32LeU
115+ | BinOp :: I32GeS
116+ | BinOp :: I32GeU
117+ | BinOp :: I64Eq
118+ | BinOp :: I64LeS
119+ | BinOp :: I64LeU
120+ | BinOp :: I64GeS
121+ | BinOp :: I64GeU => Some ( IrInstr :: Const {
122+ dest,
123+ value : IrValue :: I32 ( 1 ) ,
124+ } ) ,
123125
124126 // x != x → 0 (integers only)
125- BinOp :: I32Ne | BinOp :: I32LtS | BinOp :: I32LtU | BinOp :: I32GtS | BinOp :: I32GtU => {
126- Some ( IrInstr :: Const {
127- dest,
128- value : IrValue :: I32 ( 0 ) ,
129- } )
130- }
131- BinOp :: I64Ne | BinOp :: I64LtS | BinOp :: I64LtU | BinOp :: I64GtS | BinOp :: I64GtU => {
132- Some ( IrInstr :: Const {
133- dest,
134- value : IrValue :: I32 ( 0 ) ,
135- } )
136- }
127+ BinOp :: I32Ne
128+ | BinOp :: I32LtS
129+ | BinOp :: I32LtU
130+ | BinOp :: I32GtS
131+ | BinOp :: I32GtU
132+ | BinOp :: I64Ne
133+ | BinOp :: I64LtS
134+ | BinOp :: I64LtU
135+ | BinOp :: I64GtS
136+ | BinOp :: I64GtU => Some ( IrInstr :: Const {
137+ dest,
138+ value : IrValue :: I32 ( 0 ) ,
139+ } ) ,
137140
138141 // x - x → 0 (integers only; floats have Inf - Inf = NaN)
139142 BinOp :: I32Sub => Some ( IrInstr :: Const {
@@ -778,4 +781,53 @@ mod tests {
778781 }
779782 ) ) ;
780783 }
784+
785+ // ── Cascading optimizations (multiple passes needed) ───────────────────
786+
787+ #[ test]
788+ fn cascading_mul_one_and_add_zero ( ) {
789+ // v1 = 1; v2 = v0 * v1; v3 = 0; v4 = v2 + v3
790+ // Pass 1: v2 = v0 * 1 → v2 = Assign(v0)
791+ // v4 = v2 + 0 → NOT YET (v2 is not recognized as const)
792+ // Pass 2: v4 = v2 + 0 → v4 = Assign(v2) → v4 = Assign(v0)
793+ let mut func = make_func ( single_block ( vec ! [
794+ IrInstr :: Const {
795+ dest: VarId ( 1 ) ,
796+ value: IrValue :: I32 ( 1 ) ,
797+ } ,
798+ IrInstr :: BinOp {
799+ dest: VarId ( 2 ) ,
800+ op: BinOp :: I32Mul ,
801+ lhs: VarId ( 0 ) ,
802+ rhs: VarId ( 1 ) ,
803+ } ,
804+ IrInstr :: Const {
805+ dest: VarId ( 3 ) ,
806+ value: IrValue :: I32 ( 0 ) ,
807+ } ,
808+ IrInstr :: BinOp {
809+ dest: VarId ( 4 ) ,
810+ op: BinOp :: I32Add ,
811+ lhs: VarId ( 2 ) ,
812+ rhs: VarId ( 3 ) ,
813+ } ,
814+ ] ) ) ;
815+ eliminate ( & mut func) ;
816+ // After multiple passes, both simplifications should be applied.
817+ // v2 should be Assign(v0) and v4 should be Assign(v0) (through v2).
818+ assert ! ( matches!(
819+ func. blocks[ 0 ] . instructions[ 1 ] ,
820+ IrInstr :: Assign {
821+ dest: VarId ( 2 ) ,
822+ src: VarId ( 0 )
823+ }
824+ ) ) ;
825+ assert ! ( matches!(
826+ func. blocks[ 0 ] . instructions[ 3 ] ,
827+ IrInstr :: Assign {
828+ dest: VarId ( 4 ) ,
829+ src: VarId ( 2 )
830+ }
831+ ) ) ;
832+ }
781833}
0 commit comments