为什么这段代码生成的汇编比等价的C++/Clang代码多得多?

16

我编写了一个简单的 C++ 函数,以便检查编译器是否进行了优化:

bool f1(bool a, bool b) {
    return !a || (a && b);
}

之后,我在 Rust 中检查了等效物:

fn f1(a: bool, b: bool) -> bool {
    !a || (a && b)
}

我使用godbolt来检查汇编输出。

C++代码的结果(由clang编译并带有-O3标志)如下:

f1(bool, bool):                                # @f1(bool, bool)
    xor     dil, 1
    or      dil, sil
    mov     eax, edi
    ret

而 Rust 的等效结果则更长:

example::f1:
  push rbp
  mov rbp, rsp
  mov al, sil
  mov cl, dil
  mov dl, cl
  xor dl, -1
  test dl, 1
  mov byte ptr [rbp - 3], al
  mov byte ptr [rbp - 4], cl
  jne .LBB0_1
  jmp .LBB0_3
.LBB0_1:
  mov byte ptr [rbp - 2], 1
  jmp .LBB0_4
.LBB0_2:
  mov byte ptr [rbp - 2], 0
  jmp .LBB0_4
.LBB0_3:
  mov al, byte ptr [rbp - 4]
  test al, 1
  jne .LBB0_7
  jmp .LBB0_6
.LBB0_4:
  mov al, byte ptr [rbp - 2]
  and al, 1
  movzx eax, al
  pop rbp
  ret
.LBB0_5:
  mov byte ptr [rbp - 1], 1
  jmp .LBB0_8
.LBB0_6:
  mov byte ptr [rbp - 1], 0
  jmp .LBB0_8
.LBB0_7:
  mov al, byte ptr [rbp - 3]
  test al, 1
  jne .LBB0_5
  jmp .LBB0_6
.LBB0_8:
  test byte ptr [rbp - 1], 1
  jne .LBB0_1
  jmp .LBB0_2

我也尝试了-O选项,但输出为空(删除未使用的函数)。

我有意不使用任何库来保持输出的干净。请注意,clangrustc都使用LLVM作为后端。是什么原因导致了这种巨大的输出差异?如果只是禁用优化开关的问题,那么如何查看rustc的优化输出呢?


10
你是否使用了 -O(在发布模式下)进行编译? - Boiethios
@Boiethios 是的,我试过了,但它会删除整个代码(因为未使用)。将函数“pub extern”设置为可用即可 :) - Mariusz Jaskółka
4个回答

41
使用编译器标志-O并添加pub),我得到了以下输出(链接至Godbolt):
push    rbp
mov     rbp, rsp
xor     dil, 1
or      dil, sil
mov     eax, edi
pop     rbp
ret

一些事情:
  • Why is it still longer than the C++ version?

    The Rust version is exactly three instructions longer:

    push    rbp
    mov     rbp, rsp
    [...]
    pop     rbp
    

    These are instructions to manage the so called frame pointer or base pointer (rbp). This is mainly required to get nice stack traces. If you disable it for the C++ version via -fno-omit-frame-pointer, you get the same result. Note that this uses g++ instead of clang++ since I haven't found a comparable option for the clang compiler.

  • Why doesn't Rust omit frame pointer?

    Actually, it does. But Godbolt adds an option to the compiler to preserve frame pointer. You can read more about why this is done here. If you compile your code locally with rustc -O --crate-type=lib foo.rs --emit asm -C "llvm-args=-x86-asm-syntax=intel", you get this output:

    f1:
        xor dil, 1
        or  dil, sil
        mov eax, edi
        ret
    

    Which is exactly the output of your C++ version.

    You can "undo" what Godbolt does by passing -C debuginfo=0 to the compiler.

  • Why -O instead of --release?

    Godbolt uses rustc directly instead of cargo. The --release flag is a flag for cargo. To enable optimizations on rustc, you need to pass -O or -C opt-level=3 (or any other level between 0 and 3).


请强调您将 pub 添加到函数签名中的事实。如果没有这个,Godbolt 将产生空输出。 - Mariusz Jaskółka
1
@jaskmar 这个问题有专门的提问页面 - Lukas Kalbertodt
我是指为了未来读者防止混淆而提供信息。谢谢! - Mariusz Jaskółka

8

在godbolt上使用-C opt-level=3编译会得到以下结果:

example::f1:
  push rbp
  mov rbp, rsp
  xor dil, 1
  or dil, sil
  mov eax, edi
  pop rbp
  ret

这看起来与 C++ 版本相似。更多解释请参见 Lukas Kalbertodt 的答案

注意:我不得不将函数 pub extern,以防止编译器优化它而使其成为无用的代码。


1
哦,所以-C就是用来传递opt-level的方法啊。我之前试过用-O,但是那样会完全移除掉f1 - Bartek Banachewicz
1
仍然看起来有一个无意义的堆栈帧被创建。 - 6502
1
只使用简单的 -O,而不是 opt-level 也可以,但只有在将函数设置为 pub extern 的情况下才有效 :) - Mariusz Jaskółka

4
为了得到相同的汇编代码,需要禁用调试信息,这将移除帧指针推送。 -C opt-level=3 -C debuginfo=0 (https://godbolt.org/g/vdhB2f)

2

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