diff --git a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp index 282d4fd2ef..7ae455f2ef 100644 --- a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp @@ -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 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 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 diff --git a/Core/MIPS/ARM64/Arm64IRJit.cpp b/Core/MIPS/ARM64/Arm64IRJit.cpp index b99e116744..88cee8f012 100644 --- a/Core/MIPS/ARM64/Arm64IRJit.cpp +++ b/Core/MIPS/ARM64/Arm64IRJit.cpp @@ -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); diff --git a/Core/MIPS/ARM64/Arm64IRJit.h b/Core/MIPS/ARM64/Arm64IRJit.h index fa2428504c..c33d289a14 100644 --- a/Core/MIPS/ARM64/Arm64IRJit.h +++ b/Core/MIPS/ARM64/Arm64IRJit.h @@ -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 { diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp index 6ce1e0f0d6..8c848ef484 100644 --- a/Core/MIPS/IR/IRNativeCommon.cpp +++ b/Core/MIPS/IR/IRNativeCommon.cpp @@ -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) { diff --git a/Core/MIPS/IR/IRNativeCommon.h b/Core/MIPS/IR/IRNativeCommon.h index 7da5d3a831..e93786ec07 100644 --- a/Core/MIPS/IR/IRNativeCommon.h +++ b/Core/MIPS/IR/IRNativeCommon.h @@ -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); diff --git a/Core/MIPS/x86/X64IRCompSystem.cpp b/Core/MIPS/x86/X64IRCompSystem.cpp index 310a83424f..fd01c4ec56 100644 --- a/Core/MIPS/x86/X64IRCompSystem.cpp +++ b/Core/MIPS/x86/X64IRCompSystem.cpp @@ -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;