LLVMのOCaml bindingをためす

前からLLMVが気になっていて, きつねさんの本 をざっと読んだりしたが手を動かしてなかったのでチュートリアルをやることにした.

LLVM Tutorial: Table of Contents — LLVM 3.8 documentation

古いチュートリアルだということは知っていたが他にないので仕方なくやってみた.*1

現在の環境(LLVM3.8)でやるのは厳しかった. 途中までだがせっかくなのでメモしておく.

好みでC++ではなくOCamlを選んだ.

LLVMC++で書かれているがOCamlのbindingが公式でサポートされている. 雑に検索したところOCaml以外にHaskell, Go, Rustなどにもbindingがあるらしい.

そういえばRustの初期のコンパイラOCamlLLVMで作られていたみたいな話があった気がする.


コード

ビルドするにはLLVM(3.8)とOCamlの各種ライブラリ(llvm, ctypes, (ppx_deriving))とocamlbuildが必要. 4章の分までは一応動くが後で述べるように色々問題がある.

github.com


やったこと

1章・2章

字句解析と構文解析で, 特にLLVMに関係がない.

構文解析, 字句解析ともに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を知るのが先だなという気持ちになってきた.


感想

OCamlだけで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")))))                                                                

参考にしたもの

*1:この段階で古かったようなので相当 OCaml で LLVM -- 事始め - Oh, you `re no (fun _ → more)