めくるめくランタイムコンパイラの世界

平たく言えば、最適化はともかくとして、"高速なシーケンサとしてCPUを使いたい"がために作られる小さなコンパイラというジャンルのライブラリがある。

必要性

これらのライブラリは、パフォーマンスに関する魔法の薬では無い。ある特定の目的のためには良く働くが、通常のコンパイラよりも特殊な目的のために設計されている。
大抵、"コンパイルすべきプログラムがコンパイル時に決定できない"場合に用いられる。qemuやnanojitの場合は実行対象のプログラムであり、orcの場合はデコードのパラメタ等。
従来は、パラメタに応じてコードを全て書くとか、コードの自己書き換えのようなことが行われていた。

このコードは、Ring/Sync/FMの状態に応じて動作する「汎用の」ルーチンと言える。しかし、分岐命令を多く使っているので 遅い。高速化のためには、状態に応じて分岐をなくした専用ルーチンを用意して、それを適切に呼び出すという方法がよいと思う。 しかし、言うのは簡単だが、とっても面倒くさい。 この例でも、on/offをとるパラメータが3つ(ring/sync/fm)あるので、2^3=8種類の専用ルーチンが必要になる。

(フルセットのコンパイラをそのままつかう)

そもそも、最近のPCはリソースが潤沢なので、gccのような通常のコンパイラを裏で呼び出して実行するという手法を採用できることも多い。
例えば、Varnishは設定スクリプト(VCL)をC言語に変換し、gccコンパイルして共有ライブラリに変換している。
OpenCL実装は基本的に全てバックグラウンドでC言語コンパイラやリンカのようなtoolchainを動作させている。

ランタイムアセンブラ

Xbyak( http://homepage1.nifty.com/herumi/soft/xbyak.html )のようなランタイムで実行できるアセンブラを使うケースが有る。
self-hostingなForthは、自身をビルドするために自身で書いたアセンブラを使うため、必然的にランタイムアセンブラも使用できる。

このようなテクニックを使うと、CPU独立なコードを書くのが難しくなる。

qemuTCG

qemuTCGはそれなりに熱心な最適化を行っている。個々の変数オブジェクトには属性が有り、明示的に開放することでレジスタ割り付けを最適化することもできる。
TCGは良くデザインされているが、浮動小数点演算を行えないという絶妙な制限がある。
moshに以前バインディングを書いたことが有るが( http://d.hatena.ne.jp/mjt/20090807/p2 - コードは今はアクセスできない)、これ以外にqemuの外での活用は無いかもしれない(AFAIK)。

orc

orc (oil runtime compiler)は、SIMD命令ループの生成に特化したランタイムコンパイラで、いくつかのCPUアーキテクチャに対応したコードを出力できる。
このコンパイラの前身はliboilというライブラリで、アセンブラで最適化された色空間変換などの静的なコードを収集していた。orcではダイナミックにこれらのコードを生成できるため、CODECのパラメタに応じて処理を事前に選択するなどの最適化が可能となった。orcはTCGと異なり、分岐命令や関数callが無い。配列の演算ループの生成に特化している。
orcは興味深く、実際に機能するが、まだstableなAPIが無い。今のところGStreamerやSchrödingerで使われている。

study

TCGやorcのようなランタイムコンパイラの構造は基本的に一緒で、

  • 何らかの中間言語を持つ
  • 中間言語命令と一対一対応する、ネイティブ命令を持つ
  • 出力は、簡単なレジスタアロケータを通した後、中間言語に対応する命令を順番に書き込むだけ

ターゲット(対応するCPU)に依存する部分を大胆に省略しているのが特徴と言える。gccLLVMのような通常のポータブルなコンパイラでは、ターゲットごとに中間言語から実際のCPU命令に変換する部分をそれなりに努力して作りこんでいる。