#include "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <algorithm>
#include <iterator>
#include "Common/Math/math_util.h"
#include "Common/Profiler/Profiler.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/Core.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/CoreTiming.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
#include "Core/Debugger/SymbolMap.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 "RegCache.h"
#include "Jit.h"
#include "Core/Debugger/Breakpoints.h"
namespace MIPSComp
{
using namespace Gen;
const bool USE_JIT_MISSMAP = false;
static std::map<std::string, u32> notJitOps;
template<typename A, typename B>
std::pair<B,A> flip_pair(const std::pair<A,B> &p) {
return std::pair<B, A>(p.second, p.first);
}
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;
if (USE_JIT_MISSMAP) {
std::map<u32, std::string> notJitSorted;
std::transform(notJitOps.begin(), notJitOps.end(), std::inserter(notJitSorted, notJitSorted.begin()), flip_pair<std::string, u32>);
std::string message;
char temp[256];
int remaining = 15;
for (auto it = notJitSorted.rbegin(), end = notJitSorted.rend(); it != end && --remaining >= 0; ++it)
{
snprintf(temp, 256, " (%d), ", it->first);
message += it->second + temp;
}
if (message.size() > 2)
message.resize(message.size() - 2);
NOTICE_LOG(Log::JIT, "Top ops compiled to interpreter: %s", message.c_str());
}
return 1;
}
static u32 JitMemCheck(u32 addr, u32 pc) {
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
return 0;
if (coreState != CORE_RUNNING_CPU && coreState != CORE_NEXTFRAME)
return 1;
g_breakpoints.ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
}
static void JitLogMiss(MIPSOpcode op)
{
if (USE_JIT_MISSMAP)
notJitOps[MIPSGetName(op)]++;
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
func(op);
}
#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif
Jit::Jit(MIPSState *mipsState)
: blocks(mipsState, this), mips_(mipsState) {
blocks.Init();
gpr.SetEmitter(this);
fpr.SetEmitter(this);
AllocCodeSpace(1024 * 1024 * 16);
GenerateFixedCode(jo);
safeMemFuncs.Init(&thunks);
js.startDefaultPrefix = mips_->HasDefaultPrefix();
g_breakpoints.SetSkipFirst(0);
}
Jit::~Jit() {
}
void Jit::DoState(PointerWrap &p) {
auto s = p.Section("Jit", 1, 2);
if (!s)
return;
Do(p, js.startDefaultPrefix);
if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
}
if (s >= 2) {
Do(p, js.hasSetRounding);
if (p.mode == PointerWrap::MODE_READ) {
js.lastSetRounding = 0;
}
} else {
js.hasSetRounding = 1;
}
g_breakpoints.SetSkipFirst(0);
}
void Jit::UpdateFCR31() {
}
void Jit::GetStateAndFlushAll(RegCacheState &state) {
gpr.GetState(state.gpr);
fpr.GetState(state.fpr);
FlushAll();
}
void Jit::RestoreState(const RegCacheState& state) {
gpr.RestoreState(state.gpr);
fpr.RestoreState(state.fpr);
}
void Jit::FlushAll() {
gpr.Flush();
fpr.Flush();
FlushPrefixV();
}
void Jit::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) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_SPREFIX]), Imm32(js.prefixS));
js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_TPREFIX]), Imm32(js.prefixT));
js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_DPREFIX]), Imm32(js.prefixD));
js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
}
js.blockWrotePrefixes = true;
}
void Jit::WriteDowncount(int offset) {
const int downcount = js.downcountAmount + offset;
SUB(32, MIPSSTATE_VAR(downcount), downcount > 127 ? Imm32(downcount) : Imm8(downcount));
}
void Jit::RestoreRoundingMode(bool force) {
if (force || js.hasSetRounding) {
CALL(restoreRoundingMode);
}
}
void Jit::ApplyRoundingMode(bool force) {
if (force || js.hasSetRounding) {
CALL(applyRoundingMode);
}
}
void Jit::UpdateRoundingMode(u32 fcr31) {
if (fcr31 & 0x01000003) {
js.hasSetRounding = true;
}
}
void Jit::ClearCache()
{
blocks.Clear();
ClearCodeSpace(0);
GenerateFixedCode(jo);
}
void Jit::SaveFlags() {
PUSHF();
#if PPSSPP_ARCH(AMD64)
POP(64, R(EAX));
MOV(64, MIPSSTATE_VAR(saved_flags), R(EAX));
#endif
}
void Jit::LoadFlags() {
#if PPSSPP_ARCH(AMD64)
MOV(64, R(EAX), MIPSSTATE_VAR(saved_flags));
PUSH(64, R(EAX));
#endif
POPF();
}
void Jit::CompileDelaySlot(int flags, RegCacheState *state) {
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
if (flags & DELAYSLOT_SAFE)
SaveFlags();
js.inDelaySlot = true;
MIPSOpcode op = GetOffsetInstruction(1);
MIPSCompileOp(op, this);
js.inDelaySlot = false;
if (flags & DELAYSLOT_FLUSH) {
if (state != NULL)
GetStateAndFlushAll(*state);
else
FlushAll();
}
if (flags & DELAYSLOT_SAFE)
LoadFlags();
}
void Jit::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 Jit::Compile(u32 em_address) {
PROFILE_THIS_SCOPE("jitc");
if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
ClearCache();
}
if (!Memory::IsValidAddress(em_address) || (em_address & 3) != 0) {
Core_ExecException(em_address, em_address, ExecExceptionType::JUMP);
return;
}
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 Jit::RunLoopUntil(u64 globalticks) {
PROFILE_THIS_SCOPE("jit");
((void (*)())enterDispatcher)();
}
u32 Jit::GetCompilerPC() {
return js.compilerPC;
}
MIPSOpcode Jit::GetOffsetInstruction(int offset) {
return Memory::Read_Instruction(GetCompilerPC() + 4 * offset);
}
const u8 *Jit::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.afterOp = JitState::AFTER_NONE;
js.PrefixStart();
b->checkedEntry = GetCodePtr();
FixupBranch skip = J_CC(CC_NS);
MOV(32, MIPSSTATE_VAR(pc), Imm32(js.blockStart));
JMP(outerLoop, true);
SetJumpTarget(skip);
b->normalEntry = GetCodePtr();
MIPSAnalyst::AnalysisResults analysis = MIPSAnalyst::Analyze(em_address);
gpr.Start(mips_, &js, &jo, analysis);
fpr.Start(mips_, &js, &jo, analysis, RipAccessible(&mips_->v[0]));
js.numInstructions = 0;
while (js.compiling) {
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
MIPSCompileOp(inst, this);
if (js.afterOp & JitState::AFTER_CORE_STATE) {
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME));
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE, true);
RegCacheState state;
GetStateAndFlushAll(state);
WriteSyscallExit();
SetJumpTarget(skipCheck);
RestoreState(state);
js.afterOp = JitState::AFTER_NONE;
}
js.compilerPC += 4;
js.numInstructions++;
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
gpr.Flush();
}
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
fpr.Flush();
FlushPrefixV();
}
if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS) {
FlushAll();
WriteExit(GetCompilerPC(), js.nextExit++);
js.compiling = false;
}
}
b->codeSize = (u32)(GetCodePtr() - b->normalEntry);
NOP();
AlignCode4();
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 Jit::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 Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
if (ptr == applyRoundingMode)
name = "applyRoundingMode";
else if (ptr == dispatcher)
name = "dispatcher";
else if (ptr == dispatcherInEAXNoCheck)
name = "dispatcher (PC in EAX)";
else if (ptr == dispatcherNoCheck)
name = "dispatcherNoCheck";
else if (ptr == dispatcherCheckCoreState)
name = "dispatcherCheckCoreState";
else if (ptr == enterDispatcher)
name = "enterDispatcher";
else if (ptr == restoreRoundingMode)
name = "restoreRoundingMode";
else if (ptr == crashHandler)
name = "crashHandler";
else {
u32 jitAddr = blocks.GetAddressFromBlockPtr(ptr);
if (jitAddr == 0) {
name = "UnknownOrDeletedBlock";
} else if (jitAddr != (u32)-1) {
char temp[1024];
const std::string label = g_symbolMap ? g_symbolMap->GetDescription(jitAddr) : "";
if (!label.empty())
snprintf(temp, sizeof(temp), "%08x_%s", jitAddr, label.c_str());
else
snprintf(temp, sizeof(temp), "%08x", jitAddr);
name = temp;
} else if (IsInSpace(ptr)) {
if (ptr < endOfPregeneratedCode) {
name = "PreGenCode";
} else {
name = "Unknown";
}
} else if (thunks.IsInSpace(ptr)) {
name = "Thunk";
} else if (safeMemFuncs.IsInSpace(ptr)) {
name = "JitSafeMem";
} else {
return false;
}
}
return true;
}
void Jit::Comp_RunBlock(MIPSOpcode op) {
ERROR_LOG(Log::JIT, "Comp_RunBlock");
}
void Jit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
}
XEmitter emit(exitPoint);
bool prelinked = *emit.GetCodePointer() == 0xE9;
emit.JMP(checkedEntry, true);
if (!prelinked) {
ptrdiff_t actualSize = emit.GetWritableCodePtr() - exitPoint;
int pad = JitBlockCache::GetBlockExitSize() - (int)actualSize;
for (int i = 0; i < pad; ++i) {
emit.INT3();
}
}
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
}
XEmitter emit(checkedEntry);
emit.MOV(32, MIPSSTATE_VAR(pc), Imm32(originalAddress));
emit.JMP(MIPSComp::jit->GetDispatcher(), true);
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
bool Jit::ReplaceJalTo(u32 dest) {
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();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunction(entry->replaceFunc);
SUB(32, MIPSSTATE_VAR(downcount), R(EAX));
ApplyRoundingMode();
}
js.compilerPC += 4;
if (g_breakpoints.HasMemChecks()) {
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC() + 4));
js.afterOp |= JitState::AFTER_CORE_STATE;
}
blocks.ProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
return true;
}
void Jit::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));
}
}
Memory::Opcode origInstruction = Memory::Read_Instruction(GetCompilerPC(), true);
if (origInstruction.encoding == op.encoding) {
ERROR_LOG(Log::HLE, "Replacement broken (savestate problem?): %08x at %08x", op.encoding, GetCompilerPC());
return;
}
if (disabled) {
MIPSCompileOp(origInstruction, this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
MIPSCompileOp(origInstruction, this);
} else {
FlushAll();
MOV(32, R(ECX), MIPSSTATE_VAR(r[MIPS_REG_RA]));
js.downcountAmount += cycles;
WriteExitDestInReg(ECX);
js.compiling = false;
}
} else if (entry->replaceFunc) {
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunction(entry->replaceFunc);
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
ApplyRoundingMode();
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else {
CMP(32, R(EAX), Imm32(0));
FixupBranch positive = J_CC(CC_GE);
MOV(32, R(ECX), MIPSSTATE_VAR(pc));
ADD(32, MIPSSTATE_VAR(downcount), R(EAX));
FixupBranch done = J();
SetJumpTarget(positive);
MOV(32, R(ECX), MIPSSTATE_VAR(r[MIPS_REG_RA]));
SUB(32, MIPSSTATE_VAR(downcount), R(EAX));
SetJumpTarget(done);
ApplyRoundingMode();
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
WriteExitDestInReg(ECX);
js.compiling = false;
}
} else {
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
}
}
void Jit::Comp_Generic(MIPSOpcode op) {
FlushAll();
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
_dbg_assert_msg_((MIPSGetInfo(op) & DELAYSLOT) == 0, "Cannot use interpreter for branch ops.");
if (func)
{
RestoreRoundingMode();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
if (USE_JIT_MISSMAP)
ABI_CallFunctionC(&JitLogMiss, op.encoding);
else
ABI_CallFunctionC(func, op.encoding);
ApplyRoundingMode();
} else {
ERROR_LOG(Log::JIT, "Trying to compile instruction %08x that can't be interpreted", op.encoding);
}
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;
}
}
static void HitInvalidBranch(uint32_t dest) {
Core_ExecException(dest, currentMIPS->pc, ExecExceptionType::JUMP);
}
void Jit::WriteExit(u32 destination, int exit_num) {
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
if (!Memory::IsValidAddress(destination) || (destination & 3) != 0) {
ERROR_LOG_REPORT(Log::JIT, "Trying to write block exit to illegal destination %08x: pc = %08x", destination, currentMIPS->pc);
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
ABI_CallFunctionC(&HitInvalidBranch, destination);
js.afterOp |= JitState::AFTER_CORE_STATE;
}
if (js.afterOp & JitState::AFTER_CORE_STATE) {
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME));
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE);
WriteSyscallExit();
SetJumpTarget(skipCheck);
}
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) {
JMP(blocks.GetBlock(block)->checkedEntry, true);
b->linkStatus[exit_num] = true;
} else {
MOV(32, MIPSSTATE_VAR(pc), Imm32(destination));
JMP(dispatcher, true);
ptrdiff_t actualSize = GetWritableCodePtr() - b->exitPtrs[exit_num];
int pad = JitBlockCache::GetBlockExitSize() - (int)actualSize;
for (int i = 0; i < pad; ++i) {
INT3();
}
}
}
static u32 IsValidJumpTarget(uint32_t addr) {
if (Memory::IsValidAddress(addr) && (addr & 3) == 0)
return 1;
return 0;
}
static void HitInvalidJumpReg(uint32_t source) {
Core_ExecException(currentMIPS->pc, source, ExecExceptionType::JUMP);
currentMIPS->pc = source + 8;
}
void Jit::WriteExitDestInReg(X64Reg reg) {
if (js.afterOp & JitState::AFTER_CORE_STATE) {
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME));
} else {
X64Reg temp = reg == RAX ? RDX : RAX;
MOV(PTRBITS, R(temp), ImmPtr((const void *)&coreState));
CMP(32, MatR(temp), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE);
WriteSyscallExit();
SetJumpTarget(skipCheck);
}
MOV(32, MIPSSTATE_VAR(pc), R(reg));
WriteDowncount();
if (!g_Config.bFastMemory) {
CMP(32, R(reg), Imm32(PSP_GetKernelMemoryBase()));
FixupBranch tooLow = J_CC(CC_B);
CMP(32, R(reg), Imm32(PSP_GetUserMemoryEnd()));
FixupBranch tooHigh = J_CC(CC_AE);
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
if (reg == EAX)
J_CC(CC_NS, dispatcherInEAXNoCheck, true);
JMP(dispatcher, true);
SetJumpTarget(tooLow);
SetJumpTarget(tooHigh);
ABI_CallFunctionA((const void *)&IsValidJumpTarget, R(reg));
CMP(32, R(EAX), Imm32(0));
FixupBranch skip = J_CC(CC_NE);
ABI_CallFunctionC(&HitInvalidJumpReg, GetCompilerPC());
SetJumpTarget(skip);
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
JMP(dispatcherCheckCoreState, true);
} else if (reg == EAX) {
J_CC(CC_NS, dispatcherInEAXNoCheck, true);
JMP(dispatcher, true);
} else {
JMP(dispatcher, true);
}
}
void Jit::WriteSyscallExit() {
WriteDowncount();
JMP(dispatcherCheckCoreState, true);
}
bool Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (g_breakpoints.IsAddressBreakPoint(addr)) {
SaveFlags();
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunctionC(&JitBreakpoint, addr);
CMP(32, R(EAX), Imm32(0));
FixupBranch skip = J_CC(CC_Z);
WriteDowncount(downcountOffset);
ApplyRoundingMode();
LoadFlags();
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skip);
ApplyRoundingMode();
LoadFlags();
return true;
}
return false;
}
void Jit::CheckMemoryBreakpoint(int instructionOffset, MIPSGPReg rs, int offset) {
if (!g_breakpoints.HasMemChecks())
return;
int totalInstructionOffset = instructionOffset + (js.inDelaySlot ? 1 : 0);
uint32_t checkedPC = GetCompilerPC() + totalInstructionOffset * 4;
int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC);
int downcountOffset = js.inDelaySlot ? -2 : -1;
if (js.downcountAmount + downcountOffset < 0) {
downcountOffset = 0;
}
if (gpr.IsImm(rs)) {
uint32_t iaddr = gpr.GetImm(rs) + offset;
MemCheck check;
if (g_breakpoints.GetMemCheckInRange(iaddr, size, &check)) {
if (!(check.cond & MEMCHECK_READ) && !isWrite)
return;
if (!(check.cond & MEMCHECK_WRITE) && isWrite)
return;
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
CallProtectedFunction(&JitMemCheck, iaddr, checkedPC);
CMP(32, R(RAX), Imm32(0));
FixupBranch skipCheck = J_CC(CC_E);
WriteDowncount(downcountOffset);
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skipCheck);
}
} else {
const auto memchecks = g_breakpoints.GetMemCheckRanges(isWrite);
bool possible = !memchecks.empty();
if (!possible)
return;
gpr.Lock(rs);
gpr.MapReg(rs, true, false);
LEA(32, RAX, MDisp(gpr.RX(rs), offset));
gpr.UnlockAll();
FlushAll();
std::vector<FixupBranch> hitChecks;
hitChecks.reserve(memchecks.size());
for (auto it = memchecks.begin(), end = memchecks.end(); it != end; ++it) {
if (it->end != 0) {
CMP(32, R(RAX), Imm32(it->start - size));
FixupBranch skipNext = J_CC(CC_BE);
CMP(32, R(RAX), Imm32(it->end));
hitChecks.push_back(J_CC(CC_B, true));
SetJumpTarget(skipNext);
} else {
CMP(32, R(RAX), Imm32(it->start));
hitChecks.push_back(J_CC(CC_E, true));
}
}
FixupBranch noHits = J(true);
for (auto &fixup : hitChecks)
SetJumpTarget(fixup);
hitChecks.clear();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
CallProtectedFunction(&JitMemCheck, R(RAX), checkedPC);
CMP(32, R(RAX), Imm32(0));
FixupBranch skipCheck = J_CC(CC_E);
WriteDowncount(downcountOffset);
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skipCheck);
SetJumpTarget(noHits);
}
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1) {
ABI_CallFunctionA(thunks.ProtectFunction(func, 1), arg1);
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1, const OpArg &arg2) {
ABI_CallFunctionAA(thunks.ProtectFunction(func, 2), arg1, arg2);
}
void Jit::CallProtectedFunction(const void *func, const u32 arg1, const u32 arg2) {
ABI_CallFunctionCC(thunks.ProtectFunction(func, 2), arg1, arg2);
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1, const u32 arg2) {
ABI_CallFunctionAC(thunks.ProtectFunction(func, 2), arg1, arg2);
}
void Jit::Comp_DoNothing(MIPSOpcode op) { }
MIPSOpcode Jit::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