| 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 v2.ssa |
| 8 | |
| 9 | fn remove_unreachable_blocks(mut m ssa.Module) { |
| 10 | // Re-build CFG first |
| 11 | build_cfg(mut m) |
| 12 | for fi in 0 .. m.funcs.len { |
| 13 | if m.funcs[fi].blocks.len == 0 { |
| 14 | continue |
| 15 | } |
| 16 | // BFS/DFS from entry |
| 17 | mut reachable := map[int]bool{} |
| 18 | mut q := [m.funcs[fi].blocks[0]] |
| 19 | reachable[m.funcs[fi].blocks[0]] = true |
| 20 | |
| 21 | for q.len > 0 { |
| 22 | curr := q[q.len - 1] |
| 23 | q.delete_last() |
| 24 | for succ in m.blocks[curr].succs { |
| 25 | if !reachable[succ] { |
| 26 | reachable[succ] = true |
| 27 | q << succ |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | mut new_blocks := []int{} |
| 33 | for blk in m.funcs[fi].blocks { |
| 34 | if reachable[blk] { |
| 35 | new_blocks << blk |
| 36 | } |
| 37 | } |
| 38 | // Avoid m.funcs[fi].blocks = ... -- chained field assign broken in ARM64 self-hosted |
| 39 | mut func := m.funcs[fi] |
| 40 | func.blocks = new_blocks |
| 41 | m.funcs[fi] = func |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | fn merge_blocks(mut m ssa.Module) { |
| 46 | // If Block A jumps unconditionally to B, and B has only A as predecessor: |
| 47 | // 1. Move instructions from B to A |
| 48 | // 2. Update A's successors to B's successors |
| 49 | // 3. Remove B |
| 50 | |
| 51 | // We need to be careful about iteration while modifying. |
| 52 | // Loop until no changes. |
| 53 | mut changed := true |
| 54 | mut first_iter := true |
| 55 | for changed { |
| 56 | changed = false |
| 57 | // Only rebuild CFG on first iteration or after actual changes |
| 58 | if first_iter { |
| 59 | build_cfg(mut m) |
| 60 | first_iter = false |
| 61 | } |
| 62 | |
| 63 | for fi in 0 .. m.funcs.len { |
| 64 | // We iterate through blocks. |
| 65 | // If we merge A->B, we can't merge B->C in same pass easily. |
| 66 | mut merged := map[int]bool{} |
| 67 | |
| 68 | for blk_id in m.funcs[fi].blocks { |
| 69 | if merged[blk_id] { |
| 70 | continue |
| 71 | } |
| 72 | |
| 73 | // Check if unconditional jump |
| 74 | if m.blocks[blk_id].instrs.len > 0 { |
| 75 | last_val := m.blocks[blk_id].instrs[m.blocks[blk_id].instrs.len - 1] |
| 76 | last_instr := m.instrs[m.values[last_val].index] |
| 77 | |
| 78 | if last_instr.op == .jmp { |
| 79 | target_val := last_instr.operands[0] |
| 80 | target_id := m.get_block_from_val(target_val) |
| 81 | |
| 82 | // Check if target has phi nodes - don't merge if it does |
| 83 | mut has_phi := false |
| 84 | for vid in m.blocks[target_id].instrs { |
| 85 | if m.values[vid].kind == .instruction { |
| 86 | if m.instrs[m.values[vid].index].op == .phi { |
| 87 | has_phi = true |
| 88 | break |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // Candidate: target_id (only if no phi nodes) |
| 94 | if target_id != blk_id && m.blocks[target_id].preds.len == 1 |
| 95 | && m.blocks[target_id].preds[0] == blk_id && !has_phi { |
| 96 | // MERGE: Remove JMP from A, then append B's instrs to A |
| 97 | // Read whole struct, modify, write back (chained broken in ARM64) |
| 98 | mut merge_blk := m.blocks[blk_id] |
| 99 | merge_blk.instrs.delete_last() |
| 100 | merge_blk.instrs << m.blocks[target_id].instrs |
| 101 | m.blocks[blk_id] = merge_blk |
| 102 | |
| 103 | // Update instructions in B to point to A (for their 'block' field)? |
| 104 | // Not strictly needed if we just use the list. |
| 105 | // But we need to update Phis in successors of B? |
| 106 | // If B has successors, their Phis might refer to B. |
| 107 | // Since B is gone, they now refer to A. |
| 108 | for succ_id in m.blocks[target_id].succs { |
| 109 | n_succ_instrs := m.blocks[succ_id].instrs.len |
| 110 | for ivi in 0 .. n_succ_instrs { |
| 111 | iv := m.blocks[succ_id].instrs[ivi] |
| 112 | v := m.values[iv] |
| 113 | if v.kind != .instruction { |
| 114 | continue |
| 115 | } |
| 116 | ins := m.instrs[v.index] |
| 117 | if ins.op == .phi { |
| 118 | // Replace all occurrences (defensive - handles edge cases) |
| 119 | // i=1,3,5... are block references in phi [val0, blk0, val1, blk1, ...] |
| 120 | // Avoid m.instrs[X].operands[i] = ... -- chained broken in ARM64 self-hosted |
| 121 | mut phi_ops := ins.operands.clone() |
| 122 | mut phi_modified := false |
| 123 | for i := 1; i < phi_ops.len; i += 2 { |
| 124 | if phi_ops[i] == m.blocks[target_id].val_id { |
| 125 | phi_ops[i] = m.blocks[blk_id].val_id |
| 126 | phi_modified = true |
| 127 | } |
| 128 | } |
| 129 | if phi_modified { |
| 130 | mut phi_ins := m.instrs[v.index] |
| 131 | phi_ins.operands = phi_ops |
| 132 | m.instrs[v.index] = phi_ins |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // Remove B from func |
| 139 | merged[target_id] = true |
| 140 | changed = true |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // Filter out merged blocks |
| 147 | if merged.len > 0 { |
| 148 | mut new_blks := []int{} |
| 149 | for b in m.funcs[fi].blocks { |
| 150 | if !merged[b] { |
| 151 | new_blks << b |
| 152 | } |
| 153 | } |
| 154 | // Avoid m.funcs[fi].blocks = ... -- chained field assign broken in ARM64 self-hosted |
| 155 | mut func2 := m.funcs[fi] |
| 156 | func2.blocks = new_blks |
| 157 | m.funcs[fi] = func2 |
| 158 | } |
| 159 | } |
| 160 | // Rebuild CFG for next iteration if we made changes |
| 161 | if changed { |
| 162 | build_cfg(mut m) |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | |