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

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

前回 [http://www.koooge.com/entry/2017/01/11/123738]からの続き。 rubyのメソッドのタイプをみていきます。 method.hのrb_method_type_tです。

100typedef enum { 101 VM_METHOD_TYPE_ISEQ, // ユーザー定義は基本これ 102 VM_METHOD_TYPE_CFUNC, // 組み込みメソッドはこれ 103 VM_METHOD_TYPE_ATTRSET, 104 VM_METHOD_TYPE_IVAR, 105 VM_METHOD_TYPE_BMETHOD, 106 VM_METHOD_TYPE_ZSUPER, 107 VM_METHOD_TYPE_ALIAS, // Rubyのしくみにのってない。 108 VM_METHOD_TYPE_UNDEF, 109 VM_METHOD_TYPE_NOTIMPLEMENTED, 110 VM_METHOD_TYPE_OPTIMIZED, /* Kernel#send, Proc#call, etc / 111 VM_METHOD_TYPE_MISSING, / wrapper for method_missing(id) */ 112 VM_METHOD_TYPE_REFINED, 113 114 END_OF_ENUMERATION(VM_METHOD_TYPE) // 115} rb_method_type_t;

12種類のメソッドtypeがあります。この各メソッドについての説明は、例によってRubyのしくみ第4章に載っていますので、ご参照ください。ユーザー定義のほとんどは VM_METHOD_TYPE_ISEQだそうです。ISEQは命令列の略。組み込みメソッドはVM_METHOD_TYPE_CFUNCです。また、 VM_METHOD_TYPE_ALIAS は新しい定義みたいで、Rubyのしくみに載っていませんでした。名前から察するに、Array#lengthに対するArray#sizeみたいなのでしょうかね。

さてselect!はどれかというと、組み込みメソッドですのでVM_METHOD_TYPE_CFUNCですね。これはarray.cを見てもわかります。

6149 rb_define_method(rb_cArray, “select”, rb_ary_select, 0); 6150 rb_define_method(rb_cArray, “select!”, rb_ary_select_bang, 0);

rb_define_methodの定義はclass.c

1507void 1508rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc) 1509{ 1510 rb_add_method_cfunc(klass, rb_intern(name), func, argc, METHOD_VISI_PUBLIC); 1511}

これから可視性はMETHOD_VISI_PUBLICというところも見て取れます。 で、rb_add_method_cfunc()の定義はvm_method.c

135void 136rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_visibility_t visi) 137{ 138 if (argc < -2 || 15 < argc) rb_raise(rb_eArgError, “arity out of range: %d for -2..15”, argc); 139 if (func != rb_f_notimplement) { 140 rb_method_cfunc_t opt; 141 opt.func = func; 142 opt.argc = argc; 143 rb_add_method(klass, mid, VM_METHOD_TYPE_CFUNC, &opt, visi); // CFUNCが定義されている 144 } 145 else { 146 rb_define_notimplement_method_id(klass, mid, visi); 147 } 148}

なので、VM_METHOD_TYPE_CFUNCですね。 メソッド登録はrb_add_method()で行っていることもわかります。こうみると分かりやすい関数名です。

vm_call_method_each_typeに戻る vm_insnhelper.cに戻ってvm_call_method_each_type()の処理をみます。

2143 case VM_METHOD_TYPE_CFUNC: 2144 CI_SET_FASTPATH(cc, vm_call_cfunc, TRUE); // cc->call = vm_call_cfuncするマクロ。何これ? 2145 return vm_call_cfunc(th, cfp, calling, ci, cc); // これがselect!への関数ポインタを返すはず

(snip)

1843static VALUE 1844vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) 1845{ 1846 CALLER_SETUP_ARG(reg_cfp, calling, ci); // 何かわからないけどccを渡してないので気にしない 1847 return vm_call_cfunc_with_frame(th, reg_cfp, calling, ci, cc); // select!への関数ポインタを返すはず 1848}

CI_SET_FASTPATHという謎マクロがでてきましが、これはググったら説明がありました。 ようするに時間的局所性を狙った長さ1のキャッシュっぽいです。これ効果あるんですかね?

[ http://d.hatena.ne.jp/nagachika/20121015/ruby_trunk_changes_37180_37195:embed:cite ]

ところで、このnagachikaさんという方、crubyのすべてのコミットを追っかけていてマジすごい・・。

話を戻して、vm_call_cfunc_with_frame()みます。ついにここが終点っぽいぞ!

1729static VALUE 1730vm_call_cfunc_with_frame(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) 1731{ 1732 VALUE val; 1733 const rb_callable_method_entry_t *me = cc->me; 1734 const rb_method_cfunc_t *cfunc = vm_method_cfunc_entry(me); // ここでCFUNC用のメソッドエントリを探索しているっぽいぞ! 1735 int len = cfunc->argc; 1736 1737 VALUE recv = calling->recv; 1738 VALUE block_handler = calling->block_handler; 1739 int argc = calling->argc; 1740 1741 RUBY_DTRACE_CMETHOD_ENTRY_HOOK(th, me->owner, me->def->original_id); // メッチャ面白そうな謎マクロ① 1742 EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->def->original_id, ci->mid, me->owner, Qundef); // メッチャ面白そうな謎マクロ② 1743 1744 vm_push_frame(th, NULL, VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, recv, 1745 block_handler, (VALUE)me, 1746 0, th->cfp->sp, 0, 0); (snip) 1750 reg_cfp->sp -= argc + 1; 1751 VM_PROFILE_UP(R2C_CALL); 1752 val = (*cfunc->invoker)(cfunc->func, recv, argc, reg_cfp->sp + 1); // select!への関数ポインタの代入部分ぽい (snip) 1758 rb_vm_pop_frame(th); 1759 1760 EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->def->original_id, ci->mid, me->owner, val); // メッチャ面白そうな謎マクロ② 1761 RUBY_DTRACE_CMETHOD_RETURN_HOOK(th, me->owner, me->def->original_id); // メッチャ面白そうな謎マクロ① 1762 1763 return val; // select!(というかrb_ary_select_bang)への関数ポインタを返す 1764}

謎マクロがいくつかありますが、これはいつか追うとして無視します。 ここでは、1734行目でvm_method_cfunc_entry()という関数で関数エントリを探索し、1752行目でrb_method_cfunc_t という構造体でselect!への関数ポインタを管理しているようです。

vm_method_cfunc_entry()はreturn &me->def->body.cfunc;となっていて、これが関数エントリだったらしい。 ということは、全然気づきませんでしたが、これまでずっと引数として渡してきていたんですね~~。構造体の連結が長くなってきて良くわかりません・・・、次回整理します。

cfunc->invokerがまだちょっと良くわからないのでmethod.hを見ます。

127typedef struct rb_method_cfunc_struct { 128 VALUE (*func)(ANYARGS); // rb_ary_select_bang関数へのポインタが格納されていそうだ 129 VALUE (*invoker)(VALUE (*func)(ANYARGS), VALUE recv, int argc, const VALUE *argv); // なんだこれ(・ω・`)rz 130 int argc; // 引数の数を格納するっぽい 131} rb_method_cfunc_t;

メンバ数は少ないですが、invokerのイメージがつかめません・・・。 もう少しでメソッド呼び出しの流れが掴めそうな気がしますが、これまで構造体が色々でてきて、メンバの用途も不明点が多く、複雑になってきました。

次回、メソッドエントリ周りの構造体を整理することと、 rb_define_method(rb_cArray, “select!”, rb_ary_select_bang, 0); を逆から追ってみることで、このあたりの理解を深めたいと思います。

次回 [http://www.koooge.com/entry/2017/01/14/162730]

スポンサードリンク

[asin:4274050653:detail]