1
2
3
4
5 package escape
6
7 import (
8 "fmt"
9
10 "cmd/compile/internal/base"
11 "cmd/compile/internal/ir"
12 "cmd/compile/internal/logopt"
13 "cmd/compile/internal/typecheck"
14 "cmd/compile/internal/types"
15 "cmd/internal/src"
16 )
17
18
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 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
99
100 type closure struct {
101 k hole
102 clo *ir.ClosureExpr
103 }
104
105
106
107 type escape struct {
108 *batch
109
110 curfn *ir.Func
111
112 labels map[*types.Sym]labelState
113
114
115
116
117
118 loopDepth int
119 }
120
121 func Funcs(all []*ir.Func) {
122 ir.VisitFuncsBottomUp(all, Batch)
123 }
124
125
126
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
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.IsClosure() {
143 b.walkFunc(fn)
144 }
145 }
146
147
148
149
150
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
185 for _, n := range fn.Dcl {
186 e.newLoc(n, true)
187 }
188
189
190
191 if fn.OClosure == nil {
192 for _, n := range fn.ClosureVars {
193 e.newLoc(n.Canonical(), true)
194 }
195 }
196
197
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
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
222
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
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
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
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
293
294
295
296
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
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
338
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
351
352
353
354
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
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
389
390
391 diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
392
393 if len(fn.Body) == 0 {
394
395
396
397
398
399
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() {
410 return ""
411 }
412
413 var esc leaks
414
415
416
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
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() {
450 return ""
451 }
452
453
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
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 }
510
View as plain text