mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 14:00:16 +02:00
IR: remove pairs
They're now unused. And won't be missed. Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
This commit is contained in:
1 parent
cab02be637
commit
64a45c0d29
9 files changed
+11
-217
No files matched your search
@@ -41,21 +41,6 @@ DEF_BINOP_WITH_CONSTANT(Lshl, lslv, lsl)
|
||||
DEF_BINOP_WITH_CONSTANT(Lshr, lsrv, lsr)
|
||||
DEF_BINOP_WITH_CONSTANT(Ror, rorv, ror)
|
||||
|
||||
DEF_OP(TruncElementPair) {
|
||||
auto Op = IROp->C<IR::IROp_TruncElementPair>();
|
||||
|
||||
switch (IROp->Size) {
|
||||
case 4: {
|
||||
auto Dst = GetRegPair(Node);
|
||||
auto Src = GetRegPair(Op->Pair.ID());
|
||||
mov(ARMEmitter::Size::i32Bit, Dst.first, Src.first);
|
||||
mov(ARMEmitter::Size::i32Bit, Dst.second, Src.second);
|
||||
break;
|
||||
}
|
||||
default: LOGMAN_MSG_A_FMT("Unhandled Truncation size: {}", IROp->Size); break;
|
||||
}
|
||||
}
|
||||
|
||||
DEF_OP(Constant) {
|
||||
auto Op = IROp->C<IR::IROp_Constant>();
|
||||
auto Dst = GetReg(Node);
|
||||
@@ -1371,11 +1356,6 @@ DEF_OP(Select) {
|
||||
const auto Src2 = GetReg(Op->Cmp2.ID());
|
||||
cmp(CompareEmitSize, Src1, Src2);
|
||||
}
|
||||
} else if (IsGPRPair(Op->Cmp1.ID())) {
|
||||
const auto Src1 = GetRegPair(Op->Cmp1.ID());
|
||||
const auto Src2 = GetRegPair(Op->Cmp2.ID());
|
||||
cmp(EmitSize, Src1.first, Src2.first);
|
||||
ccmp(EmitSize, Src1.second, Src2.second, ARMEmitter::StatusFlags::None, cc);
|
||||
} else if (IsFPR(Op->Cmp1.ID())) {
|
||||
const auto Src1 = GetVReg(Op->Cmp1.ID());
|
||||
const auto Src2 = GetVReg(Op->Cmp2.ID());
|
||||
|
||||
@@ -654,12 +654,6 @@ bool Arm64JITCore::IsGPR(IR::NodeID Node) const {
|
||||
return Class == IR::GPRClass || Class == IR::GPRFixedClass;
|
||||
}
|
||||
|
||||
bool Arm64JITCore::IsGPRPair(IR::NodeID Node) const {
|
||||
auto Class = GetRegClass(Node);
|
||||
|
||||
return Class == IR::GPRPairClass;
|
||||
}
|
||||
|
||||
CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, const FEXCore::IR::IRListView* IR, FEXCore::Core::DebugData* DebugData,
|
||||
const FEXCore::IR::RegisterAllocationData* RAData) {
|
||||
FEXCORE_PROFILE_SCOPED("Arm64::CompileCode");
|
||||
|
||||
@@ -114,15 +114,6 @@ private:
|
||||
FEX_UNREACHABLE;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::pair<ARMEmitter::Register, ARMEmitter::Register> GetRegPair(IR::NodeID Node) const {
|
||||
const auto Reg = GetPhys(Node);
|
||||
|
||||
LOGMAN_THROW_AA_FMT(Reg.Class == IR::GPRPairClass.Val, "Unexpected Class: {}", Reg.Class);
|
||||
|
||||
return std::make_pair(GeneralRegisters[Reg.Reg], GeneralRegisters[Reg.Reg + 1]);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
FEXCore::IR::RegisterClassType GetRegClass(IR::NodeID Node) const;
|
||||
|
||||
@@ -253,8 +244,6 @@ private:
|
||||
bool IsFPR(IR::NodeID Node) const;
|
||||
[[nodiscard]]
|
||||
bool IsGPR(IR::NodeID Node) const;
|
||||
[[nodiscard]]
|
||||
bool IsGPRPair(IR::NodeID Node) const;
|
||||
|
||||
[[nodiscard]]
|
||||
ARMEmitter::ExtendedMemOperand GenerateMemOperand(uint8_t AccessSize, ARMEmitter::Register Base, IR::OrderedNodeWrapper Offset,
|
||||
|
||||
@@ -367,30 +367,6 @@ DEF_OP(SpillRegister) {
|
||||
}
|
||||
default: LOGMAN_MSG_A_FMT("Unhandled SpillRegister size: {}", OpSize); break;
|
||||
}
|
||||
} else if (Op->Class == FEXCore::IR::GPRPairClass) {
|
||||
const auto Src = GetRegPair(Op->Value.ID());
|
||||
switch (OpSize) {
|
||||
case 8: {
|
||||
if (SlotOffset <= 252 && (SlotOffset & 0b11) == 0) {
|
||||
stp<ARMEmitter::IndexType::OFFSET>(Src.first.W(), Src.second.W(), ARMEmitter::Reg::rsp, SlotOffset);
|
||||
} else {
|
||||
add(ARMEmitter::Size::i64Bit, TMP1, ARMEmitter::Reg::rsp, SlotOffset);
|
||||
stp<ARMEmitter::IndexType::OFFSET>(Src.first.W(), Src.second.W(), TMP1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 16: {
|
||||
if (SlotOffset <= 504 && (SlotOffset & 0b111) == 0) {
|
||||
stp<ARMEmitter::IndexType::OFFSET>(Src.first.X(), Src.second.X(), ARMEmitter::Reg::rsp, SlotOffset);
|
||||
} else {
|
||||
add(ARMEmitter::Size::i64Bit, TMP1, ARMEmitter::Reg::rsp, SlotOffset);
|
||||
stp<ARMEmitter::IndexType::OFFSET>(Src.first.X(), Src.second.X(), TMP1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: LOGMAN_MSG_A_FMT("Unhandled SpillRegister(GPRPair) size: {}", OpSize); break;
|
||||
}
|
||||
} else {
|
||||
LOGMAN_MSG_A_FMT("Unhandled SpillRegister class: {}", Op->Class.Val);
|
||||
}
|
||||
@@ -480,30 +456,6 @@ DEF_OP(FillRegister) {
|
||||
}
|
||||
default: LOGMAN_MSG_A_FMT("Unhandled FillRegister size: {}", OpSize); break;
|
||||
}
|
||||
} else if (Op->Class == FEXCore::IR::GPRPairClass) {
|
||||
const auto Src = GetRegPair(Node);
|
||||
switch (OpSize) {
|
||||
case 8: {
|
||||
if (SlotOffset <= 252 && (SlotOffset & 0b11) == 0) {
|
||||
ldp<ARMEmitter::IndexType::OFFSET>(Src.first.W(), Src.second.W(), ARMEmitter::Reg::rsp, SlotOffset);
|
||||
} else {
|
||||
add(ARMEmitter::Size::i64Bit, TMP1, ARMEmitter::Reg::rsp, SlotOffset);
|
||||
ldp<ARMEmitter::IndexType::OFFSET>(Src.first.W(), Src.second.W(), TMP1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 16: {
|
||||
if (SlotOffset <= 504 && (SlotOffset & 0b111) == 0) {
|
||||
ldp<ARMEmitter::IndexType::OFFSET>(Src.first.X(), Src.second.X(), ARMEmitter::Reg::rsp, SlotOffset);
|
||||
} else {
|
||||
add(ARMEmitter::Size::i64Bit, TMP1, ARMEmitter::Reg::rsp, SlotOffset);
|
||||
ldp<ARMEmitter::IndexType::OFFSET>(Src.first.X(), Src.second.X(), TMP1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: LOGMAN_MSG_A_FMT("Unhandled FillRegister(GPRPair) size: {}", OpSize); break;
|
||||
}
|
||||
} else {
|
||||
LOGMAN_MSG_A_FMT("Unhandled FillRegister class: {}", Op->Class.Val);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
" * Textual class to group IR ops by type",
|
||||
"* DestClass",
|
||||
" * SSA class of the return when the return type is `SSA`",
|
||||
" * Not used if the destination type is one of {GPR, GPRPair, FPR}",
|
||||
" * Not used if the destination type is one of {GPR, FPR}",
|
||||
"* DestSize",
|
||||
" * The size of the destination type",
|
||||
"* EmitValidation",
|
||||
@@ -81,7 +81,6 @@
|
||||
"constexpr FEXCore::IR::RegisterClassType GPRFixedClass {1}",
|
||||
"constexpr FEXCore::IR::RegisterClassType FPRClass {2}",
|
||||
"constexpr FEXCore::IR::RegisterClassType FPRFixedClass {3}",
|
||||
"constexpr FEXCore::IR::RegisterClassType GPRPairClass {4}",
|
||||
"constexpr FEXCore::IR::RegisterClassType ComplexClass {5}",
|
||||
"constexpr FEXCore::IR::RegisterClassType InvalidClass {7}",
|
||||
"",
|
||||
@@ -146,7 +145,6 @@
|
||||
"OpSize": "FEXCore::IR::OpSize",
|
||||
"SSA": "OrderedNode*",
|
||||
"GPR": "OrderedNode*",
|
||||
"GPRPair": "OrderedNode*",
|
||||
"FPR": "OrderedNode*",
|
||||
"FenceType": "FenceType",
|
||||
"RegisterClass": "RegisterClassType",
|
||||
@@ -245,12 +243,7 @@
|
||||
"Print SSA:$Value": {
|
||||
"HasSideEffects": true,
|
||||
"Desc": ["Debug operation that prints an SSA value to the console",
|
||||
"May only print 64bits of the value",
|
||||
"Depending on backend, may only support GPR printing"
|
||||
],
|
||||
"EmitValidation": [
|
||||
"WalkFindRegClass($Value) != GPRPairClass"
|
||||
]
|
||||
"May only print 64bits of the value"]
|
||||
},
|
||||
"GPR = AllocateGPR i1:$ForPair": {
|
||||
"Desc": ["Silly pseudo-instruction to allocate a register for a future destination",
|
||||
@@ -920,14 +913,6 @@
|
||||
"DestSize": "8"
|
||||
},
|
||||
|
||||
"GPRPair = TruncElementPair GPRPair:$Pair, u8:#ByteSize": {
|
||||
"Desc": [
|
||||
"Truncates each element of a pair to the destination size",
|
||||
"TODO: This IR op should get removed"
|
||||
],
|
||||
"DestSize": "ByteSize * 2",
|
||||
"NumElements": "2"
|
||||
},
|
||||
"GPR = CycleCounter": {
|
||||
"Desc": ["Returns the host 64bit cycle counter",
|
||||
"Useful when emulating rdtsc",
|
||||
|
||||
@@ -77,8 +77,6 @@ static void PrintArg(fextl::stringstream* out, [[maybe_unused]] const IRListView
|
||||
*out << "FPR";
|
||||
} else if (Arg == FPRFixedClass.Val) {
|
||||
*out << "FPRFixed";
|
||||
} else if (Arg == GPRPairClass.Val) {
|
||||
*out << "GPRPair";
|
||||
} else {
|
||||
*out << "Unknown Registerclass " << Arg;
|
||||
}
|
||||
@@ -100,7 +98,6 @@ static void PrintArg(fextl::stringstream* out, const IRListView* IR, OrderedNode
|
||||
case FEXCore::IR::GPRFixedClass.Val: *out << "(GPRFixed"; break;
|
||||
case FEXCore::IR::FPRClass.Val: *out << "(FPR"; break;
|
||||
case FEXCore::IR::FPRFixedClass.Val: *out << "(FPRFixed"; break;
|
||||
case FEXCore::IR::GPRPairClass.Val: *out << "(GPRPair"; break;
|
||||
case FEXCore::IR::ComplexClass.Val: *out << "(Complex"; break;
|
||||
case FEXCore::IR::InvalidClass.Val: *out << "(Invalid"; break;
|
||||
default: *out << "(Unknown"; break;
|
||||
@@ -316,7 +313,6 @@ void Dump(fextl::stringstream* out, const IRListView* IR, IR::RegisterAllocation
|
||||
case FEXCore::IR::GPRFixedClass.Val: *out << "(GPRFixed"; break;
|
||||
case FEXCore::IR::FPRClass.Val: *out << "(FPR"; break;
|
||||
case FEXCore::IR::FPRFixedClass.Val: *out << "(FPRFixed"; break;
|
||||
case FEXCore::IR::GPRPairClass.Val: *out << "(GPRPair"; break;
|
||||
case FEXCore::IR::ComplexClass.Val: *out << "(Complex"; break;
|
||||
case FEXCore::IR::InvalidClass.Val: *out << "(Invalid"; break;
|
||||
default: *out << "(Unknown"; break;
|
||||
|
||||
@@ -38,7 +38,6 @@ FEXCore::IR::RegisterClassType IREmitter::WalkFindRegClass(Ref Node) {
|
||||
auto Class = GetOpRegClass(Node);
|
||||
switch (Class) {
|
||||
case GPRClass:
|
||||
case GPRPairClass:
|
||||
case FPRClass:
|
||||
case GPRFixedClass:
|
||||
case FPRFixedClass:
|
||||
|
||||
@@ -22,13 +22,11 @@ namespace FEXCore::IR::Validation {
|
||||
struct RegState {
|
||||
static constexpr IR::NodeID UninitializedValue {0};
|
||||
static constexpr IR::NodeID InvalidReg {0xffff'ffff};
|
||||
static constexpr IR::NodeID CorruptedPair {0xffff'fffe};
|
||||
|
||||
// This class makes some assumptions about how the host registers are arranged and mapped to virtual registers:
|
||||
// 1. There will be less than 32 GPRs and 32 FPRs
|
||||
// 2. If the GPRFixed class is used, there will be 16 GPRs and 16 FixedGPRs max
|
||||
// 3. Same with FPRFixed
|
||||
// 4. If the GPRPairClass is used, it is assumed each GPRPair N will map onto GPRs N and N + 1
|
||||
|
||||
// These assumptions were all true for the state of the arm64 and x86 jits at the time this was written
|
||||
|
||||
@@ -49,11 +47,6 @@ struct RegState {
|
||||
// On arm64, there are 16 Fixed and 12 normal
|
||||
FPRsFixed[Reg.Reg] = ssa;
|
||||
return true;
|
||||
case GPRPairClass:
|
||||
// Alias paired registers onto both
|
||||
GPRs[Reg.Reg] = ssa;
|
||||
GPRs[Reg.Reg + 1] = ssa;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -66,12 +59,6 @@ struct RegState {
|
||||
case GPRFixedClass: return GPRsFixed[Reg.Reg];
|
||||
case FPRClass: return FPRs[Reg.Reg];
|
||||
case FPRFixedClass: return FPRsFixed[Reg.Reg];
|
||||
case GPRPairClass:
|
||||
// Make sure both halves of the Pair contain the same SSA
|
||||
if (GPRs[Reg.Reg] == GPRs[Reg.Reg + 1]) {
|
||||
return GPRs[Reg.Reg];
|
||||
}
|
||||
return CorruptedPair;
|
||||
}
|
||||
return InvalidReg;
|
||||
}
|
||||
@@ -139,14 +126,6 @@ void RAValidation::Run(IREmitter* IREmit) {
|
||||
if (CurrentSSAAtReg == RegState::InvalidReg) {
|
||||
HadError |= true;
|
||||
Errors << fextl::fmt::format("%{}: Arg[{}] unknown Reg: {}, class: {}\n", ID, i, PhyReg.Reg, PhyReg.Class);
|
||||
} else if (CurrentSSAAtReg == RegState::CorruptedPair) {
|
||||
HadError |= true;
|
||||
|
||||
auto Lower = BlockRegState.Get(PhysicalRegister(GPRClass, uint8_t(PhyReg.Reg * 2) + 1));
|
||||
auto Upper = BlockRegState.Get(PhysicalRegister(GPRClass, PhyReg.Reg * 2 + 1));
|
||||
|
||||
Errors << fextl::fmt::format("%{}: Arg[{}] expects paired reg{} to contain %{}, but it actually contains {{%{}, %{}}}\n", ID, i,
|
||||
PhyReg.Reg, ArgID, Lower, Upper);
|
||||
} else if (CurrentSSAAtReg == RegState::UninitializedValue) {
|
||||
HadError |= true;
|
||||
|
||||
|
||||
@@ -185,11 +185,11 @@ private:
|
||||
};
|
||||
|
||||
RegisterClass* GetClass(PhysicalRegister Reg) {
|
||||
return &Classes[(Reg.Class == GPRPairClass) ? GPRClass : Reg.Class];
|
||||
return &Classes[Reg.Class];
|
||||
};
|
||||
|
||||
uint32_t GetRegBits(PhysicalRegister Reg) {
|
||||
return ((Reg.Class == GPRPairClass) ? 0b11 : 0b1) << Reg.Reg;
|
||||
return 1 << Reg.Reg;
|
||||
};
|
||||
|
||||
bool IsInRegisterFile(Ref Old) {
|
||||
@@ -273,7 +273,7 @@ private:
|
||||
// the next set bit and then clearing on each iteration.
|
||||
#define foreach_bit(b, x) for (uint32_t __x = (x), b; ((b) = __builtin_ffs(__x) - 1, __x); __x &= ~(1 << (b)))
|
||||
|
||||
void SpillReg(RegisterClass* Class, IROp_Header* Exclude, bool Pair) {
|
||||
void SpillReg(RegisterClass* Class, IROp_Header* Exclude) {
|
||||
// Find the best node to spill according to the "furthest-first" heuristic.
|
||||
// Since we defined IPs relative to the end of the block, the furthest
|
||||
// next-use has the /smallest/ unsigned IP.
|
||||
@@ -282,12 +282,6 @@ private:
|
||||
uint8_t BestReg = ~0;
|
||||
|
||||
foreach_bit(i, Class->Allocated) {
|
||||
// We have to prioritize the pair region if we're allocating for a Pair.
|
||||
// See the comment at the call site in AssignReg.
|
||||
if (Pair && Candidate != nullptr && i >= PairRegs) {
|
||||
break;
|
||||
}
|
||||
|
||||
Ref Old = Class->RegToSSA[i];
|
||||
|
||||
LOGMAN_THROW_AA_FMT(Old != nullptr, "Invariant3");
|
||||
@@ -431,94 +425,20 @@ private:
|
||||
}
|
||||
|
||||
RegisterClassType OrigClassType = GetRegClassFromNode(IR, IROp);
|
||||
bool Pair = OrigClassType == GPRPairClass;
|
||||
RegisterClassType ClassType = Pair ? GPRClass : OrigClassType;
|
||||
RegisterClassType ClassType = OrigClassType;
|
||||
RegisterClass* Class = &Classes[ClassType];
|
||||
|
||||
// Spill to make room in the register file. Free registers need not be
|
||||
// contiguous, we'll shuffle later.
|
||||
//
|
||||
// There is one subtlety: when allocating a pair, we need at least 1 free
|
||||
// register in the pair region. Else, we could end up trying to allocate a
|
||||
// pair when the only free 2 regs are outside the pair region, and the pair
|
||||
// region is made of all pairs (so nothing to shuffle). With 1 free
|
||||
// register in the pair region, we'll be able to shuffle.
|
||||
//
|
||||
// When spilling for pairs, SpillReg prioritizes spilling the pair region
|
||||
// which ensures this loop is well-behaved.
|
||||
while (std::popcount(Class->Available) < (Pair ? 2 : 1) || (Pair && !(Class->Available & ((1u << PairRegs) - 1)))) {
|
||||
if (!Class->Available) {
|
||||
IREmit->SetWriteCursorBefore(CodeNode);
|
||||
SpillReg(Class, Pivot, Pair);
|
||||
SpillReg(Class, Pivot);
|
||||
}
|
||||
|
||||
// There are now enough free registers, but they may be fragmented.
|
||||
// Pick a scalar blocking a pair and shuffle to make room.
|
||||
uint32_t Available = AvailableMask(Class, Pair);
|
||||
if (!Available) {
|
||||
LOGMAN_THROW_A_FMT(OrigClassType == GPRPairClass, "Already spilled");
|
||||
|
||||
// Find the first free scalar. There are at least 2.
|
||||
unsigned Hole = std::countr_zero(Class->Available);
|
||||
LOGMAN_THROW_AA_FMT(Class->Available & (1u << Hole), "Definition");
|
||||
|
||||
// Its neighbour is blocking the pair.
|
||||
unsigned Blocked = Hole ^ 1;
|
||||
LOGMAN_THROW_AA_FMT(!(Class->Available & (1u << Blocked)), "Invariant7");
|
||||
LOGMAN_THROW_AA_FMT(Hole < PairRegs, "Pairable register");
|
||||
|
||||
// Find another free scalar to evict the neighbour
|
||||
unsigned NewReg = std::countr_zero(Class->Available & ~(1u << Hole));
|
||||
LOGMAN_THROW_AA_FMT(Class->Available & (1u << NewReg), "Ensured space");
|
||||
|
||||
IREmit->SetWriteCursorBefore(CodeNode);
|
||||
Ref Old = Class->RegToSSA[Blocked];
|
||||
LOGMAN_THROW_A_FMT(GetRegClassFromNode(IR, IR->GetOp<IROp_Header>(Old)) == GPRClass, "Only scalars have free neighbours");
|
||||
FreeReg(PhysicalRegister(GPRClass, Blocked));
|
||||
|
||||
Ref Clobber = nullptr;
|
||||
|
||||
// If that scalar is free because it is killed by this instruction, it
|
||||
// needs to be shuffled too, since the copy would clobber it.
|
||||
for (auto s = 0; s < IR::GetRAArgs(Pivot->Op); ++s) {
|
||||
// It is possible that the argument is to be remapped, but the actual
|
||||
// remapping in the IR only happens later in the pass so we need to
|
||||
// Map() explicitly. This can be hit with SRA shuffles.
|
||||
Ref New = Map(IR->GetNode(Pivot->Args[s]));
|
||||
const PhysicalRegister ClobberReg = SSAToReg[IR->GetID(New).Value];
|
||||
|
||||
if (ClobberReg.Class == GPRClass && ClobberReg.Reg == NewReg) {
|
||||
Clobber = IR->GetNode(Pivot->Args[s]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Clobber) {
|
||||
// Swap the registers.
|
||||
LOGMAN_THROW_A_FMT(IsOld(Clobber), "Not yet mapped");
|
||||
|
||||
auto ClobberNew = IREmit->_Swap1(Map(Clobber), Map(Old));
|
||||
Remap(Clobber, ClobberNew);
|
||||
|
||||
auto New = IREmit->_Swap2();
|
||||
Remap(Old, New);
|
||||
|
||||
SetReg(New, PhysicalRegister(GPRClass, NewReg));
|
||||
SetReg(ClobberNew, PhysicalRegister(GPRClass, Blocked));
|
||||
FreeReg(PhysicalRegister(GPRClass, Blocked));
|
||||
} else {
|
||||
// Otherwise, simply copy.
|
||||
auto Copy = IREmit->_Copy(Map(Old));
|
||||
|
||||
Remap(Old, Copy);
|
||||
SetReg(Copy, PhysicalRegister(GPRClass, NewReg));
|
||||
}
|
||||
|
||||
Available = AvailableMask(Class, Pair);
|
||||
}
|
||||
|
||||
LOGMAN_THROW_AA_FMT(Available != 0, "Post-condition of spill and shuffle");
|
||||
|
||||
// Assign a free register in the appropriate class.
|
||||
uint32_t Available = AvailableMask(Class, false);
|
||||
LOGMAN_THROW_AA_FMT(Available != 0, "Post-condition of spilling");
|
||||
|
||||
unsigned Reg = std::countr_zero(Available);
|
||||
SetReg(CodeNode, PhysicalRegister(OrigClassType, Reg));
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user