ギャルゲーが萌えるのは当然なのでやっぱり燃えるギャルゲーを作ってみたい。既出ネタをおそれずに書けば
愛を必死に奪い取る。
ごうさんのところで KAGEX なるものが公開されています (←サンプルもこのリンク先で公開されています)。
なんというかごうさんの所で公開されているサンプルを見るのが速いと思うのですが、演出で使える様々なレイヤ効果が簡単に扱えたりします。
それと上記の「ワールド拡張機能」にあるように、環境情報を適切に設定してあれば (ここで「みく」はキャラクタ名)
[みく おじぎ]
や
[みく 制服 通常更新]
のように劇的な記述量の削減ができます。しかも可読性も失われてないです。このタグだけをみても何をやっているのかの想像がつくでしょう。
ボイスの再生も【名前】を行頭に書いて
【みく】「ねえ、おきてよー。遅刻だよー!!!」
のように記述すればキャラクタ「みく」のボイスが自動的に再生されます。改行が自動的にクリック待ちになっていたりします。
ある程度特定の用途に限定されてはいますが、可読性と機能性を失わずに記述量を削減できているすばらしい拡張だと思います。
リポジトリ上の最新のソースから構築された吉里吉里コアでないとKAGEXが正常に動作しないので、週末に吉里吉里2のメンテナンスリリースを行おうと思います。
ちなみにボイス機能のサンプルに使えるような10文程度の適当なボイス募集中です。
LLVMと他のコンパイラの比較を行ってるページ、というかgccのSPEC2000ベンチマークのページですが、見つけたのでリンクを張っておきます。
http://people.redhat.com/dnovillo/spec2000.i686/gcc/individual-run-ratio.html
他のコンパイラと張り合う?ぐらいの能力はあるんですね。
ASTからSSA形式への変換 をノロノロと実装中。
ふと try ... catch/finally のサポートがすこし厄介な事がわかって(もっと前に気付けよという話も)、コードを整理中。
壊れていると思っていた HDD ですが、別コンピュータにつないで badblocks で調べてもエラーが見つからない。書き込みを伴うチェックを行うようなオプションを badblocks につけてもエラーが検出されない。
けれども、その HDD を RAID1エンクロージャ に装着すると再構築中にエラーになる (というか前回出ていた読み出し側 HDD は今度はエラーにならない)。
なんどか繰り返しているうちになぜか再構築が成功。
ARAID-99 1000Lがイカれましたかね。
ううむ。
萌えるゲーム制作 吉里吉里/KAGで作る美少女ゲームという本がインプレスより発売されるそうです。
2006/06/01発売、CD-ROM1枚付属、\2,793(本体 \2,660+税)
壊れたディスクの代替ディスクを買ってきて RAID 再構築を行っていたら、最後のほうで 元ディスク (先ほどまでまだ壊れてないと思っていた方) から読み出しエラーになりました .... orz
とりあえず、その元ディスクも、危ない箇所にさわらなきゃ動くという期待の元、その元ディスクのみで今は運用しています。
どうしよう。読み出せなかった場所は最後のほうだし、そこには何もデータが無い可能性は高いのですが。。。
ミラーデバイス(ARAID-99 1000L)に任せずに自分でブロックデバイスレベルでコピーするか、RAIDブロックデバイスレベルのコピーじゃなくてファイルシステムレベルのコピーを行うか。どちらにしろ結構長期間 kikyou.infoを止めないとならないなぁ。
ちなみに ARAID-99 1000L ですが、液晶表示では、正確にどこで読み出せなくなったかがサッパリわかりません。これもこまったもの。シリアル端子の Linux からの使用法わかんないし (あるんかな)
それとやっぱり Western Digital だからかな?? (身の回りではそういい評判を聞かないのでした)。そしてその代替ディスクも同じ型番の Western Digital 製というオチつき。だって同じ型番がいいじゃん、RAID-1なんだし。
明日の早朝あたりに緊急メンテナンスするかもしれません。
SSA 形式 がなかなかおもしろそうなので、実装してみることに。
とりあえず Risse の、スクリプトコードからバイトコードまでの変換は、 (lex + parse) → AST → SSA form → (最適化) → byte code という流れを考えています。
(最適化) の部分は無いか、あったとしても相当限定された物になりそうです。それでも TJS2 の頃よりは良質なコードを吐けそうですし、変数がプログラム中の任意の時点でとりうる「型」の推測もさほど難しくなさそうです。
lex, parse からバイトコード生成まで on the fly で処理していた TJS2 に比べると明らかにコンパイル処理は重たい物になるでしょうけど、様子をみながらコンパイルそのものも最適化を進めていきたいと考えています。
バイトコードは VM (仮想マシン) 用の物ですが、TJS2 と同じく仮想レジスタマシンにする予定です。
あとコンパイラの基礎についてちゃんと勉強してから実装、、、と行きたいところなのですが、自分は考えるより手が動いてしまう方なので中途半端なものができあがる予定。
参考
RAID1エンクロージャのステータスを見る限り、片方のHDDが壊れたようです。
またですか。
ちなみに前回はこの日に壊れて、3年間保証に釣られてこれを買ってきました。
今度は10ヶ月でお釈迦。
早速変えてこようとおもいます………。
ここ数日 LLVM を弄ってみた感想です。C++例外はどう処理されるのか、Risse で使おうとしている Boehm GC との相性はどうなのか、コルーチン大丈夫なんか、など、まだまだ検証したい項目がありますが、それもまた今度の機会に、ということで。
「簡単に独自のコンパイラや JIT 付きのインタプリタを作れる」というのは、自分はまだやったわけではないのですが、付属の Stacker という Forth ライクな言語は LLVM についてなにも知らなかった人がたったの4日間で実装したらしいですから簡単なのでしょう。付属の他のサンプルをみていてもそう難しそうには見えません。もしかしたら独自の言語を作るのが流行るかも……?
「バイナリサイズがでかい」というのは、たとえば lli (バイトコードインタプリタ/JITコンパイラ) がデバッグ情報なしで 5,955,584 バイトもあります。ダイエットすれば小さくなるのでしょうけど。
「(MinGWでは)ひたすらビルドが難しい」というのもかなりの不安要因です。
これらを鑑みて、肝心な Risse ではどうするか、ですが、Risse は最初は LLVM を使用することを考えずに、簡単な最適化機構とVM を独自に積む方向で考えたいと思います。ただ、LLVM は後に JIT コンパイラを載せたくなったときの最有力の候補ですので、様子を見続けたいと思います。
Risse での実際の用途に近い状態でのLLVM自体の最適化性能を調べるために、以下のようなプログラムを作成しました。
何をやっているかというと、Variant というクラスがあって、整数型の integer というメンバと実数型の real というメンバ、それとこのインスタンスが整数を表しているのか、実数を表しているのかを表す type というメンバを持っています。いわゆるバリアントです。
最後に test という関数があって、0~100000 の整数の合計を計算して返しています。main 関数ではそれの戻り値を表示しています。
さて、これをコンパイルして逆アセンブルしてみると ....
$ llvm-gcc -o vtest test.cpp
gccld: warning: Cannot find library 'stdc++'
$ llvm-dis -f ./vtest.exe.bc
$ cat vtest.exe.ll
; ModuleID = './vtest.exe.bc'
target endian = little
target pointersize = 32
target triple = "mingw32"
deplibs = [ "stdc++", "c", "crtend" ]
%.str_1 = internal constant [5 x sbyte] c"%ld\0A\00" ; <[5 x sbyte]*> [#uses=1]
implementation ; Functions:
declare int %printf(sbyte*, ...)
int %main() {
entry:
%tmp.0 = tail call int (sbyte*, ...)* %printf( sbyte* getelementptr ([5 x sbyte]* %.str_1, int 0, int 0), long 4999950000 ) ; <int> [#uses=0]
ret int 0
}
………。
ということで、いきなりすでに結果の値が埋め込まれてしまっています。Variant クラスの関数も全部なくて、main 関数しかなく、計算をするコードすらありません。
これはイケてるかも。
test関数のループ内に 4 を毎回加算するような以下のコードを書いても
integer_t test()
{
Variant i;
Variant sum = (integer_t)0;
Variant limit = (integer_t)100000;
for(i = (integer_t)0; i < limit; i.Increment())
{
sum += i;
sum += (integer_t)4;
}
return sum.AsInteger();
}
コンパイル結果は
%tmp.0 = tail call int (sbyte*, ...)* %printf( sbyte* getelementptr ([5 x sbyte]* %.str_1, int 0, int 0), long 5000350000 ) ; <int> [#uses=0]
のように計算済み結果となってしまいます。
以下のようなコードでやっと事前の計算をあきらめてくれました。
integer_t test()
{
Variant i;
Variant sum = (integer_t)0;
Variant limit = (integer_t)100000;
for(i = (integer_t)0; i < limit; i.Increment())
{
sum += i;
sum += sum;
}
return sum.AsInteger();
}
逆コンパイル結果は以下のようになりました (main 関数内のみ示す)。
int %main() {
entry:
br label %shortcirc_next.0.i49.i
shortcirc_next.0.i49.i: ; preds = %no_exit.i, %entry
%indvar.i = phi ulong [ 0, %entry ], [ %indvar.next.i, %no_exit.i ] ; <ulong> [#uses=3]
%sum.0.5.i = phi long [ 0, %entry ], [ %tmp.18.i.i, %no_exit.i ] ; <long> [#uses=2]
%exitcond.i = seteq ulong %indvar.i, 100000 ; <bool> [#uses=1]
br bool %exitcond.i, label %_Z4testv.exit, label %no_exit.i
no_exit.i: ; preds = %shortcirc_next.0.i49.i
%i.0.0.i = cast ulong %indvar.i to long ; <long> [#uses=1]
%tmp.18.i28.i = add long %i.0.0.i, %sum.0.5.i ; <long> [#uses=1]
%tmp.18.i.i = shl long %tmp.18.i28.i, ubyte 1 ; <long> [#uses=1]
%indvar.next.i = add ulong %indvar.i, 1 ; <ulong> [#uses=1]
br label %shortcirc_next.0.i49.i
_Z4testv.exit: ; preds = %shortcirc_next.0.i49.i
%tmp.0 = tail call int (sbyte*, ...)* %printf( sbyte* getelementptr ([5 x sbyte]* %.str_1, int 0, int 0), long %sum.0.5.i ) ; <int> [#uses=0]
ret int 0
}
いずれにしろ、Variant 内に書かれているような実数演算に関するコードはいっさい出てきていません。整数の計算しか書いてないですし、実際に整数の計算しかしていないからです。
実は、これぐらいの最適化をしてくれることを期待していたのです。
Risse はタイプ・ルーズな言語なので、変数などへのアクセスはすべて型が何であるかを実行時に毎回チェックしています。
たとえば TJS2 で以下のようなコードを書くと、
function make_sum(array)
{
var sum;
for(var i = 0; i < array.count; i++) sum += array[i];
return sum;
}
現状の実装では i の型を毎回チェックしてしまいます。
しかし、i は整数にしかなり得ないのは、インライン展開や定数伝播(constant propagation)解析や死亡コード除去(dead code elimination)などの詳細な解析をおこなえば判明するので、これにより最適化の効果が望めるところです。
Risse は動的な言語ですが、この手の最適化を妨げるほどの自由度は与えない方針です。たとえば 整数クラス Integer の += 演算子などは、とりあえずはオーバーライドや置き換えが可能にはしないと思います。
ちなみに、プログラム中の Variant::integer と Variant::real を union にすると LLVM ではこの手の最適化はいっさいダメになってしまいます。実際の Risse 内部のバリアントはすべての型の値は一つの union で共有されているので、この手の最適化を有効にするには union による共有を (LLVM用コード内では)あきらめるか、あるいはそもそも変数の型のトラッキングを Risse 側で解析してやる必要がありそうです。前者は Risse 内部のバリアント型との変換を書かなければならないし、このような最適化が聞かなかった場合に変数がストレージを食い過ぎるので、後者を採用するしかないかも、、、ってそれじゃ今回見てきたような LLVM の最適化の恩恵をあまり得られないじゃん。
どうしようかな。まさか、むしろ、いまさら、Risseをタイプルーズじゃなくするとか?
簡単なベンチマークを。
ベンチマーク用プログラムにはこちらのプログラムを使用し、655,360桁の円周率を計算するのに必要な時間を計測しました (I/Oの時間を計測する目的ではないので、ソースを修正して出力が行われないようになっています)。
まず、gcc。
$ gcc -v Reading specs from c:/k3/MinGW/bin/../lib/gcc/mingw32/3.4.5/specs Configured with: ../gcc-3.4.5/configure --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 --prefix=/mingw -- enable-threads --disable-nls --enable-languages=c,c++,f77,ada,ob jc,java --disable-win32-registry --disable-shared --enable-sjlj-e xceptions --enable-libgcj --disable-java-awt --without-x --enable -java-gc=boehm --disable-libgcj-debug --enable-interpreter --enab le-hash-synchronization --enable-libstdcxx-debug Thread model: win32 gcc version 3.4.5 (mingw special) $ gcc -O3 -mtune=athlon64 -o pi-gcc-native pi_fftca.c fftsgx.c $ time ./pi-gcc-native.exe real 0m3.171s user 0m0.015s sys 0m0.015s
結果は 3.171秒。
つぎ、LLVM。
$ llvm-gcc -o pi pi_fftca.c fftsgx.c $ time lli ./pi.exe.bc real 0m4.438s user 0m0.015s sys 0m0.015s
結果は 4.438秒。
ちなみに JIT ではなくてインタプリタで動作させた時間を計ろうとしたところ
$ lli --force-interpreter ./pi.exe.bc This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.
………あらら。実行できませんでした。
llc で LLVM がどの様なコードを実行しているのか調べてみると、どうやら SSE2 に実数演算を行わせている模様。
gcc でも同様のオプションをつけてやってみると
$ gcc -O3 -mtune=athlon64 -march=athlon64 -msse2 -mfpmath=sse -o pi-gcc-native pi_fftca.c fftsgx.c $ time ./pi-gcc-native.exe real 0m3.063s user 0m0.015s sys 0m0.015s
………差が開いてるし。
逆に LLVM で SSE のような拡張命令を使わせないと
$ time lli --mcpu=generic ./pi.exe.bc real 0m6.187s user 0m0.015s sys 0m0.015s
これは遅い。
いろいろ試したのですがおおむね LLVM のほうが2~3割遅いという結果に。まだ一つの例しか見てないのでこれで優劣を決める訳にはいかないのですが、まだまだ最適化が甘いのかもしれません。どうもアセンブリ言語の出力をみていると、レジスタの割り当てに無駄が多く、ロードとストアが多い気がします。
(ここの日記は新しい項目ほど上に来ています。「LLVMをいじる(番号)」の(番号)の順に読んでください)
さて、ここでやっと LLVM が生半可に使えるようになってるはずです。
テストプログラムを作ってみます。
$ cat hello.c
#include <stdio.h>
int main(void)
{
printf("hello world!\n");
return 0;
}
$ llvm-gcc hello.c -o hello $ ls -l total 4 -rw-r--r-- 1 Taka Administ 84 May 5 23:04 hello.c -rwxr-xr-x 1 Taka Administ 5632 May 5 22:23 hello.exe -rw-r--r-- 1 Taka Administ 177 May 5 23:04 hello.exe.bc
hello.exe と hello.exe.bc ができているのがわかります。hello.exe は hello.exe.bc を実行するためだけのスタブです。実際のプログラムは hello.exe.bc のほうです。実行してみます。
$ ./hello.exe hello world!
おー。
スタブではなくてインタプリタを起動して実行することもできます。
$ lli hello.exe.bc hello world!
hello.exe.bc の逆アセンブルをしてみます。
$ llvm-dis hello.exe.bc
hello.exe.llというファイルができます。内容を表示してみます。
$ cat hello.exe.ll
; ModuleID = 'hello.exe.bc'
target endian = little
target pointersize = 32
target triple = "mingw32"
deplibs = [ "c", "crtend" ]
%.str_1 = internal constant [14 x sbyte] c"hello world!\0A\00" ; <[14 x sbyte]*> [#uses=1]
implementation ; Functions:
declare int %printf(sbyte*, ...)
int %main() {
entry:
%tmp.0 = tail call int (sbyte*, ...)* %printf( sbyte* getelementptr ([14 x sbyte]* %.str_1, int 0, int 0) ) ; <int> [#uses=0]
ret int 0
}
これは LLVM 用のアセンブリ言語ですね。
ネイティブコードに変換してみます。
$ llc ./hello.exe.bc
hello.exe.s ができます。内容を表示してみます。
$ cat hello.exe.s .text .align 16 .globl _main _main: subl $12, %esp fnstcw 10(%esp) movb $2, 11(%esp) fldcw 10(%esp) movl $_.str_1, (%esp) call _printf xorl %eax, %eax addl $12, %esp ret .data .align 4 _.str_1: # .str_1 .asciz "hello world!\n"
ネイティブコードといってもネイティブなアセンブリ言語で記述された出力が出てくるだけなので、これを使うにはアセンブルとリンクをしないとなりません。どうも gas 用のアセンブリ言語なので gcc が使えるようです。
$ gcc hello.exe.s -o hello-native.exe $ ./hello-native.exe hello world!
簡単なサンプルですが、一通り動くことがわかりました。
なんというかだんだん疲れてきました。Cygwin はまだ UNIX に近いからいいとしても、たぶん MinGW はおろか Windows プラットフォームによる動作はまだまだ完全にサポートされてないんかなーと。
世の中の PC 用 OS って、シェアを考えると大体 Windows, Mac, Linux, *BSD, Solaris?? だと思っているのですが、メジャーな中では最後のフロンティア(謎)だった Mac OS が X になって BSD ベースになってしまい、ほとんどが UNIX ベースになりました。Windows を除いて。
もちろん UNIX 系 OS の間にも細かい差異があって完全に同じとは言えないのですが、Windows は Cygwin のようなエミュレーションレイヤを使わない限りは UNIX 系とはかけ離れた存在なので、オープンソース系のマルチプラットフォームなアプリではよく Windows だけが例外的な存在になり、サポートするのが困難な状態になってるソフトウェアを見かけます。Mac OS X も特に GUI 周りは例外的ではありますが、まだまだ Windows よりはましといった状態。
ただ、普及シェアからいうと Windows 対応は致し方ない所ですね。
LLVM もその例に漏れないのかなと。
さて、もう一度 LLVM のビルド方法を、gcc フロントエンドも含めて再度整理します。
パスを通しておきます。LLVM のバイナリと LLVM の gcc フロントエンド用のパスです。
$ export PATH="$PATH:/projects/llvm/bin:/projects/llvm-gcc/bin"
LLVM の展開とツールのビルド。前回の内容に近いですが、パッチの内容が更新されています。
$ export CPPFLAGS="-DLLVM_ON_WIN32" $ mkdir -p /projects/llvm/source $ mkdir -p /projects/llvm/source/build $ cd /projects/llvm/source $ wget http://llvm.org/releases/1.7/llvm-1.7.tar.gz $ tar zxvf llvm-1.7.tar.gz
展開したファイルに対して以下の修正を行います (パッチ)
$ cd /projects/llvm/source/build $ ../llvm/configure --prefix=/projects/llvm --enable-targets=x86 $ make tools-only TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi \ LDFLAGS+=-Wl,--no-keep-memory -r
これで LLVM のツール群が作成されます。これをインストール。
$ make install TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi \ LDFLAGS+=-Wl,--no-keep-memory -r
エラーが出るかもしれませんが人類最強なので無視。
/projects/llvm/bin にバイナリがインストールされますが、拡張子である .exe がありません。拡張子がないと exec が実行に失敗するようなので 拡張子をつけてやります。
$ cd /projects/llvm/bin $ for d in *; do mv $d $d.exe; done
次に LLVM の gcc フロントエンドをビルドします。
$ mkdir -p /projects/llvm-gcc/source $ cd /projects/llvm-gcc/source $ wget http://llvm.org/releases/1.7/cfrontend-1.7.source.tar.gz $ tar zxvf cfrontend-1.7.source.tar.gz
展開したファイルに対して以下の修正を行います (パッチ)
$ cd /projects/llvm-gcc/source/ $ mkdir build $ cd build $ ../cfrontend/src/configure --prefix=/projects/llvm-gcc \ --disable-threads --disable-nls --disable-shared \ --enable-languages=c,c++ --program-prefix=llvm- \ --srcdir=../cfrontend/src mingw32 $ make CFLAGS=-O LIBCFLAGS+=-g LIBCFLAGS+=-O2 LIBCXXFLAGS+=-g \ LIBCXXFLAGS+=-O2 LIBCXXFLAGS+=-fno-implicit-templates all
libstdc++-v3 の configure は失敗します。たぶん失敗しちゃいけないんじゃないかと思いますが ... これも無視加減でとりあえずインストールします。
$ make install
MinGW のヘッダファイルなどが参照されないのでコピーします。もしかしたらこれは llvm-gcc の configure 時に --includedir=/mingw/include とすることで回避できるかもしれません。
$ mkdir -p /projects/llvm-gcc/include/c++/3.4-llvm $ mkdir -p /projects/llvm-gcc/mingw32/include $ cp -r /mingw/include/c++/3.4.5/* /projects/llvm-gcc/include/c++/3.4-llvm $ cp -r /mingw/include/* /projects/llvm-gcc/mingw32/include
もう一度 llvm のディレクトリに戻って LLVM をビルドします。
$ cd /projects/llvm/source/build $ ../llvm/configure --prefix=/projects/llvm --enable-targets=x86 $ make TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi LDFLAGS+=-Wl,--no-keep-memory -r
examples ディレクトリ内のサンプルプログラム ParallelJIT は、pthread がインストールされてないとコンパイルエラーになるようです。pthread-win32 を mingw 用にインストールすればコンパイルが通るかもしれませんが、今回は無視。
インストールします。
$ make install TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi LDFLAGS+=-Wl,--no-keep-memory -r
途中でエラーになるかもしれませんが無視します。
LLVMのコンパイル方法を整理。
$ export CPPFLAGS="-DLLVM_ON_WIN32" $ mkdir -p /projects/llvm/source $ mkdir -p /projects/llvm/source/build $ cd /projects/llvm/source $ wget http://llvm.org/releases/1.7/llvm-1.7.tar.gz $ tar zxvf llvm-1.7.tar.gz
展開したファイルに対して以下の修正を行う (パッチ)
$ cd /projects/llvm/source/build $ ../llvm/configure --prefix=/projects/llvm --enable-targets=x86 $ make tools-only TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi \ LDFLAGS+=-Wl,--no-keep-memory -r
でこれで全部 llvm がビルドできたわけではなくて、これから llvm 用 C/C++ コンパイラをビルドしないとなりません。
前回の「リンカが処理する内容に直接影響を与えそうな最適化オプションを個別にオフにすることでリンクが通るようになりました」は気のせいだったみたいです。すみません。見誤っていました。
リンカをデバッガ上で動かして観察していると LLVMCodeGen.o という大きなオブジェクトファイルをリンクしている途中でエラーが発生しています。
こんな大きなオブジェクトファイル、どこからできたのかなと観察してみると、Makefile中で複数のオブジェクトファイル *.o をまとめて一つのオブジェクトファイル .o にする Relink と呼ばれているというルール (たぶん ld -r か ld -i をやってる) があってこれで生成されています。
そういえば現状のRisaでもサブディレクトリごとに *.o をまとめて一つの .o にまとめて、最後にそれらをリンクするという手法を用いていています。こうしないと最終リンク時にコマンドラインに並ぶオブジェクトファイルの数がとんでもないことになります。まあ、たぶんアーカイブファイルでもいいんでしょうけど。
で、LLVM、どうやらこの過程で ld がおかしなオブジェクトファイルを吐いている様子です。
Relink ではなくて、普通の アーカイブ .a を作成するように Makefile を変えたらビルドが通るようになりました。
こうやって作られたオブジェクトファイルやアーカイブファイルが、ビルドトップディレクトリ下の Release/lib にあるのですが、なぜオブジェクトファイルやアーカイブファイルを分けているのでしょうかね。あとで理由を見てみよう。
いろいろいじってる内にビルドが通るようになったのでいろいろと確認したいことがありますから、あとでもう一度手順を整理してみたいと思います。
どうもコンパイラの最適化を無しにするとリンカが asesrtion fail を起こさないようです。でも最適化無しだと 10 倍近く遅いよーみたいな警告がビルドの最後に出るのでやっぱり最適化はしたい。
結局、-O2 最適化を行いつつ、リンカが処理する内容に直接影響を与えそうな最適化オプションを個別にオフにすることでリンクが通るようになりました。
あと、 gcc 4.1 は使い慣れないので元の 3.4.5 に戻しました (こっちはこっちで落ち着いたらいずれ使うつもり)。
configure 以降は
$ ../llvm/configure --prefix=/projects/llvm --enable-targets=x86 $ make tools-only TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi \ LDFLAGS+=-Wl,--no-keep-memory -r \ CFLAGS+="-fkeep-inline-functions -fno-merge-constants -O2" \ CXXFLAGS+="-fkeep-inline-functions -fno-merge-constants -O2"
にしました (llvm に関してはどうやら CFLAGS と CXXFLAGS は make 時にこうやって渡さないとダメ)。
ちなみに LLVM のデフォルトの最適化レベルである -O3 だと gcc 3.4.5 が internal compile error するので -O2 にしてあります。
が、今度は opt.exe のリンク中に
undefined reference to `llvm::SDNode::hasNUsesOfValue(unsigned int, unsigned int) const
などという表示が。またあとで見ます ...
最適化をするに当たって、いずれ JIT コンパイラとして使おうと思っていた The LLVM Compiler Infrastructure を先にさわっておこうと思っています。
The LLVM (Low Level Virtual Machine) Compiler Infrastructure はコンパイラを実装するに当たって必要な機能のうち、最適化、コード生成と実行に関わる部分がライブラリになっています。最適化とコード生成はコンパイル時、リンク時、実行時、オフライン時 (いったんコンパイルが終わった物を最適化された状態にするために再コンパイルするような用途ですかね) にできるとなっています。実行時にネイティブコードへの変換を行ってその場で実行する JIT (Just In Time) コンパイルも可能らしいです。ドキュメントを読んでるとプロファイルフィードバックされた動的な最適化もできそうなことが書いてありますが、まだ全部読んでいません。
LLVM を使ってできることはいろいろありそうで、たとえば C/C++ コンパイラがついてくるので、プラットフォーム非依存のコンパイル済みのバイナリ形式のプラグインをつくって、それを実行時にネイティブコードに変換したり、Risse スクリプトを LLVM 用 VM 形式で保存したり、そのままネイティブコードに変換して実行できたりするんじゃないかと考えています。
LLVM での最適化が相当望めるようならば、Risse で実装する最適化は最小限の物にして、LLVM にあとの最適化をやらせるという方法もあるので、LLVM がどれほど「つかえる」のかを見極めておきたいというのがあります。
とりあえずはコンパイル。MinGW でコンパイルが通らない。
c:\k3\mingw\bin\..\lib\gcc\mingw32\4.1.0\..\..\..\..\mingw32\bin\ld.exe: BFD 2.16.92 20060416 assertion fail ../../binutils-2.16.92/bfd/cofflink.c:1928
などと出る。binutils 2.16.91 でダメだったので binutils 2.16.92 をコンパイルして使ってみているのだけれどもだめ。
gcc 3.4.5 が悪いのかと思い、gcc 4.1 をコンパイルしてそれを使ってみてもだめ (状況が同じ) 。
もうちょっとよくドキュメントとか同じようにこまってる人がいないかとか、みてみますかね。
ちなみに binutils 2.16.92 のビルド方法
$ mkdir -p /projects/binutils/source $ cd /projects/binutils/source $ wget ftp://sourceware.org/pub/binutils/snapshots/binutils-2.16.92.tar.bz2 $ tar jxvf binutils-2.16.92.tar.bz2 $ mkdir -p /projects/binutils/source/build $ cd /projects/binutils/source/build/ $ ../binutils-2.16.92/configure --host=mingw32 --build=mingw32 --target=mingw32 \ --prefix=/mingw --with-gcc --with-gnu-as --with-gnu-ld --disable-shared --disable-nls $ make CFLAGS="-O2 -mtune=i686 -D__USE_CRTIMP -fno-exceptions" LDFLAGS=-s $ make install
それと、GCC 4.1 ビルド方法 (C/C++のみ)
$ mkdir -p /projects/gcc-4.1/source $ mkdir -p /projects/gcc-4.1/source/build $ cd /projects/gcc-4.1/source $ wget ftp://tron.um.u-tokyo.ac.jp/pub/GNU/gcc/gcc-4.1.0/gcc-4.1.0.tar.bz2 $ tar jxvf gcc-4.1.0.tar.bz2 $ cd mkdir -p /projects/gcc-4.1/source/build $ CC=/mingw/bin/gcc LD=/mingw/bin/ld AS=/mingw/bin/as ../gcc-4.1.0/configure \ --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 \ --prefix=/mingw --enable-threads --disable-nls --enable-languages=c,c++ \ --disable-win32-registry --disable-shared --enable-sjlj-exceptions--without-x \ --enable-hash-synchronization --enable-libstdcxx-debug $ make $ make install
GCC はなんかもっと正当なビルド方法があったような気がしますが思い出せません。
LLVM ビルド方法 (ここを参考に)
$ mkdir -p /projects/llvm/source $ mkdir -p /projects/llvm/source/build $ cd /projects/llvm/source $ wget http://llvm.org/releases/1.7/llvm-1.7.tar.gz $ tar zxvf llvm-1.7.tar.gz $ cd /projects/llvm/source/build $ ../llvm/configure --prefix=/projects/llvm --enable-targets=x86 $ make tools-only TOOLLINKOPTSB+=-limagehlp TOOLLINKOPTSB+=-lpsapi \ LDFLAGS+=-Wl,--no-keep-memory -r
ただし、途中でエラーになると思うので以下の2点を修正
この LLVM のビルドの途中で、上記のリンカの assertion fail になります。
5月か ...
Risse は AST 生成が一通り終わったのでオプティマイザ(最適化するとこ)をどうにか実装するフェーズへ。
もともとが動的な言語なのであまり最適化による効果は期待できないものの、興味のある分野なのと、(スクリプト内で多用することの是非はともかく) goto を実装するうえでのフロー解析をしてみたいのです。
しばらくの間は文献をあさる期間が続くだろうなぁ。
ちなみに TJS2 は最適化という最適化はしてません。強いて言うなら論理圧縮と定数たたみ込みぐらい。