-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
zig cc compatibility with clang
zig cc
, as Andrew once
mentioned,
is a drop-in replacement for gcc/clang. However, it is not a 100% drop-in
replacement: there are differences. If one digs deeper, they will be found,
hopefully not stumbled upon.
This page is intended to answer the question: "gcc/clang does X, zig cc does Y. Is it a bug in zig or clang or both or neither?".
A "difference" is a situation when, all other things being equal, the behavior
of the program differs depending on the chosen compiler. If clang and gcc
behave X and zig cc
behaves Y, this is a sign of a different behavior that
should be either fixed (a bug) or added to this wiki (an intentional design
choice).
zig cc
differs from "mainstream" compilers by enabling UBSAN by
default.
Which means your program may compile successfully and crash with:
SIGILL: illegal instruction
This flag encourages program authors to fix the undefined behavior. There are many ways to find the undefined behavior.
zig cc
passes --gc-sections
to the ld.lld linker by default, which causes
problems for CGo <= 1.18. This is fixed for Go
1.19+. Until Go 1.19 is
released, it is recommended to add --no-gc-sections
to the linker when
using zig cc
to compile CGo programs.
zig cc
does not strip debug symbols by default and does not support the -g
option: debug symbols are always included in the binaries, unless the linker is
instructed to strip them.
Therefore, if one compares a simple C program (the program text is below)
compiled with gcc, clang-13 and zig cc
, the program compiled with zig cc
will have debug symbols, whereas clang-13/gcc will not:
$ for cc in gcc clang-13 "zig cc"; do \
$cc main.c -o main."$cc"; file main."$cc" | sed 's/:.* for/: for/'; done
main.gcc: for GNU/Linux 3.2.0, not stripped
main.clang-13: for GNU/Linux 3.2.0, not stripped
main.zig cc: for GNU/Linux 2.0.0, with debug_info, not stripped
More context and reasoning here.
zig cc
enables LTO by default, which can significantly affect the duration of the linking step. That can be avoided by passing -fno-lto
to the CFLAGS at the expense of a lesser-optimized artifact.
To compile/link ELF binaries, zig cc
uses two underlying sub-commands under
the hood:
-
zig clang
, a statically-compiled Clang. -
zig ld.lld
, a statically-compiled ELF linker from LLVM.
Our goal is to determine the exact flags that are passed to zig clang
, zig ld.lld
, and to compare them to their LLVM counterparts.
Take this program as an example:
#include <stdio.h>
#include <features.h>
int main() {
#ifdef __GLIBC__
printf("glibc_%d.%d\n", __GLIBC__, __GLIBC_MINOR__);
#else
printf("non-glibc\n");
#endif
return 0;
}
First, compile and link with clang
(specifically, the clang version that zig
is linked to, which can be determined by running zig clang --version
), and
note the linker flags:
clang-13 -v main.c -o main |& tee clang-13.log
Same with zig cc
:
ZIG_VERBOSE_CC=1 ZIG_VERBOSE_LINK=1 zig cc main.c -o main |& tee zig-cc.log
And peruse the log files. Please file issues and update the wiki if you spot more differences than documented above.