/*
Copyright (c) 2013, Alexey Frunze
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/

/*****************************************************************************/
/*                                                                           */
/*                               MIPS icache                                 */
/*                                                                           */
/*****************************************************************************/

// the For best performance, this file should be compiled using gcc's -O3 option.

// Define this macro to enable statistics
//#define STATS

// Define this macro to do a more rigorous check
// for invalid/unsupported instructions
// (at the expense of performance, of course).
//#define CHECK_INVALID_INSTR

#define STATIC static

// Rename non-static functions and variables
// to prevent name collisions with other code
// when compiling together with it.
#define DoSysCall       _icDoSysCall_
#define main            _icmain_
#define Regs            _icRegs_
#define HostRegs        _icHostRegs_

typedef unsigned char uchar, uint8;
typedef signed char schar, int8;
typedef unsigned short ushort, uint16;
typedef short int16;
typedef unsigned uint, uint32, size_t;
typedef int int32, ssize_t, off_t;
typedef unsigned long ulong;
typedef long long longlong, int64;
typedef unsigned long long ulonglong, uint64;

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]

//C_ASSERT(CHAR_BIT == 8);
C_ASSERT(sizeof(int) == 4);
C_ASSERT(sizeof(long) == 4);
C_ASSERT(sizeof(longlong) == 8);
C_ASSERT(sizeof(void*) == 4);
C_ASSERT(sizeof(void(*)()) == 4);
C_ASSERT(sizeof(size_t) == 4);
C_ASSERT(sizeof(ssize_t) == 4);
C_ASSERT(sizeof(off_t) == 4);
C_ASSERT(sizeof(uint16) == 2);
C_ASSERT(sizeof(uint32) == 4);
C_ASSERT(sizeof(uint64) == 8);


#pragma pack(push,1)

typedef struct
{
  uint32 a_magic;   /* magic number */
#define OMAGIC 0407 /* old impure format */

  uint32 a_text;    /* size of text segment */
  uint32 a_data;    /* size of initialized data */
  uint32 a_bss;     /* size of uninitialized data */
  uint32 a_reltext; /* size of text relocation info */
  uint32 a_reldata; /* size of data relocation info */
  uint32 a_syms;    /* size of symbol table */
  uint32 a_entry;   /* entry point */
} AoutHdr;

#pragma pack(pop)

C_ASSERT(sizeof(AoutHdr) == 32);


static inline
void* memset(void* dst, int ch, size_t size)
{
  unsigned char *p = dst;
  while (size--)
    *p++ = ch;
  return dst;
}

// flags for RetroBSD's open():
#define O_RDONLY 0x0000
#define O_WRONLY 0x0001
#define O_RDWR   0x0002
#define O_APPEND 0x0008
#define O_CREAT  0x0200
#define O_TRUNC  0x0400
#define O_TEXT   0x0000
#define O_BINARY 0x0000

// flags for RetroBSD's lseek():
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2

static
void exit(int code)
{
  asm volatile ("move $4, %0\n"
                "syscall 1" // SYS_exit
                :
                : "r" (code)
                : "$2", "$4");
}

static
int open(const char* name, int oflags)
{
  int handle;
  asm volatile ("move $4, %1\n"
                "move $5, %2\n"
                "syscall 5\n" // SYS_open
                "nop\n"
                "nop\n"
                "move %0, $2\n"
                : "=r" (handle)
                : "r" (name), "r" (oflags)
                : "$2", "$4", "$5");
  return handle;
}

static
ssize_t read(int handle, void* buf, size_t size)
{
  ssize_t sz;
  asm volatile ("move $4, %1\n"
                "move $5, %2\n"
                "move $6, %3\n"
                "syscall 3\n" // SYS_read
                "nop\n"
                "nop\n"
                "move %0, $2\n"
                : "=r" (sz)
                : "r" (handle), "r" (buf), "r" (size)
                : "$2", "$4", "$5", "$6", "memory");
  return sz;
}

static
ssize_t write(int handle, const void* buf, size_t size)
{
  ssize_t sz;
  asm volatile ("move $4, %1\n"
                "move $5, %2\n"
                "move $6, %3\n"
                "syscall 4\n" // SYS_write
                "nop\n"
                "nop\n"
                "move %0, $2\n"
                : "=r" (sz)
                : "r" (handle), "r" (buf), "r" (size)
                : "$2", "$4", "$5", "$6", "memory"); // WTF? I shouldn't need "memory" here !!!
  return sz;
}

static
off_t lseek(int handle, off_t pos, int whence)
{
  off_t p;
  asm volatile ("move $4, %1\n"
                "move $5, %2\n"
                "move $6, %3\n"
                "syscall 19\n" // SYS_lseek
                "nop\n"
                "nop\n"
                "move %0, $2\n"
                : "=r" (p)
                : "r" (handle), "r" (pos), "r" (whence)
                : "$2", "$4", "$5", "$6");
  return p;
}

#if 0
// close() is unused. We rely on the system to close
// all files/handles on process termination.
static
int close(int handle)
{
  int err;
  asm volatile ("move $4, %1\n"
                "syscall 6\n" // SYS_close
                "nop\n"
                "nop\n"
                "move %0, $2\n"
                : "=r" (err)
                : "r" (handle)
                : "$2", "$4");
  return err;
}
#endif

static
void printchr(int ch)
{
  char c = ch;
  write(1, &c, 1);
}

static
void printstr(const char* s)
{
  while (*s != '\0')
    write(1, s++, 1);
}

#ifdef STATS
static
void printdec(int n)
{
  unsigned un = n;
  if (n < 0)
  {
    un = -un;
    printchr('-');
  }
  if (un >= 10)
    printdec(un / 10);
  printchr('0' + un % 10);
}

static
char* Bin64ToDec(uint64 n)
{
  // log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322
  static char s[64 / 3 + 1 + 1];
  char* p = s;
  int i;

  memset(s, '0', sizeof s - 1);
  s[sizeof s - 1] = '\0';

  for (i = 0; i < 64; i++)
  {
    int j, carry;

    // Extract the most significant bit of n into carry
    carry = (n >> 63) & 1;
    // Shift n left
    n <<= 1;

    // Add s[] to itself in decimal, doubling it,
    // and add carry to it
    for (j = sizeof s - 2; j >= 0; j--)
    {
      s[j] += s[j] - '0' + carry;

      carry = s[j] > '9';

      if (carry)
        s[j] -= 10;
    }
  }

  while ((*p == '0') && (p < &s[sizeof s - 2]))
    p++;

  return p;
}

static
void printdec64(int64 n)
{
  uint64 un = n;
  if (n < 0)
  {
    un = -un;
    printchr('-');
  }
  printstr(Bin64ToDec(un));
}
#endif

static
void printhex(unsigned n)
{
  int i;
  for (i = 0; i < 8; i++)
    printchr("0123456789ABCDEF"[(n >> 28) & 15]), n <<= 4;
}

#ifdef STATS
uint64 EmulateCnt = 0;
uint64 CacheHits = 0;
uint64 CacheHits2 = 0;
uint64 CacheMisses = 0;
#endif


#define REG_GP 28
#define REG_SP 29
#define REG_RA 31
#define REG_LO 32
#define REG_HI 33
#define REG_PC 34
uint32 Regs[32 + 3];
uint32 HostRegs[32 + 3];


#define CACHE_BYTES_PER_INSTR  4
#ifndef CACHE_INSTRS_PER_ENTRY
#define CACHE_INSTRS_PER_ENTRY 8
#endif
#define CACHE_BYTES_PER_ENTRY  (CACHE_BYTES_PER_INSTR * CACHE_INSTRS_PER_ENTRY)

#ifndef CACHE_ENTRIES_PER_WAY
#define CACHE_ENTRIES_PER_WAY  64
#endif
#ifndef CACHE_WAYS
#define CACHE_WAYS             4
#endif
#define CACHE_ENTRIES_TOTAL    (CACHE_ENTRIES_PER_WAY * CACHE_WAYS)

#define CACHE_SIZE             (CACHE_BYTES_PER_ENTRY * CACHE_ENTRIES_TOTAL)

STATIC uint32 Cache[CACHE_ENTRIES_PER_WAY][CACHE_WAYS][CACHE_INSTRS_PER_ENTRY];
STATIC uint32 CacheTagAndValid[CACHE_ENTRIES_PER_WAY][CACHE_WAYS];

STATIC int CachedCnt = 0;
STATIC uint32* CachedInstr = &Cache[0][0][0];


extern void _icstart_(int argc, char** argv, char** env);
STATIC void Emulate(void);

STATIC int ExeHandle = -1;
STATIC uint32 ExeOffs = 0;

int main(int argc, char** argv, char** env)
{
  AoutHdr aoutHdr;

  if ((ExeHandle = open(argv[0], O_BINARY | O_RDONLY)) < 0)
  {
    printstr("Can't open "); printstr(argv[0]); printchr('\n');
    exit(-1);
  }

  if (read(ExeHandle, &aoutHdr, sizeof aoutHdr) != sizeof aoutHdr)
  {
    printstr("Can't read "); printstr(argv[0]); printchr('\n');
    exit(-1);
  }

  if (aoutHdr.a_magic != OMAGIC)
  {
    printstr(argv[0]); printstr(" is not an a.out file\n");
    exit(-1);
  }

  ExeOffs = sizeof aoutHdr + aoutHdr.a_text + aoutHdr.a_data;

  memset(CacheTagAndValid, 0xFF, sizeof CacheTagAndValid); // invalidate cache
  Regs[4] = argc;
  Regs[5] = (uint32)argv;
  Regs[6] = (uint32)env;
  // Regs[REG_SP] = ...; // _icstart() has done this
  // Regs[REG_GP] = (uint32)&_gp; // _start() will do this if needed
  Regs[REG_PC] = (uint32)&_icstart_;
  Emulate(); // isn't supposed to return...
  exit(-1); // ... but let's exit explicitly just in case
  return -1;
}

extern const char _text_begin;
extern const char _text_end;

STATIC inline
uint32 FetchProgramWord(uint32 Addr)
{
  uint32 ofs, idx, tag, way;

  if (CachedCnt > 0)
  {
    CachedCnt--;
    // Cache hit
#ifdef STATS
    CacheHits++;
    CacheHits2++;
#endif
    return *++CachedInstr;
  }

  if (Addr < (uint32)&_text_begin ||
      Addr >= (uint32)&_text_end)
  {
    CachedCnt = 0;
    CachedInstr = (uint32*)Addr;
    return *CachedInstr;
  }

  ofs = Addr % CACHE_BYTES_PER_ENTRY / CACHE_BYTES_PER_INSTR;
  idx = Addr / CACHE_BYTES_PER_ENTRY % CACHE_ENTRIES_PER_WAY;
  tag = Addr / CACHE_BYTES_PER_ENTRY / CACHE_ENTRIES_PER_WAY;
  for (way = 0; way < CACHE_WAYS; way++)
  {
    if (CacheTagAndValid[idx][way] == tag)
    {
      // Cache hit
#ifdef STATS
      CacheHits++;
#endif
      CachedCnt = CACHE_INSTRS_PER_ENTRY - 1 - ofs;
      CachedInstr = &Cache[idx][way][ofs];
      return *CachedInstr;
    }
  }

  // Cache miss
#ifdef STATS
  CacheMisses++;
#endif

  for (way = 0; way < CACHE_WAYS; way++)
  {
    if (CacheTagAndValid[idx][way] & 0x80000000)
    {
      // Use an invalid entry
      goto lreuse;
    }
  }

  // Reuse a valid entry (need to choose one for eviction and refill,
  // preferably not penalizing the same entry over and over again)
  {
    static uint32 w = CACHE_WAYS - 1;
    w = (w + 1) % CACHE_WAYS; // pseudo-LRU
    way = w;
  }

lreuse:

  if (lseek(ExeHandle,
            ExeOffs + (Addr / CACHE_BYTES_PER_ENTRY * CACHE_BYTES_PER_ENTRY - (uint32)&_text_begin),
            SEEK_SET) < 0 ||
      read(ExeHandle, Cache[idx][way], CACHE_BYTES_PER_ENTRY) != CACHE_BYTES_PER_ENTRY)
  {
    printstr("\nCan't read into the cache from the executable file\n");
    exit(-1);
  }

  CacheTagAndValid[idx][way] = tag;

  CachedCnt = CACHE_INSTRS_PER_ENTRY - 1 - ofs;
  CachedInstr = &Cache[idx][way][ofs];
  return *CachedInstr;
}

extern int DoSysCall(uint32 instr);

#ifdef STATS
STATIC
int DoSysCall2(uint32 instr)
{
  uint32 code = (instr >> 6) & 0xFFFFF;

  // intercept exit() syscall
  if ((code == 0 && Regs[2] == 10) || // SPIM's exit()
      (code == /*SYS_exit*/1)) // RetroBSD's exit()
  {
    uint32 idx, way, used = 0;
    for (idx = 0; idx < CACHE_ENTRIES_PER_WAY; idx++)
      for (way = 0; way < CACHE_WAYS; way++)
        used += CacheTagAndValid[idx][way] < 0x80000000;
    printstr("\n"); printdec64(EmulateCnt); printstr(" instruction(s) emulated\n");
    printdec((int)used); printchr('/'); printdec(CACHE_ENTRIES_TOTAL); printstr(" cache entries used\n");
    printdec64(CacheHits); printchr('(');
    printdec64(CacheHits2); printstr(")/");
    printdec64(CacheMisses); printstr(" cache hits(hits2)/misses\n");
  }
  return DoSysCall(instr);
}
#undef DoSysCall
#define DoSysCall DoSysCall2
#endif

static inline uint8 ReadByte(uint32 Addr)
{
  return *(uint8*)Addr;
}
static inline void WriteByte(uint32 Addr, uint8 Val)
{
  *(uint8*)Addr = Val;
}

static inline uint16 ReadHalfWord(uint32 Addr)
{
  return *(uint16*)Addr;
}
static inline void WriteHalfWord(uint32 Addr, uint16 Val)
{
  *(uint16*)Addr = Val;
}

static inline uint32 ReadWord(uint32 Addr)
{
  return *(uint32*)Addr;
}
static inline void WriteWord(uint32 Addr, uint32 Val)
{
  *(uint32*)Addr = Val;
}

/*
  Supported instructions:

    add, addi, addiu, addu, and, andi,
    bal, beq, beql, bgez, bgezal, bgezall,
    bgezl, bgtz, bgtzl, blez, blezl, bltz,
    bltzal, bltzall, bltzl, bne, bnel, break,
    clo, clz,
    div, divu,
    ext,
    ins,
    j, jal, jalr, jr,
    lb, lbu, lh, lhu, lui, lw, lwl, lwr,
    madd, maddu, mfhi, mflo, movn, movz, msub,
    msubu, mthi, mtlo, mul, mult, multu,
    nop, nor,
    or, ori,
    rotr, rotrv,
    sb, seb, seh, sh, sll, sllv, slt, slti, sltiu,
    sltu, sra, srav, srl, srlv, sub, subu, sw,
    swl, swlr, syscall,
    teq, teqi, tge, tgei, tgeiu, tgeu, tlt, tlti,
    tltiu, tltu, tne, tnei,
    wsbh,
    xor, xori

  Unsupported instructions:

    bc2f, bc2fl, bc2t, bc2tl,
    cache, cfc2, cop0, cop2, ctc2,
    deret, di,
    ehb, ei, eret,
    jalr.hb, jr.hb,
    ll, lwc2,
    mfc0, mfc2, mtc0, mtc2, mthc2,
    pref,
    rdhwr, rdpgpr,
    sc, sdbbp, ssnop, swc2, sync, synci,
    wait, wrpgpr
*/

STATIC void DoBreak(uint32 instr);
STATIC void DoTrap(uint32 instr);
STATIC void DoOverflow(void);
STATIC void DoInvalidInstruction(uint32 instr);

STATIC
uint32 CountLeadingZeroes(uint32 n)
{
#if 0
  uint32 c = 0;
  if (n == 0)
    return 32;
  while (n < 0x80000000)
    n <<= 1, c++;
#else
  uint32 c;
  asm volatile("clz %0, %1" : "=r" (c) : "r" (n));
#endif
  return c;
}

STATIC
uint32 CountLeadingOnes(uint32 n)
{
#if 0
  uint32 c = 0;
  while (n >= 0x80000000)
    n <<= 1, c++;
#else
  uint32 c;
  asm volatile("clo %0, %1" : "=r" (c) : "r" (n));
#endif
  return c;
}

STATIC
uint32 ShiftRightArithm(uint32 n, uint32 c)
{
#if 0
  uint32 s = -(n >> 31);
  n >>= c;
  n |= s << (31 - c) << 1;
  return n;
#else
  uint32 nn;
  asm volatile("srav %0, %1, %2" : "=r" (nn) : "r" (n), "r" (c));
  return nn;
#endif
}

STATIC
uint32 RotateRight(uint32 n, uint32 c)
{
#if 0
  return (n >> c) | (n << (31 - c) << 1);
#else
  uint32 nn;
  asm volatile("rotrv %0, %1, %2" : "=r" (nn) : "r" (n), "r" (c));
  return nn;
#endif
}

STATIC
void Emulate(void)
{
  int delaySlot = 0;
  uint32 postDelaySlotPc = 0;
  uint32 instr = 0;

  for (;;)
  {
    const uint32 pc = Regs[REG_PC];
    uint32 nextPc = pc + 4;
    /*const uint32*/ instr = FetchProgramWord(pc);
#if 0
    const uint32 op = instr >> 26;
    const uint32 r1 = (instr >> 21) & 0x1F;
    const uint32 r2 = (instr >> 16) & 0x1F;
    const uint32 r3 = (instr >> 11) & 0x1F;
    const uint32 shft = (instr >> 6) & 0x1F;
    const uint32 fxn = instr & 0x3F;
    const uint32 imm16 = instr & 0xFFFF;
    const uint32 simm16 = (int16)imm16;
    const uint32 jtgt = instr & 0x3FFFFFF;
#else
#define op     (instr >> 26)
#define r1     ((instr >> 21) & 0x1F)
#define r2     ((instr >> 16) & 0x1F)
#define r3     ((instr >> 11) & 0x1F)
#define shft   ((instr >> 6) & 0x1F)
#define fxn    (instr & 0x3F)
#define imm16  (instr & 0xFFFF)
#define simm16 ((int16)imm16)
#define jtgt   (instr & 0x3FFFFFF)
#endif

    switch (op)
    {
    case 0:
      switch (fxn)
      {
      case 0:
#ifdef CHECK_INVALID_INSTR
        if (r1)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r2] << shft;
        break; // sll d,w,shft
      case 2:
        switch (r1)
        {
        case 0: Regs[r3] = Regs[r2] >> shft; break; // srl d,w,shft
        case 1: Regs[r3] = RotateRight(Regs[r2], shft); break; // rotr d,w,shft
        default: goto lInvalidInstruction;
        }
        break;
      case 3:
#ifdef CHECK_INVALID_INSTR
        if (r1)
          goto lInvalidInstruction;
#endif
        Regs[r3] = ShiftRightArithm(Regs[r2], shft);
        break; // sra d,w,shft
      case 4:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r2] << (Regs[r1] & 31);
        break; // sllv d,w,s
      case 6:
        switch (shft)
        {
        case 0: Regs[r3] = Regs[r2] >> (Regs[r1] & 31); break; // srlv d,w,s
        case 1: Regs[r3] = RotateRight(Regs[r2], Regs[r1] & 31); break; // rotrv d,w,s
        default: goto lInvalidInstruction;
        }
        break;
      case 7:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = ShiftRightArithm(Regs[r2], Regs[r1] & 31);
        break; // srav d,w,s
      case 8:
#ifdef CHECK_INVALID_INSTR
        if (r2 | r3 | shft)
          goto lInvalidInstruction;
#endif
        nextPc = Regs[r1]; delaySlot = 1;
        break; // jr s
      case 9:
#ifdef CHECK_INVALID_INSTR
        if (r2 | shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = nextPc + 4; nextPc = Regs[r1]; delaySlot = 1;
        break; // jalr [d,] s
      case 10:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        if (Regs[r2] == 0) Regs[r3] = Regs[r1];
        break; // movz d,s,t
      case 11:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        if (Regs[r2]) Regs[r3] = Regs[r1];
        break; // movn d,s,t
      case 12:
        {
          // RetroBSD may advance PC on returning from a syscall handler,
          // skipping 2 instructions that follow the syscall instruction.
          // Those 2 instructions typically set C's errno variable and
          // are either executed on error or skipped on success.
          // Account for this peculiarity.
          uint32 skip = DoSysCall(instr);
          nextPc += skip * 4; CachedCnt -= skip; CachedInstr += skip;
        }
        break; // syscall code
      case 13: goto lBreak; break; // break code
      case 16:
#ifdef CHECK_INVALID_INSTR
        if (r1 | r2 | shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[REG_HI];
        break; // mfhi d
      case 17:
#ifdef CHECK_INVALID_INSTR
        if (r2 | r3 | shft)
          goto lInvalidInstruction;
#endif
        Regs[REG_HI] = Regs[r1];
        break; // mthi s
      case 18:
#ifdef CHECK_INVALID_INSTR
        if (r1 | r2 | shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[REG_LO];
        break; // mflo d
      case 19:
#ifdef CHECK_INVALID_INSTR
        if (r2 | r3 | shft)
          goto lInvalidInstruction;
#endif
        Regs[REG_LO] = Regs[r1];
        break; // mtlo s
      case 24:
#ifdef CHECK_INVALID_INSTR
        if (r3 | shft)
          goto lInvalidInstruction;
#endif
        {
          int64 p = (int64)(int32)Regs[r1] * (int32)Regs[r2];
          Regs[REG_LO] = (uint32)p;
          Regs[REG_HI] = (uint32)(p >> 32);
        }
        break; // mult s,t
      case 25:
#ifdef CHECK_INVALID_INSTR
        if (r3 | shft)
          goto lInvalidInstruction;
#endif
        {
          uint64 p = (uint64)Regs[r1] * Regs[r2];
          Regs[REG_LO] = (uint32)p;
          Regs[REG_HI] = (uint32)(p >> 32);
        }
        break; // multu s,t
      case 26:
#ifdef CHECK_INVALID_INSTR
        if (r3 | shft)
          goto lInvalidInstruction;
#endif
        if (!(Regs[r2] == 0 || (Regs[r1] == 0x80000000 && Regs[r2] == 0xFFFFFFFF)))
          Regs[REG_LO] = (int32)Regs[r1] / (int32)Regs[r2], Regs[REG_HI] = (int32)Regs[r1] % (int32)Regs[r2];
        break; // div s,t
      case 27:
#ifdef CHECK_INVALID_INSTR
        if (r3 | shft)
          goto lInvalidInstruction;
#endif
        if (Regs[r2])
          Regs[REG_LO] = Regs[r1] / Regs[r2], Regs[REG_HI] = Regs[r1] % Regs[r2];
        break; // divu s,t
      case 32:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        {
          uint32 sum = Regs[r1] + Regs[r2];
          if (((Regs[r1] ^ Regs[r2] ^ 0x80000000) & 0x80000000) &&
              ((sum ^ Regs[r1]) & 0x80000000))
            goto lOverflow;
          Regs[r3] = sum;
        }
        break; // add d,s,t
      case 33:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] + Regs[r2];
        break; // addu d,s,t
      case 34:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        {
          uint32 diff = Regs[r1] - Regs[r2];
          if (((Regs[r1] ^ Regs[r2]) & 0x80000000) &&
              ((diff ^ Regs[r1]) & 0x80000000))
            goto lOverflow;
          Regs[r3] = diff;
        }
        break; // sub d,s,t
      case 35:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] - Regs[r2];
        break; // subu d,s,t
      case 36:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] & Regs[r2];
        break; // and d,s,t
      case 37:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] | Regs[r2];
        break; // or d,s,t
      case 38:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] ^ Regs[r2];
        break; // xor d,s,t
      case 39:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = ~(Regs[r1] | Regs[r2]);
        break; // nor d,s,t
      case 42:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = (int32)Regs[r1] < (int32)Regs[r2];
        break; // slt d,s,t
      case 43:
#ifdef CHECK_INVALID_INSTR
        if (shft)
          goto lInvalidInstruction;
#endif
        Regs[r3] = Regs[r1] < Regs[r2];
        break; // sltu d,s,t

      case 48: if ((int32)Regs[r1] >= (int32)Regs[r2]) goto lTrap; break; // tge s,t
      case 49: if (Regs[r1] >= Regs[r2]) goto lTrap; break; // tgeu s,t
      case 50: if ((int32)Regs[r1] < (int32)Regs[r2]) goto lTrap; break; // tlt s,t
      case 51: if (Regs[r1] < Regs[r2]) goto lTrap; break; // tltu s,t
      case 52: if (Regs[r1] == Regs[r2]) goto lTrap; break; // teq s,t
      case 53: if (Regs[r1] != Regs[r2]) goto lTrap; break; // tne s,t

      default: goto lInvalidInstruction;
      }
      break;

    case 1:
      switch (r2)
      {
      case 0: if ((int32)Regs[r1] < 0) nextPc += simm16 << 2, delaySlot = 1; break; // bltz s,p
      case 1: if ((int32)Regs[r1] >= 0) nextPc += simm16 << 2, delaySlot = 1; break; // bgez s,p
      case 2: if ((int32)Regs[r1] < 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bltzl s,p
      case 3: if ((int32)Regs[r1] >= 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bgezl s,p

      case 8: if ((int32)Regs[r1] >= (int32)simm16) goto lTrap; break; // tgei s,j
      case 9: if (Regs[r1] >= (uint32)simm16) goto lTrap; break; // tgeiu s,j
      case 10: if ((int32)Regs[r1] < (int32)simm16) goto lTrap; break; // tlti s,j
      case 11: if (Regs[r1] < (uint32)simm16) goto lTrap; break; // tltiu s,j
      case 12: if (Regs[r1] == (uint32)simm16) goto lTrap; break; // teqi s,j
      case 14: if (Regs[r1] != (uint32)simm16) goto lTrap; break; // tnei s,j

      case 16: Regs[REG_RA] = nextPc + 4; if ((int32)Regs[r1] < 0) nextPc += simm16 << 2, delaySlot = 1; break; // bltzal s,p
      case 17: Regs[REG_RA] = nextPc + 4; if ((int32)Regs[r1] >= 0) nextPc += simm16 << 2, delaySlot = 1; break; // bgezal s,p
      case 18: Regs[REG_RA] = nextPc + 4; if ((int32)Regs[r1] < 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bltzall s,p
      case 19: Regs[REG_RA] = nextPc + 4; if ((int32)Regs[r1] >= 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bgezall s,p

      default: goto lInvalidInstruction;
      }
      break;

    case 2: nextPc = (pc & 0xF0000000) | (jtgt << 2); delaySlot = 1; break; // j target
    case 3: Regs[REG_RA] = nextPc + 4; nextPc = (pc & 0xF0000000) | (jtgt << 2); delaySlot = 1; break; // jal target

    case 4: if (Regs[r1] == Regs[r2]) nextPc += simm16 << 2, delaySlot = 1; break; // beq s,t,p
    case 5: if (Regs[r1] != Regs[r2]) nextPc += simm16 << 2, delaySlot = 1; break; // bne s,t,p
    case 6:
#ifdef CHECK_INVALID_INSTR
      if (r2)
        goto lInvalidInstruction;
#endif
      if ((int32)Regs[r1] <= 0) nextPc += simm16 << 2, delaySlot = 1; break; // blez s,p
    case 7:
#ifdef CHECK_INVALID_INSTR
      if (r2)
        goto lInvalidInstruction;
#endif
      if ((int32)Regs[r1] > 0) nextPc += simm16 << 2, delaySlot = 1; break; // bgtz s,p

    case 8:
      {
        uint32 sum = Regs[r1] + simm16;
        if (((Regs[r1] ^ simm16 ^ 0x80000000) & 0x80000000) &&
            ((sum ^ Regs[r1]) & 0x80000000))
          goto lOverflow;
        Regs[r2] = sum;
      }
      break; // addi d,s,const
    case 9: Regs[r2] = Regs[r1] + simm16; break; // addiu d,s,const
    case 10: Regs[r2] = (int32)Regs[r1] < (int32)simm16; break; // slti d,s,const
    case 11: Regs[r2] = Regs[r1] < imm16; break; // sltiu d,s,const
    case 12: Regs[r2] = Regs[r1] & imm16; break; // andi d,s,const
    case 13: Regs[r2] = Regs[r1] | imm16; break; // ori d,s,const
    case 14: Regs[r2] = Regs[r1] ^ imm16; break; // xori d,s,const
    case 15:
#ifdef CHECK_INVALID_INSTR
      if (r1)
        goto lInvalidInstruction;
#endif
      Regs[r2] = imm16 << 16; break; // lui d,const

    case 20: if (Regs[r1] == Regs[r2]) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // beql s,t,p
    case 21: if (Regs[r1] != Regs[r2]) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bnel s,t,p
    case 22:
#ifdef CHECK_INVALID_INSTR
      if (r2)
        goto lInvalidInstruction;
#endif
      if ((int32)Regs[r1] <= 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // blezl s,p
    case 23:
#ifdef CHECK_INVALID_INSTR
      if (r2)
        goto lInvalidInstruction;
#endif
      if ((int32)Regs[r1] > 0) nextPc += simm16 << 2, delaySlot = 1; else nextPc += 4, CachedCnt--, ++CachedInstr; break; // bgtzl s,p

    case 28:
#ifdef CHECK_INVALID_INSTR
      if (shft)
        goto lInvalidInstruction;
#endif
      switch (fxn)
      {
      case 0:
#ifdef CHECK_INVALID_INSTR
        if (r3)
          goto lInvalidInstruction;
#endif
        {
          int64 p = (int64)(int32)Regs[r1] * (int32)Regs[r2];
          if (Regs[REG_LO] > 0xFFFFFFFF - (uint32)p)
            Regs[REG_HI]++;
          Regs[REG_LO] += (uint32)p;
          Regs[REG_HI] += (uint32)(p >> 32);
        }
        break; // madd s,t
      case 1:
#ifdef CHECK_INVALID_INSTR
        if (r3)
          goto lInvalidInstruction;
#endif
        {
          uint64 p = (uint64)Regs[r1] * Regs[r2];
          if (Regs[REG_LO] > 0xFFFFFFFF - (uint32)p)
            Regs[REG_HI]++;
          Regs[REG_LO] += (uint32)p;
          Regs[REG_HI] += (uint32)(p >> 32);
        }
        break; // maddu s,t
      case 2: Regs[r3] = Regs[r1] * Regs[r2]; break; // mul d,s,t
      case 4:
#ifdef CHECK_INVALID_INSTR
        if (r3)
          goto lInvalidInstruction;
#endif
        {
          int64 p = (int64)(int32)Regs[r1] * (int32)Regs[r2];
          if (Regs[REG_LO] < (uint32)p)
            Regs[REG_HI]--;
          Regs[REG_LO] -= (uint32)p;
          Regs[REG_HI] -= (uint32)(p >> 32);
        }
        break; // msub s,t
      case 5:
#ifdef CHECK_INVALID_INSTR
        if (r3)
          goto lInvalidInstruction;
#endif
        {
          uint64 p = (uint64)Regs[r1] * Regs[r2];
          if (Regs[REG_LO] < (uint32)p)
            Regs[REG_HI]--;
          Regs[REG_LO] -= (uint32)p;
          Regs[REG_HI] -= (uint32)(p >> 32);
        }
        break; // msubu s,t

      case 32:
#ifdef CHECK_INVALID_INSTR
        if (r1 != r2)
          goto lInvalidInstruction;
#endif
        Regs[r3] = CountLeadingZeroes(Regs[r2]); break; // clz d,s
      case 33:
#ifdef CHECK_INVALID_INSTR
        if (r1 != r2)
          goto lInvalidInstruction;
#endif
        Regs[r3] = CountLeadingOnes(Regs[r2]); break; // clo d,s

      default: goto lInvalidInstruction;
      }
      break;

    case 31:
      switch (fxn)
      {
      case 0:
        if (shft + r3 <= 31)
        {
          uint size = r3 + 1;
          uint32 mask = (0xFFFFFFFF >> (32 - size)) << shft;
          Regs[r2] = (Regs[r1] & mask) >> shft;
        }
        break; // ext t,s,pos,sz
      case 4:
        if (r3 >= shft)
        {
          uint size = r3 - shft + 1;
          uint32 mask = (0xFFFFFFFF >> (32 - size)) << shft;
          Regs[r2] = (Regs[r2] & ~mask) | ((Regs[r1] << shft) & mask);
        }
        break; // ins t,s,pos,sz
      case 32:
#ifdef CHECK_INVALID_INSTR
        if (r1)
          goto lInvalidInstruction;
#endif
        switch (shft)
        {
        case 2: Regs[r3] = ((Regs[r2] & 0x00FF) << 8) |
                           ((Regs[r2] & 0xFF00) >> 8) |
                           ((Regs[r2] & 0x00FF0000) << 8) |
                           ((Regs[r2] & 0xFF000000) >> 8); break; // wsbh d,t
        case 16: Regs[r3] = (int8)Regs[r2]; break; // seb d,t
        case 24: Regs[r3] = (int16)Regs[r2]; break; // seh d,t
        default: goto lInvalidInstruction;
        }
        break;

      default: goto lInvalidInstruction;
      }
      break;

    case 32: Regs[r2] = (int8)ReadByte(Regs[r1] + simm16); break; // lb t,o(b)
    case 33: Regs[r2] = (int16)ReadHalfWord(Regs[r1] + simm16); break; // lh t,o(b)
    case 34:
      {
        uint32 v = ReadByte(Regs[r1] + simm16);
        v = (v << 8) | ReadByte(Regs[r1] + simm16 - 1);
        Regs[r2] = (Regs[r2] & 0xFFFF) | (v << 16);
      }
      break; // lwl t,o(b)
    case 35: Regs[r2] = ReadWord(Regs[r1] + simm16); break; // lw t,o(b)
    case 36: Regs[r2] = ReadByte(Regs[r1] + simm16); break; // lbu t,o(b)
    case 37: Regs[r2] = ReadHalfWord(Regs[r1] + simm16); break; // lhu t,o(b)
    case 38:
      {
        uint32 v = ReadByte(Regs[r1] + simm16);
        v |= (uint32)ReadByte(Regs[r1] + simm16 + 1) << 8;
        Regs[r2] = (Regs[r2] & 0xFFFF0000) | v;
      }
      break; // lwr t,o(b)

    case 40: WriteByte(Regs[r1] + simm16, (uint8)Regs[r2]); break; // sb t,o(b)
    case 41: WriteHalfWord(Regs[r1] + simm16, (uint16)Regs[r2]); break; // sh t,o(b)
    case 42:
      WriteByte(Regs[r1] + simm16, (uint8)(Regs[r2] >> 24));
      WriteByte(Regs[r1] + simm16 - 1, (uint8)(Regs[r2] >> 16));
      break; // swl t,o(b)
    case 43: WriteWord(Regs[r1] + simm16, Regs[r2]); break; // sw t,o(b)
    case 46:
      WriteByte(Regs[r1] + simm16, (uint8)Regs[r2]);
      WriteByte(Regs[r1] + simm16 + 1, (uint8)(Regs[r2] >> 8));
      break; // swr t,o(b)

    default:
      goto lInvalidInstruction;
    }

    Regs[0] = 0;

    Regs[REG_PC] = nextPc;

    if (delaySlot)
    {
      if (delaySlot == 1)
      {
         postDelaySlotPc = nextPc;
         Regs[REG_PC] = pc + 4;
         delaySlot = 2;
      }
      else
      {
         Regs[REG_PC] = postDelaySlotPc;
         delaySlot = 0;
         CachedCnt = 0;
      }
    }

#ifdef STATS
    EmulateCnt++;
#endif
  } // for (;;)

lBreak:
  DoBreak(instr);
  return;

lTrap:
  DoTrap(instr);
  return;

lOverflow:
  DoOverflow();
  return;

lInvalidInstruction:
  DoInvalidInstruction(instr);
  return;
#if 01
#undef op
#undef r1
#undef r2
#undef r3
#undef shft
#undef fxn
#undef imm16
#undef simm16
#undef jtgt
#endif
}

STATIC
void DoBreak(uint32 instr)
{
  uint32 code = (instr >> 16) & 0x3FF; // are there really another/extra 10 bits of the code?

  switch (code)
  {
  case 6:
    printstr("\nBreak: Signed division overflow");
    break;
  case 7:
    printstr("\nBreak: Division by 0");
    break;
  default:
    printstr("\nBreak: Code: 0x"); printhex(code);
    break;
  }
  printstr(" at PC = 0x"); printhex(Regs[REG_PC]); printchr('\n');
  exit(-1);
}

STATIC
void DoTrap(uint32 instr)
{
  uint32 code = (instr >> 6) & 0x3FF;

  switch (code)
  {
  case 6:
    printstr("\nTrap: Signed division overflow");
    break;
  case 7:
    printstr("\nTrap: Division by 0");
    break;
  default:
    printstr("\nTrap: Code: 0x"); printhex(code);
    break;
  }
  printstr(" at PC = 0x"); printhex(Regs[REG_PC]); printchr('\n');
  exit(-1);
}

STATIC
void DoOverflow(void)
{
  printstr("Signed integer addition/subtraction overflow");
  printstr(" at PC = 0x"); printhex(Regs[REG_PC]); printchr('\n');
  exit(-1);
}

STATIC
void DoInvalidInstruction(uint32 instr)
{
  printstr("Invalid/unsupported instruction: Opcode: 0x"); printhex(instr);
  printstr(" at PC = 0x"); printhex(Regs[REG_PC]); printchr('\n');
  exit(-1);
}
