言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ocaml の swflib を使って ActionScript Virtual Machine 2 コードを生成する。

また Flash VM の話題です。Flash コンテンツを生成する事で知られる haXe という言語のソースは ocaml で書かれていて、swflib というライブラリが付属してます。これがわりと汎用的に使えるみたいで、AVM2 にも対応しています。Tamarin 付属のアセンブラがヘボいのに気づいて他のやり方を探していたら、これが一番まともそうだったので挑戦してみました。アセンブラがやってくれるシンボル操作等は手でやらないといけないので大変ですが、ハンドアセンブルよりはよっぽどましです。

ocaml の使い方と swflib の準備

今回わたくし ocaml 初体験です。ocamlコンパイルの仕方は C と大体同じで、

ocamlc -g -I (ヘッダファイルのあるディレクトリ) (ソースコードやオブジェクトファイル) -o (出力ファイル)

です。他にも ocamlopt というコマンドがありますが、これだとデバッグしたりバックトレース出したりできなかったのでバイトコードコンパイラの ocamlc を使う事にしました。swflib を使うには、haXe 自体をコンパイルして欲しい部分だけコピーする事にします。

ここに出来る ocaml ディレクトリを使います。あとデバッグしたいときは install.ml と ocaml/extlib-dev/install.ml の ocamlc にことごとく -g を付けてコンパイルし直します。

プログラミング開始

今回 outabc.ml というファイルを作って次のようにしてコンパイルしました。

ocamlc -g -I ocaml -I ocaml/swflib ocaml/extLib.cma ocaml/swflib/swflib.cma -o outabc outabc.ml

ocaml/ にある .mli がヘッダファイル .cma がライブラリアーカイブ です。

Tamarin で動かす Hello World

ActionScript でかくと、一行

hello("Hello, World!!")

となるプログラムを ocaml と swflib を使ってバイトコードを生成します。

open As3

let filename = "outabc.abc"

let id i = As3parse.magic_index i
let idz i = As3parse.magic_index_nz i

swflib の中の as3 をオープンします。こうすると as3.mli にある定義を修飾なしで使えます。ファイル名を変数に入れて、便利関数 id と idz を定義します。これは、定数シンボルへのインデックスに使います。as3 モジュールでは、カプセル化のため(多分)インデックスに int を使わないで特別な 'a index という型を使っています。As3parse.magic_index は int からこの特別な型への変換を行います。

let idents:as3_ident array = [|"print"; "Hello, World!!"; ""|]
let namespaces:as3_namespace array = [| A3NPublic None |] (* public *)
let nsets:as3_ns_set array = [||]
let names:as3_multi_name array = [|A3MName (id 1, id 1)|] (* public::print *)

次に定数の定義です。今回 Hello World だけなので文字列定数二つだけです。なぜか "" をどこかに入れないとエラーになります。namespace と ns_set は非常にややこしいので、今回 public という一つのシステムネームスペースだけ使う事にします。namespace と ローカル名の組み合わせを multiname と言います。今回使うのは print 関数だけなので、public::print だけを定義します。

これらの定数にアクセスするには先ほどの id を使います。インデックスは 1 始まりで、0 には特別な意味があるらしいです。

let method_type:as3_method_type = {
  mt3_ret = None;
  mt3_args = [];
  mt3_native = false;
  mt3_var_args = false;
  mt3_arguments_defined = false;
  mt3_uses_dxns = false;
  mt3_new_block = false;
  mt3_unused_flag = false;
  mt3_debug_name = None;
  mt3_dparams = None;
  mt3_pnames = None;
}

これはメソッドシグネチャです。トップレベルに一行だけでも内部的には関数になります。ここでは引き数、返り値、名前のどれも無い関数を定義しています。

let initialize:as3_static = {
  st3_method = idz 0;
  st3_fields = [||];
}

エントリポイントの指定です。最初の関数 idz 0 を指定しています。定数テーブルでない所はゼロからのインデックスです。as3 モジュールの中では、'a index が 1 始まり、'a index_nz が 0 始まりのインデックス型を現してるようです。

let code:as3_function = {
  fun3_id = idz 0;
  fun3_stack_size = 2;
  fun3_nregs = 1;
  fun3_init_scope = 0;
  fun3_max_scope = 1;
  fun3_code = [| A3This; (* A3Reg 0 *)
                 A3Scope; 
                 A3FindPropStrict (id 1); (* public::print *)
                 A3String (id 2);  (* "Hello, World!!" *)
                 A3CallProperty (id 1, 1); (* public::print 1 *)
                 A3RetVoid|];
  fun3_trys = [||];
  fun3_locals = [||];
}

いよいよコード本体です。スタックサイズ等は適当に決めます。swflib as3 の命令の名前は Adobe の仕様と微妙に違っているので注意です。例えば local は A3Reg で、しかも local0 には A3This という特別なシンボルを使います。

let script : as3_tag = {
  as3_ints = [||];
  as3_uints = [||];
  as3_floats = [||];
  as3_idents = idents;
  as3_namespaces = namespaces;
  as3_nsets = nsets;
  as3_names = names;
  as3_method_types = [|method_type|];
  as3_metadatas = [||];
  as3_classes = [||];
  as3_statics = [||];
  as3_inits = [|initialize|];
  as3_functions = [|code|];
  as3_unknown = ""
}

これらを一つのレコードにまとめて完成です。

let write_abc (outfile:string) (ctx:as3_tag) =
  let ch = IO.output_channel (open_out_bin outfile) in
  As3parse.write ch ctx;
  IO.close_out ch

;;
write_abc filename script;;

最後にファイルに書き込む部分を作ります。

実行

では実行しましょう。

$ ocamlc -g -I ocaml -I ocaml/swflib ocaml/extLib.cma ocaml/swflib/swflib.cma -o outabc outabc.ml
$ ./outabc 
$ avmshell outabc.abc
Hello, World!!

じ〜ん。

ソース全体を http://gist.github.com/161105 に貼っておきます。