arm64jit: Speed up memchecks, add validation.

This commit is contained in:
Unknown W. Brackets 2023-09-20 08:56:50 -07:00
parent 5929aaae85
commit e433a8be4a
6 changed files with 190 additions and 36 deletions

View File

@ -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"
@ -70,6 +72,7 @@ void Arm64JitBackend::CompIR_Basic(IRInst inst) {
break;
case IROp::SetPCConst:
lastConstPC_ = inst.constant;
MOVI2R(SCRATCH1, inst.constant);
MovToPC(SCRATCH1);
break;
@ -85,37 +88,121 @@ 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);
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);
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:
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);
FixupBranch skipNext = B(CC_LS);
CMPI2R(SCRATCH1, it.end, SCRATCH2);
hitChecks.push_back(B(CC_LO));
SetJumpTarget(skipNext);
} 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) {
@ -274,6 +361,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

View File

@ -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);

View File

@ -145,6 +145,8 @@ private:
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 {

View File

@ -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"
@ -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) {

View File

@ -131,6 +131,8 @@ protected:
// 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);

View File

@ -323,23 +323,6 @@ void X64JitBackend::CompIR_Transfer(IRInst inst) {
}
}
int 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;
}
void X64JitBackend::CompIR_ValidateAddress(IRInst inst) {
CONDITIONAL_DISABLE;