go version)?$ go version go version go1.13.5 linux/amd64
And for gccgo:
$ go version go version go1.12.2 gccgo (GCC) 9.2.0 linux/amd64
Yes
go env)?Arch Linux on 64-bit x86
go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/afr/.cache/go-build"
GOENV="/home/afr/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/afr/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build475953563=/tmp/go-build -gno-record-gcc-switches"
In connection with simplebolt, I discovered that the test passed when running with go, but failed when running with gccgo. I shrunk it down to this small test case:
package main
import "fmt"
func wrap(f func() error) error {
return f()
}
func check() (result string, err error) {
result = "A"
return result, wrap(func() error {
result = "B"
return nil
})
}
func main() {
if result, _ := check(); result == "A" {
fmt.Println("Compiled with gccgo")
} else {
fmt.Println("Compiled with go")
}
}
When running this, it outputs "Compiled with gccgo" when compiled with gccgo and "Compiled with go" when compiled with go.
Also available at play.golang.org
I would expect to see the same output for both go and gccgo. I don't know which compilation is "correct".
Different output for go and gccgo.
The Go language does not fully specify the order of evaluation. With regard to this test case, in the return statement, it does not specify whether the value of the local variable result is read before or after the function is executed. So I would say that this is a legitimate difference between the two compilers, and is not a bug.
CC @griesemer @mdempsky for a second opinion.
Although it is not a bug, it is interesting that the behaviors between standard and short variable declarations are different (for gc):
package main
import "fmt"
func f(p *int) int {
*p = 123
return *p
}
func bar() {
var x int
y, z := x, f(&x)
fmt.Println(y, z) // 123 123
}
func baz() {
var x int
var y, z = x, f(&x)
fmt.Println(y, z) // 0 123
}
func main() {
bar()
baz()
}
Given:
var result string
var err error
result, err = "A", func() error { result = "B"; return nil }()
I think the result is unambiguously "A": we evaluate the RHS fully before assigning to any values to the LHS. So the "A" assignment happens last and wins.
I think arguably the same logic should apply to return statements, and in the original test case gccgo is correct, while cmd/compile is wrong.
Edit: Oh, but since the original example uses result instead of "A", the evaluation of that variable can happen after the function call, like @ianlancetaylor pointed out already.
Better assignment example is:
var result string = "A"
var err error
result, err = result, func() error { result = "B"; return nil }()
Or:
var x, y int
x, y = x + 100, func() int { x = 1; return 0 }()
// Unspecified whether x is now 100 or 101.
Agreed this is a legitimate difference in implementation specific behavior.
@go101 Your examples showing discrepancy between normal and short variable initialization are interesting and probably worth fixing, but I think would be best as a separate issue.
I'm going to go ahead and close as Working As Intended, since @ianlancetaylor and I are in agreement on that.
Thanks for looking into this!
@go101 Your examples showing discrepancy between normal and short variable initialization are interesting and probably worth fixing, but I think would be best as a separate issue.
The behavior of var y, z = x, f(&x) seems wrong to me. It's evaluated as:
var x int
var y int
y = x
var z int
z = f(&x)
The spec requires the rhs is evaluated first.
@go101 want to fire an issue 馃槃
@cuonglm The RHS has to be fully evaluated before any assignments happen to the LHS, but it's not specified whether x is evaluated before or after f(&x).
For example,
y, z = x, f(&x)
can be validly compiled as:
// Evaluate RHS
tmp1 := f(&x)
tmp0 := x
// Assign to LHS
y = tmp0
z = tmp1
@cuonglm The RHS has to be fully evaluated before any assignments happen to the LHS, but it's not specified whether
xis evaluated before or afterf(&x).For example,
y, z = x, f(&x)can be validly compiled as:
// Evaluate RHS tmp1 := f(&x) tmp0 := x // Assign to LHS y = tmp0 z = tmp1
Yes right, but I mean the current var form seems not wait f(&x) evaluated before it does assignment, compile with -m -m:
main.go:3:6: can inline f as: func(*int) int { *p = 123; return *p }
main.go:8:6: can inline bar as: func() { var x int; x = <N>; y, z := x, f(&x); println(y, z) }
main.go:10:14: inlining call to f func(*int) int { *p = 123; return *p }
main.go:15:6: can inline baz as: func() { var x int; x = <N>; var y int; y = x; var z int; z = f(&x); println(y, z) }
main.go:17:17: inlining call to f func(*int) int { *p = 123; return *p }
main.go:22:6: can inline main as: func() { bar(); baz() }
main.go:23:5: inlining call to bar func() { var x int; x = <N>; y, z := x, f(&x); println(y, z) }
main.go:23:5: inlining call to f func(*int) int { *p = 123; return *p }
main.go:24:5: inlining call to baz func() { var x int; x = <N>; var y int; y = x; var z int; z = f(&x); println(y, z) }
main.go:24:5: inlining call to f func(*int) int { *p = 123; return *p }
main.go:3:8: p does not escape
Yes right, but I mean the current
varform seems not waitf(&x)evaluated before it does assignment, compile with-m -m:
Fair point, and I think that hints to me why the behavior is different between @go101 's test cases.
But it's also just an implementation detail. Compilers are allowed to freely reorder/combine statements and operations, as long as the program visible behavior is correct. Eg, assigning to y before the RHS is done evaluating is fine because y doesn't come into scope until after the RHS anyway.