| |
| |
| |
|
|
| |
| |
| package loopvar |
|
|
| import ( |
| "cmd/compile/internal/base" |
| "cmd/compile/internal/ir" |
| "cmd/compile/internal/logopt" |
| "cmd/compile/internal/typecheck" |
| "cmd/compile/internal/types" |
| "cmd/internal/src" |
| "fmt" |
| ) |
|
|
| type VarAndLoop struct { |
| Name *ir.Name |
| Loop ir.Node |
| LastPos src.XPos |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func ForCapture(fn *ir.Func) []VarAndLoop { |
| |
| var transformed []VarAndLoop |
|
|
| describe := func(n *ir.Name) string { |
| pos := n.Pos() |
| inner := base.Ctxt.InnermostPos(pos) |
| outer := base.Ctxt.OutermostPos(pos) |
| if inner == outer { |
| return fmt.Sprintf("loop variable %v now per-iteration", n) |
| } |
| return fmt.Sprintf("loop variable %v now per-iteration (loop inlined into %s:%d)", n, outer.Filename(), outer.Line()) |
| } |
|
|
| forCapture := func() { |
| seq := 1 |
|
|
| dclFixups := make(map[*ir.Name]ir.Stmt) |
|
|
| |
| |
| |
| possiblyLeaked := make(map[*ir.Name]bool) |
|
|
| |
| loopDepth := 0 |
| returnInLoopDepth := 0 |
|
|
| |
| |
| noteMayLeak := func(x ir.Node) { |
| if n, ok := x.(*ir.Name); ok { |
| if n.Type().Kind() == types.TBLANK { |
| return |
| } |
| |
| possiblyLeaked[n] = base.Debug.LoopVar >= 11 |
| } |
| } |
|
|
| |
| |
| var lastPos src.XPos |
|
|
| updateLastPos := func(p src.XPos) { |
| pl, ll := p.Line(), lastPos.Line() |
| if p.SameFile(lastPos) && |
| (pl > ll || pl == ll && p.Col() > lastPos.Col()) { |
| lastPos = p |
| } |
| } |
|
|
| |
| |
| |
| maybeReplaceVar := func(k ir.Node, x *ir.RangeStmt) ir.Node { |
| if n, ok := k.(*ir.Name); ok && possiblyLeaked[n] { |
| desc := func() string { |
| return describe(n) |
| } |
| if base.LoopVarHash.MatchPos(n.Pos(), desc) { |
| |
| transformed = append(transformed, VarAndLoop{n, x, lastPos}) |
| tk := typecheck.TempAt(base.Pos, fn, n.Type()) |
| tk.SetTypecheck(1) |
| as := ir.NewAssignStmt(x.Pos(), n, tk) |
| as.Def = true |
| as.SetTypecheck(1) |
| x.Body.Prepend(as) |
| dclFixups[n] = as |
| return tk |
| } |
| } |
| return k |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| var scanChildrenThenTransform func(x ir.Node) bool |
| scanChildrenThenTransform = func(n ir.Node) bool { |
|
|
| if loopDepth > 0 { |
| updateLastPos(n.Pos()) |
| } |
|
|
| switch x := n.(type) { |
| case *ir.ClosureExpr: |
| if returnInLoopDepth >= loopDepth { |
| |
| |
| break |
| } |
| for _, cv := range x.Func.ClosureVars { |
| v := cv.Canonical() |
| if _, ok := possiblyLeaked[v]; ok { |
| possiblyLeaked[v] = true |
| } |
| } |
|
|
| case *ir.AddrExpr: |
| if returnInLoopDepth >= loopDepth { |
| |
| |
| break |
| } |
| |
| y := ir.OuterValue(x.X) |
| if y.Op() != ir.ONAME { |
| break |
| } |
| z, ok := y.(*ir.Name) |
| if !ok { |
| break |
| } |
| switch z.Class { |
| case ir.PAUTO, ir.PPARAM, ir.PPARAMOUT, ir.PAUTOHEAP: |
| if _, ok := possiblyLeaked[z]; ok { |
| possiblyLeaked[z] = true |
| } |
| } |
|
|
| case *ir.ReturnStmt: |
| savedRILD := returnInLoopDepth |
| returnInLoopDepth = loopDepth |
| defer func() { returnInLoopDepth = savedRILD }() |
|
|
| case *ir.RangeStmt: |
| if !(x.Def && x.DistinctVars) { |
| |
| x.DistinctVars = false |
| break |
| } |
| noteMayLeak(x.Key) |
| noteMayLeak(x.Value) |
| loopDepth++ |
| savedLastPos := lastPos |
| lastPos = x.Pos() |
| ir.DoChildren(n, scanChildrenThenTransform) |
| loopDepth-- |
| x.Key = maybeReplaceVar(x.Key, x) |
| x.Value = maybeReplaceVar(x.Value, x) |
| thisLastPos := lastPos |
| lastPos = savedLastPos |
| updateLastPos(thisLastPos) |
| x.DistinctVars = false |
| return false |
|
|
| case *ir.ForStmt: |
| if !x.DistinctVars { |
| break |
| } |
| forAllDefInInit(x, noteMayLeak) |
| loopDepth++ |
| savedLastPos := lastPos |
| lastPos = x.Pos() |
| ir.DoChildren(n, scanChildrenThenTransform) |
| loopDepth-- |
| var leaked []*ir.Name |
| |
| forAllDefInInit(x, func(z ir.Node) { |
| if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] { |
| desc := func() string { |
| return describe(n) |
| } |
| |
| if base.LoopVarHash.MatchPos(n.Pos(), desc) { |
| leaked = append(leaked, n) |
| } |
| } |
| }) |
|
|
| if len(leaked) > 0 { |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| var preBody, postBody ir.Nodes |
|
|
| |
| |
| zPrimeForZ := make(map[*ir.Name]*ir.Name) |
|
|
| |
| for _, z := range leaked { |
| transformed = append(transformed, VarAndLoop{z, x, lastPos}) |
|
|
| tz := typecheck.TempAt(base.Pos, fn, z.Type()) |
| tz.SetTypecheck(1) |
| zPrimeForZ[z] = tz |
|
|
| as := ir.NewAssignStmt(x.Pos(), z, tz) |
| as.Def = true |
| as.SetTypecheck(1) |
| z.Defn = as |
| preBody.Append(as) |
| dclFixups[z] = as |
|
|
| as = ir.NewAssignStmt(x.Pos(), tz, z) |
| as.SetTypecheck(1) |
| postBody.Append(as) |
|
|
| } |
|
|
| |
| label := typecheck.Lookup(fmt.Sprintf(".3clNext_%d", seq)) |
| seq++ |
| labelStmt := ir.NewLabelStmt(x.Pos(), label) |
| labelStmt.SetTypecheck(1) |
|
|
| loopLabel := x.Label |
| loopDepth := 0 |
| var editContinues func(x ir.Node) bool |
| editContinues = func(x ir.Node) bool { |
|
|
| switch c := x.(type) { |
| case *ir.BranchStmt: |
| |
| if c.Op() == ir.OCONTINUE && (loopDepth == 0 && c.Label == nil || loopLabel != nil && c.Label == loopLabel) { |
| c.Label = label |
| c.SetOp(ir.OGOTO) |
| } |
| case *ir.RangeStmt, *ir.ForStmt: |
| loopDepth++ |
| ir.DoChildren(x, editContinues) |
| loopDepth-- |
| return false |
| } |
| ir.DoChildren(x, editContinues) |
| return false |
| } |
| for _, y := range x.Body { |
| editContinues(y) |
| } |
| bodyContinue := x.Body |
|
|
| |
| forAllDefInInitUpdate(x, func(z ir.Node, pz *ir.Node) { |
| |
| if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] && zPrimeForZ[n] != nil { |
| *pz = zPrimeForZ[n] |
| } |
| }) |
|
|
| postNotNil := x.Post != nil |
| var tmpFirstDcl ir.Node |
| if postNotNil { |
| |
| |
| |
| tmpFirst := typecheck.TempAt(base.Pos, fn, types.Types[types.TBOOL]) |
| tmpFirstDcl = typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, true))) |
| tmpFirstSetFalse := typecheck.Stmt(ir.NewAssignStmt(x.Pos(), tmpFirst, ir.NewBool(base.Pos, false))) |
| ifTmpFirst := ir.NewIfStmt(x.Pos(), tmpFirst, ir.Nodes{tmpFirstSetFalse}, ir.Nodes{x.Post}) |
| ifTmpFirst.PtrInit().Append(typecheck.Stmt(ir.NewDecl(base.Pos, ir.ODCL, tmpFirst))) |
| preBody.Append(typecheck.Stmt(ifTmpFirst)) |
| } |
|
|
| |
| |
| |
| if x.Cond != nil { |
| notCond := ir.NewUnaryExpr(x.Cond.Pos(), ir.ONOT, x.Cond) |
| notCond.SetType(x.Cond.Type()) |
| notCond.SetTypecheck(1) |
| newBreak := ir.NewBranchStmt(x.Pos(), ir.OBREAK, nil) |
| newBreak.SetTypecheck(1) |
| ifNotCond := ir.NewIfStmt(x.Pos(), notCond, ir.Nodes{newBreak}, nil) |
| ifNotCond.SetTypecheck(1) |
| preBody.Append(ifNotCond) |
| } |
|
|
| if postNotNil { |
| x.PtrInit().Append(tmpFirstDcl) |
| } |
|
|
| |
| preBody.Append(bodyContinue...) |
| |
| preBody.Append(labelStmt) |
| preBody.Append(postBody...) |
|
|
| |
| x.Body = preBody |
|
|
| |
| x.Cond = nil |
|
|
| |
| x.Post = nil |
| } |
| thisLastPos := lastPos |
| lastPos = savedLastPos |
| updateLastPos(thisLastPos) |
| x.DistinctVars = false |
|
|
| return false |
| } |
|
|
| ir.DoChildren(n, scanChildrenThenTransform) |
|
|
| return false |
| } |
| scanChildrenThenTransform(fn) |
| if len(transformed) > 0 { |
| |
| |
| |
| |
| editNodes := func(c ir.Nodes) ir.Nodes { |
| j := 0 |
| for _, n := range c { |
| if d, ok := n.(*ir.Decl); ok { |
| if s := dclFixups[d.X]; s != nil { |
| switch a := s.(type) { |
| case *ir.AssignStmt: |
| a.PtrInit().Prepend(d) |
| delete(dclFixups, d.X) |
| default: |
| base.Fatalf("not implemented yet for node type %v", s.Op()) |
| } |
| continue |
| } |
| } |
| c[j] = n |
| j++ |
| } |
| for k := j; k < len(c); k++ { |
| c[k] = nil |
| } |
| return c[:j] |
| } |
| |
| rewriteNodes(fn, editNodes) |
| } |
| } |
| ir.WithFunc(fn, forCapture) |
| return transformed |
| } |
|
|
| |
| |
| func forAllDefInInitUpdate(x *ir.ForStmt, do func(z ir.Node, update *ir.Node)) { |
| for _, s := range x.Init() { |
| switch y := s.(type) { |
| case *ir.AssignListStmt: |
| if !y.Def { |
| continue |
| } |
| for i, z := range y.Lhs { |
| do(z, &y.Lhs[i]) |
| } |
| case *ir.AssignStmt: |
| if !y.Def { |
| continue |
| } |
| do(y.X, &y.X) |
| } |
| } |
| } |
|
|
| |
| func forAllDefInInit(x *ir.ForStmt, do func(z ir.Node)) { |
| forAllDefInInitUpdate(x, func(z ir.Node, _ *ir.Node) { do(z) }) |
| } |
|
|
| |
| func rewriteNodes(fn *ir.Func, editNodes func(c ir.Nodes) ir.Nodes) { |
| var forNodes func(x ir.Node) bool |
| forNodes = func(n ir.Node) bool { |
| if stmt, ok := n.(ir.InitNode); ok { |
| |
| stmt.SetInit(editNodes(stmt.Init())) |
| } |
| switch x := n.(type) { |
| case *ir.Func: |
| x.Body = editNodes(x.Body) |
| case *ir.InlinedCallExpr: |
| x.Body = editNodes(x.Body) |
|
|
| case *ir.CaseClause: |
| x.Body = editNodes(x.Body) |
| case *ir.CommClause: |
| x.Body = editNodes(x.Body) |
|
|
| case *ir.BlockStmt: |
| x.List = editNodes(x.List) |
|
|
| case *ir.ForStmt: |
| x.Body = editNodes(x.Body) |
| case *ir.RangeStmt: |
| x.Body = editNodes(x.Body) |
| case *ir.IfStmt: |
| x.Body = editNodes(x.Body) |
| x.Else = editNodes(x.Else) |
| case *ir.SelectStmt: |
| x.Compiled = editNodes(x.Compiled) |
| case *ir.SwitchStmt: |
| x.Compiled = editNodes(x.Compiled) |
| } |
| ir.DoChildren(n, forNodes) |
| return false |
| } |
| forNodes(fn) |
| } |
|
|
| func LogTransformations(transformed []VarAndLoop) { |
| print := 2 <= base.Debug.LoopVar && base.Debug.LoopVar != 11 |
|
|
| if print || logopt.Enabled() { |
| fileToPosBase := make(map[string]*src.PosBase) |
|
|
| |
| trueInlinedPos := func(inner src.Pos) src.XPos { |
| afn := inner.AbsFilename() |
| pb, ok := fileToPosBase[afn] |
| if !ok { |
| pb = src.NewFileBase(inner.Filename(), afn) |
| fileToPosBase[afn] = pb |
| } |
| inner.SetBase(pb) |
| return base.Ctxt.PosTable.XPos(inner) |
| } |
|
|
| type unit struct{} |
| loopsSeen := make(map[ir.Node]unit) |
| type loopPos struct { |
| loop ir.Node |
| last src.XPos |
| curfn *ir.Func |
| } |
| var loops []loopPos |
| for _, lv := range transformed { |
| n := lv.Name |
| if _, ok := loopsSeen[lv.Loop]; !ok { |
| l := lv.Loop |
| loopsSeen[l] = unit{} |
| loops = append(loops, loopPos{l, lv.LastPos, n.Curfn}) |
| } |
| pos := n.Pos() |
|
|
| inner := base.Ctxt.InnermostPos(pos) |
| outer := base.Ctxt.OutermostPos(pos) |
|
|
| if logopt.Enabled() { |
| |
| var nString any = n |
| if inner != outer { |
| nString = fmt.Sprintf("%v (from inline)", n) |
| } |
| if n.Esc() == ir.EscHeap { |
| logopt.LogOpt(pos, "iteration-variable-to-heap", "loopvar", ir.FuncName(n.Curfn), nString) |
| } else { |
| logopt.LogOpt(pos, "iteration-variable-to-stack", "loopvar", ir.FuncName(n.Curfn), nString) |
| } |
| } |
| if print { |
| if inner == outer { |
| if n.Esc() == ir.EscHeap { |
| base.WarnfAt(pos, "loop variable %v now per-iteration, heap-allocated", n) |
| } else { |
| base.WarnfAt(pos, "loop variable %v now per-iteration, stack-allocated", n) |
| } |
| } else { |
| innerXPos := trueInlinedPos(inner) |
| if n.Esc() == ir.EscHeap { |
| base.WarnfAt(innerXPos, "loop variable %v now per-iteration, heap-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line()) |
| } else { |
| base.WarnfAt(innerXPos, "loop variable %v now per-iteration, stack-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line()) |
| } |
| } |
| } |
| } |
| for _, l := range loops { |
| pos := l.loop.Pos() |
| last := l.last |
| loopKind := "range" |
| if _, ok := l.loop.(*ir.ForStmt); ok { |
| loopKind = "for" |
| } |
| if logopt.Enabled() { |
| |
| logopt.LogOptRange(pos, last, "loop-modified-"+loopKind, "loopvar", ir.FuncName(l.curfn)) |
| } |
| if print && 4 <= base.Debug.LoopVar { |
| |
| inner := base.Ctxt.InnermostPos(pos) |
| outer := base.Ctxt.OutermostPos(pos) |
|
|
| if inner == outer { |
| base.WarnfAt(pos, "%s loop ending at %d:%d was modified", loopKind, last.Line(), last.Col()) |
| } else { |
| pos = trueInlinedPos(inner) |
| last = trueInlinedPos(base.Ctxt.InnermostPos(last)) |
| 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()) |
| } |
| } |
| } |
| } |
| } |
|
|