-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
cmd/compile: global variable initialization done in unexpected order #51913
Comments
A little clarification: OP means the outputs are different between By the specification, the outputs should be always |
The two scenarios are:
In 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? |
CC @golang/runtime For additional context, see #31292. Unclear if this is a bug yet, but since it also involves the spec, CC @griesemer who was central to #31292. |
Edited. There appears to be a bug in the compiler. See #51913 (comment).
We don't need multiple files, we can just arrange the variable declarations accordingly. In the first case: package main
var A int = 3
var B int = A + 1
var C int = A
var D = f()
func f() int {
A = 1
return 1
}
func main() {
println(A, B, C, D)
} the output is
Here's the corresponding trace from the type checker's initialization order computation (this is the trace produced by
For the 2nd case: package main
var D = f()
func f() int {
A = 1
return 1
}
func main() {
println(A, B, C, D)
}
var A int = 3
var B int = A + 1
var C int = A the output is
and the corresponding init computation trace is:
Thus, in this case Closing. |
In the second case, if f runs before the assignemnt to C shouldn't that see the side effect of the call to f? |
@aarzilli Good catch, I totally glanced over this. Indeed For the 1st case:
For the 2nd case:
|
Related issue: #49150 |
cc: @mdempsky It looks like cc: @ianlancetaylor for |
The compiler issue here is that we optimize Unfortunately, I think this is more complex than just switching to types2's initialization order. I think cmd/compile is already correctly sorting the initialization statements; it's just misapplying an optimization that isn't actually safe. (And it looks like gccgo has a similar issue.) The easy fix is to just disable that optimization, but that might lead to more dynamic initialization. The more complex fix would involve actually tracking when user-written calls are sequenced during initialization, and keeping track of which global variables they might clobber, and how that limits subsequent optimization opportunities. |
Here's a minimal repro of the issue, btw:
The Go spec says this program should silently exit with success. But instead it currently panics when compiled with either cmd/compile or gccgo. |
Change https://go.dev/cl/395541 mentions this issue: |
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. |
Thanks for the reference. For what it's worth, it looks like replacing I don't feel strongly about which way to proceed here. In general, I prefer fewer special cases for package runtime, in hopes that there are fewer surprises for the runtime team when switching between writing Standard Go and Runtime Go. But package initialization is already inherently weird for package runtime, and it seems unlikely the runtime team is going to write any tricky initializers that would interfere with this optimization. If anyone else leans one way or the other here, let me know. |
I would also prefer that we not special-case the runtime here, especially if there's only one problem right now. Is the issue with (It's too bad we don't have |
Yes, if |
Change https://go.dev/cl/395994 mentions this issue: |
About source file order in a package, will it be better to always sort files in a package before compiling, to remove some unspecified behaviors? |
The Go spec recommends build systems to do that, and cmd/go already does when invoking cmd/compile. |
@mdempsky Is there anything else to do for this issue? Thanks. |
It looks like we have a fix out for this that has been +2'd but not landed. Is this low-risk enough to land at this point, or should we kick this to 1.20 and land it when the tree opens? |
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. |
Revert CL 245098. It caused incorrect initialization ordering. Adjust the runtime package to work even with the CL reverted. Original description of CL 245098: This avoids requiring an init function to initialize the variable. This can only be done if x is a static initializer. The go1.15rc1 runtime package relies on this optimization. The package has a variable "var maxSearchAddr = maxOffAddr". The maxSearchAddr variable is used by code that runs before package initialization is complete. For golang/go#51913 Change-Id: I07a896da3d97c278bd144d95238bdd3f98c9a1ab Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/395994 Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
It is probably too late now to get this in for 1.20. Maybe we want to submit the CL for 1.21 early when the tree opens. |
This issue is currently labeled as early-in-cycle for Go 1.21. |
Seems like nothing happened here for Go 1.21. Given that this was already bumped once, moving to Backlog. Feel free to punt it to Go 1.22 if you disagree. Thanks! |
This issue is currently labeled as early-in-cycle for Go 1.22. |
Currently, package runtime runs `osinit` before dynamic initialization of package-scope variables; but on GOOS=linux, `osinit` involves mutating `sigsetAllExiting`. This currently works because cmd/compile and gccgo have non-spec-conforming optimizations that statically initialize `sigsetAllExiting`, but disabling that optimization causes `sigsetAllExiting` to be dynamically initialized instead. This in turn causes the mutations in `osinit` to get lost. This CL moves the initialization of `sigsetAllExiting` from `osinit` into its initialization expression, and then removes the special case for continuing to perform the static-initialization optimization for package runtime. Updates #51913. Change-Id: I3be31454277c103372c9701d227dc774b2311dad Reviewed-on: https://go-review.googlesource.com/c/go/+/405549 Auto-Submit: Matthew Dempsky <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I have a package consisting of the following two files:
f1.go
f2.go
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:
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
Output
Additional Information
This issue was first discussed in the golang-nuts Google Group (link).
The text was updated successfully, but these errors were encountered: