TensorFlow XLA が LLVM を使って neural network の最適化をやっている話とか、k0kubun 君の LLVM-based JIT Compiler for Ruby の話を聞いて触発されたので LLVM の勉強をしてみた。Crystal は LLVM を使っているし、LLVM を使えば emscripten のような言語変換処理系を作れるというのも聞いた覚えがあるので雰囲気だけでも掴んでおくと今後良いことがありそう。


https://llvm.org/docs/tutorial/
 kaleidoscope という言語を作りながらLLVMの機能を学んだ。

LLVM IR の生成と、JIT の実装をやった。

LLVM の API はバージョンが変わるとガラッと変わるっぽくて、サンプルコードと手元の LLVM のバージョンがあってないと色々エラーがでて厳しかった。 https://github.com/llvm-mirror/llvm の該当バージョンのブランチ (例: llvm 3.6 ) からサンプルコードを取ってきて、手元のバージョンも http://releases.llvm.org/ から落として合わせると吉。

Parser については、色々クラス (LL(2) など) があるので、もっと深掘りしたほうが良さそう。

SSA については wikipedia の記事を見るとまぁわかる。 https://en.wikipedia.org/wiki/Static_single_assignment_form

IR の構築は、LLVM の API を通して行う

最適化は LLVM Pass というやつを通すと行われる。どういう最適化をさせるかはオプションで選べる。See https://llvm.org/docs/Passes.html

最適化前

ready> def test(x) (1+2+x)*(x+(1+2));
ready> Read function definition:
define double @test(double %x) {
entry:
  %addtmp = fadd double 3.000000e+00, %x
  %addtmp1 = fadd double %x, 3.000000e+00
  %multmp = fmul double %addtmp, %addtmp1
  ret double %multmp
}

最適化後 (1 + 2 が 3 になるのは標準でされてしまうっぽい)

ready>  def test(x) (1+2+x)*(x+(1+2));
ready> Read function definition:
define double @test(double %x) {
entry:
  %addtmp = fadd double %x, 3.000000e+00
  %multmp = fmul double %addtmp, %addtmp
  ret double %multmp
}

JIT した結果の in-memory address pointer を取得して、関数ポインタとして実行すると、JIT 後の関数を呼び出せる。 JIT した結果のアドレスを持っておけば何回も使いまわせる。


所感

LLVM IR を構築しさえすれば、LLVM Passes (Optimizer) や LLVM JIT の恩恵を受けられる。 クロスコンパイルしてプラットフォームごとのバイナリにすることもできる。 動的言語っぽく走らせることも、バイナリを作って走らせることもできるようになるわけだし、これは良いものだなぁ。


Further readings: