Microsoft x64 ABI と呼び出し規約
64 ビット Windows と、それを起動するファームウェアの呼び出し規約です。位置で埋まる 4 つのレジスタースロット、32 バイトのシャドウスペース、8 バイトより大きいものはすべて参照渡しという規則を、規則ごとにコンパイルしたコードとともに示します。
Microsoft の x64 規約は、MSVC、clang-cl、MinGW-w64 のどれでビルドされたかにかかわらず 64 ビット Windows 上のすべてのプログラムが使い、x64 マシンの UEFI ファームウェアも使います。Cygwin と MSYS2 の MSYS 環境も使いますが、これらは規約を保ちつつ long を 64 ビットにしています。Linux と macOS が使う System V x86-64 より単純で、位置で埋まる 4 つの引数スロットがあり、8 バイトより大きいものはレジスターに入りません。
このページのリストは、x86_64-pc-windows-msvc ターゲット向けの Clang と MinGW-w64 GCC を、どちらも -O2 で使ったものです。このターゲットの Clang は MSVC の ABI に従い、clang-cl が内部で使うコンパイラーです。各リストの横のボタンで AT&T 構文と Intel 構文を切り替えられます。
レジスター
最初の 4 つの引数は RCX、RDX、R8、R9 に入り、浮動小数点値なら XMM0 から XMM3 に入ります。関数は RAX、RCX、RDX、R8 から R11、XMM0 から XMM5 を変更して構いませんが、RBX、RBP、RDI、RSI、RSP、R12 から R15、XMM6 から XMM15 は保存しなければなりません。RDI、RSI と 6 つの XMM レジスターが呼び出し先保存であることが、2 つの規約が出会うときに最も重要になる System V との違いです。
| レジスター | 役割 | 保存される |
|---|---|---|
| RCX、RDX、R8、R9 | 整数、ポインター、小さな構造体の引数 1 から 4 | いいえ |
| XMM0 から XMM3 | 浮動小数点の引数 1 から 4 | いいえ |
| RAX | 整数の戻り値 | いいえ |
| XMM0 | 浮動小数点、ベクトル、__int128 の戻り値 | いいえ |
| R10、R11 | スクラッチ (scratch) | いいえ |
| XMM4、XMM5 | スクラッチ。__vectorcall の引数 5 と 6 | いいえ |
| RBX、RDI、RSI、R12 から R15 | 呼び出し先保存 | はい |
| RBP | 呼び出し先保存。関数がフレームポインターを使う場合はフレームポインター | はい |
| RSP | スタックポインター | はい |
| XMM6 から XMM15 | 呼び出し先保存 (下位 128 ビットのみ) | はい |
| YMM0 から YMM15 の上位半分、ZMM | スクラッチ | いいえ |
| XMM16 から XMM31 (AVX-512) | スクラッチ | いいえ |
| R16 から R29 (APX) | スクラッチ | いいえ |
| R30、R31 (APX) | 呼び出し先保存 | はい |
| TMM0 から TMM7 (AMX) | スクラッチ | いいえ |
| ST0 から ST7、MM0 から MM7 | スクラッチ。引数には使わない | いいえ |
System V と同じく、関数は方向フラグをクリアしたままにし、MXCSR と x87 制御ワードの制御ビットを保ちます。それらのステータスビットはスクラッチです。
引数の渡し方
最初の 4 つの引数にはそれぞれスロットがあり、レジスターは位置だけで決まります。2 番目の引数は、最初の引数が何であっても、整数なら RDX に、double なら XMM1 に入ります。スロットのもう一方のレジスターは使われません。5 番目以降の引数は、右から左へ 8 バイトずつスタックに置かれます。
呼び出し元は、呼び出される関数の引数が 4 つより少ない場合でも、戻りアドレスの上に常に 32 バイトのシャドウスペース (shadow space) を確保します。これはホーム領域 (home space) とも呼ばれ、レジスター引数ごとに 8 バイトのホームが 1 つあります。この領域は呼び出された関数のもので、関数はそこにレジスター引数を格納しても、ほかの何を格納しても構いません。そのため、関数に入った時点で 5 番目の引数は、戻りアドレスとシャドウスペースの後の 40(%rsp) から始まります。
sum6 は 5 番目と 6 番目をシャドウスペースの上の 40(%rsp) と 48(%rsp) に見つけます。call6 は 56 バイトを確保します。シャドウスペースに 32 バイト、2 つのスタック引数に 16 バイト、呼び出し時に RSP を 16 にアラインしておくために 8 バイトです。win-int-args.c
/* Six integer arguments: four in registers, two on the stack above
the 32-byte shadow space. */
long long sum6(long long a, long long b, long long c, long long d, long long e, long long f)
{
return a + b + c + d + e + f;
}
extern long long take6(long long, long long, long long, long long, long long, long long);
long long call6(void)
{
return take6(1, 2, 3, 4, 5, 6) + 1;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-int-args.c
sum6:
addq %rdx, %rcx
leaq (%r8,%r9), %rax
addq %rcx, %rax
addq 40(%rsp), %rax
addq 48(%rsp), %rax
retq
call6:
subq $56, %rsp
movq $6, 40(%rsp)
movq $5, 32(%rsp)
movl $1, %ecx
movl $2, %edx
movl $3, %r8d
movl $4, %r9d
callq take6
incq %rax
addq $56, %rsp
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-int-args.c
sum6:
add rcx, rdx
lea rax, [r8 + r9]
add rax, rcx
add rax, qword ptr [rsp + 40]
add rax, qword ptr [rsp + 48]
ret
call6:
sub rsp, 56
mov qword ptr [rsp + 40], 6
mov qword ptr [rsp + 32], 5
mov ecx, 1
mov edx, 2
mov r8d, 3
mov r9d, 4
call take6
inc rax
add rsp, 56
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-int-args.c
sum6:
addq %rdx, %rcx
addq %r8, %rcx
leaq (%rcx,%r9), %rax
addq 40(%rsp), %rax
addq 48(%rsp), %rax
ret
call6:
subq $56, %rsp
movl $4, %r9d
movl $3, %r8d
movl $2, %edx
movq $6, 40(%rsp)
movl $1, %ecx
movq $5, 32(%rsp)
call take6
addq $56, %rsp
addq $1, %rax
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-int-args.c
sum6:
add rcx, rdx
add rcx, r8
lea rax, [rcx+r9]
add rax, QWORD PTR 40[rsp]
add rax, QWORD PTR 48[rsp]
ret
call6:
sub rsp, 56
mov r9d, 4
mov r8d, 3
mov edx, 2
mov QWORD PTR 40[rsp], 6
mov ecx, 1
mov QWORD PTR 32[rsp], 5
call take6
add rsp, 56
add rax, 1
reta は ECX、b は XMM1、c は R8D、d は XMM3 に入り、RDX、XMM0、R9、XMM2 は使われません。e と f はスタックの 32(%rsp) と 40(%rsp) に置かれます。win-mixed-args.c
/* The position decides the register: the second argument goes to
RDX or XMM1, whatever came before it. */
extern double mix(int a, double b, int c, float d, char *e, double f);
double call_mix(char *p)
{
return mix(1, 2.0, 3, 4.0f, p, 5.0) * 2;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-mixed-args.c
__real@4000000000000000:
.quad 0x4000000000000000
__real@40800000:
.long 0x40800000
call_mix:
subq $56, %rsp
movabsq $4617315517961601024, %rax
movq %rax, 40(%rsp)
movq %rcx, 32(%rsp)
movsd __real@4000000000000000(%rip), %xmm1
movss __real@40800000(%rip), %xmm3
movl $1, %ecx
movl $3, %r8d
callq mix
addsd %xmm0, %xmm0
addq $56, %rsp
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-mixed-args.c
__real@4000000000000000:
.quad 0x4000000000000000
__real@40800000:
.long 0x40800000
call_mix:
sub rsp, 56
movabs rax, 4617315517961601024
mov qword ptr [rsp + 40], rax
mov qword ptr [rsp + 32], rcx
movsd xmm1, qword ptr [rip + __real@4000000000000000]
movss xmm3, dword ptr [rip + __real@40800000]
mov ecx, 1
mov r8d, 3
call mix
addsd xmm0, xmm0
add rsp, 56
ret呼び出し先は、より小さい引数を保持するレジスターの上位ビットについて何も仮定してはなりません。ECX の int では、RCX の上位半分は未定義です。
構造体、ベクトル、その他の型
値がレジスターで渡されるのは、1、2、4 または 8 バイトの場合だけです。それ以外の構造体や共用体、__m128 ベクトル、16 バイト以上の値はすべて参照渡しです。呼び出し元はコピーを作り、そのアドレスを引数のスロットで渡します。
pass_triple は自分のフレームにコピーを作り、そのアドレスを渡します。win-struct-args.c
/* Structs of 1, 2, 4 or 8 bytes travel like integers; any other size
is passed as a pointer to a copy the caller makes. */
struct pair32 { int a, b; }; /* 8 bytes: in a register */
struct triple32 { int a, b, c; }; /* 12 bytes: by reference */
int pair_sum(struct pair32 p)
{
return p.a + p.b;
}
int triple_sum(struct triple32 t)
{
return t.a + t.b + t.c;
}
extern int take_triple(struct triple32 t);
int pass_triple(int x)
{
struct triple32 t = { x, 2 * x, 3 * x };
return take_triple(t) + 1;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-struct-args.c
pair_sum:
movq %rcx, %rax
shrq $32, %rax
addl %ecx, %eax
retq
triple_sum:
movl 4(%rcx), %eax
addl (%rcx), %eax
addl 8(%rcx), %eax
retq
pass_triple:
subq $56, %rsp
leal (%rcx,%rcx), %eax
leal (%rcx,%rcx,2), %edx
movl %ecx, 44(%rsp)
movl %eax, 48(%rsp)
movl %edx, 52(%rsp)
leaq 44(%rsp), %rcx
callq take_triple
incl %eax
addq $56, %rsp
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-struct-args.c
pair_sum:
mov rax, rcx
shr rax, 32
add eax, ecx
ret
triple_sum:
mov eax, dword ptr [rcx + 4]
add eax, dword ptr [rcx]
add eax, dword ptr [rcx + 8]
ret
pass_triple:
sub rsp, 56
lea eax, [rcx + rcx]
lea edx, [rcx + 2*rcx]
mov dword ptr [rsp + 44], ecx
mov dword ptr [rsp + 48], eax
mov dword ptr [rsp + 52], edx
lea rcx, [rsp + 44]
call take_triple
inc eax
add rsp, 56
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-struct-args.c
pair_sum:
movq %rcx, %rax
shrq $32, %rax
addl %ecx, %eax
ret
triple_sum:
movl 4(%rcx), %eax
addl (%rcx), %eax
addl 8(%rcx), %eax
ret
pass_triple:
pushq %rbp
leal (%rcx,%rcx), %eax
movq %rsp, %rbp
andq $-16, %rsp
subq $64, %rsp
movl %ecx, 48(%rsp)
movl %eax, 52(%rsp)
movq 48(%rsp), %rdx
addl %ecx, %eax
leaq 32(%rsp), %rcx
movl %eax, 40(%rsp)
movq %rdx, 32(%rsp)
call take_triple
leave
addl $1, %eax
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-struct-args.c
pair_sum:
mov rax, rcx
shr rax, 32
add eax, ecx
ret
triple_sum:
mov eax, DWORD PTR 4[rcx]
add eax, DWORD PTR [rcx]
add eax, DWORD PTR 8[rcx]
ret
pass_triple:
push rbp
lea eax, [rcx+rcx]
mov rbp, rsp
and rsp, -16
sub rsp, 64
mov DWORD PTR 48[rsp], ecx
mov DWORD PTR 52[rsp], eax
mov rdx, QWORD PTR 48[rsp]
add eax, ecx
lea rcx, 32[rsp]
mov DWORD PTR 40[rsp], eax
mov QWORD PTR 32[rsp], rdx
call take_triple
leave
add eax, 1
retMicrosoft は、呼び出し元のコピーを 16 バイトにアラインするよう求めています。MinGW-w64 GCC は andq $-16 で RSP を切り下げてアラインします。Clang は リスト 3 の 12 バイトのコピーを 44(%rsp) に置き、これは 4 バイトにしかアラインされていません。呼び出される関数は、型そのもののアラインメントより大きなアラインメントに依存すべきではありません。
long double を見ると、Windows の 2 つのツールチェーンの違いがわかります。MSVC はこれを double と同じ型にし、MSVC ターゲット向けの Clang も同様です。MinGW-w64 は GCC の x87 の 80 ビット形式を保ちます。この形式は 16 バイトなので、参照で渡され、参照で返されます。
long double は XMM0 の double です。MinGW-w64 GCC は値へのポインターを RDX で受け取り、RCX のポインターを通して結果を格納し、そのポインターを RAX で返します。win-long-double.c
/* long double is double for MSVC, and the 80-bit x87 type for
MinGW-w64, which passes it by reference. */
long double ld_twice(long double x)
{
return x * 2;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-long-double.c
ld_twice:
addsd %xmm0, %xmm0
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-long-double.c
ld_twice:
addsd xmm0, xmm0
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-long-double.c
ld_twice:
fldt (%rdx)
pushq %rbp
movq %rcx, %rax
fadd %st(0), %st
movq %rsp, %rbp
andq $-16, %rsp
fstpt (%rcx)
leave
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-long-double.c
ld_twice:
fld TBYTE PTR [rdx]
push rbp
mov rax, rcx
fadd st, st(0)
mov rbp, rsp
and rsp, -16
fstp TBYTE PTR [rcx]
leave
ret__int128。両方の引数が参照渡しで、結果は XMM0 で返ります。win-int128.c
/* __int128 (Clang and GCC only): by reference as an argument. */
__int128 add128(__int128 a, __int128 b)
{
return a + b;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-int128.c
add128:
movq (%rdx), %rax
addq (%rcx), %rax
movq 8(%rdx), %rdx
adcq 8(%rcx), %rdx
movq %rax, %xmm0
movq %rdx, %xmm1
punpcklqdq %xmm1, %xmm0
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-int128.c
add128:
mov rax, qword ptr [rdx]
add rax, qword ptr [rcx]
mov rdx, qword ptr [rdx + 8]
adc rdx, qword ptr [rcx + 8]
movq xmm0, rax
movq xmm1, rdx
punpcklqdq xmm0, xmm1
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-int128.c
add128:
pushq %rbp
movq %rsp, %rbp
andq $-16, %rsp
subq $16, %rsp
movq (%rdx), %rax
movq 8(%rdx), %rdx
addq (%rcx), %rax
adcq 8(%rcx), %rdx
movq %rax, (%rsp)
movq %rdx, 8(%rsp)
movdqu (%rsp), %xmm0
leave
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-int128.c
add128:
push rbp
mov rbp, rsp
and rsp, -16
sub rsp, 16
mov rax, QWORD PTR [rdx]
mov rdx, QWORD PTR 8[rdx]
add rax, QWORD PTR [rcx]
adc rdx, QWORD PTR 8[rcx]
mov QWORD PTR [rsp], rax
mov QWORD PTR 8[rsp], rdx
movdqu xmm0, XMMWORD PTR [rsp]
leave
ret値の返し方
整数、ポインター、1、2、4 または 8 バイトの構造体は RAX で返り、float、double、16 バイトのベクトルは XMM0 で返ります。それ以外のものについては呼び出し元がメモリを用意します。呼び出し元はそのアドレスを最初の隠し引数として RCX で渡し、本来の引数はスロット 1 つ分後ろにずれます。関数は同じアドレスを RAX で返します。C++ では、ユーザー定義のコンストラクター、デストラクター、またはコピー代入を持つ型は、サイズにかかわらず常にメモリを通して返ります。
x は EDX で渡されます。win-returns.c
/* A struct of 8 bytes returns in RAX; a larger one through memory the
caller provides, whose address arrives in RCX. */
struct pair32 { int a, b; };
struct triple32 { int a, b, c; };
struct pair32 ret_pair(int a, int b)
{
return (struct pair32){ a, b };
}
struct triple32 ret_triple(int x)
{
return (struct triple32){ x, x, x };
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-returns.c
ret_pair:
shlq $32, %rdx
movl %ecx, %eax
orq %rdx, %rax
retq
ret_triple:
movq %rcx, %rax
movl %edx, (%rcx)
movl %edx, 4(%rcx)
movl %edx, 8(%rcx)
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-returns.c
ret_pair:
shl rdx, 32
mov eax, ecx
or rax, rdx
ret
ret_triple:
mov rax, rcx
mov dword ptr [rcx], edx
mov dword ptr [rcx + 4], edx
mov dword ptr [rcx + 8], edx
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-returns.c
ret_pair:
salq $32, %rdx
movl %ecx, %eax
orq %rdx, %rax
ret
ret_triple:
movd %edx, %xmm0
movl %edx, 8(%rcx)
movq %rcx, %rax
pshufd $0xe0, %xmm0, %xmm1
movq %xmm1, (%rcx)
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-returns.c
ret_pair:
sal rdx, 32
mov eax, ecx
or rax, rdx
ret
ret_triple:
movd xmm0, edx
mov DWORD PTR 8[rcx], edx
mov rax, rcx
pshufd xmm1, xmm0, 0xe0
movq QWORD PTR [rcx], xmm1
ret呼び出し先保存レジスター
RSI、RDI、XMM6 から XMM15 は、Windows のコードが呼び出しをまたいで保ち、System V のコードが保たないレジスターです。呼び出しを越えて残る必要がある値はこれらのどれかに置いておけ、それを使う関数は先にそのレジスターを保存します。XMM6 から XMM15 では下位 128 ビットだけが対象です。
x は f の呼び出しを越えて残る必要があるので、呼び出し先保存レジスターである XMM6 に移されます。keep は先に XMM6 を自分のフレームに保存します。win-callee-saved.c
/* RSI, RDI and XMM6 to XMM15 are callee-saved on Windows, unlike in
System V: a double that must survive a call can stay in XMM6. */
extern double f(double);
double keep(double x)
{
return f(x) + x;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-callee-saved.c
keep:
subq $56, %rsp
movaps %xmm6, 32(%rsp)
movapd %xmm0, %xmm6
callq f
addsd %xmm6, %xmm0
movaps 32(%rsp), %xmm6
addq $56, %rsp
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-callee-saved.c
keep:
sub rsp, 56
movaps xmmword ptr [rsp + 32], xmm6
movapd xmm6, xmm0
call f
addsd xmm0, xmm6
movaps xmm6, xmmword ptr [rsp + 32]
add rsp, 56
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-callee-saved.c
keep:
subq $56, %rsp
movups %xmm6, 32(%rsp)
movupd %xmm0, %xmm6
call f
addsd %xmm6, %xmm0
movups 32(%rsp), %xmm6
addq $56, %rsp
retIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-callee-saved.c
keep:
sub rsp, 56
movups XMMWORD PTR 32[rsp], xmm6
movupd xmm6, xmm0
call f
addsd xmm0, xmm6
movups xmm6, XMMWORD PTR 32[rsp]
add rsp, 56
retスタック
RSP はすべての呼び出しの時点で 16 の倍数なので、関数に入った時点では 16 の倍数より 8 大きい値です。ほかの関数を呼び出す各関数は、フレームの最下部、つまりローカル変数より下で、自分が行う呼び出しの戻りアドレスのすぐ上に、パラメーター領域を確保します。これはシャドウスペースと、その関数が行う最大の呼び出しのスタック引数のための領域からなります。関数は呼び出しのたびに引数をプッシュするのではなく、リスト 1 の call6 のように、この領域をプロローグで一度だけ確保します。
レッドゾーン (red zone) はありません。RSP より下のメモリは、オペレーティングシステム、割り込み、デバッガーによっていつでも上書きされる可能性があるので、関数はスタックに何かを格納する前に RSP を動かします。System V のリーフ関数は RSP より下にデータを置きますが、Windows のリーフ関数は呼び出し元が用意したシャドウスペースを使います。
Microsoft は、関数がプロローグとエピローグで RSP を変更したりレジスターを保存したりする方法も制限しています。.pdata と .xdata のアンワインドデータがそれらを記述しなければならないためです。そのデータについてはアンワインドのページで説明しています。
可変長引数関数
可変長引数関数はほかの関数と同じように呼び出されますが、1 つ追加があります。4 つのレジスタースロットのどれかにある浮動小数点引数は、呼び出し先がその型を知ることができないので、その XMM レジスターと、対応する汎用レジスターの両方で渡されます。プロトタイプのない関数の呼び出しでも同じです。
残りはシャドウスペースのおかげで単純です。可変長引数関数は RDX、R8、R9 を、スタック引数のすぐ下にあるそれぞれのホームに格納するので、すべての引数が 8 バイトのスロットからなる 1 つの配列になります。va_list はその配列をたどる単純な char * で、va_arg は 8 バイトずつ取り出します。
log_msg は RDX、R8、R9 を 56(%rsp) 以上にあるそれぞれのホームに格納し、va_list はその最初を指します。call_double は x を XMM1 と RDX の両方で渡します。win-varargs.c
/* A variadic function stores RDX, R8 and R9 in the shadow space, right
below the stack arguments, and va_list is a plain pointer into it. A
variadic call puts a double in both XMM1 and RDX. */
#include <stdarg.h>
extern int vlog(const char *fmt, va_list ap);
int log_msg(const char *fmt, ...)
{
va_list ap;
int r;
va_start(ap, fmt);
r = vlog(fmt, ap);
va_end(ap);
return r;
}
int call_double(double x)
{
return log_msg("%f", x);
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-varargs.c
log_msg:
subq $40, %rsp
movq %rdx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
leaq 56(%rsp), %rdx
movq %rdx, 32(%rsp)
callq vlog
addq $40, %rsp
retq
call_double:
subq $40, %rsp
leaq "??_C@_02NJPGOMH@?$CFf?$AA@"(%rip), %rcx
movdqa %xmm0, %xmm1
movq %xmm0, %rdx
callq log_msg
addq $40, %rsp
retq
"??_C@_02NJPGOMH@?$CFf?$AA@":
.asciz "%f"Intel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-varargs.c
log_msg:
sub rsp, 40
mov qword ptr [rsp + 56], rdx
mov qword ptr [rsp + 64], r8
mov qword ptr [rsp + 72], r9
lea rdx, [rsp + 56]
mov qword ptr [rsp + 32], rdx
call vlog
add rsp, 40
ret
call_double:
sub rsp, 40
lea rcx, [rip + "??_C@_02NJPGOMH@?$CFf?$AA@"]
movdqa xmm1, xmm0
movq rdx, xmm0
call log_msg
add rsp, 40
ret
"??_C@_02NJPGOMH@?$CFf?$AA@":
.asciz "%f"MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) x86_64-w64-mingw32
AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S win-varargs.c
log_msg:
subq $56, %rsp
movq %rdx, 72(%rsp)
leaq 72(%rsp), %rdx
movq %r8, 80(%rsp)
movq %r9, 88(%rsp)
movq %rdx, 40(%rsp)
call vlog
addq $56, %rsp
ret
.LC0:
.ascii "%f\0"
call_double:
movq %xmm0, %rdx
movupd %xmm0, %xmm1
leaq .LC0(%rip), %rcx
jmp log_msgIntel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-varargs.c
log_msg:
sub rsp, 56
mov QWORD PTR 72[rsp], rdx
lea rdx, 72[rsp]
mov QWORD PTR 80[rsp], r8
mov QWORD PTR 88[rsp], r9
mov QWORD PTR 40[rsp], rdx
call vlog
add rsp, 56
ret
.LC0:
.ascii "%f\0"
call_double:
movq rdx, xmm0
movupd xmm1, xmm0
lea rcx, .LC0[rip]
jmp log_msg__vectorcall
__vectorcall は、ベクトルコードのためにこの規約を拡張します。最初の 6 つの中の浮動小数点引数とベクトル引数は、XMM0 から XMM5 (__m256 なら YMM0 から YMM5) で値渡しされ、最大 4 つのベクトルからなる同種集合体 (homogeneous aggregate) は空いているベクトルレジスターを使えます。整数は 4 つのスロットを保ちます。__vectorcall 関数の名前は、@@ と引数のバイト単位のサイズで装飾されます。
__vectorcall では XMM0 と XMM1 で値渡しされます。その場合、名前は add_vectorcall@@32 になります。win-vectorcall.c
/* Vectors of 16 bytes are passed by reference by default; __vectorcall
passes them in XMM registers. */
typedef float v4sf __attribute__((vector_size(16)));
v4sf add_default(v4sf a, v4sf b)
{
return a + b;
}
v4sf __vectorcall add_vectorcall(v4sf a, v4sf b)
{
return a + b;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S win-vectorcall.c
add_default:
movaps (%rcx), %xmm0
addps (%rdx), %xmm0
retq
add_vectorcall@@32:
addps %xmm1, %xmm0
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-vectorcall.c
add_default:
movaps xmm0, xmmword ptr [rcx]
addps xmm0, xmmword ptr [rdx]
ret
add_vectorcall@@32:
addps xmm0, xmm1
retWindows と System V のコードの間の呼び出し
GCC と Clang には、ターゲットにかかわらず 1 つの関数の規約を選ぶ 2 つの関数属性 ms_abi と sysv_abi があります。Wine は Linux で Windows のプログラムを動かすために ms_abi を使います。Wine のヘッダーは x86-64 で __stdcall を ms_abi として定義しているので、Wine が実装するすべての Windows API 関数も ms_abi になります。EDK II も同じ方法で、GCC と Clang で x64 の UEFI ファームウェアをビルドします。
Windows の方法で呼び出され、通常の System V のコードを呼び出す関数は、Windows の約束を守らなければなりません。RSI、RDI、XMM6 から XMM15 は、その関数の呼び出し元にとっては呼び出し先保存ですが、その関数が呼び出す関数にとってはスクラッチです。
ms_abi 関数。引数は RCX と RDX で渡され、System V の呼び出しのために RDI と RSI に移されます。関数はその呼び出しの前後で RSI、RDI、XMM6 から XMM15 の 176 バイトを保存します。win-ms-abi.c
/* GCC's ms_abi attribute on Linux: this function receives its
arguments the Windows way, then calls a System V function, which may
change RSI, RDI and XMM6 to XMM15; the Windows caller expects them
back, so they are saved and restored. */
extern long sysv_callee(long a, long b);
__attribute__((ms_abi)) long from_windows(long a, long b)
{
return sysv_callee(a, b) + 1;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S win-ms-abi.c
from_windows:
pushq %rdi
movq %rcx, %rdi
pushq %rsi
movq %rdx, %rsi
subq $168, %rsp
movaps %xmm6, (%rsp)
movaps %xmm7, 16(%rsp)
movaps %xmm8, 32(%rsp)
movaps %xmm9, 48(%rsp)
movaps %xmm10, 64(%rsp)
movaps %xmm11, 80(%rsp)
movaps %xmm12, 96(%rsp)
movaps %xmm13, 112(%rsp)
movaps %xmm14, 128(%rsp)
movaps %xmm15, 144(%rsp)
call sysv_callee@PLT
movaps (%rsp), %xmm6
movaps 16(%rsp), %xmm7
movaps 32(%rsp), %xmm8
movaps 48(%rsp), %xmm9
addq $1, %rax
movaps 64(%rsp), %xmm10
movaps 80(%rsp), %xmm11
movaps 96(%rsp), %xmm12
movaps 112(%rsp), %xmm13
movaps 128(%rsp), %xmm14
movaps 144(%rsp), %xmm15
addq $168, %rsp
popq %rsi
popq %rdi
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win-ms-abi.c
from_windows:
push rdi
mov rdi, rcx
push rsi
mov rsi, rdx
sub rsp, 168
movaps XMMWORD PTR [rsp], xmm6
movaps XMMWORD PTR 16[rsp], xmm7
movaps XMMWORD PTR 32[rsp], xmm8
movaps XMMWORD PTR 48[rsp], xmm9
movaps XMMWORD PTR 64[rsp], xmm10
movaps XMMWORD PTR 80[rsp], xmm11
movaps XMMWORD PTR 96[rsp], xmm12
movaps XMMWORD PTR 112[rsp], xmm13
movaps XMMWORD PTR 128[rsp], xmm14
movaps XMMWORD PTR 144[rsp], xmm15
call sysv_callee@PLT
movaps xmm6, XMMWORD PTR [rsp]
movaps xmm7, XMMWORD PTR 16[rsp]
movaps xmm8, XMMWORD PTR 32[rsp]
movaps xmm9, XMMWORD PTR 48[rsp]
add rax, 1
movaps xmm10, XMMWORD PTR 64[rsp]
movaps xmm11, XMMWORD PTR 80[rsp]
movaps xmm12, XMMWORD PTR 96[rsp]
movaps xmm13, XMMWORD PTR 112[rsp]
movaps xmm14, XMMWORD PTR 128[rsp]
movaps xmm15, XMMWORD PTR 144[rsp]
add rsp, 168
pop rsi
pop rdi
retCygwin、MSYS2、UEFI
Cygwin と、その上に構築された MSYS2 の MSYS 環境は、この呼び出し規約を Linux の LP64 データモデルとともに使います。long は 64 ビットです。リスト 11 では、long 値の diff が RCX と RDX 全体を操作します。
long だけです。Clang は UEFI ターゲットではフレームポインターを保持します。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;
}
Clang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 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 構文 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
retClang 23.1.2 x86_64-pc-cygwin
AT&T 構文 clang --target=x86_64-pc-cygwin -O2 -fno-asynchronous-unwind-tables -S every-abi.c
diff:
movq %rcx, %rax
subq %rdx, %rax
retq
axpy:
mulsd %xmm1, %xmm0
addsd %xmm2, %xmm0
retqIntel 構文 clang --target=x86_64-pc-cygwin -O2 -fno-asynchronous-unwind-tables -masm=intel -S every-abi.c
diff:
mov rax, rcx
sub rax, rdx
ret
axpy:
mulsd xmm0, xmm1
addsd xmm0, xmm2
retClang 23.1.2 x86_64-unknown-uefi
AT&T 構文 clang --target=x86_64-unknown-uefi -O2 -fno-asynchronous-unwind-tables -S every-abi.c
diff:
pushq %rbp
movq %rsp, %rbp
movl %ecx, %eax
subl %edx, %eax
popq %rbp
retq
axpy:
pushq %rbp
movq %rsp, %rbp
mulsd %xmm1, %xmm0
addsd %xmm2, %xmm0
popq %rbp
retqIntel 構文 clang --target=x86_64-unknown-uefi -O2 -fno-asynchronous-unwind-tables -masm=intel -S every-abi.c
diff:
push rbp
mov rbp, rsp
mov eax, ecx
sub eax, edx
pop rbp
ret
axpy:
push rbp
mov rbp, rsp
mulsd xmm0, xmm1
addsd xmm0, xmm2
pop rbp
retx64 の UEFI ファームウェアは、ブートサービスとランタイムサービスをこの規約と LLP64 データモデルで呼び出します。これらすべてのプラットフォームでの C の型のサイズは ABI の概要にあります。
出典
- Microsoft Learn: x64 calling convention: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: Overview of x64 ABI conventions: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: x64 stack usage: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __vectorcall: コミット
f70d88cd5da7、2026-09-24 - GCC 14.2 manual: x86 Function Attributes: 2026-09-25 の公開版
- Wine 11.0: include/msvcrt/corecrt.h: コミット
db11d0fe6a16、2026-01-13 - EDK II: BaseTools/Conf/tools_def.template: コミット
2970e5699ba6、2026-08-12 - Cygwin User's Guide: Building applications for 64 bit Cygwin: 2026-09-25 の公開版
- Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: コミット
85ac56026243、2026-09-20