ARM64EC ABI 規則の概要

ARM64ECは、ARM64 バイナリを x64 コードとネイティブかつ相互運用可能に実行できるようにするアプリケーション バイナリ インターフェイス (ABI) です。 具体的には、ARM64EC ABI は、呼び出し規則、スタック使用法、データアラインメントを含む x64 ソフトウェア規則に従い、ARM64ECおよび x64 コードを相互運用可能にします。 オペレーティング システムは、バイナリの x64 部分をエミュレートします。 (ARM64ECの EC はエミュレーション互換略です。

x64 および ARM64 ABI の詳細については、「x64 ABI 規則の概要」および「ARM64 ABI 規則の概要」を参照してください

ARM64ECでは、x64 と ARM ベースのアーキテクチャのメモリ モデルの違いは解決されません。 詳細については、「Visual C++ ARM の移行に関する一般的な問題」を参照してください

定義

  • ARM64 - 従来の ARM64 コードを含む ARM64 プロセスのコード ストリーム。
  • ARM64EC - ARM64 レジスタ セットのサブセットを利用して x64 コードとの相互運用性を提供するコード ストリーム。

マッピングの登録

x64 プロセスでは、スレッドがコードARM64EC実行されている可能性があります。 したがって、x64 レジスタ コンテキストを取得することは常に可能です。ARM64ECは、エミュレートされた x64 レジスタに 1:1 をマップする ARM64 コア レジスタのサブセットを使用します。 重要ARM64EC、スレッド環境ブロック (TEB) アドレス x18の読み取りを除き、このサブセットの外部でレジスタを使用することはありません。

一部または多くの関数がARM64ECとして再コンパイルされた場合、ネイティブ ARM64 プロセスはパフォーマンスを低下させるべきではありません。 パフォーマンスメイン維持するために、ABI は次の原則に従います。

  • ARM64EC レジスタ サブセットには、ARM64 関数呼び出し規則に含まれるすべてのレジスタが含まれます。

  • ARM64EC呼び出し規則は、ARM64 呼び出し規則に直接マップされます。

カスタム呼び出し規則やレジスタを使用するなどの __chkstk_arm64ec 特殊なヘルパー ルーチン。 これらのレジスタは、レジスタのARM64ECサブセットにも含まれます。

整数レジスタのレジスタ マッピング

登録ARM64EC x64 レジスタ ARM64EC呼び出し規則 ARM64 呼び出し規則 x64 での呼び出し規則
x0 rcx volatile volatile volatile
x1 rdx volatile volatile volatile
x2 r8 volatile volatile volatile
x3 r9 volatile volatile volatile
x4 r10 volatile volatile volatile
x5 r11 volatile volatile volatile
x6 mm1 (x87 R1 レジスタの下位 64 ビット) volatile volatile volatile
x7 mm2 (x87 R2 レジスタの下位 64 ビット) volatile volatile volatile
x8 rax volatile volatile volatile
x9 mm3 (x87 R3 レジスタの下位 64 ビット) volatile volatile volatile
x10 mm4 (x87 R4 レジスタの下位 64 ビット) volatile volatile volatile
x11 mm5 (x87 R5 レジスタの下位 64 ビット) volatile volatile volatile
x12 mm6 (x87 R6 レジスタの下位 64 ビット) volatile volatile volatile
x13 該当なし 禁止 volatile 該当なし
x14 該当なし 禁止 volatile 該当なし
x15 mm7 (x87 R7 レジスタの下位 64 ビット) volatile volatile volatile
x16 各 x87 R0-R3 レジスタの上位 16 ビット volatile(xip0) volatile(xip0) volatile
x17 各 x87 R4-R7 レジスタの上位 16 ビット volatile(xip1) volatile(xip1) volatile
x18 GS.base fixed(TEB) fixed(TEB) fixed(TEB)
x19 r12 non-volatile non-volatile non-volatile
x20 r13 non-volatile non-volatile non-volatile
x21 r14 non-volatile non-volatile non-volatile
x22 r15 non-volatile non-volatile non-volatile
x23 該当なし 禁止 non-volatile 該当なし
x24 該当なし 禁止 non-volatile 該当なし
x25 rsi non-volatile non-volatile non-volatile
x26 rdi non-volatile non-volatile non-volatile
x27 rbx non-volatile non-volatile non-volatile
x28 該当なし 禁止 禁止 該当なし
fp rbp non-volatile non-volatile non-volatile
lr mm0 (x87 R0 レジスタの下位 64 ビット) 両方 両方 両方
sp rsp non-volatile non-volatile non-volatile
pc rip 命令ポインター 命令ポインター 命令ポインター
PSTATEサブセット: NZSS/C//V/1、2 RFLAGS サブセット: SF/ZF/CF/OF/TF volatile volatile volatile
該当なし RFLAGS サブセット: PF/AF 該当なし 該当なし volatile
該当なし RFLAGS サブセット: DF 該当なし 該当なし non-volatile

1 直接読み取り、書き込み、またはマッピングをPSTATERFLAGS計算しないでください。 これらのビットは将来使用される可能性があり、変更される可能性があります。

2 ARM64ECのキャリング フラグ C は、減算演算の x64 キャリング フラグ CF の逆関数です。 フラグは揮発性であるため、(ARM64EC と x64) 関数の間を遷移するときにゴミ箱に入れわれるため、特別な処理はありません。

ベクター レジスタのレジスタ マッピング

登録ARM64EC x64 レジスタ ARM64EC呼び出し規則 ARM64 呼び出し規則 x64 での呼び出し規則
v0-v5 xmm0-xmm5 volatile volatile volatile
v6-v7 xmm6-xmm7 volatile volatile non-volatile
v8-v15 xmm8-xmm15 volatile 1 volatile 1 non-volatile
v16-v31 xmm16-xmm31 禁止 volatile 許可されていません (x64 エミュレーターは AVX-512 をサポートしていません)
FPCR2 MXCSR[15:6] non-volatile non-volatile non-volatile
FPSR2 MXCSR[5:0] volatile volatile volatile

1 これらの ARM64 レジスタは、下位 64 ビットは不揮発性ですが、上位 64 ビットは揮発性であるという点で特別です。 x64 呼び出し元の観点からは、呼び出し先がデータをごみ箱に入れるので、実質的に揮発性です。

2 直接読み取り、書き込み、またはコンピューティングのマッピングをFPCRFPSR避けます。 これらのビットは将来使用される可能性があり、変更される可能性があります。

構造体のパッキング

ARM64ECは、ARM64EC コードと x64 コードの相互運用性を確保するために x64 に使用されるのと同じ構造体パッキング規則に従います。 x64 構造体パッキングの詳細と例については、「x64 ABI 規則の概要」を参照してください

エミュレーション ヘルパー ABI ルーチン

ARM64ECコードと サンクは 、エミュレーション ヘルパー ルーチンを使用して x64 関数とARM64EC関数の間を遷移します。

次の表では、各特殊な ABI ルーチンと、ABI が使用するレジスタについて説明します。 ルーチンは、ABI 列の下に一覧表示されている保持レジスタを変更しません。 一覧に登録されていないレジスタに関する想定は行われません。 ディスク上では、ABI ルーチン ポインターは null です。 読み込み時に、ローダーはポインターを更新して x64 エミュレーター ルーチンを指します。

名前 説明 ABI
__os_arm64x_dispatch_call_no_redirect x64 ターゲット (x64 関数または x64 早送りシーケンス) を呼び出すために出口サンクによって呼び出されます。 このルーチンは、ARM64ECリターン アドレス (レジスタ内 LR ) の後に、x64 エミュレーターを呼び出す命令に成功 blr x16 した命令のアドレスをプッシュします。 次に、命令を実行します blr x16 戻り値 (x8rax)
__os_arm64x_dispatch_ret エントリ サンクによって呼び出され、その x64 呼び出し元に戻ります。 スタックから x64 リターン アドレスをポップし、x64 エミュレーターを呼び出してジャンプします 該当なし
__os_arm64x_check_call 終了サンクへのポインターと、実行する間接ARM64ECターゲット アドレスを持つARM64ECコードによって呼び出されます。 ARM64ECターゲットは修正可能と見なされ、実行は常に呼び出されたデータと同じデータまたは変更されたデータを使用して呼び出し元に返されます 引数:
x9: ターゲット アドレス
x10: 出口サンク・アドレス
x11: 高速順方向シーケンス アドレス

乙:
x9: ターゲット関数が迂回された場合は、高速順方向シーケンスのアドレスが含まれます。
x10: 出口サンク・アドレス
x11: 関数が迂回された場合は、終了サンク アドレスが含まれます。 それ以外の場合は、ターゲット アドレスが

保持されているレジスタ: x0-x8, x15 ()。chkstk および q0-q7 に変更されました
__os_arm64x_check_icall x64 または ARM64EC のいずれかのターゲット アドレスへのジャンプを処理するために、終了サンクへのポインターを使用して、ARM64ECコードによって呼び出されます。 ターゲットが x64 で、x64 コードにパッチが適用されていない場合、ルーチンはターゲット アドレス レジスタを設定します。 存在する場合は、関数のARM64ECバージョンを指します。 それ以外の場合は、x64 ターゲットに遷移する終了サンクを指すレジスタを設定します。 次に、呼び出し元のARM64ECコードに戻り、レジスタ内のアドレスにジャンプします。 このルーチンは、コンパイル時にターゲット アドレスが認識されない最適化されていないバージョン __os_arm64x_check_callです

間接呼び出しの呼び出しサイトで使用されます
引数:
x9: ターゲット アドレス
x10: 出口サンク・アドレス
x11: 高速順方向シーケンス アドレス

乙:
x9: ターゲット関数が迂回された場合は、高速順方向シーケンスのアドレスが含まれます。
x10: 出口サンク・アドレス
x11: 関数が迂回された場合は、終了サンク アドレスが含まれます。 それ以外の場合は、ターゲット アドレスが

保持されているレジスタ: x0-x8x15 (chkstk)、および q0-q7
__os_arm64x_check_icall_cfg 指定したアドレスが有効な制御フロー グラフ間接呼び出しターゲットであることをチェックと__os_arm64x_check_icall同じです。 引数:
x10: 出口サンクのアドレス
x11: ターゲット関数のアドレス

乙:
x9: ターゲットが x64 の場合、関数のアドレス。 それ以外の場合は未定義
x10: 出口サンクのアドレス
x11: ターゲットが x64 の場合は、出口サンクのアドレスが含まれます。 それ以外の場合は、関数のアドレス

保持されているレジスタ: x0-x8x15 (chkstk)、および q0-q7
__os_arm64x_get_x64_information ライブ x64 レジスタ コンテキストの要求された部分を取得します。 _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo)
__os_arm64x_set_x64_information ライブ x64 レジスタ コンテキストの要求された部分を設定します _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo)
__os_arm64x_x64_jump 署名のないアジャスタやその他のサンクで使用され、任意の署名を持つ可能性のある別の関数への呼び出しを直接転送しjmp、正しいサンクの潜在的なアプリケーションを実際のターゲットに遅延させる 引数:
x9: ジャンプ先のターゲット

保持されているすべてのパラメーター レジスタ (転送)

サンク

サンクは、ARM64EC関数と x64 関数が相互に呼び出しをサポートするための低レベルのメカニズムです。 ARM64EC関数を入力するためのエントリ サンクx64 関数を呼び出すための終了サンクの 2 種類があります。

エントリ サンクと組み込みエントリ サンク: 関数呼び出しをARM64ECする x64

C/C++ 関数がARM64ECとしてコンパイルされるときに x64 呼び出し元をサポートするために、ツールチェーンは、コンピューター コードで構成される単一のエントリ サンクARM64EC生成します。 組み込み関数には、独自のエントリ サンクがあります。 他のすべての関数は、一致する呼び出し規則、パラメーター、および戻り値の型を持つすべての関数とエントリ サンクを共有します。 サンクの内容は、C/C++ 関数の呼び出し規則に依存します。

サンクは、パラメーターとリターン アドレスの処理に加えて、ARM64ECと x64 ベクター レジスタの間のボラティリティの違いを、ARM64ECベクター レジスタ マッピングによって引き起こされます。

登録ARM64EC x64 レジスタ ARM64EC呼び出し規則 ARM64 呼び出し規則 x64 での呼び出し規則
v6-v15 xmm6-xmm15 volatile ですが、エントリ サンクに保存/復元 (x64 から ARM64EC) 揮発性または部分的に揮発性の上位 64 ビット non-volatile

エントリ サンクは、次のアクションを実行します。

パラメーター番号 スタックの使用
0-4 ARM64EC v6 を格納し、呼び出し v7 元が割り当てたホーム領域に格納します。

呼び出し先はARM64ECであり、ホームスペースの概念がないため、格納された値は折り返されません。

スタックに追加の 128 バイトを割り当て、ARM64EC v8 を格納します v15
5-8 x4 = スタックからの 5 番目のパラメーター
x5 = スタックからの 6 番目のパラメーター
x6 = スタックからの 7 番目のパラメーター
x7 = スタックからの 8 番目のパラメーター

パラメーターが SIMD の場合は、 v4-v7 代わりにレジスタが使用されます。
+9 スタックに AlignUp(NumParams - 8 , 2) * 8 バイトを割り当てます。 *

9 番目のパラメーターと再メインパラメーターをこの領域にコピーします。

* 値を偶数に揃える場合、スタックが 16 バイトに再整列メイン保証されます

関数が 32 ビット整数パラメーターを受け取る場合、サンクは親レジスタの完全な 64 ビットではなく 32 ビットのみをプッシュできます。

次に、サンクは ARM64 bl 命令を使用してARM64EC関数を呼び出します。 関数が戻った後、サンク:

  1. スタック割り当てを元に戻します
  2. エミュレーター ヘルパーを __os_arm64x_dispatch_ret 呼び出して x64 リターン アドレスをポップし、x64 エミュレーションを再開します。

終了サンク: x64 関数呼び出しへのARM64EC

ARM64EC C/C++ 関数が潜在的な x64 コードを呼び出すたびに、MSVC ツールチェーンによって終了サンクが生成されます。 サンクの内容は、x64 呼び出し先のパラメーターと、呼び出し先が標準の呼び出し規則 __vectorcallを使用しているかどうかによって異なります。 コンパイラは、呼び出し先の関数宣言からこの情報を取得します。

まず、サンクは、スタックが 16 バイトにアラインされることを保証するために、ARM64EC lr レジスタ内のリターン アドレスとダミーの 8 バイト値をプッシュします。 次に、サンクがパラメーターを処理します。

パラメーター番号 スタックの使用
0-4 スタックに 32 バイトのホーム領域を割り当てます
5-8 スタックの AlignUp(NumParams - 4, 2) * 8 上位のバイト数を割り当てます。 *

5 番目以降のパラメーターを ARM64EC x4-x7 からこの余分なスペースにコピーします。
+9 9 番目のパラメーターと再メインパラメーターを追加の領域にコピーします。

* 値を偶数にアラインすると、スタックが 16 バイトに再整列メイン保証されます。

3 番目に、サンクはエミュレーター ヘルパーを __os_arm64x_dispatch_call_no_redirect 呼び出して x64 エミュレーターを呼び出し、x64 関数を実行します。 呼び出しは命令である blr x16 必要があります (便利には、 x16 揮発性レジスタです)。 blr x16 x64 エミュレーターがこの命令をヒントとして解析するため、命令が必要です。

x64 関数は通常、x64 ret 命令を使用してエミュレーター ヘルパーに戻ろうとします。 この時点で、x64 エミュレーターはコード内にあることを検出ARM64EC。 次に、ARM64 blr x16 命令である前の 4 バイト ヒントを読み取ります。 このヒントは、戻り値のアドレスがこのヘルパーにあることを示しているため、エミュレーターはこのアドレスに直接ジャンプします。

x64 関数は、x64 jmpcall. エミュレーターはこれらのシナリオも処理します。

ヘルパーがサンクに戻ると、サンク:

  1. スタック割り当てを元に戻します
  2. ARM64EC lr レジスタをポップします
  3. ARM64 ret lr 命令を実行します。

ARM64EC関数名の装飾

ARM64EC関数名には、言語固有の装飾の後に 2 番目の装飾が適用されます。 C リンケージを持つ関数の場合 (C としてコンパイルされた場合でも、使用 extern "C"する場合でも)、名前の前に a # が付加されます。 C++ 修飾関数の場合、 $$h タグが名前に挿入されます。

foo         => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ

__vectorcall

ARM64EC ツールチェーンは現在サポート __vectorcallされていません。 コンパイラは、ARM64ECでの使用状況を __vectorcall 検出するとエラーを出力します。

関連項目

ABI とアセンブリ コードARM64EC理解する
Visual C++ ARM の移行に関する一般的な問題
装飾名