特化命令(続)
その前に、前回のささださんにコメントいただいた点について、まとめます。YARV Maniacs 第9回 での解説に加えて、現在ではsendメソッドの特化機能が追加されているそうです。
id = :method
...
obj.send(id, args)
という記述を
obj.method(args)
と書いたのに近い効率で実行してくれます。具体的には
// iseq_specialized_instruction より抜粋 if (mid == idSend || mid == id__send__ || mid == id__send || mid == idFuncall || mid == id__send_bang) { OPERAND_AT(iobj, 3) |= INT2FIX(VM_CALL_SEND_BIT); }
メソッドの名前が上記の5個のどれかだったら、フラグを立てています。それぞれのシンボルは初期化の途中で
// Init_vm @ yarvcore.c より抜粋 idSend = rb_intern("send"); id__send__ = rb_intern("__send__"); id__send = rb_intern("__send"); idFuncall = rb_intern("funcall"); id__send_bang = rb_intern("__send!");
このように設定されているものです。sendってこんなに種類あったんですね。。。privateメソッドが呼べたり呼べなかったり、再定義してよかったりすべきでなかったり、廃止予定だったり、いろいろあるみたいです。どれもメソッド名のシンボルを受け取って、そのメソッドを呼び出してくれるものです。
VM_CALL_SEND_BIT が立っているsend命令をYARVのVMが実行する時には、
// DEFINE_INSN send @ insns.def より抜粋 klass = CLASS_OF(recv); mn = eval_method_search(id, klass, ic); if ((flag & VM_CALL_SEND_BIT) && mn && nd_type(mn->nd_body) == NODE_CFUNC) { NODE *node = mn->nd_body; extern VALUE rb_f_funcall(int argc, VALUE *argv, VALUE recv); extern VALUE rb_f_send(int argc, VALUE *argv, VALUE recv); if (node->nd_cfnc == rb_f_funcall || node->nd_cfnc == rb_f_send) {
まず再定義されていないこと(この場合、:send に対応するメソッドがCで実装されたrb_f_send関数であること)を確認して、確認できたら、rb_f_send は呼ばずにその場でsendの作業をしてしまいます。つまり、
VALUE sym = TOPN(num - 1);
id = SYMBOL_P(sym) ? SYM2ID(sym) : rb_to_id(sym);
... 略 ...
mn = rb_method_node(klass, id);
第一引数のメソッド名をもつメソッドを持ってきて、
/* shift arguments */ for (i=1; i<num; i++) { GET_SP()[-num+i-1] = GET_SP()[(-num+i-1)+1]; }
第二引数以降をずらして実際の引数にして・・・あとは普通のメソッド呼び出しと共通の処理になります。sendメソッド自体の呼び出し処理を省けたことになります。