snxc/バックエンド1(snxb1.c)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
[[SN/X付属コンパイラの解読]]
*バックエンド1(snxb1.c) [#be8e6881]
アセンブリの命令を書き出すプログラムです。
----
#contents
**include文とグローバル変数 [#l06b9bd8]
#include <stdio.h>
#include "snxc.h"
#include "y.tab.h"
static int lbl;
static int frametop = 0;
**命令書き出し用関数 [#ffb37223]
printf文を使って、アセンブリを書き出している関数になります。~
命令のタイプ文だけ用意されています。
***rinst関数 [#ec9be891]
void rinst (char *op, int r1, int r2, int r3) {
printf("\t%s\t$%d,\t$%d,\t$%d\n", op, r1, r2, r3);
}
R形式の命令で、レジスタを3つ指定する命令を書き出します。~
[例: add $1, $2, $3]
***rinst2関数 [#o9a7a6c1]
void rinst2 (char *op, int r1, int r2) {
printf("\t%s\t$%d,\t$%d\n", op, r1, r2);
}
R形式の命令で、レジスタを2つ指定する命令を書き出します。~
[例: not $1, $2]
***iinst関数 [#bedee348]
void iinst (char *op, int r1, int i, int r2) {
printf("\t%s\t$%d,\t%d($%d)\n", op, r1, i, r2);
}
I形式の命令を書き出します。~
[例:lda $1, 10($2)]
***sinit関数 [#k22ed145]
void sinit(int val) {
iinst("lda", 3, val, 0);
return;
}
スタックポインタの初期化を行います。~
lda $3, 127($0)という命令を書き出し、LDA命令で$3に127をロードします。
**構文解析メイン関数 [#i25f06ee]
ツリー状になっているPnode構造体のpの構造を解析して、アセンブリの命令を書き出していきます。
int ex(Pnode *p, int reg, int pres) {
汎用レジスタは$1と$2に割り当てられており、regはスタックトップレジスタを指す。~
presはもう一方のレジスタが使用中かどうかを判定する。
int lbl1, lbl2, regx, value, i, j, top;
regx = 3 - reg;
if (!p) return 0;
Pnode構造体のpを最後まで解析したら終了します。
***p->type判定(typeCon) [#jbedaaa5]
switch(p->type) {
case typeCon:
value = p->con.value;
top = 0;
for(i = 2 ; i > 0 ; i--) {
if((p->con.value >> i*7) != 0) {
value = (p->con.value >> i*7) & 127;
if(top == 0 || value != 0) {
iinst("lda", reg, value, top);
}
for(j = 0 ; j < 7 ; j++) {
rinst("add", reg, reg, reg);
}
top = reg;
}
}
value = p->con.value & 127;
if(value || top == 0) {
iinst("lda", reg, p->con.value & 127, top);
}
break;
p->typeがtypeConだったとき、これは数値を示しています。~
値の大きさはp->con.valueによって定義されています。~
lda命令で即値を与えてやることで、数値をレジスタにロードする命令を書き出します。~
~
もしもp->con.valueが8ビットで表現不可能な値のときは、
処理を分割して、その値をレジスタにロードさせるように命令を分割しています。
***p->type判定(typeId) [#d54f43a0]
case typeId:
iinst("ld", reg, p->id.i+1, 0);
break;
p->typeがtypeIdだったとき、これは変数を示しています。~
どの変数を指すかはp->id.i+1によって定義されています。~
~
ここでは変数を使うという構文が来たので、メモリに格納していた変数の値をレジスタにロードする命令を書き出します。
***p->type判定(typeOpr) [#w0a5600f]
case typeOpr:
switch(p->opr.oper) {
p->typeがtypeOprだったとき、これは何らかの命令を示しています。~
何の命令を指すかはp->opr.operによって定義されています。~
~
ここからは、p->opr.operが何の命令かによって書き出すコードを判定していきます。
***p->opr.oper判定(FDEFA) [#o5cdb23c]
case FDEFA:
printf("\tbal\t$0,\tL%03d\n", lbl1 = lbl++);
printf("foo:\n");
iinst("lda", 3, -2, 3);
iinst("st", 2, 0, 3);
iinst("st", 1, 1, 3);
p->opr.operがFDEFAに対応する場合、引数付きの関数を定義しています。おそらくFunction with DEFine Argumentみたいな言葉を示しています。~
関数の名前は必ずfooなので、ラベルにはfooを使用しています。~
~
以上に書かれているコードは関数呼出の際のヘッダになります。~
balで生成したラベル番号にジャンプする命令を書き出します。~
ラベル番号はアセンブリを生成しながら、順番に対応付けていきます。~
~
関数の命令に移ると、まずは、lda $3 -2($3))という命令を書き出し、$3 = $3 - 2という操作をします。~
$3はスタックポインタとなっていて、そのアドレスに書き込むための準備をします。~
次のst $2, 0($3)とst $1, 1($3)でによって$1と$2のレジスタをメモリに退避させます。
ここで-2だけスタックポインタをずらしたのは、2つのレジスタを退避させるためです。
ex(p->opr.op[0], 1, 0);
p->opr.op[0]について再帰的に構文を解析してアセンブリを書き出す処理を行います。~
FDEFAについてはp->opr.op[1]は持っていないので、処理する必要はありません。~
~
p->opr.op[0]の中には、foo関数の処理の構文が格納されています。
printf("fooexit:\n");
iinst("ld", 2, 0, 3);
iinst("lda", 3, 2, 3);
iinst("bal", 0, 0, 2);
printf("L%03d:\n", lbl1);
break;
foo関数の処理の定義が終了したら、returnによって処理が戻ったときにジャンプする先の処理を書き出します。~
returnによってジャンプする先はラベルfooexitとして定義されており、関数の実行ために退避させたレジスタを戻す作業を行います。~
ld $2, 0($3)で、$2に関数を呼び出す前の値を戻したら、lda $3, 2($3)でスタックポインタを2を加算して元に戻します。~
~
最後にbalで$2のアドレスにジャンプします。$1には返り値を保持したままになります。~
~
関数の実行には$1を返り値として、$2を戻るアドレスとして管理しています。~
最初にスタックポインタを2ずらしたのも、$1と$2を関数内で使用するため、元々あった値を一旦メモリに格納しておく処理を行うためです。
***p->opr.oper判定(FDEF) [#b6910b1a]
case FDEF:
printf("\tbal\t$0,\tL%03d\n", lbl1 = lbl++);
printf("foo:\n");
iinst("lda", 3, -1, 3);
iinst("st", 2, 0, 3);
p->opr.operがFDEFに対応する場合、引数なしの関数を定義しています。~
FDEFAと異なる部分は、引数として使う$1をメモリに退避させる必要がないので、~
スタックポインタの操作が-1であることと、$2のみをメモリに退避させているということです。
ex(p->opr.op[0], 1, 0);
p->opr.operについて、再帰的に構文を解析しアセンブリを書き出します。
printf("fooexit:\n");
iinst("ld", 2, 0, 3);
iinst("lda", 3, 1, 3);
iinst("bal", 0, 0, 2);
printf("L%03d:\n", lbl1);
break;
この部分はFDEFAと同じ処理になります。
***p->opr.oper判定(RETURN) [#nba89e48]
case RETURN:
if (p->opr.nops > 0) {
ex(p->opr.op[0], 1, 0);
}
printf("\tbal\t$0,\tfooexit\n");
break;
p->opr.operがRETURNに対応する場合、まずp->oprに繋がる子ノードにtypeOprのノードがあるかどうかを調べます。~
子ノードのオペランドの数はp->opr.nopsに格納されているので、これを参照して0より大きい場合、まず最初にその子ノードの構文を解析して命令を書き出します。~
~
最後に、balでfooexitにジャンプする命令を書き出します。
***p->opr.oper判定(FOR) [#sdadb803]
case FOR:
ex(p->opr.op[0], reg, pres);
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl2 = lbl++);
ex(p->opr.op[3], reg, pres);
ex(p->opr.op[2], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
p->opr.operがFORに対応する場合、forループの処理の命令を書き出していきます。~
このときp->oprは4つの子ノードopを持っていて、for( op[0] ; op[1] ; op[2] ) op[3]に対応する処理のノードを保持しています。~
~
まず、変数の初期化処理などにあたるop[0]について再帰的にアセンブリを書き出します。~
次にループの先頭となるラベルを書き出し、op[1]について書き出し、ループの終了条件判定を行います。~
そして、forループのブロック内の処理op[3]を書き出して、最後にループ毎に行う処理op[2]を書き出し、balでループの先頭にジャンプする命令を書き出します。
***p->opr.oper判定(WHILE) [#r61168e0]
case WHILE:
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[0], reg, pres);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl2 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
p->opr.operがWHILEに対応する場合、whileループの処理の命令を書き出していきます。~
このとき、p->oprは2つの子ノードopを持っていて、while( op[0] ) op[1]に対応する処理のノードを保持しています。~
~
まず、ループの先頭となるラベルを書き出し、op[0]について書き出し、ループの終了条件判定を行います。~
次に、whileループのブロック内の処理op[1]を書き出して、最後にbalでループの先頭にジャンプする命令を書き出します。
***p->opr.oper判定(HALT) [#f8ca3553]
case HALT:
printf("\thlt\n");
break;
p->opr.operがHALTに対応する場合、hlt命令を書き出します。~
hltはCPUを停止させる命令です。大抵、この命令はアセンブリコードの最後に書き出されます。
***p->opr.oper判定(IF) [#i4e21d0b]
case IF:
ex(p->opr.op[0], reg, pres);
p->opr.operがIFに対応するとき、if ( op[0] ) op[1]もしくはif( op[0] ) op[1]; else op[2];のように構文解析されています。~
まず最初にifの条件についての処理を書き出します。
if (p->opr.nops > 2) {
/* if else */
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
ex(p->opr.op[2], reg, pres);
printf("L%03d:\n", lbl2);
p->opr.nops > 2のとき、つまりifがelse構文を含むときは、bzでifの条件が成り立つかどうかを調べ、成り立つときの処理op[1]を書き出し、bal命令でif構文の終わりにジャンプします。~
また、成り立たないときの処理op[2]を書き出します。最初のbz命令でifの条件が成り立たないときは、op[2]の処理の先頭にジャンプします。
} else {
/* if */
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
ex(p->opr.op[1],reg, pres);
printf("L%03d:\n", lbl1);
}
break;
ifがelse構文を含まないときは、bzでifの条件が成り立つかどうかを調べ、成り立つときの処理のop[1]について書き出します。~
bz命令のジャンプ先はif構文が終わる場所になり、ifの条件が成り立たないときにジャンプします。
***p->opr.oper判定(PRINT) [#i7c44706]
case PRINT:
ex(p->opr.op[0], reg, pres);
iinst("st", reg, 0, 0);
break;
p->opr.operがPRINTに対応する場合、子ノードの処理の書き出しを行い、メモリの0番地にその値をストアする命令を書き出します。~
コンパイラがprintという構文をサポートしているだけで、実際にprint文は課題では使われていません。
***p->opr.oper判定(OUT) [#w06c68f4]
case OUT:
ex(p->opr.op[0], reg, pres);
ex(p->opr.op[1], regx, 1);
iinst("out", regx, 0, reg);
break;
p->opr.operがOUTに対応する場合の処理ですが、SN/Xでサポートしていないので説明を省略します。
***p->opr.oper判定(MWT) [#d2f2a546]
case MWT:
ex(p->opr.op[0], reg, pres);
ex(p->opr.op[1], regx, 1);
iinst("st", regx, (p->opr.nops > 2) ? p->opr.op[2]->id.i+1 : 0, reg);
break;
p->opr.operがMWTに対応する場合、配列への書き込みの処理を行います。~
まずop[0]に変数のインデックスについての処理が格納されているので、書き出します。~
次に、配列へ書き込む値の処理であるop[1]を書き出し、op[2]->id.i+1を参照して、どの変数かを判断し、st命令を生成します。
***p->opr.oper判定('=') [#f71e8b90]
case '=':
ex(p->opr.op[1], reg, pres);
iinst("st", reg, p->opr.op[0]->id.i+1, 0);
break;
p->opr.operが=に対応する場合、右辺を左辺に代入するような処理を行います。~
このとき、構造は子ノードを2つ持ち、 op[0] = op[1] のようになっています。~
まず、op[1]について子ノードの処理を行い命令を書き出します。そして、op[0]->id.i+1で変数の情報を取得し、この番地に値をストアさせてやります。
***p->opr.oper判定(UMINUS) [#k122a26c]
case UMINUS:
ex(p->opr.op[0], reg, pres);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
break;
p->opr.operがUMINUSに対応する場合、子ノードop[0]を負の値にするような処理を行います。~
負の値は2の補数表現で表しているので、notでビット反転をしてから、ldaで1を加算することで負の値を作っています。
***p->opr.oper判定(IN) [#qdd98038]
case IN:
ex(p->opr.op[0], reg, pres);
iinst("in", reg, 0, reg);
break;
p->opr.operがINに対応する場合の処理ですが、SN/Xでサポートしていないので説明を省略します。
***p->opr.oper判定(MRD) [#v268b490]
case MRD:
ex(p->opr.op[0], reg, pres);
iinst("ld", reg, (p->opr.nops > 1) ? p->opr.op[1]->id.i+1 : 0, reg);
break;
p->opr.operがMRDに対応する場合、配列変数からの読み出しを行います。~
まずop[0]に変数のインデックスについての処理が格納されているので、書き出します。~
次に、op[1]->id.i+1を参照して、どの変数かを判断し、ld命令を生成します。~
~
p->opr.nops > 1の判定はmemを扱うときなので、ここでは気にしなくても良い。(memを使う課題がないので)
***p->opr.oper判定(ARGV) [#x625638c]
case ARGV:
iinst("ld", reg, frametop+1, 3);
break;
p->opr.operがARGVに対応する場合、関数呼出の際、メモリに格納した引数をレジスタにロードします。~
frametopは引数をストアしたアドレスをスタックポインタからどれくらいかを示しています。
***p->opr.oper判定(FUNC) [#t3ebb74d]
case FUNC:
if(pres) {
iinst("lda", 3, -1, 3);
iinst("st", regx, 0, 3);
frametop += 1;
}
p->opr.operがFUNCに対応する場合、関数呼び出しの処理を行います。~
まずpresが1のときレジスタが使用中なので、レジスタの内容をスタックに退避させます。
ex(p->opr.op[0], 1, 0);
printf("\tbal\t$2,\tfoo\n");
まず、foo(op[0])のop[0]にあたる処理を展開して書き出し、引数を$1レジスタに格納しておきます。~
次に、戻りアドレスを$2レジスタに格納して、関数の処理が記述してあるfooにジャンプします。
if(reg!=1) {
iinst("lda", reg, 0, 1);
}
スタックトップレジスタが$1でないとき、スタックトップレジスタ$1の値を$2にコピーする。~
これは関数の返り値を受け渡す処理になる。
if(pres) {
iinst("ld", regx, 0, 3);
iinst("lda", 3, 1, 3);
frametop -= 1;
}
break;
最後に、スタックに退避させたデータがあるときは、レジスタにロードします。
***p->opr.oper判定(その他の前処理) [#b1902e0a]
default:
ex(p->opr.op[0], reg, pres);
p->opr.operが上記の条件にマッチしなかった場合、まず前処理を行います。~
まず、op[0]について処理を展開します。
if(pres) {
iinst("lda", 3, -1, 3);
iinst("st", regx, 0, 3);
frametop += 1;
}
さらにpresが1のときは、レジスタが使用中なので、スタックにレジスタの中身を退避させます。
ex(p->opr.op[1], regx, 1);
最後にop[1]について処理を展開します。~
これによってop[0] [p->opr.oper] op[1]という処理がreg [p->opr.oper] regxのように置き換えられています。
***p->opr.oper判定(その他 -> '+') [#r6fce18e]
switch(p->opr.oper) {
case '+':
rinst("add", reg, reg, regx);
break;
p->opr.operが'+'に対応するとき、add命令を書き出します。
***p->opr.oper判定(その他 -> '-') [#d2434fb4]
case '-':
rinst2("not", regx, regx);
iinst("lda", regx, 1, regx);
rinst("add", reg, reg, regx);
break;
p->opr.operが'-'に対応するとき、not, lda, add命令を使用して減算命令を実現させています。~
regxを反転し、1を加算することでregxの負の値を2の補数表現で作り、regに加算しています。
***p->opr.oper判定(その他 -> '<') [#d682602a]
case '<':
rinst("slt", reg, reg, regx);
break;
p->opr.operが'<'に対応するとき、slt命令を書き出します。
***p->opr.oper判定(その他 -> '>') [#m3d4c1de]
case '>':
rinst("slt", reg, regx, reg);
break;
p->opr.operが'>'に対応するとき、判定するreg, regxを逆にしてslt命令を書き出します。
***p->opr.oper判定(その他 -> GE[>=]) [#y8718484]
case GE:
rinst("slt", reg, regx, reg);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
iinst("lda", regx, 1, 0);
rinst("add", reg, reg, regx);
break;
p->opr.operがGE(>=)に対応するとき、slt, not, lda, add命令の組合せで判定を実現しています。
ただし、このままではバグが埋め込まれている(コンテストの未公開プログラムへのちょっとした布石)ので正しい判定が出来ていません。~
正しい判定を行うためには
rinst("slt", reg, regx, reg);
の部分を
rinst("slt", reg, reg, regx);
と修正しなければいけません。
***p->opr.oper判定(その他 -> LE[<=]) [#mc92c7e9]
case LE:
rinst("slt", reg, reg, regx);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
iinst("lda", regx, 1, 0);
rinst("add", reg, reg, regx);
break;
p->opr.operがLE(<=)に対応するとき、slt, not, lda, add命令の組合せで判定を実現しています。
ただし、このままではバグが埋め込まれている(コンテストの未公開プログラムへのちょっとした布石)ので正しい判定が出来ていません。~
正しい判定を行うためには
rinst("slt", reg, reg, regx);
の部分を
rinst("slt", reg, regx, reg);
と修正しなければいけません。
***p->opr.oper判定(その他 -> NE[!=]) [#k6c83f6d]
case NE:
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
rinst("add", reg, reg, regx);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
iinst("lda", reg, 1, 0);
printf("L%03d:\n", lbl1);
break;
p->opr.operがNE(!=)に対応するとき、not, lda, add, bz命令の組合せで判定を実現しています。~
regx-regを作って、その結果を判定して0でないときは1をロードするようにしています。
***p->opr.oper判定(その他 -> EQ[==]) [#s140e02d]
case EQ:
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
rinst("add", reg, reg, regx);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
iinst("lda", reg, 0, 0);
printf("\tbal\t$0,\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
iinst("lda",reg,1,0);
printf("L%03d:\n", lbl2);
break;
p->opr.operがEQ(==)に対応するとき、not, lda, add, bz命令の組合せで判定を実現しています。~
regx-regを作って、その結果を判定して0か1をロードするようにしています。
}
if(pres) {
iinst("ld", regx, 0, 3);
iinst("lda", 3, 1, 3);
frametop -= 1;
}
}
}
return 0;
}
最後に、スタックに退避させた値があれば、レジスタに戻します。
終了行:
[[SN/X付属コンパイラの解読]]
*バックエンド1(snxb1.c) [#be8e6881]
アセンブリの命令を書き出すプログラムです。
----
#contents
**include文とグローバル変数 [#l06b9bd8]
#include <stdio.h>
#include "snxc.h"
#include "y.tab.h"
static int lbl;
static int frametop = 0;
**命令書き出し用関数 [#ffb37223]
printf文を使って、アセンブリを書き出している関数になります。~
命令のタイプ文だけ用意されています。
***rinst関数 [#ec9be891]
void rinst (char *op, int r1, int r2, int r3) {
printf("\t%s\t$%d,\t$%d,\t$%d\n", op, r1, r2, r3);
}
R形式の命令で、レジスタを3つ指定する命令を書き出します。~
[例: add $1, $2, $3]
***rinst2関数 [#o9a7a6c1]
void rinst2 (char *op, int r1, int r2) {
printf("\t%s\t$%d,\t$%d\n", op, r1, r2);
}
R形式の命令で、レジスタを2つ指定する命令を書き出します。~
[例: not $1, $2]
***iinst関数 [#bedee348]
void iinst (char *op, int r1, int i, int r2) {
printf("\t%s\t$%d,\t%d($%d)\n", op, r1, i, r2);
}
I形式の命令を書き出します。~
[例:lda $1, 10($2)]
***sinit関数 [#k22ed145]
void sinit(int val) {
iinst("lda", 3, val, 0);
return;
}
スタックポインタの初期化を行います。~
lda $3, 127($0)という命令を書き出し、LDA命令で$3に127をロードします。
**構文解析メイン関数 [#i25f06ee]
ツリー状になっているPnode構造体のpの構造を解析して、アセンブリの命令を書き出していきます。
int ex(Pnode *p, int reg, int pres) {
汎用レジスタは$1と$2に割り当てられており、regはスタックトップレジスタを指す。~
presはもう一方のレジスタが使用中かどうかを判定する。
int lbl1, lbl2, regx, value, i, j, top;
regx = 3 - reg;
if (!p) return 0;
Pnode構造体のpを最後まで解析したら終了します。
***p->type判定(typeCon) [#jbedaaa5]
switch(p->type) {
case typeCon:
value = p->con.value;
top = 0;
for(i = 2 ; i > 0 ; i--) {
if((p->con.value >> i*7) != 0) {
value = (p->con.value >> i*7) & 127;
if(top == 0 || value != 0) {
iinst("lda", reg, value, top);
}
for(j = 0 ; j < 7 ; j++) {
rinst("add", reg, reg, reg);
}
top = reg;
}
}
value = p->con.value & 127;
if(value || top == 0) {
iinst("lda", reg, p->con.value & 127, top);
}
break;
p->typeがtypeConだったとき、これは数値を示しています。~
値の大きさはp->con.valueによって定義されています。~
lda命令で即値を与えてやることで、数値をレジスタにロードする命令を書き出します。~
~
もしもp->con.valueが8ビットで表現不可能な値のときは、
処理を分割して、その値をレジスタにロードさせるように命令を分割しています。
***p->type判定(typeId) [#d54f43a0]
case typeId:
iinst("ld", reg, p->id.i+1, 0);
break;
p->typeがtypeIdだったとき、これは変数を示しています。~
どの変数を指すかはp->id.i+1によって定義されています。~
~
ここでは変数を使うという構文が来たので、メモリに格納していた変数の値をレジスタにロードする命令を書き出します。
***p->type判定(typeOpr) [#w0a5600f]
case typeOpr:
switch(p->opr.oper) {
p->typeがtypeOprだったとき、これは何らかの命令を示しています。~
何の命令を指すかはp->opr.operによって定義されています。~
~
ここからは、p->opr.operが何の命令かによって書き出すコードを判定していきます。
***p->opr.oper判定(FDEFA) [#o5cdb23c]
case FDEFA:
printf("\tbal\t$0,\tL%03d\n", lbl1 = lbl++);
printf("foo:\n");
iinst("lda", 3, -2, 3);
iinst("st", 2, 0, 3);
iinst("st", 1, 1, 3);
p->opr.operがFDEFAに対応する場合、引数付きの関数を定義しています。おそらくFunction with DEFine Argumentみたいな言葉を示しています。~
関数の名前は必ずfooなので、ラベルにはfooを使用しています。~
~
以上に書かれているコードは関数呼出の際のヘッダになります。~
balで生成したラベル番号にジャンプする命令を書き出します。~
ラベル番号はアセンブリを生成しながら、順番に対応付けていきます。~
~
関数の命令に移ると、まずは、lda $3 -2($3))という命令を書き出し、$3 = $3 - 2という操作をします。~
$3はスタックポインタとなっていて、そのアドレスに書き込むための準備をします。~
次のst $2, 0($3)とst $1, 1($3)でによって$1と$2のレジスタをメモリに退避させます。
ここで-2だけスタックポインタをずらしたのは、2つのレジスタを退避させるためです。
ex(p->opr.op[0], 1, 0);
p->opr.op[0]について再帰的に構文を解析してアセンブリを書き出す処理を行います。~
FDEFAについてはp->opr.op[1]は持っていないので、処理する必要はありません。~
~
p->opr.op[0]の中には、foo関数の処理の構文が格納されています。
printf("fooexit:\n");
iinst("ld", 2, 0, 3);
iinst("lda", 3, 2, 3);
iinst("bal", 0, 0, 2);
printf("L%03d:\n", lbl1);
break;
foo関数の処理の定義が終了したら、returnによって処理が戻ったときにジャンプする先の処理を書き出します。~
returnによってジャンプする先はラベルfooexitとして定義されており、関数の実行ために退避させたレジスタを戻す作業を行います。~
ld $2, 0($3)で、$2に関数を呼び出す前の値を戻したら、lda $3, 2($3)でスタックポインタを2を加算して元に戻します。~
~
最後にbalで$2のアドレスにジャンプします。$1には返り値を保持したままになります。~
~
関数の実行には$1を返り値として、$2を戻るアドレスとして管理しています。~
最初にスタックポインタを2ずらしたのも、$1と$2を関数内で使用するため、元々あった値を一旦メモリに格納しておく処理を行うためです。
***p->opr.oper判定(FDEF) [#b6910b1a]
case FDEF:
printf("\tbal\t$0,\tL%03d\n", lbl1 = lbl++);
printf("foo:\n");
iinst("lda", 3, -1, 3);
iinst("st", 2, 0, 3);
p->opr.operがFDEFに対応する場合、引数なしの関数を定義しています。~
FDEFAと異なる部分は、引数として使う$1をメモリに退避させる必要がないので、~
スタックポインタの操作が-1であることと、$2のみをメモリに退避させているということです。
ex(p->opr.op[0], 1, 0);
p->opr.operについて、再帰的に構文を解析しアセンブリを書き出します。
printf("fooexit:\n");
iinst("ld", 2, 0, 3);
iinst("lda", 3, 1, 3);
iinst("bal", 0, 0, 2);
printf("L%03d:\n", lbl1);
break;
この部分はFDEFAと同じ処理になります。
***p->opr.oper判定(RETURN) [#nba89e48]
case RETURN:
if (p->opr.nops > 0) {
ex(p->opr.op[0], 1, 0);
}
printf("\tbal\t$0,\tfooexit\n");
break;
p->opr.operがRETURNに対応する場合、まずp->oprに繋がる子ノードにtypeOprのノードがあるかどうかを調べます。~
子ノードのオペランドの数はp->opr.nopsに格納されているので、これを参照して0より大きい場合、まず最初にその子ノードの構文を解析して命令を書き出します。~
~
最後に、balでfooexitにジャンプする命令を書き出します。
***p->opr.oper判定(FOR) [#sdadb803]
case FOR:
ex(p->opr.op[0], reg, pres);
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl2 = lbl++);
ex(p->opr.op[3], reg, pres);
ex(p->opr.op[2], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
p->opr.operがFORに対応する場合、forループの処理の命令を書き出していきます。~
このときp->oprは4つの子ノードopを持っていて、for( op[0] ; op[1] ; op[2] ) op[3]に対応する処理のノードを保持しています。~
~
まず、変数の初期化処理などにあたるop[0]について再帰的にアセンブリを書き出します。~
次にループの先頭となるラベルを書き出し、op[1]について書き出し、ループの終了条件判定を行います。~
そして、forループのブロック内の処理op[3]を書き出して、最後にループ毎に行う処理op[2]を書き出し、balでループの先頭にジャンプする命令を書き出します。
***p->opr.oper判定(WHILE) [#r61168e0]
case WHILE:
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[0], reg, pres);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl2 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
p->opr.operがWHILEに対応する場合、whileループの処理の命令を書き出していきます。~
このとき、p->oprは2つの子ノードopを持っていて、while( op[0] ) op[1]に対応する処理のノードを保持しています。~
~
まず、ループの先頭となるラベルを書き出し、op[0]について書き出し、ループの終了条件判定を行います。~
次に、whileループのブロック内の処理op[1]を書き出して、最後にbalでループの先頭にジャンプする命令を書き出します。
***p->opr.oper判定(HALT) [#f8ca3553]
case HALT:
printf("\thlt\n");
break;
p->opr.operがHALTに対応する場合、hlt命令を書き出します。~
hltはCPUを停止させる命令です。大抵、この命令はアセンブリコードの最後に書き出されます。
***p->opr.oper判定(IF) [#i4e21d0b]
case IF:
ex(p->opr.op[0], reg, pres);
p->opr.operがIFに対応するとき、if ( op[0] ) op[1]もしくはif( op[0] ) op[1]; else op[2];のように構文解析されています。~
まず最初にifの条件についての処理を書き出します。
if (p->opr.nops > 2) {
/* if else */
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
ex(p->opr.op[1], reg, pres);
printf("\tbal\t$0,\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
ex(p->opr.op[2], reg, pres);
printf("L%03d:\n", lbl2);
p->opr.nops > 2のとき、つまりifがelse構文を含むときは、bzでifの条件が成り立つかどうかを調べ、成り立つときの処理op[1]を書き出し、bal命令でif構文の終わりにジャンプします。~
また、成り立たないときの処理op[2]を書き出します。最初のbz命令でifの条件が成り立たないときは、op[2]の処理の先頭にジャンプします。
} else {
/* if */
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
ex(p->opr.op[1],reg, pres);
printf("L%03d:\n", lbl1);
}
break;
ifがelse構文を含まないときは、bzでifの条件が成り立つかどうかを調べ、成り立つときの処理のop[1]について書き出します。~
bz命令のジャンプ先はif構文が終わる場所になり、ifの条件が成り立たないときにジャンプします。
***p->opr.oper判定(PRINT) [#i7c44706]
case PRINT:
ex(p->opr.op[0], reg, pres);
iinst("st", reg, 0, 0);
break;
p->opr.operがPRINTに対応する場合、子ノードの処理の書き出しを行い、メモリの0番地にその値をストアする命令を書き出します。~
コンパイラがprintという構文をサポートしているだけで、実際にprint文は課題では使われていません。
***p->opr.oper判定(OUT) [#w06c68f4]
case OUT:
ex(p->opr.op[0], reg, pres);
ex(p->opr.op[1], regx, 1);
iinst("out", regx, 0, reg);
break;
p->opr.operがOUTに対応する場合の処理ですが、SN/Xでサポートしていないので説明を省略します。
***p->opr.oper判定(MWT) [#d2f2a546]
case MWT:
ex(p->opr.op[0], reg, pres);
ex(p->opr.op[1], regx, 1);
iinst("st", regx, (p->opr.nops > 2) ? p->opr.op[2]->id.i+1 : 0, reg);
break;
p->opr.operがMWTに対応する場合、配列への書き込みの処理を行います。~
まずop[0]に変数のインデックスについての処理が格納されているので、書き出します。~
次に、配列へ書き込む値の処理であるop[1]を書き出し、op[2]->id.i+1を参照して、どの変数かを判断し、st命令を生成します。
***p->opr.oper判定('=') [#f71e8b90]
case '=':
ex(p->opr.op[1], reg, pres);
iinst("st", reg, p->opr.op[0]->id.i+1, 0);
break;
p->opr.operが=に対応する場合、右辺を左辺に代入するような処理を行います。~
このとき、構造は子ノードを2つ持ち、 op[0] = op[1] のようになっています。~
まず、op[1]について子ノードの処理を行い命令を書き出します。そして、op[0]->id.i+1で変数の情報を取得し、この番地に値をストアさせてやります。
***p->opr.oper判定(UMINUS) [#k122a26c]
case UMINUS:
ex(p->opr.op[0], reg, pres);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
break;
p->opr.operがUMINUSに対応する場合、子ノードop[0]を負の値にするような処理を行います。~
負の値は2の補数表現で表しているので、notでビット反転をしてから、ldaで1を加算することで負の値を作っています。
***p->opr.oper判定(IN) [#qdd98038]
case IN:
ex(p->opr.op[0], reg, pres);
iinst("in", reg, 0, reg);
break;
p->opr.operがINに対応する場合の処理ですが、SN/Xでサポートしていないので説明を省略します。
***p->opr.oper判定(MRD) [#v268b490]
case MRD:
ex(p->opr.op[0], reg, pres);
iinst("ld", reg, (p->opr.nops > 1) ? p->opr.op[1]->id.i+1 : 0, reg);
break;
p->opr.operがMRDに対応する場合、配列変数からの読み出しを行います。~
まずop[0]に変数のインデックスについての処理が格納されているので、書き出します。~
次に、op[1]->id.i+1を参照して、どの変数かを判断し、ld命令を生成します。~
~
p->opr.nops > 1の判定はmemを扱うときなので、ここでは気にしなくても良い。(memを使う課題がないので)
***p->opr.oper判定(ARGV) [#x625638c]
case ARGV:
iinst("ld", reg, frametop+1, 3);
break;
p->opr.operがARGVに対応する場合、関数呼出の際、メモリに格納した引数をレジスタにロードします。~
frametopは引数をストアしたアドレスをスタックポインタからどれくらいかを示しています。
***p->opr.oper判定(FUNC) [#t3ebb74d]
case FUNC:
if(pres) {
iinst("lda", 3, -1, 3);
iinst("st", regx, 0, 3);
frametop += 1;
}
p->opr.operがFUNCに対応する場合、関数呼び出しの処理を行います。~
まずpresが1のときレジスタが使用中なので、レジスタの内容をスタックに退避させます。
ex(p->opr.op[0], 1, 0);
printf("\tbal\t$2,\tfoo\n");
まず、foo(op[0])のop[0]にあたる処理を展開して書き出し、引数を$1レジスタに格納しておきます。~
次に、戻りアドレスを$2レジスタに格納して、関数の処理が記述してあるfooにジャンプします。
if(reg!=1) {
iinst("lda", reg, 0, 1);
}
スタックトップレジスタが$1でないとき、スタックトップレジスタ$1の値を$2にコピーする。~
これは関数の返り値を受け渡す処理になる。
if(pres) {
iinst("ld", regx, 0, 3);
iinst("lda", 3, 1, 3);
frametop -= 1;
}
break;
最後に、スタックに退避させたデータがあるときは、レジスタにロードします。
***p->opr.oper判定(その他の前処理) [#b1902e0a]
default:
ex(p->opr.op[0], reg, pres);
p->opr.operが上記の条件にマッチしなかった場合、まず前処理を行います。~
まず、op[0]について処理を展開します。
if(pres) {
iinst("lda", 3, -1, 3);
iinst("st", regx, 0, 3);
frametop += 1;
}
さらにpresが1のときは、レジスタが使用中なので、スタックにレジスタの中身を退避させます。
ex(p->opr.op[1], regx, 1);
最後にop[1]について処理を展開します。~
これによってop[0] [p->opr.oper] op[1]という処理がreg [p->opr.oper] regxのように置き換えられています。
***p->opr.oper判定(その他 -> '+') [#r6fce18e]
switch(p->opr.oper) {
case '+':
rinst("add", reg, reg, regx);
break;
p->opr.operが'+'に対応するとき、add命令を書き出します。
***p->opr.oper判定(その他 -> '-') [#d2434fb4]
case '-':
rinst2("not", regx, regx);
iinst("lda", regx, 1, regx);
rinst("add", reg, reg, regx);
break;
p->opr.operが'-'に対応するとき、not, lda, add命令を使用して減算命令を実現させています。~
regxを反転し、1を加算することでregxの負の値を2の補数表現で作り、regに加算しています。
***p->opr.oper判定(その他 -> '<') [#d682602a]
case '<':
rinst("slt", reg, reg, regx);
break;
p->opr.operが'<'に対応するとき、slt命令を書き出します。
***p->opr.oper判定(その他 -> '>') [#m3d4c1de]
case '>':
rinst("slt", reg, regx, reg);
break;
p->opr.operが'>'に対応するとき、判定するreg, regxを逆にしてslt命令を書き出します。
***p->opr.oper判定(その他 -> GE[>=]) [#y8718484]
case GE:
rinst("slt", reg, regx, reg);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
iinst("lda", regx, 1, 0);
rinst("add", reg, reg, regx);
break;
p->opr.operがGE(>=)に対応するとき、slt, not, lda, add命令の組合せで判定を実現しています。
ただし、このままではバグが埋め込まれている(コンテストの未公開プログラムへのちょっとした布石)ので正しい判定が出来ていません。~
正しい判定を行うためには
rinst("slt", reg, regx, reg);
の部分を
rinst("slt", reg, reg, regx);
と修正しなければいけません。
***p->opr.oper判定(その他 -> LE[<=]) [#mc92c7e9]
case LE:
rinst("slt", reg, reg, regx);
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
iinst("lda", regx, 1, 0);
rinst("add", reg, reg, regx);
break;
p->opr.operがLE(<=)に対応するとき、slt, not, lda, add命令の組合せで判定を実現しています。
ただし、このままではバグが埋め込まれている(コンテストの未公開プログラムへのちょっとした布石)ので正しい判定が出来ていません。~
正しい判定を行うためには
rinst("slt", reg, reg, regx);
の部分を
rinst("slt", reg, regx, reg);
と修正しなければいけません。
***p->opr.oper判定(その他 -> NE[!=]) [#k6c83f6d]
case NE:
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
rinst("add", reg, reg, regx);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
iinst("lda", reg, 1, 0);
printf("L%03d:\n", lbl1);
break;
p->opr.operがNE(!=)に対応するとき、not, lda, add, bz命令の組合せで判定を実現しています。~
regx-regを作って、その結果を判定して0でないときは1をロードするようにしています。
***p->opr.oper判定(その他 -> EQ[==]) [#s140e02d]
case EQ:
rinst2("not", reg, reg);
iinst("lda", reg, 1, reg);
rinst("add", reg, reg, regx);
printf("\tbz\t$%d,\tL%03d\n", reg, lbl1 = lbl++);
iinst("lda", reg, 0, 0);
printf("\tbal\t$0,\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
iinst("lda",reg,1,0);
printf("L%03d:\n", lbl2);
break;
p->opr.operがEQ(==)に対応するとき、not, lda, add, bz命令の組合せで判定を実現しています。~
regx-regを作って、その結果を判定して0か1をロードするようにしています。
}
if(pres) {
iinst("ld", regx, 0, 3);
iinst("lda", 3, 1, 3);
frametop -= 1;
}
}
}
return 0;
}
最後に、スタックに退避させた値があれば、レジスタに戻します。
ページ名: