Skip to main content

synth_opt/
lib.rs

1//! Optimization passes for Synth compiler
2//!
3//! This crate provides optimization passes that improve generated code quality.
4//!
5//! ## Pass Types
6//!
7//! - **Analysis Passes**: Gather information without modifying code
8//! - **Transform Passes**: Modify code to improve quality
9//! - **Cleanup Passes**: Remove dead/redundant code
10//!
11//! ## Available Passes
12//!
13//! - Dead Code Elimination (DCE)
14//! - Constant Folding
15//! - Common Subexpression Elimination (CSE)
16//! - Loop-Invariant Code Motion (LICM)
17
18use std::collections::{HashMap, HashSet};
19use synth_cfg::{BlockId, Cfg};
20
21/// Optimization pass trait
22pub trait OptimizationPass {
23    /// Name of this pass
24    fn name(&self) -> &'static str;
25
26    /// Run the optimization pass
27    fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult;
28}
29
30/// Result of an optimization pass
31#[derive(Debug, Clone)]
32pub struct OptResult {
33    /// Whether any changes were made
34    pub changed: bool,
35
36    /// Number of instructions removed
37    pub removed_count: usize,
38
39    /// Number of instructions added
40    pub added_count: usize,
41
42    /// Number of instructions modified
43    pub modified_count: usize,
44}
45
46impl OptResult {
47    pub fn no_change() -> Self {
48        Self {
49            changed: false,
50            removed_count: 0,
51            added_count: 0,
52            modified_count: 0,
53        }
54    }
55
56    pub fn merge(&mut self, other: OptResult) {
57        self.changed |= other.changed;
58        self.removed_count += other.removed_count;
59        self.added_count += other.added_count;
60        self.modified_count += other.modified_count;
61    }
62}
63
64/// Instruction placeholder (would be actual IR in real implementation)
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct Instruction {
67    pub id: usize,
68    pub opcode: Opcode,
69    pub block_id: BlockId,
70    pub is_dead: bool,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum Opcode {
75    Nop,
76    // Arithmetic
77    Add {
78        dest: Reg,
79        src1: Reg,
80        src2: Reg,
81    },
82    Sub {
83        dest: Reg,
84        src1: Reg,
85        src2: Reg,
86    },
87    Mul {
88        dest: Reg,
89        src1: Reg,
90        src2: Reg,
91    },
92    DivS {
93        dest: Reg,
94        src1: Reg,
95        src2: Reg,
96    }, // Signed division
97    DivU {
98        dest: Reg,
99        src1: Reg,
100        src2: Reg,
101    }, // Unsigned division
102    RemS {
103        dest: Reg,
104        src1: Reg,
105        src2: Reg,
106    }, // Signed remainder
107    RemU {
108        dest: Reg,
109        src1: Reg,
110        src2: Reg,
111    }, // Unsigned remainder
112    // Bitwise
113    And {
114        dest: Reg,
115        src1: Reg,
116        src2: Reg,
117    },
118    Or {
119        dest: Reg,
120        src1: Reg,
121        src2: Reg,
122    },
123    Xor {
124        dest: Reg,
125        src1: Reg,
126        src2: Reg,
127    },
128    Shl {
129        dest: Reg,
130        src1: Reg,
131        src2: Reg,
132    }, // Shift left
133    ShrS {
134        dest: Reg,
135        src1: Reg,
136        src2: Reg,
137    }, // Shift right signed
138    ShrU {
139        dest: Reg,
140        src1: Reg,
141        src2: Reg,
142    }, // Shift right unsigned
143    Rotl {
144        dest: Reg,
145        src1: Reg,
146        src2: Reg,
147    }, // Rotate left
148    Rotr {
149        dest: Reg,
150        src1: Reg,
151        src2: Reg,
152    }, // Rotate right
153    // Bit count (unary)
154    Clz {
155        dest: Reg,
156        src: Reg,
157    }, // Count leading zeros
158    Ctz {
159        dest: Reg,
160        src: Reg,
161    }, // Count trailing zeros
162    Popcnt {
163        dest: Reg,
164        src: Reg,
165    }, // Population count
166    // Sign extension (unary)
167    Extend8S {
168        dest: Reg,
169        src: Reg,
170    }, // Sign-extend byte to 32-bit
171    Extend16S {
172        dest: Reg,
173        src: Reg,
174    }, // Sign-extend halfword to 32-bit
175    // Comparison (result is 0 or 1)
176    Eqz {
177        dest: Reg,
178        src: Reg,
179    }, // Equal to zero (unary)
180    Eq {
181        dest: Reg,
182        src1: Reg,
183        src2: Reg,
184    },
185    Ne {
186        dest: Reg,
187        src1: Reg,
188        src2: Reg,
189    },
190    LtS {
191        dest: Reg,
192        src1: Reg,
193        src2: Reg,
194    }, // Less than signed
195    LtU {
196        dest: Reg,
197        src1: Reg,
198        src2: Reg,
199    }, // Less than unsigned
200    LeS {
201        dest: Reg,
202        src1: Reg,
203        src2: Reg,
204    }, // Less or equal signed
205    LeU {
206        dest: Reg,
207        src1: Reg,
208        src2: Reg,
209    }, // Less or equal unsigned
210    GtS {
211        dest: Reg,
212        src1: Reg,
213        src2: Reg,
214    }, // Greater than signed
215    GtU {
216        dest: Reg,
217        src1: Reg,
218        src2: Reg,
219    }, // Greater than unsigned
220    GeS {
221        dest: Reg,
222        src1: Reg,
223        src2: Reg,
224    }, // Greater or equal signed
225    GeU {
226        dest: Reg,
227        src1: Reg,
228        src2: Reg,
229    }, // Greater or equal unsigned
230    // Memory (local variable access)
231    Load {
232        dest: Reg,
233        addr: u32,
234    },
235    Store {
236        src: Reg,
237        addr: u32,
238    },
239    /// Load 64-bit value from local into register pair (for i64 on 32-bit ARM)
240    I64Load {
241        dest_lo: Reg,
242        dest_hi: Reg,
243        addr: u32,
244    },
245    // Linear memory access (WASM i32.load / i32.store)
246    /// Load 32-bit value from linear memory: dest = mem[addr + offset]
247    MemLoad {
248        dest: Reg,
249        addr: Reg,
250        offset: u32,
251    },
252    /// Store 32-bit value to linear memory: mem[addr + offset] = src
253    MemStore {
254        src: Reg,
255        addr: Reg,
256        offset: u32,
257    },
258
259    /// Sub-word load: i32.load8_s/u, i32.load16_s/u and the i64 equivalents
260    /// after extension. `width` is 1 or 2 bytes; `signed` is true for the
261    /// `_s` variants. The dest is a single i32 register (sub-word loads on
262    /// ARM expand to a single LDRB/LDRH ± an SXTB/SXTH).
263    MemLoadSubword {
264        dest: Reg,
265        addr: Reg,
266        offset: u32,
267        width: u32, // 1 or 2
268        signed: bool,
269    },
270    /// Sub-word store: i32.store8 / i32.store16 / i64.store8/16/32.
271    /// `width` is 1, 2, or 4. For 4-byte store of an i64's low word we
272    /// just take the lo register.
273    MemStoreSubword {
274        src: Reg,
275        addr: Reg,
276        offset: u32,
277        width: u32, // 1, 2, or 4
278    },
279
280    /// `global.get` — load a global into a fresh vreg. The actual ARM
281    /// lowering is `LDR rd, [R9, #offset]` (R9 is the globals base, by
282    /// convention). The vreg MUST be allocated to a non-AAPCS-param
283    /// register at lowering time — otherwise the LDR would clobber a
284    /// live caller arg.
285    GlobalGet {
286        dest: Reg,
287        idx: u32,
288    },
289    /// `global.set` — store a vreg to a global slot. Emits `STR src,
290    /// [R9, #offset]`. No vreg is written.
291    GlobalSet {
292        src: Reg,
293        idx: u32,
294    },
295
296    /// `memory.size` — returns the current memory size in pages as an
297    /// i32. By convention R10 holds the memory-size word; we emit
298    /// `MOV dest, R10`. `dest` must be allocated to a non-param scratch.
299    MemorySize {
300        dest: Reg,
301    },
302    /// `memory.grow` — pops the grow-by amount, pushes the previous
303    /// page count (or -1 on failure). Embedded targets generally have
304    /// fixed memory, so this is a stub returning -1 in `dest`.
305    MemoryGrow {
306        dest: Reg,
307        delta: Reg,
308    },
309    // Control flow
310    Branch {
311        target: BlockId,
312    },
313    CondBranch {
314        cond: Reg,
315        target: BlockId,
316    },
317    /// Direct function call. Lowered to `BL func_<func_idx>` (or to an import
318    /// dispatch for `func_idx < num_imports`). The AAPCS return value lands in
319    /// `R0`, so `ir_to_arm` binds `dest` to `R0` after the BL.
320    ///
321    /// Prior to this opcode, `WasmOp::Call` fell through to `Opcode::Nop` in
322    /// `wasm_to_ir`, leaving the call result's vreg unmapped. Any downstream
323    /// consumer (e.g., `i32.add` of two `call` results, as in the recursive
324    /// `fib` example) then triggered the PR #101 defensive panic — or, with
325    /// the silent R0 fallback, silently miscompiled. See the issue-#93 family
326    /// of bugs.
327    ///
328    /// NOTE: this opcode does NOT model the call's clobber of R0..R3 — that's
329    /// a separate (deeper) issue around correct AAPCS lowering of calls in
330    /// the optimized path. This fix only closes the unmapped-vreg gap so the
331    /// compiler stops panicking on lawful WASM modules.
332    Call {
333        dest: Reg,
334        func_idx: u32,
335    },
336    Return {
337        value: Option<Reg>,
338    },
339    /// Label marker for branch targets (loop start, block end)
340    Label {
341        id: BlockId,
342    },
343    /// Copy a value (for local.tee semantics: store value and keep it on stack)
344    Copy {
345        dest: Reg,
346        src: Reg,
347    },
348    /// TeeStore: Store to local AND keep value on stack (local.tee)
349    /// Combines Store + Copy: stores src to local addr, dest gets same value
350    TeeStore {
351        dest: Reg,
352        src: Reg,
353        addr: u32,
354    },
355    // Select: dest = cond != 0 ? val_true : val_false (ternary operator)
356    Select {
357        dest: Reg,
358        val_true: Reg,
359        val_false: Reg,
360        cond: Reg,
361    },
362    // Constants
363    Const {
364        dest: Reg,
365        value: i32,
366    },
367
368    // i64 operations (use register pairs on 32-bit ARM: lo, hi)
369    // dest_lo:dest_hi = src1_lo:src1_hi OP src2_lo:src2_hi
370    I64Add {
371        dest_lo: Reg,
372        dest_hi: Reg,
373        src1_lo: Reg,
374        src1_hi: Reg,
375        src2_lo: Reg,
376        src2_hi: Reg,
377    },
378    I64Sub {
379        dest_lo: Reg,
380        dest_hi: Reg,
381        src1_lo: Reg,
382        src1_hi: Reg,
383        src2_lo: Reg,
384        src2_hi: Reg,
385    },
386    I64And {
387        dest_lo: Reg,
388        dest_hi: Reg,
389        src1_lo: Reg,
390        src1_hi: Reg,
391        src2_lo: Reg,
392        src2_hi: Reg,
393    },
394    I64Or {
395        dest_lo: Reg,
396        dest_hi: Reg,
397        src1_lo: Reg,
398        src1_hi: Reg,
399        src2_lo: Reg,
400        src2_hi: Reg,
401    },
402    I64Xor {
403        dest_lo: Reg,
404        dest_hi: Reg,
405        src1_lo: Reg,
406        src1_hi: Reg,
407        src2_lo: Reg,
408        src2_hi: Reg,
409    },
410    I64Const {
411        dest_lo: Reg,
412        dest_hi: Reg,
413        value: i64,
414    },
415
416    // i64 comparisons (result is single i32: 0 or 1)
417    // Compare src1_lo:src1_hi with src2_lo:src2_hi
418    I64Eq {
419        dest: Reg,
420        src1_lo: Reg,
421        src1_hi: Reg,
422        src2_lo: Reg,
423        src2_hi: Reg,
424    },
425    I64Ne {
426        dest: Reg,
427        src1_lo: Reg,
428        src1_hi: Reg,
429        src2_lo: Reg,
430        src2_hi: Reg,
431    },
432    I64LtS {
433        dest: Reg,
434        src1_lo: Reg,
435        src1_hi: Reg,
436        src2_lo: Reg,
437        src2_hi: Reg,
438    },
439    I64GtS {
440        dest: Reg,
441        src1_lo: Reg,
442        src1_hi: Reg,
443        src2_lo: Reg,
444        src2_hi: Reg,
445    },
446    I64LeS {
447        dest: Reg,
448        src1_lo: Reg,
449        src1_hi: Reg,
450        src2_lo: Reg,
451        src2_hi: Reg,
452    },
453    I64GeS {
454        dest: Reg,
455        src1_lo: Reg,
456        src1_hi: Reg,
457        src2_lo: Reg,
458        src2_hi: Reg,
459    },
460    // i64 unsigned comparisons
461    I64LtU {
462        dest: Reg,
463        src1_lo: Reg,
464        src1_hi: Reg,
465        src2_lo: Reg,
466        src2_hi: Reg,
467    },
468    I64GtU {
469        dest: Reg,
470        src1_lo: Reg,
471        src1_hi: Reg,
472        src2_lo: Reg,
473        src2_hi: Reg,
474    },
475    I64LeU {
476        dest: Reg,
477        src1_lo: Reg,
478        src1_hi: Reg,
479        src2_lo: Reg,
480        src2_hi: Reg,
481    },
482    I64GeU {
483        dest: Reg,
484        src1_lo: Reg,
485        src1_hi: Reg,
486        src2_lo: Reg,
487        src2_hi: Reg,
488    },
489    I64Eqz {
490        dest: Reg,
491        src_lo: Reg,
492        src_hi: Reg,
493    },
494
495    // i64 bit counting (result is single i32)
496    I64Clz {
497        dest: Reg,
498        src_lo: Reg,
499        src_hi: Reg,
500    },
501    I64Ctz {
502        dest: Reg,
503        src_lo: Reg,
504        src_hi: Reg,
505    },
506    I64Popcnt {
507        dest: Reg,
508        src_lo: Reg,
509        src_hi: Reg,
510    },
511
512    // i64 sign extension (result is i64 pair)
513    I64Extend8S {
514        dest_lo: Reg,
515        dest_hi: Reg,
516        src_lo: Reg,
517    },
518    I64Extend16S {
519        dest_lo: Reg,
520        dest_hi: Reg,
521        src_lo: Reg,
522    },
523    I64Extend32S {
524        dest_lo: Reg,
525        dest_hi: Reg,
526        src_lo: Reg,
527    },
528
529    /// i32 -> i64 zero-extension. `dest_lo` aliases `src` (same vreg by IR
530    /// convention — see `wasm_to_ir`); `dest_hi` is freshly allocated and
531    /// holds the constant 0. Lowering MUST move `src`'s ARM reg into a
532    /// non-AAPCS-param register before treating it as the i64 lo half — the
533    /// downstream `I64Shl/ShrU/ShrS` emitters use `rm_lo`/`rm_hi` as scratch
534    /// and clobber them, so we must not place a live param register there.
535    /// This was the proximate cause of issue #93 (memset infinite loop on
536    /// silicon) before this opcode existed and the bridge silently fell
537    /// through to `Opcode::Nop` + `R0`-fallback in `get_arm_reg`.
538    I64ExtendI32U {
539        dest_lo: Reg,
540        dest_hi: Reg,
541        src: Reg,
542    },
543
544    /// i32 -> i64 sign-extension. Same constraints as I64ExtendI32U; the
545    /// `dest_hi` is filled by an arithmetic shift right by 31 of the src.
546    I64ExtendI32S {
547        dest_lo: Reg,
548        dest_hi: Reg,
549        src: Reg,
550    },
551
552    /// i64 -> i32 wrap (truncate to low 32 bits). `dest` aliases the i64's
553    /// `src_lo` vreg (same value); the high half is simply discarded.
554    I32WrapI64 {
555        dest: Reg,
556        src_lo: Reg,
557    },
558
559    // i64 shifts and multiply (result is i64 pair)
560    I64Mul {
561        dest_lo: Reg,
562        dest_hi: Reg,
563        src1_lo: Reg,
564        src1_hi: Reg,
565        src2_lo: Reg,
566        src2_hi: Reg,
567    },
568    // i64 division and remainder (result is i64 pair)
569    I64DivS {
570        dest_lo: Reg,
571        dest_hi: Reg,
572        src1_lo: Reg,
573        src1_hi: Reg,
574        src2_lo: Reg,
575        src2_hi: Reg,
576    },
577    I64DivU {
578        dest_lo: Reg,
579        dest_hi: Reg,
580        src1_lo: Reg,
581        src1_hi: Reg,
582        src2_lo: Reg,
583        src2_hi: Reg,
584    },
585    I64RemS {
586        dest_lo: Reg,
587        dest_hi: Reg,
588        src1_lo: Reg,
589        src1_hi: Reg,
590        src2_lo: Reg,
591        src2_hi: Reg,
592    },
593    I64RemU {
594        dest_lo: Reg,
595        dest_hi: Reg,
596        src1_lo: Reg,
597        src1_hi: Reg,
598        src2_lo: Reg,
599        src2_hi: Reg,
600    },
601    I64Shl {
602        dest_lo: Reg,
603        dest_hi: Reg,
604        src1_lo: Reg,
605        src1_hi: Reg,
606        src2_lo: Reg,
607        src2_hi: Reg,
608    },
609    I64ShrS {
610        dest_lo: Reg,
611        dest_hi: Reg,
612        src1_lo: Reg,
613        src1_hi: Reg,
614        src2_lo: Reg,
615        src2_hi: Reg,
616    },
617    I64ShrU {
618        dest_lo: Reg,
619        dest_hi: Reg,
620        src1_lo: Reg,
621        src1_hi: Reg,
622        src2_lo: Reg,
623        src2_hi: Reg,
624    },
625    I64Rotl {
626        dest_lo: Reg,
627        dest_hi: Reg,
628        src1_lo: Reg,
629        src1_hi: Reg,
630        src2_lo: Reg,
631        src2_hi: Reg,
632    },
633    I64Rotr {
634        dest_lo: Reg,
635        dest_hi: Reg,
636        src1_lo: Reg,
637        src1_hi: Reg,
638        src2_lo: Reg,
639        src2_hi: Reg,
640    },
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
644pub struct Reg(pub u32);
645
646/// Dead Code Elimination pass
647pub struct DeadCodeElimination {
648    /// Verbose output
649    verbose: bool,
650}
651
652impl DeadCodeElimination {
653    pub fn new() -> Self {
654        Self { verbose: false }
655    }
656
657    pub fn with_verbose(mut self) -> Self {
658        self.verbose = true;
659        self
660    }
661
662    /// Mark reachable blocks via CFG traversal
663    fn mark_reachable_blocks(&self, cfg: &Cfg) -> HashSet<BlockId> {
664        let mut reachable = HashSet::new();
665        let mut worklist = vec![cfg.entry];
666
667        while let Some(block_id) = worklist.pop() {
668            if reachable.contains(&block_id) {
669                continue;
670            }
671
672            reachable.insert(block_id);
673
674            if let Some(block) = cfg.block(block_id) {
675                for &succ in &block.successors {
676                    if !reachable.contains(&succ) {
677                        worklist.push(succ);
678                    }
679                }
680            }
681        }
682
683        reachable
684    }
685
686    /// Remove instructions in unreachable blocks
687    fn remove_unreachable(&self, cfg: &Cfg, instructions: &mut [Instruction]) -> OptResult {
688        let reachable = self.mark_reachable_blocks(cfg);
689
690        let mut removed = 0;
691        for inst in instructions.iter_mut() {
692            if !reachable.contains(&inst.block_id) && !inst.is_dead {
693                inst.is_dead = true;
694                removed += 1;
695            }
696        }
697
698        if self.verbose && removed > 0 {
699            eprintln!("DCE: Removed {} unreachable instructions", removed);
700        }
701
702        OptResult {
703            changed: removed > 0,
704            removed_count: removed,
705            added_count: 0,
706            modified_count: 0,
707        }
708    }
709}
710
711impl Default for DeadCodeElimination {
712    fn default() -> Self {
713        Self::new()
714    }
715}
716
717impl OptimizationPass for DeadCodeElimination {
718    fn name(&self) -> &'static str {
719        "dead-code-elimination"
720    }
721
722    fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
723        self.remove_unreachable(cfg, instructions)
724    }
725}
726
727/// Constant Folding pass
728pub struct ConstantFolding {
729    verbose: bool,
730}
731
732impl ConstantFolding {
733    pub fn new() -> Self {
734        Self { verbose: false }
735    }
736
737    pub fn with_verbose(mut self) -> Self {
738        self.verbose = true;
739        self
740    }
741
742    /// Fold constant operations
743    fn fold_constants(&mut self, instructions: &mut [Instruction]) -> OptResult {
744        // Build a map of registers to their constant values
745        let mut const_values: HashMap<Reg, i32> = HashMap::new();
746        let mut modified = 0;
747
748        for inst in instructions.iter_mut() {
749            if inst.is_dead {
750                continue;
751            }
752
753            // Clone the opcode to avoid borrow checker issues
754            let opcode = inst.opcode.clone();
755
756            match opcode {
757                // Track constant definitions
758                Opcode::Const { dest, value } => {
759                    const_values.insert(dest, value);
760                }
761
762                // Fold Add if both operands are constant
763                Opcode::Add { dest, src1, src2 } => {
764                    if let (Some(&val1), Some(&val2)) =
765                        (const_values.get(&src1), const_values.get(&src2))
766                    {
767                        let result = val1.wrapping_add(val2);
768                        inst.opcode = Opcode::Const {
769                            dest,
770                            value: result,
771                        };
772                        const_values.insert(dest, result);
773                        modified += 1;
774
775                        if self.verbose {
776                            eprintln!(
777                                "Folded: add {} = {} + {} -> const {} = {}",
778                                dest.0, val1, val2, dest.0, result
779                            );
780                        }
781                    }
782                }
783
784                // Fold Sub if both operands are constant
785                Opcode::Sub { dest, src1, src2 } => {
786                    if let (Some(&val1), Some(&val2)) =
787                        (const_values.get(&src1), const_values.get(&src2))
788                    {
789                        let result = val1.wrapping_sub(val2);
790                        inst.opcode = Opcode::Const {
791                            dest,
792                            value: result,
793                        };
794                        const_values.insert(dest, result);
795                        modified += 1;
796
797                        if self.verbose {
798                            eprintln!(
799                                "Folded: sub {} = {} - {} -> const {} = {}",
800                                dest.0, val1, val2, dest.0, result
801                            );
802                        }
803                    }
804                }
805
806                // Fold Mul if both operands are constant
807                Opcode::Mul { dest, src1, src2 } => {
808                    if let (Some(&val1), Some(&val2)) =
809                        (const_values.get(&src1), const_values.get(&src2))
810                    {
811                        let result = val1.wrapping_mul(val2);
812                        inst.opcode = Opcode::Const {
813                            dest,
814                            value: result,
815                        };
816                        const_values.insert(dest, result);
817                        modified += 1;
818
819                        if self.verbose {
820                            eprintln!(
821                                "Folded: mul {} = {} * {} -> const {} = {}",
822                                dest.0, val1, val2, dest.0, result
823                            );
824                        }
825                    }
826                }
827
828                _ => {}
829            }
830        }
831
832        if self.verbose && modified > 0 {
833            eprintln!("Constant folding: {} operations folded", modified);
834        }
835
836        OptResult {
837            changed: modified > 0,
838            removed_count: 0,
839            added_count: 0,
840            modified_count: modified,
841        }
842    }
843}
844
845impl Default for ConstantFolding {
846    fn default() -> Self {
847        Self::new()
848    }
849}
850
851impl OptimizationPass for ConstantFolding {
852    fn name(&self) -> &'static str {
853        "constant-folding"
854    }
855
856    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
857        self.fold_constants(instructions)
858    }
859}
860
861/// Expression key for CSE
862#[derive(Debug, Clone, PartialEq, Eq, Hash)]
863enum ExprKey {
864    Add(Reg, Reg),
865    Sub(Reg, Reg),
866    Mul(Reg, Reg),
867    Load(u32),
868}
869
870/// Common Subexpression Elimination pass
871pub struct CommonSubexpressionElimination {
872    verbose: bool,
873}
874
875impl CommonSubexpressionElimination {
876    pub fn new() -> Self {
877        Self { verbose: false }
878    }
879
880    pub fn with_verbose(mut self) -> Self {
881        self.verbose = true;
882        self
883    }
884
885    /// Eliminate common subexpressions
886    fn eliminate_cse(&mut self, instructions: &mut [Instruction]) -> OptResult {
887        // Map from expression to the register holding its result
888        let mut expr_map: HashMap<ExprKey, Reg> = HashMap::new();
889
890        // Map from register to register (for copy propagation after CSE)
891        let mut reg_map: HashMap<Reg, Reg> = HashMap::new();
892
893        let mut modified = 0;
894
895        for inst in instructions.iter_mut() {
896            if inst.is_dead {
897                continue;
898            }
899
900            // Clone opcode to avoid borrow issues
901            let opcode = inst.opcode.clone();
902
903            // Resolve register mappings
904            let resolve = |r: Reg| -> Reg { reg_map.get(&r).copied().unwrap_or(r) };
905
906            // After CSE rewrites this instruction's sources, we must also
907            // clear any `reg_map` entries for the vregs this instruction
908            // *writes to* — otherwise a later use of that vreg would be
909            // rewritten back to the original CSE-aliased source, which by
910            // now is a different (potentially stale) value.
911            //
912            // Concretely: WASM lowering may emit ops whose `dest` aliases a
913            // `src` of the same vreg id (e.g. `I64ExtendI32U` has
914            // `dest_lo == src` by IR convention — see
915            // `optimizer_bridge::wasm_to_ir`). That violates pure SSA, so
916            // CSE's vreg-rename `reg_map` becomes flow-insensitive at the
917            // re-definition site. Forgetting to clear the dest entry means
918            // a *later* op that consumes `dest_lo` would resolve back to
919            // the pre-extend (i32-typed) value — sometimes a live AAPCS
920            // param register — re-introducing the issue-#93 / #104 class
921            // of clobber-via-stale-alias bugs.
922            //
923            // We collect the dests *before* mutating reg_map so the
924            // resolution pass above sees the pre-instruction state, then
925            // drop them after.
926            let dests_to_invalidate: Vec<Reg> = match &opcode {
927                Opcode::Add { dest, .. }
928                | Opcode::Sub { dest, .. }
929                | Opcode::Mul { dest, .. }
930                | Opcode::DivS { dest, .. }
931                | Opcode::DivU { dest, .. }
932                | Opcode::RemS { dest, .. }
933                | Opcode::RemU { dest, .. }
934                | Opcode::And { dest, .. }
935                | Opcode::Or { dest, .. }
936                | Opcode::Xor { dest, .. }
937                | Opcode::Shl { dest, .. }
938                | Opcode::ShrS { dest, .. }
939                | Opcode::ShrU { dest, .. }
940                | Opcode::Rotl { dest, .. }
941                | Opcode::Rotr { dest, .. }
942                | Opcode::Clz { dest, .. }
943                | Opcode::Ctz { dest, .. }
944                | Opcode::Popcnt { dest, .. }
945                | Opcode::Extend8S { dest, .. }
946                | Opcode::Extend16S { dest, .. }
947                | Opcode::Eqz { dest, .. }
948                | Opcode::Eq { dest, .. }
949                | Opcode::Ne { dest, .. }
950                | Opcode::LtS { dest, .. }
951                | Opcode::LtU { dest, .. }
952                | Opcode::LeS { dest, .. }
953                | Opcode::LeU { dest, .. }
954                | Opcode::GtS { dest, .. }
955                | Opcode::GtU { dest, .. }
956                | Opcode::GeS { dest, .. }
957                | Opcode::GeU { dest, .. }
958                | Opcode::Load { dest, .. }
959                | Opcode::MemLoad { dest, .. }
960                | Opcode::Copy { dest, .. }
961                | Opcode::TeeStore { dest, .. }
962                | Opcode::Select { dest, .. }
963                | Opcode::Const { dest, .. }
964                | Opcode::I64Eq { dest, .. }
965                | Opcode::I64Ne { dest, .. }
966                | Opcode::I64LtS { dest, .. }
967                | Opcode::I64LtU { dest, .. }
968                | Opcode::I64LeS { dest, .. }
969                | Opcode::I64LeU { dest, .. }
970                | Opcode::I64GtS { dest, .. }
971                | Opcode::I64GtU { dest, .. }
972                | Opcode::I64GeS { dest, .. }
973                | Opcode::I64GeU { dest, .. }
974                | Opcode::I64Eqz { dest, .. }
975                | Opcode::I64Clz { dest, .. }
976                | Opcode::I64Ctz { dest, .. }
977                | Opcode::I64Popcnt { dest, .. }
978                | Opcode::I32WrapI64 { dest, .. }
979                | Opcode::MemLoadSubword { dest, .. }
980                | Opcode::GlobalGet { dest, .. }
981                | Opcode::MemorySize { dest, .. }
982                | Opcode::MemoryGrow { dest, .. }
983                | Opcode::Call { dest, .. } => vec![*dest],
984                Opcode::I64Add {
985                    dest_lo, dest_hi, ..
986                }
987                | Opcode::I64Sub {
988                    dest_lo, dest_hi, ..
989                }
990                | Opcode::I64And {
991                    dest_lo, dest_hi, ..
992                }
993                | Opcode::I64Or {
994                    dest_lo, dest_hi, ..
995                }
996                | Opcode::I64Xor {
997                    dest_lo, dest_hi, ..
998                }
999                | Opcode::I64Mul {
1000                    dest_lo, dest_hi, ..
1001                }
1002                | Opcode::I64DivS {
1003                    dest_lo, dest_hi, ..
1004                }
1005                | Opcode::I64DivU {
1006                    dest_lo, dest_hi, ..
1007                }
1008                | Opcode::I64RemS {
1009                    dest_lo, dest_hi, ..
1010                }
1011                | Opcode::I64RemU {
1012                    dest_lo, dest_hi, ..
1013                }
1014                | Opcode::I64Shl {
1015                    dest_lo, dest_hi, ..
1016                }
1017                | Opcode::I64ShrS {
1018                    dest_lo, dest_hi, ..
1019                }
1020                | Opcode::I64ShrU {
1021                    dest_lo, dest_hi, ..
1022                }
1023                | Opcode::I64Rotl {
1024                    dest_lo, dest_hi, ..
1025                }
1026                | Opcode::I64Rotr {
1027                    dest_lo, dest_hi, ..
1028                }
1029                | Opcode::I64Const {
1030                    dest_lo, dest_hi, ..
1031                }
1032                | Opcode::I64Load {
1033                    dest_lo, dest_hi, ..
1034                }
1035                | Opcode::I64Extend8S {
1036                    dest_lo, dest_hi, ..
1037                }
1038                | Opcode::I64Extend16S {
1039                    dest_lo, dest_hi, ..
1040                }
1041                | Opcode::I64Extend32S {
1042                    dest_lo, dest_hi, ..
1043                }
1044                | Opcode::I64ExtendI32U {
1045                    dest_lo, dest_hi, ..
1046                }
1047                | Opcode::I64ExtendI32S {
1048                    dest_lo, dest_hi, ..
1049                } => vec![*dest_lo, *dest_hi],
1050                _ => Vec::new(),
1051            };
1052
1053            match opcode {
1054                Opcode::Add { dest, src1, src2 } => {
1055                    let src1 = resolve(src1);
1056                    let src2 = resolve(src2);
1057                    let key = ExprKey::Add(src1, src2);
1058
1059                    if let Some(&existing) = expr_map.get(&key) {
1060                        // Found duplicate expression - replace with const/copy
1061                        inst.opcode = Opcode::Const { dest, value: 0 }; // Placeholder
1062                        inst.is_dead = true; // Mark for removal
1063                        reg_map.insert(dest, existing);
1064                        modified += 1;
1065
1066                        if self.verbose {
1067                            eprintln!(
1068                                "CSE: Eliminated add r{} = r{} + r{}, reuse r{}",
1069                                dest.0, src1.0, src2.0, existing.0
1070                            );
1071                        }
1072                    } else {
1073                        expr_map.insert(key, dest);
1074                        // Update instruction with resolved registers
1075                        inst.opcode = Opcode::Add { dest, src1, src2 };
1076                    }
1077                }
1078
1079                Opcode::Sub { dest, src1, src2 } => {
1080                    let src1 = resolve(src1);
1081                    let src2 = resolve(src2);
1082                    let key = ExprKey::Sub(src1, src2);
1083
1084                    if let Some(&existing) = expr_map.get(&key) {
1085                        inst.opcode = Opcode::Const { dest, value: 0 };
1086                        inst.is_dead = true;
1087                        reg_map.insert(dest, existing);
1088                        modified += 1;
1089
1090                        if self.verbose {
1091                            eprintln!(
1092                                "CSE: Eliminated sub r{} = r{} - r{}, reuse r{}",
1093                                dest.0, src1.0, src2.0, existing.0
1094                            );
1095                        }
1096                    } else {
1097                        expr_map.insert(key, dest);
1098                        inst.opcode = Opcode::Sub { dest, src1, src2 };
1099                    }
1100                }
1101
1102                Opcode::Mul { dest, src1, src2 } => {
1103                    let src1 = resolve(src1);
1104                    let src2 = resolve(src2);
1105                    let key = ExprKey::Mul(src1, src2);
1106
1107                    if let Some(&existing) = expr_map.get(&key) {
1108                        inst.opcode = Opcode::Const { dest, value: 0 };
1109                        inst.is_dead = true;
1110                        reg_map.insert(dest, existing);
1111                        modified += 1;
1112
1113                        if self.verbose {
1114                            eprintln!(
1115                                "CSE: Eliminated mul r{} = r{} * r{}, reuse r{}",
1116                                dest.0, src1.0, src2.0, existing.0
1117                            );
1118                        }
1119                    } else {
1120                        expr_map.insert(key, dest);
1121                        inst.opcode = Opcode::Mul { dest, src1, src2 };
1122                    }
1123                }
1124
1125                Opcode::Load { dest, addr } => {
1126                    let key = ExprKey::Load(addr);
1127
1128                    if let Some(&existing) = expr_map.get(&key) {
1129                        inst.opcode = Opcode::Const { dest, value: 0 };
1130                        inst.is_dead = true;
1131                        reg_map.insert(dest, existing);
1132                        modified += 1;
1133
1134                        if self.verbose {
1135                            eprintln!(
1136                                "CSE: Eliminated load r{} = [0x{:x}], reuse r{}",
1137                                dest.0, addr, existing.0
1138                            );
1139                        }
1140                    } else {
1141                        expr_map.insert(key, dest);
1142                    }
1143                }
1144
1145                // Store invalidates loads from same address.
1146                // Also rewrite `src` through `reg_map` so the local-variable
1147                // sink picks up CSE-eliminated producers (issue #104).
1148                Opcode::Store { src, addr } => {
1149                    let src = resolve(src);
1150                    inst.opcode = Opcode::Store { src, addr };
1151                    expr_map.remove(&ExprKey::Load(addr));
1152                }
1153
1154                // TeeStore also invalidates loads from same address.
1155                // Rewrite `src` through `reg_map` for the same reason as Store.
1156                Opcode::TeeStore { dest, src, addr } => {
1157                    let src = resolve(src);
1158                    inst.opcode = Opcode::TeeStore { dest, src, addr };
1159                    expr_map.remove(&ExprKey::Load(addr));
1160                }
1161
1162                // Linear-memory load: rewrite the address vreg through
1163                // `reg_map` so when a preceding `Opcode::Load` (local.get)
1164                // is CSE-killed (its dest replaced by an earlier dest), the
1165                // MemLoad consumer still points at the live vreg.
1166                //
1167                // Issue #104: prior to this arm, the `_ => {}` fallback left
1168                // `addr` untouched. The CSE-killed Load was then dropped by
1169                // `optimize_full`'s dead-instruction filter and `ir_to_arm`
1170                // panicked with "vreg vN has no assigned ARM register".
1171                //
1172                // We intentionally do NOT CSE MemLoads themselves (there is
1173                // no alias analysis for linear memory; any MemStore can
1174                // invalidate any address).
1175                Opcode::MemLoad { dest, addr, offset } => {
1176                    let addr = resolve(addr);
1177                    inst.opcode = Opcode::MemLoad { dest, addr, offset };
1178                }
1179
1180                // Linear-memory store: rewrite both `src` and `addr` through
1181                // `reg_map`. Same rationale as `MemLoad`.
1182                Opcode::MemStore { src, addr, offset } => {
1183                    let src = resolve(src);
1184                    let addr = resolve(addr);
1185                    inst.opcode = Opcode::MemStore { src, addr, offset };
1186                }
1187
1188                // 8/16-bit sign extension (unary): resolve `src`.
1189                Opcode::Extend8S { dest, src } => {
1190                    let src = resolve(src);
1191                    inst.opcode = Opcode::Extend8S { dest, src };
1192                }
1193                Opcode::Extend16S { dest, src } => {
1194                    let src = resolve(src);
1195                    inst.opcode = Opcode::Extend16S { dest, src };
1196                }
1197
1198                // Label marks a loop start - clear all Load caches since values
1199                // can change across loop iterations (conservative but safe)
1200                Opcode::Label { .. } => {
1201                    expr_map.retain(|k, _| !matches!(k, ExprKey::Load(_)));
1202                }
1203
1204                // CondBranch could be a back-edge, clear Load caches
1205                Opcode::CondBranch { cond, target } => {
1206                    let cond = resolve(cond);
1207                    inst.opcode = Opcode::CondBranch { cond, target };
1208                    // Clear loads since this might be a loop back-edge
1209                    expr_map.retain(|k, _| !matches!(k, ExprKey::Load(_)));
1210                }
1211
1212                // Comparisons need register resolution
1213                Opcode::Eq { dest, src1, src2 } => {
1214                    let src1 = resolve(src1);
1215                    let src2 = resolve(src2);
1216                    inst.opcode = Opcode::Eq { dest, src1, src2 };
1217                }
1218                Opcode::Ne { dest, src1, src2 } => {
1219                    let src1 = resolve(src1);
1220                    let src2 = resolve(src2);
1221                    inst.opcode = Opcode::Ne { dest, src1, src2 };
1222                }
1223                Opcode::LtS { dest, src1, src2 } => {
1224                    let src1 = resolve(src1);
1225                    let src2 = resolve(src2);
1226                    inst.opcode = Opcode::LtS { dest, src1, src2 };
1227                }
1228                Opcode::LtU { dest, src1, src2 } => {
1229                    let src1 = resolve(src1);
1230                    let src2 = resolve(src2);
1231                    inst.opcode = Opcode::LtU { dest, src1, src2 };
1232                }
1233                Opcode::LeS { dest, src1, src2 } => {
1234                    let src1 = resolve(src1);
1235                    let src2 = resolve(src2);
1236                    inst.opcode = Opcode::LeS { dest, src1, src2 };
1237                }
1238                Opcode::LeU { dest, src1, src2 } => {
1239                    let src1 = resolve(src1);
1240                    let src2 = resolve(src2);
1241                    inst.opcode = Opcode::LeU { dest, src1, src2 };
1242                }
1243                Opcode::GtS { dest, src1, src2 } => {
1244                    let src1 = resolve(src1);
1245                    let src2 = resolve(src2);
1246                    inst.opcode = Opcode::GtS { dest, src1, src2 };
1247                }
1248                Opcode::GtU { dest, src1, src2 } => {
1249                    let src1 = resolve(src1);
1250                    let src2 = resolve(src2);
1251                    inst.opcode = Opcode::GtU { dest, src1, src2 };
1252                }
1253                Opcode::GeS { dest, src1, src2 } => {
1254                    let src1 = resolve(src1);
1255                    let src2 = resolve(src2);
1256                    inst.opcode = Opcode::GeS { dest, src1, src2 };
1257                }
1258                Opcode::GeU { dest, src1, src2 } => {
1259                    let src1 = resolve(src1);
1260                    let src2 = resolve(src2);
1261                    inst.opcode = Opcode::GeU { dest, src1, src2 };
1262                }
1263
1264                // Eqz needs register resolution
1265                Opcode::Eqz { dest, src } => {
1266                    let src = resolve(src);
1267                    inst.opcode = Opcode::Eqz { dest, src };
1268                }
1269
1270                Opcode::Return { value: Some(v) } => {
1271                    let v = resolve(v);
1272                    inst.opcode = Opcode::Return { value: Some(v) };
1273                }
1274
1275                Opcode::Select {
1276                    dest,
1277                    val_true,
1278                    val_false,
1279                    cond,
1280                } => {
1281                    let val_true = resolve(val_true);
1282                    let val_false = resolve(val_false);
1283                    let cond = resolve(cond);
1284                    inst.opcode = Opcode::Select {
1285                        dest,
1286                        val_true,
1287                        val_false,
1288                        cond,
1289                    };
1290                }
1291
1292                // Division and remainder need resolution
1293                Opcode::DivS { dest, src1, src2 } => {
1294                    let src1 = resolve(src1);
1295                    let src2 = resolve(src2);
1296                    inst.opcode = Opcode::DivS { dest, src1, src2 };
1297                }
1298                Opcode::DivU { dest, src1, src2 } => {
1299                    let src1 = resolve(src1);
1300                    let src2 = resolve(src2);
1301                    inst.opcode = Opcode::DivU { dest, src1, src2 };
1302                }
1303                Opcode::RemS { dest, src1, src2 } => {
1304                    let src1 = resolve(src1);
1305                    let src2 = resolve(src2);
1306                    inst.opcode = Opcode::RemS { dest, src1, src2 };
1307                }
1308                Opcode::RemU { dest, src1, src2 } => {
1309                    let src1 = resolve(src1);
1310                    let src2 = resolve(src2);
1311                    inst.opcode = Opcode::RemU { dest, src1, src2 };
1312                }
1313
1314                // Bitwise operations need resolution
1315                Opcode::And { dest, src1, src2 } => {
1316                    let src1 = resolve(src1);
1317                    let src2 = resolve(src2);
1318                    inst.opcode = Opcode::And { dest, src1, src2 };
1319                }
1320                Opcode::Or { dest, src1, src2 } => {
1321                    let src1 = resolve(src1);
1322                    let src2 = resolve(src2);
1323                    inst.opcode = Opcode::Or { dest, src1, src2 };
1324                }
1325                Opcode::Xor { dest, src1, src2 } => {
1326                    let src1 = resolve(src1);
1327                    let src2 = resolve(src2);
1328                    inst.opcode = Opcode::Xor { dest, src1, src2 };
1329                }
1330
1331                // Shifts need resolution
1332                Opcode::Shl { dest, src1, src2 } => {
1333                    let src1 = resolve(src1);
1334                    let src2 = resolve(src2);
1335                    inst.opcode = Opcode::Shl { dest, src1, src2 };
1336                }
1337                Opcode::ShrS { dest, src1, src2 } => {
1338                    let src1 = resolve(src1);
1339                    let src2 = resolve(src2);
1340                    inst.opcode = Opcode::ShrS { dest, src1, src2 };
1341                }
1342                Opcode::ShrU { dest, src1, src2 } => {
1343                    let src1 = resolve(src1);
1344                    let src2 = resolve(src2);
1345                    inst.opcode = Opcode::ShrU { dest, src1, src2 };
1346                }
1347                Opcode::Rotl { dest, src1, src2 } => {
1348                    let src1 = resolve(src1);
1349                    let src2 = resolve(src2);
1350                    inst.opcode = Opcode::Rotl { dest, src1, src2 };
1351                }
1352                Opcode::Rotr { dest, src1, src2 } => {
1353                    let src1 = resolve(src1);
1354                    let src2 = resolve(src2);
1355                    inst.opcode = Opcode::Rotr { dest, src1, src2 };
1356                }
1357
1358                // Unary operations need resolution
1359                Opcode::Clz { dest, src } => {
1360                    let src = resolve(src);
1361                    inst.opcode = Opcode::Clz { dest, src };
1362                }
1363                Opcode::Ctz { dest, src } => {
1364                    let src = resolve(src);
1365                    inst.opcode = Opcode::Ctz { dest, src };
1366                }
1367                Opcode::Popcnt { dest, src } => {
1368                    let src = resolve(src);
1369                    inst.opcode = Opcode::Popcnt { dest, src };
1370                }
1371
1372                // Copy needs resolution
1373                Opcode::Copy { dest, src } => {
1374                    let src = resolve(src);
1375                    inst.opcode = Opcode::Copy { dest, src };
1376                }
1377
1378                // ============================================================
1379                // i64 operations: resolve all src vregs through reg_map.
1380                //
1381                // CSE eliminates duplicate `Opcode::Load`s (local.get N) by
1382                // marking the later one dead and recording the rewrite
1383                // `dead_dest -> original_dest` in `reg_map`. Any later op
1384                // that names `dead_dest` as a source MUST be rewritten —
1385                // otherwise the producer is gone and `ir_to_arm`'s
1386                // `get_arm_reg` panics (post-PR-101) or silently returns
1387                // R0 (pre-PR-101 → AAPCS clobber miscompile, see issue #93).
1388                //
1389                // PR #107 covered MemLoad/MemStore; this batch covers every
1390                // i64 opcode. We DO NOT add these to `expr_map` for actual
1391                // CSE — i64 expression equivalence isn't tracked here — we
1392                // only do the source-vreg rewriting needed for correctness
1393                // when an upstream Load is killed.
1394                // ============================================================
1395                Opcode::I64Load { .. } | Opcode::I64Const { .. } => {
1396                    // Pure producers, no src to resolve.
1397                }
1398                Opcode::I64Add {
1399                    dest_lo,
1400                    dest_hi,
1401                    src1_lo,
1402                    src1_hi,
1403                    src2_lo,
1404                    src2_hi,
1405                } => {
1406                    inst.opcode = Opcode::I64Add {
1407                        dest_lo,
1408                        dest_hi,
1409                        src1_lo: resolve(src1_lo),
1410                        src1_hi: resolve(src1_hi),
1411                        src2_lo: resolve(src2_lo),
1412                        src2_hi: resolve(src2_hi),
1413                    };
1414                }
1415                Opcode::I64Sub {
1416                    dest_lo,
1417                    dest_hi,
1418                    src1_lo,
1419                    src1_hi,
1420                    src2_lo,
1421                    src2_hi,
1422                } => {
1423                    inst.opcode = Opcode::I64Sub {
1424                        dest_lo,
1425                        dest_hi,
1426                        src1_lo: resolve(src1_lo),
1427                        src1_hi: resolve(src1_hi),
1428                        src2_lo: resolve(src2_lo),
1429                        src2_hi: resolve(src2_hi),
1430                    };
1431                }
1432                Opcode::I64And {
1433                    dest_lo,
1434                    dest_hi,
1435                    src1_lo,
1436                    src1_hi,
1437                    src2_lo,
1438                    src2_hi,
1439                } => {
1440                    inst.opcode = Opcode::I64And {
1441                        dest_lo,
1442                        dest_hi,
1443                        src1_lo: resolve(src1_lo),
1444                        src1_hi: resolve(src1_hi),
1445                        src2_lo: resolve(src2_lo),
1446                        src2_hi: resolve(src2_hi),
1447                    };
1448                }
1449                Opcode::I64Or {
1450                    dest_lo,
1451                    dest_hi,
1452                    src1_lo,
1453                    src1_hi,
1454                    src2_lo,
1455                    src2_hi,
1456                } => {
1457                    inst.opcode = Opcode::I64Or {
1458                        dest_lo,
1459                        dest_hi,
1460                        src1_lo: resolve(src1_lo),
1461                        src1_hi: resolve(src1_hi),
1462                        src2_lo: resolve(src2_lo),
1463                        src2_hi: resolve(src2_hi),
1464                    };
1465                }
1466                Opcode::I64Xor {
1467                    dest_lo,
1468                    dest_hi,
1469                    src1_lo,
1470                    src1_hi,
1471                    src2_lo,
1472                    src2_hi,
1473                } => {
1474                    inst.opcode = Opcode::I64Xor {
1475                        dest_lo,
1476                        dest_hi,
1477                        src1_lo: resolve(src1_lo),
1478                        src1_hi: resolve(src1_hi),
1479                        src2_lo: resolve(src2_lo),
1480                        src2_hi: resolve(src2_hi),
1481                    };
1482                }
1483                Opcode::I64Mul {
1484                    dest_lo,
1485                    dest_hi,
1486                    src1_lo,
1487                    src1_hi,
1488                    src2_lo,
1489                    src2_hi,
1490                } => {
1491                    inst.opcode = Opcode::I64Mul {
1492                        dest_lo,
1493                        dest_hi,
1494                        src1_lo: resolve(src1_lo),
1495                        src1_hi: resolve(src1_hi),
1496                        src2_lo: resolve(src2_lo),
1497                        src2_hi: resolve(src2_hi),
1498                    };
1499                }
1500                Opcode::I64DivS {
1501                    dest_lo,
1502                    dest_hi,
1503                    src1_lo,
1504                    src1_hi,
1505                    src2_lo,
1506                    src2_hi,
1507                } => {
1508                    inst.opcode = Opcode::I64DivS {
1509                        dest_lo,
1510                        dest_hi,
1511                        src1_lo: resolve(src1_lo),
1512                        src1_hi: resolve(src1_hi),
1513                        src2_lo: resolve(src2_lo),
1514                        src2_hi: resolve(src2_hi),
1515                    };
1516                }
1517                Opcode::I64DivU {
1518                    dest_lo,
1519                    dest_hi,
1520                    src1_lo,
1521                    src1_hi,
1522                    src2_lo,
1523                    src2_hi,
1524                } => {
1525                    inst.opcode = Opcode::I64DivU {
1526                        dest_lo,
1527                        dest_hi,
1528                        src1_lo: resolve(src1_lo),
1529                        src1_hi: resolve(src1_hi),
1530                        src2_lo: resolve(src2_lo),
1531                        src2_hi: resolve(src2_hi),
1532                    };
1533                }
1534                Opcode::I64RemS {
1535                    dest_lo,
1536                    dest_hi,
1537                    src1_lo,
1538                    src1_hi,
1539                    src2_lo,
1540                    src2_hi,
1541                } => {
1542                    inst.opcode = Opcode::I64RemS {
1543                        dest_lo,
1544                        dest_hi,
1545                        src1_lo: resolve(src1_lo),
1546                        src1_hi: resolve(src1_hi),
1547                        src2_lo: resolve(src2_lo),
1548                        src2_hi: resolve(src2_hi),
1549                    };
1550                }
1551                Opcode::I64RemU {
1552                    dest_lo,
1553                    dest_hi,
1554                    src1_lo,
1555                    src1_hi,
1556                    src2_lo,
1557                    src2_hi,
1558                } => {
1559                    inst.opcode = Opcode::I64RemU {
1560                        dest_lo,
1561                        dest_hi,
1562                        src1_lo: resolve(src1_lo),
1563                        src1_hi: resolve(src1_hi),
1564                        src2_lo: resolve(src2_lo),
1565                        src2_hi: resolve(src2_hi),
1566                    };
1567                }
1568                Opcode::I64Shl {
1569                    dest_lo,
1570                    dest_hi,
1571                    src1_lo,
1572                    src1_hi,
1573                    src2_lo,
1574                    src2_hi,
1575                } => {
1576                    inst.opcode = Opcode::I64Shl {
1577                        dest_lo,
1578                        dest_hi,
1579                        src1_lo: resolve(src1_lo),
1580                        src1_hi: resolve(src1_hi),
1581                        src2_lo: resolve(src2_lo),
1582                        src2_hi: resolve(src2_hi),
1583                    };
1584                }
1585                Opcode::I64ShrS {
1586                    dest_lo,
1587                    dest_hi,
1588                    src1_lo,
1589                    src1_hi,
1590                    src2_lo,
1591                    src2_hi,
1592                } => {
1593                    inst.opcode = Opcode::I64ShrS {
1594                        dest_lo,
1595                        dest_hi,
1596                        src1_lo: resolve(src1_lo),
1597                        src1_hi: resolve(src1_hi),
1598                        src2_lo: resolve(src2_lo),
1599                        src2_hi: resolve(src2_hi),
1600                    };
1601                }
1602                Opcode::I64ShrU {
1603                    dest_lo,
1604                    dest_hi,
1605                    src1_lo,
1606                    src1_hi,
1607                    src2_lo,
1608                    src2_hi,
1609                } => {
1610                    inst.opcode = Opcode::I64ShrU {
1611                        dest_lo,
1612                        dest_hi,
1613                        src1_lo: resolve(src1_lo),
1614                        src1_hi: resolve(src1_hi),
1615                        src2_lo: resolve(src2_lo),
1616                        src2_hi: resolve(src2_hi),
1617                    };
1618                }
1619                Opcode::I64Rotl {
1620                    dest_lo,
1621                    dest_hi,
1622                    src1_lo,
1623                    src1_hi,
1624                    src2_lo,
1625                    src2_hi,
1626                } => {
1627                    inst.opcode = Opcode::I64Rotl {
1628                        dest_lo,
1629                        dest_hi,
1630                        src1_lo: resolve(src1_lo),
1631                        src1_hi: resolve(src1_hi),
1632                        src2_lo: resolve(src2_lo),
1633                        src2_hi: resolve(src2_hi),
1634                    };
1635                }
1636                Opcode::I64Rotr {
1637                    dest_lo,
1638                    dest_hi,
1639                    src1_lo,
1640                    src1_hi,
1641                    src2_lo,
1642                    src2_hi,
1643                } => {
1644                    inst.opcode = Opcode::I64Rotr {
1645                        dest_lo,
1646                        dest_hi,
1647                        src1_lo: resolve(src1_lo),
1648                        src1_hi: resolve(src1_hi),
1649                        src2_lo: resolve(src2_lo),
1650                        src2_hi: resolve(src2_hi),
1651                    };
1652                }
1653                // i64 comparisons (i64 -> i32 bool)
1654                Opcode::I64Eq {
1655                    dest,
1656                    src1_lo,
1657                    src1_hi,
1658                    src2_lo,
1659                    src2_hi,
1660                } => {
1661                    inst.opcode = Opcode::I64Eq {
1662                        dest,
1663                        src1_lo: resolve(src1_lo),
1664                        src1_hi: resolve(src1_hi),
1665                        src2_lo: resolve(src2_lo),
1666                        src2_hi: resolve(src2_hi),
1667                    };
1668                }
1669                Opcode::I64Ne {
1670                    dest,
1671                    src1_lo,
1672                    src1_hi,
1673                    src2_lo,
1674                    src2_hi,
1675                } => {
1676                    inst.opcode = Opcode::I64Ne {
1677                        dest,
1678                        src1_lo: resolve(src1_lo),
1679                        src1_hi: resolve(src1_hi),
1680                        src2_lo: resolve(src2_lo),
1681                        src2_hi: resolve(src2_hi),
1682                    };
1683                }
1684                Opcode::I64LtS {
1685                    dest,
1686                    src1_lo,
1687                    src1_hi,
1688                    src2_lo,
1689                    src2_hi,
1690                } => {
1691                    inst.opcode = Opcode::I64LtS {
1692                        dest,
1693                        src1_lo: resolve(src1_lo),
1694                        src1_hi: resolve(src1_hi),
1695                        src2_lo: resolve(src2_lo),
1696                        src2_hi: resolve(src2_hi),
1697                    };
1698                }
1699                Opcode::I64GtS {
1700                    dest,
1701                    src1_lo,
1702                    src1_hi,
1703                    src2_lo,
1704                    src2_hi,
1705                } => {
1706                    inst.opcode = Opcode::I64GtS {
1707                        dest,
1708                        src1_lo: resolve(src1_lo),
1709                        src1_hi: resolve(src1_hi),
1710                        src2_lo: resolve(src2_lo),
1711                        src2_hi: resolve(src2_hi),
1712                    };
1713                }
1714                Opcode::I64LeS {
1715                    dest,
1716                    src1_lo,
1717                    src1_hi,
1718                    src2_lo,
1719                    src2_hi,
1720                } => {
1721                    inst.opcode = Opcode::I64LeS {
1722                        dest,
1723                        src1_lo: resolve(src1_lo),
1724                        src1_hi: resolve(src1_hi),
1725                        src2_lo: resolve(src2_lo),
1726                        src2_hi: resolve(src2_hi),
1727                    };
1728                }
1729                Opcode::I64GeS {
1730                    dest,
1731                    src1_lo,
1732                    src1_hi,
1733                    src2_lo,
1734                    src2_hi,
1735                } => {
1736                    inst.opcode = Opcode::I64GeS {
1737                        dest,
1738                        src1_lo: resolve(src1_lo),
1739                        src1_hi: resolve(src1_hi),
1740                        src2_lo: resolve(src2_lo),
1741                        src2_hi: resolve(src2_hi),
1742                    };
1743                }
1744                Opcode::I64LtU {
1745                    dest,
1746                    src1_lo,
1747                    src1_hi,
1748                    src2_lo,
1749                    src2_hi,
1750                } => {
1751                    inst.opcode = Opcode::I64LtU {
1752                        dest,
1753                        src1_lo: resolve(src1_lo),
1754                        src1_hi: resolve(src1_hi),
1755                        src2_lo: resolve(src2_lo),
1756                        src2_hi: resolve(src2_hi),
1757                    };
1758                }
1759                Opcode::I64GtU {
1760                    dest,
1761                    src1_lo,
1762                    src1_hi,
1763                    src2_lo,
1764                    src2_hi,
1765                } => {
1766                    inst.opcode = Opcode::I64GtU {
1767                        dest,
1768                        src1_lo: resolve(src1_lo),
1769                        src1_hi: resolve(src1_hi),
1770                        src2_lo: resolve(src2_lo),
1771                        src2_hi: resolve(src2_hi),
1772                    };
1773                }
1774                Opcode::I64LeU {
1775                    dest,
1776                    src1_lo,
1777                    src1_hi,
1778                    src2_lo,
1779                    src2_hi,
1780                } => {
1781                    inst.opcode = Opcode::I64LeU {
1782                        dest,
1783                        src1_lo: resolve(src1_lo),
1784                        src1_hi: resolve(src1_hi),
1785                        src2_lo: resolve(src2_lo),
1786                        src2_hi: resolve(src2_hi),
1787                    };
1788                }
1789                Opcode::I64GeU {
1790                    dest,
1791                    src1_lo,
1792                    src1_hi,
1793                    src2_lo,
1794                    src2_hi,
1795                } => {
1796                    inst.opcode = Opcode::I64GeU {
1797                        dest,
1798                        src1_lo: resolve(src1_lo),
1799                        src1_hi: resolve(src1_hi),
1800                        src2_lo: resolve(src2_lo),
1801                        src2_hi: resolve(src2_hi),
1802                    };
1803                }
1804                Opcode::I64Eqz {
1805                    dest,
1806                    src_lo,
1807                    src_hi,
1808                } => {
1809                    inst.opcode = Opcode::I64Eqz {
1810                        dest,
1811                        src_lo: resolve(src_lo),
1812                        src_hi: resolve(src_hi),
1813                    };
1814                }
1815                Opcode::I64Clz {
1816                    dest,
1817                    src_lo,
1818                    src_hi,
1819                } => {
1820                    inst.opcode = Opcode::I64Clz {
1821                        dest,
1822                        src_lo: resolve(src_lo),
1823                        src_hi: resolve(src_hi),
1824                    };
1825                }
1826                Opcode::I64Ctz {
1827                    dest,
1828                    src_lo,
1829                    src_hi,
1830                } => {
1831                    inst.opcode = Opcode::I64Ctz {
1832                        dest,
1833                        src_lo: resolve(src_lo),
1834                        src_hi: resolve(src_hi),
1835                    };
1836                }
1837                Opcode::I64Popcnt {
1838                    dest,
1839                    src_lo,
1840                    src_hi,
1841                } => {
1842                    inst.opcode = Opcode::I64Popcnt {
1843                        dest,
1844                        src_lo: resolve(src_lo),
1845                        src_hi: resolve(src_hi),
1846                    };
1847                }
1848                Opcode::I64Extend8S {
1849                    dest_lo,
1850                    dest_hi,
1851                    src_lo,
1852                } => {
1853                    inst.opcode = Opcode::I64Extend8S {
1854                        dest_lo,
1855                        dest_hi,
1856                        src_lo: resolve(src_lo),
1857                    };
1858                }
1859                Opcode::I64Extend16S {
1860                    dest_lo,
1861                    dest_hi,
1862                    src_lo,
1863                } => {
1864                    inst.opcode = Opcode::I64Extend16S {
1865                        dest_lo,
1866                        dest_hi,
1867                        src_lo: resolve(src_lo),
1868                    };
1869                }
1870                Opcode::I64Extend32S {
1871                    dest_lo,
1872                    dest_hi,
1873                    src_lo,
1874                } => {
1875                    inst.opcode = Opcode::I64Extend32S {
1876                        dest_lo,
1877                        dest_hi,
1878                        src_lo: resolve(src_lo),
1879                    };
1880                }
1881                Opcode::I64ExtendI32U {
1882                    dest_lo,
1883                    dest_hi,
1884                    src,
1885                } => {
1886                    inst.opcode = Opcode::I64ExtendI32U {
1887                        dest_lo,
1888                        dest_hi,
1889                        src: resolve(src),
1890                    };
1891                }
1892                Opcode::I64ExtendI32S {
1893                    dest_lo,
1894                    dest_hi,
1895                    src,
1896                } => {
1897                    inst.opcode = Opcode::I64ExtendI32S {
1898                        dest_lo,
1899                        dest_hi,
1900                        src: resolve(src),
1901                    };
1902                }
1903                Opcode::I32WrapI64 { dest, src_lo } => {
1904                    inst.opcode = Opcode::I32WrapI64 {
1905                        dest,
1906                        src_lo: resolve(src_lo),
1907                    };
1908                }
1909
1910                // ====================================================
1911                // Sub-word memory / globals / memory.{size,grow}
1912                //
1913                // Same rationale as MemLoad/MemStore: resolve src vregs
1914                // through `reg_map` so CSE-killed Loads don't leave
1915                // stale references. We do NOT CSE the loads themselves
1916                // (no alias analysis), only fix up consumer vregs.
1917                // ====================================================
1918                Opcode::MemLoadSubword {
1919                    dest,
1920                    addr,
1921                    offset,
1922                    width,
1923                    signed,
1924                } => {
1925                    inst.opcode = Opcode::MemLoadSubword {
1926                        dest,
1927                        addr: resolve(addr),
1928                        offset,
1929                        width,
1930                        signed,
1931                    };
1932                }
1933                Opcode::MemStoreSubword {
1934                    src,
1935                    addr,
1936                    offset,
1937                    width,
1938                } => {
1939                    inst.opcode = Opcode::MemStoreSubword {
1940                        src: resolve(src),
1941                        addr: resolve(addr),
1942                        offset,
1943                        width,
1944                    };
1945                }
1946                Opcode::GlobalSet { src, idx } => {
1947                    inst.opcode = Opcode::GlobalSet {
1948                        src: resolve(src),
1949                        idx,
1950                    };
1951                }
1952                Opcode::MemoryGrow { dest, delta } => {
1953                    inst.opcode = Opcode::MemoryGrow {
1954                        dest,
1955                        delta: resolve(delta),
1956                    };
1957                }
1958
1959                _ => {}
1960            }
1961
1962            // See `dests_to_invalidate` above: clear `reg_map` entries for
1963            // any vreg this instruction writes, so that downstream uses of
1964            // those vregs are NOT rewritten back to a now-stale CSE-aliased
1965            // value. Skip if this instruction was itself marked dead by
1966            // CSE — its dest is a dead vreg whose alias should remain so
1967            // later consumers can re-use the existing live value.
1968            if !inst.is_dead {
1969                for dest in &dests_to_invalidate {
1970                    reg_map.remove(dest);
1971                }
1972            }
1973        }
1974
1975        if self.verbose && modified > 0 {
1976            eprintln!("CSE: {} subexpressions eliminated", modified);
1977        }
1978
1979        OptResult {
1980            changed: modified > 0,
1981            removed_count: modified, // Marked as dead
1982            added_count: 0,
1983            modified_count: 0,
1984        }
1985    }
1986}
1987
1988impl Default for CommonSubexpressionElimination {
1989    fn default() -> Self {
1990        Self::new()
1991    }
1992}
1993
1994impl OptimizationPass for CommonSubexpressionElimination {
1995    fn name(&self) -> &'static str {
1996        "common-subexpression-elimination"
1997    }
1998
1999    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2000        self.eliminate_cse(instructions)
2001    }
2002}
2003
2004/// Algebraic Simplification pass
2005pub struct AlgebraicSimplification {
2006    verbose: bool,
2007}
2008
2009impl AlgebraicSimplification {
2010    pub fn new() -> Self {
2011        Self { verbose: false }
2012    }
2013
2014    pub fn with_verbose(mut self) -> Self {
2015        self.verbose = true;
2016        self
2017    }
2018
2019    /// Apply algebraic simplifications
2020    fn simplify(&mut self, instructions: &mut [Instruction]) -> OptResult {
2021        // Track constant values
2022        let mut const_values: HashMap<Reg, i32> = HashMap::new();
2023        let mut modified = 0;
2024
2025        for inst in instructions.iter_mut() {
2026            if inst.is_dead {
2027                continue;
2028            }
2029
2030            let opcode = inst.opcode.clone();
2031
2032            match opcode {
2033                // Track constants
2034                Opcode::Const { dest, value } => {
2035                    const_values.insert(dest, value);
2036                }
2037
2038                // Simplify: x + 0 = x, 0 + x = x
2039                Opcode::Add {
2040                    dest: _,
2041                    src1,
2042                    src2,
2043                } => {
2044                    let val1 = const_values.get(&src1);
2045                    let val2 = const_values.get(&src2);
2046
2047                    match (val1, val2) {
2048                        (Some(&0), _) => {
2049                            // 0 + x = x (mark as dead, would need copy propagation)
2050                            inst.is_dead = true;
2051                            modified += 1;
2052                            if self.verbose {
2053                                eprintln!("Simplified: 0 + r{} -> r{}", src2.0, src2.0);
2054                            }
2055                        }
2056                        (_, Some(&0)) => {
2057                            // x + 0 = x
2058                            inst.is_dead = true;
2059                            modified += 1;
2060                            if self.verbose {
2061                                eprintln!("Simplified: r{} + 0 -> r{}", src1.0, src1.0);
2062                            }
2063                        }
2064                        _ => {}
2065                    }
2066                }
2067
2068                // Simplify: x - 0 = x, x - x = 0
2069                Opcode::Sub { dest, src1, src2 } => {
2070                    let val2 = const_values.get(&src2);
2071
2072                    if let Some(&0) = val2 {
2073                        // x - 0 = x
2074                        inst.is_dead = true;
2075                        modified += 1;
2076                        if self.verbose {
2077                            eprintln!("Simplified: r{} - 0 -> r{}", src1.0, src1.0);
2078                        }
2079                    } else if src1 == src2 {
2080                        // x - x = 0
2081                        inst.opcode = Opcode::Const { dest, value: 0 };
2082                        const_values.insert(dest, 0);
2083                        modified += 1;
2084                        if self.verbose {
2085                            eprintln!("Simplified: r{} - r{} -> 0", src1.0, src2.0);
2086                        }
2087                    }
2088                }
2089
2090                // Simplify: x * 0 = 0, 0 * x = 0, x * 1 = x, 1 * x = x
2091                Opcode::Mul { dest, src1, src2 } => {
2092                    let val1 = const_values.get(&src1);
2093                    let val2 = const_values.get(&src2);
2094
2095                    match (val1, val2) {
2096                        (Some(&0), _) | (_, Some(&0)) => {
2097                            // x * 0 = 0 or 0 * x = 0
2098                            inst.opcode = Opcode::Const { dest, value: 0 };
2099                            const_values.insert(dest, 0);
2100                            modified += 1;
2101                            if self.verbose {
2102                                eprintln!("Simplified: mul with 0 -> 0");
2103                            }
2104                        }
2105                        (Some(&1), _) => {
2106                            // 1 * x = x
2107                            inst.is_dead = true;
2108                            modified += 1;
2109                            if self.verbose {
2110                                eprintln!("Simplified: 1 * r{} -> r{}", src2.0, src2.0);
2111                            }
2112                        }
2113                        (_, Some(&1)) => {
2114                            // x * 1 = x
2115                            inst.is_dead = true;
2116                            modified += 1;
2117                            if self.verbose {
2118                                eprintln!("Simplified: r{} * 1 -> r{}", src1.0, src1.0);
2119                            }
2120                        }
2121                        _ => {}
2122                    }
2123                }
2124
2125                _ => {}
2126            }
2127        }
2128
2129        if self.verbose && modified > 0 {
2130            eprintln!(
2131                "Algebraic simplification: {} operations simplified",
2132                modified
2133            );
2134        }
2135
2136        OptResult {
2137            changed: modified > 0,
2138            removed_count: 0,
2139            added_count: 0,
2140            modified_count: modified,
2141        }
2142    }
2143}
2144
2145impl Default for AlgebraicSimplification {
2146    fn default() -> Self {
2147        Self::new()
2148    }
2149}
2150
2151impl OptimizationPass for AlgebraicSimplification {
2152    fn name(&self) -> &'static str {
2153        "algebraic-simplification"
2154    }
2155
2156    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2157        self.simplify(instructions)
2158    }
2159}
2160
2161/// Peephole Optimization pass
2162pub struct PeepholeOptimization {
2163    verbose: bool,
2164}
2165
2166impl PeepholeOptimization {
2167    pub fn new() -> Self {
2168        Self { verbose: false }
2169    }
2170
2171    pub fn with_verbose(mut self) -> Self {
2172        self.verbose = true;
2173        self
2174    }
2175
2176    /// Apply peephole optimizations (local pattern matching)
2177    fn optimize(&mut self, instructions: &mut [Instruction]) -> OptResult {
2178        let mut modified = 0;
2179
2180        // Look for patterns in a sliding window
2181        let mut i = 0;
2182        while i + 1 < instructions.len() {
2183            if instructions[i].is_dead || instructions[i + 1].is_dead {
2184                i += 1;
2185                continue;
2186            }
2187
2188            let inst1 = instructions[i].opcode.clone();
2189            let inst2 = instructions[i + 1].opcode.clone();
2190
2191            // Pattern: const r1, a; const r1, b -> eliminate first const
2192            match (&inst1, &inst2) {
2193                (Opcode::Const { dest: dest1, .. }, Opcode::Const { dest: dest2, .. })
2194                    if dest1 == dest2 =>
2195                {
2196                    // Second const overwrites first
2197                    instructions[i].is_dead = true;
2198                    modified += 1;
2199
2200                    if self.verbose {
2201                        eprintln!("Peephole: Eliminated redundant const to r{}", dest1.0);
2202                    }
2203                }
2204
2205                // Pattern: add r2, r0, r1; add r3, r0, r1 (handled by CSE, but detect for stats)
2206                _ => {}
2207            }
2208
2209            i += 1;
2210        }
2211
2212        // Look for 3-instruction patterns
2213        let mut i = 0;
2214        while i + 2 < instructions.len() {
2215            if instructions[i].is_dead || instructions[i + 1].is_dead || instructions[i + 2].is_dead
2216            {
2217                i += 1;
2218                continue;
2219            }
2220
2221            let _inst1 = instructions[i].opcode.clone();
2222            let _inst2 = instructions[i + 1].opcode.clone();
2223            let _inst3 = instructions[i + 2].opcode.clone();
2224
2225            // Pattern: const r0, 0; add r2, r1, r0; -> just mark add as dead (simplified by algebraic)
2226            // TODO: implement 3-instruction peephole patterns
2227
2228            i += 1;
2229        }
2230
2231        if self.verbose && modified > 0 {
2232            eprintln!("Peephole optimization: {} patterns matched", modified);
2233        }
2234
2235        OptResult {
2236            changed: modified > 0,
2237            removed_count: modified,
2238            added_count: 0,
2239            modified_count: 0,
2240        }
2241    }
2242}
2243
2244impl Default for PeepholeOptimization {
2245    fn default() -> Self {
2246        Self::new()
2247    }
2248}
2249
2250impl OptimizationPass for PeepholeOptimization {
2251    fn name(&self) -> &'static str {
2252        "peephole-optimization"
2253    }
2254
2255    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2256        self.optimize(instructions)
2257    }
2258}
2259
2260/// Strength Reduction pass
2261pub struct StrengthReduction {
2262    verbose: bool,
2263}
2264
2265impl StrengthReduction {
2266    pub fn new() -> Self {
2267        Self { verbose: false }
2268    }
2269
2270    pub fn with_verbose(mut self) -> Self {
2271        self.verbose = true;
2272        self
2273    }
2274
2275    /// Check if a number is a power of 2
2276    fn is_power_of_2(n: i32) -> bool {
2277        n > 0 && (n & (n - 1)) == 0
2278    }
2279
2280    /// Get log2 of a power of 2
2281    fn log2(n: i32) -> u32 {
2282        n.trailing_zeros()
2283    }
2284
2285    /// Apply strength reduction optimizations
2286    fn reduce(&mut self, instructions: &mut [Instruction]) -> OptResult {
2287        let mut const_values: HashMap<Reg, i32> = HashMap::new();
2288        let mut modified = 0;
2289
2290        for inst in instructions.iter_mut() {
2291            if inst.is_dead {
2292                continue;
2293            }
2294
2295            let opcode = inst.opcode.clone();
2296
2297            match opcode {
2298                // Track constants
2299                Opcode::Const { dest, value } => {
2300                    const_values.insert(dest, value);
2301                }
2302
2303                // Reduce: x * 2^n -> x << n
2304                Opcode::Mul {
2305                    dest: _,
2306                    src1,
2307                    src2,
2308                } => {
2309                    let val1 = const_values.get(&src1);
2310                    let val2 = const_values.get(&src2);
2311
2312                    if let Some(&val) = val2
2313                        && Self::is_power_of_2(val)
2314                    {
2315                        // Replace mul with left shift (represented as mul for now)
2316                        // In real implementation, would use shift opcode
2317                        modified += 1;
2318                        if self.verbose {
2319                            eprintln!(
2320                                "Strength reduction: r{} * {} -> r{} << {}",
2321                                src1.0,
2322                                val,
2323                                src1.0,
2324                                Self::log2(val)
2325                            );
2326                        }
2327                    } else if let Some(&val) = val1
2328                        && Self::is_power_of_2(val)
2329                    {
2330                        modified += 1;
2331                        if self.verbose {
2332                            eprintln!(
2333                                "Strength reduction: {} * r{} -> r{} << {}",
2334                                val,
2335                                src2.0,
2336                                src2.0,
2337                                Self::log2(val)
2338                            );
2339                        }
2340                    }
2341                }
2342
2343                _ => {}
2344            }
2345        }
2346
2347        if self.verbose && modified > 0 {
2348            eprintln!("Strength reduction: {} operations reduced", modified);
2349        }
2350
2351        OptResult {
2352            changed: modified > 0,
2353            removed_count: 0,
2354            added_count: 0,
2355            modified_count: modified,
2356        }
2357    }
2358}
2359
2360impl Default for StrengthReduction {
2361    fn default() -> Self {
2362        Self::new()
2363    }
2364}
2365
2366impl OptimizationPass for StrengthReduction {
2367    fn name(&self) -> &'static str {
2368        "strength-reduction"
2369    }
2370
2371    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2372        self.reduce(instructions)
2373    }
2374}
2375
2376/// Loop-Invariant Code Motion pass
2377pub struct LoopInvariantCodeMotion {
2378    verbose: bool,
2379}
2380
2381impl LoopInvariantCodeMotion {
2382    pub fn new() -> Self {
2383        Self { verbose: false }
2384    }
2385
2386    pub fn with_verbose(mut self) -> Self {
2387        self.verbose = true;
2388        self
2389    }
2390
2391    /// Detect loop-invariant computations
2392    fn detect_invariants(&self, cfg: &Cfg, instructions: &[Instruction]) -> HashSet<usize> {
2393        let mut invariants = HashSet::new();
2394
2395        // For each loop in the CFG
2396        for loop_info in &cfg.loops {
2397            // Find instructions that don't depend on loop-variant values
2398            for inst in instructions {
2399                if inst.is_dead || !loop_info.body.contains(&inst.block_id) {
2400                    continue;
2401                }
2402
2403                // Check if instruction is loop-invariant
2404                let is_invariant = match &inst.opcode {
2405                    // Constants are always invariant
2406                    Opcode::Const { .. } => true,
2407
2408                    // Arithmetic ops are invariant if operands are
2409                    Opcode::Add {
2410                        src1: _, src2: _, ..
2411                    }
2412                    | Opcode::Sub {
2413                        src1: _, src2: _, ..
2414                    }
2415                    | Opcode::Mul {
2416                        src1: _, src2: _, ..
2417                    } => {
2418                        // Simplified check: if sources are from outside loop or are constants
2419                        // In real implementation, would track def-use chains
2420                        false // Conservative: mark as not invariant
2421                    }
2422
2423                    // Loads might have side effects
2424                    Opcode::Load { .. } => false,
2425
2426                    _ => false,
2427                };
2428
2429                if is_invariant {
2430                    invariants.insert(inst.id);
2431                }
2432            }
2433        }
2434
2435        invariants
2436    }
2437
2438    /// Move loop-invariant code out of loops
2439    fn hoist(&mut self, cfg: &mut Cfg, instructions: &mut [Instruction]) -> OptResult {
2440        let invariants = self.detect_invariants(cfg, instructions);
2441
2442        // In a real implementation, would actually move instructions
2443        // For now, just count and report
2444        if self.verbose && !invariants.is_empty() {
2445            eprintln!(
2446                "LICM: {} loop-invariant instructions detected",
2447                invariants.len()
2448            );
2449        }
2450
2451        let modified = invariants.len();
2452
2453        OptResult {
2454            changed: modified > 0,
2455            removed_count: 0,
2456            added_count: 0,
2457            modified_count: modified,
2458        }
2459    }
2460}
2461
2462impl Default for LoopInvariantCodeMotion {
2463    fn default() -> Self {
2464        Self::new()
2465    }
2466}
2467
2468impl OptimizationPass for LoopInvariantCodeMotion {
2469    fn name(&self) -> &'static str {
2470        "loop-invariant-code-motion"
2471    }
2472
2473    fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2474        self.hoist(cfg, instructions)
2475    }
2476}
2477
2478/// Copy Propagation pass
2479pub struct CopyPropagation {
2480    verbose: bool,
2481}
2482
2483impl CopyPropagation {
2484    pub fn new() -> Self {
2485        Self { verbose: false }
2486    }
2487
2488    pub fn with_verbose(mut self) -> Self {
2489        self.verbose = true;
2490        self
2491    }
2492
2493    /// Perform copy propagation
2494    fn propagate(&mut self, instructions: &mut [Instruction]) -> OptResult {
2495        let copy_map: HashMap<Reg, Reg> = HashMap::new();
2496        let mut modified = 0;
2497
2498        // Build copy chains (e.g., r2 = r1, r3 = r2 => r3 = r1)
2499        // In our IR, we don't have explicit copy/move instructions,
2500        // but we track when a value passes through unchanged
2501        // For now, the copy_map is empty (could be extended in the future)
2502
2503        // Apply copy propagation
2504        for inst in instructions.iter_mut() {
2505            if inst.is_dead {
2506                continue;
2507            }
2508
2509            let mut changed = false;
2510            let opcode = inst.opcode.clone();
2511
2512            match opcode {
2513                Opcode::Add { dest, src1, src2 } => {
2514                    let new_src1 = Self::resolve(&copy_map, src1);
2515                    let new_src2 = Self::resolve(&copy_map, src2);
2516
2517                    if new_src1 != src1 || new_src2 != src2 {
2518                        inst.opcode = Opcode::Add {
2519                            dest,
2520                            src1: new_src1,
2521                            src2: new_src2,
2522                        };
2523                        changed = true;
2524                        modified += 1;
2525                    }
2526                }
2527
2528                Opcode::Sub { dest, src1, src2 } => {
2529                    let new_src1 = Self::resolve(&copy_map, src1);
2530                    let new_src2 = Self::resolve(&copy_map, src2);
2531
2532                    if new_src1 != src1 || new_src2 != src2 {
2533                        inst.opcode = Opcode::Sub {
2534                            dest,
2535                            src1: new_src1,
2536                            src2: new_src2,
2537                        };
2538                        changed = true;
2539                        modified += 1;
2540                    }
2541                }
2542
2543                Opcode::Mul { dest, src1, src2 } => {
2544                    let new_src1 = Self::resolve(&copy_map, src1);
2545                    let new_src2 = Self::resolve(&copy_map, src2);
2546
2547                    if new_src1 != src1 || new_src2 != src2 {
2548                        inst.opcode = Opcode::Mul {
2549                            dest,
2550                            src1: new_src1,
2551                            src2: new_src2,
2552                        };
2553                        changed = true;
2554                        modified += 1;
2555                    }
2556                }
2557
2558                Opcode::Load { dest: _, addr: _ } => {
2559                    // Loads don't have register operands to propagate
2560                }
2561
2562                Opcode::Store { src, addr } => {
2563                    let new_src = Self::resolve(&copy_map, src);
2564
2565                    if new_src != src {
2566                        inst.opcode = Opcode::Store { src: new_src, addr };
2567                        changed = true;
2568                        modified += 1;
2569                    }
2570                }
2571
2572                _ => {}
2573            }
2574
2575            if changed && self.verbose {
2576                eprintln!("Copy propagation: updated instruction {}", inst.id);
2577            }
2578        }
2579
2580        if self.verbose && modified > 0 {
2581            eprintln!("Copy propagation: {} instructions updated", modified);
2582        }
2583
2584        OptResult {
2585            changed: modified > 0,
2586            removed_count: 0,
2587            added_count: 0,
2588            modified_count: modified,
2589        }
2590    }
2591
2592    /// Resolve a register through copy chains
2593    fn resolve(copy_map: &HashMap<Reg, Reg>, reg: Reg) -> Reg {
2594        let mut current = reg;
2595        let mut visited = HashSet::new();
2596
2597        // Follow copy chain with cycle detection
2598        while let Some(&next) = copy_map.get(&current) {
2599            if !visited.insert(current) {
2600                // Cycle detected, stop
2601                break;
2602            }
2603            if next == current {
2604                // Self-copy, stop
2605                break;
2606            }
2607            current = next;
2608        }
2609
2610        current
2611    }
2612}
2613
2614impl Default for CopyPropagation {
2615    fn default() -> Self {
2616        Self::new()
2617    }
2618}
2619
2620impl OptimizationPass for CopyPropagation {
2621    fn name(&self) -> &'static str {
2622        "copy-propagation"
2623    }
2624
2625    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2626        self.propagate(instructions)
2627    }
2628}
2629
2630/// Instruction Combining pass
2631pub struct InstructionCombining {
2632    verbose: bool,
2633}
2634
2635impl InstructionCombining {
2636    pub fn new() -> Self {
2637        Self { verbose: false }
2638    }
2639
2640    pub fn with_verbose(mut self) -> Self {
2641        self.verbose = true;
2642        self
2643    }
2644
2645    /// Combine instructions into simpler forms
2646    fn combine(&mut self, instructions: &mut [Instruction]) -> OptResult {
2647        let mut const_values: HashMap<Reg, i32> = HashMap::new();
2648        let mut def_map: HashMap<Reg, usize> = HashMap::new();
2649        let mut inst_opcodes: HashMap<usize, Opcode> = HashMap::new();
2650        let mut modified = 0;
2651
2652        // Build value tracking (separate from modification)
2653        for inst in instructions.iter() {
2654            if inst.is_dead {
2655                continue;
2656            }
2657
2658            match &inst.opcode {
2659                Opcode::Const { dest, value } => {
2660                    const_values.insert(*dest, *value);
2661                    def_map.insert(*dest, inst.id);
2662                }
2663                Opcode::Add { dest, .. } | Opcode::Sub { dest, .. } | Opcode::Mul { dest, .. } => {
2664                    def_map.insert(*dest, inst.id);
2665                }
2666                _ => {}
2667            }
2668            inst_opcodes.insert(inst.id, inst.opcode.clone());
2669        }
2670
2671        // Apply combining transformations (now we can iterate without borrowing issues)
2672        for inst in instructions.iter() {
2673            if inst.is_dead {
2674                continue;
2675            }
2676
2677            match &inst.opcode {
2678                // (x + c1) + c2 => x + (c1 + c2)
2679                Opcode::Add {
2680                    dest: _,
2681                    src1,
2682                    src2,
2683                } => {
2684                    // Check if src1 is the result of another add with a constant
2685                    if let Some(&val2) = const_values.get(src2)
2686                        && let Some(&def_id) = def_map.get(src1)
2687                        && let Some(Opcode::Add {
2688                            dest: _,
2689                            src1: inner_src1,
2690                            src2: inner_src2,
2691                        }) = inst_opcodes.get(&def_id)
2692                        && let Some(&val1) = const_values.get(inner_src2)
2693                    {
2694                        // Found (x + c1) + c2 pattern
2695                        let combined = val1.wrapping_add(val2);
2696
2697                        // Would create a new const and update this add
2698                        // For now, just count the opportunity
2699                        modified += 1;
2700
2701                        if self.verbose {
2702                            eprintln!(
2703                                "Instruction combining: (r{} + {}) + {} => r{} + {}",
2704                                inner_src1.0, val1, val2, inner_src1.0, combined
2705                            );
2706                        }
2707                    }
2708                }
2709
2710                // x * 1 => x (already handled by algebraic simplification)
2711                // x * 0 => 0 (already handled by algebraic simplification)
2712                // x - x => 0 (already handled by algebraic simplification)
2713                Opcode::Mul {
2714                    dest: _,
2715                    src1: _,
2716                    src2: _,
2717                } => {
2718                    // Detect patterns like (x << n) which is mul by 2^n
2719                    // Already handled by strength reduction
2720                }
2721
2722                _ => {}
2723            }
2724        }
2725
2726        if self.verbose && modified > 0 {
2727            eprintln!("Instruction combining: {} opportunities found", modified);
2728        }
2729
2730        OptResult {
2731            changed: modified > 0,
2732            removed_count: 0,
2733            added_count: 0,
2734            modified_count: modified,
2735        }
2736    }
2737}
2738
2739impl Default for InstructionCombining {
2740    fn default() -> Self {
2741        Self::new()
2742    }
2743}
2744
2745impl OptimizationPass for InstructionCombining {
2746    fn name(&self) -> &'static str {
2747        "instruction-combining"
2748    }
2749
2750    fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2751        self.combine(instructions)
2752    }
2753}
2754
2755/// Optimization pass manager
2756pub struct PassManager {
2757    passes: Vec<Box<dyn OptimizationPass>>,
2758    max_iterations: usize,
2759}
2760
2761impl PassManager {
2762    pub fn new() -> Self {
2763        Self {
2764            passes: Vec::new(),
2765            max_iterations: 10,
2766        }
2767    }
2768
2769    pub fn add_pass<P: OptimizationPass + 'static>(mut self, pass: P) -> Self {
2770        self.passes.push(Box::new(pass));
2771        self
2772    }
2773
2774    pub fn with_max_iterations(mut self, max: usize) -> Self {
2775        self.max_iterations = max;
2776        self
2777    }
2778
2779    pub fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
2780        let mut total_result = OptResult::no_change();
2781        let mut iteration = 0;
2782
2783        loop {
2784            iteration += 1;
2785            if iteration > self.max_iterations {
2786                break;
2787            }
2788
2789            let mut iteration_result = OptResult::no_change();
2790
2791            for pass in &mut self.passes {
2792                let result = pass.run(cfg, instructions);
2793                iteration_result.merge(result);
2794            }
2795
2796            total_result.merge(iteration_result.clone());
2797
2798            // Stop if no changes in this iteration
2799            if !iteration_result.changed {
2800                break;
2801            }
2802        }
2803
2804        total_result
2805    }
2806}
2807
2808impl Default for PassManager {
2809    fn default() -> Self {
2810        Self::new()
2811    }
2812}
2813
2814#[cfg(test)]
2815mod tests {
2816    use super::*;
2817    use synth_cfg::CfgBuilder;
2818    use synth_cfg::Loop;
2819
2820    #[test]
2821    fn test_dce_removes_unreachable() {
2822        // Build a CFG with unreachable block
2823        let mut builder = CfgBuilder::new();
2824
2825        // Entry block
2826        builder.add_instruction();
2827
2828        // Reachable block
2829        let reachable = builder.start_block();
2830        builder.add_instruction();
2831
2832        // Unreachable block
2833        let unreachable = builder.start_block();
2834        builder.add_instruction();
2835
2836        // Connect entry to reachable only
2837        builder.set_current_block(0);
2838        builder.add_branch(reachable);
2839
2840        let mut cfg = builder.build();
2841
2842        // Create instructions
2843        let mut instructions = vec![
2844            Instruction {
2845                id: 0,
2846                opcode: Opcode::Nop,
2847                block_id: 0,
2848                is_dead: false,
2849            },
2850            Instruction {
2851                id: 1,
2852                opcode: Opcode::Nop,
2853                block_id: reachable,
2854                is_dead: false,
2855            },
2856            Instruction {
2857                id: 2,
2858                opcode: Opcode::Nop,
2859                block_id: unreachable,
2860                is_dead: false,
2861            },
2862        ];
2863
2864        // Run DCE
2865        let mut dce = DeadCodeElimination::new();
2866        let result = dce.run(&mut cfg, &mut instructions);
2867
2868        // Should remove unreachable instruction
2869        assert!(result.changed);
2870        assert_eq!(result.removed_count, 1);
2871        assert!(instructions[2].is_dead);
2872        assert!(!instructions[0].is_dead);
2873        assert!(!instructions[1].is_dead);
2874    }
2875
2876    #[test]
2877    fn test_dce_keeps_reachable() {
2878        let mut builder = CfgBuilder::new();
2879        builder.add_instruction();
2880
2881        let block1 = builder.start_block();
2882        builder.add_instruction();
2883
2884        builder.set_current_block(0);
2885        builder.add_branch(block1);
2886
2887        let mut cfg = builder.build();
2888
2889        let mut instructions = vec![
2890            Instruction {
2891                id: 0,
2892                opcode: Opcode::Nop,
2893                block_id: 0,
2894                is_dead: false,
2895            },
2896            Instruction {
2897                id: 1,
2898                opcode: Opcode::Nop,
2899                block_id: block1,
2900                is_dead: false,
2901            },
2902        ];
2903
2904        let mut dce = DeadCodeElimination::new();
2905        let result = dce.run(&mut cfg, &mut instructions);
2906
2907        // Should not remove anything
2908        assert!(!result.changed);
2909        assert_eq!(result.removed_count, 0);
2910        assert!(!instructions[0].is_dead);
2911        assert!(!instructions[1].is_dead);
2912    }
2913
2914    #[test]
2915    fn test_pass_manager() {
2916        let mut builder = CfgBuilder::new();
2917        builder.add_instruction();
2918
2919        let mut cfg = builder.build();
2920        let mut instructions = vec![Instruction {
2921            id: 0,
2922            opcode: Opcode::Nop,
2923            block_id: 0,
2924            is_dead: false,
2925        }];
2926
2927        let mut manager = PassManager::new()
2928            .add_pass(DeadCodeElimination::new())
2929            .add_pass(ConstantFolding::new());
2930
2931        let result = manager.run(&mut cfg, &mut instructions);
2932
2933        // Should complete without errors
2934        assert_eq!(result.removed_count, 0); // Nothing to remove
2935    }
2936
2937    #[test]
2938    fn test_opt_result_merge() {
2939        let mut result1 = OptResult {
2940            changed: true,
2941            removed_count: 5,
2942            added_count: 2,
2943            modified_count: 3,
2944        };
2945
2946        let result2 = OptResult {
2947            changed: false,
2948            removed_count: 1,
2949            added_count: 1,
2950            modified_count: 2,
2951        };
2952
2953        result1.merge(result2);
2954
2955        assert!(result1.changed);
2956        assert_eq!(result1.removed_count, 6);
2957        assert_eq!(result1.added_count, 3);
2958        assert_eq!(result1.modified_count, 5);
2959    }
2960
2961    #[test]
2962    fn test_constant_folding_add() {
2963        let mut builder = CfgBuilder::new();
2964        builder.add_instruction();
2965        builder.add_instruction();
2966        builder.add_instruction();
2967
2968        let mut cfg = builder.build();
2969
2970        // Create: r0 = const 10, r1 = const 20, r2 = r0 + r1
2971        let mut instructions = vec![
2972            Instruction {
2973                id: 0,
2974                opcode: Opcode::Const {
2975                    dest: Reg(0),
2976                    value: 10,
2977                },
2978                block_id: 0,
2979                is_dead: false,
2980            },
2981            Instruction {
2982                id: 1,
2983                opcode: Opcode::Const {
2984                    dest: Reg(1),
2985                    value: 20,
2986                },
2987                block_id: 0,
2988                is_dead: false,
2989            },
2990            Instruction {
2991                id: 2,
2992                opcode: Opcode::Add {
2993                    dest: Reg(2),
2994                    src1: Reg(0),
2995                    src2: Reg(1),
2996                },
2997                block_id: 0,
2998                is_dead: false,
2999            },
3000        ];
3001
3002        let mut folder = ConstantFolding::new();
3003        let result = folder.run(&mut cfg, &mut instructions);
3004
3005        // Should fold add to const 30
3006        assert!(result.changed);
3007        assert_eq!(result.modified_count, 1);
3008        assert_eq!(
3009            instructions[2].opcode,
3010            Opcode::Const {
3011                dest: Reg(2),
3012                value: 30
3013            }
3014        );
3015    }
3016
3017    #[test]
3018    fn test_constant_folding_multiple_ops() {
3019        let mut builder = CfgBuilder::new();
3020        for _ in 0..6 {
3021            builder.add_instruction();
3022        }
3023
3024        let mut cfg = builder.build();
3025
3026        // Create: r0 = 5, r1 = 3, r2 = r0 + r1, r3 = r0 - r1, r4 = r0 * r1
3027        let mut instructions = vec![
3028            Instruction {
3029                id: 0,
3030                opcode: Opcode::Const {
3031                    dest: Reg(0),
3032                    value: 5,
3033                },
3034                block_id: 0,
3035                is_dead: false,
3036            },
3037            Instruction {
3038                id: 1,
3039                opcode: Opcode::Const {
3040                    dest: Reg(1),
3041                    value: 3,
3042                },
3043                block_id: 0,
3044                is_dead: false,
3045            },
3046            Instruction {
3047                id: 2,
3048                opcode: Opcode::Add {
3049                    dest: Reg(2),
3050                    src1: Reg(0),
3051                    src2: Reg(1),
3052                },
3053                block_id: 0,
3054                is_dead: false,
3055            },
3056            Instruction {
3057                id: 3,
3058                opcode: Opcode::Sub {
3059                    dest: Reg(3),
3060                    src1: Reg(0),
3061                    src2: Reg(1),
3062                },
3063                block_id: 0,
3064                is_dead: false,
3065            },
3066            Instruction {
3067                id: 4,
3068                opcode: Opcode::Mul {
3069                    dest: Reg(4),
3070                    src1: Reg(0),
3071                    src2: Reg(1),
3072                },
3073                block_id: 0,
3074                is_dead: false,
3075            },
3076        ];
3077
3078        let mut folder = ConstantFolding::new();
3079        let result = folder.run(&mut cfg, &mut instructions);
3080
3081        // Should fold all three operations
3082        assert!(result.changed);
3083        assert_eq!(result.modified_count, 3);
3084        assert_eq!(
3085            instructions[2].opcode,
3086            Opcode::Const {
3087                dest: Reg(2),
3088                value: 8
3089            }
3090        ); // 5 + 3
3091        assert_eq!(
3092            instructions[3].opcode,
3093            Opcode::Const {
3094                dest: Reg(3),
3095                value: 2
3096            }
3097        ); // 5 - 3
3098        assert_eq!(
3099            instructions[4].opcode,
3100            Opcode::Const {
3101                dest: Reg(4),
3102                value: 15
3103            }
3104        ); // 5 * 3
3105    }
3106
3107    #[test]
3108    fn test_constant_folding_chained() {
3109        let mut builder = CfgBuilder::new();
3110        for _ in 0..4 {
3111            builder.add_instruction();
3112        }
3113
3114        let mut cfg = builder.build();
3115
3116        // Create: r0 = 2, r1 = 3, r2 = r0 + r1, r3 = r2 * r0
3117        let mut instructions = vec![
3118            Instruction {
3119                id: 0,
3120                opcode: Opcode::Const {
3121                    dest: Reg(0),
3122                    value: 2,
3123                },
3124                block_id: 0,
3125                is_dead: false,
3126            },
3127            Instruction {
3128                id: 1,
3129                opcode: Opcode::Const {
3130                    dest: Reg(1),
3131                    value: 3,
3132                },
3133                block_id: 0,
3134                is_dead: false,
3135            },
3136            Instruction {
3137                id: 2,
3138                opcode: Opcode::Add {
3139                    dest: Reg(2),
3140                    src1: Reg(0),
3141                    src2: Reg(1),
3142                },
3143                block_id: 0,
3144                is_dead: false,
3145            },
3146            Instruction {
3147                id: 3,
3148                opcode: Opcode::Mul {
3149                    dest: Reg(3),
3150                    src1: Reg(2),
3151                    src2: Reg(0),
3152                },
3153                block_id: 0,
3154                is_dead: false,
3155            },
3156        ];
3157
3158        let mut folder = ConstantFolding::new();
3159        let result = folder.run(&mut cfg, &mut instructions);
3160
3161        // First pass should fold r2 = 5
3162        assert!(result.changed);
3163        assert_eq!(result.modified_count, 2); // Both add and mul should fold
3164        assert_eq!(
3165            instructions[2].opcode,
3166            Opcode::Const {
3167                dest: Reg(2),
3168                value: 5
3169            }
3170        ); // 2 + 3
3171        assert_eq!(
3172            instructions[3].opcode,
3173            Opcode::Const {
3174                dest: Reg(3),
3175                value: 10
3176            }
3177        ); // 5 * 2
3178    }
3179
3180    #[test]
3181    fn test_constant_folding_no_change() {
3182        let mut builder = CfgBuilder::new();
3183        builder.add_instruction();
3184
3185        let mut cfg = builder.build();
3186
3187        // Create: r2 = r0 + r1 (no constants defined)
3188        let mut instructions = vec![Instruction {
3189            id: 0,
3190            opcode: Opcode::Add {
3191                dest: Reg(2),
3192                src1: Reg(0),
3193                src2: Reg(1),
3194            },
3195            block_id: 0,
3196            is_dead: false,
3197        }];
3198
3199        let mut folder = ConstantFolding::new();
3200        let result = folder.run(&mut cfg, &mut instructions);
3201
3202        // Should not change anything
3203        assert!(!result.changed);
3204        assert_eq!(result.modified_count, 0);
3205    }
3206
3207    #[test]
3208    fn test_cse_simple() {
3209        let mut builder = CfgBuilder::new();
3210        for _ in 0..3 {
3211            builder.add_instruction();
3212        }
3213
3214        let mut cfg = builder.build();
3215
3216        // Create: r2 = r0 + r1, r3 = r0 + r1 (duplicate)
3217        let mut instructions = vec![
3218            Instruction {
3219                id: 0,
3220                opcode: Opcode::Add {
3221                    dest: Reg(2),
3222                    src1: Reg(0),
3223                    src2: Reg(1),
3224                },
3225                block_id: 0,
3226                is_dead: false,
3227            },
3228            Instruction {
3229                id: 1,
3230                opcode: Opcode::Add {
3231                    dest: Reg(3),
3232                    src1: Reg(0),
3233                    src2: Reg(1),
3234                },
3235                block_id: 0,
3236                is_dead: false,
3237            },
3238        ];
3239
3240        let mut cse = CommonSubexpressionElimination::new();
3241        let result = cse.run(&mut cfg, &mut instructions);
3242
3243        // Second add should be eliminated
3244        assert!(result.changed);
3245        assert_eq!(result.removed_count, 1);
3246        assert!(instructions[1].is_dead);
3247        assert!(!instructions[0].is_dead);
3248    }
3249
3250    #[test]
3251    fn test_cse_multiple_ops() {
3252        let mut builder = CfgBuilder::new();
3253        for _ in 0..6 {
3254            builder.add_instruction();
3255        }
3256
3257        let mut cfg = builder.build();
3258
3259        // Create duplicates: r4 = r0 + r1, r5 = r0 + r1, r6 = r2 - r3, r7 = r2 - r3
3260        let mut instructions = vec![
3261            Instruction {
3262                id: 0,
3263                opcode: Opcode::Add {
3264                    dest: Reg(4),
3265                    src1: Reg(0),
3266                    src2: Reg(1),
3267                },
3268                block_id: 0,
3269                is_dead: false,
3270            },
3271            Instruction {
3272                id: 1,
3273                opcode: Opcode::Add {
3274                    dest: Reg(5),
3275                    src1: Reg(0),
3276                    src2: Reg(1),
3277                },
3278                block_id: 0,
3279                is_dead: false,
3280            },
3281            Instruction {
3282                id: 2,
3283                opcode: Opcode::Sub {
3284                    dest: Reg(6),
3285                    src1: Reg(2),
3286                    src2: Reg(3),
3287                },
3288                block_id: 0,
3289                is_dead: false,
3290            },
3291            Instruction {
3292                id: 3,
3293                opcode: Opcode::Sub {
3294                    dest: Reg(7),
3295                    src1: Reg(2),
3296                    src2: Reg(3),
3297                },
3298                block_id: 0,
3299                is_dead: false,
3300            },
3301        ];
3302
3303        let mut cse = CommonSubexpressionElimination::new();
3304        let result = cse.run(&mut cfg, &mut instructions);
3305
3306        // Both duplicates should be eliminated
3307        assert!(result.changed);
3308        assert_eq!(result.removed_count, 2);
3309        assert!(instructions[1].is_dead); // Duplicate add
3310        assert!(instructions[3].is_dead); // Duplicate sub
3311        assert!(!instructions[0].is_dead);
3312        assert!(!instructions[2].is_dead);
3313    }
3314
3315    #[test]
3316    fn test_cse_load() {
3317        let mut builder = CfgBuilder::new();
3318        for _ in 0..2 {
3319            builder.add_instruction();
3320        }
3321
3322        let mut cfg = builder.build();
3323
3324        // Create: r0 = load [0x100], r1 = load [0x100] (duplicate)
3325        let mut instructions = vec![
3326            Instruction {
3327                id: 0,
3328                opcode: Opcode::Load {
3329                    dest: Reg(0),
3330                    addr: 0x100,
3331                },
3332                block_id: 0,
3333                is_dead: false,
3334            },
3335            Instruction {
3336                id: 1,
3337                opcode: Opcode::Load {
3338                    dest: Reg(1),
3339                    addr: 0x100,
3340                },
3341                block_id: 0,
3342                is_dead: false,
3343            },
3344        ];
3345
3346        let mut cse = CommonSubexpressionElimination::new();
3347        let result = cse.run(&mut cfg, &mut instructions);
3348
3349        // Second load should be eliminated
3350        assert!(result.changed);
3351        assert_eq!(result.removed_count, 1);
3352        assert!(instructions[1].is_dead);
3353        assert!(!instructions[0].is_dead);
3354    }
3355
3356    #[test]
3357    fn test_cse_store_invalidates_load() {
3358        let mut builder = CfgBuilder::new();
3359        for _ in 0..3 {
3360            builder.add_instruction();
3361        }
3362
3363        let mut cfg = builder.build();
3364
3365        // Create: r0 = load [0x100], store r2 -> [0x100], r1 = load [0x100]
3366        let mut instructions = vec![
3367            Instruction {
3368                id: 0,
3369                opcode: Opcode::Load {
3370                    dest: Reg(0),
3371                    addr: 0x100,
3372                },
3373                block_id: 0,
3374                is_dead: false,
3375            },
3376            Instruction {
3377                id: 1,
3378                opcode: Opcode::Store {
3379                    src: Reg(2),
3380                    addr: 0x100,
3381                },
3382                block_id: 0,
3383                is_dead: false,
3384            },
3385            Instruction {
3386                id: 2,
3387                opcode: Opcode::Load {
3388                    dest: Reg(1),
3389                    addr: 0x100,
3390                },
3391                block_id: 0,
3392                is_dead: false,
3393            },
3394        ];
3395
3396        let mut cse = CommonSubexpressionElimination::new();
3397        let result = cse.run(&mut cfg, &mut instructions);
3398
3399        // Second load should NOT be eliminated (store invalidated it)
3400        assert!(!result.changed);
3401        assert_eq!(result.removed_count, 0);
3402        assert!(!instructions[0].is_dead);
3403        assert!(!instructions[1].is_dead);
3404        assert!(!instructions[2].is_dead);
3405    }
3406
3407    #[test]
3408    fn test_cse_no_duplicates() {
3409        let mut builder = CfgBuilder::new();
3410        for _ in 0..2 {
3411            builder.add_instruction();
3412        }
3413
3414        let mut cfg = builder.build();
3415
3416        // Create: r2 = r0 + r1, r3 = r0 - r1 (different operations)
3417        let mut instructions = vec![
3418            Instruction {
3419                id: 0,
3420                opcode: Opcode::Add {
3421                    dest: Reg(2),
3422                    src1: Reg(0),
3423                    src2: Reg(1),
3424                },
3425                block_id: 0,
3426                is_dead: false,
3427            },
3428            Instruction {
3429                id: 1,
3430                opcode: Opcode::Sub {
3431                    dest: Reg(3),
3432                    src1: Reg(0),
3433                    src2: Reg(1),
3434                },
3435                block_id: 0,
3436                is_dead: false,
3437            },
3438        ];
3439
3440        let mut cse = CommonSubexpressionElimination::new();
3441        let result = cse.run(&mut cfg, &mut instructions);
3442
3443        // Nothing should be eliminated
3444        assert!(!result.changed);
3445        assert_eq!(result.removed_count, 0);
3446    }
3447
3448    #[test]
3449    fn test_algebraic_add_zero() {
3450        let mut builder = CfgBuilder::new();
3451        for _ in 0..3 {
3452            builder.add_instruction();
3453        }
3454
3455        let mut cfg = builder.build();
3456
3457        // Create: r0 = 0, r2 = r1 + r0 (r1 + 0)
3458        let mut instructions = vec![
3459            Instruction {
3460                id: 0,
3461                opcode: Opcode::Const {
3462                    dest: Reg(0),
3463                    value: 0,
3464                },
3465                block_id: 0,
3466                is_dead: false,
3467            },
3468            Instruction {
3469                id: 1,
3470                opcode: Opcode::Add {
3471                    dest: Reg(2),
3472                    src1: Reg(1),
3473                    src2: Reg(0),
3474                },
3475                block_id: 0,
3476                is_dead: false,
3477            },
3478        ];
3479
3480        let mut simplify = AlgebraicSimplification::new();
3481        let result = simplify.run(&mut cfg, &mut instructions);
3482
3483        // r1 + 0 should be simplified (marked dead)
3484        assert!(result.changed);
3485        assert_eq!(result.modified_count, 1);
3486        assert!(instructions[1].is_dead);
3487    }
3488
3489    #[test]
3490    fn test_algebraic_sub_zero() {
3491        let mut builder = CfgBuilder::new();
3492        for _ in 0..2 {
3493            builder.add_instruction();
3494        }
3495
3496        let mut cfg = builder.build();
3497
3498        // Create: r0 = 0, r2 = r1 - r0 (r1 - 0)
3499        let mut instructions = vec![
3500            Instruction {
3501                id: 0,
3502                opcode: Opcode::Const {
3503                    dest: Reg(0),
3504                    value: 0,
3505                },
3506                block_id: 0,
3507                is_dead: false,
3508            },
3509            Instruction {
3510                id: 1,
3511                opcode: Opcode::Sub {
3512                    dest: Reg(2),
3513                    src1: Reg(1),
3514                    src2: Reg(0),
3515                },
3516                block_id: 0,
3517                is_dead: false,
3518            },
3519        ];
3520
3521        let mut simplify = AlgebraicSimplification::new();
3522        let result = simplify.run(&mut cfg, &mut instructions);
3523
3524        // r1 - 0 should be simplified
3525        assert!(result.changed);
3526        assert_eq!(result.modified_count, 1);
3527        assert!(instructions[1].is_dead);
3528    }
3529
3530    #[test]
3531    fn test_algebraic_sub_self() {
3532        let mut builder = CfgBuilder::new();
3533        builder.add_instruction();
3534
3535        let mut cfg = builder.build();
3536
3537        // Create: r2 = r1 - r1 (self subtraction)
3538        let mut instructions = vec![Instruction {
3539            id: 0,
3540            opcode: Opcode::Sub {
3541                dest: Reg(2),
3542                src1: Reg(1),
3543                src2: Reg(1),
3544            },
3545            block_id: 0,
3546            is_dead: false,
3547        }];
3548
3549        let mut simplify = AlgebraicSimplification::new();
3550        let result = simplify.run(&mut cfg, &mut instructions);
3551
3552        // r1 - r1 should become const 0
3553        assert!(result.changed);
3554        assert_eq!(result.modified_count, 1);
3555        assert_eq!(
3556            instructions[0].opcode,
3557            Opcode::Const {
3558                dest: Reg(2),
3559                value: 0
3560            }
3561        );
3562    }
3563
3564    #[test]
3565    fn test_algebraic_mul_zero() {
3566        let mut builder = CfgBuilder::new();
3567        for _ in 0..2 {
3568            builder.add_instruction();
3569        }
3570
3571        let mut cfg = builder.build();
3572
3573        // Create: r0 = 0, r2 = r1 * r0
3574        let mut instructions = vec![
3575            Instruction {
3576                id: 0,
3577                opcode: Opcode::Const {
3578                    dest: Reg(0),
3579                    value: 0,
3580                },
3581                block_id: 0,
3582                is_dead: false,
3583            },
3584            Instruction {
3585                id: 1,
3586                opcode: Opcode::Mul {
3587                    dest: Reg(2),
3588                    src1: Reg(1),
3589                    src2: Reg(0),
3590                },
3591                block_id: 0,
3592                is_dead: false,
3593            },
3594        ];
3595
3596        let mut simplify = AlgebraicSimplification::new();
3597        let result = simplify.run(&mut cfg, &mut instructions);
3598
3599        // r1 * 0 should become const 0
3600        assert!(result.changed);
3601        assert_eq!(result.modified_count, 1);
3602        assert_eq!(
3603            instructions[1].opcode,
3604            Opcode::Const {
3605                dest: Reg(2),
3606                value: 0
3607            }
3608        );
3609    }
3610
3611    #[test]
3612    fn test_algebraic_mul_one() {
3613        let mut builder = CfgBuilder::new();
3614        for _ in 0..2 {
3615            builder.add_instruction();
3616        }
3617
3618        let mut cfg = builder.build();
3619
3620        // Create: r0 = 1, r2 = r1 * r0
3621        let mut instructions = vec![
3622            Instruction {
3623                id: 0,
3624                opcode: Opcode::Const {
3625                    dest: Reg(0),
3626                    value: 1,
3627                },
3628                block_id: 0,
3629                is_dead: false,
3630            },
3631            Instruction {
3632                id: 1,
3633                opcode: Opcode::Mul {
3634                    dest: Reg(2),
3635                    src1: Reg(1),
3636                    src2: Reg(0),
3637                },
3638                block_id: 0,
3639                is_dead: false,
3640            },
3641        ];
3642
3643        let mut simplify = AlgebraicSimplification::new();
3644        let result = simplify.run(&mut cfg, &mut instructions);
3645
3646        // r1 * 1 should be simplified
3647        assert!(result.changed);
3648        assert_eq!(result.modified_count, 1);
3649        assert!(instructions[1].is_dead);
3650    }
3651
3652    #[test]
3653    fn test_algebraic_multiple() {
3654        let mut builder = CfgBuilder::new();
3655        for _ in 0..5 {
3656            builder.add_instruction();
3657        }
3658
3659        let mut cfg = builder.build();
3660
3661        // Create multiple simplifiable operations
3662        let mut instructions = vec![
3663            Instruction {
3664                id: 0,
3665                opcode: Opcode::Const {
3666                    dest: Reg(0),
3667                    value: 0,
3668                },
3669                block_id: 0,
3670                is_dead: false,
3671            },
3672            Instruction {
3673                id: 1,
3674                opcode: Opcode::Const {
3675                    dest: Reg(1),
3676                    value: 1,
3677                },
3678                block_id: 0,
3679                is_dead: false,
3680            },
3681            Instruction {
3682                id: 2,
3683                opcode: Opcode::Add {
3684                    dest: Reg(5),
3685                    src1: Reg(2),
3686                    src2: Reg(0),
3687                },
3688                block_id: 0,
3689                is_dead: false,
3690            },
3691            Instruction {
3692                id: 3,
3693                opcode: Opcode::Mul {
3694                    dest: Reg(6),
3695                    src1: Reg(3),
3696                    src2: Reg(1),
3697                },
3698                block_id: 0,
3699                is_dead: false,
3700            },
3701            Instruction {
3702                id: 4,
3703                opcode: Opcode::Sub {
3704                    dest: Reg(7),
3705                    src1: Reg(4),
3706                    src2: Reg(4),
3707                },
3708                block_id: 0,
3709                is_dead: false,
3710            },
3711        ];
3712
3713        let mut simplify = AlgebraicSimplification::new();
3714        let result = simplify.run(&mut cfg, &mut instructions);
3715
3716        // All three should be simplified
3717        assert!(result.changed);
3718        assert_eq!(result.modified_count, 3);
3719        assert!(instructions[2].is_dead); // r2 + 0
3720        assert!(instructions[3].is_dead); // r3 * 1
3721        assert_eq!(
3722            instructions[4].opcode,
3723            Opcode::Const {
3724                dest: Reg(7),
3725                value: 0
3726            }
3727        ); // r4 - r4
3728    }
3729
3730    #[test]
3731    fn test_peephole_redundant_const() {
3732        let mut builder = CfgBuilder::new();
3733        for _ in 0..2 {
3734            builder.add_instruction();
3735        }
3736
3737        let mut cfg = builder.build();
3738
3739        // Create: r0 = 5, r0 = 10 (second overwrites first)
3740        let mut instructions = vec![
3741            Instruction {
3742                id: 0,
3743                opcode: Opcode::Const {
3744                    dest: Reg(0),
3745                    value: 5,
3746                },
3747                block_id: 0,
3748                is_dead: false,
3749            },
3750            Instruction {
3751                id: 1,
3752                opcode: Opcode::Const {
3753                    dest: Reg(0),
3754                    value: 10,
3755                },
3756                block_id: 0,
3757                is_dead: false,
3758            },
3759        ];
3760
3761        let mut peephole = PeepholeOptimization::new();
3762        let result = peephole.run(&mut cfg, &mut instructions);
3763
3764        // First const should be eliminated
3765        assert!(result.changed);
3766        assert_eq!(result.removed_count, 1);
3767        assert!(instructions[0].is_dead);
3768        assert!(!instructions[1].is_dead);
3769    }
3770
3771    #[test]
3772    fn test_peephole_no_redundant_const() {
3773        let mut builder = CfgBuilder::new();
3774        for _ in 0..2 {
3775            builder.add_instruction();
3776        }
3777
3778        let mut cfg = builder.build();
3779
3780        // Create: r0 = 5, r1 = 10 (different registers)
3781        let mut instructions = vec![
3782            Instruction {
3783                id: 0,
3784                opcode: Opcode::Const {
3785                    dest: Reg(0),
3786                    value: 5,
3787                },
3788                block_id: 0,
3789                is_dead: false,
3790            },
3791            Instruction {
3792                id: 1,
3793                opcode: Opcode::Const {
3794                    dest: Reg(1),
3795                    value: 10,
3796                },
3797                block_id: 0,
3798                is_dead: false,
3799            },
3800        ];
3801
3802        let mut peephole = PeepholeOptimization::new();
3803        let result = peephole.run(&mut cfg, &mut instructions);
3804
3805        // Nothing should be eliminated
3806        assert!(!result.changed);
3807        assert_eq!(result.removed_count, 0);
3808    }
3809
3810    #[test]
3811    fn test_full_optimization_pipeline() {
3812        let mut builder = CfgBuilder::new();
3813        for _ in 0..10 {
3814            builder.add_instruction();
3815        }
3816
3817        let mut cfg = builder.build();
3818
3819        // Complex program with multiple optimization opportunities
3820        let mut instructions = vec![
3821            // Redundant const
3822            Instruction {
3823                id: 0,
3824                opcode: Opcode::Const {
3825                    dest: Reg(0),
3826                    value: 5,
3827                },
3828                block_id: 0,
3829                is_dead: false,
3830            },
3831            Instruction {
3832                id: 1,
3833                opcode: Opcode::Const {
3834                    dest: Reg(0),
3835                    value: 10,
3836                },
3837                block_id: 0,
3838                is_dead: false,
3839            },
3840            // Constant folding opportunity
3841            Instruction {
3842                id: 2,
3843                opcode: Opcode::Const {
3844                    dest: Reg(1),
3845                    value: 20,
3846                },
3847                block_id: 0,
3848                is_dead: false,
3849            },
3850            Instruction {
3851                id: 3,
3852                opcode: Opcode::Add {
3853                    dest: Reg(2),
3854                    src1: Reg(0),
3855                    src2: Reg(1),
3856                },
3857                block_id: 0,
3858                is_dead: false,
3859            },
3860            // Algebraic simplification
3861            Instruction {
3862                id: 4,
3863                opcode: Opcode::Const {
3864                    dest: Reg(3),
3865                    value: 0,
3866                },
3867                block_id: 0,
3868                is_dead: false,
3869            },
3870            Instruction {
3871                id: 5,
3872                opcode: Opcode::Add {
3873                    dest: Reg(4),
3874                    src1: Reg(2),
3875                    src2: Reg(3),
3876                },
3877                block_id: 0,
3878                is_dead: false,
3879            },
3880            // CSE opportunity
3881            Instruction {
3882                id: 6,
3883                opcode: Opcode::Add {
3884                    dest: Reg(5),
3885                    src1: Reg(0),
3886                    src2: Reg(1),
3887                },
3888                block_id: 0,
3889                is_dead: false,
3890            },
3891        ];
3892
3893        // Run full pipeline
3894        let mut manager = PassManager::new()
3895            .add_pass(PeepholeOptimization::new())
3896            .add_pass(ConstantFolding::new())
3897            .add_pass(AlgebraicSimplification::new())
3898            .add_pass(CommonSubexpressionElimination::new())
3899            .with_max_iterations(5);
3900
3901        let result = manager.run(&mut cfg, &mut instructions);
3902
3903        // Should have optimized multiple things
3904        assert!(result.changed);
3905
3906        // At least some optimizations should have been applied
3907        let total_opts = result.removed_count + result.modified_count;
3908        assert!(
3909            total_opts >= 2,
3910            "Expected at least 2 optimizations, got {}",
3911            total_opts
3912        );
3913
3914        // First const should be dead (peephole - redundant const)
3915        assert!(instructions[0].is_dead, "Redundant const not eliminated");
3916
3917        // Add r0+r1 should be folded to const 30 (constant folding)
3918        if let Opcode::Const { value, .. } = instructions[3].opcode {
3919            assert_eq!(value, 30, "Constant folding failed");
3920        } else {
3921            panic!("Expected const, got {:?}", instructions[3].opcode);
3922        }
3923    }
3924
3925    #[test]
3926    fn test_strength_reduction_mul_power_of_2() {
3927        let mut builder = CfgBuilder::new();
3928        for _ in 0..2 {
3929            builder.add_instruction();
3930        }
3931
3932        let mut cfg = builder.build();
3933
3934        // Create: r0 = 8, r2 = r1 * r0
3935        // 8 is 2^3, should be reduced to shift
3936        let mut instructions = vec![
3937            Instruction {
3938                id: 0,
3939                opcode: Opcode::Const {
3940                    dest: Reg(0),
3941                    value: 8,
3942                },
3943                block_id: 0,
3944                is_dead: false,
3945            },
3946            Instruction {
3947                id: 1,
3948                opcode: Opcode::Mul {
3949                    dest: Reg(2),
3950                    src1: Reg(1),
3951                    src2: Reg(0),
3952                },
3953                block_id: 0,
3954                is_dead: false,
3955            },
3956        ];
3957
3958        let mut sr = StrengthReduction::new();
3959        let result = sr.run(&mut cfg, &mut instructions);
3960
3961        // Should detect and reduce multiplication
3962        assert!(result.changed);
3963        assert_eq!(result.modified_count, 1);
3964    }
3965
3966    #[test]
3967    fn test_strength_reduction_mul_non_power_of_2() {
3968        let mut builder = CfgBuilder::new();
3969        for _ in 0..2 {
3970            builder.add_instruction();
3971        }
3972
3973        let mut cfg = builder.build();
3974
3975        // Create: r0 = 7, r2 = r1 * r0
3976        // 7 is not a power of 2, should not be reduced
3977        let mut instructions = vec![
3978            Instruction {
3979                id: 0,
3980                opcode: Opcode::Const {
3981                    dest: Reg(0),
3982                    value: 7,
3983                },
3984                block_id: 0,
3985                is_dead: false,
3986            },
3987            Instruction {
3988                id: 1,
3989                opcode: Opcode::Mul {
3990                    dest: Reg(2),
3991                    src1: Reg(1),
3992                    src2: Reg(0),
3993                },
3994                block_id: 0,
3995                is_dead: false,
3996            },
3997        ];
3998
3999        let mut sr = StrengthReduction::new();
4000        let result = sr.run(&mut cfg, &mut instructions);
4001
4002        // Should not reduce non-power-of-2
4003        assert!(!result.changed);
4004        assert_eq!(result.modified_count, 0);
4005    }
4006
4007    #[test]
4008    fn test_strength_reduction_multiple_powers() {
4009        let mut builder = CfgBuilder::new();
4010        for _ in 0..6 {
4011            builder.add_instruction();
4012        }
4013
4014        let mut cfg = builder.build();
4015
4016        // Multiple multiplications by powers of 2
4017        let mut instructions = vec![
4018            Instruction {
4019                id: 0,
4020                opcode: Opcode::Const {
4021                    dest: Reg(0),
4022                    value: 4,
4023                },
4024                block_id: 0,
4025                is_dead: false,
4026            },
4027            Instruction {
4028                id: 1,
4029                opcode: Opcode::Mul {
4030                    dest: Reg(2),
4031                    src1: Reg(1),
4032                    src2: Reg(0),
4033                },
4034                block_id: 0,
4035                is_dead: false,
4036            },
4037            Instruction {
4038                id: 2,
4039                opcode: Opcode::Const {
4040                    dest: Reg(3),
4041                    value: 16,
4042                },
4043                block_id: 0,
4044                is_dead: false,
4045            },
4046            Instruction {
4047                id: 3,
4048                opcode: Opcode::Mul {
4049                    dest: Reg(5),
4050                    src1: Reg(4),
4051                    src2: Reg(3),
4052                },
4053                block_id: 0,
4054                is_dead: false,
4055            },
4056            Instruction {
4057                id: 4,
4058                opcode: Opcode::Const {
4059                    dest: Reg(6),
4060                    value: 5,
4061                },
4062                block_id: 0,
4063                is_dead: false,
4064            },
4065            Instruction {
4066                id: 5,
4067                opcode: Opcode::Mul {
4068                    dest: Reg(8),
4069                    src1: Reg(7),
4070                    src2: Reg(6),
4071                },
4072                block_id: 0,
4073                is_dead: false,
4074            },
4075        ];
4076
4077        let mut sr = StrengthReduction::new();
4078        let result = sr.run(&mut cfg, &mut instructions);
4079
4080        // Should reduce 2 out of 3 multiplications (4 and 16 are powers of 2, 5 is not)
4081        assert!(result.changed);
4082        assert_eq!(result.modified_count, 2);
4083    }
4084
4085    #[test]
4086    fn test_licm_detect_invariants() {
4087        let mut builder = CfgBuilder::new();
4088        // Create a simple loop structure
4089        let block0 = 0; // Entry block (created by default)
4090        let block1 = builder.start_block();
4091        let block2 = builder.start_block();
4092
4093        // Connect blocks to form a loop
4094        builder.set_current_block(block0);
4095        builder.add_branch(block1);
4096
4097        builder.set_current_block(block1);
4098        builder.add_branch(block2);
4099
4100        builder.set_current_block(block2);
4101        builder.add_branch(block1); // Back edge - creates loop
4102
4103        builder.set_current_block(block1);
4104        builder.add_branch(block0); // Exit
4105
4106        let mut cfg = builder.build();
4107
4108        // Add a loop manually for testing
4109        cfg.loops.push(Loop {
4110            header: block1,
4111            body: vec![block1, block2].into_iter().collect(),
4112            depth: 1,
4113        });
4114
4115        // Create instructions
4116        let mut instructions = vec![
4117            // Loop-invariant: constant in loop
4118            Instruction {
4119                id: 0,
4120                opcode: Opcode::Const {
4121                    dest: Reg(0),
4122                    value: 10,
4123                },
4124                block_id: block1,
4125                is_dead: false,
4126            },
4127            // Loop-variant: arithmetic in loop
4128            Instruction {
4129                id: 1,
4130                opcode: Opcode::Add {
4131                    dest: Reg(2),
4132                    src1: Reg(1),
4133                    src2: Reg(0),
4134                },
4135                block_id: block1,
4136                is_dead: false,
4137            },
4138        ];
4139
4140        let mut licm = LoopInvariantCodeMotion::new();
4141        let result = licm.run(&mut cfg, &mut instructions);
4142
4143        // Should detect the constant as loop-invariant
4144        assert!(result.changed);
4145        assert_eq!(result.modified_count, 1);
4146    }
4147
4148    #[test]
4149    fn test_licm_no_loops() {
4150        let mut builder = CfgBuilder::new();
4151        builder.add_instruction();
4152
4153        let mut cfg = builder.build();
4154
4155        let mut instructions = vec![Instruction {
4156            id: 0,
4157            opcode: Opcode::Const {
4158                dest: Reg(0),
4159                value: 10,
4160            },
4161            block_id: 0,
4162            is_dead: false,
4163        }];
4164
4165        let mut licm = LoopInvariantCodeMotion::new();
4166        let result = licm.run(&mut cfg, &mut instructions);
4167
4168        // No loops, no invariants to move
4169        assert!(!result.changed);
4170        assert_eq!(result.modified_count, 0);
4171    }
4172
4173    #[test]
4174    fn test_pass_manager_with_advanced_passes() {
4175        let mut builder = CfgBuilder::new();
4176        for _ in 0..4 {
4177            builder.add_instruction();
4178        }
4179
4180        let mut cfg = builder.build();
4181
4182        // Program with strength reduction opportunity
4183        let mut instructions = vec![
4184            Instruction {
4185                id: 0,
4186                opcode: Opcode::Const {
4187                    dest: Reg(0),
4188                    value: 8,
4189                },
4190                block_id: 0,
4191                is_dead: false,
4192            },
4193            Instruction {
4194                id: 1,
4195                opcode: Opcode::Const {
4196                    dest: Reg(1),
4197                    value: 5,
4198                },
4199                block_id: 0,
4200                is_dead: false,
4201            },
4202            Instruction {
4203                id: 2,
4204                opcode: Opcode::Mul {
4205                    dest: Reg(2),
4206                    src1: Reg(1),
4207                    src2: Reg(0),
4208                },
4209                block_id: 0,
4210                is_dead: false,
4211            },
4212            Instruction {
4213                id: 3,
4214                opcode: Opcode::Add {
4215                    dest: Reg(3),
4216                    src1: Reg(0),
4217                    src2: Reg(1),
4218                },
4219                block_id: 0,
4220                is_dead: false,
4221            },
4222        ];
4223
4224        // Run with advanced passes
4225        let mut manager = PassManager::new()
4226            .add_pass(StrengthReduction::new())
4227            .add_pass(ConstantFolding::new())
4228            .with_max_iterations(3);
4229
4230        let result = manager.run(&mut cfg, &mut instructions);
4231
4232        // Should have optimized something
4233        assert!(result.changed);
4234
4235        // At least strength reduction should have run
4236        let total_opts = result.removed_count + result.modified_count;
4237        assert!(total_opts >= 1);
4238    }
4239
4240    #[test]
4241    fn test_copy_propagation_basic() {
4242        let mut builder = CfgBuilder::new();
4243        for _ in 0..2 {
4244            builder.add_instruction();
4245        }
4246
4247        let mut cfg = builder.build();
4248
4249        // Simple case with no actual copies in our IR
4250        // Copy propagation works with the copy_map which is currently empty
4251        let mut instructions = vec![
4252            Instruction {
4253                id: 0,
4254                opcode: Opcode::Const {
4255                    dest: Reg(0),
4256                    value: 10,
4257                },
4258                block_id: 0,
4259                is_dead: false,
4260            },
4261            Instruction {
4262                id: 1,
4263                opcode: Opcode::Add {
4264                    dest: Reg(2),
4265                    src1: Reg(0),
4266                    src2: Reg(1),
4267                },
4268                block_id: 0,
4269                is_dead: false,
4270            },
4271        ];
4272
4273        let mut cp = CopyPropagation::new();
4274        let result = cp.run(&mut cfg, &mut instructions);
4275
4276        // With empty copy map, no changes expected
4277        assert!(!result.changed);
4278    }
4279
4280    #[test]
4281    fn test_copy_propagation_with_store() {
4282        let mut builder = CfgBuilder::new();
4283        for _ in 0..2 {
4284            builder.add_instruction();
4285        }
4286
4287        let mut cfg = builder.build();
4288
4289        let mut instructions = vec![
4290            Instruction {
4291                id: 0,
4292                opcode: Opcode::Const {
4293                    dest: Reg(0),
4294                    value: 10,
4295                },
4296                block_id: 0,
4297                is_dead: false,
4298            },
4299            Instruction {
4300                id: 1,
4301                opcode: Opcode::Store {
4302                    src: Reg(0),
4303                    addr: 100,
4304                },
4305                block_id: 0,
4306                is_dead: false,
4307            },
4308        ];
4309
4310        let mut cp = CopyPropagation::new();
4311        let result = cp.run(&mut cfg, &mut instructions);
4312
4313        // With empty copy map, no propagation occurs
4314        assert!(!result.changed);
4315    }
4316
4317    #[test]
4318    fn test_instruction_combining_nested_add() {
4319        let mut builder = CfgBuilder::new();
4320        for _ in 0..4 {
4321            builder.add_instruction();
4322        }
4323
4324        let mut cfg = builder.build();
4325
4326        // Pattern: (x + c1) + c2
4327        let mut instructions = vec![
4328            Instruction {
4329                id: 0,
4330                opcode: Opcode::Const {
4331                    dest: Reg(1),
4332                    value: 5,
4333                },
4334                block_id: 0,
4335                is_dead: false,
4336            },
4337            Instruction {
4338                id: 1,
4339                opcode: Opcode::Add {
4340                    dest: Reg(2),
4341                    src1: Reg(0),
4342                    src2: Reg(1),
4343                },
4344                block_id: 0,
4345                is_dead: false,
4346            },
4347            Instruction {
4348                id: 2,
4349                opcode: Opcode::Const {
4350                    dest: Reg(3),
4351                    value: 10,
4352                },
4353                block_id: 0,
4354                is_dead: false,
4355            },
4356            Instruction {
4357                id: 3,
4358                opcode: Opcode::Add {
4359                    dest: Reg(4),
4360                    src1: Reg(2),
4361                    src2: Reg(3),
4362                },
4363                block_id: 0,
4364                is_dead: false,
4365            },
4366        ];
4367
4368        let mut ic = InstructionCombining::new();
4369        let result = ic.run(&mut cfg, &mut instructions);
4370
4371        // Should detect the (x + c1) + c2 pattern
4372        assert!(result.changed);
4373        assert_eq!(result.modified_count, 1);
4374    }
4375
4376    #[test]
4377    fn test_instruction_combining_no_pattern() {
4378        let mut builder = CfgBuilder::new();
4379        for _ in 0..2 {
4380            builder.add_instruction();
4381        }
4382
4383        let mut cfg = builder.build();
4384
4385        // No combining pattern
4386        let mut instructions = vec![
4387            Instruction {
4388                id: 0,
4389                opcode: Opcode::Const {
4390                    dest: Reg(0),
4391                    value: 10,
4392                },
4393                block_id: 0,
4394                is_dead: false,
4395            },
4396            Instruction {
4397                id: 1,
4398                opcode: Opcode::Const {
4399                    dest: Reg(1),
4400                    value: 20,
4401                },
4402                block_id: 0,
4403                is_dead: false,
4404            },
4405        ];
4406
4407        let mut ic = InstructionCombining::new();
4408        let result = ic.run(&mut cfg, &mut instructions);
4409
4410        // No pattern to combine
4411        assert!(!result.changed);
4412    }
4413
4414    #[test]
4415    fn test_all_passes_together() {
4416        let mut builder = CfgBuilder::new();
4417        for _ in 0..10 {
4418            builder.add_instruction();
4419        }
4420
4421        let mut cfg = builder.build();
4422
4423        // Complex program with multiple optimization opportunities
4424        let mut instructions = vec![
4425            Instruction {
4426                id: 0,
4427                opcode: Opcode::Const {
4428                    dest: Reg(0),
4429                    value: 8,
4430                },
4431                block_id: 0,
4432                is_dead: false,
4433            },
4434            Instruction {
4435                id: 1,
4436                opcode: Opcode::Const {
4437                    dest: Reg(1),
4438                    value: 5,
4439                },
4440                block_id: 0,
4441                is_dead: false,
4442            },
4443            Instruction {
4444                id: 2,
4445                opcode: Opcode::Mul {
4446                    dest: Reg(2),
4447                    src1: Reg(1),
4448                    src2: Reg(0),
4449                },
4450                block_id: 0,
4451                is_dead: false,
4452            },
4453            Instruction {
4454                id: 3,
4455                opcode: Opcode::Const {
4456                    dest: Reg(3),
4457                    value: 10,
4458                },
4459                block_id: 0,
4460                is_dead: false,
4461            },
4462            Instruction {
4463                id: 4,
4464                opcode: Opcode::Const {
4465                    dest: Reg(4),
4466                    value: 20,
4467                },
4468                block_id: 0,
4469                is_dead: false,
4470            },
4471            Instruction {
4472                id: 5,
4473                opcode: Opcode::Add {
4474                    dest: Reg(5),
4475                    src1: Reg(3),
4476                    src2: Reg(4),
4477                },
4478                block_id: 0,
4479                is_dead: false,
4480            },
4481            Instruction {
4482                id: 6,
4483                opcode: Opcode::Const {
4484                    dest: Reg(6),
4485                    value: 0,
4486                },
4487                block_id: 0,
4488                is_dead: false,
4489            },
4490            Instruction {
4491                id: 7,
4492                opcode: Opcode::Add {
4493                    dest: Reg(7),
4494                    src1: Reg(2),
4495                    src2: Reg(6),
4496                },
4497                block_id: 0,
4498                is_dead: false,
4499            },
4500        ];
4501
4502        // Run all passes
4503        let mut manager = PassManager::new()
4504            .add_pass(PeepholeOptimization::new())
4505            .add_pass(ConstantFolding::new())
4506            .add_pass(AlgebraicSimplification::new())
4507            .add_pass(StrengthReduction::new())
4508            .add_pass(CopyPropagation::new())
4509            .add_pass(InstructionCombining::new())
4510            .add_pass(CommonSubexpressionElimination::new())
4511            .add_pass(DeadCodeElimination::new())
4512            .with_max_iterations(5);
4513
4514        let result = manager.run(&mut cfg, &mut instructions);
4515
4516        // Should have optimized multiple things
4517        assert!(result.changed);
4518
4519        let total_opts = result.removed_count + result.modified_count + result.added_count;
4520        assert!(
4521            total_opts >= 3,
4522            "Expected at least 3 optimizations, got {}",
4523            total_opts
4524        );
4525    }
4526}