github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/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 import ( 8 "cmd/internal/src" 9 ) 10 11 // nilcheckelim eliminates unnecessary nil checks. 12 // runs on machine-independent code. 13 func nilcheckelim(f *Func) { 14 // A nil check is redundant if the same nil check was successful in a 15 // dominating block. The efficacy of this pass depends heavily on the 16 // efficacy of the cse pass. 17 sdom := f.sdom() 18 19 // TODO: Eliminate more nil checks. 20 // We can recursively remove any chain of fixed offset calculations, 21 // i.e. struct fields and array elements, even with non-constant 22 // indices: x is non-nil iff x.a.b[i].c is. 23 24 type walkState int 25 const ( 26 Work walkState = iota // process nil checks and traverse to dominees 27 ClearPtr // forget the fact that ptr is nil 28 ) 29 30 type bp struct { 31 block *Block // block, or nil in ClearPtr state 32 ptr *Value // if non-nil, ptr that is to be cleared in ClearPtr state 33 op walkState 34 } 35 36 work := make([]bp, 0, 256) 37 work = append(work, bp{block: f.Entry}) 38 39 // map from value ID to bool indicating if value is known to be non-nil 40 // in the current dominator path being walked. This slice is updated by 41 // walkStates to maintain the known non-nil values. 42 nonNilValues := make([]bool, f.NumValues()) 43 44 // make an initial pass identifying any non-nil values 45 for _, b := range f.Blocks { 46 for _, v := range b.Values { 47 // a value resulting from taking the address of a 48 // value, or a value constructed from an offset of a 49 // non-nil ptr (OpAddPtr) implies it is non-nil 50 if v.Op == OpAddr || v.Op == OpLocalAddr || v.Op == OpAddPtr || v.Op == OpOffPtr { 51 nonNilValues[v.ID] = true 52 } 53 } 54 } 55 56 for changed := true; changed; { 57 changed = false 58 for _, b := range f.Blocks { 59 for _, v := range b.Values { 60 // phis whose arguments are all non-nil 61 // are non-nil 62 if v.Op == OpPhi { 63 argsNonNil := true 64 for _, a := range v.Args { 65 if !nonNilValues[a.ID] { 66 argsNonNil = false 67 break 68 } 69 } 70 if argsNonNil { 71 if !nonNilValues[v.ID] { 72 changed = true 73 } 74 nonNilValues[v.ID] = true 75 } 76 } 77 } 78 } 79 } 80 81 // allocate auxiliary date structures for computing store order 82 sset := f.newSparseSet(f.NumValues()) 83 defer f.retSparseSet(sset) 84 storeNumber := make([]int32, f.NumValues()) 85 86 // perform a depth first walk of the dominee tree 87 for len(work) > 0 { 88 node := work[len(work)-1] 89 work = work[:len(work)-1] 90 91 switch node.op { 92 case Work: 93 b := node.block 94 95 // First, see if we're dominated by an explicit nil check. 96 if len(b.Preds) == 1 { 97 p := b.Preds[0].b 98 if p.Kind == BlockIf && p.Control.Op == OpIsNonNil && p.Succs[0].b == b { 99 ptr := p.Control.Args[0] 100 if !nonNilValues[ptr.ID] { 101 nonNilValues[ptr.ID] = true 102 work = append(work, bp{op: ClearPtr, ptr: ptr}) 103 } 104 } 105 } 106 107 // Next, order values in the current block w.r.t. stores. 108 b.Values = storeOrder(b.Values, sset, storeNumber) 109 110 pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block 111 pendingLines.clear() 112 113 // Next, process values in the block. 114 i := 0 115 for _, v := range b.Values { 116 b.Values[i] = v 117 i++ 118 switch v.Op { 119 case OpIsNonNil: 120 ptr := v.Args[0] 121 if nonNilValues[ptr.ID] { 122 if v.Pos.IsStmt() == src.PosIsStmt { // Boolean true is a terrible statement boundary. 123 pendingLines.add(v.Pos.Line()) 124 v.Pos = v.Pos.WithNotStmt() 125 } 126 // This is a redundant explicit nil check. 127 v.reset(OpConstBool) 128 v.AuxInt = 1 // true 129 } 130 case OpNilCheck: 131 ptr := v.Args[0] 132 if nonNilValues[ptr.ID] { 133 // This is a redundant implicit nil check. 134 // Logging in the style of the former compiler -- and omit line 1, 135 // which is usually in generated code. 136 if f.fe.Debug_checknil() && v.Pos.Line() > 1 { 137 f.Warnl(v.Pos, "removed nil check") 138 } 139 if v.Pos.IsStmt() == src.PosIsStmt { // About to lose a statement boundary 140 pendingLines.add(v.Pos.Line()) 141 } 142 v.reset(OpUnknown) 143 f.freeValue(v) 144 i-- 145 continue 146 } 147 // Record the fact that we know ptr is non nil, and remember to 148 // undo that information when this dominator subtree is done. 149 nonNilValues[ptr.ID] = true 150 work = append(work, bp{op: ClearPtr, ptr: ptr}) 151 fallthrough // a non-eliminated nil check might be a good place for a statement boundary. 152 default: 153 if pendingLines.contains(v.Pos.Line()) && v.Pos.IsStmt() != src.PosNotStmt { 154 v.Pos = v.Pos.WithIsStmt() 155 pendingLines.remove(v.Pos.Line()) 156 } 157 } 158 } 159 if pendingLines.contains(b.Pos.Line()) { 160 b.Pos = b.Pos.WithIsStmt() 161 pendingLines.remove(b.Pos.Line()) 162 } 163 for j := i; j < len(b.Values); j++ { 164 b.Values[j] = nil 165 } 166 b.Values = b.Values[:i] 167 168 // Add all dominated blocks to the work list. 169 for w := sdom[node.block.ID].child; w != nil; w = sdom[w.ID].sibling { 170 work = append(work, bp{op: Work, block: w}) 171 } 172 173 case ClearPtr: 174 nonNilValues[node.ptr.ID] = false 175 continue 176 } 177 } 178 } 179 180 // All platforms are guaranteed to fault if we load/store to anything smaller than this address. 181 // 182 // This should agree with minLegalPointer in the runtime. 183 const minZeroPage = 4096 184 185 // nilcheckelim2 eliminates unnecessary nil checks. 186 // Runs after lowering and scheduling. 187 func nilcheckelim2(f *Func) { 188 unnecessary := f.newSparseSet(f.NumValues()) 189 defer f.retSparseSet(unnecessary) 190 191 pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block 192 193 for _, b := range f.Blocks { 194 // Walk the block backwards. Find instructions that will fault if their 195 // input pointer is nil. Remove nil checks on those pointers, as the 196 // faulting instruction effectively does the nil check for free. 197 unnecessary.clear() 198 pendingLines.clear() 199 // Optimization: keep track of removed nilcheck with smallest index 200 firstToRemove := len(b.Values) 201 for i := len(b.Values) - 1; i >= 0; i-- { 202 v := b.Values[i] 203 if opcodeTable[v.Op].nilCheck && unnecessary.contains(v.Args[0].ID) { 204 if f.fe.Debug_checknil() && v.Pos.Line() > 1 { 205 f.Warnl(v.Pos, "removed nil check") 206 } 207 if v.Pos.IsStmt() == src.PosIsStmt { 208 pendingLines.add(v.Pos.Line()) 209 } 210 v.reset(OpUnknown) 211 firstToRemove = i 212 continue 213 } 214 if v.Type.IsMemory() || v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() { 215 if v.Op == OpVarDef || v.Op == OpVarKill || v.Op == OpVarLive { 216 // These ops don't really change memory. 217 continue 218 } 219 // This op changes memory. Any faulting instruction after v that 220 // we've recorded in the unnecessary map is now obsolete. 221 unnecessary.clear() 222 } 223 224 // Find any pointers that this op is guaranteed to fault on if nil. 225 var ptrstore [2]*Value 226 ptrs := ptrstore[:0] 227 if opcodeTable[v.Op].faultOnNilArg0 { 228 ptrs = append(ptrs, v.Args[0]) 229 } 230 if opcodeTable[v.Op].faultOnNilArg1 { 231 ptrs = append(ptrs, v.Args[1]) 232 } 233 for _, ptr := range ptrs { 234 // Check to make sure the offset is small. 235 switch opcodeTable[v.Op].auxType { 236 case auxSymOff: 237 if v.Aux != nil || v.AuxInt < 0 || v.AuxInt >= minZeroPage { 238 continue 239 } 240 case auxSymValAndOff: 241 off := ValAndOff(v.AuxInt).Off() 242 if v.Aux != nil || off < 0 || off >= minZeroPage { 243 continue 244 } 245 case auxInt32: 246 // Mips uses this auxType for atomic add constant. It does not affect the effective address. 247 case auxInt64: 248 // ARM uses this auxType for duffcopy/duffzero/alignment info. 249 // It does not affect the effective address. 250 case auxNone: 251 // offset is zero. 252 default: 253 v.Fatalf("can't handle aux %s (type %d) yet\n", v.auxString(), int(opcodeTable[v.Op].auxType)) 254 } 255 // This instruction is guaranteed to fault if ptr is nil. 256 // Any previous nil check op is unnecessary. 257 unnecessary.add(ptr.ID) 258 } 259 } 260 // Remove values we've clobbered with OpUnknown. 261 i := firstToRemove 262 for j := i; j < len(b.Values); j++ { 263 v := b.Values[j] 264 if v.Op != OpUnknown { 265 if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos.Line()) { 266 v.Pos = v.Pos.WithIsStmt() 267 pendingLines.remove(v.Pos.Line()) 268 } 269 b.Values[i] = v 270 i++ 271 } 272 } 273 274 if pendingLines.contains(b.Pos.Line()) { 275 b.Pos = b.Pos.WithIsStmt() 276 } 277 278 for j := i; j < len(b.Values); j++ { 279 b.Values[j] = nil 280 } 281 b.Values = b.Values[:i] 282 283 // TODO: if b.Kind == BlockPlain, start the analysis in the subsequent block to find 284 // more unnecessary nil checks. Would fix test/nilptr3_ssa.go:157. 285 } 286 }