ziglang/zig

zig ld: support LTO

xxxbxxx opened this issue · 15 comments

main.zig:

const std = @import("std");

pub fn main() anyerror!void {
    std.log.info("All your codebase are belong to us.", .{});
}
zig build-exe main.zig -OReleaseFast -target native-macos

--> ok.

touch dummy.c
zig build-exe main.zig dummy.c -OReleaseFast -target native-macos

--> error: MissingMainEntrypoint

zig build-exe main.zig dummy.c -target native-macos

--> ok

Potentially related #8552

I wanted to verify this but it's probably that wanting to link with a c file makes the zip compilation unit compile with LTO, and so the .o file is a llvm ir and not a 'normal' object file.
And probably the new linker doesn't know how to deal with it.

I wanted to verify this but it's probably that wanting to link with a c file makes the zip compilation unit compile with LTO, and so the .o file is a llvm ir and not a 'normal' object file.
And probably the new linker doesn't know how to deal with it.

Correct, zig ld is not advanced enough just yet, but it's defo on the roadmap. Thanks for filing the issue!

not sure if it is related but I noticed a similar error when using zig cc -shared. Targeting x86_64 works perfectly but I'm getting this error for aarch64.

on an Intel Mac:

~% uname -a
Darwin imac.local 20.4.0 Darwin Kernel Version 20.4.0: Thu Apr 22 21:46:47 PDT 2021; root:xnu-7195.101.2~1/RELEASE_X86_64 x86_64
~% cat hello.c
int hello() {
  return 0;
}
~% zig cc hello.c -shared
~% zig cc hello.c -shared -target aarch64-macos
error: MissingMainEntrypoint

and on a M1 Mac:

~% uname -a
Darwin m1.local 20.4.0 Darwin Kernel Version 20.4.0: Thu Apr 22 21:46:41 PDT 2021; root:xnu-7195.101.2~1/RELEASE_ARM64_T8101 arm64
~% zig cc hello.c -shared -target x86_64-macos
~% zig cc hello.c -shared
error: MissingMainEntrypoint

@wojtekmach actually, your issue is related to #8317. zig ld is not yet able to link nor produce a shared library and on x64 we fallback to LLD hence why it works on a native host. On arm64, we always use zig ld however hence why the deceptive error message which I should really tweak!

@wojtekmach when linking natively on arm64, you could fallback to the system linker for the time being btw until zig ld gains the missing abilities to link and produce shared libs: see #8728

I tried that too but I'm unfortunately getting the same error:

% zig version
0.8.0-dev.2133+ad33e3483
% ZIG_SYSTEM_LINKER_HACK=1 zig cc hello.c -shared -target aarch64-macos
error: MissingMainEntrypoint

oh I'm sorry, I was cross-compiling from a x64 host. linker hack works from the arm64 host. Thanks and sorry for the noise!

@kubkon I saw in #8317 that some zig ld preliminary work landed so just in case I gave it another shot here but I see the same result. I was wondering if this problem will be solved by this issue, #8317, or some other one. Just wondering which issue I should track is all :) In any case, thank you and the team for the work on this, I'm really keen on using Zig for this use case in particular. And again sorry for hijacking this issue!

~% uname -a
Darwin m1.local 20.4.0 Darwin Kernel Version 20.4.0: Thu Apr 22 21:46:41 PDT 2021; root:xnu-7195.101.2~1/RELEASE_ARM64_T8101 arm64
~% zig version
0.8.0-dev.2305+d228d8605
~% cat hello.c
int hello() {
  return 0;
}
~% zig cc hello.c -shared
error: MissingMainEntrypoint

not sure what changed but zig cc -shared worked on the arm64 host, thanks everyone!

~% zig version
0.8.0-dev.2729+87dae0ce9
~% uname -sm
Darwin arm64
~% zig cc hello.c -shared
~%

still can't cross-compile from an x86_64 host though.

~% uname -sm
Darwin x86_64
~% zig cc hello.c -shared -target aarch64-macos
error: MissingMainEntrypoint

I have now renamed this issue to better reflect the root issue, namely, lack of support for LTO in zig ld.

@kubkon - I think I managed to hit the same error without (to my knowledge) having LTO enabled. Here is the smalest reproduction case I could come up with:

$ uname -a
Darwin MBP-Albert.local 22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:39:35 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T8103 arm64

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-macos.13-none -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ zig build-exe -fno-lto -target aarch64-macos.13-none librepro.a -lc++ -lSystem --name repro
MachO Flush... error(link): entrypoint '_main' not found
error: MissingMainEntrypoint

The same reproduction steps seem to work when using the MacOS system linker instead:

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-macos.13-none -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ ld librepro.a -arch arm64 -platform_version macos 13.2.1 13.2.1 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib -lc++ -lSystem -o main
$ ./repro
Hello World

Finally, the same reproduction steps also seem to work when targeting aarch64-linux-gnu.2.34 for example:

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-linux-gnu.2.34 -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ zig build-exe -fno-lto -target aarch64-linux-gnu.2.34 librepro.a -lc++ --name repro
$ file repro
repro: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped

# Move the binary to a Ubuntu 22.04 container (on the same MacOS host)

$ uname -a
Linux f905d25e8b5b 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
$ ./repro
Hello World

For context, I understand that the reproduction case is a bit "weird" on its own as putting the _main entry-point into a library archive librepro.a is probably not the most common and/or encouraged approach. However, the real-world use-case that lead me to hit the issue in the first place was me trying to build a piece of software that uses googletest and has some test cases that rely on a convenience particularity offered by this test library to avoid having to repeat some main() boilerplate on the test source files:

But maybe you think that writing all those main functions is too much work? We agree with you completely, and that’s why Google Test provides a basic implementation of main(). If it fits your needs, then just link your test with the gtest_main library and you are good to go.

Source: https://google.github.io/googletest/primer.html#writing-the-main-function

I am not an expert in the C/C++ world so it is very possible that the two issues are completely unrelated. However, my searches in Google and GitHub issues kept bringing me here so I thought I would share it in case it is relevant and/or you can provide guidance/advice.

@kubkon - I think I managed to hit the same error without (to my knowledge) having LTO enabled. Here is the smalest reproduction case I could come up with:

$ uname -a
Darwin MBP-Albert.local 22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:39:35 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T8103 arm64

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-macos.13-none -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ zig build-exe -fno-lto -target aarch64-macos.13-none librepro.a -lc++ -lSystem --name repro
MachO Flush... error(link): entrypoint '_main' not found
error: MissingMainEntrypoint

The same reproduction steps seem to work when using the MacOS system linker instead:

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-macos.13-none -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ ld librepro.a -arch arm64 -platform_version macos 13.2.1 13.2.1 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib -lc++ -lSystem -o main
$ ./repro
Hello World

Finally, the same reproduction steps also seem to work when targeting aarch64-linux-gnu.2.34 for example:

$ cat > repro.cc <<'EOF'
#include <iostream>
int main(int argc, char ** argv) { std::cout << "Hello World" << std::endl; return 0; }
EOF
$ zig c++ -fno-lto -target aarch64-linux-gnu.2.34 -c repro.cc
$ zig ar rcsD librepro.a repro.o
$ zig build-exe -fno-lto -target aarch64-linux-gnu.2.34 librepro.a -lc++ --name repro
$ file repro
repro: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped

# Move the binary to a Ubuntu 22.04 container (on the same MacOS host)

$ uname -a
Linux f905d25e8b5b 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
$ ./repro
Hello World

For context, I understand that the reproduction case is a bit "weird" on its own as putting the _main entry-point into a library archive librepro.a is probably not the most common and/or encouraged approach. However, the real-world use-case that lead me to hit the issue in the first place was me trying to build a piece of software that uses googletest and has some test cases that rely on a convenience particularity offered by this test library to avoid having to repeat some main() boilerplate on the test source files:

But maybe you think that writing all those main functions is too much work? We agree with you completely, and that’s why Google Test provides a basic implementation of main(). If it fits your needs, then just link your test with the gtest_main library and you are good to go.

Source: https://google.github.io/googletest/primer.html#writing-the-main-function

I am not an expert in the C/C++ world so it is very possible that the two issues are completely unrelated. However, my searches in Google and GitHub issues kept bringing me here so I thought I would share it in case it is relevant and/or you can provide guidance/advice.

That's unrelated to LTO. The error happens because zig ld doesn't look into the archives unless there are symbols missing in any of the object files. That's interesting that this works for Apple system linker. I wonder if zig ld should perhaps look through archives in case no object file was explicitly specified. I will extract it into a separate issue as it's unrelated to LTO.

@alloveras issue extracted into #15103. Thanks for filing it!

@alloveras issue extracted into #15103. Thanks for filing it!

Thanks to you for the super fast response and for extracting it into a separate issue 🙇