1
2
3
4
5 package ssacompile
6
7 import (
8 "fmt"
9 "strconv"
10 "testing"
11
12 "cmd/compile/internal/ssa/block"
13 "cmd/compile/internal/ssa/ssaop"
14 "cmd/compile/internal/types"
15 )
16
17 func TestFuseEliminatesOneBranch(t *testing.T) {
18 c := testConfig(t)
19 ptrType := c.config.Types.BytePtr
20 fun := c.Fun("entry",
21 Bloc("entry",
22 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
23 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
24 Goto("checkPtr")),
25 Bloc("checkPtr",
26 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
27 Valu("nilptr", ssaop.OpConstNil, ptrType, 0, nil),
28 Valu("bool1", ssaop.OpNeqPtr, c.config.Types.Bool, 0, nil, "ptr1", "nilptr"),
29 If("bool1", "then", "exit")),
30 Bloc("then",
31 Goto("exit")),
32 Bloc("exit",
33 Exit("mem")))
34
35 CheckFunc(fun.f)
36 fuseLate(fun.f)
37
38 for _, b := range fun.f.Blocks {
39 if b == fun.blocks["then"] && b.Kind != block.BlockInvalid {
40 t.Errorf("then was not eliminated, but should have")
41 }
42 }
43 }
44
45 func TestFuseEliminatesBothBranches(t *testing.T) {
46 c := testConfig(t)
47 ptrType := c.config.Types.BytePtr
48 fun := c.Fun("entry",
49 Bloc("entry",
50 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
51 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
52 Goto("checkPtr")),
53 Bloc("checkPtr",
54 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
55 Valu("nilptr", ssaop.OpConstNil, ptrType, 0, nil),
56 Valu("bool1", ssaop.OpNeqPtr, c.config.Types.Bool, 0, nil, "ptr1", "nilptr"),
57 If("bool1", "then", "else")),
58 Bloc("then",
59 Goto("exit")),
60 Bloc("else",
61 Goto("exit")),
62 Bloc("exit",
63 Exit("mem")))
64
65 CheckFunc(fun.f)
66 fuseLate(fun.f)
67
68 for _, b := range fun.f.Blocks {
69 if b == fun.blocks["then"] && b.Kind != block.BlockInvalid {
70 t.Errorf("then was not eliminated, but should have")
71 }
72 if b == fun.blocks["else"] && b.Kind != block.BlockInvalid {
73 t.Errorf("else was not eliminated, but should have")
74 }
75 }
76 }
77
78 func TestFuseHandlesPhis(t *testing.T) {
79 c := testConfig(t)
80 ptrType := c.config.Types.BytePtr
81 fun := c.Fun("entry",
82 Bloc("entry",
83 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
84 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
85 Goto("checkPtr")),
86 Bloc("checkPtr",
87 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
88 Valu("nilptr", ssaop.OpConstNil, ptrType, 0, nil),
89 Valu("bool1", ssaop.OpNeqPtr, c.config.Types.Bool, 0, nil, "ptr1", "nilptr"),
90 If("bool1", "then", "else")),
91 Bloc("then",
92 Goto("exit")),
93 Bloc("else",
94 Goto("exit")),
95 Bloc("exit",
96 Valu("phi", ssaop.OpPhi, ptrType, 0, nil, "ptr1", "ptr1"),
97 Exit("mem")))
98
99 CheckFunc(fun.f)
100 fuseLate(fun.f)
101
102 for _, b := range fun.f.Blocks {
103 if b == fun.blocks["then"] && b.Kind != block.BlockInvalid {
104 t.Errorf("then was not eliminated, but should have")
105 }
106 if b == fun.blocks["else"] && b.Kind != block.BlockInvalid {
107 t.Errorf("else was not eliminated, but should have")
108 }
109 }
110 }
111
112 func TestFuseEliminatesEmptyBlocks(t *testing.T) {
113 c := testConfig(t)
114
115
116
117
118
119
120
121
122
123
124
125
126 fun := c.Fun("entry",
127 Bloc("entry",
128 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
129 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
130 Goto("z0")),
131 Bloc("z1",
132 Goto("z2")),
133 Bloc("z3",
134 Goto("exit")),
135 Bloc("z2",
136 Goto("z3")),
137 Bloc("z0",
138 Goto("z1")),
139 Bloc("exit",
140 Exit("mem"),
141 ))
142
143 CheckFunc(fun.f)
144 fuseLate(fun.f)
145
146 for k, b := range fun.blocks {
147 if k[:1] == "z" && b.Kind != block.BlockInvalid {
148 t.Errorf("case1 %s was not eliminated, but should have", k)
149 }
150 }
151
152
153
154
155
156
157
158 fun = c.Fun("entry",
159 Bloc("entry",
160 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
161 Valu("c", ssaop.OpArg, c.config.Types.Bool, 0, nil),
162 If("c", "z0", "z1")),
163 Bloc("z0",
164 Goto("exit")),
165 Bloc("z1",
166 Goto("exit")),
167 Bloc("exit",
168 Exit("mem"),
169 ))
170
171 CheckFunc(fun.f)
172 fuseLate(fun.f)
173
174 for k, b := range fun.blocks {
175 if k[:1] == "z" && b.Kind != block.BlockInvalid {
176 t.Errorf("case2 %s was not eliminated, but should have", k)
177 }
178 }
179
180
181
182
183
184
185
186
187
188 fun = c.Fun("entry",
189 Bloc("entry",
190 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
191 Valu("c1", ssaop.OpArg, c.config.Types.Bool, 0, nil),
192 If("c1", "b0", "z0")),
193 Bloc("b0",
194 Valu("c2", ssaop.OpArg, c.config.Types.Bool, 0, nil),
195 If("c2", "z1", "z0")),
196 Bloc("z0",
197 Goto("exit")),
198 Bloc("z1",
199 Goto("exit")),
200 Bloc("exit",
201 Exit("mem"),
202 ))
203
204 CheckFunc(fun.f)
205 fuseLate(fun.f)
206
207 for k, b := range fun.blocks {
208 if k[:1] == "z" && b.Kind != block.BlockInvalid {
209 t.Errorf("case3 %s was not eliminated, but should have", k)
210 }
211 }
212 }
213
214 func TestFuseSideEffects(t *testing.T) {
215 c := testConfig(t)
216
217
218
219 fun := c.Fun("entry",
220 Bloc("entry",
221 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
222 Valu("b", ssaop.OpArg, c.config.Types.Bool, 0, nil),
223 If("b", "then", "else")),
224 Bloc("then",
225 Valu("call1", ssaop.OpStaticCall, types.TypeMem, 0, AuxCallLSym("_"), "mem"),
226 Goto("empty")),
227 Bloc("else",
228 Valu("call2", ssaop.OpStaticCall, types.TypeMem, 0, AuxCallLSym("_"), "mem"),
229 Goto("empty")),
230 Bloc("empty",
231 Goto("loop")),
232 Bloc("loop",
233 Goto("loop")))
234
235 CheckFunc(fun.f)
236 fuseLate(fun.f)
237
238 for _, b := range fun.f.Blocks {
239 if b == fun.blocks["then"] && b.Kind == block.BlockInvalid {
240 t.Errorf("then is eliminated, but should not")
241 }
242 if b == fun.blocks["else"] && b.Kind == block.BlockInvalid {
243 t.Errorf("else is eliminated, but should not")
244 }
245 }
246
247
248
249
250
251
252
253 fun = c.Fun("entry",
254 Bloc("entry",
255 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
256 Valu("c1", ssaop.OpArg, c.config.Types.Bool, 0, nil),
257 Valu("p", ssaop.OpArg, c.config.Types.IntPtr, 0, nil),
258 If("c1", "z0", "exit")),
259 Bloc("z0",
260 Valu("nilcheck", ssaop.OpNilCheck, c.config.Types.IntPtr, 0, nil, "p", "mem"),
261 Goto("exit")),
262 Bloc("exit",
263 Exit("mem"),
264 ))
265 CheckFunc(fun.f)
266 fuseLate(fun.f)
267 z0, ok := fun.blocks["z0"]
268 if !ok || z0.Kind == block.BlockInvalid {
269 t.Errorf("case2 z0 is eliminated, but should not")
270 }
271 }
272
273 func TestFuseHandlesDifferentiatedPhi(t *testing.T) {
274 c := testConfig(t)
275 fun := c.Fun("entry",
276 Bloc("entry",
277 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
278 Valu("a1", ssaop.OpArg, c.config.Types.UInt64, 0, nil),
279 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
280 Valu("constTrue", ssaop.OpConstBool, c.config.Types.Bool, 1, nil),
281 Valu("lower", ssaop.OpConst64, c.config.Types.UInt64, 16, nil),
282 Valu("upper", ssaop.OpConst64, c.config.Types.UInt64, 512, nil),
283 Goto("checkUpper")),
284 Bloc("checkUpper",
285 Valu("bool1", ssaop.OpLeq64U, c.config.Types.Bool, 0, nil, "a1", "upper"),
286 If("bool1", "checkLower", "exit")),
287 Bloc("checkLower",
288 Valu("bool2", ssaop.OpLeq64U, c.config.Types.Bool, 0, nil, "lower", "a1"),
289 If("bool2", "empty", "exit")),
290 Bloc("empty",
291 Goto("exit")),
292 Bloc("exit",
293 Valu("phi", ssaop.OpPhi, c.config.Types.Bool, 0, nil, "bool1", "constTrue", "constTrue"),
294 Exit("mem")))
295
296 CheckFunc(fun.f)
297 fuse(fun.f, fuseTypeIntInRange)
298 phi := fun.values["phi"]
299 cTrue := fun.values["constTrue"]
300 if phi.Op == ssaop.OpCopy && phi.Args[0] == cTrue {
301 t.Errorf("phi mangled into always true")
302 }
303 }
304
305 func BenchmarkFuse(b *testing.B) {
306 for _, n := range [...]int{1, 10, 100, 1000, 10000} {
307 b.Run(strconv.Itoa(n), func(b *testing.B) {
308 c := testConfig(b)
309
310 blocks := make([]bloc, 0, 2*n+3)
311 blocks = append(blocks,
312 Bloc("entry",
313 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
314 Valu("cond", ssaop.OpArg, c.config.Types.Bool, 0, nil),
315 Valu("x", ssaop.OpArg, c.config.Types.Int64, 0, nil),
316 Goto("exit")))
317
318 phiArgs := make([]string, 0, 2*n)
319 for i := 0; i < n; i++ {
320 cname := fmt.Sprintf("c%d", i)
321 blocks = append(blocks,
322 Bloc(fmt.Sprintf("b%d", i), If("cond", cname, "merge")),
323 Bloc(cname, Goto("merge")))
324 phiArgs = append(phiArgs, "x", "x")
325 }
326 blocks = append(blocks,
327 Bloc("merge",
328 Valu("phi", ssaop.OpPhi, types.TypeMem, 0, nil, phiArgs...),
329 Goto("exit")),
330 Bloc("exit",
331 Exit("mem")))
332
333 b.ResetTimer()
334 for i := 0; i < b.N; i++ {
335 fun := c.Fun("entry", blocks...)
336 fuseLate(fun.f)
337 }
338 })
339 }
340 }
341
View as plain text