mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
ARM32: Start an IR-based JIT
The backend follows the ARM64 one: a dispatcher, a register cache on top of the IR one, and native exits, syscalls, breakpoints and transfers. Everything else goes to the IR interpreter for now. Floats are mapped one lane per S register (no SIMD mapping yet), and addresses are masked, as on every 32-bit build. The JIT core now uses it on ARM32 in place of the old ArmJit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2e3b844cff
commit
6d105ae35d
19 files changed
+2222
-2
No files matched your search
@@ -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
|
||||
|
||||
@@ -742,6 +742,15 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmRegCacheFPU.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRAsm.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompALU.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompBranch.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompFPU.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompLoadStore.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompSystem.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompVec.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRJit.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmIRRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\ARM\ArmJit.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
@@ -1097,6 +1106,8 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\ARM\ArmRegCacheFPU.h" />
|
||||
<ClInclude Include="MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitBlockCache.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitCommon.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitState.h" />
|
||||
|
||||
@@ -450,6 +450,33 @@
|
||||
<ClCompile Include="MIPS\ARM\ArmRegCacheFPU.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRAsm.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompALU.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompBranch.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompFPU.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompLoadStore.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompSystem.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRCompVec.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRJit.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\ARM\ArmIRRegCache.cpp">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Font\PGF.cpp">
|
||||
<Filter>Font</Filter>
|
||||
</ClCompile>
|
||||
@@ -1731,6 +1758,12 @@
|
||||
<ClInclude Include="MIPS\ARM\ArmRegCacheFPU.h">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\ARM\ArmIRJit.h">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\ARM\ArmIRRegCache.h">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Font\PGF.h">
|
||||
<Filter>Font</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -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<std::string> 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<std::string> 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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<FixupBranch> 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<FixupBranch> 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
|
||||
@@ -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
|
||||
@@ -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 <algorithm>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#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<std::string> 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<const u8 *> 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<const u8 *, int> 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
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
#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
|
||||
@@ -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 <cstddef>
|
||||
#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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -281,6 +281,8 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmRegCacheFPU.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRFrontend.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInst.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInterpreter.h" />
|
||||
@@ -573,6 +575,15 @@
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmJit.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmRegCache.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmRegCacheFPU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRAsm.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompBranch.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompFPU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompLoadStore.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompSystem.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompVec.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRJit.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRRegCache.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompBranch.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompFPU.cpp" />
|
||||
|
||||
@@ -202,6 +202,15 @@
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmJit.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmRegCache.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmRegCacheFPU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRAsm.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompBranch.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompFPU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompLoadStore.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompSystem.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRCompVec.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRJit.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\ARM\ArmIRRegCache.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompBranch.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\IR\IRCompFPU.cpp" />
|
||||
@@ -601,6 +610,8 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmRegCacheFPU.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRFrontend.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInst.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInterpreter.h" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user