v / vlib / v2 / ssa / optimize / dominators.v
374 lines · 345 sloc · 8.29 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
9// --- Dominators (Lengauer-Tarjan) ---
10
11struct LTContext {
12mut:
13 parent []int // DFS tree parent
14 semi []int // Semidominator (BlockID)
15 vertex []int // Map DFS number -> BlockID
16 bucket [][]int // bucket[w] = set of vertices v s.t. semi[v] = w
17 dfnum []int // DFS number (0 means unvisited)
18 ancestor []int // DSU parent
19 label []int // DSU label (min semi in path)
20 idom []int // Immediate dominator (flat array, indexed by block ID)
21 dom_tree [][]int // Dom tree children (flat array, indexed by block ID)
22 n int // Counter
23}
24
25// DFS frame for iterative lt_dfs
26struct DfsFrame {
27mut:
28 node int
29 succ_idx int // next successor to process
30}
31
32// DomInfo holds dominator results in flat arrays to avoid struct field access issues
33struct DomInfo {
34mut:
35 idom []int // Immediate dominator, indexed by block ID
36 dom_tree [][]int // Dom tree children, indexed by block ID
37}
38
39fn compute_dominators(mut m ssa.Module, cfg &CfgData) DomInfo {
40 max_id := m.blocks.len
41
42 mut ctx := LTContext{
43 parent: []int{len: max_id, init: -1}
44 semi: []int{len: max_id, init: -1}
45 vertex: []int{len: max_id + 1, init: -1}
46 bucket: [][]int{len: max_id}
47 dfnum: []int{len: max_id, init: 0}
48 ancestor: []int{len: max_id, init: -1}
49 label: []int{len: max_id, init: -1}
50 idom: []int{len: max_id, init: -1}
51 dom_tree: [][]int{len: max_id}
52 n: 0
53 }
54
55 for fi in 0 .. m.funcs.len {
56 func := m.funcs[fi]
57 if func.blocks.len == 0 {
58 continue
59 }
60
61 // Validate that all block IDs and their successor/predecessor
62 // references are within bounds.
63 mut valid := true
64 n_func_blocks := func.blocks.len
65 for fbi in 0 .. n_func_blocks {
66 blk_id := func.blocks[fbi]
67 if blk_id < 0 || blk_id >= max_id {
68 valid = false
69 break
70 }
71 n_succs := cfg.succs[blk_id].len
72 for si in 0 .. n_succs {
73 s := cfg.succs[blk_id][si]
74 if s < 0 || s >= max_id {
75 valid = false
76 break
77 }
78 }
79 if !valid {
80 break
81 }
82 n_preds := cfg.preds[blk_id].len
83 for pi in 0 .. n_preds {
84 p := cfg.preds[blk_id][pi]
85 if p < 0 || p >= max_id {
86 valid = false
87 break
88 }
89 }
90 if !valid {
91 break
92 }
93 }
94 if !valid {
95 continue
96 }
97
98 // Reset context for this function (only reset what's needed)
99 ctx.n = 0
100 for fbi2 in 0 .. n_func_blocks {
101 blk_id := func.blocks[fbi2]
102 ctx.parent[blk_id] = -1
103 ctx.semi[blk_id] = blk_id
104 ctx.vertex[blk_id] = -1
105 ctx.bucket[blk_id].clear()
106 ctx.dfnum[blk_id] = 0
107 ctx.ancestor[blk_id] = -1
108 ctx.label[blk_id] = blk_id
109 ctx.idom[blk_id] = -1
110 ctx.dom_tree[blk_id] = []
111 }
112
113 entry := func.blocks[0]
114 lt_dfs(entry, cfg, mut ctx)
115
116 // Process in reverse DFS order (skip root)
117 for i := ctx.n; i >= 2; i-- {
118 if i >= ctx.vertex.len {
119 continue
120 }
121 w := ctx.vertex[i]
122 if w < 0 || w >= max_id {
123 continue
124 }
125
126 // 1. Calculate Semidominator - use cfg.preds instead of m.blocks[w].preds
127 n_w_preds := cfg.preds[w].len
128 for pi2 in 0 .. n_w_preds {
129 p := cfg.preds[w][pi2]
130 if p < 0 || p >= max_id || ctx.dfnum[p] == 0 {
131 continue
132 }
133
134 u := ctx.eval(p)
135 if u < 0 || u >= max_id {
136 continue
137 }
138 semi_u := ctx.semi[u]
139 semi_w := ctx.semi[w]
140 if semi_u < 0 || semi_u >= max_id || semi_w < 0 || semi_w >= max_id {
141 continue
142 }
143 if ctx.dfnum[semi_u] < ctx.dfnum[semi_w] {
144 ctx.semi[w] = ctx.semi[u]
145 }
146 }
147
148 // Add w to bucket of its semidominator
149 semi_w2 := ctx.semi[w]
150 if semi_w2 >= 0 && semi_w2 < max_id {
151 ctx.bucket[semi_w2] << w
152 }
153
154 // Link to parent in forest
155 ctx.link(ctx.parent[w], w)
156
157 // 2. Implicitly compute IDom
158 parent_w := ctx.parent[w]
159 if parent_w < 0 || parent_w >= max_id {
160 continue
161 }
162 // Drain bucket of parent
163 n_bucket := ctx.bucket[parent_w].len
164 for bvi in 0 .. n_bucket {
165 v := ctx.bucket[parent_w][bvi]
166 if v < 0 || v >= max_id {
167 continue
168 }
169 u := ctx.eval(v)
170 if u < 0 || u >= max_id || v >= max_id {
171 continue
172 }
173 if ctx.semi[u] == ctx.semi[v] {
174 ctx.idom[v] = parent_w
175 } else {
176 ctx.idom[v] = u // Deferred: idom[v] = idom[u]
177 }
178 }
179 ctx.bucket[parent_w].clear()
180 }
181
182 // 3. Explicitly compute IDom
183 for i := 2; i <= ctx.n; i++ {
184 if i >= ctx.vertex.len {
185 continue
186 }
187 w := ctx.vertex[i]
188 if w < 0 || w >= max_id {
189 continue
190 }
191 semi_w := ctx.semi[w]
192 if semi_w < 0 || semi_w >= max_id {
193 continue
194 }
195 dfnum_semi_w := ctx.dfnum[semi_w]
196 if dfnum_semi_w < 0 || dfnum_semi_w >= ctx.vertex.len {
197 continue
198 }
199 target := ctx.vertex[dfnum_semi_w]
200 if target < 0 {
201 continue
202 }
203 if ctx.idom[w] != target {
204 idom_w := ctx.idom[w]
205 if idom_w >= 0 && idom_w < max_id {
206 next_idom := ctx.idom[idom_w]
207 if next_idom >= 0 {
208 ctx.idom[w] = next_idom
209 }
210 }
211 }
212 }
213
214 ctx.idom[entry] = entry
215
216 // Build Dom Tree Children
217 for fbi4 in 0 .. n_func_blocks {
218 blk_id := func.blocks[fbi4]
219 idom := ctx.idom[blk_id]
220 if idom != -1 && idom != blk_id {
221 if idom >= 0 && idom < max_id {
222 ctx.dom_tree[idom] << blk_id
223 }
224 }
225 }
226 }
227
228 return DomInfo{
229 idom: ctx.idom
230 dom_tree: ctx.dom_tree
231 }
232}
233
234fn lt_dfs(root int, cfg &CfgData, mut ctx LTContext) {
235 if root < 0 || root >= ctx.dfnum.len {
236 return
237 }
238 n_total_blocks := cfg.succs.len
239
240 mut stack := []DfsFrame{}
241 // Visit root
242 ctx.n++
243 if ctx.n >= ctx.vertex.len {
244 return
245 }
246 ctx.dfnum[root] = ctx.n
247 ctx.vertex[ctx.n] = root
248
249 stack << DfsFrame{
250 node: root
251 }
252
253 for stack.len > 0 {
254 top := stack.len - 1
255 node := stack[top].node
256 if node < 0 || node >= n_total_blocks {
257 stack.pop()
258 continue
259 }
260 // Use cfg.succs flat array instead of m.blocks[node].succs
261 n_succs := cfg.succs[node].len
262 si := stack[top].succ_idx
263 if si < n_succs {
264 // Avoid stack[top].succ_idx++ -- chained field increment broken in ARM64 self-hosted
265 mut frame := stack[top]
266 frame.succ_idx++
267 stack[top] = frame
268 w := cfg.succs[node][si]
269 if w >= 0 && w < ctx.dfnum.len && ctx.dfnum[w] == 0 {
270 ctx.parent[w] = node
271 ctx.n++
272 if ctx.n >= ctx.vertex.len {
273 return
274 }
275 ctx.dfnum[w] = ctx.n
276 ctx.vertex[ctx.n] = w
277 stack << DfsFrame{
278 node: w
279 }
280 }
281 } else {
282 stack.pop()
283 }
284 }
285}
286
287fn (mut ctx LTContext) compress(v int) {
288 if v < 0 || v >= ctx.ancestor.len {
289 return
290 }
291 mut chain := []int{}
292 mut cur := v
293 for cur >= 0 && cur < ctx.ancestor.len {
294 av := ctx.ancestor[cur]
295 if av < 0 || av >= ctx.ancestor.len {
296 break
297 }
298 if ctx.ancestor[av] == -1 {
299 break
300 }
301 chain << cur
302 cur = av
303 }
304 for ci := chain.len - 1; ci >= 0; ci-- {
305 node := chain[ci]
306 if node < 0 || node >= ctx.ancestor.len {
307 continue
308 }
309 anc := ctx.ancestor[node]
310 if anc < 0 || anc >= ctx.label.len || node >= ctx.label.len {
311 continue
312 }
313 label_anc := ctx.label[anc]
314 label_node := ctx.label[node]
315 if label_anc < 0 || label_anc >= ctx.semi.len || label_node < 0
316 || label_node >= ctx.semi.len {
317 continue
318 }
319 semi_label_anc := ctx.semi[label_anc]
320 semi_label_node := ctx.semi[label_node]
321 if semi_label_anc < 0 || semi_label_anc >= ctx.dfnum.len || semi_label_node < 0
322 || semi_label_node >= ctx.dfnum.len {
323 continue
324 }
325 if ctx.dfnum[semi_label_anc] < ctx.dfnum[semi_label_node] {
326 ctx.label[node] = ctx.label[anc]
327 }
328 aav := ctx.ancestor[anc]
329 if aav >= 0 {
330 ctx.ancestor[node] = aav
331 }
332 }
333}
334
335fn (mut ctx LTContext) eval(v int) int {
336 if v < 0 || v >= ctx.ancestor.len {
337 if v >= 0 && v < ctx.label.len {
338 return ctx.label[v]
339 }
340 return 0
341 }
342 if ctx.ancestor[v] == -1 {
343 return ctx.label[v]
344 }
345 ctx.compress(v)
346 av := ctx.ancestor[v]
347 if av < 0 || av >= ctx.label.len {
348 if v < ctx.label.len {
349 return ctx.label[v]
350 }
351 return 0
352 }
353 if v >= ctx.label.len {
354 return 0
355 }
356 label_av := ctx.label[av]
357 label_v := ctx.label[v]
358 if label_av < 0 || label_av >= ctx.semi.len || label_v < 0 || label_v >= ctx.semi.len {
359 return label_v
360 }
361 semi_lav := ctx.semi[label_av]
362 semi_lv := ctx.semi[label_v]
363 if semi_lav < 0 || semi_lav >= ctx.dfnum.len || semi_lv < 0 || semi_lv >= ctx.dfnum.len {
364 return label_v
365 }
366 if ctx.dfnum[semi_lav] >= ctx.dfnum[semi_lv] {
367 return label_v
368 }
369 return label_av
370}
371
372fn (mut ctx LTContext) link(v int, w int) {
373 ctx.ancestor[w] = v
374}
375