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:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-14 17:32:53 +02:00
1 parent 2e3b844cff
commit 6d105ae35d
19 files changed
+2222 -2

No files matched your search

+11
View File
@@ -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
+11
View File
@@ -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" />
+33
View File
@@ -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>
+223
View File
@@ -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
+141
View File
@@ -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
+134
View File
@@ -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
+146
View File
@@ -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
+131
View File
@@ -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
+427
View File
@@ -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
+81
View File
@@ -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
+375
View File
@@ -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
+146
View File
@@ -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
+233
View File
@@ -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
+88
View File
@@ -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
+2 -2
View File
@@ -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)
+11
View File
@@ -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" />
+11
View File
@@ -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" />
+9
View File
@@ -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)
+9
View File
@@ -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