1
2
3
4
5 package escape
6
7 import (
8 "fmt"
9 "go/constant"
10 "go/token"
11 "internal/goexperiment"
12 "slices"
13
14 "cmd/compile/internal/base"
15 "cmd/compile/internal/ir"
16 "cmd/compile/internal/logopt"
17 "cmd/compile/internal/typecheck"
18 "cmd/compile/internal/types"
19 "cmd/internal/src"
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
92 type batch struct {
93 allLocs []*location
94 closures []closure
95 reassignOracles map[*ir.Func]*ir.ReassignOracle
96
97
98
99
100 staleOracles []*ir.ReassignOracle
101
102 heapLoc location
103 mutatorLoc location
104 calleeLoc location
105 blankLoc location
106 }
107
108
109
110 type closure struct {
111 k hole
112 clo *ir.ClosureExpr
113 }
114
115
116
117 type escape struct {
118 *batch
119
120 curfn *ir.Func
121
122 labels map[*types.Sym]labelState
123
124
125
126
127
128 loopDepth int
129 }
130
131 func Funcs(all []*ir.Func) {
132
133
134
135 reassignOracles := make(map[*ir.Func]*ir.ReassignOracle)
136
137 ir.VisitFuncsBottomUp(all, func(list []*ir.Func, recursive bool) {
138 Batch(list, reassignOracles)
139 })
140 }
141
142
143
144 func Batch(fns []*ir.Func, reassignOracles map[*ir.Func]*ir.ReassignOracle) {
145 var b batch
146 b.heapLoc.attrs = attrEscapes | attrPersists | attrMutates | attrCalls
147 b.mutatorLoc.attrs = attrMutates
148 b.calleeLoc.attrs = attrCalls
149 b.reassignOracles = reassignOracles
150
151
152 for _, fn := range fns {
153 if base.Flag.W > 1 {
154 s := fmt.Sprintf("\nbefore escape %v", fn)
155 ir.Dump(s, fn)
156 }
157 b.initFunc(fn)
158 }
159 for _, fn := range fns {
160 if !fn.IsClosure() {
161 b.walkFunc(fn)
162 }
163 }
164
165
166
167
168
169 for _, closure := range b.closures {
170 b.flowClosure(closure.k, closure.clo)
171 }
172 b.closures = nil
173 b.invalidateStaleOracles()
174
175 for _, loc := range b.allLocs {
176
177 b.rewriteWithLiterals(loc.n, loc.curfn)
178
179
180
181 if why := HeapAllocReason(loc.n); why != "" {
182 b.flow(b.heapHole().addr(loc.n, why), loc)
183 }
184 }
185
186 b.walkAll()
187 b.finish(fns)
188 }
189
190 func (b *batch) with(fn *ir.Func) *escape {
191 return &escape{
192 batch: b,
193 curfn: fn,
194 loopDepth: 1,
195 }
196 }
197
198 func (b *batch) initFunc(fn *ir.Func) {
199 e := b.with(fn)
200 if fn.Esc() != escFuncUnknown {
201 base.Fatalf("unexpected node: %v", fn)
202 }
203 fn.SetEsc(escFuncPlanned)
204 if base.Flag.LowerM > 3 {
205 ir.Dump("escAnalyze", fn)
206 }
207
208
209 for _, n := range fn.Dcl {
210 e.newLoc(n, true)
211 }
212
213
214
215 if fn.OClosure == nil {
216 for _, n := range fn.ClosureVars {
217 e.newLoc(n.Canonical(), true)
218 }
219 }
220
221
222 for i, f := range fn.Type().Results() {
223 e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
224 }
225 }
226
227 func (b *batch) walkFunc(fn *ir.Func) {
228 e := b.with(fn)
229 fn.SetEsc(escFuncStarted)
230
231
232 ir.Visit(fn, func(n ir.Node) {
233 switch n.Op() {
234 case ir.OLABEL:
235 n := n.(*ir.LabelStmt)
236 if n.Label.IsBlank() {
237 break
238 }
239 if e.labels == nil {
240 e.labels = make(map[*types.Sym]labelState)
241 }
242 e.labels[n.Label] = nonlooping
243
244 case ir.OGOTO:
245
246
247 n := n.(*ir.BranchStmt)
248 if e.labels[n.Label] == nonlooping {
249 e.labels[n.Label] = looping
250 }
251 }
252 })
253
254 e.block(fn.Body)
255
256 if len(e.labels) != 0 {
257 base.FatalfAt(fn.Pos(), "leftover labels after walkFunc")
258 }
259 }
260
261 func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) {
262 for _, cv := range clo.Func.ClosureVars {
263 n := cv.Canonical()
264 loc := b.oldLoc(cv)
265 if !loc.captured {
266 base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv)
267 }
268
269
270 n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128)
271 if !n.Byval() {
272 n.SetAddrtaken(true)
273 if n.Sym().Name == typecheck.LocalDictName {
274 base.FatalfAt(n.Pos(), "dictionary variable not captured by value")
275 }
276 }
277 }
278
279
280
281 b.rewriteClosureVarsWithLiterals(clo.Func)
282
283 for _, cv := range clo.Func.ClosureVars {
284 n := cv.Canonical()
285 loc := b.oldLoc(cv)
286
287 if base.Flag.LowerM > 1 {
288 how := "ref"
289 if n.Byval() {
290 how = "value"
291 }
292 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())
293 }
294
295
296 k := k
297 if !cv.Byval() {
298 k = k.addr(cv, "reference")
299 }
300 b.flow(k.note(cv, "captured by a closure"), loc)
301 }
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316 func (b *batch) rewriteClosureVarsWithLiterals(clofn *ir.Func) {
317
318
319
320
321
322 if clofn.IsInlinedClosure() {
323 return
324 }
325
326 var ro *ir.ReassignOracle
327 var repl map[*ir.Name]*ir.Name
328 var prefix ir.Nodes
329
330 for _, cv := range clofn.ClosureVars {
331
332
333 if !cv.Byval() || !ir.ValidTypeForConst(cv.Type(), constant.MakeUnknown()) {
334 continue
335 }
336
337
338 if ro == nil {
339 ro = b.reassignOracle(clofn)
340 if ro == nil {
341 base.Fatalf("no ReassignOracle for function %v with closure parent %v", clofn, clofn.ClosureParent)
342 }
343 }
344 lit, ok := ro.StaticValue(cv).(*ir.BasicLit)
345 if !ok || !ir.ValidTypeForConst(cv.Type(), lit.Val()) {
346 continue
347 }
348 declPos := cv.Canonical().Pos()
349 if !base.LiteralAllocHash.MatchPos(declPos, nil) {
350
351 continue
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 pos := clofn.Pos()
369 name := clofn.NewLocal(declPos, cv.Sym(), cv.Type())
370 name.SetUsed(true)
371 name.SetEsc(ir.EscNever)
372 as := typecheck.Stmt(ir.NewAssignStmt(pos, name, ir.NewBasicLit(pos, cv.Type(), lit.Val())))
373 prefix.Append(typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, name)))
374 prefix.Append(as)
375 name.Defn = as.(*ir.AssignStmt)
376
377 if repl == nil {
378 repl = make(map[*ir.Name]*ir.Name)
379 }
380 repl[cv] = name
381
382 if base.Debug.EscapeDebug >= 3 {
383 base.WarnfAt(pos, "rewriting closure variable %v (%v) to %v", cv, cv.Type(), lit)
384 }
385 }
386
387 if repl == nil {
388 return
389 }
390
391
392 var edit func(ir.Node) ir.Node
393 edit = func(n ir.Node) ir.Node {
394 if n, ok := n.(*ir.Name); ok {
395 if name := repl[n]; name != nil {
396 return name
397 }
398 }
399 ir.EditChildren(n, edit)
400 return n
401 }
402 ir.EditChildren(clofn, edit)
403
404 clofn.Body.Prepend(prefix...)
405 clofn.ClosureVars = slices.DeleteFunc(clofn.ClosureVars, func(cv *ir.Name) bool {
406 return repl[cv] != nil
407 })
408
409
410 b.staleOracles = append(b.staleOracles, ro)
411 }
412
413
414
415
416
417 func (b *batch) invalidateStaleOracles() {
418 if len(b.staleOracles) == 0 {
419 return
420 }
421
422 stale := make(map[*ir.ReassignOracle]bool, len(b.staleOracles))
423 for _, ro := range b.staleOracles {
424 stale[ro] = true
425 }
426 b.staleOracles = nil
427
428 for fn, ro := range b.reassignOracles {
429 if stale[ro] {
430 delete(b.reassignOracles, fn)
431 }
432 }
433 }
434
435 func (b *batch) finish(fns []*ir.Func) {
436
437 for _, fn := range fns {
438 fn.SetEsc(escFuncTagged)
439
440 for i, param := range fn.Type().RecvParams() {
441 param.Note = b.paramTag(fn, 1+i, param)
442 }
443 }
444
445 for _, loc := range b.allLocs {
446 n := loc.n
447 if n == nil {
448 continue
449 }
450
451 if n.Op() == ir.ONAME {
452 n := n.(*ir.Name)
453 n.Opt = nil
454 }
455
456
457
458
459
460
461 goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
462
463 if loc.hasAttr(attrEscapes) {
464 if n.Op() == ir.ONAME {
465 if base.Flag.CompilingRuntime {
466 base.ErrorfAt(n.Pos(), 0, "%v escapes to heap, not allowed in runtime", n)
467 }
468 if base.Flag.LowerM != 0 {
469 base.WarnfAt(n.Pos(), "moved to heap: %v", n)
470 }
471 } else {
472 if base.Flag.LowerM != 0 && !goDeferWrapper {
473 if n.Op() == ir.OAPPEND {
474 base.WarnfAt(n.Pos(), "append escapes to heap")
475 } else {
476 base.WarnfAt(n.Pos(), "%v escapes to heap", n)
477 }
478 }
479 if logopt.Enabled() {
480 var e_curfn *ir.Func
481 logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn))
482 }
483 }
484 n.SetEsc(ir.EscHeap)
485 } else {
486 if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
487 if n.Op() == ir.OAPPEND {
488 base.WarnfAt(n.Pos(), "append does not escape")
489 } else {
490 base.WarnfAt(n.Pos(), "%v does not escape", n)
491 }
492 }
493 n.SetEsc(ir.EscNone)
494 if !loc.hasAttr(attrPersists) {
495 switch n.Op() {
496 case ir.OCLOSURE:
497 n := n.(*ir.ClosureExpr)
498 n.SetTransient(true)
499 case ir.OMETHVALUE:
500 n := n.(*ir.SelectorExpr)
501 n.SetTransient(true)
502 case ir.OSLICELIT:
503 n := n.(*ir.CompLitExpr)
504 n.SetTransient(true)
505 }
506 }
507 }
508
509
510
511 if base.Debug.ZeroCopy != 0 {
512 if n, ok := n.(*ir.ConvExpr); ok && n.Op() == ir.OSTR2BYTES && !loc.hasAttr(attrMutates) {
513 if base.Flag.LowerM >= 1 {
514 base.WarnfAt(n.Pos(), "zero-copy string->[]byte conversion")
515 }
516 n.SetOp(ir.OSTR2BYTESTMP)
517 }
518 }
519 }
520
521 if goexperiment.RuntimeFreegc {
522
523
524 for _, fn := range fns {
525 a := aliasAnalysis{}
526 a.analyze(fn)
527 }
528 }
529
530 for _, fn := range fns {
531 if ir.MatchAstDump(fn, "escape") {
532 ir.AstDump(fn, "escape, "+ir.FuncName(fn))
533 }
534 }
535 }
536
537
538
539
540
541
542 func (b *batch) inMutualBatch(fn *ir.Name) bool {
543 if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
544 if fn.Defn.Esc() == escFuncUnknown {
545 base.FatalfAt(fn.Pos(), "graph inconsistency: %v", fn)
546 }
547 return true
548 }
549 return false
550 }
551
552 const (
553 escFuncUnknown = 0 + iota
554 escFuncPlanned
555 escFuncStarted
556 escFuncTagged
557 )
558
559
560 type labelState int
561
562 const (
563 looping labelState = 1 + iota
564 nonlooping
565 )
566
567 func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
568 name := func() string {
569 if f.Nname != nil {
570 return f.Nname.Sym().Name
571 }
572 return fmt.Sprintf("arg#%d", narg)
573 }
574
575
576
577
578 diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
579
580 if len(fn.Body) == 0 {
581
582
583
584
585
586
587 fn.Pragma |= ir.UintptrKeepAlive
588
589 if f.Type.IsUintptr() {
590 if diagnose {
591 base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name())
592 }
593 return ""
594 }
595
596 if !f.Type.HasPointers() {
597 return ""
598 }
599
600 var esc leaks
601
602
603
604 if fn.Pragma&ir.Noescape != 0 {
605 if diagnose && f.Sym != nil {
606 base.WarnfAt(f.Pos, "%v does not escape", name())
607 }
608 esc.AddMutator(0)
609 esc.AddCallee(0)
610 } else {
611 if diagnose && f.Sym != nil {
612 base.WarnfAt(f.Pos, "leaking param: %v", name())
613 }
614 esc.AddHeap(0)
615 }
616
617 return esc.Encode()
618 }
619
620 if fn.Pragma&ir.UintptrEscapes != 0 {
621 if f.Type.IsUintptr() {
622 if diagnose {
623 base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name())
624 }
625 return ""
626 }
627 if f.IsDDD() && f.Type.Elem().IsUintptr() {
628
629 if diagnose {
630 base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name())
631 }
632 return ""
633 }
634 }
635
636 if !f.Type.HasPointers() {
637 return ""
638 }
639
640
641 if f.Sym == nil || f.Sym.IsBlank() {
642 var esc leaks
643 return esc.Encode()
644 }
645
646 n := f.Nname.(*ir.Name)
647 loc := b.oldLoc(n)
648 esc := loc.paramEsc
649 esc.Optimize()
650
651 if diagnose && !loc.hasAttr(attrEscapes) {
652 b.reportLeaks(f.Pos, name(), esc, fn.Type())
653 }
654
655 return esc.Encode()
656 }
657
658 func (b *batch) reportLeaks(pos src.XPos, name string, esc leaks, sig *types.Type) {
659 warned := false
660 if x := esc.Heap(); x >= 0 {
661 if x == 0 {
662 base.WarnfAt(pos, "leaking param: %v", name)
663 } else {
664
665 base.WarnfAt(pos, "leaking param content: %v", name)
666 }
667 warned = true
668 }
669 for i := 0; i < numEscResults; i++ {
670 if x := esc.Result(i); x >= 0 {
671 res := sig.Result(i).Nname.Sym().Name
672 base.WarnfAt(pos, "leaking param: %v to result %v level=%d", name, res, x)
673 warned = true
674 }
675 }
676
677 if base.Debug.EscapeMutationsCalls <= 0 {
678 if !warned {
679 base.WarnfAt(pos, "%v does not escape", name)
680 }
681 return
682 }
683
684 if x := esc.Mutator(); x >= 0 {
685 base.WarnfAt(pos, "mutates param: %v derefs=%v", name, x)
686 warned = true
687 }
688 if x := esc.Callee(); x >= 0 {
689 base.WarnfAt(pos, "calls param: %v derefs=%v", name, x)
690 warned = true
691 }
692
693 if !warned {
694 base.WarnfAt(pos, "%v does not escape, mutate, or call", name)
695 }
696 }
697
698
699
700 func (b *batch) rewriteWithLiterals(n ir.Node, fn *ir.Func) {
701 if n == nil || fn == nil {
702 return
703 }
704
705 assignTemp := func(pos src.XPos, n ir.Node, init *ir.Nodes) {
706
707 tmp := typecheck.TempAt(pos, fn, n.Type())
708 init.Append(typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp)))
709 init.Append(typecheck.Stmt(ir.NewAssignStmt(pos, tmp, n)))
710 }
711
712 switch n.Op() {
713 case ir.OMAKESLICE:
714
715
716 n := n.(*ir.MakeExpr)
717
718 r := &n.Cap
719 if n.Cap == nil {
720 r = &n.Len
721 }
722
723 if (*r).Op() != ir.OLITERAL {
724
725 ro := b.reassignOracle(fn)
726 if ro == nil {
727 base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
728 }
729
730 s := ro.StaticValue(*r)
731 switch s.Op() {
732 case ir.OLITERAL:
733 lit, ok := s.(*ir.BasicLit)
734 if !ok || lit.Val().Kind() != constant.Int {
735 base.Fatalf("unexpected BasicLit Kind")
736 }
737 if constant.Compare(lit.Val(), token.GEQ, constant.MakeInt64(0)) {
738 if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
739
740 return
741 }
742
743 assignTemp(n.Pos(), *r, n.PtrInit())
744 *r = ir.NewBasicLit(n.Pos(), (*r).Type(), lit.Val())
745 }
746 case ir.OLEN:
747 x := ro.StaticValue(s.(*ir.UnaryExpr).X)
748 if x.Op() == ir.OSLICELIT {
749 x := x.(*ir.CompLitExpr)
750
751 assignTemp(n.Pos(), *r, n.PtrInit())
752 *r = ir.NewBasicLit(n.Pos(), types.Types[types.TINT], constant.MakeInt64(x.Len))
753 }
754 }
755 }
756 case ir.OCONVIFACE:
757
758
759 conv := n.(*ir.ConvExpr)
760 if conv.X.Op() != ir.OLITERAL && !conv.X.Type().IsInterface() {
761
762
763 ro := b.reassignOracle(fn)
764 if ro == nil {
765 base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
766 }
767 v := ro.StaticValue(conv.X)
768 if v != nil && v.Op() == ir.OLITERAL && ir.ValidTypeForConst(conv.X.Type(), v.Val()) {
769 if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
770
771 return
772 }
773 if base.Debug.EscapeDebug >= 3 {
774 base.WarnfAt(n.Pos(), "rewriting OCONVIFACE value from %v (%v) to %v (%v)", conv.X, conv.X.Type(), v, v.Type())
775 }
776
777 assignTemp(conv.Pos(), conv.X, conv.PtrInit())
778 v := v.(*ir.BasicLit)
779 conv.X = ir.NewBasicLit(conv.Pos(), conv.X.Type(), v.Val())
780 typecheck.Expr(conv)
781 }
782 }
783 }
784 }
785
786
787
788
789
790
791 func (b *batch) reassignOracle(fn *ir.Func) *ir.ReassignOracle {
792 if ro, ok := b.reassignOracles[fn]; ok {
793 return ro
794 }
795
796
797
798 f := fn
799 for f.ClosureParent != nil && !f.ClosureParent.IsPackageInit() {
800 f = f.ClosureParent
801 }
802
803 if f != fn {
804
805 ro := b.reassignOracles[f]
806 if ro != nil {
807
808 b.reassignOracles[fn] = ro
809 return ro
810 }
811 }
812
813
814 ro := &ir.ReassignOracle{}
815 ro.Init(f)
816
817
818 b.reassignOracles[fn] = ro
819 if f != fn {
820
821 b.reassignOracles[f] = ro
822 }
823 return ro
824 }
825
View as plain text