| 1 | module optimize |
| 2 | |
| 3 | import os |
| 4 | import v3.ssa |
| 5 | |
| 6 | // OptimizeOptions controls optimize options behavior used by optimize. |
| 7 | pub struct OptimizeOptions { |
| 8 | pub: |
| 9 | mem2reg bool // promote scalar allocas to SSA values + phi nodes |
| 10 | eliminate_phis bool // lower phis to `assign` copies (for backends without phi) |
| 11 | verify_each_pass bool // run the structured verifier after every structural pass |
| 12 | strict_verify bool // treat historically-noncritical verifier findings as fatal |
| 13 | } |
| 14 | |
| 15 | // optimize runs the default, backend-safe optimization pipeline. Structural SSA |
| 16 | // construction (mem2reg / phi elimination) is opt-in via the environment so the |
| 17 | // proven arm64 lowering path is unchanged unless explicitly requested: |
| 18 | // V3_MEM2REG=1 enable alloca promotion + phi insertion |
| 19 | // V3_PHI_ELIM=1 additionally lower phis to assign copies |
| 20 | // V3_VERIFY=1 structured verify after each pass (V3_VERIFY_STRICT=1 = fatal) |
| 21 | pub fn optimize(mut m ssa.Module) { |
| 22 | mem2reg := os.getenv('V3_MEM2REG') != '' |
| 23 | optimize_with_options(mut m, OptimizeOptions{ |
| 24 | mem2reg: mem2reg |
| 25 | // The arm64 backend's native phi resolution is incomplete (no copies on |
| 26 | // conditional edges, no parallel-copy sequencing), so phis introduced by |
| 27 | // mem2reg must be lowered to assign copies for correct codegen. |
| 28 | eliminate_phis: mem2reg || os.getenv('V3_PHI_ELIM') != '' |
| 29 | verify_each_pass: os.getenv('V3_VERIFY') != '' |
| 30 | strict_verify: os.getenv('V3_VERIFY_STRICT') != '' |
| 31 | }) |
| 32 | } |
| 33 | |
| 34 | // optimize_with_options supports optimize with options handling for optimize. |
| 35 | pub fn optimize_with_options(mut m ssa.Module, opts OptimizeOptions) { |
| 36 | rebuild_use_lists(mut m) |
| 37 | build_cfg(mut m) |
| 38 | verify_ssa(m, 'initial normalization') |
| 39 | verify_pipeline_checkpoint(m, opts, 'input') |
| 40 | |
| 41 | constant_fold(mut m) |
| 42 | rebuild_use_lists(mut m) |
| 43 | |
| 44 | branch_fold(mut m) |
| 45 | rebuild_use_lists(mut m) |
| 46 | build_cfg(mut m) |
| 47 | // Branch folding can drop a phi block's predecessor edge; keep phis consistent. |
| 48 | prune_phi_operands(mut m) |
| 49 | rebuild_use_lists(mut m) |
| 50 | |
| 51 | if opts.mem2reg { |
| 52 | // Normalize the CFG *before* SSA construction so that every phi |
| 53 | // predecessor (a reachable CFG predecessor) stays present in the |
| 54 | // function afterwards. Block-removing passes must not run once phis |
| 55 | // exist, or their predecessor operands would dangle. |
| 56 | dead_code_elimination(mut m) |
| 57 | rebuild_use_lists(mut m) |
| 58 | build_cfg(mut m) |
| 59 | remove_unreachable_blocks(mut m) |
| 60 | merge_blocks(mut m) |
| 61 | rebuild_use_lists(mut m) |
| 62 | build_cfg(mut m) |
| 63 | |
| 64 | // Structural SSA construction: dominators -> mem2reg -> simplify phis. |
| 65 | cfg := cfg_data_from_module(m) |
| 66 | dom := compute_dominators(mut m, &cfg) |
| 67 | verify_pipeline_checkpoint(m, opts, 'compute_dominators') |
| 68 | promote_memory_to_register(mut m, dom, &cfg) |
| 69 | rebuild_use_lists(mut m) |
| 70 | verify_pipeline_checkpoint(m, opts, 'mem2reg') |
| 71 | simplify_phi_nodes(mut m) |
| 72 | rebuild_use_lists(mut m) |
| 73 | build_cfg(mut m) |
| 74 | verify_pipeline_checkpoint(m, opts, 'simplify_phi') |
| 75 | } |
| 76 | |
| 77 | // Phi elimination lowers phis to assign copies for backends that cannot |
| 78 | // resolve phis natively. Runs whenever requested (input phis from the builder |
| 79 | // or a worker merge may exist even without mem2reg). |
| 80 | if opts.eliminate_phis { |
| 81 | eliminate_phi_nodes(mut m) |
| 82 | rebuild_use_lists(mut m) |
| 83 | build_cfg(mut m) |
| 84 | verify_pipeline_checkpoint(m, opts, 'eliminate_phi') |
| 85 | } |
| 86 | |
| 87 | dead_code_elimination(mut m) |
| 88 | rebuild_use_lists(mut m) |
| 89 | build_cfg(mut m) |
| 90 | |
| 91 | remove_unreachable_blocks(mut m) |
| 92 | if !opts.mem2reg { |
| 93 | // Without SSA construction, block-merging is phi-aware and safe to run. |
| 94 | merge_blocks(mut m) |
| 95 | } |
| 96 | rebuild_use_lists(mut m) |
| 97 | build_cfg(mut m) |
| 98 | |
| 99 | // Final phi-consistency pass: any phi still present must match the final CFG. |
| 100 | prune_phi_operands(mut m) |
| 101 | rebuild_use_lists(mut m) |
| 102 | build_cfg(mut m) |
| 103 | |
| 104 | verify_ssa(m, 'optimization') |
| 105 | verify_pipeline_checkpoint(m, opts, 'final') |
| 106 | } |
| 107 | |
| 108 | // verify_pipeline_checkpoint validates verify pipeline checkpoint state for optimize. |
| 109 | fn verify_pipeline_checkpoint(m &ssa.Module, opts OptimizeOptions, pass_name string) { |
| 110 | if opts.verify_each_pass || opts.strict_verify { |
| 111 | verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{ |
| 112 | allow_noncritical: !opts.strict_verify |
| 113 | }) |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // rebuild_use_lists supports rebuild use lists handling for optimize. |
| 118 | fn rebuild_use_lists(mut m ssa.Module) { |
| 119 | for vi in 0 .. m.values.len { |
| 120 | mut val := m.values[vi] |
| 121 | val.uses = [] |
| 122 | m.values[vi] = val |
| 123 | } |
| 124 | for fi in 0 .. m.funcs.len { |
| 125 | for blk_id in m.funcs[fi].blocks { |
| 126 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 127 | continue |
| 128 | } |
| 129 | for val_id in m.blocks[blk_id].instrs { |
| 130 | if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction { |
| 131 | continue |
| 132 | } |
| 133 | instr_idx := m.values[val_id].index |
| 134 | if instr_idx < 0 || instr_idx >= m.instrs.len { |
| 135 | continue |
| 136 | } |
| 137 | instr := m.instrs[instr_idx] |
| 138 | |
| 139 | for op_id in instr.value_operands() { |
| 140 | if op_id >= 0 && op_id < m.values.len && val_id !in m.values[op_id].uses { |
| 141 | mut op_val := m.values[op_id] |
| 142 | op_val.uses << val_id |
| 143 | m.values[op_id] = op_val |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | |