| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | |
| 5 | module optimize |
| 6 | |
| 7 | import os |
| 8 | import v2.ssa |
| 9 | import time |
| 10 | |
| 11 | pub struct OptimizeOptions { |
| 12 | pub: |
| 13 | verify_each_pass bool |
| 14 | strict_verify bool |
| 15 | } |
| 16 | |
| 17 | // Optimize Module |
| 18 | pub fn optimize(mut m ssa.Module) { |
| 19 | strict_verify := os.getenv('V2_VERIFY_STRICT') != '' |
| 20 | optimize_with_options(mut m, OptimizeOptions{ |
| 21 | verify_each_pass: os.getenv('V2_VERIFY') != '' |
| 22 | strict_verify: strict_verify |
| 23 | }) |
| 24 | } |
| 25 | |
| 26 | pub fn optimize_with_options(mut m ssa.Module, opts OptimizeOptions) { |
| 27 | mut t0 := time.ticks() |
| 28 | verify_pipeline_checkpoint(m, opts, 'input') |
| 29 | rebuild_use_lists(mut m) |
| 30 | |
| 31 | // 1. Build Control Flow Graph (Predecessors) |
| 32 | cfg := build_cfg(mut m) |
| 33 | verify_pipeline_checkpoint(m, opts, 'build_cfg') |
| 34 | mut t1 := time.ticks() |
| 35 | eprintln(' opt: build_cfg: ${t1 - t0}ms') |
| 36 | t0 = t1 |
| 37 | |
| 38 | // 2. Compute Dominator Tree (Lengauer-Tarjan) |
| 39 | dom := compute_dominators(mut m, &cfg) |
| 40 | verify_pipeline_checkpoint(m, opts, 'compute_dominators') |
| 41 | t1 = time.ticks() |
| 42 | // Count dom tree stats |
| 43 | mut n_with_idom := 0 |
| 44 | mut n_in_dom_tree := 0 |
| 45 | mut n_no_idom := 0 |
| 46 | mut n_idom_self := 0 |
| 47 | mut n_idom_diff := 0 |
| 48 | for bi in 0 .. dom.idom.len { |
| 49 | if dom.idom[bi] >= 0 { |
| 50 | n_with_idom += 1 |
| 51 | if dom.idom[bi] == bi { |
| 52 | n_idom_self += 1 |
| 53 | } else { |
| 54 | n_idom_diff += 1 |
| 55 | } |
| 56 | } else { |
| 57 | n_no_idom += 1 |
| 58 | } |
| 59 | if bi < dom.dom_tree.len { |
| 60 | n_in_dom_tree += dom.dom_tree[bi].len |
| 61 | } |
| 62 | } |
| 63 | eprintln(' opt: compute_dominators: ${t1 - t0}ms (with_idom=${n_with_idom} no_idom=${n_no_idom} self=${n_idom_self} diff=${n_idom_diff} dom_tree=${n_in_dom_tree})') |
| 64 | t0 = t1 |
| 65 | |
| 66 | // 3. Promote Memory to Register (Construct SSA / Phi Nodes) |
| 67 | promote_memory_to_register(mut m, dom, &cfg) |
| 68 | verify_pipeline_checkpoint(m, opts, 'promote_memory_to_register') |
| 69 | rebuild_use_lists(mut m) |
| 70 | t1 = time.ticks() |
| 71 | // Count remaining allocas |
| 72 | mut n_alloca := 0 |
| 73 | for vi in 0 .. m.values.len { |
| 74 | val := m.values[vi] |
| 75 | if val.kind == .instruction { |
| 76 | idx := val.index |
| 77 | if idx >= 0 && idx < m.instrs.len { |
| 78 | if m.instrs[idx].op == .alloca { |
| 79 | n_alloca += 1 |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | eprintln(' opt: mem2reg: ${t1 - t0}ms (remaining allocas: ${n_alloca})') |
| 85 | t0 = t1 |
| 86 | |
| 87 | // 4. Simplify trivial Phi Nodes |
| 88 | simplify_phi_nodes(mut m) |
| 89 | verify_pipeline_checkpoint(m, opts, 'simplify_phi_nodes') |
| 90 | rebuild_use_lists(mut m) |
| 91 | t1 = time.ticks() |
| 92 | eprintln(' opt: simplify_phi: ${t1 - t0}ms') |
| 93 | t0 = t1 |
| 94 | |
| 95 | // 5. Eliminate Phi Nodes (convert to copies in predecessor blocks) |
| 96 | eliminate_phi_nodes(mut m) |
| 97 | verify_pipeline_checkpoint(m, opts, 'eliminate_phi_nodes') |
| 98 | rebuild_use_lists(mut m) |
| 99 | t1 = time.ticks() |
| 100 | eprintln(' opt: eliminate_phi: ${t1 - t0}ms') |
| 101 | t0 = t1 |
| 102 | |
| 103 | // 6. Remove dead instructions after structural SSA rewrites are complete. |
| 104 | dead_code_elimination(mut m) |
| 105 | verify_pipeline_checkpoint(m, opts, 'dead_code_elimination') |
| 106 | t1 = time.ticks() |
| 107 | eprintln(' opt: dce: ${t1 - t0}ms') |
| 108 | } |
| 109 | |
| 110 | fn rebuild_use_lists(mut m ssa.Module) { |
| 111 | for vi in 0 .. m.values.len { |
| 112 | mut val := m.values[vi] |
| 113 | val.uses = [] |
| 114 | m.values[vi] = val |
| 115 | } |
| 116 | for fi in 0 .. m.funcs.len { |
| 117 | for blk_id in m.funcs[fi].blocks { |
| 118 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 119 | continue |
| 120 | } |
| 121 | for val_id in m.blocks[blk_id].instrs { |
| 122 | if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction { |
| 123 | continue |
| 124 | } |
| 125 | instr_idx := m.values[val_id].index |
| 126 | if instr_idx < 0 || instr_idx >= m.instrs.len { |
| 127 | continue |
| 128 | } |
| 129 | for op_id in m.instrs[instr_idx].operands { |
| 130 | if op_id >= 0 && op_id < m.values.len && val_id !in m.values[op_id].uses { |
| 131 | mut op_val := m.values[op_id] |
| 132 | op_val.uses << val_id |
| 133 | m.values[op_id] = op_val |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | fn verify_pipeline_checkpoint(m &ssa.Module, opts OptimizeOptions, pass_name string) { |
| 142 | if opts.verify_each_pass || opts.strict_verify { |
| 143 | verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{ |
| 144 | allow_noncritical: !opts.strict_verify |
| 145 | }) |
| 146 | } |
| 147 | } |
| 148 | |