ここではYARVコードへのコンパイルで生成したYARVコードを実行する処理を読解したいと思います。
YARVコード実行のエントリーポイントとなるのはrb_iseq_eval関数ですがこの関数はrb_vm_set_stack_top関数を呼んだ上でvm_eval_body関数を呼んでいるだけです。
vm_set_stack_topではまずrb_vm_set_finish_env関数を呼んでフレームを生成しています。どうやらこのフレームはfinish命令を実行してYARVを終了するためのものなようです。
次にvm_push_frame関数を呼んで実行するYARVコードの情報をフレームに積んでいます。vm_push_frame関数は初期化のときもちらっと見ましたが再掲します。
vm_push_frame(th, iseq, FRAME_MAGIC_TOP, th->top_self, 0, iseq->iseq_encoded, th->cfp->sp, 0, iseq->local_size);
vm_push_frame(rb_thread_t *th, rb_iseq_t *iseq, VALUE type, VALUE self, VALUE specval, VALUE *pc, VALUE *sp, VALUE *lfp, int local_size) { ... /* nil initialize */ for (i=0; i < local_size; i++) { *sp = Qnil; sp++; } /* set special val */ *sp = GC_GUARDED_PTR(specval); dfp = sp; if (lfp == 0) { lfp = sp; } cfp = th->cfp = th->cfp - 1; cfp->pc = pc; cfp->sp = sp + 1; cfp->bp = sp + 1; cfp->iseq = iseq; cfp->flag = type; cfp->self = self; cfp->lfp = lfp; cfp->dfp = dfp; cfp->proc = 0;
例のmontecarlo.rbのiseqをフレームを積んだ後のスタックは以下のような感じになります。
|Qnil |GC_GUARDED_PTR(0) |Qnil |GC_GUARDED_PTR(GC_GUARDED_PTR(0)) |Qnil # for n |Qnil # for pi |Qnil # for svar dfp,lfp→|GC_GUARDED_PTR(0) sp,bp→| | ... | cfp→|今積んだフレーム情報 |rb_vm_set_finish_env関数で積んだフレーム情報 |th_init2関数で積んだフレーム情報
この関数はvm_eval関数を実行します。例外やbreakなどが起こるとこの関数に戻ってきて適切な再開アドレスを計算し、再びvm_eval関数を呼び出しています。
この関数がYARV命令実行の肝です。いい感じに難解なコードになっています。YARV命令はコンパイルオプションにより以下のいずれかの形式で実行されます。
詳しいからくりはvm.hを眺めてください。
各命令の処理ルーチンはどこに書かれているかですがvm.incに書かれています。
各命令の動きが知りたい場合はinsns.defを見ると書いてあります。見るとわかりますがinsns.defに書かれているのはCではありません。tool/insns2vm.rbを実行することで各種incファイルが生成されるようです。各命令は以下のフォーマットになっているようです。
DEFINE_INSN 命令名 (引数...) (スタックから拾う値) (スタックに積む値) { Cのソース }
それではYARVコードへのコンパイルでコンパイルしたコードを実行してみます。
とりあえず気になる命令を挙げておきます。