x64 での呼び出し規則

この記事では、ある関数 (呼び出し元) が x64 コードで別の関数 (呼び出し先) を呼び出すために使用する標準的なプロセスと規則について説明します。

__vectorcall呼び出し規則の詳細については、「__vectorcall」を参照してください。
__preserve_none呼び出し規則の詳細については、「__preserve_none」を参照してください。

呼び出し規則の既定値

x64 アプリケーション バイナリ インターフェイス (ABI) は、既定で 4 レジスタの高速呼び出し規則を使用します。 呼び出し履歴には、呼び出し先がそのレジスタを保存するためのシャドウ ストアとして領域が割り当てられます。

関数呼び出しの引数と、それらの引数用に使用されるレジスタとは、厳密に一対一で対応します。 8 バイトに収まらない、または 1、2、4、または 8 バイトではない引数は、参照渡しする必要があります。 1 つの引数が複数のレジスタにまたがって分散されることはありません。

x87 レジスタ スタックは使用されていません。 呼び出し先によって使用される可能性がありますが、関数呼び出し全体で揮発性であると見なします。 すべての浮動小数点演算は、16 個の XMM レジスタを使用して実行されます。

整数引数は、レジスタ RCXRDXR8、および R9で渡されます。 浮動小数点引数は、 XMM0LXMM1LXMM2L、および XMM3Lで渡されます。 16 バイトの引数は参照渡しされます。 パラメーター渡しの詳細については、「パラメーター渡し」をご覧ください。 これらのレジスタ、および RAXR10R11XMM4、および XMM5は、 揮発性と見なされるか、戻り時に呼び出し先によって変更される可能性があります。 レジスタの使用状況については、 x64 レジスタの使用状況 および Caller/呼び出し先の保存されたレジスタに関する記事で詳しく説明されています。

プロトタイプ関数の場合、すべての引数が想定される呼び出し先の型に変換されてから渡されます。 呼び出し元は、呼び出し先のパラメーターに領域を割り当てる役割を担います。 呼び出し元は、呼び出し先がそれほど多くのパラメーターを使用しなくても、4 つのレジスタ パラメーターを格納するのに十分な領域を常に割り当てる必要があります。 この規則により、プロトタイプ宣言されていない C 言語関数と vararg C/C++ 関数のサポートが簡略化されます。 vararg 関数またはプロトタイプ宣言されていない関数では、すべての浮動小数点値が、対応する汎用レジスタに複製される必要があります。 最初の 4 つを超えるパラメーターはすべて、シャドウ ストアの後で、呼び出しの前に、スタック上に格納される必要があります。 vararg 関数の詳細については、「vararg」をご覧ください。 プロトタイプ宣言されていない関数の詳細については、「プロトタイプ宣言されていない関数」をご覧ください。

Alignment

ほとんどの構造体は、その自然なアラインメントに合わせてアラインされます。 主な例外は、スタック ポインター、および malloc メモリまたは alloca メモリです。これらは、パフォーマンスを向上させるために、16 バイトにアラインされます。 16 バイトを超えるアラインメントは、手動で実行する必要があります。 16 バイトは、XMM 操作の一般的なアラインメント サイズであるため、ほとんどのコードはこの値で動作するはずです。 構造のレイアウトと配置の詳細については、「 x64 の種類とストレージのレイアウトを参照してください。 スタック レイアウトの詳細については、「x64 でのスタックの使用」をご覧ください。

アンワインド可能性

リーフ関数は、不揮発性レジスタを変更しない関数です。 非リーフ関数は、たとえば、関数を呼び出すことによって、不揮発性 RSPを変更する可能性があります。 または、ローカル変数のスタック領域を増やすことによって、 RSP が変更される可能性があります。 例外が処理されたときに不揮発性レジスタを復旧するために、非リーフ関数には静的データで注釈が付けられます。 そのデータは、任意の命令で関数を適切にアンワインドする方法を記述しています。 このデータは pdata、またはプロシージャ データとして格納されます。これによって次に、例外処理データである xdata が参照されます。 xdata にはアンワインドに関する情報が含まれており、追加の pdata または例外ハンドラー関数を指すことができます。

プロローグとエピローグは、xdata で適切に記述できるように、厳しく制限されています。 スタック ポインターは、リーフ関数内を除き、エピローグまたはプロローグに含まれないコードの任意の領域で 16 バイトにアラインしたままである必要があります。 リーフ関数は、戻り値をシミュレートするだけでアンワインドできます。そのため、pdata と xdata は必要ありません。 関数プロローグとエピローグの適切な構造の詳細については、「x64 でのプロローグとエピローグ」をご覧ください。 例外処理と、pdata および xdata の例外処理とアンワインドの詳細については、「x64 での例外処理」をご覧ください。

パラメーター渡し

x64 呼び出し規則では、既定で、最初の 4 つの引数がレジスタ内の関数に渡されます。 これらの引数に使用されるレジスタは、引数の位置と型によって異なります。 残りの引数は、スタック上で右から左の順序で渡されます。 呼び出し元は必要なスタック領域を予約し、ストア命令または移動命令を使用してこれらの引数をスタック メモリに書き込み、各引数の 8 バイトアラインメントを維持します。

左端の 4 つの位置にある整数値の引数は、それぞれ RCXRDXR8、および R9で左から右の順序で渡されます。 前述のように、5 番目以上の引数がスタックで渡されます。 整数の引数はすべてレジスタで右揃えになるため、呼び出し先では、レジスタの上位ビットを無視して、必要なレジスタの部分にのみアクセスすることができます。

最初の 4 つのパラメーターの浮動小数点引数と倍精度引数は、位置に応じて XMM0 - XMM3渡されます。 浮動小数点値は、varargs 引数がある場合にのみ、整数レジスタ RCXRDXR8、および R9 に配置されます。 詳細については、「vararg」をご覧ください。 同様に、対応する引数が整数またはポインター型の場合、 XMM0 - XMM3 レジスタは無視されます。

__m128 型、配列、および文字列がイミディエイト値によって渡されることはありません。 代わりに、呼び出し元によって割り当てられたメモリへのポインターが渡されます。 サイズが 8、16、32、または 64 ビットの構造体と共用体、および __m64 型は、同じサイズの整数であるかのように渡されます。 他のサイズの構造体または共用体は、呼び出し元によって割り当てられたメモリへのポインターとして渡されます。 ポインターとして渡されるこれらの集約型 (__m128 を含む) については、呼び出し元が割り当てた一時メモリが 16 バイトでアラインされている必要があります。

スタック領域を割り当てず、他の関数を呼び出さない組み込み関数では、追加のレジスタ引数を渡すために他の volatile レジスタが使用されることがあります。 この最適化は、コンパイラと組み込み関数の実装との間の緊密なバインドによって可能になります。

呼び出し先は、必要に応じてレジスタ パラメーターをシャドウ スペースにダンプする役割を担います。

次の表は、パラメーターを渡す方法を、型、および左から数えた位置でまとめたものです。

パラメーターのタイプ 5 番目以降 4 番目 3 番目 second 左端
浮動小数点 スタック XMM3 XMM2 XMM1 XMM0
整数 スタック R9 R8 RDX RCX
集計型 (8、16、32、または 64 ビット) と __m64 スタック R9 R8 RDX RCX
ポインターとしてのその他の集計型 スタック R9 R8 RDX RCX
__m128 (ポインターとして) スタック R9 R8 RDX RCX

引数渡しの例 1 - すべて整数

func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack

引数渡しの例 2 - すべて浮動小数点数

func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack

引数渡しの例 3 - 整数と浮動小数点数の混合

func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack

引数渡しの例 4 - __m64__m128、および集約型

func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack

vararg

パラメーターが vararg を使用して渡される場合 (例: 省略記号の引数)、通常のレジスタ パラメーター渡しの規則が適用されます。 この規則には、5 番目以降の引数のスタックへの書き込みが含まれます。 アドレスが取得された引数をダンプするのは、呼び出し先の責任です。 浮動小数点値のみの場合、呼び出し先が整数レジスタの値を想定する場合に備えて、整数レジスタと浮動小数点レジスタの両方に値を含める必要があります。

プロトタイプ宣言されていない関数

完全にプロトタイプ宣言されていない関数の場合、呼び出し元は整数値を整数として、浮動小数点値を倍精度として渡します。 浮動小数点値のみの場合、呼び出し先が整数レジスタの値を想定する場合に備えて、整数レジスタと浮動小数点レジスタの両方に浮動小数点値を含めます。

func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}

戻り値

__m64型を含め、64 ビットに収めることができるスカラー戻り値は、RAXを介して返されます。 浮動小数点型、倍精度浮動小数点型、および __m128__m128i__m128d などのベクトル型を含む非スカラー型は、XMM0 で返されます。 RAXまたはXMM0で返される値の未使用ビットの状態は未定義です。

ユーザー定義型は、グローバル関数や静的メンバー関数からの値で返すことができます。 RAXの値によってユーザー定義型を返すには、長さが 1、2、4、8、16、32、または 64 ビットである必要があります。 また、ユーザー定義のコンストラクター、デストラクター、またはコピー代入演算子は含まれません。 非静的データメンバーには、プライベートまたは保護されたメンバー、および参照型のメンバーを持つことはできません。 基底クラスまたは仮想関数は含まれません。 そして、それはこれらの要件を満たすデータ メンバーのみを含むことができます。 この定義は、基本的に C++03 POD 型と同じです。 定義は C++11 標準で変更されているため、このテストに std::is_pod を使用することはお勧めしません。 それ以外の場合、呼び出し元は戻り値のメモリを割り当て、最初の引数としてポインターを渡す必要があります。 残りの引数は、引数 1 つ分だけ右にシフトされます。 RAXで呼び出し先が同じポインターを返す必要があります。

以下の例は、宣言を指定して関数に対してパラメーターと戻り値を渡す方法を示しています。

戻り値の例 1 - 64 ビットの結果

__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.

戻り値の例 2 - 128 ビットの結果

__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.

戻り値の例 3 - ポインターによるユーザー型の結果

struct Struct1 {
   int j, k, l;    // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.

戻り値の例 4 - ユーザータイプを値で返した結果

struct Struct2 {
   int j, k;    // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.

呼び出し元または呼び出し先保存済みレジスタ

x64 ABI では、レジスタの RAXRCXRDXR8R9R10R11、および XMM0-XMM5 揮発性が考慮されます。 存在する場合、 YMM0-YMM15ZMM0-ZMM15 の上部も揮発性です。 AVX512VLでは、 ZMMYMM、および XMM レジスタ 16 から 31 も揮発性です。 AMX サポートが存在する場合、 TMM タイル レジスタは揮発性です。 プログラム全体の最適化などの分析によって安全性を確認できる場合を除き、関数呼び出しでは、volatile レジスタは破棄されたものと見なされます。

x64 ABI は、レジスタ RBXRBPRDIRSIRSPR12R13R14R15、および非揮発性 XMM6-XMM15 を考慮します。 これらは、これらを使用する関数によって保存および復元される必要があります。

APX サポートが存在する場合、レジスタ R16-R29 は揮発性です。 R30R31 は不揮発性です。

関数ポインター

関数ポインターは、単純にそれぞれの関数のラベルへのポインターです。 関数ポインターについては、目次 (TOC) の要件はありません。

古いコードのための浮動小数点サポート

MMX および浮動小数点スタック レジスタ (MM0-MM7/ST0-ST7) は、コンテキスト スイッチ間で保持されます。 これらのレジスタ用の明示的な呼び出し規則はありません。 これらのレジスタの使用は、カーネル モードのコードでは厳密には禁止されています。

FPCSR

レジスタの状態には、x87 FPU 制御ワードも含まれます。 呼び出し規則では、このレジスタが非 volatile であると指定されます。

x87 FPU 制御ワード レジスタは、プログラムの実行開始時に、次の標準値を使用して設定を行います。

レジスタ[ビット] 設定
FPCSR\[0:6] 例外によってすべての 1 がマスクされます (すべての例外がマスクされます)
FPCSR\[7] 予約済み - 0
FPCSR\[8:9] 精度制御 - 10B (倍精度)
FPCSR\[10:11] 丸め制御 - 0 (最も近い値に丸める)
FPCSR\[12] 無限制御 - 0 (使用されません)

FPCSR内のいずれかのフィールドを変更する呼び出し先は、呼び出し元に戻る前にそれらを復元する必要があります。 また、呼び出し元でこれらのフィールドのいずれかを変更した場合は、呼び出し先で合意により変更された値が想定されていない限り、そのフィールドをその標準値に復元してから呼び出し先を呼び出す必要があります。

コントロール フラグの非自発的性に関する規則には、次の 2 つの例外があります。

  • 指定された関数の文書化された目的は、不揮発性 FPCSR フラグを変更することです。

  • プログラムがこれらの規則に違反しても、(たとえばプログラム全体の分析を通じて) これらの規則に違反しない場合と同じように動作するのが明らかに正しい場合。

不揮発性と見なされているにもかかわらず、保存場所と復元元を記述する静的アンワインド記述子はありません。 FPCSRを変更する例外セーフ コードは、スタックをアンワインドするときに明示的に復元するために、例外ファイナライザー (C++ デストラクターや __finally 句など) に頼る必要があります。

MXCSR

レジスタの状態には、 MXCSRも含まれます。 呼び出し規則では、このレジスタは volatile の部分と非 volatile の部分に分割されます。 揮発性部分は、 MXCSR\[0:5]の 6 つの状態フラグで構成され、残りのレジスタ MXCSR\[6:15]は不揮発性と見なされます。

非 volatile の部分は、プログラムの実行開始時に次の標準値に設定されます。

レジスタ[ビット] 設定
MXCSR\[6] 非正規化数はゼロで表されます - 0
MXCSR\[7:12] 例外によってすべての 1 がマスクされます (すべての例外がマスクされます)
MXCSR\[13:14] 丸め制御 - 0 (最も近い値に丸める)
MXCSR\[15] マスクされたアンダーフロー用にゼロにフラッシュ - 0 (オフ)

MXCSR内の不揮発性フィールドを変更する呼び出し先は、呼び出し元に戻る前にそれらを復元する必要があります。 また、呼び出し元でこれらのフィールドのいずれかを変更した場合は、呼び出し先で合意により変更された値が想定されていない限り、そのフィールドをその標準値に復元してから呼び出し先を呼び出す必要があります。

コントロール フラグの非自発的性に関する規則には、次の 2 つの例外があります。

  • 指定された関数の文書化された目的は、不揮発性 MXCSR フラグを変更することです。

  • プログラムがこれらの規則に違反しても、(たとえばプログラム全体の分析を通じて) これらの規則に違反しない場合と同じように動作するのが明らかに正しい場合。

関数のドキュメントで明示的に説明されていない限り、関数の境界を越えて MXCSR レジスタの揮発性部分の状態について想定しません。

MXCSRの一部が不揮発性と見なされているにもかかわらず、保存場所と復元元を記述する静的アンワインド記述子はありません。 MXCSRの不揮発性部分を変更する例外セーフ コードは、スタックをアンワインドするときに明示的に復元するために、例外ファイナライザー (C++ デストラクターや __finally 句など) に頼る必要があります。

setjmp、longjmp

setjmpex.hまたはsetjmp.hを含めると、setjmpまたはlongjmpへのすべてのコールが、デストラクターおよび__finallyコールを実行するアンワインドを引き起こします。 この動作は x86 とは異なります。 setjmp.h を含めると、 __finally 句とデストラクターが呼び出されません。

setjmpの呼び出しでは、現在のスタック ポインター、不揮発性レジスタ、およびMXCSR レジスタが保持されます。 longjmp呼び出しは、最新のsetjmp呼び出しサイトに戻り、スタック ポインター、不揮発性レジスタ、およびMXCSR レジスタを、最新のsetjmp呼び出しで保持されている状態に戻します。

APX がサポートされている場合、 R30R31 は、 setjmp 呼び出された時点から、最終的に longjmp が行われる呼び出しの時点まで、関数内で変更しないでください。 この制限は、R30の結果であり、R31jmp_bufの一部として保存されません。この構造体定義は変更できません。 代わりに、アンワインダーを介して復元されます。 次の例は、データの復元方法の違いが、この制限にどのように影響するかを示しています。

jmp_buf jmpbuffer;

void function_a() {
    ...

    int val = setjmp(jmpbuffer);  // At this time R30 is 10

    ...

    if (val == 0) {
        function_b();  // At this time R30 is 20
    }

    ...
}

void function_b() {
    ...

    longjmp(jmpbuffer, 1);

    ...
}

この例では、 R30 の値は、 setjmp が呼び出されるポイントから、 function_b が呼び出されるポイントまで変化します。 function_bでは、longjmpは、setjmpを呼び出した関数(この場合はfunction_a)に到達するまでスタックをアンワインドします。 R30に復元された値は、20 (function_bが呼び出された値) ではなく10されます (setjmp呼び出された時点の値)。 つまり、setjmpが (longjmpの結果として) 2 回目に戻ると、R30の値は20ではなく10に設定されます。これは正しくありません。 このため、コンパイラは、 R30R31 が呼び出された時点から関数の最後の場所まで一定 setjmp 維持する必要があり、最終的に longjmp が呼び出される可能性があります。

longjmpは (サブルーチンだけでなく) 例外フィルターから呼び出すことができるので、R30R31は、関数の残りの部分を介して呼び出setjmp点から一定に保つ必要があることを実質的に指示します。

関連項目