v / vlib / v2 / ssa / optimize / fold.v
327 lines · 307 sloc · 7.79 KB · 9e3e02d5ea5b1cb300b5f5304cd821f47337b008
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
9fn constant_fold(mut m ssa.Module) bool {
10 mut changed := false
11 for fi in 0 .. m.funcs.len {
12 for blk_id in m.funcs[fi].blocks {
13 // Iterate directly without cloning - we don't modify the array during iteration
14 for val_id in m.blocks[blk_id].instrs {
15 if m.values[val_id].kind != .instruction {
16 continue
17 }
18
19 instr := m.instrs[m.values[val_id].index]
20
21 if instr.operands.len == 2 {
22 lhs := m.values[instr.operands[0]]
23 rhs := m.values[instr.operands[1]]
24
25 // Skip undef values - can't fold with undefined
26 if lhs.kind == .constant && lhs.name == 'undef' {
27 continue
28 }
29 if rhs.kind == .constant && rhs.name == 'undef' {
30 continue
31 }
32
33 // Try algebraic simplifications first (even with non-constants)
34 // vfmt off
35 repl, needs_zero := try_algebraic_simplify(m, val_id, instr, lhs, rhs)
36 // vfmt on
37 if repl >= 0 || repl == -2 {
38 if repl == -2 {
39 // x * 2 -> x << 1
40 other_id := if lhs.kind == .constant {
41 instr.operands[1]
42 } else {
43 instr.operands[0]
44 }
45 typ := m.values[val_id].typ
46 one_val := m.get_or_add_const(typ, '1')
47 // Avoid m.instrs[X].field = ... -- chained field assign broken in ARM64 self-hosted
48 mut shl_instr := m.instrs[m.values[val_id].index]
49 shl_instr.op = .shl
50 shl_instr.operands = [other_id, one_val]
51 m.instrs[m.values[val_id].index] = shl_instr
52 changed = true
53 continue
54 } else if needs_zero {
55 // x * 0 or x & 0 or x - x or x ^ x - reuse zero constant
56 typ := m.values[val_id].typ
57 zero_val := m.get_or_add_const(typ, '0')
58 m.replace_uses(val_id, zero_val)
59 } else {
60 m.replace_uses(val_id, repl)
61 }
62 changed = true
63 continue
64 }
65
66 // Constant folding - both operands must be constants
67 if lhs.kind == .constant && rhs.kind == .constant {
68 l_int := lhs.name.i64()
69 r_int := rhs.name.i64()
70
71 mut result := i64(0)
72 mut folded := false
73
74 match instr.op {
75 .add {
76 result = l_int + r_int
77 folded = true
78 }
79 .sub {
80 result = l_int - r_int
81 folded = true
82 }
83 .mul {
84 result = l_int * r_int
85 folded = true
86 }
87 .sdiv {
88 if r_int != 0 {
89 result = l_int / r_int
90 folded = true
91 }
92 }
93 .srem {
94 if r_int != 0 {
95 result = l_int % r_int
96 folded = true
97 }
98 }
99 .and_ {
100 result = l_int & r_int
101 folded = true
102 }
103 .or_ {
104 result = l_int | r_int
105 folded = true
106 }
107 .xor {
108 result = l_int ^ r_int
109 folded = true
110 }
111 .shl {
112 if r_int >= 0 && r_int < 64 {
113 result = i64(u64(l_int) << u64(r_int))
114 folded = true
115 }
116 }
117 .ashr {
118 if r_int >= 0 && r_int < 64 {
119 // Arithmetic shift right preserves sign
120 result = l_int >> u64(r_int)
121 folded = true
122 }
123 }
124 .lshr {
125 if r_int >= 0 && r_int < 64 {
126 // Logical shift right treats as unsigned
127 result = i64(u64(l_int) >> u64(r_int))
128 folded = true
129 }
130 }
131 .eq {
132 result = if l_int == r_int { 1 } else { 0 }
133 folded = true
134 }
135 .ne {
136 result = if l_int != r_int { 1 } else { 0 }
137 folded = true
138 }
139 .lt {
140 result = if l_int < r_int { 1 } else { 0 }
141 folded = true
142 }
143 .gt {
144 result = if l_int > r_int { 1 } else { 0 }
145 folded = true
146 }
147 .le {
148 result = if l_int <= r_int { 1 } else { 0 }
149 folded = true
150 }
151 .ge {
152 result = if l_int >= r_int { 1 } else { 0 }
153 folded = true
154 }
155 .ult {
156 result = if u64(l_int) < u64(r_int) { 1 } else { 0 }
157 folded = true
158 }
159 .ugt {
160 result = if u64(l_int) > u64(r_int) { 1 } else { 0 }
161 folded = true
162 }
163 .ule {
164 result = if u64(l_int) <= u64(r_int) { 1 } else { 0 }
165 folded = true
166 }
167 .uge {
168 result = if u64(l_int) >= u64(r_int) { 1 } else { 0 }
169 folded = true
170 }
171 else {}
172 }
173
174 if folded {
175 typ := m.values[val_id].typ
176 const_val := m.get_or_add_const(typ, result.str())
177 m.replace_uses(val_id, const_val)
178 changed = true
179 }
180 }
181 }
182 }
183 }
184 }
185 return changed
186}
187
188// Branch folding: simplify conditional branches with constant conditions
189fn branch_fold(mut m ssa.Module) bool {
190 mut changed := false
191 for fi in 0 .. m.funcs.len {
192 for blk_id in m.funcs[fi].blocks {
193 if m.blocks[blk_id].instrs.len == 0 {
194 continue
195 }
196
197 term_val_id := m.blocks[blk_id].instrs[m.blocks[blk_id].instrs.len - 1]
198 term := m.instrs[m.values[term_val_id].index]
199
200 if term.op == .br {
201 // br cond, true_blk, false_blk
202 cond_val := m.values[term.operands[0]]
203 if cond_val.kind == .constant && cond_val.name != 'undef' {
204 cond_int := cond_val.name.i64()
205 // Replace with unconditional jump to the taken branch
206 target := if cond_int != 0 { term.operands[1] } else { term.operands[2] }
207 // Avoid m.instrs[X].field = ... -- chained field assign broken in ARM64 self-hosted
208 mut jmp_instr := m.instrs[m.values[term_val_id].index]
209 jmp_instr.op = .jmp
210 jmp_instr.operands = [target]
211 m.instrs[m.values[term_val_id].index] = jmp_instr
212 changed = true
213 }
214 }
215 }
216 }
217 return changed
218}
219
220// Algebraic simplifications: x+0=x, x*1=x, x*0=0, x-x=0, x^x=0, x&x=x, x|x=x, x*2=x<<1, etc.
221// Returns (replacement_id, needs_zero) - if needs_zero is true, caller should create zero constant
222fn try_algebraic_simplify(_m &ssa.Module, val_id int, instr ssa.Instruction, lhs ssa.Value, rhs ssa.Value) (int, bool) {
223 lhs_id := instr.operands[0]
224 rhs_id := instr.operands[1]
225
226 // Check for same-operand simplifications (x op x)
227 if lhs_id == rhs_id {
228 match instr.op {
229 .sub {
230 // x - x = 0
231 return val_id, true
232 }
233 .xor {
234 // x ^ x = 0
235 return val_id, true
236 }
237 .and_ {
238 // x & x = x
239 return lhs_id, false
240 }
241 .or_ {
242 // x | x = x
243 return lhs_id, false
244 }
245 else {}
246 }
247 }
248
249 // Check if either operand is a constant
250 mut const_val := i64(0)
251 mut const_is_rhs := false
252 mut other_id := 0
253
254 if lhs.kind == .constant && lhs.name != 'undef' {
255 const_val = lhs.name.i64()
256 const_is_rhs = false
257 other_id = instr.operands[1]
258 } else if rhs.kind == .constant && rhs.name != 'undef' {
259 const_val = rhs.name.i64()
260 const_is_rhs = true
261 other_id = instr.operands[0]
262 } else {
263 return -1, false
264 }
265
266 match instr.op {
267 .add {
268 // x + 0 = 0 + x = x
269 if const_val == 0 {
270 return other_id, false
271 }
272 }
273 .sub {
274 // x - 0 = x
275 if const_is_rhs && const_val == 0 {
276 return other_id, false
277 }
278 }
279 .mul {
280 // x * 0 = 0 * x = 0
281 if const_val == 0 {
282 return val_id, true // Signal caller to create zero
283 }
284 // x * 1 = 1 * x = x
285 if const_val == 1 {
286 return other_id, false
287 }
288 // x * 2 = x << 1, 2 * x = x << 1
289 if const_val == 2 {
290 return -2, false // Signal caller to create shift
291 }
292 }
293 .sdiv {
294 // x / 1 = x
295 if const_is_rhs && const_val == 1 {
296 return other_id, false
297 }
298 }
299 .and_ {
300 // x & 0 = 0 & x = 0
301 if const_val == 0 {
302 return val_id, true // Signal caller to create zero
303 }
304 }
305 .or_ {
306 // x | 0 = 0 | x = x
307 if const_val == 0 {
308 return other_id, false
309 }
310 }
311 .xor {
312 // x ^ 0 = 0 ^ x = x
313 if const_val == 0 {
314 return other_id, false
315 }
316 }
317 .shl, .ashr, .lshr {
318 // x << 0 = x >> 0 = x
319 if const_is_rhs && const_val == 0 {
320 return other_id, false
321 }
322 }
323 else {}
324 }
325
326 return -1, false
327}
328