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:
- A build-host C++ sysroot if host differs from build. Clang is C++; the produced binary must link the host's C++ runtime.
-
Native code-generation tools that run during the build:
llvm-tblgen,clang-tblgen,llvm-config, and the pseudo/confusable generators. They run on the build machine and cannot be the cross-compiled ones. -
A target sysroot with libc and headers for any runtime library you build
(
compiler-rt,libc++,libc++abi,libunwind). The sysroot rules from 22.2 apply unchanged.
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.