早期if语句的函数中不需要的弹出指令

Chr*_*ann 7 c++ assembly gcc clang icc

在玩godbolt.org时,我注意到gcc(6.2,7.0快照),clang(3.9)和icc(17)在编译接近于

int a(int* a, int* b) {
  if (b - a < 2) return *a = ~*a;

  // register intensive code here e.g. sorting network
}
Run Code Online (Sandbox Code Playgroud)

将(-O2/-O3)编译成这样的东西:

    push    r15
    mov     rax, rcx
    push    r14
    sub     rax, rdx
    push    r13
    push    r12
    push    rbp
    push    rbx
    sub     rsp, 184
    mov     QWORD PTR [rsp], rdx
    cmp     rax, 7
    jg      .L95
    not     DWORD PTR [rdx]
 .L162:
    add     rsp, 184
    pop     rbx
    pop     rbp
    pop     r12
    pop     r13
    pop     r14
    pop     r15
    ret
Run Code Online (Sandbox Code Playgroud)

这显然在b - a <2的情况下有巨大的开销.如果-Os gcc编译为:

    mov     rax, rcx
    sub     rax, rdx
    cmp     rax, 7
    jg      .L74
    not     DWORD PTR [rdx]
    ret
.L74:
Run Code Online (Sandbox Code Playgroud)

这让我觉得没有代码阻止编译器发出这个更短的代码.

编译器有没有理由这样做?有没有办法让他们编译到较短的版本而不编译大小?


这是关于Godbolt再现这个的例子.它似乎与递归的复杂部分有关

Pet*_*des 2

这是一个已知的编译器限制,请参阅我对该问题的评论。我不知道它为什么存在;也许编译器在尚未完成保存寄存器时很难决定在不溢出的情况下可以做什么。

当提前取出的支票小到足以内联时,将其拉入包装器通常很有用。


看起来现代 gcc 有时实际上可以绕过这个编译器限制。

使用 Godbolt 编译器资源管理器上的示例,添加第二个调用者足以让 gcc6.1 -O2 为您拆分函数,因此它可以将早期输出内联到第二个调用者和外部可见中square()(结束如果jmp square(int*, int*) [clone .part.3]不采用早出返回路径)。

Godbolt 上的代码,请注意我添加了-std=gnu++14,这是 clang 编译代码所必需的。

void square_inlinewrapper(int* a, int* b) {
  //if (b - a < 16) return;  // gcc inlines this part for us, and calls a private clone of the function!

  return square(a, b);
}

# gcc6.1 -O2  (default / generic -march= and -mtune=)
    mov     rax, rsi
    sub     rax, rdi
    cmp     rax, 63
    jg      .L9
    rep ret
.L9:
    jmp     square(int*, int*) [clone .part.3]
Run Code Online (Sandbox Code Playgroud)

square()它本身编译为相同的东西,调用包含大量代码的私有克隆。来自克隆内部的递归调用调用包装函数,因此它们在不需要时不会执行额外的推送/弹出工作。


当没有其他调用者时,即使 gcc7 也不会执行此操作,即使在 -O3 下也是如此。它仍然将其中一个递归调用转换为循环,但另一个递归调用只是再次调用大函数。


Clang 3.9 和 icc17 也不克隆该函数,因此您应该手动编写可内联包装器(如果需要检查,则更改函数的主体以将其用于递归调用)。

您可能想要命名包装器square,并将主体重命名为私有名称(例如static void square_impl)。