ひとり勉強会

ひとり楽しく勉強会

特化命令(続)

その前に、前回のささださんにコメントいただいた点について、まとめます。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命令をYARVVMが実行する時には、

// 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メソッド自体の呼び出し処理を省けたことになります。