1
2
3
4
5
6
7 package loopvar
8
9 import (
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 "fmt"
17 )
18
19 type VarAndLoop struct {
20 Name *ir.Name
21 Loop ir.Node
22 LastPos src.XPos
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 func ForCapture(fn *ir.Func) []VarAndLoop {
48
49 var transformed []VarAndLoop
50
51 describe := func(n *ir.Name) string {
52 pos := n.Pos()
53 inner := base.Ctxt.InnermostPos(pos)
54 outer := base.Ctxt.OutermostPos(pos)
55 if inner == outer {
56 return fmt.Sprintf("loop variable %v now per-iteration", n)
57 }
58 return fmt.Sprintf("loop variable %v now per-iteration (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
59 }
60
61 forCapture := func() {
62 seq := 1
63
64 dclFixups := make(map[*ir.Name]ir.Stmt)
65
66
67
68
69 possiblyLeaked := make(map[*ir.Name]bool)
70
71
72 loopDepth := 0
73 returnInLoopDepth := 0
74
75
76
77 noteMayLeak := func(x ir.Node) {
78 if n, ok := x.(*ir.Name); ok {
79 if n.Type().Kind() == types.TBLANK {
80 return
81 }
82
83 possiblyLeaked[n] = base.Debug.LoopVar >= 11
84 }
85 }
86
87
88
89 var lastPos src.XPos
90
91 updateLastPos := func(p src.XPos) {
92 pl, ll := p.Line(), lastPos.Line()
93 if p.SameFile(lastPos) &&
94 (pl > ll || pl == ll && p.Col() > lastPos.Col()) {
95 lastPos = p
96 }
97 }
98
99
100
101
102 maybeReplaceVar := func(k ir.Node, x *ir.RangeStmt) ir.Node {
103 if n, ok := k.(*ir.Name); ok && possiblyLeaked[n] {
104 desc := func() string {
105 return describe(n)
106 }
107 if base.LoopVarHash.MatchPos(n.Pos(), desc) {
108
109 transformed = append(transformed, VarAndLoop{n, x, lastPos})
110 tk := typecheck.TempAt(base.Pos, fn, n.Type())
111 tk.SetTypecheck(1)
112 as := ir.NewAssignStmt(x.Pos(), n, tk)
113 as.Def = true
114 as.SetTypecheck(1)
115 x.Body.Prepend(as)
116 dclFixups[n] = as
117 return tk
118 }
119 }
120 return k
121 }
122
123
124
125
126
127
128
129
130
131 var scanChildrenThenTransform func(x ir.Node) bool
132 scanChildrenThenTransform = func(n ir.Node) bool {
133
134 if loopDepth > 0 {
135 updateLastPos(n.Pos())
136 }
137
138 switch x := n.(type) {
139 case *ir.ClosureExpr:
140 if returnInLoopDepth >= loopDepth {
141
142
143 break
144 }
145 for _, cv := range x.Func.ClosureVars {
146 v := cv.Canonical()
147 if _, ok := possiblyLeaked[v]; ok {
148 possiblyLeaked[v] = true
149 }
150 }
151
152 case *ir.AddrExpr:
153 if returnInLoopDepth >= loopDepth {
154
155
156 break
157 }
158
159 y := ir.OuterValue(x.X)
160 if y.Op() != ir.ONAME {
161 break
162 }
163 z, ok := y.(*ir.Name)
164 if !ok {
165 break
166 }
167 switch z.Class {
168 case ir.PAUTO, ir.PPARAM, ir.PPARAMOUT, ir.PAUTOHEAP:
169 if _, ok := possiblyLeaked[z]; ok {
170 possiblyLeaked[z] = true
171 }
172 }
173
174 case *ir.ReturnStmt:
175 savedRILD := returnInLoopDepth
176 returnInLoopDepth = loopDepth
177 defer func() { returnInLoopDepth = savedRILD }()
178
179 case *ir.RangeStmt:
180 if !(x.Def && x.DistinctVars) {
181
182 x.DistinctVars = false
183 break
184 }
185 noteMayLeak(x.Key)
186 noteMayLeak(x.Value)
187 loopDepth++
188 savedLastPos := lastPos
189 lastPos = x.Pos()
190 ir.DoChildren(n, scanChildrenThenTransform)
191 loopDepth--
192 x.Key = maybeReplaceVar(x.Key, x)
193 x.Value = maybeReplaceVar(x.Value, x)
194 thisLastPos := lastPos
195 lastPos = savedLastPos
196 updateLastPos(thisLastPos)
197 x.DistinctVars = false
198 return false
199
200 case *ir.ForStmt:
201 if !x.DistinctVars {
202 break
203 }
204 forAllDefInInit(x, noteMayLeak)
205 loopDepth++
206 savedLastPos := lastPos
207 lastPos = x.Pos()
208 ir.DoChildren(n, scanChildrenThenTransform)
209 loopDepth--
210 var leaked []*ir.Name
211
212 forAllDefInInit(x, func(z ir.Node) {
213 if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] {
214 desc := func() string {
215 return describe(n)
216 }
217
218 if base.LoopVarHash.MatchPos(n.Pos(), desc) {
219 leaked = append(leaked, n)
220 }
221 }
222 })
223
224 if len(leaked) > 0 {
225
226
227
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 var preBody, postBody ir.Nodes
292
293
294
295 zPrimeForZ := make(map[*ir.Name]*ir.Name)
296
297
298 for _, z := range leaked {
299 transformed = append(transformed, VarAndLoop{z, x, lastPos})
300
301 tz := typecheck.TempAt(base.Pos, fn, z.Type())
302 tz.SetTypecheck(1)
303 zPrimeForZ[z] = tz
304
305 as := ir.NewAssignStmt(x.Pos(), z, tz)
306 as.Def = true
307 as.SetTypecheck(1)
308 preBody.Append(as)
309 dclFixups[z] = as
310
311 as = ir.NewAssignStmt(x.Pos(), tz, z)
312 as.SetTypecheck(1)
313 postBody.Append(as)
314
315 }
316
317
318 label := typecheck.Lookup(fmt.Sprintf(".3clNext_%d", seq))
319 seq++
320 labelStmt := ir.NewLabelStmt(x.Pos(), label)
321 labelStmt.SetTypecheck(1)
322
323 loopLabel := x.Label
324 loopDepth := 0
325 var editContinues func(x ir.Node) bool
326 editContinues = func(x ir.Node) bool {
327
328 switch c := x.(type) {
329 case *ir.BranchStmt:
330
331 if c.Op() == ir.OCONTINUE && (loopDepth == 0 && c.Label == nil || loopLabel != nil && c.Label == loopLabel) {
332 c.Label = label
333 c.SetOp(ir.OGOTO)
334 }
335 case *ir.RangeStmt, *ir.ForStmt:
336 loopDepth++
337 ir.DoChildren(x, editContinues)
338 loopDepth--
339 return false
340 }
341 ir.DoChildren(x, editContinues)
342 return false
343 }
344 for _, y := range x.Body {
345 editContinues(y)
346 }
347 bodyContinue := x.Body
348
349
350 forAllDefInInitUpdate(x, func(z ir.Node, pz *ir.Node) {
351
352 if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] && zPrimeForZ[n] != nil {
353 *pz = zPrimeForZ[n]
354 }
355 })
356
357 postNotNil := x.Post != nil
358 var tmpFirstDcl ir.Node
359 if postNotNil {
360
361
362
363 tmpFirst := typecheck.TempAt(base.Pos, fn, types.Types[types.TBOOL])
364 tmpFirstDcl = typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, true)))
365 tmpFirstSetFalse := typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, false)))
366 ifTmpFirst := ir.NewIfStmt(x.Pos(), tmpFirst, ir.Nodes{tmpFirstSetFalse}, ir.Nodes{x.Post})
367 ifTmpFirst.PtrInit().Append(typecheck.Stmt(ir.NewDecl(base.Pos, ir.ODCL, tmpFirst)))
368 preBody.Append(typecheck.Stmt(ifTmpFirst))
369 }
370
371
372
373
374 if x.Cond != nil {
375 notCond := ir.NewUnaryExpr(x.Cond.Pos(), ir.ONOT, x.Cond)
376 notCond.SetType(x.Cond.Type())
377 notCond.SetTypecheck(1)
378 newBreak := ir.NewBranchStmt(x.Pos(), ir.OBREAK, nil)
379 newBreak.SetTypecheck(1)
380 ifNotCond := ir.NewIfStmt(x.Pos(), notCond, ir.Nodes{newBreak}, nil)
381 ifNotCond.SetTypecheck(1)
382 preBody.Append(ifNotCond)
383 }
384
385 if postNotNil {
386 x.PtrInit().Append(tmpFirstDcl)
387 }
388
389
390 preBody.Append(bodyContinue...)
391
392 preBody.Append(labelStmt)
393 preBody.Append(postBody...)
394
395
396 x.Body = preBody
397
398
399 x.Cond = nil
400
401
402 x.Post = nil
403 }
404 thisLastPos := lastPos
405 lastPos = savedLastPos
406 updateLastPos(thisLastPos)
407 x.DistinctVars = false
408
409 return false
410 }
411
412 ir.DoChildren(n, scanChildrenThenTransform)
413
414 return false
415 }
416 scanChildrenThenTransform(fn)
417 if len(transformed) > 0 {
418
419
420
421
422 editNodes := func(c ir.Nodes) ir.Nodes {
423 j := 0
424 for _, n := range c {
425 if d, ok := n.(*ir.Decl); ok {
426 if s := dclFixups[d.X]; s != nil {
427 switch a := s.(type) {
428 case *ir.AssignStmt:
429 a.PtrInit().Prepend(d)
430 delete(dclFixups, d.X)
431 default:
432 base.Fatalf("not implemented yet for node type %v", s.Op())
433 }
434 continue
435 }
436 }
437 c[j] = n
438 j++
439 }
440 for k := j; k < len(c); k++ {
441 c[k] = nil
442 }
443 return c[:j]
444 }
445
446 rewriteNodes(fn, editNodes)
447 }
448 }
449 ir.WithFunc(fn, forCapture)
450 return transformed
451 }
452
453
454
455 func forAllDefInInitUpdate(x *ir.ForStmt, do func(z ir.Node, update *ir.Node)) {
456 for _, s := range x.Init() {
457 switch y := s.(type) {
458 case *ir.AssignListStmt:
459 if !y.Def {
460 continue
461 }
462 for i, z := range y.Lhs {
463 do(z, &y.Lhs[i])
464 }
465 case *ir.AssignStmt:
466 if !y.Def {
467 continue
468 }
469 do(y.X, &y.X)
470 }
471 }
472 }
473
474
475 func forAllDefInInit(x *ir.ForStmt, do func(z ir.Node)) {
476 forAllDefInInitUpdate(x, func(z ir.Node, _ *ir.Node) { do(z) })
477 }
478
479
480 func rewriteNodes(fn *ir.Func, editNodes func(c ir.Nodes) ir.Nodes) {
481 var forNodes func(x ir.Node) bool
482 forNodes = func(n ir.Node) bool {
483 if stmt, ok := n.(ir.InitNode); ok {
484
485 stmt.SetInit(editNodes(stmt.Init()))
486 }
487 switch x := n.(type) {
488 case *ir.Func:
489 x.Body = editNodes(x.Body)
490 case *ir.InlinedCallExpr:
491 x.Body = editNodes(x.Body)
492
493 case *ir.CaseClause:
494 x.Body = editNodes(x.Body)
495 case *ir.CommClause:
496 x.Body = editNodes(x.Body)
497
498 case *ir.BlockStmt:
499 x.List = editNodes(x.List)
500
501 case *ir.ForStmt:
502 x.Body = editNodes(x.Body)
503 case *ir.RangeStmt:
504 x.Body = editNodes(x.Body)
505 case *ir.IfStmt:
506 x.Body = editNodes(x.Body)
507 x.Else = editNodes(x.Else)
508 case *ir.SelectStmt:
509 x.Compiled = editNodes(x.Compiled)
510 case *ir.SwitchStmt:
511 x.Compiled = editNodes(x.Compiled)
512 }
513 ir.DoChildren(n, forNodes)
514 return false
515 }
516 forNodes(fn)
517 }
518
519 func LogTransformations(transformed []VarAndLoop) {
520 print := 2 <= base.Debug.LoopVar && base.Debug.LoopVar != 11
521
522 if print || logopt.Enabled() {
523 fileToPosBase := make(map[string]*src.PosBase)
524
525
526 trueInlinedPos := func(inner src.Pos) src.XPos {
527 afn := inner.AbsFilename()
528 pb, ok := fileToPosBase[afn]
529 if !ok {
530 pb = src.NewFileBase(inner.Filename(), afn)
531 fileToPosBase[afn] = pb
532 }
533 inner.SetBase(pb)
534 return base.Ctxt.PosTable.XPos(inner)
535 }
536
537 type unit struct{}
538 loopsSeen := make(map[ir.Node]unit)
539 type loopPos struct {
540 loop ir.Node
541 last src.XPos
542 curfn *ir.Func
543 }
544 var loops []loopPos
545 for _, lv := range transformed {
546 n := lv.Name
547 if _, ok := loopsSeen[lv.Loop]; !ok {
548 l := lv.Loop
549 loopsSeen[l] = unit{}
550 loops = append(loops, loopPos{l, lv.LastPos, n.Curfn})
551 }
552 pos := n.Pos()
553
554 inner := base.Ctxt.InnermostPos(pos)
555 outer := base.Ctxt.OutermostPos(pos)
556
557 if logopt.Enabled() {
558
559 var nString interface{} = n
560 if inner != outer {
561 nString = fmt.Sprintf("%v (from inline)", n)
562 }
563 if n.Esc() == ir.EscHeap {
564 logopt.LogOpt(pos, "iteration-variable-to-heap", "loopvar", ir.FuncName(n.Curfn), nString)
565 } else {
566 logopt.LogOpt(pos, "iteration-variable-to-stack", "loopvar", ir.FuncName(n.Curfn), nString)
567 }
568 }
569 if print {
570 if inner == outer {
571 if n.Esc() == ir.EscHeap {
572 base.WarnfAt(pos, "loop variable %v now per-iteration, heap-allocated", n)
573 } else {
574 base.WarnfAt(pos, "loop variable %v now per-iteration, stack-allocated", n)
575 }
576 } else {
577 innerXPos := trueInlinedPos(inner)
578 if n.Esc() == ir.EscHeap {
579 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, heap-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
580 } else {
581 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, stack-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
582 }
583 }
584 }
585 }
586 for _, l := range loops {
587 pos := l.loop.Pos()
588 last := l.last
589 loopKind := "range"
590 if _, ok := l.loop.(*ir.ForStmt); ok {
591 loopKind = "for"
592 }
593 if logopt.Enabled() {
594
595 logopt.LogOptRange(pos, last, "loop-modified-"+loopKind, "loopvar", ir.FuncName(l.curfn))
596 }
597 if print && 4 <= base.Debug.LoopVar {
598
599 inner := base.Ctxt.InnermostPos(pos)
600 outer := base.Ctxt.OutermostPos(pos)
601
602 if inner == outer {
603 base.WarnfAt(pos, "%s loop ending at %d:%d was modified", loopKind, last.Line(), last.Col())
604 } else {
605 pos = trueInlinedPos(inner)
606 last = trueInlinedPos(base.Ctxt.InnermostPos(last))
607 base.WarnfAt(pos, "%s loop ending at %d:%d was modified (loop inlined into %s:%d)", loopKind, last.Line(), last.Col(), outer.Filename(), outer.Line())
608 }
609 }
610 }
611 }
612 }
613
View as plain text