言語理論とコンパイラ
第十二回:
コード生成
2010 年 6 月 25 日
http://www.sw.it.aoyama.ac.jp/2010/Compiler/lecture12.html
Martin J. Dürst
© 2005-10 Martin
J. Dürst 青山学院大学
目次
- bison の宿題について
- 先週のまとめ
- コード生成
bison の宿題について
提出物の問題点:
YYSTYPE
の定義:
数種類のデータがあり、日付に複数項目がある
ヒント: 構造体
- グローバル変数・ローカル変数: 一般性がない
ヒント 1: .lex
ファイルで yylval
に代入 (例: yylval.year = ...
)
ヒント 2: .y
ファイルで $$ など使用 (例:
$$.days = fun($1.year,...)
)
- 字句解析と構文解析の分け方:
ヒント: 日付、日数をトークンに
- 文法の書き方:
ヒント: 非終端記号をデータの種類に分ける;
問題文を文法の書き換え規則に変更
(例: 日付と日付の引き算の結果は日数 ⇒ period : date
MINUS date {...} ;
)
- shift/reduce conflict や reduce/reduce conflict
ヒント: 多くてもあせらず、例 (test.in、test.check)
が処理できればよい
- 時間の計算のための関数: date_arithmetic.c
宿題の再提出
提出: 来週の木曜日 (7 月 1日) 19 時 00 分、O 棟 529
号室の前
先週のまとめ
- 構文解析のよいエラー処理は難しい
- 意味解析は主に型のチェックなど
- 中間表現として名前表と構文木が大切
コンパイラの主な段階
- 字句解析 (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・言語特有の関数呼び出しスタックの構成を考慮する必要がある
- 関数呼び出しスタックの内容 (関数フレーム):
- 戻り番地 (関数後どこに戻るか)
- 引数、戻り値
- 前の関数フレームのベースポインタ
- 使われるレジスターの値を退避する一時変数
- ローカル変数