W.Deeの2007年9月の日記

kikyou.info»日記
最新月 : 2008年10月
2003年 [             3    4    5    6    7    8    9   10   11   12  ] 月
2004年 [   1    2    3    4    5    6    7    8    9   10   11   12  ] 月
2005年 [   1    2    3    4    5    6    7    8    9   10   11   12  ] 月
2006年 [   1    2    3    4    5    6    7    8    9   10   11   12  ] 月
2007年 [   1    2    3    4    5    6    7    8    9   10   11   12  ] 月
2008年 [   1    2    3    4    5    6    7    8    9   10   11   12  ] 月
2009年 [   1    2    3    4    5    6    7    8    9   10   11       ] 月
前月の日記  次月の日記

2007年9月29日

Risse進捗(25)

死亡コード除去を実装しました。

前のエントリ で書いた

{
    var a = 0;
    var b = false;
    if(b) a = 1;
    return a;
}

このコードはついにやっと以下のコードに。

00000 const %0, *0 // *0=0
00003 return %0

つまり、定数 0 を常に返します。

こういうのもいける口のよう。

{
    var counter = 0;
    false && (counter +=4) ; // no count
    true  && (counter +=5) ; // count
    false || (counter +=6) ; // count
    true  || (counter +=7) ; // no count
    counter ;
}

これはこうなります (11 を常に返す)。

00000 const %0, *0 // *0=11
00003 return %0

まだ大量の演算子のルール (どの型とどの型を掛け合わせるとどの型がでてくる) を書き終わってないので、それを書きます。

2007年9月24日

Risse進捗(24)

TJS2だと定数畳込みは限定的だったので、

{
    var a = 2, b = 1;
    return a + b;
}

のように文が分かれていると定数畳込みをしてくれませんでした。

Risseの定数畳込みは文を超えても定数畳込みをしてしまうことができます。

Risseでは今のところ、暗黙の型変換をあまり行わない方向です。たとえば"A" + aのようにして、aが数値だった場合は例外が発生します。Risseの場合はこのようなことをやりたい場合は "A\{a}" のようにすることが推奨されることになります。

これに関連して、たとば以下のように0以上の連続する整数をコロン区切りで返す関数を書いたとして、

function t(l)
{
    var s = '';
    for(var i = 0; i < l; i++) {
        if(s != '') s += ':';
        s += i;
    }
    return s;
}

ここで s += i が例外を発生するのは明かです。ここでは常に s が文字列で、i が整数だからです。

Risse はある程度まで型を追うことができるので、これをコンパイル時にみつけて、以下のように警告することがあります。

warning: String::+(Integer) will cause an exception at runtime

「警告することがある」というのは、警告しない場合があるということです。Risseのコンパイラはすべての変数の状態を詳細に追っている訳ではないので、場合によってはエラーになる条件を見つけることができません。ちなみに上記の場合は、Risseの現在の内部の実装では、 String + Integer の条件を検出するよりも前に、ループの最初の状態である '' + 0 の計算をコンパイル時に試行するため、そこで例外が発生してしまっているようです。最初に試行するのがエラーがでない条件だった場合、警告をしないことがあります。

また、理論的に到達しないプログラムの部分をある程度 検出する事はできますが、場合によっては実行が行われない部分でも警告が表示される事があるかもしれません。

参考程度の情報ということですね。

2007年9月21日

Risse進捗(23)

型推測と型伝播、定数伝播、定数畳込みを仮実装しました。なんか書いたコードが一発で動いて少し拍子抜け。

前のエントリで書いたこのコードは

{
    var a = 0;
    var b = false;
    if(b) a = 1;
    return a;
}

こう解釈されるようになりました

*entry_2 // alive
[1] _tmp@4 = AssignThisProxy() // _tmp@4 = constant type object
[2] _tmp@10 = AssignConstant() Value=void // _tmp@10 = constant void
[3] _tmp@17 = AssignConstant() Value=0 // _tmp@17 = constant 0, coalescable to id 0x01EA9608
[4] _tmp@26 = AssignConstant() Value=false // _tmp@26 = constant false
[5] if _tmp@26 then *if_true_37 else *if_pseudo_false_48
// LiveOut: _tmp@17

*if_true_37 // dead, pred: *entry_2
[6] _tmp@39 = AssignConstant() Value=1 // coalescable to id 0x01EA9608
_tmp@77 = _tmp@39 // coalescable to id 0x01EA9568
[7] goto *if_exit_53
// LiveOut: _tmp@77, _tmp@39

*if_pseudo_false_48 // alive, pred: *entry_2
// LiveIn: _tmp@17
[8] _tmp@50 = AssignConstant() Value=void // _tmp@50 = constant void, coalescable to id 0x01EA9568
[9] goto *if_exit_53
// LiveOut: _tmp@50, _tmp@17

*if_exit_53 // alive, pred: *if_true_37, *if_pseudo_false_48
// LiveIn: _tmp@77, _tmp@50, _tmp@39, _tmp@17
[10] a#15@60 = PHI(_tmp@39, _tmp@17) // a#15@60 = constant 0, coalescable to id 0x01EA9608
[10] _tmp@55 = PHI(_tmp@77, _tmp@50) // _tmp@55 = constant void, coalescable to id 0x01EA9568
[11] a#15@60.Return()

ちょっと見にくいですが、最後の [11] a#15@60.Return() で使われている変数 a#15@60 について、[10] で a#15@60 = constant 0 と書かれているように、必ず結果が 0 になることをコンパイラが理解できるようになっています。

絶対に通らないブロック *if_true_37 も dead としてマークされています。

また、

{
    var sum = 0;
    for(var i = 0; i < 10; i++) sum += i;
    return sum;
}

のようなコードでは

*entry_2 // alive
[1] _tmp@4 = AssignThisProxy() // _tmp@4 = constant type object
[2] _tmp@10 = AssignConstant() Value=void // _tmp@10 = constant void
[3] _tmp@17 = AssignConstant() Value=0 // _tmp@17 = constant 0, coalescable to id 0x01EA9360
[4] _tmp@26 = AssignConstant() Value=0 // _tmp@26 = constant 0, coalescable to id 0x01EA92C0
[5] goto *for_cond_35
// LiveOut: _tmp@26, _tmp@17

*for_cond_35 // alive, pred: *entry_2, *for_iter_67
// LiveIn: _tmp@75, _tmp@58, _tmp@26, _tmp@17
[6] sum@116 = PHI(_tmp@17, _tmp@58) // coalescable to id 0x01EA9360
[6] i@118 = PHI(_tmp@26, _tmp@75) // coalescable to id 0x01EA92C0
i#24@39 = i@118 // i#24@39 = constant type integer
sum#15@89 = sum@116 // sum#15@89 = constant type integer
[7] _tmp@43 = AssignConstant() Value=10 // _tmp@43 = constant 10
[8] _tmp@45 = i#24@39.Lesser(_tmp@43) // _tmp@45 = varying
[9] if _tmp@45 then *for_body_48 else *for_exit_81
// LiveOut: sum#15@89, i#24@39

*for_body_48 // alive, pred: *for_cond_35
// LiveIn: sum#15@89, i#24@39
[10] _tmp@58 = sum#15@89.Add(i#24@39) // _tmp@58 = constant type integer, coalescable to id 0x01EA9360
[11] goto *for_iter_67
// LiveOut: _tmp@58, i#24@39

*for_exit_81 // alive, pred: *for_cond_35
// LiveIn: sum#15@89
[12] _tmp@83 = AssignConstant() Value=void // _tmp@83 = constant void
[13] goto *for_break_target_93
// LiveOut: sum#15@89

*for_iter_67 // alive, pred: *for_body_48
// LiveIn: _tmp@58, i#24@39
[14] _tmp@69 = AssignConstant() Value=1 // _tmp@69 = constant 1
[15] _tmp@75 = i#24@39.Add(_tmp@69) // _tmp@75 = constant type integer, coalescable to id 0x01EA92C0
[16] goto *for_cond_35
// LiveOut: _tmp@75, _tmp@58

*for_break_target_93 // alive, pred: *for_exit_81
// LiveIn: sum#15@89
[17] sum#15@89.Return()

となっていて、これまた見にくいですが、変数 i に相当する変数

[4] _tmp@26 = AssignConstant() Value=0 // _tmp@26 = constant 0, coalescable to id 0x01EA92C0
i#24@39 = i@118 // i#24@39 = constant type integer
[15] _tmp@75 = i#24@39.Add(_tmp@69) // _tmp@75 = constant type integer, coalescable to id 0x01EA92C0

がすべて整数だとなっていて、

最後に [17] で return されている sum の型も

sum#15@89 = sum@116 // sum#15@89 = constant type integer

ということで必ず整数になることになっています。

型の推測ができると変数の型チェックをすっ飛ばすことができるのでそれなりにパフォーマンスがあがるかも、、、と思っていましたがもしかしたらあんまり効果無いかもしれません。。。

外部から来た変数や関数の戻り値、子関数と共有している変数などは型が分からないので、それを起源に持つ変数はすべて「型不明」として扱われてしまいます。明示的に途中でキャストして、あるいは変数に型を指定すれば(? そういう構文を用意するかどうかは決めてませんが)すこしは足しになるかもしれません。

とりあえず、どういう型と型を演算させたらどういう型になるというルールをコンパイラにたたき込まなければなりません。結構面倒ですがしばらくはその作業になりそう。

2007年9月19日

Risse進捗(22)

Risaは一休みして、ここしばらくは Risse のコード最適化周りを弄っていました。

ちなみに9月の初め頃のRisseが吐き出した、

{
    var a = 0;
    var b = false;
    if(b) a = 1;
    return a;
}

このスクリプトに対するVMコードは以下の通りでした。

#(1) {
00000 proxy %0
00002 copy %1, %0
00005 const %0, *0 // *0=void
00008 copy %1, %0
#(2)     var a = 0;
00011 const %0, *1 // *1=0
00014 copy %1, %0
00017 copy %2, %0
#(3)     var b = false;
00020 const %0, *2 // *2=false
00023 copy %2, %0
00026 copy %3, %0
#(4)     if(b) a = 1;
00029 copy %0, %2
00032 copy %2, %1
00035 branch %0, 00039, 00056
00039 const %0, *3 // *3=1
00042 copy %1, %0
00045 copy %3, %0
00048 copy %3, %1
00051 copy %1, %0
00054 jump 00067
00056 const %0, *0 // *0=void
00059 copy %3, %2
00062 copy %1, %0
00065 jump 00067
00067 copy %0, %1
#(5)     return a;
00070 copy %1, %3
00073 return %1

どえらい copy 文の量。ASTからSSA形式に変換する部分が馬鹿で大量のコピー文を挿入するうえに、最適化という最適化をしていなかったからです。

今では、これをゴリゴリと削ることができて、以下のようになっています。

#(1) {
00000 proxy %0
00002 const %0, *0 // *0=void
#(2)     var a = 0;
00005 const %0, *1 // *1=0
#(3)     var b = false;
00008 const %1, *2 // *2=false
#(4)     if(b) a = 1;
00011 branch %1, 00015, 00023
00015 const %0, *3 // *3=1
00018 copy %1, %0
00021 jump 00028
00023 const %1, *0 // *0=void
00026 jump 00028
#(5)     return a;
00028 return %0

コピー文がほとんど消えているのはφ関数に関する変数を合併するようになったのと、コピー伝播を行うようになったからです。ここらへんの実装が一番辛かった。

まだ上記のコードを見ると、使われないレジスタに値を代入していたり、次の命令にジャンプするだけの無駄なジャンプ命令があったり、定数条件のくせに無駄な条件分岐をやってしまっていますが、そこら辺の死亡コード除去だの定数伝播だの基本ブロックの連結だのはこれから実装します。

最終的にはこのスクリプトは 0 しか返さないことが明らかなので、

const %0, *0 // *0=0
return %0 // always constant 0, type=vtInteger

のような2文にまで縮まってほしいなーと思いつつ、いろいろ実装中です。

また、文脈からどのレジスタがどの型をとりうるかが分かるはずなので、レジスタに対する型チェックをすっ飛ばすようように指示するようなコードも将来的には吐ける可能性があります。型の推測が行えるというのは、タイプルーズなスクリプト言語としてはパフォーマンス的にはそこそこよい結果をもたらすのではないかと考えています。

2007年9月1日

吉里吉里2 2.29-dev.20070901 公開

吉里吉里2 2.29-dev.20070908でもお願いします

吉里吉里2 2.29-dev.20070901 を公開しました。今回から、ダブルバッファリングの方式として、従来の GDI と DirectDraw にくわえ、Direct3D でのダブルバッファリングが可能になっています。

Direct3Dを使うというと3Dバリバリな印象かもしれませんが、そんなことはなく、今回は単に画像の拡大のためだけに使っています。昨今の3Dゲーム用にチューニングされたチップでは3Dの描画機能を流用した方が遙かに高速に動作できる場合が多いためです。

ダブルバッファリングはフルスクリーン時のアスペクト比保持拡大機能など、吉里吉里本体が画像を拡大して表示する必要がある場合にも使用されます。

Direct3DはDirectDrawに比べて環境依存の不具合が多いと考えられます。というか環境依存の不具合の固まりです。吉里吉里本体にもいろいろな問題に対する回避コードが多数入っています。お時間のある方は実際にダウンロードしてテストしてくださると幸いです。

特に以下の点がポイントになるかと思います。もしテストしてくださる際は、画面解像度によってはフルスクリーンにしてもダブルバッファリングが有効にならない場合があるので、強制的にダブルバッファリングを行うようにする -usedb オプションをつけてみてください (このオプションをつけるとフルスクリーンであってもなくてもダブルバッファリングが使われるようになります)。

「吉里吉里設定」や「エンジン設定」では、2.29-dev.20070901 の場合、オプション情報が間違っていて正常に usedb オプションの設定をすることができないので、コマンドラインから -usedb というオプションを指定するか、あるいは *.cfu ファイルに usedb="\x79\x65\x73" という行を追加してください

2.29-dev.20070908 では治ってます

  • 画面は正しく描画されているか
  • ムービー再生中・後にフリーズしないか
  • 描画が著しく(描画しているのが目に見えるほど)遅くなっていないか
  • システムが不安定にならないか

もしなにかありましたら、ご報告の際は以下の点もご報告ください。

  • グラフィックカード (グラフィックチップ) の型番など (ログに (info) NVIDIA GeForce 7600 GT [nv4_disp.dll] のようにして表示されています)
  • グラフィックカードの搭載メモリ容量
  • オペレーティングシステム
Direct3DとDirectDrawのどちらかが使われているか

吉里吉里はダブルバッファリングを行う際、ベンチーマークを行って、もっともパフォーマンスがよい方式を使用するようになっています。これは、ログに

20:31:06 ! Passthrough: benchmark result: DirectDraw : 358.21 fps
20:31:07 ! Passthrough: benchmark result: GDI : 25.71 fps
20:31:07 ! Passthrough: benchmark result: Direct3D : 711.71 fps
20:31:07 ! Passthrough: Using passthrough draw device: Direct3D double buffering

のように記録されています (うちの開発マシンの例ですが、Direct3D が選択されています)。GDIでのベンチマーク結果も表示されますが、GDIが自動的に選択される事はありません (GDIがベンチマーク時には異常に高い結果を示すのに、実際に表示させてみてるときわめて低速である環境が報告されているためです)。

Direct3Dのバイリニアフィルタは可能か

Direct3Dのテクスチャ描画時にバイリニアフィルタ(補間)が利用できない環境では

Passthrough: Drawer object Direct3D does not have smooth zooming functionality

というログが、ベンチマーク結果付近に残っている場合があります。

DirectDrawでの補間拡大は可能か

DirectDrawでの補間拡大が可能な環境では

Passthrough: IDirectDraw::Blt seems to filter by some kind of interpolation method.

という記述がログに見られます。DirectDrawでの補間拡大ができない環境では

IDirectDraw::Blt seems to filter by nearest neighbor method.

という記述になります。

テクスチャの形式

環境によっては

Passthrough: Using non 32bit ARGB texture format

という記述がログに残ってる場合があります。吉里吉里は、吉里吉里内部の画像形式と同じ形式のテクスチャを確保できる場合は直接テクスチャに対して画像を転送するようになりますが、異なる形式のテクスチャしか選択できなかった場合はこのような記録がのこり、別の方式を使うようになっています。

よく分からない方はログを全部コピー&ペーストしてください。

もし実際にこれで配布した後に問題が起こってしまった場合は、-dbstyleオプション (ダブルバッファリング方式)でダブルバッファリングの方式を強制的に別の方式にすることや、-fsres オプション(フルスクリーン時の画面解像度)を「最も近い解像度」にしてエンジン側での拡大処理を行わないようにすることでダブルバッファリングを使用しないようにすることでも回避できると思います。

よろしくお願い致します。

  • 2007-09-01 23:05 イルク : 一点質問なのですが、krkrconf.exeでダブルバッファリングとダブルバッファリング方式のオプションがいずれも- dbstyle となっておりました。-usedbオプションが見当たらないのですがいかがでしょうか?また、ダブルバッファリングを使用するを選び設定してもkrkr.cfには特に設定が反映されていないようなのですが?私の環境だけでしょうか?
  • 2007-09-01 23:13 W.Dee : そいつはこちらのミスです、直してきます、どうもです
  • 2007-09-01 23:40 W.Dee : usedb オプションを指定する場合はコマンドラインから -usedb という指定するか、あるいは *.cfu ファイルに usedb="\x79\x65\x73" という行を書き加えてください。
  • 2007-09-02 00:11 イルク : 了解いたしました。素早い対応ありがとうございました。