| // 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 |
| } |