SCOOT C-Compiler SCコードジェネレータ 1996/2/14 小笠原博之 SC62015(ESR-L)用のコードジェネレータについての補足説明。 ●演算範囲 char, short はそれぞれ 1byte, 2byte の値を取ります。long と pointer 型は 3byte の値を取りますが、そのうち下位 20bit だけが有効になります。 上位 4bit は 0 です。よって演算範囲は次の通りです。 int 2byte (== short) 0〜65535 char 1byte 0〜255 short 2byte 0〜65535 long 3byte(20bit) 0〜1048575 pointer 3byte(20bit) 0〜1048575 そのため、long 宣言に 32bit の値が必要なプログラムは動作しません。 ●符号型 現在のコードジェネレータでは、コンパイラから渡される符号型情報を処理 していません。よって、扱える値は常に unsigned になってしまいます。こ れは SC62015 の CPU 命令セットに符号を扱える命令が全く無いためです。 ただし、一部の符号を扱うためのライブラリ関数や演算は存在し、その場合 は最上位 bit を符号と見なしています。(2の補数表現) 演算時に符号がないことが大きな影響を与えると思われる演算は次の通り * 乗算 / 除算 <,>,<=,>= 大小比較 >> bit右シフト 符号演算を直接扱う演算子(これは意図した通りに動作する) - 符号反転 将来的に改善する可能性はありますが、その場合 int 型の扱いで動作効率 が若干落ちることになります。 ●メモリの格納順 SC62015(ESR-L)は、データのバイト並びはリトルエンディアンで統一されて います。(旧SC SC61860 ESR-H はバイト並びに両方式の混在型であった) よって変数など値の格納は最下位バイトから順に並びます。データをファイ ルに書き出す場合も、この順番になります。 ●未サポート演算 現在のコードジェネレータでは、演算ストア系 &=, |=, ^=, <<=, >>= ポインタの指示先や配列へ代入する場合 が未サポートで、コード出力ができません。(エラーになります) 直接単純 変数に対する上記の演算はすべて対応しています。 <例> *var &= 0xff; × var &= 0xff; ○ ●レジスタ変数 レジスタ変数はアドレスを持たないすべての変数型に宣言できます。ただし 扱いが標準 C 言語とは異なっていて特殊です。 レジスタ変数はすべて内RAMから確保されます。ですが、確保時にそれらを スタックに退避することがありません。よってレジスタ変数は static の記 憶クラスを持ち、再帰するプログラム内でのワーク変数に使用できません。 また auto でなければならない関数引数への register 宣言ができません。 global でのレジスタ変数の割り付けも可能で、極めて特殊な扱いになって います。 使用できるレジスタ変数の個数は、そのプログラム内部で合計して 26個まで に制限されます。ですがこの制限をコンパイラは知りません。範囲を越えて もエラーが出ないので注意が必要です。 レジスタ変数を使用した場合の、動作効率の向上はどのくらいであるか、以 下に簡単なテスト結果をあげます。なお、この結果は今後さらに改善されて いく可能性があります。 for 〜 による 1800000 回ループ ・96/3/7 ver 生成コード 実行時間 ループ変数auto 174byte 36 sec ループ変数static -O 178byte 30 sec ループ変数auto -O 170byte 30 sec ループ変数register -O 162byte 23 sec レジスタ変数の確保は、内RAM の 0〜BP-$40 の範囲が用いられます。確保は 上位から下位に向かって行われます。 ●作成できるプログラムサイズ 作成できるプログラムコードは、ページ境界を越えることができません。つ まりコード領域は最大 64Kbyte となります。これは条件ジャンプやサブルー チンコールがすべて near ジャンプでコード展開されるためです。データに はサイズやページ境界による制限は一切ありません。bss なら bss は、可能 なら最大 1Mbyte まで使用できます。 ●最適化 SCOOT C-Compiler における SC コードジェネレータの出力コードレベルは MAX (コードジェネレータ命令は全部サポート)であり、コンパイラの持つ基 本的な最適化はすべて有効になっています。 さらに SC コードジェネレータは、コンパイラから与えられる情報を駆使し て独自の場合分けを行い、コード効率の改善を試みています。 さらにコンパイルドライバに -O オプションを与えることによって、SC 専用 のアセンブラコードオプティマイザを呼び出すことができます。-O の後ろに オプティマイズレベルを与えることができます。-O は -O1 と同じであり、 レベルをあげるたびにオプティマイザのパス数が増えます。よって、このオ プティマイザを使う場合は大量のメモリを消費し、さらにコンパイル時間も 大幅に長くなります。 最後の最後に、アセンブラ scasm がオプティマイズをかけます。ジャンプ命 令の相対ジャンプへの置き換え、無駄なジャンプ命令の除去、il への定数代 入の置き換え、使わないライブラリルーチンの取り除き、を行います。 つまり、次の4つのレベルでオプティマイズがかかっていることになります。 (1) SCOOT C-Compiler の構文レベルオプティマイズ (2) SCコードジェネレータの出力コード翻訳時の場合分け (3) オプティマイザによる出力されたアセンブラコードの定型置換 (4) scasm による命令単位の最終的なオプティマイズ コンパイルオプションと最適化 無指定 : (1),(2),(4)の-q -O または -O1 : (1),(2),(3)を1パス,(4)の-qa -O2 : (1),(2),(3)を2パス,(4)の-qa ●コンパイラのレジスタ割り当て このコードジェネレータが使用するレジスタの分類は次の通りです。 ba char/int/short型の演算,関数の返り値 x long/pointer型の演算,関数の返り値 i char/int/short演算時の演算サブ y long/pointer型の演算時の演算サブ u 演算パラメータの保存、引数渡し、ローカル変数の確保に 用いるスタックポインタ s サブルーチン呼び出し時の戻り番地を保持するスタックの ポインタ (__WA)3 ランタイムルーチンや複雑な演算時にコンパイラが用いる (__WB)3 内RAM上のワークメモリ。通常 BP〜BP+8 に確保される。 (__WC)3 スタートアップライブラリでアドレス変更可能。 <旧バージョンとの違い> 初期バージョンでは a char型のアキュムレータ i int/short型のアキュムレータ x long/pointer型のアキュムレータ に設定されていました。これは Galaxy-Forth の仕様とライブラリ を元に開発を行ったためです。またローカル変数の確保は s スタッ クから行い、関数呼び出し時のスタックフレームと独立していまし た。よって旧バージョンで作られたライブラリやスタートアップルー チンとは互換性が一切ありません。 ●スタックフレーム 引数渡し、ローカル変数は、すべて u スタックを使用します。ただし、こ れらスタックはすべてシステムスタックをそのまま使用しているため、大量 にローカル変数を確保したりネストを深くするとハングアップしてしまいま す。 変数はできるだけグローバルに確保するなどの対処を行って下さい。特に配 列バッファはグローバルか static で、そうでなければ malloc() で確保す るようにして下さい。 関数呼び出し時の引数はすべて後ろから評価が行われ、その順番でスタック に積まれます。これは他の多くの Cコンパイラと同じ仕様です。 スタックに引数を積む場合は、Cコンパイラは通常可能な限り int に統一し ようとします。SC の場合 int == short であるため、型変換が発生するのは char -> int の場合のみです。long や pointer はそのまま 3byte のサイズ でスタックに積まれます。 スタックフレームの開放は、引数の分も含めて呼び出された関数が行わなけ ればなりません。そのため関数呼び出し時の引数の個数は省略できず、また 呼び出された関数の引数の宣言と厳密に合っていなければなりません。(しか しコンパイラはその違いや間違いを判断することができない) またこの制限により、可変個数の引数を取る関数は、アセンブラでなければ 記述ができません。 ローカル変数のフレームポインタとして u レジスタが用いられます。オフ セット値はその時点でのスタックの個数によって常に変化します。この変化 はコードジェネレータが計算します。 よって、ローカル変数として確保できるメモリは、オフセットの関係から引 数とスタック上のデータ個数を合せて 255 byte までになります。一見少な いようですが、もともとスタックが限られているので、これでも多いくらい です。ローカル変数を大量に確保してはいけません。 補足: 本来であればスタック領域はユーザープロセスが確保すべきですが、 もともと ZAURUS で使用しているメモリはプログラムコードを置く領 域としては考えられていません。そのためシステムコール呼び出しや 割り込みが発生した合に、メモリ配置が切り替ってしまう可能性があ ります。事実、スタックをユーザーメモリに確保するとシステムコー ル時にハングアップしてしまいます。そのため、現状ではシステムス タックをそのまま用いるというこのような仕様になっています。 ●内RAMマップ コードジェネレータやランタイムルーチンは、実行時の演算ワークとして (BP)〜(BP+8) を使います。(ライブラリではさらに (BP+9)〜(BP+25) を使 います) また (0)〜(BP-$40) までは、レジスタ変数として使われます。レ ジスタ変数の確保は後ろから行われます。 $00 | register 変数用 $4f から前の方に向かって確保される $4f $50 | (システムワーク?) $8f $90 BPの設定値(startupルーチンによってこの値は変更可能) | 演算用ワーク(__WA,__WB,__WC 3byte×3) $98 ライブラリでも一時ワークとして使用 $99 | ライブラリ X保存用 $9b $9c | ライブラリ Y保存用 $9e $9f | ライブラリ I保存用 $a0 $a1 | formatライブラリでジャンプテーブルに使用 $a9 $aa | 未使用 BP〜 の演算ワーク、ライブラリワークは、呼び出したルーチン内で破壊し て構いません。 ●アセンブラインターフェース アセンブラで関数を記述し、それを C言語から呼び出したり、その逆も可能 です。 引数は後ろから順番にスタックに push されています。よってメモリ上には 引数が順番に並んでいることになります。 アセンブラルーチンでは、引数は popu ですべて受け取って下さい。レジス タの保存は BP やスタックポインタ、内RAM 上から確保しているレジスタ変 数以外、すべて破壊しても構いません。 同様にアセンブラから C言語のルーチンを呼び出す場合は、すべてのレジス タが破壊される可能性があります。必要なレジスタは呼び出し元が保存する ようにして下さい。 ●利用制限 必ず readme.doc を参照したのち、その利用制限に従って下さい。 ●参考文献 (1)「PC-E500,PC-1480U活用研究」工学社 (2) PEN_SMPL.ASM/LCDC解析資料,加古英児 -- 小笠原博之 oga@art.udn.ne.jp http://www.vector.co.jp/authors/VA004474/