高级构建配置

简介

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

配置文件引导优化 (PGO) 是一种优化 clang 生成的代码的非常好的方法。我们的多阶段 PGO 构建是生成可用于优化 clang 的 PGO 配置文件的流程。

在高级别上,PGO 的工作原理是,您构建一个带检测的编译器,然后您针对示例源文件运行该带检测的编译器。当带检测的编译器运行时,它将输出一堆包含性能计数器(.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 或完整 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 将变量传递到检测的 stage2 构建。而 CLANG_PGO_TRAINING_DEPS 选项允许您指定在构建外部项目之前要构建的其他构建目标。LLVM 测试套件需要编译器运行时才能构建,因此我们需要添加 runtimes 目标作为依赖项。

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

$ ninja stage2-instrumented-generate-profdata

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

此过程使用性能训练目录下的任何源文件作为训练数据,只要源文件使用 LIT 样式的 RUN 行进行标记。

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

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

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

在运行性能训练之前,可能需要构建其他目标,例如内置函数和运行时库。您可以为此目的使用 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),然后使用该编译器构建检测的 stage2 编译器。

stage2-instrumented-generate-profdata

依赖于 stage2-instrumented,并将使用检测的编译器根据 clang/utils/perf-training 中的训练文件生成配置文件数据

stage2

依赖于 stage2-instrumented-generate-profdata,并将使用 stage1 编译器和 stage2 配置文件数据构建 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 编译器运行测试套件(需要树内测试套件)。

BOLT

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

要配置一个构建 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

3 阶段非确定性

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

历史上,验证编译器是否确定性的测试之一是三阶段构建。三阶段构建的想法是,您获取源代码并构建一个编译器 (stage1),然后使用该编译器重新构建源代码 (stage2),然后使用该编译器使用与 stage2 构建相同的配置再次重新构建源代码 (stage3)。在最后,您将获得一个 stage2 和 stage3 编译器,它们应该在比特级别上完全相同。

您可以使用以下命令使用 LLVM 和 clang 执行其中一个 3 阶段构建

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

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