高级构建配置

简介

CMake 是一个跨平台构建生成工具。CMake 不构建项目,它为你的构建工具(GNU make、Visual Studio 等)生成构建 LLVM 所需的文件。

如果你是新的贡献者,请从 LLVM 系统入门使用 CMake 构建 LLVM 页面开始。本页面向进行更复杂构建的用户。

以下许多示例的编写都假设了特定的 CMake 生成器。除非另有明确说明,否则这些命令应适用于任何 CMake 生成器。

本文档页面上提到的许多构建配置都可以通过使用 CMake 缓存来使用。CMake 缓存本质上是一个配置文件,它为特定的构建配置设置必要的标志。Clang 的缓存位于 monorepo 中的 /clang/cmake/caches 中。它们可以使用 -C 标志传递给 CMake,如下面的示例以及其他配置标志所示。

引导构建

Clang CMake 构建系统支持引导(又名多阶段)构建。从高层次来看,多阶段构建是一系列构建,这些构建将数据从一个阶段传递到下一个阶段。其中最常见和最简单的版本是传统的引导构建。

在一个简单的两阶段引导构建中,我们使用系统编译器构建 clang,然后使用刚构建的 clang 再次构建 clang。在 CMake 中,这种最简单的引导构建形式可以使用单个选项 CLANG_ENABLE_BOOTSTRAP 进行配置。

$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DCLANG_ENABLE_BOOTSTRAP=On \
    -DLLVM_ENABLE_PROJECTS="clang" \
    <path to source>/llvm
$ ninja stage2

此命令本身并不是非常有用,因为它假设每个阶段都使用默认配置。接下来的系列示例利用 CMake 缓存脚本来提供更复杂的选项。

默认情况下,只有少数 CMake 选项会在阶段之间传递。该列表名为 _BOOTSTRAP_DEFAULT_PASSTHROUGH,在 clang/CMakeLists.txt 中定义。要强制在阶段之间传递变量,请使用 -DCLANG_BOOTSTRAP_PASSTHROUGH CMake 选项,每个变量用“;”分隔。例如

$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DCLANG_ENABLE_BOOTSTRAP=On \
    -DCLANG_BOOTSTRAP_PASSTHROUGH="CMAKE_INSTALL_PREFIX;CMAKE_VERBOSE_MAKEFILE" \
    -DLLVM_ENABLE_PROJECTS="clang" \
    <path to source>/llvm
$ ninja stage2

BOOTSTRAP_ 开头的 CMake 选项将仅传递给 stage2 构建。这提供了使用 Clang 特定构建标志的机会。例如,以下 CMake 调用将仅在 stage2 构建期间为 C 和 C++ 启用 '-fno-addrsig'。

$ cmake [..]  -DBOOTSTRAP_CMAKE_CXX_FLAGS='-fno-addrsig' -DBOOTSTRAP_CMAKE_C_FLAGS='-fno-addrsig' [..]

clang 构建系统将构建称为阶段。stage1 构建是使用主机上安装的编译器进行的标准构建,而 stage2 构建是使用 stage1 编译器构建的。这种命名法也适用于更多阶段。一般来说,stage*n* 构建是使用 stage*n-1* 的输出构建的。

Apple Clang 构建 (更复杂的引导)

Apple 的 Clang 构建是简单引导场景的一个稍微复杂的示例。Apple Clang 是使用 2 阶段构建构建的。

stage1 编译器是一个仅主机编译器,并设置了一些选项。stage1 编译器是优化与构建时间之间的平衡,因为它是一次性的。stage2 编译器是完全优化的编译器,旨在交付给用户。

设置这些编译器需要很多选项。为了简化配置,Apple Clang 构建设置包含在 CMake 缓存文件中。您可以使用以下命令构建 Apple Clang 编译器

$ cmake -G Ninja -C <path to source>/clang/cmake/caches/Apple-stage1.cmake <path to source>/llvm
$ ninja stage2-distribution

此 CMake 调用配置 stage1 主机编译器,并设置 CLANG_BOOTSTRAP_CMAKE_ARGS 以将 Apple-stage2.cmake 缓存脚本传递给 stage2 配置步骤。

当您构建 stage2-distribution 目标时,它会构建最小的 stage1 编译器和所需的工具,然后根据 Apple-stage2.cmake 中的设置配置和构建 stage2 编译器。

使用缓存脚本设置复杂设置,特别是使后续阶段构建包含缓存脚本的这种模式,在我们更高级的构建配置中很常见。

多阶段 PGO

Profile-Guided Optimizations (PGO) 是一种优化 clang 生成代码的绝佳方法。我们的多阶段 PGO 构建是生成 PGO 配置文件的流程,这些配置文件可用于优化 clang。

从高层次来看,PGO 的工作方式是您构建一个 instrumentation 编译器,然后您针对示例源文件运行 instrumentation 编译器。当 instrumentation 编译器运行时,它将输出一堆包含性能计数器的文件(.profraw 文件)。生成所有 profraw 文件后,您可以使用 llvm-profdata 将这些文件合并到一个 profdata 文件中,您可以将其馈送到 LLVM_PROFDATA_FILE 选项中。

我们的 PGO.cmake 缓存自动化了整个过程。您可以使用以下命令通过 CMake 进行配置

$ cmake -G Ninja -C <path to source>/clang/cmake/caches/PGO.cmake \
    <path to source>/llvm

缓存文件还接受几个附加选项来修改构建,特别是 PGO_INSTRUMENT_LTO 选项。将此选项设置为 Thin 或 Full 将分别启用 ThinLTO 或 full LTO,通过启用过程间优化,进一步增强 PGO 构建的性能提升。例如,要为也启用 ThinTLO 的 PGO 构建运行 CMake 配置,请使用以下命令

$ cmake -G Ninja -C <path to source>/clang/cmake/caches/PGO.cmake \
    -DPGO_INSTRUMENT_LTO=Thin \
    <path to source>/llvm

默认情况下,clang 将通过编译一个简单的 hello world 程序来生成配置文件数据。您也可以告诉 clang 使用外部项目来生成配置文件数据,这可能更适合您的用例。您指定的项目必须是 lit 测试套件(使用 CLANG_PGO_TRAINING_DATA 选项)或 CMake 项目(使用 CLANG_PERF_TRAINING_DATA_SOURCE_DIR 选项)。

例如,如果您想使用 LLVM 测试套件 生成配置文件数据,您将使用以下命令

$ cmake -G Ninja -C <path to source>/clang/cmake/caches/PGO.cmake \
     -DBOOTSTRAP_CLANG_PGO_TRAINING_DATA_SOURCE_DIR=<path to llvm-test-suite> \
     -DBOOTSTRAP_CLANG_PGO_TRAINING_DEPS=runtimes

BOOTSTRAP_ 前缀告诉 CMake 将变量传递到 instrumentation stage2 构建。CLANG_PGO_TRAINING_DEPS 选项允许您指定在构建外部项目之前要构建的其他构建目标。LLVM 测试套件需要 compiler-rt 才能构建,因此我们需要添加 runtimes 目标作为依赖项。

配置完成后,构建 stage2-instrumented-generate-profdata 目标将自动构建 stage1 编译器,使用 stage1 编译器构建 instrumentation 编译器,然后针对 perf 训练数据运行 instrumentation 编译器

$ ninja stage2-instrumented-generate-profdata

如果您让它运行几个小时左右,它将在您的构建目录中放置一个 profdata 文件。这需要很长时间,因为它构建 clang 两次,并且您必须在您的构建树中拥有 compiler-rt。

只要源文件标有 LIT 样式的 RUN 行,此过程就使用 perf-training 目录下的任何源文件作为训练数据。

完成后,您可以使用 find . -name clang.profdata 找到它,但它应该位于类似这样的路径

<build dir>/tools/clang/stage2-instrumented-bins/utils/perf-training/clang.profdata

当您构建优化的编译器时,您可以将该文件馈送到 LLVM_PROFDATA_FILE 选项中。

在运行 perf 训练之前,可能需要构建其他目标,例如 builtins 和运行时库。您可以为此目的使用 CLANG_PGO_TRAINING_DEPS CMake 变量

set(CLANG_PGO_TRAINING_DEPS builtins runtimes CACHE STRING "")

PGO 缓存具有与其他多阶段构建略有不同的阶段命名方案。它生成三个阶段:stage1、stage2-instrumented 和 stage2。stage2 的两个构建都使用 stage1 编译器构建。

PGO 缓存生成以下附加目标

stage2-instrumented

构建 stage1 编译器、运行时和所需工具 (llvm-config, llvm-profdata),然后使用该编译器构建 instrumentation stage2 编译器。

stage2-instrumented-generate-profdata

依赖于 stage2-instrumented,并将使用 instrumentation 编译器基于 clang/utils/perf-training 中的训练文件生成 profdata

stage2

依赖于 stage2-instrumented-generate-profdata,并将使用 stage1 编译器和 stage2 profdata 构建 PGO 优化的编译器。

stage2-check-llvm

依赖于 stage2,并使用 stage2 编译器运行 check-llvm。

stage2-check-clang

依赖于 stage2,并使用 stage2 编译器运行 check-clang。

stage2-check-all

依赖于 stage2,并使用 stage2 编译器运行 check-all。

stage2-test-suite

依赖于 stage2,并使用 stage2 编译器运行 test-suite(需要树内 test-suite)。

BOLT

BOLT (二进制优化和布局工具) 是一种在链接后优化二进制文件的工具,它通过在运行时分析二进制文件,然后使用该信息来优化最终二进制文件的布局以及在二进制级别执行的其他优化。还有可用于使用 BOLT 构建 LLVM/Clang 的 CMake 缓存。

要配置一个构建 LLVM/Clang 然后使用 BOLT 优化它的单阶段构建,请使用以下 CMake 配置

$ cmake <path to source>/llvm -C <path to source>/clang/cmake/caches/BOLT.cmake

然后,通过运行以下 ninja 命令构建 BOLT 优化的二进制文件

$ ninja clang-bolt

如果您在构建过程中看到错误,请尝试使用最新版本的 Clang/LLVM 进行构建,方法是将 CMAKE_C_COMPILER 和 CMAKE_CXX_COMPILER 标志设置为适当的值。

也可以在 PGO 和 (Thin)LTO 之上使用 BOLT,以获得更显著的运行时加速。要配置一个使用 ThinLTO 的三阶段 PGO 构建,该构建使用 BOLT 优化生成的二进制文件,请使用以下 CMake 配置命令

$ cmake -G Ninja <path to source>/llvm \
    -C <path to source>/clang/cmake/caches/BOLT-PGO.cmake \
    -DBOOTSTRAP_LLVM_ENABLE_LLD=ON \
    -DBOOTSTRAP_BOOTSTRAP_LLVM_ENABLE_LLD=ON \
    -DPGO_INSTRUMENT_LTO=Thin

然后,要构建最终优化的二进制文件,请构建 stage2-clang-bolt 目标

$ ninja stage2-clang-bolt

三阶段非确定性

在编译器古老的传说中,非确定性就像多头九头蛇。每当它的头出现时,就会随之而来恐怖和混乱。

从历史上看,验证编译器是否具有确定性的测试之一是三阶段构建。三阶段构建的想法是,您获取源代码并构建一个编译器 (stage1),然后使用该编译器重建源代码 (stage2),然后您使用该编译器以与 stage2 构建相同的配置第三次重建源代码 (stage3)。最后,您将得到一个 stage2 和 stage3 编译器,它们应该是逐位相同的。

您可以使用以下命令使用 LLVM 和 clang 执行这些三阶段构建之一

$ cmake -G Ninja -C <path to source>/clang/cmake/caches/3-stage.cmake <path to source>/llvm
$ ninja stage3

构建完成后,您可以比较 stage2 和 stage3 编译器。