go: cmd/compile: global variable initialization done in unexpected order
What version of Go are you using (go version)?
$ go version go version go1.16.4 darwin/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env)?
go env Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/joao/Library/Caches/go-build" GOENV="/Users/joao/Library/Application Support/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/joao/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/joao/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/Cellar/go/1.16.4/libexec" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/Cellar/go/1.16.4/libexec/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.16.4" GCCGO="gccgo" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="/dev/null" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/2d/wcw2b3c57jz69cl5tg_s2fx00000gn/T/go-build3928827666=/tmp/go-build -gno-record-gcc-switches -fno-common" GOROOT/bin/go version: go version go1.16.4 darwin/amd64 GOROOT/bin/go tool compile -V: compile version go1.16.4 uname -v: Darwin Kernel Version 21.3.0: Wed Jan 5 21:37:58 PST 2022; root:xnu-8019.80.24~20/RELEASE_X86_64 ProductName: macOS ProductVersion: 12.2.1 BuildVersion: 21D62 lldb --version: lldb-1103.0.22.10 Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53)
What did you do?
I have a package consisting of the following two files:
f1.go
package main
var A int = 3
var B int = A + 1
var C int = A
f2.go
package main
import "fmt"
var D = f()
func f() int {
A = 1
return 1
}
func main() {
fmt.Println(A, B, C)
}
What did you expect to see?
According to the Go language specification, “package-level variable initialization proceeds stepwise, with each step selecting the variable earliest in declaration order which has no dependencies on uninitialized variables”.
As such, I would expect two possible orders in which the global variables can be initialized:
- A < B < C < D - happens when you compile the project by passing f1.go first to the compiler, followed by f2.go . In this case, the output is “1 4 3”
- A < D < B < C - happens when f2.go is passed first to the compiler. In this case, the expected output would be “1 2 1”.
What did you see instead?
For the second case (when f2.go is passed first), the actual output is “1 2 3”. If instead I rewrite file f1.go to the following, I get the expected output for case 2.
Rewritten f2.go
package main
import "fmt"
var A int = initA()
var B int = initB()
var C int = initC()
func initA() int {
fmt.Println("Init A")
return 3
}
func initB() int {
fmt.Println("Init B")
return A + 1
}
func initC() int {
fmt.Println("Init C")
return A
}
Output
Init A
Init B
Init C
1 2 1
Additional Information
This issue was first discussed in the golang-nuts Google Group (link).
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 1
- Comments: 31 (20 by maintainers)
Commits related to this issue
- compiler: revert `for package-scope "a = b; b = x" just set "a = x"` Revert CL 245098. It caused incorrect initialization ordering. Adjust the runtime package to work even with the CL reverted. Or... — committed to golang/gofrontend by ianlancetaylor 2 years ago
- compiler: revert `for package-scope "a = b; b = x" just set "a = x"` Revert CL 245098. It caused incorrect initialization ordering. Adjust the runtime package to work even with the CL reverted. Or... — committed to xionghul/gcc by ianlancetaylor 2 years ago
- runtime: change maxSearchAddr into a helper function This avoids a dependency on the compiler statically initializing maxSearchAddr, which is necessary so we can disable the (overly aggressive and sp... — committed to golang/go by mdempsky 2 years ago
- compiler: revert `for package-scope "a = b; b = x" just set "a = x"` Revert CL 245098. It caused incorrect initialization ordering. Adjust the runtime package to work even with the CL reverted. Or... — committed to realqhc/gofrontend by ianlancetaylor 2 years ago
- runtime: avoid staticinit dependency with sigsetAllExiting Currently, package runtime runs `osinit` before dynamic initialization of package-scope variables; but on GOOS=linux, `osinit` involves muta... — committed to golang/go by mdempsky 2 years ago
The two scenarios are:
f1.go f2.go: declaration order isA B C Df2.go f1.go: declaration order isD A B CIn the first case it’s simple:
Expected result: 1 4 3
In the second case, I come to a different conclusion from reading the spec than you:
Expected result: 1 2 1
That is, I read “next” to mean “the next variable to be initialized”, not “the next variable following sequentially after the one which was last initialized”. Is this an incorrect reading?
We discussed this earlier today. We’re going to punt this to 1.20. We’re confident the fix is correct, but there are uncertainties about how that might affect users accidentally depending on the existing behavior. The issue has also been present for a long time (and was added to gccgo for compatibility with cmd/compile even). So there doesn’t seem to be an urgency to fix it in 1.19.
I changed gccgo to match gc’s behavior because the runtime package requires it (https://go.dev/cl/245098). I see that CL 395541 keeps the optimizations only for the runtime package, so I guess I’ll do the same in gccgo.
In the second case, if f runs before the assignemnt to C shouldn’t that see the side effect of the call to f?
Edited. There appears to be a bug in the compiler. See https://github.com/golang/go/issues/51913#issuecomment-1077913524.
~This is working as intended.~ Note that the spec also says:
We don’t need multiple files, we can just arrange the variable declarations accordingly. In the first case:
the output is
Here’s the corresponding trace from the type checker’s initialization order computation (this is the trace produced by
types2which is used by the compiler, but note that at the moment the compiler still uses its own init order computation and not thetypes2computation - still they match):For the 2nd case:
the output is
and the corresponding init computation trace is:
Thus, in this case
Dgets initialized beforeBbecause it’s beforeBin the source. This explains the difference.Closing.
A little clarification: OP means the outputs are different between
go run f1.go f2.goandgo run f2.go f1.go. And after rewriting f1.go, things changes a bit.By the specification, the outputs should be always
1 4 3.