v / vlib / v2 / ssa / optimize / pipeline_gate4_test.v
434 lines · 366 sloc · 13.35 KB · 81a5657604ec6da99c25e26546870c6888d6fdde
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 os
8import v2.ssa
9
10fn gate4_has_error(errors []VerifyError, op string, needle string) bool {
11 for err in errors {
12 if err.msg.contains(op) && err.msg.contains(needle) {
13 return true
14 }
15 }
16 return false
17}
18
19fn gate4_corrupt_uses_list_module() &ssa.Module {
20 mut m := ssa.Module.new('gate4_corrupt_uses_list')
21 i64_t := m.type_store.get_int(64)
22 func_id := m.new_function('main', i64_t, [])
23 entry := m.add_block(func_id, 'entry')
24 lhs := m.get_or_add_const(i64_t, '1')
25 rhs := m.get_or_add_const(i64_t, '2')
26 sum := m.add_instr(.add, entry, i64_t, [lhs, rhs])
27 m.add_instr(.ret, entry, 0, [sum])
28 mut lhs_val := m.values[lhs]
29 lhs_val.uses.clear()
30 m.values[lhs] = lhs_val
31 return m
32}
33
34fn test_verify_rejects_call_sret_without_target() {
35 mut m := ssa.Module.new('gate4_verify_call_sret')
36 i64_t := m.type_store.get_int(64)
37 func_id := m.new_function('caller', i64_t, [])
38 entry := m.add_block(func_id, 'entry')
39 m.add_instr(.call_sret, entry, i64_t, [])
40 zero := m.get_or_add_const(i64_t, '0')
41 m.add_instr(.ret, entry, 0, [zero])
42
43 errors := verify(m)
44 assert gate4_has_error(errors, 'call_sret', 'no operands'), 'expected call_sret without target to fail verification'
45}
46
47fn test_verify_rejects_concurrency_calls_without_target() {
48 for op in [ssa.OpCode.go_call, .spawn_call] {
49 mut m := ssa.Module.new('gate4_verify_concurrency_call')
50 func_id := m.new_function('caller', 0, [])
51 entry := m.add_block(func_id, 'entry')
52 m.add_instr(op, entry, 0, [])
53 m.add_instr(.ret, entry, 0, [])
54
55 errors := verify(m)
56 assert gate4_has_error(errors, op.str(), 'no operands'), 'expected ${op} without target to fail verification'
57 }
58}
59
60fn test_dce_preserves_global_initializer_store() {
61 mut m := ssa.Module.new('gate4_dce_global_store')
62 i64_t := m.type_store.get_int(64)
63 global_id := m.add_global('module__state', i64_t, false)
64 value := m.get_or_add_const(i64_t, '7')
65 func_id := m.new_function('_vinit', 0, [])
66 entry := m.add_block(func_id, 'entry')
67 store_id := m.add_instr(.store, entry, 0, [value, global_id])
68 m.add_instr(.ret, entry, 0, [])
69
70 changed := dead_code_elimination(mut m)
71
72 assert !changed
73 assert store_id in m.blocks[entry].instrs
74 assert m.values[global_id].uses.len == 1
75 assert m.values[global_id].uses[0] == store_id
76}
77
78fn test_dce_preserves_imported_const_initializer_store() {
79 mut m := ssa.Module.new('gate4_dce_imported_const_init')
80 i64_t := m.type_store.get_int(64)
81 global_id := m.add_global('dep__runtime_value', i64_t, false)
82 value := m.get_or_add_const(i64_t, '11')
83 func_id := m.new_function('dep____v_init_consts_dep', 0, [])
84 entry := m.add_block(func_id, 'entry')
85 store_id := m.add_instr(.store, entry, 0, [value, global_id])
86 m.add_instr(.ret, entry, 0, [])
87
88 changed := dead_code_elimination(mut m)
89
90 assert !changed
91 assert store_id in m.blocks[entry].instrs
92 assert m.values[global_id].uses.len == 1
93 assert m.values[global_id].uses[0] == store_id
94}
95
96fn test_optimize_can_verify_each_pipeline_checkpoint() {
97 mut m := ssa.Module.new('gate4_verify_each_pipeline_checkpoint')
98 i64_t := m.type_store.get_int(64)
99 func_id := m.new_function('main', i64_t, [])
100 entry := m.add_block(func_id, 'entry')
101 zero := m.get_or_add_const(i64_t, '0')
102 ret_id := m.add_instr(.ret, entry, 0, [zero])
103
104 optimize_with_options(mut m, OptimizeOptions{
105 verify_each_pass: true
106 })
107
108 assert ret_id in m.blocks[entry].instrs
109 assert verify(m).len == 0
110}
111
112fn test_optimize_can_verify_strict_each_pipeline_checkpoint() {
113 mut m := ssa.Module.new('gate4_strict_verify_each_pipeline_checkpoint')
114 i64_t := m.type_store.get_int(64)
115 func_id := m.new_function('main', i64_t, [])
116 entry := m.add_block(func_id, 'entry')
117 zero := m.get_or_add_const(i64_t, '0')
118 ret_id := m.add_instr(.ret, entry, 0, [zero])
119
120 optimize_with_options(mut m, OptimizeOptions{
121 strict_verify: true
122 })
123
124 assert ret_id in m.blocks[entry].instrs
125 assert verify(m).len == 0
126}
127
128fn test_optimize_runs_dead_code_elimination_pass() {
129 mut m := ssa.Module.new('gate4_optimize_runs_dce')
130 i64_t := m.type_store.get_int(64)
131 func_id := m.new_function('main', i64_t, [])
132 entry := m.add_block(func_id, 'entry')
133 lhs := m.get_or_add_const(i64_t, '20')
134 rhs := m.get_or_add_const(i64_t, '22')
135 dead_add := m.add_instr(.add, entry, i64_t, [lhs, rhs])
136 zero := m.get_or_add_const(i64_t, '0')
137 ret_id := m.add_instr(.ret, entry, 0, [zero])
138
139 optimize_with_options(mut m, OptimizeOptions{
140 verify_each_pass: true
141 })
142
143 assert dead_add !in m.blocks[entry].instrs
144 assert ret_id in m.blocks[entry].instrs
145}
146
147fn test_public_optimize_uses_v2_verify_pipeline_checkpoints() {
148 old_verify := os.getenv_opt('V2_VERIFY')
149 os.setenv('V2_VERIFY', '1', true)
150 defer {
151 if old := old_verify {
152 os.setenv('V2_VERIFY', old, true)
153 } else {
154 os.unsetenv('V2_VERIFY')
155 }
156 }
157
158 mut m := ssa.Module.new('gate4_public_optimize_v2_verify')
159 i64_t := m.type_store.get_int(64)
160 func_id := m.new_function('main', i64_t, [])
161 entry := m.add_block(func_id, 'entry')
162 lhs := m.get_or_add_const(i64_t, '100')
163 rhs := m.get_or_add_const(i64_t, '23')
164 dead_add := m.add_instr(.add, entry, i64_t, [lhs, rhs])
165 zero := m.get_or_add_const(i64_t, '0')
166 ret_id := m.add_instr(.ret, entry, 0, [zero])
167
168 optimize_with_options(mut m, OptimizeOptions{})
169
170 assert dead_add !in m.blocks[entry].instrs
171 assert ret_id in m.blocks[entry].instrs
172 assert verify(m).len == 0
173}
174
175fn test_public_optimize_uses_v2_verify_strict_pipeline_checkpoints() {
176 old_verify := os.getenv_opt('V2_VERIFY')
177 old_strict := os.getenv_opt('V2_VERIFY_STRICT')
178 os.unsetenv('V2_VERIFY')
179 os.setenv('V2_VERIFY_STRICT', '1', true)
180 defer {
181 if old := old_verify {
182 os.setenv('V2_VERIFY', old, true)
183 } else {
184 os.unsetenv('V2_VERIFY')
185 }
186 if old := old_strict {
187 os.setenv('V2_VERIFY_STRICT', old, true)
188 } else {
189 os.unsetenv('V2_VERIFY_STRICT')
190 }
191 }
192
193 mut m := ssa.Module.new('gate4_public_optimize_v2_verify_strict')
194 i64_t := m.type_store.get_int(64)
195 func_id := m.new_function('main', i64_t, [])
196 entry := m.add_block(func_id, 'entry')
197 zero := m.get_or_add_const(i64_t, '0')
198 ret_id := m.add_instr(.ret, entry, 0, [zero])
199
200 optimize(mut m)
201
202 assert ret_id in m.blocks[entry].instrs
203 assert verify(m).len == 0
204}
205
206fn test_optimize_rebuilds_uses_before_mem2reg_promotability() {
207 mut m := ssa.Module.new('gate4_mem2reg_rebuilds_escaped_alloca_uses')
208 i8_t := m.type_store.get_int(8)
209 ptr_i8_t := m.type_store.get_ptr(i8_t)
210 func_id := m.new_function('main', 0, [])
211 entry := m.add_block(func_id, 'entry')
212 alloca_id := m.add_instr(.alloca, entry, ptr_i8_t, [])
213 ten := m.get_or_add_const(i8_t, '10')
214 m.add_instr(.store, entry, 0, [ten, alloca_id])
215 callee := m.add_value_node(.unknown, 0, 'sink', 0)
216 m.add_instr(.call, entry, 0, [callee, alloca_id])
217 m.add_instr(.ret, entry, 0, [])
218
219 mut alloca_val := m.values[alloca_id]
220 alloca_val.uses = []
221 m.values[alloca_id] = alloca_val
222
223 optimize_with_options(mut m, OptimizeOptions{})
224
225 alloca_instr := m.instrs[m.values[alloca_id].index]
226 assert alloca_instr.op == .alloca
227}
228
229fn test_verify_and_panic_allows_c_extern_prototype_without_blocks() {
230 mut m := ssa.Module.new('gate4_c_extern_prototype')
231 func_id := m.new_function('C_puts', 0, [])
232 m.func_set_c_extern(func_id, true)
233
234 verify_and_panic(m, 'c extern prototype')
235}
236
237fn test_verify_and_panic_allows_marked_prototype_without_blocks() {
238 mut m := ssa.Module.new('gate4_marked_prototype')
239 func_id := m.new_function('registered_only', 0, [])
240 m.func_set_prototype(func_id, true)
241
242 verify_and_panic(m, 'marked prototype')
243}
244
245fn test_strict_verify_and_panic_allows_valid_declarations_without_blocks() {
246 mut m := ssa.Module.new('gate4_strict_valid_declarations')
247 c_func_id := m.new_function('C_puts', 0, [])
248 m.func_set_c_extern(c_func_id, true)
249 prototype_func_id := m.new_function('registered_only', 0, [])
250 m.func_set_prototype(prototype_func_id, true)
251
252 verify_and_panic_with_options(m, 'strict valid declarations', VerifyPanicOptions{
253 allow_noncritical: false
254 })
255}
256
257fn test_verify_and_panic_allows_uses_list_warning_by_default() {
258 mut m := gate4_corrupt_uses_list_module()
259
260 errors := verify(m)
261 assert gate4_has_error(errors, 'uses list', 'not in uses list'), 'expected uses list warning'
262
263 verify_and_panic(m, 'uses list default warning')
264}
265
266fn test_strict_verify_and_panic_rejects_uses_list_warning() {
267 tmp_dir := os.join_path(os.temp_dir(), 'v2_gate4_strict_verify_panic_${os.getpid()}')
268 os.rmdir_all(tmp_dir) or {}
269 os.mkdir_all(tmp_dir) or { panic(err) }
270 defer {
271 os.rmdir_all(tmp_dir) or {}
272 }
273 sample_path := os.join_path(tmp_dir, 'strict_verify_panic_sample.v')
274 os.write_file(sample_path, 'module main
275
276import v2.ssa
277import v2.ssa.optimize
278
279fn main() {
280 mut m := ssa.Module.new("gate4_strict_uses_list")
281 i64_t := m.type_store.get_int(64)
282 func_id := m.new_function("main", i64_t, [])
283 entry := m.add_block(func_id, "entry")
284 lhs := m.get_or_add_const(i64_t, "1")
285 rhs := m.get_or_add_const(i64_t, "2")
286 sum := m.add_instr(.add, entry, i64_t, [lhs, rhs])
287 m.add_instr(.ret, entry, 0, [sum])
288 mut lhs_val := m.values[lhs]
289 lhs_val.uses.clear()
290 m.values[lhs] = lhs_val
291 optimize.verify_and_panic_with_options(m, "strict uses list", optimize.VerifyPanicOptions{
292 allow_noncritical: false
293 })
294}
295') or {
296 panic(err)
297 }
298
299 res := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(sample_path)}')
300
301 assert res.exit_code != 0
302 assert res.output.contains('uses list'), res.output
303}
304
305fn test_strict_optimize_rejects_uses_list_warning() {
306 tmp_dir := os.join_path(os.temp_dir(), 'v2_gate4_strict_optimize_panic_${os.getpid()}')
307 os.rmdir_all(tmp_dir) or {}
308 os.mkdir_all(tmp_dir) or { panic(err) }
309 defer {
310 os.rmdir_all(tmp_dir) or {}
311 }
312 sample_path := os.join_path(tmp_dir, 'strict_optimize_panic_sample.v')
313 os.write_file(sample_path, 'module main
314
315import v2.ssa
316import v2.ssa.optimize
317
318fn main() {
319 mut m := ssa.Module.new("gate4_strict_optimize_uses_list")
320 i64_t := m.type_store.get_int(64)
321 func_id := m.new_function("main", i64_t, [])
322 entry := m.add_block(func_id, "entry")
323 lhs := m.get_or_add_const(i64_t, "1")
324 rhs := m.get_or_add_const(i64_t, "2")
325 sum := m.add_instr(.add, entry, i64_t, [lhs, rhs])
326 m.add_instr(.ret, entry, 0, [sum])
327 mut lhs_val := m.values[lhs]
328 lhs_val.uses.clear()
329 m.values[lhs] = lhs_val
330 optimize.optimize_with_options(mut m, optimize.OptimizeOptions{
331 strict_verify: true
332 })
333}
334') or {
335 panic(err)
336 }
337
338 res := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(sample_path)}')
339
340 assert res.exit_code != 0
341 assert res.output.contains('uses list'), res.output
342 assert res.output.contains('input'), res.output
343}
344
345fn test_verify_and_panic_rejects_v_function_without_blocks() {
346 tmp_dir := os.join_path(os.temp_dir(), 'v2_gate4_verify_panic_${os.getpid()}')
347 os.rmdir_all(tmp_dir) or {}
348 os.mkdir_all(tmp_dir) or { panic(err) }
349 defer {
350 os.rmdir_all(tmp_dir) or {}
351 }
352 sample_path := os.join_path(tmp_dir, 'verify_panic_sample.v')
353 os.write_file(sample_path, 'module main
354
355import v2.ssa
356import v2.ssa.optimize
357
358fn main() {
359 mut m := ssa.Module.new("gate4_v_prototype")
360 m.new_function("v_decl_without_body", 0, [])
361 optimize.verify_and_panic(m, "v prototype negative")
362}
363') or {
364 panic(err)
365 }
366
367 res := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(sample_path)}')
368
369 assert res.exit_code != 0
370 assert res.output.contains('function has no blocks'), res.output
371}
372
373fn test_dce_preserves_call_sret_side_effect() {
374 mut m := ssa.Module.new('gate4_dce_call_sret')
375 i8_t := m.type_store.get_int(8)
376 i64_t := m.type_store.get_int(64)
377 ptr_t := m.type_store.get_ptr(i8_t)
378 result_t := m.type_store.register(ssa.Type{
379 kind: .struct_t
380 fields: [i64_t, i64_t]
381 })
382 callee := m.add_value_node(.func_ref, ptr_t, 'make_pair', 0)
383 arg := m.get_or_add_const(i64_t, '3')
384 func_id := m.new_function('caller', 0, [])
385 entry := m.add_block(func_id, 'entry')
386 call_id := m.add_instr(.call_sret, entry, result_t, [callee, arg])
387 m.add_instr(.ret, entry, 0, [])
388
389 changed := dead_code_elimination(mut m)
390
391 assert !changed
392 assert call_id in m.blocks[entry].instrs
393 assert m.values[callee].uses.len == 1
394 assert m.values[callee].uses[0] == call_id
395}
396
397fn test_dce_preserves_concurrency_call_side_effects() {
398 mut m := ssa.Module.new('gate4_dce_concurrency_calls')
399 i8_t := m.type_store.get_int(8)
400 ptr_t := m.type_store.get_ptr(i8_t)
401 go_callee := m.add_value_node(.func_ref, ptr_t, 'launch_go', 0)
402 spawn_callee := m.add_value_node(.func_ref, ptr_t, 'launch_spawn', 0)
403 func_id := m.new_function('caller', 0, [])
404 entry := m.add_block(func_id, 'entry')
405 go_id := m.add_instr(.go_call, entry, 0, [go_callee])
406 spawn_id := m.add_instr(.spawn_call, entry, 0, [spawn_callee])
407 m.add_instr(.ret, entry, 0, [])
408
409 changed := dead_code_elimination(mut m)
410
411 assert !changed
412 assert go_id in m.blocks[entry].instrs
413 assert spawn_id in m.blocks[entry].instrs
414 assert m.values[go_callee].uses[0] == go_id
415 assert m.values[spawn_callee].uses[0] == spawn_id
416}
417
418fn test_dce_preserves_cmpxchg_side_effect() {
419 mut m := ssa.Module.new('gate4_dce_cmpxchg')
420 i64_t := m.type_store.get_int(64)
421 global_id := m.add_global('module__atomic_state', i64_t, false)
422 expected := m.get_or_add_const(i64_t, '0')
423 desired := m.get_or_add_const(i64_t, '1')
424 func_id := m.new_function('update', 0, [])
425 entry := m.add_block(func_id, 'entry')
426 cmpxchg_id := m.add_instr(.cmpxchg, entry, i64_t, [global_id, expected, desired])
427 m.add_instr(.ret, entry, 0, [])
428
429 changed := dead_code_elimination(mut m)
430
431 assert !changed
432 assert cmpxchg_id in m.blocks[entry].instrs
433 assert m.values[global_id].uses[0] == cmpxchg_id
434}
435