Barbarism begins at internet:2010年09月08日分

2010/09/08(Wed)

[pcc] ほんやく!

@その2

これの続きですよ。
前回に増していい加減な訳ですがクレームは/dev/nullへ。

NODE構造体
  コンパイラの基本はツリー構造を使えちゅうことです、これはノード(NODE)という構造で表現されます。
  このノードは二項(binary)、単項(unary)あるいは末端(leaf)であって、それぞれ異なる型を表現します。
  全てのノードは演算子(op)あるいは型(type)で構成されとります。

  ノードは一致するものがあれば命令(instruction)に変換されます、この処理は
  テーブルを検索することで行います。ノードからの出力は常にレジスタとみなされますが
  入力ノードは蟻ノード(?)とみなされるかもしれません。

  以下に一覧として中間ノードタイプをまとめました。フロントエンドはこれ以外のノードタイプをも
  追加する方法を選ぶかもしれません。表にしたのは(ツリー)構造の各ノード種別に使用される要素です。
 こいつらがフロントエンド(つまりpass1)かバックエンド(pass2)いずれかだけでしか有効でないなら
 名前の後ろに(1)か(2)と書き加えてあります。

注意事項:
	n_op(演算子), n_type(型) そして n_qual(修飾子)はすべてのノードタイプに有効です。
	n_name(ノード名称)は決してぬるぽにはなりません、使用しない場合は "" になるとです。
	n_lval(左辺値)はその移植対象におけるもっとも大きな数値を保持できます。
	n_dcon(浮動小数点数定数)は最低でもその移植対象のlong double型を表現できます。
	全てのノードの変換結果は最後にはレジスタになると考えられます(?)。

末端ノード
        NAME
                メモリ上の位置の参照です、例えば変数にアクセスする場合など。
                NAMEから取得したアドレスはICONになります。
                n_lval - NAMEへのオフセットです、例えば a[3] とか
                n_sp (1) - NAMEのシンボルテーブル中のエントリになります
                n_name (2) - 名前の文字列を指すポインタです

        ICON
                数値型の定数値です、素の数値にもアドレス値にもなります。
                n_lval - 定数の値です
                n_sp (1) - アドレスの場合、シンボルテーブル中のエントリになります
                n_name (2) - シンボルの名前です

        FCON
                浮動少数点数の定数値です。
                n_dcon - 定数です

        REG
                レジスタのノードです、通常生成するのはレジスタアロケータの結果だけ
                ですが、しかしながらそのほかの場所でも生成されることがありえます。
		それは例をあげれば、ターゲットがレジスタ引数を持つ場合なんかです。
		n_rval - 数値でレジスタ変数になります

        OREG
                メモリ参照で、レジスタに対するオフセットになります。
                通常はpass2のoreg()内でUMUL-PLUS-REG-ICONのチェインにより作られます。
                ただし対象CPUがサポートしてる場合だけですが。
                n_lval - 定数でレジスタからのオフセットになります
                n_rval - 数値でレジスタのインデックス順になります

	TEMP
		一時ノードで、生成されるのはフロントエンドが値を
		レジスタでもメモリでもどっちにでも置けるようにするためです。
		レジスタアロケータは後でそれらを可能であればレジスタに置こうと試みます。
		n_rval - 数値で一時変数になります

力尽きた。

@ゆっくりによる pcc の内部解説

つーことでコードリーディングのお時間、ゆっくりの声でよんでね!

↑のドキュメントで触れられている

  • 二項(binary)
  • 単項(unary)
  • 末端(leaf)

のノード分類ですが、実際にプログラム中では
以下の定数として宣言されています。

[ mip/manifest.h ]
46 /*
47  * Node types
48  */
49 #define LTYPE   02              /* leaf */
50 #define UTYPE   04              /* unary */
51 #define BITYPE  010             /* binary */

そして{末端,単項,二項}ノード種別についても以下同文。
ノード種類の説明は次回翻訳とあわせてです。

[ mip/node.h ]
147 /*
148  * Value nodes.
149  */
150 #define NAME    2
151 #define ICON    4
152 #define FCON    5
...
234 #define MAXOP   58

そして NODE は n_op フィールドにこのノード種別を保持します。

[ mip/node.h ]
62 typedef struct node {
63         struct  node *next;
64         int     n_op;
65         union {

ノード種別からノード分類へ変換するには、キバヤシよろしくノイズ除去に以下のマクロを使って
「ガラッ話は聞いた世界は滅亡するAA略」してください。

	optype(p->n_op) == BITYPE

この optype はマクロで以下の場所で宣言されています。

[ mip/pass2.h ]
400 #define optype(o)       (dope[o]&TYFLG)

このマクロ中で使われている dope は int 型の配列で以下のよに定義されとります。
他にも opst ちうのもありますが、これについては後述。

[ mip/pass2.h ]
360 extern  int dope[];     /* a vector containing operator information */
361 extern  char *opst[];   /* a vector containing names for ops */

配列の添字にはさっきのノード種別を使い、上限値は MAXOP となります。

このdope及びopstはフロントエンドが mkdope() を呼んだタイミングで初期化されます。

[ mip/common.c ]
319 int dope[DSIZE];
320 char *opst[DSIZE];
...
385 void
386 mkdope()
387 {
388         struct dopest *q;
389
390         for( q = indope; q->dopeop >= 0; ++q ){
391                 dope[q->dopeop] = q->dopeval;
392                 opst[q->dopeop] = q->opst;
393         }
394 }

初期化の為の元ソースは mip/common.c にテーブルが宣言されてる indope です。

[ mip/common.c ]
322 struct dopest {
323         int dopeop;
324         char opst[8];
325         int dopeval;
326 } indope[] = {
327         { NAME, "NAME", LTYPE, },
328         { REG, "REG", LTYPE, },
...
382         { -1,   "",     0 },
383 };

ですので今回のINCR/DECRのpass2化のように種別を増やすなんてときに
弄るのはここになります。

opst は ccom -Xb などのデバックオプションでノードツリーをダンプする際に
ノードタイプを画面表示可能な文字列に変換する用に以下同文、例えば mip/reader.c では

664         if (rv == FFAIL && !q)
665                 comperr("Cannot generate code, node %p op %s", p,opst[p->n_op]);

のように使われています、まぁ今どきのプログラムならNODE構造体にtoString()とかto_strみたいな
関数を用意するんでしょうが、なにせ30年前のコードですので以下略

これらのノード種別は pass2 で有効なものであって、言語(=フロントエンド)非依存です。
言語に依存するノード種別については C であれば cc/ccom/pass1.h に定義されています。

539 /*
540  * C compiler first pass extra defines.
541  */
542 #define QUALIFIER       (MAXOP+1)
...
572 #define INCR            (MAXOP+26)
573 #define DECR            (MAXOP+27)

今回問題になっている INCR/DECR は今のところ言語依存の扱いですやね。
なので pass1.h に定義され、pass2に渡される前に別の形に書き換えられてしまう運命なのです。

話し戻してさっきの dope[] の添字にこの pass1 オンリーのノード種別を渡したりすると、たうぜん
array index out of bounds の悲劇が発生しますので、cc/ccom/trees.c には cdope() という
wrapper 関数が定義されていて、pass1ではこっちを使います。

2647 int
2648 cdope(int op)
2649 {
2650         if (op <= MAXOP)
2651                 return dope[op];
2652         switch (op) {
...
2696         case INCR:
2697         case DECR:
2698                 return BITYPE|ASGFLG;
2699         }
...

同様に opst にも wrapper が用意されています。

2601 char *
2602 copst(int op)
2603 {
2604         if (op <= MAXOP)
2605                 return opst[op];
...
2632         SNAM(INCR,++)
2633         SNAM(DECR,--)
...

今日はここまで。