ARM ABI 規則の概要
ARM プロセッサ上の Windows 用にコンパイルされたコードのアプリケーション バイナリ インターフェイス (ABI) は、標準の ARM EABI に基づいています。この記事では、ARM 上の Windows と標準との主な相違点を取り上げています。標準の ARM EABI の詳細については、「ARM アーキテクチャ用のアプリケーション バイナリ インターフェイス (ABI)」を参照してください。
基本的な要件
ARM 上の Windows は、常に ARMv7 アーキテクチャで実行されていることを前提としています。ハードウェアで VFPv3-D32 以降の形式の浮動小数点がサポートされている必要があります。VFP が、ハードウェアで単精度と倍精度の両方の浮動小数点をサポートしている必要があります。Windows ランタイムでは、非 VFP ハードウェア上での実行を可能にするための浮動小数点のエミュレーションがサポートされていません。
Advanced SIMD 拡張 (NEON) もハードウェアでサポートされている必要があります (整数と浮動小数点の両方の演算が含まれます)。エミュレーション用のランタイム サポートは用意されていません。
整数の除算のサポート (UDIV/SDIV) は強くお勧めしますが必須ではありません。整数の除算をサポートしないプラットフォームでは、これらの演算をトラップし、場合によっては修正する必要があるため、パフォーマンスが低下する可能性があります。
エンディアン
ARM 上の Windows は、リトル エンディアン モードで実行されます。Visual C++ コンパイラと Windows ランタイムは、両方とも、常にリトル エンディアン データを想定しています。ARM 命令セット アーキテクチャ (ISA) の SETEND 命令では、ユーザー モードのコードでも現在のエンディアンを変更できますが、アプリケーションにとって危険なため推奨されていません。ビッグ エンディアン モードで例外が発生した場合の動作は予測不可能であり、ユーザー モードでのアプリケーション エラーまたはカーネル モードでのバグチェックが発生する可能性があります。
アラインメント
Windows により、ARM ハードウェアは、適切にアラインされていない整数アクセスを透過的に処理できるようになりますが、状況によってはアラインメント エラーが生成される場合があります。アラインメントについては、次の規則に従ってください。
ハーフ ワード サイズ (16 ビット) およびワード サイズ (32 ビット) の整数の読み込みおよび格納はアラインする必要がありません。ハードウェアにより効率的かつ透過的に処理されます。
浮動小数点の読み込みおよび格納はアラインする必要があります。カーネルは、アラインされていない読み込みおよび格納を透過的に処理しますが、オーバーヘッドが著しく大きくなります。
読み込みおよび格納の倍精度浮動小数点 (LDRD/STRD) および複合 (LDM/STM) 演算はアラインする必要があります。カーネルは、大部分を透過的に処理しますが、オーバーヘッドが著しく大きくなります。
キャッシュされていないメモリ アクセスは、整数アクセスの場合でもすべてアラインする必要があります。アラインされていないアクセスにより、アラインメント エラーが発生します。
命令セット
ARM 上の Windows の命令セットは Thumb-2 に厳密に制限されています。このプラットフォームで実行されるすべてのコードは、常に Thumb モードで開始され、Thumb モードのままになることが想定されています。レガシ ARM 命令セットへの切り替えが成功する場合もありますが、その場合、例外または割り込みが発生するとユーザー モードでのアプリケーション エラーまたはカーネル モードでのバグチェックが発生する可能性があります。
この要件の副作用として、すべてのコード ポインターは、下位ビット セットを持つ必要があります。このため、BLX または BX によって読み込まれ、分岐される場合、プロセッサは Thumb モードのままでターゲット コードを 32 ビット ARM 命令として実行しようとしません。
IT 命令
次の特定の場合を除き、Thumb-2 コードの IT 命令の使用は許可されていません。
IT 命令は 1 つのターゲット命令を変更する場合にのみ使用できます。
ターゲット命令は 16 ビット命令である必要があります。
ターゲット命令は、次のいずれかである必要があります。
16 ビット オペコード
クラス
制約
MOV, MVN
移動
Rm != PC, Rd != PC
LDR, LDR[S]B, LDR[S]H
メモリからの読み込み
ただし、LDR リテラル形式ではありません
STR, STRB, STRH
メモリへの格納
ADD, ADC, RSB, SBC, SUB
加算または減算
ただし、ADD/SUB SP、SP、imm7 形式ではありません
Rm != PC, Rdn != PC, Rdm != PC
CMP, CMN
Compare
Rm != PC, Rn != PC
MUL
乗算
ASR, LSL, LSR, ROR
ビット シフト
AND, BIC, EOR, ORR, TST
ビット単位の算術
BX
レジスタへの分岐
Rm != PC
現在の ARMv7 CPU は許可されていない命令形式の使用を報告できませんが、将来の世代では報告可能になる予定です。このような形式が検出された場合は、これらの形式を使用しているプログラムが未定義の命令の例外で終了します。
SDIV/UDIV 命令
整数の除算命令である SDIV と UDIV の使用が完全にサポートされています (それらを処理するネイティブ ハードウェアがないプラットフォームであっても)。全体の除算時間の 20 ~ 250 サイクル (入力によって異なる) に加えて、Cortex-A9 プロセッサ上での SDIV または UDIV 除算あたりのオーバーヘッドは約 80 サイクルです。
整数レジスタ
ARM プロセッサでは、次の 16 種類の整数レジスタをサポートします。
登録 |
Volatile? |
ロール |
---|---|---|
r0 |
Volatile |
パラメーター、結果、スクラッチ レジスタ 1 |
r1 |
Volatile |
パラメーター、結果、スクラッチ レジスタ 2 |
r2 |
Volatile |
パラメーター、スクラッチ レジスタ 3 |
r3 |
Volatile |
パラメーター、スクラッチ レジスタ 4 |
r4 |
非 volatile |
|
r5 |
非 volatile |
|
r6 |
非 volatile |
|
r7 |
非 volatile |
|
r8 |
非 volatile |
|
r9 |
非 volatile |
|
r10 |
非 volatile |
|
r11 |
非 volatile |
フレーム ポインター |
r12 |
Volatile |
プロシージャ内呼び出しスクラッチ レジスタ |
r13 (SP) |
非 volatile |
スタック ポインター |
r14 (LR) |
非 volatile |
リンク レジスタ |
r15 (PC) |
非 volatile |
プログラム カウンター |
パラメーターと戻り値のレジスタの使用方法については、この記事の「パラメーターの引き渡し」のセクションを参照してください。
Windows では、スタック フレームのファスト ウォーキングに r11 を使用します。詳細については、「スタック ウォーキング」のセクションを参照してください。この要件のため、r11 は常にチェーンの最上位のリンクを指す必要があります。r11 は汎用目的では使用しないでください。使用すると、解析中にコードが正しいスタック ウォークを生成しなくなります。
VFP レジスタ
Windows では、VFPv3-D32 コプロセッサをサポートする ARM バリアントのみがサポートされます。つまり、浮動小数点レジスタが常に存在して、パラメーターの引き渡しに利用できること、またフル セットの 32 のレジスタが利用可能であることを意味します。次の表に、VFP レジスタとその使用方法の概要を示します。
単精度 |
倍精度 |
四倍精度 |
Volatile? |
ロール |
---|---|---|---|---|
s0-s3 |
d0-d1 |
q0 |
Volatile |
パラメーター、結果、スクラッチ レジスタ |
s4-s7 |
d2-d3 |
q1 |
Volatile |
パラメーター、スクラッチ レジスタ |
s8-s11 |
d4-d5 |
q2 |
Volatile |
パラメーター、スクラッチ レジスタ |
s12-s15 |
d6-d7 |
q3 |
Volatile |
パラメーター、スクラッチ レジスタ |
s16-s19 |
d8-d9 |
q4 |
非 volatile |
|
s20-s23 |
d10-d11 |
q5 |
非 volatile |
|
s24-s27 |
d12-d13 |
q6 |
非 volatile |
|
s28-s31 |
d14-d15 |
q7 |
非 volatile |
|
d16-d31 |
q8-q15 |
Volatile |
次の表に、浮動小数点状態制御レジスタ (FPSCR) のビットフィールドを示します。
ビット |
説明 |
Volatile? |
ロール |
---|---|---|---|
31-28 |
NZCV |
Volatile |
ステータス フラグ |
27 |
QC |
Volatile |
累積的飽和 |
26 |
AHP |
非 volatile |
代替の半制度制御 |
25 |
DN |
非 volatile |
既定の NaN モード制御 |
24 |
FZ |
非 volatile |
Flush-to-zero モード制御 |
23-22 |
RMode |
非 volatile |
丸めモード制御 |
21-20 |
Stride |
非 volatile |
ベクター ストライド。常に 0 であること |
18-16 |
Len |
非 volatile |
ベクター長。常に 0 であること |
15, 12-8 |
IDE, IXE など |
非 volatile |
例外トラップ イネーブル ビット。常に 0 であること |
7, 4-0 |
IDC, IXC など |
Volatile |
累積的な例外フラグ |
浮動小数点の例外
ほとんどの ARM ハードウェアは、IEEE 浮動小数点例外をサポートしません。ハードウェア浮動小数点例外をサポートするプロセッサ バリアントでは、Windows カーネルが例外をサイレントにキャッチし、FPSCR レジスタで暗黙的に無効にします。これにより、プロセッサ バリアント全体で動作が正常に保たれます。それ以外の場合は、例外をサポートしないプラットフォームで開発されたコードが、例外をサポートするプラットフォームで実行中の場合に、予期しない例外を受け取ることができます。
パラメーターの引き渡し
非可変個引数関数の場合、ARM ABI 上の Windows は、パラメーターの引き渡しについて ARM の規則に従います (VFP および Advanced SIMD 拡張など)。これらの規則は、VFP 拡張と統合された ARM アーキテクチャのプロシージャ呼び出し標準 に従います。既定では、最初の 4 個の整数引数と最大 8 個の浮動小数点またはベクター引数をレジスタに渡すことができます。追加の引数はスタック上に渡されます。引数は、次のプロシージャを使用してレジスタまたはスタックに割り当てられます。
ステージ A: 初期化
初期化は、引数の処理が始まる前に 1 回のみ行われます。
次のコアのレジスタ番号 (NCRN) を r0 に設定します。
VFP レジスタが未割り当てとしてマークされます。
次のスタック引数アドレス (NSAA) が現在の SP に設定されます。
メモリに結果を返す関数が呼び出されると、結果のアドレスが r0 に配置され、NCRN が r1 に設定されます。
ステージ B: 引数のプレパディングおよび拡張
リストの各引数には、次のリストで最初に一致する規則が適用されます。
呼び出し元や呼び出し先からサイズを静的に決定できない複合型の引数の場合、引数はメモリにコピーされ、そのコピーへのポインターによって置き換えられます。
引数が 1 バイトまたは 16 ビット ハーフ ワードの場合、32 ビット フル ワードにゼロ拡張または符号拡張され、4 バイト引数として取り扱われます。
引数が複合型の場合、サイズが最も近い 4 の倍数に丸められます。
ステージ C: レジスタおよびスタックへの引数の割り当て
リストの各引数には、引数が割り当てられるまで次の規則が順番に適用されます。
引数が VFP 型で、適切な型の連続した未割り当ての VFP レジスタが十分にある場合、これらのレジスタのうち最小の番号のシーケンスに引数が割り当てられます。
引数が VFP 型の場合、残りのすべての未割り当てのレジスタは利用不可としてマークされます。NSAA は、引数型と正しくアラインされるまで上方向に調節され、調節された NSAA で引数がスタックにコピーされます。次に、NSAA が引数のサイズだけインクリメントされます。
引数が 8 バイト アラインメントを必要とする場合は、次の偶数のレジスタ番号まで NCRN が丸められます。
32 ビット ワードの引数のサイズが (r4 - NCRN) を超えない場合、最下位ビットが小さい番号のレジスタを占有した状態で、NCRN から始まるコア レジスタ内にその引数がコピーされます。NCRN が使用したレジスタの数だけインクリメントされます。
NCRN が r4 未満で NSAA が SP と等しい場合、引数はコア レジスタとスタックに分割されます。引数の最初の部分は、NCRN から始まり r3 までコア レジスタにコピーされます。引数の剰余は NSAA から始まるスタック上にコピーされます。NCRN が r4 に設定され、(引数のサイズ - レジスタに引き渡された量) だけ NSAA がインクリメントされます。
引数が 8 バイト アラインメントを必要とする場合は、次の 8 バイトのアラインされたアドレスまで NSAA が丸められます。
引数は NSAA でメモリにコピーされます。NSAA が引数のサイズだけインクリメントされます。
VFP レジスタは、可変個引数関数には使用されません。また、ステージ C の規則 1 および 2 は無視されます。つまり、可変個引数関数をオプションの push {r0-r3} から開始して、呼び出し元より渡された任意の追加の引数の先頭にレジスタ引数を追加し、スタックから引数リスト全体に直接アクセスできます。
整数型の値は r0 に返され、オプションで 64 ビット戻り値の r1 に拡張されます。VFP/NEON 浮動小数点または SIMD 型の値は、s0、d0 または q0 に適宜返されます。
Stack
スタックは、常に 4 バイトでアラインされ、関数境界では 8 バイトでアラインされている必要があります。この条件は、64 ビット スタック変数で頻繁に使用されるインタロック操作をサポートするために必要です。ARM EABI では、どのパブリック インターフェイスでもスタックが 8 バイトでアラインされていると示されています。一貫性を確保するために、ARM ABI 上の Windows では、すべての関数境界がパブリック インターフェイスと見なされます。
フレーム ポインターを使用する必要がある関数 (たとえば、alloca を呼び出す関数またはスタック ポインターを動的に変更する関数) は、フレーム ポインターを関数プロローグの r11 に設定し、エピローグまで変更しないでおく必要があります。フレーム ポインターを必要としない関数は、プロローグですべてのスタック更新を実行し、エピローグまでスタック ポインターを変更しないでおきます。
スタックに 4 KB 以上を割り当てる関数は、最終ページの前のページが順番に接していることを確認する必要があります。これにより、Windows がスタックを拡張するために使用するガード ページをコードが "飛び越え" ないようにできます。これは、一般的に、__chkstk ヘルパーによって行われます。このヘルパーには、4 で除算されたスタック割り当ての合計 (バイト単位) が r4 で渡され、最終のスタック割り当て量 (バイト単位) を r4 で返します。
レッド ゾーン
現在のスタック ポインターの直下にある 8 バイト領域は、解析および動的なパッチのために予約されています。この領域には、細心の注意を払って生成されたコード ([sp, #-8] に 2 つのレジスタを格納し、一時的に任意の目的で使用できる) を挿入できます。Windows カーネルでは、ユーザー モードおよびカーネル モードで例外または割り込みが発生した場合でも、この 8 バイト領域は上書きされません。
カーネル スタック
Windows の既定のカーネル モード スタックは 3 ページ (12 KB) です。カーネル モードでは、スタック バッファーの大きな関数を作成しないように注意してください。非常に小さなスタック ヘッドルームで割り込みが行われ、スタック パニック バグチェックが生じる場合があります。
C/C++ 固有の仕様
列挙の値の少なくとも 1 つに 64 ビット ダブルワード ストレージが必要になる場合を除いて、列挙は 32 ビット整数型です。そのような場合は、列挙が 64 ビット整数型に昇格されます。
他のプラットフォームとの互換性を維持するために、wchar_t は unsigned short と同等になるように定義されます。
スタック ウォーキング
Windows コードは、ファスト スタック ウォーキングを可能にするために、(/Oy (フレーム ポインターの省略)) を可能にしたフレーム ポインターを使用してコンパイルされています。一般的に、r11 レジスタは、チェーンの次のリンクである {r11, lr} ペアを示します。このペアは、スタック上の前のフレームへのポインターを指定し、アドレスを返します。プロファイリングおよびトレースを向上させるために、コードでフレーム ポインターを有効にすることをお勧めします。
例外アンワインド
例外処理中のスタック アンワインドは、アンワインド コードを使用することで可能になります。アンワインド コードは、実行可能イメージの .xdata セクションに格納されているバイト シーケンスです。このコードには、関数プロローグおよびエピローグ コードの操作が抽象的に示されています。このため、呼び出し元のスタック フレームへのアンワインドの準備段階で関数のプロローグの効果を元に戻すことができます。
ARM EABI では、アンワインド コードを使用する例外アンワインド モデルが指定されています。ただし、Windows のアンワインドでは、プロセッサが関数のプロローグまたはエピローグの中間に存在するケースを取り扱う必要があるため、この仕様は不十分です。ARM 上の Windows の例外データおよびアンワインドの詳細については、「ARM 例外処理」を参照してください。
生成されたコードが例外処理に関与できるように、RtlAddFunctionTable の呼び出しで指定された動的な関数テーブルおよび関連する関数を使用して、動的に生成されたコードを記述することをお勧めします。
サイクル カウンター
Windows を実行中の ARM プロセッサは、サイクル カウンターをサポートする必要がありますが、このカウンターを直接使用すると問題が発生する場合があります。このような問題を回避するために、ARM 上の Windows は未定義のオペコードを使用し、正規化された 64 ビット サイクル カウンター値を要求します。C または C++ からは __rdpmccntr64 組み込みを使用して適切なオペコードを出力し、アセンブリからは __rdpmccntr64 命令を使用します。サイクル カウンターを読み取るには、Cortex-A9 で約 60 サイクルかかります。
カウンターはクロックではなく真のサイクル カウンターであるため、カウント周波数はプロセッサ周波数に従って変化します。経過したクロック時間を測定する場合は、QueryPerformanceCounter を使用します。
参照
関連項目
Visual C++ の ARM への移行に関する一般的な問題