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 z.Defn = as
309 preBody.Append(as)
310 dclFixups[z] = as
311
312 as = ir.NewAssignStmt(x.Pos(), tz, z)
313 as.SetTypecheck(1)
314 postBody.Append(as)
315
316 }
317
318
319 label := typecheck.Lookup(fmt.Sprintf(".3clNext_%d", seq))
320 seq++
321 labelStmt := ir.NewLabelStmt(x.Pos(), label)
322 labelStmt.SetTypecheck(1)
323
324 loopLabel := x.Label
325 loopDepth := 0
326 var editContinues func(x ir.Node) bool
327 editContinues = func(x ir.Node) bool {
328
329 switch c := x.(type) {
330 case *ir.BranchStmt:
331
332 if c.Op() == ir.OCONTINUE && (loopDepth == 0 && c.Label == nil || loopLabel != nil && c.Label == loopLabel) {
333 c.Label = label
334 c.SetOp(ir.OGOTO)
335 }
336 case *ir.RangeStmt, *ir.ForStmt:
337 loopDepth++
338 ir.DoChildren(x, editContinues)
339 loopDepth--
340 return false
341 }
342 ir.DoChildren(x, editContinues)
343 return false
344 }
345 for _, y := range x.Body {
346 editContinues(y)
347 }
348 bodyContinue := x.Body
349
350
351 forAllDefInInitUpdate(x, func(z ir.Node, pz *ir.Node) {
352
353 if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] && zPrimeForZ[n] != nil {
354 *pz = zPrimeForZ[n]
355 }
356 })
357
358 postNotNil := x.Post != nil
359 var tmpFirstDcl ir.Node
360 if postNotNil {
361
362
363
364 tmpFirst := typecheck.TempAt(base.Pos, fn, types.Types[types.TBOOL])
365 tmpFirstDcl = typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, true)))
366 tmpFirstSetFalse := typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, false)))
367 ifTmpFirst := ir.NewIfStmt(x.Pos(), tmpFirst, ir.Nodes{tmpFirstSetFalse}, ir.Nodes{x.Post})
368 ifTmpFirst.PtrInit().Append(typecheck.Stmt(ir.NewDecl(base.Pos, ir.ODCL, tmpFirst)))
369 preBody.Append(typecheck.Stmt(ifTmpFirst))
370 }
371
372
373
374
375 if x.Cond != nil {
376 notCond := ir.NewUnaryExpr(x.Cond.Pos(), ir.ONOT, x.Cond)
377 notCond.SetType(x.Cond.Type())
378 notCond.SetTypecheck(1)
379 newBreak := ir.NewBranchStmt(x.Pos(), ir.OBREAK, nil)
380 newBreak.SetTypecheck(1)
381 ifNotCond := ir.NewIfStmt(x.Pos(), notCond, ir.Nodes{newBreak}, nil)
382 ifNotCond.SetTypecheck(1)
383 preBody.Append(ifNotCond)
384 }
385
386 if postNotNil {
387 x.PtrInit().Append(tmpFirstDcl)
388 }
389
390
391 preBody.Append(bodyContinue...)
392
393 preBody.Append(labelStmt)
394 preBody.Append(postBody...)
395
396
397 x.Body = preBody
398
399
400 x.Cond = nil
401
402
403 x.Post = nil
404 }
405 thisLastPos := lastPos
406 lastPos = savedLastPos
407 updateLastPos(thisLastPos)
408 x.DistinctVars = false
409
410 return false
411 }
412
413 ir.DoChildren(n, scanChildrenThenTransform)
414
415 return false
416 }
417 scanChildrenThenTransform(fn)
418 if len(transformed) > 0 {
419
420
421
422
423 editNodes := func(c ir.Nodes) ir.Nodes {
424 j := 0
425 for _, n := range c {
426 if d, ok := n.(*ir.Decl); ok {
427 if s := dclFixups[d.X]; s != nil {
428 switch a := s.(type) {
429 case *ir.AssignStmt:
430 a.PtrInit().Prepend(d)
431 delete(dclFixups, d.X)
432 default:
433 base.Fatalf("not implemented yet for node type %v", s.Op())
434 }
435 continue
436 }
437 }
438 c[j] = n
439 j++
440 }
441 for k := j; k < len(c); k++ {
442 c[k] = nil
443 }
444 return c[:j]
445 }
446
447 rewriteNodes(fn, editNodes)
448 }
449 }
450 ir.WithFunc(fn, forCapture)
451 return transformed
452 }
453
454
455
456 func forAllDefInInitUpdate(x *ir.ForStmt, do func(z ir.Node, update *ir.Node)) {
457 for _, s := range x.Init() {
458 switch y := s.(type) {
459 case *ir.AssignListStmt:
460 if !y.Def {
461 continue
462 }
463 for i, z := range y.Lhs {
464 do(z, &y.Lhs[i])
465 }
466 case *ir.AssignStmt:
467 if !y.Def {
468 continue
469 }
470 do(y.X, &y.X)
471 }
472 }
473 }
474
475
476 func forAllDefInInit(x *ir.ForStmt, do func(z ir.Node)) {
477 forAllDefInInitUpdate(x, func(z ir.Node, _ *ir.Node) { do(z) })
478 }
479
480
481 func rewriteNodes(fn *ir.Func, editNodes func(c ir.Nodes) ir.Nodes) {
482 var forNodes func(x ir.Node) bool
483 forNodes = func(n ir.Node) bool {
484 if stmt, ok := n.(ir.InitNode); ok {
485
486 stmt.SetInit(editNodes(stmt.Init()))
487 }
488 switch x := n.(type) {
489 case *ir.Func:
490 x.Body = editNodes(x.Body)
491 case *ir.InlinedCallExpr:
492 x.Body = editNodes(x.Body)
493
494 case *ir.CaseClause:
495 x.Body = editNodes(x.Body)
496 case *ir.CommClause:
497 x.Body = editNodes(x.Body)
498
499 case *ir.BlockStmt:
500 x.List = editNodes(x.List)
501
502 case *ir.ForStmt:
503 x.Body = editNodes(x.Body)
504 case *ir.RangeStmt:
505 x.Body = editNodes(x.Body)
506 case *ir.IfStmt:
507 x.Body = editNodes(x.Body)
508 x.Else = editNodes(x.Else)
509 case *ir.SelectStmt:
510 x.Compiled = editNodes(x.Compiled)
511 case *ir.SwitchStmt:
512 x.Compiled = editNodes(x.Compiled)
513 }
514 ir.DoChildren(n, forNodes)
515 return false
516 }
517 forNodes(fn)
518 }
519
520 func LogTransformations(transformed []VarAndLoop) {
521 print := 2 <= base.Debug.LoopVar && base.Debug.LoopVar != 11
522
523 if print || logopt.Enabled() {
524 fileToPosBase := make(map[string]*src.PosBase)
525
526
527 trueInlinedPos := func(inner src.Pos) src.XPos {
528 afn := inner.AbsFilename()
529 pb, ok := fileToPosBase[afn]
530 if !ok {
531 pb = src.NewFileBase(inner.Filename(), afn)
532 fileToPosBase[afn] = pb
533 }
534 inner.SetBase(pb)
535 return base.Ctxt.PosTable.XPos(inner)
536 }
537
538 type unit struct{}
539 loopsSeen := make(map[ir.Node]unit)
540 type loopPos struct {
541 loop ir.Node
542 last src.XPos
543 curfn *ir.Func
544 }
545 var loops []loopPos
546 for _, lv := range transformed {
547 n := lv.Name
548 if _, ok := loopsSeen[lv.Loop]; !ok {
549 l := lv.Loop
550 loopsSeen[l] = unit{}
551 loops = append(loops, loopPos{l, lv.LastPos, n.Curfn})
552 }
553 pos := n.Pos()
554
555 inner := base.Ctxt.InnermostPos(pos)
556 outer := base.Ctxt.OutermostPos(pos)
557
558 if logopt.Enabled() {
559
560 var nString interface{} = n
561 if inner != outer {
562 nString = fmt.Sprintf("%v (from inline)", n)
563 }
564 if n.Esc() == ir.EscHeap {
565 logopt.LogOpt(pos, "iteration-variable-to-heap", "loopvar", ir.FuncName(n.Curfn), nString)
566 } else {
567 logopt.LogOpt(pos, "iteration-variable-to-stack", "loopvar", ir.FuncName(n.Curfn), nString)
568 }
569 }
570 if print {
571 if inner == outer {
572 if n.Esc() == ir.EscHeap {
573 base.WarnfAt(pos, "loop variable %v now per-iteration, heap-allocated", n)
574 } else {
575 base.WarnfAt(pos, "loop variable %v now per-iteration, stack-allocated", n)
576 }
577 } else {
578 innerXPos := trueInlinedPos(inner)
579 if n.Esc() == ir.EscHeap {
580 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, heap-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
581 } else {
582 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, stack-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
583 }
584 }
585 }
586 }
587 for _, l := range loops {
588 pos := l.loop.Pos()
589 last := l.last
590 loopKind := "range"
591 if _, ok := l.loop.(*ir.ForStmt); ok {
592 loopKind = "for"
593 }
594 if logopt.Enabled() {
595
596 logopt.LogOptRange(pos, last, "loop-modified-"+loopKind, "loopvar", ir.FuncName(l.curfn))
597 }
598 if print && 4 <= base.Debug.LoopVar {
599
600 inner := base.Ctxt.InnermostPos(pos)
601 outer := base.Ctxt.OutermostPos(pos)
602
603 if inner == outer {
604 base.WarnfAt(pos, "%s loop ending at %d:%d was modified", loopKind, last.Line(), last.Col())
605 } else {
606 pos = trueInlinedPos(inner)
607 last = trueInlinedPos(base.Ctxt.InnermostPos(last))
608 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())
609 }
610 }
611 }
612 }
613 }
614
View as plain text