Files
ppsspp/Core/MIPS/IR/IRInterpreter.cpp
Henrik RydgårdandClaude Opus 5.5 d1c04d70a6 IR interpreter: Use threaded dispatch with GCC and Clang
Every op ended with a break back to one shared indirect jump, which the
CPU has to predict for every op in the program. With labels as values,
each op jumps through a table from its own site instead, which predicts
much better: an integer-heavy benchmark runs about 13% faster on an M1.
Other compilers keep the switch, and ops missing from the table fall back
to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 11:47:17 -06:00

1424 lines
46 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)((inst->src2 >> (i * 2)) & 3)];
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;
}
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)
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)
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)
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)
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;
IR_CASE(FMin)
if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) {
// See interpreter for this logic: this is for vmin, we're comparing mantissa+exp.
if (mips->fs[inst->src1] < 0 && mips->fs[inst->src2] < 0) {
mips->fs[inst->dest] = std::max(mips->fs[inst->src1], mips->fs[inst->src2]);
} else {
mips->fs[inst->dest] = std::min(mips->fs[inst->src1], mips->fs[inst->src2]);
}
} else {
mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]);
}
IR_NEXT;
IR_CASE(FMax)
if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) {
// See interpreter for this logic: this is for vmax, we're comparing mantissa+exp.
if (mips->fs[inst->src1] < 0 && mips->fs[inst->src2] < 0) {
mips->fs[inst->dest] = std::min(mips->fs[inst->src1], mips->fs[inst->src2]);
} else {
mips->fs[inst->dest] = std::max(mips->fs[inst->src1], mips->fs[inst->src2]);
}
} else {
mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]);
}
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(mips->f[inst->src1]));
IR_NEXT;
IR_CASE(FTrunc)
mips->fs[inst->dest] = SaturatedFloatToInt(truncf(mips->f[inst->src1]));
IR_NEXT;
IR_CASE(FCeil)
mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(mips->f[inst->src1]));
IR_NEXT;
IR_CASE(FFloor)
mips->fs[inst->dest] = SaturatedFloatToInt(floorf(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 = 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);
CallSyscall(op);
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;
CallSyscallUnresolvedAtPC(pc);
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);
int cycles = f->replaceFunc();
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)
RestoreHostRoundingMode();
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