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

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

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

前回の続き。今回はまず構造体の構成を確認していきます。 &me->def->body.cfuncのあたりです。

meというのはrb_callable_method_entry_t型ですが、このmeは見覚えがあって、send関数で使用しているrb_call_cache構造体のメンバとなっています。

240struct rb_call_cache {
(snip)
246    const rb_callable_method_entry_t *me;
(snip)
255};

構造体を整理します。上から順番にポインタを保持しています。

  • rb_call_cache: メソッド呼び出し用に一時的にメソッド呼び出しを格納する用のテーブル。vm_core.hに定義
  • rb_callable_method_entry_t: メソッドエントリを格納している構造体。method.hに定義
  • rb_method_definition_struct: メソッドの情報を格納するテーブル。つまりこれがrb_ary_select_bangへのポインタを持っている。method.hに定義
  • body.cfunc: これはunion型のbodyと、そのメンバcfunc。rb_ary_select_bangへの関数ポインタはこいつが保持している。メソッドがISEQやCFUNCやATTRとどれにも対応できるようにunion型にしている。そのうちのrb_method_cfunc_t型cfunc(select!は組み込みメソッドだから)

整理したら4つ程度なんですね。ついに見えてきました!ではrb_method_definition_structbody.cfuncからrb_ary_select_bangを呼ぶにあたって、関数ポインタをセットしているのが誰かを追っていきたいと思います、。

rb_define_methodを追う

今度はメソッド呼び出しとは逆に、関数を定義して関数ポインタを設定しているのは誰か?というところを追っていきます。 具体的にはarray.cでメソッドを定義している下記を丁寧に追っていきます。

rb_define_method(rb_cArray, "select!", rb_ary_select_bang, 0);

rb_define_method()の、第二引数にメソッドが名前select!、第三引数がその実装であるrb_ary_select_bangへの関数ポインタです。 これは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); // 簡単なラッパー定義。実装はrb_add_method_cfunc。
1511}

rb_intern(name)という関数で、select!をmid(メソッドID)という数値に変換しています。

次、rb_add_method_cfuncをみます。vm_method.cに定義されています。

35void
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; // ★ここに関数ポインタを格納。ただし構造体がrb_method_cfunc_t
142    opt.argc = argc;
143    rb_add_method(klass, mid, VM_METHOD_TYPE_CFUNC, &opt, visi); // ここにmidとoptを渡している
144    }
145    else {
146    rb_define_notimplement_method_id(klass, mid, visi);
147    }
148}

// 続いてrb_add_methodの実装をみます。

629rb_method_entry_t *
630rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_visibility_t visi) // optsをvoid型ポインタとしてる。VALUE型として扱うため。
631{
632    rb_method_entry_t *me = rb_method_entry_make(klass, mid, klass, visi, type, NULL, mid, opts); // rb_method_entry_tを作成している?けど特にmeをこの先使っていない。
633
634    if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) {
635    method_added(klass, mid);
636    }
637
638    return me; // 今回の場合、呼び出し元で使ってない
639}

なんだか単純な作りです。逆に言うと、メソッドを定義する側はきれいな設計だということでしょうか。 先にこっちから読めば理解しやすかったかもしれません。(;´Д`)rz

rb_method_entry_make()の戻り値はとくに使用していないので、その中でmidとoptsを使っていそうです。midとoptsを追います。 同じくvm_method.cです。

491/*
492 * klass->method_table[mid] = method_entry(defined_class, visi, def)
493 *
494 * If def is given (!= NULL), then just use it and ignore original_id and otps.
495 * If not given, then make a new def with original_id and opts.
496 */
497static rb_method_entry_t *
498rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibility_t visi,
499             rb_method_type_t type, rb_method_definition_t *def, ID original_id, void *opts)
500{
501    rb_method_entry_t *me;
(snip)
575    /* create method entry */
576    me = rb_method_entry_create(mid, defined_class, visi, NULL); // ★method_entryを作っていそう
577    if (def == NULL) def = method_definition_create(type, original_id);
578    method_definition_set(me, def, opts); // ★optsを渡している。method_entryにセットしてそう
(snip)
597    rb_id_table_insert(mtbl, mid, (VALUE)me);
(snip)
607    return me;
608}

この関数、100行弱の関数ですがインデントがめためたで読むのがつらい・・。コメントがあるのが幸いです。

この中でmidとoptsだけを見てみると、何やらrb_method_entry_t型のme(メソッドエントリ)を作っています。 そして578行目で、そのメソッドエントリにrb_ary_select_bangへの関数ポインタを持っているoptsをsetしていそうです。 なのでrb_method_entry_create関数とmethod_definition_set関数を見てみます。

rb_method_entry_craete

rb_method_entry_createの中身をみます。 vm_method.cに書かれています。

392rb_method_entry_t *
393rb_method_entry_create(ID called_id, VALUE klass, rb_method_visibility_t visi, const rb_method_definition_t *def)
394{
395    rb_method_entry_t *me = rb_method_entry_alloc(called_id, klass, filter_defined_class(klass), def); //メモリ確保
396    METHOD_ENTRY_FLAGS_SET(me, visi, ruby_running ? FALSE : TRUE);
397    if (def != NULL) method_definition_reset(me);
398    return me;
399}

// rb_method_entry_tの領域確保している関数のようだ。一応rb_method_entry_allocの中身を見る

371static rb_method_entry_t *
372rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, const rb_method_definition_t *def)
373{
374    rb_method_entry_t *me = (rb_method_entry_t *)rb_imemo_new(imemo_ment, (VALUE)def, (VALUE)called_id, owner, defined_class); // rb_imemo_newでメモリ確保する。midはcalled_idとして使っています。
375    return me;
376}

やっていることはrb_method_entry_tの領域確保だけのようですが、ここで出てきたrb_imemo_newは何でしょうか?

探してみると、internal.hもしくはgc.cにその定義があるようです。 どうやら、rubyの内部で使われているnew実装のようで、GCによしなにしてもらうようみたいです。 と、いうことは、これまで引き渡してきたmid(メソッドID)は、called_idというメモリ管理用のidと同値としていることが分かります。

method_definition_set

次に、method_definition_setの実装を読んでいきます。同じくvm_method.cに定義されています。

231static void
232method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *def, void *opts)
233{
234    *(rb_method_definition_t **)&me->def = def;
235
236    if (opts != NULL) {
237    switch (def->type) {
(snip)
258      case VM_METHOD_TYPE_CFUNC:
259        {
260        rb_method_cfunc_t *cfunc = (rb_method_cfunc_t *)opts; // これだ!
261        setup_method_cfunc_struct(&def->body.cfunc, cfunc->func, cfunc->argc);
262        return;
263        }

これだー!!!! まさに最初で参照している&me->def->body.cfuncにrb_method_cunf_tを設定している側です。 このoptsから、さらにopts->funcを辿ることで、rb_ary_select_bangへの関数ポインタが参照できますね。

まとめ

これまでrubyのメソッド呼び出し(Array#select!)の流れと、その反対にrubyの組み込みメソッド(rb_ary_select_bang)の登録方法についてみてきました。

  • メソッド呼び出しはsend関数が使われている
    • rb_call_cache構造体に呼び出すメソッドの情報が入っている
    • CALL_METHODマクロでメソッド呼び出し
  • メソッドはruby内部で12種類に分類されている
    • 組み込みメソッドはCFUNC
  • rb_call_cache
    • rb_callable_method_entry_t
      • rb_method_definition_struct
        • body.cfunc(rb_method_cfunc_t)
          • rb_method_cfunc_t型のfuncが組み込み関数ポインタ

パズルが解けたようですごくスッキリし、理解が深まりました。 またメソッド呼び出しの処理を追う間に不可思議なマクロがいくつか残っています。 これらのうち、気になる処理についてはは今後ぼちぼち見ていきたいと思います٩( ᐛ )و

スポンサードリンク

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-