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
から読み取る場合を除き、このサブセット以外のレジスタを使用することはありません。
ネイティブ ARM64 プロセスは、一部または多くの関数が ARM64EC として再コンパイルされてもパフォーマンスを低下させるべきではありません。 パフォーマンスを維持するために、この 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 |
該当なし | disallowed | volatile | 該当なし |
x14 |
該当なし | disallowed | 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 |
該当なし | disallowed | non-volatile | 該当なし |
x24 |
該当なし | disallowed | 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 |
該当なし | disallowed | disallowed | 該当なし |
fp |
rbp |
non-volatile | non-volatile | non-volatile |
lr |
mm0 (x87 R0 レジスタの下位 64 ビット) |
両方 | 両方 | 両方 |
sp |
rsp |
non-volatile | non-volatile | non-volatile |
pc |
rip |
命令ポインター | 命令ポインター | 命令ポインター |
PSTATE サブセット: N /Z /C /V /SS 1, 2 |
RFLAGS サブセット: SF /ZF /CF /OF /TF |
volatile | volatile | volatile |
該当なし | RFLAGS サブセット: PF /AF |
該当なし | 該当なし | volatile |
該当なし | RFLAGS サブセット: DF |
該当なし | 該当なし | non-volatile |
1 PSTATE
と RFLAGS
の直接的な読み取り、書き込み、あるいはこれらの間のマッピングの計算は避けてください。 これらのビットは将来的に使用される可能性があり、変更される可能性があります。
2 ARM64EC のキャリー フラグ C
は、減算演算の x64 キャリー フラグ CF
を反転したものです。 このフラグは volatile であり、(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 |
disallowed | volatile | disallowed (x64 エミュレータは AVX-512 をサポートしていません) |
FPCR 2 |
MXCSR[15:6] |
non-volatile | non-volatile | non-volatile |
FPSR 2 |
MXCSR[5:0] |
volatile | volatile | volatile |
1 これらの ARM64 レジスタは、下位 64 ビットが non-volatile である一方、上位 64 ビットは volatile であるという点で特別です。 呼び出し先がデータを破棄するため、x64 呼び出し元から見ると、これらは実質的に volatile です。
2 FPCR
と FPSR
の直接的な読み取り、書き込み、あるいはマッピングの計算は避けてください。 これらのビットは将来的に使用される可能性があり、変更される可能性があります。
構造体パッキング
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 ファストフォワード シーケンス) を呼び出すために exit サンクによって呼び出されます。 このルーチンは、(LR レジスタ内の) ARM64EC リターン アドレスをプッシュし、その後には x64 エミュレータを呼び出す blr x16 命令に続く命令のアドレスが続きます。 次に、blr x16 命令を実行します |
x8 (rax ) 内の戻り値 |
__os_arm64x_dispatch_ret |
x64 呼び出し元に戻るために entry サンクによって呼び出されます。 スタックから x64 リターン アドレスをポップし、x64 エミュレータを呼び出してそこへジャンプします | 該当なし |
__os_arm64x_check_call |
exit サンクへのポインターと、実行する間接 ARM64EC ターゲット アドレスを持つ ARM64EC コードによって呼び出されます。 ARM64EC ターゲットはパッチ適用可能と見なされ、実行は常に呼び出し時に使用されたデータまたは変更されたデータと共に呼び出し元に戻ります | 引数:x9 : ターゲット アドレスx10 : exit サンク・アドレスx11 : ファスト フォワード シーケンス アドレス出力: x9 : ターゲット関数が迂回された場合、これにはファスト フォワード シーケンスのアドレスが含まれていますx10 : exit サンク・アドレスx11 : 関数が迂回された場合、これには exit サンク アドレスが含まれています。 それ以外の場合は、ジャンプ先のターゲット アドレスとなります保持レジスタ: x0 -x8 、x15 (chkstk )。 および q0 -q7 に変更されました |
__os_arm64x_check_icall |
x64 または ARM64EC のいずれかのターゲット アドレスへのジャンプを処理するために、exit サンクへのポインターと共に、ARM64EC コードによって呼び出されます。 ターゲットが x64 で、x64 コードにパッチが適用されていない場合、ルーチンはターゲット アドレス レジスタを設定します。 これは該当関数の ARM64EC バージョンを指します (存在する場合)。 それ以外の場合は、x64 ターゲットに遷移する exit サンクを指すようにレジスタを設定します。 それから、呼び出し元の ARM64EC コードに戻った後、レジスタ内のアドレスにジャンプします。 このルーチンは最適化されていないバージョンの __os_arm64x_check_call で、ターゲット アドレスはコンパイル時には不明です間接呼び出しの呼び出しサイトで使用されます |
引数:x9 : ターゲット アドレスx10 : exit サンク・アドレスx11 : ファスト フォワード シーケンス アドレス出力: x9 : ターゲット関数が迂回された場合、これにはファスト フォワード シーケンスのアドレスが含まれていますx10 : exit サンク・アドレスx11 : 関数が迂回された場合、これには exit サンク アドレスが含まれています。 それ以外の場合は、ジャンプ先のターゲット アドレスとなります保持レジスタ: x0 -x8 、x15 (chkstk )、q0 -q7 |
__os_arm64x_check_icall_cfg |
__os_arm64x_check_icall と同じですが、指定されたアドレスが有効な制御フロー グラフの間接呼び出しターゲットであることも確認します |
引数:x10 : exit サンクのアドレスx11 : ターゲット関数のアドレス出力: x9 : ターゲットが x64 の場合は、関数のアドレス。 それ以外の場合は、未定義x10 : exit サンクのアドレスx11 : ターゲットが x64 の場合、これには exit サンクのアドレスが含まれています。 それ以外の場合は、関数のアドレス保持レジスタ: x0 -x8 、x15 (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 関数に入るための "entry サンク" と x64 関数を呼び出すための "exit サンク" という 2 種類が存在します。
entry サンクと intrinsic entry サンク: x64 から ARM64EC への関数呼び出し
C/C++ 関数が ARM64EC としてコンパイルされる場合の x64 呼び出し元をサポートするために、ツールチェーンは ARM64EC マシン コードから構成される単一の entry サンクを生成します。 intrinsic は独自の entry サンクを持ちます。 他のすべての関数は、一致する呼び出し規則、パラメーター、および戻り値の型を持つすべての関数と entry サンクを共有します。 サンクの内容は、C/C++ 関数の呼び出し規則によって決まります。
サンクは、パラメーターとリターン アドレスの処理に加えて、以下のように ARM64EC ベクトル レジスタ マッピングによって引き起こされる ARM64EC と x64 ベクトル レジスタ間のボラティリティの違いを埋めます。
ARM64EC レジスタ | x64 レジスタ | ARM64EC 呼び出し規則 | ARM64 呼び出し規則 | x64 での呼び出し規則 |
---|---|---|---|---|
v6 -v15 |
xmm6 -xmm15 |
volatile ですが、entry サンク内で保存/復元されます (x64 から ARM64EC) | volatile または部分的に volatile な上位 64 ビット | non-volatile |
entry サンクは、以下のアクションを実行します。
パラメーター番号 | スタックの使用 |
---|---|
0-4 | ARM64EC v6 と v7 を呼び出し元割り当てされたホーム スペースに保存します呼び出し先はホーム スペースの概念がない ARM64EC であるため、保存された値は上書きされません。 スタックに追加の 128 バイトを割り当て、 v15 を通して ARM64EC v8 を保存します。 |
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 関数を呼び出します。 関数がリターンした後、サンクは以下を行います。
- スタック割り当てを元に戻します
__os_arm64x_dispatch_ret
エミュレータ ヘルパーを呼び出して x64 リターン アドレスをポップし、x64 エミュレーションを再開します。
exit サンク: ARM64EC から x64 への関数呼び出し
ARM64EC C/C++ 関数が行う x64 であるかもしれないコードのすべての呼び出しに対して、MSVC ツールチェーンは exit サンクを生成します。 サンクの内容は、x64 呼び出し先のパラメーターと、呼び出し先が標準呼び出し規則と __vectorcall
のどちらを使用しているかによって決まります。 コンパイラは、呼び出し先の関数宣言からこの情報を取得します。
まず、サンクはスタックが 16 バイト アライメントされることを保証するために、ARM64EC lr
レジスタ内のリターン アドレスとダミーの 8 バイト値をプッシュします。 2 つ目に、サンクは以下のようにパラメーターを処理します。
パラメーター番号 | スタックの使用 |
---|---|
0-4 | スタックに 32 バイトのホーム スペースを割り当てます |
5-8 | スタックの上方向にさらに AlignUp(NumParams - 4, 2) * 8 バイトを割り当てます。 * 5 番目以降のすべてのパラメーターを ARM64EC の x4 -x7 からこの追加スペースにコピーします |
+9 | 9 番目と残りのパラメーターを追加スペースにコピーします |
* 値を偶数に揃えることで、スタックが 16 バイト アライメントされた状態が保たれることが保証されます。
3 つ目に、サンクは x64 エミュレータを呼び出して x64 関数を実行するために、__os_arm64x_dispatch_call_no_redirect
エミュレータ ヘルパーを呼び出します。 この呼び出しは blr x16
命令である必要があります (都合のいいことに、x16
は volatile レジスタです)。 x64 エミュレータは blr x16
命令をヒントとして解析するため、この命令は必須です。
x64 関数は通常、x64 ret
命令を使用することでエミュレータ ヘルパーにリターンしようとします。 この時点で、x64 エミュレータはそれが ARM64EC コードであることを検出します。 次に、ちょうど ARM64 blr x16
命令である先ほどの 4 バイト ヒントを読み取ります。 このヒントは、リターン アドレスがこのヘルパー内にあることを示しているため、エミュレータはこのアドレスに直接ジャンプします。
x64 関数は、x64 jmp
や call
など、任意の分岐命令を使用してエミュレータ ヘルパーに戻ることが許可されています。 エミュレータはこれらのシナリオも同様に処理します。
ヘルパーが次にサンクに戻ると、サンクは以下を行います。
- スタック割り当てをすべて元に戻します
- ARM64EC
lr
レジスタをポップします - ARM64
ret lr
命令を実行します。
ARM64EC 関数名の装飾
ARM64EC 関数名は、言語固有の装飾の後に適用される 2 番目の装飾を持っています。 C リンケージを持つ関数 (C としてコンパイルされるか、extern "C"
を使用するかに関係なく) に対しては、名前の先頭に #
が付加されます。 C++ 装飾された関数に対しては、$$h
タグが名前の中に挿入されます。
foo => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ
__vectorcall
現在、ARM64EC ツールチェーンは __vectorcall
をサポートしていません。 コンパイラは、ARM64EC での __vectorcall
の使用を検出するとエラーを発生させます。
関連項目
ARM64EC ABI とアセンブリ コードの理解
Visual C++ の ARM への移行に関する一般的な問題
装飾名