mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
FpCondFromReg's meta is "_G", so the register is in src1 - which is where every native backend, PropagateConstants and ReorderLoadStore read it. But IRInterpret read it from dest, and Comp_VecDo3 wrote it to dest to match. The other emitter passes (0, MIPS_REG_ZERO), so both fields are zero there and the disagreement stayed hidden. The result was that vsge/vslt, which save fpcond to IRTEMP_0 and restore it afterwards, restored r0 (always zero) on the native IR JITs instead of the saved value, losing any c.cond.s result live across them. Fixed both the emitter and the interpreter to use src1. Vec4Pack31To8's SSE2 path computed (v >> 24) << 1, which is (v >> 23) with bit 23 forced to zero - the scalar and NEON paths both do (v >> 23) & 0xFF. Shift left first instead. The pspautotests inputs all happen to have bit 23 clear after the clamp, so this wasn't caught. Also: - Evaluate() didn't mask constant-folded shift amounts to 5 bits, though the neighbouring one-immediate path does. - ApplyMemoryValidation only invalidated its address-check cache for ops with a 'G' destination, while Interpret and CallReplacement can write any GPR - both are barriers, so drop the whole cache when we see one. - ReduceVec4Flush indexed isVec4 with (src2 & 3) instead of (src2 & ~3) for Vec4Scale; a missed optimization rather than a miscompile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1256 lines
38 KiB
C++
1256 lines
38 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;
|
|
}
|
|
|
|
u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
|
|
while (true) {
|
|
switch (inst->op) {
|
|
case IROp::SetConst:
|
|
mips->r[inst->dest] = inst->constant;
|
|
break;
|
|
case IROp::SetConstF:
|
|
memcpy(&mips->f[inst->dest], &inst->constant, 4);
|
|
break;
|
|
case IROp::Add:
|
|
mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2];
|
|
break;
|
|
case IROp::Sub:
|
|
mips->r[inst->dest] = mips->r[inst->src1] - mips->r[inst->src2];
|
|
break;
|
|
case IROp::And:
|
|
mips->r[inst->dest] = mips->r[inst->src1] & mips->r[inst->src2];
|
|
break;
|
|
case IROp::Or:
|
|
mips->r[inst->dest] = mips->r[inst->src1] | mips->r[inst->src2];
|
|
break;
|
|
case IROp::Xor:
|
|
mips->r[inst->dest] = mips->r[inst->src1] ^ mips->r[inst->src2];
|
|
break;
|
|
case IROp::Mov:
|
|
mips->r[inst->dest] = mips->r[inst->src1];
|
|
break;
|
|
case IROp::AddConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] + inst->constant;
|
|
break;
|
|
case IROp::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;
|
|
break;
|
|
case IROp::SubConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] - inst->constant;
|
|
break;
|
|
case IROp::AndConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] & inst->constant;
|
|
break;
|
|
case IROp::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;
|
|
break;
|
|
case IROp::OrConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] | inst->constant;
|
|
break;
|
|
case IROp::OptOrConst:
|
|
mips->r[inst->dest] |= inst->constant;
|
|
break;
|
|
case IROp::XorConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant;
|
|
break;
|
|
case IROp::Neg:
|
|
mips->r[inst->dest] = (u32)(-(s32)mips->r[inst->src1]);
|
|
break;
|
|
case IROp::Not:
|
|
mips->r[inst->dest] = ~mips->r[inst->src1];
|
|
break;
|
|
case IROp::Ext8to32:
|
|
mips->r[inst->dest] = SignExtend8ToU32(mips->r[inst->src1]);
|
|
break;
|
|
case IROp::Ext16to32:
|
|
mips->r[inst->dest] = SignExtend16ToU32(mips->r[inst->src1]);
|
|
break;
|
|
case IROp::ReverseBits:
|
|
mips->r[inst->dest] = ReverseBits32(mips->r[inst->src1]);
|
|
break;
|
|
|
|
case IROp::Load8:
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::Load8Ext:
|
|
mips->r[inst->dest] = SignExtend8ToU32(Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant));
|
|
break;
|
|
case IROp::Load16:
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::Load16Ext:
|
|
mips->r[inst->dest] = SignExtend16ToU32(Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant));
|
|
break;
|
|
case IROp::Load32:
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::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));
|
|
break;
|
|
}
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::Load32Linked:
|
|
if (inst->dest != MIPS_REG_ZERO)
|
|
mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
|
|
mips->llBit = 1;
|
|
break;
|
|
case IROp::LoadFloat:
|
|
mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
|
|
case IROp::Store8:
|
|
Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::Store16:
|
|
Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::Store32:
|
|
Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::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;
|
|
}
|
|
break;
|
|
case IROp::StoreFloat:
|
|
Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant);
|
|
break;
|
|
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::StoreVec4:
|
|
{
|
|
u32 base = mips->r[inst->src1] + inst->constant;
|
|
memcpy((float *)Memory::GetPointerUnchecked(base), &mips->f[inst->dest], 4 * 4);
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4Init:
|
|
{
|
|
memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float));
|
|
break;
|
|
}
|
|
|
|
case IROp::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];
|
|
break;
|
|
}
|
|
|
|
case IROp::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];
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::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;
|
|
break;
|
|
}
|
|
|
|
case IROp::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;
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec2Pack32To16:
|
|
{
|
|
u32 val = mips->fi[inst->src1] >> 16;
|
|
mips->fi[inst->dest] = val | (mips->fi[(u32)inst->src1 + 1] & 0xFFFF0000);
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec2Pack31To16:
|
|
{
|
|
// Used in Tekken 6
|
|
u32 val = (mips->fi[inst->src1] >> 15) & 0xFFFF;
|
|
mips->fi[inst->dest] = val | ((mips->fi[(u32)inst->src1 + 1] << 1) & 0xFFFF0000);
|
|
break;
|
|
}
|
|
|
|
case IROp::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
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4Pack31To8:
|
|
{
|
|
// Used in Tekken 6, Gran Turismo
|
|
|
|
#if PPSSPP_ARCH(SSE2)
|
|
__m128i src = _mm_loadu_si128((__m128i *) & mips->fi[inst->src1]);
|
|
// Shift each 32-bit lane left by 1, then take the top byte - that is, (v >> 23) & 0xFF.
|
|
// Shifting right by 24 first would drop bit 23.
|
|
src = _mm_srli_epi32(_mm_slli_epi32(src, 1), 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)
|
|
uint32x4_t value = vld1q_u32(&mips->fi[inst->src1]);
|
|
value = vshlq_n_u32(value, 1);
|
|
uint16x4_t halved = vshrn_n_u32(value, 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 = (mips->fi[(u32)inst->src1] >> 23) & 0xFF;
|
|
val |= (mips->fi[(u32)inst->src1 + 1] >> 15) & 0xFF00;
|
|
val |= (mips->fi[(u32)inst->src1 + 2] >> 7) & 0xFF0000;
|
|
val |= (mips->fi[(u32)inst->src1 + 3] << 1) & 0xFF000000;
|
|
mips->fi[(u32)inst->dest] = val;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec2ClampToZero:
|
|
{
|
|
const u32 temp0 = mips->fi[(u32)inst->src1];
|
|
const u32 temp1 = mips->fi[(u32)inst->src1 + 1];
|
|
mips->fi[(u32)inst->dest] = (int)temp0 >= 0 ? temp0 : 0;
|
|
mips->fi[(u32)inst->dest + 1] = (int)temp1 >= 0 ? temp1 : 0;
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4ClampToZero:
|
|
{
|
|
#if PPSSPP_ARCH(SSE2)
|
|
// Trickery: Expand the sign bit, and use andnot to zero negative values.
|
|
__m128i val = _mm_load_si128((const __m128i *)&mips->fi[inst->src1]);
|
|
__m128i mask = _mm_srai_epi32(val, 31);
|
|
val = _mm_andnot_si128(mask, val);
|
|
_mm_store_si128((__m128i *)&mips->fi[inst->dest], val);
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
// On ARM we use a compare. On ARM64 we could also do a shift like on x86.
|
|
int32x4_t val = vld1q_s32((const int32_t *)&mips->fi[inst->src1]);
|
|
uint32x4_t mask = vcgtq_s32(val, vdupq_n_s32(-1)); // val > -1 → keeps >= 0
|
|
val = vandq_s32(val, vreinterpretq_s32_u32(mask)); // zero out negative lanes
|
|
vst1q_s32((int32_t *)&mips->fi[inst->dest], val);
|
|
#else
|
|
const int src1 = inst->src1;
|
|
const int dest = inst->dest;
|
|
for (int i = 0; i < 4; i++) {
|
|
u32 val = mips->fi[src1 + i];
|
|
mips->fi[dest + i] = (int)val >= 0 ? val : 0;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case IROp::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];
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::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);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::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;
|
|
break;
|
|
}
|
|
|
|
case IROp::FCmovVfpuCC:
|
|
if (((mips->vfpuCtrl[VFPU_CTRL_CC] >> (inst->src2 & 0xf)) & 1) == ((u32)inst->src2 >> 7)) {
|
|
mips->f[inst->dest] = mips->f[inst->src1];
|
|
}
|
|
break;
|
|
|
|
case IROp::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];
|
|
break;
|
|
}
|
|
|
|
case IROp::FSin:
|
|
mips->f[inst->dest] = vfpu_sin(mips->f[inst->src1]);
|
|
break;
|
|
case IROp::FCos:
|
|
mips->f[inst->dest] = vfpu_cos(mips->f[inst->src1]);
|
|
break;
|
|
case IROp::FRSqrt:
|
|
mips->f[inst->dest] = 1.0f / sqrtf(mips->f[inst->src1]);
|
|
break;
|
|
case IROp::FRecip:
|
|
mips->f[inst->dest] = 1.0f / mips->f[inst->src1];
|
|
break;
|
|
case IROp::FAsin:
|
|
mips->f[inst->dest] = vfpu_asin(mips->f[inst->src1]);
|
|
break;
|
|
|
|
case IROp::ShlImm:
|
|
mips->r[inst->dest] = mips->r[inst->src1] << (int)inst->src2;
|
|
break;
|
|
case IROp::ShrImm:
|
|
mips->r[inst->dest] = mips->r[inst->src1] >> (int)inst->src2;
|
|
break;
|
|
case IROp::SarImm:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (int)inst->src2;
|
|
break;
|
|
case IROp::RorImm:
|
|
{
|
|
u32 x = mips->r[inst->src1];
|
|
int sa = inst->src2;
|
|
mips->r[inst->dest] = (x >> sa) | (x << (32 - sa));
|
|
}
|
|
break;
|
|
|
|
case IROp::Shl:
|
|
mips->r[inst->dest] = mips->r[inst->src1] << (mips->r[inst->src2] & 31);
|
|
break;
|
|
case IROp::Shr:
|
|
mips->r[inst->dest] = mips->r[inst->src1] >> (mips->r[inst->src2] & 31);
|
|
break;
|
|
case IROp::Sar:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (mips->r[inst->src2] & 31);
|
|
break;
|
|
case IROp::Ror:
|
|
{
|
|
u32 x = mips->r[inst->src1];
|
|
int sa = mips->r[inst->src2] & 31;
|
|
mips->r[inst->dest] = (x >> sa) | (x << (32 - sa));
|
|
break;
|
|
}
|
|
|
|
case IROp::Clz:
|
|
{
|
|
mips->r[inst->dest] = clz32(mips->r[inst->src1]);
|
|
break;
|
|
}
|
|
|
|
case IROp::Slt:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2];
|
|
break;
|
|
|
|
case IROp::SltU:
|
|
mips->r[inst->dest] = mips->r[inst->src1] < mips->r[inst->src2];
|
|
break;
|
|
|
|
case IROp::SltConst:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)inst->constant;
|
|
break;
|
|
|
|
case IROp::SltUConst:
|
|
mips->r[inst->dest] = mips->r[inst->src1] < inst->constant;
|
|
break;
|
|
|
|
case IROp::MovZ:
|
|
if (mips->r[inst->src1] == 0)
|
|
mips->r[inst->dest] = mips->r[inst->src2];
|
|
break;
|
|
case IROp::MovNZ:
|
|
if (mips->r[inst->src1] != 0)
|
|
mips->r[inst->dest] = mips->r[inst->src2];
|
|
break;
|
|
|
|
case IROp::Max:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] > (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2];
|
|
break;
|
|
case IROp::Min:
|
|
mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2];
|
|
break;
|
|
|
|
case IROp::MtLo:
|
|
mips->lo = mips->r[inst->src1];
|
|
break;
|
|
case IROp::MtHi:
|
|
mips->hi = mips->r[inst->src1];
|
|
break;
|
|
case IROp::MfLo:
|
|
mips->r[inst->dest] = mips->lo;
|
|
break;
|
|
case IROp::MfHi:
|
|
mips->r[inst->dest] = mips->hi;
|
|
break;
|
|
|
|
case IROp::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).
|
|
break;
|
|
}
|
|
case IROp::MultU:
|
|
{
|
|
u64 result = (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
break;
|
|
}
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::MaddU:
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result += (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
break;
|
|
}
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::MsubU:
|
|
{
|
|
s64 result;
|
|
memcpy(&result, &mips->lo, 8);
|
|
result -= (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2];
|
|
memcpy(&mips->lo, &result, 8);
|
|
break;
|
|
}
|
|
|
|
case IROp::Div:
|
|
{
|
|
s32 numerator = (s32)mips->r[inst->src1];
|
|
s32 denominator = (s32)mips->r[inst->src2];
|
|
if (numerator == (s32)0x80000000 && denominator == -1) {
|
|
mips->lo = 0x80000000;
|
|
mips->hi = -1;
|
|
} else if (denominator != 0) {
|
|
mips->lo = (u32)(numerator / denominator);
|
|
mips->hi = (u32)(numerator % denominator);
|
|
} else {
|
|
mips->lo = numerator < 0 ? 1 : -1;
|
|
mips->hi = numerator;
|
|
}
|
|
break;
|
|
}
|
|
case IROp::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;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::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);
|
|
break;
|
|
}
|
|
case IROp::BSwap32:
|
|
{
|
|
mips->r[inst->dest] = swap32(mips->r[inst->src1]);
|
|
break;
|
|
}
|
|
|
|
case IROp::FAdd:
|
|
mips->f[inst->dest] = mips->f[inst->src1] + mips->f[inst->src2];
|
|
break;
|
|
case IROp::FSub:
|
|
mips->f[inst->dest] = mips->f[inst->src1] - mips->f[inst->src2];
|
|
break;
|
|
case IROp::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;
|
|
}
|
|
}
|
|
break;
|
|
#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];
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case IROp::FDiv:
|
|
mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2];
|
|
break;
|
|
case IROp::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]);
|
|
}
|
|
break;
|
|
case IROp::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]);
|
|
}
|
|
break;
|
|
|
|
case IROp::FMov:
|
|
mips->f[inst->dest] = mips->f[inst->src1];
|
|
break;
|
|
case IROp::FAbs:
|
|
mips->f[inst->dest] = fabsf(mips->f[inst->src1]);
|
|
break;
|
|
case IROp::FSqrt:
|
|
mips->f[inst->dest] = sqrtf(mips->f[inst->src1]);
|
|
break;
|
|
case IROp::FNeg:
|
|
mips->f[inst->dest] = -mips->f[inst->src1];
|
|
break;
|
|
case IROp::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);
|
|
break;
|
|
case IROp::FSatMinus1_1:
|
|
mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], -1.0f, 1.0f);
|
|
break;
|
|
|
|
case IROp::FSign:
|
|
{
|
|
// Bitwise trickery
|
|
u32 val;
|
|
memcpy(&val, &mips->f[inst->src1], sizeof(u32));
|
|
if (val == 0 || val == 0x80000000)
|
|
mips->f[inst->dest] = 0.0f;
|
|
else if ((val >> 31) == 0)
|
|
mips->f[inst->dest] = 1.0f;
|
|
else
|
|
mips->f[inst->dest] = -1.0f;
|
|
break;
|
|
}
|
|
|
|
case IROp::FpCondFromReg:
|
|
// Note: the register is in src1, see the "_G" meta - the native backends read it there.
|
|
mips->fpcond = mips->r[inst->src1];
|
|
break;
|
|
case IROp::FpCondToReg:
|
|
mips->r[inst->dest] = mips->fpcond;
|
|
break;
|
|
case IROp::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;
|
|
break;
|
|
case IROp::FpCtrlToReg:
|
|
// Update the fpcond bit first.
|
|
mips->fcr31 = (mips->fcr31 & ~(1 << 23)) | ((mips->fpcond & 1) << 23);
|
|
mips->r[inst->dest] = mips->fcr31;
|
|
break;
|
|
case IROp::VfpuCtrlToReg:
|
|
mips->r[inst->dest] = mips->vfpuCtrl[inst->src1];
|
|
break;
|
|
case IROp::FRound:
|
|
{
|
|
float value = mips->f[inst->src1];
|
|
if (my_isnanorinf(value)) {
|
|
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
|
|
break;
|
|
} else {
|
|
mips->fs[inst->dest] = (int)round_ieee_754(value);
|
|
}
|
|
break;
|
|
}
|
|
case IROp::FTrunc:
|
|
{
|
|
float value = mips->f[inst->src1];
|
|
if (my_isnanorinf(value)) {
|
|
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
|
|
break;
|
|
} else {
|
|
if (value >= 0.0f) {
|
|
mips->fs[inst->dest] = (int)floorf(value);
|
|
// Overflow, but it was positive.
|
|
if (mips->fs[inst->dest] == -2147483648LL) {
|
|
mips->fs[inst->dest] = 2147483647LL;
|
|
}
|
|
} else {
|
|
// Overflow happens to be the right value anyway.
|
|
mips->fs[inst->dest] = (int)ceilf(value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
case IROp::FCeil:
|
|
{
|
|
float value = mips->f[inst->src1];
|
|
if (my_isnanorinf(value)) {
|
|
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
|
|
break;
|
|
} else {
|
|
mips->fs[inst->dest] = (int)ceilf(value);
|
|
}
|
|
break;
|
|
}
|
|
case IROp::FFloor:
|
|
{
|
|
float value = mips->f[inst->src1];
|
|
if (my_isnanorinf(value)) {
|
|
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
|
|
break;
|
|
} else {
|
|
mips->fs[inst->dest] = (int)floorf(value);
|
|
}
|
|
break;
|
|
}
|
|
case IROp::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;
|
|
}
|
|
break;
|
|
|
|
case IROp::FCvtSW:
|
|
mips->f[inst->dest] = (float)mips->fs[inst->src1];
|
|
break;
|
|
case IROp::FCvtWS:
|
|
{
|
|
float src = mips->f[inst->src1];
|
|
if (my_isnanorinf(src)) {
|
|
mips->fs[inst->dest] = my_isinf(src) && src < 0.0f ? -2147483648LL : 2147483647LL;
|
|
break;
|
|
}
|
|
// TODO: Inline assembly to use here would be better.
|
|
switch (IRRoundMode(mips->fcr31 & 3)) {
|
|
case IRRoundMode::RINT_0: mips->fs[inst->dest] = (int)round_ieee_754(src); break;
|
|
case IRRoundMode::CAST_1: mips->fs[inst->dest] = (int)src; break;
|
|
case IRRoundMode::CEIL_2: mips->fs[inst->dest] = (int)ceilf(src); break;
|
|
case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = (int)floorf(src); break;
|
|
}
|
|
break; //cvt.w.s
|
|
}
|
|
case IROp::FCvtScaledSW:
|
|
mips->f[inst->dest] = (float)mips->fs[inst->src1] * (1.0f / (1UL << (inst->src2 & 0x1F)));
|
|
break;
|
|
case IROp::FCvtScaledWS:
|
|
{
|
|
float src = mips->f[inst->src1];
|
|
if (my_isnan(src)) {
|
|
// TODO: True for negatives too?
|
|
mips->fs[inst->dest] = 2147483647L;
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::FMovFromGPR:
|
|
memcpy(&mips->f[inst->dest], &mips->r[inst->src1], 4);
|
|
break;
|
|
case IROp::OptFCvtSWFromGPR:
|
|
mips->f[inst->dest] = (float)(int)mips->r[inst->src1];
|
|
break;
|
|
case IROp::FMovToGPR:
|
|
memcpy(&mips->r[inst->dest], &mips->f[inst->src1], 4);
|
|
break;
|
|
case IROp::OptFMovToGPRShr8:
|
|
{
|
|
u32 temp;
|
|
memcpy(&temp, &mips->f[inst->src1], 4);
|
|
mips->r[inst->dest] = temp >> 8;
|
|
break;
|
|
}
|
|
|
|
case IROp::ExitToConst:
|
|
return inst->constant;
|
|
|
|
case IROp::ExitToReg:
|
|
return mips->r[inst->src1];
|
|
|
|
case IROp::ExitToConstIfEq:
|
|
if (mips->r[inst->src1] == mips->r[inst->src2])
|
|
return inst->constant;
|
|
break;
|
|
case IROp::ExitToConstIfNeq:
|
|
if (mips->r[inst->src1] != mips->r[inst->src2])
|
|
return inst->constant;
|
|
break;
|
|
case IROp::ExitToConstIfGtZ:
|
|
if ((s32)mips->r[inst->src1] > 0)
|
|
return inst->constant;
|
|
break;
|
|
case IROp::ExitToConstIfGeZ:
|
|
if ((s32)mips->r[inst->src1] >= 0)
|
|
return inst->constant;
|
|
break;
|
|
case IROp::ExitToConstIfLtZ:
|
|
if ((s32)mips->r[inst->src1] < 0)
|
|
return inst->constant;
|
|
break;
|
|
case IROp::ExitToConstIfLeZ:
|
|
if ((s32)mips->r[inst->src1] <= 0)
|
|
return inst->constant;
|
|
break;
|
|
|
|
case IROp::Downcount:
|
|
mips->downcount -= (int)inst->constant;
|
|
break;
|
|
|
|
case IROp::SetPC:
|
|
mips->pc = mips->r[inst->src1];
|
|
break;
|
|
|
|
case IROp::SetPCConst:
|
|
mips->pc = inst->constant;
|
|
break;
|
|
|
|
case IROp::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);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::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);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IROp::ExitToPC:
|
|
return mips->pc;
|
|
|
|
case IROp::Interpret: // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging.
|
|
{
|
|
MIPSOpcode op(inst->constant);
|
|
MIPSInterpret(mips, op);
|
|
break;
|
|
}
|
|
|
|
case IROp::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;
|
|
break;
|
|
}
|
|
|
|
case IROp::SetCtrlVFPU:
|
|
mips->vfpuCtrl[inst->dest] = inst->constant;
|
|
break;
|
|
|
|
case IROp::SetCtrlVFPUReg:
|
|
mips->vfpuCtrl[inst->dest] = mips->r[inst->src1];
|
|
break;
|
|
|
|
case IROp::SetCtrlVFPUFReg:
|
|
memcpy(&mips->vfpuCtrl[inst->dest], &mips->f[inst->src1], 4);
|
|
break;
|
|
|
|
case IROp::ApplyRoundingMode:
|
|
ApplyHostRoundingMode(mips);
|
|
break;
|
|
case IROp::RestoreRoundingMode:
|
|
RestoreHostRoundingMode();
|
|
break;
|
|
case IROp::UpdateRoundingMode:
|
|
// TODO: Implement
|
|
break;
|
|
|
|
case IROp::Break:
|
|
Core_BreakException(mips->pc);
|
|
return mips->pc + 4;
|
|
|
|
case IROp::Breakpoint:
|
|
if (IRRunBreakpoint(inst->constant)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
|
|
case IROp::MemoryCheck:
|
|
if (IRRunMemCheck(mips->pc + inst->dest, mips->r[inst->src1] + inst->constant)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
|
|
case IROp::ValidateAddress8:
|
|
if (RunValidateAddress<1>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
case IROp::ValidateAddress16:
|
|
if (RunValidateAddress<2>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
case IROp::ValidateAddress32:
|
|
if (RunValidateAddress<4>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
case IROp::ValidateAddress128:
|
|
if (RunValidateAddress<16>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) {
|
|
CoreTiming::ForceCheck(mips);
|
|
return mips->pc;
|
|
}
|
|
break;
|
|
case IROp::LogIRBlock:
|
|
if (mipsTracer.tracing_enabled) {
|
|
mipsTracer.executed_blocks.push_back(inst->constant);
|
|
}
|
|
break;
|
|
|
|
case IROp::Nop: // Unused, add a break if we start using it to avoid UNREACHABLE.
|
|
case IROp::Bad:
|
|
default:
|
|
// Unimplemented IR op. Bad. We define it as unreachable so the compiler can optimize better (remove the range check).
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if (mips->r[0] != 0)
|
|
Crash();
|
|
#endif
|
|
inst++;
|
|
}
|
|
|
|
// We should not reach here anymore.
|
|
return 0;
|
|
}
|