github.com/bir3/gocompiler@v0.3.205/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 // An 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 // escapes reports whether the represented variable's address 70 // escapes; that is, whether the variable must be heap 71 // allocated. 72 escapes bool 73 74 // transient reports whether the represented expression's 75 // address does not outlive the statement; that is, whether 76 // its storage can be immediately reused. 77 transient bool 78 79 // paramEsc records the represented parameter's leak set. 80 paramEsc leaks 81 82 captured bool // has a closure captured this variable? 83 reassigned bool // has this variable been reassigned? 84 addrtaken bool // has this variable's address been taken? 85 } 86 87 // An edge represents an assignment edge between two Go variables. 88 type edge struct { 89 src *location 90 derefs int // >= -1 91 notes *note 92 } 93 94 func (l *location) asHole() hole { 95 return hole{dst: l} 96 } 97 98 // leak records that parameter l leaks to sink. 99 func (l *location) leakTo(sink *location, derefs int) { 100 // If sink is a result parameter that doesn't escape (#44614) 101 // and we can fit return bits into the escape analysis tag, 102 // then record as a result leak. 103 if !sink.escapes && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn { 104 ri := sink.resultIndex - 1 105 if ri < numEscResults { 106 // Leak to result parameter. 107 l.paramEsc.AddResult(ri, derefs) 108 return 109 } 110 } 111 112 // Otherwise, record as heap leak. 113 l.paramEsc.AddHeap(derefs) 114 } 115 116 func (l *location) isName(c ir.Class) bool { 117 return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c 118 } 119 120 // A hole represents a context for evaluation of a Go 121 // expression. E.g., when evaluating p in "x = **p", we'd have a hole 122 // with dst==x and derefs==2. 123 type hole struct { 124 dst *location 125 derefs int // >= -1 126 notes *note 127 128 // addrtaken indicates whether this context is taking the address of 129 // the expression, independent of whether the address will actually 130 // be stored into a variable. 131 addrtaken bool 132 } 133 134 type note struct { 135 next *note 136 where ir.Node 137 why string 138 } 139 140 func (k hole) note(where ir.Node, why string) hole { 141 if where == nil || why == "" { 142 base.Fatalf("note: missing where/why") 143 } 144 if base.Flag.LowerM >= 2 || logopt.Enabled() { 145 k.notes = ¬e{ 146 next: k.notes, 147 where: where, 148 why: why, 149 } 150 } 151 return k 152 } 153 154 func (k hole) shift(delta int) hole { 155 k.derefs += delta 156 if k.derefs < -1 { 157 base.Fatalf("derefs underflow: %v", k.derefs) 158 } 159 k.addrtaken = delta < 0 160 return k 161 } 162 163 func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) } 164 func (k hole) addr(where ir.Node, why string) hole { return k.shift(-1).note(where, why) } 165 166 func (k hole) dotType(t *types.Type, where ir.Node, why string) hole { 167 if !t.IsInterface() && !types.IsDirectIface(t) { 168 k = k.shift(1) 169 } 170 return k.note(where, why) 171 } 172 173 func (b *batch) flow(k hole, src *location) { 174 if k.addrtaken { 175 src.addrtaken = true 176 } 177 178 dst := k.dst 179 if dst == &b.blankLoc { 180 return 181 } 182 if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ... 183 return 184 } 185 if dst.escapes && k.derefs < 0 { // dst = &src 186 if base.Flag.LowerM >= 2 || logopt.Enabled() { 187 pos := base.FmtPos(src.n.Pos()) 188 if base.Flag.LowerM >= 2 { 189 fmt.Printf("%s: %v escapes to heap:\n", pos, src.n) 190 } 191 explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{}) 192 if logopt.Enabled() { 193 var e_curfn *ir.Func // TODO(mdempsky): Fix. 194 logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation) 195 } 196 197 } 198 src.escapes = true 199 return 200 } 201 202 // TODO(mdempsky): Deduplicate edges? 203 dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes}) 204 } 205 206 func (b *batch) heapHole() hole { return b.heapLoc.asHole() } 207 func (b *batch) discardHole() hole { return b.blankLoc.asHole() } 208 209 func (b *batch) oldLoc(n *ir.Name) *location { 210 if n.Canonical().Opt == nil { 211 base.Fatalf("%v has no location", n) 212 } 213 return n.Canonical().Opt.(*location) 214 } 215 216 func (e *escape) newLoc(n ir.Node, transient bool) *location { 217 if e.curfn == nil { 218 base.Fatalf("e.curfn isn't set") 219 } 220 if n != nil && n.Type() != nil && n.Type().NotInHeap() { 221 base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type()) 222 } 223 224 if n != nil && n.Op() == ir.ONAME { 225 if canon := n.(*ir.Name).Canonical(); n != canon { 226 base.Fatalf("newLoc on non-canonical %v (canonical is %v)", n, canon) 227 } 228 } 229 loc := &location{ 230 n: n, 231 curfn: e.curfn, 232 loopDepth: e.loopDepth, 233 transient: transient, 234 } 235 e.allLocs = append(e.allLocs, loc) 236 if n != nil { 237 if n.Op() == ir.ONAME { 238 n := n.(*ir.Name) 239 if n.Class == ir.PPARAM && n.Curfn == nil { 240 // ok; hidden parameter 241 } else if n.Curfn != e.curfn { 242 base.Fatalf("curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n) 243 } 244 245 if n.Opt != nil { 246 base.Fatalf("%v already has a location", n) 247 } 248 n.Opt = loc 249 } 250 } 251 return loc 252 } 253 254 // teeHole returns a new hole that flows into each hole of ks, 255 // similar to the Unix tee(1) command. 256 func (e *escape) teeHole(ks ...hole) hole { 257 if len(ks) == 0 { 258 return e.discardHole() 259 } 260 if len(ks) == 1 { 261 return ks[0] 262 } 263 // TODO(mdempsky): Optimize if there's only one non-discard hole? 264 265 // Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a 266 // new temporary location ltmp, wire it into place, and return 267 // a hole for "ltmp = _". 268 loc := e.newLoc(nil, true) 269 for _, k := range ks { 270 // N.B., "p = &q" and "p = &tmp; tmp = q" are not 271 // semantically equivalent. To combine holes like "l1 272 // = _" and "l2 = &_", we'd need to wire them as "l1 = 273 // *ltmp" and "l2 = ltmp" and return "ltmp = &_" 274 // instead. 275 if k.derefs < 0 { 276 base.Fatalf("teeHole: negative derefs") 277 } 278 279 e.flow(k, loc) 280 } 281 return loc.asHole() 282 } 283 284 // later returns a new hole that flows into k, but some time later. 285 // Its main effect is to prevent immediate reuse of temporary 286 // variables introduced during Order. 287 func (e *escape) later(k hole) hole { 288 loc := e.newLoc(nil, false) 289 e.flow(k, loc) 290 return loc.asHole() 291 } 292 293 // Fmt is called from node printing to print information about escape analysis results. 294 func Fmt(n ir.Node) string { 295 text := "" 296 switch n.Esc() { 297 case ir.EscUnknown: 298 break 299 300 case ir.EscHeap: 301 text = "esc(h)" 302 303 case ir.EscNone: 304 text = "esc(no)" 305 306 case ir.EscNever: 307 text = "esc(N)" 308 309 default: 310 text = fmt.Sprintf("esc(%d)", n.Esc()) 311 } 312 313 if n.Op() == ir.ONAME { 314 n := n.(*ir.Name) 315 if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 { 316 if text != "" { 317 text += " " 318 } 319 text += fmt.Sprintf("ld(%d)", loc.loopDepth) 320 } 321 } 322 323 return text 324 }