let lexbuf outchan l = Id.counter := 0

コンパイラ演習
第1回
(2011/10/06)
中村 晃一 野瀬 貴史 前田 俊行
秋山 茂樹 池尻 拓朗
鈴木 友博 渡邊 裕貴
潮田 資秀
小酒井 隆広
山下 諒蔵 佐藤 春旗
大山 恵弘 佐藤 秀明
住井 英二郎
プロセッサ・コンパイラ実験
全体の大目標
• 計算機システム
を作る
– 計算機システムが (ハードもソフトも)
“black box” でないことを身をもって学ぶ
プロセッサ・コンパイラ実験
全体の進め方
• 班 (4〜5人程度?) に分かれて進める
– うち1人をコンパイラ係とする
• 具体的には以下を作成する
– 自作CPUの設計・実装
– 自作CPU向けのコンパイラ (クロスコンパイラ)
– etc.
• レイトレーシングプログラムを
正しく動作させることが条件
コンパイラ係の目標
• 自作CPU向けの
コンパイラを実装する
演習の進め方
• 毎回講義を15~45分ほど行う
– 対象言語: OCaml もどき
– 記述言語: OCaml
• あとは各自で演習してもらう
– コンパイラを実装する、課題を解く等
• 講義資料等は以下に置いておきます
– http://www.yl.is.s.u-tokyo.ac.jp
/wiki/lectures/CompilerEnshu2011.ja
お手本とするコンパイラ
• 「美しい日本の ML コンパイラ」 (通称 MinCaml)
• 作者: 住井 英二郎
– 対象言語: OCaml もどき
– 記述言語: OCaml
– 対象CPU: SPARC、PowerPC
– URL:
http://min-caml.sourceforge.net/
講義内容 (変更の可能性あり)
•
•
•
•
•
•
•
•
•
•
•
•
コンパイラの概要 (今回)
K正規化、α変換、最適化
クロージャ変換
仮想マシンコード生成
レジスタ割り当て
アセンブリ生成、末尾呼び出し最適化
??????????????
??????????????
??????????????
??????????????
??????????????
??????????????
基礎編
応用編
課題について
• 二種類の課題がある
– 共通課題
• 全員が解くことを想定している課題
– コンパイラ係向け課題
• コンパイラ係が解くことを想定している課題
コンパイラ係以外の人の
単位取得条件
• 共通課題を 6 つ以上提出すること
かつ
• CPU担当教員の出す条件をクリアすること
コンパイラ係の人の
単位取得条件
• 自作 CPU (またはシミュレータ) 上で
レイトレーシングプログラムが完動すること
– MinCaml を改造する形でもよい
かつ
• 共通課題を 6 つ以上提出すること
かつ
• コンパイラ係向け課題を 1 つ以上提出すること
かつ
• CPU担当教員の出す条件をクリアすること
コンパイラの概要
コンパイラとは
• ある言語のプログラムを
他の言語に翻訳するシステム
– gcc
• C  アセンブリ
– javac
• Java  JVM bytecode
– ocamlc
• OCaml  OCaml VM bytecode
– ocamlopt
• OCaml  アセンブリ
– etc.
コンパイルの例
アセンブリコード
ソースコード
let rec gcd m n =
if m = 0 then n else
if m <= n then
gcd m (n - m)
else
gcd n (m - n)
.section
".text"
gcd.7:
cmp
%i2, 0
bne
be_else.17
nop
mov
%i3, %i2
retl
nop
be_else.17:
cmp
%i2, %i3
bg
ble_else.18
nop
sub
%i3, %i2, %i3
b
gcd.7
nop
ble_else.18:
sub
%i2, %i3, %o5
mov
%i3, %i2
mov
%o5, %i3
b
gcd.7
nop
コンパイラの難しいところ
言語間のギャップが大きい
ソースコード
• 変数
• 関数・クロージャ
• 式・文
アセンブリコード
• レジスタ・メモリ
• ジャンプ・条件分岐
• CPU命令
解決法: ちょっとずつ変換する
概念図
ソースコード
• 変数
• 関数・クロージャ
• 式・文
中間言語2
• 変数
• ジャンプ・条件分岐
• CPU命令
中間言語1
クロージャ
変換
• 変数
• ジャンプ・条件分岐
• 式・文
仮想マシンコード
生成
レジスタ
割当て
アセンブリ
• レジスタ・メモリ
• ジャンプ・条件分岐
• CPU命令
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
字句解析
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
構文解析
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
型検査
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
K 正規化
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
α 変換
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
(Closure.f
最適化
(iter !limit
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
クロージャ
(Closure.f
(iter !limit
変換
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
仮想マシン
(Closure.f
(iter !limit
コード生成
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
レジスタ
(Closure.f
(iter !limit
割当て
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
MinCaml ではこんな感じ
(* main.ml *)
let lexbuf outchan l =
Id.counter := 0;
Typing.extenv := M.empty;
Emit.f outchan
(RegAlloc.f
(Simm13.f
(Virtual.f
アセンブリ
(Closure.f
(iter !limit
生成
(Alpha.f
(KNormal.f
(Typing.f
(Parser.exp Lexer.token l)))))))))
字句解析・構文解析
• 字句解析
– 文字列をトークン (単語)の列に変換する
• 構文解析
– トークン列を抽象構文木に変換する
• MinCaml では ocamllex、 ocamlyacc を用いる
– ML演習第4回参照
MinCaml の型検査
• 基本的には ML の型推論
– ML演習第 6 回参照
• ただし、以下の点に注意
– 推論の実装に reference を用いている
– 未定義の変数があった場合に
「外部変数」とみなす
– 型多相はサポートしていない
K 正規化
• 計算途中の結果にも全て変数をつける
– アセンブリに少し近づける
– 評価順序が少し明確になる
1 + 2 - 3 + 4 - 5
let
let
let
let
d
a
b
c
d
=
=
=
=
1
a
b
c
+
+
–
2
3
4
5
in
in
in
in
α 変換
• 変数の名前をつけかえる
• 全ての変数が互いに異なる名前をもつようにする
– 最適化やその後の処理が楽になる
let x = 1 in
let x = x + 1 in
x
let x0 = 1 in
let x1 = x0 + 1 in
x1
最適化
• MinCaml では
以下の 3 つの処理を繰り返し行っている
– インライン展開
– 定数たたみこみ
– 不要定義削除
• 他にも最適化の手法はたくさんあるので
自分で色々試してみるとよい
クロージャ変換
• ネストした関数定義を平らにする
超大雑把な図式
let f a =
let g b =
a + b in
g 3
let g a b = a + b
let f a = g a 3
実際には MinCaml では
a はクロージャを介して
渡される
仮想マシンコード生成
• よりアセンブリに近い形式に変換する
• 実際のアセンブリとの違い
– レジスタは無限個ある
– if 式や関数呼出しがある
– タプルやクロージャ、配列の操作を
メモリロード・ストア命令の組み合わせに変換する
レジスタ割当て
• 仮想マシンコードのレジスタに
実際のレジスタを割り当てる
– MinCaml ではそれ程複雑なことはしていない
• そこそこの速さ & そこそこの易しさ
– MinCaml で性能を出すためには
ここの改造は必須の予感
アセンブリ生成
• 実際のアセンブリを出力する
– ここでようやく関数呼出しや if 式を
実際の命令に変換している
• 加えて末尾呼出しの最適化をしている
– ループ最適化を頑張りたい場合には
ここではちょっと遅いかも?
課題
共通課題
どれかを解けばよい
• 今回は全3問のうち
全部解いてもよい
– もちろん
共通課題 その1
• MinCaml を改造し Syntax.t や KNormal.t などの
値を出力できるようにせよ
• 出力の形式や方法は問わないが真面目にやれば
デバッグのとき役に立つかも
• 例えば以下のような感じ
入力
(* test/fib.ml *)
let rec fib n =
if n <= 1 then 1 else
fib (n-1) + fib (n-2) in
print_int(fib 30)
出力
…
If
LE
Var(n)
Int(1)
Int(1)
Add
…
共通課題 その2
• MinCaml を改造し
パースエラー時および型エラー時に
どの箇所 (例: 行番号や列番号等)
に問題があったかを表示するようにせよ
共通課題 その3
• MinCaml を改造し
出力されたアセンブリのどの命令が
入力されたソースのどの式に対応するかを
判別できるようにせよ
– 例えば、対応を表すファイルを
コンパイル時に生成するなど
課題の提出先と締切
• 提出先: [email protected]
– 質問等は [email protected] まで
• 締切: 2 週間後 (10/20) の午後 1 時(JST)
– コンパイラ係向け課題の締切は2012年2月28日
• Subject は Report <課題番号> <学籍番号:5桁> とすること
– 例: Report 1 11099
• 間にスペースを 1 つずついれること
• メールの本文にも氏名と学籍番号を明記すること
– 正常に受理されれば
通知メールが届くはずなので確認してください
課題提出についての注意
• プログラムだけでなく
説明・実行例・考察なども書くこと
• 基本的にはメール本文に解答を記述すること
– ただし、多くのソースコードを
送る必要がある場合には
tar などで固めて提出してよい
謝辞
• 本講義では、昨年度までの講義を担当された
以下の方々によって書かれた資料の多くを再
利用させていただいております。
ここに感謝いたします。
住井英二郎先生、大山恵弘先生、
佐藤秀明氏、山下諒蔵氏、佐藤春旗氏、
小酒井隆広氏、潮田資秀氏、
鈴木友博氏、渡邊裕貴氏、
秋山 茂樹氏、池尻 拓朗氏