MacOSXにおけるPowerPCのレジスタとかの役割

以下はMacOSXでのPowerPCスタックフレームや、レジスタの役割を書いたものです。ですので、Classic MacOSやLinux for PPCなどとは、一部のレジスタの取り決めが異なるかもしれません。
また、32bitモード用であるため、64bitモードで動かす場合は、一部のレジスタのサイズが違うために、32bitモードとはスタックフレーム構築に必要なサイズが異なる可能性があります。

レジスタ

PowerPCにおいて、ユーザレベルで触れる可能性があるレジスタは以下のようなものがある。

見逃したレジスタが他にもあるかもしれないが、普通は上6つの種類のレジスタで十分なはずだ。

レジスタ名レジスタ名bit数備考
汎用レジスタr0 ~ r313264bitモードではサイズが64bitである。
浮動小数レジスタf0 ~ f3164単精度も倍精度もこのレジスタを使う。浮動小数演算用。
ベクトルレジスタv0 ~ v31128AltiVecを搭載したCPU(G4, G5など)にしか存在しない。AltiVec演算用。
状態レジスタcr0 ~ cr74比較命令の結果を保存するレジスタ。
リンクレジスタLR32分岐に利用。関数を呼ぶ時、戻り先アドレスが与えられる。関数から戻るのに使われることが多い。64bitモードではサイズが64bitである。
カウンタレジスタCTR32繰り返し条件や、関数ポインタへのジャンプに使われることが多い。64bitモードではサイズが64bitである。
整数演算例外レジスタXER32キャリー、オーバーフロー、文字列ロード/ストア命令に使用される。64bitモードではサイズが64bit。
浮動小数演算の状態およびコントロールレジスタFPCSR32浮動小数演算の例外などに使用。
ベクトル特殊レジスタVRSAVE32AltiVec搭載CPUのみ。AltiVec演算の補助。

レジスタの役割と退避

PowerPCにはたくさんのレジスタが存在するので、それらに役割づけをしなければ使いづらい。また、関数呼び出しが行われた時に、どのレジスタをスタックに積まねばならないかもわからないと、壊してはいけないデータを壊してしまう可能性もある。MacOSXでは(おそらくほとんどのOSでも)、以下のような役割分担になっている。

レジスタ名レジスタ内容の特性備考
汎用レジスタ
r0揮発性PowerPCの命令によっては、特殊な扱いになる。
r1非揮発性スタックポインタ。
r2揮発性Mac OSX以外のOSではTOC(Table Of Contents。グローバルデータへのアクセス参照用)に使用されるが、OSXでは揮発性レジスタなので、r3などと同様に扱える。
r3 ~ r10揮発性引数/返り値渡しに利用される。関数内で使用可能。
r11揮発性関数内で使用可能。
r12揮発性関数ジャンプに利用される。通常のプログラム部分では使用すべきではない。
r13 ~ r31非揮発性関数内で使う場合、必ず元の内容をスタックに保存すること。
浮動小数レジスタ
f0揮発性関数内で使用可能。
f1 ~ f13揮発性引数/返り値の渡しに利用される。関数内で使用可能。
f14 ~ f31非揮発性関数内で使う場合、必ず元の内容をスタックに保存すること。
ベクトルレジスタ
v0 ~ v1揮発性関数内で使用可能。
v2 ~ v13揮発性引数/返り値の渡しに利用される。関数内で使用可能。
v14 ~ v19揮発性関数内で使用可能。
v20 ~ v31非揮発性関数内で使う場合、必ず元の内容をスタックに保存すること。
状態レジスタ
cr0揮発性汎用レジスタ命令のデフォルトの条件結果格納先。関数内で使用可能。
cr1揮発性浮動小数演算命令の例外条件の結果格納先。関数内で使用可能。
cr2 ~ cr4非揮発性関数内で使用する場合、状態レジスタの内容をスタックに保存すること。
cr5揮発性関数内で使用可能。
cr6揮発性ベクトル演算命令のデフォルトの条件結果格納先。関数内で使用可能。
cr7揮発性関数内で使用可能。
その他のレジスタ
VRSAVE非揮発性ベクトル演算命令を使うのなら、保存すること。
LR揮発性内部で関数呼び出しを行う関数は、必ずスタックに保存すること。
CTR揮発性関数を呼ばないループのカウンタに利用することが多い。
XER揮発性
※揮発性レジスタは、関数を呼んでそこから返ってきた時に、内容が不明のレジスタ。非揮発性レジスタは、関数から返ってきても内容が変わっていないレジスタ。

スタックフレーム

スタックフレームの概要

スタックを有効に使うために、PowerPC用のスタックフレーム構造が定義されている。スタックフレームは、負の方向に、16byte単位で伸びる。スタックポインタはr1に格納されている。

なお、関数へ入った時にリンクレジスタに格納されていたアドレスが、関数から戻る先である。

スタックフレームの大まかな図
呼び出し側のスタックエリア引数エリア
連結エリア
呼び出された関数のスタックエリア退避エリア
局所変数エリア
引数エリア
連結エリア
未使用スタック領域

なお、もし関数が内部で関数呼び出しを行わない葉の関数で、スタックに対し必要としているメモリサイズが224byteを上回らないのであれば、スタックフレームを構築せずに未使用領域を直接使用してよい。

連結エリア

連結エリアは、スタックポインタ、リンクレジスタ、状態レジスタを保存しておく、サイズが24byteのエリアである。ただし、リンクレジスタと状態レジスタの保存先は呼び出し側関数のスタックフレーム内の連結エリアであり、それらを保存する連結エリアにあるスタックポインタは呼び出し側関数がセットしたものである。

なお、関数内部でリンクレジスタや状態レジスタの内容を変更しないのなら、別に保存する必要はない。

連結エリアの構成(32bitモード)
呼び出し側引数エリア
呼び出し側連結エリア TOCレジスタ内容保存先(スタックポインタ+20のアドレス)※Mac OSXでは未使用
予約領域
呼び出された側のリンクレジスタ保存先(スタックポインタ+8のアドレス)
呼び出された側の状態レジスタ保存先(スタックポインタ+4のアドレス)
呼び出し側のスタックポインタ
呼び出された側のスタックフレーム

連結エリアの構築と破棄の例を、アセンブラを使って表すと、以下のようになる。

HogeHoge:;関数の先頭
mflrr0;リンクレジスタを保存
stwr0, 8(r1)
mfcrr0;状態レジスタを保存
stwr0, 4(r1)
stwur1, -STACK_FRAME_SIZE(r1);スタックフレームの構築

...;関数内部

lar1, STACK_FRAME_SIZE(r1);スタックフレームの破棄
lwzr0, 4(r1);状態レジスタの内容の復帰
mtcrr0
lwzr0, 8(r1);リンクレジスタの内容の復帰
mtlr0
blr;関数から抜ける

64bitモードでは、リンクレジスタやスタックポインタのサイズが異なるので、連結エリアの構成が異なると思われる。

退避エリア

退避する必要があるレジスタがある場合、この領域に内容を退避すること。

退避エリアの構成(32bitモード)
浮動小数保存領域
汎用レジスタ保存領域
VRSAVEレジスタ保存領域(4byte)
空(スタックのアドレス値を16の倍数に合わせる、パディング領域)
ベクトルレジスタ保存領域

局所変数エリア

局所変数はこの領域に作成すること。

引数エリア

引数エリアは、内部で関数呼び出しを行う関数に必要な領域で、内部で呼び出す関数の中で一番引数が多いものを引数エリアに置いても大丈夫なサイズを確保する必要がある。なお、最低サイズは、r3 ~ r10を格納できるサイズ(32byte)である。

この領域は、主に以下のことに使われる。

引数の渡し方

整数やアドレスは、汎用レジスタr3 ~ r10に渡される。

浮動小数に関しては、単精度でも倍精度でも、浮動小数レジスタf1 ~ f13に渡す。その際、その浮動小数データのサイズ分だけの汎用レジスタを、使用しているものと見なしてとばす。つまり、単精度の場合1つ、倍精度の場合2つの汎用レジスタをとばす。これは汎用レジスタが足りなくなり、引数エリアにはみ出る時でも同じである。

なお、可変長引数をとる関数へ浮動小数を引数として渡す場合、呼び出される関数は、次の引数がどのレジスタに入っているかわからないので、全ての引数を汎用レジスタに渡す。その際、浮動小数は全て倍精度扱いにする。

ベクトルデータは浮動小数と異なり、汎用レジスタをとばす必要はなく、単純にv2 ~ v13に渡される。ただし、ベクトルレジスタが足りなくなった時には、引数エリアを利用せざるを得ない。

関数宣言がされていない関数を呼び出す場合、可変長引数関数に渡す場合と固定長引数関数を渡す場合との、両方の場合を考えて引数を渡す。

固定長引数関数の例:
void fooFunc (int i1, float f1, double d1, short s1, double d2,
unsigned char c1, unsigned short s2, float f2, int i2);

この関数の呼び出しの為に用意する引数エリアは以下の通り。

struct params {
    int                    p_i1;  r3に当てられる
    float                  p_f1;  f1(r4の代わり)
    double                 p_d1;  f2(r5, r6の代わり)
    short                  p_s1;  r7。サイズはintと同じと見なす。
    double                 p_d2;  f3(r8, r9の代わり)
    unsigned char          p_c1;  r10。サイズはintと同じと見なす。
    unsigned short         p_s2;  引数エリアに保存。サイズはintと同じとみなす。
    float                  p_f2;  f4(引数エリアも必要)
    int                    p_i2;  引数エリアに保存。
};

これを引数エリア上で見ると、このようになる。

引数名割当て場所使用byte数備考
呼び出し側連結エリア
i1r34
f1f14代わりにr4をスキップ
d1f28代わりにr5, r6をスキップ
s1r74スタック内では、4byteの内の下2byteにデータを格納
d2f38代わりにr8, r9をスキップ
c1r104スタック内では、4byteの内の下1byteにデータを格納
s2引数エリア4スタック内では、4byteの内の下2byteにデータを格納
f2f44f4に値は格納されるが、引数エリアに領域を用意すること
i2引数エリア4
局所変数エリア

引数エリアの先頭は、スタックが負の方向に伸びることから、連結エリアに近い方(下の方)ということになる。

引数の返し方