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