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

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

## calling event

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

```c: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_HOOK`や`EXEC_EVENT_HOOK_AND_POP_FRAME`として`vm_core.h`に定義されています。これらは`EXEC_EVENT_HOOK_ORIG`を呼び出します。`EXEC_EVENT_HOOK_ORIG`のなかで、`rb_trace_arg_struct`が初期化されています。

```c: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`に書かれています。

```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`の部分でコールバックを実行しています。実装は

```c: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`を使っているところを探してみます。

```c: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`も同様の構成です。

```c: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

```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関連の箇所で使われてます。
このあたりの挙動についてはまた次回。