/////////////////////////////////////////////////////////////////////////////
//
// r3000 - Emulates R3000 CPU
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "r3000.h"

/////////////////////////////////////////////////////////////////////////////
/*
** integer overflow exceptions are not taken!
** divide by zero exceptions are not taken!
*/
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
/*
** Divider is now hardcoded
*/
#define DIVIDER (1)

/////////////////////////////////////////////////////////////////////////////
/*
** Static information
*/

/* (none) */

sint32 EMU_CALL r3000_init(void) {
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
/*
** State information
*/

#define STATE ((struct R3000_STATE*)(state))

#define C0_status (12)
#define C0_cause  (13)
#define C0_epc    (14)
#define C0_prid   (15)

struct R3000_STATE {
  uint32 regs[32];

  uint32 c0_status;
  uint32 c0_cause;
  uint32 c0_epc;
  uint32 c0_prid;

  uint32 pc;
  uint32 hi,lo;

  uint32 intpending;

  sint32 slot;
  uint32 slot_target;

  sint32 cycles_remaining;
  sint32 cycles_remaining_last_checkpoint;
  sint32 cycles_deferred_from_break;

  /* usage statistics */
  sint32 usage_total_cycles;
  sint32 usage_idle_cycles;
  sint32 usage_cycles_max;
  uint32 usage_last_fraction;

  uint32 cache_ctrl;

  uint32 cache_isolate;

  //
  // These are REGISTERED EXTERNAL POINTERS.
  //
  r3000_advance_callback_t advance;
  void *hwstate;
  struct R3000_MEMORY_MAP *map_load;
  struct R3000_MEMORY_MAP *map_store;

  //
  // The following are TEMPORARY.
  // There are no location invariance issues.
  //
  uint32 maxpc;
  void *fetchbase;
  uint32 fetchbox;
};

uint32 EMU_CALL r3000_get_state_size(void) {
  return sizeof(struct R3000_STATE);
}

void EMU_CALL r3000_clear_state(void *state) {
  memset(state, 0, sizeof(struct R3000_STATE));
  STATE->c0_prid = 0x02;
  STATE->pc = 0xBFC00000;
  /* update statistics every 20 million cycles (may overshoot a little) */
  STATE->usage_cycles_max = 20000000;
}

/////////////////////////////////////////////////////////////////////////////
/*
** Registration of external pointers within the state
*/
void EMU_CALL r3000_set_memory_maps(
  void *state,
  struct R3000_MEMORY_MAP *map_load,
  struct R3000_MEMORY_MAP *map_store
) {
  STATE->map_load  = map_load;
  STATE->map_store = map_store;
}

void EMU_CALL r3000_set_advance_callback(
  void *state,
  r3000_advance_callback_t advance,
  void *hwstate
) {
  STATE->advance = advance;
  STATE->hwstate = hwstate;
}

/////////////////////////////////////////////////////////////////////////////
/*
** Set the processor revision ID
*/
void EMU_CALL r3000_set_prid(void *state, uint32 prid) {
  STATE->c0_prid = prid;
}

/////////////////////////////////////////////////////////////////////////////

static EMU_INLINE uint32 EMU_CALL getc0(struct R3000_STATE *state, uint32 regnum) {
  switch(regnum) {
  case 12: return state->c0_status;
  case 13: return state->c0_cause;
  case 14: return state->c0_epc;
  case 15: return state->c0_prid;
  }
  return 0;
}

static EMU_INLINE void EMU_CALL setc0(struct R3000_STATE *state, uint32 regnum, uint32 value) {
  switch(regnum) {
  case 12: state->c0_status = value; break;
  case 13: state->c0_cause = value; break;
  case 14: state->c0_epc = value; break;
  case 15: break;
  }
}

/////////////////////////////////////////////////////////////////////////////

uint32 EMU_CALL r3000_getreg(void *state, sint32 regnum) {
  if(regnum >= R3000_REG_GEN && regnum < (R3000_REG_GEN+32)) { return STATE->regs[regnum - R3000_REG_GEN]; }
  if(regnum >= R3000_REG_C0 && regnum < (R3000_REG_C0+32)) { return getc0(STATE, regnum - R3000_REG_C0); }
  switch(regnum) {
  case R3000_REG_PC: return STATE->pc;
  case R3000_REG_HI: return STATE->hi;
  case R3000_REG_LO: return STATE->lo;

  case R3000_REG_CI: return STATE->cache_isolate;

  }
  return 0;
}

void EMU_CALL r3000_setreg(void *state, sint32 regnum, uint32 value) {
  if(regnum >= R3000_REG_GEN && regnum < (R3000_REG_GEN+32)) { STATE->regs[regnum - R3000_REG_GEN] = value; return; }
  if(regnum >= R3000_REG_C0 && regnum < (R3000_REG_C0+32)) { setc0(STATE, regnum - R3000_REG_C0, value); return; }
  switch(regnum) {
  case R3000_REG_PC: STATE->pc = value; return;
  case R3000_REG_HI: STATE->hi = value; return;
  case R3000_REG_LO: STATE->lo = value; return;

  case R3000_REG_CI: STATE->cache_isolate = value; return;

  }
}

/////////////////////////////////////////////////////////////////////////////

void EMU_CALL r3000_break(void *state) {
  if(STATE->cycles_remaining <= 0) return;
  STATE->cycles_deferred_from_break += STATE->cycles_remaining;
  STATE->cycles_remaining_last_checkpoint -= STATE->cycles_remaining;
  STATE->cycles_remaining                  = 0;
}

void EMU_CALL r3000_setinterrupt(void *state, uint32 intpending) {
  STATE->intpending = intpending;
  r3000_break(state);
}

/////////////////////////////////////////////////////////////////////////////
/*
** Memory map walker
*/
static EMU_INLINE struct R3000_MEMORY_TYPE* EMU_CALL mmwalk(struct R3000_MEMORY_MAP *map, uint32 a) {
  a &= 0x1FFFFFFF;
  for(;; map++) {
    uint32 x = map->x;
    uint32 y = map->y;
    if(a < x || a > y) continue;
    return &(map->type);
  }
}

/////////////////////////////////////////////////////////////////////////////

static EMU_INLINE void EMU_CALL exception(struct R3000_STATE *state, uint32 cause, uint32 IP) {
  /* Step out of the delay slot, if we were in one */
  if(state->slot) {
//    state->pc = state->slot_target & (~3);
    state->pc -= 4;
    state->slot = 0;
  }

  /* Shift mode/interrupt bits */
  state->c0_status =
    (state->c0_status & 0xFFFFFFC0) |
    ((state->c0_status & 0x0F) << 2);
  state->c0_epc = state->pc;
  state->c0_cause = ((cause & 0xF) << 2) | ((IP & 0x3F) << 10);
  /* ignore BEV for now */
  state->pc = 0x80000080;

  /* Force a recheck of the memory map */
  state->maxpc = 0;

//printf("exception %08X %08X\n",cause,IP);
}

/////////////////////////////////////////////////////////////////////////////

static EMU_INLINE void EMU_CALL update_usage(void *state) {
  /*
  ** Update usage statistics
  */
  if(STATE->usage_total_cycles >= STATE->usage_cycles_max) {
    sint64 fraction;
    /*
    ** Bounds checking
    */
    if(STATE->usage_total_cycles <= 0) { STATE->usage_total_cycles = 1; }

    if(STATE->usage_idle_cycles > STATE->usage_total_cycles) {
      STATE->usage_idle_cycles = STATE->usage_total_cycles;
    } else if(STATE->usage_idle_cycles < 0) {
      STATE->usage_idle_cycles = 0;
    }

    fraction = STATE->usage_total_cycles - STATE->usage_idle_cycles;
    fraction *= ((sint64)(0x20000));
    fraction /= ((sint64)(STATE->usage_total_cycles));
    fraction++;
    fraction /= 2;
    if(fraction < 0x00000) fraction = 0x00000;
    if(fraction > 0x10000) fraction = 0x10000;
    STATE->usage_last_fraction = (uint32)fraction;
    STATE->usage_total_cycles = 0;
    STATE->usage_idle_cycles = 0;
  }
}

/////////////////////////////////////////////////////////////////////////////

#define INS_S ((uint32)((instruction>>21)&0x1F))
#define INS_T ((uint32)((instruction>>16)&0x1F))
#define INS_D ((uint32)((instruction>>11)&0x1F))
#define INS_H ((uint32)((instruction>>6)&0x1F))
#define INS_I ((uint32)(instruction&0xFFFF))

#define   SIGNED16(x) ((sint32)(((sint16)(x))))
#define UNSIGNED16(x) ((uint32)(((uint16)(x))))

#define REL_I (PC+4+(((sint32)(SIGNED16(INS_I)))<<2))
#define ABS_I ((PC&0xF0000000)|((instruction<<2)&0x0FFFFFFC))

#define J_REL_I {STATE->slot=2;STATE->slot_target=REL_I;}
#define J_ABS_I {STATE->slot=2;STATE->slot_target=ABS_I;}

#define REGS (STATE->regs)
#define HI   (STATE->hi)
#define LO   (STATE->lo)
#define PC   (STATE->pc)

static EMU_INLINE void EMU_CALL cold_interrupt_check(struct R3000_STATE *state) {
  uint32 hotint = ((STATE->intpending) & (STATE->c0_status >> 8)) & 0x3F;
  if(!(STATE->c0_status & 1)) hotint = 0;
  if(hotint) { exception(STATE, 0, hotint); state->cycles_remaining -= DIVIDER; }
}

static EMU_INLINE void EMU_CALL hw_sync(struct R3000_STATE *state) {
  sint32 d = state->cycles_remaining_last_checkpoint - state->cycles_remaining;
  if(d > 0) {
    state->advance(state->hwstate, d);
    state->cycles_remaining_last_checkpoint = state->cycles_remaining;
  }
}

static EMU_INLINE void EMU_CALL renew_fetch_region(struct R3000_STATE *state) {
  struct R3000_MEMORY_TYPE *t = mmwalk(state->map_load, state->pc);
  if(t->n == R3000_MAP_TYPE_POINTER) {
    uint32 astart = (state->pc) & (~(t->mask));
    state->maxpc = astart + ((t->mask) + 1);
    state->fetchbase = ((uint8*)(t->p)) - astart;
  } else {
    state->maxpc = state->pc + 4;
    state->fetchbase = ((uint8*)(&(state->fetchbox)))-(state->pc);
    state->fetchbox = ((r3000_load_callback_t)(t->p))(state->hwstate, state->pc, 0xFFFFFFFF);
  }
}

static EMU_INLINE uint32 EMU_CALL lb(struct R3000_STATE *state, uint32 a) {
  struct R3000_MEMORY_TYPE *t = mmwalk(state->map_load, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a ^= EMU_ENDIAN_XOR(3);
    return *((uint8*)(((uint8*)(t->p))+a));
  } else {
    uint32 sh = (a & 3) * 8;
    hw_sync(state);
    a = ((r3000_load_callback_t)(t->p))(state->hwstate, a & (~3), 0xFF << sh);
    a >>= sh;
    return a & 0xFF;
  }
}

static EMU_INLINE uint32 EMU_CALL lh(struct R3000_STATE *state, uint32 a) {
  struct R3000_MEMORY_TYPE *t = mmwalk(state->map_load, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a ^= EMU_ENDIAN_XOR(2);
    a &= (~1);
    return *((uint16*)(((uint8*)(t->p))+a));
  } else {
    uint32 sh = (a & 2) * 8;
    hw_sync(state);
    a = ((r3000_load_callback_t)(t->p))(state->hwstate, a & (~3), 0xFFFF << sh);
    a >>= sh;
    return a & 0xFFFF;
  }
}

static EMU_INLINE uint32 EMU_CALL lw(struct R3000_STATE *state, uint32 a) {
  struct R3000_MEMORY_TYPE *t;
  /*
  ** HACK HACK HACK TODO fix later
  */
  if(a == 0xFFFE0130) {
    return state->cache_ctrl;
  }
  t = mmwalk(state->map_load, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a &= (~3);
    return *((uint32*)(((uint8*)(t->p))+a));
  } else {
    hw_sync(state);
    a = ((r3000_load_callback_t)(t->p))(state->hwstate, a & (~3), 0xFFFFFFFF);
    return a;
  }
}

//
// Performs no callbacks; suitable for prediction
//
static EMU_INLINE uint32 EMU_CALL lw_noeffects(struct R3000_STATE *state, uint32 a) {
  struct R3000_MEMORY_TYPE *t;
  t = mmwalk(state->map_load, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a &= (~3);
    return *((uint32*)(((uint8*)(t->p))+a));
  } else {
    return 0;
  }
}

static EMU_INLINE void EMU_CALL sb(struct R3000_STATE *state, uint32 a, uint32 d) {
  struct R3000_MEMORY_TYPE *t = mmwalk(state->map_store, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a ^= EMU_ENDIAN_XOR(3);
    *((uint8*)(((uint8*)(t->p))+a)) = d;
  } else {
    uint32 sh = (a & 3) * 8;
    hw_sync(state);
    d &= 0xFF;
    ((r3000_store_callback_t)(t->p))(state->hwstate, a & (~3), d << sh, 0xFF << sh);
  }
}

static EMU_INLINE void EMU_CALL sh(struct R3000_STATE *state, uint32 a, uint32 d) {
  struct R3000_MEMORY_TYPE *t = mmwalk(state->map_store, a);
  a &= t->mask;
  if(t->n == R3000_MAP_TYPE_POINTER) {
    a ^= EMU_ENDIAN_XOR(2);
    a &= (~1);
    *((uint16*)(((uint8*)(t->p))+a)) = d;
  } else {
    uint32 sh = (a & 2) * 8;
    hw_sync(state);
    d &= 0xFFFF;
    ((r3000_store_callback_t)(t->p))(state->hwstate, a & (~3), d << sh, 0xFFFF << sh);
  }
}

static EMU_INLINE void EMU_CALL sw(struct R3000_STATE *state, uint32 a, uint32 d) {
  struct R3000_MEMORY_TYPE *t;
  /*
  ** HACK HACK HACK TODO fix later
  */
  if(a == 0xFFFE0130) {
    state->cache_ctrl = d;
    if(d >= 0x10000) {
      state->cache_isolate = 0;
    } else {
      state->cache_isolate = 1;
    }
    return;
  }
  if(state->cache_isolate) return;

  t = mmwalk(state->map_store, a);
  a &= t->mask;
  a &= (~3);
  if(t->n == R3000_MAP_TYPE_POINTER) {
    *((uint32*)(((uint8*)(t->p))+a)) = d;
  } else {
    hw_sync(state);
    ((r3000_store_callback_t)(t->p))(state->hwstate, a & (~3), d, 0xFFFFFFFF);
  }
}

static EMU_INLINE uint32 EMU_CALL fetch(struct R3000_STATE *state) {
  if(state->pc >= state->maxpc) renew_fetch_region(state);
  return *((uint32*)(((uint8*)(state->fetchbase))+(state->pc)));
}

#define caseMINOR(a) case(a):
#define caseMAJOR(a) case(a):

//
// Returns 0 or positive on success
// Returns negative on error
// (value is otherwise meaningless for now)
//
sint32 EMU_CALL r3000_execute(void *state, sint32 cycles) {
  uint32 a, d;
  uint32 instruction;

  STATE->cycles_remaining_last_checkpoint = cycles;
  STATE->cycles_remaining = cycles;
  STATE->cycles_deferred_from_break = 0;

  STATE->usage_total_cycles += cycles;

  PC &= (~3);

  cold_interrupt_check(STATE);

  //
  // This guarantees location invariance on fetchbox/fetchbase
  //
  STATE->maxpc = 0;

  while(STATE->slot || STATE->cycles_remaining > 0) {
    instruction = fetch(state);
    if(instruction < 0x04000000) {
      switch(instruction & 0x3F) {
      /* MINOR */
      caseMINOR(0x00) /* sll   */ if(INS_D) { REGS[INS_D] = ((uint32)(REGS[INS_T])) << INS_H;
        } else {
          /* idle detect */
          /* if this is a nop in a branch delay slot, and the target is the
          ** previous instruction, then we are officially idle */
          if((STATE->slot) && (STATE->slot_target == (PC - 4))) {
            /* steal off cycles under cycidle */
            sint32 cycidle = STATE->cycles_remaining;
            if(cycidle < 0) { cycidle = 0; }
            STATE->cycles_remaining -= cycidle;
            STATE->usage_idle_cycles += cycidle;

            STATE->slot = 0;
            PC -= 4;
            STATE->maxpc = 0;
            goto finishing_sync;
          }
        }
        break;
      caseMINOR(0x02) /* srl   */ if(INS_D) { REGS[INS_D] = ((uint32)(REGS[INS_T])) >> INS_H; } break;
      caseMINOR(0x03) /* sra   */ if(INS_D) { REGS[INS_D] = ((sint32)(REGS[INS_T])) >> INS_H; } break;
      caseMINOR(0x04) /* sllv  */ if(INS_D) { uint32 sc=REGS[INS_S]; if(sc>=32){REGS[INS_D]=0;}else{REGS[INS_D]=REGS[INS_T]<<sc;} } break;
      caseMINOR(0x06) /* srlv  */ if(INS_D) { uint32 sc=REGS[INS_S]; if(sc>=32){REGS[INS_D]=0;}else{REGS[INS_D]=REGS[INS_T]>>sc;} } break;
      caseMINOR(0x07) /* srav  */ if(INS_D) { uint32 sc=REGS[INS_S]; if(sc>=32){sc=31;}            {REGS[INS_D]=(((sint32)(REGS[INS_T]))>>sc);} } break;
      caseMINOR(0x08) /* jr    */ if(!STATE->slot) {                      {STATE->slot=2;STATE->slot_target=REGS[INS_S];} } break;
      caseMINOR(0x09) /* jalr  */ if(!STATE->slot) { REGS[INS_D] = PC + 8;{STATE->slot=2;STATE->slot_target=REGS[INS_S];} } break;
      caseMINOR(0x0C) /*syscall*/ if(!STATE->slot) { exception(STATE, 8, 0); PC -= 4; } break;
      caseMINOR(0x10) /* mfhi  */ if(INS_D) { REGS[INS_D] = HI; } break;
      caseMINOR(0x11) /* mthi  */           { HI = REGS[INS_S]; } break;
      caseMINOR(0x12) /* mflo  */ if(INS_D) { REGS[INS_D] = LO; } break;
      caseMINOR(0x13) /* mtlo  */           { LO = REGS[INS_S]; } break;
      caseMINOR(0x18) /* mult  */ { sint64 t = ((sint64)(((sint32)(REGS[INS_S])))) * ((sint64)(((sint32)(REGS[INS_T])))); LO = (uint32)(t); HI = (uint32)(t >> 32); } break;
      caseMINOR(0x19) /* multu */ { uint64 t = ((uint64)(((uint32)(REGS[INS_S])))) * ((uint64)(((uint32)(REGS[INS_T])))); LO = (uint32)(t); HI = (uint32)(t >> 32); } break;
      caseMINOR(0x1A) /* div   */ if(REGS[INS_T]) { LO = ((sint32)(REGS[INS_S])) / ((sint32)(REGS[INS_T])); HI = ((sint32)(REGS[INS_S])) % ((sint32)(REGS[INS_T])); } break;
      caseMINOR(0x1B) /* divu  */ if(REGS[INS_T]) { LO = ((uint32)(REGS[INS_S])) / ((uint32)(REGS[INS_T])); HI = ((uint32)(REGS[INS_S])) % ((uint32)(REGS[INS_T])); } break;
      caseMINOR(0x20) /* add   */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) + ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x21) /* addu  */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) + ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x22) /* sub   */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) - ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x23) /* subu  */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) - ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x24) /* and   */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) & ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x25) /* or    */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) | ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x26) /* xor   */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) ^ ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x27) /* nor   */ if(INS_D) { REGS[INS_D] = ~(((uint32)(REGS[INS_S])) | ((uint32)(REGS[INS_T]))); } break;
      caseMINOR(0x2A) /* slt   */ if(INS_D) { REGS[INS_D] =  (((sint32)(REGS[INS_S])) < ((sint32)(REGS[INS_T]))); } break;
      caseMINOR(0x2B) /* sltu  */ if(INS_D) { REGS[INS_D] =  (((uint32)(REGS[INS_S])) < ((uint32)(REGS[INS_T]))); } break;
      default: goto badins;
      }
    } else {
      switch(instruction >> 26) {
      /* MAJOR */
      caseMAJOR(0x01)
        switch(INS_T) {
        case 0x00: /* bltz  */ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) <  0) {                J_REL_I; } } break;
        case 0x01: /* bgez  */ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) >= 0) {                J_REL_I; } } break;
        case 0x10: /* bltzal*/ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) <  0) { REGS[31]=PC+8; J_REL_I; } } break;
        case 0x11: /* bgezal*/ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) >= 0) { REGS[31]=PC+8; J_REL_I; } } break;
        default: goto badins;
        }
        break;
      caseMAJOR(0x02) /* j     */ if(!STATE->slot) {                J_ABS_I; } break;
      caseMAJOR(0x03) /* jal   */ if(!STATE->slot) { REGS[31]=PC+8; J_ABS_I; } break;
      caseMAJOR(0x04) /* beq   */ if(!STATE->slot) { if(REGS[INS_S] == REGS[INS_T]) { J_REL_I; } } break;
      caseMAJOR(0x05) /* bne   */ if(!STATE->slot) { if(REGS[INS_S] != REGS[INS_T]) { J_REL_I; } } break;
      caseMAJOR(0x06) /* blez  */ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) <= 0) { J_REL_I; } } break;
      caseMAJOR(0x07) /* bgtz  */ if(!STATE->slot) { if( ((sint32)(REGS[INS_S])) >  0) { J_REL_I; } } break;
      caseMAJOR(0x08) /* addi  */ if(INS_T) { REGS[INS_T] = REGS[INS_S] +   SIGNED16(INS_I); } break;
      caseMAJOR(0x09) /* addiu */ if(INS_T) { REGS[INS_T] = REGS[INS_S] +   SIGNED16(INS_I); } break;
      caseMAJOR(0x0A) /* slti  */ if(INS_T) { REGS[INS_T] = ( ((sint32)(REGS[INS_S])) < ((sint32)( ((sint32)( SIGNED16(INS_I) )) )) ); } break;
      caseMAJOR(0x0B) /* sltiu */ if(INS_T) { REGS[INS_T] = ( ((uint32)(REGS[INS_S])) < ((uint32)( ((sint32)( SIGNED16(INS_I) )) )) ); } break;
      caseMAJOR(0x0C) /* andi  */ if(INS_T) { REGS[INS_T] = REGS[INS_S] & UNSIGNED16(INS_I); } break;
      caseMAJOR(0x0D) /* ori   */ if(INS_T) { REGS[INS_T] = REGS[INS_S] | UNSIGNED16(INS_I); } break;
      caseMAJOR(0x0E) /* xori  */ if(INS_T) { REGS[INS_T] = REGS[INS_S] ^ UNSIGNED16(INS_I); } break;
      caseMAJOR(0x0F) /* lui   */ if(INS_T) { REGS[INS_T] = INS_I << 16; } break;
      caseMAJOR(0x10) /* cop0  */
        switch(INS_S) {
        case 0x00: /* mfc0  */ if(INS_T) { REGS[INS_T] = getc0(STATE, INS_D); } break;
        case 0x04: /* mtc0  */           { setc0(STATE, INS_D, REGS[INS_T]); r3000_break(state); } break;
        case 0x10: /* rfe   */ {
          STATE->c0_status =
            (STATE->c0_status & 0xFFFFFFC0) |
            ((STATE->c0_status & 0x3C) >> 2) |
            ((STATE->c0_status & 0x03) << 4);
          r3000_break(state); } break;
        default: goto badins;
        }
        break;
      caseMAJOR(0x20) /* lb    */ { a = REGS[INS_S] + SIGNED16(INS_I); d = lb(state, a); if(INS_T) { REGS[INS_T] = ((sint32)((sint8)(d)));  } } break;
      caseMAJOR(0x21) /* lh    */ { a = REGS[INS_S] + SIGNED16(INS_I); d = lh(state, a); if(INS_T) { REGS[INS_T] = ((sint32)((sint16)(d))); } } break;
      caseMAJOR(0x23) /* lw    */ { a = REGS[INS_S] + SIGNED16(INS_I); d = lw(state, a); if(INS_T) { REGS[INS_T] = d;                       } } break;
      caseMAJOR(0x24) /* lbu   */ { a = REGS[INS_S] + SIGNED16(INS_I); d = lb(state, a); if(INS_T) { REGS[INS_T] = d & 0x000000FF;          } } break;
      caseMAJOR(0x25) /* lhu   */ { a = REGS[INS_S] + SIGNED16(INS_I); d = lh(state, a); if(INS_T) { REGS[INS_T] = d & 0x0000FFFF;          } } break;
      caseMAJOR(0x28) /* sb    */ { a = REGS[INS_S] + SIGNED16(INS_I);     sb(state, a, REGS[INS_T] & 0x000000FF); } break;
      caseMAJOR(0x29) /* sh    */ { a = REGS[INS_S] + SIGNED16(INS_I);     sh(state, a, REGS[INS_T] & 0x0000FFFF); } break;
      caseMAJOR(0x2B) /* sw    */ { a = REGS[INS_S] + SIGNED16(INS_I);     sw(state, a, REGS[INS_T]             ); } break;

      caseMAJOR(0x22) /* lwl   */
        a = REGS[INS_S] + SIGNED16(INS_I);
        { int bitshift;
          for(bitshift = 24;; bitshift -= 8) {
            REGS[INS_T] &= ~(0xFF << bitshift);
            REGS[INS_T] |= (((uint32)(lb(state, a))) & 0xFF) << bitshift;
            if(!(a&3))break;
            a--;
          }
        }
        REGS[0] = 0;
        break;
      caseMAJOR(0x26) /* lwr   */
        a = REGS[INS_S] + SIGNED16(INS_I);
        { int bitshift;
          for(bitshift = 0;; bitshift += 8) {
            REGS[INS_T] &= ~(0xFF << bitshift);
            REGS[INS_T] |= (((uint32)(lb(state, a))) & 0xFF) << bitshift;
            if((a&3) == 3)break;
            a++;
          }
        }
        REGS[0] = 0;
        break;
      caseMAJOR(0x2A) /* swl   */
        a = REGS[INS_S] + SIGNED16(INS_I);
        { int bitshift;
          for(bitshift = 24;; bitshift -= 8) {
            sb(state, a, REGS[INS_T]>>bitshift);
            if(!(a&3))break;
            a--;
          }
        }
        break;
      caseMAJOR(0x2E) /* swr   */
        a = REGS[INS_S] + SIGNED16(INS_I);
        { int bitshift;
          for(bitshift = 0;; bitshift += 8) {
            sb(state, a, REGS[INS_T]>>bitshift);
            if((a&3) == 3)break;
            a++;
          }
        }
        break;

      default: goto badins;
      }
    }

    PC += 4;

    STATE->cycles_remaining -= DIVIDER;
    if(STATE->slot) {
      STATE->slot--;
      if(!(STATE->slot)) {
        PC = STATE->slot_target & (~3);
        STATE->maxpc = 0;
      }
    }
  }

finishing_sync:

  STATE->cycles_remaining_last_checkpoint += STATE->cycles_deferred_from_break;
  STATE->cycles_remaining                 += STATE->cycles_deferred_from_break;
  STATE->cycles_deferred_from_break = 0;
  STATE->usage_total_cycles -= STATE->cycles_remaining;
  update_usage(state);

  hw_sync(state);
  return 0;

badins:

  STATE->cycles_remaining_last_checkpoint += STATE->cycles_deferred_from_break;
  STATE->cycles_remaining                 += STATE->cycles_deferred_from_break;
  STATE->cycles_deferred_from_break = 0;
  STATE->usage_total_cycles -= STATE->cycles_remaining;
  update_usage(state);

  hw_sync(state);
  return -1;

}

/////////////////////////////////////////////////////////////////////////////
//
// Get usage statistics
//
uint32 EMU_CALL r3000_get_usage_fraction(void *state) {
  return STATE->usage_last_fraction;
}

/////////////////////////////////////////////////////////////////////////////
//
// Perform prediction of what next instruction will do
//
// first word: address of next instruction
// following words describe possible activity
// 0xxxxxxx yyyyyyyy means "read x bytes from address y"
// 1xxxxxxx yyyyyyyy means "write x bytes from address y"
// 20000000 yyyyyyyy means "jump to y"
// 30000000 yyyyyyyy means "call to y"
// 00000000 terminates the list
// max 256 bytes in prediction profile
//
#define PROFILE_READ  0x00000000
#define PROFILE_WRITE 0x10000000
#define PROFILE_JUMP  0x20000000
#define PROFILE_CALL  0x30000000

void EMU_CALL r3000_predict(void *state, uint32 *profile) {
  uint32 instruction = lw_noeffects(STATE, PC);
  *profile++ = PC + 4;
  if(instruction < 0x04000000) {
    switch(instruction & 0x3F) {
    caseMINOR(0x08) /* jr    */ *profile++ = PROFILE_JUMP; *profile++ = REGS[INS_S]; break;
    caseMINOR(0x09) /* jalr  */ *profile++ = PROFILE_CALL; *profile++ = REGS[INS_S]; break;
    }
  } else {
    switch(instruction >> 26) {
    caseMAJOR(0x01)
      switch(INS_T) {
      case 0x00: /* bltz  */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;
      case 0x01: /* bgez  */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;
      case 0x10: /* bltzal*/ *profile++ = PROFILE_CALL; *profile++ = REL_I; break;
      case 0x11: /* bgezal*/ *profile++ = PROFILE_CALL; *profile++ = REL_I; break;
      }
      break;
    caseMAJOR(0x02) /* j     */ *profile++ = PROFILE_JUMP; *profile++ = ABS_I; break;
    caseMAJOR(0x03) /* jal   */ *profile++ = PROFILE_CALL; *profile++ = ABS_I; break;
    caseMAJOR(0x04) /* beq   */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;
    caseMAJOR(0x05) /* bne   */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;
    caseMAJOR(0x06) /* blez  */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;
    caseMAJOR(0x07) /* bgtz  */ *profile++ = PROFILE_JUMP; *profile++ = REL_I; break;

    caseMAJOR(0x20) /* lb    */ *profile++ = PROFILE_READ +1; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x21) /* lh    */ *profile++ = PROFILE_READ +2; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x23) /* lw    */ *profile++ = PROFILE_READ +4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x24) /* lbu   */ *profile++ = PROFILE_READ +1; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x25) /* lhu   */ *profile++ = PROFILE_READ +2; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x28) /* sb    */ *profile++ = PROFILE_WRITE+1; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x29) /* sh    */ *profile++ = PROFILE_WRITE+2; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x2B) /* sw    */ *profile++ = PROFILE_WRITE+4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;

    // these could be done better, but this is good enough for now
    caseMAJOR(0x22) /* lwl   */ *profile++ = PROFILE_READ +4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x26) /* lwr   */ *profile++ = PROFILE_READ +4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x2A) /* swl   */ *profile++ = PROFILE_WRITE+4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    caseMAJOR(0x2E) /* swr   */ *profile++ = PROFILE_WRITE+4; *profile++ = REGS[INS_S] + SIGNED16(INS_I); break;
    }
  }
  *profile = 0;
}

/////////////////////////////////////////////////////////////////////////////
