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
452 if ir.MatchAstDump(fn, "loopvar") {
453 ir.AstDump(fn, "loopvar, "+ir.FuncName(fn))
454 }
455
456 return transformed
457 }
458
459
460
461 func forAllDefInInitUpdate(x *ir.ForStmt, do func(z ir.Node, update *ir.Node)) {
462 for _, s := range x.Init() {
463 switch y := s.(type) {
464 case *ir.AssignListStmt:
465 if !y.Def {
466 continue
467 }
468 for i, z := range y.Lhs {
469 do(z, &y.Lhs[i])
470 }
471 case *ir.AssignStmt:
472 if !y.Def {
473 continue
474 }
475 do(y.X, &y.X)
476 }
477 }
478 }
479
480
481 func forAllDefInInit(x *ir.ForStmt, do func(z ir.Node)) {
482 forAllDefInInitUpdate(x, func(z ir.Node, _ *ir.Node) { do(z) })
483 }
484
485
486 func rewriteNodes(fn *ir.Func, editNodes func(c ir.Nodes) ir.Nodes) {
487 var forNodes func(x ir.Node) bool
488 forNodes = func(n ir.Node) bool {
489 if stmt, ok := n.(ir.InitNode); ok {
490
491 stmt.SetInit(editNodes(stmt.Init()))
492 }
493 switch x := n.(type) {
494 case *ir.Func:
495 x.Body = editNodes(x.Body)
496 case *ir.InlinedCallExpr:
497 x.Body = editNodes(x.Body)
498
499 case *ir.CaseClause:
500 x.Body = editNodes(x.Body)
501 case *ir.CommClause:
502 x.Body = editNodes(x.Body)
503
504 case *ir.BlockStmt:
505 x.List = editNodes(x.List)
506
507 case *ir.ForStmt:
508 x.Body = editNodes(x.Body)
509 case *ir.RangeStmt:
510 x.Body = editNodes(x.Body)
511 case *ir.IfStmt:
512 x.Body = editNodes(x.Body)
513 x.Else = editNodes(x.Else)
514 case *ir.SelectStmt:
515 x.Compiled = editNodes(x.Compiled)
516 case *ir.SwitchStmt:
517 x.Compiled = editNodes(x.Compiled)
518 }
519 ir.DoChildren(n, forNodes)
520 return false
521 }
522 forNodes(fn)
523 }
524
525 func LogTransformations(transformed []VarAndLoop) {
526 print := 2 <= base.Debug.LoopVar && base.Debug.LoopVar != 11
527
528 if print || logopt.Enabled() {
529 fileToPosBase := make(map[string]*src.PosBase)
530
531
532 trueInlinedPos := func(inner src.Pos) src.XPos {
533 afn := inner.AbsFilename()
534 pb, ok := fileToPosBase[afn]
535 if !ok {
536 pb = src.NewFileBase(inner.Filename(), afn)
537 fileToPosBase[afn] = pb
538 }
539 inner.SetBase(pb)
540 return base.Ctxt.PosTable.XPos(inner)
541 }
542
543 type unit struct{}
544 loopsSeen := make(map[ir.Node]unit)
545 type loopPos struct {
546 loop ir.Node
547 last src.XPos
548 curfn *ir.Func
549 }
550 var loops []loopPos
551 for _, lv := range transformed {
552 n := lv.Name
553 if _, ok := loopsSeen[lv.Loop]; !ok {
554 l := lv.Loop
555 loopsSeen[l] = unit{}
556 loops = append(loops, loopPos{l, lv.LastPos, n.Curfn})
557 }
558 pos := n.Pos()
559
560 inner := base.Ctxt.InnermostPos(pos)
561 outer := base.Ctxt.OutermostPos(pos)
562
563 if logopt.Enabled() {
564
565 var nString any = n
566 if inner != outer {
567 nString = fmt.Sprintf("%v (from inline)", n)
568 }
569 if n.Esc() == ir.EscHeap {
570 logopt.LogOpt(pos, "iteration-variable-to-heap", "loopvar", ir.FuncName(n.Curfn), nString)
571 } else {
572 logopt.LogOpt(pos, "iteration-variable-to-stack", "loopvar", ir.FuncName(n.Curfn), nString)
573 }
574 }
575 if print {
576 if inner == outer {
577 if n.Esc() == ir.EscHeap {
578 base.WarnfAt(pos, "loop variable %v now per-iteration, heap-allocated", n)
579 } else {
580 base.WarnfAt(pos, "loop variable %v now per-iteration, stack-allocated", n)
581 }
582 } else {
583 innerXPos := trueInlinedPos(inner)
584 if n.Esc() == ir.EscHeap {
585 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, heap-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
586 } else {
587 base.WarnfAt(innerXPos, "loop variable %v now per-iteration, stack-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
588 }
589 }
590 }
591 }
592 for _, l := range loops {
593 pos := l.loop.Pos()
594 last := l.last
595 loopKind := "range"
596 if _, ok := l.loop.(*ir.ForStmt); ok {
597 loopKind = "for"
598 }
599 if logopt.Enabled() {
600
601 logopt.LogOptRange(pos, last, "loop-modified-"+loopKind, "loopvar", ir.FuncName(l.curfn))
602 }
603 if print && 4 <= base.Debug.LoopVar {
604
605 inner := base.Ctxt.InnermostPos(pos)
606 outer := base.Ctxt.OutermostPos(pos)
607
608 if inner == outer {
609 base.WarnfAt(pos, "%s loop ending at %d:%d was modified", loopKind, last.Line(), last.Col())
610 } else {
611 pos = trueInlinedPos(inner)
612 last = trueInlinedPos(base.Ctxt.InnermostPos(last))
613 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())
614 }
615 }
616 }
617 }
618 }
619
View as plain text