x86 and x86-64 ABIs and calling conventions
An ABI is the contract that lets separately compiled code work together. Here is which one each platform uses, what it decides, and how large each C type is under it.
An application binary interface, or ABI, is the set of rules that compiled code follows so that it can be combined with other compiled code: a program with the libraries it calls, C with hand-written assembly, the output of one compiler with that of another. The C standard leaves these details to each platform, and the x86-64 and x86 platforms settled on a handful of ABIs. Each has its own page here, which names every platform that uses it.
What an ABI decides
- Data layout
- The size and alignment of each type, and so how structs are padded.
longhas 8 bytes on Linux and 4 on Windows, so one struct declaration can describe two different layouts. - Calling convention
- Which registers or stack slots carry the arguments and the result,
which registers a called function must give back unchanged, how the
stack is aligned at a call, and how a variadic function such as
printffinds its arguments. - Stack frames and unwinding
- How debuggers, profilers and exception handlers walk from a function back to its callers.
- System calls
- Which instruction enters the kernel, and where the call number and its arguments go.
- Object files and linking
- The file format (ELF, PE/COFF or Mach-O), relocations, how symbol names are decorated, and how code reaches thread-local variables.
- C++
- Name mangling, the layout of virtual tables, how
thisis passed and how exceptions unwind the stack.
For ELF systems, the processor supplements of the System V ABI, the psABIs, define most of this: the x86-64 psABI goes from type sizes to ELF relocations and unwind tables, and has an informative chapter on Linux system calls. Microsoft documents its conventions for Windows, and Apple documents where macOS departs from the x86-64 psABI.
The same C code shows the difference. Listing 1 has two small functions compiled for three ABIs.
every-abi.c
/* Two functions, compiled for every ABI: where do the arguments
arrive, and where does the result go? */
long diff(long a, long b)
{
return a - b;
}
double axpy(double a, double x, double y)
{
return a * x + y;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -S every-abi.c
diff:
movq %rdi, %rax
subq %rsi, %rax
ret
axpy:
mulsd %xmm1, %xmm0
addsd %xmm2, %xmm0
retIntel syntax gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S every-abi.c
diff:
mov rax, rdi
sub rax, rsi
ret
axpy:
mulsd xmm0, xmm1
addsd xmm0, xmm2
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 every-abi.c
diff:
movl %ecx, %eax
subl %edx, %eax
retq
axpy:
mulsd %xmm1, %xmm0
addsd %xmm2, %xmm0
retqIntel syntax clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S every-abi.c
diff:
mov eax, ecx
sub eax, edx
ret
axpy:
mulsd xmm0, xmm1
addsd xmm0, xmm2
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 -S every-abi.c
diff:
movl 4(%esp), %eax
subl 8(%esp), %eax
ret
axpy:
fldl 4(%esp)
fmull 12(%esp)
faddl 20(%esp)
retIntel syntax gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S every-abi.c
diff:
mov eax, DWORD PTR 4[esp]
sub eax, DWORD PTR 8[esp]
ret
axpy:
fld QWORD PTR 4[esp]
fmul QWORD PTR 12[esp]
fadd QWORD PTR 20[esp]
retOn System V x86-64, the two long arguments of diff arrive in RDI
and RSI, and the result leaves in RAX. Microsoft x64 passes them in RCX
and RDX, and since long has only 32 bits there, the code works on
ECX, EDX and EAX. The 32-bit i386 ABI passes both on the stack, just
above the return address. The three double arguments of axpy
arrive in XMM0 to XMM2 on both 64-bit ABIs, and the result returns in
XMM0; i386 passes them on the stack as well and returns the result on
the x87 register stack, in ST0.
Every listing on these pages is the output of a real compiler: Clang 23.1.2, GCC 14.2.0 (Debian 14.2.0-19) and MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1), each pinned to an exact version and shown with the command that produced it. The AT&T and Intel buttons switch the syntax of every listing, and the choice is remembered. The site’s tests compile each listing again and fail if the output has changed.
Which platform uses which ABI
long and pointers are; Table 2 explains the names.| Platform | ABI | Data model | Page |
|---|---|---|---|
| Linux, Android, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, illumos and Solaris, Haiku | System V x86-64 psABI | LP64 | System V x86-64 |
| macOS on Intel processors | x86-64 psABI, with differences that Apple documents | LP64 | System V x86-64: macOS |
| Linux x32 | x86-64 psABI with 32-bit pointers | ILP32 | System V x86-64: x32 |
| Windows with MSVC, clang-cl or MinGW-w64, and MSYS2’s UCRT64 and CLANG64 environments | Microsoft x64 | LLP64 | Microsoft x64 |
| UEFI firmware on x64 | Microsoft x64 | LLP64 with MSVC or Clang’s UEFI target | Microsoft x64 |
| Cygwin, MSYS2’s MSYS environment | Microsoft x64 calling convention | LP64 | Microsoft x64 |
| 32-bit Linux, the BSDs and Android | System V i386 psABI | ILP32 | System V i386 |
| 32-bit Windows with MSVC, clang-cl or MinGW-w64 | cdecl, stdcall, fastcall, thiscall and vectorcall | ILP32 | Windows x86 |
A few rows need a word of explanation:
- UEFI firmware calls its services with the Microsoft x64
convention. EDK II, the reference implementation, marks every
interface function with GCC’s and Clang’s
ms_abiattribute when it builds for x64 with them, and Clang’sx86_64-unknown-uefitarget compiles with the Microsoft registers and type sizes. - Cygwin, and MSYS2’s MSYS environment, which is built on Cygwin,
keep the Windows calling convention but use the LP64 model of Linux,
so
longhas 64 bits. MSYS2’s other environments, such as UCRT64 and CLANG64, use Microsoft’s C runtimes and the Windows data model. - macOS on Intel processors follows the x86-64 psABI except for a few differences that Apple documents, listed on the System V page. Apple has not supported 32-bit Intel code since macOS Catalina, so the table has no 32-bit row for it.
Data models
| Model | int | long | long long | Pointers | Used by |
|---|---|---|---|---|---|
| LP64 | 32 | 64 | 64 | 64 | System V x86-64, Cygwin, MSYS2’s MSYS environment |
| LLP64 | 32 | 32 | 64 | 64 | Microsoft x64 |
| ILP32 | 32 | 32 | 64 | 32 | Linux x32 and every 32-bit x86 platform |
The names of LP64 and LLP64 list the types that have 64 bits: long
and pointers, or long long and pointers. ILP32 lists those with 32:
int, long and pointers. long long has 64 bits everywhere. Code
that keeps a pointer in an integer should use intptr_t or
uintptr_t, which fit on every platform, rather than long, which
does not on 64-bit Windows.
Sizes of C types
Tables 3 and 4 give the size and alignment of the basic C types as
the compilers of each platform see them. On every platform in them,
_Bool and char take 1 byte, short 2, and int and float 4, and char is signed; the tables leave these
types out.
| Type | System V x86-64 | Android | Linux x32 | Windows | MinGW-w64 | Cygwin, MSYS2 |
|---|---|---|---|---|---|---|
| Data model | LP64 | LP64 | ILP32 | LLP64 | LLP64 | LP64 |
long | 8 | 8 | 4 | 4 | 4 | 8 |
long long | 8 | 8 | 8 | 8 | 8 | 8 |
__int128 | 16 | 16 | 16 | 16 | 16 | 16 |
void * | 8 | 8 | 4 | 8 | 8 | 8 |
double | 8 | 8 | 8 | 8 | 8 | 8 |
long double | 16 | 16 | 16 | 8 | 16 | 16 |
long double format | x87 80-bit | binary128 | x87 80-bit | binary64 | x87 80-bit | x87 80-bit |
wchar_t | 4 | 4 | 4 | 2 | 2 | 2 |
va_list | 24 / 8 | 24 / 8 | 16 / 4 | 8 | 8 | 8 |
Platforms and compilers of each column
- System V x86-64
- Linux, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, illumos and Solaris, Haiku, macOS. Measured with GCC 14.2.0 (Debian 14.2.0-19) for
x86_64-linux-gnu; Clang 23.1.2 forx86_64-linux-gnu,x86_64-unknown-freebsd14,x86_64-unknown-netbsd10,x86_64-unknown-openbsd7,x86_64-unknown-dragonfly,x86_64-pc-solaris2.11,x86_64-unknown-haikuandx86_64-apple-macos11. - Android
- Android on x86-64. Measured with Clang 23.1.2 for
x86_64-linux-android. - Linux x32
- Linux x32. Measured with GCC 14.2.0 (Debian 14.2.0-19) for
x86_64-linux-gnux32; Clang 23.1.2 forx86_64-linux-gnux32. - Windows
- Windows with MSVC or clang-cl, UEFI. Measured with Clang 23.1.2 for
x86_64-pc-windows-msvcandx86_64-unknown-uefi. - MinGW-w64
- Windows with MinGW-w64 GCC or Clang. Measured with MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) for
x86_64-w64-mingw32; Clang 23.1.2 forx86_64-w64-windows-gnu. - Cygwin, MSYS2
- Cygwin and the MSYS2 msys environment. Measured with Clang 23.1.2 for
x86_64-pc-cygwinandx86_64-pc-msys.
| Type | System V i386 | Android | Windows | MinGW-w64 |
|---|---|---|---|---|
| Data model | ILP32 | ILP32 | ILP32 | ILP32 |
long | 4 | 4 | 4 | 4 |
long long | 8 / 4 | 8 / 4 | 8 | 8 |
__int128 | – | – | – | – |
void * | 4 | 4 | 4 | 4 |
double | 8 / 4 | 8 / 4 | 8 | 8 |
long double | 12 / 4 | 8 / 4 | 8 | 12 / 4 |
long double format | x87 80-bit | binary64 | binary64 | x87 80-bit |
wchar_t | 4 | 4 | 2 | 2 |
va_list | 4 | 4 | 4 | 4 |
Platforms and compilers of each column
- System V i386
- Linux and FreeBSD on i386. Measured with GCC 14.2.0 (Debian 14.2.0-19) for
i386-linux-gnu; Clang 23.1.2 fori686-linux-gnuandi686-unknown-freebsd14. - Android
- Android on x86. Measured with Clang 23.1.2 for
i686-linux-android. - Windows
- Windows with MSVC or clang-cl. Measured with Clang 23.1.2 for
i686-pc-windows-msvc. - MinGW-w64
- Windows with MinGW-w64 GCC or Clang. Measured with MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) for
i686-w64-mingw32; Clang 23.1.2 fori686-w64-windows-gnu.
The numbers come from compiling sizeof and _Alignof of each type
with the compilers of each platform and reading the results back from
the assembly. Each column was measured for all the targets listed
under its table, and the site’s build fails if they disagree.
long double- It differs most between platforms. The System V ABIs, MinGW-w64
and Cygwin use the x87 80-bit format, stored in 16 bytes on x86-64
and in 12 bytes aligned to 4 on 32-bit x86. Android on x86-64 uses
the 128-bit IEEE format instead, while MSVC, UEFI and Android on
32-bit x86 make
long doublethe same asdouble. doubleandlong longon 32-bit x86- System V aligns them to 4 bytes and Windows to 8, so a struct of an
intand adoubletakes 12 bytes on 32-bit Linux and 16 on 32-bit Windows. wchar_t- It has 4 bytes on the System V platforms and 2 on Windows and Cygwin.
va_list- A pointer on Windows and on 32-bit x86, but a 24-byte structure on System V x86-64 (16 bytes in x32), because a variadic function’s arguments may still be in the registers they arrived in.
__int128- Available on every 64-bit platform here, x32 included, and on none of the 32-bit ones. On Windows it is Clang’s: MSVC’s own integer types end at 64 bits.
The pages
One page for each ABI, naming every platform that uses it:
- System V x86-64, with Linux x32 and macOS
- Microsoft x64
- System V i386
- Windows x86
Pages that compare the ABIs:
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: Overview of x64 ABI conventions: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: Data type ranges: commit
f70d88cd5da7, 2026-09-24 - Microsoft Learn: Argument passing and naming conventions: commit
f70d88cd5da7, 2026-09-24 - Apple Developer: Writing 64-bit Intel code for Apple Platforms: as published on 2026-09-25
- Cygwin User's Guide: Building applications for 64 bit Cygwin: as published on 2026-09-25
- MSYS2 documentation: Environments: commit
9879735eb93f, 2026-05-14 - EDK II: BaseTools/Conf/tools_def.template: commit
2970e5699ba6, 2026-08-12 - Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: commit
85ac56026243, 2026-09-20