ppsspp/Core/MIPS/MIPSInt.cpp
Sacha 41f5abab31 PPSSPP ported to Blackberry10
Now builds on Playbook and Dev Alpha
Make emulator more compatible with other OS (case sensitivity, defines, includes)
Uses Android's code paths and backend
2012-11-05 23:09:49 +10:00

781 lines
17 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// MIPS is really trivial :)
#include <cmath>
#include "../Core.h"
#include "MIPS.h"
#include "MIPSInt.h"
#include "MIPSTables.h"
#include "../HLE/HLE.h"
#include "../System.h"
#define R(i) (currentMIPS->r[i])
#define RF(i) (*(float*)(&(currentMIPS->r[i])))
#define F(i) (currentMIPS->f[i])
#define FI(i) (*(u32*)(&(currentMIPS->f[i])))
#define FsI(i) (*(s32*)(&(currentMIPS->f[i])))
#define PC (currentMIPS->pc)
#define _RS ((op>>21) & 0x1F)
#define _RT ((op>>16) & 0x1F)
#define _RD ((op>>11) & 0x1F)
#define _FS ((op>>11) & 0x1F)
#define _FT ((op>>16) & 0x1F)
#define _FD ((op>>6 ) & 0x1F)
#define _POS ((op>>6 ) & 0x1F)
#define _SIZE ((op>>11 ) & 0x1F)
#define HI currentMIPS->hi
#define LO currentMIPS->lo
void DelayBranchTo(u32 where)
{
PC += 4;
mipsr4k.nextPC = where;
mipsr4k.inDelaySlot = true;
}
int MIPS_SingleStep()
{
#if defined(ANDROID) || defined(BLACKBERRY)
u32 op = Memory::ReadUnchecked_U32(mipsr4k.pc);
#else
u32 op = Memory::Read_Opcode_JIT(mipsr4k.pc);
#endif
/*
// Choke on VFPU
u32 info = MIPSGetInfo(op);
if (info & IS_VFPU)
{
if (!Core_IsStepping() && !GetAsyncKeyState(VK_LSHIFT))
{
Core_EnableStepping(true);
return;
}
}*/
if (mipsr4k.inDelaySlot)
{
MIPSInterpret(op);
if (mipsr4k.inDelaySlot)
{
mipsr4k.pc = mipsr4k.nextPC;
mipsr4k.inDelaySlot = false;
}
}
else
{
MIPSInterpret(op);
}
return 1;
}
u32 MIPS_GetNextPC()
{
if (mipsr4k.inDelaySlot)
return mipsr4k.nextPC;
else
return mipsr4k.pc + 4;
}
void MIPS_ClearDelaySlot()
{
mipsr4k.inDelaySlot = false;
}
namespace MIPSInt
{
void Int_Cache(u32 op)
{
DEBUG_LOG(CPU,"cache instruction %08x",op);
PC += 4;
}
void Int_Syscall(u32 op)
{
// Need to pre-move PC, as CallSyscall may result in a rescheduling!
// To do this neater, we'll need a little generated kernel loop that syscall can jump to and then RFI from
// but I don't see a need to bother.
if (mipsr4k.inDelaySlot)
{
mipsr4k.pc = mipsr4k.nextPC;
}
else
{
mipsr4k.pc += 4;
}
mipsr4k.inDelaySlot = false;
CallSyscall(op);
}
void Int_Sync(u32 op)
{
DEBUG_LOG(CPU, "sync");
PC += 4;
}
void Int_Break(u32 op)
{
DEBUG_LOG(CPU, "BREAK!");
coreState = CORE_STEPPING;
PC += 4;
}
void Int_RelBranch(u32 op)
{
int imm = (signed short)(op&0xFFFF)<<2;
int rs = _RS;
int rt = _RT;
u32 addr = PC + imm + 4;
switch (op >> 26)
{
case 4: if (R(rt) == R(rs)) DelayBranchTo(addr); else PC += 4; break; //beq
case 5: if (R(rt) != R(rs)) DelayBranchTo(addr); else PC += 4; break; //bne
case 6: if ((s32)R(rs) <= 0) DelayBranchTo(addr); else PC += 4; break; //blez
case 7: if ((s32)R(rs) > 0) DelayBranchTo(addr); else PC += 4; break; //bgtz
case 20: if (R(rt) == R(rs)) DelayBranchTo(addr); else PC += 8; break; //beql
case 21: if (R(rt) != R(rs)) DelayBranchTo(addr); else PC += 8; break; //bnel
case 22: if ((s32)R(rs) <= 0) DelayBranchTo(addr); else PC += 8; break; //blezl
case 23: if ((s32)R(rs) > 0) DelayBranchTo(addr); else PC += 8; break; //bgtzl
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
}
void Int_RelBranchRI(u32 op)
{
int imm = (signed short)(op&0xFFFF)<<2;
int rs = _RS;
u32 addr = PC + imm + 4;
switch ((op>>16) & 0x1F)
{
case 0: if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
case 1: if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
case 2: if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
case 3: if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
}
void Int_VBranch(u32 op)
{
int imm = (signed short)(op&0xFFFF)<<2;
u32 addr = PC + imm + 4;
int imm3 = (op>>18)&7;
int val = (currentMIPS->vfpuCtrl[VFPU_CTRL_CC] >> imm3) & 1;
switch ((op >> 16) & 3)
{
case 0: if (!val) DelayBranchTo(addr); else PC += 4; break; //bvf
case 1: if ( val) DelayBranchTo(addr); else PC += 4; break; //bvt
case 2: if (!val) DelayBranchTo(addr); else PC += 8; break; //bvfl
case 3: if ( val) DelayBranchTo(addr); else PC += 8; break; //bvtl
}
}
void Int_FPUBranch(u32 op)
{
int imm = (signed short)(op&0xFFFF)<<2;
u32 addr = PC + imm + 4;
switch((op>>16)&0x1f)
{
case 0: if (!currentMIPS->fpcond) DelayBranchTo(addr); else PC += 4; break;//bc1f
case 1: if ( currentMIPS->fpcond) DelayBranchTo(addr); else PC += 4; break;//bc1t
case 2: if (!currentMIPS->fpcond) DelayBranchTo(addr); else PC += 8; break;//bc1fl
case 3: if ( currentMIPS->fpcond) DelayBranchTo(addr); else PC += 8; break;//bc1tl
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
}
void Int_JumpType(u32 op)
{
if (mipsr4k.inDelaySlot)
_dbg_assert_msg_(CPU,0,"Jump in delay slot :(");
u32 off = ((op & 0x3FFFFFF) << 2);
u32 addr = (currentMIPS->pc & 0xF0000000) | off;
switch (op>>26)
{
case 2: DelayBranchTo(addr); break; //j
case 3: //jal
R(31) = PC + 8;
DelayBranchTo(addr);
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
}
void Int_JumpRegType(u32 op)
{
if (mipsr4k.inDelaySlot)
{
// There's one of these in Star Soldier at 0881808c, which seems benign - it should probably be ignored.
if (op == 0x03e00008)
return;
_dbg_assert_msg_(CPU,0,"Jump in delay slot :(");
}
int rs = (op>>21)&0x1f;
u32 addr = R(rs);
switch (op & 0x3f)
{
case 8: //jr
// LOG(CPU,"returning from: %08x",PC);
DelayBranchTo(addr);
break;
case 9: //jalr
R(31) = PC + 8;
DelayBranchTo(addr);
break;
}
}
void Int_IType(u32 op)
{
s32 simm = (s32)(s16)(op & 0xFFFF);
u32 uimm = (u32)(u16)(op & 0xFFFF);
u32 suimm = (u32)simm;
int rt = _RT;
int rs = _RS;
if (rt == 0) //destination register is zero register
return; //nop
switch (op>>26)
{
case 8: R(rt) = R(rs) + simm; break; //addi
case 9: R(rt) = R(rs) + simm; break; //addiu
case 10: R(rt) = (s32)R(rs) < simm; break; //slti
case 11: R(rt) = R(rs) < suimm; break; //sltiu
case 12: R(rt) = R(rs) & uimm; break; //andi
case 13: R(rt) = R(rs) | uimm; break; //ori
case 14: R(rt) = R(rs) ^ uimm; break; //xori
case 15: R(rt) = uimm << 16; break; //lui
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_RType3(u32 op)
{
int rt = _RT;
int rs = _RS;
int rd = _RD;
switch (op & 63)
{
case 10: if (R(rt) == 0) R(rd) = R(rs); break; //movz
case 11: if (R(rt) != 0) R(rd) = R(rs); break; //movn
case 32:
ERROR_LOG(HLE,"WARNING : exception-causing add at %08x", PC);
R(rd) = R(rs) + R(rt); break; //add
case 33: R(rd) = R(rs) + R(rt); break; //addu
case 34:
ERROR_LOG(HLE,"WARNING : exception-causing sub at %08x", PC);
R(rd) = R(rs) - R(rt); break; //sub
case 35: R(rd) = R(rs) - R(rt); break; //subu
case 36: R(rd) = R(rs) & R(rt); break; //and
case 37: R(rd) = R(rs) | R(rt); break; //or
case 38: R(rd) = R(rs) ^ R(rt); break; //xor
case 39: R(rd) = ~(R(rs) | R(rt)); break; //nor
case 42: R(rd) = (s32)R(rs) < (s32)R(rt); break; //slt
case 43: R(rd) = R(rs) < R(rt); break; //sltu
case 44: R(rd) = ((s32)R(rs) > (s32)R(rt)) ? R(rs) : R(rt); break; //max
case 45: R(rd) = ((s32)R(rs) < (s32)R(rt)) ? R(rs) : R(rt); break;//min
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_ITypeMem(u32 op)
{
int imm = (signed short)(op&0xFFFF);
int rt = _RT;
int rs = _RS;
u32 addr = R(rs) + imm;
switch (op >> 26)
{
case 32: R(rt) = (u32)(s32)(s8) Memory::Read_U8(addr); break; //lb
case 33: R(rt) = (u32)(s32)(s16)Memory::Read_U16(addr); break; //lh
case 35: R(rt) = Memory::Read_U32(addr); break; //lw
case 36: R(rt) = Memory::Read_U8 (addr); break; //lbu
case 37: R(rt) = Memory::Read_U16(addr); break; //lhu
case 40: Memory::Write_U8(R(rt), addr); break; //sb
case 41: Memory::Write_U16(R(rt), addr); break; //sh
case 43: Memory::Write_U32(R(rt), addr); break; //sw
// When there's an LWL and an LWR together, we should be able to peephole optimize that
// into a single non-alignment-checking LW.
case 34: //lwl
{
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 result = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) );
R(rt) = result;
}
break;
case 38: //lwr
{
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 regval = R(rt);
u32 result = ( regval & (0xffffff00 << (24 - shift)) ) | ( mem >> shift );
R(rt) = result;
}
break;
case 42: //swl
{
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 result = ( ( u32(R(rt)) >> (24 - shift) ) ) | ( mem & (0xffffff00 << shift) );
Memory::Write_U32(result, (addr & 0xfffffffc));
}
break;
case 46: //swr
{
u32 shift = (addr & 3) << 3;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 result = ( ( u32(R(rt)) << shift ) | (mem & (0x00ffffff >> (24 - shift)) ) );
Memory::Write_U32(result, (addr & 0xfffffffc));
}
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret Mem instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_FPULS(u32 op)
{
s32 offset = (s16)(op&0xFFFF);
int ft = ((op>>16)&0x1f);
int rs = _RS;
u32 addr = R(rs) + offset;
switch(op >> 26)
{
case 49: FI(ft) = Memory::Read_U32(addr); break; //lwc1
case 57: Memory::Write_U32(FI(ft), addr); break; //swc1
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret FPULS instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_mxc1(u32 op)
{
int fs = _FS;
int rt = _RT;
switch((op>>21)&0x1f)
{
case 0: R(rt) = FI(fs); break; //mfc1
case 2: R(rt) = currentMIPS->ReadFCR(fs); break; //cfc1
case 4: FI(fs) = R(rt); break; //mtc1
case 6: currentMIPS->WriteFCR(fs, R(rt)); break; //ctc1
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_RType2(u32 op)
{
int rs = _RS;
int rd = _RD;
switch (op & 63)
{
case 22: //clz
{ //TODO: verify
int x = 31;
int count=0;
while (!(R(rs) & (1<<x)) && x >= 0)
{
count++;
x--;
}
R(rd) = count;
}
break;
case 23: //clo
{ //TODO: verify
int x = 31;
int count=0;
while ((R(rs) & (1<<x)) && x >= 0)
{
count++;
x--;
}
R(rd) = count;
}
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_MulDivType(u32 op)
{
int rt = _RT;
int rs = _RS;
int rd = _RD;
switch (op & 63)
{
case 24: //mult
{
s64 result = (s64)(s32)R(rs) * (s64)(s32)R(rt);
u64 resultBits = (u64)(result);
LO = (u32)(resultBits);
HI = (u32)(resultBits>>32);
}
break;
case 25: //multu
{
u64 resultBits = (u64)R(rs) * (u64)R(rt);
LO = (u32)(resultBits);
HI = (u32)(resultBits>>32);
}
break;
case 28: //madd
{
u32 a=R(rs),b=R(rt),hi=HI,lo=LO;
u64 origValBits = (u64)lo | ((u64)(hi)<<32);
s64 origVal = (s64)origValBits;
s64 result = origVal + (s64)(s32)a * (s64)(s32)b;
u64 resultBits = (u64)(result);
LO = (u32)(resultBits);
HI = (u32)(resultBits>>32);
}
break;
case 29: //maddu
{
u32 a=R(rs),b=R(rt),hi=HI,lo=LO;
u64 origVal = (u64)lo | ((u64)(hi)<<32);
u64 result = origVal + (u64)a * (u64)b;
LO = (u32)(result);
HI = (u32)(result>>32);
}
break;
case 16: R(rd) = HI; break; //mfhi
case 17: HI = R(rs); break; //mthi
case 18: R(rd) = LO; break; //mflo
case 19: LO = R(rs); break; //mtlo
case 26: //div
{
s32 a = (s32)R(rs);
s32 b = (s32)R(rt);
if (b != 0) {
LO = (u32)(a/b);
HI = (u32)(a%b);
} else {
LO = HI = 0; // Not sure what the right thing to do is?
}
}
break;
case 27: //divu
{
u32 a = R(rs);
u32 b = R(rt);
if (b != 0)
{
LO = (a/b);
HI = (a%b);
} else {
LO = HI = 0;
}
}
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_ShiftType(u32 op)
{
int rt = _RT;
int rs = _RS;
int rd = _RD;
int sa = _FD;
switch (op & 0x3f)
{
case 0: R(rd) = R(rt) << sa; break; //sll
case 2:
if (_RS == 0) //srl
{
R(rd) = R(rt) >> sa;
break;
}
else if (_RS == 1) //rotr
{
R(rd) = _rotr(R(rt), sa);
break;
}
else
goto wrong;
case 3: R(rd) = (u32)(((s32)R(rt)) >> sa); break; //sra
case 4: R(rd) = R(rt) << (R(rs)&0x1F); break; //sllv
case 6:
if (_FD == 0) //srlv
{
R(rd) = R(rt) >> (R(rs)&0x1F);
break;
}
else if (_FD == 1) // rotrv
{
R(rd) = _rotr(R(rt), R(rs));
break;
}
else goto wrong;
case 7: R(rd) = (u32)(((s32)R(rt)) >> (R(rs)&0x1F)); break; //srav
default:
wrong:
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_Allegrex(u32 op)
{
int rt = _RT;
int rd = _RD;
switch((op>>6)&31)
{
case 16: // seb
R(rd) = (u32)(s32)(s8)(u8)R(rt);
break;
case 20: // bitrev
{
u32 tmp = 0;
for (int i = 0; i < 32; i++)
{
if (R(rt) & (1 << i))
{
tmp |= (0x80000000 >> i);
}
}
R(rd) = tmp;
}
break;
case 24: // seh
R(rd) = (u32)(s32)(s16)(u16)R(rt);
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret ALLEGREX instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_Allegrex2(u32 op)
{
int rt = _RT;
int rd = _RD;
switch (op & 0x3ff)
{
case 0xE0: //wsbw
R(rd) = _byteswap_ulong(R(rt));
break;
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret ALLEGREX instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_Special3(u32 op)
{
int rs = _RS;
int rt = _RT;
int pos = _POS;
switch (op & 0x3f)
{
case 0x0: //ext
{
int size = _SIZE + 1;
R(rt) = (R(rs) >> pos) & ((1<<size) - 1);
}
break;
case 0x4: // ins
{
int size = (_SIZE + 1) - pos;
int sourcemask = (1 << size) - 1;
int destmask = sourcemask << pos;
R(rt) = (R(rt) & ~destmask) | ((R(rs)&sourcemask) << pos);
}
break;
}
PC += 4;
}
void Int_FPU2op(u32 op)
{
int fs = _FS;
int fd = _FD;
switch (op & 0x3f)
{
case 4: F(fd) = sqrtf(F(fs)); break; //sqrt
case 5: F(fd) = fabsf(F(fs)); break; //abs
case 6: F(fd) = F(fs); break; //mov
case 7: F(fd) = -F(fs); break; //neg
case 12: FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s
case 13: FsI(fd) = F(fs)>=0 ? (int)floorf(F(fs)) : (int)ceilf(F(fs)); break;//trunc.w.s
case 14: FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s
case 15: FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s
case 32: F(fd) = (float)FsI(fs); break; //cvt.s.w
case 36: FsI(fd) = (int) F(fs); break; //cvt.w.s
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret FPU2Op instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_FPUComp(u32 op)
{
int fs = _FS;
int ft = _FT;
bool cond;
switch (op & 0xf)
{
case 2: //eq
cond = (F(fs) == F(ft));
break;
case 12: //lt
case 13: //nge
cond = (F(fs) < F(ft));
break;
case 14: //le
case 15: //ngt
cond = (F(fs) <= F(ft));
break;
case 0: //f
case 1: //un
case 3: //ueq
case 4: //olt
case 5: //ult
case 6: //ole
case 7: //ule
case 8: //sf
case 9: //ngle
case 10: //seq
case 11: //ngl
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret FPUComp instruction that can't be interpreted");
break;
}
currentMIPS->fpcond = cond;
PC += 4;
}
void Int_FPU3op(u32 op)
{
int ft = _FT;
int fs = _FS;
int fd = _FD;
switch (op & 0x3f)
{
case 0: F(fd) = F(fs) + F(ft); break; //add
case 1: F(fd) = F(fs) - F(ft); break; //sub
case 2: F(fd) = F(fs) * F(ft); break; //mul
case 3: F(fd) = F(fs) / F(ft); break; //div
default:
_dbg_assert_msg_(CPU,0,"Trying to interpret FPU3Op instruction that can't be interpreted");
break;
}
PC += 4;
}
void Int_Interrupt(u32 op)
{
switch (op & 1)
{
case 0:
DEBUG_LOG(CPU,"Disable/Enable Interrupt CPU instruction");
break;
}
PC += 4;
}
void Int_Emuhack(u32 op)
{
_dbg_assert_msg_(CPU,0,"Trying to interpret emuhack instruction that can't be interpreted");
}
}