常规使用 r10 和 r11 的可接受性

Acceptability of regular usage of r10 and r11

我最近一直在做很多 x64 汇编编程(Linux) 用于与我的 C/C++ 程序集成。

因为我最关心的是效率,所以我喜欢使用尽可能少的不同 regs/memory 地址,并且尽量不创建任何堆栈帧或保留寄存器(每个周期都很重要)。

根据 cdecl,r10 和 r11 寄存器未保留,我希望在我的函数中将它们用作临时变量,最好不保留。 它是否会导致任何编译器出现任何不可比性问题/错误(到目前为止还没有遇到任何问题,但这是一个问题)?

您可以像使用 rcx 和 rdx 一样自由使用 r10 和 r11。

x86-64 System V ABI 不调用其调用约定 "cdecl"。它只是 x86-64 SysV 调用约定。字符串 "cdecl" 没有出现在 the ABI doc.

r11 是一个临时的,又名调用破坏的寄存器。

r10 也是一个被调用破坏的寄存器。 ABI 说 "used for passing a function’s static chain pointer",但 C 不使用它,gcc 和 clang 生成的代码确实可以自由使用 r10 而没有 saving/restoring。 ABI 的 table 寄存器使用列表 r10 未在函数调用中保留 因此叶函数总是会破坏它。 ()

gcc 确实使用 r10 作为其 trampoline for function pointers to GNU C nested functions 的一部分,作为指向外部作用域堆栈帧的指针。堆栈上机器代码的蹦床是一种 hack,但这确实是一个静态链指针;适当支持嵌套函数的语言可能会让调用者知道它(如 lambda / 闭包)并在使用指向嵌套函数的指针时在 r10 中传递一个值。

非叶函数不需要将它们的传入 r10 传递给它们的子函数,除非它们 "nested functions" 使用支持此类事物的语言(不是 C 或 C++)。 因此r10在正常情况下也是一个纯粹的临时


r10r11 不是参数传递寄存器,不像其他调用破坏的寄存器,所以 "wrapper" 函数可以使用它们(尤其是 r11)而不用saving/restoring任何东西。

在正常函数中,RBX、RBP 和 RSP 以及 R12..R15 是调用保留的。所有其他人都可以在没有 saving/restoring. 的情况下被破坏(包括 xmm/ymm0..15 和 zmm0..31,以及 x87 堆栈和 RFLAGS 中的条件代码)。


请注意 r8..15 需要一个 REX 前缀,即使是 32 位操作数大小(如 xor r10d, r10d)。如果你有一些 64 位非指针整数,那么一定要将它们保存在 r8..r11 中,因为无论何时你使用这些值,你总是需要 64 位操作数大小的 REX 前缀。

较小的代码大小通常不会更差,有时有助于提高解码和 uop 缓存密度,以及 L1i 缓存密度。 RAX、RCX、RDX、RSI、RDI 应该是您的首选 scratch regs。 (并使用 32 位操作数大小,除非你需要 64 位。例如 xor eax,eax 是将 RAX 归零的正确方法。Silvermont 不将 xor r10,r10 识别为归零惯用语,因此使用 xor r10d,r10d 即使它不节省代码大小。)

如果你用低寄存器做 运行,理想情况下使用 r10 / r11 来处理通常与 64 位操作数大小(或 VEX 前缀)一起使用的东西反正。例如指向 64 位数据的指针或指向指针的指针。 mov eax, [r10] 需要 REX 前缀而 mov eax, [rdi] 不需要。但是 mov rax, [rdi]mov r8, [r10] 大小相同。

很难获得太多,因为你经常需要在不同的组合中一起使用不同的值,比如最终使用 cmp eax, r10d 或其他什么,但如果你想全力以赴优化,然后考虑代码-尺寸。也许还要考虑指令边界在哪里以及它如何适合 uop 缓存。

有关编写高效代码的提示,请参阅 x86 tag wiki, and especially http://agner.org/optimize/