-
Notifications
You must be signed in to change notification settings - Fork 18k
gccgo: go test -compiler gccgo github.com/golang/protobuf/proto fails #15738
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
Comments
Paging @ianlancetaylor |
Ok, I have some small idea what is happening here. gccgo appears to have a concept of a "package priority" where if package A that imports package B then package B will have a higher priority than A. In fact a package's priority is defined to be one greater than that of all the packages it imports. Init functions are then called in low to high priority order. Buuuuuut when testing a package there are two versions of a package in play, the "usual" one and the "test" one, and they don't necessarily have the same priorities:
(as it happens, package "testing" has priority 8). So when package A's external tests depend on a package B that depends on package A, package B's priority is set based on the non-testy version of A, which means its priority can be lower than that of the testy version of A. Hilarity ensues. |
Ping? My github account got flagged for a while so it's possible notifications for this issue didn't go out. |
I saw your comment. I thought the gccgo code handled this priority inversion somehow but I do not remember how. I haven't looked into the problem yet--I'm looking at the gc 1.7 release and internal stuff before I return to working on gccgo. OK, I took a very quick look. The comment for
So why isn't that working? I also see this comment, earlier, so I think that this did work at one time:
|
Ah, sorry for the noise. Sensible priorities!
Because it's not the priority of the package being tested (let's call it A) that's the problem, it's the priority of some other package (say B) that (a) depends on A (b) is depended on by A's external tests that's the problem. B's priority was fixed when it was built, without knowing that A's priority can change in this situation. The only approach to fixing this that I can think of is to walk the import graph at link time, not compute some depth measurement at compile time (or take imports in internal test files into account at compile time for priority purposes, but that sounds bonkers).
I think I probably rendered this bit unecessary in https://go-review.googlesource.com/#/c/21692/ -- the link only sees one version of the package now. |
Created a reduced test case. |
Updated test case is at |
The key thing triggering the bug is the fact that the protobuf dir contains both internal and external tests. This in turn creates a scenario where you have two copies of a single package, each with different priorities, which is where things go wobbly. I created a smaller test case to encapsulate the bug:
During the compilation phase of "go test", the builder first builds an object containing the internal test and foo, e.g. gccgo ... -c -o foo_it1.o ./foo.go ./foo_internal_test.go foo_internal_test.go imports only "testing", but "testing" has a priority of 8, this means that the priority for foo_it1.o is set to 9. Next it reruns the foo compile without the internal test: gccgo ... -c -o foo.o ./foo.go Since foo.go has no imports, foo.o gets a priority of 1. Moving on: gccgo ... -c -o bar.o ./bar.go Since bar imports only foo, bar.o is assigned a priority of 2. gccgo ... -c -o foo_ext.o ./foo_external_test.go Here foo_external_test.go imports foo, bar, and testing. Again, since testing has a priority of 8, this means that the priority for foo_it1.o is set to 9. The final link looks like: gccgo -o foo.test testmain.o -Wl,-( -Wl,--whole-archive The problem here is that we built "libbar.a" against the vanilla version of the foo package (libfoo.a), which contained a version of foo that had priority 1. However when we do to do the final link, we're using a different copy of foo (contained in libfoo_itest.a) that has priority 9. As a result the bar initializer runs before the foo initializer, which is incorrect. My instinct is to try to fix this in "go test" as opposed to trying to change gccgo -- gccgo's priority scheme works fine, it's simply the fact that we're compiling the "bar" package based on one version of "foo", then in the final link we link in a different verison of "foo". I will spend some time looking over the "go build" and "go test" srcs. |
Possible solutions:
If some other package "q" imports "p" with a dot import, this will introduce "Helper" into the file scope for "q" (meaning that it could collide with some other function).
|
There is another reason your first suggestion (single build per package) won't work: in some cases the _test.go files have If you force the testing package to have a priority of zero, I'm a little worried about correct initialization of the testing package itself vs. packages that the testing package imports. The testing package doesn't currently have any The package import graph is a DAG, and we need to run the initialization functions from the leafs up. We actually have all the information we need to build that DAG, by reading the |
OK, sure enough, your concern is indeed valid -- I prototyped the priority zero approach(which does fix my testcase) but it fails the package test for the predefined "strconv" package (presumably because the init functions are not being run in the right order). I'll look into rewriting the init order walk using the export data, that sounds like a more robust option. |
I have a tentative fix for this problem in https://go-review.googlesource.com/25301. One thing that I ran into is that you can have pseudo-cycles in the import graph when building tests for go core packages. For example, $GOROOT/src/regexp/onepass_test.go is part of package "regexp" and also imports "testing", but "testing" imports "regexp" (hmm). My code handles this, but I am wondering if I need to worry about cycles elsewhere as well. The other weird thing is that for gccgo there are references to the "internal/race" package but as far as I can tell there is no exports file for that package produced as part of the regular build: $ dump-gccgo-obj-goexports.py /s/gcc-trunk/cross/lib64/go/7.0.0/x86_64-pc-linux-gnu/sync.gox | head I have a special case to handle this at the moment but I'm not sure what the best long-term solution should be. |
CL https://golang.org/cl/25301 mentions this issue. |
In your build directory you should see a file |
The From cmd/go/test.go:
|
Hmm. My approach seems to work well in the cases where all of the exports data is available, but falls down in cases where something includes packages that indirectly include an internal package like "golang_org/x/net/http2/hpack" -- hpack has a long list of other imported packages, and it's difficult to stitch/interleave those init calls into the dependence graph without being able to read its export data. Alternate proposal: change the libgo makefile to export *.gox files for hidden/internal packages (so that we can have a single mechanism for computing init priority), but mark them in some way so that the compiler can refuse to allow things in them to be imported. For example, the ".go_export" section could be renamed ".go_export_h", or something other such scheme. |
Switch to a new method for determining the order in which import init functions are invoked: build an init fcn dependence DAG and walk the DAG to rewrite/adjust priorities to account for discrepancies introduced by "go test". This patch includes a change to the export data format generated by gccgo. Older versions of gccgo will not be able to read object files produced by a newer gccgo, but the new gcc will still be able to read old object files. Fixes golang/go#15738. Reviewed-on: https://go-review.googlesource.com/25301 git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@239708 138bc75d-0d04-0410-961f-82ee72b054a4
Switch to a new method for determining the order in which import init functions are invoked: build an init fcn dependence DAG and walk the DAG to rewrite/adjust priorities to account for discrepancies introduced by "go test". This patch includes a change to the export data format generated by gccgo. Older versions of gccgo will not be able to read object files produced by a newer gccgo, but the new gcc will still be able to read old object files. Fixes golang/go#15738. Reviewed-on: https://go-review.googlesource.com/25301 From-SVN: r239708
Please answer these questions before submitting your issue. Thanks!
go version
)?mwhudson@aeglos:~$ gccgo-5 --version
gccgo-5 (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
mwhudson@aeglos:~$ gccgo-6 --version
gccgo-6 (Ubuntu 6.0.1-0ubuntu1) 6.0.0 20160414 (experimental) [trunk revision 234994]
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
go env
)?Ubuntu 16.04
go get github.com/golang/protobuf/...
go test -compiler gccgo github.com/golang/protobuf/proto
ok github.com/golang/protobuf/proto 0.028s
The failing code is from "github.com/golang/protobuf/proto":
protoTypes is initialized in a global var block:
It is called from an init function in "github.com/golang/protobuf/ptypes/any/":
So it looks like this init function is being called before that of "github.com/golang/protobuf/proto", which seems reasonably impossible (and indeed, I can't reproduce this in a trivial test case).
The text was updated successfully, but these errors were encountered: