このインタープリタでつくったtiny Cについて、コンパイラを 作っていくことにする。最終的には、マシンコードを直接出力するコンパイラ を作るが、コード生成の考え方を簡単にするために、初回に紹介したスタック マシンをターゲットにする。スタックマシンではレジスタを扱わなくても良い ため簡単になる。初回では単純な数式のコンパイルを考えたが、言語を実行す るためにはインタプリタでやったように関数呼び出しやローカル変数をどのよ うに作るかを考えなくてはならない。コンパイラのターゲットの仮想マシンの 解説からはじめることにしよう。
ここで考えるスタックマシンの「インタプリタ」のプログラムは、以下のプロ グラムである。tiny Cのターゲットとして考えるマシンの命令は、以下の20個の命令である。
命令コード 説明 POP stackから、1つpopする。 PUSHI n 整数nをpushする。 ADD stackの上2つをpopして足し算し、結果をpushする。 SUB stackの上2つをpopして引き算し、結果をpushする。 MUL stackの上2つをpopして引き算し、結果をpushする。 GT stackの上2つをpopして比較し、>なら1、それ以外は0をpushする。 LT stackの上2つをpopして比較し、<なら1、それ以外は0をpushする。 BEQ0 L stackからpopして、0だったら,ラベルLに分岐する。 LOADA n n番目の引数をpushする。 LOADL n n番目の局所変数をpushする。 STOREA n stackのtopの値をn番目の引数に格納する。 STOREL n stackのtopの値をn番目の局所に格納する。 JUMP L ラベルLにジャンプする。 CALL e 関数エントリeを関数呼び出しをする。 RET stackのtopの値を返り値として、関数呼び出しから帰る。 POPR n n個の値をpopして、関数から帰った値をpushする。 FRAME n n個の局所変数領域を確保する。 PRINTLN s sのformatで、printlnを実行する。 ENTRY e 関数の入口を示す。(擬似命令) LABEL L ラベルLを示す。(擬似命令)
POPや、PUSHI, 演算ADD,SUBなどは、最初の講義で解説した 通り、スタックに値をセットしたり、演算したりする命令である。 コンパイラは、このスタックマシンのコードを使って、式を実行するコード列 を作る。
その手順は、
JUMP命令は、LABEL文で示されたところに制御を移す命令である。このスタッ クマシンは分岐命令は、BEQ0命令しかない。この命令は、スタック上の値を popして、これが0だったら、分岐する命令である。これを組みああわせてIF文 をコンパイルする。コードは次のようになる。
IF文のコンパイルは以下のようになる。
...条件文のコード... BEQ0 L0 /* もし、条件文が実行されて、結果が0だったら,Lに分岐*/ ...thenの部分のコード... JUMP L1 LABEL L0 ... elseの部分のコード... LABEL L1
式だけを評価するならば、これでいいが、関数が使えるようにするためには、 スタックの使い方を工夫しなくてはならない。スタックマシンは以下の3つの レジスタを持つ。
さて、関数定義に対するコードは以下のようになる。
また、関数呼び出しは、
ENTRY foo FRAME ローカル変数の個数 .... 関数本体のコード.... RET
関数のコンパイルは、以下のようになる。
引数1のpush ... 引数2のpush ... .... CALL foo POPR pushした引数の個数
次回、スタックマシンに対するコンパイラ全体について、説明することにする。