mirror of
https://github.com/golang/go
synced 2024-10-05 21:21:21 -06:00
[dev.ssa] cmd/compile: clean up nilcheck logic
Be more clear about the two conditions that we care about: 1) a block that performs a nil check (OpIsNonNil), which may be removed 2) a block that is the non-nil sucessor for an OpIsNonNil block Now we only care about removing nilchecks for two scenarios: - a type 1 block is dominated by a type 2 block for the same value - a block is both type 1 and type 2 for the same value Fixes math/big. Change-Id: I50018a4014830461ddfe2a2daf588468e4a8f0b4 Reviewed-on: https://go-review.googlesource.com/14325 Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
1fc52b61f2
commit
6bf383c7b3
@ -38,7 +38,7 @@ func nilcheckelim(f *Func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
work := make([]bp, 0, 256)
|
work := make([]bp, 0, 256)
|
||||||
work = append(work, bp{block: f.Entry, ptr: checkedptr(f.Entry)})
|
work = append(work, bp{block: f.Entry})
|
||||||
|
|
||||||
// map from value ID to bool indicating if value is known to be non-nil
|
// map from value ID to bool indicating if value is known to be non-nil
|
||||||
// in the current dominator path being walked. This slice is updated by
|
// in the current dominator path being walked. This slice is updated by
|
||||||
@ -74,26 +74,37 @@ func nilcheckelim(f *Func) {
|
|||||||
node := work[len(work)-1]
|
node := work[len(work)-1]
|
||||||
work = work[:len(work)-1]
|
work = work[:len(work)-1]
|
||||||
|
|
||||||
var pushRecPtr bool
|
|
||||||
switch node.op {
|
switch node.op {
|
||||||
case Work:
|
case Work:
|
||||||
if node.ptr != nil {
|
checked := checkedptr(node.block) // ptr being checked for nil/non-nil
|
||||||
// already have a nilcheck in the dominator path
|
nonnil := nonnilptr(node.block) // ptr that is non-nil due to this blocks pred
|
||||||
if nonNilValues[node.ptr.ID] {
|
|
||||||
|
if checked != nil {
|
||||||
|
// already have a nilcheck in the dominator path, or this block is a success
|
||||||
|
// block for the same value it is checking
|
||||||
|
if nonNilValues[checked.ID] || checked == nonnil {
|
||||||
// Eliminate the nil check.
|
// Eliminate the nil check.
|
||||||
// The deadcode pass will remove vestigial values,
|
// The deadcode pass will remove vestigial values,
|
||||||
// and the fuse pass will join this block with its successor.
|
// and the fuse pass will join this block with its successor.
|
||||||
node.block.Kind = BlockFirst
|
node.block.Kind = BlockFirst
|
||||||
node.block.Control = nil
|
node.block.Control = nil
|
||||||
} else {
|
}
|
||||||
// new nilcheck so add a ClearPtr node to clear the
|
}
|
||||||
|
|
||||||
|
if nonnil != nil && !nonNilValues[nonnil.ID] {
|
||||||
|
// this is a new nilcheck so add a ClearPtr node to clear the
|
||||||
// ptr from the map of nil checks once we traverse
|
// ptr from the map of nil checks once we traverse
|
||||||
// back up the tree
|
// back up the tree
|
||||||
work = append(work, bp{op: ClearPtr, ptr: node.ptr})
|
work = append(work, bp{op: ClearPtr, ptr: nonnil})
|
||||||
// and cause a new setPtr to be appended after the
|
|
||||||
// block's dominees
|
|
||||||
pushRecPtr = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add all dominated blocks to the work list
|
||||||
|
for _, w := range domTree[node.block.ID] {
|
||||||
|
work = append(work, bp{block: w})
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonnil != nil && !nonNilValues[nonnil.ID] {
|
||||||
|
work = append(work, bp{op: RecPtr, ptr: nonnil})
|
||||||
}
|
}
|
||||||
case RecPtr:
|
case RecPtr:
|
||||||
nonNilValues[node.ptr.ID] = true
|
nonNilValues[node.ptr.ID] = true
|
||||||
@ -102,77 +113,6 @@ func nilcheckelim(f *Func) {
|
|||||||
nonNilValues[node.ptr.ID] = false
|
nonNilValues[node.ptr.ID] = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var nilBranch *Block
|
|
||||||
for _, w := range domTree[node.block.ID] {
|
|
||||||
// We are about to traverse down the 'ptr is nil' side
|
|
||||||
// of a nilcheck block, so save it for later. This doesn't
|
|
||||||
// remove nil checks on the false side of the OpIsNonNil branch.
|
|
||||||
// This is important otherwise we would remove nil checks that
|
|
||||||
// are not redundant.
|
|
||||||
if node.block.Kind == BlockIf && node.block.Control.Op == OpIsNonNil &&
|
|
||||||
w == node.block.Succs[1] {
|
|
||||||
nilBranch = w
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
work = append(work, bp{block: w, ptr: checkedptr(w)})
|
|
||||||
}
|
|
||||||
|
|
||||||
if nilBranch != nil {
|
|
||||||
// we pop from the back of the work slice, so this sets
|
|
||||||
// up the false branch to be operated on before the
|
|
||||||
// node.ptr is recorded
|
|
||||||
work = append(work, bp{op: RecPtr, ptr: node.ptr})
|
|
||||||
work = append(work, bp{block: nilBranch, ptr: checkedptr(nilBranch)})
|
|
||||||
} else if pushRecPtr {
|
|
||||||
work = append(work, bp{op: RecPtr, ptr: node.ptr})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nilcheckelim0 is the original redundant nilcheck elimination algorithm.
|
|
||||||
func nilcheckelim0(f *Func) {
|
|
||||||
// Exit early if there are no nil checks to eliminate.
|
|
||||||
var found bool
|
|
||||||
for _, b := range f.Blocks {
|
|
||||||
if checkedptr(b) != nil {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eliminate redundant nil checks.
|
|
||||||
// A nil check is redundant if the same
|
|
||||||
// nil check has been performed by a
|
|
||||||
// dominating block.
|
|
||||||
// The efficacy of this pass depends
|
|
||||||
// heavily on the efficacy of the cse pass.
|
|
||||||
idom := dominators(f) // TODO: cache the dominator tree in the function, clearing when the CFG changes?
|
|
||||||
for _, b := range f.Blocks {
|
|
||||||
ptr := checkedptr(b)
|
|
||||||
if ptr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var elim bool
|
|
||||||
// Walk up the dominator tree,
|
|
||||||
// looking for identical nil checks.
|
|
||||||
// TODO: This loop is O(n^2). See BenchmarkNilCheckDeep*.
|
|
||||||
for c := idom[b.ID]; c != nil; c = idom[c.ID] {
|
|
||||||
if checkedptr(c) == ptr {
|
|
||||||
elim = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if elim {
|
|
||||||
// Eliminate the nil check.
|
|
||||||
// The deadcode pass will remove vestigial values,
|
|
||||||
// and the fuse pass will join this block with its successor.
|
|
||||||
b.Kind = BlockFirst
|
|
||||||
b.Control = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,3 +124,17 @@ func checkedptr(b *Block) *Value {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nonnilptr returns the Value, if any,
|
||||||
|
// that is non-nil due to b being the success block
|
||||||
|
// of an OpIsNonNil block for the value and having a single
|
||||||
|
// predecessor.
|
||||||
|
func nonnilptr(b *Block) *Value {
|
||||||
|
if len(b.Preds) == 1 {
|
||||||
|
bp := b.Preds[0]
|
||||||
|
if bp.Kind == BlockIf && bp.Control.Op == OpIsNonNil && bp.Succs[0] == b {
|
||||||
|
return bp.Control.Args[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -382,3 +382,48 @@ func TestNilcheckUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNilcheckBug reproduces a bug in nilcheckelim found by compiling math/big
|
||||||
|
func TestNilcheckBug(t *testing.T) {
|
||||||
|
ptrType := &TypeImpl{Size_: 8, Ptr: true, Name: "testptr"} // dummy for testing
|
||||||
|
c := NewConfig("amd64", DummyFrontend{t})
|
||||||
|
fun := Fun(c, "entry",
|
||||||
|
Bloc("entry",
|
||||||
|
Valu("mem", OpArg, TypeMem, 0, ".mem"),
|
||||||
|
Valu("sb", OpSB, TypeInvalid, 0, nil),
|
||||||
|
Goto("checkPtr")),
|
||||||
|
Bloc("checkPtr",
|
||||||
|
Valu("ptr1", OpConstPtr, ptrType, 0, nil, "sb"),
|
||||||
|
Valu("nilptr", OpConstNil, ptrType, 0, nil, "sb"),
|
||||||
|
Valu("bool1", OpNeqPtr, TypeBool, 0, nil, "ptr1", "nilptr"),
|
||||||
|
If("bool1", "secondCheck", "couldBeNil")),
|
||||||
|
Bloc("couldBeNil",
|
||||||
|
Goto("secondCheck")),
|
||||||
|
Bloc("secondCheck",
|
||||||
|
Valu("bool2", OpIsNonNil, TypeBool, 0, nil, "ptr1"),
|
||||||
|
If("bool2", "extra", "exit")),
|
||||||
|
Bloc("extra",
|
||||||
|
Goto("exit")),
|
||||||
|
Bloc("exit",
|
||||||
|
Exit("mem")))
|
||||||
|
|
||||||
|
CheckFunc(fun.f)
|
||||||
|
// we need the opt here to rewrite the user nilcheck
|
||||||
|
opt(fun.f)
|
||||||
|
nilcheckelim(fun.f)
|
||||||
|
|
||||||
|
// clean up the removed nil check
|
||||||
|
fuse(fun.f)
|
||||||
|
deadcode(fun.f)
|
||||||
|
|
||||||
|
CheckFunc(fun.f)
|
||||||
|
foundSecondCheck := false
|
||||||
|
for _, b := range fun.f.Blocks {
|
||||||
|
if b == fun.blocks["secondCheck"] && isNilCheck(b) {
|
||||||
|
foundSecondCheck = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundSecondCheck {
|
||||||
|
t.Errorf("secondCheck was eliminated, but shouldn't have")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3
src/cmd/dist/test.go
vendored
Executable file → Normal file
3
src/cmd/dist/test.go
vendored
Executable file → Normal file
@ -281,9 +281,6 @@ func (t *tester) registerSSATest(pkg string) {
|
|||||||
// known failures due to GOGC=off
|
// known failures due to GOGC=off
|
||||||
case "runtime", "runtime/pprof", "runtime/trace", "sync":
|
case "runtime", "runtime/pprof", "runtime/trace", "sync":
|
||||||
return
|
return
|
||||||
// TODO: fix these failures
|
|
||||||
case "math/big", "cmd/compile/internal/big":
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
t.tests = append(t.tests, distTest{
|
t.tests = append(t.tests, distTest{
|
||||||
name: "go_test_ssa:" + pkg,
|
name: "go_test_ssa:" + pkg,
|
||||||
|
Loading…
Reference in New Issue
Block a user