JitArm64: Replace dirty flag and partially replace RegType enum

Like Jit64, JitArm64 now keeps track of the location of a guest register
using three booleans: Whether it is in ppcState, whether it is in a host
register, and whether it is a known immediate. The RegType enum remains
only for the purpose of keeping track of what format FPRs are stored in
in host registers.
This commit is contained in:
JosJuice 2024-10-23 20:55:51 +02:00
parent 6929dff016
commit da0c9ec95e
2 changed files with 104 additions and 117 deletions

View File

@ -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)
if (last_used > most_stale_amount && reg.IsInHostRegister())
{
most_stale_preg = i;
most_stale_amount = last_used;
@ -197,10 +196,10 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_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)
@ -211,7 +210,7 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_reg
}
else if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
{
if (reg.IsDirty())
if (!reg.IsInPPCState())
{
const u32 imm = GetImm(index - GUEST_GPR_OFFSET);
if (imm == 0)
@ -255,10 +254,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 ||
IsImm(i),
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])
@ -271,9 +270,9 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
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)
@ -283,9 +282,9 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
m_emit->STP(IndexType::Signed, RX1, RX2, PPC_REG, u32(ppc_offset));
if (flush_all)
{
if (reg1.GetType() == RegType::Register)
if (reg1.IsInHostRegister())
UnlockRegister(reg1.GetReg());
if (reg2.GetType() == RegType::Register)
if (reg2.IsInHostRegister())
UnlockRegister(reg2.GetReg());
reg1.Flush();
reg2.Flush();
@ -305,9 +304,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);
@ -350,44 +350,33 @@ ARM64Reg Arm64GPRCache::BindForRead(size_t index)
IncrementAllUsed();
reg.ResetLastUsed();
switch (reg.GetType())
if (reg.IsInHostRegister())
{
case RegType::Register: // already in a reg
return reg.GetReg();
case RegType::Discarded: // Is an immediate or discarded
}
else if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
{
ASSERT_MSG(DYNA_REC, is_gpr && IsImm(index - GUEST_GPR_OFFSET),
"Attempted to read discarded register");
ARM64Reg host_reg = bitsize != 64 ? GetReg() : EncodeRegTo64(GetReg());
m_emit->MOVI2R(host_reg, GetImm(index - GUEST_GPR_OFFSET));
reg.Load(host_reg);
return host_reg;
}
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::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.Discard();
reg.SetDirty(dirty);
@ -403,8 +392,7 @@ void Arm64GPRCache::BindForWrite(size_t index, bool will_read, bool will_write)
reg.ResetLastUsed();
const RegType reg_type = reg.GetType();
if (reg_type != RegType::Register)
if (!reg.IsInHostRegister())
{
if (is_gpr && IsImm(index - GUEST_GPR_OFFSET))
{
@ -420,14 +408,12 @@ void Arm64GPRCache::BindForWrite(size_t index, bool will_read, bool will_write)
}
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)
{
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));
}
return;
}
}
@ -498,7 +484,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;
}
@ -508,7 +494,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;
@ -528,17 +514,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)
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");
}
}
}
@ -547,9 +533,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:
{
@ -632,28 +641,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;
@ -669,16 +656,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
@ -720,8 +708,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.
@ -732,7 +720,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);
@ -796,10 +784,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.GetReg() == host_reg)
if (reg.IsInHostRegister() && reg.GetReg() == host_reg)
{
FlushRegister(i, FlushMode::All, tmp_reg);
return;
@ -816,8 +802,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;
@ -827,8 +813,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)
@ -935,7 +921,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);
}
@ -943,18 +929,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;
}
}

View File

@ -60,15 +60,12 @@ static_assert(PPCSTATE_OFF(xer_so_ov) < 4096, "STRB can't store xer_so_ov!");
enum class RegType
{
NotLoaded,
Discarded, // Reg is in ConstantPropagation, or isn't loaded at all
Register, // Reg type is register
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
@ -91,19 +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; }
void Load(Arm64Gen::ARM64Reg reg, RegType type = RegType::Register)
void Load(Arm64Gen::ARM64Reg reg, RegType format = RegType::Register)
{
m_type = type;
m_reg = 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_dirty = true;
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;
@ -111,9 +110,10 @@ public:
void Flush()
{
// Invalidate any previous information
m_type = RegType::NotLoaded;
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
m_dirty = false;
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;
@ -122,17 +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
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
@ -444,9 +445,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;