Rubyのメソッド呼び出しの流れ(1)
Rubyの#13053を読んでみる(1)から見ていった中で、
rubyでArray#select!
を書いた後に、どうのようにrb_ary_select_bang
が呼ばれているのか気になったので見ていきます。
(現在 svnのrevisionは57251)
以下、RubyのしくみとYarvManiacsを参考に、内容を咀嚼していく物になります。
既存の情報をググる
まずググって出てきたるびまを読みました。 YARV作者のささださんが書かれたYarvManiacsという連載が、私の知りたいこととドンピシャでハマっているようなので大変参考になりました。
Rubyist Magazine - YARV Maniacs 【第 6 回】 YARV 命令セット (3) メソッドディスパッチ
この例では、Array#lengthを追っていて、メソッド呼び出しはYARVではsend命令らしい。オペランドは5つ。
最新環境(Ruby2.4)でやってみる
最新のrubyでもやってみます。
Array#length
code = <<END [1,2].length END puts RubyVM::InstructionSequence.compile(code).disasm
出力
== disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 trace 1 ( 1) 0002 duparray [1, 2] 0004 opt_length <callinfo!mid:length, argc:0, ARGS_SIMPLE>, <callcache> 0007 leave
なるほど?send命令じゃないです。
opt_length
という専用の命令に置き換えられているっぽい。
高速化の為でしょうか?オペランドも2つしかありません。
これはYarvManiacsの連載第9回に答えがありました。
Rubyist Magazine - YARV Maniacs 【第 9 回】 特化命令
特価命令っていうみたいで、 るびまの第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:<compiled>@<compiled>>================================ == 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>, <callcache>, block in <compiled> 0014 leave == disasm: #<ISeq:block in <compiled>@<compiled>>======================= == 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<Arg> 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>, <callcache> 0011 branchunless 19 0013 getlocal_OP__WC__1 3 0015 opt_send_without_block <callinfo!mid:clear, argc:0, ARGS_SIMPLE>, <callcache> 0018 pop 0019 putobject false 0021 trace 512 0023 leave
長々と張り付けておりますが、メソッド呼び出しは
0008 getlocal_OP__WC__0 3 0010 send <callinfo!mid:select!, argc:0>, <callcache>, block in <compiled>
のsend命令です。その上がレシーバ。send命令のオペランドは3つ。
YARVのsend命令が:select!
のメソッド(のシンボル)を与えている部分っぽいです。
るびまYarvManiact第6回の'ブロック付メソッド呼び出し'が参考になりました。
argc:0
から引数は0。だけど、ブロックとしてblock in <compild>
部分で<ISeq:block in <compiled>@<compiled>
命令列を渡しているみたいです。
メソッドにブロックを渡す物は、単に引数を渡す処理方法と比べて特殊ということが見て取れました。
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
マクロは、次の命令に進めるマクロのようです。
つづきは次回
スポンサードリンク
Rubyのしくみ -Ruby Under a Microscope-
- 作者: Pat Shaughnessy,島田浩二,角谷信太郎
- 出版社/メーカー: オーム社
- 発売日: 2014/11/29
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る