基于链接器插件的 LTO
-C linker-plugin-lto 标志允许将 LTO 优化延迟到实际的链接步骤,这进而允许在所有被链接的对象文件都由基于 LLVM 的工具链创建的情况下,跨越编程语言边界执行过程间优化。最主要的例子是将 Rust 代码与 Clang 编译的 C/C++ 代码一起链接。
用法
基于链接器插件的 LTO 有两种主要的使用情况
- 编译一个用作 C ABI 依赖项的 Rust
staticlib - 编译一个由
rustc调用链接器的 Rust 二进制文件
在这两种情况下,Rust 代码必须使用 -C linker-plugin-lto 进行编译,而 C/C++ 代码必须使用 -flto 或 -flto=thin 进行编译,以便将对象文件作为 LLVM bitcode 发出。
在 C/C++ 程序中将 Rust staticlib 作为依赖项
在这种情况下,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
在 Rust 中将 C/C++ 代码作为依赖项
在这种情况下,链接器将由 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 的下载页面下载。请注意,生态系统中的大多数 crates 在使用此目标时可能假定您正在使用 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 crate,您需要设置这些环境变量
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 config 中设置 linker = "lld-link.exe" 将 lld-link 指定为您的链接器,您可能会遇到一些 crate 的问题,这些 crate 会通过单独的 cargo 调用编译代码。您应该可以通过在 RUSTFLAGS 中设置 -Clinker=lld-link 来解决此问题
工具链兼容性
为了使这种 LTO 工作,LLVM 链接器插件必须能够处理 rustc 和 clang 生成的 LLVM bitcode。
使用基于完全相同版本的 LLVM 的 rustc 和 clang 可以获得最佳结果。可以使用 rustc -vV 查看给定 rustc 版本使用的 LLVM。请注意,此处给出的版本号只是一个近似值,因为 Rust 有时使用 LLVM 的不稳定修订版。然而,这个近似值通常是可靠的。
下表显示了已知良好的工具链版本组合。
| Rust 版本 | Clang 版本 |
|---|---|
| 1.34 - 1.37 | 8 |
| 1.38 - 1.44 | 9 |
| 1.45 - 1.46 | 10 |
| 1.47 - 1.51 | 11 |
| 1.52 - 1.55 | 12 |
| 1.56 - 1.59 | 13 |
| 1.60 - 1.64 | 14 |
| 1.65 - 1.69 | 15 |
| 1.70 - 1.72 | 16 |
| 1.73 - 1.77 | 17 |
| 1.78 | 18 |
请注意,此功能的兼容性策略将来可能会更改。