YARVのepはどのように作られるのか

`VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))`が0になって`rb_reg_search0`経由で`VM_EP_LEP`をした時に`VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))[0]`を読もうとしてSEGVしたり、`ep[1]`(`VM_ENV_DATA_INDEX_ENV`)が0になって`vm_env_write_slowpath`経由で`RVALUE_OLD_P_RAW`した時に`(RBASIC(ep[1])->flags`を読もうとしてSEGVしたりしているので、epがどこで作られるのか調べてそのメモを置いておくことにする。めちゃくちゃ迷走してるので多分本人以外には参考にならない

自分も忘れかねないので書いておくとepはenvironment pointerの略

## そもそもcfpはどこでどう積まれるか
死んだ状況でbtをやると、こういう感じになっている

```
#0  0x00005555556dbc48 in VM_ENV_FLAGS (ep=0x0, flag=2) at vm_core.h:1081
#1  0x00005555556dbd71 in VM_ENV_LOCAL_P (ep=0x0) at vm_core.h:1143
#2  0x00005555556dc576 in VM_EP_LEP (ep=0x0) at vm.c:28
#3  0x00005555556dc61b in VM_CF_LEP (cfp=0x7ffff7fd1ad0) at vm.c:64
#4  0x00005555556f8c1d in vm_cfp_svar_get (th=0x555555a8c470, cfp=0x7ffff7fd1ad0, key=1) at vm.c:1210
#5  0x00005555556f8cd1 in vm_svar_get (key=1) at vm.c:1224
#6  0x00005555556f8d1b in rb_backref_get () at vm.c:1237
#7  0x0000555555640a9c in rb_reg_search0 (set_backref_str=1, reverse=0, pos=0, str=93825000927280, re=93825000636760) at re.c:1508
#8  rb_reg_search (reverse=0, pos=0, str=<optimized out>, re=93825000636760) at re.c:1580
#9  reg_match_pos (pos=0, strp=<synthetic pointer>, re=93825000636760) at re.c:3014
#10 rb_reg_match (re=93825000636760, str=93825000927280) at re.c:3068
#11 0x00005555556e27db in call_cfunc_1 (func=0x5555556409d0 <rb_reg_match>, recv=93825000636760, argc=1, argv=0x7ffff7ed2430)
    at vm_insnhelper.c:1769
#12 0x00005555556e3283 in vm_call_cfunc_with_frame (th=0x555555a8c470, reg_cfp=0x7ffff7fd1ad0, calling=0x7fffffff25c0, ci=0x555555cab5b0,
    cc=0x555555cab680) at vm_insnhelper.c:1954
#13 0x00005555556e33cd in vm_call_cfunc (th=0x555555a8c470, reg_cfp=0x7ffff7fd1ad0, calling=0x7fffffff25c0, ci=0x555555cab5b0,
    cc=0x555555cab680) at vm_insnhelper.c:1970
...
```

```
#0  0x000055555558af6a in RVALUE_OLD_P_RAW (obj=0) at gc.c:1167
#1  0x000055555558afa1 in RVALUE_OLD_P (obj=0) at gc.c:1174
#2  0x0000555555594b4b in rb_gc_writebarrier_remember (obj=0) at gc.c:6036
#3  0x00007ffff57cee60 in vm_env_write_slowpath (ep=0x7ffff7ed2138, index=-9, v=8) at ./vm_insnhelper.c:341
#4  0x00007ffff57ceef6 in vm_env_write (ep=0x7ffff7ed2138, index=-9, v=8) at ./vm_insnhelper.c:355
...
#7  0x00005555556ea4fb in vm_exec_core (th=0x555555a8c470, initial=0) at insns.def:886
#8  0x00005555556fa5d1 in vm_exec (th=0x555555a8c470) at vm.c:1800
#9  0x00005555556fb220 in rb_iseq_eval (iseq=0x555555c785d0) at vm.c:2037
#10 0x00005555555c7b62 in rb_load_internal0 (th=0x555555a8c470, fname=93825000925840, wrap=0) at load.c:617
#11 0x00005555555c8a08 in rb_require_internal (fname=93825000926040, safe=0) at load.c:998
#12 0x00005555555c8bdb in rb_require_safe (fname=93825000926360, safe=0) at load.c:1044
#13 0x00005555555c8146 in rb_f_require (obj=93824998105800, fname=93825000926360) at load.c:826
#14 0x00005555556e27db in call_cfunc_1 (func=0x5555555c8121 <rb_f_require>, recv=93824998105800, argc=1, argv=0x7ffff7ed20a8)
    at vm_insnhelper.c:1769
#15 0x00005555556e3283 in vm_call_cfunc_with_frame (th=0x555555a8c470, reg_cfp=0x7ffff7fd1f80, calling=0x7fffffffd3a0, ci=0x555555e198d0,
    cc=0x555555e19c40) at vm_insnhelper.c:1954
#16 0x00005555556e33cd in vm_call_cfunc (th=0x555555a8c470, reg_cfp=0x7ffff7fd1f80, calling=0x7fffffffd3a0, ci=0x555555e198d0,
    cc=0x555555e19c40) at vm_insnhelper.c:1970
#17 0x00005555556ea4db in vm_exec_core (th=0x555555a8c470, initial=0) at insns.def:886
#18 0x00005555556fa5d1 in vm_exec (th=0x555555a8c470) at vm.c:1800
#19 0x00005555556fb260 in rb_iseq_eval_main (iseq=0x555555bebb08) at vm.c:2048
#20 0x000055555557a7fb in ruby_exec_internal (n=0x555555bebb08) at eval.c:246
#21 0x000055555557a921 in ruby_exec_node (n=0x555555bebb08) at eval.c:310
#22 0x000055555557a8f4 in ruby_run_node (n=0x555555bebb08) at eval.c:302
#23 0x0000555555577f5f in main (argc=6, argv=0x7fffffffdbc8) at ./main.c:42
```

vm_exec_coreの中でいくらでも積まれうるので何個積まれてるのかはこれだけではよくわからないが、とりあえず`vm_push_frame`への到達方法が少なくとも以下の種類存在しているように見える

### rb_iseq_eval_main

```c
VALUE
rb_iseq_eval_main(const rb_iseq_t *iseq)
{
    rb_thread_t *th = GET_THREAD();
    VALUE val;

    vm_set_main_stack(th, iseq);
    val = vm_exec(th);
    return val;
}

static void
vm_set_main_stack(rb_thread_t *th, const rb_iseq_t *iseq)
{
    VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
    rb_binding_t *bind;

    GetBindingPtr(toplevel_binding, bind);
    RUBY_ASSERT_MESG(bind, "TOPLEVEL_BINDING is not built");

    vm_set_eval_stack(th, iseq, 0, &bind->block);

    /* save binding */
    if (iseq->body->local_table_size > 0) {
        vm_bind_update_env(toplevel_binding, bind, vm_make_env_object(th, th->ec.cfp));
    }
}

static void
vm_set_eval_stack(rb_thread_t * th, const rb_iseq_t *iseq, const rb_cref_t *cref, const struct rb_block *base_block)
{
    vm_push_frame(th, iseq, VM_FRAME_MAGIC_EVAL | VM_FRAME_FLAG_FINISH,
		  vm_block_self(base_block), VM_GUARDED_PREV_EP(vm_block_ep(base_block)),
		  (VALUE)cref, /* cref or me */
		  iseq->body->iseq_encoded,
		  th->ec.cfp->sp, iseq->body->local_table_size,
		  iseq->body->stack_max);
}
```

### rb_iseq_eval
```c
VALUE
rb_iseq_eval(const rb_iseq_t *iseq)
{
    rb_thread_t *th = GET_THREAD();
    VALUE val;
    vm_set_top_stack(th, iseq);
    val = vm_exec(th);
    return val;
}

static void
vm_set_top_stack(rb_thread_t *th, const rb_iseq_t *iseq)
{
    if (iseq->body->type != ISEQ_TYPE_TOP) {
	rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence");
    }

    /* for return */
    vm_push_frame(th, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, th->top_self,
		  VM_BLOCK_HANDLER_NONE,
		  (VALUE)vm_cref_new_toplevel(th), /* cref or me */
		  iseq->body->iseq_encoded, th->ec.cfp->sp,
		  iseq->body->local_table_size, iseq->body->stack_max);
}
```

### CALL_FUNC in vm_exec_core
#### vm_call_iseq_setup_normal
```c
static VALUE
vm_call_general(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
    return vm_call_method(th, reg_cfp, calling, ci, cc);
}

/* 中略 */

static VALUE
vm_call_method_each_type(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
    switch (cc->me->def->type) {
      case VM_METHOD_TYPE_ISEQ:
        CI_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);
        return vm_call_iseq_setup(th, cfp, calling, ci, cc);
        /* 略 */
    }
}

static inline VALUE
vm_call_iseq_setup_normal(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
			  int opt_pc, int param_size, int local_size)
{
    const rb_callable_method_entry_t *me = cc->me;
    const rb_iseq_t *iseq = def_iseq_ptr(me->def);
    VALUE *argv = cfp->sp - calling->argc;
    VALUE *sp = argv + param_size;
    cfp->sp = argv - 1 /* recv */;

    vm_push_frame(th, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
		  calling->block_handler, (VALUE)me,
		  iseq->body->iseq_encoded + opt_pc, sp,
		  local_size - param_size,
		  iseq->body->stack_max);
    return Qundef;
}
```

#### vm_call_cfunc_with_frame
```c
/* 前略 */
static VALUE
vm_call_method_each_type(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
    switch (cc->me->def->type) {
      case VM_METHOD_TYPE_ISEQ:
        CI_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);
        return vm_call_iseq_setup(th, cfp, calling, ci, cc);

      case VM_METHOD_TYPE_NOTIMPLEMENTED:
      case VM_METHOD_TYPE_CFUNC:
        CI_SET_FASTPATH(cc, vm_call_cfunc, TRUE);
        return vm_call_cfunc(th, cfp, calling, ci, cc);
        /* 略 */
    }
}

static VALUE
vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
    CALLER_SETUP_ARG(reg_cfp, calling, ci);
    return vm_call_cfunc_with_frame(th, reg_cfp, calling, ci, cc);
}

static VALUE
vm_call_cfunc_with_frame(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
    VALUE val;
    const rb_callable_method_entry_t *me = cc->me;
    const rb_method_cfunc_t *cfunc = vm_method_cfunc_entry(me);
    int len = cfunc->argc;

    VALUE recv = calling->recv;
    VALUE block_handler = calling->block_handler;
    int argc = calling->argc;

    RUBY_DTRACE_CMETHOD_ENTRY_HOOK(th, me->owner, me->def->original_id);
    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->def->original_id, ci->mid, me->owner, Qundef);

    vm_push_frame(th, NULL, VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, recv,
		  block_handler, (VALUE)me,
		  0, th->ec.cfp->sp, 0, 0);

    if (len >= 0) rb_check_arity(argc, len, len);

    reg_cfp->sp -= argc + 1;
    VM_PROFILE_UP(R2C_CALL);
    val = (*cfunc->invoker)(cfunc->func, recv, argc, reg_cfp->sp + 1);

    CHECK_CFP_CONSISTENCY("vm_call_cfunc");

    rb_vm_pop_frame(th);

    EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->def->original_id, ci->mid, me->owner, val);
    RUBY_DTRACE_CMETHOD_RETURN_HOOK(th, me->owner, me->def->original_id);

    return val;
}
```

## vm_push_frameはどのようにepを用意するか
```c 
static rb_control_frame_t *
vm_push_frame(rb_thread_t *th,
	      const rb_iseq_t *iseq,
	      VALUE type,
	      VALUE self,
	      VALUE specval,
	      VALUE cref_or_me,
	      const VALUE *pc,
	      VALUE *sp,
	      int local_size,
	      int stack_max)
{
    return vm_push_frame_(&th->ec, iseq, type, self, specval, cref_or_me, pc, sp, local_size, stack_max);
}

static inline rb_control_frame_t *
vm_push_frame_(rb_execution_context_t *ec,
               const rb_iseq_t *iseq,
               VALUE type,
               VALUE self,
               VALUE specval,
               VALUE cref_or_me,
               const VALUE *pc,
               VALUE *sp,
               int local_size,
               int stack_max)
{
    rb_control_frame_t *const cfp = ec->cfp - 1;
    int i;

    vm_check_frame(type, specval, cref_or_me, iseq);
    VM_ASSERT(local_size >= 0);

    /* check stack overflow */
    CHECK_VM_STACK_OVERFLOW0(cfp, sp, local_size + stack_max);

    ec->cfp = cfp;

    /* setup new frame */
    cfp->pc = (VALUE *)pc;
    cfp->iseq = (rb_iseq_t *)iseq;
    cfp->self = self;
    cfp->block_code = NULL;

    /* setup vm value stack */

    /* initialize local variables */
    for (i=0; i < local_size; i++) {
        *sp++ = Qnil;
    }

    /* setup ep with managing data */
    VM_ASSERT(VM_ENV_DATA_INDEX_ME_CREF == -2);
    VM_ASSERT(VM_ENV_DATA_INDEX_SPECVAL == -1);
    VM_ASSERT(VM_ENV_DATA_INDEX_FLAGS   == -0);
    *sp++ = cref_or_me; /* ep[-2] / Qnil or T_IMEMO(cref) or T_IMEMO(ment) */
    *sp++ = specval     /* ep[-1] / block handler or prev env ptr */;
    *sp   = type;       /* ep[-0] / ENV_FLAGS */

    cfp->ep = sp;
    cfp->sp = sp + 1;

#if VM_DEBUG_BP_CHECK
    cfp->bp_check = sp + 1;
#endif

    if (VMDEBUG == 2) {
        SDR();
    }

    return cfp;
}
```

そういえばruby under a microscopeにも書いてあったけどepは最初sp-1なのだった。

### `ep[-2]`(`VM_ENV_DATA_INDEX_ME_CREF`) が作られる場所
```c
    *sp++ = cref_or_me; /* ep[-2] / Qnil or T_IMEMO(cref) or T_IMEMO(ment) */
```

`vm_push_frame`の第6引数がそのまま入る。先ほどの4つのケースではそれぞれ、

- rb_iseq_eval_main
  - `(VALUE)cref` → `0`
- rb_iseq_eval
  - `(VALUE)vm_cref_new_toplevel(th)`
- vm_call_iseq_setup_normal
  - `(VALUE)me` → `cc->me`
- vm_call_cfunc_with_frame
  - `(VALUE)me` → `cc->me`

うーん

`vm_call_cfunc_with_frame`直後の`ec->cfp->ep[-2]`は明らかに`cc->me`になる。
従って、`vm_call_cfunc_with_frame`で呼び出されている関数が`th->ec.cfp->ep[-2]`と参照してきて0になってるとしたら、まあ`vm_search_method`後に`cc->me`が0だったという話になりそう。

…だがgdbでフレームを辿って値を見てみるとそうではない。うーん。わからない。
というか、`ruby_current_thread->ec.cfp->ep[-2]`とかやると普通に値が入っている。

というところで、最初は`VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))`が`cfp->ep[-2]`のことだと勝手に勘違いしていたがそうでないことに気付いた。

まあとりあえず`ep[-2]`の雑な理解としてはmethod entryかcref(class reference)のどちらかが入っている

#### VM_ENV_PREV_EPとは
```c 
static inline const VALUE *
VM_ENV_PREV_EP(const VALUE *ep)
{
    VM_ASSERT(VM_ENV_LOCAL_P(ep) == 0);
    return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
}

#define GC_GUARDED_PTR_REF(p) VM_TAGGED_PTR_REF((p), 0x03)
#define VM_TAGGED_PTR_REF(v, mask) ((void *)((v) & ~mask))
#define VM_ENV_DATA_INDEX_SPECVAL    (-1) /* ep[-1] */
```

`~`はNOTなので`& ~0x03`をやると`0100`のビットが落ちることになる。まあポインタだと立ってない場所なので、何かのフラグが入ってたら打ち消す処理であり、とりあえず無視してよい。

ので、`VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))`は雑に見ると`ep[-1][-1]`ということだ。`ep[-2]`ではないのは冷静に見たら当たり前だけど。

#### 落ちてるケースのepの置き場を再確認
まあそれは置いておいて、落ちるケースにおいて、以下のような順序で参照された場合に0になることがわかっている。

``` 
(gdb) p ruby_current_thread->ec.cfp->ep
$8 = (const VALUE *) 0x7ffff7ed2448
(gdb) p vm_normal_frame(ruby_current_thread, ruby_current_thread->ec.cfp->ep)->ep
$9 = (const VALUE *) 0x555555d5a4f8
(gdb) p VM_ENV_PREV_EP(vm_normal_frame(ruby_current_thread, ruby_current_thread->ec.cfp->ep)->ep)
$10 = (const VALUE *) 0x0
(gdb) p VM_ENV_PREV_EP(ruby_current_thread->ec.cfp->ep)
$11 = (const VALUE *) 0x0
```

よく見るとこのケースでは `VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))`ですらなく`VM_ENV_PREV_EP(cfp->ep)`が0である。まあ既にbtを見た時からやり直してるので再現条件が複数あったのかもしれないが。というわけで`ep[-1]`の方を見ていく

### `ep[-1]`(`VM_ENV_DATA_INDEX_SPECVAL`) が作られる場所
ruby under a microscopeもチラ見しながらもうなんか割と`ep[-1]`の中身がわかってきた。`ep[-1]`には多分一つ上のスコープの`ep`のアドレスが入っていて、`ep[-1][-1][-1]...`とやっていくと上の階層に辿れるのだろう。

というわけで、以下の行に何が入るか、とりあえず先ほどの4つのケースを見ていく。

```c 
    *sp++ = specval     /* ep[-1] / block handler or prev env ptr */;
```

vm_push_frameの第5引数。

- rb_iseq_eval_main
  - `VM_GUARDED_PREV_EP(vm_block_ep(base_block))`
- rb_iseq_eval
  - `VM_BLOCK_HANDLER_NONE` → `0`
- vm_call_iseq_setup_normal
  - `(VALUE)me` → `calling->block_handler` → ブロックがない場合 `VM_BLOCK_HANDLER_NONE` → `0`
- vm_call_cfunc_with_frame
  - `(VALUE)me` → `calling->block_handler` → ブロックがない場合 `VM_BLOCK_HANDLER_NONE` → `0`

うーん

ていうかblock handlerの場合ってどう使われるんだろう…
… vm_invoke_block の時に`VM_CF_BLOCK_HANDLER(reg_cfp)`で`VM_EP_LEP(cfp->ep)[-1]`してblock handlerを参照し、`VM_TAGGED_PTR_REF(block_handler, 0x03)`をすることによって以下の`rb_captured_block`とかに変換されるっぽい。

```c 
struct rb_captured_block {
    VALUE self;
    const VALUE *ep;
    union {
        const rb_iseq_t *iseq;
        const struct vm_ifunc *ifunc;
        VALUE val;
    } code;
};
```

`vm_invoke_iseq_block`はこのiseqを取り出してvm_push_frameしているので、まあそう使われることがわかる。

ここにもepが入ってるから、前のepへの参照がなくとも、ブロックがepを辿りたい時は`VM_TAGGED_PTR_REF(block_handler, 0x03)->ep[-1]`とかでいけそう。

#### どのような時に`VM_EP_LEP`で`ep`が0になってしまうか
まず、`VM_EP_LEP`の定義はこう

```c 
static inline const VALUE *
VM_EP_LEP(const VALUE *ep)
{
    while (!VM_ENV_LOCAL_P(ep)) {
        ep = VM_ENV_PREV_EP(ep);
    }
    return ep;
}

static inline const VALUE *
VM_ENV_PREV_EP(const VALUE *ep)
{
    VM_ASSERT(VM_ENV_LOCAL_P(ep) == 0);
    return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
}

static inline int
VM_ENV_LOCAL_P(const VALUE *ep)
{
    return VM_ENV_FLAGS(ep, VM_ENV_FLAG_LOCAL) ? 1 : 0;
}

static inline unsigned long
VM_ENV_FLAGS(const VALUE *ep, long flag)
{
    VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS];
    VM_ASSERT(FIXNUM_P(flags));
    return flags & flag;
}

#define VM_ENV_DATA_INDEX_SPECVAL    (-1) /* ep[-1] */
#define VM_ENV_DATA_INDEX_FLAGS      ( 0) /* ep[ 0] */

VM_ENV_FLAG_LOCAL       = 0x0002,
```

まあそもそも`opt_send_without_block`とかで普通にメソッドを呼び出したらそのフレームの`ep[-1]`は`VM_BLOCK_HANDLER_NONE`で0になってしまう。
なので、普通にメソッドを呼び出した時、というのは`ep[0]`で0x0002のビットが立ってないとまず`VM_EP_LEP`から値は返らない。

というわけなので、`ep[0]`がどうセットされるかを見ることにする

## `ep[0]`(`VM_ENV_DATA_INDEX_FLAGS`) が作られる場所
名前からして、epのこの場所に何らかのフラグが一杯入るっぽい。フラグに使われそうなenumのリストはこれ

```c 
enum {
    /* Frame/Environment flag bits:
     *   MMMM MMMM MMMM MMMM ____ __FF FFFF EEEX (LSB)
     *
     * X   : tag for GC marking (It seems as Fixnum)
     * EEE : 3 bits Env flags
     * FF..: 6 bits Frame flags
     * MM..: 16 bits frame magic (to check frame corruption)
     */

    /* frame types */
    VM_FRAME_MAGIC_METHOD = 0x11110001,
    VM_FRAME_MAGIC_BLOCK  = 0x22220001,
    VM_FRAME_MAGIC_CLASS  = 0x33330001,
    VM_FRAME_MAGIC_TOP    = 0x44440001,
    VM_FRAME_MAGIC_CFUNC  = 0x55550001,
    VM_FRAME_MAGIC_IFUNC  = 0x66660001,
    VM_FRAME_MAGIC_EVAL   = 0x77770001,
    VM_FRAME_MAGIC_RESCUE = 0x88880001,
    VM_FRAME_MAGIC_DUMMY  = 0x99990001,

    VM_FRAME_MAGIC_MASK   = 0xffff0001,

    /* frame flag */
    VM_FRAME_FLAG_PASSED    = 0x0010,
    VM_FRAME_FLAG_FINISH    = 0x0020,
    VM_FRAME_FLAG_BMETHOD   = 0x0040,
    VM_FRAME_FLAG_CFRAME    = 0x0080,
    VM_FRAME_FLAG_LAMBDA    = 0x0100,

    /* env flag */
    VM_ENV_FLAG_LOCAL       = 0x0002,
    VM_ENV_FLAG_ESCAPED     = 0x0004,
    VM_ENV_FLAG_WB_REQUIRED = 0x0008
};
```

で、vm_push_frameでは、

```c
    *sp   = type;       /* ep[-0] / ENV_FLAGS */
```

第3引数のtypeがそのまま入る。`vm_push_frame`で値がチェックされるっぽいので後で見る

実際にvm_push_frameをさきほどの4つのケースでおいかけていく。

- rb_iseq_eval_main
  - `VM_FRAME_MAGIC_EVAL | VM_FRAME_FLAG_FINISH`
- rb_iseq_eval
  - `VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH`
- vm_call_iseq_setup_normal
  - `VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL`
- vm_call_cfunc_with_frame
  - `VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL`

今回は値の完全な式が書いてあってわかりやすい。

`CALL_METHOD`から呼ばれているケースではいずれも`VM_ENV_FLAG_LOCAL`が入っている。あとLEPと呼ばれているのは`ep[0]`に`VM_ENV_FLAG_LOCAL`が立ってるepのことだろう。

で、CALL_METHODで積まれるcfpのepには両方`VM_ENV_FLAG_LOCAL`が入ってるので、メソッド呼び出し直後の`th->ec.cfp->ep[0]`は0x0002が立ってないと何かおかしい。

### 結局何がおかしいのか
`ruby_current_thread->ec.cfp->ep[0]`を見てみたら`1010101010101010000000010000011`になっていた。なので0x0002は立っている。
一方で`VM_CF_LEP`の中から呼ばれる`VM_EP_LEP`の引数のepは0になってしまっていた。

で、そもそも`VM_CF_LEP`の引数のcfpと`ruby_current_thread->ec.cfp`が違っている。
これは`vm_cfp_svar_get`が(`GET_THREAD()`してきたthとそのcfpから)`vm_normal_frame(th, cfp)`したものを`VM_CF_LEP`に渡しているからだ。

すると、メソッド呼び出しで積まれたcfpから`vm_normal_frame(th, cfp)`を参照した時、その結果のcfpとそのepはどこで作られているのかを知る必要がある。

## vm_normal_frameとは
割と疲れてきた

```c 
static rb_control_frame_t *
vm_normal_frame(rb_thread_t *th, rb_control_frame_t *cfp)
{
    while (cfp->pc == 0) {
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
        if (RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
            return 0;
        }
    }
    return cfp;
}

#define RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp) ((cfp)+1)

#define RUBY_VM_END_CONTROL_FRAME(th) \
  ((rb_control_frame_t *)((th)->ec.vm_stack + (th)->ec.vm_stack_size))
#define RUBY_VM_VALID_CONTROL_FRAME_P(cfp, ecfp) \
  ((void *)(ecfp) > (void *)(cfp))
#define RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp) \
  (!RUBY_VM_VALID_CONTROL_FRAME_P((cfp), RUBY_VM_END_CONTROL_FRAME(th)))
```

えっ

pcが0のcfpはひたすらスキップされるようだ。c関数なメソッドの場合はpcもクソもなさそうなので、まあRubyで書かれてるフレームまで遡るのだろうか

一応確認しておくか…

```c
static inline rb_control_frame_t *
vm_push_frame_(rb_execution_context_t *ec,
	       const rb_iseq_t *iseq,
	       VALUE type,
	       VALUE self,
	       VALUE specval,
	       VALUE cref_or_me,
	       const VALUE *pc,
	       VALUE *sp,
	       int local_size,
	       int stack_max)
{
    rb_control_frame_t *const cfp = ec->cfp - 1;
    int i;

    vm_check_frame(type, specval, cref_or_me, iseq);
    VM_ASSERT(local_size >= 0);

    /* check stack overflow */
    CHECK_VM_STACK_OVERFLOW0(cfp, sp, local_size + stack_max);

    ec->cfp = cfp;

    /* setup new frame */
    cfp->pc = (VALUE *)pc;
    /* ... */
}

static rb_control_frame_t *
vm_push_frame(rb_thread_t *th,
	      const rb_iseq_t *iseq,
	      VALUE type,
	      VALUE self,
	      VALUE specval,
	      VALUE cref_or_me,
	      const VALUE *pc,
	      VALUE *sp,
	      int local_size,
	      int stack_max)
{
    return vm_push_frame_(&th->ec, iseq, type, self, specval, cref_or_me, pc, sp, local_size, stack_max);
}
```

第7引数がそのまま入る。

- rb_iseq_eval_main
  - `iseq->body->iseq_encoded`
- rb_iseq_eval
  - `iseq->body->iseq_encoded`
- vm_call_iseq_setup_normal
  - `iseq->body->iseq_encoded + opt_pc`
- vm_call_cfunc_with_frame
  - `0`

はい。まあそりゃそうですわ

なので、まあバックトレースを見て、一番近いRubyで実装されてるっぽいフレームの`cfp->pc`が0でなかったり`cfp->ep`で0x0002が立ってるかどうかを見るとよさそう。

でまあ`rb_backtrace`を見ると一個上はRubyのメソッドだったので、`*((ruby_current_thread->ec.cfp)+1)`とかを見てみるとpcはゼロではない。`rb_p ((ruby_current_thread->ec.cfp)+1)->iseq->body->location.label`とかやるとメソッド名も確認できる。(`rb_p rb_iseq_path((ruby_current_thread->ec.cfp)+1)->iseq)`でパスもわかる)

で、`((ruby_current_thread->ec.cfp)+1)->ep[0]`を見てみると`10101010101010101010101101011010111000001010000`だった。`CALL_METHOD`で積まれたのであればこれがおかしい。

フム

### frame magicのビットパターン
一応フレームが何なのか調べる時に使えそうなのでメモっておく

| 定数名 | 16進数 | 2進数 |
|:------|:-------|:-----|
| VM_FRAME_MAGIC_METHOD | 0x11110001 | `00010001000100010000000000000001` |
| VM_FRAME_MAGIC_BLOCK  | 0x22220001 | `00100010001000100000000000000001` |
| VM_FRAME_MAGIC_CLASS  | 0x33330001 | `00110011001100110000000000000001` |
| VM_FRAME_MAGIC_TOP    | 0x44440001 | `01000100010001000000000000000001` |
| VM_FRAME_MAGIC_CFUNC  | 0x55550001 | `01010101010101010000000000000001` |
| VM_FRAME_MAGIC_IFUNC  | 0x66660001 | `01100110011001100000000000000001` |
| VM_FRAME_MAGIC_EVAL   | 0x77770001 | `01110111011101110000000000000001` |
| VM_FRAME_MAGIC_RESCUE | 0x88880001 | `10001000100010000000000000000001` |
| VM_FRAME_MAGIC_DUMMY  | 0x99990001 | `10011001100110010000000000000001` |
| VM_FRAME_MAGIC_MASK   | 0xffff0001 | `11111111111111110000000000000001` |

さっきの奴をマスク取ってみるとこう: `1010101101011010000000000000000`
なんか壊れてるっぽい感…

まあこのフレームが積まれる過程をデバッガで追いかけてみます。記事に残しておく意味がありそうなのはここまでだと思うのでこのへんで