diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 61bf4de6b0..526a1947ea 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -38,6 +38,17 @@ list(APPEND CoreExtra MIPS/ARM/ArmRegCache.h MIPS/ARM/ArmRegCacheFPU.cpp MIPS/ARM/ArmRegCacheFPU.h + MIPS/ARM/ArmIRAsm.cpp + MIPS/ARM/ArmIRCompALU.cpp + MIPS/ARM/ArmIRCompBranch.cpp + MIPS/ARM/ArmIRCompFPU.cpp + MIPS/ARM/ArmIRCompLoadStore.cpp + MIPS/ARM/ArmIRCompSystem.cpp + MIPS/ARM/ArmIRCompVec.cpp + MIPS/ARM/ArmIRJit.cpp + MIPS/ARM/ArmIRRegCache.cpp + MIPS/ARM/ArmIRJit.h + MIPS/ARM/ArmIRRegCache.h ) list(APPEND CoreExtra diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 192b4c0ef7..46c048b232 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -742,6 +742,15 @@ true + + + + + + + + + true true @@ -1097,6 +1106,8 @@ true + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 57782a769b..5711fbc2a4 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -450,6 +450,33 @@ MIPS\ARM + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + Font @@ -1731,6 +1758,12 @@ MIPS\ARM + + MIPS\ARM + + + MIPS\ARM + Font diff --git a/Core/MIPS/ARM/ArmIRAsm.cpp b/Core/MIPS/ARM/ArmIRAsm.cpp new file mode 100644 index 0000000000..d8755a52bd --- /dev/null +++ b/Core/MIPS/ARM/ArmIRAsm.cpp @@ -0,0 +1,223 @@ +// 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/Log.h" +#include "Common/MemoryUtil.h" +#include "Core/CoreTiming.h" +#include "Core/MemMap.h" +#include "Core/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" +#include "Core/MIPS/JitCommon/JitCommon.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/Core.h" + +std::vector DisassembleArm2(const u8 *data, int size); + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +static const bool enableDisasm = false; + +void ArmJitBackend::GenerateFixedCode(MIPSState *mipsState) { + // This will be used as a writable scratch area, always 32-bit accessible. + const u8 *start = AlignCodePage(); + if (DebugProfilerEnabled()) { + ProtectMemoryPages(start, GetMemoryProtectPageSize(), MEM_PROT_READ | MEM_PROT_WRITE); + hooks_.profilerPC = (uint32_t *)GetWritableCodePtr(); + Write32(0); + hooks_.profilerStatus = (IRProfilerStatus *)GetWritableCodePtr(); + Write32(0); + } + + const u8 *disasmStart = AlignCodePage(); + BeginWrite(GetMemoryProtectPageSize()); + + // These are called with BL, so LR has to survive. + restoreRoundingMode_ = AlignCode16(); + { + VMRS(SCRATCH2); + // Outside the JIT we run with round-to-nearest and flush-to-zero off. + BIC(SCRATCH2, SCRATCH2, AssumeMakeOperand2((3 | 4) << 22)); + VMSR(SCRATCH2); + B(R_LR); + } + + // Must preserve SCRATCH1, since it may hold the PC. Destroys SCRATCH2 and R12. + applyRoundingMode_ = AlignCode16(); + { + LDR(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31)); + + TST(SCRATCH2, AssumeMakeOperand2(1 << 24)); + AND(SCRATCH2, SCRATCH2, Operand2(3)); + SetCC(CC_NEQ); + ADD(SCRATCH2, SCRATCH2, 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(SCRATCH2, 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(R12, SCRATCH2, Operand2(3)); + CMP(R12, Operand2(1)); + + SetCC(CC_EQ); ADD(SCRATCH2, SCRATCH2, Operand2(2)); + SetCC(CC_GT); SUB(SCRATCH2, SCRATCH2, Operand2(1)); + SetCC(CC_AL); + + VMRS(R12); + // Assume we're always in round-to-nearest mode beforehand. + // But we need to clear flush to zero in this case anyway. + BIC(R12, R12, AssumeMakeOperand2((3 | 4) << 22)); + ORR(R12, R12, Operand2(SCRATCH2, ST_LSL, 22)); + VMSR(R12); + + SetJumpTarget(skip); + B(R_LR); + } + + hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16(); + + // Nine registers, so we're misaligned by 4 for calls, which the SUB fixes. + PUSH(9, R4, R5, R6, R7, R8, R9, R10, R11, R_LR); + SUB(R_SP, R_SP, 4); + VPUSH(D8, 8); + + // Fixed registers, these are always kept when in Jit context. + MOVP2R(MEMBASEREG, Memory::base); + MOVP2R(CTXREG, mipsState); + + LoadStaticRegisters(); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + MovFromPC(SCRATCH1); + WriteDebugPC(SCRATCH1); + outerLoopPCInSCRATCH1_ = GetCodePtr(); + MovToPC(SCRATCH1); + outerLoop_ = GetCodePtr(); + SaveStaticRegisters(); // Advance can change the downcount, so must save/restore + RestoreRoundingMode(true); + WriteDebugProfilerStatus(IRProfilerStatus::TIMER_ADVANCE); + QuickCallFunctionR(R12, &CoreTiming::Advance, CTXREG); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + ApplyRoundingMode(true); + LoadStaticRegisters(); + + dispatcherCheckCoreState_ = GetCodePtr(); + + MOVP2R(SCRATCH1, &coreState); + LDR(SCRATCH1, SCRATCH1, 0); + CMP(SCRATCH1, Operand2(0)); + FixupBranch badCoreState = B_CC(CC_NEQ); + + // Check downcount. + CMP(DOWNCOUNTREG, Operand2(0)); + B_CC(CC_MI, outerLoop_); + FixupBranch skipToRealDispatch = B(); + + dispatcherPCInSCRATCH1_ = GetCodePtr(); + MovToPC(SCRATCH1); + + hooks_.dispatcher = GetCodePtr(); + + CMP(DOWNCOUNTREG, Operand2(0)); + FixupBranch bail = B_CC(CC_MI); + SetJumpTarget(skipToRealDispatch); + + dispatcherNoCheck_ = GetCodePtr(); + + MovFromPC(SCRATCH1); + WriteDebugPC(SCRATCH1); + static_assert(Memory::BLOCK_SHADOW_ENTRY_MASK == 0x0FFFFFFC, "BICs below assume the 32-bit shadow mask"); + BIC(SCRATCH1, SCRATCH1, Operand2(0xF0, 4)); // &= 0x0FFFFFFF. Rotation is to the right, in 2-bit increments. + BIC(SCRATCH1, SCRATCH1, Operand2(3)); + MOVP2R(SCRATCH2, Memory::blockShadow); + hooks_.dispatchFetch = GetCodePtr(); + LDR(SCRATCH1, SCRATCH2, SCRATCH1); + // The entry is the block's offset into the code space, or 0 for none. + CMP(SCRATCH1, Operand2(0)); + FixupBranch noBlock = B_CC(CC_EQ); + MOVP2R(SCRATCH2, GetBasePtr()); + ADD(SCRATCH1, SCRATCH1, SCRATCH2); + B(SCRATCH1); + SetJumpTarget(noBlock); + + // No block found, let's jit. We don't need to save static regs, they're all callee saved. + // 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. + hooks_.blockShadowHook = GetCodePtr(); + RestoreRoundingMode(true); + WriteDebugProfilerStatus(IRProfilerStatus::COMPILING); + QuickCallFunctionR(R12, &MIPSComp::JitAt, CTXREG); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + ApplyRoundingMode(true); + + // Let's just dispatch again, we'll enter the block since we know it's there. + B(dispatcherNoCheck_); + + SetJumpTarget(bail); + + MOVP2R(SCRATCH1, &coreState); + LDR(SCRATCH1, SCRATCH1, 0); + CMP(SCRATCH1, Operand2(0)); + B_CC(CC_EQ, outerLoop_); + + const uint8_t *quitLoop = GetCodePtr(); + SetJumpTarget(badCoreState); + + WriteDebugProfilerStatus(IRProfilerStatus::NOT_RUNNING); + SaveStaticRegisters(); + RestoreRoundingMode(true); + + VPOP(D8, 8); + ADD(R_SP, R_SP, 4); + POP(9, R4, R5, R6, R7, R8, R9, R10, R11, R_PC); // Returns + + hooks_.crashHandler = GetCodePtr(); + MOVP2R(SCRATCH1, &coreState); + MOVI2R(SCRATCH2, CORE_RUNTIME_ERROR); + STR(SCRATCH2, SCRATCH1, 0); + B(quitLoop); + + // Leave this at the end, add more stuff above. + if (enableDisasm) { + std::vector lines = DisassembleArm2(disasmStart, (int)(GetCodePtr() - disasmStart)); + for (auto s : lines) { + INFO_LOG(Log::JIT, "%s", s.c_str()); + } + } + + // Let's spare the pre-generated code from unprotect-reprotect. + AlignCodePage(); + jitStartOffset_ = (int)(GetCodePtr() - start); + // Don't forget to zap the instruction cache! This must stay at the end of this function. + FlushIcache(); + EndWrite(); +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompALU.cpp b/Core/MIPS/ARM/ArmIRCompALU.cpp new file mode 100644 index 0000000000..2826a6e3ab --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompALU.cpp @@ -0,0 +1,141 @@ +// 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/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for integer arithmetic, logic, shifts, compares, and multiplies. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_Arith(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Assign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Bits(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Compare(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_CondAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Div(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_HiLo(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Logic(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Mult(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Shift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompBranch.cpp b/Core/MIPS/ARM/ArmIRCompBranch.cpp new file mode 100644 index 0000000000..aa0336ad77 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompBranch.cpp @@ -0,0 +1,134 @@ +// 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/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for exits. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_Exit(IRInst inst) { + CONDITIONAL_DISABLE; + + ARMReg exitReg = INVALID_REG; + switch (inst.op) { + case IROp::ExitToConst: + FlushAll(); + WriteConstExit(inst.constant); + break; + + case IROp::ExitToReg: + exitReg = regs_.MapGPR(inst.src1); + FlushAll(); + MOV(SCRATCH1, exitReg); + B(dispatcherPCInSCRATCH1_); + break; + + case IROp::ExitToPC: + FlushAll(); + B(dispatcherCheckCoreState_); + break; + + default: + INVALIDOP; + break; + } +} + +void ArmJitBackend::CompIR_ExitIf(IRInst inst) { + CONDITIONAL_DISABLE; + + ARMReg lhs = INVALID_REG; + ARMReg rhs = INVALID_REG; + FixupBranch fixup; + switch (inst.op) { + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) { + lhs = regs_.MapGPR(inst.src2); + FlushAll(); + CMP(lhs, Operand2(0)); + } else if (regs_.IsGPRImm(inst.src2) && regs_.GetGPRImm(inst.src2) == 0) { + lhs = regs_.MapGPR(inst.src1); + FlushAll(); + CMP(lhs, Operand2(0)); + } else { + regs_.Map(inst); + lhs = regs_.R(inst.src1); + rhs = regs_.R(inst.src2); + FlushAll(); + CMP(lhs, rhs); + } + + fixup = B_CC(inst.op == IROp::ExitToConstIfEq ? CC_NEQ : CC_EQ); + WriteConstExit(inst.constant); + SetJumpTarget(fixup); + break; + + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + { + lhs = regs_.MapGPR(inst.src1); + FlushAll(); + CMP(lhs, Operand2(0)); + // Branch over the exit when the condition fails. + CCFlags skip = CC_AL; + switch (inst.op) { + case IROp::ExitToConstIfGtZ: skip = CC_LE; break; + case IROp::ExitToConstIfGeZ: skip = CC_LT; break; + case IROp::ExitToConstIfLtZ: skip = CC_GE; break; + case IROp::ExitToConstIfLeZ: skip = CC_GT; break; + default: break; + } + fixup = B_CC(skip); + WriteConstExit(inst.constant); + SetJumpTarget(fixup); + break; + } + + case IROp::ExitToConstIfFpTrue: + case IROp::ExitToConstIfFpFalse: + // Note: not used. + DISABLE; + break; + + default: + INVALIDOP; + break; + } +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompFPU.cpp b/Core/MIPS/ARM/ArmIRCompFPU.cpp new file mode 100644 index 0000000000..117477a1f6 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompFPU.cpp @@ -0,0 +1,146 @@ +// 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/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for floating point arithmetic, compares, and conversions. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_FArith(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FCompare(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FCondAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FCvt(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FRound(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FSat(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FSpecial(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_RoundingMode(IRInst inst) { + CONDITIONAL_DISABLE; + + // The helpers are called with BL and use R12, so those mustn't hold anything. + switch (inst.op) { + case IROp::RestoreRoundingMode: + regs_.FlushBeforeCall(); + RestoreRoundingMode(); + break; + + case IROp::ApplyRoundingMode: + regs_.FlushBeforeCall(); + ApplyRoundingMode(); + break; + + case IROp::UpdateRoundingMode: + UpdateRoundingMode(); + break; + + default: + INVALIDOP; + break; + } +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompLoadStore.cpp b/Core/MIPS/ARM/ArmIRCompLoadStore.cpp new file mode 100644 index 0000000000..343461b593 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompLoadStore.cpp @@ -0,0 +1,131 @@ +// 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/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for memory loads and stores. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_CondStore(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FLoad(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_FStore(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Load(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_LoadShift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_Store(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_StoreShift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_VecLoad(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_VecStore(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompSystem.cpp b/Core/MIPS/ARM/ArmIRCompSystem.cpp new file mode 100644 index 0000000000..129632f7d8 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompSystem.cpp @@ -0,0 +1,427 @@ +// 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 "Core/Core.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/ReplaceTables.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPSAnalyst.h" +#include "Core/MIPS/IR/IRInterpreter.h" +#include "Core/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for basic PC/downcount accounting, syscalls, debug funcs, etc. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_Basic(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Downcount: + SUBI2R(DOWNCOUNTREG, DOWNCOUNTREG, inst.constant, SCRATCH1); + break; + + case IROp::SetConst: + regs_.SetGPRImm(inst.dest, inst.constant); + break; + + case IROp::SetConstF: + { + regs_.Map(inst); + float f; + memcpy(&f, &inst.constant, sizeof(f)); + MOVI2F(regs_.F(inst.dest), f, SCRATCH1); + break; + } + + case IROp::SetPC: + regs_.Map(inst); + MovToPC(regs_.R(inst.src1)); + break; + + case IROp::SetPCConst: + lastConstPC_ = inst.constant; + MOVI2R(SCRATCH1, inst.constant); + MovToPC(SCRATCH1); + break; + + default: + INVALIDOP; + break; + } +} + +void ArmJitBackend::CompIR_Breakpoint(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Breakpoint: + FlushAll(); + // Note: the constant could be a delay slot. + MOVI2R(R0, inst.constant); + QuickCallFunction(R12, &IRRunBreakpoint); + CMP(R0, Operand2(0)); + B_CC(CC_NEQ, dispatcherCheckCoreState_); + break; + + case IROp::MemoryCheck: + if (regs_.IsGPRImm(inst.src1)) { + uint32_t iaddr = regs_.GetGPRImm(inst.src1) + inst.constant; + uint32_t checkedPC = lastConstPC_ + inst.dest; + int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC); + if (size == 0) { + checkedPC += 4; + size = MIPSAnalyst::OpMemoryAccessSize(checkedPC); + } + bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC); + + MemCheck check; + if (g_breakpoints.GetMemCheckInRange(iaddr, size, &check)) { + if (!(check.cond & MEMCHECK_READ) && !isWrite) + break; + if (!(check.cond & (MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE)) && isWrite) + break; + + // We need to flush, or conditions and log expressions will see old register values. + FlushAll(); + + MOVI2R(R0, checkedPC); + MOVI2R(R1, iaddr); + QuickCallFunction(R12, &IRRunMemCheck); + CMP(R0, Operand2(0)); + B_CC(CC_NEQ, dispatcherCheckCoreState_); + } + } else { + uint32_t checkedPC = lastConstPC_ + inst.dest; + int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC); + if (size == 0) { + checkedPC += 4; + size = MIPSAnalyst::OpMemoryAccessSize(checkedPC); + } + bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC); + + const auto memchecks = g_breakpoints.GetMemCheckRanges(isWrite); + // We can trivially skip if there are no checks for this type (i.e. read vs write.) + if (memchecks.empty()) + break; + + // We need to flush, or conditions and log expressions will see old register values. + // The flush may use SCRATCH1, so read the address after it. R12 is free after it too. + FlushAll(); + LDR(SCRATCH1, CTXREG, inst.src1 * 4); + ADDI2R(SCRATCH1, SCRATCH1, inst.constant, SCRATCH2); + + std::vector hitChecks; + for (auto it : memchecks) { + if (it.end != 0) { + // A hit when start - size < addr < end, as an unsigned range check. + uint32_t lo = it.start - size + 1; + SUBI2R(R12, SCRATCH1, lo, SCRATCH2); + CMPI2R(R12, it.end - lo, SCRATCH2); + hitChecks.push_back(B_CC(CC_LO)); + } else { + CMPI2R(SCRATCH1, it.start, SCRATCH2); + hitChecks.push_back(B_CC(CC_EQ)); + } + } + + FixupBranch noHits = B(); + + // Okay, now land any hit here. + for (auto &fixup : hitChecks) + SetJumpTarget(fixup); + hitChecks.clear(); + + MOV(R1, SCRATCH1); + MOVI2R(R0, checkedPC); + QuickCallFunction(R12, &IRRunMemCheck); + CMP(R0, Operand2(0)); + B_CC(CC_NEQ, dispatcherCheckCoreState_); + + SetJumpTarget(noHits); + } + break; + + default: + INVALIDOP; + break; + } +} + +void ArmJitBackend::CompIR_System(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Syscall: + FlushAll(); + SaveStaticRegisters(); + + WriteDebugProfilerStatus(IRProfilerStatus::SYSCALL); +#ifdef USE_PROFILER + // When profiling, we can't skip CallSyscall, since it times syscalls. + MOVI2R(R0, inst.constant); + QuickCallFunction(R12, &CallSyscall); +#else + // Skip the CallSyscall where possible. + { + MIPSOpcode op(inst.constant); + const HLEFunction *func = GetSyscallFunctionData(op, 0); + if (func) { + void *quickFunc = GetQuickSyscallFunc(func, op); + if (quickFunc) { + MOVP2R(R0, func); + QuickCallFunction(R12, (const u8 *)quickFunc); + } else { + MOVI2R(R0, inst.constant); + QuickCallFunction(R12, &CallSyscall); + } + } else { + // Shouldn't get here. + MOVI2R(R0, 0); + QuickCallFunction(R12, &CallSyscallUnresolvedAtPC); + } + } +#endif + + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + LoadStaticRegisters(); + // This is always followed by an ExitToPC, where we check coreState. + break; + + case IROp::SyscallUnresolved: + FlushAll(); + SaveStaticRegisters(); + WriteDebugProfilerStatus(IRProfilerStatus::SYSCALL); + MOVI2R(R0, inst.constant); + QuickCallFunction(R12, &CallSyscallUnresolvedAtPC); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + LoadStaticRegisters(); + break; + + case IROp::CallReplacement: + FlushAll(); + SaveStaticRegisters(); + WriteDebugProfilerStatus(IRProfilerStatus::REPLACEMENT); + QuickCallFunction(R12, GetReplacementFunc(inst.constant)->replaceFunc); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + LoadStaticRegisters(); + + // Absolute value the result and subtract. + CMP(R0, Operand2(0)); + SetCC(CC_MI); + RSB(SCRATCH2, R0, Operand2(0)); + SetCC(CC_PL); + MOV(SCRATCH2, R0); + SetCC(CC_AL); + SUB(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH2); + + // Everything was flushed, so this can't touch R0. + // Set dest reg to the sign of the result. + regs_.Map(inst); + ASR(regs_.R(inst.dest), R0, 31); + break; + + case IROp::Break: + FlushAll(); + // This doesn't naturally have restore/apply around it. + RestoreRoundingMode(true); + SaveStaticRegisters(); + MovFromPC(R0); + QuickCallFunction(R12, &Core_BreakException); + LoadStaticRegisters(); + ApplyRoundingMode(true); + MovFromPC(SCRATCH1); + ADDI2R(SCRATCH1, SCRATCH1, 4, SCRATCH2); + B(dispatcherPCInSCRATCH1_); + break; + + default: + INVALIDOP; + break; + } +} + +void ArmJitBackend::CompIR_Transfer(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::SetCtrlVFPU: + regs_.SetGPRImm(IRREG_VFPU_CTRL_BASE + inst.dest, inst.constant); + break; + + case IROp::SetCtrlVFPUReg: + regs_.Map(inst); + MOV(regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.R(inst.src1)); + break; + + case IROp::SetCtrlVFPUFReg: + regs_.Map(inst); + VMOV(regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.F(inst.src1)); + break; + + case IROp::FpCondFromReg: + regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } }); + MOV(regs_.R(IRREG_FPCOND), regs_.R(inst.src1)); + break; + + case IROp::FpCondToReg: + regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } }); + MOV(regs_.R(inst.dest), regs_.R(IRREG_FPCOND)); + break; + + case IROp::FpCtrlFromReg: + regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } }); + ANDI2R(SCRATCH1, regs_.R(inst.src1), 0x0181FFFF, SCRATCH2); + // Extract the new fpcond value. + UBFX(regs_.R(IRREG_FPCOND), SCRATCH1, 23, 1); + STR(SCRATCH1, CTXREG, IRREG_FCR31 * 4); + break; + + case IROp::FpCtrlToReg: + regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } }); + // Load fcr31 and clear the fpcond bit. + LDR(regs_.R(inst.dest), CTXREG, IRREG_FCR31 * 4); + BFI(regs_.R(inst.dest), regs_.R(IRREG_FPCOND), 23, 1); + // Also update mips->fcr31 while we're here. + STR(regs_.R(inst.dest), CTXREG, IRREG_FCR31 * 4); + break; + + case IROp::VfpuCtrlToReg: + regs_.Map(inst); + MOV(regs_.R(inst.dest), regs_.R(IRREG_VFPU_CTRL_BASE + inst.src1)); + break; + + case IROp::FMovFromGPR: + if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) { + regs_.MapFPR(inst.dest, MIPSMap::NOINIT); + MOVI2F(regs_.F(inst.dest), 0.0f, SCRATCH1); + } else { + regs_.Map(inst); + VMOV(regs_.F(inst.dest), regs_.R(inst.src1)); + } + break; + + case IROp::FMovToGPR: + regs_.Map(inst); + VMOV(regs_.R(inst.dest), regs_.F(inst.src1)); + break; + + default: + INVALIDOP; + break; + } +} + +void ArmJitBackend::CompIR_ValidateAddress(IRInst inst) { + CONDITIONAL_DISABLE; + + bool isWrite = inst.src2 & 1; + int alignment = 0; + switch (inst.op) { + case IROp::ValidateAddress8: + alignment = 1; + break; + + case IROp::ValidateAddress16: + alignment = 2; + break; + + case IROp::ValidateAddress32: + alignment = 4; + break; + + case IROp::ValidateAddress128: + alignment = 16; + break; + + default: + INVALIDOP; + break; + } + + regs_.Map(inst); + ADDI2R(SCRATCH1, regs_.R(inst.src1), inst.constant, SCRATCH2); + BIC(SCRATCH1, SCRATCH1, Operand2(0xC0, 4)); + + std::vector validJumps; + + FixupBranch unaligned; + if (alignment != 1) { + TST(SCRATCH1, Operand2(alignment - 1)); + unaligned = B_CC(CC_NEQ); + } + + CMPI2R(SCRATCH1, PSP_GetUserMemoryEnd() - alignment, SCRATCH2); + FixupBranch tooHighRAM = B_CC(CC_HI); + CMPI2R(SCRATCH1, PSP_GetKernelMemoryBase(), SCRATCH2); + validJumps.push_back(B_CC(CC_HS)); + + CMPI2R(SCRATCH1, PSP_GetVidMemEnd() - alignment, SCRATCH2); + FixupBranch tooHighVid = B_CC(CC_HI); + CMPI2R(SCRATCH1, PSP_GetVidMemBase(), SCRATCH2); + validJumps.push_back(B_CC(CC_HS)); + + CMPI2R(SCRATCH1, PSP_GetScratchpadMemoryEnd() - alignment, SCRATCH2); + FixupBranch tooHighScratch = B_CC(CC_HI); + CMPI2R(SCRATCH1, PSP_GetScratchpadMemoryBase(), SCRATCH2); + validJumps.push_back(B_CC(CC_HS)); + + if (alignment != 1) + SetJumpTarget(unaligned); + SetJumpTarget(tooHighRAM); + SetJumpTarget(tooHighVid); + SetJumpTarget(tooHighScratch); + + // If we got here, something unusual and bad happened, so we'll always go back to the dispatcher. + // Because of that, we can avoid flushing outside this case. The flush may use SCRATCH1, so the + // address waits on the stack (two regs, to keep it aligned for the call). + PUSH(2, SCRATCH1, SCRATCH2); + auto regsCopy = regs_; + regsCopy.FlushAll(); + POP(2, SCRATCH1, SCRATCH2); + + // Ignores the return value, always returns to the dispatcher. + // Otherwise would need a thunk to restore regs. + MOVI2R(R1, alignment); + MOVI2R(R2, isWrite ? 1 : 0); + QuickCallFunction(R12, &ReportBadAddress); + B(dispatcherCheckCoreState_); + + for (FixupBranch &b : validJumps) + SetJumpTarget(b); +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRCompVec.cpp b/Core/MIPS/ARM/ArmIRCompVec.cpp new file mode 100644 index 0000000000..ed3ab062c8 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRCompVec.cpp @@ -0,0 +1,81 @@ +// 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/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +// This file contains compilation for Vec4 operations. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +void ArmJitBackend::CompIR_VecArith(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_VecAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_VecHoriz(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +void ArmJitBackend::CompIR_VecPack(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + default: + CompIR_Generic(inst); + break; + } +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRJit.cpp b/Core/MIPS/ARM/ArmIRJit.cpp new file mode 100644 index 0000000000..605c39a180 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRJit.cpp @@ -0,0 +1,375 @@ +// 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 +#include "Common/MemoryUtil.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPSTables.h" +#include "Core/MIPS/ARM/ArmIRJit.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +std::vector DisassembleArm2(const u8 *data, int size); + +namespace MIPSComp { + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +// Invalidations need at most two MOVs and a B. +static constexpr int MIN_BLOCK_NORMAL_LEN = 12; +// As long as we can fit a B, we should be fine. +static constexpr int MIN_BLOCK_EXIT_LEN = 4; + +ArmJitBackend::ArmJitBackend(JitOptions &jitopt, IRBlockCache &blocks) + : IRNativeBackend(blocks), jo(jitopt), regs_(&jo) { + // 32-bit addresses can't be pointerified, and there are too few registers for static allocation. + jo.enablePointerify = false; + jo.useStaticAlloc = false; + jo.optimizeForInterpreter = false; + + // Since we store the offset, this is as big as it can be. + AllocCodeSpace(1024 * 1024 * 16); + + regs_.Init(this); +} + +ArmJitBackend::~ArmJitBackend() {} + +static void NoBlockExits() { + _assert_msg_(false, "Never exited block, invalid IR?"); +} + +bool ArmJitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) { + if (GetSpaceLeft() < 0x800) + return false; + + IRBlock *block = irBlockCache->GetBlock(block_num); + BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32)); + + u32 startPC = block->GetOriginalStart(); + bool wroteCheckedOffset = false; + if (jo.enableBlocklink && !jo.useBackJump) { + SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer())); + wroteCheckedOffset = true; + + WriteDebugPC(startPC); + + CMP(DOWNCOUNTREG, Operand2(0)); + FixupBranch normalEntry = B_CC(CC_PL); + MOVI2R(SCRATCH1, startPC); + B(outerLoopPCInSCRATCH1_); + SetJumpTarget(normalEntry); + } + + // Don't worry, the codespace isn't large enough to overflow offsets. + const u8 *blockStart = GetCodePointer(); + block->SetNativeOffset((int)GetOffset(blockStart)); + compilingBlockNum_ = block_num; + lastConstPC_ = 0; + + regs_.Start(irBlockCache, block_num); + + std::vector addresses; + addresses.reserve(block->GetNumIRInstructions()); + const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block); + for (int i = 0; i < block->GetNumIRInstructions(); ++i) { + const IRInst &inst = instructions[i]; + regs_.SetIRIndex(i); + addresses.push_back(GetCodePtr()); + + CompileIRInst(inst); + + if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR)) + regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR)); + + // Safety check, in case we get a bunch of really large jit ops without a lot of branching. + if (GetSpaceLeft() < 0x800) { + compilingBlockNum_ = -1; + return false; + } + } + + // We should've written an exit above. If we didn't, bad things will happen. + // Only check if debug stats are enabled - needlessly wastes jit space. + if (DebugStatsEnabled()) { + QuickCallFunction(R12, &NoBlockExits); + B(hooks_.crashHandler); + } + + int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset(); + if (len < MIN_BLOCK_NORMAL_LEN) { + // We need at least this much to invalidate blocks with. + ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len); + } + + if (!wroteCheckedOffset) { + // Always record this, even if block link disabled - it's used for size calc. + SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer())); + } + + if (jo.enableBlocklink && jo.useBackJump) { + WriteDebugPC(startPC); + + CMP(DOWNCOUNTREG, Operand2(0)); + B_CC(CC_PL, blockStart); + + MOVI2R(SCRATCH1, startPC); + B(outerLoopPCInSCRATCH1_); + } + + if (logBlocks_ > 0) { + --logBlocks_; + + std::map addressesLookup; + for (int i = 0; i < (int)addresses.size(); ++i) + addressesLookup[addresses[i]] = i; + + INFO_LOG(Log::JIT, "=============== ARM (%08x, %d bytes) ===============", startPC, len); + for (const u8 *p = blockStart; p < GetCodePointer(); ) { + auto it = addressesLookup.find(p); + if (it != addressesLookup.end()) { + const IRInst &inst = instructions[it->second]; + + char temp[512]; + DisassembleIR(temp, sizeof(temp), inst); + INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp); + } + + auto next = std::next(it); + const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first; + + auto lines = DisassembleArm2(p, (int)(nextp - p)); + for (const auto &line : lines) + INFO_LOG(Log::JIT, " A: %s", line.c_str()); + p = nextp; + } + } + + EndWrite(); + FlushIcache(); + compilingBlockNum_ = -1; + + return true; +} + +void ArmJitBackend::WriteConstExit(uint32_t pc) { + int block_num = blocks_.GetBlockNumberFromStartAddress(pc); + const IRNativeBlock *nativeBlock = GetNativeBlock(block_num); + + int exitStart = (int)GetOffset(GetCodePointer()); + if (block_num >= 0 && jo.enableBlocklink && nativeBlock && nativeBlock->checkedOffset != 0) { + B(GetBasePtr() + nativeBlock->checkedOffset); + } else { + MOVI2R(SCRATCH1, pc); + B(dispatcherPCInSCRATCH1_); + } + + if (jo.enableBlocklink) { + // In case of compression or early link, make sure it's large enough. + int len = (int)GetOffset(GetCodePointer()) - exitStart; + if (len < MIN_BLOCK_EXIT_LEN) { + ReserveCodeSpace(MIN_BLOCK_EXIT_LEN - len); + len = MIN_BLOCK_EXIT_LEN; + } + + AddLinkableExit(compilingBlockNum_, pc, exitStart, len); + } +} + +void ArmJitBackend::OverwriteExit(int srcOffset, int len, int block_num) { + _dbg_assert_(len >= MIN_BLOCK_EXIT_LEN); + + const IRNativeBlock *nativeBlock = GetNativeBlock(block_num); + if (nativeBlock) { + u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + srcOffset; + if (PlatformIsWXExclusive()) { + ProtectMemoryPages(writable, len, MEM_PROT_READ | MEM_PROT_WRITE); + } + + ARMXEmitter emitter(writable); + emitter.B(GetBasePtr() + nativeBlock->checkedOffset); + int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable); + _dbg_assert_(bytesWritten <= MIN_BLOCK_EXIT_LEN); + if (bytesWritten < len) + emitter.ReserveCodeSpace(len - bytesWritten); + emitter.FlushIcache(); + + if (PlatformIsWXExclusive()) { + ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC); + } + } +} + +void ArmJitBackend::CompIR_Generic(IRInst inst) { + // If we got here, we're going the slow way. + uint64_t value; + memcpy(&value, &inst, sizeof(inst)); + + FlushAll(); + SaveStaticRegisters(); + WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET); + // The 64-bit argument goes in R0:R1. + MOVI2R(R0, (u32)value); + MOVI2R(R1, (u32)(value >> 32)); + QuickCallFunction(R12, &DoIRInst); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + LoadStaticRegisters(); + + // We only need to check the return value if it's a potential exit. + if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) { + // The PC is already in R0 (SCRATCH1.) + CMP(R0, Operand2(0)); + B_CC(CC_NEQ, dispatcherPCInSCRATCH1_); + } +} + +void ArmJitBackend::CompIR_Interpret(IRInst inst) { + MIPSOpcode op(inst.constant); + + // IR protects us against this being a branching instruction (well, hopefully.) + FlushAll(); + SaveStaticRegisters(); + WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET); + if (DebugStatsEnabled()) { + MOVP2R(R0, MIPSGetName(op)); + QuickCallFunction(R12, &NotifyMIPSInterpret); + } + MOVI2R(R0, inst.constant); + QuickCallFunction(R12, &MIPSInterpretTrampoline); + WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); + LoadStaticRegisters(); +} + +void ArmJitBackend::FlushAll() { + regs_.FlushAll(); +} + +bool ArmJitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const { + // Used in disassembly viewer and profiling tools. + // Don't use spaces; profilers get confused or truncate them. + if (ptr == dispatcherPCInSCRATCH1_) { + name = "dispatcherPCInSCRATCH1"; + } else if (ptr == outerLoopPCInSCRATCH1_) { + name = "outerLoopPCInSCRATCH1"; + } else if (ptr == dispatcherNoCheck_) { + name = "dispatcherNoCheck"; + } else if (ptr == restoreRoundingMode_) { + name = "restoreRoundingMode"; + } else if (ptr == applyRoundingMode_) { + name = "applyRoundingMode"; + } else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) { + name = "fixedCode"; + } else { + return IRNativeBackend::DescribeCodePtr(ptr, name); + } + return true; +} + +void ArmJitBackend::ClearAllBlocks() { + ClearCodeSpace(jitStartOffset_); + FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_); + EraseAllLinks(-1); +} + +void ArmJitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) { + IRBlock *block = irBlockCache->GetBlock(block_num); + int offset = block->GetNativeOffset(); + u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset; + + // Overwrite the block with a jump to compile it again. + u32 pc = block->GetOriginalStart(); + if (pc != 0) { + // Hopefully we always have at least 12 bytes, which should be all we need. + if (PlatformIsWXExclusive()) { + ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE); + } + + ARMXEmitter emitter(writable); + emitter.MOVI2R(SCRATCH1, pc); + emitter.B(dispatcherPCInSCRATCH1_); + int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable); + if (bytesWritten < MIN_BLOCK_NORMAL_LEN) + emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten); + emitter.FlushIcache(); + + if (PlatformIsWXExclusive()) { + ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC); + } + } + + EraseAllLinks(block_num); +} + +void ArmJitBackend::RestoreRoundingMode(bool force) { + QuickCallFunction(R12, restoreRoundingMode_); +} + +void ArmJitBackend::ApplyRoundingMode(bool force) { + QuickCallFunction(R12, applyRoundingMode_); +} + +void ArmJitBackend::UpdateRoundingMode(bool force) { + // Conversions read the mode from FPSCR, so there's nothing to cache. +} + +void ArmJitBackend::MovFromPC(ARMReg r) { + LDR(r, CTXREG, offsetof(MIPSState, pc)); +} + +void ArmJitBackend::MovToPC(ARMReg r) { + STR(r, CTXREG, offsetof(MIPSState, pc)); +} + +void ArmJitBackend::WriteDebugPC(uint32_t pc) { + if (hooks_.profilerPC) { + MOVI2R(R12, pc); + WriteDebugPC(R12); + } +} + +void ArmJitBackend::WriteDebugPC(ARMReg r) { + if (hooks_.profilerPC) { + _dbg_assert_(r != SCRATCH2); + MOVP2R(SCRATCH2, hooks_.profilerPC); + STR(r, SCRATCH2, 0); + } +} + +void ArmJitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) { + if (hooks_.profilerPC) { + MOVP2R(SCRATCH2, hooks_.profilerStatus); + MOVI2R(R12, (int)status); + STR(R12, SCRATCH2, 0); + } +} + +void ArmJitBackend::SaveStaticRegisters() { + STR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); +} + +void ArmJitBackend::LoadStaticRegisters() { + LDR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); +} + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRJit.h b/Core/MIPS/ARM/ArmIRJit.h new file mode 100644 index 0000000000..0eb191f82c --- /dev/null +++ b/Core/MIPS/ARM/ArmIRJit.h @@ -0,0 +1,146 @@ +// 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 "ppsspp_config.h" +#if PPSSPP_ARCH(ARM) + +#include +#include +#include "Common/ArmEmitter.h" +#include "Core/MIPS/IR/IRJit.h" +#include "Core/MIPS/IR/IRNativeCommon.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/MIPS/JitCommon/JitCommon.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" + +namespace MIPSComp { + +class ArmJitBackend : public ArmGen::ARMXCodeBlock, public IRNativeBackend { +public: + ArmJitBackend(JitOptions &jo, IRBlockCache &blocks); + ~ArmJitBackend(); + + bool DescribeCodePtr(const u8 *ptr, std::string &name) const override; + + void GenerateFixedCode(MIPSState *mipsState) override; + bool CompileBlock(IRBlockCache *irBlockCache, int block_num) override; + void ClearAllBlocks() override; + void InvalidateBlock(IRBlockCache *irBlockCache, int block_num) override; + +protected: + const CodeBlockCommon &CodeBlock() const override { + return *this; + } + +private: + void RestoreRoundingMode(bool force = false); + void ApplyRoundingMode(bool force = false); + void UpdateRoundingMode(bool force = false); + void MovFromPC(ArmGen::ARMReg r); + void MovToPC(ArmGen::ARMReg r); + // These destroy SCRATCH2 and R12 (the uint32_t one), so they're only for when R12 isn't + // mapped: at block entry, in the dispatcher, or after a flush. SCRATCH1 (R0) is left alone, + // since it holds the PC or a return value around them. + void WriteDebugPC(uint32_t pc); + void WriteDebugPC(ArmGen::ARMReg r); + void WriteDebugProfilerStatus(IRProfilerStatus status); + + void SaveStaticRegisters(); + void LoadStaticRegisters(); + + // Note: destroys SCRATCH1. + void FlushAll(); + + void WriteConstExit(uint32_t pc); + void OverwriteExit(int srcOffset, int len, int block_num) override; + + void CompIR_Arith(IRInst inst) override; + void CompIR_Assign(IRInst inst) override; + void CompIR_Basic(IRInst inst) override; + void CompIR_Bits(IRInst inst) override; + void CompIR_Breakpoint(IRInst inst) override; + void CompIR_Compare(IRInst inst) override; + void CompIR_CondAssign(IRInst inst) override; + void CompIR_CondStore(IRInst inst) override; + void CompIR_Div(IRInst inst) override; + void CompIR_Exit(IRInst inst) override; + void CompIR_ExitIf(IRInst inst) override; + void CompIR_FArith(IRInst inst) override; + void CompIR_FAssign(IRInst inst) override; + void CompIR_FCompare(IRInst inst) override; + void CompIR_FCondAssign(IRInst inst) override; + void CompIR_FCvt(IRInst inst) override; + void CompIR_FLoad(IRInst inst) override; + void CompIR_FRound(IRInst inst) override; + void CompIR_FSat(IRInst inst) override; + void CompIR_FSpecial(IRInst inst) override; + void CompIR_FStore(IRInst inst) override; + void CompIR_Generic(IRInst inst) override; + void CompIR_HiLo(IRInst inst) override; + void CompIR_Interpret(IRInst inst) override; + void CompIR_Load(IRInst inst) override; + void CompIR_LoadShift(IRInst inst) override; + void CompIR_Logic(IRInst inst) override; + void CompIR_Mult(IRInst inst) override; + void CompIR_RoundingMode(IRInst inst) override; + void CompIR_Shift(IRInst inst) override; + void CompIR_Store(IRInst inst) override; + void CompIR_StoreShift(IRInst inst) override; + void CompIR_System(IRInst inst) override; + void CompIR_Transfer(IRInst inst) override; + void CompIR_VecArith(IRInst inst) override; + void CompIR_VecAssign(IRInst inst) override; + void CompIR_VecHoriz(IRInst inst) override; + void CompIR_VecLoad(IRInst inst) override; + void CompIR_VecPack(IRInst inst) override; + void CompIR_VecStore(IRInst inst) override; + void CompIR_ValidateAddress(IRInst inst) override; + + JitOptions &jo; + ArmIRRegCache regs_; + + const u8 *outerLoop_ = nullptr; + const u8 *outerLoopPCInSCRATCH1_ = nullptr; + const u8 *dispatcherCheckCoreState_ = nullptr; + const u8 *dispatcherPCInSCRATCH1_ = nullptr; + const u8 *dispatcherNoCheck_ = nullptr; + const u8 *restoreRoundingMode_ = nullptr; + const u8 *applyRoundingMode_ = nullptr; + + int jitStartOffset_ = 0; + int compilingBlockNum_ = -1; + int logBlocks_ = 0; + // Only useful in breakpoints, where it's set immediately prior. + uint32_t lastConstPC_ = 0; +}; + +class ArmIRJit : public IRNativeJit { +public: + ArmIRJit(MIPSState *mipsState) + : IRNativeJit(mipsState), armBackend_(jo, blocks_) { + Init(armBackend_); + } + +private: + ArmJitBackend armBackend_; +}; + +} // namespace MIPSComp + +#endif diff --git a/Core/MIPS/ARM/ArmIRRegCache.cpp b/Core/MIPS/ARM/ArmIRRegCache.cpp new file mode 100644 index 0000000000..9e2f3a31cf --- /dev/null +++ b/Core/MIPS/ARM/ArmIRRegCache.cpp @@ -0,0 +1,233 @@ +// 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) + +#ifndef offsetof +#include +#endif + +#include "Common/LogReporting.h" +#include "Core/MemMap.h" +#include "Core/MIPS/IR/IRInst.h" +#include "Core/MIPS/IR/IRAnalysis.h" +#include "Core/MIPS/ARM/ArmIRRegCache.h" +#include "Core/MIPS/JitCommon/JitState.h" + +using namespace ArmGen; +using namespace ArmIRJitConstants; + +ArmIRRegCache::ArmIRRegCache(MIPSComp::JitOptions *jo) + : IRNativeRegCacheBase(jo) { + config_.totalNativeRegs = NUM_GPRS + NUM_FPRS; + config_.mapFPUSIMD = false; + config_.mapUseVRegs = false; +} + +void ArmIRRegCache::Init(ARMXEmitter *emitter) { + emit_ = emitter; +} + +const int *ArmIRRegCache::GetAllocationOrder(MIPSLoc type, MIPSMap flags, int &count, int &base) const { + // ARMReg values are used directly as native reg numbers. + base = 0; + if (type == MIPSLoc::REG) { + // R0/R1 are scratch, R9-R11 are fixed (see ArmIRJitConstants), R13/R15 are SP/PC. + // The callee-saved ones come first, since they survive calls. + static const int allocationOrder[] = { + R4, R5, R6, R7, R8, R2, R3, R12, R14, + }; + count = ARRAY_SIZE(allocationOrder); + return allocationOrder; + } else if (type == MIPSLoc::FREG) { + // S16-S31 (D8-D15) are callee-saved. S0-S3 are scratch. + static const int allocationOrder[] = { + S16, S17, S18, S19, S20, S21, S22, S23, + S24, S25, S26, S27, S28, S29, S30, S31, + S4, S5, S6, S7, S8, S9, S10, S11, + S12, S13, S14, S15, + }; + count = ARRAY_SIZE(allocationOrder); + return allocationOrder; + } else { + _assert_msg_(false, "Allocation order not yet implemented"); + count = 0; + return nullptr; + } +} + +void ArmIRRegCache::FlushBeforeCall() { + // These are not preserved by calls. + for (int r : { R2, R3, R12, R14 }) + FlushNativeReg((IRNativeReg)r); + for (int r = S4; r <= S15; ++r) + FlushNativeReg((IRNativeReg)r); +} + +ARMReg ArmIRRegCache::TryMapTempImm(IRReg r) { + _dbg_assert_(IsValidGPR(r)); + + // If already mapped, no need for a temporary. + if (IsGPRMapped(r)) { + return R(r); + } + + if (mr[r].loc == MIPSLoc::IMM) { + // Try our luck - check for an exact match in another reg. + for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; ++i) { + if (mr[i].loc == MIPSLoc::REG_IMM && mr[i].imm == mr[r].imm) { + return (ARMReg)mr[i].nReg; + } + } + } + + return INVALID_REG; +} + +ARMReg ArmIRRegCache::GetAndLockTempGPR() { + IRNativeReg reg = AllocateReg(MIPSLoc::REG, MIPSMap::INIT); + if (reg != -1) { + nr[reg].tempLockIRIndex = irIndex_; + } + return (ARMReg)reg; +} + +ARMReg ArmIRRegCache::GetAndLockTempFPR() { + IRNativeReg reg = AllocateReg(MIPSLoc::FREG, MIPSMap::INIT); + if (reg != -1) { + nr[reg].tempLockIRIndex = irIndex_; + } + return (ARMReg)reg; +} + +ARMReg ArmIRRegCache::MapWithFPRTemp(const IRInst &inst) { + return (ARMReg)MapWithTemp(inst, MIPSLoc::FREG); +} + +ARMReg ArmIRRegCache::MapGPR(IRReg mipsReg, MIPSMap mapFlags) { + _dbg_assert_(IsValidGPR(mipsReg)); + IRNativeReg nreg = MapNativeReg(MIPSLoc::REG, mipsReg, 1, mapFlags); + return (ARMReg)nreg; +} + +ARMReg ArmIRRegCache::MapFPR(IRReg mipsReg, MIPSMap mapFlags) { + _dbg_assert_(IsValidFPR(mipsReg)); + _dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::MEM || mr[mipsReg + 32].loc == MIPSLoc::FREG); + + IRNativeReg nreg = MapNativeReg(MIPSLoc::FREG, mipsReg + 32, 1, mapFlags); + if (nreg != -1) + return (ARMReg)nreg; + return INVALID_REG; +} + +void ArmIRRegCache::AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) { + _assert_(nreg >= 0 && nreg < NUM_GPRS); + ARMReg r = (ARMReg)nreg; + // 32-bit builds always mask PSP addresses, which destroys the value. + if (state) { + _dbg_assert_(!nr[nreg].isDirty); + emit_->BIC(r, r, Operand2(0xC0, 4)); + emit_->ADD(r, r, MEMBASEREG); + } else { + _dbg_assert_(!nr[nreg].isDirty); + emit_->SUB(r, r, MEMBASEREG); + } +} + +bool ArmIRRegCache::IsNativeRegCompatible(IRNativeReg nreg, MIPSLoc type, MIPSMap flags, int lanes) { + // Every reg holds one lane. + return lanes == 1 && IRNativeRegCacheBase::IsNativeRegCompatible(nreg, type, flags, lanes); +} + +void ArmIRRegCache::LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) { + _dbg_assert_(first != MIPS_REG_ZERO); + _assert_msg_(lanes == 1, "Can't load %d lanes", lanes); + ARMReg r = (ARMReg)nreg; + if (nreg < NUM_GPRS) { + emit_->LDR(r, CTXREG, GetMipsRegOffset(first)); + } else { + _assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot load this type: %d", (int)mr[first].loc); + emit_->VLDR(r, CTXREG, GetMipsRegOffset(first)); + } +} + +void ArmIRRegCache::StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) { + _dbg_assert_(first != MIPS_REG_ZERO); + _assert_msg_(lanes == 1, "Can't store %d lanes", lanes); + ARMReg r = (ARMReg)nreg; + if (nreg < NUM_GPRS) { + _assert_(mr[first].loc == MIPSLoc::REG || mr[first].loc == MIPSLoc::REG_IMM); + emit_->STR(r, CTXREG, GetMipsRegOffset(first)); + } else { + _assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot store this type: %d", (int)mr[first].loc); + emit_->VSTR(r, CTXREG, GetMipsRegOffset(first)); + } +} + +void ArmIRRegCache::SetNativeRegValue(IRNativeReg nreg, uint32_t imm) { + _dbg_assert_(nreg >= 0 && nreg < NUM_GPRS); + emit_->MOVI2R((ARMReg)nreg, imm); +} + +void ArmIRRegCache::StoreRegValue(IRReg mreg, uint32_t imm) { + _assert_(IsValidGPRNoZero(mreg)); + // Could we get lucky? Check for an exact match in another reg. + ARMReg storeReg = INVALID_REG; + for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; ++i) { + if (mr[i].loc == MIPSLoc::REG_IMM && mr[i].imm == imm) { + storeReg = (ARMReg)mr[i].nReg; + break; + } + } + + if (storeReg == INVALID_REG) { + emit_->MOVI2R(SCRATCH1, imm); + storeReg = SCRATCH1; + } + emit_->STR(storeReg, CTXREG, GetMipsRegOffset(mreg)); +} + +void ArmIRRegCache::FlushAll(bool gprs, bool fprs) { + // Note: make sure not to change the registers when flushing: + // Branching code may expect the armreg to retain its value. + IRNativeRegCacheBase::FlushAll(gprs, fprs); +} + +ARMReg ArmIRRegCache::R(IRReg mipsReg) { + _dbg_assert_(IsValidGPR(mipsReg)); + _dbg_assert_(mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM); + if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) { + return (ARMReg)mr[mipsReg].nReg; + } else { + ERROR_LOG_REPORT(Log::JIT, "Reg %i not in arm reg", mipsReg); + return INVALID_REG; // BAAAD + } +} + +ARMReg ArmIRRegCache::F(IRReg mipsReg) { + _dbg_assert_(IsValidFPR(mipsReg)); + _dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::FREG); + if (mr[mipsReg + 32].loc == MIPSLoc::FREG) { + return (ARMReg)mr[mipsReg + 32].nReg; + } else { + ERROR_LOG_REPORT(Log::JIT, "Reg %i not in arm reg", mipsReg); + return INVALID_REG; // BAAAD + } +} + +#endif diff --git a/Core/MIPS/ARM/ArmIRRegCache.h b/Core/MIPS/ARM/ArmIRRegCache.h new file mode 100644 index 0000000000..5508dd5ae2 --- /dev/null +++ b/Core/MIPS/ARM/ArmIRRegCache.h @@ -0,0 +1,88 @@ +// 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 "ppsspp_config.h" +#if PPSSPP_ARCH(ARM) + +#include "Common/ArmEmitter.h" +#include "Core/MIPS/MIPS.h" +#include "Core/MIPS/IR/IRJit.h" +#include "Core/MIPS/IR/IRRegCache.h" + +namespace ArmIRJitConstants { + +const ArmGen::ARMReg DOWNCOUNTREG = ArmGen::R9; +const ArmGen::ARMReg CTXREG = ArmGen::R10; +const ArmGen::ARMReg MEMBASEREG = ArmGen::R11; +const ArmGen::ARMReg SCRATCH1 = ArmGen::R0; +const ArmGen::ARMReg SCRATCH2 = ArmGen::R1; +// S0-S3 (Q0) are never allocated. +const ArmGen::ARMReg SCRATCHF1 = ArmGen::S0; +const ArmGen::ARMReg SCRATCHF2 = ArmGen::S1; +const ArmGen::ARMReg SCRATCHF3 = ArmGen::S2; +const ArmGen::ARMReg SCRATCHF4 = ArmGen::S3; + +} // namespace ArmIRJitConstants + +// Native regs are numbered as ARMReg: R0-R15, then S0-S31. Floats are only mapped as single lanes +// (mapFPUSIMD is off), so a vector op sees its lanes in separate S regs. +class ArmIRRegCache : public IRNativeRegCacheBase { +public: + ArmIRRegCache(MIPSComp::JitOptions *jo); + + void Init(ArmGen::ARMXEmitter *emitter); + + // May fail and return INVALID_REG if it needs flushing. + ArmGen::ARMReg TryMapTempImm(IRReg reg); + + // Returns an ARM register containing the requested MIPS register. + ArmGen::ARMReg MapGPR(IRReg reg, MIPSMap mapFlags = MIPSMap::INIT); + ArmGen::ARMReg MapFPR(IRReg reg, MIPSMap mapFlags = MIPSMap::INIT); + + ArmGen::ARMReg MapWithFPRTemp(const IRInst &inst); + + void FlushBeforeCall(); + void FlushAll(bool gprs = true, bool fprs = true) override; + + ArmGen::ARMReg GetAndLockTempGPR(); + ArmGen::ARMReg GetAndLockTempFPR(); + + ArmGen::ARMReg R(IRReg preg); + ArmGen::ARMReg F(IRReg preg); + +protected: + const int *GetAllocationOrder(MIPSLoc type, MIPSMap flags, int &count, int &base) const override; + void AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) override; + + bool IsNativeRegCompatible(IRNativeReg nreg, MIPSLoc type, MIPSMap flags, int lanes) override; + void LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) override; + void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override; + void SetNativeRegValue(IRNativeReg nreg, uint32_t imm) override; + void StoreRegValue(IRReg mreg, uint32_t imm) override; + +private: + ArmGen::ARMXEmitter *emit_ = nullptr; + + enum { + NUM_GPRS = 16, + NUM_FPRS = 32, + }; +}; + +#endif diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp index f54802e3da..1a065d9935 100644 --- a/Core/MIPS/JitCommon/JitCommon.cpp +++ b/Core/MIPS/JitCommon/JitCommon.cpp @@ -39,7 +39,7 @@ #include "Core/MIPS/MIPSTables.h" #if PPSSPP_ARCH(ARM) -#include "../ARM/ArmJit.h" +#include "../ARM/ArmIRJit.h" #elif PPSSPP_ARCH(ARM64) #include "../ARM64/Arm64IRJit.h" #elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) @@ -105,7 +105,7 @@ namespace MIPSComp { JitInterface *CreateNativeJit(MIPSState *mipsState) { #if PPSSPP_ARCH(ARM) - return new MIPSComp::ArmJit(mipsState); + return new MIPSComp::ArmIRJit(mipsState); #elif PPSSPP_ARCH(ARM64) return new MIPSComp::Arm64IRJit(mipsState); #elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 27bf91e2ff..3b4d779e2a 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -281,6 +281,8 @@ + + @@ -573,6 +575,15 @@ + + + + + + + + + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 1d5768e59e..3ee11d3ef8 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -202,6 +202,15 @@ + + + + + + + + + @@ -601,6 +610,8 @@ + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 82486e02ab..9e59e5849b 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -520,6 +520,15 @@ ARCH_FILES := \ $(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 \ + $(SRC)/Core/MIPS/ARM/ArmIRCompFPU.cpp \ + $(SRC)/Core/MIPS/ARM/ArmIRCompLoadStore.cpp \ + $(SRC)/Core/MIPS/ARM/ArmIRCompSystem.cpp \ + $(SRC)/Core/MIPS/ARM/ArmIRCompVec.cpp \ + $(SRC)/Core/MIPS/ARM/ArmIRJit.cpp \ + $(SRC)/Core/MIPS/ARM/ArmIRRegCache.cpp \ $(SRC)/GPU/Common/VertexDecoderArm.cpp \ ArmEmitterTest.cpp else ifeq ($(findstring arm64-v8a,$(TARGET_ARCH_ABI)),arm64-v8a) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 3ddae16cdd..6d24e1899e 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -928,6 +928,15 @@ ifeq ($(WITH_DYNAREC),1) $(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 \ + $(COREDIR)/MIPS/ARM/ArmIRCompFPU.cpp \ + $(COREDIR)/MIPS/ARM/ArmIRCompLoadStore.cpp \ + $(COREDIR)/MIPS/ARM/ArmIRCompSystem.cpp \ + $(COREDIR)/MIPS/ARM/ArmIRCompVec.cpp \ + $(COREDIR)/MIPS/ARM/ArmIRJit.cpp \ + $(COREDIR)/MIPS/ARM/ArmIRRegCache.cpp \ $(EXTDIR)/disarm.cpp \ $(GPUCOMMONDIR)/VertexDecoderArm.cpp