v4 / vlib / v3 / ssa / optimize / phi.v
448 lines · 421 sloc · 11.3 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
1module optimize
2
3import v3.ssa
4
5// --- Simplify trivial phi nodes ---
6// A phi is trivial if all its non-self operands resolve to a single value, or if
7// it has no uses at all. Trivial phis are replaced and marked dead (nop bitcast).
8fn simplify_phi_nodes(mut m ssa.Module) bool {
9 mut any_changed := false
10 mut changed := true
11 for changed {
12 changed = false
13 for fi in 0 .. m.funcs.len {
14 func := m.funcs[fi]
15 for blk_id in func.blocks {
16 blk := m.blocks[blk_id]
17 for val_id in blk.instrs {
18 val := m.values[val_id]
19 if val.kind != .instruction {
20 continue
21 }
22 instr := m.instrs[val.index]
23 if instr.op != .phi {
24 continue
25 }
26
27 // Dead phi: no uses -> remove.
28 if val.uses.len == 0 {
29 for i := 0; i < instr.operands.len; i += 2 {
30 op_val := instr.operands[i]
31 if op_val != val_id {
32 remove_use(mut m, op_val, val_id)
33 }
34 }
35 mut dead_phi := m.instrs[val.index]
36 dead_phi.op = .bitcast
37 dead_phi.operands = []
38 m.instrs[val.index] = dead_phi
39 changed = true
40 any_changed = true
41 continue
42 }
43
44 // Trivial phi: all non-self value operands identical.
45 mut unique_val := -1
46 mut is_trivial := true
47 for i := 0; i < instr.operands.len; i += 2 {
48 op_val := instr.operands[i]
49 if op_val == val_id {
50 continue
51 }
52 if unique_val == -1 {
53 unique_val = op_val
54 } else if unique_val != op_val {
55 is_trivial = false
56 break
57 }
58 }
59 if is_trivial && unique_val != -1 {
60 m.replace_uses(val_id, unique_val)
61 mut triv_phi := m.instrs[val.index]
62 triv_phi.op = .bitcast
63 triv_phi.operands = []
64 m.instrs[val.index] = triv_phi
65 changed = true
66 any_changed = true
67 }
68 }
69 }
70 }
71 }
72 return any_changed
73}
74
75// prune_phi_operands drops phi operand pairs whose predecessor block is no longer
76// an actual predecessor (e.g. after branch folding / unreachable-block removal),
77// keeping phis consistent with the current CFG. A phi reduced to a single distinct
78// incoming value is replaced by that value. Requires up-to-date preds + uses.
79fn prune_phi_operands(mut m ssa.Module) {
80 for fi in 0 .. m.funcs.len {
81 for blk_id in m.funcs[fi].blocks {
82 if blk_id < 0 || blk_id >= m.blocks.len {
83 continue
84 }
85 preds := m.blocks[blk_id].preds
86 for val_id in m.blocks[blk_id].instrs {
87 if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction {
88 continue
89 }
90 idx := m.values[val_id].index
91 if m.instrs[idx].op != .phi {
92 continue
93 }
94 ops := m.instrs[idx].operands
95 mut kept := []ssa.ValueID{}
96 mut distinct := []ssa.ValueID{}
97 for i := 0; i + 1 < ops.len; i += 2 {
98 pred := int(ops[i + 1])
99 if pred in preds {
100 kept << ops[i]
101 kept << ops[i + 1]
102 if ops[i] !in distinct {
103 distinct << ops[i]
104 }
105 }
106 }
107 if kept.len == ops.len && distinct.len > 1 {
108 continue // unchanged, still a real phi
109 }
110 if distinct.len == 1 {
111 // Trivial phi -> its single incoming value.
112 m.replace_uses(val_id, distinct[0])
113 mut nop := m.instrs[idx]
114 nop.op = .bitcast
115 nop.operands = []
116 m.instrs[idx] = nop
117 } else if distinct.len == 0 {
118 mut nop := m.instrs[idx]
119 nop.op = .bitcast
120 nop.operands = []
121 m.instrs[idx] = nop
122 } else {
123 mut pruned := m.instrs[idx]
124 pruned.operands = kept
125 m.instrs[idx] = pruned
126 }
127 }
128 }
129 }
130}
131
132// --- Critical edge splitting ---
133// Inserts an empty block on every edge whose source has multiple successors and
134// whose target has multiple predecessors, so phi-elimination copies have a unique
135// home. Operands here are raw block ids (v3 convention).
136fn split_critical_edges(mut m ssa.Module) {
137 build_cfg(mut m)
138 for fi in 0 .. m.funcs.len {
139 mut edges_to_split := [][]int{}
140 func := m.funcs[fi]
141 for blk_id in func.blocks {
142 if blk_id < 0 || blk_id >= m.blocks.len {
143 continue
144 }
145 if m.blocks[blk_id].succs.len > 1 {
146 for succ_id in m.blocks[blk_id].succs {
147 if succ_id < 0 || succ_id >= m.blocks.len {
148 continue
149 }
150 if m.blocks[succ_id].preds.len > 1 {
151 edges_to_split << [int(blk_id), int(succ_id)]
152 }
153 }
154 }
155 }
156
157 for edge in edges_to_split {
158 pred_id := edge[0]
159 succ_id := edge[1]
160 if pred_id < 0 || pred_id >= m.blocks.len || succ_id < 0 || succ_id >= m.blocks.len {
161 continue
162 }
163 split_blk := m.add_block(fi, 'split_${pred_id}_${succ_id}')
164 m.add_instr(.jmp, split_blk, 0, [ssa.ValueID(succ_id)])
165
166 // Re-point predecessor terminator from succ to the split block.
167 pred_blk := m.blocks[pred_id]
168 if pred_blk.instrs.len > 0 {
169 term_val_id := pred_blk.instrs[pred_blk.instrs.len - 1]
170 if term_val_id >= 0 && term_val_id < m.values.len {
171 idx := m.values[term_val_id].index
172 mut term := m.instrs[idx]
173 for i in 0 .. term.operands.len {
174 if int(term.operands[i]) == succ_id {
175 term.operands[i] = ssa.ValueID(split_blk)
176 }
177 }
178 m.instrs[idx] = term
179 }
180 }
181
182 // Re-point phi predecessors in succ from pred to the split block.
183 for vid in m.blocks[succ_id].instrs {
184 if vid < 0 || vid >= m.values.len || m.values[vid].kind != .instruction {
185 continue
186 }
187 pidx := m.values[vid].index
188 if m.instrs[pidx].op != .phi {
189 continue
190 }
191 mut phi := m.instrs[pidx]
192 for i := 1; i < phi.operands.len; i += 2 {
193 if int(phi.operands[i]) == pred_id {
194 phi.operands[i] = ssa.ValueID(split_blk)
195 }
196 }
197 m.instrs[pidx] = phi
198 }
199 }
200 }
201 build_cfg(mut m)
202}
203
204// eliminate_phi_nodes lowers phis to `assign` copies placed in predecessor blocks,
205// sequencing the per-predecessor parallel copies (Briggs) to respect dependencies.
206fn eliminate_phi_nodes(mut m ssa.Module) {
207 split_critical_edges(mut m)
208
209 n_blocks := m.blocks.len
210 mut pred_copy_dests := [][]int{len: n_blocks}
211 mut pred_copy_srcs := [][]int{len: n_blocks}
212
213 for fi in 0 .. m.funcs.len {
214 func := m.funcs[fi]
215 mut pred_copy_blocks := []int{}
216
217 for blk_id in func.blocks {
218 if blk_id < 0 || blk_id >= m.blocks.len {
219 continue
220 }
221 for val_id in m.blocks[blk_id].instrs {
222 if val_id < 0 || val_id >= m.values.len {
223 continue
224 }
225 val := m.values[val_id]
226 if val.kind != .instruction {
227 continue
228 }
229 instr := m.instrs[val.index]
230 if instr.op != .phi {
231 continue
232 }
233 for i := 0; i + 1 < instr.operands.len; i += 2 {
234 val_in := instr.operands[i]
235 pred_blk_idx := int(instr.operands[i + 1])
236 if pred_blk_idx < 0 || pred_blk_idx >= n_blocks {
237 continue
238 }
239 if pred_copy_dests[pred_blk_idx].len == 0 {
240 pred_copy_blocks << pred_blk_idx
241 }
242 array2d_append(mut pred_copy_dests, pred_blk_idx, val_id)
243 array2d_append(mut pred_copy_srcs, pred_blk_idx, val_in)
244 }
245 }
246 }
247
248 for pred_blk in pred_copy_blocks {
249 resolve_parallel_copies(mut m, pred_blk, pred_copy_dests[pred_blk],
250 pred_copy_srcs[pred_blk])
251 }
252 for pred_blk in pred_copy_blocks {
253 pred_copy_dests[pred_blk] = []
254 pred_copy_srcs[pred_blk] = []
255 }
256
257 // Remove phi instructions (nop them).
258 for blk_id in func.blocks {
259 if blk_id < 0 || blk_id >= m.blocks.len {
260 continue
261 }
262 for val_id in m.blocks[blk_id].instrs {
263 if val_id < 0 || val_id >= m.values.len {
264 continue
265 }
266 val := m.values[val_id]
267 if val.kind != .instruction {
268 continue
269 }
270 idx := val.index
271 if m.instrs[idx].op == .phi {
272 mut cleanup := m.instrs[idx]
273 cleanup.op = .bitcast
274 cleanup.operands = []
275 m.instrs[idx] = cleanup
276 }
277 }
278 }
279 }
280}
281
282// resolve_parallel_copies sequences a set of parallel copies dest[i] = src[i]
283// using Briggs' worklist algorithm, breaking cycles with a temp.
284fn resolve_parallel_copies(mut m ssa.Module, blk_id int, dests []int, srcs []int) {
285 if dests.len == 0 {
286 return
287 }
288 mut p_dest := []int{}
289 mut p_src := []int{}
290 for ci in 0 .. dests.len {
291 if dests[ci] != srcs[ci] {
292 p_dest << dests[ci]
293 p_src << srcs[ci]
294 }
295 }
296 np := p_dest.len
297 if np == 0 {
298 return
299 }
300
301 mut max_id := m.values.len
302 for i in 0 .. np {
303 if p_dest[i] >= max_id {
304 max_id = p_dest[i] + 1
305 }
306 if p_src[i] >= max_id {
307 max_id = p_src[i] + 1
308 }
309 }
310 mut src_ref_count := []int{len: max_id + np + 4}
311
312 for i in 0 .. np {
313 src_ref_count[p_src[i]]++
314 }
315
316 mut alive := []int{len: np}
317 for i in 0 .. np {
318 alive[i] = 1
319 }
320
321 mut worklist := []int{}
322 for i in 0 .. np {
323 if src_ref_count[p_dest[i]] == 0 {
324 worklist << i
325 }
326 }
327
328 mut s_dest := []int{}
329 mut s_src := []int{}
330 mut remaining := np
331
332 for remaining > 0 {
333 if worklist.len > 0 {
334 idx := worklist.pop()
335 if idx < 0 || idx >= np || alive[idx] == 0 {
336 continue
337 }
338 alive[idx] = 0
339 remaining--
340 d := p_dest[idx]
341 s := p_src[idx]
342 s_dest << d
343 s_src << s
344 if src_ref_count[s] > 0 {
345 src_ref_count[s]--
346 }
347 if src_ref_count[s] == 0 {
348 for j in 0 .. np {
349 if alive[j] == 1 && p_dest[j] == s {
350 worklist << j
351 }
352 }
353 }
354 continue
355 }
356
357 // Cycle: break it by copying one source into a temp.
358 mut ci := -1
359 for k in 0 .. np {
360 if alive[k] == 1 {
361 ci = k
362 break
363 }
364 }
365 if ci < 0 {
366 break
367 }
368 cycle_src := p_src[ci]
369 mut typ := 0
370 if cycle_src >= 0 && cycle_src < m.values.len {
371 typ = m.values[cycle_src].typ
372 }
373 if typ == 0 && p_dest[ci] >= 0 && p_dest[ci] < m.values.len {
374 typ = m.values[p_dest[ci]].typ
375 }
376 temp := insert_temp_in_block(mut m, blk_id, cycle_src, typ)
377 for temp >= src_ref_count.len {
378 src_ref_count << 0
379 }
380 src_ref_count[temp] = 0
381 for i in 0 .. np {
382 if alive[i] == 1 && p_src[i] == cycle_src {
383 p_src[i] = temp
384 src_ref_count[temp]++
385 }
386 }
387 src_ref_count[cycle_src] = 0
388 for j in 0 .. np {
389 if alive[j] == 1 && p_dest[j] == cycle_src {
390 worklist << j
391 }
392 }
393 }
394
395 for si in 0 .. s_dest.len {
396 insert_copy_in_block(mut m, blk_id, s_dest[si], s_src[si])
397 }
398}
399
400// insert_temp_in_block updates insert temp in block state for optimize.
401fn insert_temp_in_block(mut m ssa.Module, blk_id int, src int, typ int) int {
402 m.instrs << ssa.Instruction{
403 op: .bitcast
404 block: blk_id
405 typ: typ
406 operands: [ssa.ValueID(src)]
407 }
408 temp_id := m.add_value(.instruction, typ, 'phi_tmp_${m.values.len}', m.instrs.len - 1)
409 insert_before_terminator(mut m, blk_id, temp_id)
410 return temp_id
411}
412
413// insert_copy_in_block updates insert copy in block state for optimize.
414fn insert_copy_in_block(mut m ssa.Module, blk_id int, dest int, src int) {
415 typ := m.values[dest].typ
416 m.instrs << ssa.Instruction{
417 op: .assign
418 block: blk_id
419 typ: typ
420 operands: [ssa.ValueID(dest), ssa.ValueID(src)]
421 }
422 val_id := m.add_value(.instruction, typ, 'copy', m.instrs.len - 1)
423 insert_before_terminator(mut m, blk_id, val_id)
424}
425
426// insert_before_terminator updates insert before terminator state for optimize.
427fn insert_before_terminator(mut m ssa.Module, blk_id int, new_val int) {
428 mut blk := m.blocks[blk_id]
429 mut insert_idx := blk.instrs.len
430 if insert_idx > 0 {
431 last_val_id := blk.instrs[blk.instrs.len - 1]
432 last_val := m.values[last_val_id]
433 last_instr := m.instrs[last_val.index]
434 if last_instr.op in [.ret, .br, .jmp, .switch_, .unreachable] {
435 insert_idx = blk.instrs.len - 1
436 }
437 }
438 mut new_instrs := []ssa.ValueID{cap: blk.instrs.len + 1}
439 for ii in 0 .. insert_idx {
440 new_instrs << blk.instrs[ii]
441 }
442 new_instrs << ssa.ValueID(new_val)
443 for ii in insert_idx .. blk.instrs.len {
444 new_instrs << blk.instrs[ii]
445 }
446 blk.instrs = new_instrs
447 m.blocks[blk_id] = blk
448}
449