可以说服 clang 优化这个几乎叶函数吗

Bee*_*ope 12 c++ optimization performance x86-64 clang

考虑以下几乎叶函数:

int almost_leaf(int* x) {
    if (__builtin_expect(*x >= 0, true)) {
        return *x;
    }
    return x_was_negative() + 1;
}
Run Code Online (Sandbox Code Playgroud)

几乎是叶子,因为它不是严格意义上的叶子函数(它可能调用x_was_negativeis x 为负数,但__builtin_expect提示编译器return *x通常采用分支,这不涉及任何调用。

clang-16 像这样编译它:

almost_leaf(int*):                      # @almost_leaf(int*)
        push    rax
        mov     eax, dword ptr [rdi]
        test    eax, eax
        js      .LBB0_1
        pop     rcx
        ret
.LBB0_1:
        call    x_was_negative()
        inc     eax
        pop     rcx
        ret
Run Code Online (Sandbox Code Playgroud)

快速(预期)路径上的and (直到第一个的部分)在这里push是完全不必要的:堆栈未使用,并且不会进行需要“由于 ABI”而对齐的堆栈的调用。popret

最好将堆栈对齐到x_was_negative()调用的慢速路径上,就像 gcc 那样:

almost_leaf(int*):
        mov     eax, DWORD PTR [rdi]
        test    eax, eax
        js      .L8
        ret
.L8:
        sub     rsp, 8
        call    x_was_negative()
        add     rsp, 8
        inc     eax
        ret
Run Code Online (Sandbox Code Playgroud)

可以说服 clang 有效地编译这个几乎是叶子的函数吗?


请注意,clang可以在不对齐堆栈的情况下编译几乎叶函数:例如, ifx是 anint而不是int*它可以工作,并且 ifx_was_negative可以编译为 tailcall 它也可以工作(但很简单,因为在这种情况下根本不需要对齐)。

Jér*_*ard 10

此优化现在在 Clang (trunk) 的开发版本上执行,因此应该在下一个版本(当然是 Clang 17.0)中可用。这可以在Godbolt上看到。这是新生成的代码:

almost_leaf(int*):
        mov     eax, dword ptr [rdi]
        test    eax, eax
        js      .LBB0_1
        ret
.LBB0_1:
        push    rax
        call    x_was_negative()@PLT
        inc     eax
        add     rsp, 8
        ret
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,快速路径是相同的。

请注意,对于两个版本的 Clang,初始未优化的 LLVM-IR 大致相同,但最后的低级优化步骤会导致此代码的结果略有不同。更具体地说,对所执行的 LLVM-IR 优化步骤的深入分析表明,Clang 16 在“Prologue/Epilogue Insertion & Frame Finalization”优化步骤(又名“prologepilog”)期间错过了优化。-O1即使在Clang 的开发版本中也进行了这种优化。这个优化步骤可以在Godbolt上看到。