1
2
3
4
5 package escape
6
7 import (
8 "fmt"
9 "go/constant"
10 "go/token"
11 "internal/goexperiment"
12
13 "cmd/compile/internal/base"
14 "cmd/compile/internal/ir"
15 "cmd/compile/internal/logopt"
16 "cmd/compile/internal/typecheck"
17 "cmd/compile/internal/types"
18 "cmd/internal/src"
19 )
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 type batch struct {
92 allLocs []*location
93 closures []closure
94 reassignOracles map[*ir.Func]*ir.ReassignOracle
95
96 heapLoc location
97 mutatorLoc location
98 calleeLoc location
99 blankLoc location
100 }
101
102
103
104 type closure struct {
105 k hole
106 clo *ir.ClosureExpr
107 }
108
109
110
111 type escape struct {
112 *batch
113
114 curfn *ir.Func
115
116 labels map[*types.Sym]labelState
117
118
119
120
121
122 loopDepth int
123 }
124
125 func Funcs(all []*ir.Func) {
126
127
128
129 reassignOracles := make(map[*ir.Func]*ir.ReassignOracle)
130
131 ir.VisitFuncsBottomUp(all, func(list []*ir.Func, recursive bool) {
132 Batch(list, reassignOracles)
133 })
134 }
135
136
137
138 func Batch(fns []*ir.Func, reassignOracles map[*ir.Func]*ir.ReassignOracle) {
139 var b batch
140 b.heapLoc.attrs = attrEscapes | attrPersists | attrMutates | attrCalls
141 b.mutatorLoc.attrs = attrMutates
142 b.calleeLoc.attrs = attrCalls
143 b.reassignOracles = reassignOracles
144
145
146 for _, fn := range fns {
147 if base.Flag.W > 1 {
148 s := fmt.Sprintf("\nbefore escape %v", fn)
149 ir.Dump(s, fn)
150 }
151 b.initFunc(fn)
152 }
153 for _, fn := range fns {
154 if !fn.IsClosure() {
155 b.walkFunc(fn)
156 }
157 }
158
159
160
161
162
163 for _, closure := range b.closures {
164 b.flowClosure(closure.k, closure.clo)
165 }
166 b.closures = nil
167
168 for _, loc := range b.allLocs {
169
170 b.rewriteWithLiterals(loc.n, loc.curfn)
171
172
173
174 if why := HeapAllocReason(loc.n); why != "" {
175 b.flow(b.heapHole().addr(loc.n, why), loc)
176 }
177 }
178
179 b.walkAll()
180 b.finish(fns)
181 }
182
183 func (b *batch) with(fn *ir.Func) *escape {
184 return &escape{
185 batch: b,
186 curfn: fn,
187 loopDepth: 1,
188 }
189 }
190
191 func (b *batch) initFunc(fn *ir.Func) {
192 e := b.with(fn)
193 if fn.Esc() != escFuncUnknown {
194 base.Fatalf("unexpected node: %v", fn)
195 }
196 fn.SetEsc(escFuncPlanned)
197 if base.Flag.LowerM > 3 {
198 ir.Dump("escAnalyze", fn)
199 }
200
201
202 for _, n := range fn.Dcl {
203 e.newLoc(n, true)
204 }
205
206
207
208 if fn.OClosure == nil {
209 for _, n := range fn.ClosureVars {
210 e.newLoc(n.Canonical(), true)
211 }
212 }
213
214
215 for i, f := range fn.Type().Results() {
216 e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
217 }
218 }
219
220 func (b *batch) walkFunc(fn *ir.Func) {
221 e := b.with(fn)
222 fn.SetEsc(escFuncStarted)
223
224
225 ir.Visit(fn, func(n ir.Node) {
226 switch n.Op() {
227 case ir.OLABEL:
228 n := n.(*ir.LabelStmt)
229 if n.Label.IsBlank() {
230 break
231 }
232 if e.labels == nil {
233 e.labels = make(map[*types.Sym]labelState)
234 }
235 e.labels[n.Label] = nonlooping
236
237 case ir.OGOTO:
238
239
240 n := n.(*ir.BranchStmt)
241 if e.labels[n.Label] == nonlooping {
242 e.labels[n.Label] = looping
243 }
244 }
245 })
246
247 e.block(fn.Body)
248
249 if len(e.labels) != 0 {
250 base.FatalfAt(fn.Pos(), "leftover labels after walkFunc")
251 }
252 }
253
254 func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) {
255 for _, cv := range clo.Func.ClosureVars {
256 n := cv.Canonical()
257 loc := b.oldLoc(cv)
258 if !loc.captured {
259 base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv)
260 }
261
262
263 n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128)
264 if !n.Byval() {
265 n.SetAddrtaken(true)
266 if n.Sym().Name == typecheck.LocalDictName {
267 base.FatalfAt(n.Pos(), "dictionary variable not captured by value")
268 }
269 }
270
271 if base.Flag.LowerM > 1 {
272 how := "ref"
273 if n.Byval() {
274 how = "value"
275 }
276 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())
277 }
278
279
280 k := k
281 if !cv.Byval() {
282 k = k.addr(cv, "reference")
283 }
284 b.flow(k.note(cv, "captured by a closure"), loc)
285 }
286 }
287
288 func (b *batch) finish(fns []*ir.Func) {
289
290 for _, fn := range fns {
291 fn.SetEsc(escFuncTagged)
292
293 for i, param := range fn.Type().RecvParams() {
294 param.Note = b.paramTag(fn, 1+i, param)
295 }
296 }
297
298 for _, loc := range b.allLocs {
299 n := loc.n
300 if n == nil {
301 continue
302 }
303
304 if n.Op() == ir.ONAME {
305 n := n.(*ir.Name)
306 n.Opt = nil
307 }
308
309
310
311
312
313
314 goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
315
316 if loc.hasAttr(attrEscapes) {
317 if n.Op() == ir.ONAME {
318 if base.Flag.CompilingRuntime {
319 base.ErrorfAt(n.Pos(), 0, "%v escapes to heap, not allowed in runtime", n)
320 }
321 if base.Flag.LowerM != 0 {
322 base.WarnfAt(n.Pos(), "moved to heap: %v", n)
323 }
324 } else {
325 if base.Flag.LowerM != 0 && !goDeferWrapper {
326 if n.Op() == ir.OAPPEND {
327 base.WarnfAt(n.Pos(), "append escapes to heap")
328 } else {
329 base.WarnfAt(n.Pos(), "%v escapes to heap", n)
330 }
331 }
332 if logopt.Enabled() {
333 var e_curfn *ir.Func
334 logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn))
335 }
336 }
337 n.SetEsc(ir.EscHeap)
338 } else {
339 if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
340 if n.Op() == ir.OAPPEND {
341 base.WarnfAt(n.Pos(), "append does not escape")
342 } else {
343 base.WarnfAt(n.Pos(), "%v does not escape", n)
344 }
345 }
346 n.SetEsc(ir.EscNone)
347 if !loc.hasAttr(attrPersists) {
348 switch n.Op() {
349 case ir.OCLOSURE:
350 n := n.(*ir.ClosureExpr)
351 n.SetTransient(true)
352 case ir.OMETHVALUE:
353 n := n.(*ir.SelectorExpr)
354 n.SetTransient(true)
355 case ir.OSLICELIT:
356 n := n.(*ir.CompLitExpr)
357 n.SetTransient(true)
358 }
359 }
360 }
361
362
363
364 if base.Debug.ZeroCopy != 0 {
365 if n, ok := n.(*ir.ConvExpr); ok && n.Op() == ir.OSTR2BYTES && !loc.hasAttr(attrMutates) {
366 if base.Flag.LowerM >= 1 {
367 base.WarnfAt(n.Pos(), "zero-copy string->[]byte conversion")
368 }
369 n.SetOp(ir.OSTR2BYTESTMP)
370 }
371 }
372 }
373
374 if goexperiment.RuntimeFreegc {
375
376
377 for _, fn := range fns {
378 a := aliasAnalysis{}
379 a.analyze(fn)
380 }
381 }
382
383 }
384
385
386
387
388
389
390 func (b *batch) inMutualBatch(fn *ir.Name) bool {
391 if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
392 if fn.Defn.Esc() == escFuncUnknown {
393 base.FatalfAt(fn.Pos(), "graph inconsistency: %v", fn)
394 }
395 return true
396 }
397 return false
398 }
399
400 const (
401 escFuncUnknown = 0 + iota
402 escFuncPlanned
403 escFuncStarted
404 escFuncTagged
405 )
406
407
408 type labelState int
409
410 const (
411 looping labelState = 1 + iota
412 nonlooping
413 )
414
415 func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
416 name := func() string {
417 if f.Nname != nil {
418 return f.Nname.Sym().Name
419 }
420 return fmt.Sprintf("arg#%d", narg)
421 }
422
423
424
425
426 diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
427
428 if len(fn.Body) == 0 {
429
430
431
432
433
434
435 fn.Pragma |= ir.UintptrKeepAlive
436
437 if f.Type.IsUintptr() {
438 if diagnose {
439 base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name())
440 }
441 return ""
442 }
443
444 if !f.Type.HasPointers() {
445 return ""
446 }
447
448 var esc leaks
449
450
451
452 if fn.Pragma&ir.Noescape != 0 {
453 if diagnose && f.Sym != nil {
454 base.WarnfAt(f.Pos, "%v does not escape", name())
455 }
456 esc.AddMutator(0)
457 esc.AddCallee(0)
458 } else {
459 if diagnose && f.Sym != nil {
460 base.WarnfAt(f.Pos, "leaking param: %v", name())
461 }
462 esc.AddHeap(0)
463 }
464
465 return esc.Encode()
466 }
467
468 if fn.Pragma&ir.UintptrEscapes != 0 {
469 if f.Type.IsUintptr() {
470 if diagnose {
471 base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name())
472 }
473 return ""
474 }
475 if f.IsDDD() && f.Type.Elem().IsUintptr() {
476
477 if diagnose {
478 base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name())
479 }
480 return ""
481 }
482 }
483
484 if !f.Type.HasPointers() {
485 return ""
486 }
487
488
489 if f.Sym == nil || f.Sym.IsBlank() {
490 var esc leaks
491 return esc.Encode()
492 }
493
494 n := f.Nname.(*ir.Name)
495 loc := b.oldLoc(n)
496 esc := loc.paramEsc
497 esc.Optimize()
498
499 if diagnose && !loc.hasAttr(attrEscapes) {
500 b.reportLeaks(f.Pos, name(), esc, fn.Type())
501 }
502
503 return esc.Encode()
504 }
505
506 func (b *batch) reportLeaks(pos src.XPos, name string, esc leaks, sig *types.Type) {
507 warned := false
508 if x := esc.Heap(); x >= 0 {
509 if x == 0 {
510 base.WarnfAt(pos, "leaking param: %v", name)
511 } else {
512
513 base.WarnfAt(pos, "leaking param content: %v", name)
514 }
515 warned = true
516 }
517 for i := 0; i < numEscResults; i++ {
518 if x := esc.Result(i); x >= 0 {
519 res := sig.Result(i).Nname.Sym().Name
520 base.WarnfAt(pos, "leaking param: %v to result %v level=%d", name, res, x)
521 warned = true
522 }
523 }
524
525 if base.Debug.EscapeMutationsCalls <= 0 {
526 if !warned {
527 base.WarnfAt(pos, "%v does not escape", name)
528 }
529 return
530 }
531
532 if x := esc.Mutator(); x >= 0 {
533 base.WarnfAt(pos, "mutates param: %v derefs=%v", name, x)
534 warned = true
535 }
536 if x := esc.Callee(); x >= 0 {
537 base.WarnfAt(pos, "calls param: %v derefs=%v", name, x)
538 warned = true
539 }
540
541 if !warned {
542 base.WarnfAt(pos, "%v does not escape, mutate, or call", name)
543 }
544 }
545
546
547
548 func (b *batch) rewriteWithLiterals(n ir.Node, fn *ir.Func) {
549 if n == nil || fn == nil {
550 return
551 }
552
553 assignTemp := func(pos src.XPos, n ir.Node, init *ir.Nodes) {
554
555 tmp := typecheck.TempAt(pos, fn, n.Type())
556 init.Append(typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp)))
557 init.Append(typecheck.Stmt(ir.NewAssignStmt(pos, tmp, n)))
558 }
559
560 switch n.Op() {
561 case ir.OMAKESLICE:
562
563
564 n := n.(*ir.MakeExpr)
565
566 r := &n.Cap
567 if n.Cap == nil {
568 r = &n.Len
569 }
570
571 if (*r).Op() != ir.OLITERAL {
572
573 ro := b.reassignOracle(fn)
574 if ro == nil {
575 base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
576 }
577
578 s := ro.StaticValue(*r)
579 switch s.Op() {
580 case ir.OLITERAL:
581 lit, ok := s.(*ir.BasicLit)
582 if !ok || lit.Val().Kind() != constant.Int {
583 base.Fatalf("unexpected BasicLit Kind")
584 }
585 if constant.Compare(lit.Val(), token.GEQ, constant.MakeInt64(0)) {
586 if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
587
588 return
589 }
590
591 assignTemp(n.Pos(), *r, n.PtrInit())
592 *r = ir.NewBasicLit(n.Pos(), (*r).Type(), lit.Val())
593 }
594 case ir.OLEN:
595 x := ro.StaticValue(s.(*ir.UnaryExpr).X)
596 if x.Op() == ir.OSLICELIT {
597 x := x.(*ir.CompLitExpr)
598
599 assignTemp(n.Pos(), *r, n.PtrInit())
600 *r = ir.NewBasicLit(n.Pos(), types.Types[types.TINT], constant.MakeInt64(x.Len))
601 }
602 }
603 }
604 case ir.OCONVIFACE:
605
606
607 conv := n.(*ir.ConvExpr)
608 if conv.X.Op() != ir.OLITERAL && !conv.X.Type().IsInterface() {
609
610
611 ro := b.reassignOracle(fn)
612 if ro == nil {
613 base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
614 }
615 v := ro.StaticValue(conv.X)
616 if v != nil && v.Op() == ir.OLITERAL && ir.ValidTypeForConst(conv.X.Type(), v.Val()) {
617 if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
618
619 return
620 }
621 if base.Debug.EscapeDebug >= 3 {
622 base.WarnfAt(n.Pos(), "rewriting OCONVIFACE value from %v (%v) to %v (%v)", conv.X, conv.X.Type(), v, v.Type())
623 }
624
625 assignTemp(conv.Pos(), conv.X, conv.PtrInit())
626 v := v.(*ir.BasicLit)
627 conv.X = ir.NewBasicLit(conv.Pos(), conv.X.Type(), v.Val())
628 typecheck.Expr(conv)
629 }
630 }
631 }
632 }
633
634
635
636
637
638
639 func (b *batch) reassignOracle(fn *ir.Func) *ir.ReassignOracle {
640 if ro, ok := b.reassignOracles[fn]; ok {
641 return ro
642 }
643
644
645
646 f := fn
647 for f.ClosureParent != nil && !f.ClosureParent.IsPackageInit() {
648 f = f.ClosureParent
649 }
650
651 if f != fn {
652
653 ro := b.reassignOracles[f]
654 if ro != nil {
655
656 b.reassignOracles[fn] = ro
657 return ro
658 }
659 }
660
661
662 ro := &ir.ReassignOracle{}
663 ro.Init(f)
664
665
666 b.reassignOracles[fn] = ro
667 if f != fn {
668
669 b.reassignOracles[f] = ro
670 }
671 return ro
672 }
673
View as plain text