blob: 4833ac472d78d0a15974eb419ddd5947143db226 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
// nilcheckelim eliminates unnecessary nil checks.
func nilcheckelim(f *Func) {
// A nil check is redundant if the same nil check was successful in a
// dominating block. The efficacy of this pass depends heavily on the
// efficacy of the cse pass.
idom := dominators(f)
domTree := make([][]*Block, f.NumBlocks())
// Create a block ID -> [dominees] mapping
for _, b := range f.Blocks {
if dom := idom[b.ID]; dom != nil {
domTree[dom.ID] = append(domTree[dom.ID], b)
}
}
// TODO: Eliminate more nil checks.
// We can recursively remove any chain of fixed offset calculations,
// i.e. struct fields and array elements, even with non-constant
// indices: x is non-nil iff x.a.b[i].c is.
type walkState int
const (
Work walkState = iota // clear nil check if we should and traverse to dominees regardless
RecPtr // record the pointer as being nil checked
ClearPtr
)
type bp struct {
block *Block // block, or nil in RecPtr/ClearPtr state
ptr *Value // if non-nil, ptr that is to be set/cleared in RecPtr/ClearPtr state
op walkState
}
work := make([]bp, 0, 256)
work = append(work, bp{block: f.Entry, ptr: checkedptr(f.Entry)})
// 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
// walkStates to maintain the known non-nil values.
nonNilValues := make([]bool, f.NumValues())
// make an initial pass identifying any non-nil values
for _, b := range f.Blocks {
// a value resulting from taking the address of a
// value, or a value constructed from an offset of a
// non-nil ptr (OpAddPtr) implies it is non-nil
for _, v := range b.Values {
if v.Op == OpAddr || v.Op == OpAddPtr {
nonNilValues[v.ID] = true
} else if v.Op == OpPhi {
// phis whose arguments are all non-nil
// are non-nil
argsNonNil := true
for _, a := range v.Args {
if !nonNilValues[a.ID] {
argsNonNil = false
}
}
if argsNonNil {
nonNilValues[v.ID] = true
}
}
}
}
// perform a depth first walk of the dominee tree
for len(work) > 0 {
node := work[len(work)-1]
work = work[:len(work)-1]
var pushRecPtr bool
switch node.op {
case Work:
if node.ptr != nil {
// already have a nilcheck in the dominator path
if nonNilValues[node.ptr.ID] {
// Eliminate the nil check.
// The deadcode pass will remove vestigial values,
// and the fuse pass will join this block with its successor.
node.block.Kind = BlockPlain
node.block.Control = nil
f.removePredecessor(node.block, node.block.Succs[1])
node.block.Succs = node.block.Succs[:1]
} else {
// new nilcheck so add a ClearPtr node to clear the
// ptr from the map of nil checks once we traverse
// back up the tree
work = append(work, bp{op: ClearPtr, ptr: node.ptr})
// and cause a new setPtr to be appended after the
// block's dominees
pushRecPtr = true
}
}
case RecPtr:
nonNilValues[node.ptr.ID] = true
continue
case ClearPtr:
nonNilValues[node.ptr.ID] = false
continue
}
var nilBranch *Block
for _, w := range domTree[node.block.ID] {
// TODO: Since we handle the false side of OpIsNonNil
// correctly, look into rewriting user nil checks into
// OpIsNonNil so they can be eliminated also
// we are about to traverse down the 'ptr is nil' side
// of a nilcheck block, so save it for later
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 = BlockPlain
b.Control = nil
f.removePredecessor(b, b.Succs[1])
b.Succs = b.Succs[:1]
}
}
}
// checkedptr returns the Value, if any,
// that is used in a nil check in b's Control op.
func checkedptr(b *Block) *Value {
if b.Kind == BlockIf && b.Control.Op == OpIsNonNil {
return b.Control.Args[0]
}
return nil
}