RHG 確認してみた

つうかパーサあたりは完全スルーしてたんですよね。yacc 面倒で。
で、node なオブジェクトはおそらく生成された構文木ではないか、という類推ベースですすめてみます。
codegen 手続きの先頭あたりを見るに

  switch (nt) {
  case NODE_BEGIN:
    if (val && !tree) {
      genop(s, MKOP_A(OP_LOADNIL, cursp()));
      push();
    }
    while (tree) {
      codegen(s, tree->car, tree->cdr ? NOVAL : val);
      tree = tree->cdr;
    }
    break;

構文木の car (上記の nt) が NODE_BEGIN であった場合は、構文木の cdr の car を渡して codegen してますね。で、tree に構文木の cdr の cdr を設定して繰り返しているのか。
あるいは以下も気になる。

  • 引数の val って何だ
  • genop は OP を生成、という理解で良いのかな (要掘削)
  • push とか pop も確認

確認してみます

まず val 云々からですが、NOVAL は 0 らしい。定義は codegen.c で以下。

#define NOVAL  0
#define VAL    1

む、なんか val の値見て pop するかとかそんな条件分岐もありますね。先に push とか pop とか見てみるか、と言いつつ M-t してみるとまたまた codegen.c で定義されてます。

#define nregs_update do {if (s->sp > s->nregs) s->nregs = s->sp;} while (0)
static void
push_(codegen_scope *s)
{
  if (s->sp > 511) {
    codegen_error(s, "too complex expression");
  }
  s->sp++;
  nregs_update;
}

#define push() push_(s)
#define pop_(s) ((s)->sp--)
#define pop() pop_(s)
#define pop_n(n) (s->sp-=(n))
#define cursp() (s->sp)

ええと、s って何でしょ。あ、そか。こいつらマクロなんだ。つうことは codegen の中で使われていれば引数の codegen_scope なポインタの s になるのか。

static void
codegen(codegen_scope *s, node *tree, int val)
{

codegen_scope 型ですが、これも codegen.c で定義されてます。長いので引用は略。ちなみに push や pop で操作してる sp は以下な定義になってます。

  int sp;

push で nregs も大きくなっていきますが、pop では操作されないですね。まだ全然イメージできてません。以下を掘ってみるか。

  case NODE_BEGIN:
    if (val && !tree) {
      genop(s, MKOP_A(OP_LOADNIL, cursp()));
      push();
    }

おそらく val が VAL (1) で構文木はまだ終端に達していない場合、になるはず。genop 手続きの定義はそんなに長くないので引用してみます。

static inline void
genop(codegen_scope *s, mrb_code i)
{
  if (s->pc == s->icapa) {
    s->icapa *= 2;
    s->iseq = (mrb_code *)codegen_realloc(s, s->iseq, sizeof(mrb_code)*s->icapa);
    if (s->lines) {
      s->lines = (short*)codegen_realloc(s, s->lines, sizeof(short)*s->icapa);
    }
  }
  s->iseq[s->pc] = i;
  if (s->lines) {
    s->lines[s->pc] = s->lineno;
  }
  s->pc++;
}

を、codegen_scope 使ってますね。細かい部分スルーして以下?

  • s->iseq[s->pc] にオペコードが格納?
  • s->pc 増分

あ、s->icapa は i の capa なのだな。定義を見てみると以下。

  mrb_code *iseq;
  short *lines;
  int icapa;

pc は int でした。あるいは mrb_code は以下。

typedef int32_t mrb_code;

ええと、genop に渡してる mrb_code を生成してるソレも掘削してみます。OP_LOADNIL が以下ですね (opcode.h で定義)。

OP_LOADNIL,/*   A       R(A) := nil                                     */

これは正にオペコードになります。これ、A な命令 (?) ってことで MKOP_A マクロが使われているのかな。こちらも定義は opcode.h で以下な定義。

#define MKOP_A(op,a)        (MKOPCODE(op)|MKARG_A(a))

あるいは cursp って何、って思ったらこれもマクロでした。s->sp で置換。実は上でスデに引用されてたりして。これを踏まえて MKOPCODE とか MKARG_A とか見てみましょうね。まず MKOPCODE は同様に opcode.h で定義されてて以下。

#define MKOPCODE(op)  ((op) & 0x7f)

そのまんまですね。オペコード生成してます。MKARG_A は直下で定義されてて以下。

#define MKARG_A(c)    ((mrb_code)((c) & 0x1ff) << 23)

むむ、0x1ff て確かに 511 が上限だな。なんとなく stack 式な vm 云々について再度確認してからこのあたり云々した方が良いのかどうなのか。
ちなみにこの OP_LOADNIL を vm 側ではどう処理しているのかというと以下。

    CASE(OP_LOADNIL) {
      /* A B    R(A) := nil */
      int a = GETARG_A(i);

      SET_NIL_VALUE(regs[a]);
      NEXT;
    }

regs て何だ。ちなみに i は取り出された命令が格納されてますね。定義は略で以下なカンジで初期化されてます。

  regs = mrb->stack;
  regs[0] = self;

あ、スタックだ。おそらく vm の仕様として stack の深さは固定で 512 ってキメを作ってるんだな。ちょっと stack 式 vm のナニを確認してみます。