From b3b5016f54ff3f8a4ef2285436734f36ab8112e0 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 17 Apr 2021 13:53:03 +0200 Subject: [PATCH] Jits: Fix interpreter fallback handling of discarded registers When the interpreter writes to a discarded register, its type must be changed so that it is no longer considered discarded. Fixes a 62ce1c7 regression. --- Source/Core/Core/PowerPC/Jit64/Jit.cpp | 8 ++++++++ .../Core/PowerPC/Jit64/RegCache/JitRegCache.cpp | 10 ++++++++++ .../Core/PowerPC/Jit64/RegCache/JitRegCache.h | 1 + Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 5 +++++ .../Core/PowerPC/JitArm64/JitArm64_RegCache.cpp | 17 +++++++++++++++-- .../Core/PowerPC/JitArm64/JitArm64_RegCache.h | 1 + Source/Core/Core/PowerPC/PPCAnalyst.cpp | 3 +-- Source/Core/Core/PowerPC/PPCAnalyst.h | 10 ++++++++++ 8 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index 5e436cb13e..ac8b60afee 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -419,15 +419,23 @@ void Jit64::FallBackToInterpreter(UGeckoInstruction inst) { gpr.Flush(); fpr.Flush(); + if (js.op->opinfo->flags & FL_ENDBLOCK) { MOV(32, PPCSTATE(pc), Imm32(js.compilerPC)); MOV(32, PPCSTATE(npc), Imm32(js.compilerPC + 4)); } + Interpreter::Instruction instr = PPCTables::GetInterpreterOp(inst); ABI_PushRegistersAndAdjustStack({}, 0); ABI_CallFunctionC(instr, inst.hex); ABI_PopRegistersAndAdjustStack({}, 0); + + // If the instruction wrote to any registers which were marked as discarded, + // we must mark them as no longer discarded + gpr.Reset(js.op->regsOut); + fpr.Reset(js.op->GetFregsOut()); + if (js.op->opinfo->flags & FL_ENDBLOCK) { if (js.isLastInstruction) diff --git a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp index b9ac55f210..3ca3e9f36e 100644 --- a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp +++ b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp @@ -436,6 +436,16 @@ void RegCache::Flush(BitSet32 pregs) } } +void RegCache::Reset(BitSet32 pregs) +{ + for (preg_t i : pregs) + { + ASSERT_MSG(DYNAREC, !m_regs[i].IsAway(), + "Attempted to reset a loaded register (did you mean to flush it?)"); + m_regs[i].SetFlushed(); + } +} + void RegCache::Revert() { ASSERT(IsAllUnlocked()); diff --git a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.h b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.h index 291e18018f..b3a0bbd2c7 100644 --- a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.h +++ b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.h @@ -171,6 +171,7 @@ public: RCForkGuard Fork(); void Discard(BitSet32 pregs); void Flush(BitSet32 pregs = BitSet32::AllTrue(32)); + void Reset(BitSet32 pregs); void Revert(); void Commit(); diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 49ce26c3dc..9c1b98d2f0 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -161,6 +161,11 @@ void JitArm64::FallBackToInterpreter(UGeckoInstruction inst) MOVI2R(ARM64Reg::W0, inst.hex); BLR(ARM64Reg::X8); + // If the instruction wrote to any registers which were marked as discarded, + // we must mark them as no longer discarded + gpr.ResetRegisters(js.op->regsOut); + fpr.ResetRegisters(js.op->GetFregsOut()); + if (js.op->opinfo->flags & FL_ENDBLOCK) { if (js.isLastInstruction) diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp index 5ce941da21..e41ffdbfa8 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp @@ -26,8 +26,21 @@ void Arm64RegCache::Init(ARM64XEmitter* emitter) void Arm64RegCache::DiscardRegisters(BitSet32 regs) { - for (int j : regs) - DiscardRegister(j); + for (int i : regs) + DiscardRegister(i); +} + +void Arm64RegCache::ResetRegisters(BitSet32 regs) +{ + for (int i : regs) + { + OpArg& reg = m_guest_registers[i]; + ARM64Reg host_reg = reg.GetReg(); + + ASSERT_MSG(DYNAREC, host_reg == ARM64Reg::INVALID_REG, + "Attempted to reset a loaded register (did you mean to flush it?)"); + reg.Flush(); + } } ARM64Reg Arm64RegCache::GetReg() diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h index ca7917e197..edee9f2a85 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h @@ -154,6 +154,7 @@ public: virtual void Start(PPCAnalyst::BlockRegStats& stats) {} void DiscardRegisters(BitSet32 regs); + void ResetRegisters(BitSet32 regs); // Flushes the register cache in different ways depending on the mode virtual void Flush(FlushMode mode, PPCAnalyst::CodeOp* op) = 0; diff --git a/Source/Core/Core/PowerPC/PPCAnalyst.cpp b/Source/Core/Core/PowerPC/PPCAnalyst.cpp index 833f5f476a..0017d636e7 100644 --- a/Source/Core/Core/PowerPC/PPCAnalyst.cpp +++ b/Source/Core/Core/PowerPC/PPCAnalyst.cpp @@ -957,8 +957,7 @@ u32 PPCAnalyzer::Analyze(u32 address, CodeBlock* block, CodeBuffer* buffer, std: { gprDiscardable |= op.regsOut; gprDiscardable &= ~op.regsIn; - if (op.fregOut >= 0) - fprDiscardable[op.fregOut] = true; + fprDiscardable |= op.GetFregsOut(); fprDiscardable &= ~op.fregsIn; } if (strncmp(op.opinfo->opname, "stfd", 4)) diff --git a/Source/Core/Core/PowerPC/PPCAnalyst.h b/Source/Core/Core/PowerPC/PPCAnalyst.h index 3b82618978..d1154baee6 100644 --- a/Source/Core/Core/PowerPC/PPCAnalyst.h +++ b/Source/Core/Core/PowerPC/PPCAnalyst.h @@ -67,6 +67,16 @@ struct CodeOp // 16B // (The reason why we can't always do this is because some games rely on the exact bits of // denormals and SNaNs being preserved as long as no arithmetic operation is performed on them.) BitSet32 fprIsStoreSafe; + + BitSet32 GetFregsOut() const + { + BitSet32 result; + + if (fregOut >= 0) + result[fregOut] = true; + + return result; + } }; struct BlockStats