ひとり勉強会

ひとり楽しく勉強会

データ構造とその初期化

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);

この関数でまずゼロ初期化して、

    • ruby_init
      • rb_call_inits
  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 は、スタックの先頭からはじまります。ダミーの制御フレームなので、他の情報は特に使われないのでゼロクリア。