1.1 オートマトンと状態遷移図

6.5 最終コード生成
(1)コードの形式
①絶対2進コード(AB : absolute binary)
命令後のオペランドが絶対番地指定。
このまま主記憶装置にロードして実行可能。
②相対2進コード(RB : relocatable binary)
命令後のオペランドが先頭からの相対番地指定。
他のプログラムへのデータ参照(外部参照)を含む。
絶対番地への変換はリンケージエディタやローダで行われる。
③アセンブリ語プログラム
命令語が記号(ニーモニック)コードで記述され、
オペランドも記号である。
機械語への変換はアセンブラで行われる。
(2)コード生成における留意点
(a) 命令の選択
(b) 番地指定の選択
(c) レジスタ割り当て
(d) 評価順序
(a) 命令の選択
① 設定した仮想機械命令と対象計算機の命令が対応していることが
理想的だが、適当な命令がなければ何らかの対処が必要である。
②命令の実行時間にも配慮して命令選択する。
【例】値を1だけ加算する命令 INC が備わっているとき
MOV
ADD
MOV
A, R0
#1, R0
R0, A
INC
A
(b)番地指定の選択
①番地指定法の種類や範囲は、個々の計算機でかなり異なる。
②番地指定法が強力であれば、ある程度、命令の機能を補うことが
できる。
【例】
① 配列におけるインデックスレジスタの指定
② ベースレジスタの使用による相対番地の指定
(動的リンクでの絶対番地計算を必要としないオブジェクト)
(c)レジスタ割り当て
① 変数、定数、一時変数、スタックポインタなどをどのようにレジ
スタに振り分けるか。
② レジスタへのアクセス速度はメインメモリへのアクセスに比べ
て拘束である
速度面で非常に重要となる
(d)評価順序
式の評価順序の決定は、実行効率で大きな影響を及ぼす。
(以下、後述)
(3)制御構造のコード生成
バックパッチ(back patching)
If <exp> then
S1ブロック
Else
S2ブロック
End If
前に戻って埋め込む処理
バックパッチ
<exp> のオブジェクト
JNE L1
S1ブロック
JMP L2
L1
S1ブロック
L2
バックパッチの例(スタックマシンにおける例)
if(tokenX[i].type=="Name" && tokenX[i].str=="if"){
if論理式(ref i); numPolish=0; SA0(); numStatementNo++;
int oldP=numStatement; SA設定("StNo",0,numStatementNo.ToString());
現Polishのオブジェクト設定();
IfPush(oldP,numStatement,0);
// 埋め込み位置の保存
SA設定("Number",0,""); SA設定("then",0,"then");
if(i+1 < numTokenX) MessageBox.Show("thenの後に文は書けません");
}
else if(tokenX[i].type=="Name" && tokenX[i].str=="else"){
ifStack[ifStackP].ThenP=numStatement;
// elseがあった場合
SA設定("Number",0,""); SA設定("goto",0,"goto");
numStatementNo++;
AllStatement[IfStack[IfStackP].IfP].str=numStatement.ToString(); //バックパッチ
SA設定("StNo",0,numStatementNo.ToString());
if(i+1 < numTokenX) MessageBox.Show("elseの後に文は書けません");
}
else if(tokenX[i].type=="Name" && tokenX[i].str=="endif"){
numStatementNo++; SA設定("StNo",0,numStatementNo.ToString());
int IP=IfStack[IfStackP].IfP; // Elseがあった場合となかった場合の判別が必要
if(AllStatement[IP].str=="") AllStatement[IP].str=numStatement.ToString();
else AllStatement[IfStack[IfStackP].ThenP].str=numStatement.ToString();
if(i+1 < numTokenX) MessageBox.Show("endifの後に文は書けません");
IfStackP--;
}
(4)呼出しのコード生成
計算機の機種やオペレーティングシステムによって定まっているの
でそれに従う。
【スタックを持たない(Call命令がない)機種の場合の例】
(汎用大型計算機等)
LA
R0,L0001
JMP SUB
L00000 DC
RET001 /* リターンアドレス
DC
ARG001 /* 引数1のアドレス
DC
ARG002 /* 引数2のアドレス
DC
ARG003 /* 引数3のアドレス
DC
ARG004 /* 引数4のアドレス
RET001 *
/* リターンアドレス
スタックを持つ(一般にCALL命令を持つ)機種の場合
呼び出す側
呼び出される側
入口コード
・
・
・
引数評価の
オブジェクト
Call命令
・
・
・
・
・
・
・
・
・
Call
Return
スタック
出口コード
Return命令
呼出しコード
①実引数を引き渡す準備を行う。ただし、値渡しと参照渡し
の区別は呼び出される側で行う。Cの場合は値渡しになっ
ているので、ここで処理してもよい。
②Call命令を発する。Call命令がなければ、復帰する番地を
何らかのレジスタに保存して呼ぶべき関数にジャンプする。
③上記②の方法が機種やOSによっては定まっている場合があ
る。
入口コード
① レジスタ類を保存する。
② 新たなデータフレームを確保する。具体的には実引数と
仮引数の関係付け、ローカル変数領域の確保等である。
③ 動的リンク、ディスプレイ、スタックポインタなどの各
種制御情報を更新する。
出口コード
① 関数の結果を用意する。
② フレームを開放し、各種の制御情報を呼び出し前に戻す。
③ レジスタを元に戻す。
④ Return命令の発行
(5)算術式のコード生成
スタックマシンにおける逆ポーランド記法の
実行エミュレーションとほぼ同じ。
① 変数名はプッシュする(エミュレーションのときと
同じ)。
② 演算子のときにコードを生成する(エミュレーショ
ンのとき「実行」だったことを思い出すこと)。
③ 結果をプッシュするときは、作業用の変数を生成し
てその変数をプッシュする(エミュレーションのとき
「演算結果」をプッシュしたことを思い出すこと)。
演算子のときのコード生成
(作業用変数名は前シートの③と同じもの)
① 単項演算子のとき、変数名をポップ
LOD R0,変数名
<OP> R0
STR R0,作業用変数名
(<OP>は演算子種別で異なる)
② 2項演算子のとき, 2個変数名をポップ。最初にポッ
プしたものを変数名1, 2番目にポップしたものを変数
名2とすると
LOD R0,変数名2
<OP> R0,変数名1
STR R0,作業用変数名
できあがった命令列に対して次の最適化を行う
(覗き穴最適化のひとつ(後述))
① 以下のシーケンスの命令を削除する
STR R0,作業用変数名
LOD R0,作業用変数名
② 上記①の結果、使用されなくなった作業用変数名を
削除する。
(6)論理式のコード生成
基本的には算術式と同じであるが、論理式のある部分式
だけで全体が決まってしまうことがある。これをJump命
令で代替することも多い。
【例】 X > 20 And Y < 30
LOD R0,X
SUB R0,#20
JME L001
LOD R0,Y
SUB R0,#30
JPE L001
[trueのときの処理]
L001 [falseのときの処理]
(7)四つ組からのコード生成
四つ組は機械命令に近い
四つ組中の変数や一時変数を
レジスタおよび主記憶上に
どのように割り付けるか?
コード生成で用いるデータ
①変数の番地記述(レジスタかメモリかの別を含む)
②レジスタに現在保持されている変数または一時変数
レジスタを得る関数getreg
①変数 y がレジスタ上にあり、値がその後にも使われない
場合、そのレジスタを返す。
② 空きレジスタがあるときはそれを返す。
③ あるレジスタの内容を主記憶内の一時変数に退避し、そ
のレジスタを返す。
④上記③の方法には、最も古くアクセスされたレジスタを
返す方法、最も古く使われたレジスタを返す方法の2種類
がある(タスク管理に似ている)
X = Y op Zのときのレジスタの割り当て
① 変数 Y (RY)および変数 Z (RZ)がレジスタにあれば、
<OP> RY,RZ
を生成する。
② 変数 Y (RY)がレジスタ上にあり、変数 Z がレジスタ上
になければ、
<OP> RY,Z
③ 変数 Y がレジスタ上になく、変数 Z (RZ)がレジスタ
上にあれば、前出 getreg でレジスタを得る→RY。
(getregで出力されたメモリへの退避)
<OP> RY,RZ
④ 変数 Y および変数 Z がレジスタ上になければ、前出
getreg でレジスタを得る→RY。
(getregで出力されたメモリへの退避)
<OP> RY,Z
(8)機械依存のコード最適化
① 変数をレジスタに割り付けるほうが効率が良い。
② 2つ以上の基本ブロックでレジスタに割り付けることを
大域的割付(global register allocation)という。
ループの制御変数をレジスタに割り付けることも多い。
③ 機械依存型プログラミング言語の中には、レジスタ変数
を用意して、プログラマがレジスタ割付を行う場合もあ
る。
④ 生成された結果の機械語コードに対する最適化として覗
き穴最適化(peephole optimization)がある。
覗き穴最適化
①無駄なLOAD/STORE命令の組の除去
前出にあったが、機械語によってはMOVE命令の組として
表現されることもある
MOV
MOV
A,R0
R0,A
(LOD
(STR
R0,A)
R0,A)
覗き穴最適化
②到達不能コードの除去
無条件分岐の直後の命令で、
かつ別の場所から分岐されない場所の命令
JMP
MOV
L002
R0,A
(無駄な命令)
覗き穴最適化
③分岐命令の連鎖
無条件分岐の飛び先が、さらに無条件分岐のとき
L001
JMP L001
……
JMP L002
→(JMP
L002に変更する)
なお、この処理を行った結果、L001が到達不能コードとな
ることがある。このときは、L001も前述の②で除去される。