【问题标题】:Why does LLVM appear to ignore Rust's assume intrinsic?为什么 LLVM 似乎忽略了 Rust 的假设内在?
【发布时间】:2020-05-02 04:48:15
【问题描述】:

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 内在函数?

【问题讨论】:

  • 您永远无法保证优化正在发生。 LLVM 在这种情况下没有优化代码的事实并不意味着它总是忽略这个假设。我知道您想知道为什么 LLVM 在用不同的词“理解”假设时,但我认为理解差异需要非常详细地了解 LLVM 的优化通道,而答案不会给我们很多适用于其他情况的见解。
  • 您认为带有assume 的程序集应该与带有unreachable_unchecked 的程序集匹配吗?您的问题是否有效地“为什么这些不一样?”
  • 是的。从文档中,优化器应该能够使用假设来删除未到达的代码路径,而它显然没有。
  • 有一个 LLVM 选项可以在每次通过后打印 LLVM IR,通常称为 -print-after-all。非常适合这种情况。您使用该选项编译两个近似等效项,然后查找 LLVM 行为分歧的点。这并没有告诉你为什么,至少不是靠它自己,但是当我这样做时,相关通行证的 git log 告诉了我我想知道的内容。

标签: performance rust llvm llvm-codegen


【解决方案1】:

由于 rustc 和 LLVM 的改进,这个 now compiles to just a ret 在 rustc 的最新版本上。 LLVM 忽略了内在函数,因为它以前无法优化它,但现在它有能力更好地优化它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-27
    • 2021-09-09
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 2021-10-28
    • 2016-09-30
    相关资源
    最近更新 更多