Adobe AlchemyはFlashの隠し命令を使っているみたい

今日から、Adobe MAXですが、明日、川崎さん、id:amachangid:gyuqueさん、id:moriyoshiさんとともに、C-6「Flashはもういらない」でしゃべってきます。お時間がございましたら、ぜひ、お越しください。僕は「Flash Playerの作り方」というタイトルで話します。皆様のお話、相当面白そうですよ!

昨日、MAX前夜祭がありました。Adobe の方の、プレゼンテーションを聞いていて、Flash 上でC言語のプログラムを走らせる、Adobe Alchemy の話を聞いていて、名前は出た当初から知っていたのですが、一度も実行したことがなかったので、実行させてみました。

ちなみに、Alchemy 0.5 は cygwin では、バグっていてコンパイルができなく、http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?forumid=72&catid=755&threadid=1406576&enterthread=y にしたがって、Perl コードを書き換えないと、コンパイルできません。やられる方、お気をつけください。

そして、不思議なことに、Alchemy の生成した SWF は同じ Flash Player で動くのに、実行速度が ActionScript 3 で書くよりも速いです。ベクトルの内積ベンチマークで 1.6倍速かったです。理由がわからなかったので、Alchemy が生成する ABC (ActionScript Byte Code) を見てみました。

ABCをみるには、Tamarin の abcdump がいいのですが、これは、Flash 9 までしか対応していないので、Flash 10 に対応するべく、改造しました。abcdump のコンパイル方法は、http://www.5etdemi.com/blog/archives/2007/01/as3-decompiler/Tamarin の abcdump.as から abcdump.exe を作成する方法 2009年1月版 - てっく煮ブログ 跡地 で説明されているのですが、これらは、情報不足で、このとおりやってもうまくいかなく、Tamarin Build Documentation | MDN に従ってコンパイルする必要があります。(この説明も説明不足です。すいません。)

その上で作ったのが、http://yukoba.jp/blog/2009/abcdump_flash10.zip です。

Flash 10 対応の abcdump を実行してみて驚いたんですが、Flash 10 になってから、AVM2 の命令が増えています。Adobe から増えたという発表を僕は見たことがないですし、AVM2 Overview には記載されていません。隠し命令状態になっています。

先ほどのベクトルの内積を Alchemy で SWF 化したあと、abcdump で逆コンパイルすると、

for (i = 0 ; i < 100 * 1024; i++) {
  sum += ary1[i] * ary2[i];
}

の部分は、

L20: 
1018  label         	

L19: 
1019  getlocal0     	
1020  getlocal0     	
1021  getproperty   	i3
1024  getlocal0     	
1025  getproperty   	i6
1028  add           	
1029  initproperty  	i7
1032  getlocal0     	
1033  getlocal0     	
1034  getproperty   	i4
1037  getlocal0     	
1038  getproperty   	i6
1041  add           	
1042  initproperty  	i8
1045  getlocal0     	
1046  getlocal0     	
1047  getproperty   	i8
1050  OP_0x39       	
1051  initproperty  	f1
1054  getlocal0     	
1055  getlocal0     	
1056  getproperty   	i7
1059  OP_0x39       	
1060  initproperty  	f2
1063  getlocal0     	
1064  getlocal0     	
1065  getproperty   	f1
1068  getlocal0     	
1069  getproperty   	f2
1072  multiply      	
1073  initproperty  	f1
1076  getlocal0     	
1077  getlocal0     	
1078  getproperty   	i6
1081  pushbyte      	8
1083  add           	
1084  initproperty  	i6
1087  getlocal0     	
1088  getlocal0     	
1089  getproperty   	i5
1092  pushbyte      	1
1094  add           	
1095  initproperty  	i5
1098  getlocal0     	
1099  getlocal0     	
1100  getproperty   	f1
1103  getlocal0     	
1104  getproperty   	f0
1107  add           	
1108  initproperty  	f0
1111  getlocal0     	
1112  getproperty   	i5
1115  pushint       	102400	// 0x19000
1117  equals        	
1118  iftrue        	L21

1122  jump          	L20

L21: 

になります。読みにくいので、ソースに近い擬似コードの形に変換すると、

L20:
i7 = i3 + i6
i8 = i4 + i6
f1 = OP_0x39 と i8 // *((double*)i8)
f2 = OP_0x39 と i7 // *((double*)i7)
f1 = f1 * f2
i6 = i6 + 8
i5 = i5 + 1
f0 = f0 + f1
if (i5 == 102400) goto L21
goto L20
L21:

ループ変数の i が i5 になっていて、i3, i4 が 配列のポインタです。f0 が sum です。i6 = i * 8 です。

問題は、OP_0x39 の部分です。これが、アンドキュメントです。i8 で指定された番地のメモリを、double に型キャストして 読み出しているのだと思います。OP_0x33 〜 OP_0x3F はアンドキュメントで、このあたりは、指定された番地の直接読み書きを行っているのではないかと推測しています。Number を経由することなく、double を扱っていますし、そして、さらにメモリを直接読み書きする命令があるゆえ、Alchemy は高速化できているのだと思います。

もうひとつ面白いのは、AVM2 にはローカル変数があるのですが、上記の擬似コードでの変数は全て、メンバ変数です。スタックからの読み書きも、メンバ変数の読み書きも処理速度は変わらないという理由なのでしょうか?

そして、ここまでプリミティブに近い形で操作できているにもかかわらず、Visual C++Java に比べて4倍近く遅いです。なぜ遅いのか、これまた疑問です。最適化の過程で、適切にレジスタに割り振ることが必要なんですが、メンバ変数の読み書きをレジスタに割り振っているのでしょうか?よくわかりません。

以上、今現在でわかったのは、ここまでです。

(追記)

id:nitoyonさんの、

を発見。

(追記2)

Flash 10から13個命令が追加になったようです。詳細はコメント欄をご覧ください。
http://llvm.org/devmtg/2008-08/Petersen_FlashCCompiler.pdf

(追記3)

でも、実は、Adobe Alchemyはそれほど速くない可能性も。続きは、Flashで数値計算を高速化する方法 - yukobaのブログ