Ch22.4: LLVM: Build and Host

中文(简体)

1. Clang Has No Target Build

GCC is configured for one target. A gcc built with --target=aarch64-linux-gnu emits AArch64 and nothing else. LLVM is the other design: one code generator with several backends, compiled into one clang binary. The target is chosen at invocation:

clang++ --target=aarch64-linux-gnu --sysroot=$SYSROOT ...
clang++ --target=x86_64-windows-gnu --sysroot=$MINGW ...
clang++ --target=wasm32-wasip1 --sysroot=$WASI ...

So there is no "target clang" in the GCC sense. There is a clang that runs on the host, containing the backends that were enabled with LLVM_TARGETS_TO_BUILD. If the AArch64 backend is present, the same binary can produce AArch64 code from an x86_64 machine, or from an AArch64 machine, or from a Windows machine. Cross compilation is not a separate build; it is the same native driver with a different --target.

But a compiler still has to run somewhere. That is the host. LLVM's build therefore has two triples, not three:

Concept In LLVM/CMake Meaning
Build The CMake/Ninja machine Where cmake and ninja run
Host CMAKE_C_COMPILER_TARGET, LLVM_HOST_TRIPLE The platform the produced clang binary runs on
Default target LLVM_DEFAULT_TARGET_TRIPLE What clang emits when the user does not pass --target
Enabled backends LLVM_TARGETS_TO_BUILD Which instruction sets the binary can emit

The distinction between CMAKE_C_COMPILER_TARGET and LLVM_DEFAULT_TARGET_TRIPLE is the one people get wrong. CMAKE_C_COMPILER_TARGET tells the compiler that builds clang to emit code for that triple, so the clang binary itself is for that triple. LLVM_DEFAULT_TARGET_TRIPLE only changes clang's default; the binary still runs on the build machine. Setting the first when you wanted the second produces a compiler that will not execute on your machine.

Because the target is an invocation option, clang has only two build categories, and both are decided by build versus host:

Category Relationship Meaning
Native build = host The clang binary runs where it was built. Cross compilation is this case: the same native clang is invoked with --target= and --sysroot=.
Canadian build ≠ host The clang binary is produced on another machine and runs on the host.

There is no cross build of clang. The GCC cross row in 22.1 exists because GCC fixes its target at configure time; clang does not. Crossing is a usage of a native clang, which is why it is a special case of native. The Canadian native/cross/crossback subdivision does not apply either: target is not part of the clang build, so a Canadian clang is simply Canadian.

2. What a Canadian LLVM Build Is

Suppose you are on x86_64-linux-gnu and you want a clang that runs on aarch64-linux-gnu and can emit AArch64. You cross-compile clang like any other C++ program: the compiler you use must produce AArch64, and the sysroot must be an AArch64 C/C++ sysroot, because clang links libc and a C++ runtime. That is a Canadian build: build is x86_64, host is aarch64. This is the only non-native category clang has. Target does not enter the CMake triple at all; it is the backend and the default, chosen later by --target.

If you want a clang that runs on the build machine but defaults to AArch64, do not set CMAKE_C_COMPILER_TARGET. Leave the binary native, set LLVM_DEFAULT_TARGET_TRIPLE=aarch64-linux-gnu, and make sure LLVM_TARGETS_TO_BUILD contains AArch64. This is the closest thing LLVM has to a GCC cross compiler, and it is just a native clang with an extra backend. When you then pass --target=aarch64-linux-gnu --sysroot=$SYSROOT, you are cross-compiling with that native clang; the compiler binary did not change.

In both cases the build needs the same three practical things:

3. The CMake Configuration

A CMake toolchain file is the clean way to express the host and the sysroot. This is the shape used by the reference scripts:

set(CMAKE_BUILD_TYPE Release)
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_ASM_COMPILER clang)

# The produced clang runs on this triple, so clang emits its code for it.
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_ASM_COMPILER_TARGET aarch64-linux-gnu)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_CROSSCOMPILING On)

set(CMAKE_SYSROOT /home/you/toolchains/llvm/aarch64-linux-gnu/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

The compiler that runs CMake's test programs is clang on the build machine, but CMAKE_C_COMPILER_TARGET makes it produce AArch64 binaries. CMake itself runs on the build machine; the objects it builds are for the host. That is exactly a Canadian build, expressed without a --host flag.

Then configure LLVM with the host triple, the default target, and the native tools:

cmake -G Ninja -S llvm-project/llvm -B build-aarch64 \
  -DCMAKE_TOOLCHAIN_FILE=$PWD/aarch64.cmake \
  -DCMAKE_INSTALL_PREFIX=/home/you/toolchains/llvm/aarch64-linux-gnu/llvm \
  -DLLVM_HOST_TRIPLE=aarch64-linux-gnu \
  -DLLVM_DEFAULT_TARGET_TRIPLE=aarch64-linux-gnu \
  -DLLVM_TARGETS_TO_BUILD="AArch64;X86" \
  -DLLVM_NATIVE_TOOL_DIR=/home/you/toolchains/llvm/native/bin \
  -DLLVM_ENABLE_PROJECTS="clang;lld;clang-tools-extra" \
  -DLLVM_ENABLE_ZLIB=FORCE_ON \
  -DZLIB_LIBRARY=$SYSROOT/usr/lib/libz.a \
  -DZLIB_INCLUDE_DIR=$SYSROOT/usr/include \
  -DBUILD_SHARED_LIBS=ON \
  -DLLVM_ENABLE_LTO=Thin \
  -DLLVM_ENABLE_TERMINFO=OFF \
  -DLLVM_ENABLE_LIBXML2=OFF \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_INCLUDE_BENCHMARKS=OFF \
  -DLLVM_ENABLE_ASSERTIONS=OFF

ninja -C build-aarch64
ninja -C build-aarch64 install/strip

CMake sometimes ignores a new toolchain file on the first configure. The reference scripts run the same cmake command twice for this reason; a second identical invocation is cheap and avoids a confusing "it used the host compiler" failure.

4. Native Tools: the Part That Bites

LLVM generates a large part of its own source at build time with llvm-tblgen and clang-tblgen. These programs must run on the build machine. The cross-compiled clang cannot run there, so CMake needs a native copy.

The modern answer is LLVM_NATIVE_TOOL_DIR, added by D131052 and merged in 2023. Point it at the bin directory of a native LLVM build, and CMake uses those executables instead of building its own:

-DLLVM_NATIVE_TOOL_DIR=/home/you/toolchains/llvm/native/bin

Older instructions set each tool by hand:

-DLLVM_TABLEGEN=/path/to/native/bin/llvm-tblgen
-DCLANG_TABLEGEN=/path/to/native/bin/clang-tblgen
-DLLVM_CONFIG_PATH=/path/to/native/bin/llvm-config

Both work. The important point is that ninja install does not install llvm-tblgen and clang-tblgen by default, so a naive "install clang, then use that installation to cross-build" fails with a missing TableGen. Keep the native build directory, or copy the tools somewhere stable.

5. Runtime Libraries

Clang the compiler and clang the complete toolchain are different things. The compiler alone can use GCC's libstdc++ and libgcc from the target sysroot. That is the fastest way to get a working cross compiler:

$PREFIX/bin/clang++ --target=$TARGET \
  --gcc-toolchain=$GCC_PREFIX \
  --sysroot=$SYSROOT \
  hello.cc -o hello

A self-contained LLVM toolchain also ships compiler-rt, libc++, libc++abi and libunwind. Build them as runtimes, with the newly built clang and the target sysroot:

cmake -G Ninja -S llvm-project/runtimes -B build-runtimes \
  -DCMAKE_C_COMPILER=$PREFIX/bin/clang \
  -DCMAKE_CXX_COMPILER=$PREFIX/bin/clang++ \
  -DCMAKE_C_COMPILER_TARGET=$TARGET \
  -DCMAKE_CXX_COMPILER_TARGET=$TARGET \
  -DCMAKE_SYSROOT=$SYSROOT \
  -DCMAKE_INSTALL_PREFIX=$PREFIX \
  -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
  -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON

ninja -C build-runtimes
ninja -C build-runtimes install

Build order matters: compiler-rt builtins do not depend on the C++ library; libunwind needs the C++ headers but not the C++ library; libc++abi needs libunwind; libc++ needs libc++abi. If you only need C, skip all of this and use the sysroot's libc.

Once the runtimes are installed, select them explicitly:

clang++ --target=$TARGET --sysroot=$SYSROOT \
  --rtlib=compiler-rt --stdlib=libc++ \
  -nostdinc++ -isystem $PREFIX/include/c++/v1 \
  -L$PREFIX/lib -Wl,-rpath,$PREFIX/lib \
  hello.cc -o hello

6. Failures You Will Actually See

Symptom Cause Fix
llvm-tblgen: not found during the build No native tools; the Canadian build tries to run the host binary on the build machine LLVM_NATIVE_TOOL_DIR or LLVM_TABLEGEN/CLANG_TABLEGEN
The produced clang does not execute on the build machine CMAKE_C_COMPILER_TARGET was set to the target while you wanted a native clang Remove it, or treat the result as a host binary and ship it to the host
libstdc++.so.6: version GLIBCXX_3.4.xx not found The host's C++ runtime is older than the one used to build clang LLVM_STATIC_LINK_CXX_STDLIB=ON, or ship the newer runtime, or set LD_LIBRARY_PATH
cannot find -lz, or compression silently disabled zlib was not found for the target LLVM_ENABLE_ZLIB=FORCE_ON and explicit ZLIB_LIBRARY/ZLIB_INCLUDE_DIR
Host libraries leak into the build pkg-config and CMake search the host CMAKE_FIND_ROOT_PATH, PKG_CONFIG_LIBDIR, PKG_CONFIG_PATH=""
Link errors about missing crtbegin.o or -lc No target sysroot, or the sysroot has the wrong layout Read 22.2 and check -print-search-dirs
The build wants Python, curses or libxml2 Optional projects enabled by default Disable lldb, use LLVM_ENABLE_TERMINFO=OFF, LLVM_ENABLE_LIBXML2=OFF

BUILD_SHARED_LIBS=ON is a deliberate trade. It makes the LLVM libraries shared, which reduces the install size and lets clang tools share them, but it means the host must have a compatible libstdc++.so.6 or libc++.so. If you are shipping to a machine you do not control, either build with -DLLVM_STATIC_LINK_CXX_STDLIB=ON or bundle the runtime and set an rpath. Do not assume the host's C++ runtime is new enough just because the host's C compiler works.

7. Verifying the Result

$PREFIX/bin/clang --version
$PREFIX/bin/clang -print-target-triple
$PREFIX/bin/clang -print-targets        # enabled backends
$PREFIX/bin/clang -dumpmachine

$PREFIX/bin/clang --target=$TARGET --sysroot=$SYSROOT \
  -v -E -x c /dev/null                  # include search path

file $PREFIX/bin/clang                  # must match the host, not the build machine

The last check is the one that catches a Canadian build gone wrong. If file says ELF 64-bit LSB ... x86-64 and you intended an AArch64 host, the build used the native compiler instead of the cross-targeted one. The compiler will still run, and it will still compile hello world, which is why this failure can survive a casual test. Copy the toolchain to the intended host and run clang --version there before you package it.

The reference implementation for this page is canadian/canadian_clang_aarch64-linux-gnu.txt and llvm/build_common.sh in trcrsired/toolchainbuildscripts. Those files set CMAKE_C_COMPILER_TARGET, LLVM_HOST_TRIPLE, LLVM_DEFAULT_TARGET_TRIPLE, the sysroot, zlib and the native TableGen tools in one place; after this page, the option names should read as a description of the build rather than as a list of flags.