ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

Rubyのソースコードを読んでみたけど思ったゴールには行かなかった

この前Rubyソースコードってどこにあるのかなぁとか言ってましたが、GitHubにRubyのリポジトリがあったので読んでみました。
とりあえずエントリーポイントと件のinitializeをしていると思しきところだけですが…しかしこれCで書かれてたんですね。
では早速、Object#newの処理を書いていると思しきところから見てみましょう。Object#initializeの方は長くなったのでまた今度。

//object.cの1762行目から
VALUE
rb_class_new_instance(int argc, VALUE *argv, VALUE klass)
{
    VALUE obj;

    obj = rb_obj_alloc(klass);
    rb_obj_call_init(obj, argc, argv);

    return obj;
}

ちなみにこれだろうと思った根拠は下のやつです。

//object.cの3181行目
    rb_define_method(rb_cClass, "new", rb_class_new_instance, -1);

シンプルすぎて何が何やら。とりあえず、メモリを確保して初期化処理をしているんだろうな、というのが中の関数名から読み取れますが。
次はrb_obj_call_init()を見てみましょう。

//eval.cの1226行目から
void
rb_obj_call_init(VALUE obj, int argc, VALUE *argv)
{
    PASS_PASSED_BLOCK();
    rb_funcall2(obj, idInitialize, argc, argv);
}

これまたシンプルにマクロのようなものとなんかよくわからない関数が並んでいるだけです。今度はrb_funcall2()を見ていきます。なお、ここから先の関数はすべてvm_eval.cにありました。

//vm_eval.cの804行目から
VALUE
rb_funcall2(VALUE recv, ID mid, int argc, const VALUE *argv)
{
    return rb_call(recv, mid, argc, argv, CALL_FCALL);
}
//vm_eval.cの581行目から
static inline VALUE
rb_call(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope)
{
    rb_thread_t *th = GET_THREAD();
    return rb_call0(recv, mid, argc, argv, scope, th->cfp->self);
}

名前から察するにスレッドへのポインタらしきものを取得したあと、似たような名前の関数rb_call0()へたらい回しされます。

//vm_eval.cの310行目から
static inline VALUE
rb_call0(VALUE recv, ID mid, int argc, const VALUE *argv,
	 call_type scope, VALUE self)
{
    VALUE defined_class;
    rb_method_entry_t *me =
	rb_search_method_entry(recv, mid, &defined_class);
    rb_thread_t *th = GET_THREAD();
    int call_status = rb_method_call_status(th, me, scope, self);

    if (call_status != NOEX_OK) {
	return method_missing(recv, mid, argc, argv, call_status);
    }
    stack_check();
    return vm_call0(th, recv, mid, argc, argv, me, defined_class);
}

中身が少し厚くなって来ましたね。さらにvm_call0へ飛ばされます。そろそろ終点が見えそうですね、っていうかこれひょっとして初期化ではなくて普遍的なメソッド呼び出しのルートなのでは…

//vm_eval.cの36行目から
static VALUE
vm_call0(rb_thread_t* th, VALUE recv, VALUE id, int argc, const VALUE *argv,
	 const rb_method_entry_t *me, VALUE defined_class)
{
    rb_call_info_t ci_entry, *ci = &ci_entry;

    ci->flag = 0;
    ci->mid = id;
    ci->recv = recv;
    ci->defined_class = defined_class;
    ci->argc = argc;
    ci->me = me;

    return vm_call0_body(th, ci, argv);
}

それではこのあとのvm_call0_body()を見てみましょう。

//vm_eval.cの139行目から
static VALUE
vm_call0_body(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv)
{
    VALUE ret;

    if (!ci->me->def) return Qnil;

    if (th->passed_block) {
	ci->blockptr = (rb_block_t *)th->passed_block;
	th->passed_block = 0;
    }
    else {
	ci->blockptr = 0;
    }

  again:
    switch (ci->me->def->type) {
      case VM_METHOD_TYPE_ISEQ:
	{
	    rb_control_frame_t *reg_cfp = th->cfp;
	    int i;

	    CHECK_VM_STACK_OVERFLOW(reg_cfp, ci->argc + 1);

	    *reg_cfp->sp++ = ci->recv;
	    for (i = 0; i < ci->argc; i++) {
		*reg_cfp->sp++ = argv[i];
	    }

	    vm_call_iseq_setup(th, reg_cfp, ci);
	    th->cfp->flag |= VM_FRAME_FLAG_FINISH;
	    return vm_exec(th); /* CHECK_INTS in this function */
	}
      case VM_METHOD_TYPE_NOTIMPLEMENTED:
      case VM_METHOD_TYPE_CFUNC:
	ret = vm_call0_cfunc(th, ci, argv);
	goto success;
      case VM_METHOD_TYPE_ATTRSET:
	rb_check_arity(ci->argc, 1, 1);
	ret = rb_ivar_set(ci->recv, ci->me->def->body.attr.id, argv[0]);
	goto success;
      case VM_METHOD_TYPE_IVAR:
	rb_check_arity(ci->argc, 0, 0);
	ret = rb_attr_get(ci->recv, ci->me->def->body.attr.id);
	goto success;
      case VM_METHOD_TYPE_BMETHOD:
	ret = vm_call_bmethod_body(th, ci, argv);
	goto success;
      case VM_METHOD_TYPE_ZSUPER:
      case VM_METHOD_TYPE_REFINED:
	{
	    if (ci->me->def->type == VM_METHOD_TYPE_REFINED &&
		ci->me->def->body.orig_me) {
		ci->me = ci->me->def->body.orig_me;
		goto again;
	    }

	    ci->defined_class = RCLASS_SUPER(ci->defined_class);

	    if (!ci->defined_class || !(ci->me = rb_method_entry(ci->defined_class, ci->mid, &ci->defined_class))) {
		ret = method_missing(ci->recv, ci->mid, ci->argc, argv, NOEX_SUPER);
		goto success;
	    }
	    RUBY_VM_CHECK_INTS(th);
	    if (!ci->me->def) return Qnil;
	    goto again;
	}
      case VM_METHOD_TYPE_MISSING:
	{
	    VALUE new_args = rb_ary_new4(ci->argc, argv);

	    RB_GC_GUARD(new_args);
	    rb_ary_unshift(new_args, ID2SYM(ci->mid));
	    th->passed_block = ci->blockptr;
	    return rb_funcall2(ci->recv, idMethodMissing, ci->argc+1, RARRAY_PTR(new_args));
	}
      case VM_METHOD_TYPE_OPTIMIZED:
	switch (ci->me->def->body.optimize_type) {
	  case OPTIMIZED_METHOD_TYPE_SEND:
	    ret = send_internal(ci->argc, argv, ci->recv, CALL_FCALL);
	    goto success;
	  case OPTIMIZED_METHOD_TYPE_CALL:
	    {
		rb_proc_t *proc;
		GetProcPtr(ci->recv, proc);
		ret = rb_vm_invoke_proc(th, proc, ci->argc, argv, ci->blockptr);
		goto success;
	    }
	  default:
	    rb_bug("vm_call0: unsupported optimized method type (%d)", ci->me->def->body.optimize_type);
	}
	break;
      case VM_METHOD_TYPE_UNDEF:
	break;
    }
    rb_bug("vm_call0: unsupported method type (%d)", ci->me->def->type);
    return Qundef;

  success:
    RUBY_VM_CHECK_INTS(th);
    return ret;
}

どうやらここが終着点、実際にメソッドを実行しているところのようです。しかしこのgotoの密度は初めて体験しますね。

Object#newとObject#initializeの関係を探るつもりで始めたコードリーディング、今回はあてが外れてしまったようです。ちなみに関数の定義で、返り値の型のところで

static VALUE
vm_call0_body(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv)
{

と改行しているのが印象的でした。こんな書き方初めてみましたね。