github.com/bir3/gocompiler@v0.9.2202/src/cmd/compile/internal/escape/graph.go (about) 1 // Copyright 2018 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 escape 6 7 import ( 8 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 9 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 10 "github.com/bir3/gocompiler/src/cmd/compile/internal/logopt" 11 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 12 "fmt" 13 ) 14 15 // Below we implement the methods for walking the AST and recording 16 // data flow edges. Note that because a sub-expression might have 17 // side-effects, it's important to always visit the entire AST. 18 // 19 // For example, write either: 20 // 21 // if x { 22 // e.discard(n.Left) 23 // } else { 24 // e.value(k, n.Left) 25 // } 26 // 27 // or 28 // 29 // if x { 30 // k = e.discardHole() 31 // } 32 // e.value(k, n.Left) 33 // 34 // Do NOT write: 35 // 36 // // BAD: possibly loses side-effects within n.Left 37 // if !x { 38 // e.value(k, n.Left) 39 // } 40 41 // A location represents an abstract location that stores a Go 42 // variable. 43 type location struct { 44 n ir.Node // represented variable or expression, if any 45 curfn *ir.Func // enclosing function 46 edges []edge // incoming edges 47 loopDepth int // loopDepth at declaration 48 49 // resultIndex records the tuple index (starting at 1) for 50 // PPARAMOUT variables within their function's result type. 51 // For non-PPARAMOUT variables it's 0. 52 resultIndex int 53 54 // derefs and walkgen are used during walkOne to track the 55 // minimal dereferences from the walk root. 56 derefs int // >= -1 57 walkgen uint32 58 59 // dst and dstEdgeindex track the next immediate assignment 60 // destination location during walkone, along with the index 61 // of the edge pointing back to this location. 62 dst *location 63 dstEdgeIdx int 64 65 // queued is used by walkAll to track whether this location is 66 // in the walk queue. 67 queued bool 68 69 // attrs is a bitset of location attributes. 70 attrs locAttr 71 72 // paramEsc records the represented parameter's leak set. 73 paramEsc leaks 74 75 captured bool // has a closure captured this variable? 76 reassigned bool // has this variable been reassigned? 77 addrtaken bool // has this variable's address been taken? 78 } 79 80 type locAttr uint8 81 82 const ( 83 // attrEscapes indicates whether the represented variable's address 84 // escapes; that is, whether the variable must be heap allocated. 85 attrEscapes locAttr = 1 << iota 86 87 // attrPersists indicates whether the represented expression's 88 // address outlives the statement; that is, whether its storage 89 // cannot be immediately reused. 90 attrPersists 91 92 // attrMutates indicates whether pointers that are reachable from 93 // this location may have their addressed memory mutated. This is 94 // used to detect string->[]byte conversions that can be safely 95 // optimized away. 96 attrMutates 97 98 // attrCalls indicates whether closures that are reachable from this 99 // location may be called without tracking their results. This is 100 // used to better optimize indirect closure calls. 101 attrCalls 102 ) 103 104 func (l *location) hasAttr(attr locAttr) bool { return l.attrs&attr != 0 } 105 106 // An edge represents an assignment edge between two Go variables. 107 type edge struct { 108 src *location 109 derefs int // >= -1 110 notes *note 111 } 112 113 func (l *location) asHole() hole { 114 return hole{dst: l} 115 } 116 117 // leak records that parameter l leaks to sink. 118 func (l *location) leakTo(sink *location, derefs int) { 119 // If sink is a result parameter that doesn't escape (#44614) 120 // and we can fit return bits into the escape analysis tag, 121 // then record as a result leak. 122 if !sink.hasAttr(attrEscapes) && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn { 123 ri := sink.resultIndex - 1 124 if ri < numEscResults { 125 // Leak to result parameter. 126 l.paramEsc.AddResult(ri, derefs) 127 return 128 } 129 } 130 131 // Otherwise, record as heap leak. 132 l.paramEsc.AddHeap(derefs) 133 } 134 135 // leakTo records that parameter l leaks to sink. 136 func (b *batch) leakTo(l, sink *location, derefs int) { 137 if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.hasAttr(attrEscapes) { 138 if base.Flag.LowerM >= 2 { 139 fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(sink), derefs) 140 } 141 explanation := b.explainPath(sink, l) 142 if logopt.Enabled() { 143 var e_curfn *ir.Func // TODO(mdempsky): Fix. 144 logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn), 145 fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(sink), derefs), explanation) 146 } 147 } 148 149 // If sink is a result parameter that doesn't escape (#44614) 150 // and we can fit return bits into the escape analysis tag, 151 // then record as a result leak. 152 if !sink.hasAttr(attrEscapes) && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn { 153 if ri := sink.resultIndex - 1; ri < numEscResults { 154 // Leak to result parameter. 155 l.paramEsc.AddResult(ri, derefs) 156 return 157 } 158 } 159 160 // Otherwise, record as heap leak. 161 l.paramEsc.AddHeap(derefs) 162 } 163 164 func (l *location) isName(c ir.Class) bool { 165 return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c 166 } 167 168 // A hole represents a context for evaluation of a Go 169 // expression. E.g., when evaluating p in "x = **p", we'd have a hole 170 // with dst==x and derefs==2. 171 type hole struct { 172 dst *location 173 derefs int // >= -1 174 notes *note 175 176 // addrtaken indicates whether this context is taking the address of 177 // the expression, independent of whether the address will actually 178 // be stored into a variable. 179 addrtaken bool 180 } 181 182 type note struct { 183 next *note 184 where ir.Node 185 why string 186 } 187 188 func (k hole) note(where ir.Node, why string) hole { 189 if where == nil || why == "" { 190 base.Fatalf("note: missing where/why") 191 } 192 if base.Flag.LowerM >= 2 || logopt.Enabled() { 193 k.notes = ¬e{ 194 next: k.notes, 195 where: where, 196 why: why, 197 } 198 } 199 return k 200 } 201 202 func (k hole) shift(delta int) hole { 203 k.derefs += delta 204 if k.derefs < -1 { 205 base.Fatalf("derefs underflow: %v", k.derefs) 206 } 207 k.addrtaken = delta < 0 208 return k 209 } 210 211 func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) } 212 func (k hole) addr(where ir.Node, why string) hole { return k.shift(-1).note(where, why) } 213 214 func (k hole) dotType(t *types.Type, where ir.Node, why string) hole { 215 if !t.IsInterface() && !types.IsDirectIface(t) { 216 k = k.shift(1) 217 } 218 return k.note(where, why) 219 } 220 221 func (b *batch) flow(k hole, src *location) { 222 if k.addrtaken { 223 src.addrtaken = true 224 } 225 226 dst := k.dst 227 if dst == &b.blankLoc { 228 return 229 } 230 if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ... 231 return 232 } 233 if dst.hasAttr(attrEscapes) && k.derefs < 0 { // dst = &src 234 if base.Flag.LowerM >= 2 || logopt.Enabled() { 235 pos := base.FmtPos(src.n.Pos()) 236 if base.Flag.LowerM >= 2 { 237 fmt.Printf("%s: %v escapes to heap:\n", pos, src.n) 238 } 239 explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{}) 240 if logopt.Enabled() { 241 var e_curfn *ir.Func // TODO(mdempsky): Fix. 242 logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation) 243 } 244 245 } 246 src.attrs |= attrEscapes | attrPersists | attrMutates | attrCalls 247 return 248 } 249 250 // TODO(mdempsky): Deduplicate edges? 251 dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes}) 252 } 253 254 func (b *batch) heapHole() hole { return b.heapLoc.asHole() } 255 func (b *batch) mutatorHole() hole { return b.mutatorLoc.asHole() } 256 func (b *batch) calleeHole() hole { return b.calleeLoc.asHole() } 257 func (b *batch) discardHole() hole { return b.blankLoc.asHole() } 258 259 func (b *batch) oldLoc(n *ir.Name) *location { 260 if n.Canonical().Opt == nil { 261 base.FatalfAt(n.Pos(), "%v has no location", n) 262 } 263 return n.Canonical().Opt.(*location) 264 } 265 266 func (e *escape) newLoc(n ir.Node, persists bool) *location { 267 if e.curfn == nil { 268 base.Fatalf("e.curfn isn't set") 269 } 270 if n != nil && n.Type() != nil && n.Type().NotInHeap() { 271 base.ErrorfAt(n.Pos(), 0, "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type()) 272 } 273 274 if n != nil && n.Op() == ir.ONAME { 275 if canon := n.(*ir.Name).Canonical(); n != canon { 276 base.FatalfAt(n.Pos(), "newLoc on non-canonical %v (canonical is %v)", n, canon) 277 } 278 } 279 loc := &location{ 280 n: n, 281 curfn: e.curfn, 282 loopDepth: e.loopDepth, 283 } 284 if persists { 285 loc.attrs |= attrPersists 286 } 287 e.allLocs = append(e.allLocs, loc) 288 if n != nil { 289 if n.Op() == ir.ONAME { 290 n := n.(*ir.Name) 291 if n.Class == ir.PPARAM && n.Curfn == nil { 292 // ok; hidden parameter 293 } else if n.Curfn != e.curfn { 294 base.FatalfAt(n.Pos(), "curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n) 295 } 296 297 if n.Opt != nil { 298 base.FatalfAt(n.Pos(), "%v already has a location", n) 299 } 300 n.Opt = loc 301 } 302 } 303 return loc 304 } 305 306 // teeHole returns a new hole that flows into each hole of ks, 307 // similar to the Unix tee(1) command. 308 func (e *escape) teeHole(ks ...hole) hole { 309 if len(ks) == 0 { 310 return e.discardHole() 311 } 312 if len(ks) == 1 { 313 return ks[0] 314 } 315 // TODO(mdempsky): Optimize if there's only one non-discard hole? 316 317 // Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a 318 // new temporary location ltmp, wire it into place, and return 319 // a hole for "ltmp = _". 320 loc := e.newLoc(nil, false) 321 for _, k := range ks { 322 // N.B., "p = &q" and "p = &tmp; tmp = q" are not 323 // semantically equivalent. To combine holes like "l1 324 // = _" and "l2 = &_", we'd need to wire them as "l1 = 325 // *ltmp" and "l2 = ltmp" and return "ltmp = &_" 326 // instead. 327 if k.derefs < 0 { 328 base.Fatalf("teeHole: negative derefs") 329 } 330 331 e.flow(k, loc) 332 } 333 return loc.asHole() 334 } 335 336 // later returns a new hole that flows into k, but some time later. 337 // Its main effect is to prevent immediate reuse of temporary 338 // variables introduced during Order. 339 func (e *escape) later(k hole) hole { 340 loc := e.newLoc(nil, true) 341 e.flow(k, loc) 342 return loc.asHole() 343 } 344 345 // Fmt is called from node printing to print information about escape analysis results. 346 func Fmt(n ir.Node) string { 347 text := "" 348 switch n.Esc() { 349 case ir.EscUnknown: 350 break 351 352 case ir.EscHeap: 353 text = "esc(h)" 354 355 case ir.EscNone: 356 text = "esc(no)" 357 358 case ir.EscNever: 359 text = "esc(N)" 360 361 default: 362 text = fmt.Sprintf("esc(%d)", n.Esc()) 363 } 364 365 if n.Op() == ir.ONAME { 366 n := n.(*ir.Name) 367 if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 { 368 if text != "" { 369 text += " " 370 } 371 text += fmt.Sprintf("ld(%d)", loc.loopDepth) 372 } 373 } 374 375 return text 376 }