diff --git a/Core/Core.cpp b/Core/Core.cpp index 5fb535735283..e723107d3034 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -419,6 +419,7 @@ const char *MemoryExceptionTypeAsString(MemoryExceptionType type) { case MemoryExceptionType::WRITE_WORD: return "Write Word"; case MemoryExceptionType::READ_BLOCK: return "Read Block"; case MemoryExceptionType::WRITE_BLOCK: return "Read/Write Block"; + case MemoryExceptionType::ALIGNMENT: return "Alignment"; default: return "N/A"; } diff --git a/Core/Core.h b/Core/Core.h index 62018b832d38..894accd6aeed 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -93,6 +93,7 @@ enum class MemoryExceptionType { WRITE_WORD, READ_BLOCK, WRITE_BLOCK, + ALIGNMENT, }; enum class ExecExceptionType { JUMP, diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 2a5e00a861c5..4fffc6765dfc 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -330,6 +330,8 @@ namespace MIPSComp { ir.Write(IROp::LoadVec4, vregs[0], rs, ir.AddConstant(imm)); } else { // Let's not even bother with "vertical" loads for now. + if (!g_Config.bFastMemory) + ir.Write({ IROp::ValidateAddress128, { 0 }, (u8)rs, 0, (u32)imm }); ir.Write(IROp::LoadFloat, vregs[0], rs, ir.AddConstant(imm)); ir.Write(IROp::LoadFloat, vregs[1], rs, ir.AddConstant(imm + 4)); ir.Write(IROp::LoadFloat, vregs[2], rs, ir.AddConstant(imm + 8)); @@ -342,6 +344,8 @@ namespace MIPSComp { ir.Write(IROp::StoreVec4, vregs[0], rs, ir.AddConstant(imm)); } else { // Let's not even bother with "vertical" stores for now. + if (!g_Config.bFastMemory) + ir.Write({ IROp::ValidateAddress128, { 0 }, (u8)rs, 1, (u32)imm }); ir.Write(IROp::StoreFloat, vregs[0], rs, ir.AddConstant(imm)); ir.Write(IROp::StoreFloat, vregs[1], rs, ir.AddConstant(imm + 4)); ir.Write(IROp::StoreFloat, vregs[2], rs, ir.AddConstant(imm + 8)); diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index c3a2df444a48..66a3efbafe12 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -260,6 +260,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m IRWriter *code = &ir; if (!js.hadBreakpoints) { static const IRPassFunc passes[] = { + &ApplyMemoryValidation, &RemoveLoadStoreLeftRight, &OptimizeFPMoves, &PropagateConstants, diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index b8339896e727..13b2509f18eb 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -163,6 +163,11 @@ static const IRMeta irMeta[] = { { IROp::Breakpoint, "Breakpoint", "", IRFLAG_EXIT }, { IROp::MemoryCheck, "MemoryCheck", "_GC", IRFLAG_EXIT }, + { IROp::ValidateAddress8, "ValidAddr8", "_GC", IRFLAG_EXIT }, + { IROp::ValidateAddress16, "ValidAddr16", "_GC", IRFLAG_EXIT }, + { IROp::ValidateAddress32, "ValidAddr32", "_GC", IRFLAG_EXIT }, + { IROp::ValidateAddress128, "ValidAddr128", "_GC", IRFLAG_EXIT }, + { IROp::RestoreRoundingMode, "RestoreRoundingMode", "" }, { IROp::ApplyRoundingMode, "ApplyRoundingMode", "" }, { IROp::UpdateRoundingMode, "UpdateRoundingMode", "" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 1e2b6c141080..8c7b67b00af2 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -213,8 +213,15 @@ enum class IROp : u8 { SetPCConst, // hack to make replacement know PC CallReplacement, Break, + + // Debugging breakpoints. Breakpoint, MemoryCheck, + + ValidateAddress8, + ValidateAddress16, + ValidateAddress32, + ValidateAddress128, }; enum IRComparison { diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 8538748a21e3..bc97b52b732a 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -79,6 +79,25 @@ u32 RunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING ? 1 : 0; } +template +u32 RunValidateAddress(u32 pc, u32 addr, u32 isWrite) { + const auto toss = [&](MemoryExceptionType t) { + Core_MemoryException(addr, 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); + } + if (alignment > 1 && (addr & (alignment - 1)) != 0) { + return toss(MemoryExceptionType::ALIGNMENT); + } + return 0; +} + // We cannot use NEON on ARM32 here until we make it a hard dependency. We can, however, on ARM64. u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { const IRInst *end = inst + count; @@ -142,6 +161,31 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->r[inst->dest] = ReverseBits32(mips->r[inst->src1]); break; + case IROp::ValidateAddress8: + if (RunValidateAddress<1>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { + CoreTiming::ForceCheck(); + return mips->pc; + } + break; + case IROp::ValidateAddress16: + if (RunValidateAddress<2>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { + CoreTiming::ForceCheck(); + return mips->pc; + } + break; + case IROp::ValidateAddress32: + if (RunValidateAddress<4>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { + CoreTiming::ForceCheck(); + return mips->pc; + } + break; + case IROp::ValidateAddress128: + if (RunValidateAddress<16>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { + CoreTiming::ForceCheck(); + return mips->pc; + } + break; + case IROp::Load8: mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 80aa692f86f4..5725454841b0 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -5,6 +5,7 @@ #include "Common/BitSet.h" #include "Common/Data/Convert/SmallDataConvert.h" #include "Common/Log.h" +#include "Core/Config.h" #include "Core/MIPS/IR/IRInterpreter.h" #include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/IR/IRRegCache.h" @@ -622,6 +623,18 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } break; + case IROp::ValidateAddress8: + case IROp::ValidateAddress16: + case IROp::ValidateAddress32: + case IROp::ValidateAddress128: + if (gpr.IsImm(inst.src1)) { + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); + } else { + gpr.MapIn(inst.src1); + goto doDefault; + } + break; + case IROp::Downcount: case IROp::SetPCConst: goto doDefault; @@ -1428,3 +1441,58 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { } return logBlocks; } + +bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts) { + CONDITIONAL_DISABLE; + if (g_Config.bFastMemory) + DISABLE; + + const auto addValidate = [&out](IROp validate, const IRInst &inst, bool isStore) { + out.Write({ validate, { 0 }, inst.src1, isStore ? (u8)1 : (u8)0, inst.constant }); + }; + + // TODO: Could be smart about not double-validating an address that has a load / store, etc. + bool logBlocks = false; + for (IRInst inst : in.GetInstructions()) { + switch (inst.op) { + case IROp::Load8: + case IROp::Load8Ext: + case IROp::Store8: + addValidate(IROp::ValidateAddress8, inst, inst.op == IROp::Store8); + break; + + case IROp::Load16: + case IROp::Load16Ext: + case IROp::Store16: + addValidate(IROp::ValidateAddress16, inst, inst.op == IROp::Store16); + break; + + case IROp::Load32: + case IROp::LoadFloat: + case IROp::Store32: + case IROp::StoreFloat: + addValidate(IROp::ValidateAddress32, inst, inst.op == IROp::Store32 || inst.op == IROp::StoreFloat); + break; + + case IROp::LoadVec4: + case IROp::StoreVec4: + addValidate(IROp::ValidateAddress128, inst, inst.op == IROp::StoreVec4); + break; + + case IROp::Load32Left: + case IROp::Load32Right: + case IROp::Store32Left: + case IROp::Store32Right: + // This explicitly does not require alignment, so validate as an 8-bit operation. + addValidate(IROp::ValidateAddress8, inst, inst.op == IROp::Store32Left || inst.op == IROp::Store32Right); + break; + + default: + break; + } + + // Always write out the original. We're only adding. + out.Write(inst); + } + return logBlocks; +} diff --git a/Core/MIPS/IR/IRPassSimplify.h b/Core/MIPS/IR/IRPassSimplify.h index e3020a4f61a1..7fdc5d1c7d68 100644 --- a/Core/MIPS/IR/IRPassSimplify.h +++ b/Core/MIPS/IR/IRPassSimplify.h @@ -14,3 +14,4 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts); bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts); bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &opts);