rb_funcallの先って
Rubyでオブジェクトの等値性あたりがどうなっているのか見てみた。Objectクラスの初期化時のメソッド登録は、
2496 rb_define_method(rb_cBasicObject, "==", rb_obj_equal, 1); 2497 rb_define_method(rb_cBasicObject, "equal?", rb_obj_equal, 1); 2498 rb_define_method(rb_cBasicObject, "!", rb_obj_not, 0); 2499 rb_define_method(rb_cBasicObject, "!=", rb_obj_not_equal, 1); : : 2514 rb_define_method(rb_mKernel, "nil?", rb_false, 0); 2515 rb_define_method(rb_mKernel, "===", rb_equal, 1); 2516 rb_define_method(rb_mKernel, "=~", rb_obj_match, 1); 2517 rb_define_method(rb_mKernel, "!~", rb_obj_not_match, 1); 2518 rb_define_method(rb_mKernel, "eql?", rb_obj_equal, 1); (object.c)
となっている。あれっ。「rb_obj_equal」は
91 VALUE 92 rb_obj_equal(VALUE obj1, VALUE obj2) 93 { 94 if (obj1 == obj2) return Qtrue; 95 return Qfalse; 96 } (object.c)
となっていて、これは参照が同じかどうかを判定している。あれ、それじゃあ、
>> a = "hi" => "hi" >> b = "hi" => "hi" >> a == b => true >> a.eql?(b) => true >> a.equal?(b) => false >> a.object_id => 69040500 >> b.object_id => 68879100
の説明がつかないぞと思ったけど、==演算子に関しては、各クラスで再定義しているのだった。string.cでは以下。
2117 VALUE 2118 rb_str_equal(VALUE str1, VALUE str2) 2119 { 2120 int len; 2121 2122 if (str1 == str2) return Qtrue; 2123 if (TYPE(str2) != T_STRING) { 2124 if (!rb_respond_to(str2, rb_intern("to_str"))) { 2125 return Qfalse; 2126 } 2127 return rb_equal(str2, str1); 2128 } 2129 if (!rb_str_comparable(str1, str2)) return Qfalse; 2130 if (RSTRING_LEN(str1) == (len = RSTRING_LEN(str2)) && 2131 memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), len) == 0) { 2132 return Qtrue; 2133 } 2134 return Qfalse; 2135 } 2136 : : 7062 void 7063 Init_String(void) 7064 { 7065 #undef rb_intern 7066 #define rb_intern(str) rb_intern_const(str) 7067 7068 rb_cString = rb_define_class("String", rb_cObject); 7069 rb_include_module(rb_cString, rb_mComparable); 7070 rb_define_alloc_func(rb_cString, str_alloc); 7071 rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1); 7072 rb_define_method(rb_cString, "initialize", rb_str_init, -1); 7073 rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1); 7074 rb_define_method(rb_cString, "<=>", rb_str_cmp_m, 1); 7075 rb_define_method(rb_cString, "==", rb_str_equal, 1); (string.c)
実は===演算子の挙動がよく分からない。これが一番ルーズな比較で、whenと一緒に使うことでis_a?の真偽と一致するような処理をするというのは分かるけど、何だかモヤモヤしている。これもやっぱりクラス階層の中で、よしなに定義されているということで、その挙動は各クラス依存。つまり覚えるしかないってことだ。あるいはRuby的なカンを働かせるということ。
例えば、range.cには、
725 static VALUE 726 range_eqq(VALUE range, VALUE val) 727 { 728 return rb_funcall(range, rb_intern("include?"), 1, val); 729 } : : 934 rb_define_method(rb_cRange, "===", range_eqq, 1);
とかあって、range_eqqで定義されてる。これは間接的にinclude?を呼び出していて、
745 static VALUE 746 range_include(VALUE range, VALUE val) 747 { 748 VALUE beg = RANGE_BEG(range); 749 VALUE end = RANGE_END(range); 750 int nv = FIXNUM_P(beg) || FIXNUM_P(end) || 751 rb_obj_is_kind_of(beg, rb_cNumeric) || 752 rb_obj_is_kind_of(end, rb_cNumeric); 753 754 if (nv || 755 !NIL_P(rb_check_to_integer(beg, "to_int")) || 756 !NIL_P(rb_check_to_integer(end, "to_int"))) { 757 if (r_le(beg, val)) { 758 if (EXCL(range)) { 759 if (r_lt(val, end)) 760 return Qtrue; 761 } 762 else { 763 if (r_le(val, end)) 764 return Qtrue; 765 } 766 } 767 return Qfalse; 768 } 769 else if (TYPE(beg) == T_STRING && TYPE(end) == T_STRING && 770 RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1) { 771 if (NIL_P(val)) return Qfalse; 772 if (TYPE(val) == T_STRING) { 773 if (RSTRING_LEN(val) == 0 || RSTRING_LEN(val) > 1) 774 return Qfalse; 775 else { 776 char b = RSTRING_PTR(beg)[0]; 777 char e = RSTRING_PTR(end)[0]; 778 char v = RSTRING_PTR(val)[0]; 779 780 if (ISASCII(b) && ISASCII(e) && ISASCII(v)) { 781 if (b <= v && v < e) return Qtrue; 782 if (!EXCL(range) && v == e) return Qtrue; 783 return Qfalse; 784 } 785 } 786 } 787 } 788 /* TODO: ruby_frame->this_func = rb_intern("include?"); */ 789 return rb_call_super(1, &val); 790 } 791
という自明でない処理をやってたりする。
色々見てたら、メソッドのinvokeは主に、
rb_funcall(obj, id, argc, ...)
というAPIで行っているらしいことがうっすら分かった。objがレシーバで、id はメソッドのid。引数の数をargcで渡して、後は実際のメソッドの引数をずらずら。メソッドのシンボル(文字列?)からIDへの対応付けは、rb_internがやってるっぽいのかな。
VALUE tmp = rb_funcall(str2, rb_intern("<=>"), 1, str1);
rb_internは、parse.cあたりに記述がある。シンボルとIDの相互変換は、いくつかのAPIに分かれてるようで、C文字列を渡せるrb_internやRuby文字列オブジェクトを渡せるrb_intern_strもある。ただ、実際に名前をルックアップするのは、rb_intern3で、関数の宣言部は、
14707 ID 14708 rb_intern3(const char *name, long len, rb_encoding *enc)
となっている。当たり前だけど、最終的にはC文字列と、その長さ、エンコーディング情報が揃ってはじめて処理ができる。このrb_internのように、引数の種類や数によって多段式に、rb_hoge0、rb_hoge1、rb_hoge2、rb_hoge3のように関数を整理するのは常套手段なのか、結構あちこちにあるような気がする。エントリポイントを多く用意しつつ、処理を分けることができていいってことかしら。賢い。
で、rb_intern3は、st_lookup関数でシンボルのハッシュを引いている。st.cはRuby全体で利用しているハッシュ実装で、確か3種類の目的で使われているとRHGで読んだ。
14707 ID 14708 rb_intern3(const char *name, long len, rb_encoding *enc) 14709 { 14710 const char *m = name; 14711 const char *e = m + len; 14712 unsigned char c; 14713 VALUE str; 14714 ID id; 14715 int last; 14716 int mb; 14717 struct RString fake_str; 14718 fake_str.basic.flags = T_STRING|RSTRING_NOEMBED|FL_FREEZE; 14719 fake_str.basic.klass = rb_cString; 14720 fake_str.as.heap.len = len; 14721 fake_str.as.heap.ptr = (char *)name; 14722 fake_str.as.heap.aux.capa = len; 14723 str = (VALUE)&fake_str; 14724 rb_enc_associate(str, enc); 14725 14726 if (st_lookup(global_symbols.sym_id, str, (st_data_t *)&id)) 14727 return id; 14728 14729 if (rb_cString && !rb_enc_asciicompat(enc)) { 14730 id = ID_JUNK; 14731 goto new_id; 14732 } 14733 last = len-1; 14734 id = 0; 14735 switch (*m) { 14736 case '$': 14737 id |= ID_GLOBAL; 14738 if ((mb = is_special_global_name(++m, e, enc)) != 0) { 14739 if (!--mb) enc = rb_ascii8bit_encoding(); 14740 goto new_id; 14741 } 14742 break; 14743 case '@': 14744 if (m[1] == '@') { 14745 m++; 14746 id |= ID_CLASS; 14747 } 14748 else { 14749 id |= ID_INSTANCE; 14750 } 14751 m++; 14752 break; 14753 default: 14754 c = m[0]; 14755 if (c != '_' && rb_enc_isascii(c, enc) && rb_enc_ispunct(c, enc)) { 14756 /* operators */ 14757 int i; 14758 14759 if (len == 1) { 14760 id = c; 14761 goto id_register; 14762 } 14763 for (i = 0; i < op_tbl_count; i++) { 14764 if (*op_tbl[i].name == *m && 14765 strcmp(op_tbl[i].name, m) == 0) { 14766 id = op_tbl[i].token; 14767 goto id_register; 14768 } 14769 } 14770 } 14771 14772 if (m[last] == '=') { 14773 /* attribute assignment */ 14774 id = rb_intern3(name, last, enc); 14775 if (id > tLAST_TOKEN && !is_attrset_id(id)) { 14776 enc = rb_enc_get(rb_id2str(id)); 14777 id = rb_id_attrset(id); 14778 goto id_register; 14779 } 14780 id = ID_ATTRSET; 14781 } 14782 else if (rb_enc_isupper(m[0], enc)) { 14783 id = ID_CONST; 14784 } 14785 else { 14786 id = ID_LOCAL; 14787 } 14788 break; 14789 } 14790 mb = 0; 14791 if (!rb_enc_isdigit(*m, enc)) { 14792 while (m <= name + last && is_identchar(m, e, enc)) { 14793 if (ISASCII(*m)) { 14794 m++; 14795 } 14796 else { 14797 mb = 1; 14798 m += rb_enc_mbclen(m, e, enc); 14799 } 14800 } 14801 } 14802 if (m - name < len) id = ID_JUNK; 14803 if (enc != rb_usascii_encoding()) { 14804 /* 14805 * this clause makes sense only when called from other than 14806 * rb_intern_str() taking care of code-range. 14807 */ 14808 if (!mb) { 14809 for (; m <= name + len; ++m) { 14810 if (!ISASCII(*m)) goto mbstr; 14811 } 14812 enc = rb_usascii_encoding(); 14813 } 14814 mbstr:; 14815 } 14816 new_id: 14817 id |= ++global_symbols.last_id << ID_SCOPE_SHIFT; 14818 id_register: 14819 return register_symid(id, name, len, enc); 14820 }
あれ? なんでいきなりグローバルなテーブルを引いてるんだ? メソッドのルックアップって、下から行くんじゃないのかしら。
ともあれ、実際のメソッドの実行は、vm_eval.cの、rb_call0らしい。と思って、眺めてみると、そろそろNodeという構造体が出てきたりthread生成したりで、処理系の第3の胃袋あたりに近づくのかなと思ったりして、眺め続けている。