github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/escape/escape.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 "fmt" 9 10 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 11 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 12 "github.com/bir3/gocompiler/src/cmd/compile/internal/logopt" 13 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 14 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 15 ) 16 17 // Escape analysis. 18 // 19 // Here we analyze functions to determine which Go variables 20 // (including implicit allocations such as calls to "new" or "make", 21 // composite literals, etc.) can be allocated on the stack. The two 22 // key invariants we have to ensure are: (1) pointers to stack objects 23 // cannot be stored in the heap, and (2) pointers to a stack object 24 // cannot outlive that object (e.g., because the declaring function 25 // returned and destroyed the object's stack frame, or its space is 26 // reused across loop iterations for logically distinct variables). 27 // 28 // We implement this with a static data-flow analysis of the AST. 29 // First, we construct a directed weighted graph where vertices 30 // (termed "locations") represent variables allocated by statements 31 // and expressions, and edges represent assignments between variables 32 // (with weights representing addressing/dereference counts). 33 // 34 // Next we walk the graph looking for assignment paths that might 35 // violate the invariants stated above. If a variable v's address is 36 // stored in the heap or elsewhere that may outlive it, then v is 37 // marked as requiring heap allocation. 38 // 39 // To support interprocedural analysis, we also record data-flow from 40 // each function's parameters to the heap and to its result 41 // parameters. This information is summarized as "parameter tags", 42 // which are used at static call sites to improve escape analysis of 43 // function arguments. 44 45 // Constructing the location graph. 46 // 47 // Every allocating statement (e.g., variable declaration) or 48 // expression (e.g., "new" or "make") is first mapped to a unique 49 // "location." 50 // 51 // We also model every Go assignment as a directed edges between 52 // locations. The number of dereference operations minus the number of 53 // addressing operations is recorded as the edge's weight (termed 54 // "derefs"). For example: 55 // 56 // p = &q // -1 57 // p = q // 0 58 // p = *q // 1 59 // p = **q // 2 60 // 61 // p = **&**&q // 2 62 // 63 // Note that the & operator can only be applied to addressable 64 // expressions, and the expression &x itself is not addressable, so 65 // derefs cannot go below -1. 66 // 67 // Every Go language construct is lowered into this representation, 68 // generally without sensitivity to flow, path, or context; and 69 // without distinguishing elements within a compound variable. For 70 // example: 71 // 72 // var x struct { f, g *int } 73 // var u []*int 74 // 75 // x.f = u[0] 76 // 77 // is modeled simply as 78 // 79 // x = *u 80 // 81 // That is, we don't distinguish x.f from x.g, or u[0] from u[1], 82 // u[2], etc. However, we do record the implicit dereference involved 83 // in indexing a slice. 84 85 // A batch holds escape analysis state that's shared across an entire 86 // batch of functions being analyzed at once. 87 type batch struct { 88 allLocs []*location 89 closures []closure 90 91 heapLoc location 92 blankLoc location 93 } 94 95 // A closure holds a closure expression and its spill hole (i.e., 96 // where the hole representing storing into its closure record). 97 type closure struct { 98 k hole 99 clo *ir.ClosureExpr 100 } 101 102 // An escape holds state specific to a single function being analyzed 103 // within a batch. 104 type escape struct { 105 *batch 106 107 curfn *ir.Func // function being analyzed 108 109 labels map[*types.Sym]labelState // known labels 110 111 // loopDepth counts the current loop nesting depth within 112 // curfn. It increments within each "for" loop and at each 113 // label with a corresponding backwards "goto" (i.e., 114 // unstructured loop). 115 loopDepth int 116 } 117 118 func Funcs(all []ir.Node) { 119 ir.VisitFuncsBottomUp(all, Batch) 120 } 121 122 // Batch performs escape analysis on a minimal batch of 123 // functions. 124 func Batch(fns []*ir.Func, recursive bool) { 125 for _, fn := range fns { 126 if fn.Op() != ir.ODCLFUNC { 127 base.Fatalf("unexpected node: %v", fn) 128 } 129 } 130 131 var b batch 132 b.heapLoc.escapes = true 133 134 // Construct data-flow graph from syntax trees. 135 for _, fn := range fns { 136 if base.Flag.W > 1 { 137 s := fmt.Sprintf("\nbefore escape %v", fn) 138 ir.Dump(s, fn) 139 } 140 b.initFunc(fn) 141 } 142 for _, fn := range fns { 143 if !fn.IsHiddenClosure() { 144 b.walkFunc(fn) 145 } 146 } 147 148 // We've walked the function bodies, so we've seen everywhere a 149 // variable might be reassigned or have it's address taken. Now we 150 // can decide whether closures should capture their free variables 151 // by value or reference. 152 for _, closure := range b.closures { 153 b.flowClosure(closure.k, closure.clo) 154 } 155 b.closures = nil 156 157 for _, loc := range b.allLocs { 158 if why := HeapAllocReason(loc.n); why != "" { 159 b.flow(b.heapHole().addr(loc.n, why), loc) 160 } 161 } 162 163 b.walkAll() 164 b.finish(fns) 165 } 166 167 func (b *batch) with(fn *ir.Func) *escape { 168 return &escape{ 169 batch: b, 170 curfn: fn, 171 loopDepth: 1, 172 } 173 } 174 175 func (b *batch) initFunc(fn *ir.Func) { 176 e := b.with(fn) 177 if fn.Esc() != escFuncUnknown { 178 base.Fatalf("unexpected node: %v", fn) 179 } 180 fn.SetEsc(escFuncPlanned) 181 if base.Flag.LowerM > 3 { 182 ir.Dump("escAnalyze", fn) 183 } 184 185 // Allocate locations for local variables. 186 for _, n := range fn.Dcl { 187 e.newLoc(n, false) 188 } 189 190 // Also for hidden parameters (e.g., the ".this" parameter to a 191 // method value wrapper). 192 if fn.OClosure == nil { 193 for _, n := range fn.ClosureVars { 194 e.newLoc(n.Canonical(), false) 195 } 196 } 197 198 // Initialize resultIndex for result parameters. 199 for i, f := range fn.Type().Results().FieldSlice() { 200 e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i 201 } 202 } 203 204 func (b *batch) walkFunc(fn *ir.Func) { 205 e := b.with(fn) 206 fn.SetEsc(escFuncStarted) 207 208 // Identify labels that mark the head of an unstructured loop. 209 ir.Visit(fn, func(n ir.Node) { 210 switch n.Op() { 211 case ir.OLABEL: 212 n := n.(*ir.LabelStmt) 213 if n.Label.IsBlank() { 214 break 215 } 216 if e.labels == nil { 217 e.labels = make(map[*types.Sym]labelState) 218 } 219 e.labels[n.Label] = nonlooping 220 221 case ir.OGOTO: 222 // If we visited the label before the goto, 223 // then this is a looping label. 224 n := n.(*ir.BranchStmt) 225 if e.labels[n.Label] == nonlooping { 226 e.labels[n.Label] = looping 227 } 228 } 229 }) 230 231 e.block(fn.Body) 232 233 if len(e.labels) != 0 { 234 base.FatalfAt(fn.Pos(), "leftover labels after walkFunc") 235 } 236 } 237 238 func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) { 239 for _, cv := range clo.Func.ClosureVars { 240 n := cv.Canonical() 241 loc := b.oldLoc(cv) 242 if !loc.captured { 243 base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv) 244 } 245 246 // Capture by value for variables <= 128 bytes that are never reassigned. 247 n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128) 248 if !n.Byval() { 249 n.SetAddrtaken(true) 250 if n.Sym().Name == typecheck.LocalDictName { 251 base.FatalfAt(n.Pos(), "dictionary variable not captured by value") 252 } 253 } 254 255 if base.Flag.LowerM > 1 { 256 how := "ref" 257 if n.Byval() { 258 how = "value" 259 } 260 base.WarnfAt(n.Pos(), "%v capturing by %s: %v (addr=%v assign=%v width=%d)", n.Curfn, how, n, loc.addrtaken, loc.reassigned, n.Type().Size()) 261 } 262 263 // Flow captured variables to closure. 264 k := k 265 if !cv.Byval() { 266 k = k.addr(cv, "reference") 267 } 268 b.flow(k.note(cv, "captured by a closure"), loc) 269 } 270 } 271 272 func (b *batch) finish(fns []*ir.Func) { 273 // Record parameter tags for package export data. 274 for _, fn := range fns { 275 fn.SetEsc(escFuncTagged) 276 277 narg := 0 278 for _, fs := range &types.RecvsParams { 279 for _, f := range fs(fn.Type()).Fields().Slice() { 280 narg++ 281 f.Note = b.paramTag(fn, narg, f) 282 } 283 } 284 } 285 286 for _, loc := range b.allLocs { 287 n := loc.n 288 if n == nil { 289 continue 290 } 291 if n.Op() == ir.ONAME { 292 n := n.(*ir.Name) 293 n.Opt = nil 294 } 295 296 // Update n.Esc based on escape analysis results. 297 298 // Omit escape diagnostics for go/defer wrappers, at least for now. 299 // Historically, we haven't printed them, and test cases don't expect them. 300 // TODO(mdempsky): Update tests to expect this. 301 goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper() 302 303 if loc.escapes { 304 if n.Op() == ir.ONAME { 305 if base.Flag.CompilingRuntime { 306 base.ErrorfAt(n.Pos(), "%v escapes to heap, not allowed in runtime", n) 307 } 308 if base.Flag.LowerM != 0 { 309 base.WarnfAt(n.Pos(), "moved to heap: %v", n) 310 } 311 } else { 312 if base.Flag.LowerM != 0 && !goDeferWrapper { 313 base.WarnfAt(n.Pos(), "%v escapes to heap", n) 314 } 315 if logopt.Enabled() { 316 var e_curfn *ir.Func // TODO(mdempsky): Fix. 317 logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn)) 318 } 319 } 320 n.SetEsc(ir.EscHeap) 321 } else { 322 if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper { 323 base.WarnfAt(n.Pos(), "%v does not escape", n) 324 } 325 n.SetEsc(ir.EscNone) 326 if loc.transient { 327 switch n.Op() { 328 case ir.OCLOSURE: 329 n := n.(*ir.ClosureExpr) 330 n.SetTransient(true) 331 case ir.OMETHVALUE: 332 n := n.(*ir.SelectorExpr) 333 n.SetTransient(true) 334 case ir.OSLICELIT: 335 n := n.(*ir.CompLitExpr) 336 n.SetTransient(true) 337 } 338 } 339 } 340 } 341 } 342 343 // inMutualBatch reports whether function fn is in the batch of 344 // mutually recursive functions being analyzed. When this is true, 345 // fn has not yet been analyzed, so its parameters and results 346 // should be incorporated directly into the flow graph instead of 347 // relying on its escape analysis tagging. 348 func (e *escape) inMutualBatch(fn *ir.Name) bool { 349 if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged { 350 if fn.Defn.Esc() == escFuncUnknown { 351 base.Fatalf("graph inconsistency: %v", fn) 352 } 353 return true 354 } 355 return false 356 } 357 358 const ( 359 escFuncUnknown = 0 + iota 360 escFuncPlanned 361 escFuncStarted 362 escFuncTagged 363 ) 364 365 // Mark labels that have no backjumps to them as not increasing e.loopdepth. 366 type labelState int 367 368 const ( 369 looping labelState = 1 + iota 370 nonlooping 371 ) 372 373 func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string { 374 name := func() string { 375 if f.Sym != nil { 376 return f.Sym.Name 377 } 378 return fmt.Sprintf("arg#%d", narg) 379 } 380 381 // Only report diagnostics for user code; 382 // not for wrappers generated around them. 383 // TODO(mdempsky): Generalize this. 384 diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok()) 385 386 if len(fn.Body) == 0 { 387 // Assume that uintptr arguments must be held live across the call. 388 // This is most important for syscall.Syscall. 389 // See golang.org/issue/13372. 390 // This really doesn't have much to do with escape analysis per se, 391 // but we are reusing the ability to annotate an individual function 392 // argument and pass those annotations along to importing code. 393 fn.Pragma |= ir.UintptrKeepAlive 394 395 if f.Type.IsUintptr() { 396 if diagnose { 397 base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name()) 398 } 399 return "" 400 } 401 402 if !f.Type.HasPointers() { // don't bother tagging for scalars 403 return "" 404 } 405 406 var esc leaks 407 408 // External functions are assumed unsafe, unless 409 // //go:noescape is given before the declaration. 410 if fn.Pragma&ir.Noescape != 0 { 411 if diagnose && f.Sym != nil { 412 base.WarnfAt(f.Pos, "%v does not escape", name()) 413 } 414 } else { 415 if diagnose && f.Sym != nil { 416 base.WarnfAt(f.Pos, "leaking param: %v", name()) 417 } 418 esc.AddHeap(0) 419 } 420 421 return esc.Encode() 422 } 423 424 if fn.Pragma&ir.UintptrEscapes != 0 { 425 if f.Type.IsUintptr() { 426 if diagnose { 427 base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name()) 428 } 429 return "" 430 } 431 if f.IsDDD() && f.Type.Elem().IsUintptr() { 432 // final argument is ...uintptr. 433 if diagnose { 434 base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name()) 435 } 436 return "" 437 } 438 } 439 440 if !f.Type.HasPointers() { // don't bother tagging for scalars 441 return "" 442 } 443 444 // Unnamed parameters are unused and therefore do not escape. 445 if f.Sym == nil || f.Sym.IsBlank() { 446 var esc leaks 447 return esc.Encode() 448 } 449 450 n := f.Nname.(*ir.Name) 451 loc := b.oldLoc(n) 452 esc := loc.paramEsc 453 esc.Optimize() 454 455 if diagnose && !loc.escapes { 456 if esc.Empty() { 457 base.WarnfAt(f.Pos, "%v does not escape", name()) 458 } 459 if x := esc.Heap(); x >= 0 { 460 if x == 0 { 461 base.WarnfAt(f.Pos, "leaking param: %v", name()) 462 } else { 463 // TODO(mdempsky): Mention level=x like below? 464 base.WarnfAt(f.Pos, "leaking param content: %v", name()) 465 } 466 } 467 for i := 0; i < numEscResults; i++ { 468 if x := esc.Result(i); x >= 0 { 469 res := fn.Type().Results().Field(i).Sym 470 base.WarnfAt(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) 471 } 472 } 473 } 474 475 return esc.Encode() 476 }