| 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 | // --- CFG Construction --- |
| 10 | // Flat CFG data: succs and preds stored outside BasicBlock to avoid struct field access issues |
| 11 | // in v3 (ARM64-compiled binary). Accessing m.blocks[i].succs returns garbage for large structs. |
| 12 | struct CfgData { |
| 13 | mut: |
| 14 | succs [][]int |
| 15 | preds [][]int |
| 16 | } |
| 17 | |
| 18 | // Helper: check if value is already in a small array (avoids map allocation) |
| 19 | fn arr_contains(arr []int, val int) bool { |
| 20 | for i in 0 .. arr.len { |
| 21 | if arr[i] == val { |
| 22 | return true |
| 23 | } |
| 24 | } |
| 25 | return false |
| 26 | } |
| 27 | |
| 28 | pub fn build_cfg(mut m ssa.Module) CfgData { |
| 29 | n_blocks := m.blocks.len |
| 30 | n_values := m.values.len |
| 31 | n_funcs := m.funcs.len |
| 32 | n_instrs_total := m.instrs.len |
| 33 | |
| 34 | mut cfg := CfgData{ |
| 35 | succs: [][]int{len: n_blocks} |
| 36 | preds: [][]int{len: n_blocks} |
| 37 | } |
| 38 | |
| 39 | for fi in 0 .. n_funcs { |
| 40 | func := m.funcs[fi] |
| 41 | n_func_blocks := func.blocks.len |
| 42 | |
| 43 | for fbi2 in 0 .. n_func_blocks { |
| 44 | blk_id := func.blocks[fbi2] |
| 45 | blk := m.blocks[blk_id] |
| 46 | n_instrs := blk.instrs.len |
| 47 | if n_instrs == 0 { |
| 48 | continue |
| 49 | } |
| 50 | term_val_id := blk.instrs[n_instrs - 1] |
| 51 | if term_val_id < 0 || term_val_id >= n_values { |
| 52 | continue |
| 53 | } |
| 54 | term_val := m.values[term_val_id] |
| 55 | if term_val.index < 0 || term_val.index >= n_instrs_total { |
| 56 | continue |
| 57 | } |
| 58 | term := m.instrs[term_val.index] |
| 59 | |
| 60 | n_operands := term.operands.len |
| 61 | match term.op { |
| 62 | .br { |
| 63 | if n_operands >= 3 { |
| 64 | op1 := term.operands[1] |
| 65 | op2 := term.operands[2] |
| 66 | mut s1 := -1 |
| 67 | if op1 >= 0 { |
| 68 | if op1 < n_values { |
| 69 | v1 := m.values[op1] |
| 70 | s1 = v1.index |
| 71 | } |
| 72 | } |
| 73 | mut s2 := -1 |
| 74 | if op2 >= 0 { |
| 75 | if op2 < n_values { |
| 76 | v2 := m.values[op2] |
| 77 | s2 = v2.index |
| 78 | } |
| 79 | } |
| 80 | if s1 >= 0 { |
| 81 | if s1 < n_blocks { |
| 82 | if !arr_contains(cfg.succs[blk_id], s1) { |
| 83 | cfg.succs[blk_id] << s1 |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | if s2 >= 0 { |
| 88 | if s2 < n_blocks { |
| 89 | if !arr_contains(cfg.succs[blk_id], s2) { |
| 90 | cfg.succs[blk_id] << s2 |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | .jmp { |
| 97 | if n_operands >= 1 { |
| 98 | op0 := term.operands[0] |
| 99 | mut s := -1 |
| 100 | if op0 >= 0 { |
| 101 | if op0 < n_values { |
| 102 | v0 := m.values[op0] |
| 103 | s = v0.index |
| 104 | } |
| 105 | } |
| 106 | if s >= 0 { |
| 107 | if s < n_blocks { |
| 108 | cfg.succs[blk_id] << s |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | .switch_ { |
| 114 | if n_operands >= 2 { |
| 115 | op_def := term.operands[1] |
| 116 | mut s := -1 |
| 117 | if op_def >= 0 { |
| 118 | if op_def < n_values { |
| 119 | vd := m.values[op_def] |
| 120 | s = vd.index |
| 121 | } |
| 122 | } |
| 123 | if s >= 0 { |
| 124 | if s < n_blocks { |
| 125 | if !arr_contains(cfg.succs[blk_id], s) { |
| 126 | cfg.succs[blk_id] << s |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | for i := 3; i < n_operands; i += 2 { |
| 131 | op_case := term.operands[i] |
| 132 | mut cs := -1 |
| 133 | if op_case >= 0 { |
| 134 | if op_case < n_values { |
| 135 | vc := m.values[op_case] |
| 136 | cs = vc.index |
| 137 | } |
| 138 | } |
| 139 | if cs >= 0 { |
| 140 | if cs < n_blocks { |
| 141 | if !arr_contains(cfg.succs[blk_id], cs) { |
| 142 | cfg.succs[blk_id] << cs |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | else {} |
| 150 | } |
| 151 | |
| 152 | // Build predecessors from succs |
| 153 | n_succs := cfg.succs[blk_id].len |
| 154 | for si in 0 .. n_succs { |
| 155 | s := cfg.succs[blk_id][si] |
| 156 | if s < 0 || s >= n_blocks { |
| 157 | continue |
| 158 | } |
| 159 | if !arr_contains(cfg.preds[s], blk_id) { |
| 160 | cfg.preds[s] << blk_id |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | // Also write back to m.blocks for compatibility with other passes |
| 167 | for bi in 0 .. n_blocks { |
| 168 | // Avoid m.blocks[X].succs/preds = ... -- chained field assign broken in ARM64 self-hosted |
| 169 | mut blk := m.blocks[bi] |
| 170 | blk.succs = cfg.succs[bi] |
| 171 | blk.preds = cfg.preds[bi] |
| 172 | m.blocks[bi] = blk |
| 173 | } |
| 174 | |
| 175 | return cfg |
| 176 | } |
| 177 | |