System V x86-64 ABI と呼び出し規約
Windows を除くほぼすべての x86-64 システムの呼び出し規約です。どのレジスターが何を運ぶか、構造体がどうレジスターに分けられるか、スタックがどうなっているかを、規則ごとにコンパイルしたコードとともに示します。
System V x86-64 ABI は、System V ABI の AMD64 補足である x86-64 psABI で定められています。Linux、Android、FreeBSD、NetBSD、OpenBSD、DragonFly BSD、illumos と Solaris、Haiku がすべてこれを使い、Intel プロセッサーの macOS も、macOS の節に挙げる少数の相違点を除いてこれを使います。Linux x32 は、ポインターを 32 ビットにした同じ ABI です (後述)。Windows は別の規約である Microsoft x64 を使います。ABI の概要には、どのプラットフォームがどの ABI を使うかと、それぞれでの C の型のサイズがあります。
このページのリストは、Linux 向けの GCC と Clang を -O2 で使ったものと、ページが比較する箇所では macOS、FreeBSD、x32 向けの Clang を使ったものです。各リストの横のボタンで AT&T 構文と Intel 構文を切り替えられます。
レジスター
関数は、スタックポインターと 6 つの汎用レジスターを除くどのレジスターを変更しても構いません。RBX、RBP、R12 から R15 は呼び出し元のもので、これらを使う関数は保存して復元します。ベクトルレジスターはすべてスクラッチ (scratch) で、8 つの x87 レジスターも同様です。表 1 に各レジスターの役割を示します。
| レジスター | 役割 | 保存される |
|---|---|---|
| RDI、RSI、RDX、RCX、R8、R9 | 整数とポインターの引数 1 から 6 (この順) | いいえ |
| RAX | 戻り値。可変長引数の呼び出しでは、AL が使われたベクトルレジスターの数を示す | いいえ |
| RDX | 戻り値の 2 番目の eightbyte にも使う | いいえ |
| RBX、R12、R13、R14 | 呼び出し先保存 | はい |
| RBP | 呼び出し先保存。関数がフレームポインターを使う場合はフレームポインター | はい |
| R15 | 呼び出し先保存。任意で GOT のベース | はい |
| RSP | スタックポインター | はい |
| R10 | 入れ子関数の静的チェーンポインター | いいえ |
| R11 | スクラッチ。PLT スタブが自由に使える | いいえ |
| R16 から R31 (APX) | スクラッチ | いいえ |
| XMM0 から XMM7 | 浮動小数点とベクトルの引数 1 から 8 | いいえ |
| XMM0、XMM1 | 浮動小数点とベクトルの戻り値 | いいえ |
| XMM8 から XMM31 | スクラッチ | いいえ |
| YMM、ZMM | 同じレジスターの幅を広げたもの。__m256 と __m512 用 | いいえ |
| K0 から K7 | スクラッチ (AVX-512 のマスク) | いいえ |
| ST0、ST1 | long double (ST0) と _Complex long double (両方) を返す | いいえ |
| ST2 から ST7、MM0 から MM7 | スクラッチ。呼び出し時に CPU は x87 モードでなければならない | いいえ |
| TMM0 から TMM7 | スクラッチ (AMX のタイル) | いいえ |
| FS | スレッドポインター。スレッドローカルストレージを参照 | はい |
レジスターのほかに、関数は方向フラグをクリアしたままにし、丸めモードなど MXCSR の制御ビットと x87 制御ワードを受け取ったときのまま保たなければなりません。ステータスフラグは保存されません。MMX レジスターを使う関数は、戻る前または別の関数を呼び出す前に EMMS を実行しなければなりません。
リスト 1 は、呼び出しをまたいで引数を RBX に保持します。RBX は呼び出し先保存なので f はそれを変更せずに返しますが、keep は先に呼び出し元の RBX の値を保存しなければなりません。このプッシュは、スタックフレームで説明するように、呼び出しのためにスタックのアラインメントを保つ役目も果たします。
sysv-callee-saved.c
/* x must survive the call to f, so it goes into RBX, which f has to
preserve; RBX itself is saved and restored around it. */
extern long f(long);
long keep(long x)
{
return f(x) + x;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-callee-saved.c
keep:
pushq %rbx
movq %rdi, %rbx
call f@PLT
addq %rbx, %rax
popq %rbx
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-callee-saved.c
keep:
push rbx
mov rbx, rdi
call f@PLT
add rax, rbx
pop rbx
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-callee-saved.c
keep:
pushq %rbx
movq %rdi, %rbx
callq f@PLT
addq %rbx, %rax
popq %rbx
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-callee-saved.c
keep:
push rbx
mov rbx, rdi
call f@PLT
add rax, rbx
pop rbx
ret引数の渡し方
引数は左から右へ割り当てられます。整数とポインターは RDI、RSI、RDX、RCX、R8、R9 のうち次の空きレジスターを使い、float、double、ベクトルは XMM0 から XMM7 のうち次のものを使います。2 つの並びは独立しているので、関数は 6 つの整数と 8 つの浮動小数点値をレジスターで受け取れます。収まらないものはスタックに右から左へプッシュされるので、最初のスタック引数は戻りアドレスのすぐ上に来ます。
sum8 は 7 番目と 8 番目を 8(%rsp) と 16(%rsp) に見つけ、call8 は呼び出しの前にそれらをプッシュします。sysv-int-args.c
/* Eight integer arguments: the first six arrive in registers, the
last two on the stack. */
long sum8(long a, long b, long c, long d, long e, long f, long g, long h)
{
return a + b + c + d + e + f + g + h;
}
extern long take8(long, long, long, long, long, long, long, long);
long call8(void)
{
return take8(1, 2, 3, 4, 5, 6, 7, 8);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-int-args.c
sum8:
addq %rsi, %rdi
addq %rdx, %rdi
addq %rcx, %rdi
addq %r8, %rdi
leaq (%rdi,%r9), %rax
addq 8(%rsp), %rax
addq 16(%rsp), %rax
ret
call8:
subq $8, %rsp
movl $6, %r9d
movl $5, %r8d
movl $4, %ecx
pushq $8
movl $3, %edx
movl $2, %esi
movl $1, %edi
pushq $7
call take8@PLT
addq $24, %rsp
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-int-args.c
sum8:
add rdi, rsi
add rdi, rdx
add rdi, rcx
add rdi, r8
lea rax, [rdi+r9]
add rax, QWORD PTR 8[rsp]
add rax, QWORD PTR 16[rsp]
ret
call8:
sub rsp, 8
mov r9d, 6
mov r8d, 5
mov ecx, 4
push 8
mov edx, 3
mov esi, 2
mov edi, 1
push 7
call take8@PLT
add rsp, 24
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-int-args.c
sum8:
addq %rdi, %rsi
leaq (%rdx,%rcx), %rax
addq %rsi, %rax
addq %r8, %rax
addq %r9, %rax
addq 8(%rsp), %rax
addq 16(%rsp), %rax
retq
call8:
pushq %rax
movl $1, %edi
movl $2, %esi
movl $3, %edx
movl $4, %ecx
movl $5, %r8d
movl $6, %r9d
pushq $8
pushq $7
callq take8@PLT
addq $16, %rsp
popq %rcx
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-int-args.c
sum8:
add rsi, rdi
lea rax, [rdx + rcx]
add rax, rsi
add rax, r8
add rax, r9
add rax, qword ptr [rsp + 8]
add rax, qword ptr [rsp + 16]
ret
call8:
push rax
mov edi, 1
mov esi, 2
mov edx, 3
mov ecx, 4
mov r8d, 5
mov r9d, 6
push 8
push 7
call take8@PLT
add rsp, 16
pop rcx
reti、l、p は RDI、RSI、RDX に、x、f、y は XMM0、XMM1、XMM2 に入ります。sysv-mixed-args.c
/* Integer and floating-point arguments take registers from two
separate sequences. */
extern double mix(int i, double x, long l, float f, char *p, double y);
double call_mix(char *p)
{
return mix(1, 2.0, 3, 4.0f, p, 5.0);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-mixed-args.c
call_mix:
movsd .LC0(%rip), %xmm2
movss .LC2(%rip), %xmm1
movq %rdi, %rdx
movl $3, %esi
movsd .LC1(%rip), %xmm0
movl $1, %edi
jmp mix@PLT
.LC0:
.long 0
.long 1075052544
.LC1:
.long 0
.long 1073741824
.LC2:
.long 1082130432Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-mixed-args.c
call_mix:
movsd xmm2, QWORD PTR .LC0[rip]
movss xmm1, DWORD PTR .LC2[rip]
mov rdx, rdi
mov esi, 3
movsd xmm0, QWORD PTR .LC1[rip]
mov edi, 1
jmp mix@PLT
.LC0:
.long 0
.long 1075052544
.LC1:
.long 0
.long 1073741824
.LC2:
.long 108213043264 ビットより小さい整数はレジスターの下位ビットを占め、psABI は残りのビットを規定していません。そのため受け取る側は、より広い値が必要なら自分で値を拡張しなければなりません。リスト 18 では、x86-64 の load が int のインデックスをアドレスに使う前に movslq で符号拡張しています。この点で GCC と Clang がどう違うかは小さな整数で示します。_Bool は例外で、その最下位 8 ビットは 0 か 1 を保持します。
分類
単純な整数値や浮動小数点値でないものは、まずクラスに分類されます。値の 8 バイトずつの各部分である eightbyte のクラスが、その部分をどこに置くかを決めます。
| クラス | 意味 | 渡す場所 |
|---|---|---|
| INTEGER | 汎用レジスターに収まるもの: 整数、ポインター、_Bool | RDI、RSI、RDX、RCX、R8、R9 のうち次のもの |
| SSE | ベクトルレジスターに収まるもの: float、double、_Float16、__m64 | XMM0 から XMM7 のうち次のもの |
| SSEUP | 下位の eightbyte が SSE であるベクトルの上位部分 | 同じレジスターの残りの部分 |
| X87、X87UP | long double の仮数と指数 | スタック |
| COMPLEX_X87 | _Complex long double | スタック |
| MEMORY | 大きすぎるもの、またはレジスターに適さないもの | スタック |
| NO_CLASS | パディングと空の構造体 | なし |
__m128 や __float128 は SSE の eightbyte 1 つと SSEUP 1 つなので、XMM レジスター 1 つを占めます。__m256 は SSE と 3 つの SSEUP で、プロセッサーに YMM レジスターがあれば 1 つを占めます。__int128 は 2 つの long 値からなる構造体として扱われますが、スタック上では 16 バイトにアラインされる点が異なります。
構造体、共用体、配列は、そのフィールドによって分類されます。
- 64 バイトより大きい値、またはアラインされていないフィールドを持つ値のクラスは MEMORY です。
- ユーザー提供のコピーコンストラクターやデストラクターを持つものなど、呼び出しの観点でトリビアルでない (non-trivial for the purpose of calls) C++ オブジェクトは、コピーへのポインターとして渡され、そのクラスは INTEGER です。
- それ以外では、各 eightbyte は NO_CLASS から始まり、各フィールドが自分の占める eightbyte に自分のクラスを併合します。同じクラス同士ならそのままです。NO_CLASS はもう一方のクラスに譲ります。MEMORY が優先され、次に INTEGER が優先されます。X87、X87UP、COMPLEX_X87 は結果を MEMORY にします。それ以外は SSE です。
- 最後に後処理があります。どれかの eightbyte が MEMORY なら値全体が MEMORY です。X87UP が X87 の後に続かない場合、値全体が MEMORY です。2 つより多い eightbyte からなる値は、SSE の後に SSEUP だけが続くもの以外は MEMORY です。前に SSE がない SSEUP は SSE になります。
実質的に、16 バイトより大きいものは、単独または構造体の中の __m256 か __m512 のベクトル 1 つである場合を除き、MEMORY になります。その後、eightbyte が順に割り当てられます。そのすべてに足りるだけのレジスターが残っていなければ、値全体がスタックに置かれ、レジスターは次の引数のために空いたままになります。
| C の型 | サイズ | eightbyte | 渡す場所 | 示すリスト |
|---|---|---|---|---|
struct pair { long a, b; } | 16 | INTEGER、INTEGER | 2 つの汎用レジスター | リスト 4 |
struct point { double x, y; } | 16 | SSE、SSE | 2 つの XMM レジスター | リスト 4 |
struct mixed { int a, b; double d; } | 16 | INTEGER、SSE | 汎用レジスター 1 つと XMM レジスター 1 つ | リスト 4 |
struct tagged { int tag; float value; } | 8 | INTEGER | 1 つの汎用レジスター | リスト 5 |
struct vec3 { float x, y, z; } | 12 | SSE、SSE | 2 つの XMM レジスター。x と y は 1 つ目に入る | リスト 5 |
struct triple { long a, b, c; } | 24 | MEMORY | スタック | リスト 6 |
__int128 | 16 | INTEGER、INTEGER | 2 つの汎用レジスター、またはスタック | リスト 7 |
long double | 16 | X87、X87UP | スタック | リスト 9 |
union { long double d; void *p; } | 16 | INTEGER、X87UP: MEMORY | スタック | リスト 17 |
struct pair は RDI と RSI に、struct point は XMM0 と XMM1 に入り、struct mixed は 2 つの int が RDI に、double が XMM0 に入ります。sysv-struct-regs.c
/* Structs of up to 16 bytes are split into eightbytes, and each
eightbyte goes to a general-purpose or a vector register. */
struct pair { long a, b; }; /* INTEGER, INTEGER */
struct point { double x, y; }; /* SSE, SSE */
struct mixed { int a, b; double d; }; /* INTEGER, SSE */
long pair_sum(struct pair p)
{
return p.a + p.b;
}
double point_dot(struct point p)
{
return p.x * p.y;
}
double mixed_sum(struct mixed m)
{
return m.a + m.b + m.d;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-struct-regs.c
pair_sum:
leaq (%rdi,%rsi), %rax
ret
point_dot:
mulsd %xmm1, %xmm0
ret
mixed_sum:
movq %rdi, %rax
movapd %xmm0, %xmm1
pxor %xmm0, %xmm0
shrq $32, %rax
addl %edi, %eax
cvtsi2sdl %eax, %xmm0
addsd %xmm1, %xmm0
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-struct-regs.c
pair_sum:
lea rax, [rdi+rsi]
ret
point_dot:
mulsd xmm0, xmm1
ret
mixed_sum:
mov rax, rdi
movapd xmm1, xmm0
pxor xmm0, xmm0
shr rax, 32
add eax, edi
cvtsi2sd xmm0, eax
addsd xmm0, xmm1
retmixed_sum では、2 つの int が RDI を共有します。a は下位半分にあり、b には 32 ビットのシフトが必要です。
struct tagged の int と float は INTEGER の eightbyte になるので、float は RDI の上位半分で渡されます。struct vec3 の z は 2 番目の eightbyte に単独で入り、その eightbyte は XMM1 で渡されます。sysv-struct-merge.c
/* Fields that share an eightbyte merge their classes: an int and a
float make one INTEGER eightbyte, two floats one SSE eightbyte. */
struct tagged { int tag; float value; }; /* INTEGER */
struct vec3 { float x, y, z; }; /* SSE, SSE */
float tagged_value(struct tagged t)
{
return t.value;
}
float vec3_z(struct vec3 v)
{
return v.z;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-struct-merge.c
tagged_value:
shrq $32, %rdi
movd %edi, %xmm0
ret
vec3_z:
movaps %xmm1, %xmm0
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-struct-merge.c
tagged_value:
shr rdi, 32
movd xmm0, edi
ret
vec3_z:
movaps xmm0, xmm1
retsysv-struct-memory.c
/* A struct of more than 16 bytes has class MEMORY: the caller copies
it to the stack. */
struct triple { long a, b, c; };
long triple_sum(struct triple t)
{
return t.a + t.b + t.c;
}
extern long take_triple(struct triple t);
long pass_triple(long x)
{
struct triple t = { x, 2 * x, 3 * x };
return take_triple(t);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-struct-memory.c
triple_sum:
movq 16(%rsp), %rax
addq 8(%rsp), %rax
addq 24(%rsp), %rax
ret
pass_triple:
subq $72, %rsp
leaq (%rdi,%rdi), %rax
movq %rax, 40(%rsp)
addq %rdi, %rax
movq %rdi, 32(%rsp)
movdqa 32(%rsp), %xmm0
movq %rax, 16(%rsp)
movups %xmm0, (%rsp)
call take_triple@PLT
addq $72, %rsp
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-struct-memory.c
triple_sum:
mov rax, QWORD PTR 16[rsp]
add rax, QWORD PTR 8[rsp]
add rax, QWORD PTR 24[rsp]
ret
pass_triple:
sub rsp, 72
lea rax, [rdi+rdi]
mov QWORD PTR 40[rsp], rax
add rax, rdi
mov QWORD PTR 32[rsp], rdi
movdqa xmm0, XMMWORD PTR 32[rsp]
mov QWORD PTR 16[rsp], rax
movups XMMWORD PTR [rsp], xmm0
call take_triple@PLT
add rsp, 72
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-struct-memory.c
triple_sum:
movq 16(%rsp), %rax
addq 8(%rsp), %rax
addq 24(%rsp), %rax
retq
pass_triple:
subq $56, %rsp
movq %rdi, 32(%rsp)
leaq (%rdi,%rdi), %rax
movq %rax, 40(%rsp)
leaq (%rdi,%rdi,2), %rax
movq %rax, 48(%rsp)
movq %rax, 16(%rsp)
movups 32(%rsp), %xmm0
movups %xmm0, (%rsp)
callq take_triple@PLT
addq $56, %rsp
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-struct-memory.c
triple_sum:
mov rax, qword ptr [rsp + 16]
add rax, qword ptr [rsp + 8]
add rax, qword ptr [rsp + 24]
ret
pass_triple:
sub rsp, 56
mov qword ptr [rsp + 32], rdi
lea rax, [rdi + rdi]
mov qword ptr [rsp + 40], rax
lea rax, [rdi + 2*rdi]
mov qword ptr [rsp + 48], rax
mov qword ptr [rsp + 16], rax
movups xmm0, xmmword ptr [rsp + 32]
movups xmmword ptr [rsp], xmm0
call take_triple@PLT
add rsp, 56
ret__int128 はレジスターの組で、下位半分から先に渡されます。late128 では x に R9 しか残っていないので、x はスタックに置かれ、g はそれでも R9 を受け取ります。sysv-int128.c
/* __int128 takes two general-purpose registers. When only one is
left, it goes to the stack whole, and the next argument still gets
the register. */
__int128 add128(__int128 a, __int128 b)
{
return a + b;
}
long late128(long a, long b, long c, long d, long e, __int128 x, long g)
{
return (long)x + g;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-int128.c
add128:
movq %rdx, %rax
movq %rcx, %rdx
addq %rdi, %rax
adcq %rsi, %rdx
ret
late128:
movq 8(%rsp), %rax
addq %r9, %rax
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-int128.c
add128:
mov rax, rdx
mov rdx, rcx
add rax, rdi
adc rdx, rsi
ret
late128:
mov rax, QWORD PTR 8[rsp]
add rax, r9
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-int128.c
add128:
movq %rdi, %rax
addq %rdx, %rax
adcq %rcx, %rsi
movq %rsi, %rdx
retq
late128:
movq %r9, %rax
addq 8(%rsp), %rax
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-int128.c
add128:
mov rax, rdi
add rax, rdx
adc rsi, rcx
mov rdx, rsi
ret
late128:
mov rax, r9
add rax, qword ptr [rsp + 8]
ret値の返し方
戻り値は引数と同じように分類され、そのうえで次のようになります。
- INTEGER の eightbyte は RAX、次に RDX で返り、SSE の eightbyte は XMM0、次に XMM1 で返ります。
doubleとlongからなる構造体は XMM0 と RAX で返ります。 - X87 の値である
long doubleは ST0 で返り、_Complex long doubleは ST0 と ST1 で返ります。 - MEMORY の値は、呼び出し元が用意したメモリを通して返されます。呼び出し元はそのアドレスを最初の隠し引数として RDI で渡し、本来の引数はレジスター 1 つ分後ろにずれます。関数は同じアドレスを RAX で返します。
ret_triple は結果のアドレスを RDI で受け取るので、引数 x は RSI で渡されます。sysv-returns.c
/* Return values are classified like arguments. */
struct pair { long a, b; };
struct dl { double d; long l; };
struct triple { long a, b, c; };
struct pair ret_pair(long a, long b)
{
return (struct pair){ a, b };
}
struct dl ret_dl(double d, long l)
{
return (struct dl){ d, l };
}
struct triple ret_triple(long x)
{
return (struct triple){ x, x, x };
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-returns.c
ret_pair:
movq %rdi, %rax
movq %rsi, %rdx
ret
ret_dl:
movq %rdi, %rax
ret
ret_triple:
movq %rsi, %xmm0
movq %rsi, 16(%rdi)
movq %rdi, %rax
movdqa %xmm0, %xmm1
punpcklqdq %xmm1, %xmm1
movups %xmm1, (%rdi)
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-returns.c
ret_pair:
mov rax, rdi
mov rdx, rsi
ret
ret_dl:
mov rax, rdi
ret
ret_triple:
movq xmm0, rsi
mov QWORD PTR 16[rdi], rsi
mov rax, rdi
movdqa xmm1, xmm0
punpcklqdq xmm1, xmm1
movups XMMWORD PTR [rdi], xmm1
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-returns.c
ret_pair:
movq %rsi, %rdx
movq %rdi, %rax
retq
ret_dl:
movq %rdi, %rax
retq
ret_triple:
movq %rdi, %rax
movq %rsi, (%rdi)
movq %rsi, 8(%rdi)
movq %rsi, 16(%rdi)
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-returns.c
ret_pair:
mov rdx, rsi
mov rax, rdi
ret
ret_dl:
mov rax, rdi
ret
ret_triple:
mov rax, rdi
mov qword ptr [rdi], rsi
mov qword ptr [rdi + 8], rsi
mov qword ptr [rdi + 16], rsi
retlong double は例外的です。この型の引数は常にスタックで渡されますが、結果は x87 のレジスタースタックで返ります。リスト 9 は、そうした引数 2 つを 16 バイト離れた位置でスタックから読み、そこで掛け合わせます。XMM レジスターと x87 スタックの間で値を移す命令はないので、double の変換にはメモリ、ここではレッドゾーンを経由する必要があります。
long double 引数と、ST0 の結果sysv-long-double.c
/* long double travels in memory and returns on the x87 stack. */
long double ld_mul(long double a, long double b)
{
return a * b;
}
long double ld_from(double d)
{
return d;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-long-double.c
ld_mul:
fldt 8(%rsp)
fldt 24(%rsp)
fmulp %st, %st(1)
ret
ld_from:
movsd %xmm0, -16(%rsp)
fldl -16(%rsp)
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-long-double.c
ld_mul:
fld TBYTE PTR 8[rsp]
fld TBYTE PTR 24[rsp]
fmulp st(1), st
ret
ld_from:
movsd QWORD PTR -16[rsp], xmm0
fld QWORD PTR -16[rsp]
retスタックフレーム
スタックは下へ伸びます。call 命令の時点で RSP は 16 の倍数でなければならないので、関数に入った時点、つまり戻りアドレスがプッシュされた後では RSP + 8 が 16 の倍数です。__m256 または __m512 の引数をスタックで渡す場合、この境界は 32 バイトまたは 64 バイトになります。この規則がなければ、スタック上のデータに対するアラインされた SSE のロードとストアがフォルトを起こすおそれがあります。
| アドレス | 内容 | フレーム |
|---|---|---|
16(%rbp) 以上 | メモリで渡される引数。最初のものが最も低いアドレス | 呼び出し元 |
8(%rbp) | call がプッシュした戻りアドレス | 呼び出し先 |
0(%rbp) | 関数がフレームポインターを使う場合、保存した呼び出し元の RBP | 呼び出し先 |
| その下 | 保存したレジスターとローカル変数 | 呼び出し先 |
0(%rsp) | 次の呼び出しに渡す引数。16 バイトにアラインされる | 呼び出し先 |
| RSP より下の 128 バイト | レッドゾーン | 呼び出し先 |
フレームポインターは任意です。最適化を有効にすると、Linux 向けの GCC と Clang は RSP を通してフレームにアクセスし、RBP を空けておきます。ただし、可変長配列や alloca のようにフレームのサイズが実行時に変わる場合は別です。
sysv-frame.c
/* A variable-length array needs a frame pointer, and its size is
rounded up to keep RSP 16-byte aligned for the call. */
extern void use(char *buf, long n);
void frame(long n)
{
char buf[n];
use(buf, n);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-frame.c
frame:
pushq %rbp
leaq 15(%rdi), %rax
movq %rdi, %rsi
andq $-16, %rax
movq %rsp, %rbp
subq %rax, %rsp
movq %rsp, %rdi
call use@PLT
leave
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-frame.c
frame:
push rbp
lea rax, 15[rdi]
mov rsi, rdi
and rax, -16
mov rbp, rsp
sub rsp, rax
mov rdi, rsp
call use@PLT
leave
retリスト 2 では、call8 は 2 つのスタック引数のために 16 バイトを必要とします。関数に入った時点で RSP は 16 の倍数より 8 バイトずれているので、GCC はまず 8 を引き、次に 8 バイトの引数を 2 つプッシュし、呼び出しは 16 の倍数の位置で行われます。呼び出しの後、addq $24 が 3 つすべてを取り除きます。リスト 1 では、RBX のプッシュが同じ役目を果たします。
レッドゾーン
RSP より下の 128 バイトがレッドゾーン (red zone) です。シグナルハンドラーと割り込みハンドラーはこの領域を変更してはならないので、関数は呼び出しをまたいで必要としないデータを、RSP を動かさずにそこに置けます。リーフ関数はフレーム全体をそこに置けます。
leaf は配列を RSP より下に格納します。-mno-red-zone では、先に RSP を下げ、最後に元に戻さなければなりません。sysv-red-zone.c
/* A leaf function can keep its locals below RSP, in the red zone. */
int leaf(int x)
{
volatile int tmp[4];
tmp[0] = x;
tmp[1] = x * 3;
tmp[2] = x * 5;
tmp[3] = x * 7;
return tmp[x & 3];
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-red-zone.c
leaf:
leal (%rdi,%rdi,2), %eax
movl %edi, -24(%rsp)
movl %eax, -20(%rsp)
leal (%rdi,%rdi,4), %eax
movl %eax, -16(%rsp)
leal 0(,%rdi,8), %eax
subl %edi, %eax
andl $3, %edi
movl %eax, -12(%rsp)
movl -24(%rsp,%rdi,4), %eax
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-red-zone.c
leaf:
lea eax, [rdi+rdi*2]
mov DWORD PTR -24[rsp], edi
mov DWORD PTR -20[rsp], eax
lea eax, [rdi+rdi*4]
mov DWORD PTR -16[rsp], eax
lea eax, 0[0+rdi*8]
sub eax, edi
and edi, 3
mov DWORD PTR -12[rsp], eax
mov eax, DWORD PTR -24[rsp+rdi*4]
retGCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mno-red-zone -S sysv-red-zone.c
leaf:
subq $24, %rsp
leal (%rdi,%rdi,2), %eax
movl %edi, (%rsp)
movl %eax, 4(%rsp)
leal (%rdi,%rdi,4), %eax
movl %eax, 8(%rsp)
leal 0(,%rdi,8), %eax
subl %edi, %eax
andl $3, %edi
movl %eax, 12(%rsp)
movl (%rsp,%rdi,4), %eax
addq $24, %rsp
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mno-red-zone -masm=intel -S sysv-red-zone.c
leaf:
sub rsp, 24
lea eax, [rdi+rdi*2]
mov DWORD PTR [rsp], edi
mov DWORD PTR 4[rsp], eax
lea eax, [rdi+rdi*4]
mov DWORD PTR 8[rsp], eax
lea eax, 0[0+rdi*8]
sub eax, edi
and edi, 3
mov DWORD PTR 12[rsp], eax
mov eax, DWORD PTR [rsp+rdi*4]
add rsp, 24
retオペレーティングシステムのカーネルやファームウェアのように、割り込みが同じスタックにプッシュする場所で動くコードは、レッドゾーンに頼ってはならず、-mno-red-zone でコンパイルされます。たとえば EDK II は x64 の UEFI ファームウェアをそのようにコンパイルします。
可変長引数関数
printf のような可変長引数関数の呼び出しは、ほかの呼び出しと同じレジスターを使い、さらに隠し引数を 1 つ加えます。AL には、引数を運ぶベクトルレジスターの数の上限が 0 から 8 で入ります。RAX の残りの部分は未定義です。プロトタイプのない関数の呼び出しでも AL を設定します。その関数が可変長引数関数かもしれないからです。
sysv-varargs-call.c
/* Calling a variadic function: AL says how many vector registers
carry arguments. */
extern int log_msg(const char *fmt, ...);
int call_ints(void)
{
return log_msg("%d %d", 1, 2);
}
int call_double(double x)
{
return log_msg("%f %d", x, 3);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-varargs-call.c
.LC0:
.string "%d %d"
call_ints:
movl $2, %edx
movl $1, %esi
leaq .LC0(%rip), %rdi
xorl %eax, %eax
jmp log_msg@PLT
.LC1:
.string "%f %d"
call_double:
movl $3, %esi
leaq .LC1(%rip), %rdi
movl $1, %eax
jmp log_msg@PLTIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-varargs-call.c
.LC0:
.string "%d %d"
call_ints:
mov edx, 2
mov esi, 1
lea rdi, .LC0[rip]
xor eax, eax
jmp log_msg@PLT
.LC1:
.string "%f %d"
call_double:
mov esi, 3
lea rdi, .LC1[rip]
mov eax, 1
jmp log_msg@PLTClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-varargs-call.c
call_ints:
leaq .L.str(%rip), %rdi
movl $1, %esi
movl $2, %edx
xorl %eax, %eax
jmp log_msg@PLT
call_double:
leaq .L.str.1(%rip), %rdi
movl $3, %esi
movb $1, %al
jmp log_msg@PLT
.L.str:
.asciz "%d %d"
.L.str.1:
.asciz "%f %d"Intel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-varargs-call.c
call_ints:
lea rdi, [rip + .L.str]
mov esi, 1
mov edx, 2
xor eax, eax
jmp log_msg@PLT
call_double:
lea rdi, [rip + .L.str.1]
mov esi, 3
mov al, 1
jmp log_msg@PLT
.L.str:
.asciz "%d %d"
.L.str.1:
.asciz "%f %d"可変長引数関数は名前のない引数がどこにあるかを知ることができないので、そのプロローグは引数レジスターを 176 バイトのレジスター保存領域に保存します。RDI から R9 をオフセット 0 から 40 に、続いて XMM0 から XMM7 をオフセット 48 から 160 に、それぞれ 16 バイトずつ置きます。AL が 0 のときは、ベクトルレジスターの保存を飛ばします。va_list は、次の引数がどこにあるかを記録する構造体 1 つからなる配列です。
va_list のフィールド。va_list は配列なので、それを受け取る関数はそれへのポインターを受け取ります。| オフセット | フィールド | 意味 |
|---|---|---|
| 0 | unsigned int gp_offset | 保存領域内の次の汎用レジスターのオフセット。使い切ると 48 |
| 4 | unsigned int fp_offset | 保存領域内の次のベクトルレジスターのオフセット。使い切ると 176 |
| 8 | void *overflow_arg_area | スタックで渡された次の引数のアドレス |
| 16 | void *reg_save_area | レジスター保存領域のアドレス |
log_msg の va_start。RSI から R9 と、AL が 0 でなければ XMM0 から XMM7 が、32(%rsp) のレジスター保存領域に置かれます。fmt が RDI を使ったので、gp_offset は 8 から、fp_offset は 48 から始まります。Clang は両方を 0x3000000008 の 1 つの movabsq で格納します。sysv-varargs.c
/* va_start: the prologue saves the argument registers in the register
save area, and the va_list records where the next argument is. */
#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;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-varargs.c
log_msg:
subq $216, %rsp
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
testb %al, %al
je .L3
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.L3:
leaq 224(%rsp), %rax
leaq 8(%rsp), %rsi
movl $8, 8(%rsp)
movq %rax, 16(%rsp)
leaq 32(%rsp), %rax
movl $48, 12(%rsp)
movq %rax, 24(%rsp)
call vlog@PLT
addq $216, %rsp
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-varargs.c
log_msg:
sub rsp, 216
mov QWORD PTR 40[rsp], rsi
mov QWORD PTR 48[rsp], rdx
mov QWORD PTR 56[rsp], rcx
mov QWORD PTR 64[rsp], r8
mov QWORD PTR 72[rsp], r9
test al, al
je .L3
movaps XMMWORD PTR 80[rsp], xmm0
movaps XMMWORD PTR 96[rsp], xmm1
movaps XMMWORD PTR 112[rsp], xmm2
movaps XMMWORD PTR 128[rsp], xmm3
movaps XMMWORD PTR 144[rsp], xmm4
movaps XMMWORD PTR 160[rsp], xmm5
movaps XMMWORD PTR 176[rsp], xmm6
movaps XMMWORD PTR 192[rsp], xmm7
.L3:
lea rax, 224[rsp]
lea rsi, 8[rsp]
mov DWORD PTR 8[rsp], 8
mov QWORD PTR 16[rsp], rax
lea rax, 32[rsp]
mov DWORD PTR 12[rsp], 48
mov QWORD PTR 24[rsp], rax
call vlog@PLT
add rsp, 216
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-varargs.c
log_msg:
subq $216, %rsp
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
testb %al, %al
je .LBB0_2
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.LBB0_2:
leaq 32(%rsp), %rax
movq %rax, 16(%rsp)
leaq 224(%rsp), %rax
movq %rax, 8(%rsp)
movabsq $206158430216, %rax
movq %rax, (%rsp)
movq %rsp, %rsi
callq vlog@PLT
addq $216, %rsp
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-varargs.c
log_msg:
sub rsp, 216
mov qword ptr [rsp + 40], rsi
mov qword ptr [rsp + 48], rdx
mov qword ptr [rsp + 56], rcx
mov qword ptr [rsp + 64], r8
mov qword ptr [rsp + 72], r9
test al, al
je .LBB0_2
movaps xmmword ptr [rsp + 80], xmm0
movaps xmmword ptr [rsp + 96], xmm1
movaps xmmword ptr [rsp + 112], xmm2
movaps xmmword ptr [rsp + 128], xmm3
movaps xmmword ptr [rsp + 144], xmm4
movaps xmmword ptr [rsp + 160], xmm5
movaps xmmword ptr [rsp + 176], xmm6
movaps xmmword ptr [rsp + 192], xmm7
.LBB0_2:
lea rax, [rsp + 32]
mov qword ptr [rsp + 16], rax
lea rax, [rsp + 224]
mov qword ptr [rsp + 8], rax
movabs rax, 206158430216
mov qword ptr [rsp], rax
mov rsi, rsp
call vlog@PLT
add rsp, 216
retva_arg(ap, int)。gp_offset がもう 1 つレジスターを許す間はレジスター保存領域から、その後は overflow_arg_area から 8 バイトずつ読みます。sysv-va-arg.c
/* va_arg: take the next int from the register save area while
gp_offset is below 48, then from the stack. */
#include <stdarg.h>
int next_int(va_list ap)
{
return va_arg(ap, int);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-va-arg.c
next_int:
movl (%rdi), %eax
cmpl $47, %eax
ja .L2
movl %eax, %edx
addl $8, %eax
addq 16(%rdi), %rdx
movl %eax, (%rdi)
movl (%rdx), %eax
ret
.L2:
movq 8(%rdi), %rdx
leaq 8(%rdx), %rax
movq %rax, 8(%rdi)
movl (%rdx), %eax
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-va-arg.c
next_int:
mov eax, DWORD PTR [rdi]
cmp eax, 47
ja .L2
mov edx, eax
add eax, 8
add rdx, QWORD PTR 16[rdi]
mov DWORD PTR [rdi], eax
mov eax, DWORD PTR [rdx]
ret
.L2:
mov rdx, QWORD PTR 8[rdi]
lea rax, 8[rdx]
mov QWORD PTR 8[rdi], rax
mov eax, DWORD PTR [rdx]
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-va-arg.c
next_int:
movl (%rdi), %ecx
cmpq $40, %rcx
ja .LBB0_2
movq %rcx, %rax
addq 16(%rdi), %rax
addl $8, %ecx
movl %ecx, (%rdi)
movl (%rax), %eax
retq
.LBB0_2:
movq 8(%rdi), %rax
leaq 8(%rax), %rcx
movq %rcx, 8(%rdi)
movl (%rax), %eax
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-va-arg.c
next_int:
mov ecx, dword ptr [rdi]
cmp rcx, 40
ja .LBB0_2
mov rax, rcx
add rax, qword ptr [rdi + 16]
add ecx, 8
mov dword ptr [rdi], ecx
mov eax, dword ptr [rax]
ret
.LBB0_2:
mov rax, qword ptr [rdi + 8]
lea rcx, [rax + 8]
mov qword ptr [rdi + 8], rcx
mov eax, dword ptr [rax]
ret32 バイトと 64 バイトのベクトル引数は、名前のない引数であれば常にスタックで渡され、それらを可変長引数関数に渡すにはプロトタイプが必要です。
小さな整数
リスト 15 は short を関数に渡し、int に広げて返します。どちらのコンパイラーも呼び出しの前に引数を 32 ビットに符号拡張しますが、呼び出された関数の中で再び拡張するのは GCC だけです。Clang は RDI の下位 32 ビットをそのまま使います。psABI は呼び出し元に拡張を求めていないので、Clang がコンパイルした関数は、GCC と Clang の呼び出し元が提供はするものの psABI が約束していないことに依存しています。macOS では、Apple は呼び出し元による拡張を ABI の規則にしています。これは次の節で説明します。
short の引数。GCC は呼び出し元と呼び出し先の両方で拡張し、Clang は呼び出し元だけで拡張します。sysv-small-int.c
/* A short argument: who extends it to 32 bits? */
int widen(short s)
{
return s;
}
extern int take_short(short s);
int pass_short(int x)
{
return take_short(x);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S sysv-small-int.c
widen:
movswl %di, %eax
ret
pass_short:
movswl %di, %edi
jmp take_short@PLTIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-small-int.c
widen:
movsx eax, di
ret
pass_short:
movsx edi, di
jmp take_short@PLTClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S sysv-small-int.c
widen:
movl %edi, %eax
retq
pass_short:
movswl %di, %edi
jmp take_short@PLTIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-small-int.c
widen:
mov eax, edi
ret
pass_short:
movsx edi, di
jmp take_short@PLTClang 23.1.2 x86_64-apple-macos11
AT&T 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -S sysv-small-int.c
_widen:
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
popq %rbp
retq
_pass_short:
pushq %rbp
movq %rsp, %rbp
movswl %di, %edi
popq %rbp
jmp _take_shortIntel 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -masm=intel -S sysv-small-int.c
_widen:
push rbp
mov rbp, rsp
mov eax, edi
pop rbp
ret
_pass_short:
push rbp
mov rbp, rsp
movsx edi, di
pop rbp
jmp _take_shortmacOS
Intel プロセッサーの macOS は、LP64 データモデルで psABI に従います。ただし、Apple のコンパイラーが昔から異なる動作をしてきた点は例外です。Apple は次の相違点を文書化しています。
- 小さな整数の引数
- 呼び出し元は、
intより小さい引数を、より小さい基になる型を持つenumも含めて 32 ビットに拡張し、呼び出し先はそれに依存して構いません。戻り値とスタック上の引数についても同じです。リスト 15 にその効果が表れています。macOS 向けの Clang はwidenを拡張なしでコンパイルします。 - 8 バイト以下のベクトル
- 8 バイトより小さいベクトルは INTEGER です。8 バイトのベクトルのうち、
double1 つのベクトルは MEMORY、64 ビット整数 1 つのベクトルは INTEGER、それ以外はすべて SSE です。psABI は__m64のように 8 バイトのベクトルをすべて SSE にしています。64 ビット整数 1 つのベクトルについては、Clang は FreeBSD でも Apple の規則を使います。
long long 1 つのベクトル。Linux は XMM0 で渡し、macOS と FreeBSD は x がすでにある RDI で渡します。Clang はこの 2 つのターゲットではどちらもフレームポインターを保持するので、RBP のプッシュとポップがあります。macos-vector64.c
/* An 8-byte vector of one 64-bit integer, like MMX's __m64. */
typedef long long v1di __attribute__((vector_size(8)));
extern void take_v1di(v1di v);
void pass_v1di(long long x)
{
take_v1di((v1di){ x });
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S macos-vector64.c
pass_v1di:
movq %rdi, %xmm0
jmp take_v1di@PLTIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-vector64.c
pass_v1di:
movq xmm0, rdi
jmp take_v1di@PLTClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S macos-vector64.c
pass_v1di:
movq %rdi, %xmm0
jmp take_v1di@PLTIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-vector64.c
pass_v1di:
movq xmm0, rdi
jmp take_v1di@PLTClang 23.1.2 x86_64-apple-macos11
AT&T 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -S macos-vector64.c
_pass_v1di:
pushq %rbp
movq %rsp, %rbp
popq %rbp
jmp _take_v1diIntel 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-vector64.c
_pass_v1di:
push rbp
mov rbp, rsp
pop rbp
jmp _take_v1diClang 23.1.2 x86_64-unknown-freebsd14
AT&T 構文 clang --target=x86_64-unknown-freebsd14 -O2 -fno-asynchronous-unwind-tables -S macos-vector64.c
pass_v1di:
pushq %rbp
movq %rsp, %rbp
popq %rbp
jmp take_v1diIntel 構文 clang --target=x86_64-unknown-freebsd14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-vector64.c
pass_v1di:
push rbp
mov rbp, rsp
pop rbp
jmp take_v1di- ほかのフィールドと並ぶ
long double - X87UP が X87 の後に続かないときに値を MEMORY にする後処理は行われません。代わりに、そのような X87UP の eightbyte は SSE になります。そのため リスト 17 の共用体は、スタックではなく RDI と XMM0 で渡されます。
long double とポインターの共用体。Linux 向けの GCC と Clang はそれをスタックから読み、macOS 向けの Clang は p を RDI に見つけます。macos-x87-union.c
/* A union whose second eightbyte is only the x87 exponent. */
typedef union { long double d; void *p; } odd;
void *odd_p(odd u)
{
return u.p;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S macos-x87-union.c
odd_p:
movq 8(%rsp), %rax
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-x87-union.c
odd_p:
mov rax, QWORD PTR 8[rsp]
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S macos-x87-union.c
odd_p:
movq 8(%rsp), %rax
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-x87-union.c
odd_p:
mov rax, qword ptr [rsp + 8]
retClang 23.1.2 x86_64-apple-macos11
AT&T 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -S macos-x87-union.c
_odd_p:
pushq %rbp
movq %rsp, %rbp
movq %rdi, %rax
popq %rbp
retqIntel 構文 clang --target=x86_64-apple-macos11 -O2 -fno-asynchronous-unwind-tables -masm=intel -S macos-x87-union.c
_odd_p:
push rbp
mov rbp, rsp
mov rax, rdi
pop rbp
retApple は、単純な C 以外のコードについての規約も文書化しています。
- C++ の
thread_local変数の初期化関数 (_ZTH...と_ZTW...) は、RCX、RDX、RSI、R8、R9、R10、R11 も保存します。 - Swift は、非同期関数のフレームに R14 を使うなど、独自の目的にレジスターを使います。
- Clang の拡張である任意の要素数のベクトルは、サイズとして次の 2 のべき乗を取ります。ARC の
__weakフィールドを持つ構造体は、メモリで渡され、メモリで返されます。
macOS のリストは、ABI が求めていない点でも異なります。シンボルはアンダースコアで始まり、Clang はこのターゲットではデフォルトでフレームポインターを保持するので、すべての関数に push rbp と mov rbp, rsp が加わります。Apple は macOS Catalina 以降、32 ビットの Intel コードをサポートしていません。
Linux x32
x32 は、プログラムを 64 ビットモードで、16 個の汎用レジスターすべてとこのページの呼び出し規約を使って実行しますが、データモデルは ILP32 です。long とポインターは 32 ビットで、プログラムはアドレス空間の下位 4 GB に置かれます。psABI は規則を 1 つ加えています。レジスターで渡される、または返されるポインターは、ビット 32 から 63 がクリアされています。
leal と addl はすでに正しい形のポインターを生成します。それでも GCC は pass_ptr で RDI の上位半分を再びクリアします。load はアドレスに 32 ビットレジスターを使います。x32-pointers.c
/* Pointers and long have 32 bits in x32, in 64-bit registers. */
long *next(long *p)
{
return p + 1;
}
long load(long *p, int i)
{
return p[i];
}
extern void take_ptr(void *p);
void pass_ptr(char *base, int off)
{
take_ptr(base + off);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnux32
AT&T 構文 gcc-14 -mx32 -O2 -fno-asynchronous-unwind-tables -S x32-pointers.c
next:
leal 4(%rdi), %eax
ret
load:
movl (%edi,%esi,4), %eax
ret
pass_ptr:
addl %esi, %edi
movl %edi, %edi
jmp take_ptr@PLTIntel 構文 gcc-14 -mx32 -O2 -fno-asynchronous-unwind-tables -masm=intel -S x32-pointers.c
next:
lea eax, 4[rdi]
ret
load:
mov eax, DWORD PTR [edi+esi*4]
ret
pass_ptr:
add edi, esi
mov edi, edi
jmp take_ptr@PLTClang 23.1.2 x86_64-linux-gnux32
AT&T 構文 clang --target=x86_64-linux-gnux32 -O2 -fno-asynchronous-unwind-tables -S x32-pointers.c
next:
leal 4(%rdi), %eax
retq
load:
movl (%edi,%esi,4), %eax
retq
pass_ptr:
addl %esi, %edi
jmp take_ptr@PLTIntel 構文 clang --target=x86_64-linux-gnux32 -O2 -fno-asynchronous-unwind-tables -masm=intel -S x32-pointers.c
next:
lea eax, [rdi + 4]
ret
load:
mov eax, dword ptr [edi + 4*esi]
ret
pass_ptr:
add edi, esi
jmp take_ptr@PLTGCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S x32-pointers.c
next:
leaq 8(%rdi), %rax
ret
load:
movslq %esi, %rsi
movq (%rdi,%rsi,8), %rax
ret
pass_ptr:
movslq %esi, %rsi
addq %rsi, %rdi
jmp take_ptr@PLTIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S x32-pointers.c
next:
lea rax, 8[rdi]
ret
load:
movsx rsi, esi
mov rax, QWORD PTR [rdi+rsi*8]
ret
pass_ptr:
movsx rsi, esi
add rdi, rsi
jmp take_ptr@PLTほかの規則は、型が小さくなったことから導かれます。long long や double は依然としてレジスター 1 つを使い、long とポインターはレジスター 1 つのうち 32 ビットを使います。va_list は 16 バイトに縮み、long double と __int128 は 16 バイトのままです。サイズは ABI の概要に挙げています。
出典
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: コミット
e1ce098331da、2025-03-12 - Apple Developer: Writing 64-bit Intel code for Apple Platforms: 2026-09-25 の公開版
- Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: コミット
85ac56026243、2026-09-20 - LLVM 23.1.2: llvm/lib/Target/X86/X86CallingConv.td: コミット
85ac56026243、2026-09-20 - GCC 14.2.0: gcc/config/i386/i386.cc: コミット
04696df09633、2024-08-01 - EDK II: BaseTools/Conf/tools_def.template: コミット
2970e5699ba6、2026-08-12