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

#include "vm.h"
#include "insnhelper.h"
#include "insns.inc"
#include "yarvcore.h"

#define BUFSIZE 0x100


/* imporeted from eval.c */
struct cache_entry {		/* method hash table. */
    ID mid;			/* method's id */
    ID mid0;			/* method's original id */
    VALUE klass;		/* receiver's class */
    VALUE origin;		/* where method defined  */
    NODE *method;
    int noex;
};

#define CACHE_SIZE 0x800

//RUBY_EXTERN struct cache_entry ruby_cache_table[CACHE_SIZE];
//static struct cache_entry *global_method_cache = &ruby_cache_table[0];

#define cache global_method_cache

#define CACHE_MASK 0x7ff
#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK)

NODE*
rb_get_method_body(VALUE *klassp, ID *idp, int *noexp);


/*
 * VM state dump
 */


static void stack_dump_raw(struct thread_object *th, VALUE *sp, VALUE *lfp, VALUE *dfp, VALUE *cfp){
  VALUE *p, *st, *t;
  for(p=st=th->stack; p<sp; p++){
    printf("%08x(%04d): %08x", p, p-st, *p);

    if(*p == FRAME_MAGIC_METHOD) printf(" # FRAME_MAGIC_METHOD");
    if(*p == FRAME_MAGIC_BLOCK)  printf(" # FRAME_MAGIC_BLOCK");
    if(*p == FRAME_MAGIC_CLASS)  printf(" # FRAME_MAGIC_CLASS");
    if(*p == FRAME_MAGIC_TOP)    printf(" # FRAME_MAGIC_TOP");

    t = (VALUE *)*p;
    if(th->stack <= t && t < th->sp){
      printf(" (<- %d)", t - th->stack);
    }
    
    if(p == lfp) printf(" <- lfp");
    if(p == dfp) printf(" <- dfp");
    if(p == cfp) printf(" <- cfp");
    if(p == sp)  printf(" <- sp"); /* should not be */
    
    printf("\n");
  }
  printf("-- (self, iseq, (magic), pc, cfp, lfp, dfp)\n");
}

static void stack_dump_thobj(struct thread_object *thobj){
  stack_dump_raw(thobj, thobj->sp, thobj->lfp, thobj->dfp, thobj->cfp);
}

static void stack_dump_th(VALUE th){
  struct thread_object *thobj;
  GetThreadVal(th, thobj);
  stack_dump_thobj(thobj);
}

static void stack_dump_each(struct thread_object *th,
                            VALUE *lfp, VALUE *dfp, VALUE *cfp,
                            VALUE *sp, struct iseq_object *iseqobj){
  
  int i, j;
  struct continuation_frame *cf;
  struct local_frame *lf;
  struct block_frame *bf;
  
  VALUE rstr;
  VALUE *ptr;
  
  int argc, local_size;
  int rewind = 0;
  char *name;

  if(CMETHOD_INFO_P(iseqobj)){
    struct cmethod_info *cmi = (void *)iseqobj;
    argc       = 0;
    local_size = 0;
    name       = rb_id2name(cmi->id);
  }
  else{
    argc       = iseqobj->argc;
    local_size = iseqobj->local_size;
    name       = RSTRING(iseqobj->name)->ptr;
  }
  
  // stack_dump_raw(th, sp, lfp, dfp, cfp);
  
  cf = (struct continuation_frame*) (cfp + CC_DIFF_WC());
  
  /* stack trace header */

  if(cf->magic == FRAME_MAGIC_METHOD ||
     cf->magic == FRAME_MAGIC_TOP    ||
     cf->magic == FRAME_MAGIC_BLOCK  ||
     cf->magic == FRAME_MAGIC_CLASS){

    if(cf->cfp){
      // there are some previous frames
      struct control_frame * ncf = (struct control_frame *)cf->cfp;
      struct iseq_object *niseqobj;
      
      if(CMETHOD_INFO_P(iseqobj)){
        rewind = 2;
      }
      else{
        rewind = iseqobj->rewind_frame_size;
      }
      
      // printf("cfp: %x - ", cfp); dp(ncf->iseq);
      
      if(CMETHOD_INFO_P(ncf->iseq)){
        niseqobj = (struct iseq_object *)ncf->iseq;
      }
      else{
        GetISeqVal(ncf->iseq, niseqobj);
      }
      // printf("--> %d\n", rewind);
      stack_dump_each(/* thread  */ th,
                      /* regs    */ cf->lfp, cf->dfp, cf->cfp,
                      /* sp      */ cfp - rewind,
                      /* iseqobj */ niseqobj);
    }
    
    // printf("raw lfp: %x, dfp: %x\n", lfp, dfp);
    printf("  == stack dump: SP:%4d CFP:%4d ", sp - th->stack, cfp - th->stack);
    printf("LFP:%4d DFP:%4d ",
           FRAME_IS_IN_HEAP(lfp) ? lfp : (void*)(lfp - th->stack),
           dfp && FRAME_IS_IN_HEAP(dfp) ? dfp : (void*)(dfp - th->stack));
    printf("# %s\n", name);

    // stack_dump_raw(th, sp, lfp, dfp, cfp);

    if(cf->magic == FRAME_MAGIC_BLOCK){
      /* show block frame */
      
      rstr = rb_inspect(cfp[0]);
      printf("  self    : %s\n", StringValueCStr(rstr));

      if(dfp == 0 || dfp[0] == 0){
        printf("  prev    : null\n");
      }
      else{
        printf("  prev    : %d\n",
               FRAME_IS_IN_HEAP(((VALUE*)dfp[0])) ?
               ((VALUE*)dfp[0]) : (VALUE*)(((VALUE*)dfp[0]) - th->stack));
      }

      for(i=0; i<argc; i++){
        rstr = rb_inspect(*(dfp - (iseqobj->local_size - i)));
        printf("  darg  %2d: %s\n", i, StringValueCStr(rstr));
      }
      for(; i<local_size-1; i++){
        rstr = rb_inspect(*(dfp - (local_size - i)));
        printf(" dlocal %2d: %s\n", i, StringValueCStr(rstr));
      }
      for(i=0; cfp + i + FRAME_WC() - 1 < sp; i++){
        //printf("ptr: %d (%x) (cfp: %x, sp: %x)\n", ptr - th->stack, ptr, cfp, sp);
        rstr = rb_inspect(cfp[i + FRAME_WC() - 1]);
        printf("  stack %2d: %s\n", i, StringValueCStr(rstr));
      }
    }
    else{
      /* show other frame */
      
      rstr = rb_inspect(cfp[0]);
      printf("  self    : %s\n", StringValueCStr(rstr));

      if(lfp[0] != 0){
        struct vm_block_object *bo = (struct vm_block_object *)(lfp[0]);
        if(BUILTIN_TYPE(bo->iseq) == T_NODE){
          rstr = rb_str_new2("#<IFUNC>");
        }
        else{
          rstr = rb_inspect(bo->iseq);
        }
        printf("  block   : %s \n", StringValueCStr(rstr));
      }
      else{
        printf("  block   : --\n");
      }
      
      for(i=0; i<argc; i++){
        rstr = rb_inspect(*(lfp - (local_size - i)));
        printf("  arg   %2d: %s\n", i, StringValueCStr(rstr));
      }

      for(; i<local_size-1; i++){
        rstr = rb_inspect(*(lfp -(local_size - i)));
        printf("  local %2d: %s\n", i, StringValueCStr(rstr));
      }

      /* stack */
      ptr = cfp + FRAME_WC() - 1;
      if(lfp[0] == (VALUE)(ptr - VM_BLOCK_OBJECT_SAHRE_SIZE)){
        ptr += VM_BLOCK_OBJECT_OVER_SIZE;
      }
      
      for(i=0; ptr < sp; i++, ptr++){
        //printf("ptr: %d (%x) (cfp: %x, sp: %x)\n", ptr - th->stack, ptr, cfp, sp);
        // stack_dump_raw(th, sp, lfp, dfp, cfp);
        rstr = rb_inspect(*ptr);
        printf("  stack %2d: %s (%x)\n", i, StringValueCStr(rstr), ptr);
      }
    }
  }
  else{
    rb_bug("unsupport frame type: %08x", cf->magic);
  }
}


static void debug_print_register(struct thread_object *th,
                                 int pc, VALUE *sp, VALUE *lfp, VALUE *dfp, VALUE *cfp){
  
  printf("  [PC] %04d, [SP] %04d, [LFP] %04d, [DFP] %04d, [CFP] %04d\n",
         pc, sp - th->stack, lfp - th->stack,
         dfp ? dfp - th->stack : 0, cfp - th->stack);
}

static void thread_dump_regs(VALUE th){
  struct thread_object *thobj;
  GetThreadVal(th, thobj);

  debug_print_register(thobj, 0, thobj->sp,
                       thobj->lfp, thobj->dfp, thobj->cfp);
}

static void debug_print_pre(struct thread_object *th,
                            VALUE *iseq, int pc,
                            struct iseq_object *iseqobj,
                            VALUE *sp, VALUE *lfp, VALUE *dfp, VALUE *cfp){

  iseq_disasm_insn(0, iseq, pc, iseqobj, 0);
#if VMDEBUG > 3
  printf("        (1)");
  debug_print_register(th, pc, sp, lfp, dfp, cfp);
#endif
}

static void debug_print_post(struct thread_object *th, struct iseq_object *iseq,
                             int pc, VALUE *sp, VALUE *lfp, VALUE *dfp, VALUE *cfp
#ifdef OPT_STACK_CACHING
                             , VALUE reg_a, VALUE reg_b
#endif
                             ){
  
#if VMDEBUG > 3
  printf("        (2)");
  debug_print_register(th, pc, sp, lfp, dfp, cfp);
#endif

#if VMDEBUG > 2
  // stack_dump_thobj(th);
  stack_dump_each(th, lfp, dfp, cfp, sp, iseq);
#ifdef OPT_STACK_CACHING
  {
    VALUE rstr;
    rstr = rb_inspect(reg_a);
    printf("  sc reg A: %s\n", StringValueCStr(rstr));
    rstr = rb_inspect(reg_b);
    printf("  sc reg B: %s\n", StringValueCStr(rstr));
  }
#endif
#endif
  
#if VMDEBUG > 9
  GC_CHECK();
#endif
}

#ifdef COLLECT_USAGE_ANALYSIS
/* uh = {
 *   insn(Fixnum) => ihash(Hash)
 * }
 * ihash = {
 *   -1(Fixnum) => count,      # insn usage
 *    0(Fixnum) => ophash,     # operand usage
 * }
 * ophash = {
 *   val(interned string) => count(Fixnum)
 * }
 */
void vm_analysis_insn(int insn){
  static ID usage_hash;
  static ID bigram_hash;
  static int prev_insn = -1;

  VALUE uh;
  VALUE ihash;
  VALUE cv;

  SET_YARV_STOP();
  
  if(usage_hash == 0){
    usage_hash = rb_intern("USAGE_ANALISYS_INSN");
    bigram_hash = rb_intern("USAGE_ANALISYS_INSN_BIGRAM");
  }
  uh = rb_const_get(mYarvCore, usage_hash);
  if((ihash = rb_hash_aref(uh, INT2FIX(insn))) == Qnil){
    ihash = rb_hash_new();
    rb_hash_aset(uh, INT2FIX(insn), ihash);
  }
  if((cv = rb_hash_aref(ihash, INT2FIX(-1))) == Qnil){
    cv = INT2FIX(0);
  }
  rb_hash_aset(ihash, INT2FIX(-1), INT2FIX(FIX2INT(cv)+1));

  /* calc bigram */
  if(prev_insn != -1){
    VALUE bi;
    VALUE ary[2];
    VALUE cv;
    
    ary[0] = INT2FIX(prev_insn);
    ary[1] = INT2FIX(insn);
    bi = rb_ary_new4(2, &ary[0]);
    
    uh = rb_const_get(mYarvCore, bigram_hash);
    if((cv = rb_hash_aref(uh, bi)) == Qnil){
      cv = INT2FIX(0);
    }
    rb_hash_aset(uh, bi, INT2FIX(FIX2INT(cv)+1));
  }
  prev_insn = insn;
  
  SET_YARV_START();
}

/* from disasm.c */
VALUE insn_operand_intern(int insn, int op_no, VALUE op,
                          int len, int pos, VALUE child);

void vm_analysis_operand(int insn, int n, VALUE op){
  static ID usage_hash;
  
  VALUE uh;
  VALUE ihash;
  VALUE ophash;
  VALUE valstr;
  VALUE cv;

  SET_YARV_STOP();
  
  if(usage_hash == 0){
    usage_hash  = rb_intern("USAGE_ANALISYS_INSN");
  }
  
  uh = rb_const_get(mYarvCore, usage_hash);
  if((ihash = rb_hash_aref(uh, INT2FIX(insn))) == Qnil){
    ihash = rb_hash_new();
    rb_hash_aset(uh, INT2FIX(insn), ihash);
  }
  if((ophash = rb_hash_aref(ihash, INT2FIX(n))) == Qnil){
    ophash = rb_hash_new();
    rb_hash_aset(ihash, INT2FIX(n), ophash);
  }
  /* intern */
  valstr = insn_operand_intern(insn, n, op, 0,0,0);
  
  /* set count */
  if((cv = rb_hash_aref(ophash, valstr)) == Qnil){
    cv = INT2FIX(0);
  }
  rb_hash_aset(ophash, valstr, INT2FIX(FIX2INT(cv) + 1));
  
  SET_YARV_START();
}

void vm_analysis_register(int reg, int isset){
  static ID usage_hash;
  VALUE uh;
  VALUE rhash;
  VALUE valstr;
  char *regstrs[] = {
    "pc",   // 0
    "sp",   // 1
    "cfp",  // 2
    "lfp",  // 3
    "dfp",  // 4
    "self", // 5
    "iseq", // 6
  };
  char *getsetstr[] = {
    "get",
    "set",
  };
  static VALUE syms[sizeof(regstrs)/sizeof(regstrs[0])][2];
  
  VALUE cv;

  SET_YARV_STOP();
  
  if(usage_hash == 0){
    char buff[0x10];
    int i;
    
    usage_hash = rb_intern("USAGE_ANALISYS_REGS");

    for(i=0; i<sizeof(regstrs)/sizeof(regstrs[0]); i++){
      int j;
      for(j=0; j<2; j++){
        snprintf(buff, 0x10, "%d %s %-4s", i, getsetstr[j], regstrs[i]);
        syms[i][j] = ID2SYM(rb_intern(buff));
      }
    }
  }
  valstr = syms[reg][isset];
  
  uh = rb_const_get(mYarvCore, usage_hash);
  if((cv = rb_hash_aref(uh, valstr)) == Qnil){
    cv = INT2FIX(0);
  }
  rb_hash_aset(uh, valstr, INT2FIX(FIX2INT(cv) + 1));
  
  SET_YARV_START();
}


#endif

#include "call_cfunc.inc"

VALUE thread_dump_state(VALUE self){
  struct thread_object* thobj;
  GetThreadVal(self, thobj);

  printf("Thread state dump:\n");
  printf("pc : %8x, sp : %8x\n", thobj->pc, thobj->sp);
  printf("cfp: %8x, lfp: %8x, dfp: %8x\n", thobj->cfp, thobj->lfp, thobj->dfp);

  return Qnil;
}



/*
 * prepare stack frame
 */
static inline
VALUE thread_set_environment(VALUE th, VALUE iseq, VALUE magic, VALUE *lfp,
                             VALUE selfobj, VALUE block_or_prev_env,
                             int   argc, const VALUE *argv){
  
  struct iseq_object *iseqobj;
  int i, local_size;
  VALUE *return_lfp, *return_dfp, *return_cfp;
  register struct thread_object* thobj;
  register VALUE * sp;
  
  GetThreadVal(th, thobj);
  GetISeqVal(iseq, iseqobj);

  return_lfp = thobj->lfp;
  return_dfp = thobj->dfp;
  return_cfp = thobj->cfp;
  sp         = thobj->sp;
  
  /* push local variables */
  local_size = iseqobj->local_size;

  /* copy size */
  for(i=0; i < argc; i++){
    *sp = *argv;
    sp++;
    argv++;
  }
  
  /* clear local frame */
  for( /* empty */ ; i<local_size; i++){
    *sp = Qnil;
    sp++;
  }

  thobj->lfp = lfp ? lfp : sp;
  thobj->dfp = sp;
  thobj->cfp = sp + 1;
  
  {
    /* push (method|block|class) frame */
    struct class_frame *f = (struct class_frame *)sp;
    struct control_frame *cf = &f->control_frame;
    register struct continuation_frame *cont = &cf->continuation_frame;
    f->dummy    = (VALUE)block_or_prev_env;

    cf->self    = selfobj;
    cf->iseq    = iseq;

    cont->magic = magic;
    cont->pc    = 0;           /* for return VM loop */
    cont->lfp   = return_lfp;
    cont->dfp   = return_dfp;
    cont->cfp   = return_cfp;
  }
  thobj->sp = sp + FRAME_WC();
  thobj->pc = GET_SEQ(iseqobj);

  // stack_dump_raw(thobj, thobj->sp, thobj->lfp, thobj->dfp, thobj->cfp);
  return Qtrue;
}


EXTERN VALUE ruby_top_self;

VALUE thread_set_top_stack(VALUE self, VALUE iseq){
  struct iseq_object *iseqobj;

  GetISeqVal(iseq, iseqobj);
  iseqobj->klass_nest_stack = rb_ary_push(rb_ary_new(), rb_cObject);
  
  return thread_set_environment(self, iseq, FRAME_MAGIC_TOP, 0,
                                ruby_top_self, 0, 0, 0);
}



VALUE thread_call0(VALUE self, VALUE klass, VALUE recv, VALUE id, ID oid,
                   int argc, const VALUE *argv, NODE *body, int nosuper){

  struct thread_object *thobj;
  VALUE val;

  // printf("thread_call0: %s (%x), %s (%x) (%s: %x)\n", rb_id2name(id), id, rb_id2name(oid), id, node_name(nd_type(body)), body);

  switch(nd_type(body)){
  case NODE_ATTRSET:{
    if (argc != 1){
      rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc);
    }
    val = rb_ivar_set(recv, body->nd_vid, argv[0]);
    break;
  }
  case NODE_IVAR:{
    if (argc != 0) {
      rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc);
    }
    val = rb_attr_get(recv, body->nd_vid);
    break;
  }
  case NODE_CFUNC:{
    VALUE *sp;
    struct method_frame *f;
    struct vm_block_object *bp = 0;
    struct cmethod_info ci = {0, id, klass};
    
    GetThreadVal(self, thobj);
    sp = thobj->sp;
    *sp = Qnil;
    sp += 1;

    if(thobj->block_ptr){
      bp = (struct vm_block_object *)
        (sp + FRAME_WC() - VM_BLOCK_OBJECT_SAHRE_SIZE);
      //bp->lfp  = thobj->lfp;
      //bp->dfp  = 0; /* share */
      bp->self = Qnil;
      bp->iseq = (VALUE)thobj->block_ptr;
      bp->proc = 0;
      thobj->block_ptr = 0;
    }

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

      f->control_frame.continuation_frame.magic = FRAME_MAGIC_METHOD;
      f->control_frame.continuation_frame.pc    = thobj->pc;
      f->control_frame.continuation_frame.cfp   = thobj->cfp;
      f->control_frame.continuation_frame.lfp   = thobj->lfp;
      f->control_frame.continuation_frame.dfp   = thobj->dfp;

      thobj->lfp = (VALUE*)&f->block_ptr;
      thobj->dfp = 0;
      thobj->cfp = (VALUE*)&f->control_frame;
      thobj->sp  = sp + FRAME_WC() + (bp ? VM_BLOCK_OBJECT_OVER_SIZE : 0);
    }
    // printf("** %x\n", thobj->cfp);
    //printf("thread_call0_cfunc enter (%x)\n", bp);
    val = call_cfunc(body->nd_cfnc, recv, body->nd_argc, argc, argv);
    //printf("thread_call0_cfunc return (%x)\n", bp);

    thobj->cfp = f->control_frame.continuation_frame.cfp;
    thobj->lfp = f->control_frame.continuation_frame.lfp;
    thobj->dfp = f->control_frame.continuation_frame.dfp;
    thobj->sp  = sp - 1;
    break;
  }
  case YARV_METHOD_NODE:{
    struct vm_block_object *bp = 0;
    struct vm_block_object bo;
    
    GetThreadVal(self, thobj);
    thobj->sp++;
    // printf("1> cfp: %8x, lfp: %8x, dfp: %8x, sp: %8x\n", th->cfp, th->lfp, th->dfp, th->sp);
    //stack_dump_raw(th, th->sp, th->lfp, th->dfp, th->cfp);

    if(thobj->block_ptr){
      bp = &bo;
      bo.lfp  = thobj->lfp;
      bo.dfp  = 0;
      bo.self = Qnil;
      bo.iseq = (VALUE)thobj->block_ptr;
      bo.proc = 0;
      thobj->block_ptr = 0;

      // printf("call0 - lfp: %x\n", bo.lfp);
    }

    thread_set_environment(self, (VALUE)body->nd_body, FRAME_MAGIC_METHOD, 0,
                           recv, (VALUE)bp, argc, argv);
    //printf("2> cfp: %8x, lfp: %8x, dfp: %8x, sp: %8x\n", th->cfp, th->lfp, th->dfp, th->sp);
    //stack_dump_raw(th, th->sp, th->lfp, th->dfp, th->cfp);
    {
      val = thread_eval_body(self);
      // printf("3> cfp: %8x, lfp: %8x, dfp: %8x, sp: %8x\n", th->cfp, th->lfp, th->dfp, th->sp);
    }
    break;
  }

  default:
    rb_bug("thread_call0: unknown node type %d", nd_type(body));
  }

  return val;
}


VALUE thread_call_super(VALUE self, int argc, const VALUE *argv){
  struct thread_object *thobj;
  NODE *body;
  VALUE recv, klass;
  ID id;
  int nosuper = 0;

  GetThreadVal(self, thobj);

  recv  = thobj->cfp[0];
  {
    VALUE *v = (VALUE *)thobj->cfp[1];
    if(*v == 0){
      struct cmethod_info *cmi = (void *)v;
      klass = RCLASS(cmi->klass)->super;
      id = cmi->id;
    }
    else{
      VALUE iseq = thobj->cfp[1];
      struct iseq_object *iseqobj;
      struct RArray *ary;

      rb_bug("thread_call_super: should not be reached");
      /*
      GetISeqVal(iseq, iseqobj);
      while(iseqobj->parent_iseqobj){
        iseqobj = iseqobj->parent_iseqobj;
      }
      ary = RARRAY(iseqobj->klass_nest_stack);
      klass = ary->ptr[ary->len-1];
      klass = RCLASS(klass)->super;
       */
    }
  }
  
  body = rb_method_node(klass, id);
  return thread_call0(self, klass, recv, id, id, argc, argv, body, nosuper);
}


static inline
VALUE thread_invoke_yield_cfunc(struct thread_object *thobj,
                                NODE *ifunc, int argc, VALUE *argv,
                                struct vm_block_object *bp){
  VALUE val;
  VALUE arg = Qnil;
  VALUE *lfp, *dfp, *cfp, *sp;

  if(argc == 1){
    arg = *argv;
  }
  else if(argc > 1){
    arg = rb_ary_new4(argc, argv);
  }
  
  
  {
    struct block_frame *f;
    sp = thobj->sp;

    f = (struct block_frame *)sp;
    f->prev_dfp           = bp->dfp;
    f->control_frame.self = Qnil;
    f->control_frame.iseq = symIFUNC;

    f->control_frame.continuation_frame.magic = FRAME_MAGIC_BLOCK;
    f->control_frame.continuation_frame.pc    = thobj->pc;
    f->control_frame.continuation_frame.cfp   = cfp = thobj->cfp;
    f->control_frame.continuation_frame.lfp   = lfp = thobj->lfp;
    f->control_frame.continuation_frame.dfp   = dfp = thobj->dfp;

    thobj->lfp = bp->lfp;
    thobj->dfp = bp->dfp;
    thobj->cfp = (VALUE*)&f->control_frame;
    thobj->sp  = sp + FRAME_WC();
  }
  
  val = (*ifunc->nd_cfnc)(arg, ifunc->nd_tval, Qnil);

  thobj->lfp = lfp;
  thobj->dfp = dfp;
  thobj->cfp = cfp;
  thobj->sp  = sp;
  
  return val;
}

VALUE thread_invoke_yield(VALUE self, int argc, VALUE *argv){
  struct thread_object *thobj;
  struct vm_block_object *bp;

  GetThreadVal(self, thobj);
  bp = (struct vm_block_object *)thobj->lfp[0]; /* block */

  if(0){ /* for debug */
    int i;
    for(i=0; i<argc; i++){
      dp(argv[i]);
    }
  }
  
  
  if(bp == 0){
    stack_dump_thobj(thobj);
    rb_bug("block is not given");
  }
  
  if(BUILTIN_TYPE(bp->iseq) == T_NODE){
    return thread_invoke_yield_cfunc(thobj, (NODE*)bp->iseq, argc, argv, bp);
  }
  else{
    thread_set_environment(self,              /* th */
                           bp->iseq,          /* iseq */
                           FRAME_MAGIC_BLOCK, /* magic */
                           bp->lfp,           /* lfp */
                           bp->self,          /* selfobj */
                           (VALUE)bp->dfp,    /* block or prev env */
                           argc, argv);       /* argv, argc */

    return thread_eval_body(self);
  }
}


VALUE thread_yield_light_prepare(VALUE self, int argc, VALUE *argv,
                                 struct yarv_yield_data *data){
  struct thread_object *thobj;
  struct iseq_object *iseqobj;
  struct vm_block_object *bp;
  VALUE iseq;
  VALUE *dfp;
  
  GetThreadVal(self, thobj);
  data->sp_start  = thobj->sp;
  dfp = thobj->dfp;

  bp = (struct vm_block_object *)thobj->lfp[0];

  if(bp == 0){
    rb_bug("block is not given");
  }

  if(BUILTIN_TYPE(bp->iseq) == T_NODE){
    /* none */
  }
  else{
    bp = (struct vm_block_object *)thobj->lfp[0];

    thread_set_environment(self,
                           bp->iseq,
                           FRAME_MAGIC_BLOCK,
                           bp->lfp,
                           bp->self,
                           (VALUE)bp->dfp,
                           argc, argv);

    GetISeqVal(bp->iseq, iseqobj);

    data->local_size = iseqobj->local_size;
    data->sp  = thobj->sp;
    data->pc  = thobj->pc;
    data->cfp = thobj->cfp;
    data->lfp = thobj->lfp;
    data->dfp = thobj->dfp;
    thobj->dfp = dfp; /* restore */
  }
  return Qtrue;
}


VALUE thread_yield_light_invoke(VALUE self, int argc, VALUE *argv,
                                struct yarv_yield_data *data){
  struct thread_object *thobj;
  int local_size;
  int i;
  VALUE *sp;

  /* setup environment */
  GetThreadVal(self, thobj);
  
  local_size = data->local_size;
  sp         = data->sp_start;
  
  for(i=0; i<argc; i++){
    *sp = *argv;
    sp ++;
    argv ++;
  }
  for(/* empty */; i<local_size; i++){
    *sp = Qnil;
    sp++;
  }

  /* really? */
  /* noneed? */
  // *sp = (VALUE)thread->dfp;
  
  thobj->sp  = data->sp;
  thobj->pc  = data->pc;
  thobj->cfp = data->cfp;
  thobj->lfp = data->lfp;
  // printf("%d %d\n", thobj->lfp, thobj->dfp);
  thobj->dfp = data->dfp;
  thobj->cfp[2] = FRAME_MAGIC_BLOCK;
  
  // stack_dump_thobj(thobj);
  return thread_eval_body(self);
}


VALUE thread_invoke_proc_call(VALUE self, VALUE proc, int argc, VALUE *argv){
  struct thread_object *thobj;
  struct vm_proc_object *procobj;
  VALUE* stack;
  
  GetThreadVal(self, thobj);
  GetProcVal(proc, procobj);

  /* setup environment */
  thread_set_environment(self, procobj->bo.iseq, FRAME_MAGIC_BLOCK,
                         procobj->bo.lfp, procobj->bo.self,
                         (VALUE)procobj->bo.dfp, argc, argv);

  return thread_eval_body(self);
}


VALUE thread_make_env_object(VALUE th, VALUE *envptr, VALUE *endptr){
  VALUE env, iseq, prevenv = 0;
  VALUE *dp, *sp;
  struct vm_env_control *envctrl;
  struct vm_env_object *envobj;
  struct iseq_object *iseqobj;
  int i, local_size;

  if(FRAME_IS_IN_HEAP(envptr)){
    rb_bug("thread_make_evn_object: should not be reached (frame is already in heap)");
  }
  
  /* dfp[2] == cfp[1] must be "iseq object" if env is not in heap */
  iseq = envptr[2];
  GetISeqVal(iseq, iseqobj);
  local_size = iseqobj->local_size;

  env = rb_obj_alloc(cYarvEnv);
  GetEnvVal(env, envobj);
  
  dp = envobj->env = ALLOC_N(VALUE, local_size + 1 + GET_FRAME_CONTROL_BUFF_WC());
  envobj->env_size = local_size + 1 + GET_FRAME_CONTROL_BUFF_WC();
  envobj->dfp      = &dp[local_size];
  
  
  if(envptr == endptr){
    /* this is endptr environment (maybe lfp) */
    prevenv = 0;
    envobj->prev_envobj = 0;
    envobj->lfp = envobj->dfp;
  }
  else{
    VALUE *prev_envptr = (VALUE*)*envptr;
    struct vm_env_object *prevenvobj;
    
    if(FRAME_IS_IN_HEAP(prev_envptr)){
      /* already prev env is in heap */
      prevenv = GET_FRAME_ENV_VAL(prev_envptr);
      GetEnvVal(prevenv, prevenvobj);
    }
    else{
      /* make env recursively */
      prevenv = thread_make_env_object(th, prev_envptr, endptr);
      GetEnvVal(prevenv, prevenvobj);
      envobj->orig_prev_dfp = prev_envptr;
      *envptr = (VALUE)prevenvobj->dfp; /* set new prev env ptr */
    }

    envobj->prev_envobj = prevenv;
    envobj->lfp         = prevenvobj->lfp;
  }

  sp = &envptr[-local_size];
  for(i=0; i<local_size+1 /* (method|block|class)_frame info */; i++){
    dp[i] = sp[i];
  }

  /* envctrl setup */
  envctrl = (struct vm_env_control *)&dp[local_size + 1];
  envctrl->is_orphan  = Qfalse;
  envctrl->is_in_heap = Qundef;
  envctrl->prev_env   = prevenv;
  envctrl->self       = env;
  
  envptr[0] = env;  /* for GC mark */

  return env;
}


VALUE thread_make_proc_object(VALUE th, VALUE proc_iseq, VALUE self,
                              VALUE *cfp, VALUE *orig_dfp, VALUE *orig_lfp);
/*
  store block information on stack
 */
VALUE get_block_object(VALUE th, VALUE *block_ptr){
  struct vm_block_object *bp = (struct vm_block_object *)*block_ptr;
  VALUE block;

  if(bp == 0){
    /* no block given */
    return 0;
  }
  
  if(bp->proc == 0){
    /* block */
    struct vm_proc_object *pp;
    struct thread_object *thobj;
    
    VALUE *cfp = bp->dfp + 1;
    
    block = thread_make_proc_object(th, bp->iseq, bp->self,
                                    cfp, bp->dfp, bp->lfp);
    
    GetProcVal(block, pp);
    *block_ptr = (VALUE)&pp->bo;

    GetThreadVal(th, thobj);
    bp->lfp = thobj->lfp;
    bp->dfp = thobj->dfp;
  }
  else{
    block = bp->proc;
  }

  return block;
}


VALUE thread_make_proc_object(VALUE th, VALUE proc_iseq, VALUE self,
                              VALUE *cfp, VALUE *orig_dfp, VALUE *orig_lfp){
  VALUE proc, env = Qnil;
  VALUE iseq;
  VALUE *dfp;
  VALUE *lfp;
  VALUE block;
  
  struct thread_object *thobj;
  struct vm_proc_object *procobj;
  struct vm_env_object *envobj;
  
  GetThreadVal(th, thobj);
  
  if(FRAME_IS_IN_HEAP(orig_dfp)){
    if(!FRAME_IS_IN_HEAP(orig_lfp)){
      rb_bug("lfp must point env in heap");
    }

    thobj->dfp = orig_dfp;
    thobj->lfp = orig_lfp;
    
    env = GET_FRAME_ENV_VAL(orig_dfp);
    GetEnvVal(env, envobj);

    block = get_block_object(th, orig_lfp);
  }
  else{
    VALUE *olfp, *odfp;
    VALUE ienv;
    struct vm_env_object *ienvobj;
    
    
    env = thread_make_env_object(th, orig_dfp, orig_lfp);
    GetEnvVal(env, envobj);
    ienvobj = envobj;
    
    dfp = ienvobj->dfp;
    lfp = ienvobj->lfp;
    
    odfp = ienvobj->orig_prev_dfp;
    olfp = orig_lfp;
    
    block = get_block_object(th, lfp);

    thobj->dfp = dfp;
    thobj->lfp = lfp;
    
//printf("envobj->prev_envobj: %x\n", envobj->prev_envobj);
    if(envobj->prev_envobj != 0){
      GetEnvVal(envobj->prev_envobj, ienvobj);
      dfp = ienvobj->dfp;

      /* re-link dfp and lfp */
      // stack_dump_raw(thobj, thobj->sp, orig_lfp, orig_dfp, cfp);
      
      while(cfp){
        struct control_frame *cf = (struct control_frame*)cfp;
        VALUE *td, *tl;
        td = cf->continuation_frame.dfp;
        tl = cf->continuation_frame.lfp;

        if(0)printf("cfp: %d, %d\n===: %d, %d\n",
                    td - thobj->stack, tl - thobj->stack,
                    odfp - thobj->stack,
                    olfp - thobj->stack);

        if(td != 0 && FRAME_IS_IN_HEAP(td)){
          // printf("break2\n");
          break;
        }

        if(tl == olfp){
          // printf("lfp change\n");
          cf->continuation_frame.lfp = lfp;
        }

        if(td != 0 && td == odfp){
          // printf("dfp change\n");
          cf->continuation_frame.dfp = dfp;

          if(td == olfp){
            // printf("break!\n");
            break;
          }
          else{
            odfp = ienvobj->orig_prev_dfp;
            ienv = ienvobj->prev_envobj;
            GetEnvVal(ienv, ienvobj);
            dfp = ienvobj->dfp;
          }
        }
        cfp = cf->continuation_frame.cfp;
      }
    }
  }
  proc = rb_obj_alloc(cYarvProc);

  GetProcVal(proc, procobj);
  
  procobj->bo.lfp  = envobj->lfp;
  procobj->bo.dfp  = envobj->dfp;
  procobj->bo.self = self;
  procobj->bo.iseq = proc_iseq;
  procobj->envobj  = env;
  procobj->blockobj= block;

  return proc; 
}

VALUE *thread_svar(VALUE self, int cnt){
  struct thread_object *thobj;
  struct control_frame *cf;
  
  VALUE *lfp;
  VALUE ary;
  
  GetThreadVal(self, thobj);

  if(thobj->dfp == 0){
    cf = (struct control_frame*)thobj->cfp;
    while(cf->continuation_frame.dfp == 0){
      cf = (struct control_frame*)cf->continuation_frame.cfp;
    }
    lfp = cf->continuation_frame.lfp;
  }
  else{
    lfp = thobj->lfp;
  }
  
  /* should be check thread root svar or not */
  /****/
  /****/
  
  ary = lfp[-1] = (lfp[-1] == Qnil) ? rb_ary_new() : lfp[-1];

  if(RARRAY(ary)->len <= cnt){
    rb_ary_store(ary, cnt, Qnil);
  }
  return &RARRAY(ary)->ptr[cnt];

  
}


VALUE thread_backtrace(VALUE th, int level){
  struct thread_object *thobj;
  VALUE *cfp, *pc = 0;
  VALUE ary;
  int lev = 0;
  char buf[BUFSIZE];
// printf("thread_backtrace:\n");
  GetThreadVal(th, thobj);
  ary = rb_ary_new();

  cfp = thobj->cfp;
  
  while(cfp){
    struct control_frame *f = (void *)cfp;
    if(lev > level){
      // printf("%x\n", cfp);
      if(CMETHOD_INFO_P(f->iseq)){
        struct cmethod_info *cmi = (void *)f->iseq;
        
        // dp(f->iseq);
        snprintf(buf, BUFSIZE, "in `%s'", rb_id2name(cmi->id));
        
        rb_ary_push(ary, rb_str_new2(buf));
      }
      else{
        struct iseq_object *iseq;
        int pos, sendpos, i, line_no;

        GetISeqVal(f->iseq, iseq);
        pos = pc - GET_SEQ(iseq);
        for(i=0; i<iseq->insn_info_size; i++){
          if(iseq->insn_info_tbl[i].position == pos){
            line_no = iseq->insn_info_tbl[i-1].line_no;
            sendpos = iseq->insn_info_tbl[i-1].position;
            break;
          }
        }
        if(i == iseq->insn_info_size){
          rb_bug("thread_backtrace: unkown instruction (%d)", pos);
        }

        if(iseq->type == ISEQ_TYPE_TOP){
          snprintf(buf, BUFSIZE, "%s:%d",
                   RSTRING(iseq->file_name)->ptr, line_no);
        }
        else if(iseq->type == ISEQ_TYPE_BLOCK){
          char *name = 0;
          struct iseq_object *pi = iseq;
          while(pi){
            if(pi->parent_iseqobj == 0){
              name = RSTRING(pi->name)->ptr;
            }
            pi = pi->parent_iseqobj;
          }
          snprintf(buf, BUFSIZE, "%s:%d:in `%s' (in block)",
                   RSTRING(iseq->file_name)->ptr, line_no, name);
        }
        else{
          snprintf(buf, BUFSIZE, "%s:%d:in `%s'",
                   RSTRING(iseq->file_name)->ptr, line_no,
                   RSTRING(iseq->name)->ptr);
        }
        rb_ary_push(ary, rb_str_new2(buf));
      }
    }
    //rb_ary_push();
    cfp = f->continuation_frame.cfp;
    pc  = f->continuation_frame.pc;
    lev++;
  }

  return ary;
}


/* vm main loop helper functions */
VALUE thread_get_ev_const(VALUE self, struct thread_object *th,
                          struct iseq_object *iseq, VALUE klass, ID id){
  VALUE val;
  
  if(klass == Qnil){
    /* in current lexical scope */
    VALUE cref; /* Array */
    int i, last_pos;
    
    if((cref = iseq->klass_nest_stack) == 0){
      /* in module or class scope */
      cref = th->klass_nest_stack;
    }
    last_pos = RARRAY(cref)->len-1;
    for(i = last_pos; i>0; i--){
      klass = RARRAY(cref)->ptr[i];
    search_continue:
      if(RCLASS(klass)->iv_tbl &&
         st_lookup(RCLASS(klass)->iv_tbl, id, &val)){
        if(val == Qundef){
          rb_autoload_load(klass, id);
          goto search_continue;
        }
        else{
          break;
        }
      }
    }
    if(i==0){
      klass = RARRAY(cref)->ptr[last_pos];
      val = rb_const_get(klass, id);
    }
  }
  else{
    val = rb_const_get(klass, id);
  }
  return val;
}

int thread_get_ev_defined(VALUE self, struct thread_object *th,
                          struct iseq_object *iseq, VALUE klass, ID id){
  VALUE val;
  if(klass == Qnil){
    VALUE cref;
    int i, last_pos;

    if((cref = iseq->klass_nest_stack) == 0){
      cref = th->klass_nest_stack;
    }
    last_pos = RARRAY(cref)->len-1;
    for(i = last_pos; i>0; i--){
      klass = RARRAY(cref)->ptr[i];
    search_continue:
      if(RCLASS(klass)->iv_tbl &&
         st_lookup(RCLASS(klass)->iv_tbl, id, &val)){
        if(val == Qundef){
          rb_autoload_load(klass, id);
          goto search_continue;
        }
        else{
          return 1;
        }
      }
    }
    klass = RARRAY(cref)->ptr[last_pos];
    return rb_const_defined(klass, id);
  }
  else{
    return rb_const_defined(klass, id);
  }
}


/* vm main loop */

#ifndef HAVE_TYPE_ULONG
typedef unsigned long ulong;
#endif

typedef ulong GENTRY;


/*********************************************************/
/*********************************************************/
/* import from ruby1.9/eval.c                            */

#include <stdio.h>
#if defined(HAVE_GETCONTEXT) && defined(HAVE_SETCONTEXT)
#include <ucontext.h>
#define USE_CONTEXT
#else
#include <setjmp.h>
#endif

#ifdef USE_CONTEXT
typedef struct {
    ucontext_t context;
    volatile int status;
} rb_jmpbuf_t[1];

#undef longjmp
#undef setjmp
NORETURN(static void rb_jump_context(rb_jmpbuf_t, int));
static inline void
rb_jump_context(env, val)
    rb_jmpbuf_t env;
    int val;
{
    env->status = val;
    setcontext(&env->context);
    abort();			/* ensure noreturn */
}
#define longjmp(env, val) rb_jump_context(env, val)
#define setjmp(j) ((j)->status = 0, getcontext(&(j)->context), (j)->status)
#else
typedef jmp_buf rb_jmpbuf_t;
#ifndef setjmp
#ifdef HAVE__SETJMP
#define setjmp(env) _setjmp(env)
#define longjmp(env,val) _longjmp(env,val)
#endif
#endif
#endif

struct tag {
    rb_jmpbuf_t buf;
    struct FRAME *frame;
    struct iter *iter;
    VALUE tag;
    VALUE retval;
    struct SCOPE *scope;
    VALUE dst;
    struct tag *prev;
    int blkid;
};
RUBY_EXTERN struct tag *ruby_prot_tag;

#define PUSH_TAG(ptag) do {            \
  _tag.prev = ruby_prot_tag;           \
  _tag.iter = ruby_prot_tag->iter;     \
  _tag.frame = ruby_frame;             \
  _tag.tag  = ptag;                    \
  ruby_prot_tag = &_tag;               \
} while(0)

#define POP_TAG() do {                 \
  ruby_prot_tag = ruby_prot_tag->prev; \
} while(0)

#define EXEC_TAG()                     \
  (FLUSH_REGISTER_WINDOWS, setjmp(ruby_prot_tag->buf))

#define JUMP_TAG(st) do {              \
  longjmp(ruby_prot_tag->buf,(st));    \
} while (0)

#define TAG_RETURN	0x1
#define TAG_BREAK	0x2
#define TAG_NEXT	0x3
#define TAG_RETRY	0x4
#define TAG_REDO	0x5
#define TAG_RAISE	0x6
#define TAG_THROW	0x7
#define TAG_FATAL	0x8
#define TAG_MASK	0xf

/*********************************************************/
/*********************************************************/

VALUE yarv_unredefined_flag = 0x01;

#include "vm_evalbody.inc"

#if __GNUC__
#define thread_eval_body thread_eval_body_copy
#include "vm_evalbody.inc"
#endif

