Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x86jit: Improve memory breakpoint speed #18226

Merged
merged 4 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 162 additions & 18 deletions Core/MIPS/ARM64/Arm64IRCompSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@

#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/ARM64/Arm64IRJit.h"
#include "Core/MIPS/ARM64/Arm64IRRegCache.h"
Expand Down Expand Up @@ -70,6 +72,7 @@ void Arm64JitBackend::CompIR_Basic(IRInst inst) {
break;

case IROp::SetPCConst:
lastConstPC_ = inst.constant;
MOVI2R(SCRATCH1, inst.constant);
MovToPC(SCRATCH1);
break;
Expand All @@ -85,37 +88,118 @@ void Arm64JitBackend::CompIR_Breakpoint(IRInst inst) {

switch (inst.op) {
case IROp::Breakpoint:
{
FlushAll();
// Note: the constant could be a delay slot.
MOVI2R(W0, inst.constant);
QuickCallFunction(SCRATCH2_64, &IRRunBreakpoint);

ptrdiff_t distance = dispatcherCheckCoreState_ - GetCodePointer();
if (distance >= -0x100000 && distance < 0x100000) {
CBNZ(W0, dispatcherCheckCoreState_);
} else {
FixupBranch keepOnKeepingOn = CBZ(W0);
B(dispatcherCheckCoreState_);
SetJumpTarget(keepOnKeepingOn);
}
break;
}

case IROp::MemoryCheck:
{
ARM64Reg addrBase = regs_.MapGPR(inst.src1);
FlushAll();
ADDI2R(W1, addrBase, inst.constant, SCRATCH1);
MovFromPC(W0);
ADDI2R(W0, W0, inst.dest, SCRATCH1);
QuickCallFunction(SCRATCH2_64, &IRRunMemCheck);
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 (CBreakPoints::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(W0, checkedPC);
MOVI2R(W1, iaddr);
QuickCallFunction(SCRATCH2_64, &IRRunMemCheck);

ptrdiff_t distance = dispatcherCheckCoreState_ - GetCodePointer();
if (distance >= -0x100000 && distance < 0x100000) {
CBNZ(W0, dispatcherCheckCoreState_);
} else {
FixupBranch keepOnKeepingOn = CBZ(W0);
B(dispatcherCheckCoreState_);
SetJumpTarget(keepOnKeepingOn);
}
}
} 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 = CBreakPoints::GetMemCheckRanges(isWrite);
// We can trivially skip if there are no checks for this type (i.e. read vs write.)
if (memchecks.empty())
break;

ARM64Reg addrBase = regs_.MapGPR(inst.src1);
ADDI2R(SCRATCH1, addrBase, inst.constant, SCRATCH2);

// We need to flush, or conditions and log expressions will see old register values.
FlushAll();

std::vector<FixupBranch> hitChecks;
for (auto it : memchecks) {
if (it.end != 0) {
CMPI2R(SCRATCH1, it.start - size, SCRATCH2);
MOVI2R(SCRATCH2, it.end);
CCMP(SCRATCH1, SCRATCH2, 0xF, CC_HI);
hitChecks.push_back(B(CC_LO));
} else {
CMPI2R(SCRATCH1, it.start, SCRATCH2);
hitChecks.push_back(B(CC_EQ));
}
}

FixupBranch noHits = B();

// Okay, now land any hit here.
for (auto &fixup : hitChecks)
SetJumpTarget(fixup);
hitChecks.clear();

MOVI2R(W0, checkedPC);
MOV(W1, SCRATCH1);
QuickCallFunction(SCRATCH2_64, &IRRunMemCheck);

ptrdiff_t distance = dispatcherCheckCoreState_ - GetCodePointer();
if (distance >= -0x100000 && distance < 0x100000) {
CBNZ(W0, dispatcherCheckCoreState_);
} else {
FixupBranch keepOnKeepingOn = CBZ(W0);
B(dispatcherCheckCoreState_);
SetJumpTarget(keepOnKeepingOn);
}

SetJumpTarget(noHits);
}
break;
}

default:
INVALIDOP;
break;
}

// Both return a flag on whether to bail out.
ptrdiff_t distance = dispatcherCheckCoreState_ - GetCodePointer();
if (distance >= -0x100000 && distance < 0x100000) {
CBNZ(W0, dispatcherCheckCoreState_);
} else {
FixupBranch keepOnKeepingOn = CBZ(W0);
B(dispatcherCheckCoreState_);
SetJumpTarget(keepOnKeepingOn);
}
}

void Arm64JitBackend::CompIR_System(IRInst inst) {
Expand Down Expand Up @@ -274,6 +358,66 @@ void Arm64JitBackend::CompIR_ValidateAddress(IRInst inst) {
INVALIDOP;
break;
}

if (regs_.IsGPRMappedAsPointer(inst.src1)) {
if (!jo.enablePointerify) {
SUB(SCRATCH1_64, regs_.RPtr(inst.src1), MEMBASEREG);
ADDI2R(SCRATCH1, SCRATCH1, inst.constant, SCRATCH2);
} else {
ADDI2R(SCRATCH1, regs_.R(inst.src1), inst.constant, SCRATCH2);
}
} else {
regs_.Map(inst);
ADDI2R(SCRATCH1, regs_.R(inst.src1), inst.constant, SCRATCH2);
}
ANDI2R(SCRATCH1, SCRATCH1, 0x3FFFFFFF, SCRATCH2);

std::vector<FixupBranch> validJumps;

FixupBranch unaligned;
if (alignment == 2) {
unaligned = TBNZ(SCRATCH1, 0);
} else if (alignment != 1) {
TSTI2R(SCRATCH1, alignment - 1, SCRATCH2);
unaligned = B(CC_NEQ);
}

CMPI2R(SCRATCH1, PSP_GetUserMemoryEnd() - alignment, SCRATCH2);
FixupBranch tooHighRAM = B(CC_HI);
CMPI2R(SCRATCH1, PSP_GetKernelMemoryBase(), SCRATCH2);
validJumps.push_back(B(CC_HS));

CMPI2R(SCRATCH1, PSP_GetVidMemEnd() - alignment, SCRATCH2);
FixupBranch tooHighVid = B(CC_HI);
CMPI2R(SCRATCH1, PSP_GetVidMemBase(), SCRATCH2);
validJumps.push_back(B(CC_HS));

CMPI2R(SCRATCH1, PSP_GetScratchpadMemoryEnd() - alignment, SCRATCH2);
FixupBranch tooHighScratch = B(CC_HI);
CMPI2R(SCRATCH1, PSP_GetScratchpadMemoryBase(), SCRATCH2);
validJumps.push_back(B(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.
auto regsCopy = regs_;
regsCopy.FlushAll();

// Ignores the return value, always returns to the dispatcher.
// Otherwise would need a thunk to restore regs.
MOV(W0, SCRATCH1);
MOVI2R(W1, alignment);
MOVI2R(W2, isWrite ? 1 : 0);
QuickCallFunction(SCRATCH2, &ReportBadAddress);
B(dispatcherCheckCoreState_);

for (FixupBranch &b : validJumps)
SetJumpTarget(b);
}

} // namespace MIPSComp
Expand Down
1 change: 1 addition & 0 deletions Core/MIPS/ARM64/Arm64IRJit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ bool Arm64JitBackend::CompileBlock(IRBlock *block, int block_num, bool preload)
const u8 *blockStart = GetCodePointer();
block->SetTargetOffset((int)GetOffset(blockStart));
compilingBlockNum_ = block_num;
lastConstPC_ = 0;

regs_.Start(block);

Expand Down
2 changes: 2 additions & 0 deletions Core/MIPS/ARM64/Arm64IRJit.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class Arm64JitBackend : public Arm64Gen::ARM64CodeBlock, public IRNativeBackend
int jitStartOffset_ = 0;
int compilingBlockNum_ = -1;
int logBlocks_ = 0;
// Only useful in breakpoints, where it's set immediately prior.
uint32_t lastConstPC_ = 0;
};

class Arm64IRJit : public IRNativeJit {
Expand Down
19 changes: 19 additions & 0 deletions Core/MIPS/IR/IRNativeCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include "Common/Profiler/Profiler.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Core/Core.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/IR/IRNativeCommon.h"

Expand Down Expand Up @@ -98,6 +100,23 @@ uint32_t IRNativeBackend::DoIRInst(uint64_t value) {
return IRInterpret(currentMIPS, &inst, 1);
}

int IRNativeBackend::ReportBadAddress(uint32_t addr, uint32_t alignment, uint32_t isWrite) {
const auto toss = [&](MemoryExceptionType t) {
Core_MemoryException(addr, alignment, currentMIPS->pc, t);
return coreState != CORE_RUNNING ? 1 : 0;
};

if (!Memory::IsValidRange(addr, alignment)) {
MemoryExceptionType t = isWrite == 1 ? MemoryExceptionType::WRITE_WORD : MemoryExceptionType::READ_WORD;
if (alignment > 4)
t = isWrite ? MemoryExceptionType::WRITE_BLOCK : MemoryExceptionType::READ_BLOCK;
return toss(t);
} else if (alignment > 1 && (addr & (alignment - 1)) != 0) {
return toss(MemoryExceptionType::ALIGNMENT);
}
return 0;
}

IRNativeBackend::IRNativeBackend(IRBlockCache &blocks) : blocks_(blocks) {}

void IRNativeBackend::CompileIRInst(IRInst inst) {
Expand Down
2 changes: 2 additions & 0 deletions Core/MIPS/IR/IRNativeCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class IRNativeBackend {
// Callback to log AND perform an IR interpreter inst. Returns 0 or a PC to jump to.
static uint32_t DoIRInst(uint64_t inst);

static int ReportBadAddress(uint32_t addr, uint32_t alignment, uint32_t isWrite);

void AddLinkableExit(int block_num, uint32_t pc, int exitStartOffset, int exitLen);
void EraseAllLinks(int block_num);

Expand Down
Loading