Jit64: Don't store immediate values in register cache

They're now stored in ConstantPropagation instead.

I've also removed the LocationType enum. The location of each guest
register is now tracked using three booleans: Whether it is in ppcState,
whether it is in a host register, and whether it is a known immediate.
The first two of these booleans are stored in the register cache, and
the last one is stored in ConstantPropagation. This new model allows us
to handle the combination of a value simultaneously being in a host
register and being a known immediate. It also keeps track of which
registers are dirty, which was previously kept track of in X64CachedReg.

The old model maps to the new model as follows:

                                default    host_reg    immediate

Default                         true       false       false
Discarded                       false      false       false
Bound                           (!dirty)   true        false
Immediate                       false      false       true
SpeculativeImmediate            true       false       true
[previously unrepresentable]    (!dirty)   true        true
This commit is contained in:
JosJuice 2023-08-25 15:28:07 +02:00
parent c9871f772e
commit 2454dbd6b8
10 changed files with 220 additions and 205 deletions

View File

@ -1115,10 +1115,11 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
CompileInstruction(op);
}
m_constant_propagation.Apply(constant_propagation_result, op.regsOut);
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);
}

View File

@ -75,6 +75,8 @@ public:
void IntializeSpeculativeConstants();
JitCommon::ConstantPropagation& GetConstantPropagation() { return m_constant_propagation; }
JitBlockCache* GetBlockCache() override { return &blocks; }
void Trace();

View File

@ -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;
};

View File

@ -13,16 +13,51 @@ FPURegCache::FPURegCache(Jit64& jit) : RegCache{jit}
{
}
bool FPURegCache::IsImm(preg_t preg) const
{
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)
{
ASSERT_MSG(DYNA_REC, m_regs[preg].IsBound(), "Unbound register - {}", preg);
m_emitter->MOVAPD(new_loc, m_regs[preg].Location()->GetSimpleReg());
ASSERT_MSG(DYNA_REC, m_regs[preg].IsInHostRegister(), "FPR {} not in host register", preg);
m_emitter->MOVAPD(new_loc, m_regs[preg].GetHostRegister());
}
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

View File

@ -12,10 +12,16 @@ 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 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;

View File

@ -13,16 +13,72 @@ GPRRegCache::GPRRegCache(Jit64& jit) : RegCache{jit}
{
}
bool GPRRegCache::IsImm(preg_t preg) const
{
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)
{
ASSERT_MSG(DYNA_REC, !m_regs[preg].IsDiscarded(), "Discarded register - {}", preg);
m_emitter->MOV(32, new_loc, m_regs[preg].Location().value());
if (m_regs[preg].IsInHostRegister())
{
m_emitter->MOV(32, new_loc, ::Gen::R(m_regs[preg].GetHostRegister()));
}
else
{
ASSERT_MSG(DYNA_REC, m_jit.GetConstantPropagation().HasGPR(preg),
"GPR {} not in host register or constant propagation", preg);
m_emitter->MOV(32, new_loc, ::Gen::Imm32(m_jit.GetConstantPropagation().GetGPR(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 +104,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);
m_jit.GetConstantPropagation().SetGPR(preg, imm_value);
if (dirty)
DiscardRegister(preg);
}
BitSet32 GPRRegCache::GetRegUtilization() const

View File

@ -11,12 +11,21 @@ class GPRRegCache final : public RegCache
{
public:
explicit GPRRegCache(Jit64& jit);
using RegCache::IsImm;
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 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;

View File

@ -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;
@ -381,13 +372,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);
}
}
@ -405,24 +390,7 @@ void RegCache::Flush(BitSet32 pregs)
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, false, "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);
}
}
@ -430,9 +398,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);
}
}
@ -469,7 +437,7 @@ void RegCache::PreloadRegisters(BitSet32 to_preload)
{
if (NumFreeRegisters() < 2)
return;
if (!R(preg).IsImm())
if (!IsImm(preg))
BindToRegister(preg, true, false);
}
}
@ -496,51 +464,51 @@ 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::none_of(m_regs.begin(), m_regs.end(),
[xr](const auto& r) {
return r.Location().has_value() && r.Location()->IsSimpleReg(xr);
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)));
}
@ -550,31 +518,13 @@ void RegCache::StoreFromRegister(preg_t i, FlushMode mode)
// 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;
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 (doStore)
if (!m_regs[i].IsInDefaultLocation())
StoreRegister(i, GetDefaultLocation(i));
if (mode == FlushMode::Full)
m_regs[i].SetFlushed();
if (mode == FlushMode::Full && m_regs[i].IsInHostRegister())
m_xregs[m_regs[i].GetHostRegister()].Unbind();
m_regs[i].SetFlushed(mode != FlushMode::Full);
}
X64Reg RegCache::GetFreeXReg()
@ -639,7 +589,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,
@ -660,16 +610,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)
@ -725,29 +669,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();
}
}

View File

@ -151,12 +151,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);
@ -186,6 +188,7 @@ protected:
virtual Gen::OpArg GetDefaultLocation(preg_t preg) const = 0;
virtual void StoreRegister(preg_t preg, const Gen::OpArg& new_loc) = 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;
@ -193,7 +196,7 @@ 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);
@ -202,7 +205,7 @@ protected:
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);

View File

@ -65,6 +65,8 @@ public:
m_gpr_values[gpr] = value;
}
void ClearGPR(size_t gpr) { m_gpr_values_known[gpr] = false; }
void Clear() { m_gpr_values_known = BitSet32{}; }
private: