【问题标题】:Why is RAX not used to pass a parameter in System V AMD64 ABI?为什么不使用 RAX 在 System V AMD64 ABI 中传递参数?
【发布时间】:2018-10-09 06:13:27
【问题描述】:

我不明白在 RAX 中不传递参数有什么好处, 由于返回值在 RAX 中,无论如何它都会被被调用者破坏。

谁能解释一下?

【问题讨论】:

  • @Someprogrammerdude:我认为这不是问题。
  • AFAIK,RAX 用于 varargs(如果函数是 varargs,当然)表示传递的参数个数。
  • IIRC,做了一个具体的研究,涉及分析实际代码。
  • FWIW,并不是真正的重复(question 主要是关于 Win64),但仍然在这里回答:stackoverflow.com/a/4438515/95954。它还讨论了 System V 64 位 ABI 的选择。
  • 来自同一个问题:stackoverflow.com/a/35619528/95954

标签: x86-64 calling-convention


【解决方案1】:

x86-64 System V 确实将 AL 用于可变参数函数:调用者传递 XMM 寄存器中的 FP args 的数量。

(这只是一种优化,允许被调用者不将所有向量 regs 转储到数组中;AL 中的数字允许高于 FP args 的数量。实际上,gcc 的可变参数函数的代码生成只需检查它是否为非零并转储 xmm0..7 中的任何一个或全部 8 个。我认为 ABI 保证即使实际上没有任何 FP 参数,始终通过al=8 也是安全的,并且您可以t 通过设置 al=0 来传递堆栈上的 FP 参数)


但是为什么不使用r9b,而使用RAX 作为第6 个参数呢?还是 RAX 用于一些较早的 arg?

因为 RAX 在 x86 中有很多隐含用途,并且在设计调用约定 (http://web.archive.org/web/20140414124645/http://www.x86-64.org/pipermail/discuss/2000-November/001257.html) 时的实验发现,使用 RAX 往往需要在调用方或被调用方中执行额外的指令。例如因为在调用者中计算其他 args 时经常需要 RAX,或者在代码开始使用 RAX 中传递的 args 之前对其他 args 执行某些操作时需要使用 RAX。

RAX 用于rep stos(gcc 曾经更积极地使用它来内联 memset),它用于div 和扩大(单操作数)mul/imul,gcc 用于除法通过编译时常量。 (Why does GCC use multiplication by a strange number in implementing integer division?)。

大多数其他 RAX 特殊用途只是对您也可以对其他寄存器执行的操作进行更短的编码,例如 cdqemovsxd rax, eax(或任何其他寄存器之间)。或 add eax,imm32(无 ModRM)与 add r/m32, imm32(或大多数其他 ALU 指令)。请参阅我的答案之一 Tips for golfing in x86/x64 machine code。最初的 8086 缺少许多更长的非 AX 替代品,但在 8086 和 386 之间,添加了诸如 imul r32,r32movsx/movzx 之类的东西。其他 RAX-only 指令在优化速度时不值得使用(如 xlatblodsd),或者被 P6 / AMD64 扩展淘汰(lahf 作为 FP 的一部分比较被 fucomi 淘汰并使用 SSE /SSE2 ucomisd 用于 FP 数学),或者是像 cmpxchgcpuid 这样的特殊指令,它们太罕见了,不会对调用约定设计产生影响。编译器无论如何都没有使用像 aaa 这样的 BCD 指令,AMD64 将它们删除。


x86-64 System V 调用约定的设计者(主要是 Jan Hubička 用于整数 arg 传递寄存器设计)通常旨在避免具有许多/常见隐式用途的寄存器。 rdx 在 arg 传递顺序中位于 rcx 之前,因为变量移位计数(没有 BMI2)需要 cl。这些可能比 muldiv 更常见,因为 2 操作数 imul reg,reg 允许正常的非扩展乘法而不破坏 RDX:RAX。

选择 rdirsi 作为前 2 个参数显然是出于将 memsetmemcpy 内联为 rep movs 的动机(gcc 在 2000 年就这样做了,尽管它实际上并不是在 gcc 这样做的许多情况下都是不错的选择)。即使rep-string 指令使用 RCX 作为计数器,他们仍然发现它平均保存的指令是通过 RDX 中的第三个参数而不是 RCX,所以调用约定对于 memcpy 来说不太适用987654362@/ret.

Jan Hubička 通过使用当前版本的 x86-64 gcc 编译 SpecInt,评估了 arg 传递寄存器的多种变体。有关更多详细信息和链接,请参阅我在 Why does Windows64 use a different calling convention from all other OSes on x86-64? 上的回答。

他评估的 arg-register 订单之一是 RAX, RDX, RCX, RBX, RSI, RDI,但他发现它不如其他选项好。 (请参阅上面链接的邮件列表消息)。


RISC 调用约定在第一个返回值寄存器中传递第一个参数是相当常见的。 ARM 是这样做的 (r0),我认为 PowerPC 也是如此。其他人(如 MIPS)没有。但是所有这些架构都没有隐式使用大多数整数寄存器,通常只是一个链接寄存器,也许还有堆栈指针。

x86-64 SysV 和 Windows 为 FP args 执行此操作:xmm0 用于传递和返回。

【讨论】:

  • 我发现了这个:web.archive.org/web/20140414124645/http://www.x86-64.org/… 我仍然觉得使用 rax 会增加代码大小有点令人惊讶。但我想有些论点是长期存在的,如果你使用 rax,你必须在使用任何破坏它的指令之前将其溢出。
  • @IlyaLesokhin:没错。在寄存器中传递太多参数是不好的,因为有时被调用者做的第一件事就是将其中一个传递给非内联函数调用,所以其余的都必须溢出。或者将它们复制到保留调用的 regs。如果您要做的第一件事是将其中一个未在 RAX 中传递的参数除以,则情况类似。
猜你喜欢
  • 2012-09-18
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-10
相关资源
最近更新 更多