x86 and x86-64 calling conventions compared
System V and Microsoft, 64-bit and 32-bit, side by side. The argument tables are read from the code that the compilers generate, and every row leads to the page that explains it.
Four ABIs cover nearly all x86 code: System V x86-64
for Linux, macOS and the BSDs, Microsoft x64 for
Windows and UEFI, and their 32-bit counterparts,
System V i386 and the
32-bit Windows conventions, of which the tables
show __cdecl, the default. The overview says which platform
uses which. The columns of every table link to the page of their ABI.
Arguments
Tables 1 and 2 are not typed in. Each function of a small example returns its Nth argument, and the build reads where the argument comes from out of the function’s first instruction, for each ABI; a stack slot is given relative to the stack pointer on entry, when it points at the return address.
int arguments arrive, measured from GCC and Clang output.| Argument | System V x86-64 | Microsoft x64 | System V i386 | Windows x86 |
|---|---|---|---|---|
| 1 | RDI | RCX | ESP+4 | ESP+4 |
| 2 | RSI | RDX | ESP+8 | ESP+8 |
| 3 | RDX | R8 | ESP+12 | ESP+12 |
| 4 | RCX | R9 | ESP+16 | ESP+16 |
| 5 | R8 | RSP+40 | ESP+20 | ESP+20 |
| 6 | R9 | RSP+48 | ESP+24 | ESP+24 |
| 7 | RSP+8 | RSP+56 | ESP+28 | ESP+28 |
| 8 | RSP+16 | RSP+64 | ESP+32 | ESP+32 |
double arguments arrive, measured the same way.| Argument | System V x86-64 | Microsoft x64 | System V i386 | Windows x86 |
|---|---|---|---|---|
| 1 | XMM0 | XMM0 | ESP+4 | ESP+4 |
| 2 | XMM1 | XMM1 | ESP+12 | ESP+12 |
| 3 | XMM2 | XMM2 | ESP+20 | ESP+20 |
| 4 | XMM3 | XMM3 | ESP+28 | ESP+28 |
| 5 | XMM4 | RSP+40 | ESP+36 | ESP+36 |
| 6 | XMM5 | RSP+48 | ESP+44 | ESP+44 |
| 7 | XMM6 | RSP+56 | ESP+52 | ESP+52 |
| 8 | XMM7 | RSP+64 | ESP+60 | ESP+60 |
| 9 | RSP+8 | RSP+72 | ESP+68 | ESP+68 |
| 10 | RSP+16 | RSP+80 | ESP+76 | ESP+76 |
The two 64-bit ABIs differ in more than the registers. System V keeps
two independent sequences: integers take the next of six
general-purpose registers and floating-point values the next of eight
vector registers, whatever their order. Microsoft gives each of the
first four positions one slot: the second argument is in RDX if it is
an integer and in XMM1 if it is a double. Microsoft’s stack arguments
also start 32 bytes further up, past the shadow space that the caller
reserves. On 32-bit x86, every argument is on the stack, and a
double takes two slots.
double after an int and once an int after a double. In System V it arrives in the first register of its kind, XMM0 or EDI; in Microsoft x64, in the second slot, XMM1 or EDX.mixed-slots.c
/* Mixed arguments: System V fills integer and vector registers from
separate sequences, Microsoft x64 gives each position one slot. */
double second_double(int a, double b)
{
return b;
}
int second_int(double a, int b)
{
return b;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -S mixed-slots.c
second_double:
ret
second_int:
movl %edi, %eax
retIntel syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S mixed-slots.c
second_double:
ret
second_int:
mov eax, edi
retClang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S mixed-slots.c
second_double:
movaps %xmm1, %xmm0
retq
second_int:
movl %edx, %eax
retqIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S mixed-slots.c
second_double:
movaps xmm0, xmm1
ret
second_int:
mov eax, edx
retReturn values
| Value | System V x86-64 | Microsoft x64 | System V i386 | Windows x86 |
|---|---|---|---|---|
| Integers and pointers | RAX | RAX | EAX | EAX |
| A 64-bit integer | RAX | RAX | EDX:EAX | EDX:EAX |
float, double | XMM0 | XMM0 | ST0 | ST0 |
long double | ST0 | XMM0, as a double (MSVC); memory (MinGW-w64) | ST0 | ST0 |
| Structs of up to 16 bytes | RAX, RDX, XMM0, XMM1 by class | RAX if 1, 2, 4 or 8 bytes; else memory | memory (Linux); EAX, EDX if 1, 2, 4 or 8 bytes (FreeBSD, OpenBSD) | EAX, EDX if 1, 2, 4 or 8 bytes; else memory |
| Larger structs | memory | memory | memory | memory |
| Address of the memory | RDI, returned in RAX | RCX, returned in RAX | on the stack, popped by the callee | on the stack, left to the caller |
Callee-saved registers
A callee-saved register must have the same value when a function returns as when it was called; the function saves and restores it if it uses it. All other registers are scratch, and any call may change them.
| System V x86-64 | Microsoft x64 | System V i386 | Windows x86 | |
|---|---|---|---|---|
| General-purpose | RBX, RBP, R12 to R15 | RBX, RBP, RDI, RSI, R12 to R15 | EBX, ESI, EDI, EBP | EBX, ESI, EDI, EBP |
| Vector | none | XMM6 to XMM15, the low 128 bits | none | none |
| APX (R16 to R31) | none | R30, R31 | not in 32-bit mode | not in 32-bit mode |
| Control state | x87 control word, control bits of MXCSR | the same | the same | not documented |
The difference in the first two columns is the cost of calling across ABIs: a Windows function called from System V code may overwrite nothing the caller expects kept, but a System V function called from Windows code may overwrite RSI, RDI and XMM6 to XMM15, which the caller expects back. The Microsoft x64 page shows the code that saves them.
Structs, floating point and vectors
| Argument | System V x86-64 | Microsoft x64 | System V i386 | Windows x86 |
|---|---|---|---|---|
| Struct of up to 16 bytes | classified by eightbyte: up to two registers, integer or vector | in a register if 1, 2, 4 or 8 bytes, else by reference | by value on the stack | by value on the stack |
| Larger struct | by value on the stack | by reference to a copy | by value on the stack | by value on the stack |
float, double | next of XMM0 to XMM7 | its slot among XMM0 to XMM3 | on the stack | on the stack |
long double | on the stack, 16 bytes | a double (MSVC); by reference (MinGW-w64) | on the stack, 12 bytes | a double (MSVC); on the stack (MinGW-w64) |
__m128 | in an XMM register | by reference | the first three in XMM0 to XMM2 | in XMM registers |
__m256, __m512 | in a YMM or ZMM register, if named and the CPU has them | by reference | the first three in YMM0 to YMM2 or ZMM0 to ZMM2 | by value with __vectorcall |
__m128 is: in XMM0 and XMM1 everywhere but in Microsoft x64, which passes both by reference, in RCX and RDX. GCC for i386 is given -msse2, since the i686 it otherwise compiles for has no XMM registers.vector-args.c
/* A 16-byte vector, like SSE's __m128, as argument and result. */
typedef float v4sf __attribute__((vector_size(16)));
v4sf vadd(v4sf a, v4sf b)
{
return a + b;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -S vector-args.c
vadd:
addps %xmm1, %xmm0
retIntel syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S vector-args.c
vadd:
addps xmm0, xmm1
retClang 23.1.2 x86_64-pc-windows-msvc
AT&T syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S vector-args.c
vadd:
movaps (%rcx), %xmm0
addps (%rdx), %xmm0
retqIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S vector-args.c
vadd:
movaps xmm0, xmmword ptr [rcx]
addps xmm0, xmmword ptr [rdx]
retGCC 14.2.0 (Debian 14.2.0-19) i386-linux-gnu
AT&T syntax gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -msse2 -S vector-args.c
vadd:
addps %xmm1, %xmm0
retIntel syntax gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -msse2 -masm=intel -S vector-args.c
vadd:
addps xmm0, xmm1
retClang 23.1.2 i686-pc-windows-msvc
AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S vector-args.c
_vadd:
addps %xmm1, %xmm0
retlIntel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S vector-args.c
_vadd:
addps xmm0, xmm1
retThe stack
| System V x86-64 | Microsoft x64 | System V i386 | Windows x86 | |
|---|---|---|---|---|
| Alignment at a call | 16 bytes | 16 bytes | 16 bytes | 4 bytes |
| Space the caller provides | none | 32 bytes of shadow space | none | none |
| Space below the stack pointer | 128-byte red zone | none | none | none |
| Who removes stack arguments | the caller | the caller | the caller | the caller for __cdecl, the callee otherwise |
| Variadic calls | AL gives the number of vector registers used | floating-point values also in the general-purpose register of their slot | nothing extra | __cdecl only |
The stack frames page shows how a function lays out its frame in each ABI.
Sources
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: commit
e1ce098331da, 2025-03-12 - System V Application Binary Interface, Intel386 Architecture Processor Supplement, version 1.2: commit
20ec676cd56d, 2025-08-24 - Microsoft Learn: x64 calling convention: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: Overview of x64 ABI conventions: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: Argument passing and naming conventions: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: __stdcall: commit
f70d88cd5da7, 2026-09-24