今回はイベントを呼び出す側の処理を見てみましょう。
calling event
今回新しく登場する構造体にrb_trace_arg_struct
があります。これはEvent呼び出し中に必要なargをまとめるためのものです。
/* 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
が初期化されています。
# 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
に書かれています。
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
の部分でコールバックを実行しています。実装は
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
を使っているところを探してみます。
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
も同様の構成です。
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
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関連の箇所で使われてます。
このあたりの挙動についてはまた次回。