確認してみます、な備忘録

以下な tweet を捕捉。

mruby のvmソースコードを精読することで,Ruby 言語の言語仕様を概ね理解した.
C言語に慣れている人は,このルートで学ぶほうが速いよたぶん.

https://twitter.com/monamour555/status/320051823404802048 より引用
成程。取得して確認してみます。以下からか。

$ git clone https://github.com/mruby/mruby

vm のソースは以下なのかどうか。

$ wc -l src/vm.c 
1988 src/vm.c

中を見てみると mrb_run という関数が定義されているのを発見。これかな。

mrb_value
mrb_run(mrb_state *mrb, struct RProc *proc, mrb_value self)
{

gtags したいのですがパケジ名が分からんな。自分メモも残っておらず途方に暮れかけた所で global だったことが判明。導入して gtags -v して emacs を再起動。
しかしこの mrb_run という手続き、Gauche のコード読みをしてた頃を思いだしてしまいますね。懐しや。メインループな記述がこんなカンジ。

  INIT_DISPATCH {
    CASE(OP_NOP) {
      /* do nothing */
      NEXT;
    }

    CASE(OP_MOVE) {
      /* A B    R(A) := R(B) */

以下、延々 CASE が続きます。ちょっとマクロを確認しておきましょうね。まず、INIT_DISPATCH から。vm.c で定義されているようで以下なカンジ。

#define INIT_DISPATCH for (;;) { i = *pc; CODE_FETCH_HOOK(mrb, irep, pc, regs);\
 switch (GET_OPCODE(i)) {

CODE_FETCH_HOOK も vm.c で定義されてて以下。

#ifdef ENABLE_DEBUG
#define CODE_FETCH_HOOK(mrb, irep, pc, regs) ((mrb)->code_fetch_hook ? (mrb)->code_fetch_hook((mrb), (irep), (pc), (regs)) : NULL)
#else
#define CODE_FETCH_HOOK(mrb, irep, pc, regs)
#endif

これはデバッグ用みたいスね。あるいは GET_OPCODE は opcode.h で定義されてて以下。

/* instructions: packed 32 bit      */
/* -------------------------------  */
/*     A:B:C:OP = 9: 9: 7: 7        */
/*      A:Bx:OP =    9:16: 7        */
/*        Ax:OP =      25: 7        */
/*   A:Bz:Cz:OP = 9:14: 2: 7        */

#define GET_OPCODE(i) ((int)(((mrb_code)(i)) & 0x7f))

末端 7 bit が OP って事ですね。CASE も見てみます。こちらも vm.c で定義されてて以下ですね。

#ifndef DIRECT_THREADED

#define INIT_DISPATCH for (;;) { i = *pc; CODE_FETCH_HOOK(mrb, irep, pc, regs); switch (GET_OPCODE(i)) {
#define CASE(op) case op:
#define NEXT pc++; break
#define JUMP break
#define END_DISPATCH }}

#else

#define INIT_DISPATCH JUMP; return mrb_nil_value();
#define CASE(op) L_ ## op:
#define NEXT i=*++pc; CODE_FETCH_HOOK(mrb, irep, pc, regs); goto *optable[GET_OPCODE(i)]
#define JUMP i=*pc; CODE_FETCH_HOOK(mrb, irep, pc, regs); goto *optable[GET_OPCODE(i)]

#define END_DISPATCH

#endif

DIRECT_THREADED マクロが定義されてるかどうかで、って INIT_DISPATCH も同様なのか。ちょっとここでは未定義限定で確認する方向で。(弱
あと、NEXT も一緒に定義されてますね。プログラムカウンタらしきものを進めて break しています。そして OP ですが enum で定義されてますね (opcode.h にて定義)。2^7 てことは 128 に収まる程度の量、ってことなのかどうか。

enum {
OP_NOP=0,/*                                                             */
OP_MOVE,/*      A B     R(A) := R(B)                                    */
OP_LOADL,/*     A Bx    R(A) := Lit(Bx)                                 */
OP_LOADI,/*     A sBx   R(A) := sBx                                     */

で、vm な中間コードを生成してるのは codegen.c の codegen 手続きなのかどうか。ええと、mrb_generate_code 手続きから codegen_start 手続きが呼ばれて

int
mrb_generate_code(mrb_state *mrb, parser_state *p)
{
  int start = mrb->irep_len;
  int n;

  n = codegen_start(mrb, p);
  if (n < 0) return n;

  return start;
}

codegen_start から codegen という手続きが呼び出されております。

static int
codegen_start(mrb_state *mrb, parser_state *p)
{
  codegen_scope *scope = scope_new(mrb, 0, 0);

  if (!scope) {
    return -1;
  }
  scope->mrb = mrb;
  if (p->filename) {
    scope->filename = p->filename;
  }
  if (setjmp(scope->jmp) != 0) {
    return -1;
  }
  // prepare irep
  codegen(scope, p->tree, NOVAL);
  mrb_pool_close(scope->mpool);
  return 0;
}

static な修飾子が付いとりますね。ちなみに mrb_generate_code なる手続きですが、M-x find-grep してみたところ以下な出力。

find . -type f -print0 | "xargs" -0 -e grep -nH -e mrb_generate_code
./parse.y:5204:  n = mrb_generate_code(mrb, p);
./codegen.c:2842:mrb_generate_code(mrb_state *mrb, parser_state *p)

parse.y の load_exec から呼び出されております。yacc 苦手ッス。で、codegen.c で定義されてる codegen という手続きなんですが先頭部分を以下に引用。

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

  if (!tree) return;
  nt = (intptr_t)tree->car;
  s->lineno = tree->lineno;
  tree = tree->cdr;
  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;

  case NODE_RESCUE:

node という型は何でしょ。定義は codegen.c で以下。

typedef mrb_ast_node node;

ええと、mrb_ast_node 型は compile.h で定義されてますね。

/* AST node structure */
typedef struct mrb_ast_node {
  struct mrb_ast_node *car, *cdr;
  short lineno;
} mrb_ast_node;

むむ。なんだこれは。codegen という手続きでは

  • int なポインタとして引数 tree の car 属性を取り出して int 型な nt に代入
  • 引数 tree に tree の cdr 属性を代入
  • nt の値で switch case なナニを評価

ということをしてますね。あら、つーことは codegenコンパイラではなさげ。つうか tree なソレを作るのは parser なのかな。
ちょっと整理しておくと codegen に渡される node 型な引数 tree ですが

  • codegen_start 手続きに渡される parser_state 型の tree 属性
  • codegen_start の呼び出し元の mrb_generate_code でも引数として渡されている
  • (おそらくは) mrb_generate_code の呼び出し元の parser.y で定義されてる load_exec 手続きでも引数として渡されている

という形ですね。定義は parse.y で以下。

typedef struct mrb_parser_state parser_state;

mrb_parser_state 構造体は compile.h で定義されてますね。tree 属性もあります (引用略)。ちょっと load_exec を逆に掘ってみます。呼び出し元は以下二点。

  • mrb_load_file_cxt 手続き (parse.y で定義)
  • mrb_load_nstring_cxt 手続き (parse.y で定義)

さらに追求。

  • mrb_load_file_cxt 手続きは parse.y で定義されてる mrb_load_file から呼び出されている
  • mrb_load_nstring_cxt 手続きは
    • parse.y で定義されている mrb_load_nstring 手続きから呼び出されている
    • parse.y で定義されている mrb_load_string_cxt 手続きから呼び出されている

これらをさらに追求。って

  • mrb_load_file 手続きの呼び出し元が見当らない

つうか yacc て最終的にどう使われるのかを完全に忘れとるな。こっち確認した方が良い気がしてきました。一旦置きます。