| | |
| | |
| | |
| |
|
| | |
| | |
| | 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()) |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|