This commit is contained in:
JosJuice 2025-04-27 02:06:38 -04:00 committed by GitHub
commit 3548713636
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 181 additions and 53 deletions

View file

@ -346,8 +346,12 @@ void MemChecks::Update()
{ {
const Core::CPUThreadGuard guard(m_system); const Core::CPUThreadGuard guard(m_system);
// Clear the JIT cache so it can switch the watchpoint-compatible mode. const bool registers_changed = UpdateRegistersUsedInConditions();
if (m_mem_breakpoints_set != HasAny())
// If we've added a first memcheck, clear the JIT cache so it can switch to watchpoint-compatible
// code. Or, if we've added a memcheck whose condition wants to read from a new register,
// clear the JIT cache to make the slow memory access code flush that register.
if (registers_changed || m_mem_breakpoints_set != HasAny())
{ {
m_system.GetJitInterface().ClearCache(guard); m_system.GetJitInterface().ClearCache(guard);
m_mem_breakpoints_set = HasAny(); m_mem_breakpoints_set = HasAny();
@ -356,6 +360,27 @@ void MemChecks::Update()
m_system.GetMMU().DBATUpdated(); m_system.GetMMU().DBATUpdated();
} }
bool MemChecks::UpdateRegistersUsedInConditions()
{
BitSet32 gprs_used, fprs_used;
for (TMemCheck& mem_check : m_mem_checks)
{
if (mem_check.condition)
{
gprs_used |= mem_check.condition->GetGPRsUsed();
fprs_used |= mem_check.condition->GetFPRsUsed();
}
}
const bool registers_changed =
gprs_used != m_gprs_used_in_conditions || fprs_used != m_fprs_used_in_conditions;
m_gprs_used_in_conditions = gprs_used;
m_fprs_used_in_conditions = fprs_used;
return registers_changed;
}
TMemCheck* MemChecks::GetMemCheck(u32 address, size_t size) TMemCheck* MemChecks::GetMemCheck(u32 address, size_t size)
{ {
const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) { const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) {

View file

@ -8,6 +8,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h" #include "Core/PowerPC/Expression.h"
@ -127,8 +128,16 @@ public:
void Clear(); void Clear();
bool HasAny() const { return !m_mem_checks.empty(); } bool HasAny() const { return !m_mem_checks.empty(); }
BitSet32 GetGPRsUsedInConditions() { return m_gprs_used_in_conditions; }
BitSet32 GetFPRsUsedInConditions() { return m_fprs_used_in_conditions; }
private: private:
// Returns whether any change was made
bool UpdateRegistersUsedInConditions();
TMemChecks m_mem_checks; TMemChecks m_mem_checks;
Core::System& m_system; Core::System& m_system;
BitSet32 m_gprs_used_in_conditions;
BitSet32 m_fprs_used_in_conditions;
bool m_mem_breakpoints_set = false; bool m_mem_breakpoints_set = false;
}; };

View file

@ -494,3 +494,22 @@ std::string Expression::GetText() const
{ {
return m_text; return m_text;
} }
void Expression::ComputeRegistersUsed()
{
if (m_has_computed_registers_used)
return;
for (const VarBinding& bind : m_binds)
{
switch (bind.type)
{
case VarBindingType::GPR:
m_gprs_used[bind.index] = true;
break;
case VarBindingType::FPR:
m_fprs_used[bind.index] = true;
break;
}
}
}

View file

@ -9,6 +9,8 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "Common/BitSet.h"
struct expr; struct expr;
struct expr_var_list; struct expr_var_list;
@ -41,6 +43,18 @@ public:
std::string GetText() const; std::string GetText() const;
BitSet32 GetGPRsUsed()
{
ComputeRegistersUsed();
return m_gprs_used;
}
BitSet32 GetFPRsUsed()
{
ComputeRegistersUsed();
return m_fprs_used;
}
private: private:
enum class SynchronizeDirection enum class SynchronizeDirection
{ {
@ -69,10 +83,16 @@ private:
void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const; void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const;
void Reporting(const double result) const; void Reporting(const double result) const;
void ComputeRegistersUsed();
std::string m_text; std::string m_text;
ExprPointer m_expr; ExprPointer m_expr;
ExprVarListPointer m_vars; ExprVarListPointer m_vars;
std::vector<VarBinding> m_binds; std::vector<VarBinding> m_binds;
BitSet32 m_gprs_used;
BitSet32 m_fprs_used;
bool m_has_computed_registers_used = false;
}; };
inline bool EvaluateCondition(Core::System& system, const std::optional<Expression>& condition) inline bool EvaluateCondition(Core::System& system, const std::optional<Expression>& condition)

View file

@ -1294,6 +1294,32 @@ void Jit64::IntializeSpeculativeConstants()
} }
} }
void Jit64::FlushPCBeforeSlowAccess()
{
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
}
void Jit64::FlushRegistersBeforeSlowAccess()
{
// Register values can be used by memory watchpoint conditions.
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
if (mem_checks.HasAny())
{
BitSet32 gprs = mem_checks.GetGPRsUsedInConditions();
BitSet32 fprs = mem_checks.GetFPRsUsedInConditions();
if (gprs || fprs)
{
RCForkGuard gpr_guard = gpr.Fork();
RCForkGuard fpr_guard = fpr.Fork();
gpr.Flush(gprs);
fpr.Flush(fprs);
}
}
}
bool Jit64::HandleFunctionHooking(u32 address) bool Jit64::HandleFunctionHooking(u32 address)
{ {
const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT); const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT);

View file

@ -81,6 +81,9 @@ public:
void IntializeSpeculativeConstants(); void IntializeSpeculativeConstants();
void FlushPCBeforeSlowAccess();
void FlushRegistersBeforeSlowAccess();
JitBlockCache* GetBlockCache() override { return &blocks; } JitBlockCache* GetBlockCache() override { return &blocks; }
void Trace(); void Trace();

View file

@ -110,6 +110,8 @@ void Jit64::lXXx(UGeckoInstruction inst)
PanicAlertFmt("Invalid instruction"); PanicAlertFmt("Invalid instruction");
} }
FlushRegistersBeforeSlowAccess();
// PowerPC has no 8-bit sign extended load, but x86 does, so merge extsb with the load if we find // PowerPC has no 8-bit sign extended load, but x86 does, so merge extsb with the load if we find
// it. // it.
if (CanMergeNextInstructions(1) && accessSize == 8 && js.op[1].inst.OPCD == 31 && if (CanMergeNextInstructions(1) && accessSize == 8 && js.op[1].inst.OPCD == 31 &&
@ -439,6 +441,8 @@ void Jit64::dcbz(UGeckoInstruction inst)
int a = inst.RA; int a = inst.RA;
int b = inst.RB; int b = inst.RB;
FlushRegistersBeforeSlowAccess();
{ {
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0); RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
RCOpArg Rb = gpr.Use(b, RCMode::Read); RCOpArg Rb = gpr.Use(b, RCMode::Read);
@ -477,7 +481,7 @@ void Jit64::dcbz(UGeckoInstruction inst)
SwitchToFarCode(); SwitchToFarCode();
SetJumpTarget(slow); SetJumpTarget(slow);
} }
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC)); FlushPCBeforeSlowAccess();
BitSet32 registersInUse = CallerSavedRegistersInUse(); BitSet32 registersInUse = CallerSavedRegistersInUse();
ABI_PushRegistersAndAdjustStack(registersInUse, 0); ABI_PushRegistersAndAdjustStack(registersInUse, 0);
ABI_CallFunctionPR(PowerPC::ClearDCacheLineFromJit, &m_mmu, RSCRATCH); ABI_CallFunctionPR(PowerPC::ClearDCacheLineFromJit, &m_mmu, RSCRATCH);
@ -524,6 +528,8 @@ void Jit64::stX(UGeckoInstruction inst)
return; return;
} }
FlushRegistersBeforeSlowAccess();
// If we already know the address of the write // If we already know the address of the write
if (!a || gpr.IsImm(a)) if (!a || gpr.IsImm(a))
{ {
@ -602,6 +608,8 @@ void Jit64::stXx(UGeckoInstruction inst)
break; break;
} }
FlushRegistersBeforeSlowAccess();
const bool does_clobber = WriteClobbersRegValue(accessSize, /* swap */ !byte_reverse); const bool does_clobber = WriteClobbersRegValue(accessSize, /* swap */ !byte_reverse);
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read); RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
@ -634,6 +642,8 @@ void Jit64::lmw(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD; int a = inst.RA, d = inst.RD;
FlushRegistersBeforeSlowAccess();
// TODO: This doesn't handle rollback on DSI correctly // TODO: This doesn't handle rollback on DSI correctly
{ {
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0); RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
@ -657,6 +667,8 @@ void Jit64::stmw(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD; int a = inst.RA, d = inst.RD;
FlushRegistersBeforeSlowAccess();
// TODO: This doesn't handle rollback on DSI correctly // TODO: This doesn't handle rollback on DSI correctly
for (int i = d; i < 32; i++) for (int i = d; i < 32; i++)
{ {

View file

@ -30,6 +30,8 @@ void Jit64::lfXXX(UGeckoInstruction inst)
FALLBACK_IF(!indexed && !a); FALLBACK_IF(!indexed && !a);
FlushRegistersBeforeSlowAccess();
s32 offset = 0; s32 offset = 0;
RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read); RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RegCache::Realize(addr); RegCache::Realize(addr);
@ -103,6 +105,8 @@ void Jit64::stfXXX(UGeckoInstruction inst)
FALLBACK_IF(update && jo.memcheck && indexed && a == b); FALLBACK_IF(update && jo.memcheck && indexed && a == b);
FlushRegistersBeforeSlowAccess();
if (single) if (single)
{ {
if (js.fpr_is_store_safe[s] && js.op->fprIsSingle[s]) if (js.fpr_is_store_safe[s] && js.op->fprIsSingle[s])
@ -196,6 +200,8 @@ void Jit64::stfiwx(UGeckoInstruction inst)
int a = inst.RA; int a = inst.RA;
int b = inst.RB; int b = inst.RB;
FlushRegistersBeforeSlowAccess();
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0); RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
RCOpArg Rb = gpr.Use(b, RCMode::Read); RCOpArg Rb = gpr.Use(b, RCMode::Read);
RCOpArg Rs = fpr.Use(s, RCMode::Read); RCOpArg Rs = fpr.Use(s, RCMode::Read);

View file

@ -35,6 +35,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
int w = indexed ? inst.Wx : inst.W; int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a); FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA); RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read); RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset); RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@ -69,8 +71,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly // We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly
// with just the scale bits. // with just the scale bits.
MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00)); MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00));
@ -83,8 +85,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// Some games (e.g. Dirt 2) incorrectly set the unused bits which breaks the lookup table code. // Some games (e.g. Dirt 2) incorrectly set the unused bits which breaks the lookup table code.
// Hence, we need to mask out the unused bits. The layout of the GQR register is // Hence, we need to mask out the unused bits. The layout of the GQR register is
// UU[SCALE]UUUUU[TYPE] where SCALE is 6 bits and TYPE is 3 bits, so we have to AND with // UU[SCALE]UUUUU[TYPE] where SCALE is 6 bits and TYPE is 3 bits, so we have to AND with
@ -124,6 +126,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
int w = indexed ? inst.Wx : inst.W; int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a); FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA); RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read); RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset); RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@ -144,8 +148,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// Get the high part of the GQR register // Get the high part of the GQR register
OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i); OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i);
gqr.AddMemOffset(2); gqr.AddMemOffset(2);

View file

@ -386,15 +386,10 @@ void EmuCodeBlock::SafeLoadToReg(X64Reg reg_value, const Gen::OpArg& opAddress,
SetJumpTarget(slow); SetJumpTarget(slow);
} }
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of Jit64AsmCommon routines, the state we want to store here
// interrupt checks, and printing accurate PC locations in debug logs. // isn't known at JIT time, so the caller has to store it themselves.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC)) if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
{ m_jit.FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
}
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0; size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment); ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment);
@ -457,8 +452,7 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
return; return;
} }
// Helps external systems know which instruction triggered the read. m_jit.FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
// Fall back to general-case code. // Fall back to general-case code.
ABI_PushRegistersAndAdjustStack(registersInUse, 0); ABI_PushRegistersAndAdjustStack(registersInUse, 0);
@ -560,15 +554,10 @@ void EmuCodeBlock::SafeWriteRegToReg(OpArg reg_value, X64Reg reg_addr, int acces
SetJumpTarget(slow); SetJumpTarget(slow);
} }
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of Jit64AsmCommon routines, the state we want to store here
// interrupt checks, and printing accurate PC locations in debug logs. // isn't known at JIT time, so the caller has to store it themselves.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC)) if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
{ m_jit.FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
}
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0; size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment); ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment);
@ -663,8 +652,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
} }
else else
{ {
// Helps external systems know which instruction triggered the write m_jit.FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
ABI_PushRegistersAndAdjustStack(registersInUse, 0); ABI_PushRegistersAndAdjustStack(registersInUse, 0);
switch (accessSize) switch (accessSize)

View file

@ -273,12 +273,18 @@ protected:
// !emitting_routine && mode != AlwaysSlowAccess && !jo.fastmem: X30 // !emitting_routine && mode != AlwaysSlowAccess && !jo.fastmem: X30
// !emitting_routine && mode == Auto && jo.fastmem: X30 // !emitting_routine && mode == Auto && jo.fastmem: X30
// //
// Furthermore, any callee-saved register which isn't marked in gprs_to_push/fprs_to_push // Furthermore:
// may be clobbered if mode != AlwaysFastAccess. // - Any callee-saved register which isn't marked in gprs_to_push/fprs_to_push may be
// clobbered if mode != AlwaysFastAccess.
// - If !emitting_routine && mode != AlwaysFastAccess && jo.memcheck, X30 must not
// contain a guest register.
void EmitBackpatchRoutine(u32 flags, MemAccessMode mode, Arm64Gen::ARM64Reg RS, void EmitBackpatchRoutine(u32 flags, MemAccessMode mode, Arm64Gen::ARM64Reg RS,
Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0), Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0),
BitSet32 fprs_to_push = BitSet32(0), bool emitting_routine = false); BitSet32 fprs_to_push = BitSet32(0), bool emitting_routine = false);
// temp_gpr must be a valid register, but temp_fpr can be INVALID_REG.
void FlushPPCStateBeforeSlowAccess(Arm64Gen::ARM64Reg temp_gpr, Arm64Gen::ARM64Reg temp_fpr);
// Loadstore routines // Loadstore routines
void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update); void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update);
void SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s32 offset, bool update); void SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s32 offset, bool update);

View file

@ -21,6 +21,7 @@
#include "Core/PowerPC/JitArmCommon/BackPatch.h" #include "Core/PowerPC/JitArmCommon/BackPatch.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
using namespace Arm64Gen; using namespace Arm64Gen;
@ -171,6 +172,7 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
const ARM64Reg temp_gpr = ARM64Reg::W1; const ARM64Reg temp_gpr = ARM64Reg::W1;
const int temp_gpr_index = DecodeReg(temp_gpr); const int temp_gpr_index = DecodeReg(temp_gpr);
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
BitSet32 gprs_to_push_early = {}; BitSet32 gprs_to_push_early = {};
if (memcheck) if (memcheck)
@ -189,16 +191,10 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early); ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early);
m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30); m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30);
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of JitAsm routines, the state we want to store here
// interrupt checks, and printing accurate PC locations in debug logs. // isn't known at JIT time, so the caller has to store it themselves.
//
// In the case of JitAsm routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!emitting_routine) if (!emitting_routine)
{ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, temp_fpr);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
}
if (flags & BackPatchInfo::FLAG_STORE) if (flags & BackPatchInfo::FLAG_STORE)
{ {
@ -265,7 +261,6 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
if (memcheck) if (memcheck)
{ {
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
const u64 early_push_count = (gprs_to_push & gprs_to_push_early).Count(); const u64 early_push_count = (gprs_to_push & gprs_to_push_early).Count();
const u64 early_push_size = Common::AlignUp(early_push_count, 2) * 8; const u64 early_push_size = Common::AlignUp(early_push_count, 2) * 8;
@ -316,6 +311,22 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
} }
} }
void JitArm64::FlushPPCStateBeforeSlowAccess(ARM64Reg temp_gpr, ARM64Reg temp_fpr)
{
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
MOVI2R(temp_gpr, js.compilerPC);
STR(IndexType::Unsigned, temp_gpr, PPC_REG, PPCSTATE_OFF(pc));
// Register values can be used by memory watchpoint conditions.
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
if (mem_checks.HasAny())
{
gpr.StoreRegisters(mem_checks.GetGPRsUsedInConditions(), temp_gpr, FlushMode::MaintainState);
fpr.StoreRegisters(mem_checks.GetFPRsUsedInConditions(), temp_fpr, FlushMode::MaintainState);
}
}
bool JitArm64::HandleFastmemFault(SContext* ctx) bool JitArm64::HandleFastmemFault(SContext* ctx)
{ {
const u8* pc = reinterpret_cast<const u8*>(ctx->CTX_PC); const u8* pc = reinterpret_cast<const u8*>(ctx->CTX_PC);

View file

@ -102,9 +102,7 @@ void JitArm64::psq_lXX(UGeckoInstruction inst)
{ {
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i)); LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
UBFM(type_reg, scale_reg, 16, 18); // Type UBFM(type_reg, scale_reg, 16, 18); // Type
UBFM(scale_reg, scale_reg, 24, 29); // Scale UBFM(scale_reg, scale_reg, 24, 29); // Scale
@ -260,9 +258,7 @@ void JitArm64::psq_stXX(UGeckoInstruction inst)
{ {
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i)); LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
UBFM(type_reg, scale_reg, 0, 2); // Type UBFM(type_reg, scale_reg, 0, 2); // Type
UBFM(scale_reg, scale_reg, 8, 13); // Scale UBFM(scale_reg, scale_reg, 8, 13); // Scale

View file

@ -388,14 +388,16 @@ public:
BitSet32 GetDirtyGPRs() const; BitSet32 GetDirtyGPRs() const;
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{ {
FlushRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No); FlushRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
} }
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{ {
FlushCRRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No); FlushCRRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
} }
void DiscardCRRegisters(BitSet8 regs); void DiscardCRRegisters(BitSet8 regs);
@ -459,9 +461,10 @@ public:
void FixSinglePrecision(size_t preg); void FixSinglePrecision(size_t preg);
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{ {
FlushRegisters(regs, FlushMode::All, tmp_reg); FlushRegisters(regs, flush_mode, tmp_reg);
} }
protected: protected: