mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
The prefix fields were decoded by hand in the interpreter, the IR frontend, JitState, MIPSState and the disassembler, with three copies of the constant table. They now share small decoders and one table, in MIPS.h. The backends' Vec4Shuffle lane decoding gets a helper of its own in IRInst.h. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1424 lines
47 KiB
C++
1424 lines
47 KiB
C++
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include "Common/BitSet.h"
|
|
#include "Common/BitScan.h"
|
|
#include "Common/Common.h"
|
|
#include "Common/CommonFuncs.h"
|
|
#include "Common/Data/Convert/SmallDataConvert.h"
|
|
#include "Common/Math/math_util.h"
|
|
#include "Common/Math/SIMDHeaders.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/ReplaceTables.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/MIPS/MIPSVFPUUtils.h"
|
|
#include "Core/MIPS/IR/IRInst.h"
|
|
#include "Core/MIPS/IR/IRInterpreter.h"
|
|
#include "Core/System.h"
|
|
#include "Core/MIPS/MIPSTracer.h"
|
|
|
|
#ifdef mips
|
|
// Why do MIPS compilers define something so generic? Try to keep defined, at least...
|
|
#undef mips
|
|
#define mips mips
|
|
#endif
|
|
|
|
alignas(16) static const float vec4InitValues[8][4] = {
|
|
{ 0.0f, 0.0f, 0.0f, 0.0f },
|
|
{ 1.0f, 1.0f, 1.0f, 1.0f },
|
|
{ -1.0f, -1.0f, -1.0f, -1.0f },
|
|
{ 1.0f, 0.0f, 0.0f, 0.0f },
|
|
{ 0.0f, 1.0f, 0.0f, 0.0f },
|
|
{ 0.0f, 0.0f, 1.0f, 0.0f },
|
|
{ 0.0f, 0.0f, 0.0f, 1.0f },
|
|
};
|
|
|
|
alignas(16) static const uint32_t signBits[4] = {
|
|
0x80000000, 0x80000000, 0x80000000, 0x80000000,
|
|
};
|
|
|
|
alignas(16) static const uint32_t noSignMask[4] = {
|
|
0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
|
|
};
|
|
|
|
alignas(16) static const uint32_t lowBytesMask[4] = {
|
|
0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF,
|
|
};
|
|
|
|
u32 IRRunBreakpoint(u32 pc) {
|
|
// Should we skip this breakpoint?
|
|
uint32_t skipFirst = g_breakpoints.CheckSkipFirst();
|
|
if (skipFirst == pc || skipFirst == currentMIPS->pc)
|
|
return 0;
|
|
|
|
// Did we already hit one?
|
|
if (coreState != CORE_RUNNING_CPU && coreState != CORE_NEXTFRAME)
|
|
return 1;
|
|
|
|
g_breakpoints.ExecBreakPoint(pc);
|
|
return coreState != CORE_RUNNING_CPU ? 1 : 0;
|
|
}
|
|
|
|
u32 IRRunMemCheck(u32 pc, u32 addr) {
|
|
// Should we skip this breakpoint?
|
|
uint32_t skipFirst = g_breakpoints.CheckSkipFirst();
|
|
if (skipFirst == pc || skipFirst == currentMIPS->pc)
|
|
return 0;
|
|
|
|
// Did we already hit one?
|
|
if (coreState != CORE_RUNNING_CPU && coreState != CORE_NEXTFRAME)
|
|
return 1;
|
|
|
|
g_breakpoints.ExecOpMemCheck(addr, pc);
|
|
return coreState != CORE_RUNNING_CPU ? 1 : 0;
|
|
}
|
|
|
|
// With GCC and Clang, each op jumps straight to the next op's handler through a table ("threaded"
|
|
// dispatch), which predicts much better than the one shared indirect jump of a switch. Other
|
|
// compilers get the switch. Ops missing from the table fall back to the switch, so they still work.
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define IR_THREADED_DISPATCH 1
|
|
#define IR_CASE(op) case IROp::op: L_##op:
|
|
#else
|
|
#define IR_CASE(op) case IROp::op:
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
#define IR_CHECK_ZERO_REG() if (mips->r[0] != 0) Crash();
|
|
#else
|
|
#define IR_CHECK_ZERO_REG()
|
|
#endif
|
|
|
|
#if IR_THREADED_DISPATCH
|
|
#define IR_NEXT do { IR_CHECK_ZERO_REG(); inst++; goto *dispatch[(int)inst->op]; } while (false)
|
|
#else
|
|
#define IR_NEXT { IR_CHECK_ZERO_REG(); inst++; continue; }
|
|
#endif
|
|
|
|
u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
|
|
#if IR_THREADED_DISPATCH
|
|
static const void *dispatch[256];
|
|
static bool dispatchReady = false;
|
|
if (!dispatchReady) {
|
|
for (const void *&target : dispatch)
|
|
target = &&L_switch;
|
|
dispatch[(int)IROp::SetConst] = &&L_SetConst;
|
|
dispatch[(int)IROp::SetConstF] = &&L_SetConstF;
|
|
dispatch[(int)IROp::Add] = &&L_Add;
|
|
dispatch[(int)IROp::Sub] = &&L_Sub;
|
|
dispatch[(int)IROp::And] = &&L_And;
|
|
dispatch[(int)IROp::Or] = &&L_Or;
|
|
dispatch[(int)IROp::Xor] = &&L_Xor;
|
|
dispatch[(int)IROp::Mov] = &&L_Mov;
|
|
dispatch[(int)IROp::AddConst] = &&L_AddConst;
|
|
dispatch[(int)IROp::OptAddConst] = &&L_OptAddConst;
|
|
dispatch[(int)IROp::SubConst] = &&L_SubConst;
|
|
dispatch[(int)IROp::AndConst] = &&L_AndConst;
|
|
dispatch[(int)IROp::OptAndConst] = &&L_OptAndConst;
|
|
dispatch[(int)IROp::OrConst] = &&L_OrConst;
|
|
dispatch[(int)IROp::OptOrConst] = &&L_OptOrConst;
|
|
dispatch[(int)IROp::XorConst] = &&L_XorConst;
|
|
dispatch[(int)IROp::Neg] = &&L_Neg;
|
|
dispatch[(int)IROp::Not] = &&L_Not;
|
|
dispatch[(int)IROp::Ext8to32] = &&L_Ext8to32;
|
|
dispatch[(int)IROp::Ext16to32] = &&L_Ext16to32;
|
|
dispatch[(int)IROp::ReverseBits] = &&L_ReverseBits;
|
|
dispatch[(int)IROp::Load8] = &&L_Load8;
|
|
dispatch[(int)IROp::Load8Ext] = &&L_Load8Ext;
|
|
dispatch[(int)IROp::Load16] = &&L_Load16;
|
|
dispatch[(int)IROp::Load16Ext] = &&L_Load16Ext;
|
|
dispatch[(int)IROp::Load32] = &&L_Load32;
|
|
dispatch[(int)IROp::Load32Left] = &&L_Load32Left;
|
|
dispatch[(int)IROp::Load32Right] = &&L_Load32Right;
|
|
dispatch[(int)IROp::Load32Linked] = &&L_Load32Linked;
|
|
dispatch[(int)IROp::LoadFloat] = &&L_LoadFloat;
|
|
dispatch[(int)IROp::Store8] = &&L_Store8;
|
|
dispatch[(int)IROp::Store16] = &&L_Store16;
|
|
dispatch[(int)IROp::Store32] = &&L_Store32;
|
|
dispatch[(int)IROp::Store32Left] = &&L_Store32Left;
|
|
dispatch[(int)IROp::Store32Right] = &&L_Store32Right;
|
|
dispatch[(int)IROp::Store32Conditional] = &&L_Store32Conditional;
|
|
dispatch[(int)IROp::StoreFloat] = &&L_StoreFloat;
|
|
dispatch[(int)IROp::LoadVec4] = &&L_LoadVec4;
|
|
dispatch[(int)IROp::StoreVec4] = &&L_StoreVec4;
|
|
dispatch[(int)IROp::Vec4Init] = &&L_Vec4Init;
|
|
dispatch[(int)IROp::Vec4Shuffle] = &&L_Vec4Shuffle;
|
|
dispatch[(int)IROp::Vec4Blend] = &&L_Vec4Blend;
|
|
dispatch[(int)IROp::Vec4Mov] = &&L_Vec4Mov;
|
|
dispatch[(int)IROp::Vec4Add] = &&L_Vec4Add;
|
|
dispatch[(int)IROp::Vec4Sub] = &&L_Vec4Sub;
|
|
dispatch[(int)IROp::Vec4Mul] = &&L_Vec4Mul;
|
|
dispatch[(int)IROp::Vec4Div] = &&L_Vec4Div;
|
|
dispatch[(int)IROp::Vec4Scale] = &&L_Vec4Scale;
|
|
dispatch[(int)IROp::Vec4Neg] = &&L_Vec4Neg;
|
|
dispatch[(int)IROp::Vec4Abs] = &&L_Vec4Abs;
|
|
dispatch[(int)IROp::Vec2Unpack16To31] = &&L_Vec2Unpack16To31;
|
|
dispatch[(int)IROp::Vec2Unpack16To32] = &&L_Vec2Unpack16To32;
|
|
dispatch[(int)IROp::Vec4Unpack8To32] = &&L_Vec4Unpack8To32;
|
|
dispatch[(int)IROp::Vec2Pack32To16] = &&L_Vec2Pack32To16;
|
|
dispatch[(int)IROp::Vec2Pack31To16] = &&L_Vec2Pack31To16;
|
|
dispatch[(int)IROp::Vec4Pack32To8] = &&L_Vec4Pack32To8;
|
|
dispatch[(int)IROp::Vec4Pack31To8] = &&L_Vec4Pack31To8;
|
|
dispatch[(int)IROp::Vec4DuplicateUpperBitsAndShift1] = &&L_Vec4DuplicateUpperBitsAndShift1;
|
|
dispatch[(int)IROp::FCmpVfpuBit] = &&L_FCmpVfpuBit;
|
|
dispatch[(int)IROp::FCmpVfpuAggregate] = &&L_FCmpVfpuAggregate;
|
|
dispatch[(int)IROp::FCmovVfpuCC] = &&L_FCmovVfpuCC;
|
|
dispatch[(int)IROp::Vec4Dot] = &&L_Vec4Dot;
|
|
dispatch[(int)IROp::FSin] = &&L_FSin;
|
|
dispatch[(int)IROp::FCos] = &&L_FCos;
|
|
dispatch[(int)IROp::FRSqrt] = &&L_FRSqrt;
|
|
dispatch[(int)IROp::FRecip] = &&L_FRecip;
|
|
dispatch[(int)IROp::FAsin] = &&L_FAsin;
|
|
dispatch[(int)IROp::FVSqrt] = &&L_FVSqrt;
|
|
dispatch[(int)IROp::FExp2] = &&L_FExp2;
|
|
dispatch[(int)IROp::FLog2] = &&L_FLog2;
|
|
dispatch[(int)IROp::FSinCos] = &&L_FSinCos;
|
|
dispatch[(int)IROp::FHalfToFloat] = &&L_FHalfToFloat;
|
|
dispatch[(int)IROp::ShlImm] = &&L_ShlImm;
|
|
dispatch[(int)IROp::ShrImm] = &&L_ShrImm;
|
|
dispatch[(int)IROp::SarImm] = &&L_SarImm;
|
|
dispatch[(int)IROp::RorImm] = &&L_RorImm;
|
|
dispatch[(int)IROp::Shl] = &&L_Shl;
|
|
dispatch[(int)IROp::Shr] = &&L_Shr;
|
|
dispatch[(int)IROp::Sar] = &&L_Sar;
|
|
dispatch[(int)IROp::Ror] = &&L_Ror;
|
|
dispatch[(int)IROp::Clz] = &&L_Clz;
|
|
dispatch[(int)IROp::Slt] = &&L_Slt;
|
|
dispatch[(int)IROp::SltU] = &&L_SltU;
|
|
dispatch[(int)IROp::SltConst] = &&L_SltConst;
|
|
dispatch[(int)IROp::SltUConst] = &&L_SltUConst;
|
|
dispatch[(int)IROp::MovZ] = &&L_MovZ;
|
|
dispatch[(int)IROp::MovNZ] = &&L_MovNZ;
|
|
dispatch[(int)IROp::Max] = &&L_Max;
|
|
dispatch[(int)IROp::Min] = &&L_Min;
|
|
dispatch[(int)IROp::MtLo] = &&L_MtLo;
|
|
dispatch[(int)IROp::MtHi] = &&L_MtHi;
|
|
dispatch[(int)IROp::MfLo] = &&L_MfLo;
|
|
dispatch[(int)IROp::MfHi] = &&L_MfHi;
|
|
dispatch[(int)IROp::Mult] = &&L_Mult;
|
|
dispatch[(int)IROp::MultU] = &&L_MultU;
|
|
dispatch[(int)IROp::Madd] = &&L_Madd;
|
|
dispatch[(int)IROp::MaddU] = &&L_MaddU;
|
|
dispatch[(int)IROp::Msub] = &&L_Msub;
|
|
dispatch[(int)IROp::MsubU] = &&L_MsubU;
|
|
dispatch[(int)IROp::Div] = &&L_Div;
|
|
dispatch[(int)IROp::DivU] = &&L_DivU;
|
|
dispatch[(int)IROp::BSwap16] = &&L_BSwap16;
|
|
dispatch[(int)IROp::BSwap32] = &&L_BSwap32;
|
|
dispatch[(int)IROp::FAdd] = &&L_FAdd;
|
|
dispatch[(int)IROp::FSub] = &&L_FSub;
|
|
dispatch[(int)IROp::FMul] = &&L_FMul;
|
|
dispatch[(int)IROp::FDiv] = &&L_FDiv;
|
|
dispatch[(int)IROp::FMin] = &&L_FMin;
|
|
dispatch[(int)IROp::FMax] = &&L_FMax;
|
|
dispatch[(int)IROp::FMov] = &&L_FMov;
|
|
dispatch[(int)IROp::FAbs] = &&L_FAbs;
|
|
dispatch[(int)IROp::FSqrt] = &&L_FSqrt;
|
|
dispatch[(int)IROp::FNeg] = &&L_FNeg;
|
|
dispatch[(int)IROp::FSat0_1] = &&L_FSat0_1;
|
|
dispatch[(int)IROp::FSatMinus1_1] = &&L_FSatMinus1_1;
|
|
dispatch[(int)IROp::FSign] = &&L_FSign;
|
|
dispatch[(int)IROp::FpCondFromReg] = &&L_FpCondFromReg;
|
|
dispatch[(int)IROp::FpCondToReg] = &&L_FpCondToReg;
|
|
dispatch[(int)IROp::FpCtrlFromReg] = &&L_FpCtrlFromReg;
|
|
dispatch[(int)IROp::FpCtrlToReg] = &&L_FpCtrlToReg;
|
|
dispatch[(int)IROp::VfpuCtrlToReg] = &&L_VfpuCtrlToReg;
|
|
dispatch[(int)IROp::FRound] = &&L_FRound;
|
|
dispatch[(int)IROp::FTrunc] = &&L_FTrunc;
|
|
dispatch[(int)IROp::FCeil] = &&L_FCeil;
|
|
dispatch[(int)IROp::FFloor] = &&L_FFloor;
|
|
dispatch[(int)IROp::FCmp] = &&L_FCmp;
|
|
dispatch[(int)IROp::FCvtSW] = &&L_FCvtSW;
|
|
dispatch[(int)IROp::FCvtWS] = &&L_FCvtWS;
|
|
dispatch[(int)IROp::FCvtScaledSW] = &&L_FCvtScaledSW;
|
|
dispatch[(int)IROp::FCvtScaledWS] = &&L_FCvtScaledWS;
|
|
dispatch[(int)IROp::FMovFromGPR] = &&L_FMovFromGPR;
|
|
dispatch[(int)IROp::OptFCvtSWFromGPR] = &&L_OptFCvtSWFromGPR;
|
|
dispatch[(int)IROp::FMovToGPR] = &&L_FMovToGPR;
|
|
dispatch[(int)IROp::OptFMovToGPRShr8] = &&L_OptFMovToGPRShr8;
|
|
dispatch[(int)IROp::ExitToConst] = &&L_ExitToConst;
|
|
dispatch[(int)IROp::ExitToReg] = &&L_ExitToReg;
|
|
dispatch[(int)IROp::OptExitToConstIfEqElse] = &&L_OptExitToConstIfEqElse;
|
|
dispatch[(int)IROp::OptExitToConstIfNeqElse] = &&L_OptExitToConstIfNeqElse;
|
|
dispatch[(int)IROp::OptExitToConstIfGtZElse] = &&L_OptExitToConstIfGtZElse;
|
|
dispatch[(int)IROp::OptExitToConstIfGeZElse] = &&L_OptExitToConstIfGeZElse;
|
|
dispatch[(int)IROp::OptExitToConstIfLtZElse] = &&L_OptExitToConstIfLtZElse;
|
|
dispatch[(int)IROp::OptExitToConstIfLeZElse] = &&L_OptExitToConstIfLeZElse;
|
|
dispatch[(int)IROp::ExitToConstIfEq] = &&L_ExitToConstIfEq;
|
|
dispatch[(int)IROp::ExitToConstIfNeq] = &&L_ExitToConstIfNeq;
|
|
dispatch[(int)IROp::ExitToConstIfGtZ] = &&L_ExitToConstIfGtZ;
|
|
dispatch[(int)IROp::ExitToConstIfGeZ] = &&L_ExitToConstIfGeZ;
|
|
dispatch[(int)IROp::ExitToConstIfLtZ] = &&L_ExitToConstIfLtZ;
|
|
dispatch[(int)IROp::ExitToConstIfLeZ] = &&L_ExitToConstIfLeZ;
|
|
dispatch[(int)IROp::Downcount] = &&L_Downcount;
|
|
dispatch[(int)IROp::SetPC] = &&L_SetPC;
|
|
dispatch[(int)IROp::SetPCConst] = &&L_SetPCConst;
|
|
dispatch[(int)IROp::Syscall] = &&L_Syscall;
|
|
dispatch[(int)IROp::SyscallUnresolved] = &&L_SyscallUnresolved;
|
|
dispatch[(int)IROp::ExitToPC] = &&L_ExitToPC;
|
|
dispatch[(int)IROp::Interpret] = &&L_Interpret;
|
|
dispatch[(int)IROp::CallReplacement] = &&L_CallReplacement;
|
|
dispatch[(int)IROp::SetCtrlVFPU] = &&L_SetCtrlVFPU;
|
|
dispatch[(int)IROp::SetCtrlVFPUReg] = &&L_SetCtrlVFPUReg;
|
|
dispatch[(int)IROp::SetCtrlVFPUFReg] = &&L_SetCtrlVFPUFReg;
|
|
dispatch[(int)IROp::ApplyRoundingMode] = &&L_ApplyRoundingMode;
|
|
dispatch[(int)IROp::RestoreRoundingMode] = &&L_RestoreRoundingMode;
|
|
dispatch[(int)IROp::UpdateRoundingMode] = &&L_UpdateRoundingMode;
|
|
dispatch[(int)IROp::Break] = &&L_Break;
|
|
dispatch[(int)IROp::Breakpoint] = &&L_Breakpoint;
|
|
dispatch[(int)IROp::MemoryCheck] = &&L_MemoryCheck;
|
|
dispatch[(int)IROp::ValidateAddress8] = &&L_ValidateAddress8;
|
|
dispatch[(int)IROp::ValidateAddress16] = &&L_ValidateAddress16;
|
|
dispatch[(int)IROp::ValidateAddress32] = &&L_ValidateAddress32;
|
|
dispatch[(int)IROp::ValidateAddress128] = &&L_ValidateAddress128;
|
|
dispatch[(int)IROp::LogIRBlock] = &&L_LogIRBlock;
|
|
dispatch[(int)IROp::Nop] = &&L_Nop;
|
|
dispatch[(int)IROp::Bad] = &&L_Bad;
|
|
dispatchReady = true;
|
|
}
|
|
#endif
|
|
|
|
while (true) {
|
|
#if IR_THREADED_DISPATCH
|
|
L_switch:
|
|
#endif
|
|
switch (inst->op) {
|
|
IR_CASE(SetConst)
|
|
mips->r[inst->dest] = inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(SetConstF)
|
|
memcpy(&mips->f[inst->dest], &inst->constant, 4);
|
|
IR_NEXT;
|
|
IR_CASE(Add)
|
|
mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(Sub)
|
|
mips->r[inst->dest] = mips->r[inst->src1] - mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(And)
|
|
mips->r[inst->dest] = mips->r[inst->src1] & mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(Or)
|
|
mips->r[inst->dest] = mips->r[inst->src1] | mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(Xor)
|
|
mips->r[inst->dest] = mips->r[inst->src1] ^ mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(Mov)
|
|
mips->r[inst->dest] = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(AddConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] + inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(OptAddConst) // For this one, it's worth having a "unary" variant of the above that only needs to read one register param.
|
|
mips->r[inst->dest] += inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(SubConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] - inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(AndConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] & inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(OptAndConst) // For this one, it's worth having a "unary" variant of the above that only needs to read one register param.
|
|
mips->r[inst->dest] &= inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(OrConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] | inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(OptOrConst)
|
|
mips->r[inst->dest] |= inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(XorConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(Neg)
|
|
mips->r[inst->dest] = (u32)(-(s32)mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(Not)
|
|
mips->r[inst->dest] = ~mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(Ext8to32)
|
|
mips->r[inst->dest] = SignExtend8ToU32(mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(Ext16to32)
|
|
mips->r[inst->dest] = SignExtend16ToU32(mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(ReverseBits)
|
|
mips->r[inst->dest] = ReverseBits32(mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Load8)
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Load8Ext)
|
|
mips->r[inst->dest] = SignExtend8ToU32(Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant));
|
|
IR_NEXT;
|
|
IR_CASE(Load16)
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Load16Ext)
|
|
mips->r[inst->dest] = SignExtend16ToU32(Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant));
|
|
IR_NEXT;
|
|
IR_CASE(Load32)
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Load32Left)
|
|
{
|
|
u32 addr = mips->r[inst->src1] + inst->constant;
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 destMask = 0x00ffffff >> shift;
|
|
mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem << (24 - shift));
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Load32Right)
|
|
{
|
|
u32 addr = mips->r[inst->src1] + inst->constant;
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 destMask = 0xffffff00 << (24 - shift);
|
|
mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Load32Linked)
|
|
if (inst->dest != MIPS_REG_ZERO)
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
|
|
mips->llBit = 1;
|
|
IR_NEXT;
|
|
IR_CASE(LoadFloat)
|
|
mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Store8)
|
|
Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Store16)
|
|
Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Store32)
|
|
Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
IR_CASE(Store32Left)
|
|
{
|
|
u32 addr = mips->r[inst->src1] + inst->constant;
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 memMask = 0xffffff00 << shift;
|
|
u32 result = (mips->r[inst->src3] >> (24 - shift)) | (mem & memMask);
|
|
Memory::WriteUnchecked_U32(result, addr & 0xfffffffc);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Store32Right)
|
|
{
|
|
u32 addr = mips->r[inst->src1] + inst->constant;
|
|
u32 shift = (addr & 3) * 8;
|
|
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
|
|
u32 memMask = 0x00ffffff >> (24 - shift);
|
|
u32 result = (mips->r[inst->src3] << shift) | (mem & memMask);
|
|
Memory::WriteUnchecked_U32(result, addr & 0xfffffffc);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Store32Conditional)
|
|
if (mips->llBit) {
|
|
Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
if (inst->dest != MIPS_REG_ZERO) {
|
|
mips->r[inst->dest] = 1;
|
|
}
|
|
} else if (inst->dest != MIPS_REG_ZERO) {
|
|
mips->r[inst->dest] = 0;
|
|
}
|
|
IR_NEXT;
|
|
IR_CASE(StoreFloat)
|
|
Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
IR_NEXT;
|
|
|
|
IR_CASE(LoadVec4)
|
|
{
|
|
u32 base = mips->r[inst->src1] + inst->constant;
|
|
// This compiles to a nice SSE load/store on x86, and hopefully similar on ARM.
|
|
memcpy(&mips->f[inst->dest], Memory::GetPointerUnchecked(base), 4 * 4);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(StoreVec4)
|
|
{
|
|
u32 base = mips->r[inst->src1] + inst->constant;
|
|
memcpy((float *)Memory::GetPointerUnchecked(base), &mips->f[inst->dest], 4 * 4);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Init)
|
|
{
|
|
memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float));
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Shuffle)
|
|
{
|
|
// Can't use the SSE shuffle here because it takes an immediate. pshufb with a table would work though,
|
|
// or a big switch - there are only 256 shuffles possible (4^4)
|
|
float temp[4];
|
|
for (u32 i = 0; i < 4; i++)
|
|
temp[i] = mips->f[(u32)inst->src1 + (u32)Vec4ShuffleLane(inst->src2, i)];
|
|
const u32 dest = inst->dest;
|
|
for (u32 i = 0; i < 4; i++)
|
|
mips->f[dest + i] = temp[i];
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Blend)
|
|
{
|
|
const u32 dest = inst->dest;
|
|
const u32 src1 = inst->src1;
|
|
const u32 src2 = inst->src2;
|
|
const u32 constant = inst->constant;
|
|
// 90% of calls to this is inst->constant == 7 or inst->constant == 8. Some are 1 and 4, others very rare.
|
|
// Could use _mm_blendv_ps (SSE4+BMI), vbslq_f32 (ARM), __riscv_vmerge_vvm (RISC-V)
|
|
float temp[4];
|
|
for (u32 i = 0; i < 4; i++)
|
|
temp[i] = ((constant >> i) & 1) ? mips->f[src2 + i] : mips->f[src1 + i];
|
|
for (u32 i = 0; i < 4; i++)
|
|
mips->f[dest + i] = temp[i];
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Mov)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_load_ps(&mips->f[inst->src1]));
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
vst1q_f32(&mips->f[inst->dest], vld1q_f32(&mips->f[inst->src1]));
|
|
#else
|
|
memcpy(&mips->f[inst->dest], &mips->f[inst->src1], 4 * sizeof(float));
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
// ARMv7 NEON always flushes denormals and rounds to nearest, whatever FPSCR says, so float
|
|
// arithmetic stays on VFP there, as on the other hosts (cpu/vfpu/fcr31).
|
|
IR_CASE(Vec4Add)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_add_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2])));
|
|
#elif PPSSPP_ARCH(ARM_NEON) && PPSSPP_ARCH(ARM64)
|
|
vst1q_f32(&mips->f[inst->dest], vaddq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = mips->f[inst->src1 + i] + mips->f[inst->src2 + i];
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Sub)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_sub_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2])));
|
|
#elif PPSSPP_ARCH(ARM_NEON) && PPSSPP_ARCH(ARM64)
|
|
vst1q_f32(&mips->f[inst->dest], vsubq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = mips->f[inst->src1 + i] - mips->f[inst->src2 + i];
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Mul)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2])));
|
|
#elif PPSSPP_ARCH(ARM_NEON) && PPSSPP_ARCH(ARM64)
|
|
vst1q_f32(&mips->f[inst->dest], vmulq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2 + i];
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Div)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_div_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2])));
|
|
#elif PPSSPP_ARCH(ARM64_NEON)
|
|
vst1q_f32(&mips->f[inst->dest], vdivq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = mips->f[inst->src1 + i] / mips->f[inst->src2 + i];
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Scale)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_set1_ps(mips->f[inst->src2])));
|
|
#elif PPSSPP_ARCH(ARM_NEON) && PPSSPP_ARCH(ARM64)
|
|
vst1q_f32(&mips->f[inst->dest], vmulq_lane_f32(vld1q_f32(&mips->f[inst->src1]), vdup_n_f32(mips->f[inst->src2]), 0));
|
|
#else
|
|
const float factor = mips->f[inst->src2];
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = mips->f[inst->src1 + i] * factor;
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Neg)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_xor_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)signBits)));
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
vst1q_f32(&mips->f[inst->dest], vnegq_f32(vld1q_f32(&mips->f[inst->src1])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = -mips->f[inst->src1 + i];
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Abs)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_store_ps(&mips->f[inst->dest], _mm_and_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)noSignMask)));
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
vst1q_f32(&mips->f[inst->dest], vabsq_f32(vld1q_f32(&mips->f[inst->src1])));
|
|
#else
|
|
for (int i = 0; i < 4; i++)
|
|
mips->f[inst->dest + i] = fabsf(mips->f[inst->src1 + i]);
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec2Unpack16To31)
|
|
{
|
|
const u32 dest = inst->dest;
|
|
const u32 src1 = inst->src1;
|
|
const u32 temp0 = (mips->fi[src1] << 16) >> 1;
|
|
const u32 temp1 = (mips->fi[src1] & 0xFFFF0000) >> 1;
|
|
mips->fi[dest] = temp0;
|
|
mips->fi[dest + 1] = temp1;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec2Unpack16To32)
|
|
{
|
|
const u32 dest = inst->dest;
|
|
const u32 src1 = inst->src1;
|
|
const u32 temp0 = (mips->fi[src1] << 16);
|
|
const u32 temp1 = (mips->fi[src1] & 0xFFFF0000);
|
|
mips->fi[dest] = temp0;
|
|
mips->fi[dest + 1] = temp1;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Unpack8To32)
|
|
{
|
|
// Used in Gran Turismo
|
|
#if PPSSPP_ARCH(SSE2)
|
|
__m128i src = _mm_cvtsi32_si128(mips->fi[inst->src1]);
|
|
src = _mm_unpacklo_epi8(src, _mm_setzero_si128());
|
|
src = _mm_unpacklo_epi16(src, _mm_setzero_si128());
|
|
_mm_store_si128((__m128i *)&mips->fi[inst->dest], _mm_slli_epi32(src, 24));
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
const uint8x8_t value = (uint8x8_t)vdup_n_u32(mips->fi[inst->src1]);
|
|
const uint16x8_t value16 = vmovl_u8(value);
|
|
const uint32x4_t value32 = vshlq_n_u32(vshll_n_u16(vget_low_u16(value16), 8), 16); // note: vshll has a range limited to 0..16
|
|
vst1q_u32(&mips->fi[inst->dest], value32);
|
|
#else
|
|
mips->fi[inst->dest] = (mips->fi[inst->src1] << 24);
|
|
mips->fi[inst->dest + 1] = (mips->fi[inst->src1] << 16) & 0xFF000000;
|
|
mips->fi[inst->dest + 2] = (mips->fi[inst->src1] << 8) & 0xFF000000;
|
|
mips->fi[inst->dest + 3] = (mips->fi[inst->src1]) & 0xFF000000;
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec2Pack32To16)
|
|
{
|
|
u32 val = mips->fi[inst->src1] >> 16;
|
|
mips->fi[inst->dest] = val | (mips->fi[(u32)inst->src1 + 1] & 0xFFFF0000);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec2Pack31To16)
|
|
{
|
|
// Used in Tekken 6. Negative lanes clamp to zero.
|
|
const u32 s0 = (s32)mips->fi[inst->src1] < 0 ? 0 : mips->fi[inst->src1];
|
|
const u32 s1 = (s32)mips->fi[(u32)inst->src1 + 1] < 0 ? 0 : mips->fi[(u32)inst->src1 + 1];
|
|
mips->fi[inst->dest] = ((s0 >> 15) & 0xFFFF) | ((s1 << 1) & 0xFFFF0000);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Pack32To8)
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
__m128i src = _mm_loadu_si128((__m128i *)&mips->fi[inst->src1]);
|
|
// Shift each 32-bit lane right by 24 bits
|
|
src = _mm_srli_epi32(src, 24);
|
|
// Pack 32-bit lanes to 16-bit, then 16-bit to 8-bit
|
|
// This moves our target bytes to the bottom of the XMM register
|
|
src = _mm_packs_epi32(src, src);
|
|
src = _mm_packus_epi16(src, src);
|
|
// Extract the lower 32 bits (which now contains our 4 bytes)
|
|
mips->fi[inst->dest] = (u32)_mm_cvtsi128_si32(src);
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
// 1. Load 4x32-bit lanes
|
|
uint32x4_t src = vld1q_u32(&mips->fi[inst->src1]);
|
|
// 2. Manual shift right by 24 (this is allowed on full 128-bit vectors. vshrn can't shift by more than 16.
|
|
uint32x4_t shifted = vshrq_n_u32(src, 24);
|
|
// 3. Narrow from 32-bit to 16-bit (vmovn works on the bottom 64 bits)
|
|
uint16x4_t narrow_16 = vmovn_u32(shifted);
|
|
// 4. Narrow from 16-bit to 8-bit
|
|
// We combine the 64-bit result with itself to keep the 128-bit logic happy
|
|
uint8x8_t narrow_8 = vmovn_u16(vcombine_u16(narrow_16, narrow_16));
|
|
// 5. Extract the result as a single u32
|
|
mips->fi[inst->dest] = vget_lane_u32(vreinterpret_u32_u8(narrow_8), 0);
|
|
#else
|
|
// Removed previous SSE code due to the need for unsigned 16-bit pack, which I'm too lazy to work around the lack of in SSE2.
|
|
// pshufb or SSE4 instructions can be used instead.
|
|
u32 val = mips->fi[(u32)inst->src1] >> 24;
|
|
val |= (mips->fi[(u32)inst->src1 + 1] >> 16) & 0xFF00;
|
|
val |= (mips->fi[(u32)inst->src1 + 2] >> 8) & 0xFF0000;
|
|
val |= (mips->fi[(u32)inst->src1 + 3]) & 0xFF000000;
|
|
mips->fi[inst->dest] = val;
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4Pack31To8)
|
|
{
|
|
// Used in Tekken 6, Gran Turismo
|
|
|
|
// Bits 30-23 of each lane, with negative lanes clamped to zero.
|
|
#if PPSSPP_ARCH(SSE2)
|
|
__m128i src = _mm_loadu_si128((__m128i *) & mips->fi[inst->src1]);
|
|
// An arithmetic shift leaves 0-255 for positive lanes and negative values for negative
|
|
// ones, which the unsigned saturation in the second pack turns into 0.
|
|
src = _mm_srai_epi32(src, 23);
|
|
src = _mm_packs_epi32(src, src);
|
|
src = _mm_packus_epi16(src, src);
|
|
mips->fi[inst->dest] = (u32)_mm_cvtsi128_si32(src);
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
int32x4_t value = vmaxq_s32(vld1q_s32((const int32_t *)&mips->fi[inst->src1]), vdupq_n_s32(0));
|
|
uint32x4_t shifted = vshlq_n_u32(vreinterpretq_u32_s32(value), 1);
|
|
uint16x4_t halved = vshrn_n_u32(shifted, 16);
|
|
uint8x8_t halvedAgain = vshrn_n_u16(vcombine_u16(halved, vdup_n_u16(0)), 8);
|
|
mips->fi[inst->dest] = vget_lane_u32(vreinterpret_u32_u8(halvedAgain), 0);
|
|
#else
|
|
u32 val = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
const u32 lane = mips->fi[(u32)inst->src1 + i];
|
|
if ((s32)lane > 0)
|
|
val |= ((lane >> 23) & 0xFF) << (8 * i);
|
|
}
|
|
mips->fi[(u32)inst->dest] = val;
|
|
#endif
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Vec4DuplicateUpperBitsAndShift1) // For vuc2i, the weird one.
|
|
{
|
|
const int src1 = inst->src1;
|
|
const int dest = inst->dest;
|
|
u32 temp[4];
|
|
for (int i = 0; i < 4; i++) {
|
|
u32 val = mips->fi[src1 + i];
|
|
val = val | (val >> 8);
|
|
val = val | (val >> 16);
|
|
temp[i] = val >> 1;
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
mips->fi[dest + i] = temp[i];
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FCmpVfpuBit)
|
|
{
|
|
const int op = inst->dest & 0xF;
|
|
const int bit = inst->dest >> 4;
|
|
int result = 0;
|
|
switch (op) {
|
|
case VC_EQ: result = mips->f[inst->src1] == mips->f[inst->src2]; break;
|
|
case VC_NE: result = mips->f[inst->src1] != mips->f[inst->src2]; break;
|
|
case VC_LT: result = mips->f[inst->src1] < mips->f[inst->src2]; break;
|
|
case VC_LE: result = mips->f[inst->src1] <= mips->f[inst->src2]; break;
|
|
case VC_GT: result = mips->f[inst->src1] > mips->f[inst->src2]; break;
|
|
case VC_GE: result = mips->f[inst->src1] >= mips->f[inst->src2]; break;
|
|
case VC_EZ: result = mips->f[inst->src1] == 0.0f; break;
|
|
case VC_NZ: result = mips->f[inst->src1] != 0.0f; break;
|
|
case VC_EN: result = my_isnan(mips->f[inst->src1]); break;
|
|
case VC_NN: result = !my_isnan(mips->f[inst->src1]); break;
|
|
case VC_EI: result = my_isinf(mips->f[inst->src1]); break;
|
|
case VC_NI: result = !my_isinf(mips->f[inst->src1]); break;
|
|
case VC_ES: result = my_isnanorinf(mips->f[inst->src1]); break;
|
|
case VC_NS: result = !my_isnanorinf(mips->f[inst->src1]); break;
|
|
case VC_TR: result = 1; break;
|
|
case VC_FL: result = 0; break;
|
|
default:
|
|
result = 0;
|
|
}
|
|
if (result != 0) {
|
|
mips->vfpuCtrl[VFPU_CTRL_CC] |= (1 << bit);
|
|
} else {
|
|
mips->vfpuCtrl[VFPU_CTRL_CC] &= ~(1 << bit);
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FCmpVfpuAggregate)
|
|
{
|
|
const u32 mask = inst->dest;
|
|
const u32 cc = mips->vfpuCtrl[VFPU_CTRL_CC];
|
|
int anyBit = (cc & mask) ? 0x10 : 0x00;
|
|
int allBit = (cc & mask) == mask ? 0x20 : 0x00;
|
|
mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | anyBit | allBit;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FCmovVfpuCC)
|
|
if (((mips->vfpuCtrl[VFPU_CTRL_CC] >> (inst->src2 & 0xf)) & 1) == ((u32)inst->src2 >> 7)) {
|
|
mips->f[inst->dest] = mips->f[inst->src1];
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Vec4Dot)
|
|
{
|
|
// Not quickly implementable on all platforms, unfortunately.
|
|
// Though, this is still pretty fast compared to one split into multiple IR instructions.
|
|
// This might be good though: https://gist.github.com/rikusalminen/3040241
|
|
const float *a = &mips->f[(u32)inst->src1];
|
|
const float *b = &mips->f[(u32)inst->src2];
|
|
mips->f[inst->dest] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FSin)
|
|
mips->f[inst->dest] = vfpu_sin(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FCos)
|
|
mips->f[inst->dest] = vfpu_cos(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FRSqrt)
|
|
mips->f[inst->dest] = vfpu_rsqrt(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FRecip)
|
|
mips->f[inst->dest] = vfpu_rcp(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FAsin)
|
|
mips->f[inst->dest] = vfpu_asin(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FVSqrt)
|
|
mips->f[inst->dest] = vfpu_sqrt(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FExp2)
|
|
mips->f[inst->dest] = vfpu_exp2(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FLog2)
|
|
mips->f[inst->dest] = vfpu_log2(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FSinCos)
|
|
{
|
|
float s, c;
|
|
vfpu_sincos(mips->f[inst->src1], s, c);
|
|
mips->f[inst->dest] = s;
|
|
mips->f[inst->dest + 1] = c;
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(FHalfToFloat)
|
|
mips->fi[inst->dest] = vfpu_h2f((u16)(inst->src2 ? mips->fi[inst->src1] >> 16 : mips->fi[inst->src1] & 0xFFFF));
|
|
IR_NEXT;
|
|
|
|
IR_CASE(ShlImm)
|
|
mips->r[inst->dest] = mips->r[inst->src1] << (int)inst->src2;
|
|
IR_NEXT;
|
|
IR_CASE(ShrImm)
|
|
mips->r[inst->dest] = mips->r[inst->src1] >> (int)inst->src2;
|
|
IR_NEXT;
|
|
IR_CASE(SarImm)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (int)inst->src2;
|
|
IR_NEXT;
|
|
IR_CASE(RorImm)
|
|
{
|
|
u32 x = mips->r[inst->src1];
|
|
int sa = inst->src2;
|
|
mips->r[inst->dest] = (x >> sa) | (x << (32 - sa));
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Shl)
|
|
mips->r[inst->dest] = mips->r[inst->src1] << (mips->r[inst->src2] & 31);
|
|
IR_NEXT;
|
|
IR_CASE(Shr)
|
|
mips->r[inst->dest] = mips->r[inst->src1] >> (mips->r[inst->src2] & 31);
|
|
IR_NEXT;
|
|
IR_CASE(Sar)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (mips->r[inst->src2] & 31);
|
|
IR_NEXT;
|
|
IR_CASE(Ror)
|
|
{
|
|
u32 x = mips->r[inst->src1];
|
|
int sa = mips->r[inst->src2] & 31;
|
|
mips->r[inst->dest] = (x >> sa) | (x << (32 - sa));
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Clz)
|
|
{
|
|
mips->r[inst->dest] = clz32(mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Slt)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SltU)
|
|
mips->r[inst->dest] = mips->r[inst->src1] < mips->r[inst->src2];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SltConst)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SltUConst)
|
|
mips->r[inst->dest] = mips->r[inst->src1] < inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(MovZ)
|
|
if (mips->r[inst->src1] == 0)
|
|
mips->r[inst->dest] = mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(MovNZ)
|
|
if (mips->r[inst->src1] != 0)
|
|
mips->r[inst->dest] = mips->r[inst->src2];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Max)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] > (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(Min)
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(MtLo)
|
|
mips->lo = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(MtHi)
|
|
mips->hi = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(MfLo)
|
|
mips->r[inst->dest] = mips->lo;
|
|
IR_NEXT;
|
|
IR_CASE(MfHi)
|
|
mips->r[inst->dest] = mips->hi;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Mult)
|
|
{
|
|
s64 result = (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8); // note: lo is followed by hi, so this is ok (little-endian).
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(MultU)
|
|
{
|
|
u64 result = (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Madd)
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result += (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(MaddU)
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result += (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(Msub)
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result -= (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(MsubU)
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result -= (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(Div)
|
|
{
|
|
s32 numerator = (s32)mips->r[inst->src1];
|
|
s32 denominator = (s32)mips->r[inst->src2];
|
|
if (numerator == (s32)0x80000000 && denominator == -1) {
|
|
// The one overflow. Hardware leaves the remainder at zero (cpu/cpu_alu/cpu_div).
|
|
mips->lo = 0x80000000;
|
|
mips->hi = 0;
|
|
} else if (denominator != 0) {
|
|
mips->lo = (u32)(numerator / denominator);
|
|
mips->hi = (u32)(numerator % denominator);
|
|
} else {
|
|
mips->lo = numerator < 0 ? 1 : -1;
|
|
mips->hi = numerator;
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(DivU)
|
|
{
|
|
u32 numerator = mips->r[inst->src1];
|
|
u32 denominator = mips->r[inst->src2];
|
|
if (denominator != 0) {
|
|
mips->lo = numerator / denominator;
|
|
mips->hi = numerator % denominator;
|
|
} else {
|
|
mips->lo = numerator <= 0xFFFF ? 0xFFFF : -1;
|
|
mips->hi = numerator;
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(BSwap16)
|
|
{
|
|
u32 x = mips->r[inst->src1];
|
|
// Don't think we can beat this with intrinsics.
|
|
mips->r[inst->dest] = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8);
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(BSwap32)
|
|
{
|
|
mips->r[inst->dest] = swap32(mips->r[inst->src1]);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FAdd)
|
|
mips->f[inst->dest] = mips->f[inst->src1] + mips->f[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(FSub)
|
|
mips->f[inst->dest] = mips->f[inst->src1] - mips->f[inst->src2];
|
|
IR_NEXT;
|
|
IR_CASE(FMul)
|
|
#if 1
|
|
{
|
|
float a = mips->f[inst->src1];
|
|
float b = mips->f[inst->src2];
|
|
if ((b == 0.0f && my_isinf(a)) || (a == 0.0f && my_isinf(b))) {
|
|
mips->fi[inst->dest] = 0x7fc00000;
|
|
} else {
|
|
mips->f[inst->dest] = a * b;
|
|
}
|
|
}
|
|
IR_NEXT;
|
|
#else
|
|
// Not sure if faster since it needs to load the operands twice? But the code is simpler.
|
|
{
|
|
// Takes care of negative zero by masking away the top bit, which also makes the inf check shorter.
|
|
u32 a = mips->fi[inst->src1] & 0x7FFFFFFF;
|
|
u32 b = mips->fi[inst->src2] & 0x7FFFFFFF;
|
|
if ((a == 0 && b == 0x7F800000) || (b == 0 && a == 0x7F800000)) {
|
|
mips->fi[inst->dest] = 0x7fc00000;
|
|
} else {
|
|
mips->f[inst->dest] = mips->f[inst->src1] * mips->f[inst->src2];
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
#endif
|
|
IR_CASE(FDiv)
|
|
mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2];
|
|
IR_NEXT;
|
|
// These are vmin and vmax, see vfpu_minmax_key.
|
|
IR_CASE(FMin)
|
|
{
|
|
u32 a = mips->fi[inst->src1], b = mips->fi[inst->src2];
|
|
mips->fi[inst->dest] = vfpu_minmax_key(a) < vfpu_minmax_key(b) ? a : b;
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(FMax)
|
|
{
|
|
u32 a = mips->fi[inst->src1], b = mips->fi[inst->src2];
|
|
mips->fi[inst->dest] = vfpu_minmax_key(a) > vfpu_minmax_key(b) ? a : b;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FMov)
|
|
mips->f[inst->dest] = mips->f[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FAbs)
|
|
mips->f[inst->dest] = fabsf(mips->f[inst->src1]);
|
|
IR_NEXT;
|
|
IR_CASE(FSqrt)
|
|
{
|
|
float src = mips->f[inst->src1];
|
|
mips->f[inst->dest] = sqrtf(src);
|
|
// A negative input gives a positive NaN, not the host's (cpu/fpu/roundmode).
|
|
if (src < 0.0f) {
|
|
mips->fi[inst->dest] = 0x7FC00000;
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
IR_CASE(FNeg)
|
|
mips->f[inst->dest] = -mips->f[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FSat0_1)
|
|
// We have to do this carefully to handle NAN and -0.0f.
|
|
mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], 0.0f, 1.0f);
|
|
IR_NEXT;
|
|
IR_CASE(FSatMinus1_1)
|
|
mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], -1.0f, 1.0f);
|
|
IR_NEXT;
|
|
|
|
IR_CASE(FSign)
|
|
{
|
|
// Bitwise trickery. Denormals give zero, as on the hardware.
|
|
u32 val;
|
|
memcpy(&val, &mips->f[inst->src1], sizeof(u32));
|
|
if ((val & 0x7F800000) == 0)
|
|
mips->f[inst->dest] = 0.0f;
|
|
else if ((val >> 31) == 0)
|
|
mips->f[inst->dest] = 1.0f;
|
|
else
|
|
mips->f[inst->dest] = -1.0f;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FpCondFromReg)
|
|
// Note: the register is in src1, see the "_G" meta - the native backends read it there.
|
|
mips->fpcond = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FpCondToReg)
|
|
mips->r[inst->dest] = mips->fpcond;
|
|
IR_NEXT;
|
|
IR_CASE(FpCtrlFromReg)
|
|
mips->fcr31 = mips->r[inst->src1] & 0x0181FFFF;
|
|
// Extract the new fpcond value.
|
|
// TODO: Is it really helping us to keep it separate?
|
|
mips->fpcond = (mips->fcr31 >> 23) & 1;
|
|
IR_NEXT;
|
|
IR_CASE(FpCtrlToReg)
|
|
// Update the fpcond bit first.
|
|
mips->fcr31 = (mips->fcr31 & ~(1 << 23)) | ((mips->fpcond & 1) << 23);
|
|
mips->r[inst->dest] = mips->fcr31;
|
|
IR_NEXT;
|
|
IR_CASE(VfpuCtrlToReg)
|
|
mips->r[inst->dest] = mips->vfpuCtrl[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FRound)
|
|
mips->fs[inst->dest] = SaturatedFloatToInt(round_ieee_754(FlushForConversion(mips, mips->f[inst->src1])));
|
|
IR_NEXT;
|
|
IR_CASE(FTrunc)
|
|
mips->fs[inst->dest] = SaturatedFloatToInt(truncf(FlushForConversion(mips, mips->f[inst->src1])));
|
|
IR_NEXT;
|
|
IR_CASE(FCeil)
|
|
mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(FlushForConversion(mips, mips->f[inst->src1])));
|
|
IR_NEXT;
|
|
IR_CASE(FFloor)
|
|
mips->fs[inst->dest] = SaturatedFloatToInt(floorf(FlushForConversion(mips, mips->f[inst->src1])));
|
|
IR_NEXT;
|
|
IR_CASE(FCmp)
|
|
switch (inst->dest) {
|
|
case IRFpCompareMode::False:
|
|
mips->fpcond = 0;
|
|
break;
|
|
case IRFpCompareMode::EitherUnordered:
|
|
{
|
|
float a = mips->f[inst->src1];
|
|
float b = mips->f[inst->src2];
|
|
mips->fpcond = !(a > b || a < b || a == b);
|
|
break;
|
|
}
|
|
case IRFpCompareMode::EqualOrdered:
|
|
mips->fpcond = mips->f[inst->src1] == mips->f[inst->src2];
|
|
break;
|
|
case IRFpCompareMode::EqualUnordered:
|
|
mips->fpcond = mips->f[inst->src1] == mips->f[inst->src2] || my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2]);
|
|
break;
|
|
case IRFpCompareMode::LessEqualOrdered:
|
|
mips->fpcond = mips->f[inst->src1] <= mips->f[inst->src2];
|
|
break;
|
|
case IRFpCompareMode::LessEqualUnordered:
|
|
mips->fpcond = !(mips->f[inst->src1] > mips->f[inst->src2]);
|
|
break;
|
|
case IRFpCompareMode::LessOrdered:
|
|
mips->fpcond = mips->f[inst->src1] < mips->f[inst->src2];
|
|
break;
|
|
case IRFpCompareMode::LessUnordered:
|
|
mips->fpcond = !(mips->f[inst->src1] >= mips->f[inst->src2]);
|
|
break;
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(FCvtSW)
|
|
mips->f[inst->dest] = (float)mips->fs[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FCvtWS)
|
|
{
|
|
float src = FlushForConversion(mips, mips->f[inst->src1]);
|
|
// TODO: Inline assembly to use here would be better.
|
|
switch (IRRoundMode(mips->fcr31 & 3)) {
|
|
case IRRoundMode::RINT_0: mips->fs[inst->dest] = SaturatedFloatToInt(round_ieee_754(src)); break;
|
|
case IRRoundMode::CAST_1: mips->fs[inst->dest] = SaturatedFloatToInt(truncf(src)); break;
|
|
case IRRoundMode::CEIL_2: mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(src)); break;
|
|
case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = SaturatedFloatToInt(floorf(src)); break;
|
|
}
|
|
IR_NEXT; //cvt.w.s
|
|
}
|
|
IR_CASE(FCvtScaledSW)
|
|
mips->f[inst->dest] = (float)mips->fs[inst->src1] * (1.0f / (1UL << (inst->src2 & 0x1F)));
|
|
IR_NEXT;
|
|
IR_CASE(FCvtScaledWS)
|
|
{
|
|
float src = mips->f[inst->src1];
|
|
if (my_isnan(src)) {
|
|
// TODO: True for negatives too?
|
|
mips->fs[inst->dest] = 2147483647L;
|
|
IR_NEXT;
|
|
}
|
|
|
|
float mult = (float)(1UL << (inst->src2 & 0x1F));
|
|
double sv = src * mult; // (float)0x7fffffff == (float)0x80000000
|
|
// Cap/floor it to 0x7fffffff / 0x80000000
|
|
if (sv > (double)0x7fffffff) {
|
|
mips->fs[inst->dest] = 0x7fffffff;
|
|
} else if (sv <= (double)(int)0x80000000) {
|
|
mips->fs[inst->dest] = 0x80000000;
|
|
} else {
|
|
switch (IRRoundMode(inst->src2 >> 6)) {
|
|
case IRRoundMode::RINT_0: mips->fs[inst->dest] = (int)round_ieee_754(sv); break;
|
|
case IRRoundMode::CAST_1: mips->fs[inst->dest] = src >= 0 ? (int)floor(sv) : (int)ceil(sv); break;
|
|
case IRRoundMode::CEIL_2: mips->fs[inst->dest] = (int)ceil(sv); break;
|
|
case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = (int)floor(sv); break;
|
|
}
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(FMovFromGPR)
|
|
memcpy(&mips->f[inst->dest], &mips->r[inst->src1], 4);
|
|
IR_NEXT;
|
|
IR_CASE(OptFCvtSWFromGPR)
|
|
mips->f[inst->dest] = (float)(int)mips->r[inst->src1];
|
|
IR_NEXT;
|
|
IR_CASE(FMovToGPR)
|
|
memcpy(&mips->r[inst->dest], &mips->f[inst->src1], 4);
|
|
IR_NEXT;
|
|
IR_CASE(OptFMovToGPRShr8)
|
|
{
|
|
u32 temp;
|
|
memcpy(&temp, &mips->f[inst->src1], 4);
|
|
mips->r[inst->dest] = temp >> 8;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(ExitToConst)
|
|
return inst->constant;
|
|
|
|
IR_CASE(ExitToReg)
|
|
return mips->r[inst->src1];
|
|
|
|
// The next instruction is the ExitToConst to take otherwise, never run itself.
|
|
IR_CASE(OptExitToConstIfEqElse)
|
|
return mips->r[inst->src1] == mips->r[inst->src2] ? inst->constant : inst[1].constant;
|
|
IR_CASE(OptExitToConstIfNeqElse)
|
|
return mips->r[inst->src1] != mips->r[inst->src2] ? inst->constant : inst[1].constant;
|
|
IR_CASE(OptExitToConstIfGtZElse)
|
|
return (s32)mips->r[inst->src1] > 0 ? inst->constant : inst[1].constant;
|
|
IR_CASE(OptExitToConstIfGeZElse)
|
|
return (s32)mips->r[inst->src1] >= 0 ? inst->constant : inst[1].constant;
|
|
IR_CASE(OptExitToConstIfLtZElse)
|
|
return (s32)mips->r[inst->src1] < 0 ? inst->constant : inst[1].constant;
|
|
IR_CASE(OptExitToConstIfLeZElse)
|
|
return (s32)mips->r[inst->src1] <= 0 ? inst->constant : inst[1].constant;
|
|
IR_CASE(ExitToConstIfEq)
|
|
if (mips->r[inst->src1] == mips->r[inst->src2])
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(ExitToConstIfNeq)
|
|
if (mips->r[inst->src1] != mips->r[inst->src2])
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(ExitToConstIfGtZ)
|
|
if ((s32)mips->r[inst->src1] > 0)
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(ExitToConstIfGeZ)
|
|
if ((s32)mips->r[inst->src1] >= 0)
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(ExitToConstIfLtZ)
|
|
if ((s32)mips->r[inst->src1] < 0)
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
IR_CASE(ExitToConstIfLeZ)
|
|
if ((s32)mips->r[inst->src1] <= 0)
|
|
return inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Downcount)
|
|
mips->downcount -= (int)inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SetPC)
|
|
mips->pc = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SetPCConst)
|
|
mips->pc = inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Syscall)
|
|
// IROp::SetPC was (hopefully) executed before.
|
|
{
|
|
// If we get here, the syscall is valid.
|
|
MIPSOpcode op(inst->constant);
|
|
// HLE runs in the host's normal mode, see ApplyHostRoundingMode.
|
|
RestoreHostRoundingMode();
|
|
CallSyscall(op);
|
|
ApplyHostRoundingMode(mips);
|
|
if (coreState != CORE_RUNNING_CPU) {
|
|
CoreTiming::ForceCheck(mips);
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(SyscallUnresolved)
|
|
{
|
|
// If we get here, the syscall is invalid.
|
|
u32 pc = inst->constant;
|
|
RestoreHostRoundingMode();
|
|
CallSyscallUnresolvedAtPC(pc);
|
|
ApplyHostRoundingMode(mips);
|
|
if (coreState != CORE_RUNNING_CPU) {
|
|
// hm, what's this for?
|
|
CoreTiming::ForceCheck(mips);
|
|
}
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(ExitToPC)
|
|
return mips->pc;
|
|
|
|
IR_CASE(Interpret) // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging.
|
|
{
|
|
MIPSOpcode op(inst->constant);
|
|
MIPSInterpret(mips, op);
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(CallReplacement)
|
|
{
|
|
int funcIndex = inst->constant;
|
|
const ReplacementTableEntry *f = GetReplacementFunc(funcIndex);
|
|
RestoreHostRoundingMode();
|
|
int cycles = f->replaceFunc();
|
|
ApplyHostRoundingMode(mips);
|
|
mips->r[inst->dest] = cycles < 0 ? -1 : 0;
|
|
mips->downcount -= cycles < 0 ? -cycles : cycles;
|
|
IR_NEXT;
|
|
}
|
|
|
|
IR_CASE(SetCtrlVFPU)
|
|
mips->vfpuCtrl[inst->dest] = inst->constant;
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SetCtrlVFPUReg)
|
|
mips->vfpuCtrl[inst->dest] = mips->r[inst->src1];
|
|
IR_NEXT;
|
|
|
|
IR_CASE(SetCtrlVFPUFReg)
|
|
memcpy(&mips->vfpuCtrl[inst->dest], &mips->f[inst->src1], 4);
|
|
IR_NEXT;
|
|
|
|
IR_CASE(ApplyRoundingMode)
|
|
ApplyHostRoundingMode(mips);
|
|
IR_NEXT;
|
|
IR_CASE(RestoreRoundingMode)
|
|
// Forced: a native backend may get here through its generic fallback, after its own
|
|
// asm applied the mode.
|
|
RestoreHostRoundingMode(true);
|
|
IR_NEXT;
|
|
IR_CASE(UpdateRoundingMode)
|
|
// TODO: Implement
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Break)
|
|
Core_BreakException(mips->pc);
|
|
return mips->pc + 4;
|
|
|
|
IR_CASE(Breakpoint)
|
|
if (IRRunBreakpoint(inst->constant)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(MemoryCheck)
|
|
if (IRRunMemCheck(mips->pc + inst->dest, mips->r[inst->src1] + inst->constant)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(ValidateAddress8)
|
|
if (RunValidateAddress<1>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
IR_CASE(ValidateAddress16)
|
|
if (RunValidateAddress<2>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
IR_CASE(ValidateAddress32)
|
|
if (RunValidateAddress<4>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
IR_CASE(ValidateAddress128)
|
|
if (RunValidateAddress<16>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
IR_NEXT;
|
|
IR_CASE(LogIRBlock)
|
|
if (mipsTracer.tracing_enabled) {
|
|
mipsTracer.executed_blocks.push_back(inst->constant);
|
|
}
|
|
IR_NEXT;
|
|
|
|
IR_CASE(Nop) // Unused, add a break if we start using it to avoid UNREACHABLE.
|
|
IR_CASE(Bad)
|
|
default:
|
|
// Unimplemented IR op. Bad. We define it as unreachable so the compiler can optimize better (remove the range check).
|
|
UNREACHABLE();
|
|
IR_NEXT;
|
|
}
|
|
}
|
|
|
|
// We should not reach here anymore.
|
|
return 0;
|
|
}
|
|
|
|
#undef IR_NEXT
|
|
#undef IR_CASE
|
|
#undef IR_CHECK_ZERO_REG
|