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