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