/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   Mupen64plus - interpreter_special.def                                 *
 *   Mupen64Plus homepage: http://code.google.com/p/mupen64plus/           *
 *   Copyright (C) 2002 Hacktarux                                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

DECLARE_INSTRUCTION(NOP)
{
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SLL)
{
   rrd32 = (unsigned int)(rrt32) << rsa;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SRL)
{
   rrd32 = (unsigned int)rrt32 >> rsa;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SRA)
{
   rrd32 = (signed int)rrt32 >> rsa;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SLLV)
{
   rrd32 = (unsigned int)(rrt32) << (rrs32&0x1F);
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SRLV)
{
   rrd32 = (unsigned int)rrt32 >> (rrs32 & 0x1F);
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SRAV)
{
   rrd32 = (signed int)rrt32 >> (rrs32 & 0x1F);
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_JUMP(JR,   irs32, 1, &state->reg[0], 0, 0)
DECLARE_JUMP(JALR, irs32, 1, &rrd,    0, 0)

DECLARE_INSTRUCTION(SYSCALL)
{
   state->g_cp0_regs[CP0_CAUSE_REG] = 8 << 2;
   exception_general(state);
}

DECLARE_INSTRUCTION(SYNC)
{
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MFHI)
{
   rrd = state->hi;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MTHI)
{
   state->hi = rrs;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MFLO)
{
   rrd = state->lo;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MTLO)
{
   state->lo = rrs;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSLLV)
{
   rrd = rrt << (rrs32&0x3F);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRLV)
{
   rrd = (unsigned long long)rrt >> (rrs32 & 0x3F);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRAV)
{
   rrd = (long long)rrt >> (rrs32 & 0x3F);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MULT)
{
   long long int temp;
   temp = rrs * rrt;
   state->hi = temp >> 32;
   state->lo = temp;
   sign_extended(state->lo);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(MULTU)
{
   unsigned long long int temp;
   temp = (unsigned int)rrs * (unsigned long long)((unsigned int)rrt);
   state->hi = (long long)temp >> 32;
   state->lo = temp;
   sign_extended(state->lo);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DIV)
{
   if (rrt32)
   {
     state->lo = rrs32 / rrt32;
     state->hi = rrs32 % rrt32;
     sign_extended(state->lo);
     sign_extended(state->hi);
   }
   else DebugMessage(state, M64MSG_ERROR, "DIV: divide by 0");
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DIVU)
{
   if (rrt32)
   {
     state->lo = (unsigned int)rrs32 / (unsigned int)rrt32;
     state->hi = (unsigned int)rrs32 % (unsigned int)rrt32;
     sign_extended(state->lo);
     sign_extended(state->hi);
   }
   else DebugMessage(state, M64MSG_ERROR, "DIVU: divide by 0");
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DMULT)
{
   unsigned long long int op1, op2, op3, op4;
   unsigned long long int result1, result2, result3, result4;
   unsigned long long int temp1, temp2, temp3, temp4;
   int sign = 0;
   
   if (rrs < 0)
     {
    op2 = -rrs;
    sign = 1 - sign;
     }
   else op2 = rrs;
   if (rrt < 0)
     {
    op4 = -rrt;
    sign = 1 - sign;
     }
   else op4 = rrt;
   
   op1 = op2 & 0xFFFFFFFF;
   op2 = (op2 >> 32) & 0xFFFFFFFF;
   op3 = op4 & 0xFFFFFFFF;
   op4 = (op4 >> 32) & 0xFFFFFFFF;
   
   temp1 = op1 * op3;
   temp2 = (temp1 >> 32) + op1 * op4;
   temp3 = op2 * op3;
   temp4 = (temp3 >> 32) + op2 * op4;
   
   result1 = temp1 & 0xFFFFFFFF;
   result2 = temp2 + (temp3 & 0xFFFFFFFF);
   result3 = (result2 >> 32) + temp4;
   result4 = (result3 >> 32);
   
   state->lo = result1 | (result2 << 32);
   state->hi = (result3 & 0xFFFFFFFF) | (result4 << 32);
   if (sign)
     {
    state->hi = ~state->hi;
    if (!state->lo) state->hi++;
    else state->lo = ~state->lo + 1;
     }
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DMULTU)
{
   unsigned long long int op1, op2, op3, op4;
   unsigned long long int result1, result2, result3, result4;
   unsigned long long int temp1, temp2, temp3, temp4;
   
   op1 = rrs & 0xFFFFFFFF;
   op2 = (rrs >> 32) & 0xFFFFFFFF;
   op3 = rrt & 0xFFFFFFFF;
   op4 = (rrt >> 32) & 0xFFFFFFFF;
   
   temp1 = op1 * op3;
   temp2 = (temp1 >> 32) + op1 * op4;
   temp3 = op2 * op3;
   temp4 = (temp3 >> 32) + op2 * op4;
   
   result1 = temp1 & 0xFFFFFFFF;
   result2 = temp2 + (temp3 & 0xFFFFFFFF);
   result3 = (result2 >> 32) + temp4;
   result4 = (result3 >> 32);
   
   state->lo = result1 | (result2 << 32);
   state->hi = (result3 & 0xFFFFFFFF) | (result4 << 32);
   
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DDIV)
{
   if (rrt)
   {
     state->lo = (long long int)rrs / (long long int)rrt;
     state->hi = (long long int)rrs % (long long int)rrt;
   }
   else DebugMessage(state, M64MSG_ERROR, "DDIV: divide by 0");
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DDIVU)
{
   if (rrt)
   {
     state->lo = (unsigned long long int)rrs / (unsigned long long int)rrt;
     state->hi = (unsigned long long int)rrs % (unsigned long long int)rrt;
   }
   else DebugMessage(state, M64MSG_ERROR, "DDIVU: divide by 0");
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(ADD)
{
   rrd32 = rrs32 + rrt32;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(ADDU)
{
   rrd32 = rrs32 + rrt32;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SUB)
{
   rrd32 = rrs32 - rrt32;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SUBU)
{
   rrd32 = rrs32 - rrt32;
   sign_extended(rrd);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(AND)
{
   rrd = rrs & rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(OR)
{
   rrd = rrs | rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(XOR)
{
   rrd = rrs ^ rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(NOR)
{
   rrd = ~(rrs | rrt);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SLT)
{
   if (rrs < rrt) rrd = 1;
   else rrd = 0;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(SLTU)
{
   if ((unsigned long long)rrs < (unsigned long long)rrt) 
     rrd = 1;
   else rrd = 0;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DADD)
{
   rrd = rrs + rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DADDU)
{
   rrd = rrs + rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSUB)
{
   rrd = rrs - rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSUBU)
{
   rrd = rrs - rrt;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(TEQ)
{
   if (rrs == rrt)
   {
     DebugMessage(state, M64MSG_ERROR, "trap exception in TEQ");
     state->stop=1;
   }
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSLL)
{
   rrd = rrt << rsa;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRL)
{
   rrd = (unsigned long long)rrt >> rsa;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRA)
{
   rrd = rrt >> rsa;
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSLL32)
{
   rrd = rrt << (32+rsa);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRL32)
{
   rrd = (unsigned long long int)rrt >> (32+rsa);
   ADD_TO_PC(1);
}

DECLARE_INSTRUCTION(DSRA32)
{
   rrd = (signed long long int)rrt >> (32+rsa);
   ADD_TO_PC(1);
}

/* Idle loop hack from 64th Note */
DECLARE_INSTRUCTION(BREAK)
{
   state->g_cp0_regs[CP0_COUNT_REG] = state->next_interupt;
   ADD_TO_PC(1);
}
