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  }