#include "ppsspp_config.h"
#if PPSSPP_ARCH(ARM)
#include "Common/Profiler/Profiler.h"
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSInt.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/MIPS/ARM/ArmRegCache.h"
#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
#include "ArmRegCache.h"
#include "ArmJit.h"
#include "CPUDetect.h"
#include "ext/disarm.h"
using namespace ArmJitConstants;
void DisassembleArm(const u8 *data, int size) {
char temp[256];
for (int i = 0; i < size; i += 4) {
const u32 *codePtr = (const u32 *)(data + i);
u32 inst = codePtr[0];
u32 next = (i < size - 4) ? codePtr[1] : 0;
if ((inst & 0x0FF00000) == 0x03000000 && (next & 0x0FF00000) == 0x03400000) {
u32 low = ((inst & 0x000F0000) >> 4) | (inst & 0x0FFF);
u32 hi = ((next & 0x000F0000) >> 4) | (next & 0x0FFF);
int reg0 = (inst & 0x0000F000) >> 12;
int reg1 = (next & 0x0000F000) >> 12;
if (reg0 == reg1) {
snprintf(temp, sizeof(temp), "%08x MOV32 %s, %04x%04x", (u32)inst, ArmRegName(reg0), hi, low);
INFO_LOG(Log::JIT, "A: %s", temp);
i += 4;
continue;
}
}
ArmDis((u32)codePtr, inst, temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "A: %s", temp);
}
}
static u32 JitBreakpoint(uint32_t addr) {
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc || g_breakpoints.CheckSkipFirst() == addr)
return 0;
BreakAction result = g_breakpoints.ExecBreakPoint(addr);
if ((result & BREAK_ACTION_PAUSE) == 0)
return 0;
return 1;
}
static u32 JitMemCheck(u32 pc) {
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
return 0;
const auto op = Memory::Read_Instruction(pc, true);
s32 offset = SignExtend16ToS32(op & 0xFFFF);
if (MIPSGetInfo(op) & IS_VFPU)
offset &= 0xFFFC;
u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
g_breakpoints.ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
}
namespace MIPSComp
{
using namespace ArmGen;
using namespace ArmJitConstants;
ArmJit::ArmJit(MIPSState *mipsState) : blocks(mipsState, this), gpr(mipsState, &js, &jo), fpr(mipsState, &js, &jo), mips_(mipsState) {
logBlocks = 0;
dontLogBlocks = 0;
blocks.Init();
gpr.SetEmitter(this);
fpr.SetEmitter(this);
AllocCodeSpace(1024 * 1024 * 16);
GenerateFixedCode();
INFO_LOG(Log::JIT, "ARM JIT initialized: %lld MB of code space", (long long)(GetSpaceLeft() / (1024 * 1024)));
js.startDefaultPrefix = mips_->HasDefaultPrefix();
g_breakpoints.SetSkipFirst(0);
}
ArmJit::~ArmJit() {
}
void ArmJit::DoState(PointerWrap &p)
{
auto s = p.Section("Jit", 1, 2);
if (!s)
return;
if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
}
Do(p, js.startDefaultPrefix);
if (s >= 2) {
Do(p, js.hasSetRounding);
if (p.mode == PointerWrap::MODE_READ) {
js.lastSetRounding = 0;
}
} else {
js.hasSetRounding = 1;
}
g_breakpoints.SetSkipFirst(0);
}
void ArmJit::UpdateFCR31() {
}
void ArmJit::FlushAll()
{
gpr.FlushAll();
fpr.FlushAll();
FlushPrefixV();
}
void ArmJit::FlushPrefixV() {
if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {
js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);
js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);
js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);
return;
}
if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {
gpr.SetRegImm(SCRATCHREG1, js.prefixS);
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_SPREFIX]));
js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
gpr.SetRegImm(SCRATCHREG1, js.prefixT);
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_TPREFIX]));
js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
gpr.SetRegImm(SCRATCHREG1, js.prefixD);
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_DPREFIX]));
js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
}
js.blockWrotePrefixes = true;
}
void ArmJit::ClearCache()
{
blocks.Clear();
ClearCodeSpace(0);
GenerateFixedCode();
}
void ArmJit::InvalidateCacheAt(u32 em_address, int length) {
if (blocks.RangeMayHaveEmuHacks(em_address, em_address + length)) {
blocks.InvalidateICache(em_address, length);
}
}
void ArmJit::EatInstruction(MIPSOpcode op) {
MIPSInfo info = MIPSGetInfo(op);
if (info & DELAYSLOT) {
ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");
}
if (js.inDelaySlot) {
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");
}
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
js.numInstructions++;
js.compilerPC += 4;
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
}
void ArmJit::CompileDelaySlot(int flags) {
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
if (flags & DELAYSLOT_SAFE)
MRS(R8);
js.inDelaySlot = true;
MIPSOpcode op = GetOffsetInstruction(1);
MIPSCompileOp(op, this);
js.inDelaySlot = false;
if (flags & DELAYSLOT_FLUSH)
FlushAll();
if (flags & DELAYSLOT_SAFE)
_MSR(true, false, R8);
}
void ArmJit::Compile(u32 em_address) {
PROFILE_THIS_SCOPE("jitc");
if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
ClearCache();
}
BeginWrite(JitBlockCache::MAX_BLOCK_INSTRUCTIONS * 16);
int block_num = blocks.AllocateBlock(em_address);
JitBlock *b = blocks.GetBlock(block_num);
DoJit(em_address, b);
_assert_msg_(b->originalAddress == em_address, "original %08x != em_address %08x (block %d)", b->originalAddress, em_address, b->blockNum);
blocks.FinalizeBlock(block_num, jo.enableBlocklink);
EndWrite();
bool cleanSlate = false;
if (js.hasSetRounding && !js.lastSetRounding) {
WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");
js.lastSetRounding = js.hasSetRounding;
cleanSlate = true;
}
if (js.startDefaultPrefix && js.MayHavePrefix()) {
WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4);
js.LogPrefix();
js.startDefaultPrefix = false;
cleanSlate = true;
}
if (cleanSlate) {
ClearCache();
Compile(em_address);
}
}
void ArmJit::RunLoopUntil(u64 globalticks) {
PROFILE_THIS_SCOPE("jit");
((void (*)())enterDispatcher)();
}
u32 ArmJit::GetCompilerPC() {
return js.compilerPC;
}
MIPSOpcode ArmJit::GetOffsetInstruction(int offset) {
return Memory::Read_Instruction(GetCompilerPC() + 4 * offset);
}
const u8 *ArmJit::DoJit(u32 em_address, JitBlock *b)
{
js.cancel = false;
js.blockStart = em_address;
js.compilerPC = em_address;
js.lastContinuedPC = 0;
js.initialBlockSize = 0;
js.nextExit = 0;
js.downcountAmount = 0;
js.curBlock = b;
js.compiling = true;
js.inDelaySlot = false;
js.blockWrotePrefixes = false;
js.PrefixStart();
FixupBranch bail;
if (jo.useBackJump) {
JumpTarget backJump = GetCodePtr();
gpr.SetRegImm(R0, js.blockStart);
B((const void *)outerLoopPCInR0);
b->checkedEntry = GetCodePtr();
SetCC(CC_LT);
B(backJump);
SetCC(CC_AL);
} else if (jo.useForwardJump) {
b->checkedEntry = GetCodePtr();
SetCC(CC_LT);
bail = B();
SetCC(CC_AL);
} else {
b->checkedEntry = GetCodePtr();
SetCC(CC_LT);
gpr.SetRegImm(R0, js.blockStart);
B((const void *)outerLoopPCInR0);
SetCC(CC_AL);
}
b->normalEntry = GetCodePtr();
MIPSAnalyst::AnalysisResults analysis;
gpr.Start(analysis);
fpr.Start(analysis);
js.numInstructions = 0;
while (js.compiling)
{
gpr.SetCompilerPC(GetCompilerPC());
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
MIPSCompileOp(inst, this);
js.compilerPC += 4;
js.numInstructions++;
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
gpr.FlushAll();
}
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
fpr.FlushAll();
FlushPrefixV();
}
if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS)
{
FlushAll();
WriteExit(GetCompilerPC(), js.nextExit++);
js.compiling = false;
}
}
if (jo.useForwardJump) {
SetJumpTarget(bail);
gpr.SetRegImm(R0, js.blockStart);
B((const void *)outerLoopPCInR0);
}
FlushLitPool();
char temp[256];
if (logBlocks > 0 && dontLogBlocks == 0) {
INFO_LOG(Log::JIT, "=============== mips ===============");
for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) {
MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "M: %08x %s", cpc, temp);
}
}
b->codeSize = GetCodePtr() - b->normalEntry;
if (logBlocks > 0 && dontLogBlocks == 0) {
INFO_LOG(Log::JIT, "=============== ARM ===============");
DisassembleArm(b->normalEntry, GetCodePtr() - b->normalEntry);
}
if (logBlocks > 0)
logBlocks--;
if (dontLogBlocks > 0)
dontLogBlocks--;
FlushIcache();
if (js.lastContinuedPC == 0)
b->originalSize = js.numInstructions;
else
{
blocks.ProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
b->originalSize = js.initialBlockSize;
}
return b->normalEntry;
}
void ArmJit::AddContinuedBlock(u32 dest)
{
if (js.lastContinuedPC == 0)
js.initialBlockSize = js.numInstructions;
else
blocks.ProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
js.lastContinuedPC = dest;
}
bool ArmJit::DescribeCodePtr(const u8 *ptr, std::string &name)
{
return false;
}
void ArmJit::Comp_RunBlock(MIPSOpcode op)
{
ERROR_LOG(Log::JIT, "Comp_RunBlock should never be reached!");
}
void ArmJit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
}
ARMXEmitter emit(exitPoint);
u32 op = *((const u32 *)emit.GetCodePointer());
bool prelinked = (op & 0xFF000000) == 0xEA000000;
emit.B(checkedEntry);
if (!prelinked) {
do {
op = *((const u32 *)emit.GetCodePointer());
emit.BKPT(1);
} while ((op & 0xFF000000) != 0xEA000000 && (op & 0xFFF000F0) != 0xE1200070);
}
emit.FlushIcache();
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void ArmJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
}
ARMXEmitter emit(checkedEntry);
emit.MOVI2R(R0, originalAddress);
emit.STR(R0, CTXREG, offsetof(MIPSState, pc));
emit.B(MIPSComp::jit->GetDispatcher());
emit.FlushIcache();
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
bool ArmJit::ReplaceJalTo(u32 dest) {
#if PPSSPP_ARCH(ARM)
const ReplacementTableEntry *entry = nullptr;
u32 funcSize = 0;
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
return false;
}
if (entry->flags & REPFLAG_ALLOWINLINE) {
CompileDelaySlot(DELAYSLOT_NICE);
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
js.downcountAmount += cycles;
} else {
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
SaveDowncount();
RestoreRoundingMode();
if (BLInRange((const void *)(entry->replaceFunc))) {
BL((const void *)(entry->replaceFunc));
} else {
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
BL(R0);
}
ApplyRoundingMode();
RestoreDowncount();
WriteDownCountR(R0);
}
js.compilerPC += 4;
if (g_breakpoints.HasMemChecks()) {
FlushAll();
WriteExit(GetCompilerPC() + 4, js.nextExit++);
js.compiling = false;
}
blocks.ProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
#endif
return true;
}
void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
{
int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;
const ReplacementTableEntry *entry = GetReplacementFunc(index);
if (!entry) {
ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
return;
}
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
disabled = g_breakpoints.RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
}
}
if (disabled) {
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else {
FlushAll();
LDR(R1, CTXREG, MIPS_REG_RA * 4);
js.downcountAmount += cycles;
WriteExitDestInR(R1);
js.compiling = false;
}
} else if (entry->replaceFunc) {
FlushAll();
SaveDowncount();
RestoreRoundingMode();
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
MovToPC(SCRATCHREG1);
if (BLInRange((const void *)(entry->replaceFunc))) {
BL((const void *)(entry->replaceFunc));
} else {
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
BL(R0);
}
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
ApplyRoundingMode();
RestoreDowncount();
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else {
ApplyRoundingMode();
RestoreDowncount();
CMPI2R(R0, 0, SCRATCHREG2);
FixupBranch positive = B_CC(CC_GE);
RSB(R0, R0, Operand2(0));
MovFromPC(R1);
FixupBranch done = B();
SetJumpTarget(positive);
LDR(R1, CTXREG, MIPS_REG_RA * 4);
SetJumpTarget(done);
WriteDownCountR(R0);
WriteExitDestInR(R1);
js.compiling = false;
}
} else {
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
}
}
void ArmJit::Comp_Generic(MIPSOpcode op)
{
FlushAll();
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
if (func)
{
SaveDowncount();
RestoreRoundingMode();
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
MovToPC(SCRATCHREG1);
gpr.SetRegImm(R0, op.encoding);
QuickCallFunction(R1, (void *)func);
ApplyRoundingMode();
RestoreDowncount();
}
const MIPSInfo info = MIPSGetInfo(op);
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0)
{
if ((info & OUT_EAT_PREFIX) == 0)
js.PrefixUnknown();
if ((info & OUT_VFPU_PREFIX) != 0)
js.blockWrotePrefixes = true;
}
}
void ArmJit::MovFromPC(ARMReg r) {
LDR(r, CTXREG, offsetof(MIPSState, pc));
}
void ArmJit::MovToPC(ARMReg r) {
STR(r, CTXREG, offsetof(MIPSState, pc));
}
void ArmJit::SaveDowncount() {
if (jo.downcountInRegister)
STR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
}
void ArmJit::RestoreDowncount() {
if (jo.downcountInRegister)
LDR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
}
void ArmJit::WriteDownCount(int offset) {
if (jo.downcountInRegister) {
int theDowncount = js.downcountAmount + offset;
Operand2 op2;
if (TryMakeOperand2(theDowncount, op2)) {
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, op2);
} else {
gpr.SetRegImm(R2, theDowncount);
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, R2);
}
} else {
int theDowncount = js.downcountAmount + offset;
LDR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
Operand2 op2;
if (TryMakeOperand2(theDowncount, op2)) {
SUBS(SCRATCHREG2, SCRATCHREG2, op2);
} else {
gpr.SetRegImm(R2, theDowncount);
SUBS(SCRATCHREG2, SCRATCHREG2, R2);
}
STR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
}
}
void ArmJit::WriteDownCountR(ARMReg reg) {
if (jo.downcountInRegister) {
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, reg);
} else {
LDR(R2, CTXREG, offsetof(MIPSState, downcount));
SUBS(R2, R2, reg);
STR(R2, CTXREG, offsetof(MIPSState, downcount));
}
}
void ArmJit::RestoreRoundingMode(bool force) {
if (force || js.hasSetRounding) {
QuickCallFunction(R1, restoreRoundingMode);
}
}
void ArmJit::ApplyRoundingMode(bool force) {
if (force || js.hasSetRounding) {
QuickCallFunction(R1, applyRoundingMode);
}
}
void ArmJit::UpdateRoundingMode(u32 fcr31) {
if (fcr31 & 0x01000003) {
js.hasSetRounding = true;
}
}
void ArmJit::WriteExit(u32 destination, int exit_num)
{
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
WriteDownCount();
JitBlock *b = js.curBlock;
b->exitAddress[exit_num] = destination;
b->exitPtrs[exit_num] = GetWritableCodePtr();
int block = blocks.GetBlockNumberFromStartAddress(destination);
if (block >= 0 && jo.enableBlocklink) {
B(blocks.GetBlock(block)->checkedEntry);
b->linkStatus[exit_num] = true;
} else {
gpr.SetRegImm(R0, destination);
B((const void *)dispatcherPCInR0);
}
}
void ArmJit::WriteExitDestInR(ARMReg Reg)
{
MovToPC(Reg);
WriteDownCount();
B((const void *)dispatcher);
}
void ArmJit::WriteSyscallExit()
{
WriteDownCount();
B((const void *)dispatcherCheckCoreState);
}
bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (g_breakpoints.IsAddressBreakPoint(addr)) {
MRS(R8);
FlushAll();
MOVI2R(SCRATCHREG1, GetCompilerPC());
MovToPC(SCRATCHREG1);
SaveDowncount();
RestoreRoundingMode();
MOVI2R(R0, addr);
QuickCallFunction(SCRATCHREG2, &JitBreakpoint);
CMPI2R(R0, 0, SCRATCHREG2);
FixupBranch skip = B_CC(CC_EQ);
WriteDownCount(downcountOffset);
ApplyRoundingMode();
RestoreDowncount();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(true, false, R8);
return true;
}
return false;
}
bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) {
if (g_breakpoints.HasMemChecks()) {
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
MRS(R8);
FlushAll();
SaveDowncount();
RestoreRoundingMode();
MOVI2R(R0, GetCompilerPC());
MovToPC(R0);
if (off != 0)
ADDI2R(R0, R0, off * 4, SCRATCHREG2);
QuickCallFunction(SCRATCHREG2, &JitMemCheck);
CMPI2R(R0, 0, SCRATCHREG2);
FixupBranch skip = B_CC(CC_EQ);
WriteDownCount(-1 - off);
ApplyRoundingMode();
RestoreDowncount();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(true, false, R8);
return true;
}
return false;
}
void ArmJit::Comp_DoNothing(MIPSOpcode op) { }
MIPSOpcode ArmJit::GetOriginalOp(MIPSOpcode op) {
JitBlockCache *bc = GetBlockCache();
int block_num = bc->GetBlockNumberFromEmuHackOp(op, true);
if (block_num >= 0) {
return bc->GetOriginalFirstOp(block_num);
} else {
return op;
}
}
}
#endif