スクリプトの解析が終わったら今度はコード生成をするようです。ここら辺、MRI*1よりYARVと似ているような気がします。というわけで、mrb_generate_code()を読むことにします。
それではさっそくmrb_generate_code()を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | - | | | | | | | ! - | | - | ! | | - | ! - ! | ! |
|
codegen()はnodeの種類に応じて実行コード生成を行う巨大なswitch文です。このcodegen()が実行コード生成の本体のようです。また、codegen_scopeというのが現在のスコープに関する情報を持っているようなので実行コード生成読解の鍵となるデータ構造と考えていいでしょう。
コード生成時にデータ構造が具体的にどのようになるかは実例で確認、てことでさっそく実際にコード生成をしてみましょう。スクリプト生成の時にも書きましたがmrubyに--verboseオプションを付けると実行コードがダンプされます。
./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つのコードブロックが出力されることがわかります。また、以下のことがわかります。
なお、各実行コードの説明はsrc/opcode.h内に書かれています。
ところで、irepのrepって何の略なんでしょうね?iはInstructionだと思いますが。
ちなみに、irepの番号がいきなり116から始まっているのは初期化時にRubyで書かれたライブラリの定義コードを読み込んでいるためと思われます。(つまり、115以前はライブラリ定義のirep)
ではnodeツリーをたどってどうやってコードが生成されていくかを眺めていきましょう。まずはルートのNODE_SCOPEです。なお、今後示すcase文はcodegen()の巨大なswitch文中のcase文です。
1 2 3 |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| - | | | - | | ! - | | ! | | | ! |
|
というわけで新しいスコープを作ってcodegen()を再帰呼び出ししています。トップレベルなのでs->iseqはNULL、すなわち、実行コードの最後はOP_STOPが埋め込まれるようです。
scope_new()、scope_finish()も見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | - | | | | | | | | | | | | | | | | | | | | | | | | | | | ! - | | | | | | | | - | | ! - | | ! - | | ! | | | | | ! |
|
nlocalsがローカル変数の数と一致しない理由がわかりました。そういえばYARVでも特殊変数用にスタックを割り当てていた気がします。本当にそうなのかはコード実行のところで見ることにします。
また、scope_finish()を見るとわかるようにスコープを閉じるとmrb_stateにirepが追加されるようです。
次にNODE_CLASSです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| - | | - | | ! - | | ! - | ! - | ! - | | ! | | | | | - | ! ! |
|
parse.yに書かれたNODE_CLASSの構造を考えると初めのif文で生成しているコードはクラスを定義するモジュールの参照、2つ目のif文でスーパークラスを参照しているようです。今回はトップレベルに、スーパークラス指定はなしなので両方OP_LOADNILが埋め込まれることになります。その後、クラス定義をしているirepを作ってそれを呼び出すコードを生成しています。
深さ優先にクラス定義の中に入っていくことにします。というわけでNODE_DEFを見てみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| - | | | | | | | | - | ! ! |
|
lambda_body()ですがちょっと長めです。部分部分にわけて説明します。
1
2
3
4
5
6
7
8
| - | | | | | |
|
まず新しいスコープを作っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | - | | ! ! | | | - | - | | | | | | - | ! | | | | | | | | | | | | | | | | | |
|
tree->carは引数情報が入っています。それぞれ、
を示しており、その情報がコードに埋め込まれるようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | - | | ! - | ! - | | | | | | | | | ! - | ! ! |
|
オプション引数の処理を行っています。コードをそのまま解説してもわかる人は少数でしょうからまず実例を示します。
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
実行系はまだ見てませんが何となく以下の挙動をするんだろうなぁということがわかります。
なんでこんなめんどくさいことを・・・、と思われるかもしれませんがRubyのオプション引数はデフォルト値ではなく、デフォルト「式」なのでこうしないといけません。例えば、以下のスクリプトのように、
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の値を決定するために別メソッドが呼び出されることもあるのです。
話が長くなりましたが以上を踏まえてオプション引数処理として何をやっているかというと、
ということをしています。ちなみに、genop_peep()というのは冗長なコードを簡約する処理なようです。また、上記のコードではOP_JMPが絶対アドレスジャンプになっていますが、実際のコードは相対アドレスジャンプになっているようなのでdispatch()を見るときは注意してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 | - | ! - | ! ! |
|
ようやく、メソッド定義のコードを生成し、最後にreturnの処理を入れて*4終わりです。