我正在使用 nightly rustc 和使用 clang-cl 的 LLVM 19.1。我也尝试了稳定的 rustc 1.81.0 和 clang-cl 18.1.8,但没有成功。
> rustc +nightly --version --verbose
rustc 1.83.0-nightly (9e394f551 2024-09-25)
binary: rustc
commit-hash: 9e394f551c050ff03c6fc57f190e0761cf0be6e8
commit-date: 2024-09-25
host: x86_64-pc-windows-msvc
release: 1.83.0-nightly
LLVM version: 19.1.0
> clang --version
clang version 19.1.0
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin
测试 LTO 是否有效的最少代码。
a.rs
#[no_mangle]
extern "C" fn add_2_numbers(arg: i32)->i32{
arg + arg
}
b.c
#include <stdint.h>
int32_t add_2_numbers(int32_t arg);
[[clang::noinline]]
int32_t process_2_nums(int32_t x, int32_t y) {
return add_2_numbers(x) * add_2_numbers(y);
}
main.c
#include <stdint.h>
#include <stdio.h>
int32_t process_2_nums(int32_t x, int32_t y);
int32_t main() {
int32_t x = 0;
int32_t y = 0;
scanf("%d", &x);
scanf("%d", &y);
const int32_t value = process_2_nums(x, y);
printf("Result is %d", value);
return 0;
}
如果我只使用 c:
,我还尝试了 a.c 检查 lto 是否有效#include <stdint.h>
int32_t add_2_numbers(int32_t arg) { return arg + arg; }
rustc +nightly -Copt-level=3 -Ccodegen-units=1 -Clto=fat -Clinker-plugin-lto --crate-type=staticlib --emit=obj a.rs
clang-cl /clang:-std=c17 /clang:-O3 -c /D_CRT_SECURE_NO_WARNINGS=1 /clang:-flto=thin /clang:-fuse-ld=lld-link b.c
clang-cl /clang:-std=c17 /clang:-O3 -c /D_CRT_SECURE_NO_WARNINGS=1 /clang:-flto=thin /clang:-fuse-ld=lld-link main.c
lld-link /out:c_wrap_rust.exe a.o b.obj main.obj -verbose /threads:1
出于某种原因,Rust 中的函数未内联到 C 中的函数。
sub_140001010 proc near
push rsi
push rdi
sub rsp, 28h
mov esi, edx
call sub_140001000
mov edi, eax
mov ecx, esi
call sub_140001000
imul eax, edi
add rsp, 28h
pop rdi
pop rsi
retn
sub_140001010 endp
; I would expect this function to be inlined.
sub_140001000 proc near
lea eax, [rcx+rcx]
retn
sub_140001000 endp
我还尝试使用
-Clto=thin
,将Rust代码编译到静态库并与其链接,使用clang-cl
链接代码,所有情况都相同。
当我用
a.rs
替换 a.c
并从 C 链接 3 个目标文件时,它就可以工作了。
sub_140001000 proc near
imul ecx, edx
lea eax, ds:0[rcx*4]
retn
sub_140001000 endp
我也尝试使用 Linux 机器做同样的事情,一切都很完美:
命令
clang-19 -std=c17 -O3 -c -flto=thin -o b.o b.c
clang-19 -std=c17 -O3 -c -flto=thin -o main.o main.c
rustc +nightly -Copt-level=3 -Ccodegen-units=1 -Clto=fat -Clinker-plugin-lto --crate-type=staticlib --emit=obj a.rs
clang-19 -flto -fuse-ld=lld a.o b.o main.o -o c_wrap_rust -v
llvm-objdump-19 --demangle --disassemble-symbols=process_2_nums c_wrap_rust
结果:
c_wrap_rust: file format elf64-x86-64
Disassembly of section .text:
0000000000046820 <process_2_nums>:
46820: 0f af fe imull %esi, %edi
46823: 8d 04 bd 00 00 00 00 leal (,%rdi,4), %eax
4682a: c3 retq
4682b: cc int3
4682c: cc int3
4682d: cc int3
4682e: cc int3
4682f: cc int3
我用参数
lld-link
查看了 /mllvm:-print-after-all
的输出,在我看来,它只有在完成为 Rust 发出机器代码后才开始处理来自 C 的目标文件(并且它确实在链接期间优化了 C 和 Rust 代码,但是单独)。
我还尝试将
/LTCG
标志(MS link.exe
lto 标志)传递给链接器,但没有成功。
如何在 pc-windows-msvc 上进行跨语言链接时间优化?
我找到了问题的根源:它是目标特征。当我将
-Ctarget-cpu=znver1
添加到 rustc 并将 /clang:-march=znver1
添加到 clang 时,LLVM 成功地在语言之间内联了函数。
据我了解,LLVM 拒绝 Rust 的内联函数,因为与 C 相比,它们需要更多的 CPU 功能。
因此,在 Windows 上成功 LTO 的最终命令: 中:
clang-cl /clang:-march=znver1 /clang:-std=c17 /clang:-O3 -c /D_CRT_SECURE_NO_WARNINGS=1 /clang:-flo=thin c_filename.c
对于生锈:
rustc -Copt-level=3 -Ctarget-cpu=znver1 -Ccodegen-units=1 -Clto=fat -Clinker-plugin-lto --crate-type=staticlib --emit=obj rust_filename.rs
链接:
lld-link c_filename.obj rust_filename.o