はじめに

ここではYARVコードへのコンパイルで生成したYARVコードを実行する処理を読解したいと思います。

rb_iseq_eval(vm.c)

YARVコード実行のエントリーポイントとなるのはrb_iseq_eval関数ですがこの関数はrb_vm_set_stack_top関数を呼んだ上でvm_eval_body関数を呼んでいるだけです。

vm_set_stack_top(vm.c)

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_body(vm.c)

この関数はvm_eval関数を実行します。例外やbreakなどが起こるとこの関数に戻ってきて適切な再開アドレスを計算し、再びvm_eval関数を呼び出しています。

vm_eval(vm_evalbody.c)

この関数がYARV命令実行の肝です。いい感じに難解なコードになっています。YARV命令はコンパイルオプションにより以下のいずれかの形式で実行されます。

OPT_CALL_THREADED_CODE
関数呼び出し
OPT_DIRECT_THREADED_CODE
goto
指定なし
switch文

詳しいからくりはvm.hを眺めてください。

vm.inc

各命令の処理ルーチンはどこに書かれているかですがvm.incに書かれています。

insns.def

各命令の動きが知りたい場合はinsns.defを見ると書いてあります。見るとわかりますがinsns.defに書かれているのはCではありません。tool/insns2vm.rbを実行することで各種incファイルが生成されるようです。各命令は以下のフォーマットになっているようです。

DEFINE_INSN
命令名
(引数...)
(スタックから拾う値)
(スタックに積む値)
{
    Cのソース
}

引数の取得やスタックからのポップ、スタックへのプッシュは定義から自動生成されるので命令を書く際に気にする必要はありません。ただし、任意個の要素をスタックからポップ、スタックへプッシュする際は自分で書く必要があります。

命令が実行される環境

YARVで使われるレジスタ

YARVでは以下の6つのレジスタが使われています。

cfp
現在の実行環境を表すrb_control_frame_t構造体(vm_core.h)へのポインタです。以下の5つのレジスタはrb_control_frame_tのメンバーです
dfp
ローカル変数領域へのVALUEポインタです
lfp
ローカル変数領域へのVALUEポインタです。dfpとの違いはブロック呼び出しでは変わらない点です。詳しいことはブロック呼び出しを見るときに説明します
sp
現在のスタックトップを示すVALUEポインタです
bp
現在のフレームが積まれた時のspの初期位置を示すVALUEポインタです
pc
現在実行している命令を示すVALUEポインタです

命令サポートマクロ

命令を記述する際に利用するマクロはinsnhelper.hに書かれています。スタック操作、ローカル変数操作、レジスタ操作などのマクロが定義されています。

命令サポート関数

命令を記述するする際に利用する関数はvm_insnhelper.cに書かれています。staticですがvm.cがincludeしているので命令内では問題なく使用することができます(vm_eval関数が書かれているvm_evalbody.cもvm.cがincludeしています)。

実行してみる

それではYARVコードへのコンパイルでコンパイルしたコードを実行してみます。

defineclass

この命令は名が示すようにクラスを定義します。まあそれ自体は珍しくないので珍しい部分を取り上げます。この命令ではスタックからベースクラス(スーパークラスではありません。Outer::Innerとなってる場合のOuterのことです)とスーパークラスを取得します。このうちベースクラスは以下の場合nilです。

vm_get_cbase(vm.c)

ベースクラスがnilの場合、vm_get_cbase関数が呼ばれてベースクラスを取得しています。 まず、フレームをサーチ(vm_get_ruby_level_cfp関数(eval_intern.h))してRubyで書かれたメソッドを実行しているフレームを取得しています。命令の処理ルーチンから呼ばれるので現在のフレームになると思います。 次にフレームのrb_iseq_t構造体からcref_stackという情報を取り出しています(get_cref関数(vm_insnhelper.c))。 その後、cref_stackからベースクラスを取得しています。

というのが処理の流れなのですがcref_stackっていつ設定されたのかというとYARVコードへのコンパイルの際(set_relation関数(iseq.c))に設定されています。

    if (type == ISEQ_TYPE_TOP) {
	/* toplevel is private */
	iseq->cref_stack = NEW_BLOCK(th->top_wrapper ? th->top_wrapper : rb_cObject);
	iseq->cref_stack->nd_file = 0;
	iseq->cref_stack->nd_visi = NOEX_PRIVATE;
    }
    else if (type == ISEQ_TYPE_METHOD || type == ISEQ_TYPE_CLASS) {
	iseq->cref_stack = NEW_BLOCK(0); /* place holder */
	iseq->cref_stack->nd_file = 0;
    }

というわけでトップレベルのベースクラスは普通Objectです(ベースクラスがObjectの場合はObject::CではなくCという名前になります)。 一方、クラス定義をコンパイルするときは0です。それじゃあ困るんじゃないか?といった疑問を解決するために次に行きます。

vm_cref_push(vm.c)

クラスを定義するとクラスを表すVALUEが決定されます。先ほどcref_stackのnd_clssが0だったのはこのクラスを表すVALUEが決定できないためです。この関数を呼ぶことでcref_stackにクラスが設定され、Innerをdefineclassする際にベースクラスとしてOuterを取得することができます。

まだ実行してみてません

とりあえず気になる命令を挙げておきます。

send

getinlinecache

おわりに


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS