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