呼び出し規約における AVX、AVX-512、APX、AMX
x86-64 に新しいレジスターセットが加わるたびに、呼び出しのための規則が必要になります。psABI と Microsoft が定める規則もあれば、オペレーティングシステムが定める規則もあり、呼び出し元と呼び出し先の両方のコンパイラーオプションに依存する規則も 1 つあります。
x86-64 の ABI は SSE を前提に書かれ、それ以降の各拡張は、呼び出しで扱わなければならないレジスターを加えてきました。AVX と AVX-512 ではより幅の広いベクトルレジスター、マスクレジスター、APX では 16 本の汎用レジスターの追加、そして AMX のタイルです。このページでは、64 ビットの 2 つの ABI、System V と Microsoft x64 からそれらに関する規則を集めています。命令は拡張のページで説明しています。
__m256 と __m512 の引数
System V では、__m256 引数は SSE クラスの eightbyte 1 つと、それに続く SSEUP クラスの eightbyte 3 つからなり、YMM レジスター 1 つを満たします。__m512 は ZMM レジスター 1 つを満たします。これが成り立つのは、それらのレジスターを持つプロセッサー向けにコードをコンパイルした場合だけです。AVX なしでコンパイルすると、同じ関数はベクトルをメモリで受け取るため、呼び出し元と呼び出し先は同じベクトルオプションでコンパイルしなければなりません。GCC と Clang はそのような関数について「AVX vector argument without AVX enabled changes the ABI」と警告します (-Wpsabi)。可変長引数の呼び出しでは、これらの型はいずれにしてもスタックで渡されます。
-mavx では YMM0 と YMM1 を使います。AVX を有効にした Windows 向けの Clang は、関数が __vectorcall でない限り、それらを RCX と RDX で参照渡しします。vec256.c
/* A 32-byte vector, like AVX's __m256. Whether it travels in a YMM
register depends on the processor the code is compiled for. */
typedef float v8sf __attribute__((vector_size(32)));
v8sf add8(v8sf a, v8sf b)
{
return a + b;
}
#ifdef _WIN32
v8sf __vectorcall add8_vc(v8sf a, v8sf b)
{
return a + b;
}
#endif
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S vec256.c
add8:
movaps 8(%rsp), %xmm0
addps 40(%rsp), %xmm0
movq %rdi, %rax
movaps %xmm0, (%rdi)
movaps 24(%rsp), %xmm0
addps 56(%rsp), %xmm0
movaps %xmm0, 16(%rdi)
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S vec256.c
add8:
movaps xmm0, XMMWORD PTR 8[rsp]
addps xmm0, XMMWORD PTR 40[rsp]
mov rax, rdi
movaps XMMWORD PTR [rdi], xmm0
movaps xmm0, XMMWORD PTR 24[rsp]
addps xmm0, XMMWORD PTR 56[rsp]
movaps XMMWORD PTR 16[rdi], xmm0
retGCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx -S vec256.c
add8:
vaddps %ymm1, %ymm0, %ymm0
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx -masm=intel -S vec256.c
add8:
vaddps ymm0, ymm0, ymm1
retClang 23.1.2 x86_64-pc-windows-msvc
AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -mavx -S vec256.c
add8:
vmovaps (%rcx), %ymm0
vaddps (%rdx), %ymm0, %ymm0
retq
add8_vc@@64:
vaddps %ymm1, %ymm0, %ymm0
retqIntel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -mavx -masm=intel -S vec256.c
add8:
vmovaps ymm0, ymmword ptr [rcx]
vaddps ymm0, ymm0, ymmword ptr [rdx]
ret
add8_vc@@64:
vaddps ymm0, ymm0, ymm1
ret__m512 の値は ZMM0 と ZMM1 で届き、ZMM0 で返ります。vec512.c
/* A 64-byte vector, like AVX-512's __m512, in ZMM registers. */
typedef float v16sf __attribute__((vector_size(64)));
v16sf add16(v16sf a, v16sf b)
{
return a + b;
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx512f -S vec512.c
add16:
vaddps %zmm1, %zmm0, %zmm0
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx512f -masm=intel -S vec512.c
add16:
vaddps zmm0, zmm0, zmm1
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx512f -S vec512.c
add16:
vaddps %zmm1, %zmm0, %zmm0
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx512f -masm=intel -S vec512.c
add16:
vaddps zmm0, zmm0, zmm1
retMicrosoft x64 は、プロセッサーにかかわらず、8 バイトより幅の広いすべてのベクトルを参照渡しします。__m256 の値を YMM レジスターで渡すのは __vectorcall だけです。Apple の macOS のドキュメントは、System V にも同じ依存関係があることを指摘しています。16 バイトより幅の広いベクトルの渡し方は、コードのコンパイル対象のプロセッサー機能によって決まります。
VZEROUPPER
ABI は、呼び出しをまたいで YMM レジスターと ZMM レジスターの上位半分を誰が所有するかを定めていません。上位半分はスクラッチ (scratch) です。しかし、プロセッサーは上位半分にデータがある間は古い SSE エンコーディングの命令を遅く実行するため、コンパイラーは、それらを使った関数が戻る前、または SSE を使うかもしれないほかの関数を呼び出す前に、VZEROUPPER でそれらをクリアします。GCC と Clang は AVX 向けにコンパイルするとき、デフォルトでこれを行います。GCC のマニュアルは -mvzeroupper オプションの項でその理由を説明しています。
square は YMM0 で計算し、next にジャンプする前に VZEROUPPER を実行します。vzeroupper.c
/* After using the upper halves of the YMM registers, a function clears
them with VZEROUPPER before it calls or returns to code that may use
SSE instructions. */
typedef float v8sf __attribute__((vector_size(32)));
extern void next(void);
void square(v8sf *p)
{
*p = *p * *p;
next();
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx -S vzeroupper.c
square:
vmovaps (%rdi), %ymm0
vmulps %ymm0, %ymm0, %ymm0
vmovaps %ymm0, (%rdi)
vzeroupper
jmp next@PLTIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx -masm=intel -S vzeroupper.c
square:
vmovaps ymm0, YMMWORD PTR [rdi]
vmulps ymm0, ymm0, ymm0
vmovaps YMMWORD PTR [rdi], ymm0
vzeroupper
jmp next@PLTClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx -S vzeroupper.c
square:
vmovaps (%rdi), %ymm0
vmulps %ymm0, %ymm0, %ymm0
vmovaps %ymm0, (%rdi)
vzeroupper
jmp next@PLTIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx -masm=intel -S vzeroupper.c
square:
vmovaps ymm0, ymmword ptr [rdi]
vmulps ymm0, ymm0, ymm0
vmovaps ymmword ptr [rdi], ymm0
vzeroupper
jmp next@PLTAVX-512 のマスクレジスター
8 つのマスクレジスター K0 から K7 は、System V ではスクラッチです。関数はそれらを変更でき、どれも引数や結果を運びません。関数に渡すマスクは 16 ビットの __mmask16 などの整数で、汎用レジスターで渡されます。Microsoft の呼び出し規約はマスクレジスターに触れておらず、XMM16 から XMM31 をスクラッチとしています。
avx512-mask.c
/* AVX-512 compares produce a mask in a K register, which selects
elements; the mask registers are scratch in every ABI. */
typedef int v16si __attribute__((vector_size(64)));
v16si pick(v16si a, v16si b, v16si c)
{
v16si m = a > b;
return (m & a) | (~m & c);
}
GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu
AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx512f -S avx512-mask.c
pick:
vpcmpd $6, %zmm1, %zmm0, %k1
vpblendmd %zmm0, %zmm2, %zmm0{%k1}
retIntel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -mavx512f -masm=intel -S avx512-mask.c
pick:
vpcmpd k1, zmm0, zmm1, 6
vpblendmd zmm0{k1}, zmm2, zmm0
retClang 23.1.2 x86_64-linux-gnu
AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx512f -S avx512-mask.c
pick:
vpcmpgtd %zmm1, %zmm0, %k1
vpblendmd %zmm0, %zmm2, %zmm0 {%k1}
retqIntel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -mavx512f -masm=intel -S avx512-mask.c
pick:
vpcmpgtd k1, zmm0, zmm1
vpblendmd zmm0 {k1}, zmm2, zmm0
retAPX: R16 から R31
APX は 16 本の汎用レジスターを追加します。64 ビットの 2 つの ABI は、これらを異なる方法で扱います。
| レジスター | System V x86-64 | Microsoft x64 |
|---|---|---|
| R16 から R29 | スクラッチ | スクラッチ |
| R30、R31 | スクラッチ | 呼び出し先保存 |
Microsoft は注意点を 1 つ加えています。jmp_buf には R30 と R31 を入れる場所がないため、関数は setjmp から longjmp につながる呼び出しまでの間にそれらを変更してはなりません。新しいレジスターはどれも引数を運ばないため、APX 向けにコンパイルしたコードは APX なしでコンパイルしたコードを呼び出せ、その逆も可能です。命令は APX 拡張のページ (英語)に一覧があります。
AMX タイル
AMX の 8 つのタイルレジスター TMM0 から TMM7 はどちらの ABI でもスクラッチで、System V ではタイル構成 TILECFG もスクラッチです。タイルを使う関数は LDTILECFG でタイルを構成し、TILERELEASE でタイルを初期状態に戻します。
Linux では、最初に使う前に 1 つの手順が加わります。タイルデータは、カーネルがすべてのスレッドには割り当てない 8 KB の状態なので、プロセスは arch_prctl(ARCH_REQ_XCOMP_PERM, 18) でそれを要求しなければなりません。18 はタイルデータの XSAVE のコンポーネントの番号です。要求するまでは、最初の AMX 命令が SIGILL を発生させます。要求した後は、カーネルは各スレッドが初めてタイルを使うときに、そのスレッド用のより大きなバッファーを割り当てます。この許可はプロセス全体に有効で、fork 後も保たれ、exec で解除されます。命令については AMX タイル拡張 (英語)を参照してください。
出典
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: コミット
e1ce098331da、2025-03-12 - Microsoft Learn: x64 calling convention: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: Overview of x64 ABI conventions: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __vectorcall: コミット
f70d88cd5da7、2026-09-24 - Apple Developer: Writing 64-bit Intel code for Apple Platforms: 2026-09-25 の公開版
- GCC 14.2 manual: x86 Options: 2026-09-25 の公開版
- Linux 7.3: Documentation/arch/x86/xstate.rst: コミット
c03c1a14507d、2026-10-18