mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
cmd/compile: experimental loop iterator capture semantics change
Adds: GOEXPERIMENT=loopvar (expected way of invoking) -d=loopvar={-1,0,1,2,11,12} (for per-package control and/or logging) -d=loopvarhash=... (for hash debugging) loopvar=11,12 are for testing, benchmarking, and debugging. If enabled,for loops of the form `for x,y := range thing`, if x and/or y are addressed or captured by a closure, are transformed by renaming x/y to a temporary and prepending an assignment to the body of the loop x := tmp_x. This changes the loop semantics by making each iteration's instance of x be distinct from the others (currently they are all aliased, and when this matters, it is almost always a bug). 3-range with captured iteration variables are also transformed, though it is a more complex transformation. "Optimized" to do a simpler transformation for 3-clause for where the increment is empty. (Prior optimization of address-taking under Return disabled, because it was incorrect; returns can have loops for children. Restored in a later CL.) Includes support for -d=loopvarhash=<binary string> intended for use with hash search and GOCOMPILEDEBUG=loopvarhash=<binary string> (use `gossahash -e loopvarhash command-that-fails`). Minor feature upgrades to hash-triggered features; clients can specify that file-position hashes use only the most-inline position, and/or that they use only the basenames of source files (not the full directory path). Most-inlined is the right choice for debugging loop-iteration change once the semantics are linked to the package across inlining; basename-only makes it tractable to write tests (which, otherwise, depend on the full pathname of the source file and thus vary). Updates #57969. Change-Id: I180a51a3f8d4173f6210c861f10de23de8a1b1db Reviewed-on: https://go-review.googlesource.com/c/go/+/411904 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
dbdb3359b5
commit
c20d959163
@ -33,6 +33,8 @@ type DebugFlags struct {
|
|||||||
InlStaticInit int `help:"allow static initialization of inlined calls" concurrent:"ok"`
|
InlStaticInit int `help:"allow static initialization of inlined calls" concurrent:"ok"`
|
||||||
InterfaceCycles int `help:"allow anonymous interface cycles"`
|
InterfaceCycles int `help:"allow anonymous interface cycles"`
|
||||||
Libfuzzer int `help:"enable coverage instrumentation for libfuzzer"`
|
Libfuzzer int `help:"enable coverage instrumentation for libfuzzer"`
|
||||||
|
LoopVar int `help:"shared (0, default), 1 (private loop variables), 2, private + log"`
|
||||||
|
LoopVarHash string `help:"for debugging changes in loop behavior. Overrides experiment and loopvar flag."`
|
||||||
LocationLists int `help:"print information about DWARF location list creation"`
|
LocationLists int `help:"print information about DWARF location list creation"`
|
||||||
Nil int `help:"print information about nil checks"`
|
Nil int `help:"print information about nil checks"`
|
||||||
NoOpenDefer int `help:"disable open-coded defers" concurrent:"ok"`
|
NoOpenDefer int `help:"disable open-coded defers" concurrent:"ok"`
|
||||||
|
@ -186,7 +186,61 @@ func ParseFlags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if Debug.Gossahash != "" {
|
if Debug.Gossahash != "" {
|
||||||
hashDebug = NewHashDebug("gosshash", Debug.Gossahash, nil)
|
hashDebug = NewHashDebug("gossahash", Debug.Gossahash, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three inputs govern loop iteration variable rewriting, hash, experiment, flag.
|
||||||
|
// The loop variable rewriting is:
|
||||||
|
// IF non-empty hash, then hash determines behavior (function+line match) (*)
|
||||||
|
// ELSE IF experiment and flag==0, then experiment (set flag=1)
|
||||||
|
// ELSE flag (note that build sets flag per-package), with behaviors:
|
||||||
|
// -1 => no change to behavior.
|
||||||
|
// 0 => no change to behavior (unless non-empty hash, see above)
|
||||||
|
// 1 => apply change to likely-iteration-variable-escaping loops
|
||||||
|
// 2 => apply change, log results
|
||||||
|
// 11 => apply change EVERYWHERE, do not log results (for debugging/benchmarking)
|
||||||
|
// 12 => apply change EVERYWHERE, log results (for debugging/benchmarking)
|
||||||
|
//
|
||||||
|
// The expected uses of the these inputs are, in believed most-likely to least likely:
|
||||||
|
// GOEXPERIMENT=loopvar -- apply change to entire application
|
||||||
|
// -gcflags=some_package=-d=loopvar=1 -- apply change to some_package (**)
|
||||||
|
// -gcflags=some_package=-d=loopvar=2 -- apply change to some_package, log it
|
||||||
|
// GOEXPERIMENT=loopvar -gcflags=some_package=-d=loopvar=-1 -- apply change to all but one package
|
||||||
|
// GOCOMPILEDEBUG=loopvarhash=... -- search for failure cause
|
||||||
|
//
|
||||||
|
// (*) For debugging purposes, providing loopvar flag >= 11 will expand the hash-eligible set of loops to all.
|
||||||
|
// (**) Currently this applies to all code in the compilation of some_package, including
|
||||||
|
// inlines from other packages that may have been compiled w/o the change.
|
||||||
|
|
||||||
|
if Debug.LoopVarHash != "" {
|
||||||
|
// This first little bit controls the inputs for debug-hash-matching.
|
||||||
|
basenameOnly := false
|
||||||
|
mostInlineOnly := true
|
||||||
|
if strings.HasPrefix(Debug.LoopVarHash, "FS") {
|
||||||
|
// Magic handshake for testing, use file suffixes only when hashing on a position.
|
||||||
|
// i.e., rather than /tmp/asdfasdfasdf/go-test-whatever/foo_test.go,
|
||||||
|
// hash only on "foo_test.go", so that it will be the same hash across all runs.
|
||||||
|
Debug.LoopVarHash = Debug.LoopVarHash[2:]
|
||||||
|
basenameOnly = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(Debug.LoopVarHash, "IL") {
|
||||||
|
// When hash-searching on a position that is an inline site, default is to use the
|
||||||
|
// most-inlined position only. This makes the hash faster, plus there's no point
|
||||||
|
// reporting a problem with all the inlining; there's only one copy of the source.
|
||||||
|
// However, if for some reason you wanted it per-site, you can get this. (The default
|
||||||
|
// hash-search behavior for compiler debugging is at an inline site.)
|
||||||
|
Debug.LoopVarHash = Debug.LoopVarHash[2:]
|
||||||
|
mostInlineOnly = false
|
||||||
|
}
|
||||||
|
// end of testing trickiness
|
||||||
|
LoopVarHash = NewHashDebug("loopvarhash", Debug.LoopVarHash, nil)
|
||||||
|
if Debug.LoopVar < 11 { // >= 11 means all loops are rewrite-eligible
|
||||||
|
Debug.LoopVar = 1 // 1 means those loops that syntactically escape their dcl vars are eligible.
|
||||||
|
}
|
||||||
|
LoopVarHash.SetInlineSuffixOnly(mostInlineOnly)
|
||||||
|
LoopVarHash.SetFileSuffixOnly(basenameOnly)
|
||||||
|
} else if buildcfg.Experiment.LoopVar && Debug.LoopVar == 0 {
|
||||||
|
Debug.LoopVar = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if Debug.Fmahash != "" {
|
if Debug.Fmahash != "" {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -34,16 +35,38 @@ type HashDebug struct {
|
|||||||
name string // base name of the flag/variable.
|
name string // base name of the flag/variable.
|
||||||
// what file (if any) receives the yes/no logging?
|
// what file (if any) receives the yes/no logging?
|
||||||
// default is os.Stdout
|
// default is os.Stdout
|
||||||
logfile writeSyncer
|
logfile writeSyncer
|
||||||
posTmp []src.Pos
|
posTmp []src.Pos
|
||||||
bytesTmp bytes.Buffer
|
bytesTmp bytes.Buffer
|
||||||
matches []hashAndMask // A hash matches if one of these matches.
|
matches []hashAndMask // A hash matches if one of these matches.
|
||||||
yes, no bool
|
yes, no bool
|
||||||
|
fileSuffixOnly bool // for Pos hashes, remove the directory prefix.
|
||||||
|
inlineSuffixOnly bool // for Pos hashes, remove all but the most inline position.
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileSuffixOnly controls whether hashing and reporting use the entire
|
||||||
|
// file path name, just the basename. This makes hashing more consistent,
|
||||||
|
// at the expense of being able to certainly locate the file.
|
||||||
|
func (d *HashDebug) SetFileSuffixOnly(b bool) *HashDebug {
|
||||||
|
d.fileSuffixOnly = b
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInlineSuffixOnly controls whether hashing and reporting use the entire
|
||||||
|
// inline position, or just the most-inline suffix. Compiler debugging tends
|
||||||
|
// to want the whole inlining, debugging user problems (loopvarhash, e.g.)
|
||||||
|
// typically does not need to see the entire inline tree, there is just one
|
||||||
|
// copy of the source code.
|
||||||
|
func (d *HashDebug) SetInlineSuffixOnly(b bool) *HashDebug {
|
||||||
|
d.inlineSuffixOnly = b
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default compiler-debugging HashDebug, for "-d=gossahash=..."
|
// The default compiler-debugging HashDebug, for "-d=gossahash=..."
|
||||||
var hashDebug *HashDebug
|
var hashDebug *HashDebug
|
||||||
var FmaHash *HashDebug
|
|
||||||
|
var FmaHash *HashDebug // for debugging fused-multiply-add floating point changes
|
||||||
|
var LoopVarHash *HashDebug // for debugging shared/private loop variable changes
|
||||||
|
|
||||||
// DebugHashMatch reports whether debug variable Gossahash
|
// DebugHashMatch reports whether debug variable Gossahash
|
||||||
//
|
//
|
||||||
@ -56,10 +79,10 @@ var FmaHash *HashDebug
|
|||||||
// 4. is a suffix of the sha1 hash of pkgAndName (returns true)
|
// 4. is a suffix of the sha1 hash of pkgAndName (returns true)
|
||||||
//
|
//
|
||||||
// 5. OR
|
// 5. OR
|
||||||
// if the value is in the regular language "[01]+(;[01]+)+"
|
// if the value is in the regular language "[01]+(/[01]+)+"
|
||||||
// test the [01]+ substrings after in order returning true
|
// test the [01]+ substrings after in order returning true
|
||||||
// for the first one that suffix-matches. The substrings AFTER
|
// for the first one that suffix-matches. The substrings AFTER
|
||||||
// the first semicolon are numbered 0,1, etc and are named
|
// the first slash are numbered 0,1, etc and are named
|
||||||
// fmt.Sprintf("%s%d", varname, number)
|
// fmt.Sprintf("%s%d", varname, number)
|
||||||
// Clause 5 is not really intended for human use and only
|
// Clause 5 is not really intended for human use and only
|
||||||
// matters for failures that require multiple triggers.
|
// matters for failures that require multiple triggers.
|
||||||
@ -235,6 +258,8 @@ func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool {
|
|||||||
// package name and path. The output trigger string is prefixed with "POS=" so
|
// package name and path. The output trigger string is prefixed with "POS=" so
|
||||||
// that tools processing the output can reliably tell the difference. The mutex
|
// that tools processing the output can reliably tell the difference. The mutex
|
||||||
// locking is also more frequent and more granular.
|
// locking is also more frequent and more granular.
|
||||||
|
// Note that the default answer for no environment variable (d == nil)
|
||||||
|
// is "yes", do the thing.
|
||||||
func (d *HashDebug) DebugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
|
func (d *HashDebug) DebugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
return true
|
return true
|
||||||
@ -242,6 +267,11 @@ func (d *HashDebug) DebugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
|
|||||||
if d.no {
|
if d.no {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Written this way to make inlining likely.
|
||||||
|
return d.debugHashMatchPos(ctxt, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HashDebug) debugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
defer d.mu.Unlock()
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
@ -278,9 +308,17 @@ func (d *HashDebug) bytesForPos(ctxt *obj.Link, pos src.XPos) []byte {
|
|||||||
// Reverse posTmp to put outermost first.
|
// Reverse posTmp to put outermost first.
|
||||||
b := &d.bytesTmp
|
b := &d.bytesTmp
|
||||||
b.Reset()
|
b.Reset()
|
||||||
for i := len(d.posTmp) - 1; i >= 0; i-- {
|
start := len(d.posTmp) - 1
|
||||||
|
if d.inlineSuffixOnly {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
for i := start; i >= 0; i-- {
|
||||||
p := &d.posTmp[i]
|
p := &d.posTmp[i]
|
||||||
fmt.Fprintf(b, "%s:%d:%d", p.Filename(), p.Line(), p.Col())
|
f := p.Filename()
|
||||||
|
if d.fileSuffixOnly {
|
||||||
|
f = filepath.Base(f)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "%s:%d:%d", f, p.Line(), p.Col())
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
b.WriteByte(';')
|
b.WriteByte(';')
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func (e *escape) stmt(n ir.Node) {
|
|||||||
}
|
}
|
||||||
e.loopDepth++
|
e.loopDepth++
|
||||||
default:
|
default:
|
||||||
base.Fatalf("label missing tag")
|
base.Fatalf("label %v missing tag", n.Label)
|
||||||
}
|
}
|
||||||
delete(e.labels, n.Label)
|
delete(e.labels, n.Label)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"cmd/compile/internal/inline"
|
"cmd/compile/internal/inline"
|
||||||
"cmd/compile/internal/ir"
|
"cmd/compile/internal/ir"
|
||||||
"cmd/compile/internal/logopt"
|
"cmd/compile/internal/logopt"
|
||||||
|
"cmd/compile/internal/loopvar"
|
||||||
"cmd/compile/internal/noder"
|
"cmd/compile/internal/noder"
|
||||||
"cmd/compile/internal/pgo"
|
"cmd/compile/internal/pgo"
|
||||||
"cmd/compile/internal/pkginit"
|
"cmd/compile/internal/pkginit"
|
||||||
@ -265,10 +266,12 @@ func Main(archInit func(*ssagen.ArchInfo)) {
|
|||||||
}
|
}
|
||||||
noder.MakeWrappers(typecheck.Target) // must happen after inlining
|
noder.MakeWrappers(typecheck.Target) // must happen after inlining
|
||||||
|
|
||||||
// Devirtualize.
|
// Devirtualize and get variable capture right in for loops
|
||||||
|
var transformed []*ir.Name
|
||||||
for _, n := range typecheck.Target.Decls {
|
for _, n := range typecheck.Target.Decls {
|
||||||
if n.Op() == ir.ODCLFUNC {
|
if n.Op() == ir.ODCLFUNC {
|
||||||
devirtualize.Func(n.(*ir.Func))
|
devirtualize.Func(n.(*ir.Func))
|
||||||
|
transformed = append(transformed, loopvar.ForCapture(n.(*ir.Func))...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ir.CurFunc = nil
|
ir.CurFunc = nil
|
||||||
@ -293,6 +296,46 @@ func Main(archInit func(*ssagen.ArchInfo)) {
|
|||||||
base.Timer.Start("fe", "escapes")
|
base.Timer.Start("fe", "escapes")
|
||||||
escape.Funcs(typecheck.Target.Decls)
|
escape.Funcs(typecheck.Target.Decls)
|
||||||
|
|
||||||
|
if 2 <= base.Debug.LoopVar && base.Debug.LoopVar != 11 || 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.
|
||||||
|
for _, n := range transformed {
|
||||||
|
pos := n.Pos()
|
||||||
|
if logopt.Enabled() {
|
||||||
|
// For automated checking of coverage of this transformation, include this in the JSON information.
|
||||||
|
if n.Esc() == ir.EscHeap {
|
||||||
|
logopt.LogOpt(pos, "transform-escape", "loopvar", ir.FuncName(n.Curfn))
|
||||||
|
} else {
|
||||||
|
logopt.LogOpt(pos, "transform-noescape", "loopvar", ir.FuncName(n.Curfn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner := base.Ctxt.InnermostPos(pos)
|
||||||
|
outer := base.Ctxt.OutermostPos(pos)
|
||||||
|
if inner == outer {
|
||||||
|
if n.Esc() == ir.EscHeap {
|
||||||
|
base.WarnfAt(pos, "transformed loop variable %v escapes", n)
|
||||||
|
} else {
|
||||||
|
base.WarnfAt(pos, "transformed loop variable %v does not escape", n)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Report the problem at the line where it actually occurred.
|
||||||
|
afn := inner.AbsFilename()
|
||||||
|
pb, ok := fileToPosBase[afn]
|
||||||
|
if !ok {
|
||||||
|
pb = src.NewFileBase(inner.Filename(), afn)
|
||||||
|
fileToPosBase[afn] = pb
|
||||||
|
}
|
||||||
|
inner.SetBase(pb) // rebasing w/o inline context makes it print correctly in WarnfAt; otherwise it prints as outer.
|
||||||
|
innerXPos := base.Ctxt.PosTable.XPos(inner)
|
||||||
|
|
||||||
|
if n.Esc() == ir.EscHeap {
|
||||||
|
base.WarnfAt(innerXPos, "transformed loop variable %v escapes (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
|
||||||
|
} else {
|
||||||
|
base.WarnfAt(innerXPos, "transformed loop variable %v does not escape (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Collect information for go:nowritebarrierrec
|
// Collect information for go:nowritebarrierrec
|
||||||
// checking. This must happen before transforming closures during Walk
|
// checking. This must happen before transforming closures during Walk
|
||||||
// We'll do the final check after write barriers are
|
// We'll do the final check after write barriers are
|
||||||
|
@ -163,6 +163,15 @@ func NewBranchStmt(pos src.XPos, op Op, label *types.Sym) *BranchStmt {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *BranchStmt) SetOp(op Op) {
|
||||||
|
switch op {
|
||||||
|
default:
|
||||||
|
panic(n.no("SetOp " + op.String()))
|
||||||
|
case OBREAK, OCONTINUE, OFALL, OGOTO:
|
||||||
|
n.op = op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *BranchStmt) Sym() *types.Sym { return n.Label }
|
func (n *BranchStmt) Sym() *types.Sym { return n.Label }
|
||||||
|
|
||||||
// A CaseClause is a case statement in a switch or select: case List: Body.
|
// A CaseClause is a case statement in a switch or select: case List: Body.
|
||||||
|
449
src/cmd/compile/internal/loopvar/loopvar.go
Normal file
449
src/cmd/compile/internal/loopvar/loopvar.go
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
// 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/typecheck"
|
||||||
|
"cmd/compile/internal/types"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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).
|
||||||
|
|
||||||
|
// For this code, the meaningful debug and hash flag settings
|
||||||
|
//
|
||||||
|
// base.Debug.LoopVar <= 0 => do not transform
|
||||||
|
//
|
||||||
|
// 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) []*ir.Name {
|
||||||
|
if base.Debug.LoopVar <= 0 { // code in base:flags.go ensures >= 1 if loopvarhash != ""
|
||||||
|
// TODO remove this when the transformation is made sensitive to inlining; this is least-risk for 1.21
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a loop variable is transformed it is appended to this slice for later logging
|
||||||
|
var transformed []*ir.Name
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] {
|
||||||
|
if base.LoopVarHash.DebugHashMatchPos(base.Ctxt, n.Pos()) {
|
||||||
|
// Rename the loop key, prefix body with assignment from loop key
|
||||||
|
transformed = append(transformed, n)
|
||||||
|
tk := typecheck.Temp(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, 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 {
|
||||||
|
switch x := n.(type) {
|
||||||
|
case *ir.ClosureExpr:
|
||||||
|
for _, cv := range x.Func.ClosureVars {
|
||||||
|
v := cv.Canonical()
|
||||||
|
if _, ok := possiblyLeaked[v]; ok {
|
||||||
|
possiblyLeaked[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ir.AddrExpr:
|
||||||
|
// 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.RangeStmt:
|
||||||
|
if !x.Def {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
noteMayLeak(x.Key)
|
||||||
|
noteMayLeak(x.Value)
|
||||||
|
ir.DoChildren(n, scanChildrenThenTransform)
|
||||||
|
x.Key = maybeReplaceVar(x.Key, x)
|
||||||
|
x.Value = maybeReplaceVar(x.Value, x)
|
||||||
|
return false
|
||||||
|
|
||||||
|
case *ir.ForStmt:
|
||||||
|
forAllDefInInit(x, noteMayLeak)
|
||||||
|
ir.DoChildren(n, scanChildrenThenTransform)
|
||||||
|
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] {
|
||||||
|
// Hash on n.Pos() for most precise failure location.
|
||||||
|
if base.LoopVarHash.DebugHashMatchPos(base.Ctxt, n.Pos()) {
|
||||||
|
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, z)
|
||||||
|
|
||||||
|
tz := typecheck.Temp(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.AssignStmt
|
||||||
|
if postNotNil {
|
||||||
|
// body' = prebody +
|
||||||
|
// (6) if tmp_first {tmp_first = false} else {Post} +
|
||||||
|
// if !cond {break} + ...
|
||||||
|
tmpFirst := typecheck.Temp(types.Types[types.TBOOL])
|
||||||
|
|
||||||
|
// tmpFirstAssign assigns val to tmpFirst
|
||||||
|
tmpFirstAssign := func(val bool) *ir.AssignStmt {
|
||||||
|
s := ir.NewAssignStmt(x.Pos(), tmpFirst, typecheck.OrigBool(tmpFirst, val))
|
||||||
|
s.SetTypecheck(1)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFirstDcl = tmpFirstAssign(true)
|
||||||
|
tmpFirstDcl.Def = true // also declares tmpFirst
|
||||||
|
tmpFirstSetFalse := tmpFirstAssign(false)
|
||||||
|
ifTmpFirst := ir.NewIfStmt(x.Pos(), tmpFirst, ir.Nodes{tmpFirstSetFalse}, ir.Nodes{x.Post})
|
||||||
|
ifTmpFirst.SetTypecheck(1)
|
||||||
|
preBody.Append(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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 assignemnts 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)
|
||||||
|
x.Enter = editNodes(x.Enter)
|
||||||
|
x.Exit = editNodes(x.Exit)
|
||||||
|
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)
|
||||||
|
}
|
152
src/cmd/compile/internal/loopvar/loopvar_test.go
Normal file
152
src/cmd/compile/internal/loopvar/loopvar_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// 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"
|
||||||
|
"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", "transformed loop variable i ", 0, for_files},
|
||||||
|
|
||||||
|
{"-1", "", 11, range_files[:1]},
|
||||||
|
{"0", "", 0, range_files[:1]},
|
||||||
|
{"1", "", 0, range_files[:1]},
|
||||||
|
{"2", "transformed loop variable i ", 0, range_files},
|
||||||
|
|
||||||
|
{"1", "", 0, []string{"for_nested.go"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoopVar checks that the GOEXPERIMENT and debug flags behave as expected.
|
||||||
|
func TestLoopVar(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=-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 TestLoopVarHashes(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(hash 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", root)
|
||||||
|
cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=loopvarhash=FS"+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)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := f("000100000010011111101100")
|
||||||
|
t.Logf(m)
|
||||||
|
|
||||||
|
mCount := strings.Count(m, "loopvarhash triggered POS=main.go:27:6")
|
||||||
|
otherCount := strings.Count(m, "loopvarhash")
|
||||||
|
if mCount < 1 {
|
||||||
|
t.Errorf("Did not see expected value of m compile")
|
||||||
|
}
|
||||||
|
if mCount != otherCount {
|
||||||
|
t.Errorf("Saw extraneous hash matches")
|
||||||
|
}
|
||||||
|
// 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("Did not see expected value of m run")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
115
src/cmd/compile/internal/loopvar/testdata/for_complicated_esc_address.go
vendored
Normal file
115
src/cmd/compile/internal/loopvar/testdata/for_complicated_esc_address.go
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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() {
|
||||||
|
ss, sa := shared(23)
|
||||||
|
ps, pa := private(23)
|
||||||
|
es, ea := experiment(23)
|
||||||
|
|
||||||
|
fmt.Printf("shared s, a; private, s, a; experiment s, a = %d, %d; %d, %d; %d, %d\n", ss, sa, ps, pa, es, ea)
|
||||||
|
|
||||||
|
if ss != ps || ss != es || ea != pa || sa == pa {
|
||||||
|
os.Exit(11)
|
||||||
|
} else {
|
||||||
|
fmt.Println("PASS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func experiment(x int) (int, int) {
|
||||||
|
sum := 0
|
||||||
|
var is []*int
|
||||||
|
for i := x; i != 1; i = i / 2 {
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
if i == j { // 10 skips
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum++
|
||||||
|
}
|
||||||
|
i = i*3 + 1
|
||||||
|
if i&1 == 0 {
|
||||||
|
is = append(is, &i)
|
||||||
|
for i&2 == 0 {
|
||||||
|
i = i >> 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = i + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asum := 0
|
||||||
|
for _, pi := range is {
|
||||||
|
asum += *pi
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum, asum
|
||||||
|
}
|
||||||
|
|
||||||
|
func private(x int) (int, int) {
|
||||||
|
sum := 0
|
||||||
|
var is []*int
|
||||||
|
I := x
|
||||||
|
for ; I != 1; I = I / 2 {
|
||||||
|
i := I
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
if i == j { // 10 skips
|
||||||
|
I = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum++
|
||||||
|
}
|
||||||
|
i = i*3 + 1
|
||||||
|
if i&1 == 0 {
|
||||||
|
is = append(is, &i)
|
||||||
|
for i&2 == 0 {
|
||||||
|
i = i >> 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = i + i
|
||||||
|
}
|
||||||
|
I = i
|
||||||
|
}
|
||||||
|
|
||||||
|
asum := 0
|
||||||
|
for _, pi := range is {
|
||||||
|
asum += *pi
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum, asum
|
||||||
|
}
|
||||||
|
|
||||||
|
func shared(x int) (int, int) {
|
||||||
|
sum := 0
|
||||||
|
var is []*int
|
||||||
|
i := x
|
||||||
|
for ; i != 1; i = i / 2 {
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
if i == j { // 10 skips
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum++
|
||||||
|
}
|
||||||
|
i = i*3 + 1
|
||||||
|
if i&1 == 0 {
|
||||||
|
is = append(is, &i)
|
||||||
|
for i&2 == 0 {
|
||||||
|
i = i >> 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = i + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asum := 0
|
||||||
|
for _, pi := range is {
|
||||||
|
asum += *pi
|
||||||
|
}
|
||||||
|
return sum, asum
|
||||||
|
}
|
45
src/cmd/compile/internal/loopvar/testdata/for_esc_address.go
vendored
Normal file
45
src/cmd/compile/internal/loopvar/testdata/for_esc_address.go
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
51
src/cmd/compile/internal/loopvar/testdata/for_esc_closure.go
vendored
Normal file
51
src/cmd/compile/internal/loopvar/testdata/for_esc_closure.go
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
51
src/cmd/compile/internal/loopvar/testdata/for_esc_method.go
vendored
Normal file
51
src/cmd/compile/internal/loopvar/testdata/for_esc_method.go
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
48
src/cmd/compile/internal/loopvar/testdata/for_esc_minimal_closure.go
vendored
Normal file
48
src/cmd/compile/internal/loopvar/testdata/for_esc_minimal_closure.go
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
47
src/cmd/compile/internal/loopvar/testdata/for_nested.go
vendored
Normal file
47
src/cmd/compile/internal/loopvar/testdata/for_nested.go
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/cmd/compile/internal/loopvar/testdata/inlines/a/a.go
vendored
Normal file
20
src/cmd/compile/internal/loopvar/testdata/inlines/a/a.go
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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
|
||||||
|
}
|
21
src/cmd/compile/internal/loopvar/testdata/inlines/b/b.go
vendored
Normal file
21
src/cmd/compile/internal/loopvar/testdata/inlines/b/b.go
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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
|
||||||
|
}
|
14
src/cmd/compile/internal/loopvar/testdata/inlines/c/c.go
vendored
Normal file
14
src/cmd/compile/internal/loopvar/testdata/inlines/c/c.go
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// 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
|
||||||
|
}
|
53
src/cmd/compile/internal/loopvar/testdata/inlines/main.go
vendored
Normal file
53
src/cmd/compile/internal/loopvar/testdata/inlines/main.go
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
47
src/cmd/compile/internal/loopvar/testdata/range_esc_address.go
vendored
Normal file
47
src/cmd/compile/internal/loopvar/testdata/range_esc_address.go
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
53
src/cmd/compile/internal/loopvar/testdata/range_esc_closure.go
vendored
Normal file
53
src/cmd/compile/internal/loopvar/testdata/range_esc_closure.go
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
53
src/cmd/compile/internal/loopvar/testdata/range_esc_method.go
vendored
Normal file
53
src/cmd/compile/internal/loopvar/testdata/range_esc_method.go
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
50
src/cmd/compile/internal/loopvar/testdata/range_esc_minimal_closure.go
vendored
Normal file
50
src/cmd/compile/internal/loopvar/testdata/range_esc_minimal_closure.go
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
@ -2366,7 +2366,7 @@ func (r *reader) expr() (res ir.Node) {
|
|||||||
if recv.Type().IsInterface() {
|
if recv.Type().IsInterface() {
|
||||||
// N.B., this happens currently for typeparam/issue51521.go
|
// N.B., this happens currently for typeparam/issue51521.go
|
||||||
// and typeparam/typeswitch3.go.
|
// and typeparam/typeswitch3.go.
|
||||||
if base.Flag.LowerM > 0 {
|
if base.Flag.LowerM != 0 {
|
||||||
base.WarnfAt(method.Pos(), "imprecise interface call")
|
base.WarnfAt(method.Pos(), "imprecise interface call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/internal/goexperiment/exp_loopvar_off.go
Normal file
9
src/internal/goexperiment/exp_loopvar_off.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build !goexperiment.loopvar
|
||||||
|
// +build !goexperiment.loopvar
|
||||||
|
|
||||||
|
package goexperiment
|
||||||
|
|
||||||
|
const LoopVar = false
|
||||||
|
const LoopVarInt = 0
|
9
src/internal/goexperiment/exp_loopvar_on.go
Normal file
9
src/internal/goexperiment/exp_loopvar_on.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build goexperiment.loopvar
|
||||||
|
// +build goexperiment.loopvar
|
||||||
|
|
||||||
|
package goexperiment
|
||||||
|
|
||||||
|
const LoopVar = true
|
||||||
|
const LoopVarInt = 1
|
@ -101,4 +101,8 @@ type Flags struct {
|
|||||||
// When this experiment is enabled, cgo rule checks occur regardless
|
// When this experiment is enabled, cgo rule checks occur regardless
|
||||||
// of the GODEBUG=cgocheck setting provided at runtime.
|
// of the GODEBUG=cgocheck setting provided at runtime.
|
||||||
CgoCheck2 bool
|
CgoCheck2 bool
|
||||||
|
|
||||||
|
// LoopVar changes loop semantics so that each iteration gets its own
|
||||||
|
// copy of the iteration variable.
|
||||||
|
LoopVar bool
|
||||||
}
|
}
|
||||||
|
3
src/runtime/race/testdata/mop_test.go
vendored
3
src/runtime/race/testdata/mop_test.go
vendored
@ -331,7 +331,8 @@ func TestRaceRange(t *testing.T) {
|
|||||||
var x, y int
|
var x, y int
|
||||||
_ = x + y
|
_ = x + y
|
||||||
done := make(chan bool, N)
|
done := make(chan bool, N)
|
||||||
for i, v := range a {
|
var i, v int // declare here (not in for stmt) so that i and v are shared w/ or w/o loop variable sharing change
|
||||||
|
for i, v = range a {
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
// we don't want a write-vs-write race
|
// we don't want a write-vs-write race
|
||||||
// so there is no array b here
|
// so there is no array b here
|
||||||
|
@ -73,7 +73,8 @@ func main() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var g func() int
|
var g func() int
|
||||||
for i := range [2]int{} {
|
var i int
|
||||||
|
for i = range [2]int{} {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
g = func() int {
|
g = func() int {
|
||||||
return i // test that we capture by ref here, i is mutated on every interaction
|
return i // test that we capture by ref here, i is mutated on every interaction
|
||||||
|
@ -126,7 +126,8 @@ func range_escapes2(x, y int) (*int, *int) {
|
|||||||
var p [2]*int
|
var p [2]*int
|
||||||
a[0] = x
|
a[0] = x
|
||||||
a[1] = y
|
a[1] = y
|
||||||
for k, v := range a {
|
var k, v int
|
||||||
|
for k, v = range a {
|
||||||
p[k] = &v
|
p[k] = &v
|
||||||
}
|
}
|
||||||
return p[0], p[1]
|
return p[0], p[1]
|
||||||
@ -136,7 +137,8 @@ func range_escapes2(x, y int) (*int, *int) {
|
|||||||
func for_escapes2(x int, y int) (*int, *int) {
|
func for_escapes2(x int, y int) (*int, *int) {
|
||||||
var p [2]*int
|
var p [2]*int
|
||||||
n := 0
|
n := 0
|
||||||
for i := x; n < 2; i = y {
|
i := x
|
||||||
|
for ; n < 2; i = y {
|
||||||
p[n] = &i
|
p[n] = &i
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,9 @@ func test1(iter int) {
|
|||||||
// Heap -> stack pointer eventually causes badness when stack reallocation
|
// Heap -> stack pointer eventually causes badness when stack reallocation
|
||||||
// occurs.
|
// occurs.
|
||||||
|
|
||||||
var fn func() // ERROR "moved to heap: fn$"
|
var fn func() // ERROR "moved to heap: fn$"
|
||||||
for i := 0; i < maxI; i++ { // ERROR "moved to heap: i$"
|
i := 0 // ERROR "moved to heap: i$"
|
||||||
|
for ; i < maxI; i++ {
|
||||||
// var fn func() // this makes it work, because fn stays off heap
|
// var fn func() // this makes it work, because fn stays off heap
|
||||||
j := 0 // ERROR "moved to heap: j$"
|
j := 0 // ERROR "moved to heap: j$"
|
||||||
fn = func() { // ERROR "func literal escapes to heap$"
|
fn = func() { // ERROR "func literal escapes to heap$"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user