vxx2 / vlib / v3 / tests / ssa_passes_test.v
218 lines · 184 sloc · 7.3 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
1import v3.ssa
2import v3.ssa.optimize
3
4// --- helpers ---------------------------------------------------------------
5
6// count_op supports count op handling for v3 tests.
7fn count_op(m &ssa.Module, func_id int, op ssa.OpCode) int {
8 mut n := 0
9 for blk_id in m.funcs[func_id].blocks {
10 for val_id in m.blocks[blk_id].instrs {
11 val := m.values[val_id]
12 if val.kind == .instruction && m.instrs[val.index].op == op {
13 n++
14 }
15 }
16 }
17 return n
18}
19
20// --- TypeStore: arrays, tuples, unsigned ----------------------------------
21
22// test_type_store_arrays_tuples_unsigned validates this v3 regression case.
23fn test_type_store_arrays_tuples_unsigned() {
24 mut ts := ssa.TypeStore.new()
25 s32 := ts.get_int(32)
26 un32 := ts.get_uint(32)
27 // Signed and unsigned of the same width must be distinct types.
28 assert s32 != un32
29 assert !ts.types[s32].is_unsigned
30 assert ts.types[un32].is_unsigned
31
32 arr := ts.get_array(s32, 4)
33 arr2 := ts.get_array(s32, 4)
34 assert arr == arr2 // cached
35 assert ts.types[arr].kind == .array_t
36 assert ts.types[arr].len == 4
37 assert ts.types[arr].elem_type == s32
38
39 tup := ts.get_tuple([s32, un32])
40 assert ts.types[tup].kind == .struct_t
41 assert ts.types[tup].fields == [s32, un32]
42}
43
44// test_array_type_sizing validates array type sizing behavior in v3 tests.
45fn test_array_type_sizing() {
46 mut m := ssa.Module.new()
47 s32 := m.type_store.get_int(32)
48 arr := m.type_store.get_array(s32, 5)
49 // Fixed array of 5 x i32 = 20 bytes, aligned to element alignment (4).
50 assert m.type_size(arr) == 20
51 assert m.type_align(arr) == 4
52}
53
54// --- algebraic simplification ---------------------------------------------
55
56// test_algebraic_add_zero validates algebraic add zero behavior in v3 tests.
57fn test_algebraic_add_zero() {
58 mut m := ssa.Module.new()
59 i64t := m.type_store.get_int(64)
60 func_id := m.new_function('add_zero', i64t)
61 x := m.add_value(.argument, i64t, 'x', 0)
62 m.func_add_param(func_id, x)
63 entry := m.add_block(func_id, 'entry')
64 zero := m.get_or_add_const(i64t, '0')
65 r := m.add_instr(.add, entry, i64t, [x, zero])
66 m.add_instr(.ret, entry, ssa.TypeID(0), [r])
67
68 optimize.optimize(mut m)
69
70 // x + 0 -> x, so the add is gone and ret returns the argument directly.
71 assert count_op(m, func_id, .add) == 0
72 ret_id := m.blocks[m.funcs[func_id].blocks[0]].instrs.last()
73 ret := m.instrs[m.values[ret_id].index]
74 assert m.values[ret.operands[0]].kind == .argument
75}
76
77// test_algebraic_mul_two_to_shift validates algebraic mul two to shift behavior in v3 tests.
78fn test_algebraic_mul_two_to_shift() {
79 mut m := ssa.Module.new()
80 i64t := m.type_store.get_int(64)
81 func_id := m.new_function('mul2', i64t)
82 x := m.add_value(.argument, i64t, 'x', 0)
83 m.func_add_param(func_id, x)
84 entry := m.add_block(func_id, 'entry')
85 two := m.get_or_add_const(i64t, '2')
86 r := m.add_instr(.mul, entry, i64t, [x, two])
87 m.add_instr(.ret, entry, ssa.TypeID(0), [r])
88
89 optimize.optimize(mut m)
90
91 // x * 2 -> x << 1
92 assert count_op(m, func_id, .mul) == 0
93 assert count_op(m, func_id, .shl) == 1
94}
95
96// test_algebraic_sub_self_is_zero validates algebraic sub self is zero behavior in v3 tests.
97fn test_algebraic_sub_self_is_zero() {
98 mut m := ssa.Module.new()
99 i64t := m.type_store.get_int(64)
100 func_id := m.new_function('sub_self', i64t)
101 x := m.add_value(.argument, i64t, 'x', 0)
102 m.func_add_param(func_id, x)
103 entry := m.add_block(func_id, 'entry')
104 r := m.add_instr(.sub, entry, i64t, [x, x])
105 m.add_instr(.ret, entry, ssa.TypeID(0), [r])
106
107 optimize.optimize(mut m)
108
109 assert count_op(m, func_id, .sub) == 0
110 ret_id := m.blocks[m.funcs[func_id].blocks[0]].instrs.last()
111 ret := m.instrs[m.values[ret_id].index]
112 ret_operand := m.values[ret.operands[0]]
113 assert ret_operand.kind == .constant
114 assert ret_operand.name == '0'
115}
116
117// --- DCE side-effect preservation -----------------------------------------
118
119// test_dce_preserves_side_effecting_ops validates this v3 regression case.
120fn test_dce_preserves_side_effecting_ops() {
121 mut m := ssa.Module.new()
122 i64t := m.type_store.get_int(64)
123 func_id := m.new_function('side_fx', i64t)
124 entry := m.add_block(func_id, 'entry')
125 // A pure dead add that should be removed.
126 a := m.get_or_add_const(i64t, '7')
127 b := m.get_or_add_const(i64t, '9')
128 m.add_instr(.add, entry, i64t, [a, b])
129 // Side-effecting ops with no uses that must be preserved.
130 m.add_instr(.fence, entry, ssa.TypeID(0), [])
131 m.add_instr(.cmpxchg, entry, i64t, [a])
132 m.add_instr(.ret, entry, ssa.TypeID(0), [])
133
134 optimize.optimize(mut m)
135
136 assert count_op(m, func_id, .add) == 0 // dead pure op removed
137 assert count_op(m, func_id, .fence) == 1 // preserved
138 assert count_op(m, func_id, .cmpxchg) == 1 // preserved
139}
140
141// --- structured verifier ---------------------------------------------------
142
143// test_structured_verifier_accepts_valid_module validates this v3 regression case.
144fn test_structured_verifier_accepts_valid_module() {
145 mut m := ssa.Module.new()
146 i64t := m.type_store.get_int(64)
147 func_id := m.new_function('valid', i64t)
148 entry := m.add_block(func_id, 'entry')
149 c := m.get_or_add_const(i64t, '1')
150 m.add_instr(.ret, entry, ssa.TypeID(0), [c])
151
152 optimize.optimize(mut m)
153 errors := optimize.verify(m)
154 // No structural errors on a well-formed module.
155 for e in errors {
156 assert !e.msg.contains('invalid')
157 assert !e.msg.contains('not in its')
158 }
159}
160
161// test_structured_verifier_accepts_prototype_declaration validates this v3 regression case.
162fn test_structured_verifier_accepts_prototype_declaration() {
163 mut m := ssa.Module.new()
164 i64t := m.type_store.get_int(64)
165 // A prototype/extern function with no body must be accepted (not fatal).
166 proto := m.new_function('extern_decl', i64t)
167 m.func_set_prototype(proto, true)
168 m.func_set_c_extern(proto, true)
169 // Should not panic.
170 optimize.verify_and_panic(m, 'prototype test')
171 assert true
172}
173
174// --- mem2reg + phi elimination (IR level) ---------------------------------
175
176// test_mem2reg_promotes_diamond_slot validates this v3 regression case.
177fn test_mem2reg_promotes_diamond_slot() {
178 mut m := ssa.Module.new()
179 i64t := m.type_store.get_int(64)
180 i1 := m.type_store.get_int(1)
181 ptr_i64 := m.type_store.get_ptr(i64t)
182 func_id := m.new_function('diamond', i64t)
183 cond := m.add_value(.argument, i1, 'cond', 0)
184 m.func_add_param(func_id, cond)
185
186 entry := m.add_block(func_id, 'entry')
187 then_blk := m.add_block(func_id, 'then')
188 else_blk := m.add_block(func_id, 'else')
189 merge := m.add_block(func_id, 'merge')
190
191 slot := m.add_instr(.alloca, entry, ptr_i64, [])
192 m.add_instr(.br, entry, ssa.TypeID(0), [cond, ssa.ValueID(then_blk), ssa.ValueID(else_blk)])
193
194 ten := m.get_or_add_const(i64t, '10')
195 m.add_instr(.store, then_blk, ssa.TypeID(0), [ten, slot])
196 m.add_instr(.jmp, then_blk, ssa.TypeID(0), [ssa.ValueID(merge)])
197
198 twenty := m.get_or_add_const(i64t, '20')
199 m.add_instr(.store, else_blk, ssa.TypeID(0), [twenty, slot])
200 m.add_instr(.jmp, else_blk, ssa.TypeID(0), [ssa.ValueID(merge)])
201
202 loaded := m.add_instr(.load, merge, i64t, [slot])
203 m.add_instr(.ret, merge, ssa.TypeID(0), [loaded])
204
205 optimize.optimize_with_options(mut m, optimize.OptimizeOptions{
206 mem2reg: true
207 eliminate_phis: true
208 })
209
210 // The scalar slot is fully promoted: no alloca/load/store remain.
211 assert count_op(m, func_id, .alloca) == 0
212 assert count_op(m, func_id, .load) == 0
213 assert count_op(m, func_id, .store) == 0
214 // Phi was lowered to assign copies in the predecessor blocks.
215 assert count_op(m, func_id, .assign) >= 1
216 // And no phi survives after elimination.
217 assert count_op(m, func_id, .phi) == 0
218}
219