x64 での呼び出し規則

このセクションでは、x64 コードで 1 つの関数 (呼び出し元) から別の関数 (呼び出し先) への呼び出しを行う際に使用する標準プロセスと規則について説明します。

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

呼び出し規則の既定値

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

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

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

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

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

配置

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

アンワインド可能性

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

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

パラメーター渡し

x64 呼び出し規則では、既定で、最初の 4 つの引数がレジスタ内の関数に渡されます。 これらの引数に使用されるレジスタは、引数の位置と型によって異なります。 残りの引数は、スタックで右から左へプッシュされます。

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

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

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

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

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

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

パラメーターの型 5 番目以降 4 番目 第 3 second 左端
浮動小数点 スタック XMM3 XMM2 XMM1 XMM0
integer スタック 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 pushed 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 pushed 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 pushed 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 pushed on stack, then ptr to e pushed on stack

vararg

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

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

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

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

戻り値

__m64 型を含む、64 ビットに収まるスカラー戻り値は、RAX を使用して返されます。 float 型、double 型、および __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 pushed 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 pushed 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 では、RAX、RCX、RDX、R8、R9、R10、R11、および XMM0 から XMM5 のレジスタは volatile と見なされます。 存在する場合、YMM0 から YMM15、および ZMM0 から ZMM15 の上位部分も volatile です。 AVX512VL では、ZMM、YMM、および XMM のレジスタ 16 から 31 も volatile です。 AMX サポートが存在する場合、TMM タイル レジスタは揮発性です。 プログラム全体の最適化などの分析によって安全性を確認できる場合を除き、関数呼び出しでは、volatile レジスタは破棄されたものと見なされます。

x64 ABI では、RBX、RBP、RDI、RSI、RSP、R12、R13、R14、R15、および XMM6 から XMM15 のレジスタは volatile と見なされます。 これらは、これらを使用する関数によって保存および復元される必要があります。

関数ポインター

関数ポインターは、単純にそれぞれの関数のラベルへのポインターです。 関数ポインターについては、目次 (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 内のいずれかのフィールドを変更した場合は、それらを復元してから呼び出し元に返す必要があります。 また、呼び出し元でこれらのフィールドのいずれかを変更した場合は、呼び出し先で合意により変更された値が想定されていない限り、そのフィールドをその標準値に復元してから呼び出し先を呼び出す必要があります。

制御フラグの非 volatile 性に関する規則には、次の 2 つの例外があります。

  • 非 volatile FPCSR フラグを変更することが、指定された関数のドキュメント化された目的とされている関数内。

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

MXCSR

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

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

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

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

制御フラグの非 volatile 性に関する規則には、次の 2 つの例外があります。

  • 非 volatile MXCSR フラグを変更することが、指定された関数のドキュメント化された目的とされている関数内。

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

関数のドキュメントで特に説明されていない限り、関数の境界を越えて、MXCSR のレジスタの volatile の部分の状態について想定することはありません。

setjmp、longjmp

setjmpex.h または setjmp.h をインクルードする場合、setjmp または longjmp を呼び出すたびに、デストラクターと __finally を呼び出すアンワインドが発生します。 この動作は x86 とは異なります。x86 では、setjmp.h をインクルードすると、__finally 句が生成され、デストラクターが呼び出されません。

setjmp を呼び出すと、現在のスタック ポインター、非 volatile レジスタ、および MXCSR レジスタが保持されます。 longjmp を呼び出すと、最も新しい setjmp の呼び出しサイトに戻り、スタック ポインター、非 volatile レジスタ、および MXCSR レジスタが、最も新しい setjmp 呼び出しによって保持されている状態にリセットされます。

関連項目

x64 ソフトウェア規約