v / vlib / v2 / ssa / optimize / phi_test.v
183 lines · 159 sloc · 4.49 KB · 5807d31b40f8a116454f70eec6cdf8c766512d1f
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// vtest build: macos
5
6module optimize
7
8import v2.ssa
9
10struct TestParallelCopy {
11 dest int
12 src int
13}
14
15fn resolve_test_parallel_copies(mut m ssa.Module, blk_id int, copies []TestParallelCopy) {
16 mut dests := []int{cap: copies.len}
17 mut srcs := []int{cap: copies.len}
18 for copy in copies {
19 dests << copy.dest
20 srcs << copy.src
21 }
22 mut src_ref_count := []int{len: m.values.len + copies.len + 8}
23 mut touched_ids := []int{cap: copies.len + 4}
24 resolve_parallel_copies_flat(mut m, blk_id, dests, srcs, mut src_ref_count, mut touched_ids)
25}
26
27fn collect_parallel_assigns(m &ssa.Module, blk_id int) []TestParallelCopy {
28 mut assigns := []TestParallelCopy{}
29 for val_id in m.blocks[blk_id].instrs {
30 if m.values[val_id].kind != .instruction {
31 continue
32 }
33 instr := m.instrs[m.values[val_id].index]
34 if instr.op != .assign {
35 continue
36 }
37 assert instr.operands.len == 2
38 assert instr.operands[0] != 0
39 assert instr.operands[1] != 0
40 assigns << TestParallelCopy{
41 dest: instr.operands[0]
42 src: instr.operands[1]
43 }
44 }
45 return assigns
46}
47
48fn test_parallel_copy_cycle_materializes_temp_in_block() {
49 mut m := ssa.Module.new('test_cycle')
50 i64_t := m.type_store.get_int(64)
51 fn_id := m.new_function('f', 0, [])
52 blk_id := m.add_block(fn_id, 'entry')
53
54 c1 := m.get_or_add_const(i64_t, '1')
55 c2 := m.get_or_add_const(i64_t, '2')
56 a := m.add_instr(.add, blk_id, i64_t, [c1, c2])
57 b := m.add_instr(.sub, blk_id, i64_t, [a, c1])
58 m.add_instr(.ret, blk_id, 0, [])
59
60 resolve_test_parallel_copies(mut m, blk_id, [
61 TestParallelCopy{
62 dest: a
63 src: b
64 },
65 TestParallelCopy{
66 dest: b
67 src: a
68 },
69 ])
70
71 mut temp_ids := []int{}
72 for val_id in m.blocks[blk_id].instrs {
73 if m.values[val_id].name.starts_with('phi_tmp_') {
74 temp_ids << val_id
75 }
76 }
77 assigns := collect_parallel_assigns(m, blk_id)
78
79 assert temp_ids.len > 0
80 assert assigns.len == 2
81
82 temp_id := temp_ids[0]
83 mut has_temp_src := false
84 for copy in assigns {
85 if copy.src == temp_id {
86 has_temp_src = true
87 }
88 }
89 assert has_temp_src
90}
91
92fn test_parallel_copy_acyclic_chain_does_not_create_temp() {
93 mut m := ssa.Module.new('test_chain')
94 i64_t := m.type_store.get_int(64)
95 fn_id := m.new_function('f', 0, [])
96 blk_id := m.add_block(fn_id, 'entry')
97
98 c1 := m.get_or_add_const(i64_t, '1')
99 c2 := m.get_or_add_const(i64_t, '2')
100 b := m.add_instr(.add, blk_id, i64_t, [c1, c2])
101 a := m.add_instr(.sub, blk_id, i64_t, [b, c1])
102 c := m.add_instr(.mul, blk_id, i64_t, [a, c2])
103 m.add_instr(.ret, blk_id, 0, [])
104
105 resolve_test_parallel_copies(mut m, blk_id, [
106 TestParallelCopy{
107 dest: a
108 src: b
109 },
110 TestParallelCopy{
111 dest: c
112 src: a
113 },
114 ])
115
116 mut has_temp := false
117 for val_id in m.blocks[blk_id].instrs {
118 if m.values[val_id].name.starts_with('phi_tmp_') {
119 has_temp = true
120 }
121 }
122 assigns := collect_parallel_assigns(m, blk_id)
123
124 assert !has_temp
125 assert assigns.len == 2
126 assert assigns[0].dest == c
127 assert assigns[0].src == a
128 assert assigns[1].dest == a
129 assert assigns[1].src == b
130}
131
132fn test_eliminate_phi_nodes_cycle_assign_dests_are_materialized() {
133 mut m := ssa.Module.new('test_phi_elim_cycle')
134 i64_t := m.type_store.get_int(64)
135 fn_id := m.new_function('f', 0, [])
136 entry := m.add_block(fn_id, 'entry')
137 join := m.add_block(fn_id, 'join')
138 block_val_ids := build_block_val_ids(m)
139
140 join_val := get_block_val_id(m, join, block_val_ids)
141 m.add_instr(.jmp, entry, 0, [join_val])
142
143 entry_val := get_block_val_id(m, entry, block_val_ids)
144 zero := m.get_or_add_const(i64_t, '0')
145 one := m.get_or_add_const(i64_t, '1')
146
147 phi_a := m.add_instr(.phi, join, i64_t, [zero, entry_val])
148 phi_b := m.add_instr(.phi, join, i64_t, [phi_a, entry_val])
149 // Make it a true cycle: a <- b, b <- a.
150 m.instrs[m.values[phi_a].index].operands[0] = phi_b
151 m.add_instr(.ret, join, 0, [one])
152
153 eliminate_phi_nodes(mut m)
154
155 mut materialized := map[int]bool{}
156 for blk_id in m.funcs[fn_id].blocks {
157 for val_id in m.blocks[blk_id].instrs {
158 materialized[val_id] = true
159 }
160 }
161
162 mut saw_assign := false
163 mut saw_temp := false
164 for blk_id in m.funcs[fn_id].blocks {
165 for val_id in m.blocks[blk_id].instrs {
166 if m.values[val_id].name.starts_with('phi_tmp_') {
167 saw_temp = true
168 }
169 if m.values[val_id].kind != .instruction {
170 continue
171 }
172 instr := m.instrs[m.values[val_id].index]
173 if instr.op == .assign {
174 saw_assign = true
175 dest := instr.operands[0]
176 assert dest in materialized
177 }
178 }
179 }
180
181 assert saw_assign
182 assert saw_temp
183}
184