Upgrade to Pro — share decks privately, control downloads, hide ads and more …

自作言語malgoのコンパイラをリファクタリングした話

Yuya Kono
August 30, 2020

 自作言語malgoのコンパイラをリファクタリングした話

第29回 #hiro_it で発表した資料です。
リポジトリは https://github.com/takoeight0821/malgo

Yuya Kono

August 30, 2020
Tweet

More Decks by Yuya Kono

Other Decks in Programming

Transcript

  1. 自作言語malgo • ML系言語をベースに設計した極めてコンパクトな俺言語 ◦ MinCamlとPolyTigerの間の子 • 2016年頃からちまちま作っている ◦ 当時はコンパイラの作り方を全然知らなかった。感慨深い。 •

    多相型、関数リテラル、パターンマッチなどの言語機能を持つ ◦ MLらしく、変数や関数の宣言は let ~ in ~ ◦ カリー化は未実装 • C言語で書かれたプログラムと簡単にリンクできる ◦ ただし、C言語側でラッパー関数を書く必要がある
  2. 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()
  3. 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)
  4. malgoコンパイラ • Haskell製。コード数はだいたい3000行ぐらい • LLVM IRを生成する。アセンブルやリンクにはLLVMのツールチェーンを使う • 3つの中間表現を持つ ◦ HIR:すべての式が変数に束縛された形。

    ネストした式を持たない ◦ MIR:クロージャ(関数値)を、関数ポインタと環境の組に変換した形 ◦ LIR:多相関数を明示的な型変換でvoid*を使った単相関数に変換した形
  5. Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 • ASTからの変換が容易 ◦ 複数の中間表現に渡る多段階の変換を回避 •

    ヒープに置かれる値とスタックに置かれる値の判別が容易 ◦ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 • 多相的な値の表現が統一的 ◦ コード生成における型変換の挿入を削減
  6. 考えるべきこと:多相関数のサポート方法 多相関数をサポートする方法はいくつかある 1. 型ごとに異なる関数を生成する f(1); f('a'); => f_int(1); f_char('a'); 問題点:型の数に比例してコード量が増加する

    2. 値を「多相的な値」に変換する ←従来の実装 f(1); => any2int(f(int2any(1))); 問題点:すべての型について変換処理を定義しないといけない 3. すべての値を64bitにする ←Coreで採用 問題点:メモリを余分に消費し、場合によっては計算コストも生じる
  7. 改善:Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 • ASTからの変換が容易 ◦ 複数の中間表現に渡る多段階の変換を回避 •

    ヒープに置かれる値とスタックに置かれる値の判別が容易 ◦ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 • 多相的な値の表現が統一的 ◦ コード生成における型変換の挿入を削減 解決!
  8. 参照型の導入 値を参照型でラップするためには、構造体をヒープ上に確保する必要がある Coreではlet式を使ってヒープ上に値を確保する let x = Int# 42 in x

    let式で定義した変数の値はヒープ上に確保される(ポインタはスタックに乗る) 関数や配列の定義もlet式で行う(クロージャや配列はヒープ上に確保するため)
  9. 改善:Core中間表現の設計 malgoとLLVM IRの間を結ぶ一つの中間表現を設計することに HaskellのコンパイラGHCの中間表現Coreから名前を拝借 Core中間表現に求められるのは次の3点 • ASTからの変換が容易 ◦ 複数の中間表現に渡る多段階の変換を回避 •

    ヒープに置かれる値とスタックに置かれる値の判別が容易 ◦ GCのアロケータ呼び出しの位置を特定できると最適化やコード生成が楽 • 多相的な値の表現が統一的 ◦ コード生成における型変換の挿入を削減 解決!
  10. ASTからCore、CoreからLLVM IRへの変換 • ASTとCoreはほぼ一対一で対応する ◦ ネストした式がない、ifがない、let valがmatchになるなどの細かな違い • CoreからLLVM IRの変換も難しくない

    ◦ パターンマッチはタグに対するswitch-caseとペイロードの取り出しに変換 ◦ 関数は基本的にすべてクロージャに変換 ◦ クロージャでない関数は直接呼び出すよう最適化 • Core上での最適化も実装 ◦ 関数と参照型によるラップのインライン展開 ◦ 冗長なletの除去
  11. malgoコンパイラのリファクタリング 1. 中間表現が多い。 => Core一つに! 2. 最適化がLLVMまかせ。 => Core上の最適化を実装! 3.

    多相関数の扱いがややこしい。 => 参照型による統一的な表現! 既存の設計の問題点を洗い出し、データ構造を検討し直すことで コードの品質を改善
  12. 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 () } 型検査を実装中