mruby/コード実行を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[mrubyを読む]]
#contents
*はじめに [#bdd53068]
コードも生成できたので最後にコードを実行している部分を読...
*RiteVM概観 [#p63559f8]
いきなりmrb_run()に入る前にRiteVMがどんな実行モデルなのか...
RiteVMの実行モデルはレジスタマシンです。ちなみにYARVはス...
例えば以下の単純なRubyスクリプトの場合、
def foo(a, b)
a * b
end
f = foo(1, 2)
irep 116 nregs=6 nlocals=3 pools=0 syms=1
000 OP_TCLASS R3
001 OP_LAMBDA R4 I(117) 1
002 OP_METHOD R3 'foo'
003 OP_LOADSELF R3
004 OP_LOADI R4 1
005 OP_LOADI R5 2
006 OP_LOADNIL R6
007 OP_SEND R3 'foo' 2
008 OP_MOVE R1 R3
009 OP_STOP
irep 117 nregs=7 nlocals=5 pools=0 syms=1
000 OP_ENTER 2:0:0:0:0:0:0
001 OP_MOVE R5 R1
002 OP_MOVE R6 R2
003 OP_LOADNIL R7
004 OP_SEND R5 '*' 1
005 OP_RETURN R5
f = foo(1, 2)の部分は以下のように実行されます。
+レジスタR3にselfをロード(レシーバを設定)
+レジスタR4に1をロード(引数を設定)
+レジスタR5に2をロード(引数を設定)
+レジスタR6にnilをロード(ブロック引数を設定)
+レジスタR3のオブジェクトに対して'foo'メソッドを引数が2つ...
+レジスタR1(ローカル変数f)にメソッド呼び出しの結果(R3...
mrubyではレジスタの確保場所としてスタックを使用しています...
レジスタの確保場所としてスタックを使うとはどういうことか...
トップレベル実行時のスタックベース→| nil |top_self
| |ローカル変数f...
| nil |よくわからな...
メソッドfoo実行時のスタックベース→| nil |'foo'のレシーバ
| 1 |'foo'の引数1 ...
| 2 |'foo'の引数2 ...
| nil |'foo'に対する...
| nil |よくわからな...
| 1 |'*'のレシーバ
| 2 |'*'の引数1
| nil |'*'に対するブ...
以上の前提を持ってmrb_run()に挑むと理解が深まると思います。
*mrb_run(src/vm.c) [#s87bb89e]
では、mrb_run()に見ていくことにしましょう。mrb_run()は一...
#code(C){{
INIT_DISPACTH {
CASE(OP_NOP) {
/* do nothing */
NEXT;
}
CASE(OP_MOVE) {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
NEXT;
}
...
}
END_DISPACTH;
}}
INIT_DISPACTH((スペルミスだと思うのだけど、何故修正されな...
#code(C){{
#ifdef __GNUC__
#define DIRECT_THREADED
#endif
#ifndef DIRECT_THREADED
#define INIT_DISPACTH for (;;) { i = *pc; switch (GET_OP...
#define CASE(op) case op:
#define NEXT mrb->arena_idx = ai; pc++; break
#define JUMP break
#define END_DISPACTH } }
#else
#define INIT_DISPACTH JUMP; return mrb_nil_value();
#define CASE(op) L_ ## op:
#define NEXT mrb->arena_idx = ai; i=*++pc; goto *optable...
#define JUMP i=*pc; goto *optable[GET_OPCODE(i)]
#define END_DISPACTH
#endif
}}
gccかどうかで定義が変ってます。めんどくさいけどちゃんとマ...
gccじゃない場合
#code(C){{
for (;;) { i = *pc; switch (GET_OPCODE(i)) { {
case OP_NOP: {
/* do nothing */
mrb->arena_idx = ai; pc++; break;
}
case OP_MOVE: {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
mrb->arena_idx = ai; pc++; break;
}
...
}
} };
}}
gccの場合
#code(C){{
i=*pc; goto *optable[GET_OPCODE(i)]; return mrb_nil_val...
L_OP_NOP: {
/* do nothing */
mrb->arena_idx = ai; i=*++pc; goto *optable[GET_OPC...
}
L_OP_MOVE: {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
mrb->arena_idx = ai; i=*++pc; goto *optable[GET_OPC...
}
...
}
;
}}
というわけでgccじゃない場合は無限ループ & switch文ですが...
*実行してみる [#n89a9da1]
総論は終わったので後は各論、いつものように各命令について...
**OP_LOADSELF [#dc95f7c0]
mrb_run()を上から見ていくと初めはLOAD系の命令が並んでいま...
#code(C){{
CASE(OP_LOADSELF) {
/* A R(A) := self */
regs[GETARG_A(i)] = mrb->stack[0];
NEXT;
}
}}
何でこれでselfを設定したことになるのか?というと、スタッ...
メソッドfoo実行時のスタックベース→| nil |'foo'のレシーバ
| 1 |'foo'の引数1 &...
| 2 |'foo'の引数2 &...
| nil |'foo'に対する...
というわけでstack[0]がselfになっていることがご理解いただ...
このstack[0]がselfであるということは他でも使われているの...
#code(C){{
CASE(OP_GETIV) {
/* A Bx R(A) := ivget(Bx) */
regs[GETARG_A(i)] = mrb_vm_iv_get(mrb, syms[GETARG_...
NEXT;
}
}}
src/variable.c
#code(C){{
mrb_value
mrb_vm_iv_get(mrb_state *mrb, mrb_sym sym)
{
/* get self */
return mrb_iv_get(mrb, mrb->stack[0], sym);
}
}}
**OP_SEND [#gc4bdea0]
次にメソッド呼び出しを行うOP_SENDを見てみましょう。長いの...
#code(C){{
CASE(OP_SEND) {
/* A B C R(A) := call(R(A),Sym(B),R(A+1),... ,R(A+...
int a = GETARG_A(i);
int n = GETARG_C(i);
struct RProc *m;
struct RClass *c;
mrb_callinfo *ci;
mrb_value recv;
mrb_sym mid = syms[GETARG_B(i)];
}}
コメントに書いてあるように、OP_SENDは3引数型の命令でそれ...
:第1引数|メソッドのレシーバが格納されているレジスタ
:第2引数|メソッドのシンボルテーブルへのインデックス番号
:第3引数|メソッドに渡す引数の数。引数はレシーバが格納され...
と、これがmrubyのメソッド呼び出し規約となっているようです...
| nil |R(A) 'foo'のレシーバ
| 1 |R(A+1) 'foo'の引数1
| 2 |R(A+2) 'foo'の引数2
となっており、適切に呼び出す準備がされていることがわかり...
先に進みます。
#code(C){{
recv = regs[a];
c = mrb_class(mrb, recv);
m = mrb_method_search_vm(mrb, &c, mid);
if (!m) {
mrb_value sym = mrb_symbol_value(mid);
mid = mrb_intern(mrb, "method_missing");
m = mrb_method_search_vm(mrb, &c, mid);
if (n == CALL_MAXARGS) {
mrb_ary_unshift(mrb, regs[a+1], sym);
}
else {
memmove(regs+a+2, regs+a+1, sizeof(mrb_value)*(...
regs[a+1] = sym;
n++;
}
}
}}
呼び出すメソッドの検索をしています。メソッドがない場合はm...
#code(C){{
/* push callinfo */
ci = cipush(mrb);
ci->mid = mid;
ci->proc = m;
ci->stackidx = mrb->stack - mrb->stbase;
ci->argc = n;
if (ci->argc == CALL_MAXARGS) ci->argc = -1;
ci->target_class = m->target_class;
ci->pc = pc + 1;
}}
呼び出し情報はmrb_callinfo構造体に格納されるようです。こ...
#code(C){{
/* prepare stack */
mrb->stack += a;
}}
スタックベースを調整してレシーバが入っているスタック位置...
#code(C){{
if (MRB_PROC_CFUNC_P(m)) {
mrb->stack[0] = m->body.func(mrb, recv);
mrb->arena_idx = ai;
if (mrb->exc) goto L_RAISE;
/* pop stackpos */
mrb->stack = mrb->stbase + ci->stackidx;
cipop(mrb);
NEXT;
}
}}
メソッドがCで書かれている場合です。Cで書かれたメソッドの...
#code(C){{
else {
/* fill callinfo */
ci->acc = a;
/* setup environment for calling method */
proc = mrb->ci->proc = m;
irep = m->body.irep;
pool = irep->pool;
syms = irep->syms;
ci->nregs = irep->nregs;
if (ci->argc < 0) {
stack_extend(mrb, (irep->nregs < 3) ? 3 : irep-...
}
else {
stack_extend(mrb, irep->nregs, ci->argc+2);
}
regs = mrb->stack;
pc = irep->iseq;
JUMP;
}
}
}}
メソッドがRubyで書かれている場合はこちらが実行されます。m...
**OP_ENTER [#g17feea3]
メソッド呼び出しについて見たので次はメソッドの受け側であ...
#code(C){{
CASE(OP_ENTER) {
/* Ax arg setup according to flags (24=...
/* number of optional arguments times OP_JMP should...
int ax = GETARG_Ax(i);
int m1 = (ax>>18)&0x1f;
int o = (ax>>13)&0x1f;
int r = (ax>>12)&0x1;
int m2 = (ax>>7)&0x1f;
/* unused
int k = (ax>>2)&0x1f;
int kd = (ax>>1)&0x1;
int b = (ax>>0)& 0x1;
*/
}}
まず、命令コードのオペランドから各種引数の数を取得してい...
#code(C){{
int argc = mrb->ci->argc;
mrb_value *argv = regs+1;
int len = m1 + o + r + m2;
}}
次に実際に渡された引数の取得です。
#code(C){{
if (argc < 0) {
struct RArray *ary = mrb_ary_ptr(regs[1]);
argv = ary->buf;
argc = ary->len;
regs[len+2] = regs[1]; /* save argary in registe...
}
}}
引数が配列(foo(*a)みたいなの)で渡されたときの処理です。...
#code(C){{
if (mrb->ci->proc && MRB_PROC_STRICT_P(mrb->ci->pro...
if (argc >= 0) {
if (argc < m1 + m2 || (r == 0 && argc > len)) {
argnum_error(mrb, m1+m2);
goto L_RAISE;
}
}
}
}}
引数が少ない、もしくは多過ぎるときのエラー処理です。
#code(C){{
else if (len > 1 && argc == 1 && argv[0].tt == MRB_...
argc = mrb_ary_ptr(argv[0])->len;
argv = mrb_ary_ptr(argv[0])->buf;
}
}}
yieldの場合、*を付けなくても配列展開されるようです。上記...
#code(C){{
mrb->ci->argc = len;
if (argc < len) {
regs[len+1] = argv[argc]; /* move block */
memmove(®s[1], argv, sizeof(mrb_value)*(argc-m...
memmove(®s[len-m2+1], &argv[argc-m2], sizeof(m...
if (r) { /* r */
regs[m1+o+1] = mrb_ary_new_capa(mrb, 0);
}
pc += argc - m1 - m2 + 1;
}
}}
オプション引数の処理です。引数の数からメソッドの開始位置...
def foo(a = 1, b = 'xxx')
end
irep 117 nregs=6 nlocals=5 pools=1 syms=0
000 OP_ENTER 0:2:0:0:0:0:0
001 OP_JMP 004
002 OP_JMP 005
003 OP_JMP 006
004 OP_LOADI R1 1
005 OP_STRING R2 'xxx'
006 OP_RETURN R4
-foo()と呼んだら001から実行される(つまり、004から実行さ...
-foo(2)と呼んだら002から実行される(つまり、005から実行さ...
-foo(3, 'zzz')と呼んだら003から実行される(つまり、006か...
#code(C){{
else {
memmove(®s[1], argv, sizeof(mrb_value)*(m1+o))...
if (r) { /* r */
regs[m1+o+1] = mrb_ary_new_elts(mrb, argc-m1-o-...
}
memmove(®s[m1+o+r+1], &argv[argc-m2], sizeof(m...
regs[len+1] = argv[argc]; /* move block */
pc += o + 1;
}
}}
こっちは引数が足りているときです。余った分は残余引数に詰...
#code(C){{
JUMP;
}
}}
最後に、渡された引数と想定している引数から計算されたpc((...
**OP_RETURN [#haf5ad3c]
続いて、メソッドからの復帰時に実行されるOP_RETURNを見てみ...
#code(C){{
CASE(OP_RETURN) {
/* A return R(A) */
L_RETURN:
if (mrb->exc) {
(省略)
}
else {
mrb_callinfo *ci = mrb->ci;
int acc, eidx = mrb->ci->eidx;
mrb_value v = regs[GETARG_A(i)];
}}
まず、メソッドの戻り値をレジスタから取り出しています。
#code(C){{
switch (GETARG_B(i)) {
case OP_R_NORMAL:
if (ci == mrb->cibase) {
localjump_error(mrb, "return");
goto L_RAISE;
}
ci = mrb->ci;
break;
case OP_R_BREAK:
if (proc->env->cioff < 0) {
localjump_error(mrb, "break");
goto L_RAISE;
}
ci = mrb->ci = mrb->cibase + proc->env->cioff +...
break;
case OP_R_RETURN:
if (proc->env->cioff < 0) {
localjump_error(mrb, "return");
goto L_RAISE;
}
ci = mrb->ci = mrb->cibase + proc->env->cioff;
break;
default:
/* cannot happen */
break;
}
}}
ブロック中でbreakを実行した場合も命令コードはOP_RETURNに...
#code(C){{
cipop(mrb);
acc = ci->acc;
pc = ci->pc;
regs = mrb->stack = mrb->stbase + ci->stackidx;
}}
各種実行時情報をメソッド呼び出し前に戻しています。
#code(C){{
while (eidx > mrb->ci->eidx) {
ecall(mrb, --eidx);
}
}}
ensure節の処理です。例外処理についてはまた今度説明します。
#code(C){{
if (acc < 0) {
mrb->jmp = prev_jmp;
return v;
}
}}
Cで書かれたメソッドから呼ばれた場合、accは-1になっていま...
#code(C){{
DEBUG(printf("from :%s\n", mrb_sym2name(mrb, ci->...
proc = mrb->ci->proc;
irep = proc->body.irep;
pool = irep->pool;
syms = irep->syms;
regs[acc] = v;
}
JUMP;
}
}}
各種実行情報復元その2。戻り値をレジスタに格納してメソッド...
**OP_BLKPUSH [#i8132f88]
次にブロック周りを見てみましょう。yieldをコンパイルすると...
$ ./mruby.exe --verbose -e 'def foo; yield(1); end'
irep 117 nregs=5 nlocals=3 pools=0 syms=1
000 OP_ENTER 0:0:0:0:0:0:0
001 OP_BLKPUSH R3 0:0:0:0
002 OP_LOADI R4 1
003 OP_LOADNIL R5
004 OP_SEND R3 'call' 1
005 OP_RETURN R3
というわけでOP_BLKPUSHを見てみます。
#code(C){{
CASE(OP_BLKPUSH) {
/* A Bx R(A) := block (16=6:1:5:4) */
int a = GETARG_A(i);
int bx = GETARG_Bx(i);
int m1 = (bx>>10)&0x3f;
int r = (bx>>9)&0x1;
int m2 = (bx>>4)&0x1f;
int lv = (bx>>0)&0xf;
mrb_value *stack;
if (lv == 0) stack = regs + 1;
else {
struct REnv *e = uvenv(mrb, lv-1);
stack = e->stack + 1;
}
regs[a] = stack[m1+r+m2];
NEXT;
}
}}
m1, r, m2はyieldを実行するメソッドの引数情報です。オプシ...
regs→|self |
|引数1 |
|・・・ |
|引数n |
|ブロック|
stack[m1+r+m2]というのがちょうどブロックの位置を指すこと...
**OP_CALL [#h3129eab]
では次にブロックを呼び出す個所を見てみましょう。
004 OP_SEND R3 'call' 1
タイトルにあるようにOP_CALLではなく、OP_SENDが使われてい...
src/proc.c
#code(C){{
void
mrb_init_proc(mrb_state *mrb)
{
struct RProc *m;
mrb_code *call_iseq = mrb_malloc(mrb, sizeof(mrb_code));
mrb_irep *call_irep = mrb_calloc(mrb, sizeof(mrb_irep),...
if ( call_iseq == NULL || call_irep == NULL )
return;
*call_iseq = MKOP_A(OP_CALL, 0);
call_irep->idx = -1;
call_irep->flags = MRB_IREP_NOFREE;
call_irep->iseq = call_iseq;
call_irep->ilen = 1;
mrb->proc_class = mrb_define_class(mrb, "Proc", mrb->ob...
mrb_define_method(mrb, mrb->proc_class, "initialize", m...
m = mrb_proc_new(mrb, call_irep);
mrb_define_method_raw(mrb, mrb->proc_class, mrb_intern(...
mrb_define_method_raw(mrb, mrb->proc_class, mrb_intern(...
}
}}
何をやっているかと言うと、callメソッドは呼ばれるとOP_CALL...
#code(C){{
CASE(OP_CALL) {
/* A R(A) := self.call(frame.argc, frame.argv)...
mrb_callinfo *ci;
mrb_value recv = mrb->stack[0];
struct RProc *m = mrb_proc_ptr(recv);
/* replace callinfo */
ci = mrb->ci;
ci->target_class = m->target_class;
ci->proc = m;
if (m->env) {
if (m->env->mid) {
ci->mid = m->env->mid;
}
if (!m->env->stack) {
m->env->stack = mrb->stack;
}
}
/* prepare stack */
if (MRB_PROC_CFUNC_P(m)) {
mrb->stack[0] = m->body.func(mrb, recv);
mrb->arena_idx = ai;
if (mrb->exc) goto L_RAISE;
/* pop stackpos */
regs = mrb->stack = mrb->stbase + ci->stackidx;
cipop(mrb);
NEXT;
}
else {
/* setup environment for calling method */
proc = m;
irep = m->body.irep;
pool = irep->pool;
syms = irep->syms;
ci->nregs = irep->nregs;
if (ci->argc < 0) {
stack_extend(mrb, (irep->nregs < 3) ? 3 : irep-...
}
else {
stack_extend(mrb, irep->nregs, ci->argc+2);
}
regs = mrb->stack;
regs[0] = m->env->stack[0];
pc = m->body.irep->iseq;
JUMP;
}
}
}}
実行時情報をブロックのものに書き換えることでブロック呼び...
環境情報を作っている部分も見ておきましょう。
$ ./mruby.exe -c --verbose -e 'foo { }'
irep 116 nregs=4 nlocals=2 pools=0 syms=1
000 OP_LOADSELF R2
001 OP_LAMBDA R3 I(117) 2
002 OP_SEND R2 'foo' 0
003 OP_STOP
というわけでOP_LAMBDAが引数2(OP_L_CAPTURE)で呼ばれるよう...
#code(C){{
CASE(OP_LAMBDA) {
/* A b c R(A) := lambda(SEQ[b],c) (b:c = 14:2) */
struct RProc *p;
int c = GETARG_c(i);
if (c & OP_L_CAPTURE) {
p = mrb_closure_new(mrb, mrb->irep[irep->idx+GETA...
}
else {
p = mrb_proc_new(mrb, mrb->irep[irep->idx+GETARG_...
}
if (c & OP_L_STRICT) p->flags |= MRB_PROC_STRICT;
regs[GETARG_A(i)] = mrb_obj_value(p);
NEXT;
}
}}
src/proc.c
#code(C){{
struct RProc *
mrb_closure_new(mrb_state *mrb, mrb_irep *irep)
{
struct RProc *p = mrb_proc_new(mrb, irep);
struct REnv *e;
if (!mrb->ci->env) {
e = (struct REnv*)mrb_obj_alloc(mrb, MRB_TT_ENV, (str...
e->flags= (unsigned int)mrb->ci->proc->body.irep->nlo...
e->mid = mrb->ci->mid;
e->cioff = mrb->ci - mrb->cibase;
e->stack = mrb->stack;
mrb->ci->env = e;
}
else {
e = mrb->ci->env;
}
p->env = e;
return p;
}
}}
環境情報として、ブロック定義位置のスタックを記録している...
**OP_ADD [#s90c29bd]
命令コードの最後にOP_ADDを見てみましょう。
#code(C){{
CASE(OP_ADD) {
/* A B C R(A) := R(A)+R(A+1) (Syms[B]=:+,C=1)*/
int a = GETARG_A(i);
switch (TYPES2(mrb_type(regs[a]),mrb_type(regs[a+1]...
case TYPES2(MRB_TT_FIXNUM,MRB_TT_FIXNUM):
OP_MATH_BODY(+,i,i);
break;
case TYPES2(MRB_TT_FIXNUM,MRB_TT_FLOAT):
{
mrb_int x = regs[a].value.i;
mrb_float y = regs[a+1].value.f;
SET_FLOAT_VALUE(regs[a], (mrb_float)x + y);
}
break;
case TYPES2(MRB_TT_FLOAT,MRB_TT_FIXNUM):
OP_MATH_BODY(+,f,i);
break;
case TYPES2(MRB_TT_FLOAT,MRB_TT_FLOAT):
OP_MATH_BODY(+,f,f);
break;
case TYPES2(MRB_TT_STRING,MRB_TT_STRING):
regs[a] = mrb_str_plus(mrb, regs[a], regs[a+1]);
break;
default:
i = MKOP_ABC(OP_SEND, a, GETARG_B(i), GETARG_C(i));
goto L_SEND;
}
NEXT;
}
}}
何をやっているかというと、オペランドがともに数値だったら...
*mrb_get_args(src/class.c) [#z9fe4d13]
Cで定義したメソッドがVMから引数を取得する場合、mrb_get_ar...
src/hash.c
#code(C){{
mrb_value
mrb_hash_aset(mrb_state *mrb, mrb_value self)
{
mrb_value key, val;
mrb_get_args(mrb, "oo", &key, &val);
mrb_hash_set(mrb, self, key, val);
return val;
}
}}
各引数について、何型で引数を取得したいかを指定します。詳...
*おわりに [#k818d39a]
というわけでmrubyのコード実行を見てきました。LOAD系などの...
終了行:
[[mrubyを読む]]
#contents
*はじめに [#bdd53068]
コードも生成できたので最後にコードを実行している部分を読...
*RiteVM概観 [#p63559f8]
いきなりmrb_run()に入る前にRiteVMがどんな実行モデルなのか...
RiteVMの実行モデルはレジスタマシンです。ちなみにYARVはス...
例えば以下の単純なRubyスクリプトの場合、
def foo(a, b)
a * b
end
f = foo(1, 2)
irep 116 nregs=6 nlocals=3 pools=0 syms=1
000 OP_TCLASS R3
001 OP_LAMBDA R4 I(117) 1
002 OP_METHOD R3 'foo'
003 OP_LOADSELF R3
004 OP_LOADI R4 1
005 OP_LOADI R5 2
006 OP_LOADNIL R6
007 OP_SEND R3 'foo' 2
008 OP_MOVE R1 R3
009 OP_STOP
irep 117 nregs=7 nlocals=5 pools=0 syms=1
000 OP_ENTER 2:0:0:0:0:0:0
001 OP_MOVE R5 R1
002 OP_MOVE R6 R2
003 OP_LOADNIL R7
004 OP_SEND R5 '*' 1
005 OP_RETURN R5
f = foo(1, 2)の部分は以下のように実行されます。
+レジスタR3にselfをロード(レシーバを設定)
+レジスタR4に1をロード(引数を設定)
+レジスタR5に2をロード(引数を設定)
+レジスタR6にnilをロード(ブロック引数を設定)
+レジスタR3のオブジェクトに対して'foo'メソッドを引数が2つ...
+レジスタR1(ローカル変数f)にメソッド呼び出しの結果(R3...
mrubyではレジスタの確保場所としてスタックを使用しています...
レジスタの確保場所としてスタックを使うとはどういうことか...
トップレベル実行時のスタックベース→| nil |top_self
| |ローカル変数f...
| nil |よくわからな...
メソッドfoo実行時のスタックベース→| nil |'foo'のレシーバ
| 1 |'foo'の引数1 ...
| 2 |'foo'の引数2 ...
| nil |'foo'に対する...
| nil |よくわからな...
| 1 |'*'のレシーバ
| 2 |'*'の引数1
| nil |'*'に対するブ...
以上の前提を持ってmrb_run()に挑むと理解が深まると思います。
*mrb_run(src/vm.c) [#s87bb89e]
では、mrb_run()に見ていくことにしましょう。mrb_run()は一...
#code(C){{
INIT_DISPACTH {
CASE(OP_NOP) {
/* do nothing */
NEXT;
}
CASE(OP_MOVE) {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
NEXT;
}
...
}
END_DISPACTH;
}}
INIT_DISPACTH((スペルミスだと思うのだけど、何故修正されな...
#code(C){{
#ifdef __GNUC__
#define DIRECT_THREADED
#endif
#ifndef DIRECT_THREADED
#define INIT_DISPACTH for (;;) { i = *pc; switch (GET_OP...
#define CASE(op) case op:
#define NEXT mrb->arena_idx = ai; pc++; break
#define JUMP break
#define END_DISPACTH } }
#else
#define INIT_DISPACTH JUMP; return mrb_nil_value();
#define CASE(op) L_ ## op:
#define NEXT mrb->arena_idx = ai; i=*++pc; goto *optable...
#define JUMP i=*pc; goto *optable[GET_OPCODE(i)]
#define END_DISPACTH
#endif
}}
gccかどうかで定義が変ってます。めんどくさいけどちゃんとマ...
gccじゃない場合
#code(C){{
for (;;) { i = *pc; switch (GET_OPCODE(i)) { {
case OP_NOP: {
/* do nothing */
mrb->arena_idx = ai; pc++; break;
}
case OP_MOVE: {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
mrb->arena_idx = ai; pc++; break;
}
...
}
} };
}}
gccの場合
#code(C){{
i=*pc; goto *optable[GET_OPCODE(i)]; return mrb_nil_val...
L_OP_NOP: {
/* do nothing */
mrb->arena_idx = ai; i=*++pc; goto *optable[GET_OPC...
}
L_OP_MOVE: {
/* A B R(A) := R(B) */
int a = GETARG_A(i);
int b = GETARG_B(i);
regs[a].tt = regs[b].tt;
regs[a].value = regs[b].value;
mrb->arena_idx = ai; i=*++pc; goto *optable[GET_OPC...
}
...
}
;
}}
というわけでgccじゃない場合は無限ループ & switch文ですが...
*実行してみる [#n89a9da1]
総論は終わったので後は各論、いつものように各命令について...
**OP_LOADSELF [#dc95f7c0]
mrb_run()を上から見ていくと初めはLOAD系の命令が並んでいま...
#code(C){{
CASE(OP_LOADSELF) {
/* A R(A) := self */
regs[GETARG_A(i)] = mrb->stack[0];
NEXT;
}
}}
何でこれでselfを設定したことになるのか?というと、スタッ...
メソッドfoo実行時のスタックベース→| nil |'foo'のレシーバ
| 1 |'foo'の引数1 &...
| 2 |'foo'の引数2 &...
| nil |'foo'に対する...
というわけでstack[0]がselfになっていることがご理解いただ...
このstack[0]がselfであるということは他でも使われているの...
#code(C){{
CASE(OP_GETIV) {
/* A Bx R(A) := ivget(Bx) */
regs[GETARG_A(i)] = mrb_vm_iv_get(mrb, syms[GETARG_...
NEXT;
}
}}
src/variable.c
#code(C){{
mrb_value
mrb_vm_iv_get(mrb_state *mrb, mrb_sym sym)
{
/* get self */
return mrb_iv_get(mrb, mrb->stack[0], sym);
}
}}
**OP_SEND [#gc4bdea0]
次にメソッド呼び出しを行うOP_SENDを見てみましょう。長いの...
#code(C){{
CASE(OP_SEND) {
/* A B C R(A) := call(R(A),Sym(B),R(A+1),... ,R(A+...
int a = GETARG_A(i);
int n = GETARG_C(i);
struct RProc *m;
struct RClass *c;
mrb_callinfo *ci;
mrb_value recv;
mrb_sym mid = syms[GETARG_B(i)];
}}
コメントに書いてあるように、OP_SENDは3引数型の命令でそれ...
:第1引数|メソッドのレシーバが格納されているレジスタ
:第2引数|メソッドのシンボルテーブルへのインデックス番号
:第3引数|メソッドに渡す引数の数。引数はレシーバが格納され...
と、これがmrubyのメソッド呼び出し規約となっているようです...
| nil |R(A) 'foo'のレシーバ
| 1 |R(A+1) 'foo'の引数1
| 2 |R(A+2) 'foo'の引数2
となっており、適切に呼び出す準備がされていることがわかり...
先に進みます。
#code(C){{
recv = regs[a];
c = mrb_class(mrb, recv);
m = mrb_method_search_vm(mrb, &c, mid);
if (!m) {
mrb_value sym = mrb_symbol_value(mid);
mid = mrb_intern(mrb, "method_missing");
m = mrb_method_search_vm(mrb, &c, mid);
if (n == CALL_MAXARGS) {
mrb_ary_unshift(mrb, regs[a+1], sym);
}
else {
memmove(regs+a+2, regs+a+1, sizeof(mrb_value)*(...
regs[a+1] = sym;
n++;
}
}
}}
呼び出すメソッドの検索をしています。メソッドがない場合はm...
#code(C){{
/* push callinfo */
ci = cipush(mrb);
ci->mid = mid;
ci->proc = m;
ci->stackidx = mrb->stack - mrb->stbase;
ci->argc = n;
if (ci->argc == CALL_MAXARGS) ci->argc = -1;
ci->target_class = m->target_class;
ci->pc = pc + 1;
}}
呼び出し情報はmrb_callinfo構造体に格納されるようです。こ...
#code(C){{
/* prepare stack */
mrb->stack += a;
}}
スタックベースを調整してレシーバが入っているスタック位置...
#code(C){{
if (MRB_PROC_CFUNC_P(m)) {
mrb->stack[0] = m->body.func(mrb, recv);
mrb->arena_idx = ai;
if (mrb->exc) goto L_RAISE;
/* pop stackpos */
mrb->stack = mrb->stbase + ci->stackidx;
cipop(mrb);
NEXT;
}
}}
メソッドがCで書かれている場合です。Cで書かれたメソッドの...
#code(C){{
else {
/* fill callinfo */
ci->acc = a;
/* setup environment for calling method */
proc = mrb->ci->proc = m;
irep = m->body.irep;
pool = irep->pool;
syms = irep->syms;
ci->nregs = irep->nregs;
if (ci->argc < 0) {
stack_extend(mrb, (irep->nregs < 3) ? 3 : irep-...
}
else {
stack_extend(mrb, irep->nregs, ci->argc+2);
}
regs = mrb->stack;
pc = irep->iseq;
JUMP;
}
}
}}
メソッドがRubyで書かれている場合はこちらが実行されます。m...
**OP_ENTER [#g17feea3]
メソッド呼び出しについて見たので次はメソッドの受け側であ...
#code(C){{
CASE(OP_ENTER) {
/* Ax arg setup according to flags (24=...
/* number of optional arguments times OP_JMP should...
int ax = GETARG_Ax(i);
int m1 = (ax>>18)&0x1f;
int o = (ax>>13)&0x1f;
int r = (ax>>12)&0x1;
int m2 = (ax>>7)&0x1f;
/* unused
int k = (ax>>2)&0x1f;
int kd = (ax>>1)&0x1;
int b = (ax>>0)& 0x1;
*/
}}
まず、命令コードのオペランドから各種引数の数を取得してい...
#code(C){{
int argc = mrb->ci->argc;
mrb_value *argv = regs+1;
int len = m1 + o + r + m2;
}}
次に実際に渡された引数の取得です。
#code(C){{
if (argc < 0) {
struct RArray *ary = mrb_ary_ptr(regs[1]);
argv = ary->buf;
argc = ary->len;
regs[len+2] = regs[1]; /* save argary in registe...
}
}}
引数が配列(foo(*a)みたいなの)で渡されたときの処理です。...
#code(C){{
if (mrb->ci->proc && MRB_PROC_STRICT_P(mrb->ci->pro...
if (argc >= 0) {
if (argc < m1 + m2 || (r == 0 && argc > len)) {
argnum_error(mrb, m1+m2);
goto L_RAISE;
}
}
}
}}
引数が少ない、もしくは多過ぎるときのエラー処理です。
#code(C){{
else if (len > 1 && argc == 1 && argv[0].tt == MRB_...
argc = mrb_ary_ptr(argv[0])->len;
argv = mrb_ary_ptr(argv[0])->buf;
}
}}
yieldの場合、*を付けなくても配列展開されるようです。上記...
#code(C){{
mrb->ci->argc = len;
if (argc < len) {
regs[len+1] = argv[argc]; /* move block */
memmove(®s[1], argv, sizeof(mrb_value)*(argc-m...
memmove(®s[len-m2+1], &argv[argc-m2], sizeof(m...
if (r) { /* r */
regs[m1+o+1] = mrb_ary_new_capa(mrb, 0);
}
pc += argc - m1 - m2 + 1;
}
}}
オプション引数の処理です。引数の数からメソッドの開始位置...
def foo(a = 1, b = 'xxx')
end
irep 117 nregs=6 nlocals=5 pools=1 syms=0
000 OP_ENTER 0:2:0:0:0:0:0
001 OP_JMP 004
002 OP_JMP 005
003 OP_JMP 006
004 OP_LOADI R1 1
005 OP_STRING R2 'xxx'
006 OP_RETURN R4
-foo()と呼んだら001から実行される(つまり、004から実行さ...
-foo(2)と呼んだら002から実行される(つまり、005から実行さ...
-foo(3, 'zzz')と呼んだら003から実行される(つまり、006か...
#code(C){{
else {
memmove(®s[1], argv, sizeof(mrb_value)*(m1+o))...
if (r) { /* r */
regs[m1+o+1] = mrb_ary_new_elts(mrb, argc-m1-o-...
}
memmove(®s[m1+o+r+1], &argv[argc-m2], sizeof(m...
regs[len+1] = argv[argc]; /* move block */
pc += o + 1;
}
}}
こっちは引数が足りているときです。余った分は残余引数に詰...
#code(C){{
JUMP;
}
}}
最後に、渡された引数と想定している引数から計算されたpc((...
**OP_RETURN [#haf5ad3c]
続いて、メソッドからの復帰時に実行されるOP_RETURNを見てみ...
#code(C){{
CASE(OP_RETURN) {
/* A return R(A) */
L_RETURN:
if (mrb->exc) {
(省略)
}
else {
mrb_callinfo *ci = mrb->ci;
int acc, eidx = mrb->ci->eidx;
mrb_value v = regs[GETARG_A(i)];
}}
まず、メソッドの戻り値をレジスタから取り出しています。
#code(C){{
switch (GETARG_B(i)) {
case OP_R_NORMAL:
if (ci == mrb->cibase) {
localjump_error(mrb, "return");
goto L_RAISE;
}
ci = mrb->ci;
break;
case OP_R_BREAK:
if (proc->env->cioff < 0) {
localjump_error(mrb, "break");
goto L_RAISE;
}
ci = mrb->ci = mrb->cibase + proc->env->cioff +...
break;
case OP_R_RETURN:
if (proc->env->cioff < 0) {
localjump_error(mrb, "return");
goto L_RAISE;
}
ci = mrb->ci = mrb->cibase + proc->env->cioff;
break;
default:
/* cannot happen */
break;
}
}}
ブロック中でbreakを実行した場合も命令コードはOP_RETURNに...
#code(C){{
cipop(mrb);
acc = ci->acc;
pc = ci->pc;
regs = mrb->stack = mrb->stbase + ci->stackidx;
}}
各種実行時情報をメソッド呼び出し前に戻しています。
#code(C){{
while (eidx > mrb->ci->eidx) {
ecall(mrb, --eidx);
}
}}
ensure節の処理です。例外処理についてはまた今度説明します。
#code(C){{
if (acc < 0) {
mrb->jmp = prev_jmp;
return v;
}
}}
Cで書かれたメソッドから呼ばれた場合、accは-1になっていま...
#code(C){{
DEBUG(printf("from :%s\n", mrb_sym2name(mrb, ci->...
proc = mrb->ci->proc;
irep = proc->body.irep;
pool = irep->pool;
syms = irep->syms;
regs[acc] = v;
}
JUMP;
}
}}
各種実行情報復元その2。戻り値をレジスタに格納してメソッド...
**OP_BLKPUSH [#i8132f88]
次にブロック周りを見てみましょう。yieldをコンパイルすると...
$ ./mruby.exe --verbose -e 'def foo; yield(1); end'
irep 117 nregs=5 nlocals=3 pools=0 syms=1
000 OP_ENTER 0:0:0:0:0:0:0
001 OP_BLKPUSH R3 0:0:0:0
002 OP_LOADI R4 1
003 OP_LOADNIL R5
004 OP_SEND R3 'call' 1
005 OP_RETURN R3
というわけでOP_BLKPUSHを見てみます。
#code(C){{
CASE(OP_BLKPUSH) {
/* A Bx R(A) := block (16=6:1:5:4) */
int a = GETARG_A(i);
int bx = GETARG_Bx(i);
int m1 = (bx>>10)&0x3f;
int r = (bx>>9)&0x1;
int m2 = (bx>>4)&0x1f;
int lv = (bx>>0)&0xf;
mrb_value *stack;
if (lv == 0) stack = regs + 1;
else {
struct REnv *e = uvenv(mrb, lv-1);
stack = e->stack + 1;
}
regs[a] = stack[m1+r+m2];
NEXT;
}
}}
m1, r, m2はyieldを実行するメソッドの引数情報です。オプシ...
regs→|self |
|引数1 |
|・・・ |
|引数n |
|ブロック|
stack[m1+r+m2]というのがちょうどブロックの位置を指すこと...
**OP_CALL [#h3129eab]
では次にブロックを呼び出す個所を見てみましょう。
004 OP_SEND R3 'call' 1
タイトルにあるようにOP_CALLではなく、OP_SENDが使われてい...
src/proc.c
#code(C){{
void
mrb_init_proc(mrb_state *mrb)
{
struct RProc *m;
mrb_code *call_iseq = mrb_malloc(mrb, sizeof(mrb_code));
mrb_irep *call_irep = mrb_calloc(mrb, sizeof(mrb_irep),...
if ( call_iseq == NULL || call_irep == NULL )
return;
*call_iseq = MKOP_A(OP_CALL, 0);
call_irep->idx = -1;
call_irep->flags = MRB_IREP_NOFREE;
call_irep->iseq = call_iseq;
call_irep->ilen = 1;
mrb->proc_class = mrb_define_class(mrb, "Proc", mrb->ob...
mrb_define_method(mrb, mrb->proc_class, "initialize", m...
m = mrb_proc_new(mrb, call_irep);
mrb_define_method_raw(mrb, mrb->proc_class, mrb_intern(...
mrb_define_method_raw(mrb, mrb->proc_class, mrb_intern(...
}
}}
何をやっているかと言うと、callメソッドは呼ばれるとOP_CALL...
#code(C){{
CASE(OP_CALL) {
/* A R(A) := self.call(frame.argc, frame.argv)...
mrb_callinfo *ci;
mrb_value recv = mrb->stack[0];
struct RProc *m = mrb_proc_ptr(recv);
/* replace callinfo */
ci = mrb->ci;
ci->target_class = m->target_class;
ci->proc = m;
if (m->env) {
if (m->env->mid) {
ci->mid = m->env->mid;
}
if (!m->env->stack) {
m->env->stack = mrb->stack;
}
}
/* prepare stack */
if (MRB_PROC_CFUNC_P(m)) {
mrb->stack[0] = m->body.func(mrb, recv);
mrb->arena_idx = ai;
if (mrb->exc) goto L_RAISE;
/* pop stackpos */
regs = mrb->stack = mrb->stbase + ci->stackidx;
cipop(mrb);
NEXT;
}
else {
/* setup environment for calling method */
proc = m;
irep = m->body.irep;
pool = irep->pool;
syms = irep->syms;
ci->nregs = irep->nregs;
if (ci->argc < 0) {
stack_extend(mrb, (irep->nregs < 3) ? 3 : irep-...
}
else {
stack_extend(mrb, irep->nregs, ci->argc+2);
}
regs = mrb->stack;
regs[0] = m->env->stack[0];
pc = m->body.irep->iseq;
JUMP;
}
}
}}
実行時情報をブロックのものに書き換えることでブロック呼び...
環境情報を作っている部分も見ておきましょう。
$ ./mruby.exe -c --verbose -e 'foo { }'
irep 116 nregs=4 nlocals=2 pools=0 syms=1
000 OP_LOADSELF R2
001 OP_LAMBDA R3 I(117) 2
002 OP_SEND R2 'foo' 0
003 OP_STOP
というわけでOP_LAMBDAが引数2(OP_L_CAPTURE)で呼ばれるよう...
#code(C){{
CASE(OP_LAMBDA) {
/* A b c R(A) := lambda(SEQ[b],c) (b:c = 14:2) */
struct RProc *p;
int c = GETARG_c(i);
if (c & OP_L_CAPTURE) {
p = mrb_closure_new(mrb, mrb->irep[irep->idx+GETA...
}
else {
p = mrb_proc_new(mrb, mrb->irep[irep->idx+GETARG_...
}
if (c & OP_L_STRICT) p->flags |= MRB_PROC_STRICT;
regs[GETARG_A(i)] = mrb_obj_value(p);
NEXT;
}
}}
src/proc.c
#code(C){{
struct RProc *
mrb_closure_new(mrb_state *mrb, mrb_irep *irep)
{
struct RProc *p = mrb_proc_new(mrb, irep);
struct REnv *e;
if (!mrb->ci->env) {
e = (struct REnv*)mrb_obj_alloc(mrb, MRB_TT_ENV, (str...
e->flags= (unsigned int)mrb->ci->proc->body.irep->nlo...
e->mid = mrb->ci->mid;
e->cioff = mrb->ci - mrb->cibase;
e->stack = mrb->stack;
mrb->ci->env = e;
}
else {
e = mrb->ci->env;
}
p->env = e;
return p;
}
}}
環境情報として、ブロック定義位置のスタックを記録している...
**OP_ADD [#s90c29bd]
命令コードの最後にOP_ADDを見てみましょう。
#code(C){{
CASE(OP_ADD) {
/* A B C R(A) := R(A)+R(A+1) (Syms[B]=:+,C=1)*/
int a = GETARG_A(i);
switch (TYPES2(mrb_type(regs[a]),mrb_type(regs[a+1]...
case TYPES2(MRB_TT_FIXNUM,MRB_TT_FIXNUM):
OP_MATH_BODY(+,i,i);
break;
case TYPES2(MRB_TT_FIXNUM,MRB_TT_FLOAT):
{
mrb_int x = regs[a].value.i;
mrb_float y = regs[a+1].value.f;
SET_FLOAT_VALUE(regs[a], (mrb_float)x + y);
}
break;
case TYPES2(MRB_TT_FLOAT,MRB_TT_FIXNUM):
OP_MATH_BODY(+,f,i);
break;
case TYPES2(MRB_TT_FLOAT,MRB_TT_FLOAT):
OP_MATH_BODY(+,f,f);
break;
case TYPES2(MRB_TT_STRING,MRB_TT_STRING):
regs[a] = mrb_str_plus(mrb, regs[a], regs[a+1]);
break;
default:
i = MKOP_ABC(OP_SEND, a, GETARG_B(i), GETARG_C(i));
goto L_SEND;
}
NEXT;
}
}}
何をやっているかというと、オペランドがともに数値だったら...
*mrb_get_args(src/class.c) [#z9fe4d13]
Cで定義したメソッドがVMから引数を取得する場合、mrb_get_ar...
src/hash.c
#code(C){{
mrb_value
mrb_hash_aset(mrb_state *mrb, mrb_value self)
{
mrb_value key, val;
mrb_get_args(mrb, "oo", &key, &val);
mrb_hash_set(mrb, self, key, val);
return val;
}
}}
各引数について、何型で引数を取得したいかを指定します。詳...
*おわりに [#k818d39a]
というわけでmrubyのコード実行を見てきました。LOAD系などの...
ページ名: