Merge pull request #13245 from JosJuice/mbp-condition-flush
Jit: Flush registers used in memory breakpoint conditions
This commit is contained in:
15 files changed
+189
-49
No files matched your search
@@ -10,6 +10,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/BitSet.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
@@ -341,8 +342,13 @@ 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. And conversely, if the
|
||||||
|
// aforementioned functionality is no longer needed, clear the JIT cache to switch to faster code.
|
||||||
|
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();
|
||||||
@@ -351,6 +357,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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -129,9 +130,17 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using std::isinf;
|
|||||||
using std::isnan;
|
using std::isnan;
|
||||||
#include <expr.h>
|
#include <expr.h>
|
||||||
|
|
||||||
|
#include "Common/BitSet.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
@@ -503,3 +504,26 @@ 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;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_has_computed_registers_used = true;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1294,6 +1294,25 @@ void Jit64::IntializeSpeculativeConstants()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ public:
|
|||||||
|
|
||||||
void IntializeSpeculativeConstants();
|
void IntializeSpeculativeConstants();
|
||||||
|
|
||||||
|
void FlushRegistersBeforeSlowAccess();
|
||||||
|
|
||||||
JitBlockCache* GetBlockCache() override { return &blocks; }
|
JitBlockCache* GetBlockCache() override { return &blocks; }
|
||||||
void Trace();
|
void Trace();
|
||||||
|
|
||||||
|
|||||||
@@ -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++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ void EmuCodeBlock::SwitchToNearCode()
|
|||||||
SetCodePtr(m_near_code, m_near_code_end, m_near_code_write_failed);
|
SetCodePtr(m_near_code, m_near_code_end, m_near_code_write_failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuCodeBlock::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(m_jit.js.compilerPC));
|
||||||
|
}
|
||||||
|
|
||||||
FixupBranch EmuCodeBlock::BATAddressLookup(X64Reg addr, X64Reg tmp, const void* bat_table)
|
FixupBranch EmuCodeBlock::BATAddressLookup(X64Reg addr, X64Reg tmp, const void* bat_table)
|
||||||
{
|
{
|
||||||
MOV(64, R(tmp), ImmPtr(bat_table));
|
MOV(64, R(tmp), ImmPtr(bat_table));
|
||||||
@@ -386,14 +393,11 @@ 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 isn't known
|
||||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
// when compiling the routine, 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))
|
||||||
{
|
{
|
||||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
FlushPCBeforeSlowAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
||||||
@@ -457,8 +461,7 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helps external systems know which instruction triggered the read.
|
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,14 +563,11 @@ 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 isn't known
|
||||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
// when compiling the routine, 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))
|
||||||
{
|
{
|
||||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
FlushPCBeforeSlowAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
||||||
@@ -663,8 +663,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Helps external systems know which instruction triggered the write
|
FlushPCBeforeSlowAccess();
|
||||||
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
|
|
||||||
|
|
||||||
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
|
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
|
||||||
switch (accessSize)
|
switch (accessSize)
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ public:
|
|||||||
return Gen::M(m_const_pool.GetConstant(&value, sizeof(T), N, index));
|
return Gen::M(m_const_pool.GetConstant(&value, sizeof(T), N, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlushPCBeforeSlowAccess();
|
||||||
|
|
||||||
// Writes upper 15 bits of physical address to addr and clobbers the lower 17 bits of addr.
|
// Writes upper 15 bits of physical address to addr and clobbers the lower 17 bits of addr.
|
||||||
// Jumps to the returned FixupBranch if lookup fails.
|
// Jumps to the returned FixupBranch if lookup fails.
|
||||||
Gen::FixupBranch BATAddressLookup(Gen::X64Reg addr, Gen::X64Reg tmp, const void* bat_table);
|
Gen::FixupBranch BATAddressLookup(Gen::X64Reg addr, Gen::X64Reg tmp, const void* bat_table);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 isn't known
|
||||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
// when compiling the routine, 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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in new issue
Block a user