mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding the offset into the code space, and replacement functions installed their own emuhack the same way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function hasher, games reading their own code) had to translate them back. Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS and Switch, where address space is short), with the low bits cleared so a misaligned PC can't read across two entries. Only the ranges under memory views are committed, so a bad PC still faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The dispatchers on every backend, and the IR interpreter's loop, look blocks up there. A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling replaces the entry with the block's, and destroying the block puts the hook value back. The hook value is rewritten when the JIT changes or regenerates its dispatcher. Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the hooked instruction, or when an icache invalidation finds the function's hash (immediates masked, as when matching it) changed. Those checks run when queued invalidations are processed, since cheats and the debugger queue them before writing. Installing and removing hooks invalidates deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes through MIPSState, so it drops changed hooks too. With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away, as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1330 lines
38 KiB
C++
1330 lines
38 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/.
|
|
|
|
#include <cmath>
|
|
|
|
#include "Common/Data/Convert/SmallDataConvert.h"
|
|
#include "Common/Math/math_util.h"
|
|
|
|
#include "Common/BitSet.h"
|
|
#include "Common/BitScan.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSCodeUtils.h"
|
|
#include "Core/MIPS/Interpreter.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/HLETables.h"
|
|
#include "Core/HLE/ReplaceTables.h"
|
|
#include "Core/HLE/sceKernelInterrupt.h"
|
|
#include "Core/HW/GpioMMIO.h"
|
|
|
|
#define R(i) (mips->r[i])
|
|
#define F(i) (mips->f[i])
|
|
#define FI(i) (mips->fi[i])
|
|
#define FsI(i) (mips->fs[i])
|
|
#define PC (mips->pc)
|
|
|
|
#define _SIMM16_SHL2 ((u32)(s32)(s16)(op & 0xFFFF) << 2)
|
|
#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 mips->hi
|
|
#define LO mips->lo
|
|
|
|
static inline void DelayBranchTo(MIPSState *mips, u32 where) {
|
|
if (!Memory::IsValidAddress(where) || (where & 3) != 0) {
|
|
Core_ExecException(where, PC, ExecExceptionType::JUMP);
|
|
}
|
|
PC += 4;
|
|
mips->nextPC = where;
|
|
mips->inDelaySlot = true;
|
|
}
|
|
|
|
static inline void SkipLikely(MIPSState *mips) {
|
|
MIPSInfo delaySlot = MIPSGetInfo(Memory::Read_Instruction(PC + 4));
|
|
// Don't actually skip if it is a jump (seen in Brooktown High.)
|
|
if (delaySlot & IS_JUMP) {
|
|
PC += 4;
|
|
} else {
|
|
PC += 8;
|
|
--mips->downcount;
|
|
}
|
|
}
|
|
|
|
int MIPS_InterpretSingleStep(MIPSState *mips) {
|
|
if (!Memory::IsValid4AlignedAddress(mips->pc)) {
|
|
Core_ExecException(mips->pc, mips-> pc, ExecExceptionType::JUMP);
|
|
return 0;
|
|
}
|
|
MIPSOpcode op = ReadExecutedOp(mips->pc);
|
|
// Same reason as the run loop in MIPSInterpret_RunUntil - see ApplyHostRoundingMode.
|
|
ApplyHostRoundingMode(mips);
|
|
if (mips->inDelaySlot) {
|
|
MIPSInterpret(mips, op);
|
|
if (mips->inDelaySlot) {
|
|
mips->pc = mips->nextPC;
|
|
mips->inDelaySlot = false;
|
|
}
|
|
} else {
|
|
MIPSInterpret(mips, op);
|
|
}
|
|
RestoreHostRoundingMode();
|
|
return 1;
|
|
}
|
|
|
|
// MMIO handling, for limited LLE, just enough for VSH.
|
|
//
|
|
// Unregistered reads return a distinctive poison value (not 0) so it's obvious in a live trace
|
|
// when some later computation's input traces back to an unimplemented MMIO register, rather
|
|
// than looking like an ordinary, legitimate zero.
|
|
constexpr u32 UNKNOWN_MMIO_POISON = 0x1337BEEF;
|
|
|
|
static u8 ReadMMIO_U8(MIPSState *mips, u32 addr) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 1, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
|
|
return (u8)UNKNOWN_MMIO_POISON;
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Read8 at %08x", addr);
|
|
return (u8)UNKNOWN_MMIO_POISON;
|
|
}
|
|
|
|
static u16 ReadMMIO_U16(MIPSState *mips, u32 addr) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 2, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
|
|
return (u16)UNKNOWN_MMIO_POISON;
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Read16 at %08x", addr);
|
|
return (u16)UNKNOWN_MMIO_POISON;
|
|
}
|
|
|
|
static u32 ReadMMIO_U32(MIPSState *mips, u32 addr) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 4, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
|
|
return UNKNOWN_MMIO_POISON;
|
|
}
|
|
if (GpioMMIO::IsGpioAddress(addr)) {
|
|
return GpioMMIO::Read32(addr);
|
|
}
|
|
if (SysconSerialMMIO::IsSysconSerialAddress(addr)) {
|
|
return SysconSerialMMIO::Read32(addr);
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Read32 at %08x", addr);
|
|
return UNKNOWN_MMIO_POISON;
|
|
}
|
|
|
|
void WriteMMIO_U8(MIPSState *mips, u32 addr, u8 value) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 1, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
|
|
return;
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Write8 at %08x = %02x", addr, value);
|
|
}
|
|
|
|
void WriteMMIO_U16(MIPSState *mips, u32 addr, u16 value) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 2, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
|
|
return;
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Write16 at %08x = %04x", addr, value);
|
|
}
|
|
|
|
void WriteMMIO_U32(MIPSState *mips, u32 addr, u32 value) {
|
|
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
|
Core_MemoryException(addr, 4, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
|
|
return;
|
|
}
|
|
if (GpioMMIO::IsGpioAddress(addr)) {
|
|
GpioMMIO::Write32(addr, value);
|
|
return;
|
|
}
|
|
if (SysconSerialMMIO::IsSysconSerialAddress(addr)) {
|
|
SysconSerialMMIO::Write32(addr, value);
|
|
return;
|
|
}
|
|
WARN_LOG(Log::CPU, "Unhandled MMIO Write32 at %08x = %08x", addr, value);
|
|
}
|
|
|
|
namespace MIPSInt {
|
|
void Int_Cache(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = SignExtend16ToS32(op);
|
|
int rs = _RS;
|
|
uint32_t addr = R(rs) + imm;
|
|
int func = (op >> 16) & 0x1F;
|
|
|
|
// Let's only report this once per run to be safe from impacting perf.
|
|
static bool loggedAlignment = false;
|
|
|
|
// It appears that a cache line is 0x40 (64) bytes, loops in games
|
|
// issue the cache instruction at that interval.
|
|
|
|
// These codes might be PSP-specific, they don't match regular MIPS cache codes very well
|
|
|
|
// NOTE: If you add support for more, make sure they are handled in the various Jit::Comp_Cache.
|
|
switch (func) {
|
|
// Icache
|
|
case 8:
|
|
{
|
|
// Invalidate the instruction cache at this address.
|
|
// We assume the CPU won't be reset during this, so no locking.
|
|
// Let's over invalidate to be super safe.
|
|
uint32_t alignedAddr = addr & ~0x3F;
|
|
int size = 0x40 + (addr & 0x3F);
|
|
// Through MIPSState, which also drops replacement hooks in functions the game changed.
|
|
mips->InvalidateICacheRangeImmediate(alignedAddr, size);
|
|
if (MIPSComp::jit) {
|
|
// Using a bool to avoid locking/etc. in case it's slow.
|
|
if (!loggedAlignment && (addr & 0x3F) != 0) {
|
|
// These are seen exclusively in Lego games, and are really no big deal. Reporting removed.
|
|
WARN_LOG(Log::JIT, "Unaligned icache invalidation of %08x (%08x + %d) at PC=%08x", addr, R(rs), imm, PC);
|
|
loggedAlignment = true;
|
|
}
|
|
if (alignedAddr <= PC + 4 && alignedAddr + size >= PC - 4) {
|
|
// This is probably rare so we don't use a static bool.
|
|
WARN_LOG_REPORT_ONCE(icacheInvalidatePC, Log::JIT, "Invalidating address near PC: %08x (%08x + %d) at PC=%08x", addr, R(rs), imm, PC);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Dcache
|
|
case 24:
|
|
// "Create Dirty Exclusive" - for avoiding a cacheline fill before writing to it.
|
|
// Will cause garbage on the real machine so we just ignore it, the app will overwrite the cacheline.
|
|
break;
|
|
case 25: // Hit Invalidate - zaps the line if present in cache. Should not writeback???? scary.
|
|
// No need to do anything.
|
|
break;
|
|
case 27: // D-cube. Hit Writeback Invalidate. Tony Hawk Underground 2
|
|
break;
|
|
case 30: // GTA LCS, a lot. Fill (prefetch). Tony Hawk Underground 2
|
|
break;
|
|
|
|
default:
|
|
DEBUG_LOG(Log::CPU, "cache instruction affecting %08x : function %i", addr, func);
|
|
}
|
|
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Syscall(MIPSState *mips, MIPSOpcode op) {
|
|
const u32 syscallPC = mips->pc - 4;
|
|
// 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 (mips->inDelaySlot) {
|
|
mips->pc = mips->nextPC;
|
|
} else {
|
|
mips->pc += 4;
|
|
}
|
|
mips->inDelaySlot = false;
|
|
// HLE code is host code - it must not run under the guest's rounding mode.
|
|
RestoreHostRoundingMode();
|
|
CallSyscallWithPC(op, syscallPC);
|
|
ApplyHostRoundingMode(mips);
|
|
}
|
|
|
|
void Int_Sync(MIPSState *mips, MIPSOpcode op) {
|
|
//DEBUG_LOG(Log::CPU, "sync");
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Break(MIPSState *mips, MIPSOpcode op) {
|
|
Reporting::ReportMessage("BREAK instruction hit");
|
|
Core_BreakException(PC);
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_RelBranch(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = _SIMM16_SHL2;
|
|
int rs = _RS;
|
|
int rt = _RT;
|
|
u32 addr = PC + imm + 4;
|
|
|
|
switch (op >> 26)
|
|
{
|
|
case 4: if (R(rt) == R(rs)) DelayBranchTo(mips, addr); else PC += 4; break; //beq
|
|
case 5: if (R(rt) != R(rs)) DelayBranchTo(mips, addr); else PC += 4; break; //bne
|
|
case 6: if ((s32)R(rs) <= 0) DelayBranchTo(mips, addr); else PC += 4; break; //blez
|
|
case 7: if ((s32)R(rs) > 0) DelayBranchTo(mips, addr); else PC += 4; break; //bgtz
|
|
|
|
case 20: if (R(rt) == R(rs)) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //beql
|
|
case 21: if (R(rt) != R(rs)) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //bnel
|
|
case 22: if ((s32)R(rs) <= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //blezl
|
|
case 23: if ((s32)R(rs) > 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //bgtzl
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Int_RelBranchRI(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = _SIMM16_SHL2;
|
|
int rs = _RS;
|
|
u32 addr = PC + imm + 4;
|
|
|
|
switch ((op>>16) & 0x1F) {
|
|
case 0: if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltz
|
|
case 1: if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgez
|
|
case 2: if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzl
|
|
case 3: if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezl
|
|
case 16: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltzal
|
|
case 17: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgezal
|
|
case 18: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzall
|
|
case 19: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezall
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Int_VBranch(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = _SIMM16_SHL2;
|
|
u32 addr = PC + imm + 4;
|
|
|
|
// x, y, z, w, any, all, (invalid), (invalid)
|
|
int imm3 = (op>>18)&7;
|
|
int val = (mips->vfpuCtrl[VFPU_CTRL_CC] >> imm3) & 1;
|
|
|
|
switch ((op >> 16) & 3) {
|
|
case 0: if (!val) DelayBranchTo(mips, addr); else PC += 4; break; //bvf
|
|
case 1: if ( val) DelayBranchTo(mips, addr); else PC += 4; break; //bvt
|
|
case 2: if (!val) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //bvfl
|
|
case 3: if ( val) DelayBranchTo(mips, addr); else SkipLikely(mips); break; //bvtl
|
|
}
|
|
}
|
|
|
|
void Int_FPUBranch(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = _SIMM16_SHL2;
|
|
u32 addr = PC + imm + 4;
|
|
switch((op >> 16) & 0x1f)
|
|
{
|
|
case 0: if (!mips->fpcond) DelayBranchTo(mips, addr); else PC += 4; break;//bc1f
|
|
case 1: if ( mips->fpcond) DelayBranchTo(mips, addr); else PC += 4; break;//bc1t
|
|
case 2: if (!mips->fpcond) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bc1fl
|
|
case 3: if ( mips->fpcond) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bc1tl
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Int_JumpType(MIPSState *mips, MIPSOpcode op) {
|
|
if (mips->inDelaySlot) {
|
|
// There's one of these in Star Soldier at 0881808c, which seems benign.
|
|
ERROR_LOG(Log::CPU, "Jump in delay slot at %08x", mips->pc);
|
|
}
|
|
|
|
const u32 off = ((op & 0x03FFFFFF) << 2);
|
|
const u32 addr = (mips->pc & 0xF0000000) | off;
|
|
|
|
switch (op >> 26) {
|
|
case 2: //j
|
|
if (!mips->inDelaySlot)
|
|
DelayBranchTo(mips, addr);
|
|
break;
|
|
case 3: //jal
|
|
R(MIPS_REG_RA) = PC + 8;
|
|
if (!mips->inDelaySlot)
|
|
DelayBranchTo(mips, addr);
|
|
break;
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Int_JumpRegType(MIPSState *mips, MIPSOpcode op) {
|
|
if (mips->inDelaySlot) {
|
|
// There's one of these in Star Soldier at 0881808c, which seems benign.
|
|
ERROR_LOG(Log::CPU, "Jump in delay slot at %08x", mips->pc);
|
|
}
|
|
|
|
int rs = _RS;
|
|
int rd = _RD;
|
|
u32 addr = R(rs);
|
|
switch (op & 0x3f) {
|
|
case 8: //jr
|
|
if (!mips->inDelaySlot)
|
|
DelayBranchTo(mips, addr);
|
|
break;
|
|
case 9: //jalr
|
|
if (rd != 0)
|
|
R(rd) = PC + 8;
|
|
// Update rd, but otherwise do not take the branch if we're branching.
|
|
if (!mips->inDelaySlot)
|
|
DelayBranchTo(mips, addr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Int_IType(MIPSState *mips, MIPSOpcode op) {
|
|
u32 uimm = op & 0xFFFF;
|
|
u32 suimm = SignExtend16ToU32(op);
|
|
s32 simm = SignExtend16ToS32(op);
|
|
|
|
int rt = _RT;
|
|
int rs = _RS;
|
|
|
|
if (rt == 0) { //destination register is zero register
|
|
PC += 4;
|
|
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_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_StoreSync(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = (signed short)(op & 0xFFFF);
|
|
int rt = _RT;
|
|
int rs = _RS;
|
|
u32 addr = R(rs) + imm;
|
|
|
|
switch (op >> 26) {
|
|
case 48: // ll
|
|
if (rt != 0) {
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "ll");
|
|
R(rt) = 0;
|
|
} else {
|
|
R(rt) = Memory::ReadUnchecked_U32(addr);
|
|
}
|
|
}
|
|
mips->llBit = 1;
|
|
break;
|
|
case 56: // sc
|
|
if (mips->llBit) {
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sc");
|
|
} else {
|
|
Memory::WriteUnchecked_U32(R(rt), addr);
|
|
}
|
|
// Report success even if the store got dropped - reporting failure just makes
|
|
// the usual retry loop spin on the same bad address forever.
|
|
if (rt != 0) {
|
|
R(rt) = 1;
|
|
}
|
|
} else if (rt != 0) {
|
|
R(rt) = 0;
|
|
}
|
|
break;
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_RType3(MIPSState *mips, MIPSOpcode op) {
|
|
int rt = _RT;
|
|
int rs = _RS;
|
|
int rd = _RD;
|
|
|
|
// Don't change $zr.
|
|
if (rd == 0) {
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
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: R(rd) = R(rs) + R(rt); break; //add (exception on overflow)
|
|
case 33: R(rd) = R(rs) + R(rt); break; //addu
|
|
case 34: R(rd) = R(rs) - R(rt); break; //sub (exception on overflow)
|
|
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_( 0, "Unknown MIPS instruction %08x", op.encoding);
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
// On a bad access that's set to be ignored (the default), we mirror what
|
|
// Memory::ReadOrException_*/WriteOrException_* do, which is also what the JIT's slow
|
|
// path calls: loads produce zero, stores are dropped, and PC advances either way.
|
|
// Returning without advancing PC instead would just re-execute the same instruction forever.
|
|
// When the access is set to break, Core_MemoryException has already stopped the core.
|
|
void Int_ITypeMem(MIPSState *mips, MIPSOpcode op) {
|
|
int imm = (signed short)(op&0xFFFF);
|
|
int rt = _RT;
|
|
int rs = _RS;
|
|
const u32 addr = R(rs) + imm;
|
|
|
|
if (((op >> 29) & 1) == 0 && rt == 0) {
|
|
// Don't load anything into $zr
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
switch (op >> 26) {
|
|
case 32:
|
|
if (!Memory::IsValidAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a read from MMIO, so we can handle it.
|
|
R(rt) = SignExtend8ToU32(ReadMMIO_U8(mips, addr));
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lb");
|
|
R(rt) = 0;
|
|
break;
|
|
}
|
|
R(rt) = SignExtend8ToU32(Memory::ReadUnchecked_U8(addr));
|
|
break; //lb
|
|
case 33:
|
|
if (!Memory::IsValid2AlignedAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a read from MMIO, so we can handle it.
|
|
R(rt) = SignExtend16ToU32(ReadMMIO_U16(mips, addr));
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lh");
|
|
R(rt) = 0;
|
|
break;
|
|
}
|
|
R(rt) = SignExtend16ToU32(Memory::ReadUnchecked_U16(addr));
|
|
break; //lh
|
|
case 35:
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a read from MMIO, so we can handle it.
|
|
R(rt) = ReadMMIO_U32(mips, addr);
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lw");
|
|
R(rt) = 0;
|
|
break;
|
|
}
|
|
R(rt) = Memory::ReadUnchecked_U32(addr);
|
|
break; //lw
|
|
case 36:
|
|
if (!Memory::IsValidAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a read from MMIO, so we can handle it.
|
|
R(rt) = ReadMMIO_U8(mips, addr);
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lbu");
|
|
R(rt) = 0;
|
|
break;
|
|
}
|
|
R(rt) = Memory::ReadUnchecked_U8(addr);
|
|
break; //lbu
|
|
case 37:
|
|
if (!Memory::IsValid2AlignedAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a read from MMIO, so we can handle it.
|
|
R(rt) = ReadMMIO_U16(mips, addr);
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lhu");
|
|
R(rt) = 0;
|
|
break;
|
|
}
|
|
R(rt) = Memory::ReadUnchecked_U16(addr);
|
|
break; //lhu
|
|
case 40:
|
|
if (!Memory::IsValidAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a write to MMIO, so we can handle it.
|
|
WriteMMIO_U8(mips, addr, (u8)(R(rt)));
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 1, PC, MemoryExceptionType::WRITE_WORD, "sb");
|
|
break;
|
|
}
|
|
Memory::WriteUnchecked_U8(R(rt), addr);
|
|
break; //sb
|
|
case 41:
|
|
if (!Memory::IsValid2AlignedAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a write to MMIO, so we can handle it.
|
|
WriteMMIO_U16(mips, addr, (u16)(R(rt)));
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 2, PC, MemoryExceptionType::WRITE_WORD, "sh");
|
|
break;
|
|
}
|
|
Memory::WriteUnchecked_U16(R(rt), addr);
|
|
break; //sh
|
|
case 43:
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
if (Memory::IsMMIOAccess(addr)) {
|
|
// This is a write to MMIO, so we can handle it.
|
|
WriteMMIO_U32(mips, addr, R(rt));
|
|
break;
|
|
}
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sw");
|
|
break;
|
|
}
|
|
Memory::WriteUnchecked_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
|
|
{
|
|
// Not checking for alignment here - the actual read will be aligned.
|
|
u32 mem = 0;
|
|
if (Memory::IsValidAddress(addr)) {
|
|
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
} else {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwl");
|
|
}
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 result = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) );
|
|
R(rt) = result;
|
|
}
|
|
break;
|
|
|
|
case 38: //lwr
|
|
{
|
|
// Not checking for alignment here - the actual read will be aligned.
|
|
u32 mem = 0;
|
|
if (Memory::IsValidAddress(addr)) {
|
|
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
} else {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwr");
|
|
}
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 regval = R(rt);
|
|
u32 result = ( regval & (0xffffff00 << (24 - shift)) ) | ( mem >> shift );
|
|
R(rt) = result;
|
|
}
|
|
break;
|
|
|
|
case 42: //swl
|
|
{
|
|
// Not checking for alignment here - the actual read/write will be aligned.
|
|
if (!Memory::IsValidAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swl");
|
|
break;
|
|
}
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 result = ( ( u32(R(rt)) >> (24 - shift) ) ) | ( mem & (0xffffff00 << shift) );
|
|
Memory::WriteUnchecked_U32(result, (addr & 0xfffffffc));
|
|
}
|
|
break;
|
|
|
|
case 46: //swr
|
|
{
|
|
// Not checking for alignment here - the actual read/write will be aligned.
|
|
if (!Memory::IsValidAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swr");
|
|
break;
|
|
}
|
|
u32 shift = (addr & 3) << 3;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 result = ( ( u32(R(rt)) << shift ) | (mem & (0x00ffffff >> (24 - shift)) ) );
|
|
Memory::WriteUnchecked_U32(result, (addr & 0xfffffffc));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret Mem instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_FPULS(MIPSState *mips, MIPSOpcode op) {
|
|
s32 offset = (s16)(op & 0xFFFF);
|
|
int ft = _FT;
|
|
int rs = _RS;
|
|
u32 addr = R(rs) + offset;
|
|
|
|
switch (op >> 26) {
|
|
case 49:
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwc1");
|
|
FI(ft) = 0;
|
|
break;
|
|
}
|
|
FI(ft) = Memory::ReadUnchecked_U32(addr);
|
|
break; //lwc1
|
|
case 57:
|
|
if (!Memory::IsValid4AlignedAddress(addr)) {
|
|
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swc1");
|
|
break;
|
|
}
|
|
Memory::WriteUnchecked_U32(FI(ft), addr);
|
|
break; //swc1
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret FPULS instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_mxc1(MIPSState *mips, MIPSOpcode op)
|
|
{
|
|
int fs = _FS;
|
|
int rt = _RT;
|
|
|
|
switch ((op>>21)&0x1f) {
|
|
case 0: //mfc1
|
|
if (rt != 0)
|
|
R(rt) = FI(fs);
|
|
break;
|
|
|
|
case 2: //cfc1
|
|
if (rt != 0) {
|
|
if (fs == 31) {
|
|
mips->fcr31 = (mips->fcr31 & ~(1<<23)) | ((mips->fpcond & 1)<<23);
|
|
R(rt) = mips->fcr31;
|
|
} else if (fs == 0) {
|
|
R(rt) = MIPSState::FCR0_VALUE;
|
|
} else {
|
|
WARN_LOG_REPORT(Log::CPU, "ReadFCR: Unexpected reg %d", fs);
|
|
R(rt) = 0;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 4: //mtc1
|
|
FI(fs) = R(rt);
|
|
break;
|
|
|
|
case 6: //ctc1
|
|
{
|
|
u32 value = R(rt);
|
|
if (fs == 31) {
|
|
mips->fcr31 = value & 0x0181FFFF;
|
|
mips->fpcond = (value >> 23) & 1;
|
|
// Don't bother locking, assuming the CPU can't be reset now anyway.
|
|
if (MIPSComp::jit) {
|
|
// In case of DISABLE, we need to tell jit we updated FCR31.
|
|
MIPSComp::jit->UpdateFCR31();
|
|
} else {
|
|
// The interpreter emulates the rounding mode by putting the host FPU in it,
|
|
// so it has to switch right here rather than at the next block boundary.
|
|
// Restore first: the new value may be back to the default, which Apply
|
|
// deliberately doesn't write.
|
|
RestoreHostRoundingMode();
|
|
ApplyHostRoundingMode(mips);
|
|
}
|
|
} else {
|
|
WARN_LOG_REPORT(Log::CPU, "WriteFCR: Unexpected reg %d (value %08x)", fs, value);
|
|
}
|
|
DEBUG_LOG(Log::CPU, "FCR%i written to, value %08x", fs, value);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
// Dummy COP0 register file, for kernel-mode code (e.g. flash0:/reboot.bin) that reads/
|
|
// writes COP0 state directly - ordinary PSP user-mode code never executes these
|
|
// instructions (kernel state is reached via HLE syscalls instead), so this doesn't need
|
|
// to model real COP0 semantics (interrupts, exceptions, TLB, ...), just be non-fatal and
|
|
// give writes-then-reads-back-same-value behavior. Not part of MIPSState (which is
|
|
// layout-sensitive for JIT register allocation) and not save-stated, in keeping with
|
|
// "dummy" - revisit if this ever needs to be more than a bail-out.
|
|
static u32 g_cop0Regs[32];
|
|
|
|
void Int_Cop0(MIPSState *mips, MIPSOpcode op) {
|
|
int rt = _RT;
|
|
int rd = _RD;
|
|
|
|
switch ((op >> 21) & 0x1f) {
|
|
case 0: //mfc0
|
|
if (rt != 0) {
|
|
// Real MIPS COP0 register 9 (Count) free-runs on its own (incrementing every
|
|
// other cycle) regardless of software writes - unlike the rest of this dummy
|
|
// shadow file, a plain "read back whatever was last written" (defaulting to a
|
|
// constant 0) would be wrong here and could plausibly starve boot-time
|
|
// calibration code of a nonzero seed/divisor. Tie it to CoreTiming instead;
|
|
// still not real COP0 semantics (no Compare-triggered interrupt), just a
|
|
// closer approximation. See docs/VSHBootInvestigation.md "Attempt 9".
|
|
if (rd == 9)
|
|
R(rt) = (u32)CoreTiming::GetTicks(mips);
|
|
else
|
|
R(rt) = g_cop0Regs[rd];
|
|
}
|
|
break;
|
|
|
|
case 4: //mtc0
|
|
g_cop0Regs[rd] = R(rt);
|
|
break;
|
|
|
|
case 10: //rdpgpr
|
|
if (rt != 0)
|
|
R(rt) = R(rd);
|
|
break;
|
|
|
|
case 11: //mfmc0 (di/ei)
|
|
if (rt != 0)
|
|
R(rt) = g_cop0Regs[12]; // Status
|
|
break;
|
|
|
|
case 14: //wrpgpr
|
|
R(rd) = R(rt);
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false, "Trying to interpret COP0 instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_RType2(MIPSState *mips, MIPSOpcode op) {
|
|
int rs = _RS;
|
|
int rd = _RD;
|
|
|
|
// Don't change $zr.
|
|
if (rd == 0) {
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
switch (op & 63) {
|
|
case 22: //clz
|
|
R(rd) = clz32(R(rs));
|
|
break;
|
|
case 23: //clo
|
|
R(rd) = clz32(~R(rs));
|
|
break;
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_MulDivType(MIPSState *mips, MIPSOpcode 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 46: //msub
|
|
{
|
|
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 47: //msubu
|
|
{
|
|
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: if (rd != 0) R(rd) = HI; break; //mfhi
|
|
case 17: HI = R(rs); break; //mthi
|
|
case 18: if (rd != 0) 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 (a == (s32)0x80000000 && b == -1) {
|
|
// The one overflow. Hardware leaves the remainder at zero (cpu/cpu_alu/cpu_div).
|
|
LO = 0x80000000;
|
|
HI = 0;
|
|
} else if (b != 0) {
|
|
LO = (u32)(a / b);
|
|
HI = (u32)(a % b);
|
|
} else {
|
|
LO = a < 0 ? 1 : -1;
|
|
HI = a;
|
|
}
|
|
}
|
|
break;
|
|
case 27: //divu
|
|
{
|
|
u32 a = R(rs);
|
|
u32 b = R(rt);
|
|
if (b != 0) {
|
|
LO = (a/b);
|
|
HI = (a%b);
|
|
} else {
|
|
LO = a <= 0xFFFF ? 0xFFFF : -1;
|
|
HI = a;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
|
|
void Int_ShiftType(MIPSState *mips, MIPSOpcode op)
|
|
{
|
|
int rt = _RT;
|
|
int rs = _RS;
|
|
int rd = _RD;
|
|
int sa = _FD;
|
|
|
|
// Don't change $zr.
|
|
if (rd == 0)
|
|
{
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
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_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Allegrex(MIPSState *mips, MIPSOpcode op) {
|
|
int rt = _RT;
|
|
int rd = _RD;
|
|
|
|
// Don't change $zr.
|
|
if (rd == 0) {
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
switch ((op >> 6) & 31) {
|
|
case 16: // seb
|
|
R(rd) = SignExtend8ToU32(R(rt));
|
|
break;
|
|
|
|
case 20: // bitrev
|
|
R(rd) = ReverseBits32(R(rt));
|
|
break;
|
|
|
|
case 24: // seh
|
|
R(rd) = SignExtend16ToU32(R(rt));
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret ALLEGREX instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Allegrex2(MIPSState *mips, MIPSOpcode op) {
|
|
int rt = _RT;
|
|
int rd = _RD;
|
|
|
|
// Don't change $zr.
|
|
if (rd == 0) {
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
switch (op & 0x3ff) {
|
|
case 0xA0: //wsbh
|
|
R(rd) = ((R(rt) & 0xFF00FF00) >> 8) | ((R(rt) & 0x00FF00FF) << 8);
|
|
break;
|
|
case 0xE0: //wsbw
|
|
R(rd) = swap32(R(rt));
|
|
break;
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret ALLEGREX instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
// The interrupt enable flag, shared with the HLE sceKernelCpuSuspendIntr/ResumeIntr, which on
|
|
// hardware are just mfic v0, $0; mtic zero, $0 and mtic a0, $0. Only $0 exists.
|
|
void Int_Special2(MIPSState *mips, MIPSOpcode op) {
|
|
int rt = _RT;
|
|
switch (op & 0x3F) {
|
|
case 36: // mfic
|
|
if (rt != 0)
|
|
R(rt) = __InterruptsEnabled() ? 1 : 0;
|
|
break;
|
|
case 38: // mtic
|
|
// Only bit 0 counts: mtic 2 disables. Pending interrupts run at the next scheduling point,
|
|
// not right away as with sceKernelCpuResumeIntr.
|
|
if (R(rt) & 1)
|
|
__EnableInterrupts();
|
|
else
|
|
__DisableInterrupts();
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Special3(MIPSState *mips, MIPSOpcode op) {
|
|
int rs = _RS;
|
|
int rt = _RT;
|
|
int pos = _POS;
|
|
|
|
// Don't change $zr.
|
|
if (rt == 0) {
|
|
PC += 4;
|
|
return;
|
|
}
|
|
|
|
switch (op & 0x3f) {
|
|
case 0x0: //ext
|
|
{
|
|
int size = _SIZE + 1;
|
|
u32 sourcemask = 0xFFFFFFFFUL >> (32 - size);
|
|
R(rt) = (R(rs) >> pos) & sourcemask;
|
|
}
|
|
break;
|
|
case 0x4: //ins
|
|
{
|
|
// The size field actually holds msb (= pos + size - 1), so build the mask from
|
|
// that and shift it down, the way the JITs do. Computing the width as
|
|
// (_SIZE + 1) - pos instead would shift by 32 or more when msb < pos - undefined
|
|
// behavior, and on x86 it yields an all-ones mask that writes bits the JITs leave
|
|
// alone. Hardware calls that encoding unpredictable, so all we need is to be
|
|
// consistent and not invoke UB.
|
|
const u32 mask = 0xFFFFFFFFUL >> (31 - _SIZE);
|
|
const u32 sourcemask = mask >> pos;
|
|
const u32 destmask = sourcemask << pos;
|
|
R(rt) = (R(rt) & ~destmask) | ((R(rs) & sourcemask) << pos);
|
|
}
|
|
break;
|
|
}
|
|
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_FPU2op(MIPSState *mips, MIPSOpcode op) {
|
|
int fs = _FS;
|
|
int fd = _FD;
|
|
|
|
switch (op & 0x3f)
|
|
{
|
|
case 4: //sqrt
|
|
F(fd) = sqrtf(F(fs));
|
|
// A negative input gives a positive NaN, not the host's (cpu/fpu/roundmode).
|
|
if (F(fs) < 0.0f) {
|
|
FsI(fd) = 0x7FC00000;
|
|
}
|
|
break;
|
|
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:
|
|
case 13:
|
|
case 14:
|
|
case 15:
|
|
switch (op & 0x3f)
|
|
{
|
|
// round.w.s is round-half-to-even, not half-away-from-zero - and its mode is fixed,
|
|
// so unlike cvt.w.s below it must not follow fcr31. round_ieee_754 is both.
|
|
case 12: FsI(fd) = SaturatedFloatToInt(round_ieee_754(F(fs))); break; //round.w.s
|
|
case 13: FsI(fd) = SaturatedFloatToInt(truncf(F(fs))); break; //trunc.w.s
|
|
case 14: FsI(fd) = SaturatedFloatToInt(ceilf(F(fs))); break; //ceil.w.s
|
|
case 15: FsI(fd) = SaturatedFloatToInt(floorf(F(fs))); break; //floor.w.s
|
|
}
|
|
break;
|
|
case 32: F(fd) = (float)FsI(fs); break; //cvt.s.w
|
|
|
|
case 36:
|
|
switch (mips->fcr31 & 3)
|
|
{
|
|
case 0: FsI(fd) = SaturatedFloatToInt(round_ieee_754(F(fs))); break; // RINT_0
|
|
case 1: FsI(fd) = SaturatedFloatToInt(truncf(F(fs))); break; // CAST_1
|
|
case 2: FsI(fd) = SaturatedFloatToInt(ceilf(F(fs))); break; // CEIL_2
|
|
case 3: FsI(fd) = SaturatedFloatToInt(floorf(F(fs))); break; // FLOOR_3
|
|
}
|
|
break; //cvt.w.s
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret FPU2Op instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_FPUComp(MIPSState *mips, MIPSOpcode op) {
|
|
int fs = _FS;
|
|
int ft = _FT;
|
|
bool cond;
|
|
switch (op & 0xf)
|
|
{
|
|
case 0: //f
|
|
case 8: //sf
|
|
cond = false;
|
|
break;
|
|
|
|
case 1: //un
|
|
case 9: //ngle
|
|
cond = my_isnan(F(fs)) || my_isnan(F(ft));
|
|
break;
|
|
|
|
case 2: //eq
|
|
case 10: //seq
|
|
cond = !my_isnan(F(fs)) && !my_isnan(F(ft)) && (F(fs) == F(ft));
|
|
break;
|
|
|
|
case 3: //ueq
|
|
case 11: //ngl
|
|
cond = (F(fs) == F(ft)) || my_isnan(F(fs)) || my_isnan(F(ft));
|
|
break;
|
|
|
|
case 4: //olt
|
|
case 12: //lt
|
|
cond = (F(fs) < F(ft));
|
|
break;
|
|
|
|
case 5: //ult
|
|
case 13: //nge
|
|
cond = (F(fs) < F(ft)) || my_isnan(F(fs)) || my_isnan(F(ft));
|
|
break;
|
|
|
|
case 6: //ole
|
|
case 14: //le
|
|
cond = (F(fs) <= F(ft));
|
|
break;
|
|
|
|
case 7: //ule
|
|
case 15: //ngt
|
|
cond = (F(fs) <= F(ft)) || my_isnan(F(fs)) || my_isnan(F(ft));
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret FPUComp instruction that can't be interpreted");
|
|
cond = false;
|
|
break;
|
|
}
|
|
mips->fpcond = cond;
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_FPU3op(MIPSState *mips, MIPSOpcode op) {
|
|
int ft = _FT;
|
|
int fs = _FS;
|
|
int fd = _FD;
|
|
|
|
switch (op & 0x3f)
|
|
{
|
|
case 0: F(fd) = F(fs) + F(ft); break; // add.s
|
|
case 1: F(fd) = F(fs) - F(ft); break; // sub.s
|
|
case 2: // mul.s
|
|
if ((my_isinf(F(fs)) && F(ft) == 0.0f) || (my_isinf(F(ft)) && F(fs) == 0.0f)) {
|
|
// Must be positive NAN, see #12519.
|
|
FI(fd) = 0x7fc00000;
|
|
} else {
|
|
F(fd) = F(fs) * F(ft);
|
|
}
|
|
break;
|
|
case 3: // div.s
|
|
{
|
|
// The only FPU exception we implement so far. IEEE 754 raises divide-by-zero only when
|
|
// an exact infinity comes out of finite operands, so 0/0 (invalid operation, which we
|
|
// don't detect) and inf/0 and NaN operands (no exception at all) are all excluded.
|
|
// Cause is per-instruction, so clear it here rather than leaving the last one set.
|
|
// Real hardware clears all five Cause bits in every FPU op, we only manage this one.
|
|
mips->fcr31 &= ~FCR31_CAUSE_DIV_BY_ZERO;
|
|
if (F(ft) == 0.0f && F(fs) != 0.0f && !my_isnanorinf(F(fs))) {
|
|
mips->fcr31 |= FCR31_CAUSE_DIV_BY_ZERO;
|
|
if (mips->fcr31 & FCR31_ENABLE_DIV_BY_ZERO) {
|
|
// Unmasked. Hardware takes the trap here, leaving fd alone and not setting the
|
|
// sticky flag - the handler is expected to deal with it. We can't run the
|
|
// guest's handler, and PSP threads start with this trap enabled (fcr31 =
|
|
// 0x00000e00) while games divide by zero all the time, so acting on it at all
|
|
// is a developer opt-in. The fcr31 bits above are updated either way, so what
|
|
// the game sees when it reads the register doesn't depend on the setting.
|
|
if (g_Config.bEnableFPUExceptionTraps) {
|
|
Core_FPUException(PC, FPUExceptionType::DIVIDE_BY_ZERO);
|
|
break;
|
|
}
|
|
} else {
|
|
// Masked: sticky flag, and the default result (a correctly signed infinity).
|
|
mips->fcr31 |= FCR31_FLAG_DIV_BY_ZERO;
|
|
}
|
|
}
|
|
F(fd) = F(fs) / F(ft);
|
|
break;
|
|
}
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret FPU3Op instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Interrupt(MIPSState *mips, MIPSOpcode op) {
|
|
static int reported = 0;
|
|
switch (op & 1)
|
|
{
|
|
case 0:
|
|
// unlikely to be legitimately used
|
|
if (!reported) {
|
|
Reporting::ReportMessage("INTERRUPT instruction hit (%08x) at %08x", op.encoding, mips->pc);
|
|
WARN_LOG(Log::CPU, "Disable/Enable Interrupt CPU instruction");
|
|
reported = 1;
|
|
}
|
|
break;
|
|
}
|
|
PC += 4;
|
|
}
|
|
|
|
void Int_Emuhack(MIPSState *mips, MIPSOpcode op) {
|
|
_assert_((PC & 3) == 0);
|
|
|
|
// The CPU cores substitute these for replaced instructions, memory never holds them.
|
|
// If it does, the game is executing data.
|
|
if (Memory::Read_Instruction(PC).encoding == op.encoding) {
|
|
Core_ExecException(PC, PC, ExecExceptionType::ILLEGAL);
|
|
return;
|
|
}
|
|
|
|
// It's a replacement func!
|
|
int index = op.encoding & 0xFFFFFF;
|
|
const ReplacementTableEntry *entry = GetReplacementFunc(index);
|
|
if (entry && entry->replaceFunc && (entry->flags & REPFLAG_DISABLED) == 0) {
|
|
// Like a syscall, a replacement function is host code - see Int_Syscall.
|
|
RestoreHostRoundingMode();
|
|
int cycles = entry->replaceFunc();
|
|
ApplyHostRoundingMode(mips);
|
|
|
|
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
|
// Interpret the original instruction under the hook.
|
|
MIPSInterpret(mips, Memory::Read_Instruction(PC));
|
|
} else if (cycles < 0) {
|
|
// Leave PC unchanged, call the replacement again (assumes args are modified.)
|
|
mips->downcount += cycles;
|
|
} else {
|
|
PC = mips->r[MIPS_REG_RA];
|
|
mips->downcount -= cycles;
|
|
}
|
|
} else {
|
|
if (!entry || !entry->replaceFunc) {
|
|
ERROR_LOG(Log::CPU, "Bad replacement function index %i", index);
|
|
}
|
|
// Interpret the original instruction under it.
|
|
MIPSInterpret(mips, Memory::Read_Instruction(PC));
|
|
}
|
|
}
|
|
}
|