【问题标题】:why callees don't use caller saved registers first?为什么被调用者不首先使用调用者保存的寄存器?
【发布时间】:2020-11-19 17:10:11
【问题描述】:

我们知道,按照 x86-64 约定,寄存器%rbx%rbp%r12%r15 被归类为被调用者保存的寄存器。而%r10%r111 是调用者保存的寄存器。 但是当我在大多数情况下编译 C 代码时,例如函数P 调用Q,我看到函数Q 的以下汇编代码:

Q:
   push %rbx
   movq %rdx, %rbx
   ...
   popq %rbx
   ret

我们知道,由于%rbx是一个被调用者保存的寄存器,我们必须将它存储在堆栈中,并在稍后为调用者P恢复它。

但是通过使用调用方保存的寄存器%r10 来保存堆栈操作会不会更简洁:

Q:
   movq %rdx, %r10
   ...
   ret

所以被调用者不需要为调用者保存和恢复寄存器,因为调用者在调用被调用者之前已经将它压入堆栈?

【问题讨论】:

  • 不是架构决定了谁和哪些寄存器被保存。这是调用约定。
  • 请提供一个完整的例子,包括高级语言和编译输出,因为生成的代码与整个函数有关。

标签: c assembly x86-64 compiler-optimization calling-convention


【解决方案1】:

您似乎对“呼叫者保存”的含义感到困惑。我认为这种错误的术语选择让您误以为编译器实际上将它们保存在函数调用周围的调用者中。这通常会更慢 (Why do compilers insist on using a callee-saved register here?),尤其是在进行多次调用或循环调用的函数中。

更好的术语是call-clobbered vs. call-preserved,它反映了编译器实际使用它们的方式,以及人类应该如何看待它们:寄存器在函数调用中死亡,或者没有。编译器不会t 在每个 call 周围推送/弹出一个呼叫破坏(又名呼叫者保存)寄存器。

但如果您要围绕单个函数调用推送/弹出一个值,您只需使用%rdx 即可。将其复制到 R10 只会浪费指令。所以mov %r10 没用。稍后推送它只是效率低下,没有它是不正确的。


复制到调用保留寄存器的原因是函数 arg 将在函数稍后进行的函数调用中继续存在。显然,您必须为此使用保留调用的寄存器;被调用破坏的寄存器无法在函数调用中存活。

当不需要调用保留寄存器时,是的,编译器会选择调用破坏寄存器。

如果您将示例扩展为实际的 MCVE,而不是仅显示没有源代码的 asm,这应该会更清楚。如果您编写需要 mov 来评估表达式的叶函数,或者在第一次函数调用后不需要任何参数的非叶函数,您将不会看到它浪费指令保存和使用呼叫保留注册。例如

int foo(int a) {
    return (a>>2) + (a>>3) + (a>>4);
}

https://godbolt.org/z/ceM4dP 与 GCC 和 clang -O3:

# gcc10.2
foo(int):
        mov     eax, edi
        mov     edx, edi      # using EDX, a call-clobbered register
        sar     edi, 4
        sar     eax, 2
        sar     edx, 3
        add     eax, edx
        add     eax, edi
        ret

使用 LEA 无法进行右移以进行复制和操作,并且以 3 种不同方式移动相同的输入使 GCC 使用 mov 来复制输入。 (而不是做一连串的右移:编译器喜欢以牺牲更多指令为代价来最小化延迟,因为这通常最适合广泛的 OoO 执行。)

【讨论】:

  • 那么为什么编译器在我的情况下不选择%r10
  • @amjad:因为您的 ... 包含一个函数调用,而重点是保留该函数调用中的值。
  • @amjad:就像我说的,如果您想具体解释,请发布实际的minimal reproducible example。并考虑如何手动实现特定的 C 函数/编译器如何选择这样做。
  • @R..GitHubSTOPHELPINGICE 甚至我的... 包含一个函数调用,那么我们仍然可以使用%r10 并在调用函数之前将其压入堆栈,这将与使用@ 相同987654336@,不是吗?
  • 我同意彼得的观点,“呼叫者保存”是一个非常糟糕的术语。正确的术语是 call-saved(又名 callee-saved)和 call-clobbered,这些名称只是描述了围绕调用的 ABI 合约,而不是调用者或被调用者必须执行的操作.
猜你喜欢
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
  • 2019-08-18
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
  • 2021-02-15
相关资源
最近更新 更多