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

引き続き、`Kernel.#set_trace_func`について見ていきましょう。
目標は、イベントに対してフックする仕組みと、コールバックで呼び出す仕組みを理解することです。

# CRubyからみたset_trace_func

## 使われているデータ構造

### rb_event_flag_t

`rb_event_flag_t`はそのものずばり、イベントを区別するためのフラグのようです。

```c:ruby.h
/* traditional set_trace_func events */
#define RUBY_EVENT_NONE      0x0000
#define RUBY_EVENT_LINE      0x0001
#define RUBY_EVENT_CLASS     0x0002
#define RUBY_EVENT_END       0x0004
#define RUBY_EVENT_CALL      0x0008
#define RUBY_EVENT_RETURN    0x0010
#define RUBY_EVENT_C_CALL    0x0020
#define RUBY_EVENT_C_RETURN  0x0040
#define RUBY_EVENT_RAISE     0x0080
#define RUBY_EVENT_ALL       0x00ff

/* for TracePoint extended events */
#define RUBY_EVENT_B_CALL          0x0100
#define RUBY_EVENT_B_RETURN        0x0200
#define RUBY_EVENT_THREAD_BEGIN    0x0400
#define RUBY_EVENT_THREAD_END      0x0800
#define RUBY_EVENT_TRACEPOINT_ALL  0xFFFF

/* special events */
#define RUBY_EVENT_SPECIFIED_LINE 0x10000
#define RUBY_EVENT_SWITCH         0x20000
#define RUBY_EVENT_COVERAGE       0x40000

typedef unsigned long rb_event_flag_t;
```

traditional set_trace_func events が`line, call, return, c-call, c-return, class, end, raise`のイベントと対応しています（RUBY_EVENT_NONE,RUBY_EVENT_ALLという特殊なイベントもあるようですね）。

### rb_event_hook_flag_t

次に`rb_event_hook_flag_t`です。これもフラグです（何の？）。

```c:debug.h
typedef enum {
    RUBY_EVENT_HOOK_FLAG_SAFE    = 0x01,
    RUBY_EVENT_HOOK_FLAG_DELETED = 0x02,
    RUBY_EVENT_HOOK_FLAG_RAW_ARG = 0x04
} rb_event_hook_flag_t;
```

### rb_event_hook_func_t

関数の関連で`rb_event_hook_func_t`です。
これこそが、`set_trace_func`とイベントでやり取りされているコールバック関数（のためのポインタ）なのです（たぶん）。

```c:ruby.h
typedef void (*rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass);
```

### rb_event_hook_t

`rb_event_hook_t`です。

```c:vm_trace.c
/* (1) trace mechanisms */

typedef struct rb_event_hook_struct {
    rb_event_hook_flag_t hook_flags; /* RUBY_EVENT_HOOK_FLAG_SAFEなど */
    rb_event_flag_t events; /* RUBY_EVENT_ALLなど */
    rb_event_hook_func_t func; /* procを呼び出すときに使う関数 */
    VALUE data; /* set_trace_func(proc)のproc */
    struct rb_event_hook_struct *next; /* 次の要素 */
} rb_event_hook_t;
```

trace mechanismsって書いてあります。nextから分かるようにリンクですね。
中身はイベントに関する２つのフラグと、コールバック関数、proc、次へのリンクです。

### rb_hook_list_t

`rb_hook_list_t`です。
ChangeLogに説明があり

```ChangeLog
(1) add rb_hook_list_t data structure which includes
    hooks, events (flag) and `need_clean' flag.
    If the last flag is true, then clean the hooks list.
    In other words, deleted hooks are contained by `hooks'.
    Cleanup process should run before traversing the list.
```

何のことでしょう。。。

```c:vm_core.h
typedef struct rb_hook_list_struct {
    struct rb_event_hook_struct *hooks;
    rb_event_flag_t events;
    int need_clean;
} rb_hook_list_t;
```

定義を見てみると、`rb_event_hook_struct`があり、ここがリンクのスタートになるようです。
ところで`event_flag_t`があるのですが、これは`rb_event_hook_struct`にもあるので、どのような関係なのでしょうか。

## イベントにコールバック関数を設定する

`set_trace_func`の実装は[vm_trace.c](https://github.com/ruby/ruby/blob/v2_0_0_353/vm_trace.c)にあります。

`Init_vm_trace`で、`Thread`と`global`に対してメソッドを定義しています。

```c:vm_trace.c
/* This function is called from inits.c */
void
Init_vm_trace(void)
{
    /* trace_func */
    rb_define_global_function("set_trace_func", set_trace_func, 1);
    rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1);
    rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1);
...
```

もともとの`set_trace_func`は

```c:vm_trace.c
static VALUE
set_trace_func(VALUE obj, VALUE trace)
{
...
    rb_remove_event_hook(call_trace_func);
...
    if (!rb_obj_is_proc(trace)) {
	rb_raise(rb_eTypeError, "trace_func needs to be Proc");
    }

    rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace);
    return trace;
}
```

です。`trace`は`set_trace_func(proc)`の`proc`です。`RUBY_EVENT_ALL`なので全てのイベントに対して設定されているようです。

`call_trace_func`はさきほどみた`rb_event_hook_func_t`の実体になります。

```c
typedef void (*rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass);
```

```c:vm_trace.c
static void
call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
{
    const char *srcfile = rb_sourcefile();
    VALUE eventname = rb_str_new2(get_event_name(event));
    VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil;
    VALUE argv[6];
    int line = rb_sourceline();
    rb_thread_t *th = GET_THREAD();

    if (!klass) {
	rb_thread_method_id_and_class(th, &id, &klass);
    }

    if (klass) {
	if (RB_TYPE_P(klass, T_ICLASS)) {
	    klass = RBASIC(klass)->klass;
	}
	else if (FL_TEST(klass, FL_SINGLETON)) {
	    klass = rb_iv_get(klass, "__attached__");
	}
    }

    argv[0] = eventname;
    argv[1] = filename;
    argv[2] = INT2FIX(line);
    argv[3] = id ? ID2SYM(id) : Qnil;
    argv[4] = (self && srcfile) ? rb_binding_new() : Qnil;
    argv[5] = klass ? klass : Qnil;

    rb_proc_call_with_block(proc, 6, argv, Qnil);
}
```

`argv`にevent, file, line, id, binding, klassを詰め込んで、procをcallしていることから、これが最終的にprocを呼び出していると思います。

`set_trace_func`からの呼び出しをまとめてみると

```c:vm_trace.c
- static VALUE
  set_trace_func(VALUE obj, VALUE trace)

-- void
   rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)

--- static rb_event_hook_t *
    alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data,     rb_event_hook_flag_t hook_flags)

--- static void
    connect_event_hook(rb_hook_list_t *list, rb_event_hook_t *hook)
```

となります。

`alloc_event_hook`は`rb_event_hook_t`の分のメモリを確保して、引数である`func, events, data, hook_flags`を`rb_event_hook_t`のメンバにセットして返します。
`connect_event_hook`は`hook`を`list->hooks`の先頭に挿入します。

```c:vm_trace.c
void
rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
{
    rb_event_hook_t *hook = alloc_event_hook(func, events, data, RUBY_EVENT_HOOK_FLAG_SAFE);
    connect_event_hook(&GET_VM()->event_hooks, hook);
}
```

実装はこのようになっており、VMが管理しているevent_hooksに対して新しいhookを挿入しています。
`GET_VM()`は

```c:vm_core.h
#define GET_VM() ruby_current_vm
```

のことで、

```c:vm.c
rb_vm_t *ruby_current_vm = 0;
```

となっています。`ruby_current_vm`の初期化は`vm.c`の`Init_VM`や`Init_BareVM`で行われています。
このあたりの処理はあとでみるとして、今は構造体をみてみましょう。

```c:vm_core.h
typedef struct rb_vm_struct {
    VALUE self;

    rb_global_vm_lock_t gvl;
    rb_thread_lock_t    thread_destruct_lock;

    struct rb_thread_struct *main_thread;
    struct rb_thread_struct *running_thread;

    st_table *living_threads;
    VALUE thgroup_default;

    int running;
    int thread_abort_on_exception;
    int trace_running;
    volatile int sleeper;

    /* object management */
    VALUE mark_object_ary;

    VALUE special_exceptions[ruby_special_error_count];

    /* load */
    VALUE top_self;
    VALUE load_path;
    VALUE load_path_snapshot;
    VALUE load_path_check_cache;
    VALUE expanded_load_path;
    VALUE loaded_features;
    VALUE loaded_features_snapshot;
    struct st_table *loaded_features_index;
    struct st_table *loading_table;

    /* signal */
    struct {
	VALUE cmd;
	int safe;
    } trap_list[RUBY_NSIG];

    /* hook */
    rb_hook_list_t event_hooks;

    int src_encoding_index;

    VALUE verbose, debug, progname;
    VALUE coverages;

    struct unlinked_method_entry_list_entry *unlinked_method_entry_list;

#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
    struct rb_objspace *objspace;
#endif

    /*
     * @shyouhei notes that this is not for storing normal Ruby
     * objects so do *NOT* mark this when you GC.
     */
    struct RArray at_exit;

    VALUE *defined_strings;

    /* params */
    struct { /* size in byte */
	size_t thread_vm_stack_size;
	size_t thread_machine_stack_size;
	size_t fiber_vm_stack_size;
	size_t fiber_machine_stack_size;
    } default_params;
} rb_vm_t;
```

このように

```c
VM->event_hooks->hooks->next->next ...

rb_vm_struct->rb_hook_list_struct->rb_event_hook_struct->rb_event_hook_struct ...
```

という形でprocを管理しています。