为什么 GCC 和 Clang 会在两个分支上弹出而不是只弹出一次?

问题描述 投票:0回答:1

GCC 和 Clang 都可以编译

bool pred();
void f();
void g();

void h() {
    if (pred()) {
        f();
    } else {
        g();
    }
}

的一些变体
# Clang -Os output.  -O3 is the same
h():
    push    rax
    call    pred()@PLT
    test    al, al
    je      .LBB0_2
    pop     rax
    jmp     f()@PLT
.LBB0_2:
    pop     rax
    jmp     g()@PLT

优化代码大小时。您可以在编译器资源管理器上看到这一点。

是否有理由在两个分支上发出

pop
指令,而不是在
je
之前只发出一次?

例如,弹出到不同的调用破坏寄存器以避免破坏 pred() 返回值,或使用

add rsp, 8
(在这种情况下,在现代 CPU 上实际上可能并不更快,因为它需要堆栈同步 uop。)

# hand-written example of what compilers could be doing
h():
    push    rax             # align the stack
    call    pred()@PLT
    pop     rcx             # clean up the stack, restoring the stack pointer to its initial state
    test    al, al
    je      .LBB0_2
    jmp     f()@PLT        # tailcall f
.LBB0_2:
    jmp     g()@PLT        # tailcall g
c++ assembly gcc x86-64 compiler-optimization
1个回答
0
投票

它是高级结构和通过跳转优化尾部调用的尝试的混合的一部分。每当一个函数以调用另一个函数(或返回)结束时,编译器就会使用

pop rax; jmp function()
代码。

在你的情况下,两个分支都是尾部调用。为了简洁起见,我使用三元,它生成与

if()
:

相同的代码
bool pred();
void f();
void g();
void i();

void h() {
    pred() ? f() : g();
    i();
}

// asm
h():
  push rdx
  call pred()
  test al, al
  je .L2
  call f()
  jmp .L3
.L2:
  call g()
.L3:
  pop rax
  jmp i()

看起来和预期的一样,对吧?如果我们按顺序调用三个,尾部就会弹出:

void h() {
    pred();
    f(); 
    g();
}

h():
  push rax
  call pred()
  call f()
  pop rdx
  jmp g()
© www.soinside.com 2019 - 2024. All rights reserved.