/* -*-c-*- *********************************************************
 * this file is included by vm.c
 */

#if VMDEBUG > 0
#define DECL_SC_REG(type, r, reg) register type reg_##r

#elif __GNUC__ && __x86_64
#define DECL_SC_REG(type, r, reg) register type reg_##r asm("r" reg)

#elif __GNUC__ && __i386__
#define DECL_SC_REG(type, r, reg) register type reg_##r asm("e" reg)

#else
#define DECL_SC_REG(type, r, reg) register type reg_##r
#endif
// #define DECL_SC_REG(r, reg) VALUE reg_##r

VALUE thread_eval_body(VALUE self){
  struct thread_object *th;

#ifdef OPT_STACK_CACHING
#if 0
  DECL_SC_REG(VALUE, a, "di");
  DECL_SC_REG(VALUE, b, "dx");
#else
  register VALUE reg_a;
  register VALUE reg_b;
#endif
#endif

#if 1 && __GNUC__ && __i386__
  DECL_SC_REG(VALUE *, pc, "si");
  VALUE * volatile reg_pc_store;

#undef  SET_PC
#undef  RESTORE_REGS

#define SET_PC(x) (reg_pc_store = REG_PC = (x))
#define RESTORE_REGS() { \
  REG_PC = reg_pc_store; \
}
#elif 0 /* for debug */
  DECL_SC_REG(VALUE *, pc, "si");
#else
  VALUE *reg_pc;
#endif

  VALUE *reg_sp;
  VALUE *reg_lfp;
  VALUE *reg_dfp;
  VALUE *reg_cfp;

  ID tmp_id;
  VALUE throwed;
  
  int state;
  struct tag _tag;

#ifdef DISPATCH_THREADED_CODE
#include "vmtc.inc"
#endif

#ifdef DISPATCH_DIRECT_THREADED_CODE
  if(self == 0){
    return (VALUE)insns_address_table;
  }
#endif
  
  GetThreadVal(self , th);

  reg_pc  = th->pc;
  reg_sp  = th->sp;
  reg_lfp = th->lfp;
  reg_dfp = th->dfp;
  reg_cfp = th->cfp;

#ifdef OPT_STACK_CACHING
  reg_a = reg_b = 0;
#endif
  PUSH_TAG(0);
  if((state = EXEC_TAG()) == 0){

  first:
    INSN_DISPATCH();
    
    /******************/
     #include "vm.inc"
    /******************/

    END_INSNS_DISPATCH();

    /* finish */
  finish:;
  }
  else{
    RESTORE_REGS();
    throwed = ruby_errinfo;
    if(state == TAG_RAISE){
      rb_ivar_set(throwed, idThrowState, state);
    }
    
  catch_entry:
    {
      int i;
      struct catch_table_entry *entry;
      VALUE  catch_iseq           = 0;
      struct iseq_object* iseqobj = 0;
      ulong  epc, cont, cont_sp;
      iseqobj = GET_ISEQ();
      epc     = GET_PC_COUNT();
      
      // dp(iseqobj->name);
      // printf("iseqobj->catch_table_size: %d\n", iseqobj->catch_table_size);
      
      for(i=0; i<iseqobj->catch_table_size; i++){
        entry = &iseqobj->catch_table[i];
        // printf("%d ? [%d] %d, %d\n", epc, entry->type, entry->start, entry->end);
        if(entry->start <  epc &&
           entry->end   >= epc){

          if(state == TAG_RAISE){
            if(entry->type == CATCH_TYPE_RESCUE){
              catch_iseq = entry->iseq;
              cont       = entry->cont;
              cont_sp    = entry->sp;
              break;
            }
          }
          else if(state == TAG_RETRY){
            if(entry->type == CATCH_TYPE_RETRY){
              VALUE  *escape_dfp;
              escape_dfp = GET_THROWOBJ_CATCH_POINT(throwed);
              // printf("[%d, %d]\n", GET_DFP() - th->stack, escape_dfp - th->stack);
              if(GET_DFP() == escape_dfp){
                cont       = entry->cont;
                cont_sp    = entry->sp;
                SET_PC(GET_SEQ(iseqobj) + cont);
                SET_SP(GET_CFP() + FRAME_WC() + BLOCK_WC_IF_GIVEN() + cont_sp);
                goto first;
              }
            }
          }

          /* search ensure */
          if(entry->type == CATCH_TYPE_ENSURE){
            catch_iseq = entry->iseq;
            cont       = entry->cont;
            cont_sp    = entry->sp;
            break;
          }
        }
      }

      /* search catch table */
      if(catch_iseq){
        struct iseq_object* catch_iseqobj = 0;
        VALUE *sp;
        struct block_frame *f;
        VALUE *prev_env;
        VALUE  prev_main;

        /* enter catch scope */
        GetISeqVal(catch_iseq, catch_iseqobj);
        sp = GET_CFP() + FRAME_WC() + cont_sp;

        if(GET_LFP()[0] == (VALUE)(GET_CFP() + FRAME_WC() - 1)){
          SET_SP(GET_CFP() + FRAME_WC() + cont_sp + BLOCK_WC());
        }
        else{
          SET_SP(GET_CFP() + FRAME_WC() + cont_sp);
        }
        
        /* PUSH(throwed) */
        *sp++ = throwed;
        
        /* push method frame */
        f = (struct block_frame *)sp;
        f->prev_dfp                               = GET_DFP();

        f->control_frame.self                     = GET_SELF();
        f->control_frame.iseq                     = catch_iseq;

        f->control_frame.continuation_frame.magic = FRAME_MAGIC_BLOCK;
        f->control_frame.continuation_frame.pc    = GET_SEQ(iseqobj) + cont;
        f->control_frame.continuation_frame.lfp   = GET_LFP();
        f->control_frame.continuation_frame.dfp   = GET_DFP();
        f->control_frame.continuation_frame.cfp   = GET_CFP();
        // STACKDUMP();

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

        /* others */

        state = 0;
        ruby_errinfo = Qnil;

        goto first;
      }
      else{
        /* rewind stack */
        struct continuation_frame *cf;
        struct iseq_object        *iseqobj;


        if(state == TAG_BREAK || state == TAG_RETURN){
          static VALUE  *escape_dfp;
          escape_dfp = GET_THROWOBJ_CATCH_POINT(throwed);

          throwdebug("[[st: %d] dfp: %d, lfp: %d, esc: %d, cfp: %d]\n",
                     state, GET_DFP() - th->stack,
                     GET_LFP() - th->stack, escape_dfp - th->stack,
                     GET_CFP() - th->stack);
          
          if(GET_DFP() == escape_dfp){
            if(state == TAG_RETURN){
              VALUE setdfp;
              struct continuation_frame *cf =
                (struct continuation_frame *)(GET_CFP() + CC_DIFF_WC());

              throwdebug("== %d, %d, %d\n",
                         cf->cfp - th->stack, cf->lfp - th->stack, cf->dfp - th->stack);
              
              SET_THROWOBJ_CATCH_POINT(throwed, (VALUE)cf->dfp);
              SET_THROWOBJ_STATE      (throwed, state = TAG_BREAK);
              /* through */
            }
            else{
              /* TAG_BREAK */
#ifdef OPT_STACK_CACHING
              reg_a = GET_THROWOBJ_VAL(throwed);
#else
              PUSH(GET_THROWOBJ_VAL(throwed));
#endif
              ruby_errinfo = Qnil;
              goto first;
            }
          }
        }

      rewind_stack_point:
        iseqobj = GET_ISEQ();
        cf = (struct continuation_frame *)(GET_CFP() + CC_DIFF_WC());
        CHECK_FRAME_MAGIC(cf->magic);
        CLEAR_ENV(GET_DFP());

        SET_SP (GET_CFP() - iseqobj->rewind_frame_size);
        SET_PC (cf->pc);
        SET_LFP(cf->lfp);
        SET_DFP(cf->dfp);
        SET_CFP(cf->cfp);

        if(GET_PC() != 0){
          goto catch_entry;
        }
        else{
          /* no more VM stack */

          /* need this?
          if(state != TAG_RAISE){
            ruby_errinfo = Qnil;
          }
           */
          ruby_errinfo = throwed;
          POP_TAG();
          JUMP_TAG(state);
        }
      }
    }
  }
  POP_TAG();

  th->sp  = reg_sp;
  th->pc  = reg_pc;
  th->lfp = reg_lfp;
  th->dfp = reg_dfp;
  th->cfp = reg_cfp;

  return throwed;
}
