x86-64 System V ABI 旨在最大限度地减少由第一批 AMD64 CPU 销售之前的 gcc 版本编译的 SPECint 中的指令数(以及一定程度的代码大小)。看这是一些历史记录和列表存档链接的答案.
从 5 分钟前开始,我认为所有寄存器都是相同的,但由于约定,它们的使用方式有所不同。现在一切对我来说都改变了
x86-64 不是完全正交的。某些指令隐式使用特定寄存器。例如push
隐含地使用rsp
作为堆栈指针,shl edx, cl
仅适用于班次计数cl
(直到BMI2shlx
).
更很少使用:加宽mul rdi
does rdx:rax = rax*rdi
。表示字符串指令隐式使用 RDI、RSI 和 RCX,尽管它们通常不值得使用。
事实证明,选择参数传递寄存器,以便将其参数传递给 memcpy 的函数可以将其内联为rep movs
在 Jan Hubicka 使用的度量中很有用,因此rdi
and rsi
被选为前两个参数。但那个离开rcx
直到第四个参数更好才使用,因为cl
可变计数移位需要。 (大多数函数不会碰巧使用它们的第三个参数作为移位计数。)(可能较旧的 GCC 版本内联memcpy
or memset
as rep movs
更积极地;如今对于小型阵列来说,与 SIMD 相比,它通常不值得。)
x86-64 System V ABI 使用与系统调用几乎相同的函数调用约定。这不是巧合:它意味着 libc 包装函数的实现,例如mmap
can be:
mmap:
mov r10, rcx ; syscall destroys rcx and r11; 4th arg passed in r10 for syscalls
mov eax, __NR_mmap
syscall
cmp rax, -4096
ja .set_errno_and_stuff
ret
这是一个微小的优势,但确实没有理由not去做这个。它还会在内核中保存一些设置 arg 传递寄存器的指令,然后再分派到内核中系统调用的 C 实现。 (看这个答案查看系统调用处理的一些内核端。主要是关于int 0x80
处理程序,但我想我提到了 64 位syscall
处理程序并直接从 asm 分派到函数表。)
The syscall
指令本身摧毁 RCX 和 R11(为了保存用户空间 RIP 和 RFLAGS,而不需要微代码来设置内核堆栈)因此约定不能相同,除非用户空间约定避免了 RCX 和 R11。但 RCX 是一个方便的寄存器,其低半部分可以在没有 REX 前缀的情况下使用,因此这可能比将其保留为像 R11 这样的被调用破坏的纯暂存器更糟糕。此外,用户空间约定使用 R10 作为具有一流嵌套函数(不是 C/C++)的语言的“静态链”指针。
让前 4 个参数能够避免 REX 前缀可能最适合整体代码大小,并且使用 RBX 或 RBP 而不是 RCX 会很奇怪。有几个不需要 REX 前缀 (EBX/EBP) 的调用保留寄存器是很好的。
See i386 和 x86-64 上的 UNIX 和 Linux 系统调用的调用约定是什么用于函数调用和系统调用约定。
i386 系统调用约定是一种笨重且不方便的约定: ebx
是调用保留的,因此几乎每个系统调用包装器都需要保存/恢复ebx
,除了没有参数的调用,例如getpid
。 (为此,您甚至不需要进入内核,只需调用 vDSO:请参阅Linux 系统调用权威指南(在 x86 上)了解有关 vDSO 和大量其他内容的更多信息。)
但是 i386 函数调用约定传递堆栈上的所有参数,因此 glibc 包装函数仍然需要mov
无论如何,每个参数。
另请注意,x86 寄存器的“自然”顺序是 EAX、ECX、EDX、EBX,根据它们在机器代码中的数字代码,以及pusha
/ popa
使用。看为什么前四个 x86 GPR 的命名顺序如此不直观?.