github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/src/cmd/compile/internal/ssa/nilcheck.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  // TODO: return value from newobject/newarray is non-nil.
     8  
     9  // nilcheckelim eliminates unnecessary nil checks.
    10  func nilcheckelim(f *Func) {
    11  	// A nil check is redundant if the same nil check was successful in a
    12  	// dominating block. The efficacy of this pass depends heavily on the
    13  	// efficacy of the cse pass.
    14  	idom := dominators(f)
    15  	domTree := make([][]*Block, f.NumBlocks())
    16  
    17  	// Create a block ID -> [dominees] mapping
    18  	for _, b := range f.Blocks {
    19  		if dom := idom[b.ID]; dom != nil {
    20  			domTree[dom.ID] = append(domTree[dom.ID], b)
    21  		}
    22  	}
    23  
    24  	// TODO: Eliminate more nil checks.
    25  	// We can recursively remove any chain of fixed offset calculations,
    26  	// i.e. struct fields and array elements, even with non-constant
    27  	// indices: x is non-nil iff x.a.b[i].c is.
    28  
    29  	type walkState int
    30  	const (
    31  		Work   walkState = iota // clear nil check if we should and traverse to dominees regardless
    32  		RecPtr                  // record the pointer as being nil checked
    33  		ClearPtr
    34  	)
    35  
    36  	type bp struct {
    37  		block *Block // block, or nil in RecPtr/ClearPtr state
    38  		ptr   *Value // if non-nil, ptr that is to be set/cleared in RecPtr/ClearPtr state
    39  		op    walkState
    40  	}
    41  
    42  	work := make([]bp, 0, 256)
    43  	work = append(work, bp{block: f.Entry})
    44  
    45  	// map from value ID to bool indicating if value is known to be non-nil
    46  	// in the current dominator path being walked. This slice is updated by
    47  	// walkStates to maintain the known non-nil values.
    48  	nonNilValues := make([]bool, f.NumValues())
    49  
    50  	// make an initial pass identifying any non-nil values
    51  	for _, b := range f.Blocks {
    52  		// a value resulting from taking the address of a
    53  		// value, or a value constructed from an offset of a
    54  		// non-nil ptr (OpAddPtr) implies it is non-nil
    55  		for _, v := range b.Values {
    56  			if v.Op == OpAddr || v.Op == OpAddPtr {
    57  				nonNilValues[v.ID] = true
    58  			} else if v.Op == OpPhi {
    59  				// phis whose arguments are all non-nil
    60  				// are non-nil
    61  				argsNonNil := true
    62  				for _, a := range v.Args {
    63  					if !nonNilValues[a.ID] {
    64  						argsNonNil = false
    65  					}
    66  				}
    67  				if argsNonNil {
    68  					nonNilValues[v.ID] = true
    69  				}
    70  			}
    71  		}
    72  	}
    73  
    74  	// perform a depth first walk of the dominee tree
    75  	for len(work) > 0 {
    76  		node := work[len(work)-1]
    77  		work = work[:len(work)-1]
    78  
    79  		switch node.op {
    80  		case Work:
    81  			checked := checkedptr(node.block) // ptr being checked for nil/non-nil
    82  			nonnil := nonnilptr(node.block)   // ptr that is non-nil due to this blocks pred
    83  
    84  			if checked != nil {
    85  				// already have a nilcheck in the dominator path, or this block is a success
    86  				// block for the same value it is checking
    87  				if nonNilValues[checked.ID] || checked == nonnil {
    88  					// Eliminate the nil check.
    89  					// The deadcode pass will remove vestigial values,
    90  					// and the fuse pass will join this block with its successor.
    91  
    92  					// Logging in the style of the former compiler -- and omit line 1,
    93  					// which is usually in generated code.
    94  					if f.Config.Debug_checknil() && node.block.Control.Line > 1 {
    95  						f.Config.Warnl(node.block.Control.Line, "removed nil check")
    96  					}
    97  
    98  					switch node.block.Kind {
    99  					case BlockIf:
   100  						node.block.Kind = BlockFirst
   101  						node.block.SetControl(nil)
   102  					case BlockCheck:
   103  						node.block.Kind = BlockPlain
   104  						node.block.SetControl(nil)
   105  					default:
   106  						f.Fatalf("bad block kind in nilcheck %s", node.block.Kind)
   107  					}
   108  				}
   109  			}
   110  
   111  			if nonnil != nil && !nonNilValues[nonnil.ID] {
   112  				// this is a new nilcheck so add a ClearPtr node to clear the
   113  				// ptr from the map of nil checks once we traverse
   114  				// back up the tree
   115  				work = append(work, bp{op: ClearPtr, ptr: nonnil})
   116  			}
   117  
   118  			// add all dominated blocks to the work list
   119  			for _, w := range domTree[node.block.ID] {
   120  				work = append(work, bp{block: w})
   121  			}
   122  
   123  			if nonnil != nil && !nonNilValues[nonnil.ID] {
   124  				work = append(work, bp{op: RecPtr, ptr: nonnil})
   125  			}
   126  		case RecPtr:
   127  			nonNilValues[node.ptr.ID] = true
   128  			continue
   129  		case ClearPtr:
   130  			nonNilValues[node.ptr.ID] = false
   131  			continue
   132  		}
   133  	}
   134  }
   135  
   136  // checkedptr returns the Value, if any,
   137  // that is used in a nil check in b's Control op.
   138  func checkedptr(b *Block) *Value {
   139  	if b.Kind == BlockCheck {
   140  		return b.Control.Args[0]
   141  	}
   142  	if b.Kind == BlockIf && b.Control.Op == OpIsNonNil {
   143  		return b.Control.Args[0]
   144  	}
   145  	return nil
   146  }
   147  
   148  // nonnilptr returns the Value, if any,
   149  // that is non-nil due to b being the successor block
   150  // of an OpIsNonNil or OpNilCheck block for the value and having a single
   151  // predecessor.
   152  func nonnilptr(b *Block) *Value {
   153  	if len(b.Preds) == 1 {
   154  		bp := b.Preds[0]
   155  		if bp.Kind == BlockCheck {
   156  			return bp.Control.Args[0]
   157  		}
   158  		if bp.Kind == BlockIf && bp.Control.Op == OpIsNonNil && bp.Succs[0] == b {
   159  			return bp.Control.Args[0]
   160  		}
   161  	}
   162  	return nil
   163  }