基于链接器插件的 LTO

-C linker-plugin-lto 标志允许将 LTO 优化推迟到实际链接步骤,这反过来允许在所有被链接的目标文件都是由基于 LLVM 的工具链创建的情况下,跨编程语言边界执行过程间优化。这里的主要例子是将 Rust 代码与用 Clang 编译的 C/C++ 代码链接在一起。

用法

基于链接器插件的 LTO 可以使用两种主要情况

  • 编译用作 C ABI 依赖项的 Rust staticlib
  • 编译 Rust 二进制文件,其中 rustc 调用链接器

在这两种情况下,Rust 代码都必须使用 -C linker-plugin-lto 编译,而 C/C++ 代码则使用 -flto-flto=thin 编译,以便目标文件以 LLVM 位码的形式发出。

Rust staticlib 作为 C/C++ 程序中的依赖项

在这种情况下,Rust 编译器只需要确保 staticlib 中的目标文件格式正确。对于链接,必须使用带有 LLVM 插件的链接器(例如 LLD)。

直接使用 rustc

# Compile the Rust staticlib
rustc --crate-type=staticlib -Clinker-plugin-lto -Copt-level=2 ./lib.rs
# Compile the C code with `-flto=thin`
clang -c -O2 -flto=thin -o cmain.o ./cmain.c
# Link everything, making sure that we use an appropriate linker
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o

使用 cargo

# Compile the Rust staticlib
RUSTFLAGS="-Clinker-plugin-lto" cargo build --release
# Compile the C code with `-flto=thin`
clang -c -O2 -flto=thin -o cmain.o ./cmain.c
# Link everything, making sure that we use an appropriate linker
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o

C/C++ 代码作为 Rust 中的依赖项

在这种情况下,链接器将由 rustc 调用。我们再次需要确保使用适当的链接器。

直接使用 rustc

# Compile C code with `-flto`
clang ./clib.c -flto=thin -c -o ./clib.o -O2
# Create a static library from the C code
ar crus ./libxyz.a ./clib.o

# Invoke `rustc` with the additional arguments
rustc -Clinker-plugin-lto -L. -Copt-level=2 -Clinker=clang -Clink-arg=-fuse-ld=lld ./main.rs

直接使用 cargo

# Compile C code with `-flto`
clang ./clib.c -flto=thin -c -o ./clib.o -O2
# Create a static library from the C code
ar crus ./libxyz.a ./clib.o

# Set the linking arguments via RUSTFLAGS
RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release

显式指定 rustc 要使用的链接器插件

如果想要使用除 LLD 之外的链接器,则必须显式指定 LLVM 链接器插件。否则,链接器将无法读取目标文件。插件的路径作为参数传递给 -Clinker-plugin-lto 选项

rustc -Clinker-plugin-lto="/path/to/LLVMgold.so" -L. -Copt-level=2 ./main.rs

与 clang-cl 和 x86_64-pc-windows-msvc 一起使用

跨语言 LTO 可以与 x86_64-pc-windows-msvc 目标一起使用,但这需要使用 clang-cl 编译器而不是 Visual Studio Build Tools 附带的 MSVC cl.exe,并使用 lld-link 进行链接。clang-cl 和 lld-link 都可以从 LLVM 的下载页面 下载。请注意,生态系统中的大多数板条箱可能会假设您在使用此目标时使用 cl.exe,并且某些东西,例如 vcpkg,与 clang-cl 的配合不是很好

您需要确保您的 rust 主要 LLVM 版本与您安装的 LLVM 工具版本匹配,否则您很可能会遇到链接器错误

rustc -V --verbose
clang-cl --version

如果您正在编译任何 proc-macros,您将收到此错误

error: Linker plugin based LTO is not supported together with `-C prefer-dynamic` when
targeting Windows-like targets

如果您显式设置目标,例如 cargo build --target x86_64-pc-windows-msvc,则可以解决此问题。如果没有显式的 --target,这些标志将传递给所有编译器调用(包括构建脚本和 proc-macros),请参阅 cargo 文档中的 rustflags

如果您有使用 cc 板条箱的依赖项,则需要设置这些环境变量

set CC=clang-cl
set CXX=clang-cl
set CFLAGS=/clang:-flto=thin /clang:-fuse-ld=lld-link
set CXXFLAGS=/clang:-flto=thin /clang:-fuse-ld=lld-link
REM Needed because msvc's lib.exe crashes on LLVM LTO .obj files
set AR=llvm-lib

如果您在 cargo 配置中设置 linker = "lld-link.exe" 来指定 lld-link 作为您的链接器,您可能会遇到使用单独的 cargo 调用编译代码的一些板条箱的问题。您可以通过在 RUSTFLAGS 中设置 -Clinker=lld-link 来解决此问题

工具链兼容性

为了使这种 LTO 能够正常工作,LLVM 链接器插件必须能够处理 rustcclang 生成的 LLVM 位码。

使用基于完全相同 LLVM 版本的 rustcclang 可以获得最佳效果。可以使用 rustc -vV 来查看给定 rustc 版本使用的 LLVM。请注意,这里给出的版本号只是一个近似值,因为 Rust 有时会使用 LLVM 的不稳定版本。但是,近似值通常是可靠的。

下表显示了已知的良好工具链版本组合。

Rust 版本Clang 版本
1.34 - 1.378
1.38 - 1.449
1.45 - 1.4610
1.47 - 1.5111
1.52 - 1.5512
1.56 - 1.5913
1.60 - 1.6414
1.65 - 1.6915
1.70 - 1.7216
1.73 - 1.7417

请注意,此功能的兼容性策略将来可能会更改。