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
Linker error using zig cc with Go tests using race detection #11398
Comments
This seems related to golang/go#44695, although that one isn't specific to race detection. It's not super clear to me whether this is a Go or Zig bug, so we can close this one if it turns out to definitely be with Go. |
without exact prior knowledge of the command its running, it appears its trying to link to libc symbols without calling |
perhaps |
I reproduced the issue. Using strace I discovered that the command being used in the failing scenario was:
This is the only invocation of In fact, zig is capable of providing this dependency and printing a file path to it within the zig global cache directory. However it is not doing so with this particular command line invocation without a minor enhancement. I simulated this enhancement with the following diff: --- a/src/main.zig
+++ b/src/main.zig
@@ -223,6 +223,10 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
{
return process.exit(try llvmArMain(arena, args));
} else if (mem.eql(u8, cmd, "cc")) {
+ if (mem.eql(u8, args[args.len - 1], "--print-libgcc-file-name")) {
+ try std.io.getStdOut().writeAll("/home/andy/.cache/zig/o/56efdbdbe92ae7a5e01bd4d7704b1424/libcompiler_rt.a\n");
+ return process.exit(0);
+ }
return buildOutputType(gpa, arena, args, .cc);
} else if (mem.eql(u8, cmd, "c++")) {
return buildOutputType(gpa, arena, args, .cpp); However, this resulted in the same linker error output from Go. Note also that when linking with Zig, libcompiler_rt.a (equivalent to libgcc.a) is always provided, so this extra step of detecting it that Go is apparently doing is redundant. Given that the only Zig command being executed by Go is asking where libgcc is, and it does not matter whether given an answer or not, it seems clear to me that this is first and foremost a Go issue. |
@andrewrk Thanks for the investigation. With How does Zig's compiler-rt differ from clang's/GCC's? If your test diff didn't work, then it seems like there must be some incompatibility there. Since we're not using Also, I tried wrapping clang/zig with a script which writes its arguments to a file, and I also see commands like this:
in addition to the |
Another thing I noticed is this:
This is apparently what it outputs on my machine in the |
I was not able to observe go execute zig cc beyond the --print-libgcc-file-name command. Is there some cache clearing or ignoring command that you are using? I spent a good chunk of time yesterday scratching my head and confused about whether go was invoking zig or not and I did not find my answers in the go documentation. In answer to your question, zig's compiler-rt is a drop in replacement for clang's compiler-rt (most of the code is ported from LLVM compiler-rt to zig) which is itself a drop in replacement for libgcc. The missing symbols from the original report are provided by libc, not libgcc/compiler-rt. In order to diagnose this, I need to know the sequence of zig commands that are being executed, but it seems that you and I are seeing different results of these. This issue would be trivial to solve if we looked side by side at the set of zig commands executed in the working case and non working case (-race). |
Here's some more preparation. Doing this in full isolation to bust all caches.
zig cc (non-working)resulting
gcc (working)
resulting
|
Thanks for figuring out how to bust the cache. Based on this output I can see that Ultimately, if Go wants to use Zig as a cross compiler, then it also needs to use Zig as a cross linker, too. Or the Go linker needs to ask Zig to generate a libc library for the target, and put that on the linker line. From Zig's perspective, there is nothing we can do here. I would be happy to re-open this issue if more information comes to light. |
Turns out
Note that it didn't show invoking the
It does. Passing
And the juicy part:
Specifying
Ack. Still puzzled why it works with gcc and clang-13, and still not sure why we need the external linker here after all. Passing |
I just noticed @kmicklas mentioned |
Has an associated golang issue for this already been filed? |
We don't know exactly what causes this on Go side yet, so no. |
Actually there is already golang/go#44695 (mentioned above). |
Downstream issue filed: golang/go#52690 I will re-open this issue to track the downstream issue, however, please note that the current diagnosis of this issue is that it is a Go bug, and so there is no improvement to the Zig project to be made. |
When building object files, `zig cc` will instruct lld to remove unused sections via `--gc-sections`. This is problematic for cgo. Briefly, go builds cgo executables as follows*: 1. build `_cgo_.o`, which links (on linux_amd64 systems) to `/usr/local/go/src/runtime/race/race_linux_amd64.syso`. 2. That `.syso` contains references to symbols from libc. If the actual Go program uses at least one libc symbol, it will link correctly. However, if Go is building a cgo executable, but without any C code, the sections from `.syso` file will be garbage-collected, leaving a `_cgo_.o` without any references to libc. I assume the `gc_sections` is an optimization. If yes, then it makes sense for the final executable, but not for the intermediate object. If not, please correct me. Quoting @andrewrk in [1]: > The C source code for the temporary executable needs to have dummy > calls to getuid, pthread_self, sleep, and every other libc function > that cgo/race code wants to call. I agree in this case. However, while we could potentially fix it for go, I don't know how many other systems do that, which compilcates use of `zig cc` for other projects. If we consider `zig cc` a drop-in clang replacement (except for `-fsanitize=undefined`, which I tend to agree with), then it should not be optimizing the intermediate object files. I assume this was added as an optimization. If that's correct, let's optimize the final executable, but not the intermediate objects. Fixes ziglang#11398 Fixes golang/go#44695 Fixes golang/go#52690 [*]: Empirically observed with `CGO_ENABLED=1 go test -race -x -v` [1]: golang/go#52690 (comment)
When building object files, `zig cc` will instruct lld to remove unused sections via `--gc-sections`. This is problematic cgo builds that don't explicitly use C code. Briefly, go builds cgo executables as follows*: 1. build `_cgo_.o`, which links (on linux_amd64 systems) to `/usr/local/go/src/runtime/race/race_linux_amd64.syso`. 2. That `.syso` contains references to symbols from libc. If the user program uses at least one libc symbol, it will link correctly. However, if Go is building a cgo executable, but without C code, the sections from `.syso` file will be garbage-collected, leaving a `_cgo_.o` without any references to libc, causing the final linking step to not link libc. Until now, this could be worked around by `-linkmode external` flag to `go build`. This causes Go to link the final executable using the external linker (which implicitly links libc). However, that flag brings in a whole different world of worms. I assume the `gc_sections` is an optimization; I tried to re-add `--gc-sections` to the final executable, but that didn't go well. I know removing such an optimization may be contentious, so let's start the discussion here. Quoting @andrewrk in [1] (it was about `--as-needed`, but the point remains the same): > The C source code for the temporary executable needs to have dummy > calls to getuid, pthread_self, sleep, and every other libc function > that cgo/race code wants to call. I agree this is how it *should* work. However, while we could fix it for go, I don't know how many other systems rely on that, and we'll never know we've fixed the last one. The point is, GCC/Clang does not optimize sections by default, and downstream tools rely on that. If we want to consider `zig cc` a drop-in clang replacement (except for `-fsanitize=undefined`, which I tend to agree with), then it should not be optimizing the intermediate object files. Or at least have a very prominent fine-print that this is happening, with ways to work around it. Fixes ziglang#11398 Fixes golang/go#44695 Fixes golang/go#52690 [*]: Empirically observed with `CGO_ENABLED=1 go test -race -x -v` [1]: golang/go#52690 (comment)
When building object files, `zig cc` will instruct lld to remove unused sections via `--gc-sections`. This is problematic cgo builds that don't explicitly use C code. Briefly, go builds cgo executables as follows*: 1. build `_cgo_.o`, which links (on linux_amd64 systems) to `/usr/local/go/src/runtime/race/race_linux_amd64.syso`. 2. That `.syso` contains references to symbols from libc. If the user program uses at least one libc symbol, it will link correctly. However, if Go is building a cgo executable, but without C code, the sections from `.syso` file will be garbage-collected, leaving a `_cgo_.o` without any references to libc, causing the final linking step to not link libc. Until now, this could be worked around by `-linkmode external` flag to `go build`. This causes Go to link the final executable using the external linker (which implicitly links libc). However, that flag brings in a whole different world of worms. I assume the `gc_sections` is an optimization; I tried to re-add `--gc-sections` to the final executable, but that didn't go well. I know removing such an optimization may be contentious, so let's start the discussion here. Quoting @andrewrk in [1] (it was about `--as-needed`, but the point remains the same): > The C source code for the temporary executable needs to have dummy > calls to getuid, pthread_self, sleep, and every other libc function > that cgo/race code wants to call. I agree this is how it *should* work. However, while we could fix it for go, I don't know how many other systems rely on that, and we'll never know we've fixed the last one. The point is, GCC/Clang does not optimize sections by default, and downstream tools rely on that. If we want to consider `zig cc` a drop-in clang replacement (except for `-fsanitize=undefined`, which I tend to agree with), then it should not be optimizing the intermediate object files. Or at least have a very prominent fine-print that this is happening, with ways to work around it. Fixes ziglang#11398 Fixes golang/go#44695 Fixes golang/go#52690 [*]: Empirically observed with `CGO_ENABLED=1 go test -race -x -v` [1]: golang/go#52690 (comment)
This adds the following for passthrough to lld: - `--print-gc-sections` - `--print-icf-sections` - `--print-map` I am not adding these to the cache manifest, since it does not change the produced artifacts. Tested with an example from ziglang#11398: it successfully prints the resulting map and the GC'd sections.
Golang landed the pull request with |
This adds the following for passthrough to lld: - `--print-gc-sections` - `--print-icf-sections` - `--print-map` I am not adding these to the cache manifest, since it does not change the produced artifacts. Tested with an example from ziglang#11398: it successfully prints the resulting map and the GC'd sections.
This adds the following for passthrough to lld: - `--print-gc-sections` - `--print-icf-sections` - `--print-map` I am not adding these to the cache manifest, since it does not change the produced artifacts. Tested with an example from #11398: it successfully prints the resulting map and the GC'd sections.
Zig Version
0.10.0-dev.1734+7b7f45dc2
Steps to Reproduce
go.mod
:foo.go
:foo_test.go
:Then use zig cc as the C toolchain for
go test
with race detection:This is on x86_64 Linux.
Expected Behavior
This should work, as it does without
-race
.Actual Behavior
We get errors from the Go linker:
One interesting thing to note is that adding
import "C"
(i.e. triggering the use of cgo) tofoo.go
makes this example work. However, this only seems to work most of the time in larger examples.The text was updated successfully, but these errors were encountered: