mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #12134 from JosJuice/jit-constprop
Jit: Extract immediate handling to separate ConstantPropagation class
This commit is contained in:
@@ -508,6 +508,8 @@ add_library(core
|
||||
PowerPC/Interpreter/Interpreter_Tables.cpp
|
||||
PowerPC/Interpreter/Interpreter.cpp
|
||||
PowerPC/Interpreter/Interpreter.h
|
||||
PowerPC/JitCommon/ConstantPropagation.cpp
|
||||
PowerPC/JitCommon/ConstantPropagation.h
|
||||
PowerPC/JitCommon/DivUtils.cpp
|
||||
PowerPC/JitCommon/DivUtils.h
|
||||
PowerPC/JitCommon/JitAsmCommon.cpp
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "Core/PowerPC/Jit64Common/Jit64Constants.h"
|
||||
#include "Core/PowerPC/Jit64Common/Jit64PowerPCState.h"
|
||||
#include "Core/PowerPC/Jit64Common/TrampolineCache.h"
|
||||
#include "Core/PowerPC/JitCommon/ConstantPropagation.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
@@ -369,6 +370,9 @@ void Jit64::FallBackToInterpreter(UGeckoInstruction inst)
|
||||
gpr.Reset(js.op->regsOut);
|
||||
fpr.Reset(js.op->GetFregsOut());
|
||||
|
||||
// We must also update constant propagation
|
||||
m_constant_propagation.ClearGPRs(js.op->regsOut);
|
||||
|
||||
if (js.op->opinfo->flags & FL_SET_MSR)
|
||||
EmitUpdateMembase();
|
||||
|
||||
@@ -921,6 +925,8 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
gpr.Start();
|
||||
fpr.Start();
|
||||
|
||||
m_constant_propagation.Clear();
|
||||
|
||||
js.downcountAmount = 0;
|
||||
js.skipInstructions = 0;
|
||||
js.carryFlag = CarryFlag::InPPCState;
|
||||
@@ -1105,21 +1111,55 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
m_constant_propagation.Clear();
|
||||
|
||||
CompileInstruction(op);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have an input register that is going to be used again, load it pre-emptively,
|
||||
// even if the instruction doesn't strictly need it in a register, to avoid redundant
|
||||
// loads later. Of course, don't do this if we're already out of registers.
|
||||
// As a bit of a heuristic, make sure we have at least one register left over for the
|
||||
// output, which needs to be bound in the actual instruction compilation.
|
||||
// TODO: make this smarter in the case that we're actually register-starved, i.e.
|
||||
// prioritize the more important registers.
|
||||
gpr.PreloadRegisters(op.regsIn & op.gprInUse & ~op.gprDiscardable);
|
||||
fpr.PreloadRegisters(op.fregsIn & op.fprInXmm & ~op.fprDiscardable);
|
||||
}
|
||||
const JitCommon::ConstantPropagationResult constant_propagation_result =
|
||||
m_constant_propagation.EvaluateInstruction(op.inst, opinfo->flags);
|
||||
|
||||
CompileInstruction(op);
|
||||
if (!constant_propagation_result.instruction_fully_executed)
|
||||
{
|
||||
if (!bJITRegisterCacheOff)
|
||||
{
|
||||
// If we have an input register that is going to be used again, load it pre-emptively,
|
||||
// even if the instruction doesn't strictly need it in a register, to avoid redundant
|
||||
// loads later. Of course, don't do this if we're already out of registers.
|
||||
// As a bit of a heuristic, make sure we have at least one register left over for the
|
||||
// output, which needs to be bound in the actual instruction compilation.
|
||||
// TODO: make this smarter in the case that we're actually register-starved, i.e.
|
||||
// prioritize the more important registers.
|
||||
gpr.PreloadRegisters(op.regsIn & op.gprInUse & ~op.gprDiscardable);
|
||||
fpr.PreloadRegisters(op.fregsIn & op.fprInXmm & ~op.fprDiscardable);
|
||||
}
|
||||
|
||||
CompileInstruction(op);
|
||||
}
|
||||
|
||||
m_constant_propagation.Apply(constant_propagation_result);
|
||||
|
||||
if (constant_propagation_result.gpr >= 0)
|
||||
{
|
||||
// Mark the GPR as dirty in the register cache
|
||||
gpr.SetImmediate32(constant_propagation_result.gpr,
|
||||
constant_propagation_result.gpr_value);
|
||||
}
|
||||
|
||||
if (constant_propagation_result.instruction_fully_executed)
|
||||
{
|
||||
if (constant_propagation_result.carry)
|
||||
FinalizeCarry(*constant_propagation_result.carry);
|
||||
|
||||
if (constant_propagation_result.overflow)
|
||||
GenerateConstantOverflow(*constant_propagation_result.overflow);
|
||||
|
||||
// FinalizeImmediateRC is called last, because it may trigger branch merging
|
||||
if (constant_propagation_result.compute_rc)
|
||||
FinalizeImmediateRC(constant_propagation_result.gpr_value);
|
||||
}
|
||||
}
|
||||
|
||||
js.fpr_is_store_safe = op.fprIsStoreSafeAfterInst;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "Core/PowerPC/Jit64Common/BlockCache.h"
|
||||
#include "Core/PowerPC/Jit64Common/Jit64AsmCommon.h"
|
||||
#include "Core/PowerPC/Jit64Common/TrampolineCache.h"
|
||||
#include "Core/PowerPC/JitCommon/ConstantPropagation.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
@@ -83,6 +84,8 @@ public:
|
||||
|
||||
void FlushRegistersBeforeSlowAccess();
|
||||
|
||||
JitCommon::ConstantPropagation& GetConstantPropagation() { return m_constant_propagation; }
|
||||
|
||||
JitBlockCache* GetBlockCache() override { return &blocks; }
|
||||
void Trace();
|
||||
|
||||
@@ -122,6 +125,7 @@ public:
|
||||
void FinalizeCarry(Gen::CCFlags cond);
|
||||
void FinalizeCarry(bool ca);
|
||||
void ComputeRC(preg_t preg, bool needs_test = true, bool needs_sext = true);
|
||||
void FinalizeImmediateRC(s32 value);
|
||||
|
||||
void AndWithMask(Gen::X64Reg reg, u32 mask);
|
||||
void RotateLeft(int bits, Gen::X64Reg regOp, const Gen::OpArg& arg, u8 rotate);
|
||||
@@ -288,6 +292,8 @@ private:
|
||||
GPRRegCache gpr{*this};
|
||||
FPURegCache fpr{*this};
|
||||
|
||||
JitCommon::ConstantPropagation m_constant_propagation;
|
||||
|
||||
Jit64AsmRoutineManager asm_routines{*this};
|
||||
|
||||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,111 +16,79 @@ using preg_t = size_t;
|
||||
class PPCCachedReg
|
||||
{
|
||||
public:
|
||||
enum class LocationType
|
||||
{
|
||||
/// Value is currently at its default location
|
||||
Default,
|
||||
/// Value is not stored anywhere because we know it won't be read before the next write
|
||||
Discarded,
|
||||
/// Value is currently bound to a x64 register
|
||||
Bound,
|
||||
/// Value is known as an immediate and has not been written back to its default location
|
||||
Immediate,
|
||||
/// Value is known as an immediate and is already present at its default location
|
||||
SpeculativeImmediate,
|
||||
};
|
||||
|
||||
PPCCachedReg() = default;
|
||||
|
||||
explicit PPCCachedReg(Gen::OpArg default_location_)
|
||||
: default_location(default_location_), location(default_location_)
|
||||
explicit PPCCachedReg(Gen::OpArg default_location) : m_default_location(default_location) {}
|
||||
|
||||
Gen::OpArg GetDefaultLocation() const { return m_default_location; }
|
||||
|
||||
Gen::X64Reg GetHostRegister() const
|
||||
{
|
||||
ASSERT(m_in_host_register);
|
||||
return m_host_register;
|
||||
}
|
||||
|
||||
const std::optional<Gen::OpArg>& Location() const { return location; }
|
||||
bool IsInDefaultLocation() const { return m_in_default_location; }
|
||||
bool IsInHostRegister() const { return m_in_host_register; }
|
||||
|
||||
LocationType GetLocationType() const
|
||||
void SetFlushed(bool maintain_host_register)
|
||||
{
|
||||
if (!location.has_value())
|
||||
return LocationType::Discarded;
|
||||
|
||||
if (!away)
|
||||
{
|
||||
ASSERT(!revertable);
|
||||
|
||||
if (location->IsImm())
|
||||
return LocationType::SpeculativeImmediate;
|
||||
|
||||
ASSERT(*location == default_location);
|
||||
return LocationType::Default;
|
||||
}
|
||||
|
||||
ASSERT(location->IsImm() || location->IsSimpleReg());
|
||||
return location->IsImm() ? LocationType::Immediate : LocationType::Bound;
|
||||
ASSERT(!m_revertable);
|
||||
if (!maintain_host_register)
|
||||
m_in_host_register = false;
|
||||
m_in_default_location = true;
|
||||
}
|
||||
|
||||
bool IsAway() const { return away; }
|
||||
bool IsDiscarded() const { return !location.has_value(); }
|
||||
bool IsBound() const { return GetLocationType() == LocationType::Bound; }
|
||||
|
||||
void SetBoundTo(Gen::X64Reg xreg)
|
||||
void SetInHostRegister(Gen::X64Reg xreg, bool dirty)
|
||||
{
|
||||
away = true;
|
||||
location = Gen::R(xreg);
|
||||
if (dirty)
|
||||
m_in_default_location = false;
|
||||
m_in_host_register = true;
|
||||
m_host_register = xreg;
|
||||
}
|
||||
|
||||
void SetDirty() { m_in_default_location = false; }
|
||||
|
||||
void SetDiscarded()
|
||||
{
|
||||
ASSERT(!revertable);
|
||||
away = false;
|
||||
location = std::nullopt;
|
||||
ASSERT(!m_revertable);
|
||||
m_in_default_location = false;
|
||||
m_in_host_register = false;
|
||||
}
|
||||
|
||||
void SetFlushed()
|
||||
{
|
||||
ASSERT(!revertable);
|
||||
away = false;
|
||||
location = default_location;
|
||||
}
|
||||
|
||||
void SetToImm32(u32 imm32, bool dirty = true)
|
||||
{
|
||||
away |= dirty;
|
||||
location = Gen::Imm32(imm32);
|
||||
}
|
||||
|
||||
bool IsRevertable() const { return revertable; }
|
||||
bool IsRevertable() const { return m_revertable; }
|
||||
void SetRevertable()
|
||||
{
|
||||
ASSERT(IsBound());
|
||||
revertable = true;
|
||||
ASSERT(m_in_host_register);
|
||||
m_revertable = true;
|
||||
}
|
||||
void SetRevert()
|
||||
{
|
||||
ASSERT(revertable);
|
||||
revertable = false;
|
||||
SetFlushed();
|
||||
ASSERT(m_revertable);
|
||||
m_revertable = false;
|
||||
SetFlushed(false);
|
||||
}
|
||||
void SetCommit()
|
||||
{
|
||||
ASSERT(revertable);
|
||||
revertable = false;
|
||||
ASSERT(m_revertable);
|
||||
m_revertable = false;
|
||||
}
|
||||
|
||||
bool IsLocked() const { return locked > 0; }
|
||||
void Lock() { locked++; }
|
||||
bool IsLocked() const { return m_locked > 0; }
|
||||
void Lock() { m_locked++; }
|
||||
void Unlock()
|
||||
{
|
||||
ASSERT(IsLocked());
|
||||
locked--;
|
||||
m_locked--;
|
||||
}
|
||||
|
||||
private:
|
||||
Gen::OpArg default_location{};
|
||||
std::optional<Gen::OpArg> location{};
|
||||
bool away = false; // value not in source register
|
||||
bool revertable = false;
|
||||
size_t locked = 0;
|
||||
Gen::OpArg m_default_location{};
|
||||
Gen::X64Reg m_host_register{};
|
||||
bool m_in_default_location = true;
|
||||
bool m_in_host_register = false;
|
||||
bool m_revertable = false;
|
||||
size_t m_locked = 0;
|
||||
};
|
||||
|
||||
class X64CachedReg
|
||||
@@ -128,25 +96,20 @@ class X64CachedReg
|
||||
public:
|
||||
preg_t Contents() const { return ppcReg; }
|
||||
|
||||
void SetBoundTo(preg_t ppcReg_, bool dirty_)
|
||||
void SetBoundTo(preg_t ppcReg_)
|
||||
{
|
||||
free = false;
|
||||
ppcReg = ppcReg_;
|
||||
dirty = dirty_;
|
||||
}
|
||||
|
||||
void Unbind()
|
||||
{
|
||||
ppcReg = static_cast<preg_t>(Gen::INVALID_REG);
|
||||
free = true;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
bool IsFree() const { return free && !locked; }
|
||||
|
||||
bool IsDirty() const { return dirty; }
|
||||
void MakeDirty() { dirty = true; }
|
||||
|
||||
bool IsLocked() const { return locked > 0; }
|
||||
void Lock() { locked++; }
|
||||
void Unlock()
|
||||
@@ -158,7 +121,6 @@ public:
|
||||
private:
|
||||
preg_t ppcReg = static_cast<preg_t>(Gen::INVALID_REG);
|
||||
bool free = true;
|
||||
bool dirty = false;
|
||||
size_t locked = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,16 +13,59 @@ FPURegCache::FPURegCache(Jit64& jit) : RegCache{jit}
|
||||
{
|
||||
}
|
||||
|
||||
void FPURegCache::StoreRegister(preg_t preg, const OpArg& new_loc)
|
||||
bool FPURegCache::IsImm(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsBound(), "Unbound register - {}", preg);
|
||||
m_emitter->MOVAPD(new_loc, m_regs[preg].Location()->GetSimpleReg());
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 FPURegCache::Imm32(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, false, "FPURegCache doesn't support immediates");
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 FPURegCache::SImm32(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, false, "FPURegCache doesn't support immediates");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OpArg FPURegCache::R(preg_t preg) const
|
||||
{
|
||||
if (m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
return ::Gen::R(m_regs[preg].GetHostRegister());
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInDefaultLocation(), "FPR {} missing!", preg);
|
||||
return m_regs[preg].GetDefaultLocation();
|
||||
}
|
||||
}
|
||||
|
||||
void FPURegCache::StoreRegister(preg_t preg, const OpArg& new_loc,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
if (m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
m_emitter->MOVAPD(new_loc, m_regs[preg].GetHostRegister());
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"FPR {} not in host register", preg);
|
||||
}
|
||||
}
|
||||
|
||||
void FPURegCache::LoadRegister(preg_t preg, X64Reg new_loc)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[preg].IsDiscarded(), "Discarded register - {}", preg);
|
||||
m_emitter->MOVAPD(new_loc, m_regs[preg].Location().value());
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInDefaultLocation(), "FPR {} not in default location", preg);
|
||||
m_emitter->MOVAPD(new_loc, m_regs[preg].GetDefaultLocation());
|
||||
}
|
||||
|
||||
void FPURegCache::DiscardImm(preg_t preg)
|
||||
{
|
||||
// FPURegCache doesn't support immediates, so no need to do anything
|
||||
}
|
||||
|
||||
std::span<const X64Reg> FPURegCache::GetAllocationOrder() const
|
||||
|
||||
@@ -12,10 +12,17 @@ class FPURegCache final : public RegCache
|
||||
public:
|
||||
explicit FPURegCache(Jit64& jit);
|
||||
|
||||
bool IsImm(preg_t preg) const override;
|
||||
u32 Imm32(preg_t preg) const override;
|
||||
s32 SImm32(preg_t preg) const override;
|
||||
|
||||
protected:
|
||||
Gen::OpArg R(preg_t preg) const override;
|
||||
Gen::OpArg GetDefaultLocation(preg_t preg) const override;
|
||||
void StoreRegister(preg_t preg, const Gen::OpArg& newLoc) override;
|
||||
void StoreRegister(preg_t preg, const Gen::OpArg& newLoc,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers) override;
|
||||
void LoadRegister(preg_t preg, Gen::X64Reg newLoc) override;
|
||||
void DiscardImm(preg_t preg) override;
|
||||
std::span<const Gen::X64Reg> GetAllocationOrder() const override;
|
||||
BitSet32 GetRegUtilization() const override;
|
||||
BitSet32 CountRegsIn(preg_t preg, u32 lookahead) const override;
|
||||
|
||||
@@ -13,16 +13,76 @@ GPRRegCache::GPRRegCache(Jit64& jit) : RegCache{jit}
|
||||
{
|
||||
}
|
||||
|
||||
void GPRRegCache::StoreRegister(preg_t preg, const OpArg& new_loc)
|
||||
bool GPRRegCache::IsImm(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[preg].IsDiscarded(), "Discarded register - {}", preg);
|
||||
m_emitter->MOV(32, new_loc, m_regs[preg].Location().value());
|
||||
return m_jit.GetConstantPropagation().HasGPR(preg);
|
||||
}
|
||||
|
||||
u32 GPRRegCache::Imm32(preg_t preg) const
|
||||
{
|
||||
ASSERT(m_jit.GetConstantPropagation().HasGPR(preg));
|
||||
return m_jit.GetConstantPropagation().GetGPR(preg);
|
||||
}
|
||||
|
||||
s32 GPRRegCache::SImm32(preg_t preg) const
|
||||
{
|
||||
ASSERT(m_jit.GetConstantPropagation().HasGPR(preg));
|
||||
return m_jit.GetConstantPropagation().GetGPR(preg);
|
||||
}
|
||||
|
||||
OpArg GPRRegCache::R(preg_t preg) const
|
||||
{
|
||||
if (m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
return ::Gen::R(m_regs[preg].GetHostRegister());
|
||||
}
|
||||
else if (m_jit.GetConstantPropagation().HasGPR(preg))
|
||||
{
|
||||
return ::Gen::Imm32(m_jit.GetConstantPropagation().GetGPR(preg));
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInDefaultLocation(), "GPR {} missing!", preg);
|
||||
return m_regs[preg].GetDefaultLocation();
|
||||
}
|
||||
}
|
||||
|
||||
void GPRRegCache::StoreRegister(preg_t preg, const OpArg& new_loc,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
if (m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
m_emitter->MOV(32, new_loc, ::Gen::R(m_regs[preg].GetHostRegister()));
|
||||
}
|
||||
else if (m_jit.GetConstantPropagation().HasGPR(preg))
|
||||
{
|
||||
m_emitter->MOV(32, new_loc, ::Gen::Imm32(m_jit.GetConstantPropagation().GetGPR(preg)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"GPR {} not in host register or constant propagation", preg);
|
||||
}
|
||||
}
|
||||
|
||||
void GPRRegCache::LoadRegister(preg_t preg, X64Reg new_loc)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[preg].IsDiscarded(), "Discarded register - {}", preg);
|
||||
m_emitter->MOV(32, ::Gen::R(new_loc), m_regs[preg].Location().value());
|
||||
const JitCommon::ConstantPropagation& constant_propagation = m_jit.GetConstantPropagation();
|
||||
if (constant_propagation.HasGPR(preg))
|
||||
{
|
||||
m_emitter->MOV(32, ::Gen::R(new_loc), ::Gen::Imm32(constant_propagation.GetGPR(preg)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInDefaultLocation(), "GPR {} not in default location",
|
||||
preg);
|
||||
m_emitter->MOV(32, ::Gen::R(new_loc), m_regs[preg].GetDefaultLocation());
|
||||
}
|
||||
}
|
||||
|
||||
void GPRRegCache::DiscardImm(preg_t preg)
|
||||
{
|
||||
m_jit.GetConstantPropagation().ClearGPR(preg);
|
||||
}
|
||||
|
||||
OpArg GPRRegCache::GetDefaultLocation(preg_t preg) const
|
||||
@@ -48,8 +108,9 @@ void GPRRegCache::SetImmediate32(preg_t preg, u32 imm_value, bool dirty)
|
||||
{
|
||||
// "dirty" can be false to avoid redundantly flushing an immediate when
|
||||
// processing speculative constants.
|
||||
DiscardRegContentsIfCached(preg);
|
||||
m_regs[preg].SetToImm32(imm_value, dirty);
|
||||
if (dirty)
|
||||
DiscardRegister(preg);
|
||||
m_jit.GetConstantPropagation().SetGPR(preg, imm_value);
|
||||
}
|
||||
|
||||
BitSet32 GPRRegCache::GetRegUtilization() const
|
||||
|
||||
@@ -11,12 +11,20 @@ class GPRRegCache final : public RegCache
|
||||
{
|
||||
public:
|
||||
explicit GPRRegCache(Jit64& jit);
|
||||
|
||||
bool IsImm(preg_t preg) const override;
|
||||
u32 Imm32(preg_t preg) const override;
|
||||
s32 SImm32(preg_t preg) const override;
|
||||
|
||||
void SetImmediate32(preg_t preg, u32 imm_value, bool dirty = true);
|
||||
|
||||
protected:
|
||||
Gen::OpArg R(preg_t preg) const override;
|
||||
Gen::OpArg GetDefaultLocation(preg_t preg) const override;
|
||||
void StoreRegister(preg_t preg, const Gen::OpArg& new_loc) override;
|
||||
void StoreRegister(preg_t preg, const Gen::OpArg& new_loc,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers) override;
|
||||
void LoadRegister(preg_t preg, Gen::X64Reg new_loc) override;
|
||||
void DiscardImm(preg_t preg) override;
|
||||
std::span<const Gen::X64Reg> GetAllocationOrder() const override;
|
||||
BitSet32 GetRegUtilization() const override;
|
||||
BitSet32 CountRegsIn(preg_t preg, u32 lookahead) const override;
|
||||
|
||||
@@ -136,7 +136,7 @@ bool RCOpArg::IsImm() const
|
||||
{
|
||||
if (const preg_t* preg = std::get_if<preg_t>(&contents))
|
||||
{
|
||||
return rc->R(*preg).IsImm();
|
||||
return rc->IsImm(*preg);
|
||||
}
|
||||
else if (std::holds_alternative<u32>(contents))
|
||||
{
|
||||
@@ -149,7 +149,7 @@ s32 RCOpArg::SImm32() const
|
||||
{
|
||||
if (const preg_t* preg = std::get_if<preg_t>(&contents))
|
||||
{
|
||||
return rc->R(*preg).SImm32();
|
||||
return rc->SImm32(*preg);
|
||||
}
|
||||
else if (const u32* imm = std::get_if<u32>(&contents))
|
||||
{
|
||||
@@ -163,7 +163,7 @@ u32 RCOpArg::Imm32() const
|
||||
{
|
||||
if (const preg_t* preg = std::get_if<preg_t>(&contents))
|
||||
{
|
||||
return rc->R(*preg).Imm32();
|
||||
return rc->Imm32(*preg);
|
||||
}
|
||||
else if (const u32* imm = std::get_if<u32>(&contents))
|
||||
{
|
||||
@@ -297,25 +297,16 @@ bool RegCache::SanityCheck() const
|
||||
{
|
||||
for (size_t i = 0; i < m_regs.size(); i++)
|
||||
{
|
||||
switch (m_regs[i].GetLocationType())
|
||||
{
|
||||
case PPCCachedReg::LocationType::Default:
|
||||
case PPCCachedReg::LocationType::Discarded:
|
||||
case PPCCachedReg::LocationType::SpeculativeImmediate:
|
||||
case PPCCachedReg::LocationType::Immediate:
|
||||
break;
|
||||
case PPCCachedReg::LocationType::Bound:
|
||||
if (m_regs[i].IsInHostRegister())
|
||||
{
|
||||
if (m_regs[i].IsLocked() || m_regs[i].IsRevertable())
|
||||
return false;
|
||||
|
||||
Gen::X64Reg xr = m_regs[i].Location()->GetSimpleReg();
|
||||
Gen::X64Reg xr = m_regs[i].GetHostRegister();
|
||||
if (m_xregs[xr].IsLocked())
|
||||
return false;
|
||||
if (m_xregs[xr].Contents() != i)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -379,13 +370,7 @@ void RegCache::Discard(BitSet32 pregs)
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsRevertable(), "Register transaction is in progress for {}!",
|
||||
i);
|
||||
|
||||
if (m_regs[i].IsBound())
|
||||
{
|
||||
X64Reg xr = RX(i);
|
||||
m_xregs[xr].Unbind();
|
||||
}
|
||||
|
||||
m_regs[i].SetDiscarded();
|
||||
DiscardRegister(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,25 +386,7 @@ void RegCache::Flush(BitSet32 pregs, IgnoreDiscardedRegisters ignore_discarded_r
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsRevertable(), "Register transaction is in progress for {}!",
|
||||
i);
|
||||
|
||||
switch (m_regs[i].GetLocationType())
|
||||
{
|
||||
case PPCCachedReg::LocationType::Default:
|
||||
break;
|
||||
case PPCCachedReg::LocationType::Discarded:
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"Attempted to flush discarded PPC reg {}", i);
|
||||
break;
|
||||
case PPCCachedReg::LocationType::SpeculativeImmediate:
|
||||
// We can have a cached value without a host register through speculative constants.
|
||||
// It must be cleared when flushing, otherwise it may be out of sync with PPCSTATE,
|
||||
// if PPCSTATE is modified externally (e.g. fallback to interpreter).
|
||||
m_regs[i].SetFlushed();
|
||||
break;
|
||||
case PPCCachedReg::LocationType::Bound:
|
||||
case PPCCachedReg::LocationType::Immediate:
|
||||
StoreFromRegister(i);
|
||||
break;
|
||||
}
|
||||
StoreFromRegister(i, FlushMode::Full, ignore_discarded_registers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,9 +394,9 @@ void RegCache::Reset(BitSet32 pregs)
|
||||
{
|
||||
for (preg_t i : pregs)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsAway(),
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsInHostRegister(),
|
||||
"Attempted to reset a loaded register (did you mean to flush it?)");
|
||||
m_regs[i].SetFlushed();
|
||||
m_regs[i].SetFlushed(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +432,7 @@ void RegCache::PreloadRegisters(BitSet32 to_preload)
|
||||
{
|
||||
if (NumFreeRegisters() < 2)
|
||||
return;
|
||||
if (!R(preg).IsImm())
|
||||
if (!IsImm(preg))
|
||||
BindToRegister(preg, true, false);
|
||||
}
|
||||
}
|
||||
@@ -492,84 +459,68 @@ void RegCache::FlushX(X64Reg reg)
|
||||
}
|
||||
}
|
||||
|
||||
void RegCache::DiscardRegContentsIfCached(preg_t preg)
|
||||
void RegCache::DiscardRegister(preg_t preg)
|
||||
{
|
||||
if (m_regs[preg].IsBound())
|
||||
if (m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
X64Reg xr = m_regs[preg].Location()->GetSimpleReg();
|
||||
X64Reg xr = m_regs[preg].GetHostRegister();
|
||||
m_xregs[xr].Unbind();
|
||||
m_regs[preg].SetFlushed();
|
||||
}
|
||||
|
||||
m_regs[preg].SetDiscarded();
|
||||
}
|
||||
|
||||
void RegCache::BindToRegister(preg_t i, bool doLoad, bool makeDirty)
|
||||
{
|
||||
if (!m_regs[i].IsBound())
|
||||
if (!m_regs[i].IsInHostRegister())
|
||||
{
|
||||
X64Reg xr = GetFreeXReg();
|
||||
|
||||
ASSERT_MSG(DYNA_REC, !m_xregs[xr].IsDirty(), "Xreg {} already dirty", Common::ToUnderlying(xr));
|
||||
ASSERT_MSG(DYNA_REC, !m_xregs[xr].IsLocked(), "GetFreeXReg returned locked register");
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsRevertable(), "Invalid transaction state");
|
||||
|
||||
m_xregs[xr].SetBoundTo(i, makeDirty || m_regs[i].IsAway());
|
||||
m_xregs[xr].SetBoundTo(i);
|
||||
|
||||
if (doLoad)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsDiscarded(), "Attempted to load a discarded value");
|
||||
LoadRegister(i, xr);
|
||||
}
|
||||
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
std::ranges::none_of(
|
||||
m_regs, [xr](const auto& l) { return l.has_value() && l->IsSimpleReg(xr); },
|
||||
&PPCCachedReg::Location),
|
||||
std::ranges::none_of(m_regs,
|
||||
[xr](const auto& r) {
|
||||
return r.IsInHostRegister() && r.GetHostRegister() == xr;
|
||||
}),
|
||||
"Xreg {} already bound", Common::ToUnderlying(xr));
|
||||
|
||||
m_regs[i].SetBoundTo(xr);
|
||||
m_regs[i].SetInHostRegister(xr, makeDirty);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reg location must be simplereg; memory locations
|
||||
// and immediates are taken care of above.
|
||||
if (makeDirty)
|
||||
m_xregs[RX(i)].MakeDirty();
|
||||
m_regs[i].SetDirty();
|
||||
}
|
||||
|
||||
if (makeDirty)
|
||||
DiscardImm(i);
|
||||
|
||||
ASSERT_MSG(DYNA_REC, !m_xregs[RX(i)].IsLocked(),
|
||||
"WTF, this reg ({} -> {}) should have been flushed", i, Common::ToUnderlying(RX(i)));
|
||||
}
|
||||
|
||||
void RegCache::StoreFromRegister(preg_t i, FlushMode mode)
|
||||
void RegCache::StoreFromRegister(preg_t i, FlushMode mode,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
// When a transaction is in progress, allowing the store would overwrite the old value.
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[i].IsRevertable(), "Register transaction on {} is in progress!", i);
|
||||
|
||||
bool doStore = false;
|
||||
if (!m_regs[i].IsInDefaultLocation())
|
||||
StoreRegister(i, GetDefaultLocation(i), ignore_discarded_registers);
|
||||
|
||||
switch (m_regs[i].GetLocationType())
|
||||
{
|
||||
case PPCCachedReg::LocationType::Default:
|
||||
case PPCCachedReg::LocationType::Discarded:
|
||||
case PPCCachedReg::LocationType::SpeculativeImmediate:
|
||||
return;
|
||||
case PPCCachedReg::LocationType::Bound:
|
||||
{
|
||||
X64Reg xr = RX(i);
|
||||
doStore = m_xregs[xr].IsDirty();
|
||||
if (mode == FlushMode::Full)
|
||||
m_xregs[xr].Unbind();
|
||||
break;
|
||||
}
|
||||
case PPCCachedReg::LocationType::Immediate:
|
||||
doStore = true;
|
||||
break;
|
||||
}
|
||||
if (mode == FlushMode::Full && m_regs[i].IsInHostRegister())
|
||||
m_xregs[m_regs[i].GetHostRegister()].Unbind();
|
||||
|
||||
if (doStore)
|
||||
StoreRegister(i, GetDefaultLocation(i));
|
||||
if (mode == FlushMode::Full)
|
||||
m_regs[i].SetFlushed();
|
||||
m_regs[i].SetFlushed(mode != FlushMode::Full);
|
||||
}
|
||||
|
||||
X64Reg RegCache::GetFreeXReg()
|
||||
@@ -634,7 +585,7 @@ float RegCache::ScoreRegister(X64Reg xreg) const
|
||||
// bias a bit against dirty registers. Testing shows that a bias of 2 seems roughly
|
||||
// right: 3 causes too many extra clobbers, while 1 saves very few clobbers relative
|
||||
// to the number of extra stores it causes.
|
||||
if (m_xregs[xreg].IsDirty())
|
||||
if (!m_regs[preg].IsInDefaultLocation())
|
||||
score += 2;
|
||||
|
||||
// If the register isn't actually needed in a physical register for a later instruction,
|
||||
@@ -655,16 +606,10 @@ float RegCache::ScoreRegister(X64Reg xreg) const
|
||||
return score;
|
||||
}
|
||||
|
||||
const OpArg& RegCache::R(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !m_regs[preg].IsDiscarded(), "Discarded register - {}", preg);
|
||||
return m_regs[preg].Location().value();
|
||||
}
|
||||
|
||||
X64Reg RegCache::RX(preg_t preg) const
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsBound(), "Unbound register - {}", preg);
|
||||
return m_regs[preg].Location()->GetSimpleReg();
|
||||
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInHostRegister(), "Not in host register - {}", preg);
|
||||
return m_regs[preg].GetHostRegister();
|
||||
}
|
||||
|
||||
void RegCache::Lock(preg_t preg)
|
||||
@@ -720,29 +665,23 @@ void RegCache::Realize(preg_t preg)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_regs[preg].GetLocationType())
|
||||
if (IsImm(preg))
|
||||
{
|
||||
case PPCCachedReg::LocationType::Default:
|
||||
if (kill_mem)
|
||||
{
|
||||
do_bind();
|
||||
return;
|
||||
}
|
||||
m_constraints[preg].Realized(RCConstraint::RealizedLoc::Mem);
|
||||
return;
|
||||
case PPCCachedReg::LocationType::Discarded:
|
||||
case PPCCachedReg::LocationType::Bound:
|
||||
do_bind();
|
||||
return;
|
||||
case PPCCachedReg::LocationType::Immediate:
|
||||
case PPCCachedReg::LocationType::SpeculativeImmediate:
|
||||
if (dirty || kill_imm)
|
||||
{
|
||||
do_bind();
|
||||
return;
|
||||
}
|
||||
m_constraints[preg].Realized(RCConstraint::RealizedLoc::Imm);
|
||||
break;
|
||||
else
|
||||
m_constraints[preg].Realized(RCConstraint::RealizedLoc::Imm);
|
||||
}
|
||||
else if (!m_regs[preg].IsInHostRegister())
|
||||
{
|
||||
if (kill_mem)
|
||||
do_bind();
|
||||
else
|
||||
m_constraints[preg].Realized(RCConstraint::RealizedLoc::Mem);
|
||||
}
|
||||
else
|
||||
{
|
||||
do_bind();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,12 +157,14 @@ public:
|
||||
bool IsImm(Args... pregs) const
|
||||
{
|
||||
static_assert(sizeof...(pregs) > 0);
|
||||
return (R(pregs).IsImm() && ...);
|
||||
return (IsImm(preg_t(pregs)) && ...);
|
||||
}
|
||||
u32 Imm32(preg_t preg) const { return R(preg).Imm32(); }
|
||||
s32 SImm32(preg_t preg) const { return R(preg).SImm32(); }
|
||||
|
||||
bool IsBound(preg_t preg) const { return m_regs[preg].IsBound(); }
|
||||
virtual bool IsImm(preg_t preg) const = 0;
|
||||
virtual u32 Imm32(preg_t preg) const = 0;
|
||||
virtual s32 SImm32(preg_t preg) const = 0;
|
||||
|
||||
bool IsBound(preg_t preg) const { return m_regs[preg].IsInHostRegister(); }
|
||||
|
||||
RCOpArg Use(preg_t preg, RCMode mode);
|
||||
RCOpArg UseNoImm(preg_t preg, RCMode mode);
|
||||
@@ -191,8 +193,10 @@ protected:
|
||||
friend class RCForkGuard;
|
||||
|
||||
virtual Gen::OpArg GetDefaultLocation(preg_t preg) const = 0;
|
||||
virtual void StoreRegister(preg_t preg, const Gen::OpArg& new_loc) = 0;
|
||||
virtual void StoreRegister(preg_t preg, const Gen::OpArg& new_loc,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers) = 0;
|
||||
virtual void LoadRegister(preg_t preg, Gen::X64Reg new_loc) = 0;
|
||||
virtual void DiscardImm(preg_t preg) = 0;
|
||||
|
||||
virtual std::span<const Gen::X64Reg> GetAllocationOrder() const = 0;
|
||||
|
||||
@@ -200,16 +204,18 @@ protected:
|
||||
virtual BitSet32 CountRegsIn(preg_t preg, u32 lookahead) const = 0;
|
||||
|
||||
void FlushX(Gen::X64Reg reg);
|
||||
void DiscardRegContentsIfCached(preg_t preg);
|
||||
void DiscardRegister(preg_t preg);
|
||||
void BindToRegister(preg_t preg, bool doLoad = true, bool makeDirty = true);
|
||||
void StoreFromRegister(preg_t preg, FlushMode mode = FlushMode::Full);
|
||||
void StoreFromRegister(
|
||||
preg_t preg, FlushMode mode = FlushMode::Full,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers = IgnoreDiscardedRegisters::No);
|
||||
|
||||
Gen::X64Reg GetFreeXReg();
|
||||
|
||||
int NumFreeRegisters() const;
|
||||
float ScoreRegister(Gen::X64Reg xreg) const;
|
||||
|
||||
const Gen::OpArg& R(preg_t preg) const;
|
||||
virtual Gen::OpArg R(preg_t preg) const = 0;
|
||||
Gen::X64Reg RX(preg_t preg) const;
|
||||
|
||||
void Lock(preg_t preg);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
|
||||
#include "Core/PowerPC/JitCommon/ConstantPropagation.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
@@ -278,6 +279,9 @@ void JitArm64::FallBackToInterpreter(UGeckoInstruction inst)
|
||||
fpr.ResetRegisters(js.op->GetFregsOut());
|
||||
gpr.ResetCRRegisters(js.op->crOut);
|
||||
|
||||
// We must also update constant propagation
|
||||
m_constant_propagation.ClearGPRs(js.op->regsOut);
|
||||
|
||||
if (js.op->opinfo->flags & FL_SET_MSR)
|
||||
EmitUpdateMembase();
|
||||
|
||||
@@ -1169,6 +1173,8 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
gpr.Start(js.gpa);
|
||||
fpr.Start(js.fpa);
|
||||
|
||||
m_constant_propagation.Clear();
|
||||
|
||||
if (!js.noSpeculativeConstantsAddresses.contains(js.blockStart))
|
||||
{
|
||||
IntializeSpeculativeConstants();
|
||||
@@ -1341,9 +1347,38 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
FlushCarry();
|
||||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
}
|
||||
m_constant_propagation.Clear();
|
||||
|
||||
CompileInstruction(op);
|
||||
CompileInstruction(op);
|
||||
}
|
||||
else
|
||||
{
|
||||
const JitCommon::ConstantPropagationResult constant_propagation_result =
|
||||
m_constant_propagation.EvaluateInstruction(op.inst, opinfo->flags);
|
||||
|
||||
if (!constant_propagation_result.instruction_fully_executed)
|
||||
CompileInstruction(op);
|
||||
|
||||
m_constant_propagation.Apply(constant_propagation_result);
|
||||
|
||||
if (constant_propagation_result.gpr >= 0)
|
||||
{
|
||||
// Mark the GPR as dirty in the register cache
|
||||
gpr.SetImmediate(constant_propagation_result.gpr, constant_propagation_result.gpr_value);
|
||||
}
|
||||
|
||||
if (constant_propagation_result.instruction_fully_executed)
|
||||
{
|
||||
if (constant_propagation_result.carry)
|
||||
ComputeCarry(*constant_propagation_result.carry);
|
||||
|
||||
if (constant_propagation_result.overflow)
|
||||
GenerateConstantOverflow(*constant_propagation_result.overflow);
|
||||
|
||||
if (constant_propagation_result.compute_rc)
|
||||
ComputeRC0(constant_propagation_result.gpr_value);
|
||||
}
|
||||
}
|
||||
|
||||
js.fpr_is_store_safe = op.fprIsStoreSafeAfterInst;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "Core/PowerPC/JitArm64/JitArm64Cache.h"
|
||||
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
|
||||
#include "Core/PowerPC/JitArmCommon/BackPatch.h"
|
||||
#include "Core/PowerPC/JitCommon/ConstantPropagation.h"
|
||||
#include "Core/PowerPC/JitCommon/JitAsmCommon.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
@@ -35,6 +36,8 @@ public:
|
||||
void Init() override;
|
||||
void Shutdown() override;
|
||||
|
||||
JitCommon::ConstantPropagation& GetConstantPropagation() { return m_constant_propagation; }
|
||||
|
||||
JitBaseBlockCache* GetBlockCache() override { return &blocks; }
|
||||
bool IsInCodeSpace(const u8* ptr) const { return IsInSpace(ptr); }
|
||||
bool HandleFault(uintptr_t access_address, SContext* ctx) override;
|
||||
@@ -376,13 +379,14 @@ protected:
|
||||
|
||||
void ComputeRC0(Arm64Gen::ARM64Reg reg);
|
||||
void ComputeRC0(u32 imm);
|
||||
void GenerateConstantOverflow(bool overflow);
|
||||
void ComputeCarry(Arm64Gen::ARM64Reg reg); // reg must contain 0 or 1
|
||||
void ComputeCarry(bool carry);
|
||||
void ComputeCarry();
|
||||
void LoadCarry();
|
||||
void FlushCarry();
|
||||
|
||||
void reg_imm(u32 d, u32 a, u32 value, u32 (*do_op)(u32, u32),
|
||||
void reg_imm(u32 d, u32 a, u32 value,
|
||||
void (ARM64XEmitter::*op)(Arm64Gen::ARM64Reg, Arm64Gen::ARM64Reg, u64,
|
||||
Arm64Gen::ARM64Reg),
|
||||
bool Rc = false);
|
||||
@@ -396,6 +400,8 @@ protected:
|
||||
Arm64GPRCache gpr;
|
||||
Arm64FPRCache fpr;
|
||||
|
||||
JitCommon::ConstantPropagation m_constant_propagation;
|
||||
|
||||
JitArm64BlockCache blocks{*this};
|
||||
|
||||
Arm64Gen::ARM64FloatEmitter m_float_emit;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,8 +114,7 @@ void Arm64RegCache::FlushMostStaleRegister()
|
||||
const auto& reg = m_guest_registers[i];
|
||||
const u32 last_used = reg.GetLastUsed();
|
||||
|
||||
if (last_used > most_stale_amount && reg.GetType() != RegType::NotLoaded &&
|
||||
reg.GetType() != RegType::Discarded && reg.GetType() != RegType::Immediate)
|
||||
if (last_used > most_stale_amount && reg.IsInHostRegister())
|
||||
{
|
||||
most_stale_preg = i;
|
||||
most_stale_amount = last_used;
|
||||
@@ -137,12 +136,6 @@ void Arm64RegCache::DiscardRegister(size_t preg)
|
||||
UnlockRegister(host_reg);
|
||||
}
|
||||
|
||||
// GPR Cache
|
||||
constexpr size_t GUEST_GPR_COUNT = 32;
|
||||
constexpr size_t GUEST_CR_COUNT = 8;
|
||||
constexpr size_t GUEST_GPR_OFFSET = 0;
|
||||
constexpr size_t GUEST_CR_OFFSET = GUEST_GPR_COUNT;
|
||||
|
||||
Arm64GPRCache::Arm64GPRCache() : Arm64RegCache(GUEST_GPR_COUNT + GUEST_CR_COUNT)
|
||||
{
|
||||
}
|
||||
@@ -151,6 +144,19 @@ void Arm64GPRCache::Start(PPCAnalyst::BlockRegStats& stats)
|
||||
{
|
||||
}
|
||||
|
||||
// Returns if a register is set as an immediate. Only valid for guest GPRs.
|
||||
bool Arm64GPRCache::IsImm(size_t preg) const
|
||||
{
|
||||
return m_jit->GetConstantPropagation().HasGPR(preg);
|
||||
}
|
||||
|
||||
// Gets the immediate that a register is set to. Only valid for guest GPRs.
|
||||
u32 Arm64GPRCache::GetImm(size_t preg) const
|
||||
{
|
||||
ASSERT(m_jit->GetConstantPropagation().HasGPR(preg));
|
||||
return m_jit->GetConstantPropagation().GetGPR(preg);
|
||||
}
|
||||
|
||||
bool Arm64GPRCache::IsCallerSaved(ARM64Reg reg) const
|
||||
{
|
||||
return ARM64XEmitter::CALLER_SAVED_GPRS[DecodeReg(reg)];
|
||||
@@ -192,11 +198,12 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_reg
|
||||
GuestRegInfo guest_reg = GetGuestByIndex(index);
|
||||
OpArg& reg = guest_reg.reg;
|
||||
size_t bitsize = guest_reg.bitsize;
|
||||
const bool is_gpr = index >= GUEST_GPR_OFFSET && index < GUEST_GPR_OFFSET + GUEST_GPR_COUNT;
|
||||
|
||||
if (reg.GetType() == RegType::Register)
|
||||
if (reg.IsInHostRegister())
|
||||
{
|
||||
ARM64Reg host_reg = reg.GetReg();
|
||||
if (reg.IsDirty())
|
||||
if (!reg.IsInPPCState())
|
||||
m_emit->STR(IndexType::Unsigned, host_reg, PPC_REG, u32(guest_reg.ppc_offset));
|
||||
|
||||
if (mode == FlushMode::All)
|
||||
@@ -205,11 +212,12 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_reg
|
||||
reg.Flush();
|
||||
}
|
||||
}
|
||||
else if (reg.GetType() == RegType::Immediate)
|
||||
else if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
|
||||
{
|
||||
if (reg.IsDirty())
|
||||
if (!reg.IsInPPCState())
|
||||
{
|
||||
if (!reg.GetImm())
|
||||
const u32 imm = GetImm(index - GUEST_GPR_OFFSET);
|
||||
if (imm == 0)
|
||||
{
|
||||
m_emit->STR(IndexType::Unsigned, bitsize == 64 ? ARM64Reg::ZR : ARM64Reg::WZR, PPC_REG,
|
||||
u32(guest_reg.ppc_offset));
|
||||
@@ -231,7 +239,7 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_reg
|
||||
|
||||
const ARM64Reg encoded_tmp_reg = bitsize != 64 ? tmp_reg : EncodeRegTo64(tmp_reg);
|
||||
|
||||
m_emit->MOVI2R(encoded_tmp_reg, reg.GetImm());
|
||||
m_emit->MOVI2R(encoded_tmp_reg, imm);
|
||||
m_emit->STR(IndexType::Unsigned, encoded_tmp_reg, PPC_REG, u32(guest_reg.ppc_offset));
|
||||
|
||||
if (allocated_tmp_reg)
|
||||
@@ -250,10 +258,10 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
|
||||
for (auto iter = regs.begin(); iter != regs.end(); ++iter)
|
||||
{
|
||||
const int i = *iter;
|
||||
|
||||
OpArg& reg = m_guest_registers[GUEST_GPR_OFFSET + i];
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No ||
|
||||
m_guest_registers[GUEST_GPR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No || reg.IsInPPCState() ||
|
||||
reg.IsInHostRegister() || IsImm(i),
|
||||
"Attempted to flush discarded register");
|
||||
|
||||
if (i + 1 < int(GUEST_GPR_COUNT) && regs[i + 1])
|
||||
@@ -261,27 +269,27 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
|
||||
// We've got two guest registers in a row to store
|
||||
OpArg& reg1 = m_guest_registers[GUEST_GPR_OFFSET + i];
|
||||
OpArg& reg2 = m_guest_registers[GUEST_GPR_OFFSET + i + 1];
|
||||
const bool reg1_imm = reg1.GetType() == RegType::Immediate;
|
||||
const bool reg2_imm = reg2.GetType() == RegType::Immediate;
|
||||
const bool reg1_zero = reg1_imm && reg1.GetImm() == 0;
|
||||
const bool reg2_zero = reg2_imm && reg2.GetImm() == 0;
|
||||
const bool reg1_imm = IsImm(i);
|
||||
const bool reg2_imm = IsImm(i + 1);
|
||||
const bool reg1_zero = reg1_imm && GetImm(i) == 0;
|
||||
const bool reg2_zero = reg2_imm && GetImm(i + 1) == 0;
|
||||
const bool flush_all = mode == FlushMode::All;
|
||||
if (reg1.IsDirty() && reg2.IsDirty() &&
|
||||
(reg1.GetType() == RegType::Register || (reg1_imm && (reg1_zero || flush_all))) &&
|
||||
(reg2.GetType() == RegType::Register || (reg2_imm && (reg2_zero || flush_all))))
|
||||
if (!reg1.IsInPPCState() && !reg2.IsInPPCState() &&
|
||||
(reg1.IsInHostRegister() || (reg1_imm && (reg1_zero || flush_all))) &&
|
||||
(reg2.IsInHostRegister() || (reg2_imm && (reg2_zero || flush_all))))
|
||||
{
|
||||
const size_t ppc_offset = GetGuestByIndex(i).ppc_offset;
|
||||
if (ppc_offset <= 252)
|
||||
{
|
||||
ARM64Reg RX1 = reg1_zero ? ARM64Reg::WZR : R(GetGuestByIndex(i));
|
||||
ARM64Reg RX2 = reg2_zero ? ARM64Reg::WZR : R(GetGuestByIndex(i + 1));
|
||||
ARM64Reg RX1 = reg1_zero ? ARM64Reg::WZR : BindForRead(i);
|
||||
ARM64Reg RX2 = reg2_zero ? ARM64Reg::WZR : BindForRead(i + 1);
|
||||
m_emit->STP(IndexType::Signed, RX1, RX2, PPC_REG, u32(ppc_offset));
|
||||
if (flush_all)
|
||||
{
|
||||
if (!reg1_zero)
|
||||
UnlockRegister(EncodeRegTo32(RX1));
|
||||
if (!reg2_zero)
|
||||
UnlockRegister(EncodeRegTo32(RX2));
|
||||
if (reg1.IsInHostRegister())
|
||||
UnlockRegister(reg1.GetReg());
|
||||
if (reg2.IsInHostRegister())
|
||||
UnlockRegister(reg2.GetReg());
|
||||
reg1.Flush();
|
||||
reg2.Flush();
|
||||
}
|
||||
@@ -300,9 +308,10 @@ void Arm64GPRCache::FlushCRRegisters(BitSet8 regs, FlushMode mode, ARM64Reg tmp_
|
||||
{
|
||||
for (int i : regs)
|
||||
{
|
||||
OpArg& reg = m_guest_registers[GUEST_CR_OFFSET + i];
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No ||
|
||||
m_guest_registers[GUEST_CR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No || reg.IsInPPCState() ||
|
||||
reg.IsInHostRegister(),
|
||||
"Attempted to flush discarded register");
|
||||
|
||||
FlushRegister(GUEST_CR_OFFSET + i, mode, tmp_reg);
|
||||
@@ -335,94 +344,89 @@ void Arm64GPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg,
|
||||
FlushCRRegisters(BitSet8(0xFF), mode, tmp_reg, ignore_discarded_registers);
|
||||
}
|
||||
|
||||
ARM64Reg Arm64GPRCache::R(const GuestRegInfo& guest_reg)
|
||||
ARM64Reg Arm64GPRCache::BindForRead(size_t index)
|
||||
{
|
||||
GuestRegInfo guest_reg = GetGuestByIndex(index);
|
||||
OpArg& reg = guest_reg.reg;
|
||||
size_t bitsize = guest_reg.bitsize;
|
||||
const bool is_gpr = index >= GUEST_GPR_OFFSET && index < GUEST_GPR_OFFSET + GUEST_GPR_COUNT;
|
||||
|
||||
IncrementAllUsed();
|
||||
reg.ResetLastUsed();
|
||||
|
||||
switch (reg.GetType())
|
||||
if (reg.IsInHostRegister())
|
||||
{
|
||||
case RegType::Register: // already in a reg
|
||||
return reg.GetReg();
|
||||
case RegType::Immediate: // Is an immediate
|
||||
}
|
||||
else if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
|
||||
{
|
||||
ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
m_emit->MOVI2R(host_reg, reg.GetImm());
|
||||
m_emit->MOVI2R(host_reg, GetImm(index - GUEST_GPR_OFFSET));
|
||||
reg.Load(host_reg);
|
||||
return host_reg;
|
||||
}
|
||||
break;
|
||||
case RegType::Discarded:
|
||||
ASSERT_MSG(DYNA_REC, false, "Attempted to read discarded register");
|
||||
break;
|
||||
case RegType::NotLoaded: // Register isn't loaded at /all/
|
||||
else // Register isn't loaded at /all/
|
||||
{
|
||||
// This is a bit annoying. We try to keep these preloaded as much as possible
|
||||
// This can also happen on cases where PPCAnalyst isn't feeing us proper register usage
|
||||
// statistics
|
||||
ASSERT_MSG(DYNA_REC, reg.IsInPPCState(), "Attempted to read discarded register");
|
||||
ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
reg.Load(host_reg);
|
||||
reg.SetDirty(false);
|
||||
m_emit->LDR(IndexType::Unsigned, host_reg, PPC_REG, u32(guest_reg.ppc_offset));
|
||||
return host_reg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG_FMT(DYNA_REC, "Invalid OpArg Type!");
|
||||
break;
|
||||
}
|
||||
// We've got an issue if we end up here
|
||||
return ARM64Reg::INVALID_REG;
|
||||
}
|
||||
|
||||
void Arm64GPRCache::SetImmediate(const GuestRegInfo& guest_reg, u32 imm, bool dirty)
|
||||
void Arm64GPRCache::SetImmediateInternal(size_t index, u32 imm, bool dirty)
|
||||
{
|
||||
GuestRegInfo guest_reg = GetGuestByIndex(index);
|
||||
OpArg& reg = guest_reg.reg;
|
||||
if (reg.GetType() == RegType::Register)
|
||||
if (reg.IsInHostRegister())
|
||||
UnlockRegister(EncodeRegTo32(reg.GetReg()));
|
||||
reg.LoadToImm(imm);
|
||||
reg.Discard();
|
||||
reg.SetDirty(dirty);
|
||||
m_jit->GetConstantPropagation().SetGPR(index - GUEST_GPR_OFFSET, imm);
|
||||
}
|
||||
|
||||
void Arm64GPRCache::BindToRegister(const GuestRegInfo& guest_reg, bool will_read, bool will_write)
|
||||
void Arm64GPRCache::BindForWrite(size_t index, bool will_read, bool will_write)
|
||||
{
|
||||
GuestRegInfo guest_reg = GetGuestByIndex(index);
|
||||
OpArg& reg = guest_reg.reg;
|
||||
const size_t bitsize = guest_reg.bitsize;
|
||||
const bool is_gpr = index >= GUEST_GPR_OFFSET && index < GUEST_GPR_OFFSET + GUEST_GPR_COUNT;
|
||||
|
||||
reg.ResetLastUsed();
|
||||
|
||||
const RegType reg_type = reg.GetType();
|
||||
if (reg_type == RegType::NotLoaded || reg_type == RegType::Discarded)
|
||||
if (!reg.IsInHostRegister())
|
||||
{
|
||||
const ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
reg.Load(host_reg);
|
||||
reg.SetDirty(will_write);
|
||||
if (will_read)
|
||||
if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, reg_type != RegType::Discarded, "Attempted to load a discarded value");
|
||||
m_emit->LDR(IndexType::Unsigned, host_reg, PPC_REG, u32(guest_reg.ppc_offset));
|
||||
const ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
if (will_read || !will_write)
|
||||
{
|
||||
// TODO: Emitting this instruction when (!will_read && !will_write) would be unnecessary if
|
||||
// we had some way to indicate to Flush that the immediate value should be written to
|
||||
// ppcState even though there is a host register allocated
|
||||
m_emit->MOVI2R(host_reg, GetImm(index - GUEST_GPR_OFFSET));
|
||||
}
|
||||
reg.Load(host_reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !will_read || reg.IsInPPCState(), "Attempted to load a discarded value");
|
||||
const ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
reg.Load(host_reg);
|
||||
reg.SetDirty(will_write);
|
||||
if (will_read)
|
||||
m_emit->LDR(IndexType::Unsigned, host_reg, PPC_REG, u32(guest_reg.ppc_offset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (reg_type == RegType::Immediate)
|
||||
{
|
||||
const ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
|
||||
if (will_read || !will_write)
|
||||
{
|
||||
// TODO: Emitting this instruction when (!will_read && !will_write) would be unnecessary if we
|
||||
// had some way to indicate to Flush that the immediate value should be written to ppcState
|
||||
// even though there is a host register allocated
|
||||
m_emit->MOVI2R(host_reg, reg.GetImm());
|
||||
}
|
||||
reg.Load(host_reg);
|
||||
if (will_write)
|
||||
reg.SetDirty(true);
|
||||
}
|
||||
else if (will_write)
|
||||
|
||||
if (will_write)
|
||||
{
|
||||
reg.SetDirty(true);
|
||||
if (is_gpr)
|
||||
m_jit->GetConstantPropagation().ClearGPR(index - GUEST_GPR_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +488,7 @@ BitSet32 Arm64GPRCache::GetDirtyGPRs() const
|
||||
for (size_t i = 0; i < GUEST_GPR_COUNT; ++i)
|
||||
{
|
||||
const OpArg& arg = m_guest_registers[GUEST_GPR_OFFSET + i];
|
||||
registers[i] = arg.GetType() != RegType::NotLoaded && arg.IsDirty();
|
||||
registers[i] = !arg.IsInPPCState();
|
||||
}
|
||||
return registers;
|
||||
}
|
||||
@@ -494,7 +498,7 @@ void Arm64GPRCache::FlushByHost(ARM64Reg host_reg, ARM64Reg tmp_reg)
|
||||
for (size_t i = 0; i < m_guest_registers.size(); ++i)
|
||||
{
|
||||
const OpArg& reg = m_guest_registers[i];
|
||||
if (reg.GetType() == RegType::Register && DecodeReg(reg.GetReg()) == DecodeReg(host_reg))
|
||||
if (reg.IsInHostRegister() && DecodeReg(reg.GetReg()) == DecodeReg(host_reg))
|
||||
{
|
||||
FlushRegister(i, FlushMode::All, tmp_reg);
|
||||
return;
|
||||
@@ -514,17 +518,17 @@ void Arm64FPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg,
|
||||
{
|
||||
for (size_t i = 0; i < m_guest_registers.size(); ++i)
|
||||
{
|
||||
const RegType reg_type = m_guest_registers[i].GetType();
|
||||
|
||||
if (reg_type == RegType::Discarded)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"Attempted to flush discarded register");
|
||||
}
|
||||
else if (reg_type != RegType::NotLoaded && reg_type != RegType::Immediate)
|
||||
if (m_guest_registers[i].IsInHostRegister())
|
||||
{
|
||||
FlushRegister(i, mode, tmp_reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No ||
|
||||
m_guest_registers[i].IsInPPCState(),
|
||||
"Attempted to flush discarded register");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,9 +537,32 @@ ARM64Reg Arm64FPRCache::R(size_t preg, RegType type)
|
||||
OpArg& reg = m_guest_registers[preg];
|
||||
IncrementAllUsed();
|
||||
reg.ResetLastUsed();
|
||||
|
||||
if (!reg.IsInHostRegister())
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, reg.IsInPPCState(), "Attempted to read discarded register");
|
||||
|
||||
ARM64Reg host_reg = GetReg();
|
||||
u32 load_size;
|
||||
if (type == RegType::Register)
|
||||
{
|
||||
load_size = 128;
|
||||
reg.Load(host_reg, RegType::Register);
|
||||
}
|
||||
else
|
||||
{
|
||||
load_size = 64;
|
||||
reg.Load(host_reg, RegType::LowerPair);
|
||||
}
|
||||
reg.SetDirty(false);
|
||||
m_float_emit->LDR(load_size, IndexType::Unsigned, host_reg, PPC_REG,
|
||||
static_cast<s32>(PPCSTATE_OFF_PS0(preg)));
|
||||
return host_reg;
|
||||
}
|
||||
|
||||
ARM64Reg host_reg = reg.GetReg();
|
||||
|
||||
switch (reg.GetType())
|
||||
switch (reg.GetFPRType())
|
||||
{
|
||||
case RegType::Single:
|
||||
{
|
||||
@@ -618,28 +645,6 @@ ARM64Reg Arm64FPRCache::R(size_t preg, RegType type)
|
||||
}
|
||||
return host_reg;
|
||||
}
|
||||
case RegType::Discarded:
|
||||
ASSERT_MSG(DYNA_REC, false, "Attempted to read discarded register");
|
||||
break;
|
||||
case RegType::NotLoaded: // Register isn't loaded at /all/
|
||||
{
|
||||
host_reg = GetReg();
|
||||
u32 load_size;
|
||||
if (type == RegType::Register)
|
||||
{
|
||||
load_size = 128;
|
||||
reg.Load(host_reg, RegType::Register);
|
||||
}
|
||||
else
|
||||
{
|
||||
load_size = 64;
|
||||
reg.Load(host_reg, RegType::LowerPair);
|
||||
}
|
||||
reg.SetDirty(false);
|
||||
m_float_emit->LDR(load_size, IndexType::Unsigned, host_reg, PPC_REG,
|
||||
static_cast<s32>(PPCSTATE_OFF_PS0(preg)));
|
||||
return host_reg;
|
||||
}
|
||||
default:
|
||||
DEBUG_ASSERT_MSG(DYNA_REC, false, "Invalid OpArg Type!");
|
||||
break;
|
||||
@@ -655,16 +660,17 @@ ARM64Reg Arm64FPRCache::RW(size_t preg, RegType type, bool set_dirty)
|
||||
IncrementAllUsed();
|
||||
reg.ResetLastUsed();
|
||||
|
||||
// Only the lower value will be overwritten, so we must be extra careful to store PSR1 if dirty.
|
||||
if (reg.IsDirty() && (type == RegType::LowerPair || type == RegType::LowerPairSingle))
|
||||
// If PS1 is dirty, but the caller wants a RegType with only PS0, we must write PS1 to m_ppc_state
|
||||
// now so the contents of PS1 aren't lost.
|
||||
if (!reg.IsInPPCState() && (type == RegType::LowerPair || type == RegType::LowerPairSingle))
|
||||
{
|
||||
// We must *not* change host_reg as this register might still be in use. So it's fine to
|
||||
// store this register, but it's *not* fine to convert it to double. So for double conversion,
|
||||
// a temporary register needs to be used.
|
||||
// We must *not* modify host_reg, as the current guest instruction might want to read its old
|
||||
// value before overwriting it. So it's fine to store this register, but it's *not* fine to
|
||||
// convert it to double in place. For double conversion, a temporary register needs to be used.
|
||||
ARM64Reg host_reg = reg.GetReg();
|
||||
ARM64Reg flush_reg = host_reg;
|
||||
|
||||
switch (reg.GetType())
|
||||
switch (reg.GetFPRType())
|
||||
{
|
||||
case RegType::Single:
|
||||
// For a store-safe register, conversion is just one instruction regardless of whether
|
||||
@@ -706,8 +712,8 @@ ARM64Reg Arm64FPRCache::RW(size_t preg, RegType type, bool set_dirty)
|
||||
// Store PSR1 (which is equal to PSR0) in memory.
|
||||
m_float_emit->STR(64, IndexType::Unsigned, flush_reg, PPC_REG,
|
||||
static_cast<s32>(PPCSTATE_OFF_PS1(preg)));
|
||||
reg.Load(host_reg, reg.GetType() == RegType::DuplicatedSingle ? RegType::LowerPairSingle :
|
||||
RegType::LowerPair);
|
||||
reg.Load(host_reg, reg.GetFPRType() == RegType::DuplicatedSingle ? RegType::LowerPairSingle :
|
||||
RegType::LowerPair);
|
||||
break;
|
||||
default:
|
||||
// All other types doesn't store anything in PSR1.
|
||||
@@ -718,7 +724,7 @@ ARM64Reg Arm64FPRCache::RW(size_t preg, RegType type, bool set_dirty)
|
||||
Unlock(flush_reg);
|
||||
}
|
||||
|
||||
if (reg.GetType() == RegType::NotLoaded || reg.GetType() == RegType::Discarded)
|
||||
if (!reg.IsInHostRegister())
|
||||
{
|
||||
// If not loaded at all, just alloc a new one.
|
||||
reg.Load(GetReg(), type);
|
||||
@@ -782,10 +788,8 @@ void Arm64FPRCache::FlushByHost(ARM64Reg host_reg, ARM64Reg tmp_reg)
|
||||
for (size_t i = 0; i < m_guest_registers.size(); ++i)
|
||||
{
|
||||
const OpArg& reg = m_guest_registers[i];
|
||||
const RegType reg_type = reg.GetType();
|
||||
|
||||
if (reg_type != RegType::NotLoaded && reg_type != RegType::Discarded &&
|
||||
reg_type != RegType::Immediate && reg.GetReg() == host_reg)
|
||||
if (reg.IsInHostRegister() && reg.GetReg() == host_reg)
|
||||
{
|
||||
FlushRegister(i, FlushMode::All, tmp_reg);
|
||||
return;
|
||||
@@ -802,8 +806,8 @@ bool Arm64FPRCache::IsTopHalfUsed(ARM64Reg reg) const
|
||||
{
|
||||
for (const OpArg& r : m_guest_registers)
|
||||
{
|
||||
if (r.GetReg() != ARM64Reg::INVALID_REG && DecodeReg(r.GetReg()) == DecodeReg(reg))
|
||||
return r.GetType() == RegType::Register;
|
||||
if (r.IsInHostRegister() && DecodeReg(r.GetReg()) == DecodeReg(reg))
|
||||
return r.GetFPRType() == RegType::Register;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -813,8 +817,8 @@ void Arm64FPRCache::FlushRegister(size_t preg, FlushMode mode, ARM64Reg tmp_reg)
|
||||
{
|
||||
OpArg& reg = m_guest_registers[preg];
|
||||
const ARM64Reg host_reg = reg.GetReg();
|
||||
const bool dirty = reg.IsDirty();
|
||||
RegType type = reg.GetType();
|
||||
const bool dirty = !reg.IsInPPCState();
|
||||
RegType type = reg.GetFPRType();
|
||||
|
||||
bool allocated_tmp_reg = false;
|
||||
if (tmp_reg != ARM64Reg::INVALID_REG)
|
||||
@@ -921,7 +925,7 @@ BitSet32 Arm64FPRCache::GetCallerSavedUsed() const
|
||||
|
||||
bool Arm64FPRCache::IsSingle(size_t preg, bool lower_only) const
|
||||
{
|
||||
const RegType type = m_guest_registers[preg].GetType();
|
||||
const RegType type = m_guest_registers[preg].GetFPRType();
|
||||
return type == RegType::Single || type == RegType::DuplicatedSingle ||
|
||||
(lower_only && type == RegType::LowerPairSingle);
|
||||
}
|
||||
@@ -929,18 +933,18 @@ bool Arm64FPRCache::IsSingle(size_t preg, bool lower_only) const
|
||||
void Arm64FPRCache::FixSinglePrecision(size_t preg)
|
||||
{
|
||||
OpArg& reg = m_guest_registers[preg];
|
||||
if (!reg.IsInHostRegister())
|
||||
return;
|
||||
|
||||
ARM64Reg host_reg = reg.GetReg();
|
||||
switch (reg.GetType())
|
||||
if (reg.GetFPRType() == RegType::Duplicated) // only PS0 needs to be converted
|
||||
{
|
||||
case RegType::Duplicated: // only PS0 needs to be converted
|
||||
m_float_emit->FCVT(32, 64, EncodeRegToDouble(host_reg), EncodeRegToDouble(host_reg));
|
||||
reg.Load(host_reg, RegType::DuplicatedSingle);
|
||||
break;
|
||||
case RegType::Register: // PS0 and PS1 need to be converted
|
||||
}
|
||||
else if (reg.GetFPRType() == RegType::Register) // PS0 and PS1 need to be converted
|
||||
{
|
||||
m_float_emit->FCVTN(32, EncodeRegToDouble(host_reg), EncodeRegToDouble(host_reg));
|
||||
reg.Load(host_reg, RegType::Single);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +60,12 @@ static_assert(PPCSTATE_OFF(xer_so_ov) < 4096, "STRB can't store xer_so_ov!");
|
||||
|
||||
enum class RegType
|
||||
{
|
||||
NotLoaded,
|
||||
Discarded, // Reg is not loaded because we know it won't be read before the next write
|
||||
Register, // Reg type is register
|
||||
Immediate, // Reg is really a IMM
|
||||
LowerPair, // Only the lower pair of a paired register
|
||||
Duplicated, // The lower reg is the same as the upper one (physical upper doesn't actually have
|
||||
// the duplicated value)
|
||||
Single, // Both registers are loaded as single
|
||||
LowerPairSingle, // Only the lower pair of a paired register, as single
|
||||
DuplicatedSingle, // The lower one contains both registers, as single
|
||||
Register, // PS0 and PS1, each 64-bit
|
||||
LowerPair, // PS0 only, 64-bit
|
||||
Duplicated, // PS0 and PS1 are identical, host register only stores one lane (64-bit)
|
||||
Single, // PS0 and PS1, each 32-bit
|
||||
LowerPairSingle, // PS0 only, 32-bit
|
||||
DuplicatedSingle, // PS0 and PS1 are identical, host register only stores one lane (32-bit)
|
||||
};
|
||||
|
||||
enum class FlushMode : bool
|
||||
@@ -92,26 +88,21 @@ class OpArg
|
||||
public:
|
||||
OpArg() = default;
|
||||
|
||||
RegType GetType() const { return m_type; }
|
||||
RegType GetFPRType() const { return m_fpr_type; }
|
||||
Arm64Gen::ARM64Reg GetReg() const { return m_reg; }
|
||||
u32 GetImm() const { return m_value; }
|
||||
void Load(Arm64Gen::ARM64Reg reg, RegType type = RegType::Register)
|
||||
void Load(Arm64Gen::ARM64Reg reg, RegType format = RegType::Register)
|
||||
{
|
||||
m_type = type;
|
||||
m_reg = reg;
|
||||
}
|
||||
void LoadToImm(u32 imm)
|
||||
{
|
||||
m_type = RegType::Immediate;
|
||||
m_value = imm;
|
||||
|
||||
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
|
||||
m_fpr_type = format;
|
||||
m_in_host_register = true;
|
||||
}
|
||||
void Discard()
|
||||
{
|
||||
// Invalidate any previous information
|
||||
m_type = RegType::Discarded;
|
||||
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
|
||||
m_fpr_type = RegType::Register;
|
||||
m_in_ppc_state = false;
|
||||
m_in_host_register = false;
|
||||
|
||||
// Arbitrarily large value that won't roll over on a lot of increments
|
||||
m_last_used = 0xFFFF;
|
||||
@@ -119,8 +110,10 @@ public:
|
||||
void Flush()
|
||||
{
|
||||
// Invalidate any previous information
|
||||
m_type = RegType::NotLoaded;
|
||||
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
|
||||
m_fpr_type = RegType::Register;
|
||||
m_in_ppc_state = true;
|
||||
m_in_host_register = false;
|
||||
|
||||
// Arbitrarily large value that won't roll over on a lot of increments
|
||||
m_last_used = 0xFFFF;
|
||||
@@ -129,20 +122,18 @@ public:
|
||||
u32 GetLastUsed() const { return m_last_used; }
|
||||
void ResetLastUsed() { m_last_used = 0; }
|
||||
void IncrementLastUsed() { ++m_last_used; }
|
||||
void SetDirty(bool dirty) { m_dirty = dirty; }
|
||||
bool IsDirty() const { return m_dirty; }
|
||||
void SetDirty(bool dirty) { m_in_ppc_state = !dirty; }
|
||||
bool IsInPPCState() const { return m_in_ppc_state; }
|
||||
bool IsInHostRegister() const { return m_in_host_register; }
|
||||
|
||||
private:
|
||||
// For REG_REG
|
||||
RegType m_type = RegType::NotLoaded; // store type
|
||||
Arm64Gen::ARM64Reg m_reg = Arm64Gen::ARM64Reg::INVALID_REG; // host register we are in
|
||||
|
||||
// For REG_IMM
|
||||
u32 m_value = 0; // IMM value
|
||||
RegType m_fpr_type = RegType::Register; // for FPRs only
|
||||
|
||||
u32 m_last_used = 0;
|
||||
|
||||
bool m_dirty = false;
|
||||
bool m_in_ppc_state = true;
|
||||
bool m_in_host_register = false;
|
||||
};
|
||||
|
||||
class HostReg
|
||||
@@ -328,22 +319,22 @@ public:
|
||||
|
||||
// Returns a guest GPR inside of a host register.
|
||||
// Will dump an immediate to the host register as well.
|
||||
Arm64Gen::ARM64Reg R(size_t preg) { return R(GetGuestGPR(preg)); }
|
||||
Arm64Gen::ARM64Reg R(size_t preg) { return BindForRead(GUEST_GPR_OFFSET + preg); }
|
||||
|
||||
// Returns a guest CR inside of a host register.
|
||||
Arm64Gen::ARM64Reg CR(size_t preg) { return R(GetGuestCR(preg)); }
|
||||
Arm64Gen::ARM64Reg CR(size_t preg) { return BindForRead(GUEST_CR_OFFSET + preg); }
|
||||
|
||||
// Set a register to an immediate. Only valid for guest GPRs.
|
||||
void SetImmediate(size_t preg, u32 imm, bool dirty = true)
|
||||
{
|
||||
SetImmediate(GetGuestGPR(preg), imm, dirty);
|
||||
SetImmediateInternal(GUEST_GPR_OFFSET + preg, imm, dirty);
|
||||
}
|
||||
|
||||
// Returns if a register is set as an immediate. Only valid for guest GPRs.
|
||||
bool IsImm(size_t preg) const { return GetGuestGPROpArg(preg).GetType() == RegType::Immediate; }
|
||||
// Returns whether a register is set as an immediate. Only valid for guest GPRs.
|
||||
bool IsImm(size_t preg) const;
|
||||
|
||||
// Gets the immediate that a register is set to. Only valid for guest GPRs.
|
||||
u32 GetImm(size_t preg) const { return GetGuestGPROpArg(preg).GetImm(); }
|
||||
u32 GetImm(size_t preg) const;
|
||||
|
||||
bool IsImm(size_t preg, u32 imm) const { return IsImm(preg) && GetImm(preg) == imm; }
|
||||
|
||||
@@ -374,14 +365,14 @@ public:
|
||||
// flushed. Just remember to call this function again with will_write = true after the Flush call.
|
||||
void BindToRegister(size_t preg, bool will_read, bool will_write = true)
|
||||
{
|
||||
BindToRegister(GetGuestGPR(preg), will_read, will_write);
|
||||
BindForWrite(GUEST_GPR_OFFSET + preg, will_read, will_write);
|
||||
}
|
||||
|
||||
// Binds a guest CR to a host register, optionally loading its value.
|
||||
// The description of BindToRegister above applies to this function as well.
|
||||
void BindCRToRegister(size_t preg, bool will_read, bool will_write = true)
|
||||
{
|
||||
BindToRegister(GetGuestCR(preg), will_read, will_write);
|
||||
BindForWrite(GUEST_CR_OFFSET + preg, will_read, will_write);
|
||||
}
|
||||
|
||||
BitSet32 GetCallerSavedUsed() const override;
|
||||
@@ -428,14 +419,19 @@ private:
|
||||
GuestRegInfo GetGuestCR(size_t preg);
|
||||
GuestRegInfo GetGuestByIndex(size_t index);
|
||||
|
||||
Arm64Gen::ARM64Reg R(const GuestRegInfo& guest_reg);
|
||||
void SetImmediate(const GuestRegInfo& guest_reg, u32 imm, bool dirty);
|
||||
void BindToRegister(const GuestRegInfo& guest_reg, bool will_read, bool will_write = true);
|
||||
Arm64Gen::ARM64Reg BindForRead(size_t index);
|
||||
void SetImmediateInternal(size_t index, u32 imm, bool dirty);
|
||||
void BindForWrite(size_t index, bool will_read, bool will_write = true);
|
||||
|
||||
void FlushRegisters(BitSet32 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers);
|
||||
void FlushCRRegisters(BitSet8 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers);
|
||||
|
||||
static constexpr size_t GUEST_GPR_COUNT = 32;
|
||||
static constexpr size_t GUEST_CR_COUNT = 8;
|
||||
static constexpr size_t GUEST_GPR_OFFSET = 0;
|
||||
static constexpr size_t GUEST_CR_OFFSET = GUEST_GPR_COUNT;
|
||||
};
|
||||
|
||||
class Arm64FPRCache : public Arm64RegCache
|
||||
@@ -451,9 +447,9 @@ public:
|
||||
|
||||
// Returns a guest register inside of a host register
|
||||
// Will dump an immediate to the host register as well
|
||||
Arm64Gen::ARM64Reg R(size_t preg, RegType type);
|
||||
Arm64Gen::ARM64Reg R(size_t preg, RegType format);
|
||||
|
||||
Arm64Gen::ARM64Reg RW(size_t preg, RegType type, bool set_dirty = true);
|
||||
Arm64Gen::ARM64Reg RW(size_t preg, RegType format, bool set_dirty = true);
|
||||
|
||||
BitSet32 GetCallerSavedUsed() const override;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace JitCommon
|
||||
{
|
||||
struct ConstantPropagationResult final
|
||||
{
|
||||
constexpr ConstantPropagationResult() = default;
|
||||
|
||||
constexpr ConstantPropagationResult(s8 gpr_, u32 gpr_value_, bool compute_rc_ = false)
|
||||
: gpr_value(gpr_value_), gpr(gpr_), instruction_fully_executed(true), compute_rc(compute_rc_)
|
||||
{
|
||||
}
|
||||
|
||||
// If gpr is non-negative, this is the value the instruction writes to that GPR.
|
||||
u32 gpr_value = 0;
|
||||
|
||||
// If the instruction couldn't be evaluated or doesn't output to a GPR, this is -1.
|
||||
// Otherwise, this is the GPR that the instruction writes to.
|
||||
s8 gpr = -1;
|
||||
|
||||
// Whether the instruction was able to be fully evaluated with no side effects unaccounted for,
|
||||
// or in other words, whether the JIT can skip emitting code for this instruction.
|
||||
bool instruction_fully_executed = false;
|
||||
|
||||
// If true, CR0 needs to be set based on gpr_value.
|
||||
bool compute_rc = false;
|
||||
|
||||
// If not std::nullopt, the instruction writes this to the carry flag.
|
||||
std::optional<bool> carry = std::nullopt;
|
||||
|
||||
// If not std::nullopt, the instruction writes this to the overflow flag.
|
||||
std::optional<bool> overflow = std::nullopt;
|
||||
};
|
||||
|
||||
class ConstantPropagation final
|
||||
{
|
||||
public:
|
||||
ConstantPropagationResult EvaluateInstruction(UGeckoInstruction inst, u64 flags) const;
|
||||
|
||||
void Apply(ConstantPropagationResult result);
|
||||
|
||||
template <typename... Args>
|
||||
bool HasGPR(Args... gprs) const
|
||||
{
|
||||
return HasGPRs(BitSet32{static_cast<int>(gprs)...});
|
||||
}
|
||||
|
||||
bool HasGPRs(BitSet32 gprs) const { return (m_gpr_values_known & gprs) == gprs; }
|
||||
|
||||
u32 GetGPR(size_t gpr) const { return m_gpr_values[gpr]; }
|
||||
|
||||
void SetGPR(size_t gpr, u32 value)
|
||||
{
|
||||
m_gpr_values_known[gpr] = true;
|
||||
m_gpr_values[gpr] = value;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void ClearGPR(Args... gprs)
|
||||
{
|
||||
ClearGPRs(BitSet32{static_cast<int>(gprs)...});
|
||||
}
|
||||
|
||||
void ClearGPRs(BitSet32 gprs) { m_gpr_values_known &= ~gprs; }
|
||||
|
||||
void Clear() { m_gpr_values_known = BitSet32{}; }
|
||||
|
||||
private:
|
||||
ConstantPropagationResult EvaluateMulImm(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateSubImmCarry(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateAddImm(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateAddImmCarry(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateRlwimix(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateRlwinmxRlwnmx(UGeckoInstruction inst, u32 shift) const;
|
||||
ConstantPropagationResult EvaluateBitwiseImm(UGeckoInstruction inst,
|
||||
u32 (*do_op)(u32, u32)) const;
|
||||
ConstantPropagationResult EvaluateTable31(UGeckoInstruction inst, u64 flags) const;
|
||||
ConstantPropagationResult EvaluateTable31Negx(UGeckoInstruction inst, u64 flags) const;
|
||||
ConstantPropagationResult EvaluateTable31S(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateTable31AB(UGeckoInstruction inst, u64 flags) const;
|
||||
ConstantPropagationResult EvaluateTable31ABOneRegisterKnown(UGeckoInstruction inst, u64 flags,
|
||||
u32 value, bool known_reg_is_b) const;
|
||||
ConstantPropagationResult EvaluateTable31ABIdenticalRegisters(UGeckoInstruction inst,
|
||||
u64 flags) const;
|
||||
ConstantPropagationResult EvaluateTable31SB(UGeckoInstruction inst) const;
|
||||
ConstantPropagationResult EvaluateTable31SBOneRegisterKnown(UGeckoInstruction inst, u32 value,
|
||||
bool known_reg_is_b) const;
|
||||
ConstantPropagationResult EvaluateTable31SBIdenticalRegisters(UGeckoInstruction inst) const;
|
||||
|
||||
static constexpr ConstantPropagationResult DO_NOTHING = [] {
|
||||
ConstantPropagationResult result;
|
||||
result.instruction_fully_executed = true;
|
||||
return result;
|
||||
}();
|
||||
|
||||
static constexpr size_t GPR_COUNT = 32;
|
||||
|
||||
std::array<u32, GPR_COUNT> m_gpr_values;
|
||||
BitSet32 m_gpr_values_known{};
|
||||
};
|
||||
|
||||
} // namespace JitCommon
|
||||
@@ -455,6 +455,7 @@
|
||||
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
|
||||
<ClInclude Include="Core\PowerPC\Interpreter\Interpreter_FPUtils.h" />
|
||||
<ClInclude Include="Core\PowerPC\Interpreter\Interpreter.h" />
|
||||
<ClInclude Include="Core\PowerPC\JitCommon\ConstantPropagation.h" />
|
||||
<ClInclude Include="Core\PowerPC\JitCommon\DivUtils.h" />
|
||||
<ClInclude Include="Core\PowerPC\JitCommon\JitAsmCommon.h" />
|
||||
<ClInclude Include="Core\PowerPC\JitCommon\JitBase.h" />
|
||||
@@ -1139,6 +1140,7 @@
|
||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_SystemRegisters.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Tables.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\JitCommon\ConstantPropagation.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\JitCommon\DivUtils.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\JitCommon\JitAsmCommon.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\JitCommon\JitBase.cpp" />
|
||||
|
||||
Reference in New Issue
Block a user