mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 07:50:28 +02:00
Comp_Vmfvc read vfpuCtrl[] straight from the context in all four backends, while the mfvc path in Comp_Mftv flushes first, with the comment "In case we have a saved prefix" - so "vpfxs X" followed by "vmfvc sN, $128" returned the stale value in memory. The IR frontend flushes only for the three prefix registers, which is the tighter form, so vmfvc does the same there. On ARM and ARM64 the fix alone wouldn't have been observable: those two map the destination with no flags, leaving isDirty false, so the flush dropped the loaded value without storing it. x86 already passes MAP_DIRTY | MAP_NOINIT. That part is a fix of its own, but the two are inseparable in this function - a vmfvc that reads the right value and then throws it away is no better. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3720 lines
98 KiB
C++
3720 lines
98 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/.
|
|
|
|
// Table 13.10 in http://agner.org/optimize/optimizing_assembly.pdf is cool - generate constants with
|
|
// short instruction sequences. Surprisingly many are possible.
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include "Common/Math/math_util.h"
|
|
|
|
#include "Common/CPUDetect.h"
|
|
#include "Common/Math/SIMDHeaders.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/Compatibility.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/System.h"
|
|
#include "Core/MIPS/MIPSAnalyst.h"
|
|
#include "Core/MIPS/MIPSCodeUtils.h"
|
|
#include "Core/MIPS/MIPSVFPUUtils.h"
|
|
#include "Core/MIPS/x86/Jit.h"
|
|
#include "Core/MIPS/x86/RegCache.h"
|
|
|
|
// 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.ReleaseSpillLocks(); Comp_Generic(op); return; }
|
|
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
|
|
#define DISABLE { fpr.ReleaseSpillLocks(); 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 Gen;
|
|
using namespace X64JitConstants;
|
|
|
|
static const float one = 1.0f;
|
|
static const float minus_one = -1.0f;
|
|
|
|
alignas(16) const u32 noSignMask[4] = {0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF};
|
|
alignas(16) const u32 signBitAll[4] = {0x80000000, 0x80000000, 0x80000000, 0x80000000};
|
|
alignas(16) const u32 signBitLower[4] = {0x80000000, 0, 0, 0};
|
|
alignas(16) const float oneOneOneOne[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
alignas(16) const u32 fourinfnan[4] = {0x7F800000, 0x7F800000, 0x7F800000, 0x7F800000};
|
|
alignas(16) const float identityMatrix[4][4] = { { 1.0f, 0, 0, 0 }, { 0, 1.0f, 0, 0 }, { 0, 0, 1.0f, 0 }, { 0, 0, 0, 1.0f} };
|
|
|
|
void Jit::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;
|
|
}
|
|
}
|
|
|
|
void Jit::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();
|
|
fpr.MapRegV(vregs[i], MAP_NOINIT | MAP_DIRTY);
|
|
|
|
if (!constants) {
|
|
// 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) {
|
|
ERROR_LOG_REPORT(Log::CPU, "Invalid VFPU swizzle: %08x / %d", prefix, sz);
|
|
regnum = 0;
|
|
}
|
|
fpr.SimpleRegV(origV[regnum], 0);
|
|
MOVSS(fpr.VX(vregs[i]), fpr.V(origV[regnum]));
|
|
if (abs) {
|
|
if (RipAccessible(&noSignMask)) {
|
|
ANDPS(fpr.VX(vregs[i]), M(&noSignMask)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&noSignMask));
|
|
ANDPS(fpr.VX(vregs[i]), MatR(TEMPREG));
|
|
}
|
|
}
|
|
} else {
|
|
if (RipAccessible(constantArray)) {
|
|
MOVSS(fpr.VX(vregs[i]), M(&constantArray[regnum + (abs << 2)])); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&constantArray[regnum + (abs << 2)]));
|
|
MOVSS(fpr.VX(vregs[i]), MatR(TEMPREG));
|
|
}
|
|
}
|
|
|
|
if (negate) {
|
|
if (RipAccessible(&signBitLower)) {
|
|
XORPS(fpr.VX(vregs[i]), M(&signBitLower)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&signBitLower));
|
|
XORPS(fpr.VX(vregs[i]), MatR(TEMPREG));
|
|
}
|
|
}
|
|
// TODO: This probably means it will swap out soon, inefficiently...
|
|
fpr.ReleaseSpillLockV(vregs[i]);
|
|
}
|
|
}
|
|
|
|
void Jit::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 Jit::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) {
|
|
fpr.MapRegV(vregs[i], MAP_DIRTY);
|
|
|
|
// Zero out XMM0 if it was <= +0.0f (but skip NAN.)
|
|
MOVSS(R(XMM0), fpr.VX(vregs[i]));
|
|
XORPS(XMM1, R(XMM1));
|
|
CMPLESS(XMM0, R(XMM1));
|
|
ANDNPS(XMM0, fpr.V(vregs[i]));
|
|
|
|
// Retain a NAN in XMM0 (must be second operand.)
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(fpr.VX(vregs[i]), MatR(TEMPREG));
|
|
MINSS(fpr.VX(vregs[i]), R(XMM0));
|
|
} else if (sat == 3) {
|
|
fpr.MapRegV(vregs[i], MAP_DIRTY);
|
|
|
|
// Check for < -1.0f, but careful of NANs.
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&minus_one));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
MOVSS(R(XMM0), fpr.VX(vregs[i]));
|
|
CMPLESS(XMM0, R(XMM1));
|
|
// If it was NOT less, the three ops below do nothing.
|
|
// Otherwise, they replace the value with -1.0f.
|
|
ANDPS(XMM1, R(XMM0));
|
|
ANDNPS(XMM0, fpr.V(vregs[i]));
|
|
ORPS(XMM0, R(XMM1));
|
|
|
|
// Retain a NAN in XMM0 (must be second operand.)
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(fpr.VX(vregs[i]), MatR(TEMPREG));
|
|
MINSS(fpr.VX(vregs[i]), R(XMM0));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Vector regs can overlap in all sorts of swizzled ways.
|
|
// This does allow a single overlap in sregs[i].
|
|
bool IsOverlapSafeAllowS(int dreg, int di, int sn, const u8 sregs[], int tn = 0, const 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;
|
|
}
|
|
|
|
bool IsOverlapSafe(int dreg, int di, int sn, const u8 sregs[], int tn = 0, const u8 tregs[] = NULL) {
|
|
return IsOverlapSafeAllowS(dreg, di, sn, sregs, tn, tregs) && sregs[di] != dreg;
|
|
}
|
|
|
|
void Jit::Comp_SV(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(LSU_VFPU);
|
|
|
|
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 imm = (signed short)(op&0xFFFC);
|
|
int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5);
|
|
MIPSGPReg rs = _RS;
|
|
|
|
CheckMemoryBreakpoint(0, rs, imm);
|
|
|
|
switch (op >> 26) {
|
|
case 50: // lv.s
|
|
{
|
|
gpr.Lock(rs);
|
|
fpr.MapRegV(vt, MAP_DIRTY | MAP_NOINIT);
|
|
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg src;
|
|
if (safe.PrepareRead(src, 4)) {
|
|
MOVSS(fpr.VX(vt), safe.NextFastAddress(0));
|
|
}
|
|
if (safe.PrepareSlowRead(safeMemFuncs.readU32)) {
|
|
MOVD_xmm(fpr.VX(vt), R(EAX));
|
|
}
|
|
safe.Finish();
|
|
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
break;
|
|
|
|
case 58: // sv.s
|
|
{
|
|
gpr.Lock(rs);
|
|
|
|
fpr.MapRegV(vt, 0);
|
|
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg dest;
|
|
if (safe.PrepareWrite(dest, 4)) {
|
|
MOVSS(safe.NextFastAddress(0), fpr.VX(vt));
|
|
}
|
|
if (safe.PrepareSlowWrite()) {
|
|
MOVSS(MIPSSTATE_VAR(temp), fpr.VX(vt));
|
|
safe.DoSlowWrite(safeMemFuncs.writeU32, MIPSSTATE_VAR(temp), 0);
|
|
}
|
|
safe.Finish();
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
gpr.UnlockAll();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DISABLE;
|
|
}
|
|
}
|
|
|
|
void Jit::Comp_SVQ(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(LSU_VFPU);
|
|
|
|
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;
|
|
|
|
CheckMemoryBreakpoint(0, rs, imm);
|
|
|
|
switch (op >> 26) {
|
|
case 53: //lvl.q/lvr.q
|
|
{
|
|
if (!g_Config.bFastMemory) {
|
|
DISABLE;
|
|
}
|
|
DISABLE; // The code below isn't quite working, so we fall back to interpreter for now.
|
|
|
|
gpr.MapReg(rs, true, false);
|
|
gpr.FlushLockX(ECX);
|
|
u8 vregs[4];
|
|
GetVectorRegs(vregs, V_Quad, vt);
|
|
MOV(32, R(EAX), gpr.R(rs));
|
|
ADD(32, R(EAX), Imm32(imm));
|
|
#ifdef MASKED_PSP_MEMORY
|
|
AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK));
|
|
#endif
|
|
MOV(32, R(ECX), R(EAX));
|
|
SHR(32, R(EAX), Imm8(2));
|
|
AND(32, R(EAX), Imm32(0x3));
|
|
CMP(32, R(EAX), Imm32(0));
|
|
FixupBranch next = J_CC(CC_NE);
|
|
|
|
auto PSPMemAddr = [](X64Reg scaled, int offset) {
|
|
#if PPSSPP_ARCH(X86)
|
|
return MDisp(scaled, (u32)Memory::base + offset);
|
|
#else
|
|
return MComplex(MEMBASEREG, scaled, 1, offset);
|
|
#endif
|
|
};
|
|
|
|
fpr.MapRegsV(vregs, V_Quad, MAP_DIRTY);
|
|
|
|
// Offset = 0
|
|
MOVSS(fpr.RX(vregs[3]), PSPMemAddr(EAX, 0));
|
|
|
|
FixupBranch skip0 = J();
|
|
SetJumpTarget(next);
|
|
CMP(32, R(EAX), Imm32(1));
|
|
next = J_CC(CC_NE);
|
|
|
|
// Offset = 1
|
|
MOVSS(fpr.RX(vregs[3]), PSPMemAddr(EAX, 4));
|
|
MOVSS(fpr.RX(vregs[2]), PSPMemAddr(EAX, 0));
|
|
|
|
FixupBranch skip1 = J();
|
|
SetJumpTarget(next);
|
|
CMP(32, R(EAX), Imm32(2));
|
|
next = J_CC(CC_NE);
|
|
|
|
// Offset = 2
|
|
MOVSS(fpr.RX(vregs[3]), PSPMemAddr(EAX, 8));
|
|
MOVSS(fpr.RX(vregs[2]), PSPMemAddr(EAX, 4));
|
|
MOVSS(fpr.RX(vregs[1]), PSPMemAddr(EAX, 0));
|
|
|
|
FixupBranch skip2 = J();
|
|
SetJumpTarget(next);
|
|
CMP(32, R(EAX), Imm32(3));
|
|
next = J_CC(CC_NE);
|
|
|
|
// Offset = 3
|
|
MOVSS(fpr.RX(vregs[3]), PSPMemAddr(EAX, 12));
|
|
MOVSS(fpr.RX(vregs[2]), PSPMemAddr(EAX, 8));
|
|
MOVSS(fpr.RX(vregs[1]), PSPMemAddr(EAX, 4));
|
|
MOVSS(fpr.RX(vregs[0]), PSPMemAddr(EAX, 0));
|
|
|
|
SetJumpTarget(next);
|
|
SetJumpTarget(skip0);
|
|
SetJumpTarget(skip1);
|
|
SetJumpTarget(skip2);
|
|
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
break;
|
|
|
|
case 54: //lv.q
|
|
{
|
|
gpr.Lock(rs);
|
|
// This must be in a reg or an immediate.
|
|
// Otherwise, it'll get put in EAX and we'll clobber that during NextSlowRead().
|
|
if (!gpr.IsImm(rs))
|
|
gpr.MapReg(rs, true, false);
|
|
|
|
u8 vregs[4];
|
|
GetVectorRegs(vregs, V_Quad, vt);
|
|
|
|
if (fpr.TryMapRegsVS(vregs, V_Quad, MAP_NOINIT | MAP_DIRTY)) {
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg src;
|
|
if (safe.PrepareRead(src, 16)) {
|
|
// Should be safe, since lv.q must be aligned, but let's try to avoid crashing in safe mode.
|
|
if (g_Config.bFastMemory) {
|
|
MOVAPS(fpr.VSX(vregs), safe.NextFastAddress(0));
|
|
} else {
|
|
MOVUPS(fpr.VSX(vregs), safe.NextFastAddress(0));
|
|
}
|
|
}
|
|
if (safe.PrepareSlowRead(safeMemFuncs.readU32)) {
|
|
for (int i = 0; i < 4; i++) {
|
|
safe.NextSlowRead(safeMemFuncs.readU32, i * 4);
|
|
// We use XMM0 as a temporary since MOVSS and MOVD would clear the higher bits.
|
|
MOVD_xmm(XMM0, R(EAX));
|
|
MOVSS(fpr.VSX(vregs), R(XMM0));
|
|
// Rotate things so we can read in the next higher float.
|
|
// By the end (4 rotates), they'll all be back into place.
|
|
SHUFPS(fpr.VSX(vregs), fpr.VS(vregs), _MM_SHUFFLE(0, 3, 2, 1));
|
|
}
|
|
}
|
|
safe.Finish();
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
fpr.MapRegsV(vregs, V_Quad, MAP_DIRTY | MAP_NOINIT);
|
|
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg src;
|
|
if (safe.PrepareRead(src, 16)) {
|
|
// Just copy 4 words the easiest way while not wasting registers.
|
|
for (int i = 0; i < 4; i++)
|
|
MOVSS(fpr.VX(vregs[i]), safe.NextFastAddress(i * 4));
|
|
}
|
|
if (safe.PrepareSlowRead(safeMemFuncs.readU32)) {
|
|
for (int i = 0; i < 4; i++) {
|
|
safe.NextSlowRead(safeMemFuncs.readU32, i * 4);
|
|
MOVD_xmm(fpr.VX(vregs[i]), R(EAX));
|
|
}
|
|
}
|
|
safe.Finish();
|
|
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
break;
|
|
|
|
case 62: //sv.q
|
|
{
|
|
gpr.Lock(rs);
|
|
// This must be in a reg or an immediate.
|
|
// Otherwise, it'll get put in EAX and we'll clobber that during NextSlowRead().
|
|
if (!gpr.IsImm(rs))
|
|
gpr.MapReg(rs, true, false);
|
|
|
|
u8 vregs[4];
|
|
GetVectorRegs(vregs, V_Quad, vt);
|
|
|
|
if (fpr.TryMapRegsVS(vregs, V_Quad, 0)) {
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg dest;
|
|
if (safe.PrepareWrite(dest, 16)) {
|
|
// Should be safe, since sv.q must be aligned, but let's try to avoid crashing in safe mode.
|
|
if (g_Config.bFastMemory) {
|
|
MOVAPS(safe.NextFastAddress(0), fpr.VSX(vregs));
|
|
} else {
|
|
MOVUPS(safe.NextFastAddress(0), fpr.VSX(vregs));
|
|
}
|
|
}
|
|
if (safe.PrepareSlowWrite()) {
|
|
MOVAPS(XMM0, fpr.VS(vregs));
|
|
for (int i = 0; i < 4; i++) {
|
|
MOVSS(MIPSSTATE_VAR(temp), XMM0);
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 3, 2, 1));
|
|
safe.DoSlowWrite(safeMemFuncs.writeU32, MIPSSTATE_VAR(temp), i * 4);
|
|
}
|
|
}
|
|
safe.Finish();
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Even if we don't use real SIMD there's still 8 or 16 scalar float registers.
|
|
fpr.MapRegsV(vregs, V_Quad, 0);
|
|
|
|
JitSafeMem safe(this, rs, imm);
|
|
OpArg dest;
|
|
if (safe.PrepareWrite(dest, 16)) {
|
|
for (int i = 0; i < 4; i++)
|
|
MOVSS(safe.NextFastAddress(i * 4), fpr.VX(vregs[i]));
|
|
}
|
|
if (safe.PrepareSlowWrite()) {
|
|
for (int i = 0; i < 4; i++) {
|
|
MOVSS(MIPSSTATE_VAR(temp), fpr.VX(vregs[i]));
|
|
safe.DoSlowWrite(safeMemFuncs.writeU32, MIPSSTATE_VAR(temp), i * 4);
|
|
}
|
|
}
|
|
safe.Finish();
|
|
|
|
gpr.UnlockAll();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DISABLE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Jit::Comp_VVectorInit(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int type = (op >> 16) & 0xF;
|
|
u8 dregs[4];
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
if (fpr.TryMapRegsVS(dregs, sz, MAP_NOINIT | MAP_DIRTY)) {
|
|
if (type == 6) {
|
|
XORPS(fpr.VSX(dregs), fpr.VS(dregs));
|
|
} else if (type == 7) {
|
|
if (RipAccessible(&oneOneOneOne)) {
|
|
MOVAPS(fpr.VSX(dregs), M(&oneOneOneOne)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
MOVAPS(fpr.VSX(dregs), MatR(TEMPREG));
|
|
}
|
|
} else {
|
|
DISABLE;
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case 6: // v=zeros; break; //vzero
|
|
XORPS(XMM0, R(XMM0));
|
|
break;
|
|
case 7: // v=ones; break; //vone
|
|
if (RipAccessible(&one)) {
|
|
MOVSS(XMM0, M(&one)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM0, MatR(TEMPREG));
|
|
}
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
break;
|
|
}
|
|
|
|
int n = GetNumVectorElements(sz);
|
|
fpr.MapRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
for (int i = 0; i < n; ++i)
|
|
MOVSS(fpr.VX(dregs[i]), R(XMM0));
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_VIdt(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
int vd = _VD;
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
u8 dregs[4];
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
if (fpr.TryMapRegsVS(dregs, sz, MAP_NOINIT | MAP_DIRTY)) {
|
|
int row = vd & (n - 1);
|
|
if (RipAccessible(identityMatrix)) {
|
|
MOVAPS(fpr.VSX(dregs), M(identityMatrix[row])); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&identityMatrix[row]));
|
|
MOVAPS(fpr.VSX(dregs), MatR(TEMPREG));
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
XORPS(XMM0, R(XMM0));
|
|
if (RipAccessible(&one)) {
|
|
MOVSS(XMM1, M(&one)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
}
|
|
fpr.MapRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
switch (sz) {
|
|
case V_Pair:
|
|
MOVSS(fpr.VX(dregs[0]), R((vd&1)==0 ? XMM1 : XMM0));
|
|
MOVSS(fpr.VX(dregs[1]), R((vd&1)==1 ? XMM1 : XMM0));
|
|
break;
|
|
case V_Quad:
|
|
MOVSS(fpr.VX(dregs[0]), R((vd&3)==0 ? XMM1 : XMM0));
|
|
MOVSS(fpr.VX(dregs[1]), R((vd&3)==1 ? XMM1 : XMM0));
|
|
MOVSS(fpr.VX(dregs[2]), R((vd&3)==2 ? XMM1 : XMM0));
|
|
MOVSS(fpr.VX(dregs[3]), R((vd&3)==3 ? XMM1 : XMM0));
|
|
break;
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_VDot(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
// 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);
|
|
|
|
// With SSE2, these won't really give any performance benefit on their own, but may reduce
|
|
// conversion costs from/to SIMD form. However, the SSE4.1 DPPS may be worth it.
|
|
// Benchmarking will have to decide whether to enable this on < SSE4.1. Also a HADDPS version
|
|
// for SSE3 could be written.
|
|
if (fpr.TryMapDirtyInInVS(dregs, V_Single, sregs, sz, tregs, sz)) {
|
|
switch (sz) {
|
|
case V_Pair:
|
|
if (cpu_info.bSSE4_1) {
|
|
if (fpr.VSX(dregs) != fpr.VSX(sregs) && fpr.VSX(dregs) != fpr.VSX(tregs)) {
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
DPPS(fpr.VSX(dregs), fpr.VS(tregs), 0x31);
|
|
} else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, fpr.VS(tregs), 0x31);
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
}
|
|
} else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
MULPS(XMM0, fpr.VS(tregs));
|
|
MOVAPS(R(XMM1), XMM0);
|
|
SHUFPS(XMM1, R(XMM0), _MM_SHUFFLE(1, 1, 1, 1));
|
|
ADDPS(XMM1, R(XMM0));
|
|
MOVAPS(fpr.VS(dregs), XMM1);
|
|
}
|
|
break;
|
|
case V_Triple:
|
|
if (cpu_info.bSSE4_1) {
|
|
if (fpr.VSX(dregs) != fpr.VSX(sregs) && fpr.VSX(dregs) != fpr.VSX(tregs)) {
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
DPPS(fpr.VSX(dregs), fpr.VS(tregs), 0x71);
|
|
} else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, fpr.VS(tregs), 0x71);
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
}
|
|
} else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
MULPS(XMM0, fpr.VS(tregs));
|
|
MOVAPS(R(XMM1), XMM0);
|
|
SHUFPS(XMM1, R(XMM0), _MM_SHUFFLE(3, 2, 1, 1));
|
|
ADDSS(XMM1, R(XMM0));
|
|
SHUFPS(XMM0, R(XMM1), _MM_SHUFFLE(3, 2, 2, 2));
|
|
ADDSS(XMM1, R(XMM0));
|
|
MOVAPS(fpr.VS(dregs), XMM1);
|
|
}
|
|
break;
|
|
case V_Quad:
|
|
if (cpu_info.bSSE4_1) {
|
|
if (fpr.VSX(dregs) != fpr.VSX(sregs) && fpr.VSX(dregs) != fpr.VSX(tregs)) {
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
DPPS(fpr.VSX(dregs), fpr.VS(tregs), 0xF1);
|
|
} else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, fpr.VS(tregs), 0xF1);
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
}
|
|
} /* else if (cpu_info.bSSE3) { // This is slower than the SSE2 solution on my Ivy!
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
MOVAPS(XMM1, fpr.VS(tregs));
|
|
HADDPS(XMM0, R(XMM1));
|
|
HADDPS(XMM0, R(XMM0));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
} */ else {
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
MOVAPS(XMM1, fpr.VS(tregs));
|
|
MULPS(XMM0, R(XMM1));
|
|
MOVAPS(XMM1, R(XMM0));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(2, 3, 0, 1));
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(XMM1, R(XMM0));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(0, 1, 2, 3));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
}
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
ApplyPrefixD(dregs, V_Single);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
|
|
X64Reg tempxreg = XMM0;
|
|
if (IsOverlapSafe(dregs[0], 0, n, sregs, n, tregs)) {
|
|
fpr.MapRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
tempxreg = fpr.VX(dregs[0]);
|
|
}
|
|
|
|
// Need to start with +0.0f so it doesn't result in -0.0f.
|
|
MOVSS(tempxreg, fpr.V(sregs[0]));
|
|
MULSS(tempxreg, fpr.V(tregs[0]));
|
|
for (int i = 1; i < n; i++)
|
|
{
|
|
// sum += s[i]*t[i];
|
|
MOVSS(XMM1, fpr.V(sregs[i]));
|
|
MULSS(XMM1, fpr.V(tregs[i]));
|
|
ADDSS(tempxreg, R(XMM1));
|
|
}
|
|
|
|
if (!fpr.V(dregs[0]).IsSimpleReg(tempxreg)) {
|
|
fpr.MapRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
MOVSS(fpr.V(dregs[0]), tempxreg);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, V_Single);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
|
|
void Jit::Comp_VHdp(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
u8 sregs[4], tregs[4], dregs[1];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixT(tregs, sz, _VT);
|
|
GetVectorRegsPrefixD(dregs, V_Single, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
|
|
X64Reg tempxreg = XMM0;
|
|
if (IsOverlapSafe(dregs[0], 0, n, sregs, n, tregs)) {
|
|
fpr.MapRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
tempxreg = fpr.VX(dregs[0]);
|
|
}
|
|
|
|
// Need to start with +0.0f so it doesn't result in -0.0f.
|
|
MOVSS(tempxreg, fpr.V(sregs[0]));
|
|
MULSS(tempxreg, fpr.V(tregs[0]));
|
|
for (int i = 1; i < n; i++) {
|
|
// sum += (i == n-1) ? t[i] : s[i]*t[i];
|
|
if (i == n - 1) {
|
|
ADDSS(tempxreg, fpr.V(tregs[i]));
|
|
} else {
|
|
MOVSS(XMM1, fpr.V(sregs[i]));
|
|
MULSS(XMM1, fpr.V(tregs[i]));
|
|
ADDSS(tempxreg, R(XMM1));
|
|
}
|
|
}
|
|
|
|
if (!fpr.V(dregs[0]).IsSimpleReg(tempxreg)) {
|
|
fpr.MapRegsV(dregs, V_Single, MAP_DIRTY | MAP_NOINIT);
|
|
MOVSS(fpr.V(dregs[0]), tempxreg);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, V_Single);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_VCrossQuat(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
|
|
u8 sregs[4], tregs[4], dregs[4];
|
|
GetVectorRegs(sregs, sz, _VS);
|
|
GetVectorRegs(tregs, sz, _VT);
|
|
GetVectorRegs(dregs, sz, _VD);
|
|
|
|
if (sz == V_Triple) {
|
|
// Cross product vcrsp.t
|
|
if (fpr.TryMapDirtyInInVS(dregs, sz, sregs, sz, tregs, sz)) {
|
|
MOVAPS(XMM0, fpr.VS(tregs));
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 0, 2, 1));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 0, 2, 1));
|
|
MULPS(XMM0, fpr.VS(sregs));
|
|
MULPS(XMM1, fpr.VS(tregs));
|
|
SUBPS(XMM0, R(XMM1));
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 0, 2, 1));
|
|
MOVAPS(fpr.VS(dregs), XMM0);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
fpr.MapRegsV(sregs, sz, 0);
|
|
|
|
// Compute X
|
|
MOVSS(XMM0, fpr.V(sregs[1]));
|
|
MULSS(XMM0, fpr.V(tregs[2]));
|
|
MOVSS(XMM1, fpr.V(sregs[2]));
|
|
MULSS(XMM1, fpr.V(tregs[1]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[0]), XMM0);
|
|
|
|
// Compute Y
|
|
MOVSS(XMM0, fpr.V(sregs[2]));
|
|
MULSS(XMM0, fpr.V(tregs[0]));
|
|
MOVSS(XMM1, fpr.V(sregs[0]));
|
|
MULSS(XMM1, fpr.V(tregs[2]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[1]), XMM0);
|
|
|
|
// Compute Z
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
MULSS(XMM0, fpr.V(tregs[1]));
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
MULSS(XMM1, fpr.V(tregs[0]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[2]), XMM0);
|
|
} else if (sz == V_Quad) {
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// Quaternion product vqmul.q
|
|
fpr.MapRegsV(sregs, sz, 0);
|
|
|
|
// Compute X
|
|
// d[0] = s[0] * t[3] + s[1] * t[2] - s[2] * t[1] + s[3] * t[0];
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
MULSS(XMM0, fpr.V(tregs[3]));
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
MULSS(XMM1, fpr.V(tregs[2]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[2]));
|
|
MULSS(XMM1, fpr.V(tregs[1]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[3]));
|
|
MULSS(XMM1, fpr.V(tregs[0]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[0]), XMM0);
|
|
|
|
// Compute Y
|
|
//d[1] = s[1] * t[3] + s[2] * t[0] + s[3] * t[1] - s[0] * t[2];
|
|
MOVSS(XMM0, fpr.V(sregs[1]));
|
|
MULSS(XMM0, fpr.V(tregs[3]));
|
|
MOVSS(XMM1, fpr.V(sregs[2]));
|
|
MULSS(XMM1, fpr.V(tregs[0]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[3]));
|
|
MULSS(XMM1, fpr.V(tregs[1]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[0]));
|
|
MULSS(XMM1, fpr.V(tregs[2]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[1]), XMM0);
|
|
|
|
// Compute Z
|
|
//d[2] = s[0] * t[1] - s[1] * t[0] + s[2] * t[3] + s[3] * t[2];
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
MULSS(XMM0, fpr.V(tregs[1]));
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
MULSS(XMM1, fpr.V(tregs[0]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[2]));
|
|
MULSS(XMM1, fpr.V(tregs[3]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[3]));
|
|
MULSS(XMM1, fpr.V(tregs[2]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[2]), XMM0);
|
|
|
|
// Compute W
|
|
//d[3] = -s[0] * t[0] - s[1] * t[1] - s[2] * t[2] + s[3] * t[3];
|
|
MOVSS(XMM0, fpr.V(sregs[3]));
|
|
MULSS(XMM0, fpr.V(tregs[3]));
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
MULSS(XMM1, fpr.V(tregs[1]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[2]));
|
|
MULSS(XMM1, fpr.V(tregs[2]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(XMM1, fpr.V(sregs[0]));
|
|
MULSS(XMM1, fpr.V(tregs[0]));
|
|
SUBSS(XMM0, R(XMM1));
|
|
MOVSS(fpr.V(dregs[3]), XMM0);
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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;
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
// Simplification: Disable if overlap unsafe
|
|
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs)) {
|
|
DISABLE;
|
|
}
|
|
}
|
|
|
|
if (imm3 < 6) {
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, false);
|
|
fpr.MapRegsV(dregs, sz, MAP_DIRTY);
|
|
// Test one bit of CC. This bit decides whether none or all subregisters are copied.
|
|
TEST(32, gpr.R(MIPS_REG_VFPUCC), Imm32(1 << imm3));
|
|
FixupBranch skip = J_CC(tf ? CC_NZ : CC_Z, true);
|
|
for (int i = 0; i < n; i++) {
|
|
MOVSS(fpr.VX(dregs[i]), fpr.V(sregs[i]));
|
|
}
|
|
SetJumpTarget(skip);
|
|
} else {
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, false);
|
|
fpr.MapRegsV(dregs, sz, MAP_DIRTY);
|
|
// Look at the bottom four bits of CC to individually decide if the subregisters should be copied.
|
|
for (int i = 0; i < n; i++) {
|
|
TEST(32, gpr.R(MIPS_REG_VFPUCC), Imm32(1 << i));
|
|
FixupBranch skip = J_CC(tf ? CC_NZ : CC_Z, true);
|
|
MOVSS(fpr.VX(dregs[i]), fpr.V(sregs[i]));
|
|
SetJumpTarget(skip);
|
|
}
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
static s32 DoVminSS(s32 treg) {
|
|
s32 sreg = currentMIPS->temp;
|
|
|
|
// If both are negative, we flip the comparison (not two's compliment.)
|
|
if (sreg < 0 && treg < 0) {
|
|
// If at least one side is NAN, we take the highest mantissa bits.
|
|
return treg < sreg ? sreg : treg;
|
|
} else {
|
|
// Otherwise, we take the lowest value (negative or lowest mantissa.)
|
|
return treg > sreg ? sreg : treg;
|
|
}
|
|
}
|
|
|
|
static s32 DoVmaxSS(s32 treg) {
|
|
s32 sreg = currentMIPS->temp;
|
|
|
|
// This is the same logic as vmin, just reversed.
|
|
if (sreg < 0 && treg < 0) {
|
|
return treg < sreg ? treg : sreg;
|
|
} else {
|
|
return treg > sreg ? treg : sreg;
|
|
}
|
|
}
|
|
|
|
void Jit::Comp_VecDo3(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
// Check that we can support the ops, and prepare temporary values for ops that need it.
|
|
bool allowSIMD = true;
|
|
switch (op >> 26) {
|
|
case 24: //VFPU0
|
|
switch ((op >> 23) & 7) {
|
|
case 0: // d[i] = s[i] + t[i]; break; //vadd
|
|
case 1: // d[i] = s[i] - t[i]; break; //vsub
|
|
case 7: // d[i] = s[i] / t[i]; break; //vdiv
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
break;
|
|
case 25: //VFPU1
|
|
switch ((op >> 23) & 7) {
|
|
case 0: // d[i] = s[i] * t[i]; break; //vmul
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
break;
|
|
case 27: //VFPU3
|
|
switch ((op >> 23) & 7) {
|
|
case 2: // vmin
|
|
case 3: // vmax
|
|
allowSIMD = false;
|
|
break;
|
|
case 6: // vsge
|
|
case 7: // vslt
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
break;
|
|
}
|
|
|
|
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);
|
|
|
|
if (allowSIMD && fpr.TryMapDirtyInInVS(dregs, sz, sregs, sz, tregs, sz)) {
|
|
void (XEmitter::*opFunc)(X64Reg, OpArg) = nullptr;
|
|
bool symmetric = false;
|
|
switch (op >> 26) {
|
|
case 24: //VFPU0
|
|
switch ((op >> 23) & 7) {
|
|
case 0: // d[i] = s[i] + t[i]; break; //vadd
|
|
opFunc = &XEmitter::ADDPS;
|
|
symmetric = true;
|
|
break;
|
|
case 1: // d[i] = s[i] - t[i]; break; //vsub
|
|
opFunc = &XEmitter::SUBPS;
|
|
break;
|
|
case 7: // d[i] = s[i] / t[i]; break; //vdiv
|
|
opFunc = &XEmitter::DIVPS;
|
|
break;
|
|
}
|
|
break;
|
|
case 25: //VFPU1
|
|
switch ((op >> 23) & 7)
|
|
{
|
|
case 0: // d[i] = s[i] * t[i]; break; //vmul
|
|
opFunc = &XEmitter::MULPS;
|
|
symmetric = true;
|
|
break;
|
|
}
|
|
break;
|
|
case 27: //VFPU3
|
|
switch ((op >> 23) & 7)
|
|
{
|
|
case 2: // vmin
|
|
// TODO: Mishandles NaN. Disabled for now.
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
MINPS(XMM1, fpr.VS(tregs));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM1));
|
|
break;
|
|
case 3: // vmax
|
|
// TODO: Mishandles NaN. Disabled for now.
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
MAXPS(XMM1, fpr.VS(tregs));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM1));
|
|
break;
|
|
case 6: // vsge
|
|
MOVAPS(XMM0, fpr.VS(tregs));
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
CMPPS(XMM0, R(XMM1), CMP_ORD);
|
|
CMPPS(XMM1, fpr.VS(tregs), CMP_NLT);
|
|
|
|
ANDPS(XMM1, R(XMM0));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
ANDPS(XMM1, MatR(TEMPREG));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM1));
|
|
break;
|
|
case 7: // vslt
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
CMPPS(XMM1, fpr.VS(tregs), CMP_LT);
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
ANDPS(XMM1, MatR(TEMPREG));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM1));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (opFunc != nullptr) {
|
|
if (fpr.VSX(dregs) != fpr.VSX(tregs)) {
|
|
if (fpr.VSX(dregs) != fpr.VSX(sregs)) {
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
}
|
|
(this->*opFunc)(fpr.VSX(dregs), fpr.VS(tregs));
|
|
} else if (symmetric) {
|
|
// We already know d = t.
|
|
(this->*opFunc)(fpr.VSX(dregs), fpr.VS(sregs));
|
|
} else {
|
|
MOVAPS(XMM1, fpr.VS(sregs));
|
|
(this->*opFunc)(XMM1, fpr.VS(tregs));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM1));
|
|
}
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs, n, tregs))
|
|
{
|
|
// On 32-bit we only have 6 xregs for mips regs, use XMM0/XMM1 if possible.
|
|
// But for vmin/vmax/vsge, we need XMM0/XMM1, so avoid.
|
|
if (i < 2 && (op >> 26) != 27)
|
|
tempxregs[i] = (X64Reg) (XMM0 + i);
|
|
else
|
|
{
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[i]);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[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
|
|
ADDSS(tempxregs[i], fpr.V(tregs[i]));
|
|
break;
|
|
case 1: // d[i] = s[i] - t[i]; break; //vsub
|
|
SUBSS(tempxregs[i], fpr.V(tregs[i]));
|
|
break;
|
|
case 7: // d[i] = s[i] / t[i]; break; //vdiv
|
|
DIVSS(tempxregs[i], fpr.V(tregs[i]));
|
|
break;
|
|
}
|
|
break;
|
|
case 25: //VFPU1
|
|
switch ((op >> 23) & 7)
|
|
{
|
|
case 0: // d[i] = s[i] * t[i]; break; //vmul
|
|
MULSS(tempxregs[i], fpr.V(tregs[i]));
|
|
break;
|
|
}
|
|
break;
|
|
case 27: //VFPU3
|
|
switch ((op >> 23) & 7)
|
|
{
|
|
case 2: // vmin
|
|
{
|
|
MOVSS(XMM0, fpr.V(tregs[i]));
|
|
UCOMISS(tempxregs[i], R(XMM0));
|
|
FixupBranch skip = J_CC(CC_NP, true);
|
|
|
|
MOVSS(MIPSSTATE_VAR(temp), tempxregs[i]);
|
|
MOVD_xmm(R(EAX), XMM0);
|
|
CallProtectedFunction(&DoVminSS, R(EAX));
|
|
MOVD_xmm(tempxregs[i], R(EAX));
|
|
FixupBranch finish = J();
|
|
|
|
SetJumpTarget(skip);
|
|
MINSS(tempxregs[i], R(XMM0));
|
|
SetJumpTarget(finish);
|
|
}
|
|
break;
|
|
case 3: // vmax
|
|
{
|
|
MOVSS(XMM0, fpr.V(tregs[i]));
|
|
UCOMISS(tempxregs[i], R(XMM0));
|
|
FixupBranch skip = J_CC(CC_NP, true);
|
|
|
|
MOVSS(MIPSSTATE_VAR(temp), tempxregs[i]);
|
|
MOVD_xmm(R(EAX), XMM0);
|
|
CallProtectedFunction(&DoVmaxSS, R(EAX));
|
|
MOVD_xmm(tempxregs[i], R(EAX));
|
|
FixupBranch finish = J();
|
|
|
|
SetJumpTarget(skip);
|
|
MAXSS(tempxregs[i], R(XMM0));
|
|
SetJumpTarget(finish);
|
|
}
|
|
break;
|
|
case 6: // vsge
|
|
// We can't just reverse, because of 0/-0.
|
|
MOVSS(XMM0, fpr.V(tregs[i]));
|
|
MOVSS(XMM1, R(tempxregs[i]));
|
|
CMPORDSS(XMM1, R(XMM0));
|
|
CMPNLTSS(tempxregs[i], R(XMM0));
|
|
ANDPS(tempxregs[i], R(XMM1));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
ANDPS(tempxregs[i], MatR(TEMPREG));
|
|
break;
|
|
case 7: // vslt
|
|
CMPLTSS(tempxregs[i], fpr.V(tregs[i]));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
ANDPS(tempxregs[i], MatR(TEMPREG));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
alignas(16) static const u32 vcmpMask[4][4] = {
|
|
{0x00000031, 0x00000000, 0x00000000, 0x00000000},
|
|
{0x00000011, 0x00000012, 0x00000000, 0x00000000},
|
|
{0x00000011, 0x00000012, 0x00000014, 0x00000000},
|
|
{0x00000011, 0x00000012, 0x00000014, 0x00000018},
|
|
};
|
|
|
|
void Jit::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.
|
|
switch (cond) {
|
|
case VC_EI: // c = my_isinf(s[i]); break;
|
|
case VC_NI: // c = !my_isinf(s[i]); break;
|
|
DISABLE;
|
|
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;
|
|
case VC_EN: // c = my_isnan(s[i]); break;
|
|
case VC_NN: // c = !my_isnan(s[i]); break;
|
|
if (_VS != _VT)
|
|
DISABLE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// First, let's get the trivial ones.
|
|
|
|
static const int true_bits[4] = {0x31, 0x33, 0x37, 0x3f};
|
|
|
|
if (cond == VC_TR) {
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, true);
|
|
OR(32, gpr.R(MIPS_REG_VFPUCC), Imm32(true_bits[n-1]));
|
|
return;
|
|
} else if (cond == VC_FL) {
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, true);
|
|
AND(32, gpr.R(MIPS_REG_VFPUCC), Imm32(~true_bits[n-1]));
|
|
return;
|
|
}
|
|
|
|
if (n > 1)
|
|
gpr.FlushLockX(ECX);
|
|
|
|
// Start with zero in each lane for the compare to zero.
|
|
if (cond == VC_EZ || cond == VC_NZ) {
|
|
XORPS(XMM0, R(XMM0));
|
|
if (n > 1) {
|
|
XORPS(XMM1, R(XMM1));
|
|
}
|
|
}
|
|
|
|
bool inverse = false;
|
|
|
|
if (cond == VC_GE || cond == VC_GT) {
|
|
// We flip, and we need them in regs so we don't clear the high lanes.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.MapRegsV(tregs, sz, 0);
|
|
} else {
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.MapRegsV(sregs, sz, 0);
|
|
}
|
|
|
|
// We go backwards because it's more convenient to put things in the right lanes.
|
|
int affected_bits = (1 << 4) | (1 << 5); // 4 and 5
|
|
for (int i = n - 1; i >= 0; --i) {
|
|
// Alternate between XMM0 and XMM1
|
|
X64Reg reg = i == 1 || i == 3 ? XMM1 : XMM0;
|
|
if ((i == 0 || i == 1) && n > 2) {
|
|
// We need to swap lanes... this also puts them in the right place.
|
|
SHUFPS(reg, R(reg), _MM_SHUFFLE(3, 2, 0, 1));
|
|
}
|
|
|
|
// Let's only handle the easy ones, and fall back on the interpreter for the rest.
|
|
bool compareTwo = false;
|
|
bool compareToZero = false;
|
|
int comparison = -1;
|
|
bool flip = false;
|
|
|
|
switch (cond) {
|
|
case VC_ES:
|
|
comparison = -1; // We will do the compare at the end. XMM1 will have the bits.
|
|
MOVSS(reg, fpr.V(sregs[i]));
|
|
break;
|
|
|
|
case VC_NS:
|
|
comparison = -1; // We will do the compare at the end. XMM1 will have the bits.
|
|
MOVSS(reg, fpr.V(sregs[i]));
|
|
// Note that we do this all at once at the end.
|
|
inverse = true;
|
|
break;
|
|
|
|
case VC_EN:
|
|
comparison = CMP_UNORD;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_NN:
|
|
comparison = CMP_UNORD;
|
|
compareTwo = true;
|
|
// Note that we do this all at once at the end.
|
|
inverse = true;
|
|
break;
|
|
|
|
case VC_EQ: // c = s[i] == t[i]; break;
|
|
comparison = CMP_EQ;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_LT: // c = s[i] < t[i]; break;
|
|
comparison = CMP_LT;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_LE: // c = s[i] <= t[i]; break;
|
|
comparison = CMP_LE;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_NE: // c = s[i] != t[i]; break;
|
|
comparison = CMP_NEQ;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_GE: // c = s[i] >= t[i]; break;
|
|
comparison = CMP_LE;
|
|
flip = true;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_GT: // c = s[i] > t[i]; break;
|
|
comparison = CMP_LT;
|
|
flip = true;
|
|
compareTwo = true;
|
|
break;
|
|
|
|
case VC_EZ: // c = s[i] == 0.0f || s[i] == -0.0f; break;
|
|
comparison = CMP_EQ;
|
|
compareToZero = true;
|
|
break;
|
|
|
|
case VC_NZ: // c = s[i] != 0; break;
|
|
comparison = CMP_NEQ;
|
|
compareToZero = true;
|
|
break;
|
|
|
|
default:
|
|
DISABLE;
|
|
}
|
|
|
|
if (comparison != -1) {
|
|
if (compareTwo) {
|
|
if (!flip) {
|
|
MOVSS(reg, fpr.V(sregs[i]));
|
|
CMPSS(reg, fpr.V(tregs[i]), comparison);
|
|
} else {
|
|
MOVSS(reg, fpr.V(tregs[i]));
|
|
CMPSS(reg, fpr.V(sregs[i]), comparison);
|
|
}
|
|
} else if (compareToZero) {
|
|
CMPSS(reg, fpr.V(sregs[i]), comparison);
|
|
}
|
|
}
|
|
|
|
affected_bits |= 1 << i;
|
|
}
|
|
|
|
if (n > 1) {
|
|
XOR(32, R(ECX), R(ECX));
|
|
|
|
// This combines them together.
|
|
UNPCKLPS(XMM0, R(XMM1));
|
|
|
|
// Finalize the comparison for ES/NS.
|
|
if (cond == VC_ES || cond == VC_NS) {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&fourinfnan));
|
|
ANDPS(XMM0, MatR(TEMPREG));
|
|
PCMPEQD(XMM0, MatR(TEMPREG)); // Integer comparison
|
|
// It's inversed below for NS.
|
|
}
|
|
|
|
if (inverse) {
|
|
// The canonical way to generate a bunch of ones, see https://stackoverflow.com/questions/35085059/what-are-the-best-instruction-sequences-to-generate-vector-constants-on-the-fly
|
|
PCMPEQW(XMM1, R(XMM1));
|
|
XORPS(XMM0, R(XMM1));
|
|
}
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&vcmpMask[n - 1]));
|
|
ANDPS(XMM0, MatR(TEMPREG));
|
|
MOVAPS(MIPSSTATE_VAR(vcmpResult), XMM0);
|
|
|
|
MOV(32, R(TEMPREG), MIPSSTATE_VAR(vcmpResult[0]));
|
|
for (int i = 1; i < n; ++i) {
|
|
OR(32, R(TEMPREG), MIPSSTATE_VAR_ELEM32(vcmpResult[0], i));
|
|
}
|
|
|
|
// Aggregate the bits. Urgh, expensive. Can optimize for the case of one comparison,
|
|
// which is the most common after all.
|
|
CMP(32, R(TEMPREG), Imm8(affected_bits & 0x1F));
|
|
SETcc(CC_E, R(ECX));
|
|
SHL(32, R(ECX), Imm8(5));
|
|
OR(32, R(TEMPREG), R(ECX));
|
|
} else {
|
|
// Finalize the comparison for ES/NS.
|
|
if (cond == VC_ES || cond == VC_NS) {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&fourinfnan));
|
|
ANDPS(XMM0, MatR(TEMPREG));
|
|
PCMPEQD(XMM0, MatR(TEMPREG)); // Integer comparison
|
|
// It's inversed below for NS.
|
|
}
|
|
|
|
MOVD_xmm(R(TEMPREG), XMM0);
|
|
if (inverse) {
|
|
XOR(32, R(TEMPREG), Imm32(0xFFFFFFFF));
|
|
}
|
|
AND(32, R(TEMPREG), Imm32(0x31));
|
|
}
|
|
|
|
gpr.UnlockAllX();
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, true);
|
|
AND(32, gpr.R(MIPS_REG_VFPUCC), Imm32(~affected_bits));
|
|
OR(32, gpr.R(MIPS_REG_VFPUCC), R(TEMPREG));
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
// There are no immediates for floating point, so we need to load these
|
|
// from RAM. Might as well have a table ready.
|
|
extern const float mulTableVi2f[32] = {
|
|
1.0f/(1UL<<0),1.0f/(1UL<<1),1.0f/(1UL<<2),1.0f/(1UL<<3),
|
|
1.0f/(1UL<<4),1.0f/(1UL<<5),1.0f/(1UL<<6),1.0f/(1UL<<7),
|
|
1.0f/(1UL<<8),1.0f/(1UL<<9),1.0f/(1UL<<10),1.0f/(1UL<<11),
|
|
1.0f/(1UL<<12),1.0f/(1UL<<13),1.0f/(1UL<<14),1.0f/(1UL<<15),
|
|
1.0f/(1UL<<16),1.0f/(1UL<<17),1.0f/(1UL<<18),1.0f/(1UL<<19),
|
|
1.0f/(1UL<<20),1.0f/(1UL<<21),1.0f/(1UL<<22),1.0f/(1UL<<23),
|
|
1.0f/(1UL<<24),1.0f/(1UL<<25),1.0f/(1UL<<26),1.0f/(1UL<<27),
|
|
1.0f/(1UL<<28),1.0f/(1UL<<29),1.0f/(1UL<<30),1.0f/(1UL<<31),
|
|
};
|
|
|
|
void Jit::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 = &mulTableVi2f[imm];
|
|
|
|
u8 sregs[4], dregs[4];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
int 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) {
|
|
if (RipAccessible(mult)) {
|
|
MOVSS(XMM1, M(mult)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(mult));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
}
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
fpr.MapRegV(tempregs[i], sregs[i] == dregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
if (fpr.V(sregs[i]).IsSimpleReg()) {
|
|
CVTDQ2PS(fpr.VX(tempregs[i]), fpr.V(sregs[i]));
|
|
} else {
|
|
MOVSS(fpr.VX(tempregs[i]), fpr.V(sregs[i]));
|
|
CVTDQ2PS(fpr.VX(tempregs[i]), R(fpr.VX(tempregs[i])));
|
|
}
|
|
if (*mult != 1.0f)
|
|
MULSS(fpr.VX(tempregs[i]), R(XMM1));
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
if (dregs[i] != tempregs[i]) {
|
|
fpr.MapRegV(dregs[i], MAP_DIRTY | MAP_NOINIT);
|
|
MOVSS(fpr.VX(dregs[i]), fpr.V(tempregs[i]));
|
|
}
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
// Planning for true SIMD
|
|
|
|
// Sequence for gathering sparse registers into one SIMD:
|
|
// MOVSS(XMM0, fpr.R(sregs[0]));
|
|
// MOVSS(XMM1, fpr.R(sregs[1]));
|
|
// MOVSS(XMM2, fpr.R(sregs[2]));
|
|
// MOVSS(XMM3, fpr.R(sregs[3]));
|
|
// SHUFPS(XMM0, R(XMM1), _MM_SHUFFLE(0, 0, 0, 0)); // XMM0 = S1 S1 S0 S0
|
|
// SHUFPS(XMM2, R(XMM3), _MM_SHUFFLE(0, 0, 0, 0)); // XMM2 = S3 S3 S2 S2
|
|
// SHUFPS(XMM0, R(XMM2), _MM_SHUFFLE(2, 0, 2, 0)); // XMM0 = S3 S2 S1 S0
|
|
// Some punpckwd etc would also work.
|
|
// Alternatively, MOVSS and three PINSRD (SSE4) with mem source.
|
|
// Why PINSRD instead of INSERTPS?
|
|
// http://software.intel.com/en-us/blogs/2009/01/07/using-sse41-for-mp3-encoding-quantization
|
|
|
|
// Sequence for scattering a SIMD register to sparse registers:
|
|
// (Very serial though, better methods may be possible)
|
|
// MOVSS(fpr.R(sregs[0]), XMM0);
|
|
// SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 3, 2, 1));
|
|
// MOVSS(fpr.R(sregs[1]), XMM0);
|
|
// SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 3, 2, 1));
|
|
// MOVSS(fpr.R(sregs[2]), XMM0);
|
|
// SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(3, 3, 2, 1));
|
|
// MOVSS(fpr.R(sregs[3]), XMM0);
|
|
// On SSE4 we should use EXTRACTPS.
|
|
|
|
// Translation of ryg's half_to_float5_SSE2
|
|
void Jit::Comp_Vh2f(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
#define SSE_CONST4(name, val) alignas(16) static const u32 name[4] = { (val), (val), (val), (val) }
|
|
|
|
SSE_CONST4(mask_nosign, 0x7fff);
|
|
SSE_CONST4(nan_mantissa, 0x800003ff);
|
|
SSE_CONST4(magic, (254 - 15) << 23);
|
|
SSE_CONST4(was_infnan, 0x7bff);
|
|
SSE_CONST4(exp_infnan, 255 << 23);
|
|
|
|
OpArg mask_nosign_arg, nan_mantissa_arg, magic_arg, was_infnan_arg, exp_infnan_arg;
|
|
if (RipAccessible(mask_nosign)) {
|
|
mask_nosign_arg = M(&mask_nosign[0]);
|
|
nan_mantissa_arg = M(&nan_mantissa[0]);
|
|
magic_arg = M(&magic[0]);
|
|
was_infnan_arg = M(&was_infnan[0]);
|
|
exp_infnan_arg = M(&exp_infnan[0]);
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&mask_nosign[0]));
|
|
mask_nosign_arg = MAccessibleDisp(TEMPREG, &mask_nosign[0], &mask_nosign[0]);
|
|
nan_mantissa_arg = MAccessibleDisp(TEMPREG, &mask_nosign[0], &nan_mantissa[0]);
|
|
magic_arg = MAccessibleDisp(TEMPREG, &mask_nosign[0], &magic[0]);
|
|
was_infnan_arg = MAccessibleDisp(TEMPREG, &mask_nosign[0], &was_infnan[0]);
|
|
exp_infnan_arg = MAccessibleDisp(TEMPREG, &mask_nosign[0], &exp_infnan[0]);
|
|
}
|
|
|
|
#undef SSE_CONST4
|
|
VectorSize sz = GetVecSize(op);
|
|
VectorSize outsize;
|
|
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);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
|
|
// Force ourselves an extra xreg as temp space.
|
|
X64Reg tempR = fpr.GetFreeXReg();
|
|
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
if (sz != V_Single) {
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
PUNPCKLDQ(XMM0, R(XMM1));
|
|
}
|
|
XORPS(XMM1, R(XMM1));
|
|
PUNPCKLWD(XMM0, R(XMM1));
|
|
|
|
// OK, 16 bits in each word.
|
|
// Let's go. Deep magic here.
|
|
MOVAPS(XMM1, R(XMM0));
|
|
ANDPS(XMM0, mask_nosign_arg); // xmm0 = expmant
|
|
XORPS(XMM1, R(XMM0)); // xmm1 = justsign = expmant ^ xmm0
|
|
MOVAPS(tempR, R(XMM0));
|
|
PSLLD(XMM0, 13);
|
|
MULPS(XMM0, magic_arg); /// xmm0 = scaled
|
|
PSLLD(XMM1, 16); // xmm1 = sign
|
|
ORPS(XMM0, R(XMM1));
|
|
|
|
// Now create a NAN mask, adding in the sign.
|
|
ORPS(XMM1, R(tempR)); // xmm1 = sign + original mantissa.
|
|
ANDPS(XMM1, nan_mantissa_arg); // xmm1 = original mantissa
|
|
PCMPGTD(tempR, was_infnan_arg); // xmm2 = b_wasinfnan
|
|
ORPS(XMM1, exp_infnan_arg); // xmm1 = infnan result
|
|
ANDPS(XMM1, R(tempR)); // xmm1 = infnan result OR zero if not infnan
|
|
ANDNPS(tempR, R(XMM0)); // tempR = result OR zero if infnan
|
|
ORPS(XMM1, R(tempR));
|
|
|
|
fpr.MapRegsV(dregs, outsize, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// TODO: Could apply D-prefix in parallel here...
|
|
|
|
MOVSS(fpr.V(dregs[0]), XMM1);
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 3, 2, 1));
|
|
MOVSS(fpr.V(dregs[1]), XMM1);
|
|
|
|
if (sz != V_Single) {
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 3, 2, 1));
|
|
MOVSS(fpr.V(dregs[2]), XMM1);
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 3, 2, 1));
|
|
MOVSS(fpr.V(dregs[3]), XMM1);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, outsize);
|
|
gpr.UnlockAllX();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
// The goal is to map (reversed byte order for clarity):
|
|
// AABBCCDD -> 000000AA 000000BB 000000CC 000000DD
|
|
alignas(16) static s8 vc2i_shuffle[16] = { -1, -1, -1, 0, -1, -1, -1, 1, -1, -1, -1, 2, -1, -1, -1, 3 };
|
|
// AABBCCDD -> AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
|
|
alignas(16) static s8 vuc2i_shuffle[16] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 };
|
|
|
|
void Jit::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)
|
|
|
|
// 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);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
|
|
if (bits == 16) {
|
|
MOVSS(XMM1, fpr.V(sregs[0]));
|
|
if (sz != V_Single) {
|
|
MOVSS(XMM0, fpr.V(sregs[1]));
|
|
PUNPCKLDQ(XMM1, R(XMM0));
|
|
}
|
|
|
|
// Unpack 16-bit words into 32-bit words, upper position, and we're done!
|
|
PXOR(XMM0, R(XMM0));
|
|
PUNPCKLWD(XMM0, R(XMM1));
|
|
} 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.
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
if (cpu_info.bSSSE3 && RipAccessible(vuc2i_shuffle)) {
|
|
// Not really different speed. Generates a bit less code.
|
|
PSHUFB(XMM0, M(&vuc2i_shuffle[0])); // rip accessible
|
|
} else {
|
|
// First, we change 0xDDCCBBAA to 0xDDDDCCCCBBBBAAAA.
|
|
PUNPCKLBW(XMM0, R(XMM0));
|
|
// Now, interleave each 16 bits so they're all 32 bits wide.
|
|
PUNPCKLWD(XMM0, R(XMM0));
|
|
}
|
|
} else {
|
|
if (cpu_info.bSSSE3 && RipAccessible(vc2i_shuffle)) {
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
PSHUFB(XMM0, M(&vc2i_shuffle[0]));
|
|
} else {
|
|
PXOR(XMM1, R(XMM1));
|
|
MOVSS(XMM0, fpr.V(sregs[0]));
|
|
PUNPCKLBW(XMM1, R(XMM0));
|
|
PXOR(XMM0, R(XMM0));
|
|
PUNPCKLWD(XMM0, R(XMM1));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
PSRLD(XMM0, 1);
|
|
}
|
|
|
|
if (fpr.TryMapRegsVS(dregs, outsize, MAP_NOINIT | MAP_DIRTY)) {
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
} else {
|
|
// Done! TODO: The rest of this should be possible to extract into a function.
|
|
fpr.MapRegsV(dregs, outsize, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// TODO: Could apply D-prefix in parallel here...
|
|
|
|
MOVSS(fpr.V(dregs[0]), XMM0);
|
|
PSRLDQ(XMM0, 4);
|
|
MOVSS(fpr.V(dregs[1]), XMM0);
|
|
|
|
if (outsize != V_Pair) {
|
|
PSRLDQ(XMM0, 4);
|
|
MOVSS(fpr.V(dregs[2]), XMM0);
|
|
PSRLDQ(XMM0, 4);
|
|
MOVSS(fpr.V(dregs[3]), XMM0);
|
|
}
|
|
}
|
|
|
|
ApplyPrefixD(dregs, outsize);
|
|
gpr.UnlockAllX();
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
extern const double mulTableVf2i[32] = {
|
|
(1ULL<<0),(1ULL<<1),(1ULL<<2),(1ULL<<3),
|
|
(1ULL<<4),(1ULL<<5),(1ULL<<6),(1ULL<<7),
|
|
(1ULL<<8),(1ULL<<9),(1ULL<<10),(1ULL<<11),
|
|
(1ULL<<12),(1ULL<<13),(1ULL<<14),(1ULL<<15),
|
|
(1ULL<<16),(1ULL<<17),(1ULL<<18),(1ULL<<19),
|
|
(1ULL<<20),(1ULL<<21),(1ULL<<22),(1ULL<<23),
|
|
(1ULL<<24),(1ULL<<25),(1ULL<<26),(1ULL<<27),
|
|
(1ULL<<28),(1ULL<<29),(1ULL<<30),(1ULL<<31),
|
|
};
|
|
|
|
static const double maxMinIntAsDouble[2] = { (double)0x7fffffff, (double)(int)0x80000000 }; // that's not equal to 0x80000000
|
|
|
|
void Jit::Comp_Vf2i(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
int imm = (op >> 16) & 0x1f;
|
|
const double *mult = &mulTableVf2i[imm];
|
|
|
|
int setMXCSR = -1;
|
|
int rmode = (op >> 21) & 0x1f;
|
|
switch (rmode) {
|
|
case 17:
|
|
break; //z - truncate. Easy to support.
|
|
case 16:
|
|
setMXCSR = 0;
|
|
break;
|
|
case 18:
|
|
setMXCSR = 2;
|
|
break;
|
|
case 19:
|
|
setMXCSR = 1;
|
|
break;
|
|
}
|
|
|
|
// Small optimization: 0 is our default mode anyway.
|
|
if (setMXCSR == 0 && !js.hasSetRounding) {
|
|
setMXCSR = -1;
|
|
}
|
|
// Except for truncate, we need to update MXCSR to our preferred rounding mode.
|
|
if (setMXCSR != -1) {
|
|
STMXCSR(MIPSSTATE_VAR(mxcsrTemp));
|
|
MOV(32, R(TEMPREG), MIPSSTATE_VAR(mxcsrTemp));
|
|
AND(32, R(TEMPREG), Imm32(~(3 << 13)));
|
|
if (setMXCSR != 0) {
|
|
OR(32, R(TEMPREG), Imm32(setMXCSR << 13));
|
|
}
|
|
MOV(32, MIPSSTATE_VAR(temp), R(TEMPREG));
|
|
LDMXCSR(MIPSSTATE_VAR(temp));
|
|
}
|
|
|
|
u8 sregs[4], dregs[4];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
// Really tricky to SIMD due to double precision requirement...
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_DIRTY | MAP_NOINIT);
|
|
|
|
u8 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) {
|
|
if (RipAccessible(mult)) {
|
|
MOVSD(XMM1, M(mult)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(mult));
|
|
MOVSD(XMM1, MatR(TEMPREG));
|
|
}
|
|
}
|
|
|
|
fpr.MapRegsV(tempregs, sz, MAP_DIRTY | MAP_NOINIT);
|
|
for (int i = 0; i < n; i++) {
|
|
// Need to do this in double precision to clamp correctly as float
|
|
// doesn't have enough precision to represent 0x7fffffff for example exactly.
|
|
MOVSS(XMM0, fpr.V(sregs[i]));
|
|
CVTSS2SD(XMM0, R(XMM0)); // convert to double precision
|
|
if (*mult != 1.0f) {
|
|
MULSD(XMM0, R(XMM1));
|
|
}
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(maxMinIntAsDouble));
|
|
MINSD(XMM0, MDisp(TEMPREG, 0));
|
|
MAXSD(XMM0, MDisp(TEMPREG, sizeof(double)));
|
|
// We've set the rounding mode above, so this part's easy.
|
|
switch ((op >> 21) & 0x1f) {
|
|
case 16: CVTSD2SI(TEMPREG, R(XMM0)); break; //n
|
|
case 17: CVTTSD2SI(TEMPREG, R(XMM0)); break; //z - truncate
|
|
case 18: CVTSD2SI(TEMPREG, R(XMM0)); break; //u
|
|
case 19: CVTSD2SI(TEMPREG, R(XMM0)); break; //d
|
|
}
|
|
MOVD_xmm(fpr.VX(tempregs[i]), R(TEMPREG));
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
if (dregs[i] != tempregs[i]) {
|
|
fpr.MapRegV(dregs[i], MAP_DIRTY | MAP_NOINIT);
|
|
MOVSS(fpr.VX(dregs[i]), fpr.V(tempregs[i]));
|
|
fpr.DiscardV(tempregs[i]);
|
|
}
|
|
}
|
|
|
|
if (setMXCSR != -1) {
|
|
LDMXCSR(MIPSSTATE_VAR(mxcsrTemp));
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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);
|
|
|
|
if (RipAccessible(cst_constants)) {
|
|
MOVSS(XMM0, M(&cst_constants[conNum])); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&cst_constants[conNum]));
|
|
MOVSS(XMM0, MatR(TEMPREG));
|
|
}
|
|
|
|
if (fpr.TryMapRegsVS(dregs, sz, MAP_NOINIT | MAP_DIRTY)) {
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0,0,0,0));
|
|
MOVAPS(fpr.VS(dregs), XMM0);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
fpr.MapRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
for (int i = 0; i < n; i++) {
|
|
MOVSS(fpr.V(dregs[i]), XMM0);
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs)) {
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
} else {
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[i]);
|
|
}
|
|
}
|
|
|
|
// Would be nice with more temp regs here so we could put signBitLower and oneOneOneOne into regs...
|
|
for (int i = 0; i < n; ++i) {
|
|
XORPS(XMM0, R(XMM0));
|
|
CMPEQSS(XMM0, fpr.V(sregs[i])); // XMM0 = s[i] == 0.0f
|
|
MOVSS(XMM1, fpr.V(sregs[i]));
|
|
// Preserve sign bit, replace rest with ones
|
|
if (RipAccessible(signBitLower)) {
|
|
ANDPS(XMM1, M(&signBitLower)); // rip accessible
|
|
ORPS(XMM1, M(&oneOneOneOne)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&signBitLower));
|
|
ANDPS(XMM1, MatR(TEMPREG));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
ORPS(XMM1, MatR(TEMPREG));
|
|
}
|
|
// If really was equal to zero, zap. Note that ANDN negates the destination.
|
|
ANDNPS(XMM0, R(XMM1));
|
|
MOVAPS(tempxregs[i], R(XMM0));
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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];
|
|
// Actually uses the T prefixes (despite being VS.)
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
if (js.prefixT != 0x0000F055)
|
|
GetVectorRegsPrefixT(tregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
if (js.prefixT != 0x0000F055)
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs)) {
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
} else {
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[i]);
|
|
}
|
|
}
|
|
|
|
if (js.prefixT == 0x0000F055) {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
}
|
|
for (int i = 0; i < n; ++i) {
|
|
if (js.prefixT == 0x0000F055) {
|
|
MOVSS(XMM0, R(XMM1));
|
|
} else {
|
|
MOVSS(XMM0, fpr.V(tregs[i]));
|
|
}
|
|
ADDSS(XMM0, fpr.V(sregs[i]));
|
|
MOVSS(tempxregs[i], R(XMM0));
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Vbfy(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
if (n != 2 && n != 4) {
|
|
DISABLE;
|
|
}
|
|
|
|
u8 sregs[4], dregs[4];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!IsOverlapSafe(dregs[i], i, n, sregs)) {
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
} else {
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[i]);
|
|
}
|
|
}
|
|
|
|
int subop = (op >> 16) & 0x1F;
|
|
if (subop == 3) {
|
|
// vbfy2
|
|
MOVSS(tempxregs[0], fpr.V(sregs[0]));
|
|
MOVSS(tempxregs[1], fpr.V(sregs[1]));
|
|
MOVSS(tempxregs[2], fpr.V(sregs[0]));
|
|
MOVSS(tempxregs[3], fpr.V(sregs[1]));
|
|
ADDSS(tempxregs[0], fpr.V(sregs[2]));
|
|
ADDSS(tempxregs[1], fpr.V(sregs[3]));
|
|
SUBSS(tempxregs[2], fpr.V(sregs[2]));
|
|
SUBSS(tempxregs[3], fpr.V(sregs[3]));
|
|
} else if (subop == 2) {
|
|
// vbfy1
|
|
MOVSS(tempxregs[0], fpr.V(sregs[0]));
|
|
MOVSS(tempxregs[1], fpr.V(sregs[0]));
|
|
ADDSS(tempxregs[0], fpr.V(sregs[1]));
|
|
SUBSS(tempxregs[1], fpr.V(sregs[1]));
|
|
if (n == 4) {
|
|
MOVSS(tempxregs[2], fpr.V(sregs[2]));
|
|
MOVSS(tempxregs[3], fpr.V(sregs[2]));
|
|
ADDSS(tempxregs[2], fpr.V(sregs[3]));
|
|
SUBSS(tempxregs[3], fpr.V(sregs[3]));
|
|
}
|
|
} else {
|
|
DISABLE;
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
union u32float {
|
|
u32 u;
|
|
float f;
|
|
|
|
operator float() const {
|
|
return f;
|
|
}
|
|
|
|
inline u32float &operator *=(const float &other) {
|
|
f *= other;
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
#if PPSSPP_ARCH(AMD64)
|
|
typedef float SinCosArg;
|
|
#else
|
|
typedef u32float SinCosArg;
|
|
#endif
|
|
|
|
void SinCos(SinCosArg angle, float *output) {
|
|
vfpu_sincos(angle, output[0], output[1]);
|
|
}
|
|
|
|
void SinOnly(SinCosArg angle, float *output) {
|
|
output[0] = vfpu_sin(angle);
|
|
}
|
|
|
|
void NegSinOnly(SinCosArg angle, float *output) {
|
|
output[0] = -vfpu_sin(angle);
|
|
}
|
|
|
|
void CosOnly(SinCosArg angle, float *output) {
|
|
output[1] = vfpu_cos(angle);
|
|
}
|
|
|
|
void ASinScaled(SinCosArg sine, float *output) {
|
|
output[0] = vfpu_asin(sine);
|
|
}
|
|
|
|
void SinCosNegSin(SinCosArg angle, float *output) {
|
|
vfpu_sincos(angle, output[0], output[1]);
|
|
output[0] = -output[0];
|
|
}
|
|
|
|
void Exp2(SinCosArg arg, float *output) {
|
|
output[0] = vfpu_exp2(arg);
|
|
}
|
|
|
|
void Log2(SinCosArg arg, float *output) {
|
|
output[0] = vfpu_log2(arg);
|
|
}
|
|
|
|
void RExp2(SinCosArg arg, float *output) {
|
|
output[0] = vfpu_rexp2(arg);
|
|
}
|
|
|
|
void Jit::Comp_VV2Op(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
auto specialFuncCallHelper = [this](void (*specialFunc)(SinCosArg, float *output), u8 sreg) {
|
|
#if PPSSPP_ARCH(AMD64)
|
|
MOVSS(XMM0, fpr.V(sreg));
|
|
// TODO: This reg might be different on Linux...
|
|
#ifdef _WIN32
|
|
LEA(64, RDX, MIPSSTATE_VAR(sincostemp[0]));
|
|
#else
|
|
LEA(64, RDI, MIPSSTATE_VAR(sincostemp[0]));
|
|
#endif
|
|
ABI_CallFunction(thunks.ProtectFunction((const void *)specialFunc, 0));
|
|
#else
|
|
// Sigh, passing floats with cdecl isn't pretty, ends up on the stack.
|
|
if (fpr.V(sreg).IsSimpleReg()) {
|
|
MOVD_xmm(R(EAX), fpr.VX(sreg));
|
|
} else {
|
|
MOV(32, R(EAX), fpr.V(sreg));
|
|
}
|
|
CallProtectedFunction((const void *)specialFunc, R(EAX), Imm32((uint32_t)(uintptr_t)&mips_->sincostemp[0]));
|
|
#endif
|
|
};
|
|
|
|
// Pre-processing: Eliminate silly no-op VMOVs, common in Wipeout Pure
|
|
if (((op >> 16) & 0x1f) == 0 && _VS == _VD && js.HasNoPrefix()) {
|
|
return;
|
|
}
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
u8 sregs[4], dregs[4];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
bool canSIMD = false;
|
|
// Some can be SIMD'd.
|
|
switch ((op >> 16) & 0x1f) {
|
|
case 0: // vmov
|
|
case 1: // vabs
|
|
case 2: // vneg
|
|
canSIMD = true;
|
|
break;
|
|
}
|
|
|
|
if (canSIMD && fpr.TryMapDirtyInVS(dregs, sz, sregs, sz)) {
|
|
switch ((op >> 16) & 0x1f) {
|
|
case 0: // vmov
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
break;
|
|
case 1: // vabs
|
|
if (dregs[0] != sregs[0])
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
if (RipAccessible(&noSignMask)) {
|
|
ANDPS(fpr.VSX(dregs), M(&noSignMask)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&noSignMask));
|
|
ANDPS(fpr.VSX(dregs), MatR(TEMPREG));
|
|
}
|
|
break;
|
|
case 2: // vneg
|
|
if (dregs[0] != sregs[0])
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
if (RipAccessible(&signBitAll)) {
|
|
XORPS(fpr.VSX(dregs), M(&signBitAll)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&signBitAll));
|
|
XORPS(fpr.VSX(dregs), MatR(TEMPREG));
|
|
}
|
|
break;
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (!IsOverlapSafeAllowS(dregs[i], i, n, sregs))
|
|
{
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
}
|
|
else
|
|
{
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[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.
|
|
if (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
break;
|
|
case 1: // d[i] = fabsf(s[i]); break; //vabs
|
|
if (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
if (RipAccessible(&noSignMask)) {
|
|
ANDPS(tempxregs[i], M(&noSignMask)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&noSignMask));
|
|
ANDPS(tempxregs[i], MatR(TEMPREG));
|
|
}
|
|
break;
|
|
case 2: // d[i] = -s[i]; break; //vneg
|
|
if (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
if (RipAccessible(&signBitLower)) {
|
|
XORPS(tempxregs[i], M(&signBitLower)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&signBitLower));
|
|
XORPS(tempxregs[i], MatR(TEMPREG));
|
|
}
|
|
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 (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
|
|
// Zero out XMM0 if it was <= +0.0f (but skip NAN.)
|
|
MOVSS(R(XMM0), tempxregs[i]);
|
|
XORPS(XMM1, R(XMM1));
|
|
CMPLESS(XMM0, R(XMM1));
|
|
ANDNPS(XMM0, R(tempxregs[i]));
|
|
|
|
// Retain a NAN in XMM0 (must be second operand.)
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(tempxregs[i], MatR(TEMPREG));
|
|
MINSS(tempxregs[i], R(XMM0));
|
|
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 (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
|
|
// Check for < -1.0f, but careful of NANs.
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&minus_one));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
MOVSS(R(XMM0), tempxregs[i]);
|
|
CMPLESS(XMM0, R(XMM1));
|
|
// If it was NOT less, the three ops below do nothing.
|
|
// Otherwise, they replace the value with -1.0f.
|
|
ANDPS(XMM1, R(XMM0));
|
|
ANDNPS(XMM0, R(tempxregs[i]));
|
|
ORPS(XMM0, R(XMM1));
|
|
|
|
// Retain a NAN in XMM0 (must be second operand.)
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(tempxregs[i], MatR(TEMPREG));
|
|
MINSS(tempxregs[i], R(XMM0));
|
|
break;
|
|
case 16: // d[i] = 1.0f / s[i]; break; //vrcp
|
|
if (RipAccessible(&one)) {
|
|
MOVSS(XMM0, M(&one)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM0, MatR(TEMPREG));
|
|
}
|
|
DIVSS(XMM0, fpr.V(sregs[i]));
|
|
MOVSS(tempxregs[i], R(XMM0));
|
|
break;
|
|
case 17: // d[i] = 1.0f / sqrtf(s[i]); break; //vrsq
|
|
SQRTSS(XMM0, fpr.V(sregs[i]));
|
|
if (RipAccessible(&one)) {
|
|
MOVSS(tempxregs[i], M(&one)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(tempxregs[i], MatR(TEMPREG));
|
|
}
|
|
DIVSS(tempxregs[i], R(XMM0));
|
|
break;
|
|
case 18: // d[i] = sinf((float)M_PI_2 * s[i]); break; //vsin
|
|
specialFuncCallHelper(&SinOnly, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
case 19: // d[i] = cosf((float)M_PI_2 * s[i]); break; //vcos
|
|
specialFuncCallHelper(&CosOnly, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[1]));
|
|
break;
|
|
case 20: // d[i] = powf(2.0f, s[i]); break; //vexp2
|
|
specialFuncCallHelper(&Exp2, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
case 21: // d[i] = logf(s[i])/log(2.0f); break; //vlog2
|
|
specialFuncCallHelper(&Log2, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
case 22: // d[i] = sqrtf(s[i]); break; //vsqrt
|
|
SQRTSS(tempxregs[i], fpr.V(sregs[i]));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&noSignMask));
|
|
ANDPS(tempxregs[i], MatR(TEMPREG));
|
|
break;
|
|
case 23: // d[i] = asinf(s[i]) / M_PI_2; break; //vasin
|
|
specialFuncCallHelper(&ASinScaled, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
case 24: // d[i] = -1.0f / s[i]; break; // vnrcp
|
|
// Rare so let's not bother checking for RipAccessible.
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&minus_one));
|
|
MOVSS(XMM0, MatR(TEMPREG));
|
|
DIVSS(XMM0, fpr.V(sregs[i]));
|
|
MOVSS(tempxregs[i], R(XMM0));
|
|
break;
|
|
case 26: // d[i] = -sinf((float)M_PI_2 * s[i]); break; // vnsin
|
|
specialFuncCallHelper(&NegSinOnly, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
case 28: // d[i] = 1.0f / expf(s[i] * (float)M_LOG2E); break; // vrexp2
|
|
specialFuncCallHelper(&RExp2, sregs[i]);
|
|
MOVSS(tempxregs[i], MIPSSTATE_VAR(sincostemp[0]));
|
|
break;
|
|
}
|
|
}
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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 != MIPS_REG_ZERO) {
|
|
if (imm < 128) { //R(rt) = VI(imm);
|
|
fpr.SimpleRegV(imm, 0);
|
|
if (fpr.V(imm).IsSimpleReg()) {
|
|
fpr.MapRegV(imm, 0);
|
|
gpr.MapReg(rt, false, true);
|
|
MOVD_xmm(gpr.R(rt), fpr.VX(imm));
|
|
} else {
|
|
// Let's not bother mapping the vreg.
|
|
gpr.MapReg(rt, false, true);
|
|
MOV(32, gpr.R(rt), fpr.V(imm));
|
|
}
|
|
} else if (imm < 128 + VFPU_CTRL_MAX) { //mfvc
|
|
if (imm - 128 == VFPU_CTRL_CC) {
|
|
if (gpr.IsImm(MIPS_REG_VFPUCC)) {
|
|
gpr.SetImm(rt, gpr.GetImm(MIPS_REG_VFPUCC));
|
|
} else {
|
|
gpr.Lock(rt, MIPS_REG_VFPUCC);
|
|
gpr.MapReg(rt, false, true);
|
|
gpr.MapReg(MIPS_REG_VFPUCC, true, false);
|
|
MOV(32, gpr.R(rt), gpr.R(MIPS_REG_VFPUCC));
|
|
gpr.UnlockAll();
|
|
}
|
|
} else {
|
|
// In case we have a saved prefix.
|
|
FlushPrefixV();
|
|
gpr.MapReg(rt, false, true);
|
|
MOV(32, gpr.R(rt), MIPSSTATE_VAR_ELEM32(vfpuCtrl[0], imm - 128));
|
|
}
|
|
} else {
|
|
//ERROR - maybe need to make this value too an "interlock" value?
|
|
_dbg_assert_msg_(false,"mfv - invalid register");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 7: //mtv
|
|
if (imm < 128) { // VI(imm) = R(rt);
|
|
fpr.MapRegV(imm, MAP_DIRTY | MAP_NOINIT);
|
|
// Let's not bother mapping rt if we don't have to.
|
|
if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0) {
|
|
XORPS(fpr.VX(imm), fpr.V(imm));
|
|
} else {
|
|
gpr.KillImmediate(rt, true, false);
|
|
MOVD_xmm(fpr.VX(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.Lock(rt, MIPS_REG_VFPUCC);
|
|
gpr.MapReg(rt, true, false);
|
|
gpr.MapReg(MIPS_REG_VFPUCC, false, true);
|
|
MOV(32, gpr.R(MIPS_REG_VFPUCC), gpr.R(rt));
|
|
gpr.UnlockAll();
|
|
}
|
|
} else {
|
|
gpr.MapReg(rt, true, false);
|
|
MOV(32, MIPSSTATE_VAR_ELEM32(vfpuCtrl[0], imm - 128), gpr.R(rt));
|
|
}
|
|
|
|
// TODO: Optimization if rt is Imm?
|
|
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;
|
|
}
|
|
}
|
|
|
|
void Jit::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, true, false);
|
|
MOVD_xmm(fpr.VX(vd), gpr.R(MIPS_REG_VFPUCC));
|
|
} else {
|
|
// In case we have a saved prefix.
|
|
FlushPrefixV();
|
|
MOVSS(fpr.VX(vd), MIPSSTATE_VAR_ELEM32(vfpuCtrl[0], imm));
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
} else {
|
|
fpr.MapRegV(vd, MAP_DIRTY | MAP_NOINIT);
|
|
XORPS(fpr.VX(vd), fpr.V(vd));
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
}
|
|
|
|
void Jit::Comp_Vmtvc(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
int vs = _VS;
|
|
int imm = op & 0x7F;
|
|
if (imm < VFPU_CTRL_MAX) {
|
|
fpr.MapRegV(vs, 0);
|
|
if (imm == VFPU_CTRL_CC) {
|
|
gpr.MapReg(MIPS_REG_VFPUCC, false, true);
|
|
MOVD_xmm(gpr.R(MIPS_REG_VFPUCC), fpr.VX(vs));
|
|
} else {
|
|
MOVSS(MIPSSTATE_VAR_ELEM32(vfpuCtrl[0], imm), fpr.VX(vs));
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
|
|
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 Jit::Comp_VMatrixInit(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
MatrixSize sz = GetMtxSize(op);
|
|
int n = GetMatrixSide(sz);
|
|
|
|
// Not really about trying here, it will work if enabled.
|
|
if (jo.enableVFPUSIMD) {
|
|
VectorSize vsz = GetVectorSize(sz);
|
|
u8 vecs[4];
|
|
GetMatrixColumns(_VD, sz, vecs);
|
|
switch ((op >> 16) & 0xF) {
|
|
case 3:
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&identityMatrix[0]));
|
|
break;
|
|
case 7:
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
MOVAPS(XMM0, MatR(TEMPREG));
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
u8 vec[4];
|
|
GetVectorRegs(vec, vsz, vecs[i]);
|
|
fpr.MapRegsVS(vec, vsz, MAP_NOINIT | MAP_DIRTY);
|
|
switch ((op >> 16) & 0xF) {
|
|
case 3:
|
|
MOVAPS(fpr.VSX(vec), MDisp(TEMPREG, 16 * i));
|
|
break;
|
|
case 6:
|
|
XORPS(fpr.VSX(vec), fpr.VS(vec));
|
|
break;
|
|
case 7:
|
|
MOVAPS(fpr.VSX(vec), R(XMM0));
|
|
break;
|
|
}
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
u8 dregs[16];
|
|
GetMatrixRegs(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
switch ((op >> 16) & 0xF) {
|
|
case 3: // vmidt
|
|
XORPS(XMM0, R(XMM0));
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM1, MatR(TEMPREG));
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), a == b ? XMM1 : XMM0);
|
|
}
|
|
}
|
|
break;
|
|
case 6: // vmzero
|
|
XORPS(XMM0, R(XMM0));
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), XMM0);
|
|
}
|
|
}
|
|
break;
|
|
case 7: // vmone
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&one));
|
|
MOVSS(XMM0, MatR(TEMPREG));
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), XMM0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Vmmov(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_MTX_VMMOV);
|
|
|
|
// TODO: This probably ignores prefixes?
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
MatrixSize sz = GetMtxSize(op);
|
|
int n = GetMatrixSide(sz);
|
|
|
|
if (jo.enableVFPUSIMD) {
|
|
VectorSize vsz = GetVectorSize(sz);
|
|
u8 dest[4][4];
|
|
MatrixOverlapType overlap = GetMatrixOverlap(_VD, _VS, sz);
|
|
|
|
u8 vecs[4];
|
|
if (overlap == OVERLAP_NONE) {
|
|
GetMatrixColumns(_VD, sz, vecs);
|
|
for (int i = 0; i < n; ++i) {
|
|
GetVectorRegs(dest[i], vsz, vecs[i]);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < n; ++i) {
|
|
fpr.GetTempVS(dest[i], vsz);
|
|
}
|
|
}
|
|
|
|
GetMatrixColumns(_VS, sz, vecs);
|
|
for (int i = 0; i < n; i++) {
|
|
u8 vec[4];
|
|
GetVectorRegs(vec, vsz, vecs[i]);
|
|
fpr.MapRegsVS(vec, vsz, 0);
|
|
fpr.MapRegsVS(dest[i], vsz, MAP_NOINIT);
|
|
MOVAPS(fpr.VSX(dest[i]), fpr.VS(vec));
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
if (overlap != OVERLAP_NONE) {
|
|
// Okay, move from the temps to VD now.
|
|
GetMatrixColumns(_VD, sz, vecs);
|
|
for (int i = 0; i < n; i++) {
|
|
u8 vec[4];
|
|
GetVectorRegs(vec, vsz, vecs[i]);
|
|
fpr.MapRegsVS(vec, vsz, MAP_NOINIT);
|
|
fpr.MapRegsVS(dest[i], vsz, 0);
|
|
MOVAPS(fpr.VSX(vec), fpr.VS(dest[i]));
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
u8 sregs[16], dregs[16];
|
|
GetMatrixRegs(sregs, sz, _VS);
|
|
GetMatrixRegs(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// TODO: gas doesn't allow overlap, what does the PSP do?
|
|
// Potentially detect overlap or the safe direction to move in, or just DISABLE?
|
|
// This is very not optimal, blows the regcache everytime.
|
|
u8 tempregs[16];
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
u8 temp = (u8) fpr.GetTempV();
|
|
fpr.MapRegV(temp, MAP_NOINIT | MAP_DIRTY);
|
|
MOVSS(fpr.VX(temp), fpr.V(sregs[a * 4 + b]));
|
|
fpr.StoreFromRegisterV(temp);
|
|
tempregs[a * 4 + b] = temp;
|
|
}
|
|
}
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
u8 temp = tempregs[a * 4 + b];
|
|
fpr.MapRegV(temp, 0);
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), fpr.VX(temp));
|
|
}
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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], scale;
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixT(&scale, V_Single, _VT);
|
|
GetVectorRegsPrefixD(dregs, sz, _VD);
|
|
|
|
if (fpr.TryMapDirtyInInVS(dregs, sz, sregs, sz, &scale, V_Single, true)) {
|
|
MOVSS(XMM0, fpr.VS(&scale));
|
|
if (sz != V_Single)
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0));
|
|
if (dregs[0] != sregs[0]) {
|
|
MOVAPS(fpr.VSX(dregs), fpr.VS(sregs));
|
|
}
|
|
MULPS(fpr.VSX(dregs), R(XMM0));
|
|
ApplyPrefixD(dregs, sz);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(&scale, V_Single, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// Move to XMM0 early, so we don't have to worry about overlap with scale.
|
|
MOVSS(XMM0, fpr.V(scale));
|
|
|
|
X64Reg tempxregs[4];
|
|
for (int i = 0; i < n; ++i) {
|
|
if (dregs[i] != scale || !IsOverlapSafeAllowS(dregs[i], i, n, sregs)) {
|
|
int reg = fpr.GetTempV();
|
|
fpr.MapRegV(reg, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SpillLockV(reg);
|
|
tempxregs[i] = fpr.VX(reg);
|
|
} else {
|
|
fpr.MapRegV(dregs[i], dregs[i] == sregs[i] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[i]);
|
|
tempxregs[i] = fpr.VX(dregs[i]);
|
|
}
|
|
}
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!fpr.V(sregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(tempxregs[i], fpr.V(sregs[i]));
|
|
MULSS(tempxregs[i], R(XMM0));
|
|
}
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!fpr.V(dregs[i]).IsSimpleReg(tempxregs[i]))
|
|
MOVSS(fpr.V(dregs[i]), tempxregs[i]);
|
|
}
|
|
ApplyPrefixD(dregs, sz);
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::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);
|
|
VectorSize vsz = GetVectorSize(sz);
|
|
int n = GetMatrixSide(sz);
|
|
|
|
MatrixOverlapType soverlap = GetMatrixOverlap(_VS, _VD, sz);
|
|
MatrixOverlapType toverlap = GetMatrixOverlap(_VT, _VD, sz);
|
|
// If these overlap, we won't be able to map T as singles.
|
|
MatrixOverlapType stoverlap = GetMatrixOverlap(_VS, _VT, sz);
|
|
|
|
if (jo.enableVFPUSIMD && !soverlap && !toverlap && !stoverlap) {
|
|
u8 scols[4], dcols[4], tregs[16];
|
|
|
|
int vs = _VS;
|
|
int vd = _VD;
|
|
int vt = _VT;
|
|
|
|
bool transposeDest = false;
|
|
bool transposeS = false;
|
|
|
|
if ((vd & 0x20) && sz == M_4x4) {
|
|
vd ^= 0x20;
|
|
transposeDest = true;
|
|
}
|
|
|
|
// Our algorithm needs a transposed S (which is the usual).
|
|
if (!(vs & 0x20) && sz == M_4x4) {
|
|
vs ^= 0x20;
|
|
transposeS = true;
|
|
}
|
|
|
|
// The T matrix we will address individually.
|
|
GetMatrixColumns(vd, sz, dcols);
|
|
GetMatrixRows(vs, sz, scols);
|
|
memset(tregs, 255, sizeof(tregs));
|
|
GetMatrixRegs(tregs, sz, vt);
|
|
for (int i = 0; i < 16; i++) {
|
|
if (tregs[i] != 255)
|
|
fpr.StoreFromRegisterV(tregs[i]);
|
|
}
|
|
|
|
u8 scol[4][4];
|
|
|
|
// Map all of S's columns into registers.
|
|
for (int i = 0; i < n; i++) {
|
|
if (transposeS){
|
|
fpr.StoreFromRegisterV(scols[i]);
|
|
}
|
|
GetVectorRegs(scol[i], vsz, scols[i]);
|
|
fpr.MapRegsVS(scol[i], vsz, 0);
|
|
fpr.SpillLockV(scols[i], vsz);
|
|
}
|
|
|
|
// Shorter than manually stuffing the registers. But it feels like ther'es room for optimization here...
|
|
auto transposeInPlace = [=](u8 col[4][4]) {
|
|
MOVAPS(XMM0, fpr.VS(col[0]));
|
|
UNPCKLPS(fpr.VSX(col[0]), fpr.VS(col[2]));
|
|
UNPCKHPS(XMM0, fpr.VS(col[2]));
|
|
|
|
MOVAPS(fpr.VSX(col[2]), fpr.VS(col[1]));
|
|
UNPCKLPS(fpr.VSX(col[1]), fpr.VS(col[3]));
|
|
UNPCKHPS(fpr.VSX(col[2]), fpr.VS(col[3]));
|
|
|
|
MOVAPS(fpr.VSX(col[3]), fpr.VS(col[0]));
|
|
UNPCKLPS(fpr.VSX(col[0]), fpr.VS(col[1]));
|
|
UNPCKHPS(fpr.VSX(col[3]), fpr.VS(col[1]));
|
|
|
|
MOVAPS(fpr.VSX(col[1]), R(XMM0));
|
|
UNPCKLPS(fpr.VSX(col[1]), fpr.VS(col[2]));
|
|
UNPCKHPS(XMM0, fpr.VS(col[2]));
|
|
|
|
MOVAPS(fpr.VSX(col[2]), fpr.VS(col[1]));
|
|
MOVAPS(fpr.VSX(col[1]), fpr.VS(col[3]));
|
|
MOVAPS(fpr.VSX(col[3]), R(XMM0));
|
|
};
|
|
|
|
// Some games pass in S as an E matrix (transposed). Let's just transpose the data before we do the multiplication instead.
|
|
// This is shorter than trying to combine a discontinous matrix with lots of shufps.
|
|
if (transposeS) {
|
|
transposeInPlace(scol);
|
|
}
|
|
|
|
// Now, work our way through the matrix, loading things as we go.
|
|
// TODO: With more temp registers, can generate much more efficient code.
|
|
for (int i = 0; i < n; i++) {
|
|
MOVSS(XMM1, fpr.V(tregs[4 * i])); // TODO: AVX broadcastss to replace this and the SHUFPS
|
|
MOVSS(XMM0, fpr.V(tregs[4 * i + 1]));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(0, 0, 0, 0));
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0));
|
|
MULPS(XMM1, fpr.VS(scol[0]));
|
|
MULPS(XMM0, fpr.VS(scol[1]));
|
|
ADDPS(XMM1, R(XMM0));
|
|
for (int j = 2; j < n; j++) {
|
|
MOVSS(XMM0, fpr.V(tregs[4 * i + j]));
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0));
|
|
MULPS(XMM0, fpr.VS(scol[j]));
|
|
ADDPS(XMM1, R(XMM0));
|
|
}
|
|
// Map the D column.
|
|
u8 dcol[4];
|
|
GetVectorRegs(dcol, vsz, dcols[i]);
|
|
#if !PPSSPP_ARCH(AMD64)
|
|
fpr.MapRegsVS(dcol, vsz, MAP_DIRTY | MAP_NOINIT | MAP_NOLOCK);
|
|
#else
|
|
fpr.MapRegsVS(dcol, vsz, MAP_DIRTY | MAP_NOINIT);
|
|
#endif
|
|
MOVAPS(fpr.VS(dcol), XMM1);
|
|
}
|
|
if (transposeS){
|
|
for (int i = 0; i < n; i++){
|
|
fpr.DiscardVS(scols[i]);
|
|
}
|
|
}
|
|
|
|
#if !PPSSPP_ARCH(AMD64)
|
|
fpr.ReleaseSpillLocks();
|
|
#endif
|
|
if (transposeDest) {
|
|
u8 dcol[4][4];
|
|
for (int i = 0; i < n; i++) {
|
|
GetVectorRegs(dcol[i], vsz, dcols[i]);
|
|
fpr.MapRegsVS(dcol[i], vsz, MAP_DIRTY);
|
|
}
|
|
transposeInPlace(dcol);
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
u8 sregs[16], tregs[16], dregs[16];
|
|
GetMatrixRegs(sregs, sz, _VS);
|
|
GetMatrixRegs(tregs, sz, _VT);
|
|
GetMatrixRegs(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// 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) {
|
|
u8 tempregs[16];
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
MOVSS(XMM0, fpr.V(sregs[b * 4]));
|
|
MULSS(XMM0, fpr.V(tregs[a * 4]));
|
|
for (int c = 1; c < n; c++) {
|
|
MOVSS(XMM1, fpr.V(sregs[b * 4 + c]));
|
|
MULSS(XMM1, fpr.V(tregs[a * 4 + c]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
}
|
|
u8 temp = (u8) fpr.GetTempV();
|
|
fpr.MapRegV(temp, MAP_NOINIT | MAP_DIRTY);
|
|
MOVSS(fpr.VX(temp), R(XMM0));
|
|
fpr.StoreFromRegisterV(temp);
|
|
tempregs[a * 4 + b] = temp;
|
|
}
|
|
}
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
u8 temp = tempregs[a * 4 + b];
|
|
fpr.MapRegV(temp, 0);
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), fpr.VX(temp));
|
|
}
|
|
}
|
|
} else {
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
MOVSS(XMM0, fpr.V(sregs[b * 4]));
|
|
MULSS(XMM0, fpr.V(tregs[a * 4]));
|
|
for (int c = 1; c < n; c++) {
|
|
MOVSS(XMM1, fpr.V(sregs[b * 4 + c]));
|
|
MULSS(XMM1, fpr.V(tregs[a * 4 + c]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
}
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), XMM0);
|
|
}
|
|
}
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Vmscl(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_MTX_VMSCL);
|
|
|
|
// TODO: This op probably ignores prefixes?
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
MatrixSize sz = GetMtxSize(op);
|
|
int n = GetMatrixSide(sz);
|
|
|
|
u8 sregs[16], dregs[16], scale;
|
|
GetMatrixRegs(sregs, sz, _VS);
|
|
GetVectorRegs(&scale, V_Single, _VT);
|
|
GetMatrixRegs(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(&scale, V_Single, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// Move to XMM0 early, so we don't have to worry about overlap with scale.
|
|
MOVSS(XMM0, fpr.V(scale));
|
|
|
|
// TODO: test overlap, optimize.
|
|
u8 tempregs[16];
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
u8 temp = (u8) fpr.GetTempV();
|
|
fpr.MapRegV(temp, MAP_NOINIT | MAP_DIRTY);
|
|
MOVSS(fpr.VX(temp), fpr.V(sregs[a * 4 + b]));
|
|
MULSS(fpr.VX(temp), R(XMM0));
|
|
fpr.StoreFromRegisterV(temp);
|
|
tempregs[a * 4 + b] = temp;
|
|
}
|
|
}
|
|
for (int a = 0; a < n; a++) {
|
|
for (int b = 0; b < n; b++) {
|
|
u8 temp = tempregs[a * 4 + b];
|
|
fpr.MapRegV(temp, 0);
|
|
MOVSS(fpr.V(dregs[a * 4 + b]), fpr.VX(temp));
|
|
}
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Vtfm(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_MTX_VTFM);
|
|
|
|
// TODO: This probably ignores prefixes? Or maybe uses D?
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
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;
|
|
}
|
|
|
|
if (jo.enableVFPUSIMD) {
|
|
u8 scols[4], dcol[4], tregs[4];
|
|
|
|
int vs = _VS;
|
|
int vd = _VD;
|
|
int vt = _VT; // vector!
|
|
|
|
// The T matrix we will address individually.
|
|
GetVectorRegs(dcol, sz, vd);
|
|
GetMatrixRows(vs, msz, scols);
|
|
GetVectorRegs(tregs, sz, vt);
|
|
for (int i = 0; i < n; i++) {
|
|
fpr.StoreFromRegisterV(tregs[i]);
|
|
}
|
|
|
|
// We need the T regs in individual regs, but they could overlap with S regs.
|
|
// If that happens, we copy the T reg to a temp.
|
|
auto flushConflictingTRegsToTemps = [&](u8 regs[4]) {
|
|
for (int i = 0; i < n; ++i) {
|
|
for (int j = 0; j < n; ++j) {
|
|
if (regs[i] != tregs[j]) {
|
|
continue;
|
|
}
|
|
|
|
// They match. Let's replace this treg with a temp reg.
|
|
// Note that it will spill if there's contention, unfortunately...
|
|
tregs[j] = fpr.GetTempV();
|
|
fpr.MapRegV(tregs[j], MAP_NOINIT);
|
|
MOVSS(fpr.VX(tregs[j]), fpr.V(regs[i]));
|
|
}
|
|
}
|
|
};
|
|
|
|
u8 scol[4][4];
|
|
|
|
// Map all of S's columns into registers.
|
|
for (int i = 0; i < n; i++) {
|
|
GetVectorRegs(scol[i], sz, scols[i]);
|
|
flushConflictingTRegsToTemps(scol[i]);
|
|
fpr.MapRegsVS(scol[i], sz, 0);
|
|
}
|
|
|
|
// Now, work our way through the matrix, loading things as we go.
|
|
// TODO: With more temp registers, can generate much more efficient code.
|
|
MOVSS(XMM1, fpr.V(tregs[0])); // TODO: AVX broadcastss to replace this and the SHUFPS (but take care of temps, unless we force store them.)
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(0, 0, 0, 0));
|
|
MULPS(XMM1, fpr.VS(scol[0]));
|
|
for (int j = 1; j < n; j++) {
|
|
if (!homogenous || j != n - 1) {
|
|
MOVSS(XMM0, fpr.V(tregs[j]));
|
|
SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0));
|
|
MULPS(XMM0, fpr.VS(scol[j]));
|
|
ADDPS(XMM1, R(XMM0));
|
|
} else {
|
|
ADDPS(XMM1, fpr.VS(scol[j]));
|
|
}
|
|
}
|
|
// Map the D column. Release first in case of overlap.
|
|
for (int i = 0; i < n; i++) {
|
|
fpr.ReleaseSpillLockV(scol[i], sz);
|
|
}
|
|
fpr.MapRegsVS(dcol, sz, MAP_DIRTY | MAP_NOINIT);
|
|
MOVAPS(fpr.VS(dcol), XMM1);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
u8 sregs[16], dregs[4], tregs[4];
|
|
GetMatrixRegs(sregs, msz, _VS);
|
|
GetVectorRegs(tregs, sz, _VT);
|
|
GetVectorRegs(dregs, sz, _VD);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, msz, 0);
|
|
fpr.SimpleRegsV(tregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, sz, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// TODO: test overlap, optimize.
|
|
u8 tempregs[4];
|
|
for (int i = 0; i < n; i++) {
|
|
MOVSS(XMM0, fpr.V(sregs[i * 4]));
|
|
MULSS(XMM0, fpr.V(tregs[0]));
|
|
for (int k = 1; k < n; k++)
|
|
{
|
|
MOVSS(XMM1, fpr.V(sregs[i * 4 + k]));
|
|
if (!homogenous || k != n - 1)
|
|
MULSS(XMM1, fpr.V(tregs[k]));
|
|
ADDSS(XMM0, R(XMM1));
|
|
}
|
|
|
|
u8 temp = (u8) fpr.GetTempV();
|
|
fpr.MapRegV(temp, MAP_NOINIT | MAP_DIRTY);
|
|
MOVSS(fpr.VX(temp), R(XMM0));
|
|
fpr.StoreFromRegisterV(temp);
|
|
tempregs[i] = temp;
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
u8 temp = tempregs[i];
|
|
fpr.MapRegV(temp, 0);
|
|
MOVSS(fpr.V(dregs[i]), fpr.VX(temp));
|
|
}
|
|
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_VCrs(MIPSOpcode op) {
|
|
DISABLE;
|
|
}
|
|
|
|
void Jit::Comp_VDet(MIPSOpcode op) {
|
|
DISABLE;
|
|
}
|
|
|
|
// The goal is to map (reversed byte order for clarity):
|
|
// 000000AA 000000BB 000000CC 000000DD -> AABBCCDD
|
|
alignas(16) static const s8 vi2xc_shuffle[16] = { 3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
|
// 0000AAAA 0000BBBB 0000CCCC 0000DDDD -> AAAABBBB CCCCDDDD
|
|
alignas(16) static const s8 vi2xs_shuffle[16] = { 2, 3, 6, 7, 10, 11, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1 };
|
|
|
|
void Jit::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)
|
|
|
|
// 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);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, outsize, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
// First, let's assemble the sregs into lanes of a single xmm reg.
|
|
// For quad inputs, we need somewhere for the bottom regs. Ideally dregs[0].
|
|
X64Reg dst0 = XMM0;
|
|
if (sz == V_Quad) {
|
|
int vreg = dregs[0];
|
|
if (!IsOverlapSafeAllowS(dregs[0], 0, 4, sregs)) {
|
|
// Will be discarded on release.
|
|
vreg = fpr.GetTempV();
|
|
}
|
|
fpr.MapRegV(vreg, vreg == sregs[0] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(vreg);
|
|
dst0 = fpr.VX(vreg);
|
|
} else {
|
|
// Pair, let's check if we should use dregs[0] directly. No temp needed.
|
|
int vreg = dregs[0];
|
|
if (IsOverlapSafeAllowS(dregs[0], 0, 2, sregs)) {
|
|
fpr.MapRegV(vreg, vreg == sregs[0] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(vreg);
|
|
dst0 = fpr.VX(vreg);
|
|
}
|
|
}
|
|
|
|
if (!fpr.V(sregs[0]).IsSimpleReg(dst0)) {
|
|
MOVSS(dst0, fpr.V(sregs[0]));
|
|
}
|
|
MOVSS(XMM1, fpr.V(sregs[1]));
|
|
// With this, we have the lower half in dst0.
|
|
PUNPCKLDQ(dst0, R(XMM1));
|
|
if (sz == V_Quad) {
|
|
MOVSS(XMM0, fpr.V(sregs[2]));
|
|
MOVSS(XMM1, fpr.V(sregs[3]));
|
|
PUNPCKLDQ(XMM0, R(XMM1));
|
|
// Now we need to combine XMM0 into dst0.
|
|
PUNPCKLQDQ(dst0, R(XMM0));
|
|
} else {
|
|
// Otherwise, we need to zero out the top 2.
|
|
// We expect XMM1 to be zero below.
|
|
PXOR(XMM1, R(XMM1));
|
|
PUNPCKLQDQ(dst0, R(XMM1));
|
|
}
|
|
|
|
// For "u" type ops, we clamp to zero and shift off the sign bit first.
|
|
if (unsignedOp) {
|
|
if (cpu_info.bSSE4_1) {
|
|
if (sz == V_Quad) {
|
|
// Zeroed in the other case above.
|
|
PXOR(XMM1, R(XMM1));
|
|
}
|
|
PMAXSD(dst0, R(XMM1));
|
|
PSLLD(dst0, 1);
|
|
} else {
|
|
// Get a mask of the sign bit in dst0, then and in the values. This clamps to 0.
|
|
MOVDQA(XMM1, R(dst0));
|
|
PSRAD(dst0, 31);
|
|
PSLLD(XMM1, 1);
|
|
PANDN(dst0, R(XMM1));
|
|
}
|
|
}
|
|
|
|
// At this point, everything is aligned in the high bits of our lanes.
|
|
if (cpu_info.bSSSE3) {
|
|
if (RipAccessible(vi2xc_shuffle)) {
|
|
PSHUFB(dst0, bits == 8 ? M(vi2xc_shuffle) : M(vi2xs_shuffle)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), bits == 8 ? ImmPtr(vi2xc_shuffle) : ImmPtr(vi2xs_shuffle));
|
|
PSHUFB(dst0, MatR(TEMPREG));
|
|
}
|
|
} else {
|
|
// Let's *arithmetically* shift in the sign so we can use saturating packs.
|
|
PSRAD(dst0, 32 - bits);
|
|
// XMM1 used for the high part just so there's no dependency. It contains garbage or 0.
|
|
PACKSSDW(dst0, R(XMM1));
|
|
if (bits == 8) {
|
|
PACKSSWB(dst0, R(XMM1));
|
|
}
|
|
}
|
|
|
|
if (!fpr.V(dregs[0]).IsSimpleReg(dst0)) {
|
|
MOVSS(fpr.V(dregs[0]), dst0);
|
|
}
|
|
if (outsize == V_Pair) {
|
|
fpr.MapRegV(dregs[1], MAP_NOINIT | MAP_DIRTY);
|
|
MOVDQA(fpr.V(dregs[1]), dst0);
|
|
// Shift out the lower result to get the result we want.
|
|
PSRLDQ(fpr.VX(dregs[1]), 4);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, outsize);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
alignas(16) static const float vavg_table[4] = { 1.0f, 1.0f / 2.0f, 1.0f / 3.0f, 1.0f / 4.0f };
|
|
|
|
void Jit::Comp_Vhoriz(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
VectorSize sz = GetVecSize(op);
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
u8 sregs[4], dregs[1];
|
|
GetVectorRegsPrefixS(sregs, sz, _VS);
|
|
GetVectorRegsPrefixD(dregs, V_Single, _VD);
|
|
if (fpr.TryMapDirtyInVS(dregs, V_Single, sregs, sz)) {
|
|
if (cpu_info.bSSE4_1) {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&oneOneOneOne));
|
|
switch (sz) {
|
|
case V_Pair:
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, MatR(TEMPREG), 0x31);
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
case V_Triple:
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, MatR(TEMPREG), 0x71);
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
case V_Quad:
|
|
XORPS(XMM1, R(XMM1));
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
DPPS(XMM0, MatR(TEMPREG), 0xF1);
|
|
// In every other case, +0.0 is selected by the mask and added.
|
|
// But, here we need to manually add it to the result.
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
} else {
|
|
switch (sz) {
|
|
case V_Pair:
|
|
XORPS(XMM1, R(XMM1));
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
ADDPS(XMM1, R(XMM0));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 2, 1, 1));
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
case V_Triple:
|
|
XORPS(XMM1, R(XMM1));
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
ADDPS(XMM1, R(XMM0));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 2, 1, 1));
|
|
ADDPS(XMM0, R(XMM1));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(3, 2, 1, 2));
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
case V_Quad:
|
|
XORPS(XMM1, R(XMM1));
|
|
MOVAPS(XMM0, fpr.VS(sregs));
|
|
// This flips the sign of any -0.000.
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVHLPS(XMM1, XMM0);
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(XMM1, R(XMM0));
|
|
SHUFPS(XMM1, R(XMM1), _MM_SHUFFLE(1, 1, 1, 1));
|
|
ADDPS(XMM0, R(XMM1));
|
|
MOVAPS(fpr.VSX(dregs), R(XMM0));
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
}
|
|
if (((op >> 16) & 31) == 7) { // vavg
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&vavg_table[n - 1]));
|
|
MULSS(fpr.VSX(dregs), MatR(TEMPREG));
|
|
}
|
|
ApplyPrefixD(dregs, V_Single);
|
|
fpr.ReleaseSpillLocks();
|
|
return;
|
|
}
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(sregs, sz, 0);
|
|
fpr.SimpleRegsV(dregs, V_Single, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
X64Reg reg = XMM0;
|
|
if (IsOverlapSafe(dregs[0], 0, n, sregs)) {
|
|
fpr.MapRegV(dregs[0], dregs[0] == sregs[0] ? MAP_DIRTY : MAP_NOINIT);
|
|
fpr.SpillLockV(dregs[0]);
|
|
reg = fpr.VX(dregs[0]);
|
|
}
|
|
|
|
// We have to start zt +0.000 in case any values are -0.000.
|
|
XORPS(reg, R(reg));
|
|
for (int i = 0; i < n; ++i) {
|
|
ADDSS(reg, fpr.V(sregs[i]));
|
|
}
|
|
|
|
switch ((op >> 16) & 31) {
|
|
case 6: // vfad
|
|
break;
|
|
case 7: // vavg
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&vavg_table[n - 1]));
|
|
MULSS(reg, MatR(TEMPREG));
|
|
break;
|
|
}
|
|
|
|
if (reg == XMM0) {
|
|
MOVSS(fpr.V(dregs[0]), XMM0);
|
|
}
|
|
|
|
ApplyPrefixD(dregs, V_Single);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Viim(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
u8 dreg;
|
|
GetVectorRegs(&dreg, V_Single, _VT);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(&dreg, V_Single, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
s32 imm = SignExtend16ToS32(op);
|
|
FP32 fp;
|
|
fp.f = (float)imm;
|
|
MOV(32, R(TEMPREG), Imm32(fp.u));
|
|
fpr.MapRegV(dreg, MAP_DIRTY | MAP_NOINIT);
|
|
MOVD_xmm(fpr.VX(dreg), R(TEMPREG));
|
|
|
|
ApplyPrefixD(&dreg, V_Single);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_Vfim(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_XFER);
|
|
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
u8 dreg;
|
|
GetVectorRegs(&dreg, V_Single, _VT);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(&dreg, V_Single, MAP_NOINIT | MAP_DIRTY);
|
|
|
|
FP16 half;
|
|
half.u = op & 0xFFFF;
|
|
FP32 fval = half_to_float_fast5(half);
|
|
MOV(32, R(TEMPREG), Imm32(fval.u));
|
|
fpr.MapRegV(dreg, MAP_DIRTY | MAP_NOINIT);
|
|
MOVD_xmm(fpr.VX(dreg), R(TEMPREG));
|
|
|
|
ApplyPrefixD(&dreg, V_Single);
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::CompVrotShuffle(u8 *dregs, int imm, int n, bool negSin) {
|
|
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';
|
|
|
|
// TODO: shufps SIMD version
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
fpr.MapRegV(dregs[i], MAP_DIRTY | MAP_NOINIT);
|
|
switch (what[i]) {
|
|
case 'C': MOVSS(fpr.V(dregs[i]), XMM1); break;
|
|
case 'S':
|
|
MOVSS(fpr.V(dregs[i]), XMM0);
|
|
if (negSin) {
|
|
if (RipAccessible(&signBitLower)) {
|
|
XORPS(fpr.VX(dregs[i]), M(&signBitLower)); // rip accessible
|
|
} else {
|
|
MOV(PTRBITS, R(TEMPREG), ImmPtr(&signBitLower));
|
|
XORPS(fpr.VX(dregs[i]), MatR(TEMPREG));
|
|
}
|
|
}
|
|
break;
|
|
case '0':
|
|
{
|
|
XORPS(fpr.VX(dregs[i]), fpr.V(dregs[i]));
|
|
break;
|
|
}
|
|
default:
|
|
ERROR_LOG(Log::JIT, "Bad what in vrot");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Very heavily used by FF:CC
|
|
void Jit::Comp_VRot(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
if (js.HasUnknownPrefix()) {
|
|
DISABLE;
|
|
}
|
|
if (!js.HasNoPrefix()) {
|
|
// Prefixes work strangely for this, see IRCompVFPU.
|
|
WARN_LOG_REPORT(Log::JIT, "vrot instruction using prefixes at %08x", GetCompilerPC());
|
|
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 with the same angle argument. Let's join them (can share sin/cos results).
|
|
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);
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(&sreg, V_Single, 0);
|
|
|
|
int imm = (op >> 16) & 0x1f;
|
|
|
|
gpr.FlushBeforeCall();
|
|
fpr.Flush();
|
|
|
|
bool negSin1 = (imm & 0x10) ? true : false;
|
|
|
|
#if PPSSPP_ARCH(AMD64)
|
|
#ifdef _WIN32
|
|
LEA(64, RDX, MIPSSTATE_VAR(sincostemp));
|
|
#else
|
|
LEA(64, RDI, MIPSSTATE_VAR(sincostemp));
|
|
#endif
|
|
MOVSS(XMM0, fpr.V(sreg));
|
|
ABI_CallFunction(negSin1 ? (const void *)&SinCosNegSin : (const void *)&SinCos);
|
|
#else
|
|
// Sigh, passing floats with cdecl isn't pretty, ends up on the stack.
|
|
ABI_CallFunctionAC(negSin1 ? (const void *)&SinCosNegSin : (const void *)&SinCos, fpr.V(sreg), (uintptr_t)mips_->sincostemp);
|
|
#endif
|
|
|
|
MOVSS(XMM0, MIPSSTATE_VAR(sincostemp[0]));
|
|
MOVSS(XMM1, MIPSSTATE_VAR(sincostemp[1]));
|
|
|
|
CompVrotShuffle(dregs, imm, n, 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, n, negSin1 != negSin2);
|
|
EatInstruction(nextOp);
|
|
}
|
|
fpr.ReleaseSpillLocks();
|
|
}
|
|
|
|
void Jit::Comp_ColorConv(MIPSOpcode op) {
|
|
CONDITIONAL_DISABLE(VFPU_VEC);
|
|
if (js.HasUnknownPrefix())
|
|
DISABLE;
|
|
|
|
int vd = _VD;
|
|
int vs = _VS;
|
|
|
|
DISABLE;
|
|
#if 0
|
|
VectorSize sz = V_Quad;
|
|
int n = GetNumVectorElements(sz);
|
|
|
|
switch ((op >> 16) & 3) {
|
|
case 1:
|
|
break;
|
|
default:
|
|
DISABLE;
|
|
}
|
|
|
|
u8 sregs[4];
|
|
u8 dregs[1];
|
|
// WARNING: Prefixes.
|
|
GetVectorRegs(sregs, sz, vs);
|
|
GetVectorRegs(dregs, V_Pair, vd);
|
|
|
|
if (fpr.TryMapDirtyInVS(dregs, V_Single, sregs, sz)) {
|
|
switch ((op >> 16) & 3) {
|
|
case 1: // 4444
|
|
{
|
|
//int a = ((in >> 24) & 0xFF) >> 4;
|
|
//int b = ((in >> 16) & 0xFF) >> 4;
|
|
//int g = ((in >> 8) & 0xFF) >> 4;
|
|
//int r = ((in)& 0xFF) >> 4;
|
|
//col = (a << 12) | (b << 8) | (g << 4) | (r);
|
|
//PACKUSW
|
|
break;
|
|
}
|
|
case 2: // 5551
|
|
{
|
|
//int a = ((in >> 24) & 0xFF) >> 7;
|
|
//int b = ((in >> 16) & 0xFF) >> 3;
|
|
//int g = ((in >> 8) & 0xFF) >> 3;
|
|
//int r = ((in)& 0xFF) >> 3;
|
|
//col = (a << 15) | (b << 10) | (g << 5) | (r);
|
|
break;
|
|
}
|
|
case 3: // 565
|
|
{
|
|
//int b = ((in >> 16) & 0xFF) >> 3;
|
|
//int g = ((in >> 8) & 0xFF) >> 2;
|
|
//int r = ((in)& 0xFF) >> 3;
|
|
//col = (b << 11) | (g << 5) | (r);
|
|
break;
|
|
}
|
|
}
|
|
DISABLE;
|
|
|
|
// Flush SIMD.
|
|
fpr.SimpleRegsV(&sreg, V_Pair, MAP_NOINIT | MAP_DIRTY);
|
|
fpr.SimpleRegsV(&dreg, V_Pair, MAP_NOINIT | MAP_DIRTY);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|