自作のインタプリタ言語にHIRは必要なのか

ryota2357
·

自作言語のパーサからVMまでを1から作り直している。ここでコンパイルはVMバイトコードの生成を意味する。

パーサは手書きのASTからRedGreenTreesを利用するものに変えた。パース方法は変わらず再帰降下+Prattでやってる。

僕の言語はRustで実装してるので、RedGreenTreesはrowanを利用している。rowanのASTはRedNode/Treeのview的なものであるから、コンパイラが利用するには少し使いずらい。

僕の言語の場合、rowanからのASTでコンパイラを作ろうと思えばASTを意味解析してコンパイル可能か検査したら、できる。ただ少し実装が難しい。

旧実装のあるASTのNode(僕の言語ではChunkと名前がついている)にはCaptureという項目があり、関数等の特定のスコープからの外部変数のキャプチャがあるか+あるならどれをキャプチャするか、のVecがあった。これをASTに乗っけてるのはどうなのか、というのはあるが、解析の都合上ASTに乗っけていた。

rowanのASTはRedNode/Treeのviewだと書いた。viewなのだからもちろん構文要素以外の追加のデータは持てない。(というかこれが本来あるべきASTの形)

また、Capture以外にも新規構文としてif式やloop/for式などの分岐が考えられる式が導入する予定がある。これらに対応するためにフロー解析的なのが必要となっている。

var a = if cond then expr1 else expr2 end

これが書ける。例えばexpr2が返り値を持たない場合(代入など)はコンパイルエラーにしたいのだ。

HIRが欲しい理由は「CaptureデータをNode構造に持たせたい」「解析等に都合のいいNodeが欲しい」である。2つ目ついては、例えばwhile式とfor式でほぼ同じような解析をするなら、while/forをloop+ifに変換して、解析アルゴリズムを1つにまとめたい、というものや、実はASTのifはexprとして常に処理されるので、statementとしてのifを生やしたいなど色々ある。

じゃあHIRを作ればいいのではとなるのだが、パフォーマンスに問題が出てきそうなのである。

まず、僕の言語はインタプリタなのでソースコードのコンパイルが実行時に行われる。(例: ファイルAからファイルBがインポート(require)されたとき)なのでパース・コンパイル時間は短くなければならない。

HIRを生成するのことは大きなアロケーションの発生となる。僕の言語の場合HIRの目的は最適化以外にも解析がある。解析では当然エラーが発生する可能性があるので、ASTやRedNode/GreenNodeへのポインタをHIRに保持する必要がある。そのためHIRの各ノードサイズが大きくなってしまい、そこそこなアロケーションになってしまう。

HIRのノードをいい感じに必要最低限のデータだけ載せるようにして、アロケーションを避けなければならない。一応この対策としてはArenaとstring internの導入である程度は(総アロケーション量は変わらないけど)改善可能である。

別にHIRが必ず必要なわけではない。実装は複雑になるがASTだけでも要件は達成可能であるし、おそらくパフォーマンスはHIRを使わないほうが良いであろう。

ひとまず今はHIRを作成する方向でコードを書いている。