关于Assembly:为什么Rust编译器不能优化Box :: downcast的Err分支?

Why can the Rust compiler not optimize away the Err arm of Box::downcast?

我有一个Box,我知道基础类型,所以我想优化Box::downcast()(源)中的测试。

首先,我尝试使用std::hint::unreachable_unchecked()

1
2
3
4
5
6
7
8
pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    if let Ok(value) = value.downcast() {
        value
    } else {
        std::hint::unreachable_unchecked()
    }
}

1
2
3
pub unsafe fn downcast() -> Box<i32> {
    any().downcast().map_err(|_| std::hint::unreachable_unchecked()).unwrap()
}

rustc -C opt-level=3都导致(省略40行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
example::downcast:
        push    rbx
        sub     rsp, 16
        call    any@PLT
        mov     rbx, rax
        mov     qword ptr [rsp], rax
        mov     qword ptr [rsp + 8], rdx
        mov     rdi, rax
        call    qword ptr [rdx + 24]
        mov     rax, rbx
        add     rsp, 16
        pop     rbx
        ret
        mov     rbx, rax
        mov     rdi, rsp
        call    core::ptr::drop_in_place
        mov     rdi, rbx
        call    _Unwind_Resume@PLT
        ud2

由于这不是我想要的优化,所以我尝试了

1
2
3
4
5
pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    std::intrinsics::assume(value.is::<i32>());
    value.downcast().unwrap()
}

但这变得更糟(省略了118行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
example::downcast:
        push    r15
        push    r14
        push    rbx
        sub     rsp, 32
        call    any@PLT
        mov     rbx, rax
        mov     r14, rdx
        mov     qword ptr [rsp], rax
        mov     qword ptr [rsp + 8], rdx
        mov     r15, qword ptr [rdx + 24]
        mov     rdi, rax
        call    r15
        mov     qword ptr [rsp + 16], rbx
        mov     qword ptr [rsp + 24], r14
        mov     rdi, rbx
        call    r15
        movabs  rcx, -5015437470765251660     ;TypeId::of::<i32>()
        cmp     rax, rcx
        jne     .LBB5_7
        mov     rax, rbx
        add     rsp, 32
        pop     rbx
        pop     r14
        pop     r15
        ret
.LBB5_7:
        mov     rdi, rbx
        mov     rsi, r14
        call    core::result::unwrap_failed
        ud2
        mov     rbx, rax
        lea     rdi, [rsp + 16]
        call    core::ptr::drop_in_place
        mov     rdi, rbx
        call    _Unwind_Resume@PLT
        ud2
        mov     rbx, rax
        mov     rdi, rsp
        call    core::ptr::drop_in_place
        mov     rdi, rbx
        call    _Unwind_Resume@PLT
        ud2

我期望生成这样的代码,这是Box::downcastOk分支:

1
2
3
4
5
pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    let raw: *mut dyn Any = Box::into_raw(value);
    Box::from_raw(raw as *mut i32)
}

结果(省略零行):

1
2
3
4
5
example::downcast:
        push    rax
        call    any@PLT
        pop     rcx
        ret

为什么编译器不能以这种方式优化代码?

所有组件均由Godbolt生成。


让我们尝试尽可能地手动优化您的代码。 如果手动内联downcast(),则会得到以下信息:

1
2
3
4
5
6
7
8
9
pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    if value.is::<i32>() {
        let raw: *mut Any = Box::into_raw(value);
        Box::from_raw(raw as *mut i32)
    } else {
        std::hint::unreachable_unchecked()
    }
}

我们可以将其转换为:

1
2
3
4
5
6
pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    value.is::<i32>();
    let raw: *mut Any = Box::into_raw(value);
    Box::from_raw(raw as *mut i32)
}

value.is::()未使用! 我们可以删除它吗? 这就是问题所在。

is方法在dyn Any对象上调用get_type_id。 该方法只能在运行时 而且可能会有副作用。 因此无法将其删除。

您可以从以下函数中看到与示例相同的冗长的汇编代码:

1
2
3
4
#![feature(get_type_id)]
pub fn nop(any: Box<dyn Any>) {
    any.get_type_id();
}

现在您可能会争辩说Any::get_type_id是由编译器通用定义的,不能被覆盖,但是编译器不够聪明,无法意识到这一点。