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

Linker error using zig cc with Go tests using race detection #11398

Closed
kmicklas opened this issue Apr 6, 2022 · 16 comments
Closed

Linker error using zig cc with Go tests using race detection #11398

kmicklas opened this issue Apr 6, 2022 · 16 comments
Labels
downstream An issue with a third party project that uses Zig. zig cc Zig as a drop-in C compiler feature
Milestone

Comments

@kmicklas
Copy link
Contributor

kmicklas commented Apr 6, 2022

Zig Version

0.10.0-dev.1734+7b7f45dc2

Steps to Reproduce

go.mod:

module test

go 1.18

foo.go:

package test

func foo() {}

foo_test.go:

package test

import (
	"testing"
)

func TestFoo(t *testing.T) {
	foo()
}

Then use zig cc as the C toolchain for go test with race detection:

CGO_ENABLED=1 CC="zig cc" go test -race test

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:

# test.test
runtime/race(.text): relocation target getuid not defined
runtime/race(.text): relocation target pthread_self not defined
runtime/race(.text): relocation target sleep not defined
runtime/race(.text): relocation target usleep not defined
runtime/race(.text): relocation target abort not defined
runtime/race(.text): relocation target isatty not defined
runtime/race(.text): relocation target pthread_attr_getstack not defined
runtime/race(.text): relocation target sigaction not defined
runtime/race(.text): relocation target getrusage not defined
runtime/race(.text): relocation target syslog not defined
runtime/race(.text): relocation target confstr not defined
runtime/race(.text): relocation target getrlimit not defined
runtime/race(.text): relocation target pipe not defined
runtime/race(.text): relocation target sched_getaffinity not defined
runtime/race(.text): relocation target __sched_cpucount not defined
runtime/race(.text): relocation target pthread_attr_init not defined
runtime/race(.text): relocation target pthread_getattr_np not defined
runtime/race(.text): relocation target pthread_attr_destroy not defined
runtime/race(.text): relocation target exit not defined
runtime/race(.text): relocation target sysconf not defined
runtime/race(.text): relocation target setrlimit not defined
/home/kmicklas/go/pkg/tool/linux_amd64/link: too many errors
FAIL	test [build failed]
FAIL

One interesting thing to note is that adding import "C" (i.e. triggering the use of cgo) to foo.go makes this example work. However, this only seems to work most of the time in larger examples.

@kmicklas kmicklas added the bug Observed behavior contradicts documented or intended behavior label Apr 6, 2022
@kmicklas
Copy link
Contributor Author

kmicklas commented Apr 6, 2022

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.

@andrewrk andrewrk added this to the 0.10.0 milestone Apr 7, 2022
@andrewrk andrewrk added the zig cc Zig as a drop-in C compiler feature label Apr 7, 2022
@nektro
Copy link
Contributor

nektro commented Apr 7, 2022

without exact prior knowledge of the command its running, it appears its trying to link to libc symbols without calling -lc, which zig does not provide unless you explicitly pass that flag

@nektro
Copy link
Contributor

nektro commented Apr 7, 2022

perhaps go test hits a lot fewer symbols than go run or go build and thus is much less likely to hit a import "C" as you pointed out

@andrewrk
Copy link
Member

I reproduced the issue.

Using strace I discovered that the command being used in the failing scenario was:

["zig", "cc", "-m64", "--print-libgcc-file-name"]

This is the only invocation of zig cc in the failing scenario. This is intended to print a full path to the libgcc.a installed on the system. Instead, zig is printing only the basename, libgcc.a, which is what C compilers do when they don't know where an installed file is.

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.

@kmicklas
Copy link
Contributor Author

@andrewrk Thanks for the investigation. With CC=clang the build works, but I checked and clang --print-libgcc-file-name prints /usr/bin/../lib/gcc/x86_64-linux-gnu/9/libgcc.a.CC=clang -Wno-unused-command-line-argument --rtlib=compiler-rt also works, so it seems that Go can support clang's compiler-rt too.

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 -linkmode external, it's using Go's linker and trying to link in this file.

Also, I tried wrapping clang/zig with a script which writes its arguments to a file, and I also see commands like this:

-I /home/kmicklas/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build4178160365/b080=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build4178160365/b080/_cgo_.o /tmp/go-build4178160365/b080/_cgo_main.o /tmp/go-build4178160365/b080/_x001.o /tmp/go-build4178160365/b080/_x002.o /tmp/go-build4178160365/b080/_x003.o /tmp/go-build4178160365/b080/_x004.o /tmp/go-build4178160365/b080/_x005.o /tmp/go-build4178160365/b080/_x006.o /tmp/go-build4178160365/b080/_x007.o /tmp/go-build4178160365/b080/_x008.o /tmp/go-build4178160365/b080/_x009.o /tmp/go-build4178160365/b080/_x010.o /tmp/go-build4178160365/b080/_x011.o /tmp/go-build4178160365/b080/_x012.o /tmp/go-build4178160365/b080/_x013.o -g -O2 -lpthread
-I /home/kmicklas/go/src/runtime/race -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build4178160365/b079=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build4178160365/b079/_cgo_.o /tmp/go-build4178160365/b079/_cgo_main.o /tmp/go-build4178160365/b079/_x001.o /tmp/go-build4178160365/b079/_x002.o /home/kmicklas/go/src/runtime/race/race_linux_amd64.syso -g -O2

in addition to the --print-libgcc-file-name command.

@kmicklas
Copy link
Contributor Author

Another thing I noticed is this:

> zig cc -m64 --print-libgcc-file-name
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/libgcc.a

This is apparently what it outputs on my machine in the go test invocation, so maybe the rtlib path is a red herring?

@andrewrk
Copy link
Member

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

@motiejus
Copy link
Contributor

motiejus commented Apr 21, 2022

Here's some more preparation. Doing this in full isolation to bust all caches.

wget https://ziglang.org/builds/zig-linux-x86_64-0.10.0-dev.1888+e3cbea934.tar.xz
tar --strip-components=1 -xf zig-linux-x86_64-0.10.0-dev.1888+e3cbea934.tar.xz
docker run -v `pwd`:/x -w /x -ti --rm golang sh -c 'CGO_ENABLED=1 CC=/x/zcc go test -race test'

zcc:

#!/bin/bash
>&2 echo "$*"
echo "$*" >> /x/flags.zcc
exec /x/zig cc "$@"

zig cc (non-working)

resulting flags.zcc:

-E -dM -m64 -I /tmp/go-build3109262104/b080/ -O2 -Wall -Werror -I . /tmp/cgo-gcc-input-1685788830.c
-fno-caret-diagnostics -c -x c - -o /dev/null
-Qunused-arguments -c -x c - -o /dev/null
-fdebug-prefix-map=a=b -c -x c - -o /dev/null
-gno-record-gcc-switches -c -x c - -o /dev/null
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x001.o -c _cgo_export.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x002.o -c cgo.cgo2.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x003.o -c gcc_context.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x004.o -c gcc_fatalf.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x005.o -c gcc_libinit.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x006.o -c gcc_linux_amd64.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x007.o -c gcc_mmap.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x008.o -c gcc_setenv.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x009.o -c gcc_sigaction.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x010.o -c gcc_traceback.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x011.o -c gcc_util.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x012.o -c linux_syscall.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_x013.o -c gcc_amd64.S
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3109262104/b080/_cgo_main.o -c _cgo_main.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b080=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build3109262104/b080/_cgo_.o /tmp/go-build3109262104/b080/_cgo_main.o /tmp/go-build3109262104/b080/_x001.o /tmp/go-build3109262104/b080/_x002.o /tmp/go-build3109262104/b080/_x003.o /tmp/go-build3109262104/b080/_x004.o /tmp/go-build3109262104/b080/_x005.o /tmp/go-build3109262104/b080/_x006.o /tmp/go-build3109262104/b080/_x007.o /tmp/go-build3109262104/b080/_x008.o /tmp/go-build3109262104/b080/_x009.o /tmp/go-build3109262104/b080/_x010.o /tmp/go-build3109262104/b080/_x011.o /tmp/go-build3109262104/b080/_x012.o /tmp/go-build3109262104/b080/_x013.o -g -O2 -lpthread
-E -dM -m64 -I /tmp/go-build3109262104/b079/ -O2 -I . /tmp/cgo-gcc-input-1878490623.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3109262104/b079/_x001.o -c _cgo_export.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3109262104/b079/_x002.o -c race.cgo2.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3109262104/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3109262104/b079/_cgo_main.o -c _cgo_main.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3109262104/b079=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build3109262104/b079/_cgo_.o /tmp/go-build3109262104/b079/_cgo_main.o /tmp/go-build3109262104/b079/_x001.o /tmp/go-build3109262104/b079/_x002.o /usr/local/go/src/runtime/race/race_linux_amd64.syso -g -O2
-m64 --print-libgcc-file-name

gcc (working)

zgcc

#!/bin/bash
>&2 echo "$*"
echo "$*" >> /x/flags.gcc
exec gcc "$@"
docker run -v `pwd`:/x -w /x -ti --rm golang sh -c 'CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=/x/zgcc go test -race test'
<...>

resulting flags.gcc:

-E -dM -m64 -I /tmp/go-build3800608063/b080/ -O2 -Wall -Werror -I . /tmp/cgo-gcc-input-2374899420.c
-fno-caret-diagnostics -c -x c - -o /dev/null
-Qunused-arguments -c -x c - -o /dev/null
-fdebug-prefix-map=a=b -c -x c - -o /dev/null
-gno-record-gcc-switches -c -x c - -o /dev/null
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x001.o -c _cgo_export.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x002.o -c cgo.cgo2.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x003.o -c gcc_context.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x004.o -c gcc_fatalf.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x005.o -c gcc_libinit.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x006.o -c gcc_linux_amd64.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x007.o -c gcc_mmap.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x008.o -c gcc_setenv.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x009.o -c gcc_sigaction.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x010.o -c gcc_traceback.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x011.o -c gcc_util.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x012.o -c linux_syscall.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_x013.o -c gcc_amd64.S
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b080/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/local/go/src/runtime/cgo=/_/runtime/cgo -o /tmp/go-build3800608063/b080/_cgo_main.o -c _cgo_main.c
-I /usr/local/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b080=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build3800608063/b080/_cgo_.o /tmp/go-build3800608063/b080/_cgo_main.o /tmp/go-build3800608063/b080/_x001.o /tmp/go-build3800608063/b080/_x002.o /tmp/go-build3800608063/b080/_x003.o /tmp/go-build3800608063/b080/_x004.o /tmp/go-build3800608063/b080/_x005.o /tmp/go-build3800608063/b080/_x006.o /tmp/go-build3800608063/b080/_x007.o /tmp/go-build3800608063/b080/_x008.o /tmp/go-build3800608063/b080/_x009.o /tmp/go-build3800608063/b080/_x010.o /tmp/go-build3800608063/b080/_x011.o /tmp/go-build3800608063/b080/_x012.o /tmp/go-build3800608063/b080/_x013.o -g -O2 -lpthread
-E -dM -m64 -I /tmp/go-build3800608063/b079/ -O2 -I . /tmp/cgo-gcc-input-2609734704.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3800608063/b079/_x001.o -c _cgo_export.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3800608063/b079/_x002.o -c race.cgo2.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b079=/tmp/go-build -gno-record-gcc-switches -I /tmp/go-build3800608063/b079/ -g -O2 -fdebug-prefix-map=/usr/local/go/src/runtime/race=/_/runtime/race -o /tmp/go-build3800608063/b079/_cgo_main.o -c _cgo_main.c
-I /usr/local/go/src/runtime/race -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3800608063/b079=/tmp/go-build -gno-record-gcc-switches -o /tmp/go-build3800608063/b079/_cgo_.o /tmp/go-build3800608063/b079/_cgo_main.o /tmp/go-build3800608063/b079/_x001.o /tmp/go-build3800608063/b079/_x002.o /usr/local/go/src/runtime/race/race_linux_amd64.syso -g -O2

@andrewrk
Copy link
Member

andrewrk commented Apr 30, 2022

Thanks for figuring out how to bust the cache. Based on this output I can see that zig cc was asked to do an identical set of tasks as gcc, and succeeded at all of them. The output which includes lines such as runtime/race(.text): relocation target getuid not defined comes from the Go linker. It looks like the Go linker did not put libc onto the linker line, and is therefore seeing missing symbols from libc. If Go instead had used zig cc as the linker, it would have worked fine. Based on this output, it is unclear why the gcc case is working. My suspicion is that the Go linker has a special case that tells it to put libc onto the linker line when compiling with the system C compiler.

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.

@andrewrk andrewrk added upstream An issue with a third party project that Zig uses. downstream An issue with a third party project that uses Zig. and removed bug Observed behavior contradicts documented or intended behavior upstream An issue with a third party project that Zig uses. labels Apr 30, 2022
@motiejus
Copy link
Contributor

motiejus commented Apr 30, 2022

Thanks for figuring out how to bust the cache.

Turns out rm -fr ~/.cache/go-build also works on my machine to bust the go build cache. And go build has -x flag, where it prints all subcommand invocations; this caught my eye:

/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/test.test -importcfg $WORK/b001/importcfg.link -installsuffix race -s -w -buildmode=exe -buildid=N81LdO7dMPk5pDkd5OGd/5ne5CIzHz_4zYER3sqNI/egkFBdcsgyQt7pxfL175/N81LdO7dMPk5pDkd5OGd -race -v "-extld=zig cc" $WORK/b001/_pkg_.a

Note that it didn't show invoking the zig cc linker later, even though -extld is specified (the "external linker").

Based on this output I can see that zig cc was asked to do an identical set of tasks as gcc, and succeeded at all of them. The output which includes lines such as runtime/race(.text): relocation target getuid not defined comes from the Go linker. It looks like the Go linker did not put libc onto the linker line, and is therefore seeing missing symbols from libc. If Go instead had used zig cc as the linker, it would have worked fine.

It does. Passing -linkmode external makes the test pass. Full command:

$ CGO_ENABLED=1 CC="zig cc"  go test  -race -ldflags "-linkmode external"  test
ok      test    0.020s

And the juicy part:

$ CGO_ENABLED=1 CC="zig cc"  go test  -race -ldflags "-linkmode external -v"  test
# test.test
HEADER = -H5 -T0x401000 -R0x1000
host link: "zig" "cc" "-m64" "-s" "-o" "/tmp/go-build3748042302/b001/test.test" "-rdynamic" "/tmp/go-link-498474588/go.o" "/tmp/go-link-498474588/000000.o" "/tmp/go-link-498474588/000001.o" "/tmp/go-link-498474588/000002.o" "/tmp/go-link-498474588/000003.o" "/tmp/go-link-498474588/000004.o" "/tmp/go-link-498474588/000005.o" "/tmp/go-link-498474588/000006.o" "/tmp/go-link-498474588/000007.o" "/tmp/go-link-498474588/000008.o" "/tmp/go-link-498474588/000009.o" "/tmp/go-link-498474588/000010.o" "/tmp/go-link-498474588/000011.o" "/tmp/go-link-498474588/000012.o" "/tmp/go-link-498474588/000013.o" "/tmp/go-link-498474588/000014.o" "/tmp/go-link-498474588/000015.o" "-g" "-O2" "-g" "-O2" "-lpthread"
127171 symbols, 37927 reachable
        49748 package symbols, 52413 hashed symbols, 21554 non-package symbols, 3456 external symbols
164022 liveness data
ok      test    0.018s

Based on this output, it is unclear why the gcc case is working. My suspicion is that the Go linker has a special case that tells it to put libc onto the linker line when compiling with the system C compiler.

Specifying CC=clang-13 also works; so there does not seem to be anythin special about gcc.

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.

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 -linkmode external may be overly heavy-handed, given that we still don't fully understand the problem (I don't).

@motiejus
Copy link
Contributor

motiejus commented May 2, 2022

I just noticed @kmicklas mentioned -linkmode=external before. Unfortunately, that's not always an option; using an external linker introduces it's own world of worms.

@ajbouh
Copy link

ajbouh commented May 2, 2022

Has an associated golang issue for this already been filed?

@motiejus
Copy link
Contributor

motiejus commented May 2, 2022

Has an associated golang issue for this already been filed?

We don't know exactly what causes this on Go side yet, so no.

@kmicklas
Copy link
Contributor Author

kmicklas commented May 3, 2022

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

@andrewrk
Copy link
Member

andrewrk commented May 3, 2022

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.

@andrewrk andrewrk reopened this May 3, 2022
@andrewrk andrewrk modified the milestones: 0.10.0, 1.1.0 May 3, 2022
motiejus added a commit to motiejus/zig that referenced this issue May 4, 2022
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)
motiejus added a commit to motiejus/zig that referenced this issue May 4, 2022
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)
motiejus added a commit to motiejus/zig that referenced this issue May 5, 2022
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)
motiejus added a commit to motiejus/zig that referenced this issue May 9, 2022
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.
@motiejus
Copy link
Contributor

motiejus commented Jun 2, 2022

Golang landed the pull request with -Wl,--no-gc-sections, and this problem will disappear in Go 1.19. I think we can close this issue.

motiejus added a commit to motiejus/zig that referenced this issue Jun 26, 2022
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.
@andrewrk andrewrk modified the milestones: 1.1.0, 0.10.0 Jun 28, 2022
andrewrk pushed a commit that referenced this issue Sep 9, 2022
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
downstream An issue with a third party project that uses Zig. zig cc Zig as a drop-in C compiler feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants