言語理論とコンパイラ
第十一回:
コード生成
2007 年 6月29日
http://www.sw.it.aoyama.ac.jp/2007/Compiler/lecture11.html
Martin J. Dürst
duerst@it.aoyama.ac.jp, O 棟
529号室
© 2005-7 Martin
J. Dürst 青山学院大学
目次
宿題: 有理数の電卓
提出: 再来週の木曜日 (6月28日) 19時00分、O 棟
529号室の前
簡単な電卓を有理数の電卓に拡張してください。
有理数の表し方として、[分子,分母]
を追加
して下さい。
[]
内には割り算は許されないように文法を設計してください。
rational.lex
と rational.y
を A4
の紙で提出ください。
宿題の正解例
[都合により削除]
先週の復習
- 構文解析のよいエラー処理は難しい
- 意味解析は主に型のチェックなど
- 中間表現として名前表と構文木が大切
コンパイルの主な段階
- 字句解析 (lexical analysis)
- 構文解析 (parsing; syntax analysis)
- 意味解析 (semantic analysis)
- コード生成 (code generation)
- 最適化 (optimization)
コード生成と最適化の関係
- 構文木で最適化、そこからコード生成
- 生成したコードを分析、最適化
- 実際は両方の組み合わせが多い
コード生成の難しさ
コード生成の手法
- 構文木を作らないで文法規則ごとにコード生成 (例:
スタック・マシーン、条件文など)
- 構文木を辿りながらノードごとにコードを書き出す
- 構文木の構造をコード生成用のパターンに比べてコードを書き出す
機械の主な種類
- スタック・マシーン:
演算は全てスタックで行われて、バーチャルマシーンに多い
- RISC:
演算は全てレジスタ内で行われて、純粋のロードとストアしかない
- CISC:
命令の数が多くて複雑 (例: Intel 系)
コードの書き方: アセンブリ言語
(assembly language)
- アセンブリは機械語を若干抽象化し、人間が読めるようにしたもの
- アセンブラ (assembler) によって機械語に変換される
- 機械などによって種類が多いが、書き方はだいたい似ている
- 機械命令一個を一行に書く
- 一行は四つの部分からなる:
- ラベル
- 命令 (演算子など)
- 被演算子 (レジスタ、変数名、定数など)
- コメント (普通は
;
後)
超単純アセンブリ言語
- 命令は次の通り:
LOAD
: メモリ (変数)
からレジスタへデータを移動
STORE
:
レジスタからメモリへデータの移動
ADD
, SUB
, MUL
,
DIV
: レジスタ間の演算
JUMP<
、JUMP<=
など:
二つのレジスタの比較が真であればラベルへジャンプ
- メモリのアドレスは変数名 (小文字) で表す
- レジスタは
R1
, R2
,...
で表し、数の制限はない
- 被演算子の順番は結果が一番最初に
超単純アセンブリ言語の一例
入力のプログラムの一部:
sum += price * 25;
出力:
LOAD R1, price ; R1 (レジスタ1) に price というアドレスからロード
LOAD 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 (a>b)
→ a-b; jumpLE0
- ジャンプの行き先がまだ分からない場合が多い
関数呼び出しのコード生成
- 呼び出し側と関数側に特別なコードが必要
- 機械・OS・言語特有の関数呼び出しスタックの構成を考慮する必要がある
- 関数呼び出しスタックの内容 (関数フレーム):
- 戻り番地 (関数後どこに戻るか)
- 引数、戻り値
- 前の関数フレームのベースポインタ
- 使われるレジスターの値を退避する一時変数
- ローカル変数