300 likes | 540 Vues
「美しい日本の ML コンパイラ」 を読む~ MinCaml 解説. 福盛秀雄 http://fukumori.org. Ver.2005.08.11. MinCaml とは. OCaml のサブセット. あるもの: 型推論 基本型 : int 、 float 派生型 : tuple, array 高階関数. ないもの: パターンマッチング 多相性(ポリモルフィズム) ガーベッジコレクション レコード型. コンパイラを構成する ファイルとモジュール. <ファイル名> .ml で暗黙のモジュールが構成される.
E N D
「美しい日本のMLコンパイラ」を読む~MinCaml解説「美しい日本のMLコンパイラ」を読む~MinCaml解説 福盛秀雄 http://fukumori.org Ver.2005.08.11
MinCamlとは OCamlのサブセット • あるもの: • 型推論 • 基本型: int、float • 派生型: tuple, array • 高階関数 • ないもの: • パターンマッチング • 多相性(ポリモルフィズム) • ガーベッジコレクション • レコード型
コンパイラを構成するファイルとモジュール <ファイル名>.mlで暗黙のモジュールが構成される syntax.ml → Syntaxモジュール typing.ml → Typingモジュール kNormal.ml → KNormalモジュール などなど <ファイル名>.mliはインタフェース宣言 同名のモジュールの外部仕様を定義する typing.mli → Typingモジュールの外部インタフェース
.mli(インタフェース)を押さえる <モジュール名>.fが 各モジュールの主力関数 typing.mli 3: val f : Syntax.t -> Syntax.t kNormal.mli 28: val f : Syntax.t -> t alpha.mli 1: val f : KNormal.t -> KNormal.t などなど
モジュール間のデータの流れ .mliファイルに記述されている内容と Mainモジュールの処理をつき合わせてみると… main.ml 13: Emit.f outchan 14: (RegAlloc.f 15: (Simm13.f 16: (Virtual.f 17: (Closure.f 18: (iter !limit 19: (Alpha.f 20: (KNormal.f 21: (Typing.f 22: (Parser.exp Lexer.token l))))))))) モジュール間で データを変形させながら 流していく姿が見える SparcAsm.prog SparcAsm.prog Closure.prog KNormal.t KNormal.t KNormal.t Syntax.t iterの内容は 次のページで Syntax.t
モジュール間のデータの流れ(2) 関数“iter”の内容はこんなカンジ: main.ml (Closure.fへ) モジュール間で データを変形させながら 流していく姿が見える KNormal.t 6: ... Elim.f (ConstFold.f (Inline.f (Assoc.f (Beta.f e)))) KNormal.t KNormal.t KNormal.t KNormal.t KNormal.t (Alpha.fから) 流れているデータの内容はどうなっているのだろう?
データ型定義を押さえる <モジュール名>.tがデータ型 序盤~中盤の処理で重要なのは以下の3つ: Syntax.t 構文木を表現するデータ型 KNormal.t K正規系(中間表現)を表すデータ型 Type.t 型表現を表すデータ型
構文木(Syntax.t) syntax.ml 1: type t = (* MinCamlの構文を表現するデータ型 *) 2: |Unit 3: |Boolof bool 4: |Intof int 8: |Addof t * t 19: |Ifof t * t * t 20: |Letof (Id.t * Type.t) * t * t 21: |VarofId.t 22: |LetRecof fundef list * t (* mutual recursion *) 29: and fundef = { name : Id.t * Type.t; args : (Id.t * Type.t) list; body : t } 再帰データ型でツリーを構成
構文木の例 letrec sum x = if x <= 0 then 0 else sum (x - 1) + x in print_int (sum 10000)
K正規形(KNormal.t) Syntax.tと見た目はほとんど同じだが… kNormal.ml(i) Boolは消滅―Intに変換されている 1: type t = 2: |Unit 3: |Intof int 6: |AddofId.t * Id.t 13: |IfEqofId.t * Id.t * t * t 14: |IfLEofId.t * Id.t * t * t 15: |Letof (Id.t * Type.t) * t * t 16: |VarofId.t 17: |LetRec of fundef list * t 25: and fundef = { name : Id.t * Type.t; args : (Id.t * Type.t) list; body : t } tからId.tへ―非再帰型データ化 Ifは2種類の表現を使用 条件判断部を非再帰化 他は(おおむね)変更なし
K正規形の例 letrec sum x = if x <= 0 then 0 else sum (x - 1) + x in print_int (sum 10000)
型表現(Type.t) Syntax.t, KNormal.tの中で使われる type.ml 1: type t = (* MinCamlの型を表現するデータ型 *) 2: |Unit 3: |Bool 4: |Int 5: |Float 6: |Funof t list * t (* arguments are uncurried *) 7: |Tupleof t list 8: |Arrayof t 9: |Varof t option ref 10: 11: let gentyp () = Var(ref None) (* 新しい型変数を作る *) 「引数のリスト」と 「返り値」の組 ある変数の「型」 型の確定前は“ref None” 確定後は“ref Some t”となる
重要な変数とイディオム environmentの略 “env” Id.t(変数名/関数名)をキーとするMap 値は「型」(Typing,KNormal)だったり 「変数名/関数名の別名」(Alpha, Beta,他)だったり M.emptyを初期値とし、再帰処理のときに これを引数にして引き回す
重要な変数とイディオム(2) M.add x t env xという変数/関数名に対応する型 (あるいは別名)tをenvに追加した、 新たなenvを返す 例) M.add “a” Type.Int env 追加 旧env 新env “a” -> Type.Int “f” -> Type.Float “f” -> Type.Float
重要な変数とイディオム(3) “member” M.mem x env xに対応する型/別名がenvに存在するか M.find x env xに対応する型/別名を返す 例) M.find “a” env env “a” -> Type.Int Type.Int “f” -> Type.Float
型推論 Typingモジュールで実行 Syntax.t->Syntax.tの変換 main.ml 20: (KNormal.f 21: (Typing.f 22: (Parser.exp Lexer.token l))) Syntax.t Syntax.t
Typingモジュール Typingモジュールで定義される関数: val deref_typ : Type.t -> Type.t val deref_id_typ : 'a * Type.t -> 'a * Type.t val deref_term : Syntax.t -> Syntax.t val occur : Type.t option ref -> Type.t -> bool val unify : Type.t -> Type.t -> unit val g : Type.t M.t -> Syntax.t -> Type.t val f : Syntax.t -> Syntax.t (基本的に) 参照関係は下から上へ = 下の関数が上の関数を呼び出す
Typing.f eはプログラム全体を指すSyntax.t型データ typing.ml プログラム全体の 型推論はここで実施 164: let f e = 165: extenv := M.empty; 171: (try unify Type.Unit (g M.empty e) 172: withUnify _ -> failwith ”..."); 173: extenv := M.map deref_typ !extenv; 174: deref_term e 型推論の結果を 整理する
Typing.g typing.ml 88: let rec g env e = (* 型推論ルーチン *) 89: try 90: match e with 91: | Unit -> Type.Unit 92: | Bool(_) -> Type.Bool 101: | Add(e1, e2) | Sub(e1, e2) -> 102: unify Type.Int (g env e1); 103: unify Type.Int (g env e2); 104: Type.Int Syntax.tから Type.tへの変換 e1,e2の型推論結果が Intであることを期待 Add/Subの結果はInt
Typing.g (2) “let x = e1 in e2”の型推論: typing.ml 121: | Let((x, t), e1, e2) -> (* letの型推論 *) 122: unify t (g env e1); 123: g (M.add x t env) e2 e1の型推論結果と tを一致させる 変数xを型tとして追加し、 e2の型推論に進む
Typing.unify 型変数間のチェック、代入を行う関数 二つの変数に対する パターンマッチ typing.ml 66: letrec unify t1 t2 = 67: match t1, t2 with 68: |Type.Unit, Type.Unit|Type.Bool, Type.Bool |Type.Int, Type.Int|Type.Float, Type.Float -> () 86: | _, _ -> raise (Unify(t1, t2)) t1とt2の型が既に判明しており、 かつ両者が同一の場合は何もしない 他のパターンについてのチェック および(必要ならば)型の確定 match文内のパターン(68-85行)で拾えなかった場合の処理: t1とt2間に型の不整合ありとみなし、例外を投げる
Typing.unify 片方が未確定の型を持つ変数であった場合 typing.ml 66: letrec unify t1 t2 = 67: match t1, t2 with 80: | Type.Var({ contents = None } as r1), _ -> 81: if occur r1 t2 then raise (Unify(t1, t2)); 82: r1 := Some(t2) t1が型未確定のケース r1と同じ型変数がt2内に 出現していないかチェック (無限長の型が発生することを防止) 例) let a = 1 左辺“a”の型は Type.Var(ref None)から Type.Var(ref Some(Type.Int))に変化 t1の型をt2に確定
Typing.fの実行結果 MinCamlプログラム “let a = 1 in let b = 2 in a + b”を例に Parser.expからTyping.fに入る構文木 Syntax.Let (("a", Type.Var {contents = None}), Syntax.Int 1, Syntax.Let (("b", Type.Var {contents = None}), Syntax.Int 2, Syntax.Add (Syntax.Var "a", Syntax.Var "b"))) Typing.fの“unify”まで実行した構文木 Syntax.Let (("a", Type.Var {contents = Some Type.Int}), Syntax.Int 1, Syntax.Let (("b", Type.Var {contents = Some Type.Int}), Syntax.Int 2, Syntax.Add (Syntax.Var "a", Syntax.Var "b")))
Typing.fの実行結果(2) Typing.fの“unify”まで実行した構文木 Syntax.Let (("a", Type.Var {contents = Some Type.Int}), Syntax.Int 1, Syntax.Let (("b", Type.Var {contents = Some Type.Int}), Syntax.Int 2, Syntax.Add (Syntax.Var "a", Syntax.Var "b"))) Typing.fの“deref_term”まで実行した構文木 Syntax.Let (("a", Type.Int), Syntax.Int 1, Syntax.Let (("b", Type.Int), Syntax.Int 2, Syntax.Add (Syntax.Var "a", Syntax.Var "b")))
K正規化 KNormalモジュールで実行 Syntax.t->KNormal.tの変換 main.ml 19: (Alpha.f 20: (KNormal.f 21: (Typing.f KNormal.t Syntax.t
Syntax.tからKNormal.tへ “a+b+c-d”を例に: 実行前(Syntax.t) 実行後(KNormal.t) KNormal.f 「演算のネスト」を「Letのネスト」へ
実はこのままでは読みにくいので… 後ほどAssocモジュールにてletの簡約を行う Assoc.f (図は説明のためのイメージ) 実際は変数名などの付け替えがあるため、少し異なる
KNormal.f fstは2要素のタプルの1番目の要素を返す関数 例) fst (1,2) = 1 kNormal.ml 195: let f e = fst (g M.empty e) KNormal.gはK簡約形のツリーと型の組 すなわち(KNormal.t, Type.t)を返す関数 (型はKNormal.insert_letで使用するために必要)
KNormal.g kNormal.ml 58: letrec g env = function (* K正規化ルーチン本体 *) 59: | Syntax.Unit -> Unit, Type.Unit 60: | Syntax.Bool(b) -> Int(if b then 1 else 0), Type.Int 63: | Syntax.Not(e) -> g env (Syntax.If(e, Syntax.Bool(false), Syntax.Bool(true))) BoolからInt (0 or 1) への変換 Syntax.NotからSyntax.Ifへ 変換して再びgに廻す
KNormal.g(2) 67: | Syntax.Add(e1, e2) -> (* 足し算のK正規化 *) 68: insert_let (g env e1) 69: (fun x -> insert_let (g env e2) 70: (fun y -> Add(x, y), Type.Int)) 例) (a+b) + (c+d) KNormal.g