コンパイル処理の流れ
- iseq = th_compile_from_node(thread, node, file) @ yarvcore.c
解析済みの構文木(node)を受け取って、YARVのマシン語列(iseq)に変換する「コンパイル」処理の関数です。
トップレベルのスクリプトをコンパイルする場合と、evalで文字列をコンパイルする場合とで少し引数を変えていますが、基本的には、yarv_iseq_new 関数にそのまま処理を丸投げするだけです。実際evalじゃない場合の実行パスだけ見てみると、この通りです。
VALUE iseq; iseq = yarv_iseq_new(node, rb_str_new2("<main>"), file, Qfalse, ISEQ_TYPE_TOP); return iseq;
- yarv_iseq_new(node, name, file, parent, type) @ iseq.c
デフォルトのオプションを指定して、あとはそのままyarv_iseq_new_with_optに丸投げするだけです。オプションでは色々な最適化をON/OFFできます。どんなオプションがあるかについて詳しくは、最適化のコードを読むときに調べます。
return yarv_iseq_new_with_opt(node, name, file_name,
parent, type, Qfalse,
&COMPILE_OPTION_DEFAULT);
- yarv_iseq_new_with_opt @ iseq.c
複数の作業を順番にこなしていきます。
VALUE yarv_iseq_new_with_opt(NODE*node, VALUE name, VALUE file_name, VALUE parent, VALUE type, VALUE block_opt, const yarv_compile_option_t *option) { yarv_iseq_t *iseq; VALUE self = iseq_alloc(cYarvISeq); GetISeqPtr(self, iseq); iseq->self = self; prepare_iseq_build(iseq, name, file_name, parent, type, block_opt, option); iseq_compile(self, node); cleanup_iseq_build(iseq); return self; }
どんどん潜っちゃいましょう。iseq_compileへ。
- iseq_compile(self, node) @ compile.c
長いのでソースは適当に抜粋です。
VALUE iseq_compile(VALUE self, NODE *narg) { DECL_ANCHOR(list_anchor);
引数selfは、さっき作ったオブジェクトです。構文木nargをコンパイルして、selfに格納するのがこの関数の仕事です。ここで、selfにいきなりマシン語列を突っ込むことはせずに、一度は命令列をリストで管理するようです。DECL_ANCHORマクロで、リストのデータ構造を宣言しています(後述)。
debugs("[compile step 1 (traverse each node)]\n");
ここから、ステップごとにデバッグメッセージが埋め込まれててわかりやすいですね!ステップ1は、まず単純に構文木をたどってリストにYARVの命令列をくっつけていくステップです。ノードの種類によって複雑に分岐してますが、最終的に、このステップの処理はCOMPILEというマクロに行きつきます。
COMPILE(list_anchor, "top level node", node);
COMPILEマクロの定義は…
#define COMPILE(anchor, desc, node) \ (debug_compile("== " desc "\n", \ iseq_compile_each(iseq, anchor, node, 0)))
デバッグメッセージを記録しつつ、iseq_compile_eachという関数を呼び出しています。iseq_compile_eachは、構文木をたどりながら2000行のswitch文でひたすら命令列を生成しています。具体的な「構文木→命令列」の変換規則についてはまたのちほど。今日はこのあとの流れを先に見てしまいましょう。
return iseq_setup(iseq, list_anchor);
}
このあとのステップは、iseq_setupという関数に続きます。
- iseq_setup(iseq, anchor) @
わかりやすいのでデバッグメッセージだけ抜き出してみました。
//debugs("[compile step 2] (iseq_array_to_linkedlist)\n"); debugs("[compile step 3.1 (iseq_optimize)]\n"); debugs("[compile step 3.2 (iseq_insns_unification)]\n"); debugs("[compile step 3.3 (set_sequence_stackcaching)]\n"); debugs("[compile step 4.1 (set_sequence)]\n"); debugs("[compile step 4.2 (set_exception_table)]\n"); debugs("[compile step 4.3 (set_optargs_table)] \n"); debugs("[compile step 5 (iseq_translate_direct_threaded_code)] \n"); debugs("[compile step: finish]\n");
幾つかのステップは、オプションによってif文で実行されたりされなかったりします。ステップ2はコメントアウトされてました。iseq_array_to_linkedlistという名前から察するに、昔はリストではなく配列でステップ1の結果を作っていて、ここではじめてリストに変換していたのでしょうか?