LLVMのOCaml bindingをためす
前からLLMVが気になっていて, きつねさんの本 をざっと読んだりしたが手を動かしてなかったのでチュートリアルをやることにした.
LLVM Tutorial: Table of Contents — LLVM 3.8 documentation
古いチュートリアルだということは知っていたが他にないので仕方なくやってみた.*1
現在の環境(LLVM3.8)でやるのは厳しかった. 途中までだがせっかくなのでメモしておく.
LLVMはC++で書かれているがOCamlのbindingが公式でサポートされている. 雑に検索したところOCaml以外にHaskell, Go, Rustなどにもbindingがあるらしい.
そういえばRustの初期のコンパイラはOCamlとLLVMで作られていたみたいな話があった気がする.
コード
ビルドするにはLLVM(3.8)とOCamlの各種ライブラリ(llvm, ctypes, (ppx_deriving))とocamlbuildが必要. 4章の分までは一応動くが後で述べるように色々問題がある.
やったこと
1章・2章
構文解析, 字句解析ともにCamlp4をバリバリ使っているけど, 今はppxが推奨されるらしいのでどうなのかという気がする.
私はocamllexとmenhirを使った.
3章
ここからLLVMを使う.
3章では抽象構文木からLLVM IRという中間コード生成をする. そのままコンパイルしようとするとaddやmulでdoubleを使ってることを怒られる. faddやfmulに書き換えてdoubleをfloatにするとコンパイルできる.
4章
最適化のためにパスをいくつか入れて, そのあとJITで動かす.
4章のコードをビルドしようとすると
Error: Unbound module ExecutionEngine
になる. ググると同じエラーが出てる人が多い.
3.8にはそもそもLlvm_executionengine.ExecutionEngineなるモジュールがない. 3.5まではあるみたいなので, LLVMのバージョン3.6のリリースノート を見ると
LLVM 3.5 has, unfortunately, shipped a broken Llvm_executionengine implementation. In LLVM 3.6, the bindings now fully support MCJIT, however the interface is reworked from scratch using ctypes and is not backwards compatible.
ということらしい.
パスはとりあえず諦めてJITで動かすことを目標にした. 名前と型とモジュールの説明からそれっぽい関数を探して書き換えていくと一応動いた.
# 1 + 1; define float @"00"() { entry: ret float 2.000000e+00 } Evaluated to 2.
# def foo(a b) a*a + 2*a*b + b*b; define float @foo(float %a, float %b) { entry: %fmultmp = fmul float %a, %a %fmultmp1 = fmul float 2.000000e+00, %a %fmultmp2 = fmul float %fmultmp1, %b %faddtmp = fadd float %fmultmp, %fmultmp2 %fmultmp3 = fmul float %b, %b %faddtmp4 = fadd float %faddtmp, %fmultmp3 ret float %faddtmp4 } # foo(2,3); define float @"00"() { entry: %calltmp = call float @foo(float 2.000000e+00, float 3.000000e+00) ret float %calltmp } Evaluated to 25.
それでもまだ色々問題がある
- 無名関数が実行できない(チュートリアル通り無名関数の名前を""にしてるとexception Llvm_executionengine.Error("Function not found")になる
- トップレベルのexpressionの評価が一度しか行えない(二度目はエラーになる)
- extern cosなどで宣言した関数が正しく動かない
とりあえず無名関数にも内部では名前("00")をつけたり, 一度トップレベルのexpressionを評価するたびにllmoduleを作り直したりすることでそれっぽい挙動にはなるが, 評価のたびに関数の定義が消えるというような他の問題が生じる.
5章以降では構文も増えてこのまま進むのは無理そうなので一旦OCamlを離れてC++でLLVMを知るのが先だなという気持ちになってきた.
感想
その他
構文解析木をプリントするのに初めてppx_derivingを使ってみたら便利だった. 今まで書いてきた手書きプリンターはなんだったのか.
# def foo(a b c) (a - b) * (b - c) + (c - a); (Ast.Def Ast.Function (Ast.Prototype ("foo", [|"a"; "b"; "c"|]), Ast.Binary_c ("+", Ast.Binary_c ("*", Ast.Binary_c ("-", (Ast.Variable "a"), (Ast.Variable "b")), Ast.Binary_c ("-", (Ast.Variable "b"), (Ast.Variable "c"))), Ast.Binary_c ("-", (Ast.Variable "c"), (Ast.Variable "a")))))
参考にしたもの
LLVMのOCaml bindingのページ モジュールの説明があるがこれだけでわかるものでもない.
*1:この段階で古かったようなので相当 OCaml で LLVM -- 事始め - Oh, you `re no (fun _ → more)