ワナビーエンジニアのブログ

なんでもいいから文化的な生活を送りたい

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

前回からの続き。 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のキャッシュっぽいです。これ効果あるんですかね?

d.hatena.ne.jp

ところで、この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); を逆から追ってみることで、このあたりの理解を深めたいと思います。

次回

スポンサードリンク

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-