確認してみます、な備忘録
以下な 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 て最終的にどう使われるのかを完全に忘れとるな。こっち確認した方が良い気がしてきました。一旦置きます。