v / vlib / v2 / ssa / optimize / blocks.v
165 lines · 149 sloc · 4.67 KB · 6c710c0b07df2d9824cda86d0ac59ffc2088e23d
Raw
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
5module optimize
6
7import v2.ssa
8
9fn 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
45fn 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