Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

language: go and gccgo has different behavior when returning multiple values #36430

Closed
xyproto opened this issue Jan 7, 2020 · 11 comments
Closed
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@xyproto
Copy link
Contributor

xyproto commented Jan 7, 2020

What version of Go are you using (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

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (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"

What did you do?

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

What did you expect to see?

I would expect to see the same output for both go and gccgo. I don't know which compilation is "correct".

What did you see instead?

Different output for go and gccgo.

@gopherbot gopherbot added this to the Gccgo milestone Jan 7, 2020
@ianlancetaylor ianlancetaylor changed the title go and gccgo has different behavior when returning multiple values language: go and gccgo has different behavior when returning multiple values Jan 7, 2020
@ianlancetaylor
Copy link
Member

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.

@zigo101
Copy link

zigo101 commented Jan 7, 2020

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

@toothrot toothrot added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 7, 2020
@mdempsky
Copy link
Contributor

mdempsky commented Jan 7, 2020

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.

@mdempsky
Copy link
Contributor

mdempsky commented Jan 7, 2020

@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.

@mdempsky
Copy link
Contributor

mdempsky commented Jan 7, 2020

I'm going to go ahead and close as Working As Intended, since @ianlancetaylor and I are in agreement on that.

@mdempsky mdempsky closed this as completed Jan 7, 2020
@xyproto
Copy link
Contributor Author

xyproto commented Jan 7, 2020

Thanks for looking into this!

@cuonglm
Copy link
Member

cuonglm commented Jan 8, 2020

@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 😄

@mdempsky
Copy link
Contributor

mdempsky commented Jan 8, 2020

@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
Copy link
Member

cuonglm commented Jan 8, 2020

@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

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

@mdempsky
Copy link
Contributor

mdempsky commented Jan 8, 2020

Yes right, but I mean the current var form seems not wait f(&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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

7 participants