2010-3-20[土] LLVMで Cに変換してみる

LLVM (llc) には Cソースコードを生成する機能があり、 C++ソースをCソースにすることができる模様。

ただし C++ から C への直接変換するのではなく、 一旦.ll/.bc(llvm用アセンブラソース,ビットコード)にしたものを、 llc.exe で(ターゲットCPU用アセンブラソースのかわりに) Cソースへ変換するというもの。

普通のアセンブラソースをCに変換した場合 オーバーヘッドは当然あり、 全体として問題になることはそうないかもだが (昔アセンブラソースからCに変換しての移植作業を何本かやったときの印象)、 何割(何倍)か遅くなっていてもおかしくない。

が llvmの .ll/.bc は、 普通のアセンブラソース/オブジェファイルでなく、 それより情報の多い中間コードなわけだし、 llvm-gcc & llc でオプティマイズしているため、 Cソースになっても元よりも速くなる可能性もありそう。

ということで、ちょっと、試してみた。 3/12にしたテストの1つ目。 C++でなく Cソースだけど。 (32bit色640x480→1920x1080拡大の50回生成の平均時間を求める奴. 実行環境は前回同様)

llvm-gcc で

llvm-gcc -S -o tmp.ll -emit-llvm -O2 -DNDEBUG test.c
llvm-as  -f -o tmp.bc tmp.ll
llc -march=c -f -o test_dst.c tmp.bc

として test_dst.c を生成。
なのだが、このまま別の(複数の)コンパイラでコンパイルしてみると __imp__iobやQueryPerformanceCounter等が未定義参照だと リンカに怒られる。 __imp__iob は stderr の本体(を収めた配列名)、 QueryPerfomance…は時間取得につかってるWin-APIのもの。

ヘッダ込みでコンパイルされた結果を C化してるのだから、 
llvm-gcc でコンパイルする場合のヘッダ/ライブラリと、
ターゲット・コンパイラでのヘッダ/ライブラリが同じ(矛盾しない)
でないと当然マズいのだった。

とりあえずstderrやwin-apiを使わないようにして(外部はC標準関数だけにして)回避。
ソースは、 これこれ
それを llc で変換した後のソースは、 これ

ついでに 一旦 ldでまとめたものを.llに戻してから llc したものも試す。

llvm-gcc -S -o tmp.ll -emit-llvm -O2 -DNDEBUG test.c
llvm-as  -f -o tmp.ll.bc  tmp.ll
llvm-ld     -o tmp.ll.exe tmp.ll.bc
llvm-dis -f -o tmp.2.ll   tmp.ll.exe.bc
llvm-as  -f -o tmp.2.bc   tmp.2.ll
llc -march=c -f -o test_dst.c tmp.2.bc

変換後のソースは、 これ

前者の変換後ソースには元の関数を維持しているが、 後者の変換後ソースでは関数はmain()だけになっている。


2つの変換後のソースは、だいたい他のコンパイラでもコンパイル可能。
(ダミーの alloca.h の読み込みを用意したり、 dmc では bool のtypedefが衝突するのでダミー alloca.h 内で誤魔化したり する必要はあった) (このとき使ったllvmは rev.98686 )

が失敗したモノもあり、
vc64に関しては、ハング(作られたソースが32ビットcpu専用のソースかも?)。
bcc 5.5.1 は宣言に型が多すぎる、と怒ってくれてコンパイルできず (bcc 5.8.2は可)。
clang は型チェック関係でひっかかるので挫折。
mingw440 は、前者の変換ソースのものを実行するとハング。
pcc では、後者の変換ソースでは、DEP チェックにひっかかりハング。

実行できたものについての結果は以下 (単位:ミリ秒)

コンパイラ 元の結果前者の変換での結果後者の変換での結果
vc9sp1 197153162
cygwin gcc344 256238237
mingw gcc345 255239237
mingw gcc440 269---255
LLVM-gcc42(SSE?使用) 103109104
open watcom(1.9rc1) 397327317
dmc(8.51) 352301317
pcc 323308---
coins 493334317
PellesC 408353350
TinyCC 449397396

測定したタイミングによっては10,20ぐらいの差がでたりするかもで、 ざっくりとした感じに受け止めてもらうとして。

LLVMのツールであるllvm-gccではあまり差はないようだが、 他のコンパイラでの結果は、多少なりとも llc で変換したソースのほうが速くなってる雰囲気。

ただ前者と後者の変換については、モノによって多少違うも、 測定誤差でありえる範囲の差で、あまり違いはなさそうに思う (関数の分割具合の違いによるオプティマイズの トレードオフはあるだろうで、それが微妙に出てる可能性もあるけれど)

他のソースも試すべきたんだろうが、面倒で挫折... 他の例でも速くなるのか遅くなるのかどうかは結局ソースしだいと思うが、 それでも元のソースよりも速くなる例ができたわけだから (元ソースがヘボいのだろう、というのは置いといて)、 他の例でもそう酷く遅くなるようなことはないんじゃないかと期待.

※'元の結果'は3/12の結果と比べて少しずれてる。やってることはほぼ同じだけれどソース的には多少(時間取得関数だけでなくclang測定対策等)違うせいか、測定誤差といいきれない差のものがある. 前回のexeを実行すればやはり前回と同様の値なので、ソース変更の影響のよう。今回は今回、ということで。

zip


C++ → C

で、 肝心の c++ → c .
C++標準ライブラリの使用については、 実体がすべてヘッダでの定義ですんでいるようなモノ(std::sortとか)を使う分には、 (実体がライブラリ(アーカイブ .libや.a)化されているものを使わない範囲では)、 変換ソースを他のCコンパイラでもコンパイル&実行できる(た)。

が、g++(clang)側のライブラリ(.a)に実体がある関数や機能を使った場合は、 コンパイルできても当然リンクが行えない状態。 bad_alloc みたいなライブラリものもそうだけれど、 virtual使ったり 例外を使った場合もそう。その他諸々。 C++らしい組み方をしてるとまずその手のものは使われてるだろうで...

足りてないリンク物の代用品をターゲット環境用にも用意できれば なんとかなるかもだが、大仕事だろう。(あるいはターゲット対応をllcに施すとか... どちらにしろ個人がチョロですむ作業ではなく)

I/Oを含まずtemplateを使ったような処理をベターCの範囲で書いてCに持ってくる、くらいなら...と思うも、やっぱ面倒だなあ.


あと、llc は c だけじゃなく c++ ソースも生成できる。
計測に使ったソースを後者の変換をしてみたものが これ

なんかすごい、というか、目が点になる、というか。
効率どうこう以前に趣旨が違うもののような気もする。
(パッと見でincludeフォルダ設定を追加してコンパイルを 試しみたけど、どれも上手くいかず。あまり追及する気にもなれず)


※と、まあ、発展途上のllvmを使っての感想なんで今後どうなっていくか、わからないけれど

他のC++ → C トランスレータ

ついでのメモ。

元祖のC++実装の cfront は、Cソースへのトランスレータとして実装されている。 例外が実装されずに開発は終わっているが、 こちら でR3.0.3等いくつかのバージョンのソースが公開されている模様。 (使えるかどうかはしらない)

現行のC++をサポートしているだろうトランスレータとしては Comeau C/C++。 有料だけど低価格($50)。 面倒がって未購入だけど。 他環境向けのCソース生成ならこちらのほうがよい?(夢見てるかも)... といっても、ターゲット環境ごとの個別対応は必要だろうで、 Comeauのサイトで対応と書かれているのは比較的メジャーな環境のみのよう。