データ構造とその初期化
VM
まず、YARV の仮想マシンを表すデータ構造があります。yarvcore.h で定義されている yarv_vm_t 構造体です。
typedef struct yarv_vm_struct { VALUE self; ...略... struct yarv_thread_struct *main_thread; struct yarv_thread_struct *running_thread; st_table *living_threads; ...略... } yarv_vm_t;
大ざっぱにまとめると、yarv_vm_t は、VM の上で走っているスレッドの集まりです。それ以外にも結構な数のメンバがあったのですが、ここでは思いっきり省略して引用してます (^-^;)。後でコードを読んでて登場したらその時に改めて読み直そうと思います。先頭の self メンバには、VMを表すRubyのオブジェクトが入ります。
VMのインスタンスは、グローバル変数 theYarvVM に格納されます。初期化は、
yarv_vm_t *vm = ALLOC(yarv_vm_t); vm_init2(vm); theYarvVM = vm;
-
-
-
-
- vm_init2
-
-
-
MEMZERO(vm, yarv_vm_t, 1);
この関数でまずゼロ初期化して、
yarv_vm_t *vm; vm = theYarvVM; thval = yarv_thread_alloc(cYarvThread); GetThreadPtr(thval, th); vm->main_thread = th; vm->running_thread = th;
Init_vmで、現在のスレッドをメインのスレッドとして設定しています。
スレッド
各スレッドは、yarv_thread_t という構造体です。
typedef struct yarv_thread_struct { VALUE self; yarv_vm_t *vm; /* execution information */ VALUE *stack; /* must free, must mark */ unsigned long stack_size; yarv_control_frame_t *cfp; ... 略 ... } yarv_thread_t;
... 略 ... の部分は豪快に60行くらい省略してしまいましたが、yarv_thread_t が管理するデータで一番肝心なのが、上に抜粋した部分だと思います。思いっきり大ざっぱにまとめると、スレッドは
- スタック (stack, stack_size)
- 現在の制御フレーム (cfp)
の2つからできています。スタックは単純にベタのメモリ領域です。制御フレームは、プログラム実行中の状態を表すデータ構造です。
制御フレーム
制御フレームは、プログラム実行中の状態を表すデータ構造です。
typedef struct { VALUE *pc; // cfp[0] VALUE *sp; // cfp[1] VALUE *bp; // cfp[2] yarv_iseq_t *iseq; // cfp[3] VALUE magic; // cfp[4] VALUE self; // cfp[5] // block[0] VALUE *lfp; // cfp[6] // block[1] VALUE *dfp; // cfp[7] // block[2] yarv_iseq_t *block_iseq; // cfp[8] // block[3] VALUE proc; // cfp[9] // block[4] ID callee_id; // cfp[10] ID method_id; // cfp[11] saved in special case VALUE method_klass; // cfp[12] saved in special case VALUE prof_time_self; // cfp[13] VALUE prof_time_chld; // cfp[14] VALUE dummy; // cfp[15] } yarv_control_frame_t;
- pc : 次に実行する命令のあるアドレス
- sp : スタックポインタ
- bp : ベースポインタ ([bp, sp) が現在のスコープが使うスタック領域)
- iseq : 現在実行中の命令列
- ...
- lfp : 現在のメソッドのローカル変数があるアドレス
などなど。
スレッドと制御フレームの初期化は、まとめて
で行われます。
static void th_init2(yarv_thread_t *th) { MEMZERO(th, yarv_thread_t, 1); /* allocate thread stack */ th->stack = ALLOC_N(VALUE, YARV_THREAD_STACK_SIZE); th->stack_size = YARV_THREAD_STACK_SIZE;
まずスタックのメモリ領域割り当て。
th->cfp = (void *)(th->stack + th->stack_size);
th->cfp--;
続いて、制御フレームを割り当て。といっても、スレッド初期化のときはまだ実行コードのコンパイル前なので、ここの制御フレームはほとんどダミーのデータです。制御フレームは、スタック領域の底から上に向かって割り当てられていくようです。(後述)
th->cfp->pc = 0; th->cfp->sp = th->stack; th->cfp->bp = 0; th->cfp->lfp = th->stack; th->cfp->dfp = th->stack; th->cfp->self = Qnil; th->cfp->magic = 0; th->cfp->iseq = 0; th->cfp->proc = 0; th->cfp->block_iseq = 0;
スタックポインタ sp は、スタックの先頭からはじまります。ダミーの制御フレームなので、他の情報は特に使われないのでゼロクリア。