mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 13:30:02 +00:00
jit-ir: Implement memory breakpoints.
These generally work, but likely delay slots will make downcount slightly off, and won't resume when you hit run again without manually stepping through them.
This commit is contained in:
parent
7cd666c351
commit
4578c3cb54
@ -364,6 +364,31 @@ void CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc)
|
||||
check->Action(address, write, size, pc);
|
||||
}
|
||||
|
||||
void CBreakPoints::ExecOpMemCheck(u32 address, u32 pc)
|
||||
{
|
||||
// Note: currently, we don't check "on changed" for HLE (ExecMemCheck.)
|
||||
// We'd need to more carefully specify memory changes in HLE for that.
|
||||
int size = MIPSAnalyst::OpMemoryAccessSize(pc);
|
||||
if (size == 0 && MIPSAnalyst::OpHasDelaySlot(pc)) {
|
||||
// This means that the delay slot is what tripped us.
|
||||
pc += 4;
|
||||
size = MIPSAnalyst::OpMemoryAccessSize(pc);
|
||||
}
|
||||
|
||||
bool write = MIPSAnalyst::IsOpMemoryWrite(pc);
|
||||
auto check = GetMemCheck(address, size);
|
||||
if (check) {
|
||||
int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE;
|
||||
if (write && (check->cond & mask) == mask) {
|
||||
if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) {
|
||||
check->Action(address, write, size, pc);
|
||||
}
|
||||
} else {
|
||||
check->Action(address, write, size, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CBreakPoints::ExecMemCheckJitBefore(u32 address, bool write, int size, u32 pc)
|
||||
{
|
||||
auto check = GetMemCheck(address, size);
|
||||
@ -421,6 +446,11 @@ const std::vector<BreakPoint> CBreakPoints::GetBreakpoints()
|
||||
return breakPoints_;
|
||||
}
|
||||
|
||||
bool CBreakPoints::HasMemChecks()
|
||||
{
|
||||
return !memChecks_.empty();
|
||||
}
|
||||
|
||||
void CBreakPoints::Update(u32 addr)
|
||||
{
|
||||
if (MIPSComp::jit)
|
||||
|
@ -134,6 +134,7 @@ public:
|
||||
|
||||
static MemCheck *GetMemCheck(u32 address, int size);
|
||||
static void ExecMemCheck(u32 address, bool write, int size, u32 pc);
|
||||
static void ExecOpMemCheck(u32 address, u32 pc);
|
||||
|
||||
// Executes memchecks but used by the jit. Cleanup finalizes after jit is done.
|
||||
static void ExecMemCheckJitBefore(u32 address, bool write, int size, u32 pc);
|
||||
@ -148,6 +149,8 @@ public:
|
||||
static const std::vector<MemCheck> GetMemChecks();
|
||||
static const std::vector<BreakPoint> GetBreakpoints();
|
||||
|
||||
static bool HasMemChecks();
|
||||
|
||||
static void Update(u32 addr = 0);
|
||||
|
||||
private:
|
||||
|
@ -78,6 +78,8 @@ void IRFrontend::Comp_FPULS(MIPSOpcode op) {
|
||||
int ft = _FT;
|
||||
MIPSGPReg rs = _RS;
|
||||
|
||||
CheckMemoryBreakpoint(rs, offset);
|
||||
|
||||
switch (op >> 26) {
|
||||
case 49: //FI(ft) = Memory::Read_U32(addr); break; //lwc1
|
||||
ir.Write(IROp::LoadFloat, ft, rs, ir.AddConstant(offset));
|
||||
|
@ -15,30 +15,8 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
|
||||
// Optimization ideas:
|
||||
//
|
||||
// It's common to see sequences of stores writing or reading to a contiguous set of
|
||||
// addresses in function prologues/epilogues:
|
||||
// sw s5, 104(sp)
|
||||
// sw s4, 100(sp)
|
||||
// sw s3, 96(sp)
|
||||
// sw s2, 92(sp)
|
||||
// sw s1, 88(sp)
|
||||
// sw s0, 84(sp)
|
||||
// sw ra, 108(sp)
|
||||
// mov s4, a0
|
||||
// mov s3, a1
|
||||
// ...
|
||||
// Such sequences could easily be detected and turned into nice contiguous
|
||||
// sequences of ARM stores instead of the current 3 instructions per sw/lw.
|
||||
//
|
||||
// Also, if we kept track of the likely register content of a cached register,
|
||||
// (pointer or data), we could avoid many BIC instructions.
|
||||
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/MIPSAnalyst.h"
|
||||
#include "Core/MIPS/MIPSCodeUtils.h"
|
||||
@ -81,6 +59,8 @@ namespace MIPSComp {
|
||||
return;
|
||||
}
|
||||
|
||||
CheckMemoryBreakpoint(rs, offset);
|
||||
|
||||
int addrReg = IRTEMP_0;
|
||||
switch (o) {
|
||||
// Load
|
||||
|
@ -19,17 +19,17 @@
|
||||
|
||||
#include "math/math_util.h"
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/MIPSTables.h"
|
||||
#include "Core/MIPS/MIPSAnalyst.h"
|
||||
#include "Core/MIPS/MIPSCodeUtils.h"
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/Reporting.h"
|
||||
|
||||
#include "Core/MIPS/IR/IRFrontend.h"
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
#include "Core/Reporting.h"
|
||||
|
||||
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE.
|
||||
@ -277,6 +277,9 @@ namespace MIPSComp {
|
||||
s32 offset = (signed short)(op & 0xFFFC);
|
||||
int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5);
|
||||
MIPSGPReg rs = _RS;
|
||||
|
||||
CheckMemoryBreakpoint(rs, offset);
|
||||
|
||||
switch (op >> 26) {
|
||||
case 50: //lv.s
|
||||
ir.Write(IROp::LoadFloat, vfpuBase + voffset[vt], rs, ir.AddConstant(offset));
|
||||
@ -300,6 +303,8 @@ namespace MIPSComp {
|
||||
u8 vregs[4];
|
||||
GetVectorRegs(vregs, V_Quad, vt);
|
||||
|
||||
CheckMemoryBreakpoint(rs, imm);
|
||||
|
||||
switch (op >> 26) {
|
||||
case 54: //lv.q
|
||||
if (IsConsecutive4(vregs)) {
|
||||
|
@ -91,7 +91,7 @@ void IRFrontend::EatInstruction(MIPSOpcode op) {
|
||||
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot.");
|
||||
}
|
||||
|
||||
CheckBreakpoint(GetCompilerPC() + 4, 0);
|
||||
CheckBreakpoint(GetCompilerPC() + 4);
|
||||
js.numInstructions++;
|
||||
js.compilerPC += 4;
|
||||
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
|
||||
@ -99,7 +99,7 @@ void IRFrontend::EatInstruction(MIPSOpcode op) {
|
||||
|
||||
void IRFrontend::CompileDelaySlot() {
|
||||
js.inDelaySlot = true;
|
||||
CheckBreakpoint(GetCompilerPC() + 4, -2);
|
||||
CheckBreakpoint(GetCompilerPC() + 4);
|
||||
MIPSOpcode op = GetOffsetInstruction(1);
|
||||
MIPSCompileOp(op, this);
|
||||
js.inDelaySlot = false;
|
||||
@ -239,7 +239,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, std::v
|
||||
js.numInstructions = 0;
|
||||
while (js.compiling) {
|
||||
// Jit breakpoints are quite fast, so let's do them in release too.
|
||||
CheckBreakpoint(GetCompilerPC(), 0);
|
||||
CheckBreakpoint(GetCompilerPC());
|
||||
|
||||
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
|
||||
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
|
||||
@ -318,16 +318,19 @@ void IRFrontend::Comp_RunBlock(MIPSOpcode op) {
|
||||
ERROR_LOG(JIT, "Comp_RunBlock should never be reached!");
|
||||
}
|
||||
|
||||
void IRFrontend::CheckBreakpoint(u32 addr, int downcountOffset) {
|
||||
void IRFrontend::CheckBreakpoint(u32 addr) {
|
||||
if (CBreakPoints::IsAddressBreakPoint(addr)) {
|
||||
FlushAll();
|
||||
|
||||
RestoreRoundingMode();
|
||||
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
|
||||
// 0 because we normally execute before increasing.
|
||||
// TODO: In likely branches, downcount will be incorrect.
|
||||
int downcountOffset = js.inDelaySlot && js.downcountAmount >= 2 ? -2 : 0;
|
||||
int downcountAmount = js.downcountAmount + downcountOffset;
|
||||
ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8);
|
||||
// Note that this means downcount can't be metadata on the block.
|
||||
js.downcountAmount = -downcountAmount;
|
||||
js.downcountAmount = -downcountOffset;
|
||||
ir.Write(IROp::Breakpoint);
|
||||
ApplyRoundingMode();
|
||||
|
||||
@ -335,4 +338,27 @@ void IRFrontend::CheckBreakpoint(u32 addr, int downcountOffset) {
|
||||
}
|
||||
}
|
||||
|
||||
void IRFrontend::CheckMemoryBreakpoint(int rs, int offset) {
|
||||
if (CBreakPoints::HasMemChecks()) {
|
||||
FlushAll();
|
||||
|
||||
RestoreRoundingMode();
|
||||
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
|
||||
// 0 because we normally execute before increasing.
|
||||
int downcountOffset = js.inDelaySlot ? -2 : -1;
|
||||
// TODO: In likely branches, downcount will be incorrect. This might make resume fail.
|
||||
if (js.downcountAmount == 0) {
|
||||
downcountOffset = 0;
|
||||
}
|
||||
int downcountAmount = js.downcountAmount + downcountOffset;
|
||||
ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8);
|
||||
// Note that this means downcount can't be metadata on the block.
|
||||
js.downcountAmount = -downcountOffset;
|
||||
ir.Write(IROp::MemoryCheck, 0, rs, ir.AddConstant(offset));
|
||||
ApplyRoundingMode();
|
||||
|
||||
js.hadBreakpoints = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -107,7 +107,8 @@ private:
|
||||
void EatInstruction(MIPSOpcode op);
|
||||
MIPSOpcode GetOffsetInstruction(int offset);
|
||||
|
||||
void CheckBreakpoint(u32 addr, int downcountOffset);
|
||||
void CheckBreakpoint(u32 addr);
|
||||
void CheckMemoryBreakpoint(int rs, int offset);
|
||||
|
||||
// Utility compilation functions
|
||||
void BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely);
|
||||
|
@ -156,7 +156,8 @@ static const IRMeta irMeta[] = {
|
||||
{ IROp::SetPC, "SetPC", "_G" },
|
||||
{ IROp::SetPCConst, "SetPC", "_C" },
|
||||
{ IROp::CallReplacement, "CallRepl", "_C" },
|
||||
{ IROp::Breakpoint, "Breakpoint", "" },
|
||||
{ IROp::Breakpoint, "Breakpoint", "", IRFLAG_EXIT },
|
||||
{ IROp::MemoryCheck, "MemoryCheck", "_GC", IRFLAG_EXIT },
|
||||
};
|
||||
|
||||
const IRMeta *metaIndex[256];
|
||||
|
@ -196,7 +196,7 @@ enum class IROp : u8 {
|
||||
// Fake/System instructions
|
||||
Interpret,
|
||||
|
||||
// Emit this before you exits. Semantic is to set the downcount
|
||||
// Emit this before you exit. Semantic is to set the downcount
|
||||
// that will be used at the actual exit.
|
||||
Downcount, // src1 + (src2<<8)
|
||||
|
||||
@ -220,6 +220,7 @@ enum class IROp : u8 {
|
||||
CallReplacement,
|
||||
Break,
|
||||
Breakpoint,
|
||||
MemoryCheck,
|
||||
};
|
||||
|
||||
enum IRComparison {
|
||||
|
@ -47,6 +47,15 @@ u32 RunBreakpoint(u32 pc) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 RunMemCheck(u32 pc, u32 addr) {
|
||||
// Should we skip this breakpoint?
|
||||
if (CBreakPoints::CheckSkipFirst() == pc)
|
||||
return 0;
|
||||
|
||||
CBreakPoints::ExecOpMemCheck(addr, pc);
|
||||
return coreState != CORE_RUNNING ? 1 : 0;
|
||||
}
|
||||
|
||||
u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count) {
|
||||
const IRInst *end = inst + count;
|
||||
while (inst != end) {
|
||||
@ -803,8 +812,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c
|
||||
|
||||
case IROp::Breakpoint:
|
||||
if (RunBreakpoint(mips->pc)) {
|
||||
if (coreState != CORE_RUNNING)
|
||||
CoreTiming::ForceCheck();
|
||||
CoreTiming::ForceCheck();
|
||||
return mips->pc;
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::MemoryCheck:
|
||||
if (RunMemCheck(mips->pc, mips->r[inst->src1] + constPool[inst->src2])) {
|
||||
CoreTiming::ForceCheck();
|
||||
return mips->pc;
|
||||
}
|
||||
break;
|
||||
|
@ -561,6 +561,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) {
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
case IROp::Breakpoint:
|
||||
case IROp::MemoryCheck:
|
||||
default:
|
||||
{
|
||||
doDefaultAndFlush:
|
||||
|
@ -582,6 +582,41 @@ namespace MIPSAnalyst {
|
||||
return (op & MIPSTABLE_IMM_MASK) == 0xF8000000;
|
||||
}
|
||||
|
||||
int OpMemoryAccessSize(u32 pc) {
|
||||
const auto op = Memory::Read_Instruction(pc, true);
|
||||
MIPSInfo info = MIPSGetInfo(op);
|
||||
if ((info & (IN_MEM | OUT_MEM)) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Verify lwl/lwr/etc.?
|
||||
switch (info & MEMTYPE_MASK) {
|
||||
case MEMTYPE_BYTE:
|
||||
return 1;
|
||||
case MEMTYPE_HWORD:
|
||||
return 2;
|
||||
case MEMTYPE_WORD:
|
||||
case MEMTYPE_FLOAT:
|
||||
return 4;
|
||||
case MEMTYPE_VQUAD:
|
||||
return 16;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IsOpMemoryWrite(u32 pc) {
|
||||
const auto op = Memory::Read_Instruction(pc, true);
|
||||
MIPSInfo info = MIPSGetInfo(op);
|
||||
return (info & OUT_MEM) != 0;
|
||||
}
|
||||
|
||||
bool OpHasDelaySlot(u32 pc) {
|
||||
const auto op = Memory::Read_Instruction(pc, true);
|
||||
MIPSInfo info = MIPSGetInfo(op);
|
||||
return (info & DELAYSLOT) != 0;
|
||||
}
|
||||
|
||||
bool OpWouldChangeMemory(u32 pc, u32 addr, u32 size) {
|
||||
const auto op = Memory::Read_Instruction(pc, true);
|
||||
|
||||
|
@ -131,14 +131,15 @@ namespace MIPSAnalyst
|
||||
bool IsSyscall(MIPSOpcode op);
|
||||
|
||||
bool OpWouldChangeMemory(u32 pc, u32 addr, u32 size);
|
||||
int OpMemoryAccessSize(u32 pc);
|
||||
bool IsOpMemoryWrite(u32 pc);
|
||||
bool OpHasDelaySlot(u32 pc);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
typedef struct {
|
||||
DebugInterface* cpu;
|
||||
u32 opcodeAddress;
|
||||
MIPSOpcode encodedOpcode;
|
||||
|
||||
|
||||
// shared between branches and conditional moves
|
||||
bool isConditional;
|
||||
bool conditionMet;
|
||||
|
Loading…
Reference in New Issue
Block a user