X86调用约定

历史背景

在微型计算机出现之前，计算机厂商几乎都会提供一份操作系统和为不同编程语言编写的编译器。平台所使用的调用约定都是由厂商的软件实现定义的。
在Apple Ⅱ出现之前的早期微机几乎都是“裸机”，少有一份OS或编译器的，即是IBM PC也是如此。IBM PC兼容机的唯一的硬件标准是由Intel处理器(8086, 80386)定义的，并由IBM分发出去。硬件扩展和所有的软件标准(BIOS调用约定)都开放有市场竞争。
一群独立的软件公司提供了操作系统，不同语言的编译器以及一些应用软件。基于不同的需求，历史实践和开发人员的创造力，这些公司都使用了各自不同的调用约定，往往差异很大。
在IBM兼容机市场洗牌后，微软操作系统和编程工具(有不同的调用约定)占据了统治地位，此时位于第二层次的公司如Borland和Novell，以及开源项目如GCC，都还各自维护自己的标准。互操作性的规定最终被硬件供应商和软件产品所采纳，简化了选择可行标准的问题。

调用者清理

在这些约定中，调用者自己清理堆栈上的参数(arguments)，这样就允许了可变参数列表的实现，如printf()。

syscall

与cdecl类似，参数被从右到左推入堆栈中。EAX, ECX和EDX不会保留值。参数列表的大小被放置在AL寄存器中(?)。
syscall是32位OS/2 API的标准。

optlink

参数也是从右到左被推入堆栈。从最左边开始的三个字符变元会被放置在EAX, EDX和ECX中，最多四个浮点变元会被传入ST(0)到ST(3)中----虽然这四个参数的空间也会在参数列表的栈上保留。函数的返回值在EAX或ST(0)中。保留的寄存器有EBP, EBX, ESI和EDI。
optlink在IBM VisualAge编译器中被使用。

被调用者清理

如果被调用者要清理栈上的参数，需要在编译阶段知道栈上有多少字节要处理。因此，此类的调用约定并不能兼容于可变参数列表，如printf()。然而，这种调用约定也许会更有效率，因为需要解堆栈的代码不要在每次调用时都生成一遍。
使用此规则的函数容易在asm代码被认出，因为它们会在返回前解堆栈。x86 ret指令允许一个可选的16位参数说明栈字节数，用来在返回给调用者之前解堆栈。代码类似如下：
 ret 12

pascal

基于Pascal语言的调用约定，参数从左至右入栈(与cdecl相反)。被调用者负责在返回前清理堆栈。
此调用约定常见在如下16-bit 平台的编译器：OS/2 1.x，微软Windows 3.x，以及Borland Delphi版本1.x。

register

Borland fastcall的别名。

stdcall

stdcall是由微软创建的调用约定，是Windows API的标准调用约定。非微软的编译器并不总是支持该调用协议。GCC编译器如下使用：
int __attribute__((__stdcall__ )) func()
stdcall是Pascal调用约定与cdecl调用约定的折衷：被调用者负责清理线程栈，参数从右往左入栈。其他各方面基本与cdecl相同。但是编译后的函数名后缀以符号"@"，后跟传递的函数参数所占的栈空间的字节长度。寄存器EAX, ECX和EDX被指定在函数中使用，返回值放置在EAX中。stdcall对于微软Win32 API和Open Watcom C++是标准。
微软的编译工具规定：PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall均是指此种调用约定。

fastcall

此约定还未被标准化，不同编译器的实现也不一致。

Microsoft/GCC fastcall

Microsoft或GCC的__fastcall约定(也即__msfastcall)把第一个(从左至右)不超过32比特的参数通过寄存器ECX/CX/CL传递，第二个不超过32比特的参数通过寄存器EDX/DX/DL，其他参数按照自右到左顺序压栈传递。

Borland fastcall

从左至右，传入三个参数至EAX, EDX和ECX中。剩下的参数推入栈，也是从左至右。
在32位编译器Embarcadero Delphi中，这是缺省调用约定，在编译器中以register形式为人知。
在i386上的某些版本Linux也使用了此约定。

thiscall

在调用C++非静态成员函数时使用此约定。基于所使用的编译器和函数是否使用可变参数，有两个主流版本的thiscall。
对于GCC编译器，thiscall几乎与cdecl等同：调用者清理堆栈，参数从右到左传递。差别在于this指针，thiscall会在最后把this指针推入栈中，即相当于在函数原型中是隐式的左数第一个参数。
在微软Visual C++编译器中，this指针通过ECX寄存器传递，其余同cdecl约定。当函数使用可变参数，此时调用者负责清理堆栈(参考cdecl)。thiscall约定只在微软Visual C++ 2005及其之后的版本被显式指定。其他编译器中，thiscall并不是一个关键字(反汇编器如IDA使用__thiscall)。

WINAPI

WINAPI是平台的缺省调用约定。Windows操作系统上默认是StdCall；Windows CE上默认是Cdecl。

Intel ABI

根据Intel ABI，EAX、EDX及ECX可以自由在过程或函数中使用，不需要保留。

x86-64调用约定

x86-64调用约定得益于更多的寄存器可以用来传参。而且，不兼容的调用约定也更少了，不过还是有2种主流的规则。

微软x86-64调用约定

在Windows x64环境下编译代码时，只有一种调用约定，也就是说32位下的各种约定在64位下统一成一种了。
微软x64调用约定使用RCX, RDX, R8, R9四个寄存器用于存储函数调用时的4个参数(从左到右)，使用XMM0, XMM1, XMM2, XMM3来传递浮点变量。其他的参数直接入栈(从右至左)。整型返回值放置在RAX中，浮点返回值在XMM0中。少于64位的参数并没有做零扩展，此时高位充斥着垃圾。
在微软x64调用约定中，调用者的一个职责是在调用函数之前(无论实际的传参使用多大空间)，在栈上的函数返回地址之上（靠近栈顶）分配一个32字节的“影子空间”；并且在调用结束后从栈上弹掉此空间。影子空间是用来给RCX, RDX, R8和R9提供保存值的空间，即使是对于少于四个参数的函数也要分配这32个字节。
例如， 一个函数拥有5个整型参数，第一个到第四个放在寄存器中，第五个就被推到影子空间之外的栈顶。当函数被调用，此栈用来组成返回值----影子空间32位+第五个参数。
在x86-64体系下，Visual Studio 2008在XMM6和XMM7中(同样的有XMM8到XMM15)存储浮点数。结果对于用户写的汇编语言例程，必须保存XMM6和XMM7(x86不用保存这两个寄存器)，这也就是说，在x86和x86-64之间移植汇编例程时，需要注意在函数调用之前/之后，要保存/恢复XMM6和XMM7。