Ch22: Canadian Compilation
Why This Chapter Exists
Every chapter so far assumed that the compiler is already there. You write a source file, you
invoke g++ or clang++, and a binary comes out. This chapter removes
that assumption. A compiler is a program too, it has to be built, and it has to be built for
the machine that will run it. On a new architecture, there is no compiler to build the
compiler with. That is the bootstrapping problem, and Canadian compilation is its answer.
The topic sounds like distribution plumbing. It is not. The same three triples decide whether
a binary can run on the machine in front of you, whether a sysroot contains the right C
library, and whether "cross compilation" means one thing or two. If you have ever copied a
toolchain to another machine and watched it fail on crt1.o, a missing
ld, or a libstdc++.so.6 version, this chapter is the explanation.
The chapter is short and concrete: one page for the naming system, one for the sysroot, and one each for the GCC and LLVM build models.
The Four Pages
The Short Version
A platform is named by a triplet: instruction set, vendor, operating system
and C library, in that order. The vendor and sometimes the C library are optional. If any
component differs, the platform differs and binaries do not run on each other. Desktop
x86_64-linux-gnu and Android x86_64-linux-android30 share an
instruction set and a kernel; they are still different platforms, because glibc and bionic
are different C libraries and Android adds the app sandbox. A libc-only difference is not a
smaller distance than an ISA difference.
On ARM, the pragmatic escape hatch is a Windows PE run under Wine: the same ARM64
.exe runs natively on Windows on ARM and under Wine on GNU/Linux, Android,
Apple Silicon macOS, FreeBSD/ARM64 and other ARM64 hosts, because Wine supplies the Windows
ABI. This is why people joke that Win32 is the only stable ABI on Linux.
Three triples describe a compiler build: build is the machine that builds the compiler, host is the machine that runs the compiler, and target is the machine whose code the compiler emits. Native means all three are equal. Cross means build equals host but not target. Canadian means build differs from host. A Canadian build is either Canadian native (host equals target) or Canadian cross (host differs from target). A crossback is the special case of Canadian cross where build equals target. It is the one you use every day when you test a Windows build from Linux.
The compiler does not supply the target's C library. A sysroot does, together with the startup objects, kernel headers and dynamic loader. GCC supplies libgcc and libstdc++; LLVM supplies compiler-rt, libc++, libc++abi and libunwind. If you do not provide a libc, the target is freestanding, and the toolchain must say so instead of pretending to be Linux.
The build machine itself needs the same thing before any of this starts: a C/C++ development environment with headers, libc and a C++ runtime, because GCC and Clang are C++ programs. Python, Go and Rust do not define a second sysroot; their runtimes and native modules are built against the C/C++ one. That is why a new platform gets a C/C++ toolchain first.
GCC is configured with --build, --host and --target,
and its Canadian build needs two compilers: one that runs on the build machine and emits host
code, and one that runs on the build machine and emits target code. LLVM has only build and
host. For clang there are only two build categories: native
(build = host) and Canadian (build ≠ host).
Cross compilation is not a build category; it is a native clang invoked with
--target= and --sysroot=, so it is a special case of native. A
single clang binary contains every backend it was configured with, and --target=
chooses one per invocation.
Key Takeaways
- A triplet names the ISA, vendor, OS and C library.
- A sysroot is the target’s root filesystem:
bin,include,libandshare. - GCC and Clang consume the same sysroot layout.
- Build, host and target are three different machines.