Jan*_*nis 97 windows x86-64 calling-convention
AMD has an ABI specification that describes the calling convention to use on x86-64. All OSes follow it, except for Windows which has it's own x86-64 calling convention. Why?
Does anyone know the technical, historical, or political reasons for this difference, or is it purely a matter of NIHsyndrome?
I understand that different OSes may have different needs for higher level things, but that doesn't explain why for example the register parameter passing order on Windows is rcx - rdx - r8 - r9 - rest on stack while everyone else uses rdi - rsi - rdx - rcx - r8 - r9 - rest on stack.
PS我知道这些调用约定一般如何不同,如果需要,我知道在哪里可以找到详细信息.我想知道的是为什么.
编辑:有关如何,请参阅维基百科条目和那里的链接.
Fra*_*kH. 77
关于x86要记住的一件事是注册名称为"reg number"编码并不明显; 在指令编码方面(MOD R/M字节,见http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm),寄存器号0 ... 7按顺序 - ?AX,?CX,?DX,?BX,?SP,?BP,?SI,?DI.
因此,选择A/C/D(regs 0..2)作为返回值,前两个参数(即"经典"32位__fastcall约定)是一个逻辑选择.至于要64位来讲,"更高"的REG是有序的,而微软和UN*X/Linux的去了R8/ R9为第一人.
牢记这一点,微软的选择RAX(返回值)和RCX,RDX,R8,R9(ARG [0..3]),如果你选择是可以理解的选择4级为参数的寄存器.
我不知道为什么AMD64 UN*X ABI RDX之前选择了RCX.
在RISC体系结构上,UN*X传统上在寄存器中进行了参数传递 - 特别是对于前六个参数(至少在PPC,SPARC,MIPS上).这可能是AMD64(UN*X)ABI设计人员选择在该架构上使用六个寄存器的主要原因之一.
所以,如果你想6个寄存器来传递参数,这是合乎逻辑的选择RCX,RDX,R8并R9为他们四个,你应该选择哪两个其他?
"更高"的regs需要一个额外的指令前缀字节来选择它们,因此具有更大的指令大小,所以如果你有选项,你不会想要选择其中任何一个.在经典寄存器中,由于隐含的含义RBP而且RSP这些寄存器不可用,并且RBX传统上在UN*X(全局偏移表)上有特殊用途,似乎AMD64 ABI设计者不希望不必要地变得不兼容.
Ergo,唯一的选择是RSI/ RDI.
所以如果你必须把RSI/ RDI作为参数寄存器,它们应该是哪个参数?
制作它们arg[0]并arg[1]具有一些优势.见cHao的评论.
?SI并且?DI是字符串指令源/目标操作数,正如cHao所提到的,它们用作参数寄存器意味着使用AMD64 UN*X调用约定,strcpy()例如,最简单的函数只包含两个CPU指令,repz movsb; ret因为源/目标地址已被调用者放入正确的寄存器中.特别是在低级和编译器生成的"粘合"代码中(例如,想想一些C++堆分配器在构造上零填充对象,或者内核零填充堆页面sbrk(),或写入时复制页面故障)大量的块复制/填充,因此它对于经常用于保存两个或三个CPU指令的代码非常有用,否则这些指令会将这些源/目标地址参数加载到"正确"的登记册.
因此,在某种程度上,联合国*X和Win64中是只有在UN*X"预规划"两个额外的参数不同,在特意挑选RSI/ RDI寄存器,为四个参数中的自然选择RCX,RDX,R8和R9.
UN*X和Windows x64 ABI之间存在更多差异,而不仅仅是将参数映射到特定寄存器.有关Win64的概述,请检查:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64和AMD64 UN*X在使用堆栈空间的方式上也有惊人的不同; 例如,在Win64上,调用者必须为函数参数分配堆栈空间,即使args 0 ... 3在寄存器中传递.另一方面,在UN*X上,如果一个叶子函数需要不超过128个字节,那么它甚至根本不需要分配堆栈空间(是的,你拥有并且可以使用它)一定数量的堆栈而不分配它...好吧,除非你是内核代码,一个漂亮的bug的来源).所有这些都是特别的优化选择,大多数理由都在原始海报的维基百科参考指向的完整ABI参考文献中进行了解释.
Pet*_*des 33
IDK为什么Windows做了他们做的事情.请参阅此答案的结尾以进行猜测.我很好奇SysV调用约定是如何决定的,所以我挖掘了邮件列表存档并发现了一些简洁的东西.
阅读AMD64邮件列表中的一些旧线程很有意思,因为AMD架构师积极参与其中.例如,选择寄存器名称是其中一个难点:AMD考虑重命名原来的8个寄存器r0-r7,或者调用新的寄存器UAX.
此外,来自内核开发人员的反馈也确定了原始设计syscall和swapgs无法使用的内容.这就是AMD 在发布任何实际芯片之前更新指令以解决这个问题的方法.同样有趣的是,在2000年末,英特尔可能不会采用AMD64.
SysV(Linux)调用约定,以及关于应该保留被调用者数量的寄存器与调用者保存的决定,最初是在2000年11月由Jan Hubicka(gcc开发人员)制作的.他编译了SPEC2000并查看了代码大小和指令数量.这个讨论主题围绕着一些与SO问题的答案和评论相同的想法.在第二个线程中,他提出当前序列是最优的并且希望是最终的,产生比一些替代品更小的代码.
他使用术语"全局"来表示调用保留寄存器,如果使用则必须按下/弹出.
的选择rdi,rsi,rdx作为第三个参数的动机是:
memset在其args上调用或其他C字符串函数的函数中的次要代码大小保存(其中gcc内联一个rep字符串操作?)rbx是保留呼叫保留,因为有两个呼叫保留的regs可以访问没有REX前缀(rbx和rbp)是一个胜利.大概选择因为它是唯一没有被任何指令隐含使用的其他reg.(rep字符串,移位计数和mul/div输出/输入触摸其他所有内容).我们试图在序列的早期避免RCX,因为它通常用于特殊目的的寄存器,如EAX,因此它在序列中有相同的目的.它也不能用于系统调用,我们希望使syscall序列尽可能地匹配函数调用序列.
(后台:syscall/ sysret不可避免地破坏rcx(带rip)和r11(带RFLAGS),所以内核无法看到运行rcx时最初的内容syscall.)
选择内核系统调用ABI来匹配函数调用ABI,除了r10代替之外rcx,所以libc包装函数就像mmap(2)只能mov %rcx, %r10/ mov $0x9, %eax/ syscall.
请注意,与Window的32位__vectorcall相比,i386 Linux使用的SysV调用约定很糟糕. 它传递堆栈上的所有内容,只返回edx:eaxint64,而不是小结构.毫不奇怪,为保持与它的兼容性做了很多努力.当没有理由不这样做时,他们做了保持rbx呼叫保留的事情,因为他们决定在原始的8中使用另一个(不需要REX前缀)是好的.
使得ABI最佳是多少更重要的长期比任何其他考虑.我认为他们做得很好.我不完全确定返回打包到寄存器中的结构,而不是不同的regs中的不同字段.我想在没有实际操作字段的情况下按值传递它们的代码会以这种方式获胜,但是解压缩的额外工作看起来很愚蠢.他们可能有更多的整数返回寄存器,不仅仅是rdx:rax,所以返回一个包含4个成员的结构可以在rdi,rsi,rdx,rax或其他东西中返回它们.
他们考虑在向量寄存器中传递整数,因为SSE2可以对整数进行操作.幸运的是他们没有这样做. 整数经常用作指针偏移,而堆栈内存的往返非常便宜.SSE2指令也比整数指令占用更多的代码字节.
我怀疑Windows ABI设计人员可能一直致力于最大限度地减少32位和64位之间的差异,这对于那些必须将asm从一个端口移植到另一个端口的人来说是有益的,或者可以#ifdef在某些ASM中使用一些s,因此相同的源可以更容易地构建32或64位版本的功能.
最小化工具链的变化似乎不太可能.x86-64编译器需要一个单独的表,其中哪个寄存器用于什么,以及调用约定是什么.与32位的小重叠不太可能显着节省工具链代码大小/复杂性.
cHa*_*Hao 12
Win32有自己的ESI和EDI用途,并且要求它们不被修改(或者至少在调用API之前它们被恢复).我想象64位代码对RSI和RDI的作用是一样的,这可以解释为什么它们不用于传递函数参数.
我不知道为什么RCX和RDX会被切换.
Mic*_*urr 12
请记住,微软最初"对AMD64早期的努力正式不承认"(来自Matthew Kerner和Neil Padgett的"现代64位计算历史"),因为他们是英特尔在IA64架构上的强大合作伙伴.我认为这意味着即使他们原本愿意与ABCC的GCC工程师合作在Unix和Windows上使用它们,他们也不会这样做,因为它意味着当他们没有公开支持AMD64的努力时尚未正式完成(并且可能会让英特尔感到不安).
最重要的是,在那些日子里,微软绝对没有倾向于与开源项目保持友好关系.当然不是Linux或GCC.
那他们为什么要在ABI上合作呢?我猜这些ABI之所以不同,仅仅是因为它们的设计或多或少是在同一时间并且是孤立的.
另一句话来自"现代64位计算史":
在与微软合作的同时,AMD还与开源社区合作筹备该芯片.AMD与Code Sorcery和SuSE签订了工具链合同(红帽已经被英特尔公司用于IA64工具链端口).Russell解释说SuSE生成了C和FORTRAN编译器,Code Sorcery生成了一个Pascal编译器.Weber解释说,该公司还与Linux社区合作准备一个Linux端口.这项工作非常重要:它激励微软继续投资AMD64 Windows的工作,同时也确保了当时正在成为重要操作系统的Linux在芯片发布后即可使用.
韦伯甚至说Linux工作对AMD64的成功至关重要,因为它使得AMD能够在没有任何其他公司帮助的情况下生产端到端系统.这种可能性确保AMD即使其他合作伙伴退出也有最坏情况的生存策略,这反过来又让其他合作伙伴保持参与,因为他们害怕被抛在身后.
这表明即使AMD也不认为合作必然是MS和Unix之间最重要的事情,但是拥有Unix/Linux支持非常重要.甚至试图说服一方或双方妥协或合作也不值得努力或冒险(?)激怒他们中的任何一方?也许AMD认为即使建议一个共同的ABI可能会延迟或破坏更简单的目标,即在芯片准备就绪时准备好软件支持.
我个人的猜测,但我认为ABI不同的主要原因是MS和Unix/Linux方面没有合作的政治原因,AMD没有把这看作是一个问题.