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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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"
|
2023-05-04 16:08:13 -04:00
|
|
|
"cmd/compile/internal/logopt"
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
"cmd/compile/internal/typecheck"
|
|
|
|
|
"cmd/compile/internal/types"
|
2023-05-04 16:08:13 -04:00
|
|
|
"cmd/internal/src"
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
2023-05-04 16:08:13 -04:00
|
|
|
type VarAndLoop struct {
|
|
|
|
|
Name *ir.Name
|
|
|
|
|
Loop ir.Node // the *ir.ForStmt or *ir.ForStmt. Used for identity and position
|
|
|
|
|
LastPos src.XPos // the last position observed within Loop
|
|
|
|
|
}
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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).
|
|
|
|
|
//
|
2023-01-25 17:08:16 -05:00
|
|
|
// 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:
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
//
|
|
|
|
|
// 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.
|
2023-05-04 16:08:13 -04:00
|
|
|
func ForCapture(fn *ir.Func) []VarAndLoop {
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// if a loop variable is transformed it is appended to this slice for later logging
|
2023-05-04 16:08:13 -04:00
|
|
|
var transformed []VarAndLoop
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
|
2023-05-10 19:29:18 -04:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
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)
|
|
|
|
|
|
2023-02-28 17:36:38 -05:00
|
|
|
// these enable an optimization of "escape" under return statements
|
|
|
|
|
loopDepth := 0
|
|
|
|
|
returnInLoopDepth := 0
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-04 16:08:13 -04:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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] {
|
2023-05-10 19:29:18 -04:00
|
|
|
desc := func() string {
|
|
|
|
|
return describe(n)
|
|
|
|
|
}
|
|
|
|
|
if base.LoopVarHash.MatchPos(n.Pos(), desc) {
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// Rename the loop key, prefix body with assignment from loop key
|
2023-05-04 16:08:13 -04:00
|
|
|
transformed = append(transformed, VarAndLoop{n, x, lastPos})
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
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:
|
2023-01-25 17:08:16 -05:00
|
|
|
// 1. if x is a for/range w/ DistinctVars, note declared iteration variables possiblyLeaked (PL)
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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 {
|
2023-05-04 16:08:13 -04:00
|
|
|
|
|
|
|
|
if loopDepth > 0 {
|
|
|
|
|
updateLastPos(n.Pos())
|
|
|
|
|
}
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
switch x := n.(type) {
|
|
|
|
|
case *ir.ClosureExpr:
|
2023-02-28 17:36:38 -05:00
|
|
|
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
|
|
|
|
|
}
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
for _, cv := range x.Func.ClosureVars {
|
|
|
|
|
v := cv.Canonical()
|
|
|
|
|
if _, ok := possiblyLeaked[v]; ok {
|
|
|
|
|
possiblyLeaked[v] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case *ir.AddrExpr:
|
2023-02-28 17:36:38 -05:00
|
|
|
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
|
|
|
|
|
}
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-28 17:36:38 -05:00
|
|
|
case *ir.ReturnStmt:
|
|
|
|
|
savedRILD := returnInLoopDepth
|
|
|
|
|
returnInLoopDepth = loopDepth
|
|
|
|
|
defer func() { returnInLoopDepth = savedRILD }()
|
|
|
|
|
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
case *ir.RangeStmt:
|
2023-01-25 17:08:16 -05:00
|
|
|
if !(x.Def && x.DistinctVars) {
|
|
|
|
|
// range loop must define its iteration variables AND have distinctVars.
|
|
|
|
|
x.DistinctVars = false
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
noteMayLeak(x.Key)
|
|
|
|
|
noteMayLeak(x.Value)
|
2023-02-28 17:36:38 -05:00
|
|
|
loopDepth++
|
2023-05-04 16:08:13 -04:00
|
|
|
savedLastPos := lastPos
|
|
|
|
|
lastPos = x.Pos() // this sets the file.
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
ir.DoChildren(n, scanChildrenThenTransform)
|
2023-02-28 17:36:38 -05:00
|
|
|
loopDepth--
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
x.Key = maybeReplaceVar(x.Key, x)
|
|
|
|
|
x.Value = maybeReplaceVar(x.Value, x)
|
2023-05-04 16:08:13 -04:00
|
|
|
thisLastPos := lastPos
|
|
|
|
|
lastPos = savedLastPos
|
|
|
|
|
updateLastPos(thisLastPos) // this will propagate lastPos if in the same file.
|
2023-01-25 17:08:16 -05:00
|
|
|
x.DistinctVars = false
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
case *ir.ForStmt:
|
2023-01-25 17:08:16 -05:00
|
|
|
if !x.DistinctVars {
|
|
|
|
|
break
|
|
|
|
|
}
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
forAllDefInInit(x, noteMayLeak)
|
2023-02-28 17:36:38 -05:00
|
|
|
loopDepth++
|
2023-05-04 16:08:13 -04:00
|
|
|
savedLastPos := lastPos
|
|
|
|
|
lastPos = x.Pos() // this sets the file.
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
ir.DoChildren(n, scanChildrenThenTransform)
|
2023-02-28 17:36:38 -05:00
|
|
|
loopDepth--
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
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] {
|
2023-05-10 19:29:18 -04:00
|
|
|
desc := func() string {
|
|
|
|
|
return describe(n)
|
|
|
|
|
}
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// Hash on n.Pos() for most precise failure location.
|
2023-05-10 19:29:18 -04:00
|
|
|
if base.LoopVarHash.MatchPos(n.Pos(), desc) {
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
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 {
|
2023-05-04 16:08:13 -04:00
|
|
|
transformed = append(transformed, VarAndLoop{z, x, lastPos})
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2023-05-04 16:08:13 -04:00
|
|
|
thisLastPos := lastPos
|
|
|
|
|
lastPos = savedLastPos
|
|
|
|
|
updateLastPos(thisLastPos) // this will propagate lastPos if in the same file.
|
2023-01-25 17:08:16 -05:00
|
|
|
x.DistinctVars = false
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-06 00:11:33 +08:00
|
|
|
// forAllDefInInitUpdate applies "do" to all the defining assignments in the Init clause of a ForStmt.
|
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>
2022-06-12 15:33:57 -04:00
|
|
|
// 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)
|
|
|
|
|
}
|
2023-05-04 16:08:13 -04:00
|
|
|
|
|
|
|
|
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()
|
2023-05-05 16:29:37 -04:00
|
|
|
|
|
|
|
|
inner := base.Ctxt.InnermostPos(pos)
|
|
|
|
|
outer := base.Ctxt.OutermostPos(pos)
|
|
|
|
|
|
2023-05-04 16:08:13 -04:00
|
|
|
if logopt.Enabled() {
|
|
|
|
|
// For automated checking of coverage of this transformation, include this in the JSON information.
|
2023-05-05 16:29:37 -04:00
|
|
|
var nString interface{} = n
|
|
|
|
|
if inner != outer {
|
|
|
|
|
nString = fmt.Sprintf("%v (from inline)", n)
|
|
|
|
|
}
|
2023-05-04 16:08:13 -04:00
|
|
|
if n.Esc() == ir.EscHeap {
|
2023-05-05 16:29:37 -04:00
|
|
|
logopt.LogOpt(pos, "iteration-variable-to-heap", "loopvar", ir.FuncName(n.Curfn), nString)
|
2023-05-04 16:08:13 -04:00
|
|
|
} else {
|
2023-05-05 16:29:37 -04:00
|
|
|
logopt.LogOpt(pos, "iteration-variable-to-stack", "loopvar", ir.FuncName(n.Curfn), nString)
|
2023-05-04 16:08:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if print {
|
|
|
|
|
if inner == outer {
|
|
|
|
|
if n.Esc() == ir.EscHeap {
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(pos, "loop variable %v now per-iteration, heap-allocated", n)
|
2023-05-04 16:08:13 -04:00
|
|
|
} else {
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(pos, "loop variable %v now per-iteration, stack-allocated", n)
|
2023-05-04 16:08:13 -04:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
innerXPos := trueInlinedPos(inner)
|
|
|
|
|
if n.Esc() == ir.EscHeap {
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(innerXPos, "loop variable %v now per-iteration, heap-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
|
2023-05-04 16:08:13 -04:00
|
|
|
} else {
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(innerXPos, "loop variable %v now per-iteration, stack-allocated (loop inlined into %s:%d)", n, outer.Filename(), outer.Line())
|
2023-05-04 16:08:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, l := range loops {
|
|
|
|
|
pos := l.loop.Pos()
|
|
|
|
|
last := l.last
|
|
|
|
|
if logopt.Enabled() {
|
|
|
|
|
// Intended to
|
2023-05-05 16:29:37 -04:00
|
|
|
logopt.LogOptRange(pos, last, "loop-modified", "loopvar", ir.FuncName(l.curfn))
|
2023-05-04 16:08:13 -04:00
|
|
|
}
|
|
|
|
|
if print && 3 <= 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 {
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(pos, "loop ending at %d:%d was modified", last.Line(), last.Col())
|
2023-05-04 16:08:13 -04:00
|
|
|
} else {
|
|
|
|
|
pos = trueInlinedPos(inner)
|
|
|
|
|
last = trueInlinedPos(base.Ctxt.InnermostPos(last))
|
2023-05-05 16:29:37 -04:00
|
|
|
base.WarnfAt(pos, "loop ending at %d:%d was modified (loop inlined into %s:%d)", last.Line(), last.Col(), outer.Filename(), outer.Line())
|
2023-05-04 16:08:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|