为什么在System V AMD64 ABI中不使用RAX传递参数?

6

我不明白在RAX中不传递参数的好处在哪里。因为返回值位于RAX中,无论如何都会被调用者覆盖。

有人能解释一下吗?


@Someprogrammerdude:我认为那不是问题的关键。 - Rudy Velthuis
据我所知,RAX用于varargs(如果函数是varargs的话)来指示传递的参数数量。 - Rudy Velthuis
据我所知,进行了一项特定的研究,其中涉及对实际代码进行分析。 - Margaret Bloom
值得一提的是,这不算是重复的问题(主要关于Win64),但仍然在此处得到了回答:https://dev59.com/6G855IYBdhLWcg3wSiM7#4438515。它还讨论了System V 64位ABI的选择。 - Rudy Velthuis
1
从同一个问题:https://dev59.com/6G855IYBdhLWcg3wSiM7#35619528 - Rudy Velthuis
Borland在他们的32位约定中使用了eax。https://en.wikipedia.org/wiki/X86_calling_conventions#Borland_register - Alex Guteniev
1个回答

6
x86-64 System V 确实 在可变参数函数中使用AL寄存器:调用者在XMM寄存器中传递FP参数的数量。(这只是一种优化,允许被调用方不将所有向量寄存器转储到数组中;AL中的数字可以比FP参数的数量更高。实际上,gcc针对可变参数函数的代码生成只检查它是否为非零,并转储xmm0..7中的所有8个或不转储。我认为ABI保证了即使没有实际的FP参数,始终传递al=8是安全的,并且不能通过设置al=0来在堆栈上传递FP参数。)

为什么不使用r9b,并将RAX用于第6个参数?或者在之前的某个参数中使用RAX?

因为在x86中,RAX有太多的隐式用途,而在设计调用约定时进行的实验(http://web.archive.org/web/20140414124645/http://www.x86-64.org/pipermail/discuss/2000-November/001257.html)发现使用RAX往往需要在调用者或被调用者中额外添加指令。例如,因为RAX通常是计算调用者中其他参数的一部分所需,或者在执行与其中一个参数相关的操作时需要RAX,然后代码继续使用以RAX传递的参数。

RAX用于rep stos(gcc曾经更积极地使用它来内联memset),以及用于div和扩展(单操作数)mul/imul,gcc用于编译时常量除法。(为什么GCC在实现整数除法时使用奇怪的数字进行乘法运算?)。
大多数其他 RAX 特殊用途只是其他寄存器也可以做的事情的较短编码,比如 cdqe vs. movsxd rax, eax(或任何其他寄存器之间)。或者 add eax, imm32(没有 ModRM)vs. add r/m32, imm32(或大多数其他 ALU 指令)。请参见我在Tips for golfing in x86/x64 machine code上的一个答案。原始 8086 缺少许多更长的非 AX 替代品,但在 8086 和 386 之间,添加了一些诸如 imul r32, r32movsx/movzx 的东西。其他仅限于 RAX 的指令在优化速度时不值得使用(例如 xlatblodsd),或者已经被 P6 / AMD64 扩展所淘汰(将 lahf 作为 FP 比较的一部分淘汰,并使用 SSE/SSE2 的 ucomisd 进行 FP 数学),或者是专门的指令,如 cmpxchgcpuid,对调用约定设计没有影响。编译器根本不使用像 aaa 这样的 BCD 指令,而 AMD64 已将其删除。
设计x86-64 System V调用约定的人(主要是Jan Hubička,负责整数参数传递寄存器设计)通常旨在避免具有许多/常见隐含用途的寄存器。在参数传递顺序中,rdxrcx 之前,因为在没有BMI2的情况下需要 cl 进行可变移位计数。这些可能比 muldiv 更常见,因为2操作数imul reg,reg允许正常非扩展乘法而不破坏 RDX:RAX。
作为前两个参数的选择rdirsi显然是出于内联memsetmemcpy作为rep movs的动机(即使在gcc执行这些操作时,在许多情况下这并不是一个好选择)。尽管rep-字符串指令使用RCX作为计数器,但他们仍然发现将第三个参数传递到RDX而不是RCX平均可以节省指令,因此调用约定不能完全满足memcpy成为rep stosb/ret的要求。
Jan Hubička通过使用当时最新版本的x86-64 gcc编译SpecInt来评估了多种参数传递寄存器的变化。有关详细信息和链接,请参见我的答案为什么Windows64使用与x86-64上的所有其他操作系统不同的调用约定?
他评估的一种arg-register顺序是RAX,RDX,RCX,RBX,RSI,RDI,但他发现这不如其他选项好。(请参见上面链接的邮件列表消息)。
RISC调用约定通常将第一个参数传递到第一个返回值寄存器中,ARM(r0)和PowerPC也是如此。其他架构(例如MIPS)则不是这样。但是所有这些体系结构都没有隐式使用大多数整数寄存器,通常只有链接寄存器和堆栈指针。
x86-64 SysV和Windows在FP参数方面也是这样做的:xmm0用于传递和返回。

我找到了这个链接:http://web.archive.org/web/20140414124645/http://www.x86-64.org/pipermail/discuss/2000-November/001257.html 我仍然觉得使用rax会增加代码大小有点令人惊讶,但我猜有些参数是长期存在的,如果你使用rax,你必须在使用任何覆盖它的指令之前强制将其溢出。 - Ilya Lesokhin
2
@IlyaLesokhin:没错。在寄存器中传递太多参数是不好的,因为有时被调用者要做的第一件事就是将其中一个参数传递给非内联函数调用,这样所有其他参数都必须被溢出。或者将它们复制到调用保留寄存器中。如果你想做的第一件事是除以没有在RAX中传递的参数之一,情况也是类似的。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接