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; }