1
2
3
4
5 package ssacompile
6
7 import (
8 "strconv"
9 "testing"
10
11 "cmd/compile/internal/ssa"
12 "cmd/compile/internal/ssa/block"
13 "cmd/compile/internal/ssa/ssaop"
14 "cmd/compile/internal/types"
15 )
16
17 func BenchmarkNilCheckDeep1(b *testing.B) { benchmarkNilCheckDeep(b, 1) }
18 func BenchmarkNilCheckDeep10(b *testing.B) { benchmarkNilCheckDeep(b, 10) }
19 func BenchmarkNilCheckDeep100(b *testing.B) { benchmarkNilCheckDeep(b, 100) }
20 func BenchmarkNilCheckDeep1000(b *testing.B) { benchmarkNilCheckDeep(b, 1000) }
21 func BenchmarkNilCheckDeep10000(b *testing.B) { benchmarkNilCheckDeep(b, 10000) }
22
23
24
25
26
27 func benchmarkNilCheckDeep(b *testing.B, depth int) {
28 c := testConfig(b)
29 ptrType := c.config.Types.BytePtr
30
31 var blocs []bloc
32 blocs = append(blocs,
33 Bloc("entry",
34 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
35 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
36 Goto(blockn(0)),
37 ),
38 )
39 for i := 0; i < depth; i++ {
40 blocs = append(blocs,
41 Bloc(blockn(i),
42 Valu(ptrn(i), ssaop.OpAddr, ptrType, 0, nil, "sb"),
43 Valu(booln(i), ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, ptrn(i)),
44 If(booln(i), blockn(i+1), "exit"),
45 ),
46 )
47 }
48 blocs = append(blocs,
49 Bloc(blockn(depth), Goto("exit")),
50 Bloc("exit", Exit("mem")),
51 )
52
53 fun := c.Fun("entry", blocs...)
54
55 CheckFunc(fun.f)
56 b.SetBytes(int64(depth))
57 b.ResetTimer()
58 b.ReportAllocs()
59
60 for i := 0; i < b.N; i++ {
61 nilcheckelim(fun.f)
62 }
63 }
64
65 func blockn(n int) string { return "b" + strconv.Itoa(n) }
66 func ptrn(n int) string { return "p" + strconv.Itoa(n) }
67 func booln(n int) string { return "c" + strconv.Itoa(n) }
68
69 func isNilCheck(b *ssa.Block) bool {
70 return b.Kind == block.BlockIf && b.Controls[0].Op == ssaop.OpIsNonNil
71 }
72
73
74 func TestNilcheckSimple(t *testing.T) {
75 c := testConfig(t)
76 ptrType := c.config.Types.BytePtr
77 fun := c.Fun("entry",
78 Bloc("entry",
79 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
80 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
81 Goto("checkPtr")),
82 Bloc("checkPtr",
83 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
84 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
85 If("bool1", "secondCheck", "exit")),
86 Bloc("secondCheck",
87 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
88 If("bool2", "extra", "exit")),
89 Bloc("extra",
90 Goto("exit")),
91 Bloc("exit",
92 Exit("mem")))
93
94 CheckFunc(fun.f)
95 nilcheckelim(fun.f)
96
97
98 fuse(fun.f, fuseTypePlain)
99 deadcode(fun.f)
100
101 CheckFunc(fun.f)
102 for _, b := range fun.f.Blocks {
103 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
104 t.Errorf("secondCheck was not eliminated")
105 }
106 }
107 }
108
109
110
111 func TestNilcheckDomOrder(t *testing.T) {
112 c := testConfig(t)
113 ptrType := c.config.Types.BytePtr
114 fun := c.Fun("entry",
115 Bloc("entry",
116 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
117 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
118 Goto("checkPtr")),
119 Bloc("checkPtr",
120 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
121 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
122 If("bool1", "secondCheck", "exit")),
123 Bloc("exit",
124 Exit("mem")),
125 Bloc("secondCheck",
126 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
127 If("bool2", "extra", "exit")),
128 Bloc("extra",
129 Goto("exit")))
130
131 CheckFunc(fun.f)
132 nilcheckelim(fun.f)
133
134
135 fuse(fun.f, fuseTypePlain)
136 deadcode(fun.f)
137
138 CheckFunc(fun.f)
139 for _, b := range fun.f.Blocks {
140 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
141 t.Errorf("secondCheck was not eliminated")
142 }
143 }
144 }
145
146
147 func TestNilcheckAddr(t *testing.T) {
148 c := testConfig(t)
149 ptrType := c.config.Types.BytePtr
150 fun := c.Fun("entry",
151 Bloc("entry",
152 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
153 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
154 Goto("checkPtr")),
155 Bloc("checkPtr",
156 Valu("ptr1", ssaop.OpAddr, ptrType, 0, nil, "sb"),
157 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
158 If("bool1", "extra", "exit")),
159 Bloc("extra",
160 Goto("exit")),
161 Bloc("exit",
162 Exit("mem")))
163
164 CheckFunc(fun.f)
165 nilcheckelim(fun.f)
166
167
168 fuse(fun.f, fuseTypePlain)
169 deadcode(fun.f)
170
171 CheckFunc(fun.f)
172 for _, b := range fun.f.Blocks {
173 if b == fun.blocks["checkPtr"] && isNilCheck(b) {
174 t.Errorf("checkPtr was not eliminated")
175 }
176 }
177 }
178
179
180 func TestNilcheckAddPtr(t *testing.T) {
181 c := testConfig(t)
182 ptrType := c.config.Types.BytePtr
183 fun := c.Fun("entry",
184 Bloc("entry",
185 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
186 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
187 Goto("checkPtr")),
188 Bloc("checkPtr",
189 Valu("off", ssaop.OpConst64, c.config.Types.Int64, 20, nil),
190 Valu("ptr1", ssaop.OpAddPtr, ptrType, 0, nil, "sb", "off"),
191 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
192 If("bool1", "extra", "exit")),
193 Bloc("extra",
194 Goto("exit")),
195 Bloc("exit",
196 Exit("mem")))
197
198 CheckFunc(fun.f)
199 nilcheckelim(fun.f)
200
201
202 fuse(fun.f, fuseTypePlain)
203 deadcode(fun.f)
204
205 CheckFunc(fun.f)
206 for _, b := range fun.f.Blocks {
207 if b == fun.blocks["checkPtr"] && isNilCheck(b) {
208 t.Errorf("checkPtr was not eliminated")
209 }
210 }
211 }
212
213
214
215 func TestNilcheckPhi(t *testing.T) {
216 c := testConfig(t)
217 ptrType := c.config.Types.BytePtr
218 fun := c.Fun("entry",
219 Bloc("entry",
220 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
221 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
222 Valu("sp", ssaop.OpSP, c.config.Types.Uintptr, 0, nil),
223 Valu("baddr", ssaop.OpLocalAddr, c.config.Types.Bool, 0, ssa.StringToAux("b"), "sp", "mem"),
224 Valu("bool1", ssaop.OpLoad, c.config.Types.Bool, 0, nil, "baddr", "mem"),
225 If("bool1", "b1", "b2")),
226 Bloc("b1",
227 Valu("ptr1", ssaop.OpAddr, ptrType, 0, nil, "sb"),
228 Goto("checkPtr")),
229 Bloc("b2",
230 Valu("ptr2", ssaop.OpAddr, ptrType, 0, nil, "sb"),
231 Goto("checkPtr")),
232
233 Bloc("checkPtr",
234 Valu("phi", ssaop.OpPhi, ptrType, 0, nil, "ptr1", "ptr2"),
235 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "phi"),
236 If("bool2", "extra", "exit")),
237 Bloc("extra",
238 Goto("exit")),
239 Bloc("exit",
240 Exit("mem")))
241
242 CheckFunc(fun.f)
243 nilcheckelim(fun.f)
244
245
246 fuse(fun.f, fuseTypePlain)
247 deadcode(fun.f)
248
249 CheckFunc(fun.f)
250 for _, b := range fun.f.Blocks {
251 if b == fun.blocks["checkPtr"] && isNilCheck(b) {
252 t.Errorf("checkPtr was not eliminated")
253 }
254 }
255 }
256
257
258
259 func TestNilcheckKeepRemove(t *testing.T) {
260 c := testConfig(t)
261 ptrType := c.config.Types.BytePtr
262 fun := c.Fun("entry",
263 Bloc("entry",
264 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
265 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
266 Goto("checkPtr")),
267 Bloc("checkPtr",
268 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
269 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
270 If("bool1", "differentCheck", "exit")),
271 Bloc("differentCheck",
272 Valu("ptr2", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
273 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr2"),
274 If("bool2", "secondCheck", "exit")),
275 Bloc("secondCheck",
276 Valu("bool3", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
277 If("bool3", "extra", "exit")),
278 Bloc("extra",
279 Goto("exit")),
280 Bloc("exit",
281 Exit("mem")))
282
283 CheckFunc(fun.f)
284 nilcheckelim(fun.f)
285
286
287 fuse(fun.f, fuseTypePlain)
288 deadcode(fun.f)
289
290 CheckFunc(fun.f)
291 foundDifferentCheck := false
292 for _, b := range fun.f.Blocks {
293 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
294 t.Errorf("secondCheck was not eliminated")
295 }
296 if b == fun.blocks["differentCheck"] && isNilCheck(b) {
297 foundDifferentCheck = true
298 }
299 }
300 if !foundDifferentCheck {
301 t.Errorf("removed differentCheck, but shouldn't have")
302 }
303 }
304
305
306
307 func TestNilcheckInFalseBranch(t *testing.T) {
308 c := testConfig(t)
309 ptrType := c.config.Types.BytePtr
310 fun := c.Fun("entry",
311 Bloc("entry",
312 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
313 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
314 Goto("checkPtr")),
315 Bloc("checkPtr",
316 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
317 Valu("bool1", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
318 If("bool1", "extra", "secondCheck")),
319 Bloc("secondCheck",
320 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
321 If("bool2", "extra", "thirdCheck")),
322 Bloc("thirdCheck",
323 Valu("bool3", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
324 If("bool3", "extra", "exit")),
325 Bloc("extra",
326 Goto("exit")),
327 Bloc("exit",
328 Exit("mem")))
329
330 CheckFunc(fun.f)
331 nilcheckelim(fun.f)
332
333
334 fuse(fun.f, fuseTypePlain)
335 deadcode(fun.f)
336
337 CheckFunc(fun.f)
338 foundSecondCheck := false
339 foundThirdCheck := false
340 for _, b := range fun.f.Blocks {
341 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
342 foundSecondCheck = true
343 }
344 if b == fun.blocks["thirdCheck"] && isNilCheck(b) {
345 foundThirdCheck = true
346 }
347 }
348 if !foundSecondCheck {
349 t.Errorf("removed secondCheck, but shouldn't have [false branch]")
350 }
351 if !foundThirdCheck {
352 t.Errorf("removed thirdCheck, but shouldn't have [false branch]")
353 }
354 }
355
356
357
358 func TestNilcheckUser(t *testing.T) {
359 c := testConfig(t)
360 ptrType := c.config.Types.BytePtr
361 fun := c.Fun("entry",
362 Bloc("entry",
363 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
364 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
365 Goto("checkPtr")),
366 Bloc("checkPtr",
367 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
368 Valu("nilptr", ssaop.OpConstNil, ptrType, 0, nil),
369 Valu("bool1", ssaop.OpNeqPtr, c.config.Types.Bool, 0, nil, "ptr1", "nilptr"),
370 If("bool1", "secondCheck", "exit")),
371 Bloc("secondCheck",
372 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
373 If("bool2", "extra", "exit")),
374 Bloc("extra",
375 Goto("exit")),
376 Bloc("exit",
377 Exit("mem")))
378
379 CheckFunc(fun.f)
380
381 opt(fun.f)
382 nilcheckelim(fun.f)
383
384
385 fuse(fun.f, fuseTypePlain)
386 deadcode(fun.f)
387
388 CheckFunc(fun.f)
389 for _, b := range fun.f.Blocks {
390 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
391 t.Errorf("secondCheck was not eliminated")
392 }
393 }
394 }
395
396
397 func TestNilcheckBug(t *testing.T) {
398 c := testConfig(t)
399 ptrType := c.config.Types.BytePtr
400 fun := c.Fun("entry",
401 Bloc("entry",
402 Valu("mem", ssaop.OpInitMem, types.TypeMem, 0, nil),
403 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
404 Goto("checkPtr")),
405 Bloc("checkPtr",
406 Valu("ptr1", ssaop.OpLoad, ptrType, 0, nil, "sb", "mem"),
407 Valu("nilptr", ssaop.OpConstNil, ptrType, 0, nil),
408 Valu("bool1", ssaop.OpNeqPtr, c.config.Types.Bool, 0, nil, "ptr1", "nilptr"),
409 If("bool1", "secondCheck", "couldBeNil")),
410 Bloc("couldBeNil",
411 Goto("secondCheck")),
412 Bloc("secondCheck",
413 Valu("bool2", ssaop.OpIsNonNil, c.config.Types.Bool, 0, nil, "ptr1"),
414 If("bool2", "extra", "exit")),
415 Bloc("extra",
416
417 Valu("store", ssaop.OpStore, types.TypeMem, 0, ptrType, "ptr1", "nilptr", "mem"),
418 Goto("exit")),
419 Bloc("exit",
420 Valu("phi", ssaop.OpPhi, types.TypeMem, 0, nil, "mem", "store"),
421 Exit("phi")))
422
423 CheckFunc(fun.f)
424
425 opt(fun.f)
426 nilcheckelim(fun.f)
427
428
429 fuse(fun.f, fuseTypePlain)
430 deadcode(fun.f)
431
432 CheckFunc(fun.f)
433 foundSecondCheck := false
434 for _, b := range fun.f.Blocks {
435 if b == fun.blocks["secondCheck"] && isNilCheck(b) {
436 foundSecondCheck = true
437 }
438 }
439 if !foundSecondCheck {
440 t.Errorf("secondCheck was eliminated, but shouldn't have")
441 }
442 }
443
View as plain text