为什么 LLVM 似乎忽略了 Rust 的内在假设?



LLVM似乎忽略了core::intrinsics::assume(..)调用。它们最终会出现在字节码中,但不会更改生成的机器代码。例如,采用以下(无意义的(代码:

pub fn one(xs: &mut Vec<i32>) {
if let Some(x) = xs.pop() {
xs.push(x);
}
}

这编译成一大堆汇编:

example::one:
push    rbp
push    r15
push    r14
push    r12
push    rbx
mov     rbx, qword ptr [rdi + 16]
test    rbx, rbx
je      .LBB0_9
mov     r14, rdi
lea     rsi, [rbx - 1]
mov     qword ptr [rdi + 16], rsi
mov     rdi, qword ptr [rdi]
mov     ebp, dword ptr [rdi + 4*rbx - 4]
cmp     rsi, qword ptr [r14 + 8]
jne     .LBB0_8
lea     rax, [rsi + rsi]
cmp     rax, rbx
cmova   rbx, rax
mov     ecx, 4
xor     r15d, r15d
mov     rax, rbx
mul     rcx
mov     r12, rax
setno   al
jo      .LBB0_11
mov     r15b, al
shl     r15, 2
test    rsi, rsi
je      .LBB0_4
shl     rsi, 2
mov     edx, 4
mov     rcx, r12
call    qword ptr [rip + __rust_realloc@GOTPCREL]
mov     rdi, rax
test    rax, rax
je      .LBB0_10
.LBB0_7:
mov     qword ptr [r14], rdi
mov     qword ptr [r14 + 8], rbx
mov     rsi, qword ptr [r14 + 16]
.LBB0_8:
or      ebp, 1
mov     dword ptr [rdi + 4*rsi], ebp
add     qword ptr [r14 + 16], 1
.LBB0_9:
pop     rbx
pop     r12
pop     r14
pop     r15
pop     rbp
ret
.LBB0_4:
mov     rdi, r12
mov     rsi, r15
call    qword ptr [rip + __rust_alloc@GOTPCREL]
mov     rdi, rax
test    rax, rax
jne     .LBB0_7
.LBB0_10:
mov     rdi, r12
mov     rsi, r15
call    qword ptr [rip + alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
.LBB0_11:
call    qword ptr [rip + alloc::raw_vec::capacity_overflow@GOTPCREL]
ud2

现在我们可以引入一个假设,即xs在之后没有满(满负荷(pop()(仅限每晚(:

#![feature(core_intrinsics)]
pub fn one(xs: &mut Vec<i32>) {
if let Some(x) = xs.pop() {
unsafe {
core::intrinsics::assume(xs.len() < xs.capacity());
}
xs.push(x);
}
}

然而,尽管 LLVM 字节码中显示了assume,但程序集是 变。但是,我们使用core::hint::unreachable_unchecked()来创建 非假设情况下的发散路径,例如:

pub fn one(xs: &mut Vec<i32>) {
if let Some(x) = xs.pop() {
if xs.len() >= xs.capacity() {
unsafe { core::hint::unreachable_unchecked() }
}
xs.push(x);
}
}

我们得到以下结果:

example::one:
mov     rax, qword ptr [rdi + 16]
test    rax, rax
je      .LBB0_2
mov     qword ptr [rdi + 16], rax
.LBB0_2:
ret

这本质上是一个无操作,但还不错。当然,我们可以通过使用以下方法将值保留在原位:

pub fn one(xs: &mut Vec<i32>) {
xs.last_mut().map(|_e| ());
}

它编译成我们期望的:

example::one:
ret

为什么LLVM似乎忽略了内在assume

由于rustc和LLVM的改进,现在编译为最新版本的rustc的一个ret。LLVM忽略了内在的,因为它以前无法优化它,但现在它有能力更好地优化它。

相关内容

  • 没有找到相关文章

最新更新