Japanese

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

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

前回 [http://www.koooge.com/entry/2017/01/12/233500]の続き。今回はまず構造体の構成を確認していきます。 &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_structのbody.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が組み込み関数ポインタ

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

スポンサードリンク [asin:4274050653:detail]