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

  1. 22.1 Triplets
  2. 22.2 Sysroot
  3. 22.3 GCC: Build, Host, Target
  4. 22.4 LLVM: Build and Host

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