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

因为我最关心效率,所以我喜欢使用尽可能少的不同寄存器/内存地址,以及尝试不创建任何堆栈帧或保留寄存器(每个周期计数)。

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

最佳答案

x86-64 System V ABI没有将其调用约定称为“ cdecl”。只是x86-64 SysV调用约定。字符串“ cdecl”没有出现在the ABI doc中。

r11是一个临时的,又称调用优先级的寄存器。

r10也是呼叫密集的寄存器。 ABI说“用于传递函数的静态链指针”,但是C不使用它,并且gcc和clang生成的代码可以自由使用r10而不保存/恢复它。 ABI的寄存器使用情况表列出了r10在函数调用中未保留的内容,因此叶函数可以始终对其进行破坏。 (Which registers to use as temporaries when writing AMD64 SysV assembly?

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

非叶子函数不需要将传入的r10传递给其子对象,除非它们是支持这种事情的语言(不是C或C ++)的“嵌套函数”。因此,r10在正常情况下也是纯临时性的。



r10r11不是传递参数的寄存器,这与其他调用调用的寄存器不同,因此“包装器”函数可以使用它们(尤其是r11)而无需保存/恢复任何内容。

在正常功能中,RBX,RBP和RSP与R12..R15一起被保留。无需保存/恢复即可破坏所有其他文件。 (其中包括xmm / ymm0..15和zmm0..31,以及x87堆栈,以及RFLAGS中的条件代码)。



请注意,即使具有32位操作数大小(如r8..15),xor r10d, r10d也需要REX前缀。如果您有一些64位非指针整数,那么请确保将它们保留在r8..r11中,因为无论如何无论何时使用这些值,您始终需要REX前缀作为64位操作数大小。

较小的代码大小通常不会更糟,并且有时有助于解码和uop缓存密度以及L1i缓存密度。 RAX,RCX,RDX,RSI,RDI应该是暂存法规的首选。 (并且使用32位操作数大小,除非需要64位。例如,xor eax,eax是将RAX归零的正确方法。Silvermont无法将xor r10,r10识别为归零习惯,因此即使使用xor r10d,r10d也不能使用r10保存代码大小。)

如果确实用完了低寄存器,则对于通常将始终与64位操作数大小(或VEX前缀)一起使用的事物,最好使用r11 / mov eax, [r10]。例如指向64位数据的指针或指向指针的指针。 mov eax, [rdi]需要REX前缀,而mov rax, [rdi]则不需要。但是mov r8, [r10]cmp eax, r10d的大小相同。

很难获得很多收益,因为您经常需要以不同的组合一起使用不同的值,例如最终使用或其他方法,但是如果您想全力以赴进行优化,请考虑使用代码大小。也许还要考虑指令边界在哪里以及它将如何适合uop缓存。

有关编写高效代码的提示,请参见x86 tag wiki,尤其是http://agner.org/optimize/

07-24 09:45