4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[CRuby] Ruby VM 周辺(Kernel.#set_trace_func)3

Last updated at Posted at 2014-03-08

今回はイベントを呼び出す側の処理を見てみましょう。

calling event

今回新しく登場する構造体にrb_trace_arg_structがあります。これはEvent呼び出し中に必要なargをまとめるためのものです。

vm_core.h
/* tracer */
struct rb_trace_arg_struct {
    rb_event_flag_t event;
    rb_thread_t *th;
    rb_control_frame_t *cfp;
    VALUE self;
    ID id;
    VALUE klass;
    VALUE data;

    int klass_solved;

    /* calc from cfp */
    int lineno;
    VALUE path;
};

EXEC_EVENT_HOOK

イベントの呼び出しはEXEC_EVENT_HOOKEXEC_EVENT_HOOK_AND_POP_FRAMEとしてvm_core.hに定義されています。これらはEXEC_EVENT_HOOK_ORIGを呼び出します。EXEC_EVENT_HOOK_ORIGのなかで、rb_trace_arg_structが初期化されています。

vm_core.h
# define EXEC_EVENT_HOOK_ORIG(th_, flag_, self_, id_, klass_, data_, pop_p_) do { \
    if (UNLIKELY(ruby_vm_event_flags & (flag_))) { \
  if (((th)->event_hooks.events | (th)->vm->event_hooks.events) & (flag_)) { \
      struct rb_trace_arg_struct trace_arg; \
      trace_arg.event = (flag_); \
      trace_arg.th = (th_); \
      trace_arg.cfp = (trace_arg.th)->cfp; \
      trace_arg.self = (self_); \
      trace_arg.id = (id_); \
      trace_arg.klass = (klass_); \
      trace_arg.data = (data_); \
      trace_arg.path = Qundef; \
      trace_arg.klass_solved = 0; \
      if (pop_p_) rb_threadptr_exec_event_hooks_and_pop_frame(&trace_arg); \
      else rb_threadptr_exec_event_hooks(&trace_arg); \
  } \
    } \
} while (0)

# define EXEC_EVENT_HOOK(th_, flag_, self_, id_, klass_, data_) \
  EXEC_EVENT_HOOK_ORIG(th_, flag_, self_, id_, klass_, data_, 0)

# define EXEC_EVENT_HOOK_AND_POP_FRAME(th_, flag_, self_, id_, klass_, data_) \
  EXEC_EVENT_HOOK_ORIG(th_, flag_, self_, id_, klass_, data_, 1)

rb_threadptr_exec_event_hooks_orig

処理はvm_trace.cに書かれています。

vm_trace.c
void
rb_threadptr_exec_event_hooks_and_pop_frame(rb_trace_arg_t *trace_arg)
{
    rb_threadptr_exec_event_hooks_orig(trace_arg, 1);
}

void
rb_threadptr_exec_event_hooks(rb_trace_arg_t *trace_arg)
{
    rb_threadptr_exec_event_hooks_orig(trace_arg, 0);
}

static void
rb_threadptr_exec_event_hooks_orig(rb_trace_arg_t *trace_arg, int pop_p)
{
    rb_thread_t *th = trace_arg->th;
    if (th->trace_arg == 0 &&
	trace_arg->self != rb_mRubyVMFrozenCore /* skip special methods. TODO: remove it. */) {
	...
	th->vm->trace_running++;
	th->trace_arg = trace_arg;
	{
	    rb_hook_list_t *list;

	    /* thread local traces */
	    list = &th->event_hooks;
	    if (list->events & trace_arg->event) {
		state = exec_hooks(th, list, trace_arg, TRUE);
		if (state) goto terminate;
	    }

	    /* vm global traces */
	    list = &th->vm->event_hooks;
	    if (list->events & trace_arg->event) {
		state = exec_hooks(th, list, trace_arg, !vm_tracing);
		if (state) goto terminate;
	    }
	    th->errinfo = errinfo;
	}
      terminate:
      ...

	th->state = outer_state;
    }
}

exec_hooks

exec_hooksの部分でコールバックを実行しています。実装は

vm_trace.c
static int
exec_hooks(rb_thread_t *th, rb_hook_list_t *list, const rb_trace_arg_t *trace_arg, int can_clean_hooks)
{
    int state;
    volatile int raised;

    if (UNLIKELY(list->need_clean > 0) && can_clean_hooks) {
	clean_hooks(list);
    }

    raised = rb_threadptr_reset_raised(th);

    /* TODO: Support !RUBY_EVENT_HOOK_FLAG_SAFE hooks */

    TH_PUSH_TAG(th);
    if ((state = TH_EXEC_TAG()) == 0) {
	rb_event_hook_t *hook;

	for (hook = list->hooks; hook; hook = hook->next) {
	    if (LIKELY(!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED)) && (trace_arg->event & hook->events)) {
		if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_RAW_ARG)) {
		    (*hook->func)(trace_arg->event, hook->data, trace_arg->self, trace_arg->id, trace_arg->klass);
		}
		else {
		    (*((rb_event_hook_raw_arg_func_t)hook->func))(hook->data, trace_arg);
		}
	    }
	}
    }
    TH_POP_TAG();

    if (raised) {
	rb_threadptr_set_raised(th);
    }

    return state;
}

forの内部で(*hook->func)(trace_arg->event, hook->data, trace_arg->self, trace_arg->id, trace_arg->klass)を実行することで、コールバックをしています。

実際にEXEC_EVENT_HOOKを使っているところを探してみます。

vm_eval.c
static VALUE
vm_call0_cfunc(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv)
{
    VALUE val;

    RUBY_DTRACE_CMETHOD_ENTRY_HOOK(th, ci->defined_class, ci->mid);
    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, ci->recv, ci->mid, ci->defined_class, Qnil);
    ...
    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, ci->recv, ci->mid, ci->defined_class, val);
    RUBY_DTRACE_CMETHOD_RETURN_HOOK(th, ci->defined_class, ci->mid);

    return val;
}

ここはCで書かれたメソッドの呼び出しとリターンに関するイベントを発生させています。vm_call0_cfunc_with_frameも同様の構成です。

vm.c
static VALUE
vm_exec(rb_thread_t *th)
{
...
	while (th->cfp->pc == 0 || th->cfp->iseq == 0) {
	    if (UNLIKELY(VM_FRAME_TYPE(th->cfp) == VM_FRAME_MAGIC_CFUNC)) {
		const rb_method_entry_t *me = th->cfp->me;
		EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, th->cfp->self, me->called_id, me->klass, Qnil);
		RUBY_DTRACE_METHOD_RETURN_HOOK(th, me->klass, me->called_id);
	    }
	    th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp);
	}

...
	else {
	    /* skip frame */

	    switch (VM_FRAME_TYPE(th->cfp)) {
	      case VM_FRAME_MAGIC_METHOD:
		RUBY_DTRACE_METHOD_RETURN_HOOK(th, 0, 0);
		EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_RETURN, th->cfp->self, 0, 0, Qnil);
		break;
	      case VM_FRAME_MAGIC_BLOCK:
		EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_B_RETURN, th->cfp->self, 0, 0, Qnil);
		break;
	      case VM_FRAME_MAGIC_CLASS:
		EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_END, th->cfp->self, 0, 0, Qnil);
		break;
	    }
...
}

こういう探し方をしていても、RUBY_EVENT_CLASSなどは見つかりません。では、RUBY_EVENT_CLASSがどこで使われているかというと、

compile.c

compile.c
VALUE
rb_iseq_compile_node(VALUE self, NODE *node)
{
...
	switch (iseq->type) {
	  case ISEQ_TYPE_BLOCK:
	    {
		LABEL *start = iseq->compile_data->start_label = NEW_LABEL(0);
		LABEL *end = iseq->compile_data->end_label = NEW_LABEL(0);

		ADD_LABEL(ret, start);
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_B_CALL);
		COMPILE(ret, "block body", node->nd_body);
		ADD_LABEL(ret, end);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_B_RETURN);

		/* wide range catch handler must put at last */
		ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, 0, start);
		ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, 0, end);
		break;
	    }
	  case ISEQ_TYPE_CLASS:
	    {
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CLASS);
		COMPILE(ret, "scoped node", node->nd_body);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_END);
		break;
	    }
	  case ISEQ_TYPE_METHOD:
	    {
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL);
		COMPILE(ret, "scoped node", node->nd_body);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN);
		break;
	    }
	  default: {
	    COMPILE(ret, "scoped node", node->nd_body);
	    break;
	  }
	}
    }
...

と、compile関連の箇所で使われてます。
このあたりの挙動についてはまた次回。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?