为什么Rust优化器不会删除那些无用的指令(在Godbolt Compiler Explorer上测试)?

时间:2017-08-09 09:04:04

标签: assembly rust x86-64

我想查看一个小的Rust函数的程序集输出:

pub fn double(n: u8) -> u8 {
    n + n
}

我使用Godbolt Compiler Explorer生成并查看程序集(当然,带有-O标志)。它显示了这个输出:

example::double:
    push    rbp
    mov     rbp, rsp
    add     dil, dil
    mov     eax, edi
    pop     rbp
    ret

现在我有点困惑,因为有一些说明似乎没有做任何有用的事情:push rbpmov rbp, rsppop rbp。据我所知,我认为单独执行这三条指令并没有任何副作用。 那么为什么Rust优化器不会删除那些无用的指令呢?

为了进行比较,我还测试了a C++ version

unsigned char doubleN(unsigned char n) {
    return n + n;
}

汇编输出(带-O标志):

doubleN(unsigned char): # @doubleN(unsigned char)
    add dil, dil
    mov eax, edi
    ret

事实上,这些"无用的"正如我对优化输出所期望的那样,缺少上面的指令。

1 个答案:

答案 0 :(得分:16)

简短回答: Godbolt添加了一个-C debuginfo=1标志,强制优化器保留管理帧指针的所有指令。在使用优化进行编译并且没有调试信息时,Rust也会删除这些指令。

这些指示在做什么?

这三条说明是function prologue and epilogue的一部分。特别是,他们在这里管理所谓的frame pointer or base pointer (rbp on x86_64)。注意:不要将基指针堆栈指针(x86_64上的rsp)混淆! 基指针始终指向当前堆栈帧:

                          ┌──────────────────────┐                         
                          │  function arguments  │                      
                          │         ...          │   
                          ├──────────────────────┤   
                          │    return address    │   
                          ├──────────────────────┤   
              [rbp] ──>   │       last rbp       │   
                          ├──────────────────────┤   
                          │   local variables    │   
                          │         ...          │   
                          └──────────────────────┘    

关于基指针的有趣之处在于它指向堆栈中的一块内存,它存储rbp的最后一个值。这意味着我们可以很容易地找到前一个堆栈帧的基本指针(来自调用“us”的函数中的一个)。

更好:所有基本指针形成类似于链表的东西!我们可以轻松地跟随所有last rbp来向上走。这意味着在程序执行期间的每个点上,我们确切地知道哪些函数称为其他函数,以便我们最终“在这里”。

让我们再次查看说明:

; We store the "old" rbp on the stack
push    rbp

; We update rbp to hold the new value
mov     rbp, rsp

; We undo what we've done: we remove the old rbp
; from the stack and store it in the rbp register
pop     rbp

这些说明适用于哪些?

基本指针及其“链表”属性对于调试和分析程序行为(例如分析)非常重要。如果没有基指针,生成堆栈跟踪和定位当前执行的函数会更加困难。

此外,管理帧指针通常不会减慢很多事情。

为什么它们不被优化器删除,我该如何强制执行呢?

如果是Godbolt didn't pass -C debuginfo=1 to the compiler,通常会是这样。这指示编译器保留与帧指针处理相关的所有内容,因为我们需要它来进行调试。请注意,调试不一定需要帧指针 - 其他类型的调试信息通常就足够了。存储任何类型的调试信息时都会保留帧指针,因为在Rust程序中删除帧指针仍然存在一些小问题。这正在this GitHub tracking issue中讨论。

您只需adding the flag -C debuginfo=0 yourself即可“撤消”它。这导致与C ++版本完全相同的输出:

example::double:
    add     dil, dil
    mov     eax, edi
    ret

您也可以通过执行以下方式在本地测试:

$ rustc -O --crate-type=lib --emit asm -C "llvm-args=-x86-asm-syntax=intel" example.rs

如果您没有明确打开调试信息,则使用优化(-O)进行编译会自动删除rbp处理。