/**
 *  $Id: compile.c 150 2005-03-01 04:54:42Z ko1 $
 * Create : K.S. 04/01/01 03:42:15
 *
 * ruby node tree -> yarv code
 */


#include <ruby.h>
#include <node.h>
#include <env.h>

#include "yarvcore.h"
#include "compile.h"
#include "insns.inc"


/* for debug */
static long gl_node_level = 0;
static long gl_tmp = 0;

static VALUE iseq_compile_each(VALUE self, NODE* n, int);
static VALUE iseq_setup(VALUE self, VALUE seq_ary, struct iseq_object *iseqobj);
static VALUE iseq_optimize(VALUE self, VALUE seq_ary);
static VALUE iseq_insns_unification(VALUE self, VALUE seq_ary);

static VALUE set_sequence_stackcaching(VALUE iseq);

static VALUE set_sequence(VALUE self, VALUE seq_ary_op);
static VALUE set_exception_table(VALUE self);
static VALUE set_localtbl(VALUE self, ID* tbl);
static VALUE set_arguments(VALUE self, NODE *node);
static NODE* set_block_local_tbl(VALUE self, NODE *node, VALUE seq_ary);
static VALUE set_exception_tbl(VALUE self);
static VALUE set_optargs_table(VALUE self);
static VALUE new_label(int );

#include "optinsn.inc"
#include "optunifs.inc"

#define REWIND_DSIZE()  1
#define MREWIND_DSIZE() 2

VALUE iseq_compile(VALUE self, VALUE narg){
  VALUE volatile seq_ary = NEW_SEQ();
  VALUE tmp;
  struct iseq_object *iseqobj;
  NODE *node = (NODE*)narg;

  debugs("[compile step 1]\n");
  
  GetISeqVal(self, iseqobj);

  if(iseqobj->type == ISEQ_TYPE_BLOCK){
    node = set_block_local_tbl(self, node, seq_ary);
  }
  /*
    compile_each return nested array.
    ex) [insnA, [insnB, ope1], "label", ...]
   */
  if(node && nd_type(node) == NODE_SCOPE){
    /* with node scope */
    NODE *sn_body = node->nd_next; /* sn: scope node */
    NODE *ndargs  = 0;

    if(iseqobj->type != ISEQ_TYPE_BLOCK){
      set_localtbl(self, ((NODE*)node)->nd_tbl);
    }

    if(sn_body){
      switch(nd_type(sn_body)){
      case NODE_BLOCK:
        if(nd_type(sn_body->nd_head) == NODE_ARGS){
          /* some method attribute process */
          ndargs = sn_body->nd_head;
          ADD_SEQ(seq_ary, set_arguments(self, ndargs));
          
          /* with sn_body->nd_head */
          if(iseqobj->type == ISEQ_TYPE_METHOD){
            iseqobj->rewind_frame_size = iseqobj->local_size + MREWIND_DSIZE();
            ADD_SEQ(seq_ary, COMPILE("normal method", sn_body->nd_next));
          }
          else if(iseqobj->type == ISEQ_TYPE_CLASS){
            iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
            ADD_SEQ(seq_ary, COMPILE("class/module", sn_body->nd_next));
          }
          else{
            rb_bug("must be class or method");
          }

        }
        else{
          /* normal block */

          if(iseqobj->type == ISEQ_TYPE_CLASS){
            iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
            ADD_SEQ(seq_ary, COMPILE("class/module", sn_body));
          }
          else if(iseqobj->type == ISEQ_TYPE_BLOCK){
            iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
            ADD_SEQ(seq_ary, COMPILE("normal block", sn_body));
          }
          else{
            rb_bug("must be class or block");
          }
        }
        break;
      case NODE_ARGS:
        /* empty method */
        /* some method attribute process */
        debugs("empty method\n");
        
        ADD_SEQ (seq_ary, set_arguments(self, sn_body));
        ADD_INSN(seq_ary, nd_line(sn_body), putnil);

        iseqobj->rewind_frame_size = iseqobj->local_size + MREWIND_DSIZE();
        break;

      default:
        ADD_SEQ(seq_ary, COMPILE("other scope", sn_body));
        
        iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
        break;
      }
    }
    else{
      /* sn_body == 0 */
      ADD_INSN(seq_ary, 0, putnil);
      
      iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
    }
  }
  else{
    if(iseqobj->type == ISEQ_TYPE_BLOCK){
      volatile VALUE endl;
      endl = iseqobj->compile_data->end_label = NEW_LABEL(0);
      iseqobj->compile_data->start_label = NEW_LABEL(0);
      iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
      ADD_LABEL(seq_ary, iseqobj->compile_data->start_label);
      ADD_SEQ  (seq_ary ,COMPILE("block body", node));
      ADD_LABEL(seq_ary, iseqobj->compile_data->end_label);
    }
    else if(iseqobj->type == ISEQ_TYPE_TOP){
      set_localtbl(self, ruby_scope->local_tbl);
      iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
      ADD_SEQ(seq_ary, COMPILE("top level node", node));
    }
    else if(iseqobj->type == ISEQ_TYPE_RESCUE){
      set_exception_tbl(self);
      iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
      ADD_SEQ(seq_ary, COMPILE("rescue", node));
    }
    else if(iseqobj->type == ISEQ_TYPE_ENSURE){
      set_exception_tbl(self);
      iseqobj->rewind_frame_size = iseqobj->local_size + REWIND_DSIZE();
      ADD_SEQ(seq_ary, COMPILE_POPED("ensure", node));
    }
    else if(node == 0){
      ADD_SEQ(seq_ary, COMPILE("nil", node));
    }
    else{
      rb_bug("unknown scope");
    }
  }

  GC_CHECK();

  if(iseqobj->type == ISEQ_TYPE_RESCUE ||
     iseqobj->type == ISEQ_TYPE_ENSURE){
    
    ADD_INSN2(seq_ary, 0, getdynamic, I2F(1), I2F(0));
    ADD_INSN1(seq_ary, 0, throw, I2F(0));
  }
  else{
    ADD_INSN1(seq_ary, 0, end, I2F(iseqobj->rewind_frame_size));
  }

  return iseq_setup(self, seq_ary, iseqobj);
}


VALUE thread_eval_body(VALUE);

#ifdef DISPATCH_DIRECT_THREADED_CODE
static VALUE iseq_translate_direct_threaded_code(VALUE self){
  struct iseq_object *iseqobj;
  void **table = (void **)thread_eval_body(0);
  int i, len;
  
  GetISeqVal(self, iseqobj);
  iseqobj->iseq_dt = ALLOC_N(VALUE, iseqobj->size);
  MEMCPY(iseqobj->iseq_dt, iseqobj->iseq, VALUE, iseqobj->size);
  
  for(i=0; i<iseqobj->size; /* */){
    int insn = iseqobj->iseq_dt[i];
    int len  = insn_len(insn);
    iseqobj->iseq_dt[i] = (VALUE)table[insn];
    i += len;
  }
  return self;
}
#endif


static VALUE iseq_setup(VALUE self, VALUE seq_ary, struct iseq_object *iseqobj){
  debugs("[compile step 2]\n");
  /* must flatten! seq_ary */
  rb_funcall(seq_ary, rb_intern("flatten!"), 0);

  GC_CHECK();

#if CPDEBUG > 5
  iseq_disasm_ary_dump(seq_ary);
#endif
  GC_CHECK();

  debugs("[compile step 3.1 (iseq_optimize)]\n");

  seq_ary = iseq_optimize(self, seq_ary);
  if(seq_ary == 0){ return 0; }

  debugs("[compile step 3.1.1 (iseq_insns_unification)]\n");
  seq_ary = iseq_insns_unification(self, seq_ary);

  debugs("[compile step 3.2 (set_sequence_stackcaching)]\n");
  seq_ary = set_sequence_stackcaching(seq_ary);
  if(seq_ary == 0){ return 0; }

  debugs("[compile step 4 (set_sequence)]\n");
  set_sequence(self, seq_ary);

  iseqobj->insns_ary = seq_ary;


  GC_CHECK();
  debugs("[compile step 4.1 (set_exception_table)]\n");
  set_exception_table(self);

  //printf(RSTRING(iseq_disasm(self))->ptr);
  debugs("[compile step 5]\n");

  set_optargs_table(self);
  debugs("[compile step 6]\n");

  GC_CHECK();

#if CPDEBUG > 5
  iseq_disasm(self);
#endif
  
#ifdef DISPATCH_DIRECT_THREADED_CODE
  iseq_translate_direct_threaded_code(self);
#endif
  
  return self;
}

VALUE iseq_assemble_setup(VALUE self, VALUE args, VALUE locals, VALUE insn_ary){
  struct iseq_object *iseqobj;
  int alen = RARRAY(args)->len, llen = RARRAY(locals)->len;
  int i;
  
  GetISeqVal(self, iseqobj);

  /* setup iseqobj (args, locals) */
  iseqobj->argc = RARRAY(args)->len;
  
  /* TODO: rest and opt should be supported */
  iseqobj->arg_simple = 1;

  if(iseqobj->local_tbl){
    ruby_xfree(iseqobj->local_tbl);
  }
  iseqobj->local_tbl  = (ID*)ALLOC_N(ID*, alen + llen);
  iseqobj->local_size = alen + llen;

  for(i=0; i<alen; i++){
    iseqobj->local_tbl[i]      = SYM2ID(RARRAY(args)->ptr[i]);
  }
  for(i=0; i<llen; i++){
    iseqobj->local_tbl[i+alen] = SYM2ID(RARRAY(locals)->ptr[i]);
  }
  
  iseqobj->catch_table_ary = rb_ary_new();
  return iseq_setup(self, insn_ary, iseqobj);
}


VALUE set_exception_tbl(VALUE self){
  struct iseq_object *iseqobj;
  static ID id_dollar_bang;
  GetISeqVal(self, iseqobj);

  if(!id_dollar_bang){
    id_dollar_bang = rb_intern("$!");
  }
  iseqobj->local_tbl = (ID*)ALLOC_N(ID*, 1);
  iseqobj->local_size = 1;
  iseqobj->local_tbl[0] = id_dollar_bang;
  return self;
}


NODE* set_block_local_tbl(VALUE self, NODE *node, VALUE seq_ary){
  struct iseq_object *iseqobj;
  VALUE vars = rb_ary_new();
  VALUE tmp;
  NODE *nargs, *ntmp, *nelem;
  GetISeqVal(self, iseqobj);
  
  /* argument check */
  if(iseqobj->type != ISEQ_TYPE_BLOCK){
    rb_bug("set_block_local_tbl: unexpected iseq type");
  }

  nargs = node->nd_var;
  if(nargs){
    switch(nd_type(nargs)){
    case NODE_MASGN:
      if(nargs->nd_head == 0){
        rb_bug("unsupport this type block parameter");
      }
      else{
        iseqobj->argc = nargs->nd_head->nd_alen;
        ntmp = nargs->nd_head;
        while(ntmp){
          // printf("-- %s\n", node_name(nd_type(ntmp)));
          nelem = ntmp->nd_head;
          if(nd_type(nelem) != NODE_DASGN_CURR){
            char buff[0x20];
            ID   id;
            int  idx = iseqobj->argc - RARRAY(vars)->len;
            snprintf(buff, 0x20, "__tp%d__", idx);
            id = rb_intern(buff);
            /* idx-th param, current level*/
            ADD_INSN2(seq_ary, nd_line(node), getdynamic, I2F(idx), 0); 
            ADD_SEQ(seq_ary,
                    rb_ary_pop(COMPILE("set_block_local_tbl#masgn/other",
                                       nelem)));
            rb_ary_push(vars, I2F(id));
          }
          else{
            rb_ary_push(vars, I2F(nelem->nd_vid));
            debugi("set_block_local_tbl#NODE_MASGN", (nelem->nd_vid));
          }
          ntmp = ntmp->nd_next;
        }
      }
      break;
    case NODE_DASGN_CURR:
      iseqobj->argc = 1;
      rb_ary_push(vars, I2F(nargs->nd_vid));
      debugi("set_block_local_tbl#NODE_DASGN_CURR", (nargs->nd_vid));
      break;

    /* for 1.x compatibility */
    default:{
      /* initial values */
      static ID tid;
      if(tid == 0){
        tid = rb_intern("__tp__");
      }
      iseqobj->argc = 1;
      ADD_INSN2(seq_ary, nd_line(node), getdynamic, I2F(1), 0); /* first param, current level*/
      ADD_SEQ  (seq_ary, rb_ary_pop(COMPILE("set_block_local_tbl#lasgn/dasgn", nargs)));
      rb_ary_push(vars, I2F(tid));
      debugi("set_block_local_tbl#lasgin/dasgn", (nargs->nd_vid));
      break;
    }
    }
  }
  else{
    iseqobj->argc = 0;
  }
  iseqobj->arg_rest = 0;
  node = node->nd_body;

  /* block local variables check */
  /* this part will be deleted   */
  while(node != 0 && nd_type(node) == NODE_BLOCK){
    nelem = node->nd_head;
    if(nelem == 0                         ||
       nd_type(nelem)  != NODE_DASGN_CURR ||
       nelem->nd_value != 0               ||
       rb_ary_includes(vars, tmp = I2F(nelem->nd_vid))){
      break;
    }
    rb_ary_push(vars, tmp);
    node = node->nd_next;
  }

  /* This code will be vanished                                     */
  /* because variable appeared in block first will be method local. */
  /* patch from Kent Sibilev. Thanks.                               */
  if(node && nd_type(node) == NODE_BLOCK){
    nelem = node->nd_head;
    while(nelem && nd_type(nelem) == NODE_DASGN_CURR){
      if(!rb_ary_includes(vars, tmp = I2F(nelem->nd_vid))){
        rb_ary_push(vars, tmp);
      }
      nelem = nelem->nd_value;
    }
  }
  
  {
    int i, size = RARRAY(vars)->len;
    
    if(size > 0){
      iseqobj->local_tbl = (ID*)ALLOC_N(ID*, size);
      for(i=0; i<size; i++){
        iseqobj->local_tbl[i] = FIX2INT(RARRAY(vars)->ptr[i]);
        debugi("block param", iseqobj->local_tbl[i]);
      }
    }
    iseqobj->local_size = size;
  }
  return node;
}

static int get_dyna_var_idx_at_raw(struct iseq_object *iseqobj, ID id){
  int i;
  for(i=0; i<iseqobj->local_size; i++){
    if(iseqobj->local_tbl[i] == id){
      return i;
    }
  }
  return -1;
}

static int get_dyna_var_idx_at(VALUE self, ID id){
  struct iseq_object *iseqobj;
  GetISeqVal(self, iseqobj);
  return get_dyna_var_idx_at_raw(iseqobj, id);
}

static int get_dyna_var_idx(VALUE self, ID id, int *level, int *ls){
  struct iseq_object *iseqobj;
  int lv = 0, idx;
  GetISeqVal(self, iseqobj);
  
  while(iseqobj){
    if((idx = get_dyna_var_idx_at_raw(iseqobj, id)) >= 0){
      *level = lv;
      *ls    = iseqobj->local_size;
      return idx;
    }
    iseqobj = iseqobj->parent_iseqobj;
    lv++;
  }
  return -1;
}

/**

 */
VALUE set_arguments(VALUE self, NODE *node){
  struct iseq_object *iseqobj;
  VALUE optargs = NEW_SEQ();
  int i;
  
  GetISeqVal(self, iseqobj);
  
  if(node){
    /* normal method */
    iseqobj->argc     = node->nd_cnt;
    iseqobj->arg_rest = node->nd_rest;
    if(iseqobj->arg_rest > 0){
      iseqobj->arg_rest -= 2; /* for parser convension */
    }

    /* optional initializer */
    if(node->nd_opt){
      int oargs = 0;
      NODE* optarg = node->nd_opt;
      VALUE label;
      VALUE labels = rb_ary_new();
      i = 0;
      while(optarg){
        label = NEW_LABEL(nd_line(node));
        rb_ary_push(labels,  label);
        rb_ary_push(optargs, label);
        rb_ary_push(optargs, COMPILE_POPED("optarg", optarg->nd_head));

        optarg = optarg->nd_next;
        i += 1;
      }
      /* last label */
      label = NEW_LABEL(nd_line(node));
      rb_ary_push(labels,  label);
      rb_ary_push(optargs, label);
      i+=1;
      
      iseqobj->arg_opts = i;
      iseqobj->arg_opt_tbl = ALLOC_N(VALUE, i);
      MEMCPY(iseqobj->arg_opt_tbl, RARRAY(labels)->ptr, VALUE, i);
    }
    else{
      iseqobj->arg_opts = 0;
    }
  }
  else{
    iseqobj->argc     =  0;
    iseqobj->arg_rest = -1;
    iseqobj->arg_opts =  0;
  }

  if(iseqobj->arg_rest + 1 != 0 ||
     iseqobj->arg_opts     != 0){
    iseqobj->arg_simple = 0;
  }
  else{
    iseqobj->arg_simple = 1;
  }

  return optargs;
}


/**

 */
VALUE set_localtbl(VALUE self, ID* tbl){
  struct iseq_object *iseqobj;
  int size;
  
  GetISeqVal(self, iseqobj);
  if(tbl){
    size = *tbl - 2 + 1;
  }
  else{
    size = 1;
  }
  iseqobj->local_tbl = (ID*)ALLOC_N(ID*, size);
  if(tbl && size > 1){
    MEMCPY(iseqobj->local_tbl + 1, tbl + 3, ID*, size - 1);
  }
  iseqobj->local_size = size;
  return self;
}

/**
  ruby insn object array -> raw instruction sequence
 */
VALUE set_sequence(VALUE self, VALUE seq){
  struct RArray *ary = RARRAY(seq);

  VALUE volatile *iseq;
  VALUE volatile obj;
  
  int i, k, pos, stack_max = 0;
  struct label_object *lobj;
  struct insn_object  *iobj;
  struct insn_info_struct *insn_info_tbl;
  struct iseq_object *iseqobj;
  GetISeqVal(self, iseqobj);

  GC_CHECK();

  // set label position
  for(i=0, k=0, pos=0; i<ary->len; i++){
    obj = ary->ptr[i];
    GC_CHECK();
    
    if(obj == Qnil){
      /* ignore */
    }
    else if(CLASS_OF(obj) == cYarvInsn){
      pos += insn_length(obj);
      k++;
    }
    else if(CLASS_OF(obj) == cYarvLabel){
      GetLabelVal(obj, lobj);
      lobj->position = pos;
      lobj->set      = Qtrue;
    }
    else{
      dpv("error(obj)  ", obj);
      dpv("error(class)", CLASS_OF(obj));
      rb_bug("must be LABEL");
      return 0;
    }
  }
  
  // make instruction sequence
  iseq = ALLOC_N(VALUE, pos);
  insn_info_tbl = ALLOC_N(struct insn_info_struct, k);

  GC_CHECK();

  for(i=0, pos=0, k=0; i<ary->len; i++){
    obj = ary->ptr[i];
    if(CLASS_OF(obj) == cYarvInsn){
      int j, len, insn;
      char *types;
      VALUE *operands = 0;
      
      GetInsnVal(obj, iobj);
      insn = iobj->insn_id;
      iseq[pos]  = insn;
      types      = insn_op_types(insn);
      len        = insn_len(insn);
      stack_max += insn_ret_num(insn);

      if(iobj->operands){
        operands = RARRAY(iobj->operands)->ptr;
        if(RARRAY(iobj->operands)->len != len -1){
          dp(obj);
          dp(iobj->operands);
          rb_bug("operand size miss! (%d for %d)", RARRAY(iobj->operands)->len, len - 1);
          return 0;
        }
      }
      else{
        if(len != 1){
          rb_bug("operand size miss!");
          return 0;
        }
      }
      
      for(j=0; types[j]; j++){
        char type = types[j];
        switch(type){
        case 'L':          /* label(destination position) */
          GetLabelVal(operands[j], lobj);
          if(lobj->set != Qtrue){
            rb_bug("unknown label");
          }
          iseq[pos+1+j] = lobj->position - (pos + len);
          break;
        case 'U':          /* ulong */
          iseq[pos+1+j] = FIX2INT(operands[j]);
          break;
        case 'V':          /* VALUE */
        case 'C':          /* class path */
          {
            VALUE v = operands[j];
            iseq[pos+1+j] = v;
            /* to mark ruby object */
            rb_ary_push(iseqobj->iseq_mark_ary, v);
          }
          break;
        case 'M':          /* inline cache */
          {
            VALUE v = (VALUE)NEW_INLINE_CACHE_ENTRY();
            iseq[pos+1+j] = v;
            rb_ary_push(iseqobj->iseq_mark_ary, v);
            break;
          }
        case 'I':          /* ID */
          iseq[pos+1+j] = SYM2ID(operands[j]);
          break;
        case 'G':
          {
            struct global_entry *entry = (struct global_entry*)(operands[j] & (~1));
            iseq[pos+1+j] = (VALUE)entry;
          }
          break;
        default:
          rb_bug("unknown operand type: %c", type);
          return 0;
        }
      }
      insn_info_tbl[k].line_no  = iobj->line_no;
      insn_info_tbl[k].position = pos;

      pos += len;
      k++;
    }
    else{
      /* do nothing */
    }
  }
  
  {
    // set self
    volatile struct iseq_object *iseqobj;
    GetISeqVal(self, iseqobj);
    iseqobj->iseq = (void*)iseq;
    iseqobj->size  = pos;
    iseqobj->insn_info_tbl = insn_info_tbl;
    iseqobj->insn_info_size= k;
    iseqobj->stack_max = stack_max;
  }
  return self;
}

static int label_get_position(VALUE self){
  struct label_object *lobj;
  
  GetLabelVal(self, lobj);
  return lobj->position;
}

VALUE set_exception_table(VALUE self){
  struct iseq_object *iseqobj;
  VALUE *tptr, *ptr;
  int tlen, i;
  struct catch_table_entry *entry;

  GetISeqVal(self, iseqobj);
  tlen = RARRAY(iseqobj->catch_table_ary)->len;
  tptr = RARRAY(iseqobj->catch_table_ary)->ptr;

  iseqobj->catch_table = ALLOC_N(struct catch_table_entry, tlen);
  iseqobj->catch_table_size = tlen;
  
  for(i=0; i<tlen; i++){
    ptr   = RARRAY(tptr[i])->ptr;
    entry = &iseqobj->catch_table[i];
    entry->type  = ptr[0];
    entry->start = label_get_position(ptr[1]);
    entry->end   = label_get_position(ptr[2]);
    entry->iseq  = ptr[3];
    
    if(entry->iseq != 0){
      rb_ary_push(iseqobj->iseq_mark_ary, entry->iseq);
    }
    
    entry->sp    = FIX2INT(ptr[4]);
    if(ptr[5]){
      entry->cont  = label_get_position(ptr[5]);
    }
    else{
      entry->cont  = 0;
    }
  }
  //
  iseqobj->catch_table_ary = 0; /* free */
  return self;
}

VALUE set_optargs_table(VALUE self){
  struct iseq_object *iseqobj;
  int i;
  
  GetISeqVal(self, iseqobj);

  if(iseqobj->arg_opts != 0){
    for(i=0; i< iseqobj->arg_opts; i++){
      // dp((VALUE)iseqobj->arg_opt_tbl[i]);
      iseqobj->arg_opt_tbl[i] =
        label_get_position((VALUE)iseqobj->arg_opt_tbl[i]);
    }
  }

  return self;
}

VALUE iseq_optimize(VALUE self, VALUE seq_ary){
  return seq_ary;
}

VALUE iseq_insns_unification(VALUE self, VALUE seq_ary){
#ifdef OPT_INSNS_UNIFICATION
  return insns_unification(self, seq_ary);
#else
  return seq_ary;
#endif
}

#ifdef OPT_STACK_CACHING

#define SC_INSN(insn, stat) sc_insn_info[(insn)][(stat)]
#define SC_NEXT(insn)       sc_insn_next[insn]

#include "opt_sc.inc"

int insn_set_sc_state(VALUE insn, int state){
  struct insn_object *iobj;
  int nstate;
  int insn_id;
  GetInsnVal(insn, iobj);

  insn_id = iobj->insn_id;
  
  iobj->insn_id = SC_INSN(insn_id, state);
  nstate        = SC_NEXT(iobj->insn_id);

  if(insn_id == BIN(jump) ||
     insn_id == BIN(if)   ||
     insn_id == BIN(unless)){
    VALUE label = RARRAY(iobj->operands)->ptr[0];
    struct label_object *lobj;
    GetLabelVal(label, lobj);
    if(lobj->sc_state != 0){
      if(lobj->sc_state != nstate){
        printf("%d, %d: ", lobj->sc_state, nstate);
        dp(insn);
        dp(label);
        rb_bug("insn_set_sc_state error\n");
        return 0;
      }
    }
    else{
      lobj->sc_state = nstate;
    }
  }
  else if(insn_id == BIN(end)){
    nstate = SCS_XX;
  }
  
  return nstate;
}

int label_set_sc_state(VALUE label, int state){
  struct label_object *lobj;
  GetLabelVal(label, lobj);

  if(lobj->sc_state != 0){
    if(lobj->sc_state != state){
      state = lobj->sc_state;
    }
  }
  else{
    lobj->sc_state = state;
  }
  
  return state;
}

VALUE rb_ary_insert_one_element(VALUE ary, int idx, VALUE e){
  VALUE rest;
  struct RArray *a = RARRAY(ary);

  if(a->len < idx){
    rb_bug("rb_ary_insert_one_element: idx is too large");
    return 0;
  }

  rest = rb_ary_new4(a->len - idx, a->ptr + idx);
  a->len = idx;

  rb_ary_push(ary, e);
  rb_ary_concat(ary, rest);
  return ary;
}

#endif

static VALUE set_sequence_stackcaching(VALUE ary){
  VALUE e, *seq;
  int i;
  int state;
  unsigned long size;

#ifdef OPT_STACK_CACHING
  size   = RARRAY(ary)->len;
  seq    = RARRAY(ary)->ptr;

  state = SCS_XX;
  for(i=0; i<size; i++){
  redo_point:
    e = seq[i];

    if(CLASS_OF(e) == cYarvInsn){
      struct insn_object *iobj;
      int insn_id;
      GetInsnVal(e, iobj);
      
      insn_id = iobj->insn_id;
      switch(insn_id){
      case BIN(jump):
      case BIN(if):
      case BIN(unless):
        if(state == SCS_BA){
          VALUE swap_insn = new_insn(BIN(swap), 0, Qfalse);
          ary = rb_ary_insert_one_element(ary, i, swap_insn);
          size = RARRAY(ary)->len;
          seq  = RARRAY(ary)->ptr;
          goto redo_point;
        }
        break;
        
      case BIN(nop):
        /* exception merge point */
        if(state != SCS_AX){
          VALUE reput_insn = new_insn(BIN(reput), 0, Qfalse);
          e = seq[i] = reput_insn;
        }
        break;
      }
      
      state = insn_set_sc_state(e, state);
    }
    else if(CLASS_OF(e) == cYarvLabel){
      state = label_set_sc_state(e, state);
    }
    else if(e == Qnil){
      /* ignore */
    }
    else{
      return 0;
    }
  }
#endif
  return ary;
}

static VALUE new_label(int line){
  static int label_no = 0;
  VALUE obj;
  VALUE args[16];

  args[0] = I2F(label_no++);
  obj = rb_class_new_instance(1, args, cYarvLabel);
  return obj;
}


static VALUE new_insn(int insn, int line_no, VALUE operands){
  VALUE obj;
  VALUE args[3];
  args[0] = insn;
  args[1] = line_no;
  args[2] = operands;
  
  obj = rb_class_new_instance(3, args, cYarvInsn);
  obj = insn_optimize(obj);
  return obj;
}

VALUE compile_dstr(VALUE self, NODE *node){
  NODE *list = node->nd_next;
  VALUE lit  = node->nd_lit;
  VALUE ret;
  VALUE tmp;
  int cnt = 0;

  debugp_param("nd_lit", lit);
  if(RSTRING(lit)->len == 0){
    ret = NEW_SEQ();
    cnt = 0;
  }
  else{
    ret = NEW_INSN_SEQ1(nd_line(node), putobject, node->nd_lit);
    cnt = 1;
  }

  while(list){
    ADD_SEQ(ret, COMPILE("each string", list->nd_head));
    cnt++;
    list = list->nd_next;
  }
  if(cnt > 1){
    ADD_INSN1(ret, nd_line(node), concatstrings, I2F(cnt));
  }
  return ret;
}


static int get_root_iseq_localsize(struct iseq_object *iop){
  struct iseq_object *iseqobj = iop;
  while(iseqobj){
    if(iseqobj->parent_iseqobj){
      iseqobj = iseqobj->parent_iseqobj;
    }
    else{
      return iseqobj->local_size;
    }
  }
  rb_bug("get_root_iseq_localsize error");
  return -1;
}


static VALUE compile_array(VALUE self, NODE *node_root, VALUE opt_p){
  NODE *node = node_root;
  VALUE obj, tmp, ret;
  int i, len = node->nd_alen, line = nd_line(node);
  
  ret = NEW_SEQ();
  while(node){
    if(opt_p && nd_type(node->nd_head) != NODE_LIT){
      opt_p = Qfalse;
    }
    ADD_SEQ(ret, COMPILE("element", node->nd_head));
    node = node->nd_next;
  }

  if(opt_p == Qtrue){
    ret = rb_ary_new();
    node = node_root;
    while(node){
      rb_ary_push(ret, node->nd_head->nd_lit);
      node = node->nd_next;
    }
    ret = NEW_INSN_SEQ1(nd_line(node_root), duparray, ret);
  }
  else{
    ADD_INSN1(ret, line, newarray, I2F(len));
  }
  return ret;
}

static VALUE make_masgn_lhs(VALUE self, NODE *node){
  VALUE ret;
  
  switch(nd_type(node)){
  case NODE_ATTRASGN:{
    VALUE lhs = COMPILE("masgn lhs (NODE_ATTRASGN)", node);
    struct insn_object *iobj;
    VALUE sendinsn = rb_ary_pop(lhs);
    VALUE dupidx;

    GetInsnVal(sendinsn, iobj);
    dupidx = RARRAY(iobj->operands)->ptr[1]; /* send sym, num, ... */
    dupidx = I2F(FIX2INT(dupidx) + 1);
    RARRAY(iobj->operands)->ptr[1] = dupidx;

    ADD_INSN1(lhs, nd_line(node), topn, dupidx);
    ADD_SEQ  (lhs, sendinsn);
    ADD_INSN (lhs, nd_line(node), pop); /* result */
    ADD_INSN (lhs, nd_line(node), pop); /* rhs    */

    ret = lhs;
    break;
  }

  default:{
    VALUE lhs  = COMPILE_POPED("masgn lhs", node);
    VALUE asgn = rb_ary_pop(lhs);

    ret = asgn;
  }
  }
  return ret;
}




static int defined_expr(VALUE self, NODE *node, VALUE ret, VALUE lfinish, VALUE needstr){
  char *estr = 0;
  
  switch(nd_type(node)){

  /* easy literals */
  case NODE_NIL:
    estr = "nil";
    break;
  case NODE_SELF:
    estr = "self";
    break;
  case NODE_TRUE:
    estr = "true";
    break;
  case NODE_FALSE:
    estr = "false";
    break;

  /* variables */
  case NODE_LVAR:
    estr = "local-variable";
    break;
  case NODE_DVAR:
    estr = "local-variable(in-block)";
    break;

  case NODE_IVAR:
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_IVAR), ID2SYM(node->nd_vid), needstr);
    return 1;
  case NODE_GVAR:
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_GVAR), ((int)node->nd_entry) | 1, needstr);
    return 1;
  case NODE_CVAR:
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_CVAR), ID2SYM(node->nd_vid), needstr);
    return 1;

  case NODE_CONST:
    ADD_INSN (ret, nd_line(node), putnil);
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_CONST), ID2SYM(node->nd_vid), needstr);
    return 1;
  case NODE_COLON2:
    if(rb_is_const_id(node->nd_mid)){
      VALUE lcont = NEW_LABEL(nd_line(node));
      defined_expr(self, node->nd_head, ret, lfinish, Qfalse);
      
      ADD_INSNL(ret, nd_line(node), if, lcont);
      ADD_INSN (ret, nd_line(node), putnil);
      ADD_INSNL(ret, nd_line(node), jump, lfinish);
      
      ADD_LABEL(ret, lcont);
      ADD_SEQ(ret, COMPILE("defined/colon2#nd_head", node->nd_head));
      ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_CONST), ID2SYM(node->nd_mid), needstr);
    }
    else{
      VALUE lcont = NEW_LABEL(nd_line(node));
      defined_expr(self, node->nd_head, ret, lfinish, Qfalse);
      
      ADD_INSNL(ret, nd_line(node), if, lcont);
      ADD_INSN (ret, nd_line(node), putnil);
      ADD_INSNL(ret, nd_line(node), jump, lfinish);
      
      ADD_LABEL(ret, lcont);
      ADD_SEQ(ret, COMPILE("defined/colon2#nd_head", node->nd_head));
      ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_METHOD), ID2SYM(node->nd_mid), needstr);
    }
    return 1;
  case NODE_COLON3:
    ADD_INSN1(ret, nd_line(node), putobject, rb_cObject);
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_CONST), ID2SYM(node->nd_mid), needstr);
    return 1;


  /* method dispatch */
  case NODE_CALL:
  case NODE_VCALL:
  case NODE_FCALL:
    if(nd_type(node) == NODE_CALL){
      VALUE lcont = NEW_LABEL(nd_line(node));
      
      defined_expr(self, node->nd_recv, ret, lfinish, Qfalse);
      ADD_INSNL(ret, nd_line(node), if, lcont);
      ADD_INSN (ret, nd_line(node), putnil);
      ADD_INSNL(ret, nd_line(node), jump, lfinish);
      
      ADD_LABEL(ret, lcont);
      ADD_SEQ  (ret, COMPILE("defined/recv", node->nd_recv));
    }
    else{
      ADD_INSN(ret, nd_line(node), putself);
    }
    ADD_INSN3(ret, nd_line(node), defined, I2F(DEFINED_METHOD), ID2SYM(node->nd_mid), needstr);
    return 1;
  default:
    rb_bug("unimplemented defined");
  }

  if(estr != 0){
    if(needstr != Qfalse){
      ADD_INSN1(ret, nd_line(node), putstring, rb_str_new2(estr));
    }
    else{
      ADD_INSN1(ret, nd_line(node), putobject, Qtrue);
    }
    return 1;
  }
  return 0;
}



/**
  compile each node

  self:  InstructionSequence
  node:  Ruby compiled node
  poped: This node will be poped
 */
static VALUE iseq_compile_each(VALUE self, NODE* node, int poped){
  VALUE volatile tmp; /* reserved by macro */
  VALUE volatile ret  = Qnil;
  VALUE volatile iseq = Qnil;
  VALUE volatile l1, l2;
  VALUE volatile head;
  VALUE volatile next;
  VALUE volatile obj;
  NODE  volatile *n = node;
  VALUE args[10];
  int lv, type;
  struct iseq_object *iseqobj;

  GetISeqVal(self, iseqobj);
  
  GC_CHECK();

  if(node == 0){
    if(!poped){
      debug_nodeprint("NODE_NIL(implicit)");
      debug_nodeprint_close();
      return NEW_INSN_SEQ(0, putnil);
    }
    return ret;
  }

  debug_nodeprint(node_name(nd_type(node)));
  
  type = nd_type(node);
  
  switch(type){

  case NODE_METHOD:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_METHOD"));
    break;
  }
  case NODE_FBODY:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_FBODY"));
    break;
  }
  case NODE_CFUNC:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_CFUNC"));
    break;
  }
  case NODE_SCOPE:{
    /* OK */
    COMPILE_ERROR(("BUG: shouldn't reach: NODE_SCOPE"));
    break;
  }
  case NODE_BLOCK:{
    ret  = NEW_SEQ();
    while(node && nd_type(node) == NODE_BLOCK){
      ADD_SEQ(ret, COMPILE_("BLOCK body", node->nd_head,
                            (node->nd_next == 0 && poped == 0) ? 0 : 1));
      node = node->nd_next;
    }
    if(node){
      ADD_SEQ(ret, COMPILE_("BLOCK next", node->nd_next, poped));
    }
    break;
  }
  case NODE_IF:{
    volatile VALUE cond;
    volatile VALUE thn;
    volatile VALUE els;

    cond = COMPILE("cond", node->nd_cond);
    thn  = COMPILE_("then", node->nd_body, poped);
    els  = COMPILE_("else", node->nd_else, poped);
    
    l1   = NEW_LABEL(nd_line(node));
    l2   = NEW_LABEL(nd_line(node));
    ret  = NEW_SEQ  ();

    debugp_verbose("cond", cond);
    debugp_verbose("then", thn);
    debugp_verbose("else", els);
    
    ADD_SEQ  (ret, cond);
    ADD_INSNL(ret, nd_line(node), unless, l1);
    ADD_SEQ  (ret, thn);
    ADD_INSNL(ret, nd_line(node), jump,   l2);
    ADD_LABEL(ret, l1);
    ADD_SEQ  (ret, els);
    ADD_LABEL(ret, l2);
    
    break;
  }
  case NODE_CASE:{
    volatile VALUE head = COMPILE("case base", node->nd_head);
    NODE* vals;
    NODE* val;
    NODE* tempnode = node;
    volatile VALUE endlabel;
    volatile VALUE body_seq;

    node = node->nd_body;
    type = nd_type(node);
    
    if(type != NODE_WHEN){
      COMPILE_ERROR(("NODE_CASE: unexpected node. must be NODE_WHEN, byt %s", node_name(type)));
    }
    
    ret      = NEW_SEQ();
    body_seq = NEW_SEQ();
    endlabel = NEW_LABEL(nd_line(node));
    ADD_SEQ (ret, head);
    
    while(type == NODE_WHEN){
      l1  = NEW_LABEL(nd_line(node));
      ADD_LABEL(body_seq, l1);
      ADD_INSN (body_seq, nd_line(node), pop);
      ADD_SEQ  (body_seq, COMPILE_("when body", node->nd_body, poped));
      ADD_INSNL(body_seq, nd_line(node), jump, endlabel);

      vals = node->nd_head;
      if(vals && nd_type(vals) == NODE_ARRAY){
        while(vals){
          val = vals->nd_head;
          /*
            u| obj
             | obj(dupped)
             | cond_val(self)
            -> # swap
            u| obj
             | cond_val(self)
             | obj(dupped)
           */
          ADD_INSN (ret, nd_line(node), dup);
          ADD_SEQ  (ret,                COMPILE("when cond", val));
          ADD_INSN (ret, nd_line(node), swap);
          ADD_SEND (ret, nd_line(node), ID2SYM(idEqq), INT2FIX(1));
          ADD_INSNL(ret, nd_line(node), if, l1);
          vals = vals->nd_next;
        }
      }
      else{
        COMPILE_ERROR(("NODE_CASAE: must be NODE_ARRAY, but %s\n", node_name(nd_type(vals))));
      }
      node = node->nd_next;
      if(!node){
        break;
      }
      type = nd_type(node);
    }
    /* else */
    if(node){
      ADD_INSN (ret, nd_line(node), pop);
      ADD_SEQ  (ret,                COMPILE_("else", node, poped));
      ADD_INSNL(ret, nd_line(node), jump, endlabel);
    }
    else{
      debugs("== else(implicit)\n");
      ADD_INSN (ret, nd_line(tempnode), pop);
      if(!poped){
        ADD_INSN (ret, nd_line(tempnode), putnil);
      }
      ADD_INSNL(ret, nd_line(tempnode), jump, endlabel);
    }

    ADD_SEQ  (ret, body_seq);
    ADD_LABEL(ret, endlabel);
    break;
  }
  case NODE_WHEN:{
    NODE* vals;
    NODE* val;
    volatile VALUE endlabel;
    volatile VALUE body_seq;

    ret      = NEW_SEQ();
    body_seq = NEW_SEQ();
    endlabel = NEW_LABEL(nd_line(node));

    while(nd_type(node) == NODE_WHEN){
      l1  = NEW_LABEL(nd_line(node));
      ADD_LABEL(body_seq, l1);
      ADD_SEQ  (body_seq, COMPILE_("when", node->nd_body, poped));
      ADD_INSNL(body_seq, nd_line(node), jump, endlabel);
      
      vals = node->nd_head;
      if(vals && nd_type(vals) == NODE_ARRAY){
        while(vals){
          val = vals->nd_head;
          ADD_SEQ  (ret, COMPILE("when2", val));
          ADD_INSNL(ret, nd_line(val), if, l1);
          vals = vals->nd_next;
        }
      }
      else{
        COMPILE_ERROR(("NODE_WHEN: must be NODE_ARRAY, but %s\n", node_name(nd_type(vals))));
      }
      node = node->nd_next;
    }
    /* else */
    ADD_SEQ  (ret, COMPILE_("else", node, poped));
    ADD_INSNL(ret, nd_line(node), jump, endlabel);

    ADD_SEQ  (ret, body_seq);
    ADD_LABEL(ret, endlabel);
    
    break;
  }
  case NODE_OPT_N:{
    /* unsupport currently */
    COMPILE_ERROR(("BUG: unknown node: NODE_OPT_N"));
    break;
  }
  case NODE_WHILE:
  case NODE_UNTIL:{
    volatile VALUE prev_start_label = iseqobj->compile_data->start_label;
    volatile VALUE prev_end_label   = iseqobj->compile_data->end_label;
    volatile VALUE prev_redo_label  = iseqobj->compile_data->redo_label;
    volatile VALUE prev_loopval_popped = iseqobj->compile_data->loopval_popped;

    volatile VALUE cond = COMPILE("while cond", node->nd_cond);
    
    volatile VALUE start_label = iseqobj->compile_data->start_label =
      NEW_LABEL(nd_line(node)); /* next  */
    volatile VALUE end_label2  = iseqobj->compile_data->end_label   =
      NEW_LABEL(nd_line(node)); /* break */
    volatile VALUE redo_label  = iseqobj->compile_data->redo_label  =
      NEW_LABEL(nd_line(node)); /* redo  */
    volatile VALUE end_label = NEW_LABEL(nd_line(node));

    iseqobj->compile_data->loopval_popped = poped;
    
    ret = NEW_SEQ();
    
    ADD_LABEL(ret, start_label);
    ADD_SEQ  (ret, cond);
    if(type == NODE_WHILE){
      ADD_INSNL(ret, nd_line(node), unless, end_label);
    }
    else{
      ADD_INSNL(ret, nd_line(node), if, end_label);
    }
    ADD_LABEL(ret, redo_label);
    ADD_SEQ  (ret, COMPILE_("while body", node->nd_body, 1));
    ADD_INSNL(ret, nd_line(node), jump, start_label);

    ADD_LABEL(ret, end_label);
    
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }

    ADD_LABEL(ret, end_label2); /* braek */
    
    
    iseqobj->compile_data->start_label = prev_start_label;
    iseqobj->compile_data->end_label   = prev_end_label;
    iseqobj->compile_data->redo_label  = prev_redo_label;
    iseqobj->compile_data->loopval_popped = prev_loopval_popped;
    break;
  }
  case NODE_ITER:
  case NODE_FOR:{
    VALUE prevblock = iseqobj->compile_data->current_block;
    VALUE retry_label = NEW_LABEL(nd_line(node));
    VALUE retry_end_l = NEW_LABEL(nd_line(node));
    VALUE name = rb_str_new2("b@");
    rb_str_concat(name, iseqobj->name);
    
    iseqobj->compile_data->current_block = NEW_CHILD_ISEQOBJ(node, name,
                                               self, ISEQ_TYPE_BLOCK);
    ret = NEW_SEQ();
    ADD_LABEL(ret, retry_label);
    ADD_SEQ  (ret, COMPILE_("iter caller", node->nd_iter, poped));
    ADD_LABEL(ret, retry_end_l);
    iseqobj->compile_data->current_block = prevblock;

    ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, retry_label, retry_end_l, 0, 0, retry_label);
    break;
  }
  case NODE_BREAK:{
    if(iseqobj->compile_data->redo_label != 0){
      /* while/until */
      ret = NEW_SEQ();
      ADD_SEQ  (ret, COMPILE_("break val(while/until)", node->nd_stts,
                              iseqobj->compile_data->loopval_popped));
      ADD_INSNL(ret, nd_line(node), jump, iseqobj->compile_data->end_label);
    }
    else if(iseqobj->type == ISEQ_TYPE_BLOCK){
      /* escape from block */
      ret = NEW_SEQ();
      ADD_SEQ  (ret, COMPILE("break val(block)", node->nd_stts));
      ADD_INSN1(ret, nd_line(node), throw, I2F(0x02) /* TAG_BREAK */);
    }
    else{
      COMPILE_ERROR(("can't put break"));
    }

    break;
  }
  case NODE_NEXT:{
    ret = NEW_SEQ();
    if(iseqobj->compile_data->redo_label != 0){
      ADD_INSNL(ret, nd_line(node), jump, iseqobj->compile_data->start_label);
    }
    else if(iseqobj->compile_data->end_label){
      ADD_SEQ  (ret, COMPILE("next val", node->nd_stts));
      ADD_INSNL(ret, nd_line(node), jump, iseqobj->compile_data->end_label);
    }
    else{
      COMPILE_ERROR(("can't next"));
    }
    break;
  }
  case NODE_REDO:{
    ret = NEW_SEQ();
    if(iseqobj->compile_data->redo_label){
      ADD_INSNL(ret, nd_line(node), jump, iseqobj->compile_data->redo_label);
    }
    else if(iseqobj->compile_data->start_label){
      ADD_INSNL(ret, nd_line(node), jump, iseqobj->compile_data->start_label);
    }
    else{
      COMPILE_ERROR(("can't redo"));
    }
    break;
  }
  case NODE_RETRY:{
    if(iseqobj->type == ISEQ_TYPE_BLOCK ||
       iseqobj->type == ISEQ_TYPE_RESCUE){
      ret = NEW_SEQ();
      ADD_INSN (ret, nd_line(node), putnil);
      ADD_INSN1(ret, nd_line(node), throw, I2F(0x04) /* TAG_RETRY */);
    }
    else{
      COMPILE_ERROR(("can't put retry"));
    }
    break;
  }
  case NODE_BEGIN:{
    ret = NEW_SEQ();
    ADD_SEQ(ret, COMPILE_("NODE_BEGIN", node->nd_body, poped));
    break;
  }
  case NODE_RESCUE:{
    VALUE lstart = NEW_LABEL(nd_line(node));
    VALUE lend   = NEW_LABEL(nd_line(node));
    VALUE lcont  = NEW_LABEL(nd_line(node));
    VALUE rescue = NEW_CHILD_ISEQOBJ(node->nd_resq, rb_str_new2("rescue clause"),
                                     self, ISEQ_TYPE_RESCUE);
    
    ret = NEW_SEQ();
    ADD_LABEL(ret, lstart);
    ADD_SEQ(ret, COMPILE("rescue head", node->nd_head));
    ADD_LABEL(ret, lend);
    if(node->nd_else){
      ADD_SEQ(ret, COMPILE("rescue else", node->nd_else));
    }
    ADD_INSN (ret, nd_line(node), nop);
    ADD_LABEL(ret, lcont);

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    
    /* resgister catch entry */
    ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, 0, lcont);
    ADD_CATCH_ENTRY(CATCH_TYPE_RETRY,  lend, lcont, 0, 0, lstart);
    break;
  }
  case NODE_RESBODY:{
    NODE *resq = node;
    NODE *narg;
    VALUE label_miss, label_hit;
    
    ret = NEW_SEQ();
    while(resq){
      label_miss = NEW_LABEL(nd_line(node));
      label_hit  = NEW_LABEL(nd_line(node));
      
      narg = resq->nd_args;
      while(narg){
        ADD_SEQ  (ret, COMPILE("rescue arg", narg->nd_head));
        ADD_INSN2(ret, nd_line(node), getdynamic, I2F(1), I2F(0));
        ADD_SEND (ret, nd_line(node), ID2SYM(idEqq), INT2FIX(1));
        ADD_INSNL(ret, nd_line(node), if, label_hit);
        narg = narg->nd_next;
      }
      if(resq->nd_args == 0){
        ADD_INSN1(ret, nd_line(node), putobject, rb_eStandardError);
        ADD_INSN2(ret, nd_line(node), getdynamic, I2F(1), I2F(0));
        ADD_SEND (ret, nd_line(node), ID2SYM(idEqq), INT2FIX(1));
        ADD_INSNL(ret, nd_line(node), if, label_hit);
      }
      ADD_INSNL(ret, nd_line(node), jump, label_miss);
      ADD_LABEL(ret, label_hit);
      ADD_SEQ  (ret, COMPILE("resbody body", resq->nd_body));
      ADD_INSN1(ret, nd_line(node), end, I2F(iseqobj->rewind_frame_size));
      ADD_LABEL(ret, label_miss);
      resq = resq->nd_head;
    }
    break;
  }
  case NODE_ENSURE:{
    VALUE ensure = NEW_CHILD_ISEQOBJ(node->nd_ensr, rb_str_new2("ensure clause"),
                                     self, ISEQ_TYPE_ENSURE);
    VALUE lstart = NEW_LABEL(nd_line(node));
    VALUE lend   = NEW_LABEL(nd_line(node));
    VALUE lcont  = NEW_LABEL(nd_line(node));
    VALUE ensr   = COMPILE_POPED("ensure ensr", node->nd_ensr);
    VALUE prev_in_ensure = iseqobj->compile_data->in_ensure;
    iseqobj->compile_data->in_ensure = Qtrue;
    
    ret = NEW_SEQ();
    ADD_LABEL(ret, lstart);
    ADD_SEQ(ret, COMPILE_("ensure head", node->nd_head, poped));
    ADD_LABEL(ret, lend);
    if(ensr == Qnil){
      ADD_INSN(ret, nd_line(node), nop);
    }
    else{
      ADD_SEQ(ret, ensr);
    }
    ADD_LABEL(ret, lcont);

    iseqobj->compile_data->in_ensure = prev_in_ensure;
    ADD_CATCH_ENTRY(CATCH_TYPE_ENSURE, lstart, lend, ensure, 0, lcont);
    break;
  }
    
  case NODE_AND:
  case NODE_OR:{
    l1 = NEW_LABEL(nd_line(node));
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("nd_1st", node->nd_1st));
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    
    if(type == NODE_AND){
      ADD_INSNL(ret, nd_line(node), unless, l1);
    }
    else{
      ADD_INSNL(ret, nd_line(node), if, l1);
    }
    ADD_INSN (ret, nd_line(node), pop);
    ADD_SEQ  (ret, COMPILE_("nd_2nd", node->nd_2nd, poped));
    ADD_LABEL(ret, l1);
    
    break;
  }
    
  case NODE_NOT:{
    ret = NEW_SEQ();
    ADD_SEQ( ret, COMPILE("value", node->nd_body));
    ADD_INSN(ret, nd_line(node), putnot);
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
    
  case NODE_MASGN:{
    //VALUE mlhs = COMPILE("masgin mlhs", node->nd_head); // for debug
    //VALUE mrhs = COMPILE("masgin mrhs", node->nd_value); // for debug

    NODE *rhsn = node->nd_value;
    NODE *lhsn = node->nd_head;
    VALUE mlhs_seq;
    
    int rlen, llen, i, max, si = 0;
    VALUE lhs_splat = Qfalse;
    
    ret = NEW_SEQ();
    mlhs_seq = NEW_SEQ();
    llen = 0;
    while(lhsn){
      ADD_SEQ(mlhs_seq, make_masgn_lhs(self, lhsn->nd_head));
      llen += 1;
      lhsn  = lhsn->nd_next;
    }
    
    if(node->nd_args && (long)node->nd_args != -1){
      ADD_SEQ(mlhs_seq, make_masgn_lhs(self, node->nd_args));
      lhs_splat = Qtrue;
    }
    
    
    switch(nd_type(rhsn)){
    case NODE_ARRAY:
      rlen = rhsn->nd_alen;
      max = rlen > llen ? rlen : llen;
      debugs("l: %d, r: %d\n", llen, rlen);
      for(i=0; i<max; i++){
        if(i<rlen && i<llen){
          /* a, b = c, d*/
          ADD_SEQ(ret, COMPILE("masgn val1", rhsn->nd_head));
          rhsn = rhsn->nd_next;
        }
        else if(i<rlen){
          if(lhs_splat){
            while(rhsn){
              si++;
              ADD_SEQ(ret, COMPILE("masgn rhs for lhs splat", rhsn->nd_head));
              rhsn = rhsn->nd_next;
            }
            break;
          }
          else{
            /* a, b = c, d, e */
            ADD_SEQ(ret, COMPILE_POPED("masgn val2", rhsn->nd_head));
            rhsn = rhsn->nd_next;
          }
        }
        else if(i<llen){
          /* a, b, c = c, d */
          ADD_INSN(ret, nd_line(node), putnil);
        }
      }
      
      if(lhs_splat){
        ADD_INSN1(ret, nd_line(node), newarray, I2F(si));
      }
      break;
    case NODE_TO_ARY:
      ADD_SEQ  (ret, COMPILE("rhs to ary", rhsn->nd_head));
      ADD_INSN2(ret, nd_line(node), expandarray, I2F(llen), lhs_splat);
      break;
      
    case NODE_SPLAT:
      ADD_SEQ  (ret, COMPILE("rhs to ary (splat)", rhsn->nd_head));
      ADD_INSN2(ret, nd_line(node), expandarray, I2F(llen), lhs_splat);
      break;

    case NODE_ARGSCAT:{
      NODE *ary = rhsn->nd_head;
      int idx = 0;
      
      while(ary){
        if(idx < llen || lhs_splat){
          ADD_SEQ(ret, COMPILE("rhs aggscat each head", ary->nd_head));
        }
        else{
          ADD_SEQ(ret, COMPILE_POPED("rhs aggscat each head (popped)", ary->nd_head));
        }
        ary = ary->nd_next;
        idx++;
      }
      rlen = rhsn->nd_head->nd_alen;
      if(llen > idx){
        ADD_SEQ  (ret, COMPILE("rhs to ary (argscat/splat)", rhsn->nd_body));
        ADD_INSN2(ret, nd_line(node), expandarray, I2F(llen - idx), lhs_splat);
      }
      else if(lhs_splat){
        ADD_SEQ  (ret, COMPILE("rhs to ary (argscat/splat)", rhsn->nd_body));
        ADD_INSN2(ret, nd_line(node), expandarray, I2F(llen - idx), lhs_splat);
      }
      break;
    }
    default:
      rb_bug("unknown rhs: %s", node_name(nd_type(node->nd_value)));
    }

    ADD_SEQ(ret, rb_ary_reverse(mlhs_seq));

    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    break;
  }
  case NODE_LASGN:{
    int idx = get_root_iseq_localsize(iseqobj) + 2 - node->nd_cnt;
    debugs("lvar: %d\n", idx);
    obj = COMPILE("lvalue", node->nd_value);
    ret = NEW_SEQ();
    ADD_SEQ  (ret, obj);
    
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    ADD_INSN1(ret, nd_line(node), setlocal, I2F(idx));
    
    break;
  }
  case NODE_DASGN:
  case NODE_DASGN_CURR:{
    VALUE val = COMPILE("dvalue", node->nd_value);
    int idx, lv, ls;
    
    debugp_param("dassn id", rb_str_new2(rb_id2name(node->nd_vid)));
    ret = NEW_SEQ();
    ADD_SEQ(ret, val);
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    if(nd_type(node) == NODE_DASGN){
      idx = get_dyna_var_idx(self, node->nd_vid, &lv, &ls);
    }
    else{
      idx = get_dyna_var_idx_at(self, node->nd_vid);
      ls = iseqobj->local_size;
      lv = 0;
    }
    if(idx < 0){
      debugi("unknown id", node->nd_vid);
      COMPILE_ERROR(("NODE_DASGN error"));
    }
    ADD_INSN2(ret, nd_line(node), setdynamic,
              I2F(ls - idx), I2F(lv));
    break;
  }
  case NODE_GASGN:{
    VALUE val = COMPILE("lvalue", node->nd_value);

    ret = NEW_SEQ();
    ADD_SEQ(ret, val);
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    ADD_INSN1(ret, nd_line(node), setglobal, (((long)node->nd_entry) | 1));
    break;
  }
  case NODE_IASGN:{
    ret = NEW_SEQ();
    ADD_SEQ(ret, COMPILE("lvalue", node->nd_value));
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    ADD_INSN1(ret, nd_line(node), setinstancevariable, ID2SYM(node->nd_vid));
    break;
  }
  case NODE_CDECL:{
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("lvalue", node->nd_value));

    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }

    if(node->nd_vid){
      ADD_INSN (ret, nd_line(node), putnil);
      ADD_INSN1(ret, nd_line(node), setconstant, ID2SYM(node->nd_vid));
    }
    else{
      ADD_SEQ  (ret, COMPILE("nd_else->nd_head", node->nd_else->nd_head));
      ADD_INSN1(ret, nd_line(node), setconstant, ID2SYM(node->nd_else->nd_mid));
    }
    break;
  }
  case NODE_CVASGN:
  case NODE_CVDECL:{
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("cvasgn val", node->nd_value));
    if(!poped){
      ADD_INSN(ret, nd_line(node), dup);
    }
    ADD_INSN2(ret, nd_line(node), setclassvariable, ID2SYM(node->nd_vid),
              nd_type(node) == NODE_CVDECL ? Qtrue : Qfalse);
    break;
  }
  case NODE_OP_ASGN1:{
    VALUE args;
    int   argc;
    ID    id = node->nd_mid;
    ret = NEW_SEQ();

    /*
     * a[x] (op)= y
     *
     * eval a    # a
     * eval x    # a x
     * dupn 2    # a x a x
     * send :[]  # a x a[x]
     * eval y    # a x a[x] y
     * send op   # a x a[x]+y
     * send []=  # ret
     */
    ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN1 recv", node->nd_recv));

    args = compile_array(self, node->nd_args->nd_next, Qfalse);
    rb_ary_pop(args); rb_ary_pop(args);
    argc =node->nd_args->nd_alen - 2;

    ADD_SEQ  (ret, args);
    ADD_INSN1(ret, nd_line(node), dupn, I2F(argc+1));
    ADD_SEND (ret, nd_line(node), ID2SYM(idAREF), I2F(argc));

    if(id == 0 || id == 1){
      /* 0: or, 1: and
        a[x] ||= y

        unless/if a[x]
          a[x]= y
        else
          nil
        end
       */
      VALUE label = NEW_LABEL(nd_line(node));
      VALUE lfin  = NEW_LABEL(nd_line(node));
      
      if(id == 0){
        ADD_INSN (ret, nd_line(node), dup);
        ADD_INSNL(ret, nd_line(node), if, label);
        ADD_INSN (ret, nd_line(node), pop);
      }
      else{
        ADD_INSNL(ret, nd_line(node), unless, label);
      }
      ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN1 args->head: ", node->nd_args->nd_head));
      ADD_SEND (ret, nd_line(node), ID2SYM(idASET), I2F(argc+1));
      ADD_INSNL(ret, nd_line(node), jump, lfin);
      ADD_LABEL(ret, label);
      if(id == 1){ /* and */
        ADD_INSN (ret, nd_line(node), putnil);
      }
      ADD_LABEL(ret, lfin);
    }
    else{
      ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN1 args->head: ", node->nd_args->nd_head));
      ADD_SEND (ret, nd_line(node), ID2SYM(id), I2F(1));
      ADD_SEND (ret, nd_line(node), ID2SYM(idASET), I2F(argc+1));
    }

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }

    break;
  }
  case NODE_OP_ASGN2:{
    ID    atype = node->nd_next->nd_mid;
    VALUE lfin  = new_label(nd_line(node));
    VALUE lcfin  = new_label(nd_line(node));
    /*
      class C; attr_accessor :c; end
      r = C.new
      r.a &&= v # asgn2

      eval r    # r
      dup       # r r
      eval r.a  # r o
      
      # or
      dup       # r o o
      if lcfin  # r o
      pop       # r
      eval v    # r v
      send a=   # v
      jump lfin # v
      
    lcfin:      # r o
      swap      # o r
      pop       # o

    lfin:       # v

      # and
      dup       # r o o
      unless lcfin
      pop       # r
      eval v    # r v
      send a=   # v
      jump lfin # v

      # others
      eval v    # r o v
      send ??   # r w
      send a=   # w
      
     */

    ret = NEW_SEQ();
    ADD_SEQ (ret, COMPILE("NODE_OP_ASGN2#recv", node->nd_recv));
    ADD_INSN (ret, nd_line(node), dup);
    ADD_SEND(ret, nd_line(node), ID2SYM(node->nd_next->nd_vid), I2F(0));

    if(atype == 0 || atype == 1){ /* 0: OR or 1: AND */
      ADD_INSN (ret, nd_line(node), dup);
      if(atype == 0){
        ADD_INSNL(ret, nd_line(node), if, lcfin);
      }
      else{
        ADD_INSNL(ret, nd_line(node), unless, lcfin);
      }
      ADD_INSN (ret, nd_line(node), pop);
      ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN2 val", node->nd_value));
      ADD_SEND (ret, nd_line(node), ID2SYM(node->nd_next->nd_aid), I2F(1));
      ADD_INSNL(ret, nd_line(node), jump, lfin);
      
      ADD_LABEL(ret, lcfin);
      ADD_INSN (ret, nd_line(node), swap);
      ADD_INSN (ret, nd_line(node), pop);
      
      ADD_LABEL(ret, lfin);
    }
    else{
      ADD_SEQ (ret, COMPILE("NODE_OP_ASGN2 val", node->nd_value));
      ADD_SEND(ret, nd_line(node), ID2SYM(node->nd_next->nd_mid), I2F(1));
      ADD_SEND(ret, nd_line(node), ID2SYM(node->nd_next->nd_aid), I2F(1));
    }
    
    if(poped){
      /* we can apply more optimize */
      ADD_INSN (ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_OP_ASGN_AND:
  case NODE_OP_ASGN_OR:{
    VALUE lfin = new_label(nd_line(node));
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN_AND#nd_head", node->nd_head));
    ADD_INSN (ret, nd_line(node), dup);
    if(nd_type(node) == NODE_OP_ASGN_AND){
      ADD_INSNL(ret, nd_line(node), unless, lfin);
    }
    else{
      ADD_INSNL(ret, nd_line(node), if, lfin);
    }
    ADD_INSN (ret, nd_line(node), pop);
    ADD_SEQ  (ret, COMPILE("NODE_OP_ASGN_AND#nd_value", node->nd_value));
    ADD_LABEL(ret, lfin);
    
    if(poped){
      /* we can apply more optimize */
      ADD_INSN (ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_CALL:
  case NODE_FCALL:
  case NODE_VCALL:{ /* VCALL: variable or call */
    /*
      call:  obj.method(...)
      fcall: func(...)
      vcall: func()
     */
    VALUE recv;
    VALUE args;
    VALUE argc;
    VALUE flag  = 0;
    VALUE block = 0;
    VALUE parent_block = iseqobj->compile_data->current_block;
    iseqobj->compile_data->current_block = Qfalse;

#if SUPPORT_GOTO
    /* only joke */
    {
      static ID goto_id;
      static ID label_id;
      VALUE label;
      VALUE label_sym;

      if(goto_id == 0){
        goto_id  = rb_intern("__goto__");
        label_id = rb_intern("__label__");
      }
      
      if(nd_type(node) == NODE_FCALL &&
         (node->nd_mid == goto_id || node->nd_mid == label_id)){
        if(nd_type(node->nd_args->nd_head) == NODE_LIT &&
           SYMBOL_P(node->nd_args->nd_head->nd_lit)){

          label_sym = label = node->nd_args->nd_head->nd_lit;
          if((label = rb_hash_aref(iseqobj->compile_data, label_sym)) == Qnil){
            rb_hash_aset(iseqobj->compile_data, label_sym,
                         label = NEW_LABEL(nd_line(node)));
          }
        }
        else{
          rb_bug("illegal goto/label format");
        }
        
        ret = NEW_SEQ();
        
        if(node->nd_mid == goto_id){
          ADD_INSNL(ret, nd_line(node), jump, label);
        }
        else{
          ADD_LABEL(ret, label);
        }
        break;
      }
    }
#endif

    /* reciever */
    if(type == NODE_CALL){
      recv = COMPILE("recv", node->nd_recv);
    }
    else if(type == NODE_FCALL ||
            type == NODE_VCALL){
      recv = NEW_INSN_SEQ(nd_line(node), putself);
    }

    /* args */
    if(type != NODE_VCALL && node->nd_args){
      if(nd_type(node->nd_args) == NODE_SPLAT){
        args  = COMPILE("args(splat)", node->nd_args->nd_head);
        argc  = I2F(1);
        flag |= VM_CALL_ARGS_SPLAT_BIT;
      }
      else if(nd_type(node->nd_args) == NODE_ARGSCAT){
        args = compile_array(self, node->nd_args->nd_head, Qfalse);
        rb_ary_pop(args);
        
        argc = I2F(RARRAY(args)->len + 1);
        rb_ary_concat(args,
                      COMPILE("args(cat: splat)", node->nd_args->nd_body));

        flag |= VM_CALL_ARGS_SPLAT_BIT;
      }
      else{
        args = compile_array(self, node->nd_args, Qfalse);
        argc = OPERAND(rb_ary_pop(args), 0);
     }
    }
    else{
      args = Qnil;
      argc = I2F(0);
    }

    ret = NEW_SEQ();
    ADD_SEQ(ret, recv);
    ADD_SEQ(ret, args);
    
    if(parent_block){
      if(CLASS_OF(parent_block) == cYarvISeq){
        block = parent_block;
      }
      else{
        flag |= VM_CALL_ARGS_BLOCKARG_BIT;
        ADD_SEQ(ret, parent_block);
      }
    }

    
    /* optimize */
    /*
      fib(32)
      ruby:           24.95
      yarv/dt/no opt: 16.08
      yarv/dt/opt:     7.35
      yarv/no opt:    16.50
      yarv/opt:        8.00
      */

#ifdef OPT_BASIC_OPERATIONS
    if(parent_block == 0 && flag == 0){
      /* no block given */
      if((argc == I2F(1)) && (type == NODE_CALL) && 1){
        if(node->nd_mid == idPLUS){
          ADD_INSN(ret, nd_line(node), opt_plus);
        }
        else if(node->nd_mid == idMINUS){
          ADD_INSN(ret, nd_line(node), opt_minus);
        }
        else if(node->nd_mid == idLT){
          ADD_INSN(ret, nd_line(node), opt_lt);
        }
        else if(node->nd_mid == idLTLT){
          ADD_INSN(ret, nd_line(node), opt_ltlt);
        }
        else{
          goto normal_call;
        }

        if(poped){
          ADD_INSN(ret, nd_line(node), pop);
        }
        break;
      }
    }
#endif

  normal_call:
    
    /* */
    debugp_verbose("call args",    args);
    debugp_param("call args argc", argc);
    debugp_param("call method",    ID2SYM(node->nd_mid));
    
    ADD_SEND_R(ret, nd_line(node), ID2SYM(node->nd_mid), argc,
               block, I2F(flag));
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_SUPER:{
    VALUE args;
    VALUE argc;
    VALUE flag = 0;
    
    /* args */
    if(type != NODE_VCALL && node->nd_args){
      if(nd_type(node->nd_args) == NODE_SPLAT){
        args  = COMPILE("args(splat)", node->nd_args->nd_head);
        argc  = I2F(1);
        flag |= VM_CALL_ARGS_SPLAT_BIT;
      }
      else if(nd_type(node->nd_args) == NODE_ARGSCAT){
        args = compile_array(self, node->nd_args->nd_head, Qfalse);
        rb_ary_pop(args);
        
        argc = I2F(RARRAY(args)->len + 1);
        rb_ary_concat(args,
                      COMPILE("args(cat: splat)", node->nd_args->nd_body));

        flag |= VM_CALL_ARGS_SPLAT_BIT;
      }
      else{
        args = compile_array(self, node->nd_args, Qfalse);
        argc = OPERAND(rb_ary_pop(args), 0);
     }
    }
    else{
      args = Qnil;
      argc = I2F(0);
    }

    ret = NEW_SEQ();
    ADD_INSN(ret, nd_line(node), putnil); /* dummy reciever */
    ADD_SEQ(ret, args);
    ADD_INSN2(ret, nd_line(node), super, argc, I2F(flag));

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_ZSUPER:{
    ret = NEW_SEQ();
    ADD_INSN(ret, nd_line(node), zsuper);

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_ARRAY:{
    ret = compile_array(self, node, Qtrue);
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_ZARRAY:{
    if(!poped){
      ret = NEW_INSN_SEQ1(nd_line(node), newarray, I2F(0));
    }
    break;
  }
  case NODE_VALUES:{
    NODE *n = node;
    ret = NEW_SEQ();
    while(n){
      ADD_SEQ(ret, COMPILE("values item", n->nd_head));
      n = n->nd_next;
    }
    ADD_INSN1(ret, nd_line(node), newarray, I2F(node->nd_alen));
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_HASH:{
    VALUE list;
    VALUE size;
    int type = node->nd_head ? nd_type(node->nd_head) : NODE_ZARRAY;

    ret = NEW_SEQ();
    
    switch(type){
    case NODE_ARRAY:{
      VALUE iary;
      list = compile_array(self, node->nd_head, Qfalse);
      iary = rb_ary_pop(list);
      debugp_param("hash ary: ", iary);
      size = OPERAND(iary, 0);
      ADD_SEQ  (ret, list);
      break;
    } 
    case NODE_ZARRAY:
      size = I2F(0);
      break;
      
    default:
      COMPILE_ERROR(("can't make hash with this node: %s", node_name(type)));
    }

    ADD_INSN1(ret, nd_line(node), newhash, size);

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_RETURN:{
    struct iseq_object *is = iseqobj;
    ret = NEW_SEQ();

    while(is){
      if(is->type == ISEQ_TYPE_TOP ||
         is->type == ISEQ_TYPE_CLASS){
        COMPILE_ERROR(("can't put return"));
        break;
      }
      else if(is->type == ISEQ_TYPE_RESCUE ||
              is->type == ISEQ_TYPE_ENSURE){
        is = is->parent_iseqobj;
      }
      else{
        ADD_SEQ(ret, COMPILE("return nd_stts(return val)", node->nd_stts));

        if(is->type == ISEQ_TYPE_METHOD && is->compile_data->in_ensure == Qfalse){
          ADD_INSN1(ret, nd_line(node), end, I2F(iseqobj->rewind_frame_size));
        }
        else if(is->type == ISEQ_TYPE_BLOCK || is->compile_data->in_ensure == Qtrue){
          ADD_INSN1(ret, nd_line(node), throw, I2F(0x01) /* TAG_RETURN */);
        }
        else{
          COMPILE_ERROR(("illegal return"));
        }
        break;
      }
    }
    
    break;
  }
  case NODE_YIELD:{
    VALUE args;
    int argc;
    
    if(iseqobj->type == ISEQ_TYPE_TOP ||
       iseqobj->type == ISEQ_TYPE_CLASS){
      COMPILE_ERROR(("can't put yield"));
    }
    
    if(node->nd_head){
      if(nd_type(node->nd_head) == NODE_ARRAY){
        args = compile_array(self, node->nd_head, Qfalse);
        rb_ary_pop(args);
        argc = RARRAY(args)->len;
        debugs("argc: %d\n", argc);
      }
      else{
        args = COMPILE("nd_head(1)", node->nd_head);
        argc = 1;
      }
    }
    else{
      args = Qnil;
      argc = 0;
    }
    ret = NEW_SEQ();
    ADD_SEQ  (ret, args);
    ADD_INSN2(ret, nd_line(node), yield, I2F(argc), I2F(0));

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_LVAR:{
    if(!poped){
      int idx = get_root_iseq_localsize(iseqobj) + 2 - node->nd_cnt;
      debugs("idx: %d\n", idx);
      ret = NEW_INSN_SEQ1(nd_line(node), getlocal, I2F(idx));
    }
    break;
  }
  case NODE_DVAR:{
    int lv, idx, ls;
    debugi("nd_vid", node->nd_vid);
    if(!poped){
      idx = get_dyna_var_idx(self, node->nd_vid, &lv, &ls);
      if(idx < 0){
        COMPILE_ERROR(("unknown dvar"));
      }
      ret = NEW_SEQ();
      ADD_INSN2(ret, nd_line(node), getdynamic, I2F(ls - idx), I2F(lv));
    }
    break;
  }
  case NODE_GVAR:{
    ret = NEW_INSN_SEQ1(nd_line(node), getglobal, (((long)node->nd_entry) | 1));
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_IVAR:{
    debugi("nd_vid", node->nd_vid);
    if(!poped){
      ret = NEW_SEQ();
      ADD_INSN1(ret, nd_line(node), getinstancevariable, ID2SYM(node->nd_vid));
    }
    break;
  }
  case NODE_CONST:{
    debugi("nd_vid", node->nd_vid);
    if(!poped){
      ret = NEW_SEQ();
      if(iseqobj->compile_data->cached_const == Qfalse){
        VALUE lstart = NEW_LABEL(nd_line(node));
        VALUE lend   = NEW_LABEL(nd_line(node));
        
        ADD_LABEL(ret, lstart);
        ADD_INSN2(ret, nd_line(node), getinlinecache, NEW_INLINE_CACHE_ENTRY(), lend);
        /* ADD_INSN (ret, nd_line(node), pop); */
        /* ADD_INSN (ret, nd_line(node), putnil); */ /* target class */
        ADD_INSN1(ret, nd_line(node), getconstant, ID2SYM(node->nd_vid));
        ADD_INSN1(ret, nd_line(node), setinlinecache, lstart);
        ADD_LABEL(ret, lend);
      }
      else{
        ADD_INSN (ret, nd_line(node), putnil); /* target class */
        ADD_INSN1(ret, nd_line(node), getconstant, ID2SYM(node->nd_vid));
      }
    }
    break;
  }
  case NODE_CVAR:{
    ret = NEW_SEQ();
    if(!poped){
      ADD_INSN1(ret, nd_line(node), getclassvariable, ID2SYM(node->nd_vid));
    }
    break;
  }
  case NODE_NTH_REF:{
    ret = NEW_SEQ();
    ADD_INSN2(ret, nd_line(node), getspecial, I2F(node->nd_cnt), I2F(node->nd_nth << 1));
    break;
  }
  case NODE_BACK_REF:{
    ret = NEW_SEQ();
    ADD_INSN2(ret, nd_line(node), getspecial, I2F(node->nd_cnt), I2F(0x01 | (node->nd_nth << 1)));
    break;
  }
  case NODE_MATCH:
  case NODE_MATCH2:
  case NODE_MATCH3:{
    VALUE recv;
    VALUE val;

    if(nd_type(node) == NODE_MATCH){
      recv = NEW_SEQ();
      ADD_INSN1(recv, nd_line(node), putobject, node->nd_lit);
      val  = NEW_SEQ();
      ADD_INSN2(val, nd_line(node), getspecial, I2F(0), I2F(0));
    }
    else{
      recv = COMPILE("reciever", node->nd_recv);
      val  = COMPILE("value", node->nd_value);
    }
    
    ret = NEW_SEQ();

#ifdef OPT_REGEXP_MATCH
    if(RARRAY(recv)->len == 1 &&
       INSN_OF(RARRAY(recv)->ptr[0]) == BIN(putobject)){
      ADD_SEQ  (ret, val);
      ADD_INSN1(ret, nd_line(node), opt_regexpmatch1, OPERAND(RARRAY(RARRAY(recv)->ptr[0]), 0));
    }
    else{
      ADD_SEQ(ret, recv);
      ADD_SEQ(ret, val);
      ADD_INSN(ret, nd_line(node), opt_regexpmatch2);
    }
#else
    ADD_SEQ(ret, recv);
    ADD_SEQ(ret, val);
    ADD_SEND(ret, nd_line(node), ID2SYM(idEqTilde), INT2FIX(1));
#endif
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_LIT:{
    debugp_param("lit", node->nd_lit);
    if(!poped){
      ret = NEW_INSN_SEQ1(nd_line(node), putobject, node->nd_lit);
    }
    break;
  }
  case NODE_STR:{
    debugp_param("nd_lit", node->nd_lit);
    if(!poped){
      ret = NEW_INSN_SEQ1(nd_line(node), putstring, node->nd_lit);
    }
    break;
  }
  case NODE_DSTR:{
    VALUE dstr = compile_dstr(self, node);

    ret = NEW_SEQ();
    ADD_SEQ(ret, dstr);
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_XSTR:{
    ret = NEW_SEQ();
    ADD_INSN (ret, nd_line(node), putself);
    ADD_INSN1(ret, nd_line(node), putobject, node->nd_lit);
    ADD_SEND (ret, nd_line(node), ID2SYM(idBackquote), INT2FIX(1));
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_DXSTR:{
    VALUE dstr = compile_dstr(self, node);
    ret = NEW_SEQ();
    
    ADD_INSN(ret, nd_line(node), putself);
    ADD_SEQ (ret, dstr);
    ADD_SEND(ret, nd_line(node), ID2SYM(idBackquote), INT2FIX(1));

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_EVSTR:{
    VALUE body = COMPILE("nd_body", node->nd_body);
    ret = NEW_SEQ();
    ADD_SEQ (ret, body);
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    else{
      ADD_INSN(ret, nd_line(node), tostring);
    }
    break;
  }
  case NODE_DREGX:{
    VALUE dstr = compile_dstr(self, node);

    ret = NEW_SEQ();
    ADD_SEQ(ret, dstr);
    ADD_INSN1(ret, nd_line(node), toregexp, I2F(node->nd_cflag));
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_DREGX_ONCE:{
    /* fix me */
    VALUE dstr = compile_dstr(self, node);

    ret = NEW_SEQ();
    ADD_SEQ(ret, dstr);
    ADD_INSN1(ret, nd_line(node), toregexp, I2F(node->nd_cflag));
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_ARGS:{
    /* OK */
    COMPILE_ERROR(("BUG: should not reach here: compile_each#NODE_ARGS"));
    break;
  }
  case NODE_ARGSCAT:{
    NODE *n = node->nd_head;
    int i = 0;
    ret = NEW_SEQ();
    while(n){
      i++;
      ADD_SEQ(ret, COMPILE("argscat head", n->nd_head));
      n = n->nd_next;
    }
    n = node->nd_body;
    while(n){
      i++;
      ADD_SEQ(ret, COMPILE("argscat body", n->nd_head));
      n = n->nd_next;
    }
    ADD_INSN1(ret, nd_line(node), newarray, I2F(i));
    break;
  }
  case NODE_ARGSPUSH:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_ARGSPUSH"));
    break;
  }
  case NODE_SPLAT:{
    ret = COMPILE("splat", node->nd_head);
    break;
  }
  case NODE_TO_ARY:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_TO_ARY"));
    break;
  }
  case NODE_SVALUE:{
    ret = NEW_SEQ();
    ADD_SEQ(ret, COMPILE("svalue head", node->nd_head));
    break;
  }
  case NODE_BLOCK_ARG:{
    iseqobj->arg_block = node->nd_cnt - 2;
    iseqobj->arg_simple = 0;
    
    break;
  }
  case NODE_BLOCK_PASS:{
    VALUE prevblock = iseqobj->compile_data->current_block;
    VALUE retry_label = NEW_LABEL(nd_line(node));
    VALUE retry_end_l = NEW_LABEL(nd_line(node));
    VALUE name = rb_str_new2("b@");
    rb_str_concat(name, iseqobj->name);
    
    iseqobj->compile_data->current_block = NEW_SEQ();
    ADD_SEQ(iseqobj->compile_data->current_block, COMPILE("block pass proc", node->nd_body));
    
    ret = NEW_SEQ();
    ADD_LABEL(ret, retry_label);
    ADD_SEQ  (ret, COMPILE_("iter caller (NODE_BLOCK_PASS)", node->nd_iter, poped));
    ADD_LABEL(ret, retry_end_l);
    
    iseqobj->compile_data->current_block = prevblock;
    ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, retry_label, retry_end_l, 0, 0, retry_label);
    break;
  }
  case NODE_DEFN:{
    iseq = NEW_ISEQOBJ(node->nd_defn,
                       rb_str_new2(rb_id2name(node->nd_mid)),
                       ISEQ_TYPE_METHOD);
    
    debugp_param("defn/iseq", iseq);

    ret = NEW_SEQ();
    ADD_INSN2(ret, nd_line(node), methoddef, ID2SYM(node->nd_mid), iseq);
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    debugp_param("defn", iseq);
    break;
  }
  case NODE_DEFS:{
    iseq = NEW_ISEQOBJ(node->nd_defn,
                       rb_str_new2(rb_id2name(node->nd_mid)),
                       ISEQ_TYPE_METHOD);

    debugp_param("defs/iseq", iseq);

    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("defs: recv", node->nd_recv));
    ADD_INSN2(ret, nd_line(node), singletonmethoddef, ID2SYM(node->nd_mid), iseq);
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    break;
  }
  case NODE_ALIAS:{
    VALUE s1, s2;
    
    if(nd_type(node->u1.node) != NODE_LIT ||
       nd_type(node->u2.node) != NODE_LIT){
      rb_bug("alias args must be NODE_LIT");
    }
    s1 = node->u1.node->nd_lit;
    s2 = node->u2.node->nd_lit;

    ret = NEW_SEQ();
    ADD_INSN3(ret, nd_line(node), alias, Qfalse, ID2SYM(rb_to_id(s1)), ID2SYM(rb_to_id(s2)));
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    break;
  }
  case NODE_VALIAS:{
    ret = NEW_SEQ();
    ADD_INSN3(ret, nd_line(node), alias, Qtrue, ID2SYM(node->u1.id), ID2SYM(node->u2.id));
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    break;
  }
  case NODE_UNDEF:{
    if(nd_type(node->u2.node) != NODE_LIT){
      rb_bug("undef args must be NODE_LIT");
    }
    ret = NEW_SEQ();
    ADD_INSN1(ret, nd_line(node), undef, ID2SYM(rb_to_id(node->u2.node->nd_lit)));
    if(!poped){
      ADD_INSN(ret, nd_line(node), putnil);
    }
    break;
  }
  case NODE_CLASS:{
    iseq = NEW_ISEQOBJ(node->nd_body, rb_str_new2("class"),
                       ISEQ_TYPE_CLASS);
    
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("cbase", node->nd_cpath->nd_head));
    ADD_SEQ  (ret, COMPILE("super", node->nd_super));
    ADD_INSN2(ret, nd_line(node), classdef, ID2SYM(node->nd_cpath->nd_mid), iseq);
    ADD_INSN (ret, nd_line(node), popcref);
    
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_MODULE:{
    iseq = NEW_ISEQOBJ(node->nd_body, rb_str_new2("module"),
                       ISEQ_TYPE_CLASS);
    ret = NEW_SEQ();
    ADD_SEQ  (ret, COMPILE("mbase", node->nd_cpath->nd_head));
    ADD_INSN2(ret, nd_line(node), moduledef, ID2SYM(node->nd_cpath->nd_mid), iseq);
    ADD_INSN (ret, nd_line(node), popcref);
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_SCLASS:{
    ret = NEW_SEQ();
    iseq = NEW_ISEQOBJ(node->nd_body, rb_str_new2("singletonclass"),
                       ISEQ_TYPE_CLASS);
    ADD_SEQ  (ret, COMPILE("sclass#recv", node->nd_recv));
    ADD_INSN1(ret, nd_line(node), singletonclassdef, iseq);
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_COLON2:{
    ret = NEW_SEQ();
    
    if(rb_is_const_id(node->nd_mid)){
      /* constant */
      debugi("nd_mid", node->nd_mid);
      if(iseqobj->compile_data->cached_const == Qfalse){
        VALUE lstart = NEW_LABEL(nd_line(node));
        VALUE lend   = NEW_LABEL(nd_line(node));
        /* add cache insn */
        iseqobj->compile_data->cached_const = Qtrue;
        ADD_LABEL(ret, lstart);
        ADD_INSN2(ret, nd_line(node), getinlinecache, NEW_INLINE_CACHE_ENTRY(), lend);
        ADD_INSN (ret, nd_line(node), pop);
        ADD_SEQ(ret, COMPILE("colon2#nd_head", node->nd_head));
        ADD_INSN1(ret, nd_line(node), getconstant, ID2SYM(node->nd_mid));
        ADD_INSN1(ret, nd_line(node), setinlinecache, lstart);
        ADD_LABEL(ret, lend);
        iseqobj->compile_data->cached_const = Qfalse;
      }
      else{
        ADD_SEQ(ret, COMPILE("colon2#nd_head", node->nd_head));
        ADD_INSN1(ret, nd_line(node), getconstant, ID2SYM(node->nd_mid));
      }
    }
    else{
      /* function call */
      ADD_INSN(ret, nd_line(node), putself);
      ADD_SEQ (ret, COMPILE("colon2#nd_head", node->nd_head));
      ADD_SEND(ret, nd_line(node), ID2SYM(node->nd_mid), INT2FIX(1));
    }
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_COLON3:{
    debugi("colon3#nd_mid", node->nd_mid);
    ret = NEW_SEQ();
    if(iseqobj->compile_data->cached_const == Qfalse){
      VALUE lstart = NEW_LABEL(nd_line(node));
      VALUE lend   = NEW_LABEL(nd_line(node));

      /* add cache insn */
      ADD_LABEL(ret, lstart);
      ADD_INSN2(ret, nd_line(node), getinlinecache, NEW_INLINE_CACHE_ENTRY(), lend);
      ADD_INSN (ret, nd_line(node), pop);
      ADD_INSN1(ret, nd_line(node), putobject     , rb_cObject);
      ADD_INSN1(ret, nd_line(node), getconstant   , ID2SYM(node->nd_mid));
      ADD_INSN1(ret, nd_line(node), setinlinecache, lstart);
      ADD_LABEL(ret, lend);
    }
    else{
      ADD_INSN1(ret, nd_line(node), putobject, rb_cObject);
      ADD_INSN1(ret, nd_line(node), getconstant, ID2SYM(node->nd_mid));
    }
    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_CREF:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_CREF"));
    break;
  }
  case NODE_DOT2:
  case NODE_DOT3:{
    int flag = type == NODE_DOT2 ? INT2FIX(0) : INT2FIX(1);
    if(!poped){
      ret = NEW_SEQ();
      ADD_SEQ  (ret, COMPILE("min", (NODE*)node->nd_lit));
      ADD_SEQ  (ret, COMPILE("max", (NODE*)node->nd_end));
      ADD_INSN1(ret, nd_line(node), newrange, flag);
    }
    break;
  }
  case NODE_FLIP2:
  case NODE_FLIP3:{
    VALUE lend, lfin, ltrue, lfalse;
    lend   = NEW_LABEL(nd_line(node));
    lfin   = NEW_LABEL(nd_line(node));
    ltrue  = NEW_LABEL(nd_line(node));
    lfalse = NEW_LABEL(nd_line(node));
    
    ret = NEW_SEQ();
    ADD_INSN2(ret, nd_line(node), getspecial, I2F(node->nd_cnt), I2F(0));
    ADD_INSNL(ret, nd_line(node), if, lend);

    /* *flip == 0 */
    ADD_SEQ  (ret, COMPILE("flip2 beg", node->nd_beg));
    ADD_INSN (ret, nd_line(node), dup);
    ADD_INSNL(ret, nd_line(node), unless, lfin);
    if(nd_type(node) == NODE_FLIP3){
      ADD_INSN (ret, nd_line(node), dup);
      ADD_INSN2(ret, nd_line(node), setspecial, I2F(node->nd_cnt), I2F(0));
      ADD_INSNL(ret, nd_line(node), jump, lfin);
    }
    else{
      ADD_INSN2(ret, nd_line(node), setspecial, I2F(node->nd_cnt), I2F(0));
    }

    /* *flip == 1 */
    ADD_LABEL(ret, lend);
    ADD_SEQ  (ret, COMPILE("flip2 end", node->nd_end));
    ADD_INSNL(ret, nd_line(node), unless, ltrue);
    ADD_INSN1(ret, nd_line(node), putobject, Qfalse);
    ADD_INSN2(ret, nd_line(node), setspecial, I2F(node->nd_cnt), I2F(0));
    
    ADD_LABEL(ret, ltrue);
    ADD_INSN1(ret, nd_line(node), putobject, Qtrue);
    
    ADD_LABEL(ret, lfin);
    
    break;
  }
  case NODE_ATTRSET:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_ATTRSET"));
    break;
  }
  case NODE_SELF:{
    if(!poped){
      ret = NEW_INSN_SEQ(nd_line(node), putself);
    }

    break;
  }
  case NODE_NIL:{
    if(!poped){
      ret = NEW_INSN_SEQ(nd_line(node), putnil);
    }
    break;
  }
  case NODE_TRUE:{
    if(!poped){
      ret = NEW_INSN_SEQ1(nd_line(node), putobject, Qtrue);
    }
    break;
  }
  case NODE_FALSE:{
    if(!poped){
      ret = NEW_INSN_SEQ1(nd_line(node), putobject, Qfalse);
    }
    break;
  }
  case NODE_ERRINFO:{
    if(!poped){
      ret = NEW_SEQ();
      if(iseqobj->type == ISEQ_TYPE_RESCUE){
        ADD_INSN2(ret, nd_line(node), getdynamic, I2F(1), I2F(0));
      }
      else{
        ADD_INSN(ret, nd_line(node), putnil);
      }
    }
    break;
  }
  case NODE_DEFINED:{
    ret = NEW_SEQ();
    if(!poped){
      VALUE lfinish = NEW_LABEL(nd_line(node));
      defined_expr(self, node->nd_head, ret, lfinish, Qtrue);
      ADD_LABEL(ret, lfinish);
    }
    break;
  }
  case NODE_POSTEXE:{
    COMPILE_ERROR(("BUG: unknown node: NODE_POSTEXE"));
    break;
  }
#ifdef C_ALLOCA
  case NODE_ALLOCA:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_ALLOCA"));
    break;
  }
#endif
  case NODE_BMETHOD:{
    /* block method, OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_BMETHOD"));
    break;
  }
  case NODE_MEMO:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_MEMO"));
    break;
  }
  case NODE_IFUNC:{
    /* OK */
    COMPILE_ERROR(("BUG: unknown node: NODE_IFUNC"));
    break;
  }
  case NODE_DSYM:{
    VALUE dstr = compile_dstr(self, node);

    ret = NEW_SEQ();
    ADD_SEQ (ret, dstr);
    if(!poped){
      ADD_SEND(ret, nd_line(node), ID2SYM(idIntern), I2F(0));
    }
    else{
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  case NODE_ATTRASGN:{
    VALUE recv;
    VALUE args; 
    VALUE argc;
    VALUE argcv;

    if(node->nd_args){
      args = compile_array(self, node->nd_args, Qfalse);
      argcv = rb_ary_pop(args);
      argc = OPERAND(argcv, 0);
    }
    else{
      args = rb_ary_new();
      argc = I2F(0); /* massign */
    }
    
    if(node->nd_recv == (NODE *)1){
      recv = NEW_INSN_SEQ(nd_line(node), putself);
    }
    else{
      recv = COMPILE("recv", node->nd_recv);
    }
    
    debugp_param("nd_recv", recv);
    debugp_param("nd_args", args);
    debugp_param("argc"   , argc);
    debugp_param("nd_mid",  ID2SYM(node->nd_mid));
    
    ret = NEW_SEQ();
    ADD_SEQ(ret, recv);
    ADD_SEQ(ret, args);

    ADD_SEND(ret, nd_line(node), ID2SYM(node->nd_mid), argc);

    if(poped){
      ADD_INSN(ret, nd_line(node), pop);
    }
    break;
  }
  default:
    COMPILE_ERROR(("BUG: unknown node: %s", node_name(type)));
    return Qnil;
  }

  debug_nodeprint_close();
  return ret;
}


/***************************/
/* instruction information */
/***************************/

#define TS_LONG     "L"
#define TS_OFFSET   "L"
#define TS_ULONG    "U"
#define TS_VALUE    "V"
#define TS_ID       "I"
#define TS_GENTRY   "G"
#define TS_IC       "M"
#define TS_VARIABLE "."


#include "insns_info.inc"

/* some utility */

#if CPDEBUG > 5

#define INSN_CHECK(insn)                                         \
  if(insn < 0 ||                                                 \
     insn > sizeof(insn_name_info) / sizeof(insn_name_info[0])){ \
    COMPILE_ERROR(("(in)unknown instruction: %d", insn));        \
  }                                                              \

#else

#define INSN_CHECK(insn) /* */

#endif

char *insn_name(int insn){
  INSN_CHECK(insn);
  return insn_name_info[insn];
}

int insn_len(int insn){
  INSN_CHECK(insn);
  return insn_len_info[insn];
}

char *insn_op_types(int insn){
  INSN_CHECK(insn);
  return insn_operand_info[insn];
}

int insn_op_type(int insn, int pos){
  int len = insn_len(insn) - 1;
  if(pos < len){
    return insn_operand_info[insn][pos];
  }
  else{
    return TS_LONG[0];
  }
}

int insn_ret_num(int insn){
  INSN_CHECK(insn);
  return insn_stack_push_num_info[insn];
}

/**************************************/
/* methods of Instruction Class       */
/**************************************/

int insn_length(VALUE self){
  struct insn_object *insnobj;
  GetInsnVal(self, insnobj);
  return insn_len(insnobj->insn_id);
}

unsigned long insn_line_no(VALUE self){
  struct insn_object *insnobj;
  GetInsnVal(self, insnobj);
  return insn_len(insnobj->line_no);
}

VALUE insn_to_s_detail(VALUE self){
  VALUE str = rb_str_new(0,0);
  VALUE tmp;
  char buff[0x100];
  struct insn_object *insnobj;

  GetInsnVal(self, insnobj);
  snprintf(buff, sizeof(buff), "%-16s", insn_name(insnobj->insn_id));
  rb_str_cat2(str, buff);
  if(insnobj->operands){
    char *types = insn_op_types(insnobj->insn_id);
    int j;
    
    for(j=0;types[j];j++){
      char type = types[j];
      switch(type){
      case 'L':          /* label(destination position) */
      case 'U':          /* ulong */
      case 'V':          /* VALUE */
        rb_str_concat(str, rb_inspect(RARRAY(insnobj->operands)->ptr[j]));
        break;
      case 'I':          /* ID */
        rb_str_concat(str, rb_inspect(ID2SYM(FIX2INT(RARRAY(insnobj->operands)->ptr[j]))));
        break;
      case 'G':
        {
          struct global_entry *entry = (struct global_entry*)
            (RARRAY(insnobj->operands)->ptr[j] & (~1));
          rb_str_cat2(str, rb_id2name(entry->id));
        }
      case 'C':
        {
          rb_str_cat2(str, "<ic>");
          break;
        }
        break;
      default:{
        rb_bug("unknown operand type: %c", type);
      }
      }
      if(types[j+1]){
        rb_str_cat2(str, ", ");
      }
    }
  }
  return str;
}

VALUE insns_name_array(){
  VALUE ary = rb_ary_new();
  int i;
  for(i=0; i< sizeof(insn_name_info) / sizeof(insn_name_info[0]); i++){
    rb_ary_push(ary, rb_str_new2(insn_name_info[i]));
  }
  return ary;
}

