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

10

我有一个 Box<dyn Any>,并且我知道其中的基础类型,因此我想优化掉Box::downcast()中的测试(源代码)。

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

pub unsafe fn downcast() -> Box<i32> {
    let value = any();
    if let Ok(value) = value.downcast() {
        value
    } else {
        std::hint::unreachable_unchecked()
    }
}

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

rustc -C opt-level=3使用,两者结果均为以下内容(省略了40行):

Translated text:

Using rustc -C opt-level=3, both results are the following (with 40 lines omitted):

Note: I kept the original formatting and HTML tags in the translated text.
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

由于这不是我寻找的优化方式,因此我尝试了

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

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

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::downcast中的Ok分支:

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)
}

这将导致如下结果(省略行):

example::downcast:
        push    rax
        call    any@PLT
        pop     rcx
        ret

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

godbolt 生成的所有汇编代码


1
“我希望生成这样的代码,这是从Box :: downcastOk分支:”-- 在Ok分支之前,您正在省略一个调用: self.is::<T>()。如果在那里插入该调用,即使您丢弃结果(因此 let value = any(); value.is::<i32>(); let raw: ...),也将获得相同的代码。我还不知道它实现中具体难以优化的原因是什么。 - user743382
那是我知道是真的并且期望编译器优化掉的语句。 - Tim Diekmann
我认为在self.is::<T>()内部保留了对any::get_type_id()的虚拟调用。编译器不能看穿它,并在其中加入一个取消块,以防它具有副作用。 - wartmanm
@wartmanm 我在研究和发布答案后才看到你的评论... 如果早点看到就能省下我很多工作 :P - orlp
1个回答

2

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

最初的回答:

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()
    }
}

我们可以将这个转换为:

最初的回答

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)
}

"最初的回答"翻译成中文是“Original Answer”。

value.is::<i32>() 没有被使用!我们能否删除它?这就是问题所在。

is 方法调用了 dyn Any 对象上的 get_type_id 方法。该方法只能在运行时确定。并且它可能会产生副作用。因此,它不能被删除。

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

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

现在你可能会争辩说Any::get_type_id是由编译器普遍定义的,无法被覆盖,但编译器不够聪明以认识到这一点。"最初的回答"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接