Brainf*ckのインタープリタとJITコンパイラ

Adventures in JIT compilation: Part 1 - an interpreter - Eli Bendersky's website

Adventures in JIT compilation: Part 2 - an x64 JIT - Eli Bendersky's website

これらの記事を読み、part2まで真似して書いてみた。

brainf*ck jit · GitHub

記事ではC++で書かれているが、自分はC++は全くわからないのでRustで書いた。

まずシンプルなインタープリタを書いて、遅いよねってなって、まずはインタープリタとして徐々に最適化していく。

で、結構速くなったよね、というところで JIT コンパイルしてみる。

そうすると頑張って最適化したインタープリタよりも速いねぇとなり、さらに JIT しつつ最適化もするともっと速くなるよね。となりpart2は終わり。

このあとLLVMをbackendにしてやってみるとかが続くが、まだやってない。

この記事はJIT初心者の練習の題材としてとても良くて、JITといえばなんとなく実行時にコンパイルしてるんだろうな、という程度にしか理解がなく、JITコンパイラを書いてみるとかいうと途端に意味不明になるようなレベルの人ならこれを書いてみるとなるほど、となるところがあるかもしれない。

個人的にはどういうところが良いと感じたかというと、

  • パースしたコードをインタープリタとしてそのまま実行するのではなくて
  • パースした結果を機械語コンパイル
  • 確保しておいた実行可能なメモリ領域に書き込んで
  • そのメモリ領域を実行する

というところを実際にコードを書いて体験できたので理解が進んだ。

ここで書いたのはbrainf*ckであり個々の命令が単純なので全部を変換したが、実際にはまずはインタープリタでブロックの呼び出し回数をトレースしつつ実行し、頻繁に呼び出されるものだけをコンパイルして実行する、とかになるんだろう。

LuaJITとかがまさにそうで、実行回数を数えておいて必要に応じてJITの処理をする。

参考: - LuaJIT 解析 - Non-public jit.* API

むしろ他にどういったものがあるのかとかも興味あるが、まずは雰囲気がわかってきたのでLuaJITとか、最近だとJavaで書かれたGraalとかがあるんでそのへん調べてみたい。

余談だけど機械語を手書きするときは Online x86 and x64 Intel Instruction Assembler がとても便利。 as とか nasm でやってもいいのかもしれないけど、まとまって書くわけでもないときはこれが楽。