为什么 LLVM 似乎忽略了 Rust 的 assume intrinsic?
Why does LLVM appear to ignore Rust's assume intrinsic?
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);
}
}
然而,尽管 assume
出现在 LLVM 字节码中,程序集是
不变。但是,如果我们使用 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 now compiles to just a ret
。 LLVM 忽略了内在函数,因为它以前无法优化它,但现在它有能力更好地优化它。
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);
}
}
然而,尽管 assume
出现在 LLVM 字节码中,程序集是
不变。但是,如果我们使用 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 now compiles to just a ret
。 LLVM 忽略了内在函数,因为它以前无法优化它,但现在它有能力更好地优化它。