言語理論とコンパイラ
第十一回:
コード生成
2009 年 6 月 26 日
http://www.sw.it.aoyama.ac.jp/2008/Compiler/lecture11.html
Martin J. Dürst
duerst@it.aoyama.ac.jp, O 棟 529
号室
© 2005-9 Martin
J. Dürst 青山学院大学
目次
宿題の正解例
calc.y, calc.lex
入力の一例
先週の復習
- 構文解析のよいエラー処理は難しい
- 意味解析は主に型のチェックなど
- 中間表現として名前表と構文木が大切
コンパイラの主な段階
- 字句解析 (lexical analysis)
- 構文解析 (parsing; syntax analysis)
- 意味解析 (semantic analysis)
- コード生成 (code generation)
- 最適化 (optimization)
コード生成と最適化の関係
- 構文木で最適化、そこからコード生成
- 生成したコードを分析、最適化
- 実際は両方の組み合わせが多い
コード生成の難しさ
コード生成の手法
- 構文木を作らないで文法規則ごとにコード生成 (例:
スタック・マシーン、条件文など)
- 構文木を辿りながらノードごとにコードを書き出す
- 構文木の構造をコード生成用のパターンに比べてコードを書き出す
機械の主な種類
- スタック・マシーン:
演算は全てスタックで行われて、バーチャルマシーンに多い
- RISC:
演算は全てレジスタ内で行われて、純粋のロードとストアしかない
- CISC:
命令の数が多くて複雑 (例: Intel 系)
コードの書き方: アセンブリ言語
(assembly language)
- アセンブリは機械語を若干抽象化し、人間が読めるようにしたもの
- アセンブラ (assembler) によって機械語に変換される
- 機械などによって種類が多いが、書き方はだいたい似ている
- 機械命令一個を一行に書く
- 一行は四つの部分からなる:
- ラベル
- 命令 (演算子など)
- 被演算子 (レジスタ、変数名、定数など)
- コメント (普通は
;
後)
命令の種類
命令 |
被演算子 |
説明 (コメント) |
LOAD |
R1, a |
メモリの変数 a の値をレジスタ R1 に代入 |
STORE |
a, R1 |
レジスタ R1 の値をメモリの変数 a に代入 |
CONST |
R1, 5 |
レジスタ R1 に 5 の定数を代入 |
JUMP |
label |
無条件の label へジャンプ |
JUMP< |
R1, label |
レジスタ R1 が 0 より小さい時 label へジャンプ
(同様に JUMP<=, JUMP==, JUMP!=, JUMP>= とJUMP>
がある。) |
ADD |
R1, R2, R3 |
R2 と R3 を足して R1
に代入。同じレジスタを何回使ってもよい。同様に
SUB、MUL、DIV がある。 |
- メモリのアドレスは変数名 (小文字) で表す
- レジスタは R1, R2,... で表し、数は特定の制限がない
- 被演算子の順番は結果が一番最初に
(第一回の授業の宿題や去年の期末試験問題と違う)
超単純アセンブリ言語の一例
入力のプログラムの一部:
sum += price * 25;
出力:
LOAD R1, price ; R1 (レジスタ1) に price というアドレスからロード
CONST R2, 25 ; R2 (レジスタ2) に 25 の定数をロード
MUL R1, R1, R2 ; R1 に R1 と R2 の掛け算の結果を入れる
LOAD R2, sum ; R2 に sum というアドレスからロード
ADD R2, R1, R2 ; R2 に R1 と R2 の合計を入れる
STORE sum, R2 ; sum というアドレスに R2 を入れる
if 文などのコード生成
- 条件を条件付きジャンプ命令に変更
- 条件付き命令は前の演算から残るフラグを使ったり、0
との比較が多い
- 条件が合わない場合にジャンプすることが多い
- ジャンプの行き先がまだ分からない場合が多い
(アセンブリ言語ではラベルが使える)
if
文のコード生成の例
文: if (a>10) { b = 15; }
生成されるコード:
LOAD R1, a
CONST R2, 10
SUB R3, R1, R2 ; R3 = a-10
JUMP<= R3, endif ; jump over if part if a-10<=0
CONST R4, 15
STORE b, R4
endif:
関数呼び出しのコード生成
- 呼び出し側と関数側に特別なコードが必要
- 機械・OS・言語特有の関数呼び出しスタックの構成を考慮する必要がある
- 関数呼び出しスタックの内容 (関数フレーム):
- 戻り番地 (関数後どこに戻るか)
- 引数、戻り値
- 前の関数フレームのベースポインタ
- 使われるレジスターの値を退避する一時変数
- ローカル変数