web-dev-qa-db-ja.com

ARM C呼び出し規約で保存するレジスタは何ですか?

アームアセンブラーを最後にコーディングしてからしばらく経ちましたが、細部に少し錆びています。 armからC関数を呼び出す場合、r0-r3とlrを保存するだけでいいのですか?

C関数が他のレジスタを使用している場合、それらをスタックに保存して復元する必要がありますか?言い換えると、コンパイラーはC関数に対してこれを行うためのコードを生成します。

たとえば、アセンブラー関数でr10を使用する場合、その値をスタックまたはメモリーにプッシュし、C呼び出し後にポップ/復元する必要はありませんか?

これはarm-eabi-gcc 4.3.0用です。

50
richq

コンパイルするプラットフォームの [〜#〜] abi [〜#〜] に依存します。 Linuxには、2つのARM ABI;古いものと新しいもの。AFAIK、新しいもの(EABI)は実際にはARMのAAPCSです。完全なEABI定義は現在有効です ARMの情報センターにあります

AAPCS、§5.1.1 から:

  • r0-r3は引数およびスクラッチレジスタです。 r0-r1は結果レジスタでもあります
  • r4-r8は呼び出し先保存レジスタです
  • r9は呼び出し先保存レジスタであるかどうか(AAPCSの一部のバリアントでは特殊レジスタ)
  • r10-r11は呼び出し先保存レジスタです
  • r12-r15は特殊レジスターです

呼び出し先保存レジスタは、呼び出し先によって保存される必要があります(呼び出し元保存レジスタとは反対に、呼び出し元がレジスタを保存します)。したがって、ifこれは使用しているABIです。別の関数を呼び出す前にr10を保存する必要はありません(他の関数がそれを保存する責任があります)。

Edit:使用しているコンパイラは違いはありません。特にgccはいくつかの異なるABI向けに設定でき、コマンドラインで変更することもできます。それが生成するプロローグ/エピローグコードを見るのはそれほど便利ではありません。それは各関数に合わせて調整されているためですand関数)。


用語:「callee-save」は「non-volatile」または「call-preserved」の同義語です。 呼び出し先および呼び出し元の保存レジスタとは
関数呼び出しを行う場合、r4-r11(r9を除く)の値はr0-r3(call-clobbered/volatile)ではなく(call-preserved)後にまだ存在すると仮定できます。

69
CesarB

NEONレジスタに関する欠落情報を追加するには:

AAPCS から、§5.1.1コアレジスタ:

  • r0-r3は引数およびスクラッチレジスタです。 r0-r1は結果レジスタでもあります
  • r4-r8は呼び出し先保存レジスタです
  • r9は呼び出し先保存レジスタであるかどうか(AAPCSの一部のバリアントでは特殊レジスタ)
  • r10-r11は呼び出し先保存レジスタです
  • r12-r15は特殊レジスターです

AAPCSから、5.1.2.1 VFPレジスタの使用規則:

  • s16–s31(d8–d15、q4–q7)を保持する必要があります
  • s0–s15(d0–d7、q0–q3)およびd16–d31(q8–q15)である必要はない保存された

元の投稿:
arm-to-c-calling-convention-neon-registers-to-save

24
Pavel

64ビットARM、A64の場合(ARM 64ビットアーキテクチャのプロシージャコール標準から)

A64命令セットには31個の64ビット汎用(整数)レジスタが表示されます。これらにはr0-rというラベルが付いています。 64ビットコンテキストでは、これらのレジスタは通常、名前x0-x;を使用して参照されます。 32ビットコンテキストでは、レジスタはw0-wを使用して指定されます。さらに、スタックポインタレジスタ[〜#〜] sp [〜#〜]は、限られた数の命令で使用できます。

  • [〜#〜] sp [〜#〜]スタックポインター
  • r LRリンクレジスタ
  • r29 FPフレームポインター
  • r19…r28呼び出し先保存レジスタ
  • r18必要に応じてプラットフォームレジスタ。それ以外の場合は一時レジスタ。
  • r17 IP1 2番目の手続き内呼び出し一時レジスタ(呼び出しベニアとPLTコードで使用可能)。それ以外の場合は、一時的なレジスタとして使用できます。
  • r16 IP0最初のプロシージャ内コールスクラッチレジスタ(コールベニアおよびPLTコードで使用可能)。それ以外の場合は、一時的なレジスタとして使用できます。
  • r9…r15一時レジスタ
  • r8間接結果ロケーションレジスタ
  • r0…r7パラメータ/結果レジスタ

最初の8つのレジスタr0-r7は、引数値をサブルーチンに渡し、関数から結果値を返すために使用されます。また、ルーチン内で中間値を保持するためにも使用できます(ただし、一般的には、サブルーチン呼び出し間のみ)。

レジスタr16(IP0)およびr17(IP1)は、リンカによって、ルーチンとそれが呼び出すサブルーチン間のスクラッチレジスタとして使用できます。また、ルーチン内で使用して、サブルーチン呼び出し間の中間値を保持することもできます。

レジスタr18の役割はプラットフォーム固有です。プラットフォームABIが、プロシージャ間状態(スレッドコンテキストなど)を保持する専用の汎用レジスタを必要とする場合、その目的でこのレジスタを使用する必要があります。プラットフォームABIにそのような要件がない場合は、追加の一時レジスタとしてr18を使用する必要があります。プラットフォームABI仕様では、このレジスタの使用法を文書化する必要があります。

[〜#〜] simd [〜#〜]

ARM 64ビットアーキテクチャには、さらに32個のレジスタv0-v31があり、SIMDおよび浮動小数点演算で使用できます。正確な名前レジスタのサイズは、アクセスのサイズを示して変化します。

注: AArch32とは異なり、AArch64では、SIMDおよび浮動小数点レジスタの128ビットおよび64ビットビューは、狭いビューで複数のレジスタとオーバーラップしません。so q1、d1およびs1はすべて、レジスタバンクの同じエントリを参照します。

最初の8つのレジスタv0-v7は、引数値をサブルーチンに渡し、関数から結果値を返すために使用されます。また、ルーチン内で中間値を保持するためにも使用できます(ただし、一般的には、サブルーチン呼び出し間のみ)。

レジスタv8-v15は、サブルーチン呼び出し間で呼び出し先によって保持される必要があります。残りのレジスター(v0-v7、v16-v31)を保存する必要はありません(または呼び出し元が保存する必要があります)。さらに、v8-v15に保存されている各値の下位64ビットのみを保持する必要があります。大きな値を保持するのは呼び出し側の責任です。

17
auselen

CesarBとPavelの回答はAAPCSからの引用を提供しましたが、未解決の問題は残っています。呼び出し先はr9を保存しますか? r12はどうですか? r14はどうですか?さらに、回答は非常に一般的であり、要求されたarm-eabiツールチェーンに固有のものではありませんでした。ここでは、どのレジスターが呼び出し先に保存され、どのレジスターが保存されないかを確認するための実用的なアプローチを示します。

次のCコードには、レジスターr0〜r12およびr14の変更を要求するインラインアセンブリブロックが含まれています。コンパイラは、ABIに必要なレジスタを保存するコードを生成します。

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

コマンドラインarm-eabi-gcc-4.7 -O2 -S -o - foo.cを使用して、プラットフォームのスイッチ(たとえば、-mcpu=arm7tdmiなど)を追加します。このコマンドは、生成されたアセンブリコードをSTDOUTに出力します。次のようになります。

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

コンパイラが生成したコードは、r4-r11を保存および復元することに注意してください。コンパイラーはr0-r3、r12を保存しません。 r14(エイリアスlr)を復元することは、終了コードが保存されたlrをr0にロードし、「bx lr」ではなく「bx r0」を実行する可能性があることを経験から知っているように、純粋に偶然です。 -mcpu=arm7tdmi -mno-thumb-interworkを追加するか、-mcpu=cortex-m4 -mthumbを使用して、次のようなわずかに異なるアセンブリコードを取得します。

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

繰り返しますが、r4-r11は保存および復元されます。ただし、r14(別名lr)は復元されません。

要約する:

  • r0-r3はnot callee-saved
  • r4-r11は呼び出し先に保存されます
  • r12(エイリアスIP)はnot callee-saved
  • r13(エイリアスsp)は呼び出し先保存済み
  • r14(別名lr)はnot callee-saved
  • r15(エイリアスpc)はプログラムカウンターであり、関数呼び出しの前にlrの値に設定されます

これは、少なくともarm-eabi-gccのデフォルトの場合に当てはまります。結果に影響する可能性のあるコマンドラインスイッチ(特に-mabiスイッチ)があります。

7
Sven

また、少なくとも関数呼び出しと割り込みのCortex M3アーキテクチャには違いがあります。

割り込みが発生すると、R0-R3、R12、LR、PCがスタックに自動的にプッシュされ、IRQから自動POPが返されます。 IRQルーチンで他のレジスタを使用する場合、手動でStackにプッシュ/ポップする必要があります。

この自動プッシュとPOPは、関数呼び出し(ジャンプ命令)のために作られたとは思いません。慣例では、R0-R3は引数、結果、またはスクラッチレジスタとしてのみ使用できると規定されているため、関数を呼び出す前に値を保存する必要はありません。ただし、割り込みと同じように、関数で他のCPUレジスタを使用する場合は、それらをすべて保存する必要があります。

0
Frik