diff --git a/CMakeLists.txt b/CMakeLists.txt
index 33958432e9..2c97e3906f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1397,8 +1397,6 @@ if(UNITTEST)
unittest/TestLoongArch64Emitter.cpp
unittest/TestThreadManager.cpp
unittest/JitHarness.cpp
- Core/MIPS/ARM/ArmRegCache.cpp
- Core/MIPS/ARM/ArmRegCacheFPU.cpp
)
if(APPLE)
target_sources(PPSSPPUnitTest PRIVATE
diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 526a1947ea..c31541ba96 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -25,19 +25,6 @@ list(APPEND CoreExtra
)
list(APPEND CoreExtra
- MIPS/ARM/ArmAsm.cpp
- MIPS/ARM/ArmCompALU.cpp
- MIPS/ARM/ArmCompBranch.cpp
- MIPS/ARM/ArmCompFPU.cpp
- MIPS/ARM/ArmCompLoadStore.cpp
- MIPS/ARM/ArmCompVFPU.cpp
- MIPS/ARM/ArmCompReplace.cpp
- MIPS/ARM/ArmJit.cpp
- MIPS/ARM/ArmJit.h
- MIPS/ARM/ArmRegCache.cpp
- MIPS/ARM/ArmRegCache.h
- MIPS/ARM/ArmRegCacheFPU.cpp
- MIPS/ARM/ArmRegCacheFPU.h
MIPS/ARM/ArmIRAsm.cpp
MIPS/ARM/ArmIRCompALU.cpp
MIPS/ARM/ArmIRCompBranch.cpp
diff --git a/Core/Config.cpp b/Core/Config.cpp
index 8d461a4212..33cadb64a9 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -1587,8 +1587,8 @@ bool Config::Save(const char *saveReason) {
// A lot more cleanup tasks should be moved into here, and some of these are severely outdated.
void Config::PostLoadCleanup() {
- // JIT_IR is the same core as JIT now that the legacy x86 and ARM64 JITs are gone, and the
- // settings no longer offer it.
+ // JIT_IR is the same core as JIT now that every native JIT is IR-based, and the settings no
+ // longer offer it.
if (iCpuCore == (int)CPUCore::JIT_IR) {
iCpuCore = (int)CPUCore::JIT;
}
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 46c048b232..163be032c6 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -685,63 +685,6 @@
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
@@ -751,22 +694,6 @@
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
@@ -1089,23 +1016,6 @@
-
- true
- true
- true
- true
- true
- true
-
-
- true
- true
- true
- true
- true
- true
-
-
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 5711fbc2a4..9e17d9263a 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -420,36 +420,9 @@
Ext\Snappy
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
Ext
-
- MIPS\ARM
-
MIPS\ARM
@@ -571,9 +544,6 @@
HLE
-
- MIPS\ARM
-
HW
@@ -1749,15 +1719,6 @@
Ext\Snappy
-
- MIPS\ARM
-
-
- MIPS\ARM
-
-
- MIPS\ARM
-
MIPS\ARM
diff --git a/Core/MIPS/ARM/ArmAsm.cpp b/Core/MIPS/ARM/ArmAsm.cpp
deleted file mode 100644
index a91efc6b26..0000000000
--- a/Core/MIPS/ARM/ArmAsm.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-// 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 "Core/MemMap.h"
-#include "Core/MIPS/MIPS.h"
-#include "Core/Core.h"
-#include "Core/CoreTiming.h"
-#include "Common/MemoryUtil.h"
-#include "Common/CPUDetect.h"
-#include "Common/ArmEmitter.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/JitCommon/JitCommon.h"
-
-using namespace ArmGen;
-
-//static int temp32; // unused?
-
-static const bool enableDebug = false;
-static const bool disasm = false;
-
-//static bool enableStatistics = false; //unused?
-
-//The standard ARM calling convention allocates the 16 ARM registers as:
-
-// r15 is the program counter.
-// r14 is the link register. (The BL instruction, used in a subroutine call, stores the return address in this register).
-// r13 is the stack pointer. (The Push/Pop instructions in "Thumb" operating mode use this register only).
-// r12 is the Intra-Procedure-call scratch register.
-// r4 to r11: used to hold local variables.
-// r0 to r3: used to hold argument values passed to a subroutine, and also hold results returned from a subroutine.
-
-// Mappable registers:
-// R2, R3, R4, R5, R6, R8, R11
-
-// STATIC ALLOCATION ARM:
-// R10 : MIPS state
-// R11 : Memory base pointer.
-// R7 : Down counter
-extern volatile CoreState coreState;
-
-void ShowPC(u32 sp) {
- ERROR_LOG(Log::JIT, "ShowPC : %08x ArmSP : %08x", currentMIPS->pc, sp);
- // Sleep(1);
-}
-
-void DisassembleArm(const u8 *data, int size);
-
-// PLAN: no more block numbers - crazy opcodes just contain offset within
-// dynarec buffer
-// At this offset - 4, there is an int specifying the block number.
-
-namespace MIPSComp {
-
-using namespace ArmJitConstants;
-
-void ArmJit::GenerateFixedCode() {
- BeginWrite(GetMemoryProtectPageSize());
- const u8 *start = AlignCodePage();
-
- // LR == SCRATCHREG2 on ARM32 so it needs to be pushed.
- restoreRoundingMode = AlignCode16(); {
- PUSH(1, R_LR);
- VMRS(SCRATCHREG2);
- // Outside the JIT we run with round-to-nearest and flush0 off.
- BIC(SCRATCHREG2, SCRATCHREG2, AssumeMakeOperand2((3 | 4) << 22));
- VMSR(SCRATCHREG2);
- POP(1, R_PC);
- }
-
- // Must preserve SCRATCHREG1 (R0), destroys SCRATCHREG2 (LR)
- applyRoundingMode = AlignCode16(); {
- PUSH(2, SCRATCHREG1, R_LR);
- LDR(SCRATCHREG2, CTXREG, offsetof(MIPSState, fcr31));
-
- TST(SCRATCHREG2, AssumeMakeOperand2(1 << 24));
- AND(SCRATCHREG2, SCRATCHREG2, Operand2(3));
- SetCC(CC_NEQ);
- ADD(SCRATCHREG2, SCRATCHREG2, Operand2(4));
- SetCC(CC_AL);
-
- // We can skip if the rounding mode is nearest (0) and flush is not set.
- // (as restoreRoundingMode cleared it out anyway)
- CMP(SCRATCHREG2, Operand2(0));
- FixupBranch skip = B_CC(CC_EQ);
-
- // MIPS Rounding Mode: ARM Rounding Mode
- // 0: Round nearest 0
- // 1: Round to zero 3
- // 2: Round up (ceil) 1
- // 3: Round down (floor) 2
- AND(SCRATCHREG1, SCRATCHREG2, Operand2(3));
- CMP(SCRATCHREG1, Operand2(1));
-
- SetCC(CC_EQ); ADD(SCRATCHREG2, SCRATCHREG2, Operand2(2));
- SetCC(CC_GT); SUB(SCRATCHREG2, SCRATCHREG2, Operand2(1));
- SetCC(CC_AL);
-
- VMRS(SCRATCHREG1);
- // Assume we're always in round-to-nearest mode beforehand.
- // But we need to clear flush to zero in this case anyway.
- BIC(SCRATCHREG1, SCRATCHREG1, AssumeMakeOperand2((3 | 4) << 22));
- ORR(SCRATCHREG1, SCRATCHREG1, Operand2(SCRATCHREG2, ST_LSL, 22));
- VMSR(SCRATCHREG1);
-
- SetJumpTarget(skip);
- POP(2, SCRATCHREG1, R_PC);
- }
-
- FlushLitPool();
-
- enterDispatcher = AlignCode16();
-
- DEBUG_LOG(Log::JIT, "Base: %08x", (u32)Memory::base);
-
- SetCC(CC_AL);
-
- PUSH(9, R4, R5, R6, R7, R8, R9, R10, R11, R_LR);
- // Take care to 8-byte align stack for function calls.
- // We are misaligned here because of an odd number of args for PUSH.
- // It's not like x86 where you need to account for an extra 4 bytes
- // consumed by CALL.
- SUB(R_SP, R_SP, 4);
- // Now we are correctly aligned and plan to stay that way.
- VPUSH(D8, 8);
-
- // Fixed registers, these are always kept when in Jit context.
- // R8 is used to hold flags during delay slots. Not always needed.
- // R13 cannot be used as it's the stack pointer.
- // TODO: Consider statically allocating:
- // * r2-r4
- // Really starting to run low on registers already though...
-
- // R11, R10, R9
- MOVP2R(MEMBASEREG, Memory::base);
- MOVP2R(CTXREG, mips_);
- MOVP2R(JITBASEREG, GetBasePtr());
-
- RestoreDowncount();
- MovFromPC(R0);
- outerLoopPCInR0 = GetCodePtr();
- MovToPC(R0);
- outerLoop = GetCodePtr();
- SaveDowncount();
- RestoreRoundingMode(true);
- QuickCallFunctionR(R1, &CoreTiming::Advance, CTXREG);
- ApplyRoundingMode(true);
- RestoreDowncount();
- FixupBranch skipToCoreStateCheck = B(); //skip the downcount check
-
- dispatcherCheckCoreState = GetCodePtr();
-
- // The result of slice decrementation should be in flags if somebody jumped here
- // IMPORTANT - We jump on negative, not carry!!!
- FixupBranch bailCoreState = B_CC(CC_MI);
-
- SetJumpTarget(skipToCoreStateCheck);
-
- MOVI2R(R0, (u32)(uintptr_t)&coreState);
- LDR(R0, R0);
- CMP(R0, 0);
- FixupBranch badCoreState = B_CC(CC_NEQ);
- FixupBranch skipToRealDispatch2 = B(); //skip the sync and compare first time
-
- dispatcherPCInR0 = GetCodePtr();
- // TODO: Do we always need to write PC to RAM here?
- MovToPC(R0);
-
- // At this point : flags = EQ. Fine for the next check, no need to jump over it.
- dispatcher = GetCodePtr();
-
- // The result of slice decrementation should be in flags if somebody jumped here
- // IMPORTANT - We jump on negative, not carry!!!
- FixupBranch bail = B_CC(CC_MI);
-
- SetJumpTarget(skipToRealDispatch2);
-
- dispatcherNoCheck = GetCodePtr();
-
- // Debug
- if (enableDebug) {
- MOV(R0, R13);
- QuickCallFunction(R1, (void *)&ShowPC);
- }
-
- LDR(R0, CTXREG, offsetof(MIPSState, pc));
- static_assert(Memory::BLOCK_SHADOW_ENTRY_MASK == 0x0FFFFFFC, "BICs below assume the 32-bit shadow mask");
- BIC(R0, R0, Operand2(0xF0, 4)); // &= 0x0FFFFFFF. Rotation is to the right, in 2-bit increments.
- BIC(R0, R0, Operand2(3));
- MOVI2R(R1, (u32)(uintptr_t)Memory::blockShadow);
- dispatcherFetch = GetCodePtr();
- LDR(R0, R1, R0);
- // The entry is the block's offset into the code space, or 0 for none.
- CMP(R0, Operand2(0));
- SetCC(CC_NEQ);
-#if PPSSPP_PLATFORM(IOS)
- // On iOS, R9 (JITBASEREG) is volatile. We have to reload it.
- MOVI2R(JITBASEREG, (u32)(uintptr_t)GetBasePtr());
-#endif
- ADD(R0, R0, JITBASEREG);
- B(R0);
- SetCC(CC_AL);
-
- // No block found, let's jit
- // A replacement hook's block shadow entry jumps here too (GetBlockShadowHookValue()). JitAt
- // replaces it with the new block's entry, and destroying that block puts the hook value back.
- blockShadowHook = GetCodePtr();
- SaveDowncount();
- RestoreRoundingMode(true);
- QuickCallFunctionR(R2, (void *)&MIPSComp::JitAt, CTXREG);
- ApplyRoundingMode(true);
- RestoreDowncount();
-
- B(dispatcherNoCheck); // no point in special casing this
-
- SetJumpTarget(bail);
- SetJumpTarget(bailCoreState);
-
- MOVI2R(R0, (u32)(uintptr_t)&coreState);
- LDR(R0, R0);
- CMP(R0, 0);
- B_CC(CC_EQ, outerLoop);
-
- const uint8_t *quitLoop = GetCodePtr();
- SetJumpTarget(badCoreState);
-
- SaveDowncount();
- RestoreRoundingMode(true);
-
- VPOP(D8, 8);
-
- ADD(R_SP, R_SP, 4);
-
- POP(9, R4, R5, R6, R7, R8, R9, R10, R11, R_PC); // Returns
-
- crashHandler = GetCodePtr();
- MOVP2R(R0, &coreState);
- MOVI2R(R1, CORE_RUNTIME_ERROR);
- STR(R1, R0, 0);
- B(quitLoop);
-
- // Uncomment if you want to see the output...
- if (disasm) {
- INFO_LOG(Log::JIT, "THE DISASM ========================");
- DisassembleArm(start, GetCodePtr() - start);
- INFO_LOG(Log::JIT, "END OF THE DISASM ========================");
- }
-
- // Don't forget to zap the instruction cache!
- FlushLitPool();
- FlushIcache();
-
- // Let's spare the pre-generated code from unprotect-reprotect.
- AlignCodePage();
- EndWrite();
-}
-
-} // namespace MIPSComp
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompALU.cpp b/Core/MIPS/ARM/ArmCompALU.cpp
deleted file mode 100644
index 44d22199bb..0000000000
--- a/Core/MIPS/ARM/ArmCompALU.cpp
+++ /dev/null
@@ -1,898 +0,0 @@
-// 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
-
-#include "Common/BitSet.h"
-#include "Common/CPUDetect.h"
-#include "Common/Data/Convert/SmallDataConvert.h"
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/MIPS/MIPSCodeUtils.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-
-using namespace MIPSAnalyst;
-
-#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)
-
-// 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(flag) { Comp_Generic(op); return; }
-#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
-#define DISABLE { Comp_Generic(op); return; }
-
-namespace MIPSComp
-{
- using namespace ArmGen;
- using namespace ArmJitConstants;
-
- static u32 EvalOr(u32 a, u32 b) { return a | b; }
- static u32 EvalEor(u32 a, u32 b) { return a ^ b; }
- static u32 EvalAnd(u32 a, u32 b) { return a & b; }
- static u32 EvalAdd(u32 a, u32 b) { return a + b; }
- static u32 EvalSub(u32 a, u32 b) { return a - b; }
-
- void ArmJit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARMXEmitter::*arith)(ARMReg dst, ARMReg src, Operand2 op2), bool (ARMXEmitter::*tryArithI2R)(ARMReg dst, ARMReg src, u32 val), u32 (*eval)(u32 a, u32 b))
- {
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rt, (*eval)(gpr.GetImm(rs), uimm));
- } else {
- gpr.MapDirtyIn(rt, rs);
- if (!(this->*tryArithI2R)(gpr.R(rt), gpr.R(rs), uimm)) {
- gpr.SetRegImm(SCRATCHREG1, uimm);
- (this->*arith)(gpr.R(rt), gpr.R(rs), SCRATCHREG1);
- }
- }
- }
-
- void ArmJit::Comp_IType(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU_IMM);
- u32 uimm = op & 0xFFFF;
- s32 simm = SignExtend16ToS32(op);
- u32 suimm = SignExtend16ToU32(op);
-
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
-
- // noop, won't write to ZERO.
- if (rt == 0)
- return;
-
- switch (op >> 26)
- {
- case 8: // same as addiu?
- case 9: // R(rt) = R(rs) + simm; break; //addiu
- CompImmLogic(rs, rt, simm, &ARMXEmitter::ADD, &ARMXEmitter::TryADDI2R, &EvalAdd);
- break;
-
- case 12: CompImmLogic(rs, rt, uimm, &ARMXEmitter::AND, &ARMXEmitter::TryANDI2R, &EvalAnd); break;
- case 13: CompImmLogic(rs, rt, uimm, &ARMXEmitter::ORR, &ARMXEmitter::TryORI2R, &EvalOr); break;
- case 14: CompImmLogic(rs, rt, uimm, &ARMXEmitter::EOR, &ARMXEmitter::TryEORI2R, &EvalEor); break;
-
- case 10: // R(rt) = (s32)R(rs) < simm; break; //slti
- {
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rt, (s32)gpr.GetImm(rs) < simm ? 1 : 0);
- break;
- } else if (simm == 0) {
- gpr.MapDirtyIn(rt, rs);
- // Shift to get the sign bit only (for < 0.)
- LSR(gpr.R(rt), gpr.R(rs), 31);
- break;
- }
- gpr.MapDirtyIn(rt, rs);
- if (!TryCMPI2R(gpr.R(rs), simm)) {
- gpr.SetRegImm(SCRATCHREG1, simm);
- CMP(gpr.R(rs), SCRATCHREG1);
- }
- SetCC(CC_LT);
- MOVI2R(gpr.R(rt), 1);
- SetCC(CC_GE);
- MOVI2R(gpr.R(rt), 0);
- SetCC(CC_AL);
- }
- break;
-
- case 11: // R(rt) = R(rs) < suimm; break; //sltiu
- {
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rt, gpr.GetImm(rs) < suimm ? 1 : 0);
- break;
- }
- gpr.MapDirtyIn(rt, rs);
- if (!TryCMPI2R(gpr.R(rs), suimm)) {
- gpr.SetRegImm(SCRATCHREG1, suimm);
- CMP(gpr.R(rs), SCRATCHREG1);
- }
- SetCC(CC_LO);
- MOVI2R(gpr.R(rt), 1);
- SetCC(CC_HS);
- MOVI2R(gpr.R(rt), 0);
- SetCC(CC_AL);
- }
- break;
-
- case 15: // R(rt) = uimm << 16; //lui
- gpr.SetImm(rt, uimm << 16);
- break;
-
- default:
- Comp_Generic(op);
- break;
- }
- }
-
- void ArmJit::Comp_RType2(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU_BIT);
- MIPSGPReg rs = _RS;
- MIPSGPReg rd = _RD;
-
- // Don't change $zr.
- if (rd == 0)
- return;
-
- switch (op & 63)
- {
- case 22: //clz
- if (gpr.IsImm(rs)) {
- u32 value = gpr.GetImm(rs);
- int x = 31;
- int count = 0;
- while (x >= 0 && !(value & (1 << x))) {
- count++;
- x--;
- }
- gpr.SetImm(rd, count);
- break;
- }
- gpr.MapDirtyIn(rd, rs);
- CLZ(gpr.R(rd), gpr.R(rs));
- break;
- case 23: //clo
- if (gpr.IsImm(rs)) {
- u32 value = gpr.GetImm(rs);
- int x = 31;
- int count = 0;
- while (x >= 0 && (value & (1 << x))) {
- count++;
- x--;
- }
- gpr.SetImm(rd, count);
- break;
- }
- gpr.MapDirtyIn(rd, rs);
- MVN(SCRATCHREG1, gpr.R(rs));
- CLZ(gpr.R(rd), SCRATCHREG1);
- break;
- default:
- DISABLE;
- }
- }
-
- void ArmJit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARMXEmitter::*arith)(ARMReg dst, ARMReg rm, Operand2 rn), bool (ARMXEmitter::*tryArithI2R)(ARMReg dst, ARMReg rm, u32 val), u32 (*eval)(u32 a, u32 b), bool symmetric)
- {
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, (*eval)(gpr.GetImm(rs), gpr.GetImm(rt)));
- return;
- }
-
- if (gpr.IsImm(rt) || (gpr.IsImm(rs) && symmetric)) {
- MIPSGPReg lhs = gpr.IsImm(rs) ? rt : rs;
- MIPSGPReg rhs = gpr.IsImm(rs) ? rs : rt;
- u32 rhsImm = gpr.GetImm(rhs);
- gpr.MapDirtyIn(rd, lhs);
- if ((this->*tryArithI2R)(gpr.R(rd), gpr.R(lhs), rhsImm)) {
- return;
- }
- // If rd is rhs, we may have lost it in the MapDirtyIn(). lhs was kept.
- // This means the rhsImm value was never flushed to rhs, and would be garbage.
- if (rd == rhs) {
- // Luckily, it was just an imm.
- gpr.SetImm(rhs, rhsImm);
- }
- } else if (gpr.IsImm(rs) && !symmetric) {
- Operand2 op2;
- // For SUB, we can use RSB as a reverse operation.
- if (eval == &EvalSub && TryMakeOperand2(gpr.GetImm(rs), op2)) {
- gpr.MapDirtyIn(rd, rt);
- RSB(gpr.R(rd), gpr.R(rt), op2);
- return;
- }
- }
-
- // Generic solution. If it's an imm, better to flush at this point.
- gpr.MapDirtyInIn(rd, rs, rt);
- (this->*arith)(gpr.R(rd), gpr.R(rs), gpr.R(rt));
- }
-
- void ArmJit::Comp_RType3(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU);
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- MIPSGPReg rd = _RD;
-
- // noop, won't write to ZERO.
- if (rd == 0)
- return;
-
- switch (op & 63)
- {
- case 10: //if (!R(rt)) R(rd) = R(rs); break; //movz
- if (rd == rs || (gpr.IsImm(rd) && gpr.IsImm(rs) && gpr.GetImm(rd) == gpr.GetImm(rs)))
- break;
- if (!gpr.IsImm(rt)) {
- Operand2 op2;
- // Avoid flushing the imm if possible.
- if (gpr.IsImm(rs) && TryMakeOperand2(gpr.GetImm(rs), op2)) {
- gpr.MapDirtyIn(rd, rt, false);
- } else {
- gpr.MapDirtyInIn(rd, rt, rs, false);
- op2 = gpr.R(rs);
- }
- CMP(gpr.R(rt), Operand2(0));
- SetCC(CC_EQ);
- MOV(gpr.R(rd), op2);
- SetCC(CC_AL);
- } else if (gpr.GetImm(rt) == 0) {
- // Yes, this actually happens.
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rd, gpr.GetImm(rs));
- } else {
- gpr.MapDirtyIn(rd, rs);
- MOV(gpr.R(rd), gpr.R(rs));
- }
- }
- break;
- case 11:// if (R(rt)) R(rd) = R(rs); break; //movn
- if (rd == rs || (gpr.IsImm(rd) && gpr.IsImm(rs) && gpr.GetImm(rd) == gpr.GetImm(rs)))
- break;
- if (!gpr.IsImm(rt)) {
- Operand2 op2;
- // Avoid flushing the imm if possible.
- if (gpr.IsImm(rs) && TryMakeOperand2(gpr.GetImm(rs), op2)) {
- gpr.MapDirtyIn(rd, rt, false);
- } else {
- gpr.MapDirtyInIn(rd, rt, rs, false);
- op2 = gpr.R(rs);
- }
- CMP(gpr.R(rt), Operand2(0));
- SetCC(CC_NEQ);
- MOV(gpr.R(rd), op2);
- SetCC(CC_AL);
- } else if (gpr.GetImm(rt) != 0) {
- // Yes, this actually happens.
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rd, gpr.GetImm(rs));
- } else {
- gpr.MapDirtyIn(rd, rs);
- MOV(gpr.R(rd), gpr.R(rs));
- }
- }
- break;
-
- case 32: //R(rd) = R(rs) + R(rt); break; //add
- case 33: //R(rd) = R(rs) + R(rt); break; //addu
- // We optimize out 0 as an operand2 ADD.
- CompType3(rd, rs, rt, &ARMXEmitter::ADD, &ARMXEmitter::TryADDI2R, &EvalAdd, true);
- break;
-
- case 34: //R(rd) = R(rs) - R(rt); break; //sub
- case 35: //R(rd) = R(rs) - R(rt); break; //subu
- CompType3(rd, rs, rt, &ARMXEmitter::SUB, &ARMXEmitter::TrySUBI2R, &EvalSub, false);
- break;
- case 36: //R(rd) = R(rs) & R(rt); break; //and
- CompType3(rd, rs, rt, &ARMXEmitter::AND, &ARMXEmitter::TryANDI2R, &EvalAnd, true);
- break;
- case 37: //R(rd) = R(rs) | R(rt); break; //or
- CompType3(rd, rs, rt, &ARMXEmitter::ORR, &ARMXEmitter::TryORI2R, &EvalOr, true);
- break;
- case 38: //R(rd) = R(rs) ^ R(rt); break; //xor/eor
- CompType3(rd, rs, rt, &ARMXEmitter::EOR, &ARMXEmitter::TryEORI2R, &EvalEor, true);
- break;
-
- case 39: // R(rd) = ~(R(rs) | R(rt)); break; //nor
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, ~(gpr.GetImm(rs) | gpr.GetImm(rt)));
- } else if (gpr.IsImm(rs) || gpr.IsImm(rt)) {
- MIPSGPReg lhs = gpr.IsImm(rs) ? rt : rs;
- MIPSGPReg rhs = gpr.IsImm(rs) ? rs : rt;
- u32 rhsImm = gpr.GetImm(rhs);
- Operand2 op2;
- if (TryMakeOperand2(rhsImm, op2)) {
- gpr.MapDirtyIn(rd, lhs);
- } else {
- gpr.MapDirtyInIn(rd, rs, rt);
- op2 = gpr.R(rhs);
- }
- if (rhsImm == 0) {
- MVN(gpr.R(rd), gpr.R(lhs));
- } else {
- ORR(gpr.R(rd), gpr.R(lhs), op2);
- MVN(gpr.R(rd), gpr.R(rd));
- }
- } else {
- gpr.MapDirtyInIn(rd, rs, rt);
- ORR(gpr.R(rd), gpr.R(rs), gpr.R(rt));
- MVN(gpr.R(rd), gpr.R(rd));
- }
- break;
-
- case 42: //R(rd) = (int)R(rs) < (int)R(rt); break; //slt
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, (s32)gpr.GetImm(rs) < (s32)gpr.GetImm(rt));
- } else {
- CCFlags caseOne = CC_LT;
- CCFlags caseZero = CC_GE;
- Operand2 op2;
- bool negated;
- if (gpr.IsImm(rs) && TryMakeOperand2_AllowNegation(gpr.GetImm(rs), op2, &negated)) {
- gpr.MapDirtyIn(rd, rt);
- if (!negated)
- CMP(gpr.R(rt), op2);
- else
- CMN(gpr.R(rt), op2);
-
- // Swap the condition since we swapped the arguments.
- caseOne = CC_GT;
- caseZero = CC_LE;
- } else if (gpr.IsImm(rt) && TryMakeOperand2_AllowNegation(gpr.GetImm(rt), op2, &negated)) {
- gpr.MapDirtyIn(rd, rs);
- if (!negated)
- CMP(gpr.R(rs), op2);
- else
- CMN(gpr.R(rs), op2);
- } else {
- gpr.MapDirtyInIn(rd, rs, rt);
- CMP(gpr.R(rs), gpr.R(rt));
- }
-
- SetCC(caseOne);
- MOVI2R(gpr.R(rd), 1);
- SetCC(caseZero);
- MOVI2R(gpr.R(rd), 0);
- SetCC(CC_AL);
- }
- break;
-
- case 43: //R(rd) = R(rs) < R(rt); break; //sltu
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, gpr.GetImm(rs) < gpr.GetImm(rt));
- } else {
- CCFlags caseOne = CC_LO;
- CCFlags caseZero = CC_HS;
- Operand2 op2;
- bool negated;
- if (gpr.IsImm(rs) && TryMakeOperand2_AllowNegation(gpr.GetImm(rs), op2, &negated)) {
- gpr.MapDirtyIn(rd, rt);
- if (!negated)
- CMP(gpr.R(rt), op2);
- else
- CMN(gpr.R(rt), op2);
-
- // Swap the condition since we swapped the arguments.
- caseOne = CC_HI;
- caseZero = CC_LS;
- } else if (gpr.IsImm(rt) && TryMakeOperand2_AllowNegation(gpr.GetImm(rt), op2, &negated)) {
- gpr.MapDirtyIn(rd, rs);
- if (!negated)
- CMP(gpr.R(rs), op2);
- else
- CMN(gpr.R(rs), op2);
- } else {
- gpr.MapDirtyInIn(rd, rs, rt);
- CMP(gpr.R(rs), gpr.R(rt));
- }
-
- SetCC(caseOne);
- MOVI2R(gpr.R(rd), 1);
- SetCC(caseZero);
- MOVI2R(gpr.R(rd), 0);
- SetCC(CC_AL);
- }
- break;
-
- case 44: //R(rd) = max(R(rs), R(rt); break; //max
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, std::max((s32)gpr.GetImm(rs), (s32)gpr.GetImm(rt)));
- break;
- }
- gpr.MapDirtyInIn(rd, rs, rt);
- CMP(gpr.R(rs), gpr.R(rt));
- SetCC(CC_GT);
- if (rd != rs)
- MOV(gpr.R(rd), gpr.R(rs));
- SetCC(CC_LE);
- if (rd != rt)
- MOV(gpr.R(rd), gpr.R(rt));
- SetCC(CC_AL);
- break;
-
- case 45: //R(rd) = min(R(rs), R(rt)); break; //min
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- gpr.SetImm(rd, std::min((s32)gpr.GetImm(rs), (s32)gpr.GetImm(rt)));
- break;
- }
- gpr.MapDirtyInIn(rd, rs, rt);
- CMP(gpr.R(rs), gpr.R(rt));
- SetCC(CC_LT);
- if (rd != rs)
- MOV(gpr.R(rd), gpr.R(rs));
- SetCC(CC_GE);
- if (rd != rt)
- MOV(gpr.R(rd), gpr.R(rt));
- SetCC(CC_AL);
- break;
-
- default:
- Comp_Generic(op);
- break;
- }
- }
-
- void ArmJit::CompShiftImm(MIPSOpcode op, ArmGen::ShiftType shiftType, int sa)
- {
- MIPSGPReg rd = _RD;
- MIPSGPReg rt = _RT;
-
- if (gpr.IsImm(rt)) {
- switch (shiftType) {
- case ST_LSL:
- gpr.SetImm(rd, gpr.GetImm(rt) << sa);
- break;
- case ST_LSR:
- gpr.SetImm(rd, gpr.GetImm(rt) >> sa);
- break;
- case ST_ASR:
- gpr.SetImm(rd, (int)gpr.GetImm(rt) >> sa);
- break;
- case ST_ROR:
- gpr.SetImm(rd, (gpr.GetImm(rt) >> sa) | (gpr.GetImm(rt) << (32 - sa)));
- break;
- default:
- DISABLE;
- }
- } else {
- gpr.MapDirtyIn(rd, rt);
- MOV(gpr.R(rd), Operand2(gpr.R(rt), shiftType, sa));
- }
- }
-
- void ArmJit::CompShiftVar(MIPSOpcode op, ArmGen::ShiftType shiftType)
- {
- MIPSGPReg rd = _RD;
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- if (gpr.IsImm(rs)) {
- int sa = gpr.GetImm(rs) & 0x1F;
- CompShiftImm(op, shiftType, sa);
- return;
- }
- gpr.MapDirtyInIn(rd, rs, rt);
- AND(SCRATCHREG1, gpr.R(rs), Operand2(0x1F));
- MOV(gpr.R(rd), Operand2(gpr.R(rt), shiftType, SCRATCHREG1));
- }
-
- void ArmJit::Comp_ShiftType(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU);
- MIPSGPReg rs = _RS;
- MIPSGPReg rd = _RD;
- int fd = _FD;
- int sa = _SA;
-
- // noop, won't write to ZERO.
- if (rd == 0)
- return;
-
- // WARNING : ROTR
- switch (op & 0x3f) {
- case 0: CompShiftImm(op, ST_LSL, sa); break; //sll
- case 2: CompShiftImm(op, (rs & 1) ? ST_ROR : ST_LSR, sa); break; //srl
- case 3: CompShiftImm(op, ST_ASR, sa); break; //sra
- case 4: CompShiftVar(op, ST_LSL); break; //sllv
- case 6: CompShiftVar(op, (fd & 1) ? ST_ROR : ST_LSR); break; //srlv
- case 7: CompShiftVar(op, ST_ASR); break; //srav
-
- default:
- Comp_Generic(op);
- break;
- }
- }
-
- void ArmJit::Comp_Special3(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU_BIT);
-
- MIPSGPReg rs = _RS;
- MIPSGPReg rt = _RT;
-
- int pos = _POS;
- int size = _SIZE + 1;
- u32 mask = 0xFFFFFFFFUL >> (32 - size);
-
- // Don't change $zr.
- if (rt == 0)
- return;
-
- switch (op & 0x3f) {
- case 0x0: //ext
- if (gpr.IsImm(rs)) {
- gpr.SetImm(rt, (gpr.GetImm(rs) >> pos) & mask);
- return;
- }
-
- gpr.MapDirtyIn(rt, rs);
-#if PPSSPP_ARCH(ARMV7)
- // Bits past 31 come out as zero, and UBFX can't encode them.
- UBFX(gpr.R(rt), gpr.R(rs), pos, std::min(size, 32 - pos));
-#else
- MOV(gpr.R(rt), Operand2(gpr.R(rs), ST_LSR, pos));
- ANDI2R(gpr.R(rt), gpr.R(rt), mask, SCRATCHREG1);
-#endif
- break;
-
- case 0x4: //ins
- {
- u32 sourcemask = mask >> pos;
- u32 destmask = ~(sourcemask << pos);
- // With msb below pos there's nothing to insert, and no BFI for it.
- if (sourcemask == 0)
- return;
- if (gpr.IsImm(rs)) {
- u32 inserted = (gpr.GetImm(rs) & sourcemask) << pos;
- if (gpr.IsImm(rt)) {
- gpr.SetImm(rt, (gpr.GetImm(rt) & destmask) | inserted);
- return;
- }
-
- gpr.MapReg(rt, MAP_DIRTY);
- ANDI2R(gpr.R(rt), gpr.R(rt), destmask, SCRATCHREG1);
- if (inserted != 0) {
- ORI2R(gpr.R(rt), gpr.R(rt), inserted, SCRATCHREG1);
- }
- } else {
- gpr.MapDirtyIn(rt, rs, false);
-#if PPSSPP_ARCH(ARMV7)
- BFI(gpr.R(rt), gpr.R(rs), pos, size - pos);
-#else
- ANDI2R(SCRATCHREG1, gpr.R(rs), sourcemask, SCRATCHREG2);
- ANDI2R(gpr.R(rt), gpr.R(rt), destmask, SCRATCHREG2);
- ORR(gpr.R(rt), gpr.R(rt), Operand2(SCRATCHREG1, ST_LSL, pos));
-#endif
- }
- }
- break;
- }
- }
-
- void ArmJit::Comp_Allegrex(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU_BIT);
- MIPSGPReg rt = _RT;
- MIPSGPReg rd = _RD;
- // Don't change $zr.
- if (rd == 0)
- return;
-
- switch (AllegrexBitOp(op)) {
- case 16: // seb // R(rd) = SignExtend8ToU32(R(rt));
- if (gpr.IsImm(rt)) {
- gpr.SetImm(rd, SignExtend8ToU32(gpr.GetImm(rt)));
- return;
- }
- gpr.MapDirtyIn(rd, rt);
- SXTB(gpr.R(rd), gpr.R(rt));
- break;
-
- case 24: // seh
- if (gpr.IsImm(rt)) {
- gpr.SetImm(rd, SignExtend16ToU32(gpr.GetImm(rt)));
- return;
- }
- gpr.MapDirtyIn(rd, rt);
- SXTH(gpr.R(rd), gpr.R(rt));
- break;
-
- case 20: //bitrev
- if (gpr.IsImm(rt)) {
- // http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
- u32 v = gpr.GetImm(rt);
- v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); // odd<->even
- v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); // pair<->pair
- v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); // nibb<->nibb
- v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); // byte<->byte
- v = ( v >> 16 ) | ( v << 16); // hword<->hword
- gpr.SetImm(rd, v);
- return;
- }
-
-#if PPSSPP_ARCH(ARMV7)
- gpr.MapDirtyIn(rd, rt);
- RBIT(gpr.R(rd), gpr.R(rt));
-#else
- Comp_Generic(op);
-#endif
- break;
- default:
- Comp_Generic(op);
- return;
- }
- }
-
- void ArmJit::Comp_Allegrex2(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(ALU_BIT);
- MIPSGPReg rt = _RT;
- MIPSGPReg rd = _RD;
- // Don't change $zr.
- if (rd == 0)
- return;
-
- switch (AllegrexBitOp(op)) {
- case 2: //wsbh
- if (gpr.IsImm(rt)) {
- gpr.SetImm(rd, ((gpr.GetImm(rt) & 0xFF00FF00) >> 8) | ((gpr.GetImm(rt) & 0x00FF00FF) << 8));
- } else {
- gpr.MapDirtyIn(rd, rt);
- REV16(gpr.R(rd), gpr.R(rt));
- }
- break;
- case 3: //wsbw
- if (gpr.IsImm(rt)) {
- gpr.SetImm(rd, swap32(gpr.GetImm(rt)));
- } else {
- gpr.MapDirtyIn(rd, rt);
- REV(gpr.R(rd), gpr.R(rt));
- }
- break;
- default:
- Comp_Generic(op);
- break;
- }
- }
-
- void ArmJit::Comp_MulDivType(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(MULDIV);
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- MIPSGPReg rd = _RD;
-
- switch (op & 63) {
- case 16: // R(rd) = HI; //mfhi
- if (rd != MIPS_REG_ZERO) {
- if (gpr.IsImm(MIPS_REG_HI)) {
- gpr.SetImm(rd, gpr.GetImm(MIPS_REG_HI));
- break;
- }
- gpr.MapDirtyIn(rd, MIPS_REG_HI);
- MOV(gpr.R(rd), gpr.R(MIPS_REG_HI));
- }
- break;
-
- case 17: // HI = R(rs); //mthi
- if (gpr.IsImm(rs)) {
- gpr.SetImm(MIPS_REG_HI, gpr.GetImm(rs));
- break;
- }
- gpr.MapDirtyIn(MIPS_REG_HI, rs);
- MOV(gpr.R(MIPS_REG_HI), gpr.R(rs));
- break;
-
- case 18: // R(rd) = LO; break; //mflo
- if (rd != MIPS_REG_ZERO) {
- if (gpr.IsImm(MIPS_REG_LO)) {
- gpr.SetImm(rd, gpr.GetImm(MIPS_REG_LO));
- break;
- }
- gpr.MapDirtyIn(rd, MIPS_REG_LO);
- MOV(gpr.R(rd), gpr.R(MIPS_REG_LO));
- }
- break;
-
- case 19: // LO = R(rs); break; //mtlo
- if (gpr.IsImm(rs)) {
- gpr.SetImm(MIPS_REG_LO, gpr.GetImm(rs));
- break;
- }
- gpr.MapDirtyIn(MIPS_REG_LO, rs);
- MOV(gpr.R(MIPS_REG_LO), gpr.R(rs));
- break;
-
- case 24: //mult (the most popular one). lo,hi = signed mul (rs * rt)
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- s64 result = (s64)(s32)gpr.GetImm(rs) * (s64)(s32)gpr.GetImm(rt);
- u64 resultBits = (u64)result;
- gpr.SetImm(MIPS_REG_LO, (u32)(resultBits >> 0));
- gpr.SetImm(MIPS_REG_HI, (u32)(resultBits >> 32));
- break;
- }
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt);
- SMULL(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_HI), gpr.R(rs), gpr.R(rt));
- break;
-
- case 25: //multu (2nd) lo,hi = unsigned mul (rs * rt)
- if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
- u64 resultBits = (u64)gpr.GetImm(rs) * (u64)gpr.GetImm(rt);
- gpr.SetImm(MIPS_REG_LO, (u32)(resultBits >> 0));
- gpr.SetImm(MIPS_REG_HI, (u32)(resultBits >> 32));
- break;
- }
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt);
- UMULL(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_HI), gpr.R(rs), gpr.R(rt));
- break;
-
- case 26: //div
- if (cpu_info.bIDIVa) {
- // INT_MIN / -1: SDIV gives INT_MIN, and the MUL/SUB below wraps the remainder to 0,
- // which is what the hardware does (cpu/cpu_alu/cpu_div).
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt);
-
- CMPI2R(gpr.R(rt), 0, SCRATCHREG1);
- FixupBranch skipZero = B_CC(CC_NEQ);
- MOV(gpr.R(MIPS_REG_HI), gpr.R(rs));
- MOVI2R(gpr.R(MIPS_REG_LO), -1);
- CMPI2R(gpr.R(rs), 0, SCRATCHREG1);
- SetCC(CC_LT);
- MOVI2R(gpr.R(MIPS_REG_LO), 1);
- SetCC(CC_AL);
- FixupBranch skipDiv = B();
-
- SetJumpTarget(skipZero);
- SDIV(gpr.R(MIPS_REG_LO), gpr.R(rs), gpr.R(rt));
- SetJumpTarget(skipDiv);
- MUL(SCRATCHREG1, gpr.R(rt), gpr.R(MIPS_REG_LO));
- SUB(gpr.R(MIPS_REG_HI), gpr.R(rs), Operand2(SCRATCHREG1));
- } else {
- DISABLE;
- }
- break;
-
- case 27: //divu
- // Do we have a known power-of-two denominator? Yes, this happens.
- if (gpr.IsImm(rt) && (gpr.GetImm(rt) & (gpr.GetImm(rt) - 1)) == 0 && gpr.GetImm(rt) != 0) {
- u32 denominator = gpr.GetImm(rt);
- gpr.MapDirtyDirtyIn(MIPS_REG_LO, MIPS_REG_HI, rs);
- // Remainder is just an AND, neat.
- ANDI2R(gpr.R(MIPS_REG_HI), gpr.R(rs), denominator - 1, SCRATCHREG1);
- int shift = 0;
- while (denominator != 0) {
- ++shift;
- denominator >>= 1;
- }
- // The shift value is one too much for the divide by the same value.
- if (shift > 1) {
- LSR(gpr.R(MIPS_REG_LO), gpr.R(rs), shift - 1);
- } else {
- MOV(gpr.R(MIPS_REG_LO), gpr.R(rs));
- }
- } else if (cpu_info.bIDIVa) {
- // TODO: Does this handle INT_MAX, 0, etc. correctly?
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt);
-
- CMPI2R(gpr.R(rt), 0, SCRATCHREG1);
- FixupBranch skipZero = B_CC(CC_NEQ);
- MOV(gpr.R(MIPS_REG_HI), gpr.R(rs));
- MOVI2R(SCRATCHREG1, 0xFFFF);
- CMP(gpr.R(rs), SCRATCHREG1);
- MOVI2R(gpr.R(MIPS_REG_LO), -1);
- SetCC(CC_LS);
- MOV(gpr.R(MIPS_REG_LO), SCRATCHREG1);
- SetCC(CC_AL);
- FixupBranch skipDiv = B();
-
- SetJumpTarget(skipZero);
- UDIV(gpr.R(MIPS_REG_LO), gpr.R(rs), gpr.R(rt));
- SetJumpTarget(skipDiv);
- MUL(SCRATCHREG1, gpr.R(rt), gpr.R(MIPS_REG_LO));
- SUB(gpr.R(MIPS_REG_HI), gpr.R(rs), Operand2(SCRATCHREG1));
- } else {
- // If rt is 0, we either caught it above, or it's not an imm.
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt);
- MOV(SCRATCHREG1, gpr.R(rt));
- // We start at rs for the remainder and subtract out.
- MOV(gpr.R(MIPS_REG_HI), gpr.R(rs));
-
- CMP(gpr.R(rt), 0);
- FixupBranch skipper = B_CC(CC_EQ);
-
- // Double SCRATCHREG1 until it would be (but isn't) bigger than the numerator.
- CMP(SCRATCHREG1, Operand2(gpr.R(rs), ST_LSR, 1));
- const u8 *doubleLoop = GetCodePtr();
- SetCC(CC_LS);
- MOV(SCRATCHREG1, Operand2(SCRATCHREG1, ST_LSL, 1));
- SetCC(CC_AL);
- CMP(SCRATCHREG1, Operand2(gpr.R(rs), ST_LSR, 1));
- B_CC(CC_LS, doubleLoop);
-
- MOV(gpr.R(MIPS_REG_LO), 0);
-
- // Subtract and halve SCRATCHREG1 (doubling and adding the result) until it's below the denominator.
- const u8 *subLoop = GetCodePtr();
- CMP(gpr.R(MIPS_REG_HI), SCRATCHREG1);
- SetCC(CC_HS);
- SUB(gpr.R(MIPS_REG_HI), gpr.R(MIPS_REG_HI), SCRATCHREG1);
- SetCC(CC_AL);
- // Carry will be set if we subtracted.
- ADC(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_LO));
- MOV(SCRATCHREG1, Operand2(SCRATCHREG1, ST_LSR, 1));
- CMP(SCRATCHREG1, gpr.R(rt));
- B_CC(CC_HS, subLoop);
-
- // We didn't change rt. If it was 0, then clear HI and LO.
- FixupBranch zeroSkip = B();
- SetJumpTarget(skipper);
- MOVI2R(SCRATCHREG1, 0xFFFF);
- CMP(gpr.R(rs), SCRATCHREG1);
- MOVI2R(gpr.R(MIPS_REG_LO), -1);
- SetCC(CC_LS);
- MOV(gpr.R(MIPS_REG_LO), SCRATCHREG1);
- SetCC(CC_AL);
- SetJumpTarget(zeroSkip);
- }
- break;
-
- case 28: //madd
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt, false);
- SMLAL(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_HI), gpr.R(rs), gpr.R(rt));
- break;
-
- case 29: //maddu
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt, false);
- UMLAL(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_HI), gpr.R(rs), gpr.R(rt));
- break;
-
- case 46: // msub
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt, false);
- SMULL(SCRATCHREG1, SCRATCHREG2, gpr.R(rs), gpr.R(rt));
- SUBS(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_LO), SCRATCHREG1);
- SBC(gpr.R(MIPS_REG_HI), gpr.R(MIPS_REG_HI), SCRATCHREG2);
- break;
-
- case 47: // msubu
- gpr.MapDirtyDirtyInIn(MIPS_REG_LO, MIPS_REG_HI, rs, rt, false);
- UMULL(SCRATCHREG1, SCRATCHREG2, gpr.R(rs), gpr.R(rt));
- SUBS(gpr.R(MIPS_REG_LO), gpr.R(MIPS_REG_LO), SCRATCHREG1);
- SBC(gpr.R(MIPS_REG_HI), gpr.R(MIPS_REG_HI), SCRATCHREG2);
- break;
-
- default:
- DISABLE;
- }
- }
-
-}
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompBranch.cpp b/Core/MIPS/ARM/ArmCompBranch.cpp
deleted file mode 100644
index f1634164d4..0000000000
--- a/Core/MIPS/ARM/ArmCompBranch.cpp
+++ /dev/null
@@ -1,655 +0,0 @@
-// 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 "Common/Data/Convert/SmallDataConvert.h"
-#include "Common/Profiler/Profiler.h"
-
-#include "Core/Config.h"
-#include "Core/Core.h"
-#include "Core/Reporting.h"
-#include "Core/MemMap.h"
-#include "Core/HLE/HLE.h"
-#include "Core/HLE/HLETables.h"
-
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/MIPSCodeUtils.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/MIPS/MIPSTables.h"
-
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Core/MIPS/JitCommon/JitBlockCache.h"
-
-#include "Common/ArmEmitter.h"
-
-#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 _IMM26 (op & 0x03FFFFFF)
-#define TARGET16 ((int)(SignExtend16ToU32(op) << 2))
-#define TARGET26 (_IMM26 << 2)
-
-#define LOOPOPTIMIZATION 0
-
-// We can disable nice delay slots.
-// #define CONDITIONAL_NICE_DELAYSLOT branchInfo.delaySlotIsNice = false;
-#define CONDITIONAL_NICE_DELAYSLOT ;
-
-using namespace MIPSAnalyst;
-
-namespace MIPSComp
-{
- using namespace ArmGen;
- using namespace ArmJitConstants;
-
-void ArmJit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely)
-{
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- int offset = TARGET16;
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- u32 targetAddr = GetCompilerPC() + offset + 4;
-
- BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
- branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rt, rs);
- CONDITIONAL_NICE_DELAYSLOT;
-
- bool immBranch = false;
- bool immBranchTaken = false;
- if (gpr.IsImm(rs) && gpr.IsImm(rt) && !branchInfo.delaySlotIsBranch) {
- // The cc flags are opposites: when NOT to take the branch.
- bool immBranchNotTaken;
- s32 rsImm = (s32)gpr.GetImm(rs);
- s32 rtImm = (s32)gpr.GetImm(rt);
-
- switch (cc)
- {
- case CC_EQ: immBranchNotTaken = rsImm == rtImm; break;
- case CC_NEQ: immBranchNotTaken = rsImm != rtImm; break;
- default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSRTComp().");
- }
- immBranch = true;
- immBranchTaken = !immBranchNotTaken;
- }
-
- js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
-
- u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
- if (immBranch) {
- // Continuing is handled above, this is just static jumping.
- if (immBranchTaken || !likely)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- else
- FlushAll();
-
- const u32 destAddr = immBranchTaken ? targetAddr : notTakenTarget;
- WriteExit(destAddr, js.nextExit++);
- } else {
- if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_NICE);
-
- // We might be able to flip the condition (EQ/NEQ are easy.)
- const bool canFlip = cc == CC_EQ || cc == CC_NEQ;
-
- Operand2 op2;
- bool negated;
- if (gpr.IsImm(rt) && TryMakeOperand2_AllowNegation(gpr.GetImm(rt), op2, &negated)) {
- gpr.MapReg(rs);
- if (!negated)
- CMP(gpr.R(rs), op2);
- else
- CMN(gpr.R(rs), op2);
- } else {
- if (gpr.IsImm(rs) && TryMakeOperand2_AllowNegation(gpr.GetImm(rs), op2, &negated) && canFlip) {
- gpr.MapReg(rt);
- if (!negated)
- CMP(gpr.R(rt), op2);
- else
- CMN(gpr.R(rt), op2);
- } else {
- gpr.MapInIn(rs, rt);
- CMP(gpr.R(rs), gpr.R(rt));
- }
- }
-
- ArmGen::FixupBranch ptr;
- if (!likely) {
- if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
- else
- FlushAll();
- ptr = B_CC(cc);
- } else {
- FlushAll();
- ptr = B_CC(cc);
- if (!branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- }
-
- if (branchInfo.delaySlotIsBranch) {
- // We still link when the branch is taken (targetAddr case.)
- // Remember, it's from the perspective of the delay slot, so +12.
- if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
- if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
- gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
- FlushAll();
- }
-
- // Take the branch
- WriteExit(targetAddr, js.nextExit++);
-
- SetJumpTarget(ptr);
- // Not taken
- WriteExit(notTakenTarget, js.nextExit++);
- }
-
- js.compiling = false;
-}
-
-
-void ArmJit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool likely)
-{
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- int offset = TARGET16;
- MIPSGPReg rs = _RS;
- u32 targetAddr = GetCompilerPC() + offset + 4;
-
- BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), andLink, likely);
- branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rs);
- CONDITIONAL_NICE_DELAYSLOT;
-
- bool immBranch = false;
- bool immBranchTaken = false;
- if (gpr.IsImm(rs) && !branchInfo.delaySlotIsBranch) {
- // The cc flags are opposites: when NOT to take the branch.
- bool immBranchNotTaken;
- s32 imm = (s32)gpr.GetImm(rs);
-
- switch (cc)
- {
- case CC_GT: immBranchNotTaken = imm > 0; break;
- case CC_GE: immBranchNotTaken = imm >= 0; break;
- case CC_LT: immBranchNotTaken = imm < 0; break;
- case CC_LE: immBranchNotTaken = imm <= 0; break;
- default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSZeroComp().");
- }
- immBranch = true;
- immBranchTaken = !immBranchNotTaken;
- }
-
- js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
-
- u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
- if (immBranch) {
- // Continuing is handled above, this is just static jumping.
- if (andLink)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
- if (immBranchTaken || !likely)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- else
- FlushAll();
-
- const u32 destAddr = immBranchTaken ? targetAddr : notTakenTarget;
- WriteExit(destAddr, js.nextExit++);
- } else {
- if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_NICE);
-
- gpr.MapReg(rs);
- CMP(gpr.R(rs), Operand2(0, TYPE_IMM));
-
- if (andLink)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
-
- ArmGen::FixupBranch ptr;
- if (!likely)
- {
- if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
- else
- FlushAll();
- ptr = B_CC(cc);
- }
- else
- {
- FlushAll();
- ptr = B_CC(cc);
- if (!branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- }
-
- if (branchInfo.delaySlotIsBranch) {
- // We still link when the branch is taken (targetAddr case.)
- // Remember, it's from the perspective of the delay slot, so +12.
- if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
- if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
- gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
- FlushAll();
- }
-
- // Take the branch
- WriteExit(targetAddr, js.nextExit++);
-
- SetJumpTarget(ptr);
- // Not taken
- WriteExit(notTakenTarget, js.nextExit++);
- }
- js.compiling = false;
-}
-
-
-void ArmJit::Comp_RelBranch(MIPSOpcode op)
-{
- // The CC flags here should be opposite of the actual branch becuase they skip the branching action.
- switch (op >> 26)
- {
- case 4: BranchRSRTComp(op, CC_NEQ, false); break;//beq
- case 5: BranchRSRTComp(op, CC_EQ, false); break;//bne
-
- case 6: BranchRSZeroComp(op, CC_GT, false, false); break;//blez
- case 7: BranchRSZeroComp(op, CC_LE, false, false); break;//bgtz
-
- case 20: BranchRSRTComp(op, CC_NEQ, true); break;//beql
- case 21: BranchRSRTComp(op, CC_EQ, true); break;//bnel
-
- case 22: BranchRSZeroComp(op, CC_GT, false, true); break;//blezl
- case 23: BranchRSZeroComp(op, CC_LE, false, true); break;//bgtzl
-
- default:
- _dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
- break;
- }
-}
-
-void ArmJit::Comp_RelBranchRI(MIPSOpcode op)
-{
- switch ((op >> 16) & 0x1F)
- {
- case 0: BranchRSZeroComp(op, CC_GE, false, false); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
- case 1: BranchRSZeroComp(op, CC_LT, false, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
- case 2: BranchRSZeroComp(op, CC_GE, false, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
- case 3: BranchRSZeroComp(op, CC_LT, false, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
- case 16: BranchRSZeroComp(op, CC_GE, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltzal
- case 17: BranchRSZeroComp(op, CC_LT, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgezal
- case 18: BranchRSZeroComp(op, CC_GE, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else SkipLikely(); break;//bltzall
- case 19: BranchRSZeroComp(op, CC_LT, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else SkipLikely(); break;//bgezall
- default:
- _dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
- break;
- }
-}
-
-// If likely is set, discard the branch slot if NOT taken.
-void ArmJit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely)
-{
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- int offset = TARGET16;
- u32 targetAddr = GetCompilerPC() + offset + 4;
-
- BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
- branchInfo.delaySlotIsNice = IsDelaySlotNiceFPU(op, branchInfo.delaySlotOp);
- CONDITIONAL_NICE_DELAYSLOT;
-
- js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
- if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_NICE);
-
- gpr.MapReg(MIPS_REG_FPCOND);
- TST(gpr.R(MIPS_REG_FPCOND), Operand2(1, TYPE_IMM));
-
- ArmGen::FixupBranch ptr;
- if (!likely)
- {
- if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
- else
- FlushAll();
- ptr = B_CC(cc);
- }
- else
- {
- FlushAll();
- ptr = B_CC(cc);
- if (!branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- }
-
- if (branchInfo.delaySlotIsBranch) {
- // We still link when the branch is taken (targetAddr case.)
- // Remember, it's from the perspective of the delay slot, so +12.
- if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
- if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
- gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
- FlushAll();
- }
-
- // Take the branch
- WriteExit(targetAddr, js.nextExit++);
-
- SetJumpTarget(ptr);
- // Not taken
- WriteExit(ResolveNotTakenTarget(branchInfo), js.nextExit++);
- js.compiling = false;
-}
-
-void ArmJit::Comp_FPUBranch(MIPSOpcode op)
-{
- switch((op >> 16) & 0x1f)
- {
- case 0: BranchFPFlag(op, CC_NEQ, false); break; // bc1f
- case 1: BranchFPFlag(op, CC_EQ, false); break; // bc1t
- case 2: BranchFPFlag(op, CC_NEQ, true); break; // bc1fl
- case 3: BranchFPFlag(op, CC_EQ, true); break; // bc1tl
- default:
- _dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
- break;
- }
-}
-
-// If likely is set, discard the branch slot if NOT taken.
-void ArmJit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely)
-{
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- int offset = TARGET16;
- u32 targetAddr = GetCompilerPC() + offset + 4;
-
- BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
- // Sometimes there's a VFPU branch in a delay slot (Disgaea 2: Dark Hero Days, Zettai Hero Project, La Pucelle)
- // The behavior is undefined - the CPU may take the second branch even if the first one passes.
- // However, it does consistently try each branch, which these games seem to expect.
- branchInfo.delaySlotIsNice = IsDelaySlotNiceVFPU(op, branchInfo.delaySlotOp);
- CONDITIONAL_NICE_DELAYSLOT;
-
- js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
- if (!likely && branchInfo.delaySlotIsNice)
- CompileDelaySlot(DELAYSLOT_NICE);
-
- int imm3 = (op >> 18) & 7;
-
- gpr.MapReg(MIPS_REG_VFPUCC);
- TST(gpr.R(MIPS_REG_VFPUCC), Operand2(1 << imm3, TYPE_IMM));
-
- ArmGen::FixupBranch ptr;
- js.inDelaySlot = true;
- if (!likely)
- {
- if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
- else
- FlushAll();
- ptr = B_CC(cc);
- }
- else
- {
- FlushAll();
- ptr = B_CC(cc);
- if (!branchInfo.delaySlotIsBranch)
- CompileDelaySlot(DELAYSLOT_FLUSH);
- }
- js.inDelaySlot = false;
-
- if (branchInfo.delaySlotIsBranch) {
- // We still link when the branch is taken (targetAddr case.)
- // Remember, it's from the perspective of the delay slot, so +12.
- if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
- if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
- gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
- FlushAll();
- }
-
- // Take the branch
- WriteExit(targetAddr, js.nextExit++);
-
- SetJumpTarget(ptr);
- // Not taken
- WriteExit(ResolveNotTakenTarget(branchInfo), js.nextExit++);
- js.compiling = false;
-}
-
-void ArmJit::Comp_VBranch(MIPSOpcode op)
-{
- switch ((op >> 16) & 3)
- {
- case 0: BranchVFPUFlag(op, CC_NEQ, false); break; // bvf
- case 1: BranchVFPUFlag(op, CC_EQ, false); break; // bvt
- case 2: BranchVFPUFlag(op, CC_NEQ, true); break; // bvfl
- case 3: BranchVFPUFlag(op, CC_EQ, true); break; // bvtl
- }
-}
-
-static void HitInvalidJump(uint32_t dest) {
- Core_ExecException(dest, currentMIPS->pc - 8, ExecExceptionType::JUMP);
-}
-
-void ArmJit::Comp_Jump(MIPSOpcode op) {
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- u32 off = TARGET26;
- u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off;
-
- // Might be a stubbed address or something?
- if (!Memory::IsValidAddress(targetAddr) || (targetAddr & 3) != 0) {
- if (js.nextExit == 0) {
- ERROR_LOG(Log::JIT, "Jump to invalid address: %08x", targetAddr);
- } else {
- js.compiling = false;
- }
- // TODO: Mark this block dirty or something? May be indication it will be changed by imports.
- CompileDelaySlot(DELAYSLOT_NICE);
- FlushAll();
- gpr.SetRegImm(SCRATCHREG1, GetCompilerPC() + 8);
- MovToPC(SCRATCHREG1);
- MOVI2R(R0, targetAddr);
- QuickCallFunction(SCRATCHREG2, &HitInvalidJump);
- WriteSyscallExit();
- return;
- }
-
- switch (op >> 26) {
- case 2: //j
- CompileDelaySlot(DELAYSLOT_NICE);
- FlushAll();
- WriteExit(targetAddr, js.nextExit++);
- break;
-
- case 3: //jal
- gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
- CompileDelaySlot(DELAYSLOT_NICE);
- FlushAll();
- WriteExit(targetAddr, js.nextExit++);
- break;
-
- default:
- _dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
- break;
- }
- js.compiling = false;
-}
-
-void ArmJit::Comp_JumpReg(MIPSOpcode op)
-{
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT(Log::JIT, "Branch in JumpReg delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
- return;
- }
- MIPSGPReg rs = _RS;
- MIPSGPReg rd = _RD;
- bool andLink = (op & 0x3f) == 9 && rd != MIPS_REG_ZERO;
-
- MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
- js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
- bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rs);
- if (andLink && rs == rd)
- delaySlotIsNice = false;
- CONDITIONAL_NICE_DELAYSLOT;
-
- ARMReg destReg = R8;
- if (IsSyscall(delaySlotOp)) {
- gpr.MapReg(rs);
- MovToPC(gpr.R(rs)); // For syscall to be able to return.
- if (andLink)
- gpr.SetImm(rd, GetCompilerPC() + 8);
- CompileDelaySlot(DELAYSLOT_FLUSH);
- return; // Syscall wrote exit code.
- } else if (delaySlotIsNice) {
- if (andLink)
- gpr.SetImm(rd, GetCompilerPC() + 8);
- CompileDelaySlot(DELAYSLOT_NICE);
-
- if (!andLink && rs == MIPS_REG_RA && g_Config.bDiscardRegsOnJRRA) {
- // According to the MIPS ABI, there are some regs we don't need to preserve.
- // Let's discard them so we don't need to write them back.
- // NOTE: Not all games follow the MIPS ABI! Tekken 6, for example, will crash
- // with this enabled.
- gpr.DiscardR(MIPS_REG_COMPILER_SCRATCH);
- for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; i++)
- gpr.DiscardR((MIPSGPReg)i);
- gpr.DiscardR(MIPS_REG_T8);
- gpr.DiscardR(MIPS_REG_T9);
- }
-
- gpr.MapReg(rs);
- destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs
- FlushAll();
- } else {
- // Delay slot - this case is very rare, might be able to free up R8.
- gpr.MapReg(rs);
- MOV(R8, gpr.R(rs));
- if (andLink)
- gpr.SetImm(rd, GetCompilerPC() + 8);
- CompileDelaySlot(DELAYSLOT_NICE);
- FlushAll();
- }
-
- switch (op & 0x3f)
- {
- case 8: //jr
- break;
- case 9: //jalr
- break;
- default:
- _dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
- break;
- }
-
- WriteExitDestInR(destReg);
- js.compiling = false;
-}
-
-
-void ArmJit::Comp_Syscall(MIPSOpcode op)
-{
- if (op.encoding == 0x03FFFFcc) {
- WARN_LOG(Log::JIT, "Encountered bad syscall instruction at %08x (%08x)", js.compilerPC, op.encoding);
- }
-
- if (!g_Config.bSkipDeadbeefFilling)
- {
- // All of these will be overwritten with DEADBEEF anyway.
- gpr.DiscardR(MIPS_REG_COMPILER_SCRATCH);
- // We need to keep A0 - T3, which are used for args.
- gpr.DiscardR(MIPS_REG_T4);
- gpr.DiscardR(MIPS_REG_T5);
- gpr.DiscardR(MIPS_REG_T6);
- gpr.DiscardR(MIPS_REG_T7);
- gpr.DiscardR(MIPS_REG_T8);
- gpr.DiscardR(MIPS_REG_T9);
-
- gpr.DiscardR(MIPS_REG_HI);
- gpr.DiscardR(MIPS_REG_LO);
- }
-
- // If we're in a delay slot, this is off by one.
- const int offset = js.inDelaySlot ? -1 : 0;
- WriteDownCount(offset);
- RestoreRoundingMode();
- js.downcountAmount = -offset;
-
- if (!js.inDelaySlot) {
- gpr.SetRegImm(SCRATCHREG1, GetCompilerPC() + 4);
- MovToPC(SCRATCHREG1);
- }
-
- FlushAll();
-
- SaveDowncount();
-#ifdef USE_PROFILER
- // When profiling, we can't skip CallSyscall, since it times syscalls.
- gpr.SetRegImm(R0, op.encoding);
- QuickCallFunction(R1, (void *)&CallSyscall);
-#else
- // Skip the CallSyscall where possible.
- const HLEFunction *func = GetSyscallFunctionData(op, js.compilerPC);
- if (func) {
- void *quickFunc = GetQuickSyscallFunc(func, op);
- if (quickFunc) {
- gpr.SetRegImm(R0, (uintptr_t)func);
- // Already flushed, so R1 is safe.
- QuickCallFunction(R1, quickFunc);
- } else {
- gpr.SetRegImm(R0, op.encoding);
- QuickCallFunction(R1, (void *)&CallSyscall);
- }
- } else {
- gpr.SetRegImm(R0, js.compilerPC);
- QuickCallFunction(R1, (void *)&CallSyscallUnresolvedAtPC);
- }
-#endif
- ApplyRoundingMode();
- RestoreDowncount();
-
- WriteSyscallExit();
- js.compiling = false;
-}
-
-void ArmJit::Comp_Break(MIPSOpcode op)
-{
- Comp_Generic(op);
- WriteSyscallExit();
- js.compiling = false;
-}
-
-} // namespace Mipscomp
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompFPU.cpp b/Core/MIPS/ARM/ArmCompFPU.cpp
deleted file mode 100644
index ad931188f6..0000000000
--- a/Core/MIPS/ARM/ArmCompFPU.cpp
+++ /dev/null
@@ -1,445 +0,0 @@
-// 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 "Core/Config.h"
-#include "Core/MemMap.h"
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/MIPSCodeUtils.h"
-#include "Core/MIPS/MIPSTables.h"
-
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Common/CPUDetect.h"
-
-#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)
-
-// 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(flag) { Comp_Generic(op); return; }
-#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
-#define DISABLE { Comp_Generic(op); return; }
-
-namespace MIPSComp
-{
- using namespace ArmGen;
- using namespace ArmJitConstants;
-
-void ArmJit::Comp_FPU3op(MIPSOpcode op)
-{
- CONDITIONAL_DISABLE(FPU);
-
- int ft = _FT;
- int fs = _FS;
- int fd = _FD;
-
- fpr.MapDirtyInIn(fd, fs, ft);
- switch (op & 0x3f)
- {
- case 0: VADD(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) + F(ft); //add
- case 1: VSUB(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) - F(ft); //sub
- case 2: { //F(fd) = F(fs) * F(ft); //mul
- MIPSOpcode nextOp = GetOffsetInstruction(1);
- // Optimization possible if destination is the same
- if (fd == (int)((nextOp>>6) & 0x1F)) {
- // VMUL + VNEG -> VNMUL
- if (!strcmp(MIPSGetName(nextOp), "neg.s")) {
- if (fd == (int)((nextOp>>11) & 0x1F)) {
- VNMUL(fpr.R(fd), fpr.R(fs), fpr.R(ft));
- EatInstruction(nextOp);
- }
- return;
- }
- }
- VMUL(fpr.R(fd), fpr.R(fs), fpr.R(ft));
- break;
- }
- case 3: VDIV(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) / F(ft); //div
- default:
- DISABLE;
- return;
- }
-}
-
-extern int logBlocks;
-
-void ArmJit::Comp_FPULS(MIPSOpcode op)
-{
- CONDITIONAL_DISABLE(LSU_FPU);
- 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 = SignExtend16ToS32(op & 0xFFFF);
- int ft = _FT;
- MIPSGPReg rs = _RS;
- bool doCheck = false;
- switch(op >> 26)
- {
- case 49: //lwc1
- if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset < 0x400 && offset > -0x400) {
- gpr.MapRegAsPointer(rs);
- fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY);
- VLDR(fpr.R(ft), gpr.RPtr(rs), offset);
- break;
- }
-
- fpr.SpillLock(ft);
- fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY);
- 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.R(ft), R0, 0);
- if (doCheck) {
- SetJumpTarget(skip);
- }
-#else
- VLDR(fpr.R(ft), R0, 0);
- if (doCheck) {
- SetCC(CC_EQ);
- MOVI2R(R0, 0);
- VMOV(fpr.R(ft), R0);
- SetCC(CC_AL);
- }
-#endif
- fpr.ReleaseSpillLocksAndDiscardTemps();
- break;
-
- case 57: //swc1
- if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset < 0x400 && offset > -0x400) {
- gpr.MapRegAsPointer(rs);
- fpr.MapReg(ft, 0);
- VSTR(fpr.R(ft), gpr.RPtr(rs), offset);
- break;
- }
-
- fpr.MapReg(ft);
- 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 skip2;
- if (doCheck) {
- skip2 = B_CC(CC_EQ);
- }
- VSTR(fpr.R(ft), R0, 0);
- if (doCheck) {
- SetJumpTarget(skip2);
- }
-#else
- VSTR(fpr.R(ft), R0, 0);
- if (doCheck) {
- SetCC(CC_AL);
- }
-#endif
- break;
-
- default:
- Comp_Generic(op);
- return;
- }
-}
-
-void ArmJit::Comp_FPUComp(MIPSOpcode op) {
- CONDITIONAL_DISABLE(FPU_COMP);
-
- int opc = op & 0xF;
- if (opc >= 8) opc -= 8; // alias
- if (opc == 0) { // f, sf (signalling false)
- gpr.SetImm(MIPS_REG_FPCOND, 0);
- return;
- }
-
- int fs = _FS;
- int ft = _FT;
- gpr.MapReg(MIPS_REG_FPCOND, MAP_DIRTY | MAP_NOINIT);
- fpr.MapInIn(fs, ft);
- VCMP(fpr.R(fs), fpr.R(ft));
- VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
- switch(opc)
- {
- case 1: // un, ngle (unordered)
- SetCC(CC_VS);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_VC);
- break;
- case 2: // eq, seq (equal, ordered)
- SetCC(CC_EQ);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_NEQ);
- break;
- case 3: // ueq, ngl (equal, unordered)
- SetCC(CC_EQ);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_NEQ);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 0);
- SetCC(CC_VS);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_AL);
- return;
- case 4: // olt, lt (less than, ordered)
- SetCC(CC_LO);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_HS);
- break;
- case 5: // ult, nge (less than, unordered)
- SetCC(CC_LT);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_GE);
- break;
- case 6: // ole, le (less equal, ordered)
- SetCC(CC_LS);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_HI);
- break;
- case 7: // ule, ngt (less equal, unordered)
- SetCC(CC_LE);
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 1);
- SetCC(CC_GT);
- break;
- default:
- Comp_Generic(op);
- return;
- }
- MOVI2R(gpr.R(MIPS_REG_FPCOND), 0);
- SetCC(CC_AL);
-}
-
-// round.w.s, ceil.w.s and floor.w.s use their own rounding mode. Only RMode is swapped, so the
-// guest's flush-to-zero still applies to a denormal input (cpu/fpu/denormal).
-void ArmJit::CompFixedRoundConversion(int fd, int fs, int rmode) {
- fpr.MapDirtyIn(fd, fs);
- VMRS(SCRATCHREG2);
- BIC(SCRATCHREG1, SCRATCHREG2, AssumeMakeOperand2(3 << 22));
- if (rmode != 0) {
- ORR(SCRATCHREG1, SCRATCHREG1, AssumeMakeOperand2(rmode << 22));
- }
- VMSR(SCRATCHREG1);
- VCMP(fpr.R(fs), fpr.R(fs));
- VCVT(fpr.R(fd), fpr.R(fs), TO_INT | IS_SIGNED);
- VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
- SetCC(CC_VS);
- MOVIU2F(fpr.R(fd), 0x7FFFFFFF, SCRATCHREG1);
- SetCC(CC_AL);
- VMSR(SCRATCHREG2);
-}
-
-void ArmJit::Comp_FPU2op(MIPSOpcode op) {
- CONDITIONAL_DISABLE(FPU);
-
- int fs = _FS;
- int fd = _FD;
-
- switch (op & 0x3f) {
- case 4: //F(fd) = sqrtf(F(fs)); break; //sqrt
- fpr.MapDirtyIn(fd, fs);
- VSQRT(fpr.R(fd), fpr.R(fs));
- break;
- case 5: //F(fd) = fabsf(F(fs)); break; //abs
- fpr.MapDirtyIn(fd, fs);
- VABS(fpr.R(fd), fpr.R(fs));
- break;
- case 6: //F(fd) = F(fs); break; //mov
- fpr.MapDirtyIn(fd, fs);
- VMOV(fpr.R(fd), fpr.R(fs));
- break;
- case 7: //F(fd) = -F(fs); break; //neg
- fpr.MapDirtyIn(fd, fs);
- VNEG(fpr.R(fd), fpr.R(fs));
- break;
- case 12: //FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s
- CompFixedRoundConversion(fd, fs, 0);
- break;
- case 13: //FsI(fd) = Rto0(F(fs))); break; //trunc.w.s
- fpr.MapDirtyIn(fd, fs);
- VCMP(fpr.R(fs), fpr.R(fs));
- VCVT(fpr.R(fd), fpr.R(fs), TO_INT | IS_SIGNED | ROUND_TO_ZERO);
- VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
- SetCC(CC_VS);
- MOVIU2F(fpr.R(fd), 0x7FFFFFFF, SCRATCHREG1);
- SetCC(CC_AL);
- break;
- case 14: //FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s
- CompFixedRoundConversion(fd, fs, 1);
- break;
- case 15: //FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s
- CompFixedRoundConversion(fd, fs, 2);
- break;
- case 32: //F(fd) = (float)FsI(fs); break; //cvt.s.w
- fpr.MapDirtyIn(fd, fs);
- VCVT(fpr.R(fd), fpr.R(fs), TO_FLOAT | IS_SIGNED);
- break;
- case 36: //FsI(fd) = (int) F(fs); break; //cvt.w.s
- fpr.MapDirtyIn(fd, fs);
- VCMP(fpr.R(fs), fpr.R(fs));
- VCVT(fpr.R(fd), fpr.R(fs), TO_INT | IS_SIGNED);
- VMRS_APSR(); // Move FP flags from FPSCR to APSR (regular flags).
- SetCC(CC_VS);
- MOVIU2F(fpr.R(fd), 0x7FFFFFFF, SCRATCHREG1);
- SetCC(CC_AL);
- break;
- default:
- DISABLE;
- }
-}
-
-void ArmJit::Comp_mxc1(MIPSOpcode op)
-{
- CONDITIONAL_DISABLE(FPU_XFER);
-
- int fs = _FS;
- MIPSGPReg rt = _RT;
-
- switch ((op >> 21) & 0x1f)
- {
- case 0: // R(rt) = FI(fs); break; //mfc1
- if (rt == MIPS_REG_ZERO) {
- return;
- }
- gpr.MapReg(rt, MAP_DIRTY | MAP_NOINIT);
- if (fpr.IsMapped(fs)) {
- VMOV(gpr.R(rt), fpr.R(fs));
- } else {
- LDR(gpr.R(rt), CTXREG, fpr.GetMipsRegOffset(fs));
- }
- return;
-
- case 2: //cfc1
- if (rt == MIPS_REG_ZERO) {
- return;
- }
- if (fs == 31) {
- if (gpr.IsImm(MIPS_REG_FPCOND)) {
- gpr.MapReg(rt, MAP_DIRTY | MAP_NOINIT);
- LDR(gpr.R(rt), CTXREG, offsetof(MIPSState, fcr31));
- if (gpr.GetImm(MIPS_REG_FPCOND) & 1) {
- ORI2R(gpr.R(rt), gpr.R(rt), 0x1 << 23, SCRATCHREG2);
- } else {
- ANDI2R(gpr.R(rt), gpr.R(rt), ~(0x1 << 23), SCRATCHREG2);
- }
- } else {
- gpr.MapDirtyIn(rt, MIPS_REG_FPCOND);
- LDR(gpr.R(rt), CTXREG, offsetof(MIPSState, fcr31));
-#if PPSSPP_ARCH(ARMV7)
- BFI(gpr.R(rt), gpr.R(MIPS_REG_FPCOND), 23, 1);
-#else
- AND(SCRATCHREG1, gpr.R(MIPS_REG_FPCOND), Operand2(1)); // Just in case
- ANDI2R(gpr.R(rt), gpr.R(rt), ~(0x1 << 23), SCRATCHREG2); // SCRATCHREG2 won't be used, this turns into a simple BIC.
- ORR(gpr.R(rt), gpr.R(rt), Operand2(SCRATCHREG1, ST_LSL, 23));
-#endif
- }
- } else if (fs == 0) {
- gpr.SetImm(rt, MIPSState::FCR0_VALUE);
- } else {
- // Unsupported regs are always 0.
- gpr.SetImm(rt, 0);
- }
- return;
-
- case 4: //FI(fs) = R(rt); break; //mtc1
- if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0) {
- fpr.MapReg(fs, MAP_NOINIT);
- MOVI2F(fpr.R(fs), 0.0f, R0);
- } else {
- gpr.MapReg(rt);
- fpr.MapReg(fs, MAP_NOINIT);
- VMOV(fpr.R(fs), gpr.R(rt));
- }
- return;
-
- case 6: //ctc1
- if (fs == 31) {
- // Must clear before setting, since ApplyRoundingMode() assumes it was cleared.
- RestoreRoundingMode();
- bool wasImm = gpr.IsImm(rt);
- u32 immVal = -1;
- if (wasImm) {
- immVal = gpr.GetImm(rt);
- gpr.SetImm(MIPS_REG_FPCOND, (immVal >> 23) & 1);
- gpr.MapReg(rt);
- } else {
- gpr.MapDirtyIn(MIPS_REG_FPCOND, rt);
- }
-
- // Update MIPS state. Only these bits can be written (pspautotests cpu/fpu/fcr).
- ANDI2R(SCRATCHREG1, gpr.R(rt), 0x0181FFFF, SCRATCHREG2);
- STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, fcr31));
- if (!wasImm) {
-#if PPSSPP_ARCH(ARMV7)
- UBFX(gpr.R(MIPS_REG_FPCOND), gpr.R(rt), 23, 1);
-#else
- MOV(SCRATCHREG1, Operand2(gpr.R(rt), ST_LSR, 23));
- AND(gpr.R(MIPS_REG_FPCOND), SCRATCHREG1, Operand2(1));
-#endif
- UpdateRoundingMode();
- } else {
- UpdateRoundingMode(immVal);
- }
- ApplyRoundingMode();
- } else {
- Comp_Generic(op);
- }
- return;
- }
-}
-
-} // namespace MIPSComp
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp
deleted file mode 100644
index 249d4a9f69..0000000000
--- a/Core/MIPS/ARM/ArmCompLoadStore.cpp
+++ /dev/null
@@ -1,424 +0,0 @@
-// 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 "Core/MemMap.h"
-#include "Core/Config.h"
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/MIPS/MIPSCodeUtils.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-
-#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)
-
-// 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(flag) { Comp_Generic(op); return; }
-#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
-#define DISABLE { Comp_Generic(op); return; }
-
-namespace MIPSComp
-{
- using namespace ArmGen;
- using namespace ArmJitConstants;
-
- void ArmJit::SetR0ToEffectiveAddress(MIPSGPReg rs, s16 offset) {
- Operand2 op2;
- if (offset) {
- bool negated;
- if (TryMakeOperand2_AllowNegation(offset, op2, &negated)) {
- if (!negated)
- ADD(R0, gpr.R(rs), op2);
- else
- SUB(R0, gpr.R(rs), op2);
- } else {
- // Try to avoid using MOVT
- if (offset < 0) {
- gpr.SetRegImm(R0, (u32)(-offset));
- SUB(R0, gpr.R(rs), R0);
- } else {
- gpr.SetRegImm(R0, (u32)offset);
- ADD(R0, gpr.R(rs), R0);
- }
- }
- BIC(R0, R0, Operand2(0xC0, 4)); // &= 0x3FFFFFFF
- } else {
- BIC(R0, gpr.R(rs), Operand2(0xC0, 4)); // &= 0x3FFFFFFF
- }
- }
-
- void ArmJit::SetCCAndR0ForSafeAddress(MIPSGPReg rs, s16 offset, ARMReg tempReg, bool reverse) {
- SetR0ToEffectiveAddress(rs, offset);
-
- // There are three valid ranges. Each one gets a bit.
- const u32 BIT_SCRATCH = 1, BIT_RAM = 2, BIT_VRAM = 4;
- MOVI2R(tempReg, BIT_SCRATCH | BIT_RAM | BIT_VRAM);
-
- CMP(R0, AssumeMakeOperand2(PSP_GetScratchpadMemoryBase()));
- SetCC(CC_LO);
- BIC(tempReg, tempReg, BIT_SCRATCH);
- SetCC(CC_HS);
- CMP(R0, AssumeMakeOperand2(PSP_GetScratchpadMemoryEnd()));
- BIC(tempReg, tempReg, BIT_SCRATCH);
-
- // If it was in that range, later compares don't matter.
- CMP(R0, AssumeMakeOperand2(PSP_GetVidMemBase()));
- SetCC(CC_LO);
- BIC(tempReg, tempReg, BIT_VRAM);
- SetCC(CC_HS);
- CMP(R0, AssumeMakeOperand2(PSP_GetVidMemEnd()));
- BIC(tempReg, tempReg, BIT_VRAM);
-
- CMP(R0, AssumeMakeOperand2(PSP_GetKernelMemoryBase()));
- SetCC(CC_LO);
- BIC(tempReg, tempReg, BIT_RAM);
- SetCC(CC_HS);
- CMP(R0, AssumeMakeOperand2(PSP_GetUserMemoryEnd()));
- BIC(tempReg, tempReg, BIT_RAM);
-
- // If we left any bit set, the address is OK.
- SetCC(CC_AL);
- CMP(tempReg, 0);
- SetCC(reverse ? CC_EQ : CC_GT);
- }
-
- void ArmJit::Comp_ITypeMemLR(MIPSOpcode op, bool load) {
- CONDITIONAL_DISABLE(LSU);
- CheckMemoryBreakpoint();
- int offset = SignExtend16ToS32(op & 0xFFFF);
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- int o = op >> 26;
-
- if (!js.inDelaySlot && !jo.Disabled(JitDisable::LSU_UNALIGNED)) {
- // Optimisation: Combine to single unaligned load/store
- bool isLeft = (o == 34 || o == 42);
- CheckMemoryBreakpoint(1);
- MIPSOpcode nextOp = GetOffsetInstruction(1);
- // Find a matching shift in opposite direction with opposite offset.
- if (nextOp == (isLeft ? (op.encoding + (4<<26) - 3)
- : (op.encoding - (4<<26) + 3)))
- {
- EatInstruction(nextOp);
- nextOp = MIPSOpcode(((load ? 35 : 43) << 26) | ((isLeft ? nextOp : op) & 0x03FFFFFF)); //lw, sw
- Comp_ITypeMem(nextOp);
- return;
- }
- }
-
- u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF;
- bool doCheck = false;
- FixupBranch skip;
-
- if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) {
- u32 addr = iaddr & 0x3FFFFFFF;
- // Need to initialize since this only loads part of the register.
- // But rs no longer matters (even if rs == rt) since we have the address.
- gpr.MapReg(rt, load ? MAP_DIRTY : 0);
- gpr.SetRegImm(R0, addr & ~3);
-
- u8 shift = (addr & 3) * 8;
-
- switch (o) {
- case 34: // lwl
- LDR(R0, MEMBASEREG, R0);
- ANDI2R(gpr.R(rt), gpr.R(rt), 0x00ffffff >> shift, SCRATCHREG2);
- ORR(gpr.R(rt), gpr.R(rt), Operand2(R0, ST_LSL, 24 - shift));
- break;
-
- case 38: // lwr
- LDR(R0, MEMBASEREG, R0);
- ANDI2R(gpr.R(rt), gpr.R(rt), 0xffffff00 << (24 - shift), SCRATCHREG2);
- ORR(gpr.R(rt), gpr.R(rt), Operand2(R0, ST_LSR, shift));
- break;
-
- case 42: // swl
- LDR(SCRATCHREG2, MEMBASEREG, R0);
- // Don't worry, can't use temporary.
- ANDI2R(SCRATCHREG2, SCRATCHREG2, 0xffffff00 << shift, R0);
- ORR(SCRATCHREG2, SCRATCHREG2, Operand2(gpr.R(rt), ST_LSR, 24 - shift));
- STR(SCRATCHREG2, MEMBASEREG, R0);
- break;
-
- case 46: // swr
- LDR(SCRATCHREG2, MEMBASEREG, R0);
- // Don't worry, can't use temporary.
- ANDI2R(SCRATCHREG2, SCRATCHREG2, 0x00ffffff >> (24 - shift), R0);
- ORR(SCRATCHREG2, SCRATCHREG2, Operand2(gpr.R(rt), ST_LSL, shift));
- STR(SCRATCHREG2, MEMBASEREG, R0);
- break;
- }
- return;
- }
-
- // This gets hit in a few games, as a result of never-taken delay slots (some branch types
- // conditionally execute the delay slot instructions). Ignore in those cases.
- if (!js.inDelaySlot) {
- _dbg_assert_msg_(!gpr.IsImm(rs), "Invalid immediate address %08x? CPU bug?", iaddr);
- }
-
- if (load) {
- gpr.MapDirtyIn(rt, rs, false);
- } else {
- gpr.MapInIn(rt, rs);
- }
-
- if (!g_Config.bFastMemory && rs != MIPS_REG_SP) {
- SetCCAndR0ForSafeAddress(rs, offset, SCRATCHREG2, true);
- doCheck = true;
- } else {
- SetR0ToEffectiveAddress(rs, offset);
- }
- if (doCheck) {
- skip = B();
- }
- SetCC(CC_AL);
-
- // Need temp regs. TODO: Get from the regcache?
- static const ARMReg LR_SCRATCHREG3 = R9;
- static const ARMReg LR_SCRATCHREG4 = R10;
- if (load) {
- PUSH(1, LR_SCRATCHREG3);
- } else {
- PUSH(2, LR_SCRATCHREG3, LR_SCRATCHREG4);
- }
-
- // Here's our shift amount.
- AND(SCRATCHREG2, R0, 3);
- LSL(SCRATCHREG2, SCRATCHREG2, 3);
-
- // Now align the address for the actual read.
- BIC(R0, R0, 3);
-
- switch (o) {
- case 34: // lwl
- MOVI2R(LR_SCRATCHREG3, 0x00ffffff);
- LDR(R0, MEMBASEREG, R0);
- AND(gpr.R(rt), gpr.R(rt), Operand2(LR_SCRATCHREG3, ST_LSR, SCRATCHREG2));
- RSB(SCRATCHREG2, SCRATCHREG2, 24);
- ORR(gpr.R(rt), gpr.R(rt), Operand2(R0, ST_LSL, SCRATCHREG2));
- break;
-
- case 38: // lwr
- MOVI2R(LR_SCRATCHREG3, 0xffffff00);
- LDR(R0, MEMBASEREG, R0);
- LSR(R0, R0, SCRATCHREG2);
- RSB(SCRATCHREG2, SCRATCHREG2, 24);
- AND(gpr.R(rt), gpr.R(rt), Operand2(LR_SCRATCHREG3, ST_LSL, SCRATCHREG2));
- ORR(gpr.R(rt), gpr.R(rt), R0);
- break;
-
- case 42: // swl
- MOVI2R(LR_SCRATCHREG3, 0xffffff00);
- LDR(LR_SCRATCHREG4, MEMBASEREG, R0);
- AND(LR_SCRATCHREG4, LR_SCRATCHREG4, Operand2(LR_SCRATCHREG3, ST_LSL, SCRATCHREG2));
- RSB(SCRATCHREG2, SCRATCHREG2, 24);
- ORR(LR_SCRATCHREG4, LR_SCRATCHREG4, Operand2(gpr.R(rt), ST_LSR, SCRATCHREG2));
- STR(LR_SCRATCHREG4, MEMBASEREG, R0);
- break;
-
- case 46: // swr
- MOVI2R(LR_SCRATCHREG3, 0x00ffffff);
- LDR(LR_SCRATCHREG4, MEMBASEREG, R0);
- RSB(SCRATCHREG2, SCRATCHREG2, 24);
- AND(LR_SCRATCHREG4, LR_SCRATCHREG4, Operand2(LR_SCRATCHREG3, ST_LSR, SCRATCHREG2));
- RSB(SCRATCHREG2, SCRATCHREG2, 24);
- ORR(LR_SCRATCHREG4, LR_SCRATCHREG4, Operand2(gpr.R(rt), ST_LSL, SCRATCHREG2));
- STR(LR_SCRATCHREG4, MEMBASEREG, R0);
- break;
- }
-
- if (load) {
- POP(1, LR_SCRATCHREG3);
- } else {
- POP(2, LR_SCRATCHREG3, LR_SCRATCHREG4);
- }
-
- if (doCheck) {
- SetJumpTarget(skip);
- }
- }
-
- void ArmJit::Comp_ITypeMem(MIPSOpcode op)
- {
- CONDITIONAL_DISABLE(LSU);
- 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 offset = (signed short)(op&0xFFFF);
- bool load = false;
- MIPSGPReg rt = _RT;
- MIPSGPReg rs = _RS;
- int o = op>>26;
- // 16-bit accesses can go through the depth layout (Memory::DepthMirrored16).
- if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) {
- DISABLE;
- }
- if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) {
- // Don't load anything into $zr
- return;
- }
-
- u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF;
- bool doCheck = false;
- ARMReg addrReg = R0;
-
- switch (o) {
- case 32: //lb
- case 33: //lh
- case 35: //lw
- case 36: //lbu
- case 37: //lhu
- load = true;
- case 40: //sb
- case 41: //sh
- case 43: //sw
- // Map base register as pointer and go from there - if the displacement isn't too big.
- // This is faster if there are multiple loads from the same pointer. Need to hook up the MIPS analyzer..
- if (jo.cachePointers && g_Config.bFastMemory) {
- // ARM has smaller load/store immediate displacements than MIPS, 12 bits - and some memory ops only have 8 bits.
- int offsetRange = 0x3ff;
- if (o == 41 || o == 33 || o == 37 || o == 32)
- offsetRange = 0xff; // 8 bit offset only
- if (!gpr.IsImm(rs) && rs != rt && (offset <= offsetRange) && offset >= -offsetRange) {
- gpr.SpillLock(rs, rt);
- gpr.MapRegAsPointer(rs);
- gpr.MapReg(rt, load ? MAP_NOINIT : 0);
- switch (o) {
- case 35: LDR (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 37: LDRH (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 33: LDRSH(gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 36: LDRB (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 32: LDRSB(gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- // Store
- case 43: STR (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 41: STRH (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- case 40: STRB (gpr.R(rt), gpr.RPtr(rs), Operand2(offset, TYPE_IMM)); break;
- }
- gpr.ReleaseSpillLocks();
- break;
- }
- }
-
- if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) {
- // TODO: Avoid mapping a register for the "zero" register, use R0 instead.
-
- // We can compute the full address at compile time. Kickass.
- u32 addr = iaddr & 0x3FFFFFFF;
-
- if (addr == iaddr && offset == 0) {
- // It was already safe. Let's shove it into a reg and use it directly.
- load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs);
- addrReg = gpr.R(rs);
- } else {
- // In this case, only map rt. rs+offset will be in R0.
- gpr.MapReg(rt, load ? MAP_NOINIT : 0);
- gpr.SetRegImm(R0, addr);
- addrReg = R0;
- }
- } else {
- _dbg_assert_msg_(!gpr.IsImm(rs), "Invalid immediate address? CPU bug?");
- load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs);
-
- if (!g_Config.bFastMemory && rs != MIPS_REG_SP) {
- SetCCAndR0ForSafeAddress(rs, offset, SCRATCHREG2);
- doCheck = true;
- } else {
- SetR0ToEffectiveAddress(rs, offset);
- }
- addrReg = R0;
- }
-
- switch (o)
- {
- // Load
- case 35: LDR (gpr.R(rt), MEMBASEREG, addrReg); break;
- case 37: LDRH (gpr.R(rt), MEMBASEREG, addrReg); break;
- case 33: LDRSH(gpr.R(rt), MEMBASEREG, addrReg); break;
- case 36: LDRB (gpr.R(rt), MEMBASEREG, addrReg); break;
- case 32: LDRSB(gpr.R(rt), MEMBASEREG, addrReg); break;
- // Store
- case 43: STR (gpr.R(rt), MEMBASEREG, addrReg); break;
- case 41: STRH (gpr.R(rt), MEMBASEREG, addrReg); break;
- case 40: STRB (gpr.R(rt), MEMBASEREG, addrReg); break;
- }
- if (doCheck) {
- if (load) {
- SetCC(CC_EQ);
- MOVI2R(gpr.R(rt), 0);
- }
- SetCC(CC_AL);
- }
- break;
- case 34: //lwl
- case 38: //lwr
- load = true;
- case 42: //swl
- case 46: //swr
- Comp_ITypeMemLR(op, load);
- break;
- default:
- Comp_Generic(op);
- return;
- }
- }
-
- void ArmJit::Comp_StoreSync(MIPSOpcode op) {
- CONDITIONAL_DISABLE(LSU);
-
- DISABLE;
- }
-
- void ArmJit::Comp_Cache(MIPSOpcode op) {
- CONDITIONAL_DISABLE(LSU);
-
- int func = (op >> 16) & 0x1F;
-
- // See Int_Cache for the definitions.
- switch (func) {
- case 24: break;
- case 25: break;
- case 27: break;
- case 30: break;
- default:
- // Fall back to the interpreter.
- DISABLE;
- }
- }
-}
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompReplace.cpp b/Core/MIPS/ARM/ArmCompReplace.cpp
deleted file mode 100644
index dbe49cd256..0000000000
--- a/Core/MIPS/ARM/ArmCompReplace.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2013- 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 "Common/CPUDetect.h"
-#include "Core/MemMap.h"
-#include "Core/MIPS/JitCommon/JitCommon.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-
-namespace MIPSComp {
-
-int ArmJit::Replace_fabsf() {
- fpr.MapDirtyIn(0, 12);
- VABS(fpr.R(0), fpr.R(12));
- return 4; // Number of instructions in the MIPS function
-}
-
-}
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmCompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp
deleted file mode 100644
index d5569adf06..0000000000
--- a/Core/MIPS/ARM/ArmCompVFPU.cpp
+++ /dev/null
@@ -1,2419 +0,0 @@
-// 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
-#include
-#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)
diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp
deleted file mode 100644
index f4f3498edc..0000000000
--- a/Core/MIPS/ARM/ArmJit.cpp
+++ /dev/null
@@ -1,797 +0,0 @@
-// 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 "Common/Profiler/Profiler.h"
-
-#include "Common/Log.h"
-#include "Common/Serialize/Serializer.h"
-#include "Common/Serialize/SerializeFuncs.h"
-
-#include "Core/Reporting.h"
-#include "Core/Config.h"
-#include "Core/Core.h"
-#include "Core/CoreTiming.h"
-#include "Core/Debugger/Breakpoints.h"
-#include "Core/Debugger/SymbolMap.h"
-#include "Core/MemMap.h"
-
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/MIPS/MIPSCodeUtils.h"
-#include "Core/MIPS/Interpreter.h"
-#include "Core/MIPS/MIPSTables.h"
-#include "Core/HLE/ReplaceTables.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
-
-#include "ArmRegCache.h"
-#include "ArmJit.h"
-#include "CPUDetect.h"
-
-#include "ext/disarm.h"
-
-using namespace ArmJitConstants;
-
-void DisassembleArm(const u8 *data, int size) {
- char temp[256];
- for (int i = 0; i < size; i += 4) {
- const u32 *codePtr = (const u32 *)(data + i);
- u32 inst = codePtr[0];
- u32 next = (i < size - 4) ? codePtr[1] : 0;
- // MAGIC SPECIAL CASE for MOVW/MOVT readability!
- if ((inst & 0x0FF00000) == 0x03000000 && (next & 0x0FF00000) == 0x03400000) {
- u32 low = ((inst & 0x000F0000) >> 4) | (inst & 0x0FFF);
- u32 hi = ((next & 0x000F0000) >> 4) | (next & 0x0FFF);
- int reg0 = (inst & 0x0000F000) >> 12;
- int reg1 = (next & 0x0000F000) >> 12;
- if (reg0 == reg1) {
- snprintf(temp, sizeof(temp), "%08x MOV32 %s, %04x%04x", (u32)inst, ArmRegName(reg0), hi, low);
- INFO_LOG(Log::JIT, "A: %s", temp);
- i += 4;
- continue;
- }
- }
- ArmDis((u32)codePtr, inst, temp, sizeof(temp), true);
- INFO_LOG(Log::JIT, "A: %s", temp);
- }
-}
-
-static u32 JitBreakpoint(uint32_t addr) {
- // Should we skip this breakpoint?
- if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc || g_breakpoints.CheckSkipFirst() == addr)
- return 0;
-
- BreakAction result = g_breakpoints.ExecBreakPoint(addr);
- if ((result & BREAK_ACTION_PAUSE) == 0)
- return 0;
-
- return 1;
-}
-
-static u32 JitMemCheck(u32 pc) {
- if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
- return 0;
-
- // Note: pc may be the delay slot.
- const auto op = Memory::Read_Instruction(pc);
- s32 offset = SignExtend16ToS32(op & 0xFFFF);
- if (MIPSGetInfo(op) & IS_VFPU)
- offset &= 0xFFFC;
- u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
-
- g_breakpoints.ExecOpMemCheck(addr, pc);
- return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
-}
-
-namespace MIPSComp {
-using namespace ArmGen;
-using namespace ArmJitConstants;
-
-ArmJit::ArmJit(MIPSState *mipsState) : blocks(mipsState, this), gpr(mipsState, &js, &jo), fpr(mipsState, &js, &jo), mips_(mipsState) {
- logBlocks = 0;
- dontLogBlocks = 0;
- blocks.Init();
- gpr.SetEmitter(this);
- fpr.SetEmitter(this);
- AllocCodeSpace(1024 * 1024 * 16); // 32MB is the absolute max because that's what an ARM branch instruction can reach, backwards and forwards.
- GenerateFixedCode();
-
- INFO_LOG(Log::JIT, "ARM JIT initialized: %lld MB of code space", (long long)(GetSpaceLeft() / (1024 * 1024)));
-
- js.startDefaultPrefix = mips_->HasDefaultPrefix();
-
- // The debugger sets this so that "go" on a breakpoint will actually... go.
- // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
- g_breakpoints.SetSkipFirst(0);
-}
-
-ArmJit::~ArmJit() {
-}
-
-void ArmJit::DoState(PointerWrap &p)
-{
- auto s = p.Section("Jit", 1, 2);
- if (!s)
- return;
-
- Do(p, js.startDefaultPrefix);
- if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
- WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
- }
- if (s >= 2) {
- Do(p, js.hasSetRounding);
- if (p.mode == PointerWrap::MODE_READ) {
- js.lastSetRounding = 0;
- }
- } else {
- js.hasSetRounding = 1;
- }
-
- // The debugger sets this so that "go" on a breakpoint will actually... go.
- // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
- g_breakpoints.SetSkipFirst(0);
-}
-
-void ArmJit::UpdateFCR31() {
- // fcr31 can reach a non-default mode without a compiled ctc1 (an interpreted one, a savestate,
- // a core switch, a thread switch). Blocks compiled before that don't switch modes around
- // syscalls or conversions, so they're thrown away, at the next safe point.
- if ((mips_->fcr31 & 0x01000003) && !js.hasSetRounding) {
- js.hasSetRounding = true;
- js.lastSetRounding = true;
- mips_->ClearJitCacheDeferred();
- }
-}
-
-void ArmJit::FlushAll()
-{
- gpr.FlushAll();
- fpr.FlushAll();
- FlushPrefixV();
-}
-
-void ArmJit::FlushPrefixV() {
- if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {
- // They started default, we never modified in memory, and they're default now.
- // No reason to modify memory. This is common at end of blocks. Just clear dirty.
- js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);
- js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);
- js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);
- return;
- }
-
- if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {
- gpr.SetRegImm(SCRATCHREG1, js.prefixS);
- STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_SPREFIX]));
- js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
- }
-
- if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
- gpr.SetRegImm(SCRATCHREG1, js.prefixT);
- STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_TPREFIX]));
- js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
- }
-
- if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
- gpr.SetRegImm(SCRATCHREG1, js.prefixD);
- STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_DPREFIX]));
- js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
- }
-
- // If we got here, we must've written prefixes to memory in this block.
- js.blockWrotePrefixes = true;
-}
-
-void ArmJit::ClearCache()
-{
- blocks.Clear();
- ClearCodeSpace(0);
- GenerateFixedCode();
- // The compile path may have moved, and replacement hooks' block shadow entries point to it.
- Replacement_SetBlockShadowHook(GetBlockShadowHookValue());
-}
-
-void ArmJit::InvalidateCacheAt(u32 em_address, int length) {
- if (blocks.RangeMayHaveBlocks(em_address, em_address + length)) {
- blocks.InvalidateICache(em_address, length);
- }
-}
-
-void ArmJit::EatInstruction(MIPSOpcode op) {
- MIPSInfo info = MIPSGetInfo(op);
- if (info & DELAYSLOT) {
- ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");
- }
- if (js.inDelaySlot) {
- ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");
- }
-
- CheckJitBreakpoint(GetCompilerPC() + 4, 0);
- js.numInstructions++;
- js.compilerPC += 4;
- js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
-}
-
-void ArmJit::CompileDelaySlot(int flags) {
- // Need to offset the downcount which was already incremented for the branch + delay slot.
- CheckJitBreakpoint(GetCompilerPC() + 4, -2);
-
- // preserve flag around the delay slot! Maybe this is not always necessary on ARM where
- // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the
- // delay slot, we're screwed.
- if (flags & DELAYSLOT_SAFE)
- MRS(R8); // Save flags register. R8 is preserved through function calls and is not allocated.
-
- js.inDelaySlot = true;
- MIPSOpcode op = GetOffsetInstruction(1);
- MIPSCompileOp(op, this);
- js.inDelaySlot = false;
-
- if (flags & DELAYSLOT_FLUSH)
- FlushAll();
- if (flags & DELAYSLOT_SAFE)
- _MSR(true, false, R8); // Restore flags register
-}
-
-void ArmJit::Compile(u32 em_address) {
- PROFILE_THIS_SCOPE("jitc");
-
- if (!Memory::IsValid4AlignedAddress(em_address)) {
- Core_ExecException(em_address, em_address, ExecExceptionType::JUMP);
- return;
- }
-
- if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
- ClearCache();
- }
-
- BeginWrite(JitBlockCache::MAX_BLOCK_INSTRUCTIONS * 16);
-
- int block_num = blocks.AllocateBlock(em_address);
- JitBlock *b = blocks.GetBlock(block_num);
- b->DoIntegrityCheck(em_address, block_num, "BeforeJit");
- DoJit(em_address, b);
- b->DoIntegrityCheck(em_address, block_num, "BeforeFinalize");
- blocks.FinalizeBlock(block_num, jo.enableBlocklink);
- b->DoIntegrityCheck(em_address, block_num, "AfterFinalize");
- EndWrite();
-
- _dbg_assert_(js.nextExit <= 2);
-
- bool cleanSlate = false;
-
- if (js.hasSetRounding && !js.lastSetRounding) {
- WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");
- // Won't loop, since hasSetRounding is only ever set to 1.
- js.lastSetRounding = js.hasSetRounding;
- cleanSlate = true;
- }
-
- // Drat. The VFPU hit an uneaten prefix at the end of a block.
- if (js.startDefaultPrefix && js.MayHavePrefix()) {
- WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4);
- js.LogPrefix();
-
- // Let's try that one more time. We won't get back here because we toggled the value.
- js.startDefaultPrefix = false;
- cleanSlate = true;
- }
-
- if (cleanSlate) {
- // Our assumptions are all wrong so it's clean-slate time.
- ClearCache();
- Compile(em_address);
- }
-}
-
-void ArmJit::RunLoopUntil(u64 globalticks) {
- PROFILE_THIS_SCOPE("jit");
- ((void (*)())enterDispatcher)();
-}
-
-u32 ArmJit::GetCompilerPC() {
- return js.compilerPC;
-}
-
-MIPSOpcode ArmJit::GetOffsetInstruction(int offset) {
- const u32 addr = GetCompilerPC() + 4 * offset;
- return Memory::IsValid4AlignedAddress(addr) ? ReadExecutedOp(addr) : Memory::Read_Instruction(addr);
-}
-
-void ArmJit::DoJit(u32 em_address, JitBlock *b) {
- js.Begin(b);
-
- // We add a downcount flag check before the block, used when entering from a linked block.
- // The last block decremented downcounter, and the flag should still be available.
- // Got three variants here of where we position the code, needs detailed benchmarking.
-
- FixupBranch bail;
- if (jo.useBackJump) {
- // Moves the MOVI2R and B *before* checkedEntry, and just branch backwards there.
- // Speedup seems to be zero unfortunately but I guess it may vary from device to device.
- // Not intrusive so keeping it around here to experiment with, may help on ARMv6 due to
- // large/slow construction of 32-bit immediates?
- JumpTarget backJump = GetCodePtr();
- gpr.SetRegImm(R0, js.blockStart);
- B((const void *)outerLoopPCInR0);
- b->checkedEntry = GetCodePtr();
- SetCC(CC_LT);
- B(backJump);
- SetCC(CC_AL);
- } else if (jo.useForwardJump) {
- b->checkedEntry = GetCodePtr();
- SetCC(CC_LT);
- bail = B();
- SetCC(CC_AL);
- } else {
- b->checkedEntry = GetCodePtr();
- SetCC(CC_LT);
- gpr.SetRegImm(R0, js.blockStart);
- B((const void *)outerLoopPCInR0);
- SetCC(CC_AL);
- }
-
- b->normalEntry = GetCodePtr();
-
- gpr.Start();
- fpr.Start();
-
- js.numInstructions = 0;
- while (js.compiling)
- {
- gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages
- // Jit breakpoints are quite fast, so let's do them in release too.
- CheckJitBreakpoint(GetCompilerPC(), 0);
-
- MIPSOpcode inst = ReadExecutedOp(GetCompilerPC());
- //MIPSInfo info = MIPSGetInfo(inst);
- //if (info & IS_VFPU) {
- // logBlocks = 1;
- //}
-
- js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
-
- MIPSCompileOp(inst, this);
-
- js.compilerPC += 4;
- js.numInstructions++;
-
- if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
- gpr.FlushAll();
- }
- if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
- fpr.FlushAll();
- FlushPrefixV();
- }
-
- // Safety check, in case we get a bunch of really large jit ops without a lot of branching.
- if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS)
- {
- FlushAll();
- WriteExit(GetCompilerPC(), js.nextExit++);
- js.compiling = false;
- }
- }
-
- if (jo.useForwardJump) {
- SetJumpTarget(bail);
- gpr.SetRegImm(R0, js.blockStart);
- B((const void *)outerLoopPCInR0);
- }
-
- FlushLitPool();
-
- char temp[256];
- if (logBlocks > 0 && dontLogBlocks == 0) {
- INFO_LOG(Log::JIT, "=============== mips ===============");
- for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) {
- MIPSDisAsm(Memory::ReadUnchecked_Instruction(cpc), cpc, temp, sizeof(temp), true);
- INFO_LOG(Log::JIT, "M: %08x %s", cpc, temp);
- }
- }
-
- b->codeSize = GetCodePtr() - b->normalEntry;
-
- if (logBlocks > 0 && dontLogBlocks == 0) {
- INFO_LOG(Log::JIT, "=============== ARM ===============");
- DisassembleArm(b->normalEntry, GetCodePtr() - b->normalEntry);
- }
- if (logBlocks > 0)
- logBlocks--;
- if (dontLogBlocks > 0)
- dontLogBlocks--;
-
- // Don't forget to zap the newly written instructions in the instruction cache!
- FlushIcache();
-
- b->originalSize = js.numInstructions;
-}
-
-bool ArmJit::DescribeCodePtr(const u8 *ptr, std::string &name)
-{
- // TODO: Not used by anything yet (except the modified VerySleepy on Windows)
- return false;
-}
-
-void ArmJit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
- if (PlatformIsWXExclusive()) {
- ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
- }
-
- ARMXEmitter emit(exitPoint);
- u32 op = *((const u32 *)emit.GetCodePointer());
- bool prelinked = (op & 0xFF000000) == 0xEA000000;
- // Jump directly to the block, yay.
- emit.B(checkedEntry);
-
- if (!prelinked) {
- do {
- op = *((const u32 *)emit.GetCodePointer());
- // Overwrite whatever is here with a breakpoint.
- emit.BKPT(1);
- // Stop after overwriting the next unconditional branch or BKPT.
- // It can be a BKPT if we unlinked, and are now linking a different one.
- } while ((op & 0xFF000000) != 0xEA000000 && (op & 0xFFF000F0) != 0xE1200070);
- }
- emit.FlushIcache();
- if (PlatformIsWXExclusive()) {
- ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
- }
-}
-
-void ArmJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
- if (PlatformIsWXExclusive()) {
- ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
- }
- // Send anyone who tries to run this block back to the dispatcher.
- // Not entirely ideal, but .. pretty good.
- // I hope there's enough space...
- // checkedEntry is the only "linked" entrance so it's enough to overwrite that.
- ARMXEmitter emit(checkedEntry);
- emit.MOVI2R(R0, originalAddress);
- emit.STR(R0, CTXREG, offsetof(MIPSState, pc));
- emit.B(MIPSComp::jit->GetDispatcher());
- emit.FlushIcache();
- if (PlatformIsWXExclusive()) {
- ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
- }
-}
-
-void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
-{
- // We get here if we execute the first instruction of a replaced function. This means
- // that we do need to return to RA.
-
- // Inlined function calls (caught in jal) are handled differently.
-
- // Memory never holds these: if it does, the game is executing data. Let the interpreter raise it.
- if (Memory::Read_Instruction(GetCompilerPC()).encoding == op.encoding) {
- Comp_Generic(op);
- return;
- }
-
- int index = op.encoding & MIPS_REPLACEMENT_INDEX_MASK;
-
- const ReplacementTableEntry *entry = GetReplacementFunc(index);
- if (!entry) {
- ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
- return;
- }
-
- u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
- bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
- if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
- // We don't need to disable hooks, the code will still run.
- if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
- // Any breakpoint at the func entry was already tripped, so we can still run the replacement.
- // That's a common case - just to see how often the replacement hits.
- disabled = g_breakpoints.RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
- }
- }
-
- if (disabled) {
- MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
- } else if (entry->jitReplaceFunc) {
- MIPSReplaceFunc repl = entry->jitReplaceFunc;
- int cycles = (this->*repl)();
-
- if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
- // Compile the original instruction at this address. We ignore cycles for hooks.
- MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
- } else {
- FlushAll();
- // Flushed, so R1 is safe.
- LDR(R1, CTXREG, MIPS_REG_RA * 4);
- js.downcountAmount += cycles;
- WriteExitDestInR(R1);
- js.compiling = false;
- }
- } else if (entry->replaceFunc) {
- FlushAll();
- SaveDowncount();
- RestoreRoundingMode();
- gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
- MovToPC(SCRATCHREG1);
-
- // Standard function call, nothing fancy.
- // The function returns the number of cycles it took in EAX.
- if (BLInRange((const void *)(entry->replaceFunc))) {
- BL((const void *)(entry->replaceFunc));
- } else {
- MOVI2R(R0, (uintptr_t)entry->replaceFunc);
- BL(R0);
- }
-
- if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
- // Compile the original instruction at this address. We ignore cycles for hooks.
- ApplyRoundingMode();
- RestoreDowncount();
- MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
- } else {
- ApplyRoundingMode();
- RestoreDowncount();
-
- CMPI2R(R0, 0, SCRATCHREG2);
- FixupBranch positive = B_CC(CC_GE);
-
- RSB(R0, R0, Operand2(0));
- MovFromPC(R1);
- FixupBranch done = B();
-
- SetJumpTarget(positive);
- LDR(R1, CTXREG, MIPS_REG_RA * 4);
-
- SetJumpTarget(done);
- WriteDownCountR(R0);
- WriteExitDestInR(R1);
- js.compiling = false;
- }
- } else {
- ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
- }
-}
-
-void ArmJit::Comp_Generic(MIPSOpcode op)
-{
- FlushAll();
- MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
- const MIPSInfo info = MIPSGetInfo(op);
- if (func)
- {
- SaveDowncount();
- // Interpreted ops run in the guest's rounding mode, like on the interpreter. A syscall is host code
- // (like a compiled one), and only ctc1 to fcr31 changes the mode, so only those need it switched.
- const bool isSyscall = (info & IS_SYSCALL) != 0;
- const bool setsMode = MIPS_IS_CTC1_FCR31(op.encoding);
- if (isSyscall)
- RestoreRoundingMode();
- if (setsMode)
- UpdateRoundingMode();
- gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
- MovToPC(SCRATCHREG1);
- gpr.SetRegImm(R0, op.encoding);
- QuickCallFunction(R1, (void *)&MIPSInterpretTrampoline);
- if (setsMode)
- RestoreRoundingMode(true);
- if (isSyscall || setsMode)
- ApplyRoundingMode(setsMode);
- RestoreDowncount();
- }
-
- if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0)
- {
- // If it does eat them, it'll happen in MIPSCompileOp().
- if ((info & OUT_EAT_PREFIX) == 0)
- js.PrefixUnknown();
-
- // Even if DISABLE'd, we want to set this flag so we overwrite.
- if ((info & OUT_VFPU_PREFIX) != 0)
- js.blockWrotePrefixes = true;
- }
-}
-
-void ArmJit::MovFromPC(ARMReg r) {
- LDR(r, CTXREG, offsetof(MIPSState, pc));
-}
-
-void ArmJit::MovToPC(ARMReg r) {
- STR(r, CTXREG, offsetof(MIPSState, pc));
-}
-
-void ArmJit::SaveDowncount() {
- if (jo.downcountInRegister)
- STR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
-}
-
-void ArmJit::RestoreDowncount() {
- if (jo.downcountInRegister)
- LDR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
-}
-
-void ArmJit::WriteDownCount(int offset) {
- if (jo.downcountInRegister) {
- int theDowncount = js.downcountAmount + offset;
- Operand2 op2;
- if (TryMakeOperand2(theDowncount, op2)) {
- SUBS(DOWNCOUNTREG, DOWNCOUNTREG, op2);
- } else {
- // Should be fine to use R2 here, flushed the regcache anyway.
- // If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
- gpr.SetRegImm(R2, theDowncount);
- SUBS(DOWNCOUNTREG, DOWNCOUNTREG, R2);
- }
- } else {
- int theDowncount = js.downcountAmount + offset;
- LDR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
- Operand2 op2;
- if (TryMakeOperand2(theDowncount, op2)) {
- SUBS(SCRATCHREG2, SCRATCHREG2, op2);
- } else {
- // Should be fine to use R2 here, flushed the regcache anyway.
- // If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
- gpr.SetRegImm(R2, theDowncount);
- SUBS(SCRATCHREG2, SCRATCHREG2, R2);
- }
- STR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
- }
-}
-
-// Abuses R2
-void ArmJit::WriteDownCountR(ARMReg reg) {
- if (jo.downcountInRegister) {
- SUBS(DOWNCOUNTREG, DOWNCOUNTREG, reg);
- } else {
- LDR(R2, CTXREG, offsetof(MIPSState, downcount));
- SUBS(R2, R2, reg);
- STR(R2, CTXREG, offsetof(MIPSState, downcount));
- }
-}
-
-// Destroys SCRATCHREG2. Does not destroy SCRATCHREG1.
-void ArmJit::RestoreRoundingMode(bool force) {
- // If the game has never set an interesting rounding mode, we can safely skip this.
- if (force || js.hasSetRounding) {
- QuickCallFunction(R1, restoreRoundingMode);
- }
-}
-
-// Does not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
-void ArmJit::ApplyRoundingMode(bool force) {
- // If the game has never set an interesting rounding mode, we can safely skip this.
- if (force || js.hasSetRounding) {
- QuickCallFunction(R1, applyRoundingMode);
- }
-}
-
-// Does (must!) not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
-void ArmJit::UpdateRoundingMode(u32 fcr31) {
- // We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.
- // The fcr31 parameter is -1 when not known at compile time, so we just assume it was changed.
- if (fcr31 & 0x01000003) {
- js.hasSetRounding = true;
- }
-}
-
-// IDEA - could have a WriteDualExit that takes two destinations and two condition flags,
-// and just have conditional that set PC "twice". This only works when we fall back to dispatcher
-// though, as we need to have the SUBS flag set in the end. So with block linking in the mix,
-// I don't think this gives us that much benefit.
-void ArmJit::WriteExit(u32 destination, int exit_num)
-{
- // NOTE: Can't blindly check for bad destination addresses here, sometimes exits with bad destinations are written intentionally (like breaks).
- _assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
-
- WriteDownCount();
- //If nobody has taken care of this yet (this can be removed when all branches are done)
- JitBlock *b = js.curBlock;
- b->exitAddress[exit_num] = destination;
- b->exitPtrs[exit_num] = GetWritableCodePtr();
-
- // Link opportunity!
- int block = blocks.GetBlockNumberFromStartAddress(destination);
- if (block >= 0 && jo.enableBlocklink) {
- // It exists! Joy of joy!
- B(blocks.GetBlock(block)->checkedEntry);
- b->linkStatus[exit_num] = true;
- } else {
- gpr.SetRegImm(R0, destination);
- B((const void *)dispatcherPCInR0);
- }
-}
-
-void ArmJit::WriteExitDestInR(ARMReg Reg)
-{
- // TODO: If not fast memory, check for invalid address in reg and trigger exception.
- MovToPC(Reg);
- WriteDownCount();
- // TODO: shouldn't need an indirect branch here...
- B((const void *)dispatcher);
-}
-
-void ArmJit::WriteSyscallExit()
-{
- WriteDownCount();
- B((const void *)dispatcherCheckCoreState);
-}
-
-bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
- if (g_breakpoints.NeedsBreakCheckAt(addr)) {
- MRS(R8);
- FlushAll();
- MOVI2R(SCRATCHREG1, GetCompilerPC());
- MovToPC(SCRATCHREG1);
- SaveDowncount();
- RestoreRoundingMode();
- MOVI2R(R0, addr);
- QuickCallFunction(SCRATCHREG2, &JitBreakpoint);
-
- // If 0, the conditional breakpoint wasn't taken.
- CMPI2R(R0, 0, SCRATCHREG2);
- FixupBranch skip = B_CC(CC_EQ);
- WriteDownCount(downcountOffset);
- ApplyRoundingMode();
- RestoreDowncount();
- B((const void *)dispatcherCheckCoreState);
- SetJumpTarget(skip);
-
- ApplyRoundingMode();
- _MSR(true, false, R8);
- return true;
- }
-
- return false;
-}
-
-bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) {
- if (g_breakpoints.HasMemChecks()) {
- int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
-
- MRS(R8);
- FlushAll();
- SaveDowncount();
- RestoreRoundingMode();
- MOVI2R(R0, GetCompilerPC());
- MovToPC(R0);
- if (off != 0)
- ADDI2R(R0, R0, off * 4, SCRATCHREG2);
- QuickCallFunction(SCRATCHREG2, &JitMemCheck);
-
- // If 0, the breakpoint wasn't tripped.
- CMPI2R(R0, 0, SCRATCHREG2);
- FixupBranch skip = B_CC(CC_EQ);
- WriteDownCount(-1 - off);
- ApplyRoundingMode();
- RestoreDowncount();
- B((const void *)dispatcherCheckCoreState);
- SetJumpTarget(skip);
-
- ApplyRoundingMode();
- _MSR(true, false, R8);
- return true;
- }
-
- return false;
-}
-
-void ArmJit::Comp_DoNothing(MIPSOpcode op) { }
-
-} // namespace
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h
deleted file mode 100644
index 68de99d7b7..0000000000
--- a/Core/MIPS/ARM/ArmJit.h
+++ /dev/null
@@ -1,288 +0,0 @@
-// 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/.
-
-#pragma once
-
-#include "Common/CPUDetect.h"
-#include "Common/ArmCommon.h"
-#include "Common/ArmEmitter.h"
-#include "Core/MIPS/JitCommon/JitState.h"
-#include "Core/MIPS/JitCommon/JitBlockCache.h"
-#include "Core/MIPS/JitCommon/JitCommon.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
-#include "Core/MIPS/MIPSVFPUUtils.h"
-
-#ifndef offsetof
-#include "stddef.h"
-#endif
-
-namespace MIPSComp {
-
-class ArmJit : public ArmGen::ARMXCodeBlock, public JitInterface, public MIPSFrontendInterface {
-public:
- ArmJit(MIPSState *mipsState);
- virtual ~ArmJit();
-
- void DoState(PointerWrap &p) override;
-
- const JitOptions &GetJitOptions() { return jo; }
-
- // Compiled ops should ignore delay slots
- // the compiler will take care of them by itself
- // OR NOT
- void Comp_Generic(MIPSOpcode op) override;
-
- void RunLoopUntil(u64 globalticks) override;
-
- void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
-
- const u8 *GetCrashHandler() const override { return crashHandler; }
- bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
- bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
-
- void Comp_ReplacementFunc(MIPSOpcode op) override;
-
- // Ops
- void Comp_ITypeMem(MIPSOpcode op) override;
- void Comp_StoreSync(MIPSOpcode op) override;
- void Comp_Cache(MIPSOpcode op) override;
-
- void Comp_RelBranch(MIPSOpcode op) override;
- void Comp_RelBranchRI(MIPSOpcode op) override;
- void Comp_FPUBranch(MIPSOpcode op) override;
- void Comp_FPULS(MIPSOpcode op) override;
- void Comp_FPUComp(MIPSOpcode op) override;
- void Comp_Jump(MIPSOpcode op) override;
- void Comp_JumpReg(MIPSOpcode op) override;
- void Comp_Syscall(MIPSOpcode op) override;
- void Comp_Break(MIPSOpcode op) override;
-
- void Comp_IType(MIPSOpcode op) override;
- void Comp_RType2(MIPSOpcode op) override;
- void Comp_RType3(MIPSOpcode op) override;
- void Comp_ShiftType(MIPSOpcode op) override;
- void Comp_Allegrex(MIPSOpcode op) override;
- void Comp_Allegrex2(MIPSOpcode op) override;
- void Comp_VBranch(MIPSOpcode op) override;
- void Comp_MulDivType(MIPSOpcode op) override;
- void Comp_Special3(MIPSOpcode op) override;
-
- void Comp_FPU3op(MIPSOpcode op) override;
- void Comp_FPU2op(MIPSOpcode op) override;
- void Comp_mxc1(MIPSOpcode op) override;
-
- void Comp_DoNothing(MIPSOpcode op) override;
-
- void Comp_SV(MIPSOpcode op) override;
- void Comp_SVQ(MIPSOpcode op) override;
- void Comp_VPFX(MIPSOpcode op) override;
- void Comp_VVectorInit(MIPSOpcode op) override;
- void Comp_VMatrixInit(MIPSOpcode op) override;
- void Comp_VDot(MIPSOpcode op) override;
- void Comp_VecDo3(MIPSOpcode op) override;
- void Comp_VV2Op(MIPSOpcode op) override;
- void Comp_Mftv(MIPSOpcode op) override;
- void Comp_Vmfvc(MIPSOpcode op) override;
- void Comp_Vmtvc(MIPSOpcode op) override;
- void Comp_Vmmov(MIPSOpcode op) override;
- void Comp_VScl(MIPSOpcode op) override;
- void Comp_Vmmul(MIPSOpcode op) override;
- void Comp_Vmscl(MIPSOpcode op) override;
- void Comp_Vtfm(MIPSOpcode op) override;
- void Comp_VHdp(MIPSOpcode op) override;
- void Comp_VCrs(MIPSOpcode op) override;
- void Comp_VDet(MIPSOpcode op) override;
- void Comp_Vi2x(MIPSOpcode op) override;
- void Comp_Vx2i(MIPSOpcode op) override;
- void Comp_Vf2i(MIPSOpcode op) override;
- void Comp_Vi2f(MIPSOpcode op) override;
- void Comp_Vh2f(MIPSOpcode op) override;
- void Comp_Vcst(MIPSOpcode op) override;
- void Comp_Vhoriz(MIPSOpcode op) override;
- void Comp_VRot(MIPSOpcode op) override;
- void Comp_VIdt(MIPSOpcode op) override;
- void Comp_Vcmp(MIPSOpcode op) override;
- void Comp_Vcmov(MIPSOpcode op) override;
- void Comp_Viim(MIPSOpcode op) override;
- void Comp_Vfim(MIPSOpcode op) override;
- void Comp_VCrossQuat(MIPSOpcode op) override;
- void Comp_Vsgn(MIPSOpcode op) override;
- void Comp_Vocp(MIPSOpcode op) override;
- void Comp_ColorConv(MIPSOpcode op) override;
- void Comp_Vbfy(MIPSOpcode op) override;
-
- int Replace_fabsf() override;
-
- JitBlockCache *GetBlockCache() override { return &blocks; }
- JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; }
-
- void ClearCache() override;
- void InvalidateCacheAt(u32 em_address, int length = 4) override;
- void UpdateFCR31() override;
-
- void EatPrefix() override { js.EatPrefix(); }
-
- const u8 *GetDispatcher() const override {
- return dispatcher;
- }
- u32 GetBlockShadowHookValue() const override {
- return (u32)(blockShadowHook - GetBasePtr());
- }
- bool IsAtDispatchFetch(const u8 *ptr) const override {
- return ptr == dispatcherFetch;
- }
-
- void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
- void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;
-
- const u8 *GetCodeBase() const override { return GetCodePtr(); }
-
-private:
- void DoJit(u32 em_address, JitBlock *b);
-
- void GenerateFixedCode();
- void FlushAll();
- void FlushPrefixV();
-
- u32 GetCompilerPC();
- void CompileDelaySlot(int flags);
- void EatInstruction(MIPSOpcode op);
- MIPSOpcode GetOffsetInstruction(int offset);
-
- void WriteDownCount(int offset = 0);
- void WriteDownCountR(ArmGen::ARMReg reg);
- void RestoreRoundingMode(bool force = false);
- void ApplyRoundingMode(bool force = false);
- void UpdateRoundingMode(u32 fcr31 = -1);
- void MovFromPC(ArmGen::ARMReg r);
- void MovToPC(ArmGen::ARMReg r);
-
- void SaveDowncount();
- void RestoreDowncount();
-
- void WriteExit(u32 destination, int exit_num);
- void WriteExitDestInR(ArmGen::ARMReg Reg);
- void WriteSyscallExit();
- bool CheckJitBreakpoint(u32 addr, int downcountOffset);
- bool CheckMemoryBreakpoint(int instructionOffset = 0);
-
- // Utility compilation functions
- void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely);
- void BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely);
- void BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool likely);
- void BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely);
-
- // Utilities to reduce duplicated code
- void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARMXEmitter::*arith)(ArmGen::ARMReg dst, ArmGen::ARMReg src, ArmGen::Operand2 op2), bool (ARMXEmitter::*tryArithI2R)(ArmGen::ARMReg dst, ArmGen::ARMReg src, u32 val), u32 (*eval)(u32 a, u32 b));
- void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARMXEmitter::*arithOp2)(ArmGen::ARMReg dst, ArmGen::ARMReg rm, ArmGen::Operand2 rn), bool (ARMXEmitter::*tryArithI2R)(ArmGen::ARMReg dst, ArmGen::ARMReg rm, u32 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false);
-
- void CompShiftImm(MIPSOpcode op, ArmGen::ShiftType shiftType, int sa);
- void CompShiftVar(MIPSOpcode op, ArmGen::ShiftType shiftType);
- void CompVrotShuffle(u8 *dregs, int imm, VectorSize sz, bool negSin);
- void CompVV2OpCall(MIPSOpcode op);
- void CompFixedRoundConversion(int fd, int fs, int rmode);
-
- void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz);
- void ApplyPrefixD(const u8 *vregs, VectorSize sz);
- void GetVectorRegsPrefixS(u8 *regs, VectorSize sz, int vectorReg) {
- _assert_(js.prefixSFlag & JitState::PREFIX_KNOWN);
- GetVectorRegs(regs, sz, vectorReg);
- ApplyPrefixST(regs, js.prefixS, sz);
- }
- void GetVectorRegsPrefixT(u8 *regs, VectorSize sz, int vectorReg) {
- _assert_(js.prefixTFlag & JitState::PREFIX_KNOWN);
- GetVectorRegs(regs, sz, vectorReg);
- ApplyPrefixST(regs, js.prefixT, sz);
- }
- void GetVectorRegsPrefixD(u8 *regs, VectorSize sz, int vectorReg);
-
-
- // For NEON mappings, it will be easier to deal directly in ARM registers.
-
- ArmGen::ARMReg NEONMapPrefixST(int vfpuReg, VectorSize sz, u32 prefix, int mapFlags);
- ArmGen::ARMReg NEONMapPrefixS(int vfpuReg, VectorSize sz, int mapFlags) {
- return NEONMapPrefixST(vfpuReg, sz, js.prefixS, mapFlags);
- }
- ArmGen::ARMReg NEONMapPrefixT(int vfpuReg, VectorSize sz, int mapFlags) {
- return NEONMapPrefixST(vfpuReg, sz, js.prefixT, mapFlags);
- }
-
- struct DestARMReg {
- ArmGen::ARMReg rd;
- ArmGen::ARMReg backingRd;
- VectorSize sz;
-
- operator ArmGen::ARMReg() const { return rd; }
- };
-
- struct MappedRegs {
- ArmGen::ARMReg vs;
- ArmGen::ARMReg vt;
- DestARMReg vd;
- bool overlap;
- };
-
- MappedRegs NEONMapDirtyInIn(MIPSOpcode op, VectorSize dsize, VectorSize ssize, VectorSize tsize, bool applyPrefixes = true);
- MappedRegs NEONMapInIn(MIPSOpcode op, VectorSize ssize, VectorSize tsize, bool applyPrefixes = true);
- MappedRegs NEONMapDirtyIn(MIPSOpcode op, VectorSize dsize, VectorSize ssize, bool applyPrefixes = true);
-
- DestARMReg NEONMapPrefixD(int vfpuReg, VectorSize sz, int mapFlags);
- void NEONApplyPrefixD(DestARMReg dest);
-
- // NEON utils
- void NEONMaskToSize(ArmGen::ARMReg vs, VectorSize sz);
- void NEONTranspose4x4(ArmGen::ARMReg cols[4]);
-
- // Utils
- void SetR0ToEffectiveAddress(MIPSGPReg rs, s16 offset);
- void SetCCAndR0ForSafeAddress(MIPSGPReg rs, s16 offset, ArmGen::ARMReg tempReg, bool reverse = false);
- void Comp_ITypeMemLR(MIPSOpcode op, bool load);
-
- JitBlockCache blocks;
- JitOptions jo;
- JitState js;
-
- ArmRegCache gpr;
- ArmRegCacheFPU fpr;
-
- MIPSState *mips_;
-
- int dontLogBlocks;
- int logBlocks;
-
-public:
- // Code pointers
- const u8 *enterDispatcher;
-
- const u8 *outerLoop;
- const u8 *outerLoopPCInR0;
- const u8 *dispatcherCheckCoreState;
- const u8 *dispatcherPCInR0;
- const u8 *dispatcher;
- const u8 *dispatcherFetch;
- const u8 *dispatcherNoCheck;
-
- const u8 *restoreRoundingMode;
- const u8 *applyRoundingMode;
-
- const u8 *crashHandler;
- const u8 *blockShadowHook;
-};
-
-} // namespace MIPSComp
-
diff --git a/Core/MIPS/ARM/ArmRegCache.cpp b/Core/MIPS/ARM/ArmRegCache.cpp
deleted file mode 100644
index 2471a44829..0000000000
--- a/Core/MIPS/ARM/ArmRegCache.cpp
+++ /dev/null
@@ -1,637 +0,0 @@
-// 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 "Core/MemMap.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/Reporting.h"
-#include "Common/ArmEmitter.h"
-
-#ifndef offsetof
-#include "stddef.h"
-#endif
-
-using namespace ArmGen;
-using namespace ArmJitConstants;
-
-ArmRegCache::ArmRegCache(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo) : mips_(mipsState), js_(js), jo_(jo) {}
-
-void ArmRegCache::Init(ARMXEmitter *emitter) {
- emit_ = emitter;
-}
-
-void ArmRegCache::Start() {
- for (int i = 0; i < NUM_ARMREG; i++) {
- ar[i].mipsReg = MIPS_REG_INVALID;
- ar[i].isDirty = false;
- }
- for (int i = 0; i < NUM_MIPSREG; i++) {
- mr[i].loc = ML_MEM;
- mr[i].reg = INVALID_REG;
- mr[i].imm = -1;
- mr[i].spillLock = false;
- }
-}
-
-const ARMReg *ArmRegCache::GetMIPSAllocationOrder(int &count) {
- // Note that R0 is reserved as scratch for now.
- // R12 is also potentially usable.
- // R4-R7 are registers we could use for static allocation or downcount.
- // R8 is used to preserve flags in nasty branches.
- // R9 and upwards are reserved for jit basics.
- // R14 (LR) is used as a scratch reg (overwritten on calls/return.)
- if (jo_->downcountInRegister) {
- static const ARMReg allocationOrder[] = {
- R1, R2, R3, R4, R5, R6, R12,
- };
- count = sizeof(allocationOrder) / sizeof(const int);
- return allocationOrder;
- } else {
- static const ARMReg allocationOrder2[] = {
- R1, R2, R3, R4, R5, R6, R7, R12,
- };
- count = sizeof(allocationOrder2) / sizeof(const int);
- return allocationOrder2;
- }
-}
-
-void ArmRegCache::FlushBeforeCall() {
- // R4-R11 are preserved. Others need flushing.
- FlushArmReg(R1);
- FlushArmReg(R2);
- FlushArmReg(R3);
- FlushArmReg(R12);
-}
-
-ARMReg ArmRegCache::MapRegAsPointer(MIPSGPReg mipsReg) { // read-only, non-dirty.
- // If already mapped as a pointer, bail.
- if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
- return mr[mipsReg].reg;
- }
- // First, make sure the register is already mapped.
- MapReg(mipsReg, 0);
- // If it's dirty, flush it.
- ARMReg armReg = mr[mipsReg].reg;
- if (ar[armReg].isDirty) {
- emit_->STR(armReg, CTXREG, GetMipsRegOffset(ar[armReg].mipsReg));
- }
- // Convert to a pointer by adding the base and clearing off the top bits.
- // If SP, we can probably avoid the top bit clear, let's play with that later.
- emit_->BIC(armReg, armReg, Operand2(0xC0, 4)); // &= 0x3FFFFFFF
- emit_->ADD(armReg, MEMBASEREG, armReg);
- ar[armReg].isDirty = false;
- ar[armReg].mipsReg = mipsReg;
- mr[mipsReg].loc = ML_ARMREG_AS_PTR;
- return armReg;
-}
-
-bool ArmRegCache::IsMappedAsPointer(MIPSGPReg mipsReg) {
- return mr[mipsReg].loc == ML_ARMREG_AS_PTR;
-}
-
-bool ArmRegCache::IsMapped(MIPSGPReg mipsReg) {
- return mr[mipsReg].loc == ML_ARMREG;
-}
-
-void ArmRegCache::SetRegImm(ARMReg reg, u32 imm) {
- // If we can do it with a simple Operand2, let's do that.
- Operand2 op2;
- bool inverse;
- if (TryMakeOperand2_AllowInverse(imm, op2, &inverse)) {
- if (!inverse)
- emit_->MOV(reg, op2);
- else
- emit_->MVN(reg, op2);
- return;
- }
-
- // Okay, so it's a bit more complex. Let's see if we have any useful regs with imm values.
- for (int i = 0; i < NUM_MIPSREG; i++) {
- const auto &mreg = mr[i];
- if (mreg.loc != ML_ARMREG_IMM)
- continue;
-
- if (mreg.imm - imm < 256) {
- emit_->SUB(reg, mreg.reg, mreg.imm - imm);
- return;
- }
- if (imm - mreg.imm < 256) {
- emit_->ADD(reg, mreg.reg, imm - mreg.imm);
- return;
- }
- // This could be common when using an address.
- if ((mreg.imm & 0x3FFFFFFF) == imm) {
- emit_->BIC(reg, mreg.reg, Operand2(0xC0, 4)); // &= 0x3FFFFFFF
- return;
- }
- // TODO: All sorts of things are possible here, shifted adds, ands/ors, etc.
- }
-
- // No luck. Let's go with a regular load.
- emit_->MOVI2R(reg, imm);
-}
-
-void ArmRegCache::MapRegTo(ARMReg reg, MIPSGPReg mipsReg, int mapFlags) {
- ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
- if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
- if (mipsReg == MIPS_REG_ZERO) {
- // If we get a request to load the zero register, at least we won't spend
- // time on a memory access...
- // TODO: EOR?
- emit_->MOV(reg, 0);
-
- // This way, if we SetImm() it, we'll keep it.
- mr[mipsReg].loc = ML_ARMREG_IMM;
- mr[mipsReg].imm = 0;
- } else {
- switch (mr[mipsReg].loc) {
- case ML_MEM:
- emit_->LDR(reg, CTXREG, GetMipsRegOffset(mipsReg));
- mr[mipsReg].loc = ML_ARMREG;
- break;
- case ML_IMM:
- SetRegImm(reg, mr[mipsReg].imm);
- ar[reg].isDirty = true; // IMM is always dirty.
-
- // If we are mapping dirty, it means we're gonna overwrite.
- // So the imm value is no longer valid.
- if (mapFlags & MAP_DIRTY)
- mr[mipsReg].loc = ML_ARMREG;
- else
- mr[mipsReg].loc = ML_ARMREG_IMM;
- break;
- default:
- mr[mipsReg].loc = ML_ARMREG;
- break;
- }
- }
- } else {
- mr[mipsReg].loc = ML_ARMREG;
- }
- ar[reg].mipsReg = mipsReg;
- mr[mipsReg].reg = reg;
-}
-
-ARMReg ArmRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
- int allocCount;
- const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount);
-
- static const int UNUSED_LOOKAHEAD_OPS = 30;
-
- *clobbered = false;
- for (int i = 0; i < allocCount; i++) {
- ARMReg reg = allocOrder[i];
- if (ar[reg].mipsReg != MIPS_REG_INVALID && mr[ar[reg].mipsReg].spillLock)
- continue;
-
- // Awesome, a clobbered reg. Let's use it.
- if (MIPSAnalyst::IsRegisterClobbered(ar[reg].mipsReg, compilerPC_, UNUSED_LOOKAHEAD_OPS)) {
- *clobbered = true;
- return reg;
- }
-
- // Not awesome. A used reg. Let's try to avoid spilling.
- if (unusedOnly && MIPSAnalyst::IsRegisterUsed(ar[reg].mipsReg, compilerPC_, UNUSED_LOOKAHEAD_OPS)) {
- continue;
- }
-
- return reg;
- }
-
- return INVALID_REG;
-}
-
-// TODO: Somewhat smarter spilling - currently simply spills the first available, should do
-// round robin or FIFO or something.
-ARMReg ArmRegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) {
- // Let's see if it's already mapped. If so we just need to update the dirty flag.
- // We don't need to check for ML_NOINIT because we assume that anyone who maps
- // with that flag immediately writes a "known" value to the register.
- if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) {
- ARMReg armReg = mr[mipsReg].reg;
- if (ar[armReg].mipsReg != mipsReg) {
- ERROR_LOG_REPORT(Log::JIT, "Register mapping out of sync! %i", mipsReg);
- }
- if (mapFlags & MAP_DIRTY) {
- // Mapping dirty means the old imm value is invalid.
- mr[mipsReg].loc = ML_ARMREG;
- ar[armReg].isDirty = true;
- }
- return (ARMReg)mr[mipsReg].reg;
- } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
- // Was mapped as pointer, now we want it mapped as a value, presumably to
- // add or subtract stuff to it. Later we could allow such things but for now
- // let's just convert back to a register value by reloading from the backing storage.
- ARMReg armReg = mr[mipsReg].reg;
- if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
- emit_->LDR(armReg, CTXREG, GetMipsRegOffset(mipsReg));
- }
- mr[mipsReg].loc = ML_ARMREG;
- if (mapFlags & MAP_DIRTY) {
- ar[armReg].isDirty = true;
- }
- return (ARMReg)mr[mipsReg].reg;
- }
-
- // Okay, not mapped, so we need to allocate an ARM register.
-
- int allocCount;
- const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount);
-
- ARMReg desiredReg = INVALID_REG;
- // Try to "statically" allocate the first 6 regs after v0.
- int desiredOrder = allocCount - (6 - (mipsReg - (int)MIPS_REG_V0));
- if (desiredOrder >= 0 && desiredOrder < allocCount)
- desiredReg = allocOrder[desiredOrder];
-
- if (desiredReg != INVALID_REG) {
- if (ar[desiredReg].mipsReg == MIPS_REG_INVALID) {
- // With this placement, we may be able to optimize flush.
- MapRegTo(desiredReg, mipsReg, mapFlags);
- return desiredReg;
- }
- }
-
-allocate:
- for (int i = 0; i < allocCount; i++) {
- ARMReg reg = allocOrder[i];
-
- if (ar[reg].mipsReg == MIPS_REG_INVALID) {
- // That means it's free. Grab it, and load the value into it (if requested).
- MapRegTo(reg, mipsReg, mapFlags);
- return reg;
- }
- }
-
- // Still nothing. Let's spill a reg and goto 10.
- // TODO: Use age or something to choose which register to spill?
- // TODO: Spill dirty regs first? or opposite?
- bool clobbered;
- ARMReg bestToSpill = FindBestToSpill(true, &clobbered);
- if (bestToSpill == INVALID_REG) {
- bestToSpill = FindBestToSpill(false, &clobbered);
- }
-
- if (bestToSpill != INVALID_REG) {
- // ERROR_LOG(Log::JIT, "Out of registers at PC %08x - spills register %i.", mips_->pc, bestToSpill);
- // TODO: Broken somehow in Dante's Inferno, but most games work. Bad flags in MIPSTables somewhere?
- if (clobbered) {
- DiscardR(ar[bestToSpill].mipsReg);
- } else {
- FlushArmReg(bestToSpill);
- }
- goto allocate;
- }
-
- // Uh oh, we have all of them spilllocked....
- ERROR_LOG_REPORT(Log::JIT, "Out of spillable registers at PC %08x!!!", mips_->pc);
- return INVALID_REG;
-}
-
-void ArmRegCache::MapInIn(MIPSGPReg rd, MIPSGPReg rs) {
- SpillLock(rd, rs);
- MapReg(rd);
- MapReg(rs);
- ReleaseSpillLocks();
-}
-
-void ArmRegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) {
- SpillLock(rd, rs);
- bool load = !avoidLoad || rd == rs;
- MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rs);
- ReleaseSpillLocks();
-}
-
-void ArmRegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) {
- SpillLock(rd, rs, rt);
- bool load = !avoidLoad || (rd == rs || rd == rt);
- MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rt);
- MapReg(rs);
- ReleaseSpillLocks();
-}
-
-void ArmRegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad) {
- SpillLock(rd1, rd2, rs);
- bool load1 = !avoidLoad || rd1 == rs;
- bool load2 = !avoidLoad || rd2 == rs;
- MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rs);
- ReleaseSpillLocks();
-}
-
-void ArmRegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) {
- SpillLock(rd1, rd2, rs, rt);
- bool load1 = !avoidLoad || (rd1 == rs || rd1 == rt);
- bool load2 = !avoidLoad || (rd2 == rs || rd2 == rt);
- MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rt);
- MapReg(rs);
- ReleaseSpillLocks();
-}
-
-void ArmRegCache::FlushArmReg(ARMReg r) {
- if (ar[r].mipsReg == MIPS_REG_INVALID) {
- // Nothing to do, reg not mapped.
- if (ar[r].isDirty) {
- ERROR_LOG_REPORT(Log::JIT, "Dirty but no mipsreg?");
- }
- return;
- }
- if (ar[r].mipsReg != MIPS_REG_INVALID) {
- auto &mreg = mr[ar[r].mipsReg];
- if (mreg.loc == ML_ARMREG_IMM || ar[r].mipsReg == MIPS_REG_ZERO) {
- // We know its immedate value, no need to STR now.
- mreg.loc = ML_IMM;
- mreg.reg = INVALID_REG;
- } else {
- if (ar[r].isDirty && mreg.loc == ML_ARMREG)
- emit_->STR(r, CTXREG, GetMipsRegOffset(ar[r].mipsReg));
- mreg.loc = ML_MEM;
- mreg.reg = INVALID_REG;
- mreg.imm = 0;
- }
- }
- ar[r].isDirty = false;
- ar[r].mipsReg = MIPS_REG_INVALID;
-}
-
-void ArmRegCache::DiscardR(MIPSGPReg mipsReg) {
- const RegMIPSLoc prevLoc = mr[mipsReg].loc;
- if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_AS_PTR || prevLoc == ML_ARMREG_IMM) {
- ARMReg armReg = mr[mipsReg].reg;
- ar[armReg].isDirty = false;
- ar[armReg].mipsReg = MIPS_REG_INVALID;
- mr[mipsReg].reg = INVALID_REG;
- if (mipsReg == MIPS_REG_ZERO) {
- mr[mipsReg].loc = ML_IMM;
- } else {
- mr[mipsReg].loc = ML_MEM;
- }
- mr[mipsReg].imm = 0;
- }
- if (prevLoc == ML_IMM && mipsReg != MIPS_REG_ZERO) {
- mr[mipsReg].loc = ML_MEM;
- mr[mipsReg].imm = 0;
- }
-}
-
-void ArmRegCache::FlushR(MIPSGPReg r) {
- switch (mr[r].loc) {
- case ML_IMM:
- // IMM is always "dirty".
- if (r != MIPS_REG_ZERO) {
- SetRegImm(SCRATCHREG1, mr[r].imm);
- emit_->STR(SCRATCHREG1, CTXREG, GetMipsRegOffset(r));
- }
- break;
-
- case ML_ARMREG:
- case ML_ARMREG_IMM:
- if (mr[r].reg == INVALID_REG) {
- ERROR_LOG_REPORT(Log::JIT, "FlushR: MipsReg %d had bad ArmReg", r);
- }
- if (ar[mr[r].reg].isDirty) {
- if (r != MIPS_REG_ZERO) {
- emit_->STR((ARMReg)mr[r].reg, CTXREG, GetMipsRegOffset(r));
- }
- ar[mr[r].reg].isDirty = false;
- }
- ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
- break;
-
- case ML_ARMREG_AS_PTR:
- // Never dirty.
- if (ar[mr[r].reg].isDirty) {
- ERROR_LOG_REPORT(Log::JIT, "ARMREG_AS_PTR cannot be dirty (yet)");
- }
- ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
- break;
-
- case ML_MEM:
- // Already there, nothing to do.
- break;
-
- default:
- ERROR_LOG_REPORT(Log::JIT, "FlushR: MipsReg %d with invalid location %d", r, mr[r].loc);
- break;
- }
- if (r == MIPS_REG_ZERO) {
- mr[r].loc = ML_IMM;
- } else {
- mr[r].loc = ML_MEM;
- }
- mr[r].reg = INVALID_REG;
- mr[r].imm = 0;
-}
-
-// Note: if allowFlushImm is set, this also flushes imms while checking the sequence.
-int ArmRegCache::FlushGetSequential(MIPSGPReg startMipsReg, bool allowFlushImm) {
- // Only start a sequence on a dirty armreg.
- // TODO: Could also start with an imm?
- const auto &startMipsInfo = mr[startMipsReg];
- if ((startMipsInfo.loc != ML_ARMREG && startMipsInfo.loc != ML_ARMREG_IMM) || startMipsInfo.reg == INVALID_REG || !ar[startMipsInfo.reg].isDirty) {
- return 0;
- }
-
- int allocCount;
- const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount);
-
- int c = 1;
- // The sequence needs to have ascending arm regs for STMIA.
- int lastArmReg = startMipsInfo.reg;
- // Can't use HI/LO, only regs in the main r[] array.
- for (int r = (int)startMipsReg + 1; r < 32; ++r) {
- if ((mr[r].loc == ML_ARMREG || mr[r].loc == ML_ARMREG_IMM) && mr[r].reg != INVALID_REG) {
- if ((int)mr[r].reg > lastArmReg && ar[mr[r].reg].isDirty) {
- ++c;
- lastArmReg = mr[r].reg;
- continue;
- }
- // If we're not allowed to flush imms, don't even consider them.
- } else if (allowFlushImm && mr[r].loc == ML_IMM && MIPSGPReg(r) != MIPS_REG_ZERO) {
- // Okay, let's search for a free (and later) reg to put this imm into.
- bool found = false;
- for (int j = 0; j < allocCount; ++j) {
- ARMReg immReg = allocOrder[j];
- if ((int)immReg > lastArmReg && ar[immReg].mipsReg == MIPS_REG_INVALID) {
- ++c;
- lastArmReg = immReg;
-
- // Even if the sequence fails, we'll need it in a reg anyway, might as well be this one.
- MapRegTo(immReg, MIPSGPReg(r), 0);
- found = true;
- break;
- }
- }
- if (found) {
- continue;
- }
- }
-
- // If it didn't hit a continue above, the chain is over.
- // There's no way to skip a slot with STMIA.
- break;
- }
-
- return c;
-}
-
-void ArmRegCache::FlushAll() {
- // ADD + STMIA is probably better than STR + STR, so let's merge 2 into a STMIA.
- const int minSequential = 2;
-
- // Let's try to put things in order and use STMIA.
- // First we have to save imms. We have to use a separate loop because otherwise
- // we would overwrite existing regs, and other code assumes FlushAll() won't do that.
- for (int i = 0; i < NUM_MIPSREG; i++) {
- MIPSGPReg mipsReg = MIPSGPReg(i);
-
- // This happens to also flush imms to regs as much as possible.
- int c = FlushGetSequential(mipsReg, true);
- if (c >= minSequential) {
- // Skip the next c (adjust down 1 because the loop increments.)
- i += c - 1;
- }
- }
-
- // Okay, now the real deal: this time NOT flushing imms.
- for (int i = 0; i < NUM_MIPSREG; i++) {
- MIPSGPReg mipsReg = MIPSGPReg(i);
-
- int c = FlushGetSequential(mipsReg, false);
- if (c >= minSequential) {
- u16 regs = 0;
- for (int j = 0; j < c; ++j) {
- regs |= 1 << mr[i + j].reg;
- }
-
- emit_->ADD(SCRATCHREG1, CTXREG, GetMipsRegOffset(mipsReg));
- emit_->STMBitmask(SCRATCHREG1, true, false, false, regs);
-
- // Okay, those are all done now, discard them.
- for (int j = 0; j < c; ++j) {
- DiscardR(MIPSGPReg(i + j));
- }
- // Skip the next c (adjust down 1 because the loop increments.)
- i += c - 1;
- } else {
- FlushR(mipsReg);
- }
- }
- // Sanity check
- for (int i = 0; i < NUM_ARMREG; i++) {
- if (ar[i].mipsReg != MIPS_REG_INVALID) {
- ERROR_LOG_REPORT(Log::JIT, "Flush fail: ar[%i].mipsReg=%i", i, ar[i].mipsReg);
- }
- }
-}
-
-void ArmRegCache::SetImm(MIPSGPReg r, u32 immVal) {
- if (r == MIPS_REG_ZERO && immVal != 0) {
- ERROR_LOG_REPORT(Log::JIT, "Trying to set immediate %08x to r0 at %08x", immVal, compilerPC_);
- return;
- }
-
- if (mr[r].loc == ML_ARMREG_IMM && mr[r].imm == immVal) {
- // Already have that value, let's keep it in the reg.
- return;
- }
- // Zap existing value if cached in a reg
- if (mr[r].reg != INVALID_REG) {
- ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
- ar[mr[r].reg].isDirty = false;
- }
- mr[r].loc = ML_IMM;
- mr[r].imm = immVal;
- mr[r].reg = INVALID_REG;
-}
-
-bool ArmRegCache::IsImm(MIPSGPReg r) const {
- if (r == MIPS_REG_ZERO) return true;
- return mr[r].loc == ML_IMM || mr[r].loc == ML_ARMREG_IMM;
-}
-
-u32 ArmRegCache::GetImm(MIPSGPReg r) const {
- if (r == MIPS_REG_ZERO) return 0;
- if (mr[r].loc != ML_IMM && mr[r].loc != ML_ARMREG_IMM) {
- ERROR_LOG_REPORT(Log::JIT, "Trying to get imm from non-imm register %i", r);
- }
- return mr[r].imm;
-}
-
-int ArmRegCache::GetMipsRegOffset(MIPSGPReg r) {
- if (r < 32)
- return r * 4;
- switch (r) {
- case MIPS_REG_HI:
- return offsetof(MIPSState, hi);
- case MIPS_REG_LO:
- return offsetof(MIPSState, lo);
- case MIPS_REG_FPCOND:
- return offsetof(MIPSState, fpcond);
- case MIPS_REG_VFPUCC:
- return offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_CC]);
- default:
- ERROR_LOG_REPORT(Log::JIT, "bad mips register %i", r);
- return 0; // or what?
- }
-}
-
-void ArmRegCache::SpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPReg r4) {
- mr[r1].spillLock = true;
- if (r2 != MIPS_REG_INVALID) mr[r2].spillLock = true;
- if (r3 != MIPS_REG_INVALID) mr[r3].spillLock = true;
- if (r4 != MIPS_REG_INVALID) mr[r4].spillLock = true;
-}
-
-void ArmRegCache::ReleaseSpillLocks() {
- for (int i = 0; i < NUM_MIPSREG; i++) {
- mr[i].spillLock = false;
- }
-}
-
-void ArmRegCache::ReleaseSpillLock(MIPSGPReg reg) {
- mr[reg].spillLock = false;
-}
-
-ARMReg ArmRegCache::R(MIPSGPReg mipsReg) {
- if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) {
- return (ARMReg)mr[mipsReg].reg;
- } else {
- ERROR_LOG_REPORT(Log::JIT, "Reg %i not in arm reg. compilerPC = %08x", mipsReg, compilerPC_);
- return INVALID_REG; // BAAAD
- }
-}
-
-ARMReg ArmRegCache::RPtr(MIPSGPReg mipsReg) {
- if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
- return (ARMReg)mr[mipsReg].reg;
- } else {
- ERROR_LOG_REPORT(Log::JIT, "Reg %i not in arm reg as pointer. compilerPC = %08x", mipsReg, compilerPC_);
- return INVALID_REG; // BAAAD
- }
-}
-
-#endif // PPSSPP_ARCH(ARM)
diff --git a/Core/MIPS/ARM/ArmRegCache.h b/Core/MIPS/ARM/ArmRegCache.h
deleted file mode 100644
index e06953d60a..0000000000
--- a/Core/MIPS/ARM/ArmRegCache.h
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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/.
-
-#pragma once
-
-#include "Core/MIPS/MIPS.h"
-#include "ArmEmitter.h"
-
-namespace ArmJitConstants {
-
-const ArmGen::ARMReg JITBASEREG = ArmGen::R9;
-const ArmGen::ARMReg CTXREG = ArmGen::R10;
-const ArmGen::ARMReg MEMBASEREG = ArmGen::R11;
-const ArmGen::ARMReg SCRATCHREG1 = ArmGen::R0;
-const ArmGen::ARMReg SCRATCHREG2 = ArmGen::R14;
-const ArmGen::ARMReg DOWNCOUNTREG = ArmGen::R7;
-
-enum {
- TOTAL_MAPPABLE_MIPSREGS = 36,
-};
-
-enum RegMIPSLoc {
- ML_IMM,
- ML_ARMREG,
- // In an arm reg, but as a pre-adjusted pointer, not the actual reg.
- ML_ARMREG_AS_PTR,
- // In an arm reg, but also has a known immediate value.
- ML_ARMREG_IMM,
- ML_MEM,
-};
-
-// Initing is the default so the flag is reversed.
-enum {
- MAP_DIRTY = 1,
- MAP_NOINIT = 2 | MAP_DIRTY,
-};
-
-}
-
-// R1 to R6: mapped MIPS regs
-// R8 = flags (maybe we could do better here?)
-// R9 = code pointers
-// R10 = MIPS context
-// R11 = base pointer
-// R14 = scratch (actually LR)
-
-
-typedef int MIPSReg;
-
-struct RegARM {
- MIPSGPReg mipsReg; // if -1, no mipsreg attached.
- bool isDirty; // Should the register be written back?
-};
-
-struct RegMIPS {
- // Where is this MIPS register?
- ArmJitConstants::RegMIPSLoc loc;
- // Data (both or only one may be used, depending on loc.)
- u32 imm;
- ArmGen::ARMReg reg; // reg index
- bool spillLock; // if true, this register cannot be spilled.
- // If loc == ML_MEM, it's back in its location in the CPU context struct.
-};
-
-namespace MIPSComp {
- struct JitOptions;
- struct JitState;
-}
-
-class ArmRegCache {
-public:
- ArmRegCache(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo);
- ~ArmRegCache() {}
-
- void Init(ArmGen::ARMXEmitter *emitter);
- void Start();
-
- // Protect the arm register containing a MIPS register from spilling, to ensure that
- // it's being kept allocated.
- void SpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID);
- void ReleaseSpillLock(MIPSGPReg reg);
- void ReleaseSpillLocks();
-
- void SetImm(MIPSGPReg reg, u32 immVal);
- bool IsImm(MIPSGPReg reg) const;
- u32 GetImm(MIPSGPReg reg) const;
- // Optimally set a register to an imm value (possibly using another register.)
- void SetRegImm(ArmGen::ARMReg reg, u32 imm);
-
- // Returns an ARM register containing the requested MIPS register.
- ArmGen::ARMReg MapReg(MIPSGPReg reg, int mapFlags = 0);
- ArmGen::ARMReg MapRegAsPointer(MIPSGPReg reg); // read-only, non-dirty.
-
- bool IsMapped(MIPSGPReg reg);
- bool IsMappedAsPointer(MIPSGPReg reg);
-
- void MapInIn(MIPSGPReg rd, MIPSGPReg rs);
- void MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad = true);
- void MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad = true);
- void MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad = true);
- void MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad = true);
- void FlushArmReg(ArmGen::ARMReg r);
- void FlushR(MIPSGPReg r);
- void FlushBeforeCall();
- void FlushAll();
- void DiscardR(MIPSGPReg r);
-
- ArmGen::ARMReg R(MIPSGPReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
- ArmGen::ARMReg RPtr(MIPSGPReg preg); // Returns a cached register, while checking that it's mapped as a pointer
-
- void SetEmitter(ArmGen::ARMXEmitter *emitter) { emit_ = emitter; }
-
- // For better log output only.
- void SetCompilerPC(u32 compilerPC) { compilerPC_ = compilerPC; }
-
- int GetMipsRegOffset(MIPSGPReg r);
-
-private:
- const ArmGen::ARMReg *GetMIPSAllocationOrder(int &count);
- void MapRegTo(ArmGen::ARMReg reg, MIPSGPReg mipsReg, int mapFlags);
- int FlushGetSequential(MIPSGPReg startMipsReg, bool allowFlushImm);
- ArmGen::ARMReg FindBestToSpill(bool unusedOnly, bool *clobbered);
-
- MIPSState *mips_;
- ArmGen::ARMXEmitter *emit_;
- MIPSComp::JitState *js_;
- MIPSComp::JitOptions *jo_;
- u32 compilerPC_;
-
- enum {
- NUM_ARMREG = 16,
- NUM_MIPSREG = ArmJitConstants::TOTAL_MAPPABLE_MIPSREGS,
- };
-
- RegARM ar[NUM_ARMREG];
- RegMIPS mr[NUM_MIPSREG];
-};
diff --git a/Core/MIPS/ARM/ArmRegCacheFPU.cpp b/Core/MIPS/ARM/ArmRegCacheFPU.cpp
deleted file mode 100644
index fe2d783622..0000000000
--- a/Core/MIPS/ARM/ArmRegCacheFPU.cpp
+++ /dev/null
@@ -1,957 +0,0 @@
-// 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
-
-#include "Common/CPUDetect.h"
-#include "Common/Log.h"
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/MIPSTables.h"
-
-using namespace ArmGen;
-using namespace ArmJitConstants;
-
-ArmRegCacheFPU::ArmRegCacheFPU(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo) : mips_(mipsState), js_(js), jo_(jo), vr(mr + 32) {}
-
-void ArmRegCacheFPU::Start() {
- if (!initialReady) {
- SetupInitialRegs();
- initialReady = true;
- }
-
- memcpy(ar, arInitial, sizeof(ar));
- memcpy(mr, mrInitial, sizeof(mr));
- pendingFlush = false;
-}
-
-void ArmRegCacheFPU::SetupInitialRegs() {
- for (int i = 0; i < NUM_ARMFPUREG; i++) {
- arInitial[i].mipsReg = -1;
- arInitial[i].isDirty = false;
- }
- for (int i = 0; i < NUM_MIPSFPUREG; i++) {
- mrInitial[i].loc = ML_MEM;
- mrInitial[i].reg = INVALID_REG;
- mrInitial[i].spillLock = false;
- mrInitial[i].tempLock = false;
- }
- for (int i = 0; i < NUM_ARMQUADS; i++) {
- qr[i].isDirty = false;
- qr[i].mipsVec = -1;
- qr[i].sz = V_Invalid;
- qr[i].spillLock = false;
- qr[i].isTemp = false;
- memset(qr[i].vregs, 0xff, 4);
- }
-}
-
-const ARMReg *ArmRegCacheFPU::GetMIPSAllocationOrder(int &count) {
- // VFP mapping
- // VFPU registers and regular FP registers are mapped interchangably on top of the standard
- // 16 FPU registers.
-
- // NEON mapping
- // We map FPU and VFPU registers entirely separately. FPU is mapped to 12 of the bottom 16 S registers.
- // VFPU is mapped to the upper 48 regs, 32 of which can only be reached through NEON
- // (or D16-D31 as doubles, but not relevant).
- // Might consider shifting the split in the future, giving more regs to NEON allowing it to map more quads.
-
- // We should attempt to map scalars to low Q registers and wider things to high registers,
- // as the NEON instructions are all 2-vector or 4-vector, they don't do scalar, we want to be
- // able to use regular VFP instructions too.
- static const ARMReg allocationOrderNEON[] = {
- // Reserve four temp registers. Useful when building quads until we really figure out
- // how to do that best.
- S4, S5, S6, S7, // Q1
- S8, S9, S10, S11, // Q2
- S12, S13, S14, S15, // Q3
- S16, S17, S18, S19, // Q4
- S20, S21, S22, S23, // Q5
- S24, S25, S26, S27, // Q6
- S28, S29, S30, S31, // Q7
- // Q8-Q15 free for NEON tricks
- };
-
- count = sizeof(allocationOrderNEON) / sizeof(const ARMReg);
- return allocationOrderNEON;
-}
-
-bool ArmRegCacheFPU::IsMapped(MIPSReg r) {
- return mr[r].loc == ML_ARMREG;
-}
-
-ARMReg ArmRegCacheFPU::MapReg(MIPSReg mipsReg, int mapFlags) {
- pendingFlush = true;
- // Let's see if it's already mapped. If so we just need to update the dirty flag.
- // We don't need to check for ML_NOINIT because we assume that anyone who maps
- // with that flag immediately writes a "known" value to the register.
- if (mr[mipsReg].loc == ML_ARMREG) {
- if (ar[mr[mipsReg].reg].mipsReg != mipsReg) {
- ERROR_LOG(Log::JIT, "Reg mapping out of sync! MR %i", mipsReg);
- }
- if (mapFlags & MAP_DIRTY) {
- ar[mr[mipsReg].reg].isDirty = true;
- }
- //INFO_LOG(Log::JIT, "Already mapped %i to %i", mipsReg, mr[mipsReg].reg);
- return (ARMReg)(mr[mipsReg].reg + S0);
- }
-
- // Okay, not mapped, so we need to allocate an ARM register.
-
- int allocCount;
- const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount);
-
-allocate:
- for (int i = 0; i < allocCount; i++) {
- int reg = allocOrder[i] - S0;
-
- if (ar[reg].mipsReg == -1) {
- // That means it's free. Grab it, and load the value into it (if requested).
- ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
- if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
- if (mipsReg < TEMP0 && mr[mipsReg].loc == ML_MEM) {
- emit_->VLDR((ARMReg)(reg + S0), CTXREG, GetMipsRegOffset(mipsReg));
- }
- }
- ar[reg].mipsReg = mipsReg;
- mr[mipsReg].loc = ML_ARMREG;
- mr[mipsReg].reg = reg;
- //INFO_LOG(Log::JIT, "Mapped %i to %i", mipsReg, mr[mipsReg].reg);
- return (ARMReg)(reg + S0);
- }
- }
-
-
- // Still nothing. Let's spill a reg and goto 10.
- // TODO: Use age or something to choose which register to spill?
- // TODO: Spill dirty regs first? or opposite?
- int bestToSpill = -1;
- for (int i = 0; i < allocCount; i++) {
- int reg = allocOrder[i] - S0;
- if (ar[reg].mipsReg != -1 && (mr[ar[reg].mipsReg].spillLock || mr[ar[reg].mipsReg].tempLock))
- continue;
- bestToSpill = reg;
- break;
- }
-
- if (bestToSpill != -1) {
- FlushArmReg((ARMReg)(S0 + bestToSpill));
- goto allocate;
- }
-
- // Uh oh, we have all them spilllocked....
- ERROR_LOG(Log::JIT, "Out of spillable registers at PC %08x!!!", js_->compilerPC);
- return INVALID_REG;
-}
-
-void ArmRegCacheFPU::MapInIn(MIPSReg rd, MIPSReg rs) {
- SpillLock(rd, rs);
- MapReg(rd);
- MapReg(rs);
- ReleaseSpillLock(rd);
- ReleaseSpillLock(rs);
-}
-
-void ArmRegCacheFPU::MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad) {
- SpillLock(rd, rs);
- bool load = !avoidLoad || rd == rs;
- MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rs);
- ReleaseSpillLock(rd);
- ReleaseSpillLock(rs);
-}
-
-void ArmRegCacheFPU::MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad) {
- SpillLock(rd, rs, rt);
- bool load = !avoidLoad || (rd == rs || rd == rt);
- MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
- MapReg(rt);
- MapReg(rs);
- ReleaseSpillLock(rd);
- ReleaseSpillLock(rs);
- ReleaseSpillLock(rt);
-}
-
-void ArmRegCacheFPU::SpillLockV(const u8 *v, VectorSize sz) {
- for (int i = 0; i < GetNumVectorElements(sz); i++) {
- vr[v[i]].spillLock = true;
- }
-}
-
-void ArmRegCacheFPU::SpillLockV(int vec, VectorSize sz) {
- u8 v[4];
- GetVectorRegs(v, sz, vec);
- SpillLockV(v, sz);
-}
-
-void ArmRegCacheFPU::MapRegV(int vreg, int flags) {
- MapReg(vreg + 32, flags);
-}
-
-void ArmRegCacheFPU::LoadToRegV(ARMReg armReg, int vreg) {
- if (vr[vreg].loc == ML_ARMREG) {
- emit_->VMOV(armReg, (ARMReg)(S0 + vr[vreg].reg));
- } else {
- MapRegV(vreg);
- emit_->VMOV(armReg, V(vreg));
- }
-}
-
-void ArmRegCacheFPU::MapRegsAndSpillLockV(int vec, VectorSize sz, int flags) {
- u8 v[4];
- GetVectorRegs(v, sz, vec);
- SpillLockV(v, sz);
- for (int i = 0; i < GetNumVectorElements(sz); i++) {
- MapRegV(v[i], flags);
- }
-}
-
-void ArmRegCacheFPU::MapRegsAndSpillLockV(const u8 *v, VectorSize sz, int flags) {
- SpillLockV(v, sz);
- for (int i = 0; i < GetNumVectorElements(sz); i++) {
- MapRegV(v[i], flags);
- }
-}
-
-void ArmRegCacheFPU::MapInInV(int vs, int vt) {
- SpillLockV(vs);
- SpillLockV(vt);
- MapRegV(vs);
- MapRegV(vt);
- ReleaseSpillLockV(vs);
- ReleaseSpillLockV(vt);
-}
-
-void ArmRegCacheFPU::MapDirtyInV(int vd, int vs, bool avoidLoad) {
- bool load = !avoidLoad || (vd == vs);
- SpillLockV(vd);
- SpillLockV(vs);
- MapRegV(vd, load ? MAP_DIRTY : MAP_NOINIT);
- MapRegV(vs);
- ReleaseSpillLockV(vd);
- ReleaseSpillLockV(vs);
-}
-
-void ArmRegCacheFPU::MapDirtyInInV(int vd, int vs, int vt, bool avoidLoad) {
- bool load = !avoidLoad || (vd == vs || vd == vt);
- SpillLockV(vd);
- SpillLockV(vs);
- SpillLockV(vt);
- MapRegV(vd, load ? MAP_DIRTY : MAP_NOINIT);
- MapRegV(vs);
- MapRegV(vt);
- ReleaseSpillLockV(vd);
- ReleaseSpillLockV(vs);
- ReleaseSpillLockV(vt);
-}
-
-void ArmRegCacheFPU::FlushArmReg(ARMReg r) {
- if (r >= S0 && r <= S31) {
- int reg = r - S0;
- if (ar[reg].mipsReg == -1) {
- // Nothing to do, reg not mapped.
- return;
- }
- if (ar[reg].mipsReg != -1) {
- if (ar[reg].isDirty && mr[ar[reg].mipsReg].loc == ML_ARMREG)
- {
- //INFO_LOG(Log::JIT, "Flushing ARM reg %i", reg);
- emit_->VSTR(r, CTXREG, GetMipsRegOffset(ar[reg].mipsReg));
- }
- // IMMs won't be in an ARM reg.
- mr[ar[reg].mipsReg].loc = ML_MEM;
- mr[ar[reg].mipsReg].reg = INVALID_REG;
- } else {
- ERROR_LOG(Log::JIT, "Dirty but no mipsreg?");
- }
- ar[reg].isDirty = false;
- ar[reg].mipsReg = -1;
- } else if (r >= D0 && r <= D31) {
- // TODO: Convert to S regs and flush them individually.
- } else if (r >= Q0 && r <= Q15) {
- QFlush(r);
- }
-}
-
-void ArmRegCacheFPU::FlushV(MIPSReg r) {
- FlushR(r + 32);
-}
-
-/*
-void ArmRegCacheFPU::FlushQWithV(MIPSReg r) {
- // Look for it in all the quads. If it's in any, flush that quad clean.
- int flushCount = 0;
- for (int i = 0; i < MAX_ARMQUADS; i++) {
- if (qr[i].sz == V_Invalid)
- continue;
-
- int n = qr[i].sz;
- bool flushThis = false;
- for (int j = 0; j < n; j++) {
- if (qr[i].vregs[j] == r) {
- flushThis = true;
- }
- }
-
- if (flushThis) {
- QFlush(i);
- flushCount++;
- }
- }
-
- if (flushCount > 1) {
- WARN_LOG(Log::JIT, "ERROR: More than one quad was flushed to flush reg %i", r);
- }
-}
-*/
-
-void ArmRegCacheFPU::FlushR(MIPSReg r) {
- switch (mr[r].loc) {
- case ML_IMM:
- // IMM is always "dirty".
- // IMM is not allowed for FP (yet).
- ERROR_LOG(Log::JIT, "Imm in FP register?");
- break;
-
- case ML_ARMREG:
- if (mr[r].reg == INVALID_REG) {
- ERROR_LOG(Log::JIT, "FlushR: MipsReg had bad ArmReg");
- }
-
- if (mr[r].reg >= Q0 && mr[r].reg <= Q15) {
- // This should happen rarely, but occasionally we need to flush a single stray
- // mipsreg that's been part of a quad.
- int quad = mr[r].reg - Q0;
- if (qr[quad].isDirty) {
- WARN_LOG(Log::JIT, "FlushR found quad register %i - PC=%08x", quad, js_->compilerPC);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffset(r), R1);
- emit_->VST1_lane(F_32, (ARMReg)mr[r].reg, R0, mr[r].lane, true);
- }
- } else {
- if (ar[mr[r].reg].isDirty) {
- //INFO_LOG(Log::JIT, "Flushing dirty reg %i", mr[r].reg);
- emit_->VSTR((ARMReg)(mr[r].reg + S0), CTXREG, GetMipsRegOffset(r));
- ar[mr[r].reg].isDirty = false;
- }
- ar[mr[r].reg].mipsReg = -1;
- }
- break;
-
- case ML_MEM:
- // Already there, nothing to do.
- break;
-
- default:
- //BAD
- break;
- }
- mr[r].loc = ML_MEM;
- mr[r].reg = (int)INVALID_REG;
-}
-
-// Scalar only. Need a similar one for sequential Q vectors.
-int ArmRegCacheFPU::FlushGetSequential(int a) {
- int c = 1;
- int lastMipsOffset = GetMipsRegOffset(ar[a].mipsReg);
- a++;
- while (a < 32) {
- if (!ar[a].isDirty || ar[a].mipsReg == -1)
- break;
- int mipsOffset = GetMipsRegOffset(ar[a].mipsReg);
- if (mipsOffset != lastMipsOffset + 4) {
- break;
- }
-
- lastMipsOffset = mipsOffset;
- a++;
- c++;
- }
- return c;
-}
-
-void ArmRegCacheFPU::FlushAll() {
- if (!pendingFlush) {
- // Nothing allocated. FPU regs are not nearly as common as GPR.
- return;
- }
-
- // Discard temps!
- for (int i = TEMP0; i < TEMP0 + NUM_TEMPS; i++) {
- DiscardR(i);
- }
-
- // Flush quads!
- // These could also use sequential detection.
- for (int i = 4; i < NUM_ARMQUADS; i++) {
- QFlush(i);
- }
-
- // Loop through the ARM registers, then use GetMipsRegOffset to determine if MIPS registers are
- // sequential. This is necessary because we store VFPU registers in a staggered order to get
- // columns sequential (most VFPU math in nearly all games is in columns, not rows).
-
- int numArmRegs;
- // We rely on the allocation order being sequential.
- const ARMReg baseReg = GetMIPSAllocationOrder(numArmRegs)[0];
-
- for (int i = 0; i < numArmRegs; i++) {
- int a = (baseReg - S0) + i;
- int m = ar[a].mipsReg;
-
- if (ar[a].isDirty) {
- if (m == -1) {
- INFO_LOG(Log::JIT, "ARM reg %i is dirty but has no mipsreg", a);
- continue;
- }
-
- int c = FlushGetSequential(a);
- if (c == 1) {
- // INFO_LOG(Log::JIT, "Got single register: %i (%i)", a, m);
- emit_->VSTR((ARMReg)(a + S0), CTXREG, GetMipsRegOffset(m));
- } else if (c == 2) {
- // Probably not worth using VSTMIA for two.
- int offset = GetMipsRegOffset(m);
- emit_->VSTR((ARMReg)(a + S0), CTXREG, offset);
- emit_->VSTR((ARMReg)(a + 1 + S0), CTXREG, offset + 4);
- } else {
- // INFO_LOG(Log::JIT, "Got sequence: %i at %i (%i)", c, a, m);
- emit_->ADDI2R(SCRATCHREG1, CTXREG, GetMipsRegOffset(m), SCRATCHREG2);
- // INFO_LOG(Log::JIT, "VSTMIA R0, %i, %i", a, c);
- emit_->VSTMIA(SCRATCHREG1, false, (ARMReg)(S0 + a), c);
- }
-
- // Skip past, and mark as non-dirty.
- for (int j = 0; j < c; j++) {
- int b = a + j;
- mr[ar[b].mipsReg].loc = ML_MEM;
- mr[ar[b].mipsReg].reg = (int)INVALID_REG;
- ar[a + j].mipsReg = -1;
- ar[a + j].isDirty = false;
- }
- i += c - 1;
- } else {
- if (m != -1) {
- mr[m].loc = ML_MEM;
- mr[m].reg = (int)INVALID_REG;
- }
- ar[a].mipsReg = -1;
- // already not dirty
- }
- }
-
- // Sanity check
- for (int i = 0; i < NUM_ARMFPUREG; i++) {
- if (ar[i].mipsReg != -1) {
- ERROR_LOG(Log::JIT, "Flush fail: ar[%i].mipsReg=%i", i, ar[i].mipsReg);
- }
- }
- pendingFlush = false;
-}
-
-void ArmRegCacheFPU::DiscardR(MIPSReg r) {
- switch (mr[r].loc) {
- case ML_IMM:
- // IMM is always "dirty".
- // IMM is not allowed for FP (yet).
- ERROR_LOG(Log::JIT, "Imm in FP register?");
- break;
-
- case ML_ARMREG:
- if (mr[r].reg == INVALID_REG) {
- ERROR_LOG(Log::JIT, "DiscardR: MipsReg had bad ArmReg");
- } else {
- // Note that we DO NOT write it back here. That's the whole point of Discard.
- ar[mr[r].reg].isDirty = false;
- ar[mr[r].reg].mipsReg = -1;
- }
- break;
-
- case ML_MEM:
- // Already there, nothing to do.
- break;
-
- default:
- //BAD
- break;
- }
- mr[r].loc = ML_MEM;
- mr[r].reg = (int)INVALID_REG;
- mr[r].tempLock = false;
- mr[r].spillLock = false;
-}
-
-bool ArmRegCacheFPU::IsTempX(ARMReg r) const {
- return ar[r - S0].mipsReg >= TEMP0;
-}
-
-int ArmRegCacheFPU::GetTempR() {
- pendingFlush = true;
- for (int r = TEMP0; r < TEMP0 + NUM_TEMPS; ++r) {
- if (mr[r].loc == ML_MEM && !mr[r].tempLock) {
- mr[r].tempLock = true;
- return r;
- }
- }
-
- ERROR_LOG(Log::CPU, "Out of temp regs! Might need to DiscardR() some");
- _assert_msg_(false, "Regcache ran out of temp regs, might need to DiscardR() some.");
- return -1;
-}
-
-int ArmRegCacheFPU::GetMipsRegOffset(MIPSReg r) {
- // These are offsets within the MIPSState structure. First there are the GPRS, then FPRS, then the "VFPURs", then the VFPU ctrls.
- if (r < 0 || r > 32 + 128 + NUM_TEMPS) {
- ERROR_LOG(Log::JIT, "bad mips register %i, out of range", r);
- return 0; // or what?
- }
-
- if (r < 32 || r >= 32 + 128) {
- return (32 + r) << 2;
- } else {
- // r is between 32 and 128 + 32
- return (32 + 32 + voffset[r - 32]) << 2;
- }
-}
-
-void ArmRegCacheFPU::SpillLock(MIPSReg r1, MIPSReg r2, MIPSReg r3, MIPSReg r4) {
- mr[r1].spillLock = true;
- if (r2 != -1) mr[r2].spillLock = true;
- if (r3 != -1) mr[r3].spillLock = true;
- if (r4 != -1) mr[r4].spillLock = true;
-}
-
-// This is actually pretty slow with all the 160 regs...
-void ArmRegCacheFPU::ReleaseSpillLocksAndDiscardTemps() {
- for (int i = 0; i < NUM_MIPSFPUREG; i++) {
- mr[i].spillLock = false;
- }
- for (int i = TEMP0; i < TEMP0 + NUM_TEMPS; ++i) {
- DiscardR(i);
- }
- for (int i = 0; i < NUM_ARMQUADS; i++) {
- qr[i].spillLock = false;
- if (qr[i].isTemp) {
- qr[i].isTemp = false;
- qr[i].sz = V_Invalid;
- }
- }
-}
-
-ARMReg ArmRegCacheFPU::R(int mipsReg) {
- if (mr[mipsReg].loc == ML_ARMREG) {
- return (ARMReg)(mr[mipsReg].reg + S0);
- } else {
- if (mipsReg < 32) {
- ERROR_LOG(Log::JIT, "FReg %i not in ARM reg. compilerPC = %08x : %s", mipsReg, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
- } else if (mipsReg < 32 + 128) {
- ERROR_LOG(Log::JIT, "VReg %i not in ARM reg. compilerPC = %08x : %s", mipsReg - 32, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
- } else {
- ERROR_LOG(Log::JIT, "Tempreg %i not in ARM reg. compilerPC = %08x : %s", mipsReg - 128 - 32, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
- }
- return INVALID_REG; // BAAAD
- }
-}
-
-inline ARMReg QuadAsD(int quad) {
- return (ARMReg)(D0 + quad * 2);
-}
-
-inline ARMReg QuadAsQ(int quad) {
- return (ARMReg)(Q0 + quad);
-}
-
-bool MappableQ(int quad) {
- return quad >= 4;
-}
-
-void ArmRegCacheFPU::QLoad4x4(MIPSGPReg regPtr, int vquads[4]) {
- ERROR_LOG(Log::JIT, "QLoad4x4 not implemented");
- // TODO
-}
-
-void ArmRegCacheFPU::QFlush(int quad) {
- if (!MappableQ(quad)) {
- ERROR_LOG(Log::JIT, "Cannot flush non-mappable quad %i", quad);
- return;
- }
-
- if (qr[quad].isDirty && !qr[quad].isTemp) {
- INFO_LOG(Log::JIT, "Flushing Q%i (%s)", quad, GetVectorNotation(qr[quad].mipsVec, qr[quad].sz).c_str());
-
- ARMReg q = QuadAsQ(quad);
- // Unlike reads, when writing to the register file we need to be careful to write the correct
- // number of floats.
-
- switch (qr[quad].sz) {
- case V_Single:
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1_lane(F_32, q, R0, 0, true);
- // WARN_LOG(Log::JIT, "S: Falling back to individual flush: pc=%08x", js_->compilerPC);
- break;
- case V_Pair:
- if (Consecutive(qr[quad].vregs[0], qr[quad].vregs[1])) {
- // Can combine, it's a column!
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1(F_32, q, R0, 1, ALIGN_NONE); // TODO: Allow ALIGN_64 when applicable
- } else {
- // WARN_LOG(Log::JIT, "P: Falling back to individual flush: pc=%08x", js_->compilerPC);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1_lane(F_32, q, R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[1]), R1);
- emit_->VST1_lane(F_32, q, R0, 1, true);
- }
- break;
- case V_Triple:
- if (Consecutive(qr[quad].vregs[0], qr[quad].vregs[1], qr[quad].vregs[2])) {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1(F_32, QuadAsD(quad), R0, 1, ALIGN_NONE, REG_UPDATE); // TODO: Allow ALIGN_64 when applicable
- emit_->VST1_lane(F_32, q, R0, 2, true);
- } else {
- // WARN_LOG(Log::JIT, "T: Falling back to individual flush: pc=%08x", js_->compilerPC);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1_lane(F_32, q, R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[1]), R1);
- emit_->VST1_lane(F_32, q, R0, 1, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[2]), R1);
- emit_->VST1_lane(F_32, q, R0, 2, true);
- }
- break;
- case V_Quad:
- if (Consecutive(qr[quad].vregs[0], qr[quad].vregs[1], qr[quad].vregs[2], qr[quad].vregs[3])) {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1(F_32, QuadAsD(quad), R0, 2, ALIGN_NONE); // TODO: Allow ALIGN_64 when applicable
- } else {
- // WARN_LOG(Log::JIT, "Q: Falling back to individual flush: pc=%08x", js_->compilerPC);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[0]), R1);
- emit_->VST1_lane(F_32, q, R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[1]), R1);
- emit_->VST1_lane(F_32, q, R0, 1, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[2]), R1);
- emit_->VST1_lane(F_32, q, R0, 2, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(qr[quad].vregs[3]), R1);
- emit_->VST1_lane(F_32, q, R0, 3, true);
- }
- break;
- default:
- ERROR_LOG(Log::JIT, "Unknown quad size %i", qr[quad].sz);
- break;
- }
-
- qr[quad].isDirty = false;
-
- int n = GetNumVectorElements(qr[quad].sz);
- for (int i = 0; i < n; i++) {
- int vr = qr[quad].vregs[i];
- if (vr < 0 || vr > 128) {
- ERROR_LOG(Log::JIT, "Bad vr %i", vr);
- }
- FPURegMIPS &m = mr[32 + vr];
- m.loc = ML_MEM;
- m.lane = -1;
- m.reg = -1;
- }
-
- } else {
- if (qr[quad].isTemp) {
- WARN_LOG(Log::JIT, "Not flushing quad %i; dirty = %i, isTemp = %i", quad, qr[quad].isDirty, qr[quad].isTemp);
- }
- }
-
- qr[quad].isTemp = false;
- qr[quad].mipsVec = -1;
- qr[quad].sz = V_Invalid;
- memset(qr[quad].vregs, 0xFF, 4);
-}
-
-int ArmRegCacheFPU::QGetFreeQuad(int start, int count, const char *reason) {
- // Search for a free quad. A quad is free if the first register in it is free.
- for (int i = 0; i < count; i++) {
- int q = (i + start) & 15;
-
- if (!MappableQ(q))
- continue;
-
- // Don't steal temp quads!
- if (qr[q].mipsVec == (int)INVALID_REG && !qr[q].isTemp) {
- // INFO_LOG(Log::JIT, "Free quad: %i", q);
- // Oh yeah! Free quad!
- return q;
- }
- }
-
- // Okay, find the "best scoring" reg to replace. Scoring algorithm TBD but may include some
- // sort of age.
- int bestQuad = -1;
- int bestScore = -1;
- for (int i = 0; i < count; i++) {
- int q = (i + start) & 15;
-
- if (!MappableQ(q))
- continue;
- if (qr[q].spillLock)
- continue;
- if (qr[q].isTemp)
- continue;
-
- int score = 0;
- if (!qr[q].isDirty) {
- score += 5;
- }
-
- if (score > bestScore) {
- bestQuad = q;
- bestScore = score;
- }
- }
-
- if (bestQuad == -1) {
- ERROR_LOG(Log::JIT, "Failed finding a free quad. Things will now go haywire!");
- return -1;
- } else {
- INFO_LOG(Log::JIT, "No register found in %i and the next %i, kicked out #%i (%s)", start, count, bestQuad, reason ? reason : "no reason");
- QFlush(bestQuad);
- return bestQuad;
- }
-}
-
-ARMReg ArmRegCacheFPU::QAllocTemp(VectorSize sz) {
- int q = QGetFreeQuad(8, 16, "allocating temporary"); // Prefer high quads as temps
- if (q < 0) {
- ERROR_LOG(Log::JIT, "Failed to allocate temp quad");
- q = 0;
- }
- qr[q].spillLock = true;
- qr[q].isTemp = true;
- qr[q].sz = sz;
- qr[q].isDirty = false; // doesn't matter
-
- INFO_LOG(Log::JIT, "Allocated temp quad %i", q);
-
- if (sz == V_Single || sz == V_Pair) {
- return D_0(ARMReg(Q0 + q));
- } else {
- return ARMReg(Q0 + q);
- }
-}
-
-bool ArmRegCacheFPU::Consecutive(int v1, int v2) const {
- return (voffset[v1] + 1) == voffset[v2];
-}
-
-bool ArmRegCacheFPU::Consecutive(int v1, int v2, int v3) const {
- return Consecutive(v1, v2) && Consecutive(v2, v3);
-}
-
-bool ArmRegCacheFPU::Consecutive(int v1, int v2, int v3, int v4) const {
- return Consecutive(v1, v2) && Consecutive(v2, v3) && Consecutive(v3, v4);
-}
-
-void ArmRegCacheFPU::QMapMatrix(ARMReg *regs, int matrix, MatrixSize mz, int flags) {
- u8 vregs[4];
- if (flags & MAP_MTX_TRANSPOSED) {
- GetMatrixRows(matrix, mz, vregs);
- } else {
- GetMatrixColumns(matrix, mz, vregs);
- }
-
- // TODO: Zap existing mappings, reserve 4 consecutive regs, then do a fast load.
- int n = GetMatrixSide(mz);
- VectorSize vsz = GetVectorSize(mz);
- for (int i = 0; i < n; i++) {
- regs[i] = QMapReg(vregs[i], vsz, flags);
- }
-}
-
-ARMReg ArmRegCacheFPU::QMapReg(int vreg, VectorSize sz, int flags) {
- qTime_++;
-
- int n = GetNumVectorElements(sz);
- u8 vregs[4];
- GetVectorRegs(vregs, sz, vreg);
-
- // Range of registers to consider
- int start = 0;
- int count = 16;
-
- if (flags & MAP_PREFER_HIGH) {
- start = 8;
- } else if (flags & MAP_PREFER_LOW) {
- start = 4;
- } else if (flags & MAP_FORCE_LOW) {
- start = 4;
- count = 4;
- } else if (flags & MAP_FORCE_HIGH) {
- start = 8;
- count = 8;
- }
-
- // Let's check if they are all mapped in a quad somewhere.
- // At the same time, check for the quad already being mapped.
- // Later we can check for possible transposes as well.
-
- // First just loop over all registers. If it's here and not in range, or overlapped, kick.
- std::vector quadsToFlush;
- for (int i = 0; i < 16; i++) {
- int q = (i + start) & 15;
- if (!MappableQ(q))
- continue;
-
- // Skip unmapped quads.
- if (qr[q].sz == V_Invalid)
- continue;
-
- // Check if completely there already. If so, set spill-lock, transfer dirty flag and exit.
- if (vreg == qr[q].mipsVec && sz == qr[q].sz) {
- if (i < count) {
- INFO_LOG(Log::JIT, "Quad already mapped: %i : %i (size %i)", q, vreg, sz);
- qr[q].isDirty = qr[q].isDirty || (flags & MAP_DIRTY);
- qr[q].spillLock = true;
-
- // Sanity check vregs
- for (int i = 0; i < n; i++) {
- if (vregs[i] != qr[q].vregs[i]) {
- ERROR_LOG(Log::JIT, "Sanity check failed: %i vs %i", vregs[i], qr[q].vregs[i]);
- }
- }
-
- return (ARMReg)(Q0 + q);
- } else {
- INFO_LOG(Log::JIT, "Quad already mapped at %i which is out of requested range [%i-%i) (count = %i), needs moving. For now we flush.", q, start, start+count, count);
- quadsToFlush.push_back(q);
- continue;
- }
- }
-
- // Check for any overlap. Overlap == flush.
- int origN = GetNumVectorElements(qr[q].sz);
- for (int a = 0; a < n; a++) {
- for (int b = 0; b < origN; b++) {
- if (vregs[a] == qr[q].vregs[b]) {
- quadsToFlush.push_back(q);
- goto doubleBreak;
- }
- }
- }
- doubleBreak:
- ;
- }
-
- // We didn't find the extra register, but we got a list of regs to flush. Flush 'em.
- // Here we can check for opportunities to do a "transpose-flush" of row vectors, etc.
- if (!quadsToFlush.empty()) {
- INFO_LOG(Log::JIT, "New mapping %s collided with %d quads, flushing them.", GetVectorNotation(vreg, sz).c_str(), (int)quadsToFlush.size());
- }
- for (size_t i = 0; i < quadsToFlush.size(); i++) {
- QFlush(quadsToFlush[i]);
- }
-
- // Find where we want to map it, obeying the constraints we gave.
- int quad = QGetFreeQuad(start, count, "mapping");
- if (quad < 0)
- return INVALID_REG;
-
- // If parts of our register are elsewhere, and we are dirty, we need to flush them
- // before we reload in a new location.
- // This may be problematic if inputs overlap irregularly with output, say:
- // vdot S700, R000, C000
- // It might still work by accident...
- if (flags & MAP_DIRTY) {
- for (int i = 0; i < n; i++) {
- FlushV(vregs[i]);
- }
- }
-
- qr[quad].sz = sz;
- qr[quad].mipsVec = vreg;
-
- if ((flags & MAP_NOINIT) != MAP_NOINIT) {
- // Okay, now we will try to load the whole thing in one go. This is possible
- // if it's a row and easy if it's a single.
- // Rows are rare, columns are common - but thanks to our register reordering,
- // columns are actually in-order in memory.
- switch (sz) {
- case V_Single:
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 0, true);
- break;
- case V_Pair:
- if (Consecutive(vregs[0], vregs[1])) {
- // Can combine, it's a column!
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1(F_32, QuadAsD(quad), R0, 1, ALIGN_NONE); // TODO: Allow ALIGN_64 when applicable
- } else {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[1]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 1, true);
- }
- break;
- case V_Triple:
- if (Consecutive(vregs[0], vregs[1], vregs[2])) {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1(F_32, QuadAsD(quad), R0, 1, ALIGN_NONE, REG_UPDATE); // TODO: Allow ALIGN_64 when applicable
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 2, true);
- } else {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[1]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 1, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[2]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 2, true);
- }
- break;
- case V_Quad:
- if (Consecutive(vregs[0], vregs[1], vregs[2], vregs[3])) {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1(F_32, QuadAsD(quad), R0, 2, ALIGN_NONE); // TODO: Allow ALIGN_64 when applicable
- } else {
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[0]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 0, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[1]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 1, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[2]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 2, true);
- emit_->ADDI2R(R0, CTXREG, GetMipsRegOffsetV(vregs[3]), R1);
- emit_->VLD1_lane(F_32, QuadAsQ(quad), R0, 3, true);
- }
- break;
- default:
- ;
- }
- }
-
- // OK, let's fill out the arrays to confirm that we have grabbed these registers.
- for (int i = 0; i < n; i++) {
- int mipsReg = 32 + vregs[i];
- mr[mipsReg].loc = ML_ARMREG;
- mr[mipsReg].reg = QuadAsQ(quad);
- mr[mipsReg].lane = i;
- qr[quad].vregs[i] = vregs[i];
- }
- qr[quad].isDirty = (flags & MAP_DIRTY) != 0;
- qr[quad].spillLock = true;
-
- INFO_LOG(Log::JIT, "Mapped Q%i to vfpu %i (%s), sz=%i, dirty=%i", quad, vreg, GetVectorNotation(vreg, sz).c_str(), (int)sz, qr[quad].isDirty);
- if (sz == V_Single || sz == V_Pair) {
- return D_0(QuadAsQ(quad));
- } else {
- return QuadAsQ(quad);
- }
-}
-
diff --git a/Core/MIPS/ARM/ArmRegCacheFPU.h b/Core/MIPS/ARM/ArmRegCacheFPU.h
deleted file mode 100644
index 8fb2492cd5..0000000000
--- a/Core/MIPS/ARM/ArmRegCacheFPU.h
+++ /dev/null
@@ -1,209 +0,0 @@
-// 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/.
-
-#pragma once
-
-#pragma once
-
-#include "Core/MIPS/MIPS.h"
-#include "Core/MIPS/ARM/ArmRegCache.h"
-#include "Core/MIPS/MIPSVFPUUtils.h"
-#include "Common/ArmEmitter.h"
-
-namespace ArmJitConstants {
-
-enum {
- NUM_TEMPS = 16,
- TEMP0 = 32 + 128,
- TOTAL_MAPPABLE_MIPSFPUREGS = 32 + 128 + NUM_TEMPS,
-};
-
-enum {
- MAP_READ = 0,
- MAP_MTX_TRANSPOSED = 16,
- MAP_PREFER_LOW = 16,
- MAP_PREFER_HIGH = 32,
-
- // Force is not yet correctly implemented, if the reg is already mapped it will not move
- MAP_FORCE_LOW = 64, // Only map Q0-Q7 (and probably not Q0-Q3 as they are S registers so that leaves Q8-Q15)
- MAP_FORCE_HIGH = 128, // Only map Q8-Q15
-};
-
-}
-
-struct FPURegARM {
- int mipsReg; // if -1, no mipsreg attached.
- bool isDirty; // Should the register be written back?
-};
-
-struct FPURegQuad {
- int mipsVec;
- VectorSize sz;
- u8 vregs[4];
- bool isDirty;
- bool spillLock;
- bool isTemp;
-};
-
-struct FPURegMIPS {
- // Where is this MIPS register?
- ArmJitConstants::RegMIPSLoc loc;
- // Data (only one of these is used, depending on loc. Could make a union).
- u32 reg;
- int lane;
-
- bool spillLock; // if true, this register cannot be spilled.
- bool tempLock;
- // If loc == ML_MEM, it's back in its location in the CPU context struct.
-};
-
-namespace MIPSComp {
- struct JitOptions;
- struct JitState;
-}
-
-class ArmRegCacheFPU {
-public:
- ArmRegCacheFPU(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo);
- ~ArmRegCacheFPU() {}
-
- void Init(ArmGen::ARMXEmitter *emitter);
-
- void Start();
-
- // Protect the arm register containing a MIPS register from spilling, to ensure that
- // it's being kept allocated.
- void SpillLock(MIPSReg reg, MIPSReg reg2 = -1, MIPSReg reg3 = -1, MIPSReg reg4 = -1);
- void SpillLockV(MIPSReg r) { SpillLock(r + 32); }
-
- void ReleaseSpillLocksAndDiscardTemps();
- void ReleaseSpillLock(int mipsreg) {
- mr[mipsreg].spillLock = false;
- }
- void ReleaseSpillLockV(int mipsreg) {
- ReleaseSpillLock(mipsreg + 32);
- }
-
- void SetImm(MIPSReg reg, u32 immVal);
- bool IsImm(MIPSReg reg) const;
- u32 GetImm(MIPSReg reg) const;
-
- // Returns an ARM register containing the requested MIPS register.
- ArmGen::ARMReg MapReg(MIPSReg reg, int mapFlags = 0);
- void MapInIn(MIPSReg rd, MIPSReg rs);
- void MapDirty(MIPSReg rd);
- void MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad = true);
- void MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad = true);
- bool IsMapped(MIPSReg r);
- void FlushArmReg(ArmGen::ARMReg r);
- void FlushR(MIPSReg r);
- void DiscardR(MIPSReg r);
- ArmGen::ARMReg R(int preg); // Returns a cached register
-
- // VFPU register as single ARM VFP registers. Must not be used in the upcoming NEON mode!
- void MapRegV(int vreg, int flags = 0);
- void LoadToRegV(ArmGen::ARMReg armReg, int vreg);
- void MapInInV(int rt, int rs);
- void MapDirtyInV(int rd, int rs, bool avoidLoad = true);
- void MapDirtyInInV(int rd, int rs, int rt, bool avoidLoad = true);
-
- bool IsTempX(ArmGen::ARMReg r) const;
- MIPSReg GetTempV() { return GetTempR() - 32; }
- // VFPU registers as single VFP registers.
- ArmGen::ARMReg V(int vreg) { return R(vreg + 32); }
-
- int FlushGetSequential(int a);
- void FlushAll();
-
- // This one is allowed at any point.
- void FlushV(MIPSReg r);
-
- // VFPU registers mapped to match NEON quads (and doubles, for pairs and singles)
- // Here we return the ARM register directly instead of providing a "V" accessor
- // and so on. Might switch to this model for the other regallocs later.
-
- // Quad mapping does NOT look into the ar array. Instead we use the qr array to keep
- // track of what's in each quad.
-
- // Note that we automatically spill-lock EVERY Q REGISTER we map, unlike other types.
- // Need to explicitly allow spilling to get spilling.
- ArmGen::ARMReg QMapReg(int vreg, VectorSize sz, int flags);
-
- // TODO
- // Maps a matrix as a set of columns (yes, even transposed ones, always columns
- // as those are faster to load/flush). When possible it will map into consecutive
- // quad registers, enabling blazing-fast full-matrix loads, transposed or not.
- void QMapMatrix(ArmGen::ARMReg *regs, int matrix, MatrixSize mz, int flags);
-
- ArmGen::ARMReg QAllocTemp(VectorSize sz);
-
- void QAllowSpill(int quad);
- void QFlush(int quad);
- void QLoad4x4(MIPSGPReg regPtr, int vquads[4]);
- //void FlushQWithV(MIPSReg r);
-
- // NOTE: These require you to release spill locks manually!
- void MapRegsAndSpillLockV(int vec, VectorSize vsz, int flags);
- void MapRegsAndSpillLockV(const u8 *v, VectorSize vsz, int flags);
-
- void SpillLockV(const u8 *v, VectorSize vsz);
- void SpillLockV(int vec, VectorSize vsz);
-
- void SetEmitter(ArmGen::ARMXEmitter *emitter) { emit_ = emitter; }
-
- int GetMipsRegOffset(MIPSReg r);
- int GetMipsRegOffsetV(MIPSReg r) {
- return GetMipsRegOffset(r + 32);
- }
-
-private:
- bool Consecutive(int v1, int v2) const;
- bool Consecutive(int v1, int v2, int v3) const;
- bool Consecutive(int v1, int v2, int v3, int v4) const;
-
- MIPSReg GetTempR();
- const ArmGen::ARMReg *GetMIPSAllocationOrder(int &count);
- // This one WILL get a free quad as long as you haven't spill-locked them all.
- int QGetFreeQuad(int start, int count, const char *reason);
-
- void SetupInitialRegs();
-
- MIPSState *mips_;
- ArmGen::ARMXEmitter *emit_;
- MIPSComp::JitState *js_;
- MIPSComp::JitOptions *jo_;
-
- int qTime_;
-
- enum {
- // With NEON, we have 64 S = 32 D = 16 Q registers. Only the first 32 S registers
- // are individually mappable though.
- NUM_ARMFPUREG = 32,
- NUM_ARMQUADS = 16,
- NUM_MIPSFPUREG = ArmJitConstants::TOTAL_MAPPABLE_MIPSFPUREGS,
- };
-
- FPURegARM ar[NUM_ARMFPUREG];
- FPURegMIPS mr[NUM_MIPSFPUREG];
- FPURegQuad qr[NUM_ARMQUADS];
- FPURegMIPS *vr;
-
- bool pendingFlush;
- bool initialReady = false;
- FPURegARM arInitial[NUM_ARMFPUREG];
- FPURegMIPS mrInitial[NUM_MIPSFPUREG];
-};
diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp
index 62dd71f78a..7ed0442a33 100644
--- a/UI/DeveloperToolsScreen.cpp
+++ b/UI/DeveloperToolsScreen.cpp
@@ -153,8 +153,7 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) {
if (!canUseJit) {
core->HideChoice(1);
}
- // Every native JIT except ARM32's is IR-based and used for the JIT option, and ARM32 uses its
- // legacy JIT for both, so "JIT using IR" is the same as "JIT" everywhere.
+ // Every native JIT is IR-based, so "JIT using IR" is the same as "JIT".
core->HideChoice(3);
// Only the interpreter raises these, but don't hide it behind the CPU core choice - it'd just
diff --git a/UI/ImDebugger/ImJitViewer.cpp b/UI/ImDebugger/ImJitViewer.cpp
index 382562f498..fc5218d817 100644
--- a/UI/ImDebugger/ImJitViewer.cpp
+++ b/UI/ImDebugger/ImJitViewer.cpp
@@ -33,12 +33,8 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
return;
}
- // Every native JIT except ARM32's is IR-based.
-#if PPSSPP_ARCH(ARM)
- const bool hasIR = core == CPUCore::IR_INTERPRETER;
-#else
+ // Every native JIT is IR-based.
const bool hasIR = true;
-#endif
const bool hasTarget = core == CPUCore::JIT || core == CPUCore::JIT_IR;
// Options menu
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index 3b4d779e2a..cdb0b5f19b 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -278,9 +278,6 @@
-
-
-
@@ -565,16 +562,6 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 3ee11d3ef8..6c900373eb 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -192,16 +192,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -607,9 +597,6 @@
-
-
-
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 9e59e5849b..dd00926e83 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -510,16 +510,6 @@ ARCH_FILES := \
$(SRC)/GPU/Common/VertexDecoderX86.cpp
else ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a)
ARCH_FILES := \
- $(SRC)/Core/MIPS/ARM/ArmCompALU.cpp \
- $(SRC)/Core/MIPS/ARM/ArmCompBranch.cpp \
- $(SRC)/Core/MIPS/ARM/ArmCompFPU.cpp \
- $(SRC)/Core/MIPS/ARM/ArmCompLoadStore.cpp \
- $(SRC)/Core/MIPS/ARM/ArmCompVFPU.cpp \
- $(SRC)/Core/MIPS/ARM/ArmCompReplace.cpp \
- $(SRC)/Core/MIPS/ARM/ArmAsm.cpp \
- $(SRC)/Core/MIPS/ARM/ArmJit.cpp \
- $(SRC)/Core/MIPS/ARM/ArmRegCache.cpp \
- $(SRC)/Core/MIPS/ARM/ArmRegCacheFPU.cpp \
$(SRC)/Core/MIPS/ARM/ArmIRAsm.cpp \
$(SRC)/Core/MIPS/ARM/ArmIRCompALU.cpp \
$(SRC)/Core/MIPS/ARM/ArmIRCompBranch.cpp \
@@ -1097,7 +1087,6 @@ ifeq ($(UNITTEST),1)
LOCAL_MODULE := ppsspp_unittest
LOCAL_SRC_FILES := \
$(SRC)/unittest/JitHarness.cpp \
- $(SRC)/Core/MIPS/ARM/ArmRegCacheFPU.cpp \
$(SRC)/Core/Util/DisArm64.cpp \
$(SRC)/ext/riscv-disas.cpp \
$(SRC)/ext/loongarch-disasm.cpp \
diff --git a/docs/cpu-equivalence.md b/docs/cpu-equivalence.md
index af3fd6aa4d..6850786411 100644
--- a/docs/cpu-equivalence.md
+++ b/docs/cpu-equivalence.md
@@ -186,8 +186,9 @@ sign.
### ARM32
Measured on a Galaxy S8+ and A21 with the armeabi-v7a build (`android/ab.sh APP_ABI=armeabi-v7a HEADLESS=1
-UNITTEST=1 SKIPAPP=1`, run over adb). ARM32 has three cores: `CreateNativeJit` returns the legacy
-`ArmJit` for both `--cpu=jit` and `--cpu=jit-ir`.
+UNITTEST=1 SKIPAPP=1`, run over adb). These measurements predate the IR-based ARM32 JIT, when
+`CreateNativeJit` returned the legacy `ArmJit` (since removed). The IR JIT gives the IR interpreter's
+end state in every fuzz case under qemu-arm (`./b.sh --armhf`, see building.md).
- The interpreter gives the same end state as on ARM64 in all 20000 integer/FPU/memory fuzz
cases, and in all but 3 of 20000 mixed ones (horizontal-op NaNs into vmin/vmax, as on x86-64).
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 6d24e1899e..abb9c6accb 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -918,16 +918,6 @@ ifeq ($(WITH_DYNAREC),1)
ifeq ($(TARGET_ARCH),arm)
DYNAFLAGS += -D_ARCH_32
SOURCES_CXX += $(COMMONDIR)/ArmEmitter.cpp \
- $(COREDIR)/MIPS/ARM/ArmAsm.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompALU.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompBranch.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompFPU.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompLoadStore.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompVFPU.cpp \
- $(COREDIR)/MIPS/ARM/ArmCompReplace.cpp \
- $(COREDIR)/MIPS/ARM/ArmJit.cpp \
- $(COREDIR)/MIPS/ARM/ArmRegCache.cpp \
- $(COREDIR)/MIPS/ARM/ArmRegCacheFPU.cpp \
$(COREDIR)/MIPS/ARM/ArmIRAsm.cpp \
$(COREDIR)/MIPS/ARM/ArmIRCompALU.cpp \
$(COREDIR)/MIPS/ARM/ArmIRCompBranch.cpp \
diff --git a/unittest/TestArmEmitter.cpp b/unittest/TestArmEmitter.cpp
index 406c095a10..207cd1f970 100644
--- a/unittest/TestArmEmitter.cpp
+++ b/unittest/TestArmEmitter.cpp
@@ -1,12 +1,8 @@
#include "Common/ArmEmitter.h"
-#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
-#include "Core/MIPS/ARM/ArmJit.h"
-#include "Core/MIPS/JitCommon/JitState.h"
-#include "Core/MIPS/MIPSAnalyst.h"
-#include "Core/MIPS/MIPSVFPUUtils.h"
#include "ext/disarm.h"
#include
+#include
#include "UnitTest.h"
@@ -19,19 +15,6 @@ static bool CheckLast(const ArmGen::ARMXEmitter &emit, const char *comp) {
return true;
}
-static void DisassembleARMBetween(const u8 *start, const u8 *end) {
- while (start < end) {
- char disasm[512];
- uint32_t instr;
- memcpy(&instr, start, 4);
- ArmDis(0, instr, disasm, sizeof(disasm), true);
- if (g_testLog) {
- printf("%s\n", disasm);
- }
- start += 4;
- }
-}
-
bool TestArmEmitter() {
using namespace ArmGen;
@@ -240,54 +223,5 @@ bool TestArmEmitter() {
//emitter.VMOV(S1, 112);
//RET(CheckLast(emitter, "eef70a00 VMOV.f32 s1, #112"));
-
- const u8 *codeStart = emitter.GetCodePointer();
-
- MIPSState mipsState;
- MIPSComp::JitState js;
- MIPSComp::JitOptions jo;
- ArmRegCacheFPU fpr(&mipsState, &js, &jo);
- fpr.SetEmitter(&emitter);
- int C000 = GetColumnName(0, M_4x4, 0, 0);
- int C010 = GetColumnName(0, M_4x4, 1, 0);
- int C020 = GetColumnName(0, M_4x4, 2, 0);
- int C030 = GetColumnName(0, M_4x4, 3, 0);
- int R000 = GetRowName(0, M_4x4, 0, 0);
- int R001 = GetRowName(0, M_4x4, 1, 0);
- int R002 = GetRowName(0, M_4x4, 2, 0);
- int R003 = GetRowName(0, M_4x4, 3, 0);
- if (g_testLog) {
- printf("Col 010: %s\n", GetVectorNotation(C010, V_Quad).c_str());
- printf("Row 003: %s\n", GetVectorNotation(R003, V_Quad).c_str());
- }
-
- MIPSAnalyst::AnalysisResults results;
- memset(&results, 0, sizeof(results));
-
- using namespace ArmJitConstants;
-
- fpr.Start();
- fpr.QMapReg(C000, V_Quad, MAP_DIRTY);
- fpr.QMapReg(C010, V_Quad, MAP_DIRTY);
- fpr.QMapReg(C020, V_Quad, MAP_DIRTY);
- fpr.QMapReg(C030, V_Quad, MAP_DIRTY);
- emitter.ORR(R0, R0, R0);
- fpr.QMapReg(R000, V_Quad, MAP_DIRTY);
- fpr.FlushAll();
-
- fpr.Start();
- emitter.ORR(R0, R0, R0);
- fpr.QMapReg(R000, V_Quad, MAP_DIRTY);
- fpr.QMapReg(R001, V_Quad, MAP_DIRTY);
- fpr.QMapReg(R002, V_Quad, MAP_DIRTY);
- fpr.QMapReg(R003, V_Quad, MAP_DIRTY);
- emitter.ORR(R0, R0, R0);
- fpr.QMapReg(C000, V_Quad, MAP_DIRTY);
- fpr.FlushAll();
-
- const u8 *codeEnd = emitter.GetCodePointer();
-
- DisassembleARMBetween(codeStart, codeEnd);
-
return true;
}