Japanese

Rubyのメソッド呼び出しの流れ(1)

  • このエントリーをはてなブックマークに追加

Rubyの#13053を読んでみる(1) [http://www.koooge.com/entry/2016/12/22/013733]から見ていった中で、 rubyでArray#select!を書いた後に、どうのようにrb_ary_select_bangが呼ばれているのか気になったので見ていきます。 (現在 svnのrevisionは57251)

以下、RubyのしくみとYarvManiacsを参考に、内容を咀嚼していく物になります。

既存の情報をググる まずググって出てきたるびまを読みました。 YARV作者のささださんが書かれたYarvManiacsという連載が、私の知りたいこととドンピシャでハマっているようなので大変参考になりました。

[http://magazine.rubyist.net/?0012-YarvManiacs:title]

この例では、Array#lengthを追っていて、メソッド呼び出しはYARVではsend命令らしい。オペランドは5つ。

最新環境(Ruby2.4)でやってみる 最新のrubyでもやってみます。

Array#length るびまの例をそのままYARVを出力。

code = «END [1,2].length END

puts RubyVM::InstructionSequence.compile(code).disasm

出力

== disasm: #<ISeq:@>================================ 0000 trace 1 ( 1) 0002 duparray [1, 2] 0004 opt_length <callinfo!mid:length, argc:0, ARGS_SIMPLE>, 0007 leave

なるほど?send命令じゃないです。 opt_lengthという専用の命令に置き換えられているっぽい。 高速化の為でしょうか?オペランドも2つしかありません。 これはYarvManiacsの連載第9回に答えがありました。

[http://magazine.rubyist.net/?0017-YarvManiacs:title]

特価命令っていうみたいで、 るびまの第6回の時代のrubyとは、処理系が変わっていますね。 ということは、当然今日までも結構変わっていそうですね・。・;

Array#select! テスト用に用いたコード。#13053のままですが、簡略化のためにpを削っています。

code = «END ary = [1,2,3,4,5] ary.select! { |i| ary.clear if i==5; false }

p ary

p ary.size

END

puts RubyVM::InstructionSequence.compile(code).disasm

結果↓

== disasm: #<ISeq:@>================================ == catch table

catch type: break st: 0008 ed: 0014 sp: 0000 cont: 0014
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] ary
0000 trace 1 ( 1)
0002 duparray [1, 2, 3, 4, 5]
0004 setlocal_OP__WC__0 3
0006 trace 1 ( 2)
0008 getlocal_OP__WC__0 3
0010 send <callinfo!mid:select!, argc:0>, , block in
0014 leave
== disasm: #<ISeq:block in @>=======================
== catch table
catch type: redo st: 0002 ed: 0021 sp: 0000 cont: 0002
catch type: next st: 0002 ed: 0021 sp: 0000 cont: 0021
————————————————————————
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] i
0000 trace 256 ( 2)
0002 trace 1
0004 getlocal_OP__WC__0 3
0006 putobject 5
0008 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0011 branchunless 19
0013 getlocal_OP__WC__1 3
0015 opt_send_without_block <callinfo!mid:clear, argc:0, ARGS_SIMPLE>,
0018 pop
0019 putobject false
0021 trace 512
0023 leave

長々と張り付けておりますが、メソッド呼び出しは

0008 getlocal_OP__WC__0 3 0010 send <callinfo!mid:select!, argc:0>, , block in

のsend命令です。その上がレシーバ。send命令のオペランドは3つ。 YARVのsend命令が:select!のメソッド(のシンボル)を与えている部分っぽいです。 るびまYarvManiact第6回の’ブロック付メソッド呼び出し’が参考になりました。 argc:0から引数は0。だけど、ブロックとしてblock in 部分で<ISeq:block in @命令列を渡しているみたいです。 メソッドにブロックを渡す物は、単に引数を渡す処理方法と比べて特殊ということが見て取れました。

YARVのsend命令の実装 YARVが上記send命令での:select!をどう実行しているか追います。 sendはinsns.defで実装されています。

952/** 953 @c method/iterator 954 @e invoke method. 955 @j メソッド呼び出しを行う。ci に必要な情報が格納されている。 956 */ 957DEFINE_INSN 958send 959(CALL_INFO ci, CALL_CACHE cc, ISEQ blockiseq) 960(…) 961(VALUE val) // inc += - (int)(ci->orig_argc + ((ci->flag & VM_CALL_ARGS_BLOCKARG) ? 1 : 0)); 962{ 963 struct rb_calling_info calling; 964 965 vm_caller_setup_arg_block(th, reg_cfp, &calling, ci, blockiseq, FALSE); 966 vm_search_method(ci, cc, calling.recv = TOPN(calling.argc = ci->orig_argc)); 967 CALL_METHOD(&calling, ci, cc); 968}

C言語チックですが、引数の実装が良くわからない感じになっています。(ここ後日追いたい)。 959行目で引数が3つですが、、これはsend命令のオペランドの数と合致していることを表現しているみたいです。 ciに呼び出したいメソッドの情報が入っています。

この処理のざっくり解釈としては、

  • vm_caller_setup_arg_block(): vm_args.cに定義されている。ブロックを渡された場合は、 calling->block_handlerにセットする関数みたい(いつかちゃんと読む)

  • vm_search_method(): vm_insnhelper.cに定義されている。コールキャッシュに乗っていれば即返し、なければ rb_call_cache構造対に色々セットしている。 * 1230 cc->me = rb_callable_method_entry(klass, ci->mid);: me(method entry)を探索している

    • 1232 cc->call = vm_call_general;: ナニコレ?
  • CALL_METHODマクロ: メソッド呼び出しの実施

です。ところでいちいちインデントが不揃いで読みづらいところが気になります。。

CALL_METHODマクロ メソッド実施の処理マクロですが、キモっぽいので読んでいきます。

124#define CALL_METHOD(calling, ci, cc) do {
125 VALUE v = (*(cc)->call)(th, GET_CFP(), (calling), (ci), (cc));
126 if (v == Qundef) {
127 RESTORE_REGS();
128 NEXT_INSN();
129 }
130 else {
131 val = v;
132 }
133} while (0)

do{ }while(0)はマクロでブロックっぽく書くための小技なので無視。 125行目の(*(cc)->call)が目的の関数への関数ポインタみたいです。 cc(rb_call_cache構造体)の構造を知っておく必要がありそうですね。 他のRESTORE_REGS、NEXT_INSNマクロは、次の命令に進めるマクロのようです。

つづきは次回 [http://www.koooge.com/entry/2017/01/11/123738]

スポンサードリンク [asin:4274050653:detail]