/** ##skip
  -*-c-*-
  $Id: insns.def,v 1.12 2004/02/05 04:09:25 ko1 Exp $
  Create : K.S. 04/01/01 01:17:55

  YARV instruction definitions.
*/

/** ##skip
  instruction comment
  @c: category
  @e: english description
  @j: japanese description

  instruction form:
    DEFINE_INSN
    instrunction_name
    (instruction_operands, ...)
    (pop_values, ...)
    (return value)
    {
       ... // insn body
    }

 */


/**
  @c nop
  @e nop
  @j nop
 */
DEFINE_INSN
nop
()
()
()
{
  /* none */
}

/**********************************************************/
/* deal with variables                                    */
/**********************************************************/

/**
  @c variable
  @e get local variable(which is pointed by idx).
  @j idx で指定されたローカル変数をスタックに置く。
 */
DEFINE_INSN
getlocal
(ulong idx)
()
(VALUE val)
{
  val = *(GET_LFP() - idx);
}

/**
  @c variable
  @e get local variable (which is pointed by idx) as val.
  @j idx で指定されたローカル変数を val にする。
 */
DEFINE_INSN
setlocal
(ulong idx)
(VALUE val)
()
{
  (*(GET_LFP() - idx)) = val;
}

/**
  @c variable
  @e get special local variable ($~, ...)
  @j 特殊なローカル変数の値を得る
 */
DEFINE_INSN
getspecial
(ulong idx, ulong type)
()
(VALUE val)
{
  VALUE ary = *(GET_LFP() - 1);

  if(ary == Qnil){
    val = Qnil;
  }
  else if(type == 0){
    val = rb_ary_entry(ary, idx);
  }
  else{
    VALUE backref = rb_ary_entry(ary, idx);
    if(type & 0x01){
      switch (type >> 1){
      case '&':
        val = rb_reg_last_match(backref);
        break;
      case '`':
        val = rb_reg_match_pre(backref);
        break;
      case '\'':
        val = rb_reg_match_post(backref);
        break;
      case '+':
        val = rb_reg_match_last(backref);
        break;
      default:
        rb_bug("unexpected back-ref");
      }
    }
    else{
      val = rb_reg_nth_match(type >> 1, backref);
    }
  }
}

/**
  @c variable
  @e set special local variables
  @j 特別なローカル変数を設定する
 */
DEFINE_INSN
setspecial
(ulong idx, ulong type)
(VALUE obj)
()
{
  VALUE ary = *(GET_LFP() - 1);
  if(ary == Qnil){
    ary = *(GET_LFP() - 1) = rb_ary_new();
  }
  rb_ary_store(ary, idx, obj);
}

/**
  @c variable
  @e get block local variable(which is pointed by idx and level).
     level means nest level of block, and specify how above this variable.
  @j level, idx で指定されたブロックローカル変数の値をスタックに置く。
     level はブロックのネストレベルで、何段上かを示す。
 */
DEFINE_INSN
getdynamic
(ulong idx, ulong level)
()
(VALUE val)
{
  int i;
  VALUE *dfp2 = GET_DFP();
  for(i=0; i<level; i++){
    dfp2 = (VALUE*)dfp2[0];
#if VMDEBUG > 0
    if(dfp2 == 0){
      rb_bug("getdynamic: unknown level");
    }
#endif
  }
  val = *(dfp2-idx);
}

/**
  @c variable
  @e set block local variable(which is pointed by 'idx') as val.
     level means nest level of block, and specify how above this variable.
  @j level, idx で指定されたブロックローカル変数の値を val にする。
     level はブロックのネストレベルで、何段上かを示す。
 */
DEFINE_INSN
setdynamic
(ulong idx, ulong level)
(VALUE val)
()
{
  int i;
  VALUE *dfp2 = GET_DFP();
  for(i=0; i<level; i++){
    dfp2 = (VALUE *)dfp2[0];
#if VMDEBUG > 0
    if(dfp2 == 0){
      rb_bug("setdynamic: unknown level");
    }
#endif
  }
  *(dfp2-idx) = val;
}

/**
  @c variable
  @e get instance variable id of obj.
  @j obj のインスタンス変数 id を得る。
 */
DEFINE_INSN
getinstancevariable
(ID id)
()
(VALUE val)
{
  val = rb_ivar_get(GET_SELF(), id);
}

/**
  @c variable
  @e set instance variable id of obj as val.
  @j obj のインスタンス変数を val にする。
 */
DEFINE_INSN
setinstancevariable
(ID id)
(VALUE val)
()
{
  rb_ivar_set(GET_SELF(), id, val);
}

/**
  @c variable
  @e get class variable id of klass as val.
  @j klass のクラス変数 id を得る。
 */
DEFINE_INSN
getclassvariable
(ID id)
()
(VALUE val)
{
  VALUE klass;
  GET_EV_KLASS(klass);
  
  val = rb_cvar_get(klass, id);
}

/**
  @c variable
  @e set class variable id of klass as val.
  @j klass のクラス変数 id を val にする。
 */
DEFINE_INSN
setclassvariable
(ID id, VALUE declp)
(VALUE val)
()
{
  VALUE klass;
  GET_EV_KLASS(klass);
  
  if(declp == Qtrue && RTEST(ruby_verbose) && FL_TEST(klass, FL_SINGLETON)) {
    rb_warn("declaring singleton class variable");
  }

  rb_cvar_set(klass, id, val, declp);
}

/**
  @c variable
  @e
   get constant variable id. if klass is Qfalse, constant
   is able to access in this scope. if klass is Qnil, get
   top level constant. otherwise, get constant under klass
   class or module.
  @j
   定数を得る。klass が Qfalse なら、そのスコープで得られ
   る定数を得る。Qnil なら、トップレベルスコープを得る。
   それ以外なら、klass クラスの下の定数を得る。
 */
DEFINE_INSN
getconstant
(ID id)
(VALUE klass)
(VALUE val)
{
  val = thread_get_ev_const(self, th, GET_ISEQ(), klass, id);
}

/**
  @c variable
  @e
   set constant variable id. if klass is Qfalse, constant
   is able to access in this scope. if klass is Qnil, set
   top level constant. otherwise, set constant under klass
   class or module.

  @j
   定数 id を val にする。klass が Qfalse なら、そのスコープ
   で得られる定数を設定 id を設定する。Qnil なら、トップレベ
   ルスコープを設定、それ以外なら、klass クラスの下の定数を
   設定する。
 */
DEFINE_INSN
setconstant
(ID id)
(VALUE val, VALUE klass)
()
{
  if(klass == Qnil){
    klass = rb_ary_entry(th->klass_nest_stack, -1);
  }
  rb_const_set(klass, id, val);
  INC_VM_STATE_VERSION();
}

/**
  @c variable
  @e get global variable id.
  @j グローバル変数 id を得る。
 */
DEFINE_INSN
getglobal
(GENTRY entry)
()
(VALUE val)
{
  val = GET_GLOBAL(entry);
}

/**
  @c variable
  @e set global variable id as val.
  @j グローバル変数 id を得る。
 */
DEFINE_INSN
setglobal
(GENTRY entry)
(VALUE val)
()
{
  SET_GLOBAL(entry, val);
}


/**********************************************************/
/* deal with values                                       */
/**********************************************************/

/**
  @c put
  @e put nil
  @j put nil
 */
DEFINE_INSN
putnil
()
()
(VALUE val)
{
  val = Qnil;
}

/**
  @c put
  @e put self.
  @j self を置く。
 */
DEFINE_INSN
putself
()
()
(VALUE val)
{
  val = GET_SELF();
}

/**
  @c put
  @e put some object.
     i.e. Fixnum, true, false, nil, and so on.
  @j オブジェクトを置く。i.e. Fixnum, true, false, nil, and so on.
 */
DEFINE_INSN
putobject
(VALUE val)
()
(VALUE val)
{
  /* */
}

/**
  @c put
  @e put string val. string will be copied.
  @j 文字列を置く。文字列はコピーしとく。
 */

DEFINE_INSN
putstring
(VALUE val)
()
(VALUE val)
{
  val = rb_str_new3(val);
}

/**
  @c put
  @e put concatenate strings
  @j 文字列を連結して置く。
 */
DEFINE_INSN
concatstrings
(ulong num)
(...)
(VALUE val)
{
  int i;
  int len = 0;
  VALUE v;
  VALUE buff;

  val = rb_str_new(0, 0);
  for(i=num-1; i>=0; i--){
    v = TOPN(i);
    rb_str_append(val, v);
  }
  POPN(num);
}

/**
  @c put
  @e to_str
  @j to_str
 */
DEFINE_INSN
tostring
()
(VALUE val)
(VALUE val)
{
  val = rb_obj_as_string(val);
}

/**
  @c put
  @e to Regexp
  @j to Regexp
 */
DEFINE_INSN
toregexp
(ulong flag)
(VALUE str)
(VALUE val)
{
  val = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len, flag);
}

/**
  @c put
  @e put new array.
  @j 新しい配列をスタック上の num 個の値で初期化して置く。
 */
DEFINE_INSN
newarray
(ulong num)
(...)
(VALUE val)
{
  val = rb_ary_new4((long)num, STACK_ADDR_FROM_TOP(num));
  POPN(num);
}

/**
  @c put
  @e dup array
  @j 配列を dup してスタックに置く
 */
DEFINE_INSN
duparray
(VALUE ary)
()
(VALUE val)
{
  val = rb_ary_dup(ary);
}

/**
  @c put
  @e expand array to num objects.
  @j スタックトップのオブジェクトが配列であれば、それを展開する。
     配列オブジェクトの要素数が num以下ならば、代わりに nil を積む。num以上なら、
     num以上の要素は切り捨てる。
     配列オブジェクトでなければ、num - 1 個の nil を積む。
     もし flag が真なら、残り要素の配列を積む
 */
DEFINE_INSN
expandarray
(ulong num, VALUE flag)
(..., VALUE ary)
(...)
{
  int i;
  if((long)num >= 0){
    
    if(CLASS_OF(ary) == rb_cArray){
      int len = RARRAY(ary)->len;
      for(i=0; i<len && i<num; i++){
        PUSH(RARRAY(ary)->ptr[i]);
      }
      for(; i<num; i++){
        PUSH(Qnil);
      }
      if(flag){
        if(len > num){
          PUSH(rb_ary_new4(len - num, &RARRAY(ary)->ptr[num]));
        }
        else{
          PUSH(rb_ary_new());
        }
      }
    }
    else{
      if(num > 0){
        PUSH(ary);
      }
      for(i=1; i<num; i++){
        PUSH(Qnil);
      }
      if(flag){
        if(num > 0){
          PUSH(rb_ary_new());
        }
        else{
          PUSH(rb_ary_new3(1, ary));
        }
      }
    }
  }
  else{
    long holdnum = -num;
    VALUE val;
    
    val = rb_ary_new4(holdnum, STACK_ADDR_FROM_TOP(holdnum));
    if(CLASS_OF(ary) == rb_cArray){
      val = rb_ary_concat(val, ary);
    }
    else{
      rb_ary_push(val, ary);
    }
    POPN(holdnum);
    PUSH(val);
  }
}

/**
  @c put
  @e put new Hash.
  @j Hash.new
 */
DEFINE_INSN
newhash
(ulong num)
(...)
(VALUE val)
{
  int i;
  VALUE k, v;
  val = rb_hash_new();

  for(i=num; i>0; i-=2){
    v = TOPN(i-2);
    k = TOPN(i-1);
    rb_hash_aset(val, k, v);
  }
  POPN(num);
}

/**
  @c put
  @e put new Range object.(Range.new(low, high, flag))
  @j Range.new(low, high, flag) のようなオブジェクトを置く。
 */
DEFINE_INSN
newrange
(ulong flag)
(VALUE low, VALUE high)
(VALUE val)
{
  val = rb_range_new(low, high, flag);
}

/**
  @c put
  @e put !val.
  @j !val であるオブジェクトを置く。
 */
DEFINE_INSN
putnot
()
(VALUE obj)
(VALUE val)
{
  if(RTEST(obj)){
    val = Qfalse;
  }
  else{
    val = Qtrue;
  }
}


/**********************************************************/
/* deal with stack operation                              */
/**********************************************************/

/**
  @c stack
  @e pop from stack.
  @j スタックから一つポップする。
 */
DEFINE_INSN
pop
()
(VALUE val)
()
{
  /* none */
}

/**
  @c stack
  @e duplicate stack top.
  @j スタックトップをコピーしてスタックにおく
 */
DEFINE_INSN
dup
()
(VALUE val)
(VALUE val1, VALUE val2)
{
  val1 = val2 = val;
}

/**
  @c stack
  @e duplicate stack top n elements
  @j スタックトップから n 個をコピーしてスタックにおく
 */
DEFINE_INSN
dupn
(ulong n)
(...)
(...)
{
  int i;
  VALUE *sp = STACK_ADDR_FROM_TOP(n);
  for(i=0; i<n; i++){
    GET_SP()[i] = sp[i];
  }
  INC_SP(n);
}


/**
  @c stack
  @e swap top 2 vals
  @j スタックトップの2つを交換する。
 */
DEFINE_INSN
swap
()
(VALUE val, VALUE obj)
(VALUE obj, VALUE val)
{
  /* none */
}


/**
  @c stack
  @e
  @j
 */
DEFINE_INSN
reput
()
(..., VALUE val)
(VALUE val)
{
  /* none */
}


/**
  @c stack
  @e get nth stack value from stack top
  @j スタックトップから n 個目をスタックトップにコピー
 */
DEFINE_INSN
topn
(ulong n)
(...)
(VALUE val)
{
  val = TOPN(n);
}


/**********************************************************/
/* deal with setting                                      */
/**********************************************************/

/**
  @c setting
  @e define method id as body
  @j メソッド m を body として定義する。
 */
DEFINE_INSN
methoddef
(ID id, VALUE body)
()
()
{
  NODE *newbody;
  VALUE klass;
  struct iseq_object *iseqobj;

  /* dup */
  GetISeqVal(body, iseqobj);
  iseqobj->klass_nest_stack = rb_ary_dup(th->klass_nest_stack);

  /* make new node */
  newbody = NEW_NODE(YARV_METHOD_NODE, NOEX_PUBLIC, body, 0);
  
  //debugp_(-1, "self", GET_SELF());
  //printf("%s\n", body_name(nd_type(newbody)));
  //fflush(stdout);

  if(GET_ISEQ()->klass_nest_stack){
    /* method scope? */
    klass = rb_ary_entry(GET_ISEQ()->klass_nest_stack, -1);
  }
  else{
    /* class scope? */
    klass = GET_SELF();
  }
  
  rb_add_method(klass, id, newbody, NOEX_PUBLIC);
  INC_VM_STATE_VERSION();
}

/**
  @c setting
  @e define singleton method id as body to obj.
  @j 特異メソッド m を body として obj に定義する。
 */
DEFINE_INSN
singletonmethoddef
(ID id, VALUE body)
(VALUE obj)
()
{
  VALUE klass;
  NODE *newbody;
  struct iseq_object *iseqobj;

  if(FIXNUM_P(obj) || SYMBOL_P(obj)){
    rb_raise(rb_eTypeError,
             "can't define singleton method \"%s\" for %s",
             rb_id2name(id),
             rb_obj_classname(obj));
  }

  if(OBJ_FROZEN(obj)){
    rb_error_frozen("object");
  }

  klass = rb_singleton_class(obj);
  if(st_lookup(RCLASS(klass)->m_tbl, id, (void *)&newbody)){
    if(ruby_safe_level >= 4){
      rb_raise(rb_eSecurityError, "redefining method prohibited");
    }
    if(RTEST(ruby_verbose)){
      rb_warning("redefine %s", rb_id2name(id));
    }
  }
  
  GetISeqVal(body, iseqobj);
  iseqobj->klass_nest_stack = rb_ary_dup(th->klass_nest_stack);

  newbody = NEW_NODE(YARV_METHOD_NODE, NOEX_PUBLIC, body, 0);

  rb_add_method(klass, id, newbody, NOEX_PUBLIC); /* TODO: visibility */
  
  INC_VM_STATE_VERSION();
}


/**
  @c setting
  @e make alias (if v_p is Qtrue, make valias)
  @j alias を作る。もし v_p が Qtrue なら、valias (global variable) を作る
 */
DEFINE_INSN
alias
(VALUE v_p, ID id1, ID id2)
()
()
{
  VALUE klass;
  
  if(v_p == Qtrue){
    rb_alias_variable(id1, id2);
  }
  else{
    GET_EV_KLASS(klass);
    rb_alias(klass, id1, id2);
  }
}


/**
  @c setting
  @e undef
  @j undef
 */
DEFINE_INSN
undef
(ID id)
()
()
{
  VALUE klass;
  GET_EV_KLASS(klass);
  rb_undef(klass, id);
  INC_VM_STATE_VERSION();
}


/**
  @c setting
  @e defined?
  @j defined?
 */
DEFINE_INSN
defined
(ulong type, VALUE obj, VALUE needstr)
(...)
(VALUE val)
{
  VALUE klass;
  char *expr_type = 0;
  
  val = Qnil;
  
  switch(type){
  case DEFINED_IVAR:
    if(rb_ivar_defined(GET_SELF(), SYM2ID(obj))){
      expr_type = "instance-variable";
    }
    break;
  case DEFINED_GVAR:
    if (rb_gvar_defined((struct global_entry *)(obj & ~1))){
      expr_type = "global-variable";
    }
    break;
  case DEFINED_CVAR:
    GET_EV_KLASS(klass); /* TODO: it should be such as cvar_cbase */
    if(rb_cvar_defined(klass, SYM2ID(obj))){
      expr_type = "class variable";
    }
    break;
  case DEFINED_CONST:
    klass = TOPN(0); POP();
    if(thread_get_ev_defined(self, th, GET_ISEQ(), klass, SYM2ID(obj))){
      expr_type = "constant";
    }
    break;
  case DEFINED_METHOD:
    klass = CLASS_OF(TOPN(0)); POP();
    if(rb_method_boundp(klass, SYM2ID(obj), 0)){
      expr_type = "method";
    }
    break;
  default:
    rb_bug("unimplemented defined? type (vm)");
    break;
  }
  if(expr_type != 0){
    if(needstr != Qfalse){
      val = rb_str_new2(expr_type);
    }
    else{
      val = Qtrue;
    }
  }
}


/**********************************************************/
/* deal with control flow 1: class/module                 */
/**********************************************************/

/**
  @c class/module
  @e
   enter class definition scope. if super is Qfalse, and clsas
   "klass" is defined, it's redefine. otherwise, define "klass" class.
  @j
   クラス定義スコープへ移行する。もし super が Qfalse で klassクラスが
   定義されていれば、再定義である。そうでなければ、klass クラスを定義する。
 */
DEFINE_INSN
classdef
(ID id, VALUE klass_iseq)
(VALUE cbase, VALUE super)
(VALUE val)
{
  struct iseq_object *klass_iseqobj;
  int i;
  VALUE klass;
  
  if(super == Qnil){
    super = rb_cObject;
  }
  if(cbase == Qnil){
    cbase = rb_ary_entry(th->klass_nest_stack, -1);
  }

  /* find klass */
  if((super == rb_cObject) &&
     rb_const_defined_at(cbase, id)){
    
    klass = rb_const_get_at(cbase, id);
    /* already exist */
    
    /* type check */
    
  }
  else{
    /* new class declaration */
    klass = rb_define_class_id(id, super);
    rb_set_class_path(klass, cbase, rb_id2name(id));
    rb_const_set(cbase, id, klass);
  }
  
  /* enter scope */
  {
    VALUE *sp;
    struct class_frame *f;

    GetISeqVal(klass_iseq, klass_iseqobj);

    sp = GET_SP();
    if(klass_iseqobj->local_tbl){
      int ls = klass_iseqobj->local_size;
      for(i=0; i<ls; i++, sp++){
        *sp = Qnil;
      }
    }


    /* push method frame */
    f = (struct class_frame *)sp;
    f->dummy = Qfalse;
    f->control_frame.self                     = klass;
    f->control_frame.iseq                     = klass_iseq;

    f->control_frame.continuation_frame.magic = FRAME_MAGIC_CLASS;
    f->control_frame.continuation_frame.pc    = GET_PC();
    f->control_frame.continuation_frame.lfp   = GET_LFP();
    f->control_frame.continuation_frame.dfp   = GET_DFP();
    f->control_frame.continuation_frame.cfp   = GET_CFP();

    SET_PC (GET_SEQ(klass_iseqobj));
    SET_SP (sp + FRAME_WC());
    SET_CFP(sp + CF_DIFF_WC());
    SET_LFP(sp);
    SET_DFP(sp);
  }


  /* others */
  rb_ary_push(th->klass_nest_stack, klass);
  INC_VM_STATE_VERSION();
  
  NEXT_INSN();
  
  /* unreachable */
  rb_bug("classdef unreachable");
}

/**
  @c class/module
  @e enter singleton class definition scope.
  @j 特異クラス定義スコープへ移行する。
 */
DEFINE_INSN
singletonclassdef
(VALUE sclass_iseq)
(VALUE obj)
(VALUE val)
{
  val = Qnil;
}

/**
  @c class/module
  @e enter module definition scope.
  @j モジュール定義スコープへ移行する。
 */
DEFINE_INSN
moduledef
(ID id, VALUE module_iseq)
(VALUE mbase)
(VALUE val)
{
  struct iseq_object *module_iseqobj;
  int i;
  VALUE module;
  
  if(mbase == Qnil){
    mbase = rb_ary_entry(th->klass_nest_stack, -1);
  }
  
  /* find klass */
  if(rb_const_defined_at(mbase, id)){
    module = rb_const_get_at(mbase, id);
    /* already exist */
    
    /* type check */
    
  }
  else{
    /* new class declaration */
    module = rb_define_module_id(id);
    rb_set_class_path(module, mbase, rb_id2name(id));
    rb_const_set(mbase, id, module);
  }
  
  /* enter scope */
  {
    VALUE *sp;
    struct class_frame *f;

    GetISeqVal(module_iseq, module_iseqobj);
    
    sp = GET_SP();
    if(module_iseqobj->local_tbl){
      int ls = module_iseqobj->local_size;
      for(i=0; i<ls; i++, sp++){
        *sp = Qnil;
      }
    }


    /* push method frame */
    f = (struct class_frame *)sp;
    f->dummy = Qfalse;

    f->control_frame.self                     = module;
    f->control_frame.iseq                     = module_iseq;

    f->control_frame.continuation_frame.magic = FRAME_MAGIC_CLASS;
    f->control_frame.continuation_frame.pc    = GET_PC();
    f->control_frame.continuation_frame.lfp   = GET_LFP();
    f->control_frame.continuation_frame.dfp   = GET_DFP();
    f->control_frame.continuation_frame.cfp   = GET_CFP();

    SET_PC (GET_SEQ(module_iseqobj));
    SET_SP (sp + FRAME_WC());
    SET_CFP(sp + CF_DIFF_WC());
    SET_LFP(sp);
    SET_DFP(sp);
  }

  /* others */
  rb_ary_push(th->klass_nest_stack, module);
  INC_VM_STATE_VERSION();
  
  NEXT_INSN();
  
  /* unreachable */
  rb_bug("moduledef unreachable");
}

/**
  @c class/module
  @e pop class reference
  @j クラスのネスト関係情報をひとつポップする
 */
DEFINE_INSN
popcref
()
()
()
{
  rb_ary_pop(th->klass_nest_stack);
}


/**********************************************************/
/* deal with control flow 2: method/iterator              */
/**********************************************************/

/**
  @c method/iterator
  @e obj.send(id, args) # args.size => num
  @j obj.send(id, args) # args.size => num
  もし block & 0x01 == true なら、stack top は Proc である
  もし flag  & 0x01 != 0 なら、最後の引数を展開する
 */
DEFINE_INSN
send
(ID id, ulong argc, VALUE block, ulong flag, IC ic)
(...)
(VALUE val)
{
  NODE *mn;
  int i;
  VALUE recv;
  VALUE procblock = 0; /* block arg */
  VALUE klass;
  struct vm_block_object *block_ptr = 0;
  ulong num = argc;
  
  /* procblock? */
  if(flag & VM_CALL_ARGS_BLOCKARG_BIT){
    struct vm_proc_object *po;
    procblock = TOPN(0);
    GetProcVal(procblock, po);
    block_ptr = &po->bo;
    INC_SP(-1);
  }
  
  recv  = TOPN(num);
  klass = CLASS_OF(recv);

  /* expand top of stack? */
  if(flag & VM_CALL_ARGS_SPLAT_BIT){
    VALUE ary = TOPN(0);
    if(CLASS_OF(ary) != rb_cArray){
      /* why no action? why not bug? */
    }
    else{
      VALUE *ptr = RARRAY(ary)->ptr;
      VALUE *dst = GET_SP() - 1;
      int i;
      
      for(i=0; i<RARRAY(ary)->len; i++){
        dst[i] = ptr[i];
      }
      num += i-1;
      INC_SP(i-1);
    }
  }
  
  /********************************************************
   * method search
   */

//#undef  INLINE_METHOD_CACHE
//#define FAKE_INLINE_METHOD_CACHE
//#define FAKE_INLINE_METHOD_CACHE2
//#define INLINE_METHOD_CACHE
//#define ONLY_GLOBAL_METHOD_CACHE

#ifdef __GCC__

#define LIKELY(x)   (__builtin_expect(x, 1))
#define UNLIKELY(x) (__builtin_expect(x, 0))

#else

#define LIKELY(x)   ((x) == 1)
#define UNLIKELY(x) ((x) == 0)

#endif

#if 0
1  error
#elif defined(FAKE_INLINE_METHOD_CACHE)
  {
    /* don't support polymorphic method call */
    if(LIKELY(ic->ic_method != 0)){
      mn = ic->ic_method;
    }
    else{
      ic->ic_method = mn = rb_method_node(klass, id);
    }
  }
#elif defined(FAKE_INLINE_METHOD_CACHE2)
  {
    if(LIKELY(klass == ic->ic_klass)){
      mn = ic->ic_method;
    }
    else{
      mn = rb_method_node(klass, id);
      ic->ic_klass  = klass;
      ic->ic_method = mn;
    }
  }
#elif defined(INLINE_METHOD_CACHE)
  {
    if(LIKELY(klass == ic->ic_klass) &&
       LIKELY(GET_VM_STATE_VERSION() == ic->ic_vmstat)){
      mn = ic->ic_method;
    }
    else{
      mn = rb_method_node(klass, id);
      ic->ic_klass  = klass;
      ic->ic_method = mn;
      ic->ic_vmstat = GET_VM_STATE_VERSION();
    }
  }

#elif defined(ONLY_GLOBAL_METHOD_CACHE)
  {
    struct cache_entry *ent = cache + EXPR1(klass, id);
    int noex;
    
    if (LIKELY(ent->mid == id) && LIKELY(ent->klass == klass)){
      if(LIKELY(ent->method != 0)){
        // klass = ent->origin;
        // id    = ent->mid0;
        // noex  = ent->noex;
        mn    = ent->method;
      }
      else{
        mn = 0;
      }
    }
    else{
      mn = rb_method_node(klass, id);
    }
  }
#else
  error!
  mn = rb_method_node(klass, id);
#endif

  /* from other point */
#if CURRENT_INSN_send || CURRENT_INSN_send_SC_xx_ax
  if(0){
  LABEL_IS_SC(start_init_in_send_for_opt_1):
    id        = tmp_id;
    num       = 1;
    // flag      = 0;
    recv      = TOPN(1);
    klass     = CLASS_OF(recv);
    block_ptr = 0;
    procblock = 0;
    mn        = rb_method_node(klass, id);
    
    //goto end_init_in_send;
    // end_init_in_send:
    ;
  }

  if(0){
    LABEL_IS_SC(start_init_in_super):
    {
      struct iseq_object *iseqobj = GET_ISEQ();
      struct iseq_object *ip = iseqobj;
      VALUE *sp;
      int i;
      
      /* really? */
      while(ip->parent_iseqobj){
        ip = ip->parent_iseqobj;
      }

      id    = rb_to_id(ip->name);
      num   = (ulong)tmp_id;
      recv  = GET_SELF();
      klass = RCLASS(CLASS_OF(recv))->super;

      block     = 0;
      block_ptr = 0;
      mn        = rb_method_node(klass, id);
    }
  }
  if(0){
  LABEL_IS_SC(start_init_in_zsuper):
    {
      struct iseq_object *iseqobj = GET_ISEQ();
      struct iseq_object *ip = iseqobj;
      VALUE *sp, *lp;
      int i;
      
      /* really? */
      while(ip->parent_iseqobj){
        ip = ip->parent_iseqobj;
      }

      id = rb_to_id(ip->name);
      num   = ip->argc;
      recv  = GET_SELF();

      {
        struct RArray *ary = RARRAY(ip->klass_nest_stack);
        klass = ary->ptr[ary->len-1];
        klass = RCLASS(klass)->super;
      }

      block     = 0;
      block_ptr = (struct vm_block_object *)GET_BLOCK();
      mn        = rb_method_node(klass, id);

      /* copy locals */
      sp = GET_SP();
      *sp = recv; sp++;
      lp = GET_LFP() - ip->local_size;
      for(i=0; i<num; i++, sp++, lp++){
        *sp = *lp;
      }
      SET_SP(sp);
    }
  }
#endif

  /*
   * method missing
   */
  if(mn == 0){
    /* temporarily */
    if(id == idMethodMissing){
      // rb_method_missing(argc, STACK_ADDR_FROM_TOP(num), recv);
      rb_bug("method missing");
    }
    else{
      VALUE *argv = STACK_ADDR_FROM_TOP(num+1);
      argv[0] = ID2SYM(id);
      val = rb_funcall2(recv, idMethodMissing, num+1, argv);
    }
    POPN(num+1);
  }
  else{
  /********************************************************
   * method invocation
   */

  switch(nd_type(mn)){
  case NODE_CFUNC:{
    /********************************************************/
    /* call cfunc */
    struct method_frame *f;
    VALUE *sp = GET_SP();
    struct cmethod_info cmi = {0, id, klass};
    
    if(block){
      struct vm_block_object *bp = block_ptr = 
        (struct vm_block_object *) (sp + FRAME_WC() - VM_BLOCK_OBJECT_SAHRE_SIZE);;
      bp->self   = GET_SELF();
      bp->iseq   = block;
      bp->proc   = 0;
      
      th->sp = sp + FRAME_WC() + VM_BLOCK_OBJECT_OVER_SIZE;
    }
    else{
      th->sp = sp + FRAME_WC();
    }

    {
      f = (struct method_frame *)sp;
      f->block_ptr = block_ptr;
      f->control_frame.self = recv;
      f->control_frame.iseq = (VALUE)&cmi;

      f->control_frame.continuation_frame.magic = FRAME_MAGIC_METHOD;
      f->control_frame.continuation_frame.pc    = GET_PC();
      f->control_frame.continuation_frame.cfp   = GET_CFP();
      f->control_frame.continuation_frame.lfp   = GET_LFP();
      f->control_frame.continuation_frame.dfp   = GET_DFP();

      th->lfp = (VALUE*)&f->block_ptr;
      th->dfp = 0;
      th->cfp = (VALUE*)&f->control_frame;
    }

    ruby_frame->orig_func = id; /* for coerse calculation */
//printf("A> cfp: %8x, lfp: %8x, dfp: %8x, sp: %8x\n", th->cfp, th->lfp, th->dfp, th->sp);
    val = call_cfunc(mn->nd_cfnc, recv, mn->nd_argc, num,
                     STACK_ADDR_FROM_TOP(num));

    th->cfp = GET_CFP();
    SET_LFP(f->control_frame.continuation_frame.lfp);
    SET_DFP(f->control_frame.continuation_frame.dfp);
    
//printf("B> cfp: %8x, lfp: %8x, dfp: %8x, sp: %8x\n", th->cfp, th->lfp, th->dfp, th->sp);
    
    /* pop stacks (recv + argc) */
    POPN(num+1);
    break;
  }
  case NODE_ATTRSET:{
    val = rb_ivar_set(recv, mn->nd_vid, TOPN(0));
    POPN(2);
    break;
  }
  case NODE_IVAR:{
    val = rb_ivar_get(recv, mn->nd_vid);
    POP();
    break;
  }
  case YARV_METHOD_NODE:{
    /********************************************************/
    /* yarv method */
    struct iseq_object *niseqobj;
    VALUE  newiseq;
    VALUE *newlfp;
    VALUE *sp = GET_SP();
    int   block_words = 0;
    int   opt_pc      = 0;
    int   clear_local_size;

    newiseq = (VALUE)mn->nd_body;
    GetISeqVal(newiseq, niseqobj);

    clear_local_size = niseqobj->local_size - num;
    // printf("===> %d, %d\n", num, niseqobj->local_size);
    
    if(niseqobj->arg_simple){
      if(niseqobj->argc != num){
        rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
                 num, niseqobj->argc);
      }
    }
    else{
      /************************/
      /* not simple arguments */
      /************************/

      /********************************************************/
      /* rest argument */

      if(niseqobj->arg_rest == -2){
        sp += niseqobj->argc - num + 1;
        num = niseqobj->argc;
      }
      else if(niseqobj->arg_rest != -1){
        int pack_size = num - niseqobj->arg_rest + niseqobj->arg_opts;
        if(pack_size < 0){
          rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
                   num, niseqobj->arg_rest - niseqobj->arg_opts);
        }
        
        /*
         * def m(x,y,z,*a) =>
         * x, y, z, a, b, c <SP> => x, y, z, [a,b,c], <SP>
         */

        sp     -= pack_size - 1;
        sp[-1]  = rb_ary_new4(pack_size, &sp[-1]);
        num    -= pack_size;
        clear_local_size += pack_size - 1;
      }

      /********************************************************/
      /* check arguments size */
      if(niseqobj->arg_opts){
        int argc = niseqobj->argc;
        int opts = niseqobj->arg_opts;
        int idx  = 0;

        if(num < argc || num > argc + opts){
          rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
                   num, argc);
        }
        idx = num - argc;
        opt_pc = niseqobj->arg_opt_tbl[idx];
      }
      else{
        if(niseqobj->argc != num){
          rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
                   num, niseqobj->argc);
        }
      }

      /********************************************************/
      /* block argument */
      if(niseqobj->arg_block != -1){
        VALUE arg_block_val = rb_cObject;//Qnil;
        
        /* make Proc object */
        if(block){
          /* make Proc object */
          arg_block_val = thread_make_proc_object(
            self, block, GET_SELF(),
            GET_CFP(), GET_DFP(), GET_LFP());
          /* important */
          SET_LFP(th->lfp);
          SET_DFP(th->dfp);
        }
        else if(procblock){
          arg_block_val = procblock;
        }
        sp[-niseqobj->arg_block] = arg_block_val;
        sp++;
        clear_local_size--;
      }
    }

    /********************************************************/
    /* YARV method invocation */

    /* stack overflow check */
    if(th->stack + YARV_THREAD_STACK_SIZE <
       sp + niseqobj->stack_max){
      rb_bug("stack overflow");
    }
    /* TODO: fix me to set exact stack limit */
    th->stack_mark_pointer = th->stack + YARV_THREAD_STACK_SIZE;
    
    /* clear locals */
    if(niseqobj->local_tbl){
      for(; 0<clear_local_size; clear_local_size--, sp++){
        *sp = Qnil;
      }
    }

    if(block){
      struct vm_block_object *bo = block_ptr =
        (struct vm_block_object *) (sp + FRAME_WC() - VM_BLOCK_OBJECT_SAHRE_SIZE);
      bo->iseq = block;
      bo->self = GET_SELF();
      bo->proc = 0;

      SET_SP(sp + FRAME_WC() + VM_BLOCK_OBJECT_OVER_SIZE);
    }
    else{
      SET_SP (sp + FRAME_WC());
    }
    
    /* push method frame */
    {
      struct method_frame *f = (struct method_frame *)sp;
      
      f->block_ptr                              = block_ptr;
      
      f->control_frame.self                     = recv;
      f->control_frame.iseq                     = newiseq;
      f->control_frame.continuation_frame.magic = FRAME_MAGIC_METHOD;

      if(0){
        /* reserved for tailcall optimization */
        struct continuation_frame *cont =
          GET_CONTINUATION_FRAME_PTR(GET_CFP());
        f->control_frame.continuation_frame.pc    = cont->pc;
        f->control_frame.continuation_frame.cfp   = cont->cfp;
        f->control_frame.continuation_frame.lfp   = cont->lfp;
        f->control_frame.continuation_frame.dfp   = cont->dfp;
      }
      else{
        f->control_frame.continuation_frame.pc    = GET_PC();
        f->control_frame.continuation_frame.cfp   = GET_CFP();
        f->control_frame.continuation_frame.lfp   = GET_LFP();
        f->control_frame.continuation_frame.dfp   = GET_DFP();
      }
    }
    
    SET_PC (th->pc = GET_SEQ(niseqobj) + opt_pc);
    SET_LFP(sp);
    SET_DFP(sp);
    SET_CFP(sp + CF_DIFF_WC());
    
    NEXT_INSN();
    rb_bug("send unreachable");
    break;
  }
  default:
    rb_bug("sorry, don't support this method type: %s", node_name(nd_type(mn)));
  }
  }
}

/**
  @c method/iterator
  @e super(args) # args.size => num
  @j super(args) # args.size => num
 */
DEFINE_INSN
super
(ulong num, ulong flag)
(...)
(VALUE val)
{
  /* expand flag */
  tmp_id = (ID)num;
  goto LABEL_IS_SC(start_init_in_super);
}

/**
  @c method/iterator
  @e super
  @j super
 */
DEFINE_INSN
zsuper
()
(...)
(VALUE val)
{
  goto LABEL_IS_SC(start_init_in_zsuper);
}

/**
  @c method/iterator
  @e yield(args) # args.size => num, flag shows expand argument or not
  @j yield(args) # args.size => num
 */
DEFINE_INSN
yield
(ulong num, ulong flag)
(...)
(VALUE val)
{
  struct vm_block_object *bo = (struct vm_block_object *)GET_BLOCK();
  VALUE iseq;
  VALUE *prev_dfp, *prev_lfp;
  VALUE  prev_self;
  
  struct iseq_object *iseqobj;
  int i;
  
  iseq      = bo->iseq;

  if(BUILTIN_TYPE(iseq) == T_NODE){
    th->sp = GET_SP();
    th->pc = GET_PC();
    
    val = thread_invoke_yield_cfunc(th, (NODE*)iseq, num, STACK_ADDR_FROM_TOP(num), bo);
    POPN(num);
  }
  else{
    VALUE *sp;
    struct block_frame *f;
    GetISeqVal(iseq, iseqobj);

    prev_dfp  = bo->dfp;
    prev_lfp  = bo->lfp;
    prev_self = bo->self;

    if(iseqobj->argc < num){
      rb_bug("wrong number of arguments (%d for  %d)", num, iseqobj->argc);
    }
    
    sp = GET_SP();
    if(iseqobj->local_tbl){
      int ls = iseqobj->local_size - num;
      for(i=0; i<ls; i++, sp++){
        *sp = Qnil;
      }
    }

    /* push method frame */
    f = (struct block_frame *)sp;
    f->prev_dfp                               = prev_dfp;
    
    f->control_frame.self                     = prev_self;
    f->control_frame.iseq                     = iseq;

    f->control_frame.continuation_frame.magic = FRAME_MAGIC_BLOCK;
    f->control_frame.continuation_frame.pc    = GET_PC();
    f->control_frame.continuation_frame.lfp   = GET_LFP();
    f->control_frame.continuation_frame.dfp   = GET_DFP();
    f->control_frame.continuation_frame.cfp   = GET_CFP();

    
    SET_PC (GET_SEQ(iseqobj));
    SET_SP (sp + FRAME_WC());
    SET_CFP(sp + CF_DIFF_WC());
    SET_LFP(prev_lfp);
    SET_DFP(sp);

    // STACKDUMP();

    NEXT_INSN();
    /* unreachable */
  }
}


/**
  @c method/iterator
  @e return from this scope.
  @j このスコープから抜ける。
 */
DEFINE_INSN
end
(ulong idx)
(VALUE val)
(VALUE val)
{
#ifdef YARV_AOT_COMPILED
  throwed = val;
#else
  struct continuation_frame *cf;
  cf = GET_CONTINUATION_FRAME_PTR(GET_CFP());
  CHECK_FRAME_MAGIC(cf->magic);

//  STACKDUMP();
  
  /* clear environment */
  CLEAR_ENV(GET_DFP());
  SET_SP ((GET_CFP() - idx));
  
  SET_PC (cf->pc );
  SET_LFP(cf->lfp);
  SET_DFP(cf->dfp);
  SET_CFP(cf->cfp);

  if(GET_PC() == 0){
    throwed = val;
    goto finish;
  }
#endif
}


/**********************************************************/
/* deal with control flow 3: exception                    */
/**********************************************************/

/**
  @c exception
  @e longjump
  @j longjump
 */
DEFINE_INSN
throw
(ulong throw_state)
(VALUE throwobj)
()
{
  VALUE err;
  if(throw_state != 0){
    ulong pt;
    /* new throw */

    if(throw_state == TAG_BREAK || throw_state == TAG_RETRY){
      /* set current dfp */
      pt = (ulong)*(GET_DFP());
    }
    else if(throw_state == TAG_RETURN){
      /* set current lfp */
      pt = (ulong)GET_LFP();
    }
    else{
      rb_bug("isns(throw): unsupport thorw type");
    }
    throwed = (VALUE)NEW_THROW_OBJECT(throwobj, pt, state = throw_state);
    
    goto catch_entry;
  }
  else{
    /* continue throw */
    err = throwed = throwobj;
    if(BUILTIN_TYPE(err) == T_NODE){
      state = GET_THROWOBJ_STATE(err);
    }
    else{
      state = rb_ivar_get(err, idThrowState);
    }
    goto rewind_stack_point;
  }
  /* unreachable */
}

/**********************************************************/
/* deal with control flow 4: local jump                   */
/**********************************************************/

/**
  @c jump
  @e set PC to (PC + dst).
  @j PC を (PC + dst) にする。
 */
DEFINE_INSN
jump
(OFFSET dst)
()
()
{
  JUMP(dst);
}

/**
  @c jump
  @e if val is not false or nil, set PC to (PC + dst).
  @j もし val が false か nil でなければ、PC を (PC + dst) にする。
 */
DEFINE_INSN
if
(OFFSET dst)
(VALUE val)
()
{
  if(RTEST(val)){
    JUMP(dst);
  }
}

/**
  @c jump
  @e if val is false or nil, set PC to (PC + dst).
  @j もし val が false か nil ならば、PC を (PC + dst) にする。
 */
DEFINE_INSN
unless
(OFFSET dst)
(VALUE val)
()
{
  if(!RTEST(val)){
    JUMP(dst);
  }
}


/**********************************************************/
/* for optimize                                           */
/**********************************************************/

/**
  @c optimize
  @e inline cache
  @j inline cache
 */
DEFINE_INSN
getinlinecache
(IC ic, OFFSET dst)
()
(VALUE val)
{
  if(ic->ic_vmstat == GET_VM_STATE_VERSION()){
    val = ic->ic_value;
    JUMP(dst);
  }
  else{
    /* none */
    val = Qnil;
  }
}

/**
  @c optimize
  @e set inline cache
  @j set inline cahce
 */
DEFINE_INSN
setinlinecache
(OFFSET dst)
(VALUE val)
(VALUE val)
{
  IC ic = (IC) *(GET_PC() + dst + 1);
  debugs("PC: %04d\n", GET_PC() + dst);

  ic->ic_vmstat = GET_VM_STATE_VERSION();
  ic->ic_value  = val;
}


/**
  @c optimize
  @e optimized X+Y.
  @j 最適化された X+Y。
 */
DEFINE_INSN
opt_plus
()
(VALUE recv, VALUE obj)
(VALUE val)
{
  if(0){
    
  }
#if 1
  else if(FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED(FIXNUM_PLUS)){
    /* fixnum + fixnum */
    val = (recv + (obj & (~1)));
    if((~(recv^obj)&(recv^val))&0x80000000){
      val = rb_big_plus(rb_int2big(INT2FIX(recv)),
                        rb_int2big(INT2FIX(obj)));
    }
  }
#endif
  
#if 0
  else if(CLASS_OF(recv) == rb_cFloat &&
          CLASS_OF(obj)  == rb_cFloat &&
          BASIC_OP_UNREDEFINED(FLOAT_PLUS)){
    val = rb_float_new(RFLOAT(recv)->value + RFLOAT(obj)->value);
  }
#endif
  
#if 1
  else if(CLASS_OF(recv) == rb_cString &&
          CLASS_OF(obj)  == rb_cString &&
          BASIC_OP_UNREDEFINED(STRING_PLUS)){
    val = rb_str_plus(recv, obj);
  }
#endif

  else{
#ifdef YARV_AOT_COMPILED
    val = rb_funcall(recv, idPLUS, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idPLUS;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
}

/**
  @c optimize
  @e optimized X-Y.
  @j 最適化された X-Y。
 */
DEFINE_INSN
opt_minus
()
(VALUE recv, VALUE obj)
(VALUE val)
{
  if(FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED(FIXNUM_MINUS)){
    long a, b, c;

    a = FIX2LONG(recv);
    b = FIX2LONG(obj);
    c = a - b;
    val = LONG2FIX(c);

    if (FIX2LONG(val) != c) {
      val = rb_big_minus(rb_int2big(a), rb_int2big(b));
    }
  }
  else{
    /* other */
#ifdef YARV_AOT_COMPILED
    val = rb_funcall(recv, idMINUS, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idMINUS;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
}

/**
  @c optimize
  @e optimized X<Y.
  @j 最適化された X<Y。
 */
DEFINE_INSN
opt_lt
()
(VALUE recv, VALUE obj)
(VALUE val)
{
  if(FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED(FIXNUM_LT)){
    long a = FIX2LONG(recv), b = FIX2LONG(obj);
    
    if (a < b){
      val = Qtrue;
    }
    else{
      val = Qfalse;
    }
  }
  else{
    /* other */
#ifdef YARV_AOT_COMPILED
    val = rb_funcall(recv, idLT, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idLT;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
}


/**
  @c optimize
  @e <<
  @j <<
 */
DEFINE_INSN
opt_ltlt
()
(VALUE recv, VALUE obj)
(VALUE val)
{
  if(0){
  }
  else if(CLASS_OF(recv) == rb_cString &&
          CLASS_OF(obj)  == rb_cString &&
          BASIC_OP_UNREDEFINED(STRING_LTLT)){
    val = rb_str_concat(recv, obj);
  }
  else if(CLASS_OF(recv) == rb_cArray &&
          BASIC_OP_UNREDEFINED(ARRAY_LTLT)){
    val = rb_ary_push(recv, obj);
  }
  else{
    /* other */
#ifdef YARV_AOT_COMPILED
    val = rb_funcall(recv, idLTLT, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idLTLT;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
}

/**
  @c optimize
  @e optimized regexp match
  @j 最適化された正規表現マッチ
 */
DEFINE_INSN
opt_regexpmatch1
(VALUE r)
(VALUE obj)
(VALUE val)
{
  val = rb_reg_match(r, obj);
}

/**
  @c optimize
  @e optimized regexp match 2
  @j 最適化された正規表現マッチ 2
 */
DEFINE_INSN
opt_regexpmatch2
()
(VALUE obj2, VALUE obj1)
(VALUE val)
{
  if (TYPE(obj2) == T_STRING){
    val = rb_reg_match(obj1, obj2);
  }
  else {
    val = rb_funcall(obj2, idEqTilde, 1, obj1);
  }
}

/**
  @c optimize
  @e call native compiled method
  @j ネイティブコンパイルしたメソッドを kick
 */
DEFINE_INSN
opt_call_native_compiled
()
()
()
{
#ifdef __GNUC__
  struct iseq_object *iseq = GET_ISEQ();
  void *label = (void *)iseq->jit_compiled;
  
  breakpoint();
  SET_PC(iseq->iseq_orig);
  goto *label;
#else
  rb_bug("opt_call_native_compiled is not supported");
#endif
}

