The -C linker-plugin-lto flag allows for deferring the LTO optimization to the actual linking step, which in turn allows for performing interprocedural optimizations across programming language boundaries if all the object files being linked were created by LLVM-based toolchains using the same LTO mode: either thin LTO or fat LTO. The examples would be linking Rust code together with Clang-compiled C/C++ code and LLVM Flang-compiled Fortran code.
There are two main cases how linker-plugin-based LTO can be used:
staticlib that is used as a C ABI dependencyrustc invokes the linkerIn both cases, the Rust code must be compiled with -C linker-plugin-lto. By default, this enables thin LTO, so any interoperable language code must also be compiled in thin LTO mode. To use fat LTO with linker-plugin-based LTO, the rustc compiler requires the additional -C lto=fat flag, and the interoperable language must likewise be compiled in fat LTO mode. Note that interoperable language must be compiled using the LLVM infrastructure (see more details in toolchain compability).
The following table summarizes how to enable thin LTO and fat LTO in different compilers:
| Compiler | Thin LTO | Fat LTO |
|---|---|---|
| rustc | -Clto=thin | -Clto=fat |
| clang | -flto=thin | -flto=full |
| clang++ | -flto=thin | -flto=full |
| flang | -flto=thin (WIP) | -flto=full |
staticlib as dependency in C/C++ programIn this case the Rust compiler just has to make sure that the object files in the staticlib are in the right format. For linking, a linker with the LLVM plugin must be used (e.g. LLD).
Using rustc directly:
# 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
Using 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
In this case the linker will be invoked by rustc. We again have to make sure that an appropriate linker is used.
Using rustc directly:
# Compile C code with `-flto=thin` 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
Using cargo directly:
# Compile C code with `-flto=thin` 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
Rust code can also be linked together with Fortran code compiled by LLVM flang. The following examples demonstrate fat LTO usage, as LLVM flang has WIP status for thin LTO. The same approach can be applied to compile C and C++ code with fat LTO.
Using rustc directly:
# Compile Fortran code with `-flto=full` flang ./ftnlib.f90 -flto=full -c -o ./ftnlib.f90.o -O3 # Create a static library from the Fortran code ar crus ./libftn.a ./ftnlib.f90.o # Invoke `rustc` with the additional arguments, `-Clto=fat` is mandatory rustc -Clinker-plugin-lto -Clto=fat -Clink-arg=path/to/libftn.a -Copt-level=3 -Clinker=flang -C default-linker-libraries=yes -Clink-arg=-fuse-ld=lld ./main.rs
Using cargo directly:
# Compile Fortran code with `-flto=full` flang ./ftnlib.f90 -flto=full -c -o ./ftnlib.f90.o -O3 # Create a static library from the Fortran code ar crus ./libftn.a ./ftnlib.f90.o # Set the linking arguments via RUSTFLAGS, `-Clto=fat` is mandatory RUSTFLAGS="-Clinker-plugin-lto -Clto=fat -Clink-arg=path/to/libftn.a -Clinker=flang -C default-linker-libraries=yes -Clink-arg=-fuse-ld=lld" cargo build --release
Note, LLVM flang can be used as a linker driver starting from flang 21.1.8. The -C default-linker-libraries=yes option may be omitted if the Fortran runtime is not required; however, most Fortran code depends on the runtime, so enabling default linker libraries is usually necessary.
rustcIf one wants to use a linker other than LLD, the LLVM linker plugin has to be specified explicitly. Otherwise the linker cannot read the object files. The path to the plugin is passed as an argument to the -Clinker-plugin-lto option:
rustc -Clinker-plugin-lto="/path/to/LLVMgold.so" -L. -Copt-level=2 ./main.rs
Cross language LTO can be used with the x86_64-pc-windows-msvc target, but this requires using the clang-cl compiler instead of the MSVC cl.exe included with Visual Studio Build Tools, and linking with lld-link. Both clang-cl and lld-link can be downloaded from LLVM's download page. Note that most crates in the ecosystem are likely to assume you are using cl.exe if using this target and that some things, like for example vcpkg, don't work very well with clang-cl.
You will want to make sure your rust major LLVM version matches your installed LLVM tooling version, otherwise it is likely you will get linker errors:
rustc -V --verbose clang-cl --version
If you are compiling any proc-macros, you will get this error:
error: Linker plugin based LTO is not supported together with `-C prefer-dynamic` when targeting Windows-like targets
This is fixed if you explicitly set the target, for example cargo build --target x86_64-pc-windows-msvc Without an explicit --target the flags will be passed to all compiler invocations (including build scripts and proc macros), see cargo docs on rustflags
If you have dependencies using the cc crate, you will need to set these environment variables:
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
If you are specifying lld-link as your linker by setting linker = "lld-link.exe" in your cargo config, you may run into issues with some crates that compile code with separate cargo invocations. You should be able to get around this problem by setting -Clinker=lld-link in RUSTFLAGS
In order for this kind of LTO to work, the LLVM linker plugin must be able to handle the LLVM bitcode produced by rustc and by compilers of all interoperable languages. A good rule of thumb is to use an LLVM linker plugin whose version is at least as new as the newest compiler involved.
Best results are achieved by using a rustc and LLVM compilers that are based on the exact same version of LLVM. One can use rustc -vV in order to view the LLVM used by a given rustc version. Note that the version number given here is only an approximation as Rust sometimes uses unstable revisions of LLVM. However, the approximation is usually reliable.
The following table shows known good combinations of toolchain versions.
| Rust Version | Clang Version |
|---|---|
| 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 - 1.81 | 18 |
| 1.82 - 1.86 | 19 |
| 1.87 - 1.90 | 20 |
| 1.91 - 1.93 | 21 |
Note that the compatibility policy for this feature might change in the future.