Slide 1

Slide 1 text

自作言語malgoのコンパイラを リファクタリングした話 第29回 #hiro_it 星にゃーん(@takoeight0821)

Slide 2

Slide 2 text

自作言語malgo ● ML系言語をベースに設計した極めてコンパクトな俺言語 ○ MinCamlとPolyTigerの間の子 ● 2016年頃からちまちま作っている ○ 当時はコンパイラの作り方を全然知らなかった。感慨深い。 ● 多相型、関数リテラル、パターンマッチなどの言語機能を持つ ○ MLらしく、変数や関数の宣言は let ~ in ~ ○ カリー化は未実装 ● C言語で書かれたプログラムと簡単にリンクできる ○ ただし、C言語側でラッパー関数を書く必要がある

Slide 3

Slide 3 text

malgoで書いたフィボナッチ数計算プログラム let extern print_int : Int -> {} = "print_int" in let extern newline : () -> {} = "newline" in let fun fib(n) : Int = if n <= 1 then 1 else fib(n - 1) + fib(n - 2) in print_int(fib(10)); newline()

Slide 4

Slide 4 text

malgoの機能 ● 変数の定義 let val x = 5 in ~ ● 関数の定義 let fun f(x) = x + 1 in ~ ● 外部関数の定義 let extern print_int : Int -> {} = "print_int" ● パターンマッチ match {1, 2} with | {x, y} => x + y ● 無名関数 fun x => x + 1 ● 配列 let val a = [1, 2, 3] in a.(1) <- 4; a.(0)

Slide 5

Slide 5 text

malgoコンパイラ ● Haskell製。コード数はだいたい3000行ぐらい ● LLVM IRを生成する。アセンブルやリンクにはLLVMのツールチェーンを使う ● 3つの中間表現を持つ ○ HIR:すべての式が変数に束縛された形。 ネストした式を持たない ○ MIR:クロージャ(関数値)を、関数ポインタと環境の組に変換した形 ○ LIR:多相関数を明示的な型変換でvoid*を使った単相関数に変換した形

Slide 6

Slide 6 text

malgoコンパイラの問題点 1. 中間表現が多い 似ているようで違う4つの言語を扱う必要があり、機能追加が難しい 2. 最適化がLLVMまかせ GCが絡むコードの最適化がうまくいかない 3. 多相関数の扱いがややこしい コード生成パスにおけるバグの温床 これらの問題点を解決するため、malgoとLLVM IRを結ぶ新たな中間表現を設計

Slide 7

Slide 7 text

Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 ● ASTからの変換が容易 ○ 複数の中間表現に渡る多段階の変換を回避 ● ヒープに置かれる値とスタックに置かれる値の判別が容易 ○ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 ● 多相的な値の表現が統一的 ○ コード生成における型変換の挿入を削減

Slide 8

Slide 8 text

考えるべきこと:多相関数のサポート方法 多相関数をサポートする方法はいくつかある 1. 型ごとに異なる関数を生成する f(1); f('a'); => f_int(1); f_char('a'); 問題点:型の数に比例してコード量が増加する 2. 値を「多相的な値」に変換する ←従来の実装 f(1); => any2int(f(int2any(1))); 問題点:すべての型について変換処理を定義しないといけない 3. すべての値を64bitにする ←Coreで採用 問題点:メモリを余分に消費し、場合によっては計算コストも生じる

Slide 9

Slide 9 text

参照型と値型 すべての値を64bitで表現するため、次のような表現を使う ● 参照型:以下のような構造体へのポインタ ● 値型:整数や文字などの値。64bitとは限らない 整数は以下のような参照型でラップする(文字なども同様) 8ビットのタグ(パターンマッチで使用) 任意長のペイロード Int#(整数型を表すタグ) (64bit符号付き整数)

Slide 10

Slide 10 text

改善:Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 ● ASTからの変換が容易 ○ 複数の中間表現に渡る多段階の変換を回避 ● ヒープに置かれる値とスタックに置かれる値の判別が容易 ○ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 ● 多相的な値の表現が統一的 ○ コード生成における型変換の挿入を削減 解決!

Slide 11

Slide 11 text

参照型の展開 参照型を使って計算するためには、ペイロードを取り出す必要がある Coreではパターンマッチを使ってペイロードを取り出せるようにする こんな式でペイロードを取り出すことにする match (式) with | Int# x -> x これはmalgoのパターンマッチの構文と対応している パターンマッチで取り出した値はスタック上に置かれる

Slide 12

Slide 12 text

参照型の導入 値を参照型でラップするためには、構造体をヒープ上に確保する必要がある Coreではlet式を使ってヒープ上に値を確保する let x = Int# 42 in x let式で定義した変数の値はヒープ上に確保される(ポインタはスタックに乗る) 関数や配列の定義もlet式で行う(クロージャや配列はヒープ上に確保するため)

Slide 13

Slide 13 text

改善:Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 ● ASTからの変換が容易 ○ 複数の中間表現に渡る多段階の変換を回避 ● ヒープに置かれる値とスタックに置かれる値の判別が容易 ○ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 ● 多相的な値の表現が統一的 ○ コード生成における型変換の挿入を削減 解決!

Slide 14

Slide 14 text

ASTからCore、CoreからLLVM IRへの変換 ● ASTとCoreはほぼ一対一で対応する ○ ネストした式がない、ifがない、let valがmatchになるなどの細かな違い ● CoreからLLVM IRの変換も難しくない ○ パターンマッチはタグに対するswitch-caseとペイロードの取り出しに変換 ○ 関数は基本的にすべてクロージャに変換 ○ クロージャでない関数は直接呼び出すよう最適化 ● Core上での最適化も実装 ○ 関数と参照型によるラップのインライン展開 ○ 冗長なletの除去

Slide 15

Slide 15 text

malgoコンパイラのリファクタリング 1. 中間表現が多い。 => Core一つに! 2. 最適化がLLVMまかせ。 => Core上の最適化を実装! 3. 多相関数の扱いがややこしい。 => 参照型による統一的な表現! 既存の設計の問題点を洗い出し、データ構造を検討し直すことで コードの品質を改善

Slide 16

Slide 16 text

TODO:共通言語基盤としてのCore CoreはAny型とパターンマッチを持つ単純型付きラムダ計算(STL) ● STLは関数型言語の理論的モデルとしてよく使われる malgoとは異なる言語のバックエンドとしても使えるはず ● Coreへ「コンパイル」すればいい ● 一から書くより楽に書ける ○ malgoのコードベースを再利用できるため

Slide 17

Slide 17 text

Griff infixl 0 (|>) (|>) :: a -> (a -> b) -> b x |> f = f x if :: Bool -> {a} -> {a} -> a if c t f = c |> { True -> t! | False -> f! } forign import print_string :: String# -> () forign import newline :: () -> () putStrLn :: String -> () putStrLn = { String# str -> print_string str; newline () } 型検査を実装中