mruby/実行コード生成を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[mrubyを読む]]
#contents
*はじめに [#ud081715]
スクリプトの解析が終わったら今度はコード生成をするようで...
* mrb_generate_code(src/codegen.c) [#qc9db2a0]
それではさっそくmrb_generate_code()を見てみましょう。
#code(C){{
int
mrb_generate_code(mrb_state *mrb, node *tree)
{
int start = mrb->irep_len;
int n;
n = codegen_start(mrb, tree);
if (n < 0) return n;
return start;
}
static int
codegen_start(mrb_state *mrb, node *tree)
{
codegen_scope *scope = scope_new(mrb, 0, 0);
if (!scope) {
return -1;
}
scope->mrb = mrb;
if (setjmp(scope->jmp) != 0) {
return -1;
}
// prepare irep
codegen(scope, tree, NOVAL);
return 0;
}
}}
codegen()はnodeの種類に応じて実行コード生成を行う巨大なsw...
* コード生成してみる [#udb8a47b]
コード生成時にデータ構造が具体的にどのようになるかは実例...
./mruby.exe -c --verbose ../../montecarlo.rb
irep 116 nregs=7 nlocals=4 pools=2 syms=5
000 OP_LOADNIL R4
001 OP_LOADNIL R5
002 OP_CLASS R4 'MonteCarlo'
003 OP_EXEC R4 I(117)
004 OP_LOADI R4 10000
005 OP_LOADI R5 10000
006 OP_LOADNIL R6
007 OP_SEND R4 '*' 1
008 OP_MOVE R1 R4
009 OP_GETCONST R4 'MonteCarlo'
010 OP_LOADNIL R5
011 OP_SEND R4 'new' 0
012 OP_MOVE R5 R1
013 OP_LOADNIL R6
014 OP_SEND R4 'pi' 1
015 OP_MOVE R2 R4
016 OP_LOADSELF R4
017 OP_STRING R5 'pi = '
018 OP_MOVE R6 R2
019 OP_STRCAT R5 R6
020 OP_STRING R6 ''
021 OP_STRCAT R5 R6
022 OP_LOADNIL R6
023 OP_SEND R4 'puts' 1
024 OP_STOP
irep 117 nregs=3 nlocals=2 pools=0 syms=1
000 OP_TCLASS R2
001 OP_LAMBDA R3 I(118) 1
002 OP_METHOD R2 'pi'
003 OP_LOADNIL R2
004 OP_RETURN R4
irep 118 nregs=7 nlocals=5 pools=0 syms=4
000 OP_ENTER 1:0:0:0:0:0:0
001 OP_LOADI R3 0
002 OP_LOADI R5 1
003 OP_MOVE R6 R1
004 OP_RANGE R5 R5 0
005 OP_LAMBDA R6 I(119) 2
006 OP_SEND R5 'each' 0
007 OP_MOVE R5 R3
008 OP_LOADNIL R6
009 OP_SEND R5 'to_f' 0
010 OP_MOVE R6 R1
011 OP_LOADNIL R7
012 OP_SEND R5 '/' 1
013 OP_LOADI R6 4
014 OP_LOADNIL R7
015 OP_SEND R5 '*' 1
016 OP_RETURN R5
irep 119 nregs=7 nlocals=4 pools=0 syms=4
000 OP_LOADSELF R4
001 OP_LOADNIL R5
002 OP_SEND R4 'rand' 0
003 OP_MOVE R1 R4
004 OP_LOADSELF R4
005 OP_LOADNIL R5
006 OP_SEND R4 'rand' 0
007 OP_MOVE R2 R4
008 OP_MOVE R4 R1
009 OP_MOVE R5 R1
010 OP_LOADNIL R6
011 OP_SEND R4 '*' 1
012 OP_MOVE R5 R2
013 OP_MOVE R6 R2
014 OP_LOADNIL R7
015 OP_SEND R5 '*' 1
016 OP_LOADNIL R6
017 OP_ADD R4 '+' 1
018 OP_LOADI R5 1
019 OP_LOADNIL R6
020 OP_LE R4 '<=' 1
021 OP_JMPNOT R4 028
022 OP_GETUPVAR R4 3 0
023 OP_LOADI R5 1
024 OP_LOADNIL R6
025 OP_ADD R4 '+' 2
026 OP_SETUPVAR R4 3 0
027 OP_LOADNIL R4
028 OP_RETURN R4
トップレベル、クラス定義、メソッド定義、ブロックの4つのコ...
-symsはコードブロック内で使っているシンボルの数と思われる
-poolsはコードブロック内で使っている文字列の数と思われる
-nlocalsはコードブロック内のローカル変数の数と思われるが...
-nregsはコードブロック内で使っているレジスタ数な気がする...
なお、各実行コードの説明はsrc/opcode.h内に書かれています。
ところで、irepのrepって何の略なんでしょうね?iはInstructi...
ちなみに、irepの番号がいきなり116から始まっているのは初期...
** NODE_SCOPE [#laf52f6e]
ではnodeツリーをたどってどうやってコードが生成されていく...
#code(C){{
case NODE_SCOPE:
scope_body(s, tree);
break;
}}
#code(C){{
static int
scope_body(codegen_scope *s, node *tree)
{
codegen_scope *scope = scope_new(s->mrb, s, tree->car);
int idx = scope->idx;
if (!s->iseq) {
codegen(scope, tree->cdr, VAL);
genop(scope, MKOP_A(OP_STOP, 0));
}
else {
codegen(scope, tree->cdr, VAL);
genop(scope, MKOP_AB(OP_RETURN, cursp(), OP_R_NORMAL));
}
scope_finish(scope, idx);
return idx - s->idx;
}
}}
というわけで新しいスコープを作ってcodegen()を再帰呼び出し...
scope_new()、scope_finish()も見てみましょう。
#code(C){{
static codegen_scope*
scope_new(mrb_state *mrb, codegen_scope *prev, node *lv)
{
mrb_pool *pool = mrb_pool_open(mrb);
codegen_scope *p = mrb_pool_alloc(pool, sizeof(codegen_...
if (!p) return 0;
memset(p, 0, sizeof(codegen_scope));
p->mrb = mrb;
p->mpool = pool;
if (!prev) return p;
p->prev = prev;
p->ainfo = -1;
p->mrb = prev->mrb;
p->icapa = 1024;
p->iseq = mrb_malloc(mrb, sizeof(mrb_code)*p->icapa);
p->pcapa = 32;
p->pool = mrb_malloc(mrb, sizeof(mrb_value)*p->pcapa);
p->syms = mrb_malloc(mrb, sizeof(mrb_sym)*256);
p->lv = lv;
p->sp += node_len(lv)+2;
p->nlocals = p->sp;
p->idx = mrb->irep_len++;
return p;
}
static void
scope_finish(codegen_scope *s, int idx)
{
mrb_state *mrb = s->mrb;
mrb_irep *irep;
mrb_add_irep(mrb, idx);
irep = mrb->irep[idx] = mrb_malloc(mrb, sizeof(mrb_irep...
irep->idx = idx;
irep->flags = 0;
if (s->iseq) {
irep->iseq = codegen_realloc(s, s->iseq, sizeof(mrb_c...
irep->ilen = s->pc;
}
if (s->pool) {
irep->pool = codegen_realloc(s, s->pool, sizeof(mrb_v...
irep->plen = s->plen;
}
if (s->syms) {
irep->syms = codegen_realloc(s, s->syms, sizeof(mrb_s...
irep->slen = s->slen;
}
irep->nlocals = s->nlocals;
irep->nregs = s->nregs;
mrb_pool_close(s->mpool);
}
}}
nlocalsがローカル変数の数と一致しない理由がわかりました。...
また、scope_finish()を見るとわかるようにスコープを閉じる...
**NODE_CLASS [#mc225f51]
次にNODE_CLASSです。
#code(C){{
case NODE_CLASS:
{
int idx;
if (tree->car->car == (node*)0) {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
else if (tree->car->car == (node*)1) {
genop(s, MKOP_A(OP_OCLASS, cursp()));
push();
}
else {
codegen(s, tree->car->car, VAL);
}
if (tree->cdr->car) {
codegen(s, tree->cdr->car, VAL);
}
else {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
pop(); pop();
idx = new_msym(s, (mrb_sym)tree->car->cdr);
genop(s, MKOP_AB(OP_CLASS, cursp(), idx));
idx = scope_body(s, tree->cdr->cdr->car);
genop(s, MKOP_ABx(OP_EXEC, cursp(), idx));
if (val) {
push();
}
}
break;
}}
parse.yに書かれたNODE_CLASSの構造を考えると初めのif文で生...
**NODE_DEF [#g15f08d4]
深さ優先にクラス定義の中に入っていくことにします。という...
#code(C){{
case NODE_DEF:
{
int sym = new_msym(s, (mrb_sym)tree->car);
int idx = lambda_body(s, tree->cdr, 0);
genop(s, MKOP_A(OP_TCLASS, cursp()));
push();
genop(s, MKOP_Abc(OP_LAMBDA, cursp(), idx, OP_L_MET...
pop();
genop(s, MKOP_AB(OP_METHOD, cursp(), sym));
if (val) {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
}
}
break;
}}
lambda_body()ですがちょっと長めです。部分部分にわけて説明...
#code(C){{
static int
lambda_body(codegen_scope *s, node *tree, int blk)
{
int idx, base = s->idx;
mrb_code c;
s = scope_new(s->mrb, s, tree->car);
idx = s->idx;
}}
まず新しいスコープを作っています。
#code(C){{
if (blk) {
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
lp->pc1 = new_label(s);
}
}
}}
ブロック定義の場合の処理がされていますが今はメソッド定義...
#code(C){{
tree = tree->cdr;
if (tree->car) {
int ma, oa, ra, pa, ka, kd, ba, a;
int pos, i;
node *n, *opt;
ma = node_len(tree->car->car);
n = tree->car->car;
while (n) {
n = n->cdr;
}
oa = node_len(tree->car->cdr->car);
ra = tree->car->cdr->cdr->car ? 1 : 0;
pa = node_len(tree->car->cdr->cdr->cdr->car);
ka = kd = 0;
ba = tree->car->cdr->cdr->cdr->cdr ? 1 : 0;
a = ((ma & 0x1f) << 18)
| ((oa & 0x1f) << 13)
| ((ra & 1) << 12)
| ((pa & 0x1f) << 7)
| ((ka & 0x1f) << 2)
| ((kd & 1)<< 1)
| (ba & 1);
s->ainfo = (((ma+oa) & 0x3f) << 6) /* (12bits = 6:1:5...
| ((ra & 1) << 5)
| (pa & 0x1f);
genop(s, MKOP_Ax(OP_ENTER, a));
}}
tree->carは引数情報が入っています。それぞれ、
:ma|通常引数
:oa|オプション引数
:ra|残余引数
:pa|ポスト引数((いつの間にかこんな引数種別増えたんですね...
:ka|キーワード引数((CRubyでもまだサポートされていません))
:ba|ブロック引数
を示しており、その情報がコードに埋め込まれるようです。
#code(C){{
pos = new_label(s);
for (i=0; i<oa; i++) {
new_label(s);
genop(s, MKOP_Ax(OP_JMP, 0));
}
if (oa > 0) {
genop(s, MKOP_Ax(OP_JMP, 0));
}
opt = tree->car->cdr->car;
i = 0;
while (opt) {
int idx;
dispatch(s, pos+i);
codegen(s, opt->car->cdr, VAL);
idx = lv_idx(s, (mrb_sym)opt->car->car);
pop();
genop_peep(s, MKOP_AB(OP_MOVE, idx, cursp()), NOVAL);
i++;
opt = opt->cdr;
}
if (oa > 0) {
dispatch(s, pos+i);
}
}
}}
オプション引数の処理を行っています。コードをそのまま解説...
def foo(a = 1, b = 'xxx')
end
上記のRubyスクリプトの実行コードは以下のようになります。
$ ./mruby.exe -c --verbose ../../optarg.rb
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か...
なんでこんなめんどくさいことを・・・、と思われるかもしれ...
def bar
123
end
def foo(a = 1, b = bar)
end
./mruby.exe -c --verbose ../../optarg2.rb
irep 118 nregs=6 nlocals=5 pools=0 syms=1
000 OP_ENTER 0:2:0:0:0:0:0
001 OP_JMP 004
002 OP_JMP 005
003 OP_JMP 009
004 OP_LOADI R1 1
005 OP_LOADSELF R5
006 OP_LOADNIL R6
007 OP_SEND R5 'bar' 0
008 OP_MOVE R2 R5
009 OP_RETURN R4
と、bの値を決定するために別メソッドが呼び出されることもあ...
話が長くなりましたが以上を踏まえてオプション引数処理とし...
+まず、OP_JMPを埋め込み
+各オプション引数について
++OP_JMPのジャンプ先を決定し(dispatch()に書かれています)
++デフォルト式に対応するコードを生成し
++デフォルト式の結果を格納
+最後にメソッド本体へのOP_JMP先を決定して終わり
ということをしています。ちなみに、genop_peep()というのは...
#code(C){{
codegen(s, tree->cdr->car, VAL);
pop();
c = s->iseq[s->pc-1];
if (GET_OPCODE(c) != OP_RETURN || GETARG_B(c) != OP_R_N...
genop(s, MKOP_AB(OP_RETURN, cursp(), OP_R_NORMAL));
}
if (blk) {
loop_pop(s, NOVAL);
}
scope_finish(s, idx);
return idx - base;
}
}}
ようやく、メソッド定義のコードを生成し、最後にreturnの処...
**NODE_ASGN [#f2f06f88]
次はNODE_ASGNです。
#code(C){{
case NODE_ASGN:
codegen(s, tree->cdr, VAL);
pop();
gen_assignment(s, tree->car, cursp(), val);
break;
}}
cdrは右辺、carが格納先の左辺です。
gen_assignment()は代入先の種類に応じて生成するコードを変...
#code(C){{
static void
gen_assignment(codegen_scope *s, node *node, int sp, int ...
{
int idx;
int type = (intptr_t)node->car;
node = node->cdr;
switch ((intptr_t)type) {
case NODE_GVAR:
...
case NODE_LVAR:
idx = lv_idx(s, (mrb_sym)node);
if (idx > 0) {
if (idx != sp) {
genop_peep(s, MKOP_AB(OP_MOVE, idx, sp), val);
}
break;
}
else { /* upvar */
int lv = 0;
codegen_scope *up = s->prev;
while (up) {
idx = lv_idx(up, (mrb_sym)node);
if (idx > 0) {
genop_peep(s, MKOP_ABC(OP_SETUPVAR, sp, idx, lv...
break;
}
lv++;
up = up->prev;
}
// assert(up!=0);
}
break;
case NODE_IVAR:
...
case NODE_CVAR:
...
case NODE_CONST:
...
case NODE_COLON2:
...
case NODE_CALL:
...
default:
...
}
if (val) push();
}
}}
前半部分は普通のローカル変数、後半(else部分)はブロック...
ちなみに、NODE_CALLのcaseは配列要素やハッシュ要素への代入...
**NODE_CALL [#n32d968f]
次にNODE_CALL。
#code(C){{
case NODE_CALL:
gen_call(s, tree, 0, 0, val);
break;
}}
gen_call()に進みます。長いのでまたわけて解説します。
#code(C){{
static void
gen_call(codegen_scope *s, node *tree, mrb_sym name, int ...
{
mrb_sym sym = name ? name : (mrb_sym)tree->cdr->car;
int idx;
int n = 0, noop = 0, sendv = 0;
codegen(s, tree->car, VAL); /* receiver */
}}
コメント通りにレシーバーを生成しているようです。
#code(C){{
idx = new_msym(s, sym);
tree = tree->cdr->cdr->car;
if (tree) {
n = gen_values(s, tree->car);
if (n < 0) {
n = noop = sendv = 1;
push();
}
}
if (sp) {
if (sendv) {
pop();
genop(s, MKOP_AB(OP_ARYPUSH, cursp(), sp));
push();
}
else {
genop(s, MKOP_AB(OP_MOVE, cursp(), sp));
push();
n++;
}
}
}}
引数を設定するコードを生成しています。gen_values()は引数...
#code(C){{
if (tree && tree->cdr) {
noop = 1;
codegen(s, tree->cdr, VAL);
pop();
}
else {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
}
pop_n(n+1);
}}
ブロックを積む処理をしているようです。
#code(C){{
{
const char *name = mrb_sym2name(s->mrb, sym);
if (!noop && name[0] == '+' && strlen(name) == 1) {
genop(s, MKOP_ABC(OP_ADD, cursp(), idx, n));
}
(中略。-, <, <=, >, >=について同様の特別処理)
else {
if (sendv) n = CALL_MAXARGS;
genop(s, MKOP_ABC(OP_SEND, cursp(), idx, n));
}
}
if (val) {
push();
}
}
}}
最後にメソッド呼び出しのコードを埋め込んでいます。+など、...
**NODE_BLOCK [#v274929e]
次はNODE_BLOCKです。
#code(C){{
case NODE_BLOCK:
{
int idx = lambda_body(s, tree, 1);
genop(s, MKOP_Abc(OP_LAMBDA, cursp(), idx, OP_L_BLO...
push();
}
break;
}}
NODE_CALLと同様にlambda_body()を呼び出してます。ただし、...
#code(C){{
static int
lambda_body(codegen_scope *s, node *tree, int blk)
{
int idx, base = s->idx;
mrb_code c;
s = scope_new(s->mrb, s, tree->car);
idx = s->idx;
if (blk) {
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
lp->pc1 = new_label(s);
}
(本体処理。詳しくは上記参照)
if (blk) {
loop_pop(s, NOVAL);
}
scope_finish(s, idx);
return idx - base;
}
}}
loopinfoにはブロック内でnextやbreakを実行したときにどこに...
**NODE_IF [#mdc96c26]
次にNODE_IFです。
#code(C){{
case NODE_IF:
{
int pos1, pos2;
node *e = tree->cdr->cdr->car;
codegen(s, tree->car, VAL);
pop();
pos1 = new_label(s);
genop(s, MKOP_AsBx(OP_JMPNOT, cursp(), 0));
codegen(s, tree->cdr->car, val);
if (e) {
if (val) pop();
pos2 = new_label(s);
genop(s, MKOP_sBx(OP_JMP, 0));
dispatch(s, pos1);
codegen(s, e, val);
dispatch(s, pos2);
}
else {
if (val) {
pop();
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
dispatch(s, pos1);
}
}
break;
}}
やっていることは以下の通りです。
+条件節に対応するコードを生成
+条件が成り立たないときのジャンプ命令を埋め込み
+then節に対応するコードを生成
+else節がある場合
++then節の末尾にif全体の末尾にジャンプする命令を埋め込み
++条件が成り立たない場合のジャンプ先としてelse節の先頭を...
++else節に対応するコードを生成
+else節がない場合
++条件が成り立たない場合のジャンプ先としてif全体の末尾を...
言葉だけだとわかりにくいと思うので実際に変換したコードを...
if true
1
else
2
end
irep 116 nregs=3 nlocals=2 pools=0 syms=0
000 OP_LOADT R2
001 OP_JMPNOT R2 004
002 OP_LOADI R2 1
003 OP_JMP 005
004 OP_LOADI R2 2
005 OP_STOP
**NODE_DSTR [#zeb37344]
最後にNODE_DSTRについて見てみます。
#code(C){{
case NODE_DSTR:
if (val) {
node *n = tree;
codegen(s, n->car, VAL);
n = n->cdr;
while (n) {
codegen(s, n->car, VAL);
pop(); pop();
genop(s, MKOP_AB(OP_STRCAT, cursp(), cursp()+1));
push();
n = n->cdr;
}
}
else {
node *n = tree;
while (n) {
if ((intptr_t)n->car->car != NODE_STR) {
codegen(s, n->car, NOVAL);
}
n = n->cdr;
}
}
break;
}}
valの値によって大きく処理が分かれています。今まで特に触れ...
**nregs [#y2075d6b]
最後の最後にnregsについて。レジスタ数っぽいと上で書きまし...
#code(C){{
#define nregs_update do {if (s->sp > s->nregs) s->nregs =...
static void
push_(codegen_scope *s)
{
if (s->sp > 511) {
codegen_error(s, "too complex expression");
}
s->sp++;
nregs_update;
}
}}
そのスコープでの最大スタック伸長数のようです。
*おわりに [#p8075c82]
以上、mrubyのコード生成を見てきました。基本的には淡々とNO...
終了行:
[[mrubyを読む]]
#contents
*はじめに [#ud081715]
スクリプトの解析が終わったら今度はコード生成をするようで...
* mrb_generate_code(src/codegen.c) [#qc9db2a0]
それではさっそくmrb_generate_code()を見てみましょう。
#code(C){{
int
mrb_generate_code(mrb_state *mrb, node *tree)
{
int start = mrb->irep_len;
int n;
n = codegen_start(mrb, tree);
if (n < 0) return n;
return start;
}
static int
codegen_start(mrb_state *mrb, node *tree)
{
codegen_scope *scope = scope_new(mrb, 0, 0);
if (!scope) {
return -1;
}
scope->mrb = mrb;
if (setjmp(scope->jmp) != 0) {
return -1;
}
// prepare irep
codegen(scope, tree, NOVAL);
return 0;
}
}}
codegen()はnodeの種類に応じて実行コード生成を行う巨大なsw...
* コード生成してみる [#udb8a47b]
コード生成時にデータ構造が具体的にどのようになるかは実例...
./mruby.exe -c --verbose ../../montecarlo.rb
irep 116 nregs=7 nlocals=4 pools=2 syms=5
000 OP_LOADNIL R4
001 OP_LOADNIL R5
002 OP_CLASS R4 'MonteCarlo'
003 OP_EXEC R4 I(117)
004 OP_LOADI R4 10000
005 OP_LOADI R5 10000
006 OP_LOADNIL R6
007 OP_SEND R4 '*' 1
008 OP_MOVE R1 R4
009 OP_GETCONST R4 'MonteCarlo'
010 OP_LOADNIL R5
011 OP_SEND R4 'new' 0
012 OP_MOVE R5 R1
013 OP_LOADNIL R6
014 OP_SEND R4 'pi' 1
015 OP_MOVE R2 R4
016 OP_LOADSELF R4
017 OP_STRING R5 'pi = '
018 OP_MOVE R6 R2
019 OP_STRCAT R5 R6
020 OP_STRING R6 ''
021 OP_STRCAT R5 R6
022 OP_LOADNIL R6
023 OP_SEND R4 'puts' 1
024 OP_STOP
irep 117 nregs=3 nlocals=2 pools=0 syms=1
000 OP_TCLASS R2
001 OP_LAMBDA R3 I(118) 1
002 OP_METHOD R2 'pi'
003 OP_LOADNIL R2
004 OP_RETURN R4
irep 118 nregs=7 nlocals=5 pools=0 syms=4
000 OP_ENTER 1:0:0:0:0:0:0
001 OP_LOADI R3 0
002 OP_LOADI R5 1
003 OP_MOVE R6 R1
004 OP_RANGE R5 R5 0
005 OP_LAMBDA R6 I(119) 2
006 OP_SEND R5 'each' 0
007 OP_MOVE R5 R3
008 OP_LOADNIL R6
009 OP_SEND R5 'to_f' 0
010 OP_MOVE R6 R1
011 OP_LOADNIL R7
012 OP_SEND R5 '/' 1
013 OP_LOADI R6 4
014 OP_LOADNIL R7
015 OP_SEND R5 '*' 1
016 OP_RETURN R5
irep 119 nregs=7 nlocals=4 pools=0 syms=4
000 OP_LOADSELF R4
001 OP_LOADNIL R5
002 OP_SEND R4 'rand' 0
003 OP_MOVE R1 R4
004 OP_LOADSELF R4
005 OP_LOADNIL R5
006 OP_SEND R4 'rand' 0
007 OP_MOVE R2 R4
008 OP_MOVE R4 R1
009 OP_MOVE R5 R1
010 OP_LOADNIL R6
011 OP_SEND R4 '*' 1
012 OP_MOVE R5 R2
013 OP_MOVE R6 R2
014 OP_LOADNIL R7
015 OP_SEND R5 '*' 1
016 OP_LOADNIL R6
017 OP_ADD R4 '+' 1
018 OP_LOADI R5 1
019 OP_LOADNIL R6
020 OP_LE R4 '<=' 1
021 OP_JMPNOT R4 028
022 OP_GETUPVAR R4 3 0
023 OP_LOADI R5 1
024 OP_LOADNIL R6
025 OP_ADD R4 '+' 2
026 OP_SETUPVAR R4 3 0
027 OP_LOADNIL R4
028 OP_RETURN R4
トップレベル、クラス定義、メソッド定義、ブロックの4つのコ...
-symsはコードブロック内で使っているシンボルの数と思われる
-poolsはコードブロック内で使っている文字列の数と思われる
-nlocalsはコードブロック内のローカル変数の数と思われるが...
-nregsはコードブロック内で使っているレジスタ数な気がする...
なお、各実行コードの説明はsrc/opcode.h内に書かれています。
ところで、irepのrepって何の略なんでしょうね?iはInstructi...
ちなみに、irepの番号がいきなり116から始まっているのは初期...
** NODE_SCOPE [#laf52f6e]
ではnodeツリーをたどってどうやってコードが生成されていく...
#code(C){{
case NODE_SCOPE:
scope_body(s, tree);
break;
}}
#code(C){{
static int
scope_body(codegen_scope *s, node *tree)
{
codegen_scope *scope = scope_new(s->mrb, s, tree->car);
int idx = scope->idx;
if (!s->iseq) {
codegen(scope, tree->cdr, VAL);
genop(scope, MKOP_A(OP_STOP, 0));
}
else {
codegen(scope, tree->cdr, VAL);
genop(scope, MKOP_AB(OP_RETURN, cursp(), OP_R_NORMAL));
}
scope_finish(scope, idx);
return idx - s->idx;
}
}}
というわけで新しいスコープを作ってcodegen()を再帰呼び出し...
scope_new()、scope_finish()も見てみましょう。
#code(C){{
static codegen_scope*
scope_new(mrb_state *mrb, codegen_scope *prev, node *lv)
{
mrb_pool *pool = mrb_pool_open(mrb);
codegen_scope *p = mrb_pool_alloc(pool, sizeof(codegen_...
if (!p) return 0;
memset(p, 0, sizeof(codegen_scope));
p->mrb = mrb;
p->mpool = pool;
if (!prev) return p;
p->prev = prev;
p->ainfo = -1;
p->mrb = prev->mrb;
p->icapa = 1024;
p->iseq = mrb_malloc(mrb, sizeof(mrb_code)*p->icapa);
p->pcapa = 32;
p->pool = mrb_malloc(mrb, sizeof(mrb_value)*p->pcapa);
p->syms = mrb_malloc(mrb, sizeof(mrb_sym)*256);
p->lv = lv;
p->sp += node_len(lv)+2;
p->nlocals = p->sp;
p->idx = mrb->irep_len++;
return p;
}
static void
scope_finish(codegen_scope *s, int idx)
{
mrb_state *mrb = s->mrb;
mrb_irep *irep;
mrb_add_irep(mrb, idx);
irep = mrb->irep[idx] = mrb_malloc(mrb, sizeof(mrb_irep...
irep->idx = idx;
irep->flags = 0;
if (s->iseq) {
irep->iseq = codegen_realloc(s, s->iseq, sizeof(mrb_c...
irep->ilen = s->pc;
}
if (s->pool) {
irep->pool = codegen_realloc(s, s->pool, sizeof(mrb_v...
irep->plen = s->plen;
}
if (s->syms) {
irep->syms = codegen_realloc(s, s->syms, sizeof(mrb_s...
irep->slen = s->slen;
}
irep->nlocals = s->nlocals;
irep->nregs = s->nregs;
mrb_pool_close(s->mpool);
}
}}
nlocalsがローカル変数の数と一致しない理由がわかりました。...
また、scope_finish()を見るとわかるようにスコープを閉じる...
**NODE_CLASS [#mc225f51]
次にNODE_CLASSです。
#code(C){{
case NODE_CLASS:
{
int idx;
if (tree->car->car == (node*)0) {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
else if (tree->car->car == (node*)1) {
genop(s, MKOP_A(OP_OCLASS, cursp()));
push();
}
else {
codegen(s, tree->car->car, VAL);
}
if (tree->cdr->car) {
codegen(s, tree->cdr->car, VAL);
}
else {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
pop(); pop();
idx = new_msym(s, (mrb_sym)tree->car->cdr);
genop(s, MKOP_AB(OP_CLASS, cursp(), idx));
idx = scope_body(s, tree->cdr->cdr->car);
genop(s, MKOP_ABx(OP_EXEC, cursp(), idx));
if (val) {
push();
}
}
break;
}}
parse.yに書かれたNODE_CLASSの構造を考えると初めのif文で生...
**NODE_DEF [#g15f08d4]
深さ優先にクラス定義の中に入っていくことにします。という...
#code(C){{
case NODE_DEF:
{
int sym = new_msym(s, (mrb_sym)tree->car);
int idx = lambda_body(s, tree->cdr, 0);
genop(s, MKOP_A(OP_TCLASS, cursp()));
push();
genop(s, MKOP_Abc(OP_LAMBDA, cursp(), idx, OP_L_MET...
pop();
genop(s, MKOP_AB(OP_METHOD, cursp(), sym));
if (val) {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
}
}
break;
}}
lambda_body()ですがちょっと長めです。部分部分にわけて説明...
#code(C){{
static int
lambda_body(codegen_scope *s, node *tree, int blk)
{
int idx, base = s->idx;
mrb_code c;
s = scope_new(s->mrb, s, tree->car);
idx = s->idx;
}}
まず新しいスコープを作っています。
#code(C){{
if (blk) {
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
lp->pc1 = new_label(s);
}
}
}}
ブロック定義の場合の処理がされていますが今はメソッド定義...
#code(C){{
tree = tree->cdr;
if (tree->car) {
int ma, oa, ra, pa, ka, kd, ba, a;
int pos, i;
node *n, *opt;
ma = node_len(tree->car->car);
n = tree->car->car;
while (n) {
n = n->cdr;
}
oa = node_len(tree->car->cdr->car);
ra = tree->car->cdr->cdr->car ? 1 : 0;
pa = node_len(tree->car->cdr->cdr->cdr->car);
ka = kd = 0;
ba = tree->car->cdr->cdr->cdr->cdr ? 1 : 0;
a = ((ma & 0x1f) << 18)
| ((oa & 0x1f) << 13)
| ((ra & 1) << 12)
| ((pa & 0x1f) << 7)
| ((ka & 0x1f) << 2)
| ((kd & 1)<< 1)
| (ba & 1);
s->ainfo = (((ma+oa) & 0x3f) << 6) /* (12bits = 6:1:5...
| ((ra & 1) << 5)
| (pa & 0x1f);
genop(s, MKOP_Ax(OP_ENTER, a));
}}
tree->carは引数情報が入っています。それぞれ、
:ma|通常引数
:oa|オプション引数
:ra|残余引数
:pa|ポスト引数((いつの間にかこんな引数種別増えたんですね...
:ka|キーワード引数((CRubyでもまだサポートされていません))
:ba|ブロック引数
を示しており、その情報がコードに埋め込まれるようです。
#code(C){{
pos = new_label(s);
for (i=0; i<oa; i++) {
new_label(s);
genop(s, MKOP_Ax(OP_JMP, 0));
}
if (oa > 0) {
genop(s, MKOP_Ax(OP_JMP, 0));
}
opt = tree->car->cdr->car;
i = 0;
while (opt) {
int idx;
dispatch(s, pos+i);
codegen(s, opt->car->cdr, VAL);
idx = lv_idx(s, (mrb_sym)opt->car->car);
pop();
genop_peep(s, MKOP_AB(OP_MOVE, idx, cursp()), NOVAL);
i++;
opt = opt->cdr;
}
if (oa > 0) {
dispatch(s, pos+i);
}
}
}}
オプション引数の処理を行っています。コードをそのまま解説...
def foo(a = 1, b = 'xxx')
end
上記のRubyスクリプトの実行コードは以下のようになります。
$ ./mruby.exe -c --verbose ../../optarg.rb
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か...
なんでこんなめんどくさいことを・・・、と思われるかもしれ...
def bar
123
end
def foo(a = 1, b = bar)
end
./mruby.exe -c --verbose ../../optarg2.rb
irep 118 nregs=6 nlocals=5 pools=0 syms=1
000 OP_ENTER 0:2:0:0:0:0:0
001 OP_JMP 004
002 OP_JMP 005
003 OP_JMP 009
004 OP_LOADI R1 1
005 OP_LOADSELF R5
006 OP_LOADNIL R6
007 OP_SEND R5 'bar' 0
008 OP_MOVE R2 R5
009 OP_RETURN R4
と、bの値を決定するために別メソッドが呼び出されることもあ...
話が長くなりましたが以上を踏まえてオプション引数処理とし...
+まず、OP_JMPを埋め込み
+各オプション引数について
++OP_JMPのジャンプ先を決定し(dispatch()に書かれています)
++デフォルト式に対応するコードを生成し
++デフォルト式の結果を格納
+最後にメソッド本体へのOP_JMP先を決定して終わり
ということをしています。ちなみに、genop_peep()というのは...
#code(C){{
codegen(s, tree->cdr->car, VAL);
pop();
c = s->iseq[s->pc-1];
if (GET_OPCODE(c) != OP_RETURN || GETARG_B(c) != OP_R_N...
genop(s, MKOP_AB(OP_RETURN, cursp(), OP_R_NORMAL));
}
if (blk) {
loop_pop(s, NOVAL);
}
scope_finish(s, idx);
return idx - base;
}
}}
ようやく、メソッド定義のコードを生成し、最後にreturnの処...
**NODE_ASGN [#f2f06f88]
次はNODE_ASGNです。
#code(C){{
case NODE_ASGN:
codegen(s, tree->cdr, VAL);
pop();
gen_assignment(s, tree->car, cursp(), val);
break;
}}
cdrは右辺、carが格納先の左辺です。
gen_assignment()は代入先の種類に応じて生成するコードを変...
#code(C){{
static void
gen_assignment(codegen_scope *s, node *node, int sp, int ...
{
int idx;
int type = (intptr_t)node->car;
node = node->cdr;
switch ((intptr_t)type) {
case NODE_GVAR:
...
case NODE_LVAR:
idx = lv_idx(s, (mrb_sym)node);
if (idx > 0) {
if (idx != sp) {
genop_peep(s, MKOP_AB(OP_MOVE, idx, sp), val);
}
break;
}
else { /* upvar */
int lv = 0;
codegen_scope *up = s->prev;
while (up) {
idx = lv_idx(up, (mrb_sym)node);
if (idx > 0) {
genop_peep(s, MKOP_ABC(OP_SETUPVAR, sp, idx, lv...
break;
}
lv++;
up = up->prev;
}
// assert(up!=0);
}
break;
case NODE_IVAR:
...
case NODE_CVAR:
...
case NODE_CONST:
...
case NODE_COLON2:
...
case NODE_CALL:
...
default:
...
}
if (val) push();
}
}}
前半部分は普通のローカル変数、後半(else部分)はブロック...
ちなみに、NODE_CALLのcaseは配列要素やハッシュ要素への代入...
**NODE_CALL [#n32d968f]
次にNODE_CALL。
#code(C){{
case NODE_CALL:
gen_call(s, tree, 0, 0, val);
break;
}}
gen_call()に進みます。長いのでまたわけて解説します。
#code(C){{
static void
gen_call(codegen_scope *s, node *tree, mrb_sym name, int ...
{
mrb_sym sym = name ? name : (mrb_sym)tree->cdr->car;
int idx;
int n = 0, noop = 0, sendv = 0;
codegen(s, tree->car, VAL); /* receiver */
}}
コメント通りにレシーバーを生成しているようです。
#code(C){{
idx = new_msym(s, sym);
tree = tree->cdr->cdr->car;
if (tree) {
n = gen_values(s, tree->car);
if (n < 0) {
n = noop = sendv = 1;
push();
}
}
if (sp) {
if (sendv) {
pop();
genop(s, MKOP_AB(OP_ARYPUSH, cursp(), sp));
push();
}
else {
genop(s, MKOP_AB(OP_MOVE, cursp(), sp));
push();
n++;
}
}
}}
引数を設定するコードを生成しています。gen_values()は引数...
#code(C){{
if (tree && tree->cdr) {
noop = 1;
codegen(s, tree->cdr, VAL);
pop();
}
else {
genop(s, MKOP_A(OP_LOADNIL, cursp()));
}
pop_n(n+1);
}}
ブロックを積む処理をしているようです。
#code(C){{
{
const char *name = mrb_sym2name(s->mrb, sym);
if (!noop && name[0] == '+' && strlen(name) == 1) {
genop(s, MKOP_ABC(OP_ADD, cursp(), idx, n));
}
(中略。-, <, <=, >, >=について同様の特別処理)
else {
if (sendv) n = CALL_MAXARGS;
genop(s, MKOP_ABC(OP_SEND, cursp(), idx, n));
}
}
if (val) {
push();
}
}
}}
最後にメソッド呼び出しのコードを埋め込んでいます。+など、...
**NODE_BLOCK [#v274929e]
次はNODE_BLOCKです。
#code(C){{
case NODE_BLOCK:
{
int idx = lambda_body(s, tree, 1);
genop(s, MKOP_Abc(OP_LAMBDA, cursp(), idx, OP_L_BLO...
push();
}
break;
}}
NODE_CALLと同様にlambda_body()を呼び出してます。ただし、...
#code(C){{
static int
lambda_body(codegen_scope *s, node *tree, int blk)
{
int idx, base = s->idx;
mrb_code c;
s = scope_new(s->mrb, s, tree->car);
idx = s->idx;
if (blk) {
struct loopinfo *lp = loop_push(s, LOOP_BLOCK);
lp->pc1 = new_label(s);
}
(本体処理。詳しくは上記参照)
if (blk) {
loop_pop(s, NOVAL);
}
scope_finish(s, idx);
return idx - base;
}
}}
loopinfoにはブロック内でnextやbreakを実行したときにどこに...
**NODE_IF [#mdc96c26]
次にNODE_IFです。
#code(C){{
case NODE_IF:
{
int pos1, pos2;
node *e = tree->cdr->cdr->car;
codegen(s, tree->car, VAL);
pop();
pos1 = new_label(s);
genop(s, MKOP_AsBx(OP_JMPNOT, cursp(), 0));
codegen(s, tree->cdr->car, val);
if (e) {
if (val) pop();
pos2 = new_label(s);
genop(s, MKOP_sBx(OP_JMP, 0));
dispatch(s, pos1);
codegen(s, e, val);
dispatch(s, pos2);
}
else {
if (val) {
pop();
genop(s, MKOP_A(OP_LOADNIL, cursp()));
push();
}
dispatch(s, pos1);
}
}
break;
}}
やっていることは以下の通りです。
+条件節に対応するコードを生成
+条件が成り立たないときのジャンプ命令を埋め込み
+then節に対応するコードを生成
+else節がある場合
++then節の末尾にif全体の末尾にジャンプする命令を埋め込み
++条件が成り立たない場合のジャンプ先としてelse節の先頭を...
++else節に対応するコードを生成
+else節がない場合
++条件が成り立たない場合のジャンプ先としてif全体の末尾を...
言葉だけだとわかりにくいと思うので実際に変換したコードを...
if true
1
else
2
end
irep 116 nregs=3 nlocals=2 pools=0 syms=0
000 OP_LOADT R2
001 OP_JMPNOT R2 004
002 OP_LOADI R2 1
003 OP_JMP 005
004 OP_LOADI R2 2
005 OP_STOP
**NODE_DSTR [#zeb37344]
最後にNODE_DSTRについて見てみます。
#code(C){{
case NODE_DSTR:
if (val) {
node *n = tree;
codegen(s, n->car, VAL);
n = n->cdr;
while (n) {
codegen(s, n->car, VAL);
pop(); pop();
genop(s, MKOP_AB(OP_STRCAT, cursp(), cursp()+1));
push();
n = n->cdr;
}
}
else {
node *n = tree;
while (n) {
if ((intptr_t)n->car->car != NODE_STR) {
codegen(s, n->car, NOVAL);
}
n = n->cdr;
}
}
break;
}}
valの値によって大きく処理が分かれています。今まで特に触れ...
**nregs [#y2075d6b]
最後の最後にnregsについて。レジスタ数っぽいと上で書きまし...
#code(C){{
#define nregs_update do {if (s->sp > s->nregs) s->nregs =...
static void
push_(codegen_scope *s)
{
if (s->sp > 511) {
codegen_error(s, "too complex expression");
}
s->sp++;
nregs_update;
}
}}
そのスコープでの最大スタック伸長数のようです。
*おわりに [#p8075c82]
以上、mrubyのコード生成を見てきました。基本的には淡々とNO...
ページ名: