Файловый менеджер - Редактировать - /var/www/html/loopvar.zip
Ðазад
PK ! |�[]~O ~O loopvar.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package loopvar applies the proper variable capture, according // to experiment, flags, language version, etc. 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 // the *ir.RangeStmt or *ir.ForStmt. Used for identity and position LastPos src.XPos // the last position observed within Loop } // ForCapture transforms for and range loops that declare variables that might be // captured by a closure or escaped to the heap, using a syntactic check that // conservatively overestimates the loops where capture occurs, but still avoids // transforming the (large) majority of loops. It returns the list of names // subject to this change, that may (once transformed) be heap allocated in the // process. (This allows checking after escape analysis to call out any such // variables, in case it causes allocation/performance problems). // // The decision to transform loops is normally encoded in the For/Range loop node // field DistinctVars but is also dependent on base.LoopVarHash, and some values // of base.Debug.LoopVar (which is set per-package). Decisions encoded in DistinctVars // are preserved across inlining, so if package a calls b.F and loops in b.F are // transformed, then they are always transformed, whether b.F is inlined or not. // // Per-package, the debug flag settings that affect this transformer: // // base.LoopVarHash != nil => use hash setting to govern transformation. // note that LoopVarHash != nil sets base.Debug.LoopVar to 1 (unless it is >= 11, for testing/debugging). // // base.Debug.LoopVar == 11 => transform ALL loops ignoring syntactic/potential escape. Do not log, can be in addition to GOEXPERIMENT. // // The effect of GOEXPERIMENT=loopvar is to change the default value (0) of base.Debug.LoopVar to 1 for all packages. func ForCapture(fn *ir.Func) []VarAndLoop { // if a loop variable is transformed it is appended to this slice for later logging 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) // possibly leaked includes names of declared loop variables that may be leaked; // the mapped value is true if the name is *syntactically* leaked, and those loops // will be transformed. possiblyLeaked := make(map[*ir.Name]bool) // these enable an optimization of "escape" under return statements loopDepth := 0 returnInLoopDepth := 0 // noteMayLeak is called for candidate variables in for range/3-clause, and // adds them (mapped to false) to possiblyLeaked. noteMayLeak := func(x ir.Node) { if n, ok := x.(*ir.Name); ok { if n.Type().Kind() == types.TBLANK { return } // default is false (leak candidate, not yet known to leak), but flag can make all variables "leak" possiblyLeaked[n] = base.Debug.LoopVar >= 11 } } // For reporting, keep track of the last position within any loop. // Loops nest, also need to be sensitive to inlining. 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 unshares an iteration variable for a range loop, // if that variable was actually (syntactically) leaked, // subject to hash-variable debugging. 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) { // Rename the loop key, prefix body with assignment from loop key 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 } // scanChildrenThenTransform processes node x to: // 1. if x is a for/range w/ DistinctVars, note declared iteration variables possiblyLeaked (PL) // 2. search all of x's children for syntactically escaping references to v in PL, // meaning either address-of-v or v-captured-by-a-closure // 3. for all v in PL that had a syntactically escaping reference, transform the declaration // and (in case of 3-clause loop) the loop to the unshared loop semantics. // This is all much simpler for range loops; 3-clause loops can have an arbitrary number // of iteration variables and the transformation is more involved, range loops have at most 2. 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 { // This expression is a child of a return, which escapes all loops above // the return, but not those between this expression and the return. break } for _, cv := range x.Func.ClosureVars { v := cv.Canonical() if _, ok := possiblyLeaked[v]; ok { possiblyLeaked[v] = true } } case *ir.AddrExpr: if returnInLoopDepth >= loopDepth { // This expression is a child of a return, which escapes all loops above // the return, but not those between this expression and the return. break } // Explicitly note address-taken so that return-statements can be excluded 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) { // range loop must define its iteration variables AND have distinctVars. x.DistinctVars = false break } noteMayLeak(x.Key) noteMayLeak(x.Value) loopDepth++ savedLastPos := lastPos lastPos = x.Pos() // this sets the file. ir.DoChildren(n, scanChildrenThenTransform) loopDepth-- x.Key = maybeReplaceVar(x.Key, x) x.Value = maybeReplaceVar(x.Value, x) thisLastPos := lastPos lastPos = savedLastPos updateLastPos(thisLastPos) // this will propagate lastPos if in the same file. x.DistinctVars = false return false case *ir.ForStmt: if !x.DistinctVars { break } forAllDefInInit(x, noteMayLeak) loopDepth++ savedLastPos := lastPos lastPos = x.Pos() // this sets the file. ir.DoChildren(n, scanChildrenThenTransform) loopDepth-- var leaked []*ir.Name // Collect the leaking variables for the much-more-complex transformation. forAllDefInInit(x, func(z ir.Node) { if n, ok := z.(*ir.Name); ok && possiblyLeaked[n] { desc := func() string { return describe(n) } // Hash on n.Pos() for most precise failure location. if base.LoopVarHash.MatchPos(n.Pos(), desc) { leaked = append(leaked, n) } } }) if len(leaked) > 0 { // need to transform the for loop just so. /* Contrived example, w/ numbered comments from the transformation: BEFORE: var escape []*int for z := 0; z < n; z++ { if reason() { escape = append(escape, &z) continue } z = z + z stuff } AFTER: for z', tmp_first := 0, true; ; { // (4) // (5) body' follows: z := z' // (1) if tmp_first {tmp_first = false} else {z++} // (6) if ! (z < n) { break } // (7) // (3, 8) body_continue if reason() { escape = append(escape, &z) goto next // rewritten continue } z = z + z stuff next: // (9) z' = z // (2) } In the case that the loop contains no increment (z++), there is no need for step 6, and thus no need to test, update, or declare tmp_first (part of step 4). Similarly if the loop contains no exit test (z < n), then there is no need for step 7. */ // Expressed in terms of the input ForStmt // // type ForStmt struct { // init Nodes // Label *types.Sym // Cond Node // empty if OFORUNTIL // Post Node // Body Nodes // HasBreak bool // } // OFOR: init; loop: if !Cond {break}; Body; Post; goto loop // (1) prebody = {z := z' for z in leaked} // (2) postbody = {z' = z for z in leaked} // (3) body_continue = {body : s/continue/goto next} // (4) init' = (init : s/z/z' for z in leaked) + tmp_first := true // (5) body' = prebody + // appears out of order below // (6) if tmp_first {tmp_first = false} else {Post} + // (7) if !cond {break} + // (8) body_continue (3) + // (9) next: postbody (2) // (10) cond' = {} // (11) post' = {} // minor optimizations: // if Post is empty, tmp_first and step 6 can be skipped. // if Cond is empty, that code can also be skipped. var preBody, postBody ir.Nodes // Given original iteration variable z, what is the corresponding z' // that carries the value from iteration to iteration? zPrimeForZ := make(map[*ir.Name]*ir.Name) // (1,2) initialize preBody and postBody 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) preBody.Append(as) dclFixups[z] = as as = ir.NewAssignStmt(x.Pos(), tz, z) as.SetTypecheck(1) postBody.Append(as) } // (3) rewrite continues in body -- rewrite is inplace, so works for top level visit, too. 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 this is a continue targeting the loop currently being rewritten, transform it to an appropriate GOTO 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 // (4) rewrite init forAllDefInInitUpdate(x, func(z ir.Node, pz *ir.Node) { // note tempFor[n] can be nil if hash searching. 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 { // body' = prebody + // (6) if tmp_first {tmp_first = false} else {Post} + // if !cond {break} + ... 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))) // declares tmpFirst preBody.Append(typecheck.Stmt(ifTmpFirst)) } // body' = prebody + // if tmp_first {tmp_first = false} else {Post} + // (7) if !cond {break} + ... 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) } // (8) preBody.Append(bodyContinue...) // (9) preBody.Append(labelStmt) preBody.Append(postBody...) // (5) body' = prebody + ... x.Body = preBody // (10) cond' = {} x.Cond = nil // (11) post' = {} x.Post = nil } thisLastPos := lastPos lastPos = savedLastPos updateLastPos(thisLastPos) // this will propagate lastPos if in the same file. x.DistinctVars = false return false } ir.DoChildren(n, scanChildrenThenTransform) return false } scanChildrenThenTransform(fn) if len(transformed) > 0 { // editNodes scans a slice C of ir.Node, looking for declarations that // appear in dclFixups. Any declaration D whose "fixup" is an assignmnt // statement A is removed from the C and relocated to the Init // of A. editNodes returns the modified slice of ir.Node. 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) // can't be sure of visit order, wouldn't want to visit twice. default: base.Fatalf("not implemented yet for node type %v", s.Op()) } continue // do not copy this node, and do not increment j } } c[j] = n j++ } for k := j; k < len(c); k++ { c[k] = nil } return c[:j] } // fixup all tagged declarations in all the statements lists in fn. rewriteNodes(fn, editNodes) } } ir.WithFunc(fn, forCapture) return transformed } // forAllDefInInitUpdate applies "do" to all the defining assignments in the Init clause of a ForStmt. // This abstracts away some of the boilerplate from the already complex and verbose for-3-clause case. 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) } } } // forAllDefInInit is forAllDefInInitUpdate without the update option. func forAllDefInInit(x *ir.ForStmt, do func(z ir.Node)) { forAllDefInInitUpdate(x, func(z ir.Node, _ *ir.Node) { do(z) }) } // rewriteNodes applies editNodes to all statement lists in fn. 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 { // process init list 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() { // 11 is do them all, quietly, 12 includes debugging. fileToPosBase := make(map[string]*src.PosBase) // used to remove inline context for innermost reporting. // trueInlinedPos rebases inner w/o inline context so that it prints correctly in WarnfAt; otherwise it prints as outer. 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() { // For automated checking of coverage of this transformation, include this in the JSON information. var nString interface{} = 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() { // Intended to help with performance debugging, we record whole loop ranges logopt.LogOptRange(pos, last, "loop-modified-"+loopKind, "loopvar", ir.FuncName(l.curfn)) } if print && 4 <= base.Debug.LoopVar { // TODO decide if we want to keep this, or not. It was helpful for validating logopt, otherwise, eh. 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()) } } } } } PK ! yGk�, �, loopvar_test.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package loopvar_test import ( "internal/testenv" "os/exec" "path/filepath" "regexp" "runtime" "strings" "testing" ) type testcase struct { lvFlag string // ==-2, -1, 0, 1, 2 buildExpect string // message, if any expectRC int files []string } var for_files = []string{ "for_esc_address.go", // address of variable "for_esc_closure.go", // closure of variable "for_esc_minimal_closure.go", // simple closure of variable "for_esc_method.go", // method value of variable "for_complicated_esc_address.go", // modifies loop index in body } var range_files = []string{ "range_esc_address.go", // address of variable "range_esc_closure.go", // closure of variable "range_esc_minimal_closure.go", // simple closure of variable "range_esc_method.go", // method value of variable } var cases = []testcase{ {"-1", "", 11, for_files[:1]}, {"0", "", 0, for_files[:1]}, {"1", "", 0, for_files[:1]}, {"2", "loop variable i now per-iteration,", 0, for_files}, {"-1", "", 11, range_files[:1]}, {"0", "", 0, range_files[:1]}, {"1", "", 0, range_files[:1]}, {"2", "loop variable i now per-iteration,", 0, range_files}, {"1", "", 0, []string{"for_nested.go"}}, } // TestLoopVar checks that the GOEXPERIMENT and debug flags behave as expected. func TestLoopVarGo1_21(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) tmpdir := t.TempDir() output := filepath.Join(tmpdir, "foo.exe") for i, tc := range cases { for _, f := range tc.files { source := f cmd := testenv.Command(t, gocmd, "build", "-o", output, "-gcflags=-lang=go1.21 -d=loopvar="+tc.lvFlag, source) cmd.Env = append(cmd.Env, "GOEXPERIMENT=loopvar", "HOME="+tmpdir) cmd.Dir = "testdata" t.Logf("File %s loopvar=%s expect '%s' exit code %d", f, tc.lvFlag, tc.buildExpect, tc.expectRC) b, e := cmd.CombinedOutput() if e != nil { t.Error(e) } if tc.buildExpect != "" { s := string(b) if !strings.Contains(s, tc.buildExpect) { t.Errorf("File %s test %d expected to match '%s' with \n-----\n%s\n-----", f, i, tc.buildExpect, s) } } // run what we just built. cmd = testenv.Command(t, output) b, e = cmd.CombinedOutput() if tc.expectRC != 0 { if e == nil { t.Errorf("Missing expected error, file %s, case %d", f, i) } else if ee, ok := (e).(*exec.ExitError); !ok || ee.ExitCode() != tc.expectRC { t.Error(e) } else { // okay } } else if e != nil { t.Error(e) } } } } func TestLoopVarInlinesGo1_21(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) tmpdir := t.TempDir() root := "cmd/compile/internal/loopvar/testdata/inlines" f := func(pkg string) string { // This disables the loopvar change, except for the specified package. // The effect should follow the package, even though everything (except "c") // is inlined. cmd := testenv.Command(t, gocmd, "run", "-gcflags="+root+"/...=-lang=go1.21", "-gcflags="+pkg+"=-d=loopvar=1", root) cmd.Env = append(cmd.Env, "GOEXPERIMENT=noloopvar", "HOME="+tmpdir) cmd.Dir = filepath.Join("testdata", "inlines") b, e := cmd.CombinedOutput() if e != nil { t.Error(e) } return string(b) } a := f(root + "/a") b := f(root + "/b") c := f(root + "/c") m := f(root) t.Logf(a) t.Logf(b) t.Logf(c) t.Logf(m) if !strings.Contains(a, "f, af, bf, abf, cf sums = 100, 45, 100, 100, 100") { t.Errorf("Did not see expected value of a") } if !strings.Contains(b, "f, af, bf, abf, cf sums = 100, 100, 45, 45, 100") { t.Errorf("Did not see expected value of b") } if !strings.Contains(c, "f, af, bf, abf, cf sums = 100, 100, 100, 100, 45") { t.Errorf("Did not see expected value of c") } if !strings.Contains(m, "f, af, bf, abf, cf sums = 45, 100, 100, 100, 100") { t.Errorf("Did not see expected value of m") } } func countMatches(s, re string) int { slice := regexp.MustCompile(re).FindAllString(s, -1) return len(slice) } func TestLoopVarHashes(t *testing.T) { // This behavior does not depend on Go version (1.21 or greater) switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) tmpdir := t.TempDir() root := "cmd/compile/internal/loopvar/testdata/inlines" f := func(hash string) string { // This disables the loopvar change, except for the specified hash pattern. // -trimpath is necessary so we get the same answer no matter where the // Go repository is checked out. This is not normally a concern since people // do not normally rely on the meaning of specific hashes. cmd := testenv.Command(t, gocmd, "run", "-trimpath", root) cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=loopvarhash="+hash, "HOME="+tmpdir) cmd.Dir = filepath.Join("testdata", "inlines") b, _ := cmd.CombinedOutput() // Ignore the error, sometimes it's supposed to fail, the output test will catch it. return string(b) } for _, arg := range []string{"v001100110110110010100100", "vx336ca4"} { m := f(arg) t.Logf(m) mCount := countMatches(m, "loopvarhash triggered cmd/compile/internal/loopvar/testdata/inlines/main.go:27:6: .* 001100110110110010100100") otherCount := strings.Count(m, "loopvarhash") if mCount < 1 { t.Errorf("%s: did not see triggered main.go:27:6", arg) } if mCount != otherCount { t.Errorf("%s: too many matches", arg) } mCount = countMatches(m, "cmd/compile/internal/loopvar/testdata/inlines/main.go:27:6: .* \\[bisect-match 0x7802e115b9336ca4\\]") otherCount = strings.Count(m, "[bisect-match ") if mCount < 1 { t.Errorf("%s: did not see bisect-match for main.go:27:6", arg) } if mCount != otherCount { t.Errorf("%s: too many matches", arg) } // This next test carefully dodges a bug-to-be-fixed with inlined locations for ir.Names. if !strings.Contains(m, ", 100, 100, 100, 100") { t.Errorf("%s: did not see expected value of m run", arg) } } } // TestLoopVarVersionEnableFlag checks for loopvar transformation enabled by command line flag (1.22). func TestLoopVarVersionEnableFlag(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) // loopvar=3 logs info but does not change loopvarness cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt.go") cmd.Dir = filepath.Join("testdata") b, err := cmd.CombinedOutput() m := string(b) t.Logf(m) yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)") nCount := strings.Count(m, "shared") if yCount != 1 { t.Errorf("yCount=%d != 1", yCount) } if nCount > 0 { t.Errorf("nCount=%d > 0", nCount) } if err != nil { t.Errorf("err=%v != nil", err) } } // TestLoopVarVersionEnableGoBuild checks for loopvar transformation enabled by go:build version (1.22). func TestLoopVarVersionEnableGoBuild(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) // loopvar=3 logs info but does not change loopvarness cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt-122.go") cmd.Dir = filepath.Join("testdata") b, err := cmd.CombinedOutput() m := string(b) t.Logf(m) yCount := strings.Count(m, "opt-122.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-122.go:31)") nCount := strings.Count(m, "shared") if yCount != 1 { t.Errorf("yCount=%d != 1", yCount) } if nCount > 0 { t.Errorf("nCount=%d > 0", nCount) } if err != nil { t.Errorf("err=%v != nil", err) } } // TestLoopVarVersionDisableFlag checks for loopvar transformation DISABLED by command line version (1.21). func TestLoopVarVersionDisableFlag(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) // loopvar=3 logs info but does not change loopvarness cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.21 -d=loopvar=3", "opt.go") cmd.Dir = filepath.Join("testdata") b, err := cmd.CombinedOutput() m := string(b) t.Logf(m) // expect error yCount := strings.Count(m, "opt.go:16:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt.go:29)") nCount := strings.Count(m, "shared") if yCount != 0 { t.Errorf("yCount=%d != 0", yCount) } if nCount > 0 { t.Errorf("nCount=%d > 0", nCount) } if err == nil { // expect error t.Errorf("err=%v == nil", err) } } // TestLoopVarVersionDisableGoBuild checks for loopvar transformation DISABLED by go:build version (1.21). func TestLoopVarVersionDisableGoBuild(t *testing.T) { switch runtime.GOOS { case "linux", "darwin": default: t.Skipf("Slow test, usually avoid it, os=%s not linux or darwin", runtime.GOOS) } switch runtime.GOARCH { case "amd64", "arm64": default: t.Skipf("Slow test, usually avoid it, arch=%s not amd64 or arm64", runtime.GOARCH) } testenv.MustHaveGoBuild(t) gocmd := testenv.GoToolPath(t) // loopvar=3 logs info but does not change loopvarness cmd := testenv.Command(t, gocmd, "run", "-gcflags=-lang=go1.22 -d=loopvar=3", "opt-121.go") cmd.Dir = filepath.Join("testdata") b, err := cmd.CombinedOutput() m := string(b) t.Logf(m) // expect error yCount := strings.Count(m, "opt-121.go:18:6: loop variable private now per-iteration, heap-allocated (loop inlined into ./opt-121.go:31)") nCount := strings.Count(m, "shared") if yCount != 0 { t.Errorf("yCount=%d != 0", yCount) } if nCount > 0 { t.Errorf("nCount=%d > 0", nCount) } if err == nil { // expect error t.Errorf("err=%v == nil", err) } } PK ! %An� � testdata/range_esc_address.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var ints = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} func main() { sum := 0 var is []*int for _, i := range ints { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, &i) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, pi := range is { sum += *pi } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! ��� � testdata/for_esc_address.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) func main() { sum := 0 var is []*int for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, &i) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, pi := range is { sum += *pi } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! �7�G G testdata/opt-121.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.21 package main import ( "fmt" "os" ) var is []func() int func inline(j, k int) []*int { var a []*int for private := j; private < k; private++ { a = append(a, &private) } return a } //go:noinline func notinline(j, k int) ([]*int, *int) { for shared := j; shared < k; shared++ { if shared == k/2 { // want the call inlined, want "private" in that inline to be transformed, // (believe it ends up on init node of the return). // but do not want "shared" transformed, return inline(j, k), &shared } } return nil, &j } func main() { a, p := notinline(2, 9) fmt.Printf("a[0]=%d,*p=%d\n", *a[0], *p) if *a[0] != 2 { os.Exit(1) } } PK ! ({r: testdata/inlines/c/c.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package c //go:noinline func F() []*int { var s []*int for i := 0; i < 10; i++ { s = append(s, &i) } return s } PK ! s�d� � testdata/inlines/b/b.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package b var slice = []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024} func F() ([]*int, []*int) { return g() } func g() ([]*int, []*int) { var s []*int var t []*int for i, j := range slice { s = append(s, &i) t = append(t, &j) } return s[:len(s)-1], t } PK ! "�$� � testdata/inlines/main.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "cmd/compile/internal/loopvar/testdata/inlines/a" "cmd/compile/internal/loopvar/testdata/inlines/b" "cmd/compile/internal/loopvar/testdata/inlines/c" "fmt" "os" ) func sum(s []*int) int { sum := 0 for _, pi := range s { sum += *pi } return sum } var t []*int func F() []*int { var s []*int for i, j := 0, 0; j < 10; i, j = i+1, j+1 { s = append(s, &i) t = append(s, &j) } return s } func main() { f := F() af := a.F() bf, _ := b.F() abf := a.Fb() cf := c.F() sf, saf, sbf, sabf, scf := sum(f), sum(af), sum(bf), sum(abf), sum(cf) fmt.Printf("f, af, bf, abf, cf sums = %d, %d, %d, %d, %d\n", sf, saf, sbf, sabf, scf) // Special failure just for use with hash searching, to prove it fires exactly once. // To test: `gossahash -e loopvarhash go run .` in this directory. // This is designed to fail in two different ways, because gossahash searches randomly // it will find both failures over time. if os.Getenv("GOCOMPILEDEBUG") != "" && (sabf == 45 || sf == 45) { os.Exit(11) } os.Exit(0) } PK ! c�@6t t testdata/inlines/a/a.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package a import "cmd/compile/internal/loopvar/testdata/inlines/b" func F() []*int { var s []*int for i := 0; i < 10; i++ { s = append(s, &i) } return s } func Fb() []*int { bf, _ := b.F() return bf } PK ! �\�tA A testdata/range_esc_method.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) type I int func (x *I) method() int { return int(*x) } var ints = []I{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} func main() { sum := 0 var is []func() int for _, i := range ints { for j := 0; j < 10; j++ { if int(i) == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, i.method) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, m := range is { sum += m() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! �(i` % testdata/range_esc_minimal_closure.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var is []func() int var ints = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} func main() { sum := 0 for _, i := range ints { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, func() int { return i }) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, f := range is { sum += f() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! �Lda^ ^ testdata/for_nested.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) func main() { x := f(60) fmt.Println(x) if x != 54 { os.Exit(11) } } var escape *int func f(i int) int { a := 0 outer: for { switch { case i > 55: i-- continue case i == 55: for j := i; j != 1; j = j / 2 { a++ if j == 4 { escape = &j i-- continue outer } if j&1 == 1 { j = 2 * (3*j + 1) } } return a case i < 55: return i } } } PK ! ���� � # testdata/for_esc_minimal_closure.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var is []func() int func main() { sum := 0 for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, func() int { return i }) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, f := range is { sum += f() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! g��4 4 testdata/opt.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var is []func() int func inline(j, k int) []*int { var a []*int for private := j; private < k; private++ { a = append(a, &private) } return a } //go:noinline func notinline(j, k int) ([]*int, *int) { for shared := j; shared < k; shared++ { if shared == k/2 { // want the call inlined, want "private" in that inline to be transformed, // (believe it ends up on init node of the return). // but do not want "shared" transformed, return inline(j, k), &shared } } return nil, &j } func main() { a, p := notinline(2, 9) fmt.Printf("a[0]=%d,*p=%d\n", *a[0], *p) if *a[0] != 2 { os.Exit(1) } } PK ! 4,v= = testdata/range_esc_closure.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var is []func() int var ints = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} func main() { sum := 0 for _, i := range ints { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, func() int { if i%17 == 15 { i++ } return i }) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, f := range is { sum += f() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! �� � testdata/for_esc_closure.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) var is []func() int func main() { sum := 0 for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { if i == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, func() int { if i%17 == 15 { i++ } return i }) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, f := range is { sum += f() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! Kw�G G testdata/opt-122.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.22 package main import ( "fmt" "os" ) var is []func() int func inline(j, k int) []*int { var a []*int for private := j; private < k; private++ { a = append(a, &private) } return a } //go:noinline func notinline(j, k int) ([]*int, *int) { for shared := j; shared < k; shared++ { if shared == k/2 { // want the call inlined, want "private" in that inline to be transformed, // (believe it ends up on init node of the return). // but do not want "shared" transformed, return inline(j, k), &shared } } return nil, &j } func main() { a, p := notinline(2, 9) fmt.Printf("a[0]=%d,*p=%d\n", *a[0], *p) if *a[0] != 2 { os.Exit(1) } } PK ! �-pr testdata/for_esc_method.gonu �[��� // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "os" ) type I int func (x *I) method() int { return int(*x) } func main() { sum := 0 var is []func() int for i := I(0); int(i) < 10; i++ { for j := 0; j < 10; j++ { if int(i) == j { // 10 skips continue } sum++ } if i&1 == 0 { is = append(is, i.method) } } bug := false if sum != 100-10 { fmt.Printf("wrong sum, expected %d, saw %d\n", 90, sum) bug = true } sum = 0 for _, m := range is { sum += m() } if sum != 2+4+6+8 { fmt.Printf("wrong sum, expected %d, saw %d\n", 20, sum) bug = true } if !bug { fmt.Printf("PASS\n") } else { os.Exit(11) } } PK ! ��]y y '