Files
ppsspp/Core/MIPS/ARM/ArmCompVFPU.cpp
T
Henrik RydgårdandClaude Opus 5.5 775e4b776e VFPU: Produce the PSP's NaNs in the single-lane ops, on every core
The VFPU never keeps a NaN's payload (cpu/vfpu/nan_gen): vadd and vsub
produce 0x7F800001, and vmul, vdiv, vscl and vcrs the same with the
sign of s ^ t. The cores returned whatever NaN the host made, which
differs between x86 and ARM64. Now all four cores on both hosts match
the PSP for these ops. The horizontal ops (vdot and friends) are left
for when they move to the accurate dot product.

The IR gets FVfpuNaN and FVfpuProductNaN, scalar and Vec4, run after
the arithmetic. The native backends check for a NaN and branch past
the fixup, which only runs when there is one. PurgeTemps no longer
renames the dest of an FPR op that also reads it, which the product
ops are the first to do.

vsat0 and vsat1 also keep a NaN's bits now. The ARM64 JITs quieted a
signaling NaN through FMIN/FMAX, and the x86-64 IR JIT turned NaN into
0 in vsat0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:47:33 +02:00

2448 lines
65 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "ppsspp_config.h"
#if PPSSPP_ARCH(ARM)
#include <cmath>
#include <cstring>
#include "Common/CPUDetect.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Math/math_util.h"
#include "Core/Compatibility.h"
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/System.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/ARM/ArmJit.h"
#include "Core/MIPS/ARM/ArmRegCache.h"
// Cool NEON references:
// http://www.delmarnorth.com/microwave/requirements/neon-test-tutorial.pdf
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
// #define CONDITIONAL_DISABLE { fpr.ReleaseSpillLocksAndDiscardTemps(); Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { fpr.ReleaseSpillLocksAndDiscardTemps(); Comp_Generic(op); return; }
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
namespace MIPSComp
{
using namespace ArmGen;
using namespace ArmJitConstants;
// Vector regs can overlap in all sorts of swizzled ways.
// This does allow a single overlap in sregs[i].
static bool IsOverlapSafeAllowS(int dreg, int di, int sn, u8 sregs[], int tn = 0, u8 tregs[] = NULL)
{
for (int i = 0; i < sn; ++i)
{
if (sregs[i] == dreg && i != di)
return false;
}
for (int i = 0; i < tn; ++i)
{
if (tregs[i] == dreg)
return false;
}
// Hurray, no overlap, we can write directly.
return true;
}
static bool IsOverlapSafe(int dreg, int di, int sn, u8 sregs[], int tn = 0, u8 tregs[] = NULL)
{
return IsOverlapSafeAllowS(dreg, di, sn, sregs, tn, tregs) && sregs[di] != dreg;
}
void ArmJit::Comp_VPFX(MIPSOpcode op)
{
CONDITIONAL_DISABLE(VFPU_XFER);
int data = op & 0xFFFFF;
int regnum = (op >> 24) & 3;
switch (regnum) {
case 0: // S
js.prefixS = data;
js.prefixSFlag = JitState::PREFIX_KNOWN_DIRTY;
break;
case 1: // T
js.prefixT = data;
js.prefixTFlag = JitState::PREFIX_KNOWN_DIRTY;
break;
case 2: // D
js.prefixD = data & 0x00000FFF;
js.prefixDFlag = JitState::PREFIX_KNOWN_DIRTY;
break;
default:
ERROR_LOG(Log::CPU, "VPFX - bad regnum %i : data=%08x", regnum, data);
break;
}
}
void ArmJit::ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz) {
if (prefix == 0xE4)
return;
int n = GetNumVectorElements(sz);
u8 origV[4];
static const float constantArray[8] = {0.f, 1.f, 2.f, 0.5f, 3.f, 1.f/3.f, 0.25f, 1.f/6.f};
for (int i = 0; i < n; i++)
origV[i] = vregs[i];
for (int i = 0; i < n; i++) {
int regnum = (prefix >> (i*2)) & 3;
int abs = (prefix >> (8+i)) & 1;
int negate = (prefix >> (16+i)) & 1;
int constants = (prefix >> (12+i)) & 1;
// Unchanged, hurray.
if (!constants && regnum == i && !abs && !negate)
continue;
// This puts the value into a temp reg, so we won't write the modified value back.
vregs[i] = fpr.GetTempV();
if (!constants) {
fpr.MapDirtyInV(vregs[i], origV[regnum]);
fpr.SpillLockV(vregs[i]);
// Prefix may say "z, z, z, z" but if this is a pair, we force to x.
// TODO: But some ops seem to use const 0 instead?
if (regnum >= n) {
WARN_LOG(Log::CPU, "JIT: Invalid VFPU swizzle: %08x : %d / %d at PC = %08x (%s)", prefix, regnum, n, GetCompilerPC(), MIPSDisasmAt(GetCompilerPC()).c_str());
regnum = 0;
}
if (abs) {
VABS(fpr.V(vregs[i]), fpr.V(origV[regnum]));
if (negate)
VNEG(fpr.V(vregs[i]), fpr.V(vregs[i]));
} else {
if (negate)
VNEG(fpr.V(vregs[i]), fpr.V(origV[regnum]));
else
VMOV(fpr.V(vregs[i]), fpr.V(origV[regnum]));
}
} else {
fpr.MapRegV(vregs[i], MAP_DIRTY | MAP_NOINIT);
fpr.SpillLockV(vregs[i]);
MOVI2F(fpr.V(vregs[i]), constantArray[regnum + (abs<<2)], SCRATCHREG1, negate != 0);
}
}
}
void ArmJit::GetVectorRegsPrefixD(u8 *regs, VectorSize sz, int vectorReg) {
_assert_(js.prefixDFlag & JitState::PREFIX_KNOWN);
GetVectorRegs(regs, sz, vectorReg);
if (js.prefixD == 0)
return;
int n = GetNumVectorElements(sz);
for (int i = 0; i < n; i++) {
// Hopefully this is rare, we'll just write it into a reg we drop.
if (js.VfpuWriteMask(i))
regs[i] = fpr.GetTempV();
}
}
void ArmJit::ApplyPrefixD(const u8 *vregs, VectorSize sz) {
_assert_(js.prefixDFlag & JitState::PREFIX_KNOWN);
if (!js.prefixD)
return;
int n = GetNumVectorElements(sz);
for (int i = 0; i < n; i++) {
if (js.VfpuWriteMask(i))
continue;
int sat = (js.prefixD >> (i * 2)) & 3;
if (sat == 1) {
// clamped = x < 0 ? (x > 1 ? 1 : x) : x [0, 1]
fpr.MapRegV(vregs[i], MAP_DIRTY);
MOVI2F(S0, 0.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
VCMP(fpr.V(vregs[i]), S0);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_LS);
VMOV(fpr.V(vregs[i]), S0);
SetCC(CC_AL);
VCMP(fpr.V(vregs[i]), S1);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_GT);
VMOV(fpr.V(vregs[i]), S1);
SetCC(CC_AL);
} else if (sat == 3) {
// clamped = x < -1 ? (x > 1 ? 1 : x) : x [-1, 1]
fpr.MapRegV(vregs[i], MAP_DIRTY);
MOVI2F(S0, -1.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
VCMP(fpr.V(vregs[i]), S0);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_LO);
VMOV(fpr.V(vregs[i]), S0);
SetCC(CC_AL);
VCMP(fpr.V(vregs[i]), S1);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_GT);
VMOV(fpr.V(vregs[i]), S1);
SetCC(CC_AL);
}
}
}
void ArmJit::Comp_SV(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU_VFPU);
CheckMemoryBreakpoint();
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
s32 offset = (signed short)(op & 0xFFFC);
int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5);
MIPSGPReg rs = _RS;
bool doCheck = false;
switch (op >> 26)
{
case 50: //lv.s
{
if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset < 0x400 && offset > -0x400) {
gpr.MapRegAsPointer(rs);
fpr.MapRegV(vt, MAP_NOINIT | MAP_DIRTY);
VLDR(fpr.V(vt), gpr.RPtr(rs), offset);
break;
}
// CC might be set by slow path below, so load regs first.
fpr.MapRegV(vt, MAP_DIRTY | MAP_NOINIT);
if (gpr.IsImm(rs)) {
u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF;
gpr.SetRegImm(R0, addr + (u32)Memory::base);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetR0ToEffectiveAddress(rs, offset);
} else {
SetCCAndR0ForSafeAddress(rs, offset, SCRATCHREG2);
doCheck = true;
}
ADD(R0, R0, MEMBASEREG);
}
#ifdef __ARM_ARCH_7S__
FixupBranch skip;
if (doCheck) {
skip = B_CC(CC_EQ);
}
VLDR(fpr.V(vt), R0, 0);
if (doCheck) {
SetJumpTarget(skip);
SetCC(CC_AL);
}
#else
VLDR(fpr.V(vt), R0, 0);
if (doCheck) {
SetCC(CC_EQ);
MOVI2F(fpr.V(vt), 0.0f, SCRATCHREG1);
SetCC(CC_AL);
}
#endif
}
break;
case 58: //sv.s
{
if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset < 0x400 && offset > -0x400) {
gpr.MapRegAsPointer(rs);
fpr.MapRegV(vt, 0);
VSTR(fpr.V(vt), gpr.RPtr(rs), offset);
break;
}
// CC might be set by slow path below, so load regs first.
fpr.MapRegV(vt);
if (gpr.IsImm(rs)) {
u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF;
gpr.SetRegImm(R0, addr + (u32)Memory::base);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetR0ToEffectiveAddress(rs, offset);
} else {
SetCCAndR0ForSafeAddress(rs, offset, SCRATCHREG2);
doCheck = true;
}
ADD(R0, R0, MEMBASEREG);
}
#ifdef __ARM_ARCH_7S__
FixupBranch skip;
if (doCheck) {
skip = B_CC(CC_EQ);
}
VSTR(fpr.V(vt), R0, 0);
if (doCheck) {
SetJumpTarget(skip);
SetCC(CC_AL);
}
#else
VSTR(fpr.V(vt), R0, 0);
if (doCheck) {
SetCC(CC_AL);
}
#endif
}
break;
default:
DISABLE;
}
}
void ArmJit::Comp_SVQ(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU_VFPU);
CheckMemoryBreakpoint();
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
int imm = (signed short)(op&0xFFFC);
int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
MIPSGPReg rs = _RS;
bool doCheck = false;
switch (op >> 26)
{
case 54: //lv.q
{
// CC might be set by slow path below, so load regs first.
u8 vregs[4];
GetVectorRegs(vregs, V_Quad, vt);
fpr.MapRegsAndSpillLockV(vregs, V_Quad, MAP_DIRTY | MAP_NOINIT);
if (gpr.IsImm(rs)) {
u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF;
gpr.SetRegImm(R0, addr + (u32)Memory::base);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetR0ToEffectiveAddress(rs, imm);
} else {
SetCCAndR0ForSafeAddress(rs, imm, SCRATCHREG2);
doCheck = true;
}
ADD(R0, R0, MEMBASEREG);
}
#ifdef __ARM_ARCH_7S__
FixupBranch skip;
if (doCheck) {
skip = B_CC(CC_EQ);
}
bool consecutive = true;
for (int i = 0; i < 3 && consecutive; i++)
if ((fpr.V(vregs[i]) + 1) != fpr.V(vregs[i+1]))
consecutive = false;
if (consecutive) {
VLDMIA(R0, false, fpr.V(vregs[0]), 4);
} else {
for (int i = 0; i < 4; i++)
VLDR(fpr.V(vregs[i]), R0, i * 4);
}
if (doCheck) {
SetJumpTarget(skip);
SetCC(CC_AL);
}
#else
bool consecutive = true;
for (int i = 0; i < 3 && consecutive; i++)
if ((fpr.V(vregs[i]) + 1) != fpr.V(vregs[i+1]))
consecutive = false;
if (consecutive) {
VLDMIA(R0, false, fpr.V(vregs[0]), 4);
} else {
for (int i = 0; i < 4; i++)
VLDR(fpr.V(vregs[i]), R0, i * 4);
}
if (doCheck) {
SetCC(CC_EQ);
MOVI2R(SCRATCHREG1, 0);
for (int i = 0; i < 4; i++)
VMOV(fpr.V(vregs[i]), SCRATCHREG1);
SetCC(CC_AL);
}
#endif
}
break;
case 62: //sv.q
{
// CC might be set by slow path below, so load regs first.
u8 vregs[4];
GetVectorRegs(vregs, V_Quad, vt);
fpr.MapRegsAndSpillLockV(vregs, V_Quad, 0);
if (gpr.IsImm(rs)) {
u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF;
gpr.SetRegImm(R0, addr + (u32)Memory::base);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetR0ToEffectiveAddress(rs, imm);
} else {
SetCCAndR0ForSafeAddress(rs, imm, SCRATCHREG2);
doCheck = true;
}
ADD(R0, R0, MEMBASEREG);
}
#ifdef __ARM_ARCH_7S__
FixupBranch skip;
if (doCheck) {
skip = B_CC(CC_EQ);
}
bool consecutive = true;
for (int i = 0; i < 3 && consecutive; i++)
if ((fpr.V(vregs[i]) + 1) != fpr.V(vregs[i+1]))
consecutive = false;
if (consecutive) {
VSTMIA(R0, false, fpr.V(vregs[0]), 4);
} else {
for (int i = 0; i < 4; i++)
VSTR(fpr.V(vregs[i]), R0, i * 4);
}
if (doCheck) {
SetJumpTarget(skip);
SetCC(CC_AL);
}
#else
bool consecutive = true;
for (int i = 0; i < 3 && consecutive; i++)
if ((fpr.V(vregs[i]) + 1) != fpr.V(vregs[i+1]))
consecutive = false;
if (consecutive) {
VSTMIA(R0, false, fpr.V(vregs[0]), 4);
} else {
for (int i = 0; i < 4; i++)
VSTR(fpr.V(vregs[i]), R0, i * 4);
}
if (doCheck) {
SetCC(CC_AL);
}
#endif
}
break;
default:
DISABLE;
break;
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VVectorInit(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
// WARNING: No prefix support!
if (js.HasUnknownPrefix()) {
DISABLE;
}
switch ((op >> 16) & 0xF)
{
case 6: // v=zeros; break; //vzero
MOVI2F(S0, 0.0f, SCRATCHREG1);
break;
case 7: // v=ones; break; //vone
MOVI2F(S0, 1.0f, SCRATCHREG1);
break;
default:
DISABLE;
break;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 dregs[4];
GetVectorRegsPrefixD(dregs, sz, _VD);
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
for (int i = 0; i < n; ++i)
VMOV(fpr.V(dregs[i]), S0);
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VIdt(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
// Only pairs and quads are handled here.
if (sz != V_Pair && sz != V_Quad) {
DISABLE;
}
MOVI2F(S0, 0.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
u8 dregs[4];
GetVectorRegsPrefixD(dregs, sz, _VD);
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
switch (sz)
{
case V_Pair:
VMOV(fpr.V(dregs[0]), (vd&1)==0 ? S1 : S0);
VMOV(fpr.V(dregs[1]), (vd&1)==1 ? S1 : S0);
break;
case V_Quad:
VMOV(fpr.V(dregs[0]), (vd&3)==0 ? S1 : S0);
VMOV(fpr.V(dregs[1]), (vd&3)==1 ? S1 : S0);
VMOV(fpr.V(dregs[2]), (vd&3)==2 ? S1 : S0);
VMOV(fpr.V(dregs[3]), (vd&3)==3 ? S1 : S0);
break;
default:
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
break;
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VMatrixInit(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
if (js.HasUnknownPrefix()) {
// Don't think matrix init ops care about prefixes.
// DISABLE;
}
MatrixSize sz = GetMtxSize(op);
int n = GetMatrixSide(sz);
u8 dregs[16];
GetMatrixRegs(dregs, sz, _VD);
switch ((op >> 16) & 0xF) {
case 3: // vmidt
MOVI2F(S0, 0.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
for (int a = 0; a < n; a++) {
for (int b = 0; b < n; b++) {
fpr.MapRegV(dregs[a * 4 + b], MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(dregs[a * 4 + b]), a == b ? S1 : S0);
}
}
break;
case 6: // vmzero
MOVI2F(S0, 0.0f, SCRATCHREG1);
for (int a = 0; a < n; a++) {
for (int b = 0; b < n; b++) {
fpr.MapRegV(dregs[a * 4 + b], MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(dregs[a * 4 + b]), S0);
}
}
break;
case 7: // vmone
MOVI2F(S1, 1.0f, SCRATCHREG1);
for (int a = 0; a < n; a++) {
for (int b = 0; b < n; b++) {
fpr.MapRegV(dregs[a * 4 + b], MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(dregs[a * 4 + b]), S1);
}
}
break;
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VHdp(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
// TODO: Force read one of them into regs? probably not.
u8 sregs[4], tregs[4], dregs[1];
GetVectorRegsPrefixS(sregs, sz, vs);
GetVectorRegsPrefixT(tregs, sz, vt);
GetVectorRegsPrefixD(dregs, V_Single, vd);
// TODO: applyprefixST here somehow (shuffle, etc...)
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
fpr.MapRegsAndSpillLockV(tregs, sz, 0);
VMUL(S0, fpr.V(sregs[0]), fpr.V(tregs[0]));
int n = GetNumVectorElements(sz);
for (int i = 1; i < n; i++) {
// sum += s[i]*t[i];
if (i == n - 1) {
VADD(S0, S0, fpr.V(tregs[i]));
} else {
VMLA(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
}
}
fpr.ReleaseSpillLocksAndDiscardTemps();
fpr.MapRegV(dregs[0], MAP_NOINIT | MAP_DIRTY);
VMOV(fpr.V(dregs[0]), S0);
ApplyPrefixD(dregs, V_Single);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
alignas(16) static const float vavg_table[4] = { 1.0f, 1.0f / 2.0f, 1.0f / 3.0f, 1.0f / 4.0f };
void ArmJit::Comp_Vhoriz(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
// TODO: Force read one of them into regs? probably not.
u8 sregs[4], dregs[1];
GetVectorRegsPrefixS(sregs, sz, vs);
GetVectorRegsPrefixD(dregs, V_Single, vd);
// TODO: applyprefixST here somehow (shuffle, etc...)
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
int n = GetNumVectorElements(sz);
bool is_vavg = ((op >> 16) & 0x1f) == 7;
if (is_vavg) {
MOVI2F(S1, vavg_table[n - 1], R0);
}
// Have to start at +0.000 for the correct sign.
MOVI2F(S0, 0.0f, SCRATCHREG1);
for (int i = 0; i < n; i++) {
// sum += s[i];
VADD(S0, S0, fpr.V(sregs[i]));
}
fpr.MapRegV(dregs[0], MAP_NOINIT | MAP_DIRTY);
if (is_vavg) {
VMUL(fpr.V(dregs[0]), S0, S1);
} else {
VMOV(fpr.V(dregs[0]), S0);
}
ApplyPrefixD(dregs, V_Single);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VDot(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
// TODO: Force read one of them into regs? probably not.
u8 sregs[4], tregs[4], dregs[1];
GetVectorRegsPrefixS(sregs, sz, vs);
GetVectorRegsPrefixT(tregs, sz, vt);
GetVectorRegsPrefixD(dregs, V_Single, vd);
// TODO: applyprefixST here somehow (shuffle, etc...)
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
fpr.MapRegsAndSpillLockV(tregs, sz, 0);
VMUL(S0, fpr.V(sregs[0]), fpr.V(tregs[0]));
int n = GetNumVectorElements(sz);
for (int i = 1; i < n; i++) {
// sum += s[i]*t[i];
VMLA(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
}
fpr.ReleaseSpillLocksAndDiscardTemps();
fpr.MapRegV(dregs[0], MAP_NOINIT | MAP_DIRTY);
VMOV(fpr.V(dregs[0]), S0);
ApplyPrefixD(dregs, V_Single);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
// The VFPU's NaNs (see VfpuSumNaN in InterpreterVFPU.cpp): 0x7F800001, and for a product or
// quotient with the sign of a ^ b, which must not be r.
void ArmJit::EmitVfpuSumNaN(ARMReg r) {
VCMP(r, r);
VMRS_APSR();
FixupBranch notNaN = B_CC(CC_VC);
MOVI2R(SCRATCHREG1, 0x7F800001);
VMOV(r, SCRATCHREG1);
SetJumpTarget(notNaN);
}
void ArmJit::EmitVfpuProductNaN(ARMReg r, ARMReg a, ARMReg b) {
VCMP(r, r);
VMRS_APSR();
FixupBranch notNaN = B_CC(CC_VC);
VMOV(SCRATCHREG1, a);
VMOV(SCRATCHREG2, b);
EOR(SCRATCHREG1, SCRATCHREG1, SCRATCHREG2);
ANDI2R(SCRATCHREG1, SCRATCHREG1, 0x80000000, SCRATCHREG2);
ORI2R(SCRATCHREG1, SCRATCHREG1, 0x7F800001, SCRATCHREG2);
VMOV(r, SCRATCHREG1);
SetJumpTarget(notNaN);
}
void ArmJit::Comp_VecDo3(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], tregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixT(tregs, sz, _VT);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; i++) {
if (!IsOverlapSafe(dregs[i], i, n, sregs, n, tregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
// Map first, then work. This will allow us to use VLDMIA more often
// (when we add the appropriate map function) and the instruction ordering
// will improve.
// Note that mapping like this (instead of first all sregs, first all tregs etc)
// reduces the amount of continuous registers a lot :(
for (int i = 0; i < n; i++) {
fpr.MapDirtyInInV(tempregs[i], sregs[i], tregs[i]);
fpr.SpillLockV(tempregs[i]);
fpr.SpillLockV(sregs[i]);
fpr.SpillLockV(tregs[i]);
}
for (int i = 0; i < n; i++) {
switch (op >> 26) {
case 24: //VFPU0
switch ((op >> 23)&7) {
case 0: // d[i] = s[i] + t[i]; break; //vadd
VADD(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuSumNaN(fpr.V(tempregs[i]));
break;
case 1: // d[i] = s[i] - t[i]; break; //vsub
VSUB(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuSumNaN(fpr.V(tempregs[i]));
break;
case 7: // d[i] = s[i] / t[i]; break; //vdiv
// The NaN's sign needs the operands, which d may be.
if (tempregs[i] == sregs[i] || tempregs[i] == tregs[i]) {
VDIV(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuProductNaN(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
VMOV(fpr.V(tempregs[i]), S0);
} else {
VDIV(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuProductNaN(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
}
break;
default:
DISABLE;
}
break;
case 25: //VFPU1
switch ((op >> 23) & 7) {
case 0: // d[i] = s[i] * t[i]; break; //vmul
if (tempregs[i] == sregs[i] || tempregs[i] == tregs[i]) {
VMUL(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuProductNaN(S0, fpr.V(sregs[i]), fpr.V(tregs[i]));
VMOV(fpr.V(tempregs[i]), S0);
} else {
VMUL(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
EmitVfpuProductNaN(fpr.V(tempregs[i]), fpr.V(sregs[i]), fpr.V(tregs[i]));
}
break;
default:
DISABLE;
}
break;
// Unfortunately there is no VMIN/VMAX on ARM without NEON.
case 27: //VFPU3
switch ((op >> 23) & 7) {
case 2: // vmin
{
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
FixupBranch skipNAN = B_CC(CC_VC);
VMOV(SCRATCHREG1, fpr.V(sregs[i]));
VMOV(SCRATCHREG2, fpr.V(tregs[i]));
// If both are negative, we reverse the comparison. We want the highest mantissa then.
// Also, between -NAN and -5.0, we want -NAN to be less.
TST(SCRATCHREG1, SCRATCHREG2);
FixupBranch cmpPositive = B_CC(CC_PL);
CMP(SCRATCHREG2, SCRATCHREG1);
FixupBranch skipPositive = B();
SetJumpTarget(cmpPositive);
CMP(SCRATCHREG1, SCRATCHREG2);
SetJumpTarget(skipPositive);
SetCC(CC_AL);
SetJumpTarget(skipNAN);
SetCC(CC_LT);
VMOV(fpr.V(tempregs[i]), fpr.V(sregs[i]));
SetCC(CC_GE);
VMOV(fpr.V(tempregs[i]), fpr.V(tregs[i]));
SetCC(CC_AL);
break;
}
case 3: // vmax
{
VCMP(fpr.V(tregs[i]), fpr.V(sregs[i]));
VMRS_APSR();
FixupBranch skipNAN = B_CC(CC_VC);
VMOV(SCRATCHREG1, fpr.V(sregs[i]));
VMOV(SCRATCHREG2, fpr.V(tregs[i]));
// If both are negative, we reverse the comparison. We want the lowest mantissa then.
// Also, between -NAN and -5.0, we want -5.0 to be greater.
TST(SCRATCHREG2, SCRATCHREG1);
FixupBranch cmpPositive = B_CC(CC_PL);
CMP(SCRATCHREG1, SCRATCHREG2);
FixupBranch skipPositive = B();
SetJumpTarget(cmpPositive);
CMP(SCRATCHREG2, SCRATCHREG1);
SetJumpTarget(skipPositive);
SetCC(CC_AL);
SetJumpTarget(skipNAN);
SetCC(CC_LT);
VMOV(fpr.V(tempregs[i]), fpr.V(sregs[i]));
SetCC(CC_GE);
VMOV(fpr.V(tempregs[i]), fpr.V(tregs[i]));
SetCC(CC_AL);
break;
}
case 6: // vsge
DISABLE; // pending testing
VCMP(fpr.V(tregs[i]), fpr.V(sregs[i]));
VMRS_APSR();
// Unordered is always 0.
SetCC(CC_GE);
MOVI2F(fpr.V(tempregs[i]), 1.0f, SCRATCHREG1);
SetCC(CC_LT);
MOVI2F(fpr.V(tempregs[i]), 0.0f, SCRATCHREG1);
SetCC(CC_AL);
break;
case 7: // vslt
DISABLE; // pending testing
VCMP(fpr.V(tregs[i]), fpr.V(sregs[i]));
VMRS_APSR();
// Unordered is always 0.
SetCC(CC_LO);
MOVI2F(fpr.V(tempregs[i]), 1.0f, SCRATCHREG1);
SetCC(CC_HS);
MOVI2F(fpr.V(tempregs[i]), 0.0f, SCRATCHREG1);
SetCC(CC_AL);
break;
}
break;
default:
DISABLE;
}
}
for (int i = 0; i < n; i++) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VV2Op(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
// Pre-processing: Eliminate silly no-op VMOVs, common in Wipeout Pure
if (((op >> 16) & 0x1f) == 0 && _VS == _VD && js.HasNoPrefix()) {
return;
}
// Catch the disabled operations immediately so we don't map registers unnecessarily later.
// Move these down to the big switch below as they are implemented.
switch ((op >> 16) & 0x1f) {
case 18: // d[i] = sinf((float)M_PI_2 * s[i]); break; //vsin
DISABLE;
break;
case 19: // d[i] = cosf((float)M_PI_2 * s[i]); break; //vcos
DISABLE;
break;
case 20: // d[i] = powf(2.0f, s[i]); break; //vexp2
DISABLE;
break;
case 21: // d[i] = logf(s[i])/log(2.0f); break; //vlog2
DISABLE;
break;
case 16: // vrcp
case 17: // vrsq
case 22: // vsqrt
case 24: // vnrcp
CompVV2OpCall(op);
return;
case 26: // d[i] = -sinf((float)M_PI_2 * s[i]); break; // vnsin
DISABLE;
break;
case 28: // d[i] = 1.0f / expf(s[i] * (float)M_LOG2E); break; // vrexp2
DISABLE;
break;
default:
;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
// Get some extra temps, used by vasin only.
ARMReg t2 = INVALID_REG, t3 = INVALID_REG, t4 = INVALID_REG;
if (((op >> 16) & 0x1f) == 23) {
// Only get here on vasin.
int t[3] = { fpr.GetTempV(), fpr.GetTempV(), fpr.GetTempV() };
fpr.MapRegV(t[0], MAP_NOINIT);
fpr.MapRegV(t[1], MAP_NOINIT);
fpr.MapRegV(t[2], MAP_NOINIT);
t2 = fpr.V(t[0]);
t3 = fpr.V(t[1]);
t4 = fpr.V(t[2]);
}
// Pre map the registers to get better instruction ordering.
// Note that mapping like this (instead of first all sregs, first all tempregs etc)
// reduces the amount of continuous registers a lot :(
for (int i = 0; i < n; i++) {
fpr.MapDirtyInV(tempregs[i], sregs[i]);
fpr.SpillLockV(tempregs[i]);
fpr.SpillLockV(sregs[i]);
}
// Warning: sregs[i] and tempxregs[i] may be the same reg.
// Helps for vmov, hurts for vrcp, etc.
for (int i = 0; i < n; i++) {
switch ((op >> 16) & 0x1f) {
case 0: // d[i] = s[i]; break; //vmov
// Probably for swizzle.
VMOV(fpr.V(tempregs[i]), fpr.V(sregs[i]));
break;
case 1: // d[i] = fabsf(s[i]); break; //vabs
VABS(fpr.V(tempregs[i]), fpr.V(sregs[i]));
break;
case 2: // d[i] = -s[i]; break; //vneg
VNEG(fpr.V(tempregs[i]), fpr.V(sregs[i]));
break;
case 4: // if (s[i] < 0) d[i] = 0; else {if(s[i] > 1.0f) d[i] = 1.0f; else d[i] = s[i];} break; // vsat0
if (i == 0) {
MOVI2F(S0, 0.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
}
VCMP(fpr.V(sregs[i]), S0);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
VMOV(fpr.V(tempregs[i]), fpr.V(sregs[i]));
SetCC(CC_LS);
VMOV(fpr.V(tempregs[i]), S0);
SetCC(CC_AL);
VCMP(fpr.V(sregs[i]), S1);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_GT);
VMOV(fpr.V(tempregs[i]), S1);
SetCC(CC_AL);
break;
case 5: // if (s[i] < -1.0f) d[i] = -1.0f; else {if(s[i] > 1.0f) d[i] = 1.0f; else d[i] = s[i];} break; // vsat1
if (i == 0) {
MOVI2F(S0, -1.0f, SCRATCHREG1);
MOVI2F(S1, 1.0f, SCRATCHREG1);
}
VCMP(fpr.V(sregs[i]), S0);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
VMOV(fpr.V(tempregs[i]), fpr.V(sregs[i]));
SetCC(CC_LO);
VMOV(fpr.V(tempregs[i]), S0);
SetCC(CC_AL);
VCMP(fpr.V(sregs[i]), S1);
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_GT);
VMOV(fpr.V(tempregs[i]), S1);
SetCC(CC_AL);
break;
case 23: // d[i] = asinf(s[i] * (float)M_2_PI); break; //vasin
// Seems to work well enough but can disable if it becomes a problem.
// Should be easy enough to translate to NEON. There we can load all the constants
// in one go of course.
VCMP(fpr.V(sregs[i])); // flags = sign(sregs[i])
VMRS_APSR();
MOVI2F(S0, 1.0f, SCRATCHREG1);
VABS(t4, fpr.V(sregs[i])); // t4 = |sregs[i]|
VSUB(t3, S0, t4);
VSQRT(t3, t3); // t3 = sqrt(1 - |sregs[i]|)
MOVI2F(S1, -0.0187293f, SCRATCHREG1);
MOVI2F(t2, 0.0742610f, SCRATCHREG1);
VMLA(t2, t4, S1);
MOVI2F(S1, -0.2121144f, SCRATCHREG1);
VMLA(S1, t4, t2);
MOVI2F(t2, 1.5707288f, SCRATCHREG1);
VMLA(t2, t4, S1);
MOVI2F(fpr.V(tempregs[i]), M_PI / 2, SCRATCHREG1);
VMLS(fpr.V(tempregs[i]), t2, t3); // tr[i] = M_PI / 2 - t2 * t3
{
FixupBranch br = B_CC(CC_GE);
VNEG(fpr.V(tempregs[i]), fpr.V(tempregs[i]));
SetJumpTarget(br);
}
// Correction factor for PSP range. Could be baked into the calculation above?
MOVI2F(S1, 1.0f / (M_PI / 2), SCRATCHREG1);
VMUL(fpr.V(tempregs[i]), fpr.V(tempregs[i]), S1);
break;
default:
ERROR_LOG(Log::JIT, "case missing in vfpu vv2op");
DISABLE;
break;
}
}
for (int i = 0; i < n; ++i) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vi2f(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
int imm = (op >> 16) & 0x1f;
const float mult = 1.0f / (float)(1UL << imm);
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
if (mult != 1.0f)
MOVI2F(S0, mult, SCRATCHREG1);
for (int i = 0; i < n; i++) {
fpr.MapDirtyInV(tempregs[i], sregs[i]);
VCVT(fpr.V(tempregs[i]), fpr.V(sregs[i]), TO_FLOAT | IS_SIGNED);
if (mult != 1.0f)
VMUL(fpr.V(tempregs[i]), fpr.V(tempregs[i]), S0);
}
for (int i = 0; i < n; ++i) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vh2f(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
// This multi-VCVT.F32.F16 is only available in the VFPv4 extension.
// The VFPv3 one is VCVTB, VCVTT which we don't yet have support for.
if (!(cpu_info.bHalf && cpu_info.bVFPv4)) {
// No hardware support for half-to-float, fallback to interpreter
// TODO: Translate the fast SSE solution to standard integer/VFP stuff
// for the weaker CPUs.
DISABLE;
}
u8 sregs[4], dregs[4];
VectorSize sz = GetVecSize(op);
VectorSize outSz;
switch (sz) {
case V_Single:
outSz = V_Pair;
break;
case V_Pair:
outSz = V_Quad;
break;
default:
DISABLE;
}
int n = GetNumVectorElements(sz);
int nOut = n * 2;
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, outSz, _VD);
static const ARMReg tmp[4] = { S0, S1, S2, S3 };
for (int i = 0; i < n; i++) {
fpr.MapRegV(sregs[i], sz);
VMOV(tmp[i], fpr.V(sregs[i]));
}
// This always converts four 16-bit floats in D0 to four 32-bit floats
// in Q0. If we are dealing with a pair here, we just ignore the upper two outputs.
// There are also a couple of other instructions that do it one at a time but doesn't
// seem worth the trouble.
VCVTF32F16(Q0, D0);
for (int i = 0; i < nOut; i++) {
fpr.MapRegV(dregs[i], MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(dregs[i]), tmp[i]);
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vf2i(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
DISABLE;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
int imm = (op >> 16) & 0x1f;
float mult = (float)(1ULL << imm);
switch ((op >> 21) & 0x1f)
{
case 17:
break; //z - truncate. Easy to support.
case 16:
case 18:
case 19:
DISABLE;
break;
}
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
if (mult != 1.0f)
MOVI2F(S1, mult, SCRATCHREG1);
for (int i = 0; i < n; i++) {
fpr.MapDirtyInV(tempregs[i], sregs[i]);
switch ((op >> 21) & 0x1f) {
case 16: /* TODO */ break; //n
case 17:
if (mult != 1.0f) {
VMUL(S0, fpr.V(sregs[i]), S1);
VCVT(fpr.V(tempregs[i]), S0, TO_INT | ROUND_TO_ZERO);
} else {
VCVT(fpr.V(tempregs[i]), fpr.V(sregs[i]), TO_INT | ROUND_TO_ZERO);
}
break;
case 18: /* TODO */ break; //u
case 19: /* TODO */ break; //d
}
}
for (int i = 0; i < n; ++i) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Mftv(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
int imm = op & 0xFF;
MIPSGPReg rt = _RT;
switch ((op >> 21) & 0x1f) {
case 3: //mfv / mfvc
// rt = 0, imm = 255 appears to be used as a CPU interlock by some games.
if (rt != 0) {
if (imm < 128) { //R(rt) = VI(imm);
fpr.MapRegV(imm, 0);
gpr.MapReg(rt, MAP_NOINIT | MAP_DIRTY);
VMOV(gpr.R(rt), fpr.V(imm));
} else if (imm < 128 + VFPU_CTRL_MAX) { //mtvc
if (imm - 128 == VFPU_CTRL_CC) {
if (gpr.IsImm(MIPS_REG_VFPUCC)) {
gpr.SetImm(rt, gpr.GetImm(MIPS_REG_VFPUCC));
} else {
gpr.MapDirtyIn(rt, MIPS_REG_VFPUCC);
MOV(gpr.R(rt), gpr.R(MIPS_REG_VFPUCC));
}
} else {
// In case we have a saved prefix.
FlushPrefixV();
gpr.MapReg(rt, MAP_NOINIT | MAP_DIRTY);
LDR(gpr.R(rt), CTXREG, offsetof(MIPSState, vfpuCtrl) + 4 * (imm - 128));
}
} else {
//ERROR - maybe need to make this value too an "interlock" value?
ERROR_LOG(Log::CPU, "mfv - invalid register %i", imm);
}
}
break;
case 7: // mtv
if (imm < 128) {
gpr.MapReg(rt);
fpr.MapRegV(imm, MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(imm), gpr.R(rt));
} else if (imm < 128 + VFPU_CTRL_MAX) { //mtvc //currentMIPS->vfpuCtrl[imm - 128] = R(rt);
if (imm - 128 == VFPU_CTRL_CC) {
if (gpr.IsImm(rt)) {
gpr.SetImm(MIPS_REG_VFPUCC, gpr.GetImm(rt));
} else {
gpr.MapDirtyIn(MIPS_REG_VFPUCC, rt);
MOV(gpr.R(MIPS_REG_VFPUCC), gpr.R(rt));
}
} else {
gpr.MapReg(rt);
STR(gpr.R(rt), CTXREG, offsetof(MIPSState, vfpuCtrl) + 4 * (imm - 128));
}
// TODO: Optimization if rt is Imm?
// Set these BEFORE disable!
if (imm - 128 == VFPU_CTRL_SPREFIX) {
js.prefixSFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
} else if (imm - 128 == VFPU_CTRL_TPREFIX) {
js.prefixTFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
} else if (imm - 128 == VFPU_CTRL_DPREFIX) {
js.prefixDFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
}
} else {
//ERROR
_dbg_assert_msg_(false,"mtv - invalid register");
}
break;
default:
DISABLE;
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vmfvc(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
int vd = _VD;
int imm = (op >> 8) & 0x7F;
if (imm < VFPU_CTRL_MAX) {
fpr.MapRegV(vd, MAP_DIRTY | MAP_NOINIT);
if (imm == VFPU_CTRL_CC) {
gpr.MapReg(MIPS_REG_VFPUCC, 0);
VMOV(fpr.V(vd), gpr.R(MIPS_REG_VFPUCC));
} else {
// In case we have a saved prefix.
FlushPrefixV();
ADDI2R(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[0]) + imm * 4, SCRATCHREG2);
VLDR(fpr.V(vd), SCRATCHREG1, 0);
}
fpr.ReleaseSpillLocksAndDiscardTemps();
} else {
fpr.MapRegV(vd);
MOVI2F(fpr.V(vd), 0.0f, SCRATCHREG1);
}
}
void ArmJit::Comp_Vmtvc(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
int vs = _VS;
int imm = op & 0x7F;
if (imm < VFPU_CTRL_MAX) {
fpr.MapRegV(vs);
if (imm == VFPU_CTRL_CC) {
gpr.MapReg(MIPS_REG_VFPUCC, MAP_DIRTY | MAP_NOINIT);
VMOV(gpr.R(MIPS_REG_VFPUCC), fpr.V(vs));
} else {
ADDI2R(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[0]) + imm * 4, SCRATCHREG2);
VSTR(fpr.V(vs), SCRATCHREG1, 0);
}
fpr.ReleaseSpillLocksAndDiscardTemps();
if (imm == VFPU_CTRL_SPREFIX) {
js.prefixSFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
} else if (imm == VFPU_CTRL_TPREFIX) {
js.prefixTFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
} else if (imm == VFPU_CTRL_DPREFIX) {
js.prefixDFlag = JitState::PREFIX_UNKNOWN;
js.blockWrotePrefixes = true;
}
}
}
void ArmJit::Comp_Vmmov(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_MTX_VMMOV);
// This probably ignores prefixes for all sane intents and purposes.
if (_VS == _VD) {
// A lot of these no-op matrix moves in Wipeout... Just drop the instruction entirely.
return;
}
MatrixSize sz = GetMtxSize(op);
int n = GetMatrixSide(sz);
u8 sregs[16], dregs[16];
GetMatrixRegs(sregs, sz, _VS);
GetMatrixRegs(dregs, sz, _VD);
// Rough overlap check.
bool overlap = false;
if (GetMtx(_VS) == GetMtx(_VD)) {
// Potential overlap (guaranteed for 3x3 or more).
overlap = true;
}
if (overlap) {
// Not so common, fallback.
DISABLE;
} else {
for (int a = 0; a < n; a++) {
for (int b = 0; b < n; b++) {
fpr.MapDirtyInV(dregs[a * 4 + b], sregs[a * 4 + b]);
VMOV(fpr.V(dregs[a * 4 + b]), fpr.V(sregs[a * 4 + b]));
}
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
}
void ArmJit::Comp_VScl(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], dregs[4], treg;
GetVectorRegsPrefixS(sregs, sz, _VS);
// TODO: Prefixes seem strange...
GetVectorRegsPrefixT(&treg, V_Single, _VT);
GetVectorRegsPrefixD(dregs, sz, _VD);
// Move to S0 early, so we don't have to worry about overlap with scale.
fpr.LoadToRegV(S0, treg);
// For prefixes to work, we just have to ensure that none of the output registers spill
// and that there's no overlap.
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
// Need to use temp regs
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
// The meat of the function!
for (int i = 0; i < n; i++) {
fpr.MapDirtyInV(tempregs[i], sregs[i]);
// The NaN's sign needs s, which d may be.
if (tempregs[i] == sregs[i]) {
VMUL(S1, fpr.V(sregs[i]), S0);
EmitVfpuProductNaN(S1, fpr.V(sregs[i]), S0);
VMOV(fpr.V(tempregs[i]), S1);
} else {
VMUL(fpr.V(tempregs[i]), fpr.V(sregs[i]), S0);
EmitVfpuProductNaN(fpr.V(tempregs[i]), fpr.V(sregs[i]), S0);
}
}
for (int i = 0; i < n; i++) {
// All must be mapped for prefixes to work.
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vmmul(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_MTX_VMMUL);
if (!js.HasNoPrefix()) {
DISABLE;
}
if (PSP_CoreParameter().compat.flags().MoreAccurateVMMUL) {
// Fall back to interpreter, which has the accurate implementation.
// Later we might do something more optimized here.
DISABLE;
}
MatrixSize sz = GetMtxSize(op);
int n = GetMatrixSide(sz);
u8 sregs[16], tregs[16], dregs[16];
GetMatrixRegs(sregs, sz, _VS);
GetMatrixRegs(tregs, sz, _VT);
GetMatrixRegs(dregs, sz, _VD);
// Rough overlap check.
bool overlap = false;
if (GetMtx(_VS) == GetMtx(_VD) || GetMtx(_VT) == GetMtx(_VD)) {
// Potential overlap (guaranteed for 3x3 or more).
overlap = true;
}
if (overlap) {
DISABLE;
} else {
for (int a = 0; a < n; a++) {
for (int b = 0; b < n; b++) {
fpr.MapInInV(sregs[b * 4], tregs[a * 4]);
VMUL(S0, fpr.V(sregs[b * 4]), fpr.V(tregs[a * 4]));
for (int c = 1; c < n; c++) {
fpr.MapInInV(sregs[b * 4 + c], tregs[a * 4 + c]);
VMLA(S0, fpr.V(sregs[b * 4 + c]), fpr.V(tregs[a * 4 + c]));
}
fpr.MapRegV(dregs[a * 4 + b], MAP_DIRTY | MAP_NOINIT);
VMOV(fpr.V(dregs[a * 4 + b]), S0);
}
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
}
void ArmJit::Comp_Vmscl(MIPSOpcode op) {
DISABLE;
}
void ArmJit::Comp_Vtfm(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_MTX_VTFM);
if (js.HasUnknownPrefix()) {
DISABLE;
}
// TODO: This probably ignores prefixes? Or maybe uses D?
VectorSize sz = GetVecSize(op);
MatrixSize msz = GetMtxSize(op);
int n = GetNumVectorElements(sz);
int ins = (op >> 23) & 7;
bool homogenous = false;
if (n == ins) {
n++;
sz = (VectorSize)((int)(sz) + 1);
msz = (MatrixSize)((int)(msz) + 1);
homogenous = true;
}
// Otherwise, n should already be ins + 1.
else if (n != ins + 1) {
DISABLE;
}
u8 sregs[16], dregs[4], tregs[4];
GetMatrixRegs(sregs, msz, _VS);
GetVectorRegs(tregs, sz, _VT);
GetVectorRegs(dregs, sz, _VD);
// TODO: test overlap, optimize.
int tempregs[4];
for (int i = 0; i < n; i++) {
fpr.MapInInV(sregs[i * 4], tregs[0]);
VMUL(S0, fpr.V(sregs[i * 4]), fpr.V(tregs[0]));
for (int k = 1; k < n; k++) {
if (!homogenous || k != n - 1) {
fpr.MapInInV(sregs[i * 4 + k], tregs[k]);
VMLA(S0, fpr.V(sregs[i * 4 + k]), fpr.V(tregs[k]));
} else {
fpr.MapRegV(sregs[i * 4 + k]);
VADD(S0, S0, fpr.V(sregs[i * 4 + k]));
}
}
int temp = fpr.GetTempV();
fpr.MapRegV(temp, MAP_NOINIT | MAP_DIRTY);
fpr.SpillLockV(temp);
VMOV(fpr.V(temp), S0);
tempregs[i] = temp;
}
for (int i = 0; i < n; i++) {
u8 temp = tempregs[i];
fpr.MapRegV(dregs[i], MAP_NOINIT | MAP_DIRTY);
VMOV(fpr.V(dregs[i]), fpr.V(temp));
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VCrs(MIPSOpcode op) {
DISABLE;
}
void ArmJit::Comp_VDet(MIPSOpcode op) {
DISABLE;
}
void ArmJit::Comp_Vi2x(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int bits = ((op >> 16) & 2) == 0 ? 8 : 16; // vi2uc/vi2c (0/1), vi2us/vi2s (2/3)
bool unsignedOp = ((op >> 16) & 1) == 0; // vi2uc (0), vi2us (2)
if (unsignedOp) {
// Requires a tricky clamp operation that we can't do without more temps, see below
DISABLE;
}
// These instructions pack pairs or quads of integers into 32 bits.
// The unsigned (u) versions skip the sign bit when packing.
VectorSize sz = GetVecSize(op);
VectorSize outsize;
if (bits == 8) {
outsize = V_Single;
if (sz != V_Quad) {
DISABLE;
}
} else {
switch (sz) {
case V_Pair:
outsize = V_Single;
break;
case V_Quad:
outsize = V_Pair;
break;
default:
DISABLE;
}
}
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, outsize, _VD);
// First, let's assemble the sregs into lanes of either D0 (pair) or Q0 (quad).
bool quad = sz == V_Quad;
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
VMOV(S0, fpr.V(sregs[0]));
VMOV(S1, fpr.V(sregs[1]));
if (quad) {
VMOV(S2, fpr.V(sregs[2]));
VMOV(S3, fpr.V(sregs[3]));
}
// TODO: For "u" type ops, we clamp to zero and shift off the sign bit first.
// Need some temp regs to do that efficiently, right?
// At this point, we simply need to collect the high bits of each 32-bit lane into one register.
if (bits == 8) {
// Really want to do a VSHRN(..., 24) but that can't be encoded. So we synthesize it.
VSHR(I_32, Q0, Q0, 16);
VSHRN(I_32, D0, Q0, 8);
VMOVN(I_16, D0, Q0);
} else {
VSHRN(I_32, D0, Q0, 16);
}
fpr.MapRegsAndSpillLockV(dregs, outsize, MAP_DIRTY|MAP_NOINIT);
VMOV(fpr.V(dregs[0]), S0);
if (outsize == V_Pair) {
VMOV(fpr.V(dregs[1]), S1);
}
ApplyPrefixD(dregs, outsize);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vx2i(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int bits = ((op >> 16) & 2) == 0 ? 8 : 16; // vuc2i/vc2i (0/1), vus2i/vs2i (2/3)
bool unsignedOp = ((op >> 16) & 1) == 0; // vuc2i (0), vus2i (2)
if (bits == 8 && unsignedOp) {
// vuc2i is odd and needs temp registers for implementation.
DISABLE;
}
// vs2i or vus2i unpack pairs of 16-bit integers into 32-bit integers, with the values
// at the top. vus2i shifts it an extra bit right afterward.
// vc2i and vuc2i unpack quads of 8-bit integers into 32-bit integers, with the values
// at the top too. vuc2i is a bit special (see below.)
// Let's do this similarly as h2f - we do a solution that works for both singles and pairs
// then use it for both.
VectorSize sz = GetVecSize(op);
VectorSize outsize;
if (bits == 8) {
outsize = V_Quad;
} else {
switch (sz) {
case V_Single:
outsize = V_Pair;
break;
case V_Pair:
outsize = V_Quad;
break;
default:
DISABLE;
}
}
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, outsize, _VD);
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
if (sz == V_Single) {
VMOV(S0, fpr.V(sregs[0]));
} else if (sz == V_Pair) {
VMOV(S0, fpr.V(sregs[0]));
VMOV(S1, fpr.V(sregs[1]));
} else if (bits == 8) {
// For some reason, sz is quad on vc2i.
VMOV(S0, fpr.V(sregs[0]));
}
if (bits == 16) {
// Simply expand, to upper bits.
VSHLL(I_16, Q0, D0, 16);
} else if (bits == 8) {
if (unsignedOp) {
// vuc2i is a bit special. It spreads out the bits like this:
// s[0] = 0xDDCCBBAA -> d[0] = (0xAAAAAAAA >> 1), d[1] = (0xBBBBBBBB >> 1), etc.
// TODO
} else {
VSHLL(I_8, Q0, D0, 8);
VSHLL(I_16, Q0, D0, 16);
}
}
// At this point we have the regs in the 4 lanes.
// In the "u" mode, we need to shift it out of the sign bit.
if (unsignedOp) {
ArmGen::ARMReg reg = (outsize == V_Quad) ? Q0 : D0;
VSHR(I_32 | I_UNSIGNED, reg, reg, 1);
}
fpr.MapRegsAndSpillLockV(dregs, outsize, MAP_NOINIT);
VMOV(fpr.V(dregs[0]), S0);
VMOV(fpr.V(dregs[1]), S1);
if (outsize == V_Quad) {
VMOV(fpr.V(dregs[2]), S2);
VMOV(fpr.V(dregs[3]), S3);
}
ApplyPrefixD(dregs, outsize);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_VCrossQuat(MIPSOpcode op) {
// This op does not support prefixes anyway.
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix())
DISABLE;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], tregs[4], dregs[4];
GetVectorRegs(sregs, sz, _VS);
GetVectorRegs(tregs, sz, _VT);
GetVectorRegs(dregs, sz, _VD);
// Map everything into registers.
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
fpr.MapRegsAndSpillLockV(tregs, sz, 0);
if (sz == V_Triple) {
MIPSReg temp3 = fpr.GetTempV();
fpr.MapRegV(temp3, MAP_DIRTY | MAP_NOINIT);
// Cross product vcrsp.t
// Compute X
VMUL(S0, fpr.V(sregs[1]), fpr.V(tregs[2]));
VMLS(S0, fpr.V(sregs[2]), fpr.V(tregs[1]));
// Compute Y
VMUL(S1, fpr.V(sregs[2]), fpr.V(tregs[0]));
VMLS(S1, fpr.V(sregs[0]), fpr.V(tregs[2]));
// Compute Z
VMUL(fpr.V(temp3), fpr.V(sregs[0]), fpr.V(tregs[1]));
VMLS(fpr.V(temp3), fpr.V(sregs[1]), fpr.V(tregs[0]));
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_NOINIT);
VMOV(fpr.V(dregs[0]), S0);
VMOV(fpr.V(dregs[1]), S1);
VMOV(fpr.V(dregs[2]), fpr.V(temp3));
} else if (sz == V_Quad) {
MIPSReg temp3 = fpr.GetTempV();
MIPSReg temp4 = fpr.GetTempV();
fpr.MapRegV(temp3, MAP_DIRTY | MAP_NOINIT);
fpr.MapRegV(temp4, MAP_DIRTY | MAP_NOINIT);
// Quaternion product vqmul.q untested
// d[0] = s[0] * t[3] + s[1] * t[2] - s[2] * t[1] + s[3] * t[0];
VMUL(S0, fpr.V(sregs[0]), fpr.V(tregs[3]));
VMLA(S0, fpr.V(sregs[1]), fpr.V(tregs[2]));
VMLS(S0, fpr.V(sregs[2]), fpr.V(tregs[1]));
VMLA(S0, fpr.V(sregs[3]), fpr.V(tregs[0]));
//d[1] = -s[0] * t[2] + s[1] * t[3] + s[2] * t[0] + s[3] * t[1];
VNMUL(S1, fpr.V(sregs[0]), fpr.V(tregs[2]));
VMLA(S1, fpr.V(sregs[1]), fpr.V(tregs[3]));
VMLA(S1, fpr.V(sregs[2]), fpr.V(tregs[0]));
VMLA(S1, fpr.V(sregs[3]), fpr.V(tregs[1]));
//d[2] = s[0] * t[1] - s[1] * t[0] + s[2] * t[3] + s[3] * t[2];
VMUL(fpr.V(temp3), fpr.V(sregs[0]), fpr.V(tregs[1]));
VMLS(fpr.V(temp3), fpr.V(sregs[1]), fpr.V(tregs[0]));
VMLA(fpr.V(temp3), fpr.V(sregs[2]), fpr.V(tregs[3]));
VMLA(fpr.V(temp3), fpr.V(sregs[3]), fpr.V(tregs[2]));
//d[3] = -s[0] * t[0] - s[1] * t[1] - s[2] * t[2] + s[3] * t[3];
VNMUL(fpr.V(temp4), fpr.V(sregs[0]), fpr.V(tregs[0]));
VMLS(fpr.V(temp4), fpr.V(sregs[1]), fpr.V(tregs[1]));
VMLS(fpr.V(temp4), fpr.V(sregs[2]), fpr.V(tregs[2]));
VMLA(fpr.V(temp4), fpr.V(sregs[3]), fpr.V(tregs[3]));
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_NOINIT);
VMOV(fpr.V(dregs[0]), S0);
VMOV(fpr.V(dregs[1]), S1);
VMOV(fpr.V(dregs[2]), fpr.V(temp3));
VMOV(fpr.V(dregs[3]), fpr.V(temp4));
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vcmp(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_COMP);
if (js.HasUnknownPrefix())
DISABLE;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
VCondition cond = (VCondition)(op & 0xF);
u8 sregs[4], tregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixT(tregs, sz, _VT);
// Some, we just fall back to the interpreter.
// ES is just really equivalent to (value & 0x7F800000) == 0x7F800000.
switch (cond) {
case VC_EI: // c = my_isinf(s[i]); break;
case VC_NI: // c = !my_isinf(s[i]); break;
DISABLE;
case VC_ES: // c = my_isnan(s[i]) || my_isinf(s[i]); break; // Tekken Dark Resurrection
case VC_NS: // c = !my_isnan(s[i]) && !my_isinf(s[i]); break;
case VC_EN: // c = my_isnan(s[i]); break;
case VC_NN: // c = !my_isnan(s[i]); break;
if (_VS != _VT)
DISABLE;
break;
case VC_EZ:
case VC_NZ:
break;
default:
;
}
// First, let's get the trivial ones.
int affected_bits = (1 << 4) | (1 << 5); // 4 and 5
MOVI2R(SCRATCHREG1, 0);
for (int i = 0; i < n; ++i) {
// Let's only handle the easy ones, and fall back on the interpreter for the rest.
CCFlags flag = CC_AL;
switch (cond) {
case VC_FL: // c = 0;
break;
case VC_TR: // c = 1
if (i == 0) {
if (n == 1) {
MOVI2R(SCRATCHREG1, 0x31);
} else {
MOVI2R(SCRATCHREG1, 1 << i);
}
} else {
ORR(SCRATCHREG1, SCRATCHREG1, 1 << i);
}
break;
case VC_ES: // c = my_isnan(s[i]) || my_isinf(s[i]); break; // Tekken Dark Resurrection
case VC_NS: // c = !(my_isnan(s[i]) || my_isinf(s[i])); break;
// For these, we use the integer ALU as there is no support on ARM for testing for INF.
// Testing for nan or inf is the same as testing for &= 0x7F800000 == 0x7F800000.
// We need an extra temporary register so we store away SCRATCHREG1.
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, temp));
fpr.MapRegV(sregs[i], 0);
MOVI2R(SCRATCHREG1, 0x7F800000);
VMOV(SCRATCHREG2, fpr.V(sregs[i]));
AND(SCRATCHREG2, SCRATCHREG2, SCRATCHREG1);
CMP(SCRATCHREG2, SCRATCHREG1); // (SCRATCHREG2 & 0x7F800000) == 0x7F800000
flag = cond == VC_ES ? CC_EQ : CC_NEQ;
LDR(SCRATCHREG1, CTXREG, offsetof(MIPSState, temp));
break;
case VC_EN: // c = my_isnan(s[i]); break; // Tekken 6
// Should we involve T? Where I found this used, it compared a register with itself so should be fine.
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_VS; // overflow = unordered : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Chdhcfbc.html
break;
case VC_NN: // c = !my_isnan(s[i]); break;
// Should we involve T? Where I found this used, it compared a register with itself so should be fine.
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_VC; // !overflow = !unordered : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Chdhcfbc.html
break;
case VC_EQ: // c = s[i] == t[i]
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_EQ;
break;
case VC_LT: // c = s[i] < t[i]
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_LO;
break;
case VC_LE: // c = s[i] <= t[i];
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_LS;
break;
case VC_NE: // c = s[i] != t[i]
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_NEQ;
break;
case VC_GE: // c = s[i] >= t[i]
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_GE;
break;
case VC_GT: // c = s[i] > t[i]
fpr.MapInInV(sregs[i], tregs[i]);
VCMP(fpr.V(sregs[i]), fpr.V(tregs[i]));
VMRS_APSR();
flag = CC_GT;
break;
case VC_EZ: // c = s[i] == 0.0f || s[i] == -0.0f
fpr.MapRegV(sregs[i]);
VCMP(fpr.V(sregs[i])); // vcmp(sregs[i], #0.0)
VMRS_APSR();
flag = CC_EQ;
break;
case VC_NZ: // c = s[i] != 0
fpr.MapRegV(sregs[i]);
VCMP(fpr.V(sregs[i])); // vcmp(sregs[i], #0.0)
VMRS_APSR();
flag = CC_NEQ;
break;
default:
DISABLE;
}
if (flag != CC_AL) {
SetCC(flag);
if (i == 0) {
if (n == 1) {
MOVI2R(SCRATCHREG1, 0x31);
} else {
MOVI2R(SCRATCHREG1, 1); // 1 << i, but i == 0
}
} else {
ORR(SCRATCHREG1, SCRATCHREG1, 1 << i);
}
SetCC(CC_AL);
}
affected_bits |= 1 << i;
}
// Aggregate the bits. Urgh, expensive. Can optimize for the case of one comparison, which is the most common
// after all.
if (n > 1) {
CMP(SCRATCHREG1, affected_bits & 0xF);
SetCC(CC_EQ);
ORR(SCRATCHREG1, SCRATCHREG1, 1 << 5);
SetCC(CC_AL);
CMP(SCRATCHREG1, 0);
SetCC(CC_NEQ);
ORR(SCRATCHREG1, SCRATCHREG1, 1 << 4);
SetCC(CC_AL);
}
gpr.MapReg(MIPS_REG_VFPUCC, MAP_DIRTY);
BIC(gpr.R(MIPS_REG_VFPUCC), gpr.R(MIPS_REG_VFPUCC), affected_bits);
ORR(gpr.R(MIPS_REG_VFPUCC), gpr.R(MIPS_REG_VFPUCC), SCRATCHREG1);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vcmov(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_COMP);
if (js.HasUnknownPrefix()) {
DISABLE;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
int tf = (op >> 19) & 1;
int imm3 = (op >> 16) & 7;
for (int i = 0; i < n; ++i) {
// Simplification: Disable if overlap unsafe
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs)) {
DISABLE;
}
}
if (imm3 < 6) {
// Test one bit of CC. This bit decides whether none or all subregisters are copied.
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_DIRTY);
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
gpr.MapReg(MIPS_REG_VFPUCC);
TST(gpr.R(MIPS_REG_VFPUCC), 1 << imm3);
SetCC(tf ? CC_EQ : CC_NEQ);
for (int i = 0; i < n; i++) {
VMOV(fpr.V(dregs[i]), fpr.V(sregs[i]));
}
SetCC(CC_AL);
} else {
// Look at the bottom four bits of CC to individually decide if the subregisters should be copied.
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_DIRTY);
fpr.MapRegsAndSpillLockV(sregs, sz, 0);
gpr.MapReg(MIPS_REG_VFPUCC);
for (int i = 0; i < n; i++) {
TST(gpr.R(MIPS_REG_VFPUCC), 1 << i);
SetCC(tf ? CC_EQ : CC_NEQ);
VMOV(fpr.V(dregs[i]), fpr.V(sregs[i]));
SetCC(CC_AL);
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Viim(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
if (js.HasUnknownPrefix()) {
DISABLE;
}
u8 dreg;
GetVectorRegs(&dreg, V_Single, _VT);
s32 imm = SignExtend16ToS32(op);
fpr.MapRegV(dreg, MAP_DIRTY | MAP_NOINIT);
MOVI2F(fpr.V(dreg), (float)imm, SCRATCHREG1);
ApplyPrefixD(&dreg, V_Single);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vfim(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
if (js.HasUnknownPrefix()) {
DISABLE;
}
u8 dreg;
GetVectorRegs(&dreg, V_Single, _VT);
FP16 half;
half.u = op & 0xFFFF;
FP32 fval = half_to_float_fast5(half);
fpr.MapRegV(dreg, MAP_DIRTY | MAP_NOINIT);
MOVI2F(fpr.V(dreg), fval.f, SCRATCHREG1);
ApplyPrefixD(&dreg, V_Single);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vcst(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_XFER);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int conNum = (op >> 16) & 0x1f;
int vd = _VD;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 dregs[4];
GetVectorRegsPrefixD(dregs, sz, _VD);
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
gpr.SetRegImm(SCRATCHREG1, (u32)(void *)&cst_constants[conNum]);
VLDR(S0, SCRATCHREG1, 0);
for (int i = 0; i < n; ++i)
VMOV(fpr.V(dregs[i]), S0);
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
// Float bits in and out, so the calls look the same with softfp and hardfp.
static u32 CallWithBits(float (*func)(float), u32 x) {
float f;
memcpy(&f, &x, sizeof(f));
f = func(f);
memcpy(&x, &f, sizeof(x));
return x;
}
static u32 VRcpBits(u32 x) {
return CallWithBits(&vfpu_rcp, x);
}
static u32 VNRcpBits(u32 x) {
return CallWithBits(&vfpu_rcp, x) ^ 0x80000000;
}
static u32 VRSqrtBits(u32 x) {
return CallWithBits(&vfpu_rsqrt, x);
}
static u32 VSqrtBits(u32 x) {
return CallWithBits(&vfpu_sqrt, x);
}
// vrcp, vrsq, vsqrt and vnrcp call the exact functions. The lanes stay in S16-S19 across the
// calls (callee-saved), and the results are stored to the destinations' homes.
void ArmJit::CompVV2OpCall(MIPSOpcode op) {
// Like the interpreter, these apply the prefixes to the last lane only.
if (js.HasSPrefix() || (js.HasDPrefix() && GetVecSize(op) != V_Single)) {
DISABLE;
}
const int optype = (op >> 16) & 0x1f;
u32 (*func)(u32) = &VRcpBits;
if (optype == 17) {
func = &VRSqrtBits;
} else if (optype == 22) {
func = &VSqrtBits;
} else if (optype == 24) {
func = &VNRcpBits;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], dregs[4];
GetVectorRegs(sregs, sz, _VS);
GetVectorRegs(dregs, sz, _VD);
gpr.FlushBeforeCall();
fpr.FlushAll();
for (int i = 0; i < n; i++) {
VLDR((ARMReg)(S16 + i), CTXREG, fpr.GetMipsRegOffsetV(sregs[i]));
}
for (int i = 0; i < n; i++) {
VMOV(R0, (ARMReg)(S16 + i));
// FlushBeforeCall saves R1.
QuickCallFunction(R1, func);
VMOV((ARMReg)(S16 + i), R0);
}
for (int i = 0; i < n; i++) {
VSTR((ARMReg)(S16 + i), CTXREG, fpr.GetMipsRegOffsetV(dregs[i]));
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
static double SinCos(float angle) {
union { struct { float sin; float cos; }; double out; } sincos;
vfpu_sincos(angle, sincos.sin, sincos.cos);
return sincos.out;
}
static double SinCosNegSin(float angle) {
union { struct { float sin; float cos; }; double out; } sincos;
vfpu_sincos(angle, sincos.sin, sincos.cos);
sincos.sin = -sincos.sin;
return sincos.out;
}
void ArmJit::CompVrotShuffle(u8 *dregs, int imm, VectorSize sz, bool negSin) {
int n = GetNumVectorElements(sz);
char what[4] = {'0', '0', '0', '0'};
if (((imm >> 2) & 3) == (imm & 3)) {
for (int i = 0; i < 4; i++)
what[i] = 'S';
}
what[(imm >> 2) & 3] = 'S';
what[imm & 3] = 'C';
fpr.MapRegsAndSpillLockV(dregs, sz, MAP_DIRTY | MAP_NOINIT);
for (int i = 0; i < n; i++) {
switch (what[i]) {
case 'C': VMOV(fpr.V(dregs[i]), S1); break;
case 'S': if (negSin) VNEG(fpr.V(dregs[i]), S0); else VMOV(fpr.V(dregs[i]), S0); break;
case '0':
{
MOVI2F(fpr.V(dregs[i]), 0.0f, SCRATCHREG1);
break;
}
default:
ERROR_LOG(Log::JIT, "Bad what in vrot");
break;
}
}
}
// Very heavily used by FF:CC. Should be replaced by a fast approximation instead of
// calling the math library.
void ArmJit::Comp_VRot(MIPSOpcode op) {
// VRot probably doesn't accept prefixes anyway.
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
int vd = _VD;
int vs = _VS;
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 dregs[4];
u8 dregs2[4];
MIPSOpcode nextOp = GetOffsetInstruction(1);
int vd2 = -1;
int imm2 = -1;
if ((nextOp >> 26) == 60 && ((nextOp >> 21) & 0x1F) == 29 && _VS == MIPS_GET_VS(nextOp)) {
// Pair of vrot. Let's join them.
vd2 = MIPS_GET_VD(nextOp);
imm2 = (nextOp >> 16) & 0x1f;
// NOTICE_LOG(Log::JIT, "Joint VFPU at %08x", js.blockStart);
}
u8 sreg;
GetVectorRegs(dregs, sz, vd);
if (vd2 >= 0)
GetVectorRegs(dregs2, sz, vd2);
GetVectorRegs(&sreg, V_Single, vs);
// With the angle in a destination lane, the cosine is taken of what was written there.
// The assembler refuses that, so leave it to the interpreter, and don't pair such a vrot.
for (int i = 0; i < n; i++) {
if (dregs[i] == sreg) {
DISABLE;
}
if (vd2 >= 0 && dregs2[i] == sreg) {
vd2 = -1;
}
}
int imm = (op >> 16) & 0x1f;
gpr.FlushBeforeCall();
fpr.FlushAll();
bool negSin1 = (imm & 0x10) ? true : false;
fpr.MapRegV(sreg);
// We should write a custom pure-asm function instead.
#if defined(__ARM_PCS_VFP) // Hardfp
VMOV(S0, fpr.V(sreg));
#else // Softfp
VMOV(R0, fpr.V(sreg));
#endif
// FlushBeforeCall saves R1.
QuickCallFunction(R1, negSin1 ? (void *)&SinCosNegSin : (void *)&SinCos);
#if !defined(__ARM_PCS_VFP)
// Returns D0 on hardfp and R0,R1 on softfp due to union joining the two floats
VMOV(D0, R0, R1);
#endif
CompVrotShuffle(dregs, imm, sz, false);
if (vd2 != -1) {
// If the negsin setting differs between the two joint invocations, we need to flip the second one.
bool negSin2 = (imm2 & 0x10) ? true : false;
CompVrotShuffle(dregs2, imm2, sz, negSin1 != negSin2);
EatInstruction(nextOp);
}
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vsgn(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
u8 sregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
for (int i = 0; i < n; ++i) {
fpr.MapDirtyInV(tempregs[i], sregs[i]);
VCMP(fpr.V(sregs[i])); // vcmp(sregs[i], #0.0)
VMOV(SCRATCHREG1, fpr.V(sregs[i]));
VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
SetCC(CC_NEQ);
AND(SCRATCHREG1, SCRATCHREG1, AssumeMakeOperand2(0x80000000));
ORR(SCRATCHREG1, SCRATCHREG1, AssumeMakeOperand2(0x3F800000));
SetCC(CC_EQ);
MOV(SCRATCHREG1, AssumeMakeOperand2(0x0));
SetCC(CC_AL);
VMOV(fpr.V(tempregs[i]), SCRATCHREG1);
}
for (int i = 0; i < n; ++i) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_Vocp(MIPSOpcode op) {
CONDITIONAL_DISABLE(VFPU_VEC);
if (js.HasUnknownPrefix()) {
DISABLE;
}
VectorSize sz = GetVecSize(op);
int n = GetNumVectorElements(sz);
// This is a hack that modifies prefixes. We eat them later, so just overwrite.
// S prefix forces the negate flags.
js.prefixS |= 0x000F0000;
// T prefix forces constants on and regnum to 1.
// That means negate still works, and abs activates a different constant.
js.prefixT = (js.prefixT & ~0x000000FF) | 0x00000055 | 0x0000F000;
u8 sregs[4], tregs[4], dregs[4];
GetVectorRegsPrefixS(sregs, sz, _VS);
GetVectorRegsPrefixT(tregs, sz, _VS);
GetVectorRegsPrefixD(dregs, sz, _VD);
MIPSReg tempregs[4];
for (int i = 0; i < n; ++i) {
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
tempregs[i] = fpr.GetTempV();
} else {
tempregs[i] = dregs[i];
}
}
for (int i = 0; i < n; ++i) {
fpr.MapDirtyInInV(tempregs[i], sregs[i], tregs[i]);
VADD(fpr.V(tempregs[i]), fpr.V(tregs[i]), fpr.V(sregs[i]));
}
for (int i = 0; i < n; ++i) {
if (dregs[i] != tempregs[i]) {
fpr.MapDirtyInV(dregs[i], tempregs[i]);
VMOV(fpr.V(dregs[i]), fpr.V(tempregs[i]));
}
}
ApplyPrefixD(dregs, sz);
fpr.ReleaseSpillLocksAndDiscardTemps();
}
void ArmJit::Comp_ColorConv(MIPSOpcode op) {
DISABLE;
}
void ArmJit::Comp_Vbfy(MIPSOpcode op) {
DISABLE;
}
}
#endif // PPSSPP_ARCH(ARM)