aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/include/rcc/CodeGen/MachineFunctionCloner.h6
-rw-r--r--backend/include/rcc/CodeGen/MachineIRBuilder.h12
-rw-r--r--backend/include/rcc/CodeGen/MachineIRTypes.h2
-rw-r--r--backend/include/rcc/CodeGen/MachineVerifier.h4
-rw-r--r--backend/include/rcc/CodeGen/TargetDescription.h98
-rw-r--r--backend/include/rcc/CodeGen/TargetRegisterInfo.h192
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt8
-rw-r--r--backend/lib/CodeGen/InstructionDescriptor.cpp8
-rw-r--r--backend/lib/CodeGen/MachineFunctionCloner.cpp7
-rw-r--r--backend/lib/CodeGen/MachineIRBuilder.cpp39
-rw-r--r--backend/lib/CodeGen/MachineVerifier.cpp20
-rw-r--r--backend/lib/CodeGen/TargetDescription.cpp610
-rw-r--r--backend/lib/CodeGen/TargetRegisterInfo.cpp603
-rw-r--r--backend/unittests/CMakeLists.txt1
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt44
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp37
-rw-r--r--backend/unittests/CodeGen/MachineIRBuilderTest.cpp53
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTargetTest.cpp485
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp115
-rw-r--r--backend/unittests/CodeGen/TargetFunctionStateTest.cpp16
-rw-r--r--backend/unittests/CodeGen/TargetRegisterInfoTest.cpp359
-rw-r--r--backend/unittests/Target/CMakeLists.txt17
-rw-r--r--backend/unittests/Target/TestTarget.cpp177
-rw-r--r--backend/unittests/Target/TestTarget.h64
24 files changed, 2821 insertions, 156 deletions
diff --git a/backend/include/rcc/CodeGen/MachineFunctionCloner.h b/backend/include/rcc/CodeGen/MachineFunctionCloner.h
index 658614f..09877d2 100644
--- a/backend/include/rcc/CodeGen/MachineFunctionCloner.h
+++ b/backend/include/rcc/CodeGen/MachineFunctionCloner.h
@@ -15,10 +15,10 @@
namespace rcc {
-class InstructionSet;
class MachineBasicBlock;
class MachineFunction;
class MachineInstr;
+class TargetDescription;
struct MachineFunctionCloneOptions final {
MachineFunctionStableID NewStableID;
@@ -81,12 +81,12 @@ private:
const std::unordered_map<const MachineInstr *, MachineInstr *> &Instructions;
friend Expected<MachineFunctionCloneResult>
- cloneMachineFunction(const MachineFunction &Source, const InstructionSet &Instructions,
+ cloneMachineFunction(const MachineFunction &Source, const TargetDescription &Target,
MachineFunctionCloneOptions Options);
};
[[nodiscard]] Expected<MachineFunctionCloneResult>
-cloneMachineFunction(const MachineFunction &Source, const InstructionSet &Instructions,
+cloneMachineFunction(const MachineFunction &Source, const TargetDescription &Target,
MachineFunctionCloneOptions Options);
} // namespace rcc
diff --git a/backend/include/rcc/CodeGen/MachineIRBuilder.h b/backend/include/rcc/CodeGen/MachineIRBuilder.h
index 47fd63c..08ec9ba 100644
--- a/backend/include/rcc/CodeGen/MachineIRBuilder.h
+++ b/backend/include/rcc/CodeGen/MachineIRBuilder.h
@@ -3,10 +3,10 @@
#ifndef RCC_CODEGEN_MACHINEIRBUILDER_H
#define RCC_CODEGEN_MACHINEIRBUILDER_H
-#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/MachineIRTypes.h"
#include "rcc/CodeGen/MachineOperand.h"
#include "rcc/CodeGen/Register.h"
+#include "rcc/CodeGen/TargetDescription.h"
#include "rcc/Support/Error.h"
#include <cstddef>
@@ -90,7 +90,7 @@ public:
[[nodiscard]] Expected<MachineInstr *> build();
private:
- MachineInstrBuilder(MachineFunction &Function, const InstructionSet &Instructions,
+ MachineInstrBuilder(MachineFunction &Function, const TargetDescription &Target,
const InstructionDescriptor &Descriptor, MachineBasicBlock &InsertBlock,
MachineInstr *InsertBefore) noexcept;
@@ -106,7 +106,7 @@ private:
bool IsImplicit);
MachineFunction *Function;
- const InstructionSet *Instructions;
+ const TargetDescription *Target;
const InstructionDescriptor *Descriptor;
MachineBasicBlock *InsertBlock;
MachineInstr *InsertBefore;
@@ -124,8 +124,8 @@ private:
class MachineIRBuilder final {
public:
- MachineIRBuilder(MachineFunction &Function, const InstructionSet &Instructions) noexcept
- : Function(&Function), Instructions(&Instructions) {}
+ MachineIRBuilder(MachineFunction &Function, const TargetDescription &Target) noexcept
+ : Function(&Function), Target(&Target) {}
MachineIRBuilder(const MachineIRBuilder &) = delete;
MachineIRBuilder &operator=(const MachineIRBuilder &) = delete;
MachineIRBuilder(MachineIRBuilder &&) = delete;
@@ -154,7 +154,7 @@ private:
[[nodiscard]] Expected<void> validateInstruction(const MachineInstr &Instruction) const;
MachineFunction *Function;
- const InstructionSet *Instructions;
+ const TargetDescription *Target;
MachineBasicBlock *InsertBlock = nullptr;
MachineInstr *InsertBefore = nullptr;
};
diff --git a/backend/include/rcc/CodeGen/MachineIRTypes.h b/backend/include/rcc/CodeGen/MachineIRTypes.h
index 058cacb..8906bbe 100644
--- a/backend/include/rcc/CodeGen/MachineIRTypes.h
+++ b/backend/include/rcc/CodeGen/MachineIRTypes.h
@@ -44,6 +44,7 @@ struct ConstantPoolIndexTag;
struct JumpTableIndexTag;
struct TargetIndexTag;
struct SubRegIndexTag;
+struct RegisterUnitIDTag;
struct RegisterBankIDTag;
struct RegisterClassIDTag;
struct SchedulingClassIDTag;
@@ -66,6 +67,7 @@ using ConstantPoolIndex = MachineIndex<detail::ConstantPoolIndexTag>;
using JumpTableIndex = MachineIndex<detail::JumpTableIndexTag>;
using TargetIndex = MachineIndex<detail::TargetIndexTag>;
using SubRegIndex = MachineIndex<detail::SubRegIndexTag>;
+using RegisterUnitID = MachineIndex<detail::RegisterUnitIDTag>;
using RegisterBankID = MachineIndex<detail::RegisterBankIDTag>;
using RegisterClassID = MachineIndex<detail::RegisterClassIDTag>;
using SchedulingClassID = MachineIndex<detail::SchedulingClassIDTag>;
diff --git a/backend/include/rcc/CodeGen/MachineVerifier.h b/backend/include/rcc/CodeGen/MachineVerifier.h
index b1035a2..efe430e 100644
--- a/backend/include/rcc/CodeGen/MachineVerifier.h
+++ b/backend/include/rcc/CodeGen/MachineVerifier.h
@@ -3,7 +3,7 @@
#ifndef RCC_CODEGEN_MACHINEVERIFIER_H
#define RCC_CODEGEN_MACHINEVERIFIER_H
-#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/TargetDescription.h"
#include "rcc/Support/Error.h"
#include <cstdint>
@@ -27,7 +27,7 @@ struct MachineVerifierOptions final {
class MachineVerifier final {
public:
[[nodiscard]] static Expected<void> verify(const MachineFunction &Function,
- const InstructionSet &Instructions,
+ const TargetDescription &Target,
MachineVerifierOptions Options = {});
};
diff --git a/backend/include/rcc/CodeGen/TargetDescription.h b/backend/include/rcc/CodeGen/TargetDescription.h
new file mode 100644
index 0000000..51a820a
--- /dev/null
+++ b/backend/include/rcc/CodeGen/TargetDescription.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_TARGETDESCRIPTION_H
+#define RCC_CODEGEN_TARGETDESCRIPTION_H
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/Support/Error.h"
+
+#include <optional>
+#include <span>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+class MachineBasicBlock;
+class MachineInstr;
+class MachineMemOperand;
+class MachineRegisterInfo;
+struct VirtualRegisterInfo;
+
+class TargetDescription final {
+public:
+ TargetDescription(const TargetDescription &) = delete;
+ TargetDescription &operator=(const TargetDescription &) = delete;
+ TargetDescription(TargetDescription &&) noexcept = default;
+ TargetDescription &operator=(TargetDescription &&) noexcept = default;
+ ~TargetDescription() = default;
+
+ [[nodiscard]] static Expected<TargetDescription> create(InstructionSet Instructions,
+ TargetRegisterInfo Registers);
+
+ [[nodiscard]] const InstructionSet &instructions() const noexcept {
+ return Instructions;
+ }
+
+ [[nodiscard]] const TargetRegisterInfo &registers() const noexcept {
+ return Registers;
+ }
+
+ [[nodiscard]] const InstructionDescriptor *
+ instructionDescriptor(Opcode InstructionOpcode) const noexcept {
+ return Instructions.descriptor(InstructionOpcode);
+ }
+
+ [[nodiscard]] Expected<void> validateExplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand> Operands,
+ const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void> validateOperandTies(Opcode InstructionOpcode,
+ std::span<const MachineOperand> ExplicitOperands,
+ std::span<const MachineOperandTie> Ties,
+ const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void>
+ validateImplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand *const> ImplicitOperands,
+ const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void>
+ validateEarlyClobber(std::span<const MachineOperand> ExplicitOperands,
+ std::span<const MachineOperand *const> ImplicitOperands,
+ std::span<const MachineOperandTie> Ties) const;
+ [[nodiscard]] Expected<void>
+ validateMemOperands(Opcode InstructionOpcode,
+ std::span<const MachineMemOperand *const> MemOperands) const {
+ return Instructions.validateMemOperands(InstructionOpcode, MemOperands);
+ }
+ [[nodiscard]] Expected<void> validateInstruction(const MachineInstr &Instruction,
+ const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void> validateRegisterInfo(const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void> validateBlockLiveIns(const MachineBasicBlock &Block) const;
+
+ [[nodiscard]] Expected<std::vector<MachineOperand>>
+ createDefaultImplicitOperands(const InstructionDescriptor &Descriptor) const {
+ return Instructions.createDefaultImplicitOperands(Descriptor);
+ }
+
+private:
+ TargetDescription(InstructionSet Instructions, TargetRegisterInfo Registers)
+ : Instructions(std::move(Instructions)), Registers(std::move(Registers)) {}
+
+ [[nodiscard]] Expected<void> validateTargetOperand(const OperandDescriptor *Descriptor,
+ const MachineOperand &Operand,
+ const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void> validateVirtualRegister(Register Reg,
+ const VirtualRegisterInfo &Info) const;
+ [[nodiscard]] std::optional<RegisterClassID>
+ effectiveRegisterClass(const MachineOperand &Operand, const MachineRegisterInfo &RegInfo) const;
+ [[nodiscard]] Expected<void> validateTieCompatibility(const MachineOperand &Def,
+ const MachineOperand &Use,
+ const MachineRegisterInfo &RegInfo) const;
+
+ InstructionSet Instructions;
+ TargetRegisterInfo Registers;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_TARGETDESCRIPTION_H
diff --git a/backend/include/rcc/CodeGen/TargetRegisterInfo.h b/backend/include/rcc/CodeGen/TargetRegisterInfo.h
new file mode 100644
index 0000000..5d7de2c
--- /dev/null
+++ b/backend/include/rcc/CodeGen/TargetRegisterInfo.h
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_TARGETREGISTERINFO_H
+#define RCC_CODEGEN_TARGETREGISTERINFO_H
+
+#include "rcc/CodeGen/MachineIRTypes.h"
+#include "rcc/CodeGen/Register.h"
+#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+struct RegisterUnitDesc final {
+ RegisterUnitID ID;
+ std::string Name;
+};
+
+struct RegisterUnitLane final {
+ RegisterUnitID Unit;
+ LaneMask Lanes;
+};
+
+struct PhysicalRegisterDesc final {
+ PhysicalRegister Reg;
+ std::string Name;
+ std::uint32_t Encoding = 0;
+ std::vector<RegisterUnitLane> Units;
+ bool IsConstant = false;
+ bool IsArtificial = false;
+};
+
+struct SubRegIndexDesc final {
+ SubRegIndex ID;
+ std::string Name;
+ LaneMask Lanes;
+ std::uint32_t SizeInBits = 0;
+ std::uint32_t OffsetInBits = 0;
+};
+
+struct PhysicalSubRegisterDesc final {
+ PhysicalRegister SuperReg;
+ SubRegIndex Index;
+ PhysicalRegister SubReg;
+};
+
+struct SubRegIndexComposition final {
+ SubRegIndex First;
+ SubRegIndex Second;
+ SubRegIndex Result;
+};
+
+struct RegisterClassDesc final {
+ RegisterClassID ID;
+ std::string Name;
+ std::vector<PhysicalRegister> Registers;
+ std::vector<PhysicalRegister> AllocationOrder;
+ std::uint32_t SizeInBits = 0;
+ std::uint32_t SpillSizeInBits = 0;
+ std::uint32_t SpillAlignmentInBits = 0;
+ LaneMask Lanes;
+ std::vector<SubRegIndex> SupportedSubRegs;
+ std::vector<RegisterClassID> SuperClasses;
+ RegisterBankID DefaultBank;
+ bool Allocatable = true;
+ std::uint32_t CopyCost = 1;
+ bool CoveredBySubRegs = false;
+};
+
+struct SubRegClassMapping final {
+ RegisterClassID SourceClass;
+ SubRegIndex Index;
+ RegisterClassID ResultClass;
+};
+
+struct RegisterBankDesc final {
+ RegisterBankID ID;
+ std::string Name;
+ std::uint32_t MaximumSizeInBits = 0;
+ std::vector<RegisterClassID> RegisterClasses;
+};
+
+struct TargetRegisterInfoDesc final {
+ std::vector<RegisterUnitDesc> RegisterUnits;
+ std::vector<PhysicalRegisterDesc> PhysicalRegisters;
+ std::vector<SubRegIndexDesc> SubRegIndices;
+ std::vector<PhysicalSubRegisterDesc> PhysicalSubRegisters;
+ std::vector<SubRegIndexComposition> SubRegCompositions;
+ std::vector<RegisterClassDesc> RegisterClasses;
+ std::vector<SubRegClassMapping> SubRegClassMappings;
+ std::vector<RegisterBankDesc> RegisterBanks;
+};
+
+class TargetRegisterInfo final {
+public:
+ [[nodiscard]] static Expected<TargetRegisterInfo> create(TargetRegisterInfoDesc Description);
+
+ [[nodiscard]] std::span<const RegisterUnitDesc> registerUnits() const noexcept {
+ return Description.RegisterUnits;
+ }
+
+ [[nodiscard]] std::span<const PhysicalRegisterDesc> physicalRegisters() const noexcept {
+ return Description.PhysicalRegisters;
+ }
+
+ [[nodiscard]] std::span<const SubRegIndexDesc> subRegIndices() const noexcept {
+ return Description.SubRegIndices;
+ }
+
+ [[nodiscard]] std::span<const RegisterClassDesc> registerClasses() const noexcept {
+ return Description.RegisterClasses;
+ }
+
+ [[nodiscard]] std::span<const RegisterBankDesc> registerBanks() const noexcept {
+ return Description.RegisterBanks;
+ }
+
+ [[nodiscard]] const RegisterUnitDesc *registerUnit(RegisterUnitID ID) const noexcept;
+ [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(PhysicalRegister Reg) const noexcept;
+ [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(std::string_view Name) const noexcept;
+ [[nodiscard]] const SubRegIndexDesc *subRegIndex(SubRegIndex Index) const noexcept;
+ [[nodiscard]] const RegisterClassDesc *registerClass(RegisterClassID ID) const noexcept;
+ [[nodiscard]] const RegisterBankDesc *registerBank(RegisterBankID ID) const noexcept;
+
+ [[nodiscard]] bool isKnownPhysicalRegister(PhysicalRegister Reg) const noexcept {
+ return physicalRegister(Reg) != nullptr;
+ }
+
+ [[nodiscard]] LaneMask registerLaneMask(PhysicalRegister Reg) const noexcept;
+ [[nodiscard]] bool isValidLaneMask(PhysicalRegister Reg, LaneMask Lanes) const noexcept;
+ [[nodiscard]] bool regsOverlap(PhysicalRegister Left, PhysicalRegister Right) const noexcept;
+ [[nodiscard]] std::span<const PhysicalRegister> aliases(PhysicalRegister Reg) const noexcept;
+
+ [[nodiscard]] std::optional<PhysicalRegister> subRegister(PhysicalRegister SuperReg,
+ SubRegIndex Index) const noexcept;
+ [[nodiscard]] bool isSubRegister(PhysicalRegister SuperReg,
+ PhysicalRegister SubReg) const noexcept;
+ [[nodiscard]] std::span<const PhysicalRegister>
+ superRegisters(PhysicalRegister Reg) const noexcept;
+ [[nodiscard]] std::optional<SubRegIndex> composeSubRegIndices(SubRegIndex First,
+ SubRegIndex Second) const noexcept;
+
+ [[nodiscard]] bool registerClassContains(RegisterClassID Class,
+ PhysicalRegister Reg) const noexcept;
+ [[nodiscard]] bool isSubclass(RegisterClassID Candidate, RegisterClassID Required) const noexcept;
+ [[nodiscard]] bool registerClassesOverlap(RegisterClassID Left,
+ RegisterClassID Right) const noexcept;
+ [[nodiscard]] std::optional<RegisterClassID> subRegClass(RegisterClassID Source,
+ SubRegIndex Index) const noexcept;
+
+ [[nodiscard]] bool bankCoversClass(RegisterBankID Bank, RegisterClassID Class) const noexcept;
+ [[nodiscard]] bool bankContainsRegister(RegisterBankID Bank, PhysicalRegister Reg) const noexcept;
+ [[nodiscard]] bool registerBanksOverlap(RegisterBankID Left, RegisterBankID Right) const noexcept;
+
+ [[nodiscard]] Expected<void> validateRegisterMask(const RegisterMask &Mask) const;
+
+private:
+ explicit TargetRegisterInfo(TargetRegisterInfoDesc Description)
+ : Description(std::move(Description)) {}
+
+ [[nodiscard]] static std::uint64_t pairKey(std::uint32_t First, std::uint32_t Second) noexcept {
+ return (static_cast<std::uint64_t>(First) << 32U) | Second;
+ }
+
+ TargetRegisterInfoDesc Description;
+ std::unordered_map<RegisterUnitID, std::size_t> UnitToIndex;
+ std::unordered_map<PhysicalRegister, std::size_t> RegisterToIndex;
+ std::unordered_map<std::string, std::size_t> RegisterNameToIndex;
+ std::unordered_map<SubRegIndex, std::size_t> SubRegIndexToIndex;
+ std::unordered_map<RegisterClassID, std::size_t> ClassToIndex;
+ std::unordered_map<RegisterBankID, std::size_t> BankToIndex;
+ std::unordered_map<std::uint64_t, PhysicalRegister> SubRegisterMap;
+ std::unordered_map<std::uint64_t, SubRegIndex> CompositionMap;
+ std::unordered_map<std::uint64_t, RegisterClassID> SubRegClassMap;
+ std::unordered_set<std::uint64_t> SubclassRelations;
+ std::vector<std::vector<PhysicalRegister>> Aliases;
+ std::vector<std::vector<PhysicalRegister>> SuperRegisters;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_TARGETREGISTERINFO_H
diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt
index 68ebf04..3a45cec 100644
--- a/backend/lib/CodeGen/CMakeLists.txt
+++ b/backend/lib/CodeGen/CMakeLists.txt
@@ -23,7 +23,9 @@ set(
Register.cpp
RegisterMask.cpp
TargetConstant.cpp
+ TargetDescription.cpp
TargetFunctionState.cpp
+ TargetRegisterInfo.cpp
)
set(
RCCBackendCodeGenHeaders
@@ -49,7 +51,9 @@ set(
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/RegisterMask.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/StableID.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetConstant.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetDescription.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetFunctionState.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetRegisterInfo.h"
)
add_library(RCCBackendCodeGen ${RCCBackendCodeGenSources})
@@ -102,7 +106,9 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfo.cpp"
${RCCBackendCodeGenHeaders}
)
rcc_backend_register_tidy_files(
@@ -127,5 +133,7 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfo.cpp"
)
diff --git a/backend/lib/CodeGen/InstructionDescriptor.cpp b/backend/lib/CodeGen/InstructionDescriptor.cpp
index 9f4a178..3d97440 100644
--- a/backend/lib/CodeGen/InstructionDescriptor.cpp
+++ b/backend/lib/CodeGen/InstructionDescriptor.cpp
@@ -493,14 +493,6 @@ Expected<void> InstructionSet::validateOperand(const OperandDescriptor &Descript
auto Info = RegInfo.virtualRegisterInfo(*Reg);
if (!Info)
return std::unexpected(Info.error());
- if (Descriptor.RegBank && Info->get().Bank != Descriptor.RegBank) {
- return makeUnexpected(ErrorCode::VerificationError,
- "virtual register bank does not match operand descriptor");
- }
- if (Descriptor.RegClass && Info->get().RegClass != Descriptor.RegClass) {
- return makeUnexpected(ErrorCode::VerificationError,
- "virtual register class does not match operand descriptor");
- }
}
}
diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp
index a8fe15e..d365fd0 100644
--- a/backend/lib/CodeGen/MachineFunctionCloner.cpp
+++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp
@@ -15,6 +15,7 @@
#include "rcc/CodeGen/MachineOperand.h"
#include "rcc/CodeGen/MachineRegisterInfo.h"
#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
#include "rcc/CodeGen/TargetFunctionState.h"
#include <cstddef>
@@ -553,9 +554,9 @@ MachineFunctionCloneResult::operator=(MachineFunctionCloneResult &&) noexcept =
MachineFunctionCloneResult::~MachineFunctionCloneResult() = default;
Expected<MachineFunctionCloneResult> cloneMachineFunction(const MachineFunction &Source,
- const InstructionSet &Instructions,
+ const TargetDescription &Target,
MachineFunctionCloneOptions Options) {
- auto SourceVerification = MachineVerifier::verify(Source, Instructions);
+ auto SourceVerification = MachineVerifier::verify(Source, Target);
if (!SourceVerification)
return std::unexpected(SourceVerification.error());
if (!Options.NewStableID.isValid()) {
@@ -615,7 +616,7 @@ Expected<MachineFunctionCloneResult> cloneMachineFunction(const MachineFunction
return std::unexpected(CompilationStatus.error());
Result.ClonedFunction->compilationStatus() = std::move(*CompilationStatus);
Result.ClonedFunction->properties() = Source.properties();
- auto DestinationVerification = MachineVerifier::verify(*Result.ClonedFunction, Instructions);
+ auto DestinationVerification = MachineVerifier::verify(*Result.ClonedFunction, Target);
if (!DestinationVerification)
return std::unexpected(DestinationVerification.error());
return Result;
diff --git a/backend/lib/CodeGen/MachineIRBuilder.cpp b/backend/lib/CodeGen/MachineIRBuilder.cpp
index 74ca55b..7f6421b 100644
--- a/backend/lib/CodeGen/MachineIRBuilder.cpp
+++ b/backend/lib/CodeGen/MachineIRBuilder.cpp
@@ -20,16 +20,15 @@
namespace rcc {
-MachineInstrBuilder::MachineInstrBuilder(MachineFunction &Function,
- const InstructionSet &Instructions,
+MachineInstrBuilder::MachineInstrBuilder(MachineFunction &Function, const TargetDescription &Target,
const InstructionDescriptor &Descriptor,
MachineBasicBlock &InsertBlock,
MachineInstr *InsertBefore) noexcept
- : Function(&Function), Instructions(&Instructions), Descriptor(&Descriptor),
- InsertBlock(&InsertBlock), InsertBefore(InsertBefore) {}
+ : Function(&Function), Target(&Target), Descriptor(&Descriptor), InsertBlock(&InsertBlock),
+ InsertBefore(InsertBefore) {}
MachineInstrBuilder::MachineInstrBuilder(MachineInstrBuilder &&Other) noexcept
- : Function(Other.Function), Instructions(Other.Instructions), Descriptor(Other.Descriptor),
+ : Function(Other.Function), Target(Other.Target), Descriptor(Other.Descriptor),
InsertBlock(Other.InsertBlock), InsertBefore(Other.InsertBefore),
ExplicitOperands(std::move(Other.ExplicitOperands)),
ExtraImplicitOperands(std::move(Other.ExtraImplicitOperands)),
@@ -37,7 +36,7 @@ MachineInstrBuilder::MachineInstrBuilder(MachineInstrBuilder &&Other) noexcept
Flags(Other.Flags), Location(std::move(Other.Location)), Origin(Other.Origin),
IsBuilt(Other.IsBuilt) {
Other.Function = nullptr;
- Other.Instructions = nullptr;
+ Other.Target = nullptr;
Other.Descriptor = nullptr;
Other.InsertBlock = nullptr;
Other.InsertBefore = nullptr;
@@ -49,7 +48,7 @@ MachineInstrBuilder &MachineInstrBuilder::operator=(MachineInstrBuilder &&Other)
return *this;
Function = Other.Function;
- Instructions = Other.Instructions;
+ Target = Other.Target;
Descriptor = Other.Descriptor;
InsertBlock = Other.InsertBlock;
InsertBefore = Other.InsertBefore;
@@ -63,7 +62,7 @@ MachineInstrBuilder &MachineInstrBuilder::operator=(MachineInstrBuilder &&Other)
IsBuilt = Other.IsBuilt;
Other.Function = nullptr;
- Other.Instructions = nullptr;
+ Other.Target = nullptr;
Other.Descriptor = nullptr;
Other.InsertBlock = nullptr;
Other.InsertBefore = nullptr;
@@ -448,12 +447,12 @@ Expected<MachineInstr *> MachineInstrBuilder::build() {
}
}
- auto ExplicitValidation = Instructions->validateExplicitOperands(
+ auto ExplicitValidation = Target->validateExplicitOperands(
Descriptor->InstructionOpcode, ExplicitOperands, Function->registerInfo());
if (!ExplicitValidation)
return std::unexpected(ExplicitValidation.error());
- auto DefaultImplicit = Instructions->createDefaultImplicitOperands(*Descriptor);
+ auto DefaultImplicit = Target->createDefaultImplicitOperands(*Descriptor);
if (!DefaultImplicit)
return std::unexpected(DefaultImplicit.error());
@@ -463,19 +462,23 @@ Expected<MachineInstr *> MachineInstrBuilder::build() {
ImplicitOperands.push_back(&Operand);
for (const MachineOperand &Operand : ExtraImplicitOperands)
ImplicitOperands.push_back(&Operand);
- auto ImplicitValidation =
- Instructions->validateImplicitOperands(Descriptor->InstructionOpcode, ImplicitOperands);
+ auto ImplicitValidation = Target->validateImplicitOperands(
+ Descriptor->InstructionOpcode, ImplicitOperands, Function->registerInfo());
if (!ImplicitValidation)
return std::unexpected(ImplicitValidation.error());
const std::vector<MachineOperandTie> Ties = collectOperandTies();
- auto TieValidation =
- Instructions->validateOperandTies(Descriptor->InstructionOpcode, ExplicitOperands, Ties);
+ auto TieValidation = Target->validateOperandTies(Descriptor->InstructionOpcode, ExplicitOperands,
+ Ties, Function->registerInfo());
if (!TieValidation)
return std::unexpected(TieValidation.error());
- auto MemoryValidation =
- Instructions->validateMemOperands(Descriptor->InstructionOpcode, MemOperands);
+ auto EarlyClobberValidation =
+ Target->validateEarlyClobber(ExplicitOperands, ImplicitOperands, Ties);
+ if (!EarlyClobberValidation)
+ return std::unexpected(EarlyClobberValidation.error());
+
+ auto MemoryValidation = Target->validateMemOperands(Descriptor->InstructionOpcode, MemOperands);
if (!MemoryValidation)
return std::unexpected(MemoryValidation.error());
@@ -577,7 +580,7 @@ Expected<MachineInstrBuilder> MachineIRBuilder::buildInstr(Opcode InstructionOpc
if (InsertBlock == nullptr) {
return makeUnexpected(ErrorCode::InvalidArgument, "machine IR builder has no insertion point");
}
- const InstructionDescriptor *Descriptor = Instructions->descriptor(InstructionOpcode);
+ const InstructionDescriptor *Descriptor = Target->instructionDescriptor(InstructionOpcode);
if (Descriptor == nullptr) {
return makeUnexpected(ErrorCode::InvalidArgument,
"machine IR builder opcode is not registered");
@@ -589,7 +592,7 @@ Expected<MachineInstrBuilder> MachineIRBuilder::buildInstr(Opcode InstructionOpc
return makeUnexpected(ErrorCode::InvalidArgument,
"machine IR builder insertion cursor is stale");
}
- return MachineInstrBuilder(*Function, *Instructions, *Descriptor, *InsertBlock, InsertBefore);
+ return MachineInstrBuilder(*Function, *Target, *Descriptor, *InsertBlock, InsertBefore);
}
} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp
index 0c05a07..b507079 100644
--- a/backend/lib/CodeGen/MachineVerifier.cpp
+++ b/backend/lib/CodeGen/MachineVerifier.cpp
@@ -26,9 +26,9 @@ namespace {
class FunctionVerifier final {
public:
- FunctionVerifier(const MachineFunction &Function, const InstructionSet &Instructions,
+ FunctionVerifier(const MachineFunction &Function, const TargetDescription &Target,
MachineVerifierOptions Options) noexcept
- : Function(Function), Instructions(Instructions), Options(Options) {}
+ : Function(Function), Target(Target), Options(Options) {}
[[nodiscard]] Expected<void> run() {
if (Function.name().empty() || !Function.stableID().isValid())
@@ -346,10 +346,10 @@ private:
if (!LocalResult)
return LocalResult;
- const InstructionDescriptor *Descriptor = Instructions.descriptor(Instruction.opcode());
+ const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode());
if (Descriptor == nullptr)
return fail("live instruction uses an unregistered opcode");
- auto DescriptorResult = Instructions.validateInstruction(Instruction, Function.registerInfo());
+ auto DescriptorResult = Target.validateInstruction(Instruction, Function.registerInfo());
if (!DescriptorResult)
return failWith("instruction descriptor", DescriptorResult.error());
@@ -466,6 +466,9 @@ private:
return fail("block live-in list contains invalid or duplicate entries");
}
}
+ auto TargetLiveInResult = Target.validateBlockLiveIns(*Block);
+ if (!TargetLiveInResult)
+ return failWith("block live-ins", TargetLiveInResult.error());
}
return {};
}
@@ -509,6 +512,9 @@ private:
return fail("virtual register has an invalid or duplicate allocation hint");
}
}
+ auto TargetResult = Target.validateRegisterInfo(RegInfo);
+ if (!TargetResult)
+ return failWith("target register info", TargetResult.error());
return {};
}
@@ -577,7 +583,7 @@ private:
}
const MachineFunction &Function;
- const InstructionSet &Instructions;
+ const TargetDescription &Target;
MachineVerifierOptions Options;
std::unordered_set<const MachineBasicBlock *> RegisteredBlocks;
std::unordered_set<const MachineBasicBlock *> LinkedBlocks;
@@ -600,9 +606,9 @@ private:
} // namespace
Expected<void> MachineVerifier::verify(const MachineFunction &Function,
- const InstructionSet &Instructions,
+ const TargetDescription &Target,
MachineVerifierOptions Options) {
- return FunctionVerifier(Function, Instructions, Options).run();
+ return FunctionVerifier(Function, Target, Options).run();
}
} // namespace rcc
diff --git a/backend/lib/CodeGen/TargetDescription.cpp b/backend/lib/CodeGen/TargetDescription.cpp
new file mode 100644
index 0000000..3a6ea31
--- /dev/null
+++ b/backend/lib/CodeGen/TargetDescription.cpp
@@ -0,0 +1,610 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] const OperandDescriptor &descriptorForOperand(const InstructionDescriptor &Descriptor,
+ std::size_t Index) {
+ if (Index < Descriptor.Operands.size())
+ return Descriptor.Operands[Index];
+ return Descriptor.Operands.back();
+}
+
+[[nodiscard]] std::uint64_t minimumTypeSizeInBits(LLT Type) noexcept {
+ if (Type.isToken())
+ return 0;
+ const std::uint64_t ScalarSize = Type.scalarBitWidth();
+ if (!Type.isVector())
+ return ScalarSize;
+ return ScalarSize * Type.minimumElementCount();
+}
+
+[[nodiscard]] bool isPureDef(const MachineOperand &Operand) noexcept {
+ return Operand.isRegister() && Operand.isDef() && !Operand.isUse();
+}
+
+[[nodiscard]] bool isPureUse(const MachineOperand &Operand) noexcept {
+ return Operand.isRegister() && Operand.isUse() && !Operand.isDef();
+}
+
+} // namespace
+
+Expected<TargetDescription> TargetDescription::create(InstructionSet Instructions,
+ TargetRegisterInfo Registers) {
+ for (const InstructionDescriptor &Instruction : Instructions.descriptors()) {
+ for (const OperandDescriptor &Operand : Instruction.Operands) {
+ if (Operand.RegClass && Registers.registerClass(*Operand.RegClass) == nullptr) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "instruction operand references an unknown target register class");
+ }
+ if (Operand.RegBank && Registers.registerBank(*Operand.RegBank) == nullptr) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "instruction operand references an unknown target register bank");
+ }
+ if (Operand.RegClass && Operand.RegBank &&
+ !Registers.bankCoversClass(*Operand.RegBank, *Operand.RegClass)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "instruction operand register class is not covered by its bank");
+ }
+ }
+
+ auto ValidateImplicit =
+ [&Registers](const ImplicitRegisterDescriptor &Implicit) -> Expected<void> {
+ const PhysicalRegisterDesc *Register = Registers.physicalRegister(Implicit.Reg);
+ if (Register == nullptr) {
+ return makeUnexpected(
+ ErrorCode::InvalidArgument,
+ "instruction implicit operand references an unknown physical register");
+ }
+ if (Implicit.Renamable && (Register->IsConstant || Register->IsArtificial)) {
+ return makeUnexpected(
+ ErrorCode::InvalidArgument,
+ "renamable implicit operand references a constant or artificial register");
+ }
+ return {};
+ };
+ for (const ImplicitRegisterDescriptor &Implicit : Instruction.ImplicitDefs) {
+ auto Validation = ValidateImplicit(Implicit);
+ if (!Validation)
+ return std::unexpected(Validation.error());
+ }
+ for (const ImplicitRegisterDescriptor &Implicit : Instruction.ImplicitUses) {
+ auto Validation = ValidateImplicit(Implicit);
+ if (!Validation)
+ return std::unexpected(Validation.error());
+ }
+ for (const ImplicitRegisterDescriptor &Def : Instruction.ImplicitDefs) {
+ if (!Def.EarlyClobber)
+ continue;
+ if (std::ranges::any_of(Instruction.ImplicitUses,
+ [&Registers, Def](const ImplicitRegisterDescriptor &Use) {
+ return Registers.regsOverlap(Def.Reg, Use.Reg);
+ })) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "early-clobber implicit definition overlaps an implicit use");
+ }
+ }
+ }
+
+ return TargetDescription(std::move(Instructions), std::move(Registers));
+}
+
+Expected<void> TargetDescription::validateVirtualRegister(Register Reg,
+ const VirtualRegisterInfo &Info) const {
+ if (!Reg.isVirtual()) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "target virtual-register validation received a non-virtual register");
+ }
+
+ const RegisterBankDesc *Bank = nullptr;
+ if (Info.Bank) {
+ Bank = Registers.registerBank(*Info.Bank);
+ if (Bank == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register references an unknown target register bank");
+ }
+ }
+
+ const RegisterClassDesc *Class = nullptr;
+ if (Info.RegClass) {
+ Class = Registers.registerClass(*Info.RegClass);
+ if (Class == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register references an unknown target register class");
+ }
+ }
+
+ if (Info.Bank && Info.RegClass && !Registers.bankCoversClass(*Info.Bank, *Info.RegClass)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register class is not covered by its selected register bank");
+ }
+
+ if (Info.Type) {
+ const std::uint64_t MinimumSize = minimumTypeSizeInBits(*Info.Type);
+ if (MinimumSize == 0 && (Bank != nullptr || Class != nullptr)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "token virtual register cannot have a bank or register class");
+ }
+ if (Bank != nullptr && MinimumSize > Bank->MaximumSizeInBits) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register type exceeds its register bank maximum size");
+ }
+ if (Class != nullptr && MinimumSize > Class->SizeInBits) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register type exceeds its register class size");
+ }
+ }
+
+ for (const PhysicalRegister Hint : Info.AllocationHints) {
+ const PhysicalRegisterDesc *HintDesc = Registers.physicalRegister(Hint);
+ if (HintDesc == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register allocation hint names an unknown physical register");
+ }
+ if (HintDesc->IsConstant || HintDesc->IsArtificial) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "virtual register allocation hint names a constant or artificial register");
+ }
+ if (Info.RegClass && !Registers.registerClassContains(*Info.RegClass, Hint)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register allocation hint is outside its register class");
+ }
+ if (!Info.RegClass && Info.Bank && !Registers.bankContainsRegister(*Info.Bank, Hint)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register allocation hint is outside its register bank");
+ }
+ }
+ return {};
+}
+
+std::optional<RegisterClassID>
+TargetDescription::effectiveRegisterClass(const MachineOperand &Operand,
+ const MachineRegisterInfo &RegInfo) const {
+ const Register *Reg = Operand.getIf<Register>();
+ if (Reg == nullptr || !Reg->isVirtual())
+ return std::nullopt;
+
+ auto Info = RegInfo.virtualRegisterInfo(*Reg);
+ if (!Info)
+ return std::nullopt;
+ const std::optional<RegisterClassID> RegClass = Info->get().RegClass;
+ if (!RegClass)
+ return std::nullopt;
+ if (Operand.subReg().value() == 0)
+ return RegClass;
+ return Registers.subRegClass(*RegClass, Operand.subReg());
+}
+
+Expected<void> TargetDescription::validateTargetOperand(const OperandDescriptor *Descriptor,
+ const MachineOperand &Operand,
+ const MachineRegisterInfo &RegInfo) const {
+ if (const auto *Mask = Operand.getIf<RegisterMaskOperand>(); Mask != nullptr) {
+ if (!Mask->Mask) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "register-mask operand contains a null mask");
+ }
+ return Registers.validateRegisterMask(*Mask->Mask);
+ }
+
+ const Register *Reg = Operand.getIf<Register>();
+ if (Reg == nullptr)
+ return {};
+ if (Reg->isNoRegister()) {
+ if (Operand.subReg().value() != 0) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "noreg operand cannot carry a subregister index");
+ }
+ if (Operand.registerFlags().has(RegisterOperandFlag::Renamable)) {
+ return makeUnexpected(ErrorCode::VerificationError, "noreg operand cannot be renamable");
+ }
+ return {};
+ }
+
+ if (const auto PhysReg = Reg->physicalRegister()) {
+ const PhysicalRegister PhysRegValue = *PhysReg;
+ const PhysicalRegisterDesc *PhysRegDesc = Registers.physicalRegister(PhysRegValue);
+ if (PhysRegDesc == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "operand references an unknown physical register");
+ }
+ if (Operand.subReg().value() != 0) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "physical register operand cannot carry a subregister index");
+ }
+ if (Descriptor != nullptr && Descriptor->RegClass &&
+ !Registers.registerClassContains(*Descriptor->RegClass, PhysRegValue)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "physical register operand is outside its required register class");
+ }
+ if (Descriptor != nullptr && Descriptor->RegBank &&
+ !Registers.bankContainsRegister(*Descriptor->RegBank, PhysRegValue)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "physical register operand is outside its required register bank");
+ }
+ const bool IsReservedOrAlias = std::ranges::any_of(
+ RegInfo.reservedRegisters(), [this, PhysRegValue](PhysicalRegister Reserved) {
+ return Registers.regsOverlap(Reserved, PhysRegValue);
+ });
+ if (Operand.registerFlags().has(RegisterOperandFlag::Renamable) &&
+ (IsReservedOrAlias || PhysRegDesc->IsConstant || PhysRegDesc->IsArtificial)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "fixed physical register operand cannot be renamable");
+ }
+ return {};
+ }
+
+ if (Operand.registerFlags().has(RegisterOperandFlag::Renamable)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register operand cannot be renamable");
+ }
+
+ auto Info = RegInfo.virtualRegisterInfo(*Reg);
+ if (!Info)
+ return std::unexpected(Info.error());
+ auto RegisterValidation = validateVirtualRegister(*Reg, Info->get());
+ if (!RegisterValidation)
+ return std::unexpected(RegisterValidation.error());
+
+ if (Operand.subReg().value() != 0) {
+ if (Registers.subRegIndex(Operand.subReg()) == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "operand references an unknown subregister index");
+ }
+ const std::optional<RegisterClassID> RegClass = Info->get().RegClass;
+ if (!RegClass) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic virtual register cannot carry a subregister index");
+ }
+ if (!Registers.subRegClass(*RegClass, Operand.subReg())) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "virtual register class does not support the operand subregister index");
+ }
+ }
+
+ if (Descriptor != nullptr && Descriptor->RegBank && Info->get().Bank != Descriptor->RegBank) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register bank does not match the operand descriptor");
+ }
+ if (Descriptor != nullptr && Descriptor->RegClass) {
+ const auto EffectiveClass = effectiveRegisterClass(Operand, RegInfo);
+ if (!EffectiveClass || !Registers.isSubclass(*EffectiveClass, *Descriptor->RegClass)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "virtual register class does not satisfy the operand descriptor");
+ }
+ }
+ return {};
+}
+
+Expected<void>
+TargetDescription::validateExplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand> Operands,
+ const MachineRegisterInfo &RegInfo) const {
+ auto StructuralValidation =
+ Instructions.validateExplicitOperands(InstructionOpcode, Operands, RegInfo);
+ if (!StructuralValidation)
+ return std::unexpected(StructuralValidation.error());
+
+ const InstructionDescriptor *Descriptor = Instructions.descriptor(InstructionOpcode);
+ if (Descriptor == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered");
+ }
+ for (std::size_t Index = 0; Index < Operands.size(); ++Index) {
+ auto TargetValidation =
+ validateTargetOperand(&descriptorForOperand(*Descriptor, Index), Operands[Index], RegInfo);
+ if (!TargetValidation)
+ return std::unexpected(TargetValidation.error());
+ }
+ return {};
+}
+
+Expected<void>
+TargetDescription::validateTieCompatibility(const MachineOperand &Def, const MachineOperand &Use,
+ const MachineRegisterInfo &RegInfo) const {
+ const Register *DefReg = Def.getIf<Register>();
+ const Register *UseReg = Use.getIf<Register>();
+ if (DefReg == nullptr || UseReg == nullptr) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "target tie validation received a non-register operand");
+ }
+ if (DefReg->isNoRegister() || UseReg->isNoRegister())
+ return {};
+
+ const auto DefPhys = DefReg->physicalRegister();
+ const auto UsePhys = UseReg->physicalRegister();
+ if (DefPhys && UsePhys) {
+ if (*DefPhys != *UsePhys) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied physical register operands must name the same register");
+ }
+ return {};
+ }
+
+ const auto DefClass = effectiveRegisterClass(Def, RegInfo);
+ const auto UseClass = effectiveRegisterClass(Use, RegInfo);
+ if (DefClass && UseClass && !Registers.registerClassesOverlap(*DefClass, *UseClass)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied virtual register classes have no compatible physical register");
+ }
+
+ auto BankFor = [&RegInfo](Register Reg) -> std::optional<RegisterBankID> {
+ if (!Reg.isVirtual())
+ return std::nullopt;
+ auto Info = RegInfo.virtualRegisterInfo(Reg);
+ return Info ? Info->get().Bank : std::nullopt;
+ };
+ const auto DefBank = BankFor(*DefReg);
+ const auto UseBank = BankFor(*UseReg);
+
+ auto ClassIntersectsBank = [this](RegisterClassID ClassID, RegisterBankID BankID) {
+ const RegisterClassDesc *Class = Registers.registerClass(ClassID);
+ return Class != nullptr &&
+ std::ranges::any_of(Class->Registers, [this, BankID](PhysicalRegister Reg) {
+ return Registers.bankContainsRegister(BankID, Reg);
+ });
+ };
+
+ if (DefPhys && UseClass && !Registers.registerClassContains(*UseClass, *DefPhys)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied physical register is outside the virtual use register class");
+ }
+ if (UsePhys && DefClass && !Registers.registerClassContains(*DefClass, *UsePhys)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied physical register is outside the virtual def register class");
+ }
+ if (DefPhys && UseBank && !Registers.bankContainsRegister(*UseBank, *DefPhys)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied physical register is outside the virtual use register bank");
+ }
+ if (UsePhys && DefBank && !Registers.bankContainsRegister(*DefBank, *UsePhys)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied physical register is outside the virtual def register bank");
+ }
+ if (DefClass && UseBank && !ClassIntersectsBank(*DefClass, *UseBank)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied def register class and use register bank do not intersect");
+ }
+ if (UseClass && DefBank && !ClassIntersectsBank(*UseClass, *DefBank)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied use register class and def register bank do not intersect");
+ }
+ if (!DefClass && !UseClass && DefBank && UseBank &&
+ !Registers.registerBanksOverlap(*DefBank, *UseBank)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "tied virtual register banks have no compatible physical register");
+ }
+ return {};
+}
+
+Expected<void> TargetDescription::validateOperandTies(
+ Opcode InstructionOpcode, std::span<const MachineOperand> ExplicitOperands,
+ std::span<const MachineOperandTie> Ties, const MachineRegisterInfo &RegInfo) const {
+ auto StructuralValidation =
+ Instructions.validateOperandTies(InstructionOpcode, ExplicitOperands, Ties);
+ if (!StructuralValidation)
+ return std::unexpected(StructuralValidation.error());
+
+ for (const MachineOperandTie Tie : Ties) {
+ auto Compatibility = validateTieCompatibility(ExplicitOperands[Tie.DefOperand],
+ ExplicitOperands[Tie.UseOperand], RegInfo);
+ if (!Compatibility)
+ return std::unexpected(Compatibility.error());
+ }
+ return {};
+}
+
+Expected<void>
+TargetDescription::validateImplicitOperands(Opcode InstructionOpcode,
+ std::span<const MachineOperand *const> ImplicitOperands,
+ const MachineRegisterInfo &RegInfo) const {
+ auto StructuralValidation =
+ Instructions.validateImplicitOperands(InstructionOpcode, ImplicitOperands);
+ if (!StructuralValidation)
+ return std::unexpected(StructuralValidation.error());
+
+ for (const MachineOperand *Operand : ImplicitOperands) {
+ if (Operand == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "implicit operand sequence contains a null operand");
+ }
+ auto TargetValidation = validateTargetOperand(nullptr, *Operand, RegInfo);
+ if (!TargetValidation)
+ return std::unexpected(TargetValidation.error());
+ }
+ return {};
+}
+
+Expected<void>
+TargetDescription::validateEarlyClobber(std::span<const MachineOperand> ExplicitOperands,
+ std::span<const MachineOperand *const> ImplicitOperands,
+ std::span<const MachineOperandTie> Ties) const {
+ struct IndexedOperand final {
+ const MachineOperand *Operand = nullptr;
+ std::optional<std::size_t> ExplicitIndex;
+ };
+
+ std::vector<IndexedOperand> Operands;
+ Operands.reserve(ExplicitOperands.size() + ImplicitOperands.size());
+ for (std::size_t Index = 0; Index < ExplicitOperands.size(); ++Index)
+ Operands.push_back(IndexedOperand{&ExplicitOperands[Index], Index});
+ for (const MachineOperand *Operand : ImplicitOperands)
+ Operands.push_back(IndexedOperand{Operand, std::nullopt});
+
+ auto AreTied = [Ties](const IndexedOperand &Def, const IndexedOperand &Use) {
+ if (!Def.ExplicitIndex || !Use.ExplicitIndex)
+ return false;
+ return std::ranges::any_of(Ties, [&Def, &Use](MachineOperandTie Tie) {
+ return Tie.DefOperand == *Def.ExplicitIndex && Tie.UseOperand == *Use.ExplicitIndex;
+ });
+ };
+
+ for (const IndexedOperand &DefEntry : Operands) {
+ if (DefEntry.Operand == nullptr)
+ continue;
+ const MachineOperand &Def = *DefEntry.Operand;
+ if (!Def.isDef() || !Def.registerFlags().has(RegisterOperandFlag::EarlyClobber))
+ continue;
+
+ const Register *DefReg = Def.getIf<Register>();
+ if (DefReg == nullptr || DefReg->isNoRegister())
+ continue;
+ for (const IndexedOperand &UseEntry : Operands) {
+ if (UseEntry.Operand == nullptr || UseEntry.Operand == DefEntry.Operand)
+ continue;
+ const MachineOperand &Use = *UseEntry.Operand;
+ if (!Use.isUse() || AreTied(DefEntry, UseEntry))
+ continue;
+
+ const Register *UseReg = Use.getIf<Register>();
+ if (UseReg == nullptr || UseReg->isNoRegister())
+ continue;
+ if (DefReg->isVirtual() && UseReg->isVirtual() && *DefReg == *UseReg) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "early-clobber virtual definition overlaps an untied use");
+ }
+
+ const auto DefPhys = DefReg->physicalRegister();
+ const auto UsePhys = UseReg->physicalRegister();
+ if (DefPhys && UsePhys && Registers.regsOverlap(*DefPhys, *UsePhys)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "early-clobber physical definition overlaps an untied use");
+ }
+ }
+ }
+ return {};
+}
+
+Expected<void> TargetDescription::validateInstruction(const MachineInstr &Instruction,
+ const MachineRegisterInfo &RegInfo) const {
+ auto StructuralValidation = Instructions.validateInstruction(Instruction, RegInfo);
+ if (!StructuralValidation)
+ return std::unexpected(StructuralValidation.error());
+
+ const auto Operands = Instruction.operands();
+ std::size_t ExplicitCount = 0;
+ while (ExplicitCount < Operands.size() && !Operands[ExplicitCount].isImplicit()) {
+ ++ExplicitCount;
+ }
+
+ auto ExplicitValidation =
+ validateExplicitOperands(Instruction.opcode(), Operands.first(ExplicitCount), RegInfo);
+ if (!ExplicitValidation)
+ return std::unexpected(ExplicitValidation.error());
+
+ std::vector<const MachineOperand *> ImplicitOperands;
+ for (std::size_t Index = ExplicitCount; Index < Operands.size(); ++Index)
+ ImplicitOperands.push_back(&Operands[Index]);
+ auto ImplicitValidation =
+ validateImplicitOperands(Instruction.opcode(), ImplicitOperands, RegInfo);
+ if (!ImplicitValidation)
+ return std::unexpected(ImplicitValidation.error());
+
+ std::vector<MachineOperandTie> Ties;
+ for (std::size_t Index = 0; Index < ExplicitCount; ++Index) {
+ const MachineOperand &Operand = Operands[Index];
+ if (!Operand.tiedOperandIndex() || Index > *Operand.tiedOperandIndex()) {
+ continue;
+ }
+ const std::size_t OtherIndex = *Operand.tiedOperandIndex();
+ if (isPureDef(Operand) && isPureUse(Operands[OtherIndex])) {
+ Ties.push_back(MachineOperandTie{Index, OtherIndex});
+ } else {
+ Ties.push_back(MachineOperandTie{OtherIndex, Index});
+ }
+ }
+ auto TieValidation =
+ validateOperandTies(Instruction.opcode(), Operands.first(ExplicitCount), Ties, RegInfo);
+ if (!TieValidation)
+ return std::unexpected(TieValidation.error());
+
+ return validateEarlyClobber(Operands.first(ExplicitCount), ImplicitOperands, Ties);
+}
+
+Expected<void> TargetDescription::validateRegisterInfo(const MachineRegisterInfo &RegInfo) const {
+ for (const PhysicalRegisterLiveIn &LiveIn : RegInfo.liveIns()) {
+ if (!Registers.isKnownPhysicalRegister(LiveIn.PhysReg)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "function live-in names an unknown physical register");
+ }
+ if (!LiveIn.InitialVirtualRegister)
+ continue;
+ auto Info = RegInfo.virtualRegisterInfo(*LiveIn.InitialVirtualRegister);
+ if (!Info)
+ return std::unexpected(Info.error());
+ const std::optional<RegisterClassID> RegClass = Info->get().RegClass;
+ if (RegClass && !Registers.registerClassContains(*RegClass, LiveIn.PhysReg)) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "function live-in physical register is outside its initial virtual register class");
+ }
+ const std::optional<RegisterBankID> Bank = Info->get().Bank;
+ if (Bank && !Registers.bankContainsRegister(*Bank, LiveIn.PhysReg)) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "function live-in physical register is outside its initial virtual register bank");
+ }
+ }
+ for (const PhysicalRegister Reg : RegInfo.reservedRegisters()) {
+ if (!Registers.isKnownPhysicalRegister(Reg)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "reserved-register list names an unknown physical register");
+ }
+ }
+
+ const auto VirtualRegisters = RegInfo.virtualRegisters();
+ for (std::size_t Index = 0; Index < VirtualRegisters.size(); ++Index) {
+ const VirtualRegisterInfo &Info = VirtualRegisters[Index];
+ if (Info.IsTombstone)
+ continue;
+ auto Reg = Register::makeVirtual(Index);
+ if (!Reg)
+ return std::unexpected(Reg.error());
+ auto Validation = validateVirtualRegister(*Reg, Info);
+ if (!Validation)
+ return std::unexpected(Validation.error());
+ for (const PhysicalRegister Hint : Info.AllocationHints) {
+ if (std::ranges::any_of(RegInfo.reservedRegisters(), [this, Hint](PhysicalRegister Reserved) {
+ return Registers.regsOverlap(Reserved, Hint);
+ })) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "virtual register allocation hint overlaps a reserved physical register");
+ }
+ }
+ }
+ return {};
+}
+
+Expected<void> TargetDescription::validateBlockLiveIns(const MachineBasicBlock &Block) const {
+ for (const MachineBasicBlockLiveIn &LiveIn : Block.liveIns()) {
+ if (!Registers.isKnownPhysicalRegister(LiveIn.PhysReg)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "block live-in names an unknown physical register");
+ }
+ if (!Registers.isValidLaneMask(LiveIn.PhysReg, LiveIn.Lanes)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "block live-in lane mask is not valid for its physical register");
+ }
+ }
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/TargetRegisterInfo.cpp b/backend/lib/CodeGen/TargetRegisterInfo.cpp
new file mode 100644
index 0000000..7bd4834
--- /dev/null
+++ b/backend/lib/CodeGen/TargetRegisterInfo.cpp
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <ranges>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] std::unexpected<Error> invalidDescription(std::string Message) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target register description: " + std::move(Message));
+}
+
+[[nodiscard]] bool isPowerOfTwo(std::uint32_t Value) noexcept {
+ return Value != 0 && std::has_single_bit(Value);
+}
+
+template <typename RangeT, typename ValueT>
+[[nodiscard]] bool contains(const RangeT &Range, const ValueT &Value) {
+ return std::ranges::find(Range, Value) != std::ranges::end(Range);
+}
+
+[[nodiscard]] const RegisterUnitLane *findUnit(const PhysicalRegisterDesc &Register,
+ RegisterUnitID Unit) noexcept {
+ const auto Position = std::ranges::find(Register.Units, Unit, &RegisterUnitLane::Unit);
+ return Position == Register.Units.end() ? nullptr : &*Position;
+}
+
+} // namespace
+
+Expected<TargetRegisterInfo> TargetRegisterInfo::create(TargetRegisterInfoDesc Description) {
+ if (Description.RegisterUnits.empty() || Description.PhysicalRegisters.empty() ||
+ Description.RegisterClasses.empty() || Description.RegisterBanks.empty()) {
+ return invalidDescription(
+ "register units, physical registers, classes, and banks must be nonempty");
+ }
+
+ TargetRegisterInfo Result(std::move(Description));
+
+ std::unordered_set<std::string> UnitNames;
+ for (std::size_t Index = 0; Index < Result.Description.RegisterUnits.size(); ++Index) {
+ const RegisterUnitDesc &Unit = Result.Description.RegisterUnits[Index];
+ if (Unit.ID.value() == 0 || Unit.Name.empty()) {
+ return invalidDescription("register unit requires a nonzero ID and name");
+ }
+ if (!Result.UnitToIndex.emplace(Unit.ID, Index).second || !UnitNames.insert(Unit.Name).second) {
+ return invalidDescription("register unit IDs and names must be unique");
+ }
+ }
+
+ for (std::size_t Index = 0; Index < Result.Description.PhysicalRegisters.size(); ++Index) {
+ const PhysicalRegisterDesc &Register = Result.Description.PhysicalRegisters[Index];
+ if (!Register.Reg.isValid() || Register.Name.empty()) {
+ return invalidDescription("physical register requires a valid number and name");
+ }
+ if (!Result.RegisterToIndex.emplace(Register.Reg, Index).second ||
+ !Result.RegisterNameToIndex.emplace(Register.Name, Index).second) {
+ return invalidDescription("physical register numbers and names must be unique");
+ }
+ }
+
+ std::unordered_set<std::string> SubRegNames;
+ for (std::size_t Index = 0; Index < Result.Description.SubRegIndices.size(); ++Index) {
+ const SubRegIndexDesc &SubReg = Result.Description.SubRegIndices[Index];
+ if (SubReg.ID.value() == 0 || SubReg.Name.empty() || SubReg.Lanes.empty() ||
+ SubReg.SizeInBits == 0 ||
+ static_cast<std::uint64_t>(SubReg.OffsetInBits) + SubReg.SizeInBits >
+ std::numeric_limits<std::uint32_t>::max()) {
+ return invalidDescription(
+ "subregister index requires a nonzero ID, name, lanes, and valid bit range");
+ }
+ if (!Result.SubRegIndexToIndex.emplace(SubReg.ID, Index).second ||
+ !SubRegNames.insert(SubReg.Name).second) {
+ return invalidDescription("subregister index IDs and names must be unique");
+ }
+ }
+
+ std::unordered_set<std::string> ClassNames;
+ for (std::size_t Index = 0; Index < Result.Description.RegisterClasses.size(); ++Index) {
+ const RegisterClassDesc &Class = Result.Description.RegisterClasses[Index];
+ if (Class.ID.value() == 0 || Class.Name.empty()) {
+ return invalidDescription("register class requires a nonzero ID and name");
+ }
+ if (!Result.ClassToIndex.emplace(Class.ID, Index).second ||
+ !ClassNames.insert(Class.Name).second) {
+ return invalidDescription("register class IDs and names must be unique");
+ }
+ }
+
+ std::unordered_set<std::string> BankNames;
+ for (std::size_t Index = 0; Index < Result.Description.RegisterBanks.size(); ++Index) {
+ const RegisterBankDesc &Bank = Result.Description.RegisterBanks[Index];
+ if (Bank.ID.value() == 0 || Bank.Name.empty() || Bank.MaximumSizeInBits == 0) {
+ return invalidDescription("register bank requires a nonzero ID, name, and maximum size");
+ }
+ if (!Result.BankToIndex.emplace(Bank.ID, Index).second || !BankNames.insert(Bank.Name).second) {
+ return invalidDescription("register bank IDs and names must be unique");
+ }
+ }
+
+ std::unordered_set<RegisterUnitID> ReferencedUnits;
+ for (const PhysicalRegisterDesc &Register : Result.Description.PhysicalRegisters) {
+ if (Register.Units.empty())
+ return invalidDescription("physical register must reference at least one register unit");
+
+ std::unordered_set<RegisterUnitID> SeenUnits;
+ LaneMask RegisterLanes;
+ for (const RegisterUnitLane &Unit : Register.Units) {
+ if (Result.registerUnit(Unit.Unit) == nullptr || Unit.Lanes.empty() ||
+ !SeenUnits.insert(Unit.Unit).second) {
+ return invalidDescription(
+ "physical register has an unknown, empty-lane, or duplicate register unit");
+ }
+ ReferencedUnits.insert(Unit.Unit);
+ RegisterLanes = RegisterLanes | Unit.Lanes;
+ }
+ if (RegisterLanes.empty())
+ return invalidDescription("physical register has no addressable lanes");
+ }
+ if (ReferencedUnits.size() != Result.Description.RegisterUnits.size())
+ return invalidDescription("every declared register unit must be used by a physical register");
+
+ const std::size_t RegisterCount = Result.Description.PhysicalRegisters.size();
+ std::vector<std::vector<bool>> IsSubRegister(RegisterCount,
+ std::vector<bool>(RegisterCount, false));
+ for (const PhysicalSubRegisterDesc &Mapping : Result.Description.PhysicalSubRegisters) {
+ const PhysicalRegisterDesc *Super = Result.physicalRegister(Mapping.SuperReg);
+ const PhysicalRegisterDesc *Sub = Result.physicalRegister(Mapping.SubReg);
+ const SubRegIndexDesc *Index = Result.subRegIndex(Mapping.Index);
+ if (Super == nullptr || Sub == nullptr || Index == nullptr ||
+ Mapping.SuperReg == Mapping.SubReg) {
+ return invalidDescription(
+ "physical subregister mapping references an unknown or identical register");
+ }
+
+ const std::uint64_t Key = pairKey(Mapping.SuperReg.number(), Mapping.Index.value());
+ if (!Result.SubRegisterMap.emplace(Key, Mapping.SubReg).second)
+ return invalidDescription("physical subregister mapping key must be unique");
+
+ LaneMask ProjectedLanes;
+ for (const RegisterUnitLane &SubUnit : Sub->Units) {
+ const RegisterUnitLane *SuperUnit = findUnit(*Super, SubUnit.Unit);
+ if (SuperUnit == nullptr) {
+ return invalidDescription(
+ "physical subregister units must be a subset of its superregister units");
+ }
+ ProjectedLanes = ProjectedLanes | SuperUnit->Lanes;
+ }
+ if (ProjectedLanes != Index->Lanes) {
+ return invalidDescription(
+ "physical subregister units do not match the subregister index lane mask");
+ }
+
+ const std::size_t SuperIndex = Result.RegisterToIndex.at(Mapping.SuperReg);
+ const std::size_t SubIndex = Result.RegisterToIndex.at(Mapping.SubReg);
+ IsSubRegister[SuperIndex][SubIndex] = true;
+ }
+
+ for (std::size_t Through = 0; Through < RegisterCount; ++Through) {
+ for (std::size_t Super = 0; Super < RegisterCount; ++Super) {
+ if (!IsSubRegister[Super][Through])
+ continue;
+ for (std::size_t Sub = 0; Sub < RegisterCount; ++Sub)
+ IsSubRegister[Super][Sub] = IsSubRegister[Super][Sub] || IsSubRegister[Through][Sub];
+ }
+ }
+ for (std::size_t Index = 0; Index < RegisterCount; ++Index) {
+ if (IsSubRegister[Index][Index])
+ return invalidDescription("physical subregister graph must be acyclic");
+ }
+
+ Result.SuperRegisters.resize(RegisterCount);
+ for (std::size_t Sub = 0; Sub < RegisterCount; ++Sub) {
+ for (std::size_t Super = 0; Super < RegisterCount; ++Super) {
+ if (IsSubRegister[Super][Sub]) {
+ Result.SuperRegisters[Sub].push_back(Result.Description.PhysicalRegisters[Super].Reg);
+ }
+ }
+ }
+
+ for (const SubRegIndexComposition &Composition : Result.Description.SubRegCompositions) {
+ const SubRegIndexDesc *First = Result.subRegIndex(Composition.First);
+ const SubRegIndexDesc *Second = Result.subRegIndex(Composition.Second);
+ const SubRegIndexDesc *Composed = Result.subRegIndex(Composition.Result);
+ if (First == nullptr || Second == nullptr || Composed == nullptr) {
+ return invalidDescription("subregister composition references an unknown index");
+ }
+ const std::uint64_t ComposedOffset =
+ static_cast<std::uint64_t>(First->OffsetInBits) + Second->OffsetInBits;
+ if (static_cast<std::uint64_t>(Second->OffsetInBits) + Second->SizeInBits > First->SizeInBits ||
+ Composed->SizeInBits != Second->SizeInBits || Composed->OffsetInBits != ComposedOffset) {
+ return invalidDescription("subregister composition has inconsistent bit ranges");
+ }
+ const std::uint64_t Key = pairKey(Composition.First.value(), Composition.Second.value());
+ if (!Result.CompositionMap.emplace(Key, Composition.Result).second)
+ return invalidDescription("subregister composition key must be unique");
+ }
+
+ for (const SubRegIndexComposition &Composition : Result.Description.SubRegCompositions) {
+ for (const PhysicalRegisterDesc &Super : Result.Description.PhysicalRegisters) {
+ const auto Intermediate = Result.subRegister(Super.Reg, Composition.First);
+ if (!Intermediate)
+ continue;
+ const auto Final = Result.subRegister(*Intermediate, Composition.Second);
+ if (!Final)
+ continue;
+ const auto Composed = Result.subRegister(Super.Reg, Composition.Result);
+ if (!Composed || *Composed != *Final) {
+ return invalidDescription(
+ "subregister composition conflicts with physical subregister mappings");
+ }
+ }
+ }
+
+ for (const RegisterBankDesc &Bank : Result.Description.RegisterBanks) {
+ if (Bank.RegisterClasses.empty())
+ return invalidDescription("register bank must cover at least one register class");
+
+ std::unordered_set<RegisterClassID> SeenClasses;
+ for (const RegisterClassID ClassID : Bank.RegisterClasses) {
+ const RegisterClassDesc *Class = Result.registerClass(ClassID);
+ if (Class == nullptr || !SeenClasses.insert(ClassID).second) {
+ return invalidDescription("register bank has an unknown or duplicate register class");
+ }
+ if (Class->SizeInBits > Bank.MaximumSizeInBits) {
+ return invalidDescription(
+ "register bank maximum size is smaller than a covered register class");
+ }
+ }
+ }
+
+ for (const RegisterClassDesc &Class : Result.Description.RegisterClasses) {
+ if (Class.Registers.empty() || Class.SizeInBits == 0 ||
+ Class.SpillSizeInBits < Class.SizeInBits || !isPowerOfTwo(Class.SpillAlignmentInBits) ||
+ Class.Lanes.empty()) {
+ return invalidDescription(
+ "register class requires members, sizes, power-of-two spill alignment, and lanes");
+ }
+
+ std::unordered_set<PhysicalRegister> Members;
+ for (const PhysicalRegister Reg : Class.Registers) {
+ if (!Result.isKnownPhysicalRegister(Reg) || !Members.insert(Reg).second ||
+ !Result.isValidLaneMask(Reg, Class.Lanes)) {
+ return invalidDescription(
+ "register class has an unknown, duplicate, or lane-incompatible member");
+ }
+ }
+
+ std::unordered_set<PhysicalRegister> AllocationRegisters;
+ for (const PhysicalRegister Reg : Class.AllocationOrder) {
+ if (!Members.contains(Reg) || !AllocationRegisters.insert(Reg).second) {
+ return invalidDescription(
+ "register class allocation order must contain unique class members");
+ }
+ }
+ if ((Class.Allocatable && Class.AllocationOrder.empty()) ||
+ (!Class.Allocatable && !Class.AllocationOrder.empty())) {
+ return invalidDescription(
+ "allocatable classes require an allocation order and nonallocatable classes forbid one");
+ }
+
+ std::unordered_set<SubRegIndex> SupportedSubRegs;
+ LaneMask CoveredSubRegLanes;
+ for (const SubRegIndex SubReg : Class.SupportedSubRegs) {
+ const SubRegIndexDesc *SubRegDesc = Result.subRegIndex(SubReg);
+ if (SubRegDesc == nullptr || !SupportedSubRegs.insert(SubReg).second ||
+ !Class.Lanes.contains(SubRegDesc->Lanes)) {
+ return invalidDescription(
+ "register class has an unknown, duplicate, or lane-incompatible subregister index");
+ }
+ CoveredSubRegLanes = CoveredSubRegLanes | SubRegDesc->Lanes;
+ }
+ if (Class.CoveredBySubRegs &&
+ (Class.SupportedSubRegs.empty() || !CoveredSubRegLanes.contains(Class.Lanes))) {
+ return invalidDescription(
+ "subregister-covered class must cover every class lane with supported indices");
+ }
+
+ std::unordered_set<RegisterClassID> SuperClasses;
+ for (const RegisterClassID SuperClass : Class.SuperClasses) {
+ if (Result.registerClass(SuperClass) == nullptr || SuperClass == Class.ID ||
+ !SuperClasses.insert(SuperClass).second) {
+ return invalidDescription("register class has an unknown, self, or duplicate superclass");
+ }
+ Result.SubclassRelations.insert(pairKey(Class.ID.value(), SuperClass.value()));
+ }
+ Result.SubclassRelations.insert(pairKey(Class.ID.value(), Class.ID.value()));
+
+ if (Result.registerBank(Class.DefaultBank) == nullptr ||
+ !Result.bankCoversClass(Class.DefaultBank, Class.ID)) {
+ return invalidDescription(
+ "register class default bank must exist and explicitly cover the class");
+ }
+ }
+
+ bool RelationsChanged = true;
+ while (RelationsChanged) {
+ RelationsChanged = false;
+ for (const RegisterClassDesc &Candidate : Result.Description.RegisterClasses) {
+ for (const RegisterClassDesc &Intermediate : Result.Description.RegisterClasses) {
+ if (!Result.isSubclass(Candidate.ID, Intermediate.ID))
+ continue;
+ for (const RegisterClassDesc &Required : Result.Description.RegisterClasses) {
+ if (!Result.isSubclass(Intermediate.ID, Required.ID))
+ continue;
+ RelationsChanged |=
+ Result.SubclassRelations.insert(pairKey(Candidate.ID.value(), Required.ID.value()))
+ .second;
+ }
+ }
+ }
+ }
+
+ for (const RegisterClassDesc &Class : Result.Description.RegisterClasses) {
+ for (const RegisterClassDesc &Other : Result.Description.RegisterClasses) {
+ if (Class.ID != Other.ID && Result.isSubclass(Class.ID, Other.ID) &&
+ Result.isSubclass(Other.ID, Class.ID)) {
+ return invalidDescription("register class hierarchy must be acyclic");
+ }
+ }
+ for (const RegisterClassID SuperID : Class.SuperClasses) {
+ const RegisterClassDesc *Super = Result.registerClass(SuperID);
+ if (Super == nullptr || Class.SizeInBits != Super->SizeInBits ||
+ Class.SpillSizeInBits != Super->SpillSizeInBits ||
+ Class.SpillAlignmentInBits != Super->SpillAlignmentInBits ||
+ !std::ranges::all_of(Class.Registers, [Super](PhysicalRegister Reg) {
+ return contains(Super->Registers, Reg);
+ })) {
+ return invalidDescription(
+ "register class must be storage-compatible with each declared superclass");
+ }
+ }
+ }
+
+ for (const SubRegClassMapping &Mapping : Result.Description.SubRegClassMappings) {
+ const RegisterClassDesc *Source = Result.registerClass(Mapping.SourceClass);
+ const SubRegIndexDesc *Index = Result.subRegIndex(Mapping.Index);
+ const RegisterClassDesc *Mapped = Result.registerClass(Mapping.ResultClass);
+ if (Source == nullptr || Index == nullptr || Mapped == nullptr ||
+ !contains(Source->SupportedSubRegs, Mapping.Index)) {
+ return invalidDescription(
+ "subregister class mapping references an unknown or unsupported entity");
+ }
+ if (Mapped->SizeInBits != Index->SizeInBits) {
+ return invalidDescription("subregister class mapping result size must match its index size");
+ }
+ if (static_cast<std::uint64_t>(Index->OffsetInBits) + Index->SizeInBits > Source->SizeInBits) {
+ return invalidDescription("subregister class mapping index exceeds its source class size");
+ }
+
+ const std::uint64_t Key = pairKey(Mapping.SourceClass.value(), Mapping.Index.value());
+ if (!Result.SubRegClassMap.emplace(Key, Mapping.ResultClass).second) {
+ return invalidDescription("subregister class mapping key must be unique");
+ }
+ for (const PhysicalRegister SuperReg : Source->Registers) {
+ const auto SubReg = Result.subRegister(SuperReg, Mapping.Index);
+ if (!SubReg || !Result.registerClassContains(Mapping.ResultClass, *SubReg)) {
+ return invalidDescription("subregister class mapping does not cover every source register");
+ }
+ }
+ }
+
+ for (const RegisterClassDesc &Class : Result.Description.RegisterClasses) {
+ for (const SubRegIndex SubReg : Class.SupportedSubRegs) {
+ if (!Result.subRegClass(Class.ID, SubReg)) {
+ return invalidDescription(
+ "every supported class subregister index requires a class mapping");
+ }
+ }
+ }
+
+ for (const SubRegIndexComposition &Composition : Result.Description.SubRegCompositions) {
+ for (const RegisterClassDesc &Source : Result.Description.RegisterClasses) {
+ const auto Intermediate = Result.subRegClass(Source.ID, Composition.First);
+ if (!Intermediate)
+ continue;
+ const auto Final = Result.subRegClass(*Intermediate, Composition.Second);
+ if (!Final)
+ continue;
+ const auto Composed = Result.subRegClass(Source.ID, Composition.Result);
+ if (!Composed || !Result.isSubclass(*Final, *Composed)) {
+ return invalidDescription("subregister composition conflicts with register class mappings");
+ }
+ }
+ }
+
+ for (const PhysicalRegisterDesc &Register : Result.Description.PhysicalRegisters) {
+ if (std::ranges::none_of(Result.Description.RegisterClasses,
+ [&Register](const RegisterClassDesc &Class) {
+ return contains(Class.Registers, Register.Reg);
+ })) {
+ return invalidDescription(
+ "every physical register must belong to at least one register class");
+ }
+ }
+
+ Result.Aliases.resize(RegisterCount);
+ for (std::size_t Left = 0; Left < RegisterCount; ++Left) {
+ for (std::size_t Right = 0; Right < RegisterCount; ++Right) {
+ if (Left != Right && Result.regsOverlap(Result.Description.PhysicalRegisters[Left].Reg,
+ Result.Description.PhysicalRegisters[Right].Reg)) {
+ Result.Aliases[Left].push_back(Result.Description.PhysicalRegisters[Right].Reg);
+ }
+ }
+ }
+
+ return Result;
+}
+
+const RegisterUnitDesc *TargetRegisterInfo::registerUnit(RegisterUnitID ID) const noexcept {
+ const auto Position = UnitToIndex.find(ID);
+ return Position == UnitToIndex.end() ? nullptr : &Description.RegisterUnits[Position->second];
+}
+
+const PhysicalRegisterDesc *
+TargetRegisterInfo::physicalRegister(PhysicalRegister Reg) const noexcept {
+ const auto Position = RegisterToIndex.find(Reg);
+ return Position == RegisterToIndex.end() ? nullptr
+ : &Description.PhysicalRegisters[Position->second];
+}
+
+const PhysicalRegisterDesc *
+TargetRegisterInfo::physicalRegister(std::string_view Name) const noexcept {
+ const auto Position = RegisterNameToIndex.find(std::string(Name));
+ return Position == RegisterNameToIndex.end() ? nullptr
+ : &Description.PhysicalRegisters[Position->second];
+}
+
+const SubRegIndexDesc *TargetRegisterInfo::subRegIndex(SubRegIndex Index) const noexcept {
+ const auto Position = SubRegIndexToIndex.find(Index);
+ return Position == SubRegIndexToIndex.end() ? nullptr
+ : &Description.SubRegIndices[Position->second];
+}
+
+const RegisterClassDesc *TargetRegisterInfo::registerClass(RegisterClassID ID) const noexcept {
+ const auto Position = ClassToIndex.find(ID);
+ return Position == ClassToIndex.end() ? nullptr : &Description.RegisterClasses[Position->second];
+}
+
+const RegisterBankDesc *TargetRegisterInfo::registerBank(RegisterBankID ID) const noexcept {
+ const auto Position = BankToIndex.find(ID);
+ return Position == BankToIndex.end() ? nullptr : &Description.RegisterBanks[Position->second];
+}
+
+LaneMask TargetRegisterInfo::registerLaneMask(PhysicalRegister Reg) const noexcept {
+ const PhysicalRegisterDesc *Register = physicalRegister(Reg);
+ if (Register == nullptr)
+ return {};
+
+ LaneMask Result;
+ for (const RegisterUnitLane &Unit : Register->Units)
+ Result = Result | Unit.Lanes;
+ return Result;
+}
+
+bool TargetRegisterInfo::isValidLaneMask(PhysicalRegister Reg, LaneMask Lanes) const noexcept {
+ if (physicalRegister(Reg) == nullptr || Lanes.empty())
+ return false;
+ if (Lanes == LaneMask::all())
+ return true;
+ return registerLaneMask(Reg).contains(Lanes);
+}
+
+bool TargetRegisterInfo::regsOverlap(PhysicalRegister Left, PhysicalRegister Right) const noexcept {
+ const PhysicalRegisterDesc *LeftDesc = physicalRegister(Left);
+ const PhysicalRegisterDesc *RightDesc = physicalRegister(Right);
+ if (LeftDesc == nullptr || RightDesc == nullptr)
+ return false;
+
+ return std::ranges::any_of(LeftDesc->Units, [RightDesc](const RegisterUnitLane &Unit) {
+ return findUnit(*RightDesc, Unit.Unit) != nullptr;
+ });
+}
+
+std::span<const PhysicalRegister> TargetRegisterInfo::aliases(PhysicalRegister Reg) const noexcept {
+ const auto Position = RegisterToIndex.find(Reg);
+ if (Position == RegisterToIndex.end())
+ return {};
+ return Aliases[Position->second];
+}
+
+std::optional<PhysicalRegister> TargetRegisterInfo::subRegister(PhysicalRegister SuperReg,
+ SubRegIndex Index) const noexcept {
+ const auto Position = SubRegisterMap.find(pairKey(SuperReg.number(), Index.value()));
+ if (Position == SubRegisterMap.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+bool TargetRegisterInfo::isSubRegister(PhysicalRegister SuperReg,
+ PhysicalRegister SubReg) const noexcept {
+ const auto Position = RegisterToIndex.find(SubReg);
+ if (Position == RegisterToIndex.end())
+ return false;
+ return contains(SuperRegisters[Position->second], SuperReg);
+}
+
+std::span<const PhysicalRegister>
+TargetRegisterInfo::superRegisters(PhysicalRegister Reg) const noexcept {
+ const auto Position = RegisterToIndex.find(Reg);
+ if (Position == RegisterToIndex.end())
+ return {};
+ return SuperRegisters[Position->second];
+}
+
+std::optional<SubRegIndex>
+TargetRegisterInfo::composeSubRegIndices(SubRegIndex First, SubRegIndex Second) const noexcept {
+ const auto Position = CompositionMap.find(pairKey(First.value(), Second.value()));
+ if (Position == CompositionMap.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+bool TargetRegisterInfo::registerClassContains(RegisterClassID Class,
+ PhysicalRegister Reg) const noexcept {
+ const RegisterClassDesc *ClassDesc = registerClass(Class);
+ return ClassDesc != nullptr && contains(ClassDesc->Registers, Reg);
+}
+
+bool TargetRegisterInfo::isSubclass(RegisterClassID Candidate,
+ RegisterClassID Required) const noexcept {
+ return SubclassRelations.contains(pairKey(Candidate.value(), Required.value()));
+}
+
+bool TargetRegisterInfo::registerClassesOverlap(RegisterClassID Left,
+ RegisterClassID Right) const noexcept {
+ const RegisterClassDesc *LeftClass = registerClass(Left);
+ const RegisterClassDesc *RightClass = registerClass(Right);
+ if (LeftClass == nullptr || RightClass == nullptr)
+ return false;
+ return std::ranges::any_of(LeftClass->Registers, [RightClass](PhysicalRegister Reg) {
+ return contains(RightClass->Registers, Reg);
+ });
+}
+
+std::optional<RegisterClassID> TargetRegisterInfo::subRegClass(RegisterClassID Source,
+ SubRegIndex Index) const noexcept {
+ const auto Position = SubRegClassMap.find(pairKey(Source.value(), Index.value()));
+ if (Position == SubRegClassMap.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+bool TargetRegisterInfo::bankCoversClass(RegisterBankID Bank,
+ RegisterClassID Class) const noexcept {
+ const RegisterBankDesc *BankDesc = registerBank(Bank);
+ return BankDesc != nullptr && contains(BankDesc->RegisterClasses, Class);
+}
+
+bool TargetRegisterInfo::bankContainsRegister(RegisterBankID Bank,
+ PhysicalRegister Reg) const noexcept {
+ const RegisterBankDesc *BankDesc = registerBank(Bank);
+ if (BankDesc == nullptr)
+ return false;
+ return std::ranges::any_of(BankDesc->RegisterClasses, [this, Reg](RegisterClassID Class) {
+ return registerClassContains(Class, Reg);
+ });
+}
+
+bool TargetRegisterInfo::registerBanksOverlap(RegisterBankID Left,
+ RegisterBankID Right) const noexcept {
+ const RegisterBankDesc *LeftBank = registerBank(Left);
+ const RegisterBankDesc *RightBank = registerBank(Right);
+ if (LeftBank == nullptr || RightBank == nullptr)
+ return false;
+ return std::ranges::any_of(Description.PhysicalRegisters,
+ [this, Left, Right](const PhysicalRegisterDesc &Register) {
+ return bankContainsRegister(Left, Register.Reg) &&
+ bankContainsRegister(Right, Register.Reg);
+ });
+}
+
+Expected<void> TargetRegisterInfo::validateRegisterMask(const RegisterMask &Mask) const {
+ constexpr std::uint64_t BitsPerWord = 64;
+ for (std::size_t WordIndex = 0; WordIndex < Mask.words().size(); ++WordIndex) {
+ std::uint64_t Word = Mask.words()[WordIndex];
+ while (Word != 0) {
+ const std::uint64_t BitIndex = static_cast<std::uint64_t>(std::countr_zero(Word));
+ const std::uint64_t RegisterNumber =
+ static_cast<std::uint64_t>(WordIndex) * BitsPerWord + BitIndex;
+ const auto Reg = PhysicalRegister::create(RegisterNumber);
+ if (!Reg || !isKnownPhysicalRegister(*Reg)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "register mask preserves an unknown physical register");
+ }
+ Word &= Word - 1;
+ }
+ }
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/unittests/CMakeLists.txt b/backend/unittests/CMakeLists.txt
index dfaff97..e612449 100644
--- a/backend/unittests/CMakeLists.txt
+++ b/backend/unittests/CMakeLists.txt
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(Support)
+add_subdirectory(Target)
add_subdirectory(CodeGen)
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt
index 4a0b9fd..b0bdd13 100644
--- a/backend/unittests/CodeGen/CMakeLists.txt
+++ b/backend/unittests/CodeGen/CMakeLists.txt
@@ -12,9 +12,11 @@ set(
MachineIRCoreTest.cpp
MachineJumpTableInfoTest.cpp
MachineVerifierTest.cpp
+ MachineVerifierTargetTest.cpp
MachineValueTypesTest.cpp
TargetConstantTest.cpp
TargetFunctionStateTest.cpp
+ TargetRegisterInfoTest.cpp
)
add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp)
@@ -78,7 +80,7 @@ add_executable(
rcc_backend_configure_target(RCCBackendCodeGenMachineFunctionClonerTests)
target_link_libraries(
RCCBackendCodeGenMachineFunctionClonerTests
- PRIVATE RCCBackend::CodeGen
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
)
add_test(
@@ -90,7 +92,7 @@ add_executable(RCCBackendCodeGenMachineIRBuilderTests MachineIRBuilderTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenMachineIRBuilderTests)
target_link_libraries(
RCCBackendCodeGenMachineIRBuilderTests
- PRIVATE RCCBackend::CodeGen
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
)
add_test(
@@ -102,7 +104,7 @@ add_executable(RCCBackendCodeGenMachineVerifierTests MachineVerifierTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenMachineVerifierTests)
target_link_libraries(
RCCBackendCodeGenMachineVerifierTests
- PRIVATE RCCBackend::CodeGen
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
)
add_test(
@@ -110,6 +112,21 @@ add_test(
COMMAND RCCBackendCodeGenMachineVerifierTests
)
+add_executable(
+ RCCBackendCodeGenMachineVerifierTargetTests
+ MachineVerifierTargetTest.cpp
+)
+rcc_backend_configure_target(RCCBackendCodeGenMachineVerifierTargetTests)
+target_link_libraries(
+ RCCBackendCodeGenMachineVerifierTargetTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(
+ NAME RCCBackend.CodeGen.MachineVerifierTarget
+ COMMAND RCCBackendCodeGenMachineVerifierTargetTests
+)
+
add_executable(RCCBackendCodeGenMachineValueTypesTests MachineValueTypesTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenMachineValueTypesTests)
target_link_libraries(RCCBackendCodeGenMachineValueTypesTests PRIVATE RCCBackend::CodeGen)
@@ -148,13 +165,28 @@ add_test(
add_executable(RCCBackendCodeGenTargetFunctionStateTests TargetFunctionStateTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenTargetFunctionStateTests)
-target_link_libraries(RCCBackendCodeGenTargetFunctionStateTests PRIVATE RCCBackend::CodeGen)
+target_link_libraries(
+ RCCBackendCodeGenTargetFunctionStateTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
add_test(
NAME RCCBackend.CodeGen.TargetFunctionState
COMMAND RCCBackendCodeGenTargetFunctionStateTests
)
+add_executable(RCCBackendCodeGenTargetRegisterInfoTests TargetRegisterInfoTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenTargetRegisterInfoTests)
+target_link_libraries(
+ RCCBackendCodeGenTargetRegisterInfoTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(
+ NAME RCCBackend.CodeGen.TargetRegisterInfo
+ COMMAND RCCBackendCodeGenTargetRegisterInfoTests
+)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
@@ -166,9 +198,11 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
@@ -181,7 +215,9 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp"
)
diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
index 3d766dc..d330f6a 100644
--- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
+++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
@@ -2,6 +2,7 @@
#include "rcc/CodeGen/MachineFunctionCloner.h"
+#include "Target/TestTarget.h"
#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
@@ -176,7 +177,7 @@ struct CloneTargetState final {
return Descriptor;
}
-[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() {
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP");
auto Def = instructionDescriptor(DefOpcode, "TEST_DEF");
@@ -233,7 +234,10 @@ struct CloneTargetState final {
Descriptors.push_back(std::move(Load));
Descriptors.push_back(std::move(ConstantPool));
Descriptors.push_back(std::move(JumpTable));
- return rcc::InstructionSet::create(std::move(Descriptors));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
}
[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
@@ -289,8 +293,7 @@ struct SourceFixture final {
};
[[nodiscard]] bool populateSource(TestContext &Context, rcc::MachineFunction &Function,
- const rcc::InstructionSet &InstructionSet,
- SourceFixture &Fixture) {
+ const rcc::TargetDescription &Target, SourceFixture &Fixture) {
auto HistoricalBlock = Function.createBasicBlock();
auto HistoricalInstruction = Function.createInstruction(rcc::Opcode::fromValue(NopOpcode));
Context.expect(HistoricalBlock && HistoricalInstruction, "source arena history must be created");
@@ -393,7 +396,7 @@ struct SourceFixture final {
Fixture.Entry->addLiveIn(LiveIn).has_value(),
"source register configuration must be recorded");
- rcc::MachineIRBuilder Builder(Function, InstructionSet);
+ rcc::MachineIRBuilder Builder(Function, Target);
Context.expect(Builder.setInsertPoint(*Fixture.Entry).has_value(),
"source entry insertion point must be valid");
auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefOpcode));
@@ -504,17 +507,17 @@ struct SourceFixture final {
}
void testDeepClone(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Source = createFunction(Context, "source_function", 1);
SourceFixture Fixture;
- if (!InstructionSet || !Source || !populateSource(Context, *Source, *InstructionSet, Fixture)) {
+ if (!Target || !Source || !populateSource(Context, *Source, *Target, Fixture)) {
return;
}
- Context.expect(rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(),
+ Context.expect(rcc::MachineVerifier::verify(*Source, *Target).has_value(),
"source function must verify before cloning");
auto Clone = rcc::cloneMachineFunction(
- *Source, *InstructionSet,
+ *Source, *Target,
rcc::MachineFunctionCloneOptions{rcc::MachineFunctionStableID::fromValue(99),
std::string("cloned_function")});
Context.expect(Clone.has_value(), "valid source function must deep-clone");
@@ -526,8 +529,8 @@ void testDeepClone(TestContext &Context) {
Destination.stableID() == rcc::MachineFunctionStableID::fromValue(99),
"clone must use its explicit function identity");
Context.expect(Destination.properties() == Source->properties() &&
- rcc::MachineVerifier::verify(Destination, *InstructionSet).has_value() &&
- rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(),
+ rcc::MachineVerifier::verify(Destination, *Target).has_value() &&
+ rcc::MachineVerifier::verify(*Source, *Target).has_value(),
"source and destination must independently preserve valid properties");
Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 &&
Source->allInstructions().size() == 9 &&
@@ -761,18 +764,18 @@ void testDeepClone(TestContext &Context) {
}
void testCloneFailures(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Source = createFunction(Context, "failure_source", 2);
- if (!InstructionSet || !Source)
+ if (!Target || !Source)
return;
Context.expect(
- !rcc::cloneMachineFunction(*Source, *InstructionSet,
+ !rcc::cloneMachineFunction(*Source, *Target,
rcc::MachineFunctionCloneOptions{
rcc::MachineFunctionStableID::fromValue(0), std::nullopt}),
"clone must reject an invalid destination StableID");
Context.expect(
- !rcc::cloneMachineFunction(*Source, *InstructionSet,
+ !rcc::cloneMachineFunction(*Source, *Target,
rcc::MachineFunctionCloneOptions{
rcc::MachineFunctionStableID::fromValue(3), std::string()}),
"clone must reject an explicitly empty destination name");
@@ -784,7 +787,7 @@ void testCloneFailures(TestContext &Context) {
Context.expect(Unknown && Block->appendInstruction(**Unknown).has_value(),
"invalid clone source must contain an unknown opcode");
Context.expect(
- !rcc::cloneMachineFunction(*Invalid, *InstructionSet,
+ !rcc::cloneMachineFunction(*Invalid, *Target,
rcc::MachineFunctionCloneOptions{
rcc::MachineFunctionStableID::fromValue(5), std::nullopt}),
"clone must verify and reject an invalid source function");
@@ -800,7 +803,7 @@ void testCloneFailures(TestContext &Context) {
Context.expect(State && UnmappedState->setTargetState(std::move(*State)).has_value(),
"unmapped target state negative test must be installed");
Context.expect(
- !rcc::cloneMachineFunction(*UnmappedState, *InstructionSet,
+ !rcc::cloneMachineFunction(*UnmappedState, *Target,
rcc::MachineFunctionCloneOptions{
rcc::MachineFunctionStableID::fromValue(7), std::nullopt}),
"clone must reject target state that does not map its identities");
diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
index 2b5c97d..15f6cd2 100644
--- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
+++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
@@ -2,6 +2,7 @@
#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "Target/TestTarget.h"
#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
@@ -66,7 +67,7 @@ private:
return Descriptor;
}
-[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() {
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
auto Copy = instructionDescriptor(CopyOpcode, "TEST_COPY");
auto CopyDef = registerDescriptor(rcc::RegisterOperandRole::Def);
auto CopyUse = registerDescriptor(rcc::RegisterOperandRole::Use);
@@ -126,7 +127,10 @@ private:
Descriptors.push_back(std::move(Dynamic));
Descriptors.push_back(std::move(ConstantPool));
Descriptors.push_back(std::move(JumpTable));
- return rcc::InstructionSet::create(std::move(Descriptors));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
}
[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
@@ -172,10 +176,10 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) {
}
void testTransactionalBuild(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "transactional", 1);
- Context.expect(InstructionSet.has_value(), "builder instruction set must be valid");
- if (!InstructionSet || !Function)
+ Context.expect(Target.has_value(), "builder target description must be valid");
+ if (!Target || !Function)
return;
rcc::MachineBasicBlock *Block = createBlock(Context, *Function);
if (Block == nullptr)
@@ -188,7 +192,7 @@ void testTransactionalBuild(TestContext &Context) {
if (!Destination || !Source)
return;
- rcc::MachineIRBuilder Builder(*Function, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
Context.expect(!Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)),
"an unset insertion point must reject instruction construction");
Context.expect(Function->allInstructions().empty(),
@@ -242,9 +246,8 @@ void testTransactionalBuild(TestContext &Context) {
Instruction->sourceLocation() == rcc::SourceLocation{"builder.mir", 7, 3, 1} &&
Instruction->origin() == rcc::OriginID::fromValue(19),
"builder metadata must be preserved on the published instruction");
- Context.expect(
- InstructionSet->validateInstruction(*Instruction, Function->registerInfo()).has_value(),
- "builder output must satisfy the shared instruction descriptor");
+ Context.expect(Target->validateInstruction(*Instruction, Function->registerInfo()).has_value(),
+ "builder output must satisfy the shared instruction descriptor");
Context.expect(!Complete->build(), "a successful pending builder must be single-use");
Context.expect(!Complete->setFlags({}) && !Complete->clearSourceLocation() &&
!Complete->clearOrigin(),
@@ -264,15 +267,15 @@ void testTransactionalBuild(TestContext &Context) {
}
void testInsertionOrdering(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "ordering", 2);
- if (!InstructionSet || !Function)
+ if (!Target || !Function)
return;
rcc::MachineBasicBlock *Block = createBlock(Context, *Function);
if (Block == nullptr)
return;
- rcc::MachineIRBuilder Builder(*Function, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value(),
"ordering builder insertion point must be valid");
rcc::MachineInstr *First = buildNop(Context, Builder);
@@ -298,10 +301,10 @@ void testInsertionOrdering(TestContext &Context) {
}
void testImplicitAndMemoryValidation(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "implicit_memory", 3);
auto OtherFunction = createFunction(Context, "foreign", 4);
- if (!InstructionSet || !Function || !OtherFunction)
+ if (!Target || !Function || !OtherFunction)
return;
rcc::MachineBasicBlock *Block = createBlock(Context, *Function);
rcc::MachineBasicBlock *OtherBlock = createBlock(Context, *OtherFunction);
@@ -315,7 +318,7 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
if (!Destination || !Source)
return;
- rcc::MachineIRBuilder Builder(*Function, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value(),
"implicit test insertion point must be accepted");
@@ -334,9 +337,8 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
(*BuiltAdd)->operandAt(3)->registerFlags().has(rcc::RegisterOperandFlag::Renamable) &&
(*BuiltAdd)->operandAt(4)->isImplicit(),
"default implicit defs and uses must follow explicit operands");
- Context.expect(
- InstructionSet->validateInstruction(**BuiltAdd, Function->registerInfo()).has_value(),
- "default implicit builder output must pass shared validation");
+ Context.expect(Target->validateInstruction(**BuiltAdd, Function->registerInfo()).has_value(),
+ "default implicit builder output must pass shared validation");
const std::size_t BeforeRejectedImplicit = Function->allInstructions().size();
auto ExtraImplicit = Builder.buildInstr(rcc::Opcode::fromValue(AddOpcode));
@@ -363,7 +365,7 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
auto BuiltDynamic = Dynamic->build();
Context.expect(
BuiltDynamic.has_value() &&
- InstructionSet->validateInstruction(**BuiltDynamic, Function->registerInfo()).has_value(),
+ Target->validateInstruction(**BuiltDynamic, Function->registerInfo()).has_value(),
"dynamic implicit builder output must validate");
const std::size_t BeforeMissingMemory = Function->allInstructions().size();
@@ -444,10 +446,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
if (!Load)
return;
auto BuiltLoad = Load->build();
- Context.expect(
- BuiltLoad.has_value() &&
- InstructionSet->validateInstruction(**BuiltLoad, Function->registerInfo()).has_value(),
- "matching load MMO must build and validate");
+ Context.expect(BuiltLoad.has_value() &&
+ Target->validateInstruction(**BuiltLoad, Function->registerInfo()).has_value(),
+ "matching load MMO must build and validate");
const std::size_t BeforeStoreMismatch = Function->allInstructions().size();
auto StoreMismatch = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode));
@@ -468,9 +469,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
}
void testStaleCursorsAndOwnedOperands(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "ownership", 5);
- if (!InstructionSet || !Function)
+ if (!Target || !Function)
return;
rcc::MachineBasicBlock *Block = createBlock(Context, *Function);
if (Block == nullptr)
@@ -482,7 +483,7 @@ void testStaleCursorsAndOwnedOperands(TestContext &Context) {
if (!Destination || !Source)
return;
- rcc::MachineIRBuilder Builder(*Function, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value(),
"ownership test insertion point must be valid");
auto Copy = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode));
diff --git a/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp b/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp
new file mode 100644
index 0000000..3eb926d
--- /dev/null
+++ b/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp
@@ -0,0 +1,485 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+constexpr std::uint32_t DefGPR64Opcode = 1;
+constexpr std::uint32_t DefGPR32Opcode = 2;
+constexpr std::uint32_t EarlyClobberOpcode = 3;
+constexpr std::uint32_t TiedAnyOpcode = 4;
+constexpr std::uint32_t CallOpcode = 5;
+constexpr std::uint32_t RenamableImplicitOpcode = 6;
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+struct FunctionFixture final {
+ std::unique_ptr<rcc::MachineFunction> Function;
+ rcc::MachineBasicBlock *Block = nullptr;
+};
+
+[[nodiscard]] rcc::PhysicalRegister phys(std::uint32_t Number) {
+ return *rcc::PhysicalRegister::create(Number);
+}
+
+[[nodiscard]] rcc::OperandDescriptor
+registerOperand(rcc::RegisterOperandRole Role,
+ std::optional<rcc::RegisterClassID> Class = std::nullopt,
+ std::optional<rcc::RegisterBankID> Bank = std::nullopt, bool AllowSubReg = false,
+ bool EarlyClobber = false) {
+ rcc::OperandDescriptor Operand;
+ Operand.Kinds = {rcc::MachineOperand::Kind::Register};
+ Operand.RegisterRole = Role;
+ Operand.RegClass = Class;
+ Operand.RegBank = Bank;
+ Operand.AllowSubReg = AllowSubReg;
+ Operand.EarlyClobber = EarlyClobber;
+ return Operand;
+}
+
+[[nodiscard]] rcc::InstructionDescriptor instruction(std::uint32_t OpcodeValue, std::string Name) {
+ rcc::InstructionDescriptor Instruction;
+ Instruction.InstructionOpcode = rcc::Opcode::fromValue(OpcodeValue);
+ Instruction.Name = std::move(Name);
+ Instruction.Flags = {rcc::InstructionFlag::ProducesCode};
+ return Instruction;
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ auto DefGPR64 = instruction(DefGPR64Opcode, "TEST_DEF_GPR64");
+ DefGPR64.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::GPR, true));
+ DefGPR64.NumExplicitDefs = 1;
+
+ auto DefGPR32 = instruction(DefGPR32Opcode, "TEST_DEF_GPR32");
+ DefGPR32.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR32,
+ rcc::test::TestRegisterBank::GPR, true));
+ DefGPR32.NumExplicitDefs = 1;
+
+ auto EarlyClobber = instruction(EarlyClobberOpcode, "TEST_EARLY_CLOBBER");
+ EarlyClobber.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::GPR, false, true));
+ EarlyClobber.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Use,
+ rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::GPR));
+ EarlyClobber.NumExplicitDefs = 1;
+
+ auto TiedAny = instruction(TiedAnyOpcode, "TEST_TIED_ANY");
+ auto TiedDef = registerOperand(rcc::RegisterOperandRole::Def);
+ auto TiedUse = registerOperand(rcc::RegisterOperandRole::Use);
+ TiedDef.DefaultTiedOperand = 1;
+ TiedUse.DefaultTiedOperand = 0;
+ TiedAny.Operands.push_back(TiedDef);
+ TiedAny.Operands.push_back(TiedUse);
+ TiedAny.NumExplicitDefs = 1;
+
+ auto Call = instruction(CallOpcode, "TEST_CALL");
+ rcc::OperandDescriptor Mask;
+ Mask.Kinds = {rcc::MachineOperand::Kind::RegisterMask};
+ Call.Operands.push_back(Mask);
+ Call.Flags.set(rcc::InstructionFlag::Call);
+ Call.Flags.set(rcc::InstructionFlag::HasSideEffects);
+
+ auto RenamableImplicit = instruction(RenamableImplicitOpcode, "TEST_RENAMABLE_IMPLICIT");
+ RenamableImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), false, true});
+
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(DefGPR64));
+ Descriptors.push_back(std::move(DefGPR32));
+ Descriptors.push_back(std::move(EarlyClobber));
+ Descriptors.push_back(std::move(TiedAny));
+ Descriptors.push_back(std::move(Call));
+ Descriptors.push_back(std::move(RenamableImplicit));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+[[nodiscard]] FunctionFixture createFunction(TestContext &Context, std::string Name,
+ std::uint64_t ID) {
+ auto Function =
+ rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID));
+ Context.expect(Function.has_value(), "target verifier function must be created");
+ if (!Function)
+ return {};
+
+ auto Block = (*Function)->createBasicBlock();
+ Context.expect(Block && (*Function)->appendBlock(**Block).has_value(),
+ "target verifier block must be linked");
+ if (!Block)
+ return {};
+ return FunctionFixture{std::move(*Function), *Block};
+}
+
+[[nodiscard]] rcc::Expected<void> classify(rcc::MachineRegisterInfo &RegInfo, rcc::Register Reg,
+ rcc::RegisterBankID Bank, rcc::RegisterClassID Class) {
+ auto BankResult = RegInfo.setRegisterBank(Reg, Bank);
+ if (!BankResult)
+ return std::unexpected(BankResult.error());
+ return RegInfo.setRegisterClass(Reg, Class);
+}
+
+[[nodiscard]] rcc::Expected<rcc::MachineInstr *>
+appendInstruction(FunctionFixture &Fixture, std::uint32_t OpcodeValue,
+ std::vector<rcc::MachineOperand> Operands,
+ std::optional<rcc::MachineOperandTie> Tie = std::nullopt) {
+ auto Instruction = Fixture.Function->createInstruction(rcc::Opcode::fromValue(OpcodeValue));
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ for (rcc::MachineOperand &Operand : Operands) {
+ auto AddResult = (*Instruction)->addOperand(std::move(Operand));
+ if (!AddResult)
+ return std::unexpected(AddResult.error());
+ }
+ if (Tie) {
+ auto TieResult = (*Instruction)->tieOperands(Tie->DefOperand, Tie->UseOperand);
+ if (!TieResult)
+ return std::unexpected(TieResult.error());
+ }
+ auto AppendResult = Fixture.Block->appendInstruction(**Instruction);
+ if (!AppendResult)
+ return std::unexpected(AppendResult.error());
+ return *Instruction;
+}
+
+void testValidTargetFacts(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Fixture = createFunction(Context, "valid_target_facts", 1);
+ if (!Fixture.Function)
+ return;
+
+ auto Type = rcc::LLT::makeInteger(64);
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ if (!Reg)
+ return;
+ Context.expect(classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::GPR,
+ rcc::test::TestRegisterClass::GPR64Alloc)
+ .has_value(),
+ "valid target verifier vreg must be classified");
+
+ const auto G0 = phys(rcc::test::TestRegister::G0);
+ const auto G1 = phys(rcc::test::TestRegister::G1);
+ Context.expect(Fixture.Function->registerInfo().addLiveIn(G0, *Reg).has_value() &&
+ Fixture.Function->registerInfo()
+ .reservePhysicalRegister(phys(rcc::test::TestRegister::StackPointer))
+ .has_value() &&
+ Fixture.Block->addLiveIn(G0, rcc::LaneMask::fromRaw(0x3)).has_value(),
+ "valid target live-in and reserved register facts must be recorded");
+
+ rcc::MachineIRBuilder Builder(*Fixture.Function, Target);
+ Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(),
+ "valid target builder insertion point must be set");
+ auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefGPR64Opcode));
+ Context.expect(Def && Def->addDef(*Reg).has_value() && Def->build(),
+ "register subclass must satisfy a target operand superclass");
+
+ auto Early = Builder.buildInstr(rcc::Opcode::fromValue(EarlyClobberOpcode));
+ Context.expect(
+ Early &&
+ Early->addDef(rcc::Register::fromPhysical(G0), {rcc::RegisterOperandFlag::EarlyClobber})
+ .has_value() &&
+ Early->addUse(rcc::Register::fromPhysical(G1)).has_value() && Early->build(),
+ "disjoint physical registers must satisfy early-clobber");
+
+ auto Call = Builder.buildInstr(rcc::Opcode::fromValue(CallOpcode));
+ Context.expect(Call &&
+ Call->addRegisterMask(rcc::test::createTestCallPreservedMask()).has_value() &&
+ Call->build(),
+ "valid target call mask must build");
+ Context.expect(rcc::MachineVerifier::verify(*Fixture.Function, Target).has_value(),
+ "complete valid target facts must pass MachineVerifier");
+}
+
+void testSubRegisterProjection(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Fixture = createFunction(Context, "subregister_projection", 2);
+ if (!Fixture.Function)
+ return;
+
+ auto Type = rcc::LLT::makeInteger(64);
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ if (!Reg)
+ return;
+ Context.expect(classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::GPR,
+ rcc::test::TestRegisterClass::GPR64)
+ .has_value(),
+ "subregister source vreg must be classified");
+
+ rcc::MachineIRBuilder Builder(*Fixture.Function, Target);
+ Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(),
+ "subregister builder insertion point must be set");
+ auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefGPR32Opcode));
+ Context.expect(Def && Def->addDef(*Reg, {}, rcc::test::TestSubReg::Low32).has_value() &&
+ Def->build(),
+ "projected GPR32Low class must satisfy the GPR32 operand class");
+ Context.expect(rcc::MachineVerifier::verify(*Fixture.Function, Target).has_value(),
+ "valid virtual subregister projection must verify");
+}
+
+void testUnknownAndMalformedTargetFacts(TestContext &Context,
+ const rcc::TargetDescription &Target) {
+ {
+ auto Fixture = createFunction(Context, "unknown_physreg", 3);
+ auto Operand = rcc::MachineOperand::registerOperand(rcc::Register::fromPhysical(phys(99)),
+ {rcc::RegisterOperandFlag::Define});
+ if (Fixture.Function && Operand) {
+ std::vector<rcc::MachineOperand> Operands;
+ Operands.push_back(std::move(*Operand));
+ Context.expect(appendInstruction(Fixture, DefGPR64Opcode, std::move(Operands)) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject unknown physical registers");
+ }
+ }
+ {
+ auto Fixture = createFunction(Context, "physical_subreg", 4);
+ auto Operand = rcc::MachineOperand::registerOperand(
+ rcc::Register::fromPhysical(phys(rcc::test::TestRegister::G0)),
+ {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32);
+ if (Fixture.Function && Operand) {
+ std::vector<rcc::MachineOperand> Operands;
+ Operands.push_back(std::move(*Operand));
+ Context.expect(appendInstruction(Fixture, DefGPR64Opcode, std::move(Operands)) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject physical operands with SubRegIndex");
+ }
+ }
+ {
+ auto Fixture = createFunction(Context, "unknown_bank", 5);
+ auto Type = rcc::LLT::makeInteger(64);
+ if (!Fixture.Function || !Type)
+ return;
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ Context.expect(Reg &&
+ Fixture.Function->registerInfo()
+ .setRegisterBank(*Reg, rcc::RegisterBankID::fromValue(99))
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject unknown virtual register bank IDs");
+ }
+ {
+ auto Fixture = createFunction(Context, "wide_bank_value", 6);
+ auto Type = rcc::LLT::makeInteger(128);
+ if (!Fixture.Function || !Type)
+ return;
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ Context.expect(Reg &&
+ Fixture.Function->registerInfo()
+ .setRegisterBank(*Reg, rcc::test::TestRegisterBank::GPR)
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject LLTs wider than their register bank");
+ }
+ {
+ auto Fixture = createFunction(Context, "bad_function_livein", 7);
+ Context.expect(Fixture.Function &&
+ Fixture.Function->registerInfo().addLiveIn(phys(99)).has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject unknown function physical live-ins");
+ }
+ {
+ auto Fixture = createFunction(Context, "bad_block_lanes", 8);
+ Context.expect(
+ Fixture.Function &&
+ Fixture.Block
+ ->addLiveIn(phys(rcc::test::TestRegister::G0), rcc::LaneMask::fromRaw(0x10))
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject target-invalid block live-in lanes");
+ }
+ {
+ auto Fixture = createFunction(Context, "bad_allocation_hint", 9);
+ auto Type = rcc::LLT::makeInteger(64);
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ Context.expect(Reg &&
+ classify(Fixture.Function->registerInfo(), *Reg,
+ rcc::test::TestRegisterBank::FPR,
+ rcc::test::TestRegisterClass::FPR64)
+ .has_value() &&
+ Fixture.Function->registerInfo()
+ .addAllocationHint(*Reg, phys(rcc::test::TestRegister::G0))
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject allocation hints outside a vreg class");
+ }
+ {
+ auto Fixture = createFunction(Context, "reserved_allocation_hint", 14);
+ auto Type = rcc::LLT::makeInteger(64);
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ const auto G0 = phys(rcc::test::TestRegister::G0);
+ Context.expect(Reg &&
+ classify(Fixture.Function->registerInfo(), *Reg,
+ rcc::test::TestRegisterBank::GPR,
+ rcc::test::TestRegisterClass::GPR64)
+ .has_value() &&
+ Fixture.Function->registerInfo().addAllocationHint(*Reg, G0).has_value() &&
+ Fixture.Function->registerInfo().reservePhysicalRegister(G0).has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject allocation hints that overlap reserved registers");
+ }
+ {
+ auto Fixture = createFunction(Context, "incompatible_function_livein", 15);
+ auto Type = rcc::LLT::makeInteger(64);
+ auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ Context.expect(
+ Reg &&
+ classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::FPR,
+ rcc::test::TestRegisterClass::FPR64)
+ .has_value() &&
+ Fixture.Function->registerInfo()
+ .addLiveIn(phys(rcc::test::TestRegister::G0), *Reg)
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject a function live-in outside its initial vreg constraints");
+ }
+}
+
+void testRegMaskEarlyClobberAndTies(TestContext &Context, const rcc::TargetDescription &Target) {
+ {
+ auto Fixture = createFunction(Context, "bad_regmask", 10);
+ auto Mask = std::make_shared<const rcc::RegisterMask>(
+ std::vector<std::uint64_t>{std::uint64_t{1} << 63U});
+ auto Operand = rcc::MachineOperand::registerMask(std::move(Mask));
+ if (Fixture.Function && Operand) {
+ std::vector<rcc::MachineOperand> Operands;
+ Operands.push_back(std::move(*Operand));
+ Context.expect(appendInstruction(Fixture, CallOpcode, std::move(Operands)) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject RegMask bits for unknown registers");
+ }
+ }
+ {
+ auto Fixture = createFunction(Context, "early_overlap", 11);
+ const auto G0 = phys(rcc::test::TestRegister::G0);
+ rcc::MachineIRBuilder Builder(*Fixture.Function, Target);
+ Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(),
+ "early-clobber builder insertion point must be set");
+ auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(EarlyClobberOpcode));
+ Context.expect(
+ Pending &&
+ Pending
+ ->addDef(rcc::Register::fromPhysical(G0), {rcc::RegisterOperandFlag::EarlyClobber})
+ .has_value() &&
+ Pending->addUse(rcc::Register::fromPhysical(G0)).has_value() && !Pending->build() &&
+ Fixture.Function->allInstructions().empty(),
+ "Builder must reject overlapping early-clobber before publication");
+
+ auto Def = rcc::MachineOperand::registerOperand(
+ rcc::Register::fromPhysical(G0),
+ {rcc::RegisterOperandFlag::Define, rcc::RegisterOperandFlag::EarlyClobber});
+ auto Use = rcc::MachineOperand::registerOperand(rcc::Register::fromPhysical(G0),
+ {rcc::RegisterOperandFlag::Use});
+ if (Def && Use) {
+ std::vector<rcc::MachineOperand> Operands;
+ Operands.push_back(std::move(*Def));
+ Operands.push_back(std::move(*Use));
+ Context.expect(appendInstruction(Fixture, EarlyClobberOpcode, std::move(Operands)) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject overlapping early-clobber");
+ }
+ }
+ {
+ auto Fixture = createFunction(Context, "incompatible_tie", 12);
+ auto Type = rcc::LLT::makeInteger(64);
+ auto GPR = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ auto FPR = Fixture.Function->registerInfo().createVirtualRegister(*Type);
+ if (!GPR || !FPR)
+ return;
+ Context.expect(classify(Fixture.Function->registerInfo(), *GPR,
+ rcc::test::TestRegisterBank::GPR, rcc::test::TestRegisterClass::GPR64)
+ .has_value() &&
+ classify(Fixture.Function->registerInfo(), *FPR,
+ rcc::test::TestRegisterBank::FPR,
+ rcc::test::TestRegisterClass::FPR64)
+ .has_value(),
+ "incompatible tied vregs must have individually valid classes");
+
+ auto Def = rcc::MachineOperand::registerOperand(*GPR, {rcc::RegisterOperandFlag::Define});
+ auto Use = rcc::MachineOperand::registerOperand(*FPR, {rcc::RegisterOperandFlag::Use});
+ if (Def && Use) {
+ std::vector<rcc::MachineOperand> Operands;
+ Operands.push_back(std::move(*Def));
+ Operands.push_back(std::move(*Use));
+ Context.expect(appendInstruction(Fixture, TiedAnyOpcode, std::move(Operands),
+ rcc::MachineOperandTie{0, 1}) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject tied classes without a physical intersection");
+ }
+ }
+ {
+ auto Fixture = createFunction(Context, "reserved_renamable", 13);
+ const auto G0 = phys(rcc::test::TestRegister::G0);
+ Context.expect(Fixture.Function->registerInfo().reservePhysicalRegister(G0).has_value(),
+ "reserved renamable test register must be reserved");
+
+ rcc::MachineIRBuilder Builder(*Fixture.Function, Target);
+ Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(),
+ "reserved renamable builder insertion point must be set");
+ auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(RenamableImplicitOpcode));
+ Context.expect(Pending && !Pending->build(),
+ "Builder must reject a renamable reserved implicit register");
+
+ const rcc::InstructionDescriptor *Descriptor =
+ Target.instructionDescriptor(rcc::Opcode::fromValue(RenamableImplicitOpcode));
+ if (Descriptor == nullptr)
+ return;
+ auto Implicits = Target.createDefaultImplicitOperands(*Descriptor);
+ if (Implicits) {
+ Context.expect(appendInstruction(Fixture, RenamableImplicitOpcode, std::move(*Implicits)) &&
+ !rcc::MachineVerifier::verify(*Fixture.Function, Target),
+ "MachineVerifier must reject a renamable reserved implicit register");
+ }
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "target-aware verifier test target must be valid");
+ if (!Target)
+ return Context.result();
+
+ testValidTargetFacts(Context, *Target);
+ testSubRegisterProjection(Context, *Target);
+ testUnknownAndMalformedTargetFacts(Context, *Target);
+ testRegMaskEarlyClobberAndTies(Context, *Target);
+ return Context.result();
+}
diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp
index 3cc79d1..a803c08 100644
--- a/backend/unittests/CodeGen/MachineVerifierTest.cpp
+++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp
@@ -2,6 +2,7 @@
#include "rcc/CodeGen/MachineVerifier.h"
+#include "Target/TestTarget.h"
#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
@@ -71,7 +72,7 @@ private:
return Descriptor;
}
-[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() {
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
auto GenericDef = instructionDescriptor(GenericDefOpcode, "TEST_GENERIC_DEF");
GenericDef.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def));
GenericDef.NumExplicitDefs = 1;
@@ -140,7 +141,10 @@ private:
Descriptors.push_back(std::move(Nop));
Descriptors.push_back(std::move(ConstantPool));
Descriptors.push_back(std::move(JumpTable));
- return rcc::InstructionSet::create(std::move(Descriptors));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
}
[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
@@ -178,7 +182,7 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32
}
[[nodiscard]] bool populateValidPreLegalizer(TestContext &Context, rcc::MachineFunction &Function,
- const rcc::InstructionSet &InstructionSet) {
+ const rcc::TargetDescription &Target) {
rcc::MachineBasicBlock *Entry = createBlock(Context, Function);
rcc::MachineBasicBlock *Exit = createBlock(Context, Function);
if (Entry == nullptr || Exit == nullptr)
@@ -190,7 +194,7 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32
if (!Value)
return false;
- rcc::MachineIRBuilder Builder(Function, InstructionSet);
+ rcc::MachineIRBuilder Builder(Function, Target);
Context.expect(Builder.setInsertPoint(*Entry).has_value(), "entry insertion point must be valid");
auto Def = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode));
Context.expect(Def && Def->addDef(*Value).has_value(), "generic definition must be assembled");
@@ -219,18 +223,17 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32
}
void testValidFunctionAndModes(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "valid", 1);
- if (!InstructionSet || !Function ||
- !populateValidPreLegalizer(Context, *Function, *InstructionSet)) {
+ if (!Target || !Function || !populateValidPreLegalizer(Context, *Function, *Target)) {
return;
}
- Context.expect(rcc::MachineVerifier::verify(*Function, *InstructionSet).has_value(),
+ Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(),
"well-formed function must pass generic verification");
Context.expect(
rcc::MachineVerifier::verify(
- *Function, *InstructionSet,
+ *Function, *Target,
rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true})
.has_value(),
"typed SSA input must pass pre-legalizer verification");
@@ -239,20 +242,20 @@ void testValidFunctionAndModes(TestContext &Context) {
auto DetachedInstruction = Function->createInstruction(rcc::Opcode::fromValue(NopOpcode));
Context.expect(DetachedBlock && DetachedInstruction,
"detached arena history must be allocatable");
- Context.expect(rcc::MachineVerifier::verify(*Function, *InstructionSet).has_value(),
+ Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(),
"empty detached arena objects must not invalidate live layout");
Function->properties().set(rcc::MachineFunctionProperty::Legalized);
Context.expect(
!rcc::MachineVerifier::verify(
- *Function, *InstructionSet,
+ *Function, *Target,
rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true}),
"pre-legalizer mode must reject later-stage properties");
}
void testReachableStructuralFailures(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
- if (!InstructionSet)
+ auto Target = createTarget();
+ if (!Target)
return;
auto Unknown = createFunction(Context, "unknown_opcode", 2);
@@ -261,7 +264,7 @@ void testReachableStructuralFailures(TestContext &Context) {
auto Instruction = Unknown->createInstruction(rcc::Opcode::fromValue(999));
Context.expect(Instruction && UnknownBlock->appendInstruction(**Instruction).has_value(),
"unknown opcode negative test must be linked");
- Context.expect(!rcc::MachineVerifier::verify(*Unknown, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Unknown, *Target),
"live unregistered opcode must fail verification");
}
@@ -272,7 +275,7 @@ void testReachableStructuralFailures(TestContext &Context) {
auto Instruction = WrongOperands->createInstruction(rcc::Opcode::fromValue(GenericDefOpcode));
Context.expect(Instruction && WrongBlock->appendInstruction(**Instruction).has_value(),
"wrong-operand negative test must be linked");
- Context.expect(!rcc::MachineVerifier::verify(*WrongOperands, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*WrongOperands, *Target),
"descriptor operand-count mismatch must fail verification");
}
@@ -282,7 +285,7 @@ void testReachableStructuralFailures(TestContext &Context) {
auto Instruction = DetachedChild->createInstruction(rcc::Opcode::fromValue(NopOpcode));
Context.expect(Block && Instruction && (*Block)->appendInstruction(**Instruction).has_value(),
"detached block negative test must contain an instruction");
- Context.expect(!rcc::MachineVerifier::verify(*DetachedChild, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*DetachedChild, *Target),
"detached block retaining instructions must fail verification");
}
@@ -294,14 +297,14 @@ void testReachableStructuralFailures(TestContext &Context) {
.addSuccessor(**Detached, *Linked, rcc::BranchProbability::one())
.has_value(),
"detached CFG negative test edge must be created");
- Context.expect(!rcc::MachineVerifier::verify(*DetachedCFG, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*DetachedCFG, *Target),
"detached block retaining CFG state must fail verification");
}
}
void testPropertyFailures(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
- if (!InstructionSet)
+ auto Target = createTarget();
+ if (!Target)
return;
auto Type = rcc::LLT::makeInteger(64);
@@ -309,7 +312,7 @@ void testPropertyFailures(TestContext &Context) {
rcc::MachineBasicBlock *SSABlock = SSA ? createBlock(Context, *SSA) : nullptr;
if (SSA && SSABlock != nullptr) {
auto Value = SSA->registerInfo().createVirtualRegister(*Type);
- rcc::MachineIRBuilder Builder(*SSA, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*SSA, *Target);
Context.expect(Value && Builder.setInsertPoint(*SSABlock).has_value(),
"SSA negative test setup must succeed");
for (int Index = 0; Index != 2 && Value; ++Index) {
@@ -318,7 +321,7 @@ void testPropertyFailures(TestContext &Context) {
"SSA negative test definition must build");
}
SSA->properties().set(rcc::MachineFunctionProperty::IsSSA);
- Context.expect(!rcc::MachineVerifier::verify(*SSA, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*SSA, *Target),
"IsSSA must reject a multiply-defined virtual register");
}
@@ -326,14 +329,14 @@ void testPropertyFailures(TestContext &Context) {
rcc::MachineBasicBlock *PHIBlock = PHI ? createBlock(Context, *PHI) : nullptr;
if (PHI && PHIBlock != nullptr) {
auto Value = PHI->registerInfo().createVirtualRegister(*Type);
- rcc::MachineIRBuilder Builder(*PHI, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*PHI, *Target);
Context.expect(Value && Builder.setInsertPoint(*PHIBlock).has_value(),
"PHI negative test setup must succeed");
auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(PhiOpcode));
Context.expect(Pending && Pending->addDef(*Value).has_value() && Pending->build(),
"PHI negative test instruction must build");
PHI->properties().set(rcc::MachineFunctionProperty::NoPHIs);
- Context.expect(!rcc::MachineVerifier::verify(*PHI, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*PHI, *Target),
"NoPHIs must reject a PHI instruction");
}
@@ -341,7 +344,7 @@ void testPropertyFailures(TestContext &Context) {
if (NoVRegs) {
rcc::MachineBasicBlock *Block = createBlock(Context, *NoVRegs);
auto Value = NoVRegs->registerInfo().createVirtualRegister(*Type);
- rcc::MachineIRBuilder Builder(*NoVRegs, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*NoVRegs, *Target);
Context.expect(Block != nullptr && Builder.setInsertPoint(*Block).has_value(),
"NoVRegs negative test setup must succeed");
auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode));
@@ -351,12 +354,12 @@ void testPropertyFailures(TestContext &Context) {
return;
auto Built = Pending->build();
NoVRegs->properties().set(rcc::MachineFunctionProperty::NoVRegs);
- Context.expect(Value && Built && !rcc::MachineVerifier::verify(*NoVRegs, *InstructionSet),
+ Context.expect(Value && Built && !rcc::MachineVerifier::verify(*NoVRegs, *Target),
"NoVRegs must reject a live virtual register");
Context.expect(Value && Built && Block != nullptr &&
Block->removeInstruction(**Built).has_value() &&
NoVRegs->registerInfo().eraseVirtualRegister(*Value).has_value() &&
- rcc::MachineVerifier::verify(*NoVRegs, *InstructionSet).has_value(),
+ rcc::MachineVerifier::verify(*NoVRegs, *Target).has_value(),
"NoVRegs must accept tombstoned def-use history");
}
@@ -367,7 +370,7 @@ void testPropertyFailures(TestContext &Context) {
StackSlot.Size = 8;
StackSlot.Align = *rcc::Alignment::create(8);
auto FrameIndex = Frame->frameInfo().createObject(StackSlot);
- rcc::MachineIRBuilder Builder(*Frame, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Frame, *Target);
Context.expect(FrameIndex && Builder.setInsertPoint(*FrameBlock).has_value(),
"frame-index negative test setup must succeed");
if (!FrameIndex)
@@ -376,27 +379,27 @@ void testPropertyFailures(TestContext &Context) {
Context.expect(Pending && Pending->addFrameIndex(*FrameIndex).has_value() && Pending->build(),
"frame-index negative test instruction must build");
Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices);
- Context.expect(!rcc::MachineVerifier::verify(*Frame, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Frame, *Target),
"NoFrameIndices must reject a frame-index operand");
}
auto Pseudo = createFunction(Context, "pseudo", 10);
rcc::MachineBasicBlock *PseudoBlock = Pseudo ? createBlock(Context, *Pseudo) : nullptr;
if (Pseudo && PseudoBlock != nullptr) {
- rcc::MachineIRBuilder Builder(*Pseudo, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Pseudo, *Target);
Context.expect(Builder.setInsertPoint(*PseudoBlock).has_value() &&
buildNoOperand(Context, Builder, PseudoOpcode) != nullptr,
"pseudo negative test instruction must build");
Pseudo->properties().set(rcc::MachineFunctionProperty::NoExecutablePseudos);
- Context.expect(!rcc::MachineVerifier::verify(*Pseudo, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Pseudo, *Target),
"NoExecutablePseudos must reject an executable pseudo");
}
}
void testSelectedStateAndPseudoDeadline(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Type = rcc::LLT::makeInteger(64);
- if (!InstructionSet)
+ if (!Target)
return;
auto Tied = createFunction(Context, "tied", 11);
@@ -416,7 +419,7 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) {
"selected-state register constraints must be recorded");
}
- rcc::MachineIRBuilder Builder(*Tied, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Tied, *Target);
Context.expect(Builder.setInsertPoint(*TiedBlock).has_value(),
"tied-state insertion point must be valid");
auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode));
@@ -428,30 +431,30 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) {
rcc::MachineFunctionProperty::Selected, rcc::MachineFunctionProperty::TiedOpsRewritten}) {
Tied->properties().set(Property);
}
- Context.expect(!rcc::MachineVerifier::verify(*Tied, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Tied, *Target),
"TiedOpsRewritten must reject unequal tied registers");
rcc::MachineInstr *Instruction = TiedBlock->firstInstruction();
Context.expect(Instruction != nullptr &&
Instruction->operandAt(1)->setRegister(*Def).has_value() &&
- rcc::MachineVerifier::verify(*Tied, *InstructionSet).has_value(),
+ rcc::MachineVerifier::verify(*Tied, *Target).has_value(),
"equal tied registers must satisfy TiedOpsRewritten");
}
auto Pseudo = createFunction(Context, "pseudo_deadline", 12);
rcc::MachineBasicBlock *Block = Pseudo ? createBlock(Context, *Pseudo) : nullptr;
if (Pseudo && Block != nullptr) {
- rcc::MachineIRBuilder Builder(*Pseudo, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Pseudo, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value() &&
buildNoOperand(Context, Builder, PseudoOpcode) != nullptr,
"deadline pseudo must build");
Context.expect(
- rcc::MachineVerifier::verify(*Pseudo, *InstructionSet,
+ rcc::MachineVerifier::verify(*Pseudo, *Target,
rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic,
rcc::PipelineStage::Selected})
.has_value(),
"pseudo may remain at its declared deadline");
Context.expect(!rcc::MachineVerifier::verify(
- *Pseudo, *InstructionSet,
+ *Pseudo, *Target,
rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic,
rcc::PipelineStage::RegisterAllocated}),
"pseudo must not survive beyond its declared deadline");
@@ -460,23 +463,23 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) {
auto Dependency = createFunction(Context, "property_dependency", 13);
if (Dependency) {
Dependency->properties().set(rcc::MachineFunctionProperty::Selected);
- Context.expect(!rcc::MachineVerifier::verify(*Dependency, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Dependency, *Target),
"Selected must require prior pipeline properties");
Dependency->properties().clear(rcc::MachineFunctionProperty::Selected);
Dependency->properties().set(rcc::MachineFunctionProperty::FrameFinalized);
- Context.expect(!rcc::MachineVerifier::verify(*Dependency, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(*Dependency, *Target),
"FrameFinalized must require NoVRegs and NoFrameIndices");
}
}
void testFrameStateFailures(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function = createFunction(Context, "frame_references", 14);
rcc::MachineBasicBlock *Block = Function ? createBlock(Context, *Function) : nullptr;
- if (!InstructionSet || !Function || Block == nullptr)
+ if (!Target || !Function || Block == nullptr)
return;
- rcc::MachineIRBuilder Builder(*Function, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Function, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value(),
"frame reference insertion point must be valid");
auto Unknown = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode));
@@ -494,7 +497,7 @@ void testFrameStateFailures(TestContext &Context) {
Context.expect(Live && Live->addFrameIndex(*FrameIndex).has_value() && Live->build(),
"known live frame index must build");
Context.expect(Function->frameInfo().markObjectDead(*FrameIndex).has_value() &&
- !rcc::MachineVerifier::verify(*Function, *InstructionSet),
+ !rcc::MachineVerifier::verify(*Function, *Target),
"verifier must reject an instruction that references a dead stack object");
auto Lifetime = createFunction(Context, "frame_lifetime", 15);
@@ -506,16 +509,16 @@ void testFrameStateFailures(TestContext &Context) {
LifetimeSlot.Lifetime.push_back(
{rcc::MachineInstrStableID::fromValue(1), rcc::MachineInstrStableID::fromValue(2)});
Context.expect(Lifetime->frameInfo().createObject(LifetimeSlot).has_value() &&
- !rcc::MachineVerifier::verify(*Lifetime, *InstructionSet),
+ !rcc::MachineVerifier::verify(*Lifetime, *Target),
"verifier must reject stack lifetimes that reference unknown instructions");
}
void testTableReferenceFailures(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto ConstantFunction = createFunction(Context, "constant_reference", 16);
rcc::MachineBasicBlock *ConstantBlock =
ConstantFunction ? createBlock(Context, *ConstantFunction) : nullptr;
- if (!InstructionSet || !ConstantFunction || ConstantBlock == nullptr)
+ if (!Target || !ConstantFunction || ConstantBlock == nullptr)
return;
rcc::MachineConstantPoolEntryDesc ConstantDescription;
@@ -524,7 +527,7 @@ void testTableReferenceFailures(TestContext &Context) {
ConstantDescription.Size = 4;
ConstantDescription.Align = *rcc::Alignment::create(4);
auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription));
- rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *InstructionSet);
+ rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *Target);
Context.expect(ConstantIndex && ConstantBuilder.setInsertPoint(*ConstantBlock).has_value(),
"constant-pool verifier setup must succeed");
if (!ConstantIndex)
@@ -534,7 +537,7 @@ void testTableReferenceFailures(TestContext &Context) {
ConstantInstruction->addConstantPool(*ConstantIndex).has_value() &&
ConstantInstruction->build() &&
ConstantFunction->constantPool().eraseEntry(*ConstantIndex).has_value() &&
- !rcc::MachineVerifier::verify(*ConstantFunction, *InstructionSet),
+ !rcc::MachineVerifier::verify(*ConstantFunction, *Target),
"verifier must reject a tombstoned constant-pool reference");
auto JumpFunction = createFunction(Context, "jump_reference", 17);
@@ -544,7 +547,7 @@ void testTableReferenceFailures(TestContext &Context) {
rcc::MachineJumpTableDesc JumpDescription;
JumpDescription.Targets = {JumpBlock};
auto JumpIndex = JumpFunction->jumpTableInfo().createTable(std::move(JumpDescription));
- rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *InstructionSet);
+ rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *Target);
Context.expect(JumpIndex && JumpBuilder.setInsertPoint(*JumpBlock).has_value(),
"jump-table verifier setup must succeed");
if (!JumpIndex)
@@ -553,14 +556,14 @@ void testTableReferenceFailures(TestContext &Context) {
Context.expect(JumpInstruction && JumpInstruction->addJumpTable(*JumpIndex).has_value() &&
JumpInstruction->build() &&
JumpFunction->jumpTableInfo().eraseTable(*JumpIndex).has_value() &&
- !rcc::MachineVerifier::verify(*JumpFunction, *InstructionSet),
+ !rcc::MachineVerifier::verify(*JumpFunction, *Target),
"verifier must reject a tombstoned jump-table reference");
}
void testCompilationStatusReferences(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Unknown = createFunction(Context, "unknown_failure_instruction", 18);
- if (!InstructionSet || !Unknown)
+ if (!Target || !Unknown)
return;
rcc::MachineCompilationFailure UnknownFailure;
@@ -572,14 +575,14 @@ void testCompilationStatusReferences(TestContext &Context) {
Unknown->compilationStatus()
.fail(rcc::MachineCompilationState::SelectionFailed, std::move(UnknownFailure))
.has_value() &&
- !rcc::MachineVerifier::verify(*Unknown, *InstructionSet),
+ !rcc::MachineVerifier::verify(*Unknown, *Target),
"verifier must reject compilation status that references an unknown instruction");
auto Live = createFunction(Context, "live_failure_instruction", 19);
rcc::MachineBasicBlock *Block = Live ? createBlock(Context, *Live) : nullptr;
if (!Live || Block == nullptr)
return;
- rcc::MachineIRBuilder Builder(*Live, *InstructionSet);
+ rcc::MachineIRBuilder Builder(*Live, *Target);
Context.expect(Builder.setInsertPoint(*Block).has_value(),
"live failure instruction insertion point must be valid");
rcc::MachineInstr *Instruction = buildNoOperand(Context, Builder, NopOpcode);
@@ -595,7 +598,7 @@ void testCompilationStatusReferences(TestContext &Context) {
Live->compilationStatus()
.fail(rcc::MachineCompilationState::FinalizationFailed, std::move(LiveFailure))
.has_value() &&
- rcc::MachineVerifier::verify(*Live, *InstructionSet).has_value(),
+ rcc::MachineVerifier::verify(*Live, *Target).has_value(),
"verifier must accept a structurally valid failed function");
}
diff --git a/backend/unittests/CodeGen/TargetFunctionStateTest.cpp b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp
index a306b8f..2286400 100644
--- a/backend/unittests/CodeGen/TargetFunctionStateTest.cpp
+++ b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp
@@ -2,6 +2,7 @@
#include "rcc/CodeGen/TargetFunctionState.h"
+#include "Target/TestTarget.h"
#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/MachineFunction.h"
#include "rcc/CodeGen/MachineFunctionCloner.h"
@@ -146,14 +147,17 @@ struct CollidingFunctionInfo final {
const CollidingFunctionInfo &Right) = default;
};
-[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() {
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
rcc::InstructionDescriptor Descriptor;
Descriptor.InstructionOpcode = rcc::Opcode::fromValue(1);
Descriptor.Name = "TEST_NOP";
Descriptor.Flags = {rcc::InstructionFlag::ProducesCode};
std::vector<rcc::InstructionDescriptor> Descriptors;
Descriptors.push_back(std::move(Descriptor));
- return rcc::InstructionSet::create(std::move(Descriptors));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
}
void testTargetFunctionStateContract(TestContext &Context) {
@@ -212,10 +216,10 @@ void testTargetFunctionStateFailures(TestContext &Context) {
}
void testMachineFunctionOwnershipAndVerification(TestContext &Context) {
- auto InstructionSet = createInstructionSet();
+ auto Target = createTarget();
auto Function =
rcc::MachineFunction::create("target_state", rcc::MachineFunctionStableID::fromValue(1));
- if (!InstructionSet || !Function)
+ if (!Target || !Function)
return;
Context.expect(!(*Function)->setTargetState(rcc::TargetFunctionState{}),
@@ -225,7 +229,7 @@ void testMachineFunctionOwnershipAndVerification(TestContext &Context) {
"machine function must accept one verified target state");
Context.expect((*Function)->getTargetInfo<TestFunctionInfo>() != nullptr &&
(*Function)->getTargetInfo<CollidingFunctionInfo>() == nullptr &&
- rcc::MachineVerifier::verify(**Function, *InstructionSet).has_value(),
+ rcc::MachineVerifier::verify(**Function, *Target).has_value(),
"machine function must expose and verify its strong target state");
auto Replacement = rcc::TargetFunctionState::create(TestFunctionInfo{8192, true, "lp64d"});
@@ -236,7 +240,7 @@ void testMachineFunctionOwnershipAndVerification(TestContext &Context) {
if (Info == nullptr)
return;
Info->CallingConvention.clear();
- Context.expect(!rcc::MachineVerifier::verify(**Function, *InstructionSet),
+ Context.expect(!rcc::MachineVerifier::verify(**Function, *Target),
"machine verifier must reject invalid concrete target state");
}
diff --git a/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp
new file mode 100644
index 0000000..da13e19
--- /dev/null
+++ b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+[[nodiscard]] rcc::PhysicalRegister phys(std::uint32_t Number) {
+ return *rcc::PhysicalRegister::create(Number);
+}
+
+[[nodiscard]] rcc::OperandDescriptor registerOperand(rcc::RegisterOperandRole Role,
+ rcc::RegisterClassID Class,
+ rcc::RegisterBankID Bank,
+ bool AllowSubReg = false) {
+ rcc::OperandDescriptor Operand;
+ Operand.Kinds = {rcc::MachineOperand::Kind::Register};
+ Operand.RegisterRole = Role;
+ Operand.RegClass = Class;
+ Operand.RegBank = Bank;
+ Operand.AllowSubReg = AllowSubReg;
+ return Operand;
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription>
+createTarget(rcc::TargetRegisterInfoDesc RegisterDescription,
+ rcc::InstructionDescriptor Instruction) {
+ auto Registers = rcc::TargetRegisterInfo::create(std::move(RegisterDescription));
+ if (!Registers)
+ return std::unexpected(Registers.error());
+
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Instruction));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::TargetDescription::create(std::move(*Instructions), std::move(*Registers));
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription>
+createOperandTarget(rcc::OperandDescriptor Operand,
+ rcc::TargetRegisterInfoDesc RegisterDescription =
+ rcc::test::createTestTargetRegisterInfoDescription()) {
+ rcc::InstructionDescriptor Instruction;
+ Instruction.InstructionOpcode = rcc::Opcode::fromValue(1);
+ Instruction.Name = "TEST_CONSTRAINED";
+ Instruction.Operands.push_back(Operand);
+ if (Instruction.Operands.front().RegisterRole == rcc::RegisterOperandRole::Def) {
+ Instruction.NumExplicitDefs = 1;
+ }
+ Instruction.Flags = {rcc::InstructionFlag::ProducesCode};
+
+ return createTarget(std::move(RegisterDescription), std::move(Instruction));
+}
+
+void testRegisterTopology(TestContext &Context) {
+ auto Target = rcc::test::createTestTargetRegisterInfo();
+ Context.expect(Target.has_value(), "TestTarget register description must be valid");
+ if (!Target)
+ return;
+
+ const auto G0 = phys(rcc::test::TestRegister::G0);
+ const auto G0Low32 = phys(rcc::test::TestRegister::G0Low32);
+ const auto G0High32 = phys(rcc::test::TestRegister::G0High32);
+ const auto G0Low16 = phys(rcc::test::TestRegister::G0Low16);
+ const auto G1 = phys(rcc::test::TestRegister::G1);
+ const auto F0 = phys(rcc::test::TestRegister::F0);
+
+ Context.expect(Target->registerUnits().size() == 11 && Target->physicalRegisters().size() == 11 &&
+ Target->registerClasses().size() == 8 && Target->registerBanks().size() == 2,
+ "TestTarget must expose its complete static register tables");
+ const rcc::PhysicalRegisterDesc *Named = Target->physicalRegister("g0");
+ Context.expect(Named != nullptr && Named->Reg == G0 && Named->Encoding == 0,
+ "physical register name lookup must preserve identity and encoding");
+
+ Context.expect(Target->regsOverlap(G0, G0Low32) && Target->regsOverlap(G0, G0High32) &&
+ Target->regsOverlap(G0Low32, G0Low16) &&
+ !Target->regsOverlap(G0Low32, G0High32) && !Target->regsOverlap(G0, G1),
+ "physical overlap must be derived from shared register units");
+ Context.expect(std::ranges::find(Target->aliases(G0), G0Low32) != Target->aliases(G0).end() &&
+ std::ranges::find(Target->aliases(G0Low32), G0High32) ==
+ Target->aliases(G0Low32).end(),
+ "alias queries must include only overlapping physical registers");
+
+ Context.expect(Target->subRegister(G0, rcc::test::TestSubReg::Low32) == G0Low32 &&
+ Target->subRegister(G0Low32, rcc::test::TestSubReg::Low16) == G0Low16 &&
+ Target->isSubRegister(G0, G0Low16) &&
+ std::ranges::find(Target->superRegisters(G0Low16), G0) !=
+ Target->superRegisters(G0Low16).end() &&
+ Target->composeSubRegIndices(rcc::test::TestSubReg::Low32,
+ rcc::test::TestSubReg::Low16) ==
+ rcc::test::TestSubReg::Low16,
+ "subregister queries must preserve direct, transitive, and composed mappings");
+ Context.expect(Target->isValidLaneMask(G0, rcc::LaneMask::fromRaw(0x3)) &&
+ Target->isValidLaneMask(G0, rcc::LaneMask::all()) &&
+ !Target->isValidLaneMask(G0, rcc::LaneMask::fromRaw(0x10)),
+ "lane masks must be checked against target register coverage");
+
+ Context.expect(
+ Target->isSubclass(rcc::test::TestRegisterClass::GPR64Alloc,
+ rcc::test::TestRegisterClass::GPR64) &&
+ !Target->isSubclass(rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterClass::GPR64Alloc) &&
+ Target->registerClassesOverlap(rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterClass::GPR64Alloc) &&
+ Target->subRegClass(rcc::test::TestRegisterClass::GPR64, rcc::test::TestSubReg::Low32) ==
+ rcc::test::TestRegisterClass::GPR32Low,
+ "register class queries must preserve hierarchy and subregister projection");
+ Context.expect(Target->bankContainsRegister(rcc::test::TestRegisterBank::GPR, G0) &&
+ !Target->bankContainsRegister(rcc::test::TestRegisterBank::GPR, F0) &&
+ !Target->registerBanksOverlap(rcc::test::TestRegisterBank::GPR,
+ rcc::test::TestRegisterBank::FPR),
+ "register bank coverage must remain distinct from register classes");
+
+ const auto CallMask = rcc::test::createTestCallPreservedMask();
+ Context.expect(CallMask->preserves(G1) &&
+ CallMask->preserves(phys(rcc::test::TestRegister::StackPointer)) &&
+ CallMask->preserves(phys(rcc::test::TestRegister::F1)) &&
+ CallMask->clobbers(G0) && Target->validateRegisterMask(*CallMask).has_value(),
+ "call register mask must use preserved-bit semantics");
+ Context.expect(Target->validateRegisterMask(rcc::RegisterMask({})).has_value(),
+ "an empty register mask must represent clobber-all");
+ Context.expect(!Target->validateRegisterMask(rcc::RegisterMask({std::uint64_t{1} << 63U})) &&
+ !Target->validateRegisterMask(rcc::RegisterMask({1})),
+ "register masks must reject preserved bits for unknown registers and noreg");
+}
+
+void testDescriptionRejection(TestContext &Context) {
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterUnits.back().ID = Description.RegisterUnits.front().ID;
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "duplicate register unit IDs must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.PhysicalSubRegisters.front().SubReg = phys(rcc::test::TestRegister::G1Low32);
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "subregister mappings with unrelated units must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterClasses.front().SuperClasses.push_back(
+ rcc::test::TestRegisterClass::GPR64Alloc);
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "cyclic register class hierarchies must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterBanks.front().MaximumSizeInBits = 32;
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "register banks smaller than a covered class must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.SubRegClassMappings.pop_back();
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "supported subregisters without class projections must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterClasses.front().Lanes = rcc::LaneMask::fromRaw(0x10);
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "register class lanes outside a member register must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterBanks.front().RegisterClasses.erase(
+ Description.RegisterBanks.front().RegisterClasses.begin());
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "a class default bank must explicitly cover that class");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterUnits.push_back({rcc::RegisterUnitID::fromValue(99), "unused"});
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "unreferenced register units must be rejected");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.SubRegCompositions.push_back(
+ {rcc::test::TestSubReg::Low16, rcc::test::TestSubReg::Low16, rcc::test::TestSubReg::Low32});
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "subregister composition bit ranges must be internally consistent");
+ }
+ {
+ auto Description = rcc::test::createTestTargetRegisterInfoDescription();
+ Description.RegisterClasses[3].CoveredBySubRegs = true;
+ Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
+ "subregister-covered classes must cover all class lanes");
+ }
+}
+
+void testTargetDescriptionConstraints(TestContext &Context) {
+ auto SubclassTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::GPR));
+ auto Function =
+ rcc::MachineFunction::create("constraints", rcc::MachineFunctionStableID::fromValue(1));
+ auto Type = rcc::LLT::makeInteger(64);
+ if (!SubclassTarget || !Function || !Type)
+ return;
+
+ auto Reg = (*Function)->registerInfo().createVirtualRegister(*Type);
+ if (!Reg)
+ return;
+ Context.expect((*Function)
+ ->registerInfo()
+ .setRegisterBank(*Reg, rcc::test::TestRegisterBank::GPR)
+ .has_value() &&
+ (*Function)
+ ->registerInfo()
+ .setRegisterClass(*Reg, rcc::test::TestRegisterClass::GPR64Alloc)
+ .has_value(),
+ "constrained test virtual register must be classified");
+
+ auto Operand = rcc::MachineOperand::registerOperand(*Reg, {rcc::RegisterOperandFlag::Define});
+ Context.expect(
+ Operand && SubclassTarget
+ ->validateExplicitOperands(rcc::Opcode::fromValue(1),
+ std::span<const rcc::MachineOperand>(&*Operand, 1),
+ (*Function)->registerInfo())
+ .has_value(),
+ "an actual register subclass must satisfy a superclass operand constraint");
+
+ auto SubRegTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR32,
+ rcc::test::TestRegisterBank::GPR, true));
+ auto BaseClassResult =
+ (*Function)->registerInfo().setRegisterClass(*Reg, rcc::test::TestRegisterClass::GPR64);
+ auto SubRegOperand = rcc::MachineOperand::registerOperand(
+ *Reg, {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32);
+ Context.expect(
+ SubRegTarget && BaseClassResult && SubRegOperand &&
+ SubRegTarget
+ ->validateExplicitOperands(rcc::Opcode::fromValue(1),
+ std::span<const rcc::MachineOperand>(&*SubRegOperand, 1),
+ (*Function)->registerInfo())
+ .has_value(),
+ "subregister class projection must satisfy a compatible superclass constraint");
+
+ auto PhysicalSubReg = rcc::MachineOperand::registerOperand(
+ rcc::Register::fromPhysical(phys(rcc::test::TestRegister::G0)),
+ {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32);
+ Context.expect(PhysicalSubReg && !SubRegTarget->validateExplicitOperands(
+ rcc::Opcode::fromValue(1),
+ std::span<const rcc::MachineOperand>(&*PhysicalSubReg, 1),
+ (*Function)->registerInfo()),
+ "physical register operands must reject nonzero subregister indices");
+
+ auto WideType = rcc::LLT::makeInteger(128);
+ if (!WideType)
+ return;
+ auto WideReg = (*Function)->registerInfo().createVirtualRegister(*WideType);
+ Context.expect(WideReg &&
+ (*Function)
+ ->registerInfo()
+ .setRegisterBank(*WideReg, rcc::test::TestRegisterBank::GPR)
+ .has_value() &&
+ !SubclassTarget->validateRegisterInfo((*Function)->registerInfo()),
+ "virtual register type must fit its selected bank");
+
+ auto UnknownClassOperand =
+ registerOperand(rcc::RegisterOperandRole::Def, rcc::RegisterClassID::fromValue(99),
+ rcc::test::TestRegisterBank::GPR);
+ Context.expect(!createOperandTarget(UnknownClassOperand),
+ "target creation must reject unknown operand register classes");
+ auto MismatchedBankOperand =
+ registerOperand(rcc::RegisterOperandRole::Def, rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::FPR);
+ Context.expect(!createOperandTarget(MismatchedBankOperand),
+ "target creation must reject incompatible class and bank constraints");
+
+ auto ConstantDescription = rcc::test::createTestTargetRegisterInfoDescription();
+ ConstantDescription.PhysicalRegisters.front().IsConstant = true;
+ auto ConstantTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def,
+ rcc::test::TestRegisterClass::GPR64,
+ rcc::test::TestRegisterBank::GPR),
+ std::move(ConstantDescription));
+ auto HintFunction =
+ rcc::MachineFunction::create("constant_hint", rcc::MachineFunctionStableID::fromValue(2));
+ if (ConstantTarget && HintFunction) {
+ auto HintReg = (*HintFunction)->registerInfo().createVirtualRegister(*Type);
+ Context.expect(HintReg &&
+ (*HintFunction)
+ ->registerInfo()
+ .setRegisterClass(*HintReg, rcc::test::TestRegisterClass::GPR64)
+ .has_value() &&
+ (*HintFunction)
+ ->registerInfo()
+ .addAllocationHint(*HintReg, phys(rcc::test::TestRegister::G0))
+ .has_value() &&
+ !ConstantTarget->validateRegisterInfo((*HintFunction)->registerInfo()),
+ "constant physical registers must be rejected as allocation hints");
+ }
+
+ auto RenamableDescription = rcc::test::createTestTargetRegisterInfoDescription();
+ RenamableDescription.PhysicalRegisters.front().IsArtificial = true;
+ rcc::InstructionDescriptor RenamableImplicit;
+ RenamableImplicit.InstructionOpcode = rcc::Opcode::fromValue(2);
+ RenamableImplicit.Name = "TEST_BAD_IMPLICIT";
+ RenamableImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), false, true});
+ Context.expect(!createTarget(std::move(RenamableDescription), std::move(RenamableImplicit)),
+ "target creation must reject renamable artificial implicit registers");
+
+ rcc::InstructionDescriptor ConflictingImplicit;
+ ConflictingImplicit.InstructionOpcode = rcc::Opcode::fromValue(3);
+ ConflictingImplicit.Name = "TEST_BAD_EARLY_IMPLICIT";
+ ConflictingImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), true, false});
+ ConflictingImplicit.ImplicitUses.push_back(
+ {phys(rcc::test::TestRegister::G0Low32), false, false});
+ Context.expect(!createTarget(rcc::test::createTestTargetRegisterInfoDescription(),
+ std::move(ConflictingImplicit)),
+ "target creation must reject overlapping early-clobber implicit operands");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testRegisterTopology(Context);
+ testDescriptionRejection(Context);
+ testTargetDescriptionConstraints(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
new file mode 100644
index 0000000..514cb97
--- /dev/null
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_library(RCCBackendTestTarget TestTarget.cpp)
+add_library(RCCBackend::TestTarget ALIAS RCCBackendTestTarget)
+
+rcc_backend_configure_target(RCCBackendTestTarget)
+target_include_directories(
+ RCCBackendTestTarget
+ PUBLIC "${RCC_BACKEND_SOURCE_DIR}/unittests"
+)
+target_link_libraries(RCCBackendTestTarget PUBLIC RCCBackend::CodeGen)
+
+rcc_backend_register_format_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
+)
+rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp")
diff --git a/backend/unittests/Target/TestTarget.cpp b/backend/unittests/Target/TestTarget.cpp
new file mode 100644
index 0000000..30fa22f
--- /dev/null
+++ b/backend/unittests/Target/TestTarget.cpp
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Target/TestTarget.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace rcc::test {
+namespace {
+
+[[nodiscard]] PhysicalRegister phys(std::uint32_t Number) {
+ return *PhysicalRegister::create(Number);
+}
+
+[[nodiscard]] RegisterUnitDesc unit(std::uint32_t ID, const char *Name) {
+ return RegisterUnitDesc{RegisterUnitID::fromValue(ID), Name};
+}
+
+[[nodiscard]] RegisterUnitLane unitLane(std::uint32_t ID, std::uint64_t Lanes) {
+ return RegisterUnitLane{RegisterUnitID::fromValue(ID), LaneMask::fromRaw(Lanes)};
+}
+
+[[nodiscard]] PhysicalRegisterDesc physicalRegister(std::uint32_t Number, const char *Name,
+ std::uint32_t Encoding,
+ std::vector<RegisterUnitLane> Units) {
+ return PhysicalRegisterDesc{phys(Number), Name, Encoding, std::move(Units)};
+}
+
+[[nodiscard]] RegisterClassDesc
+registerClass(RegisterClassID ID, const char *Name, std::vector<PhysicalRegister> Registers,
+ std::vector<PhysicalRegister> AllocationOrder, std::uint32_t SizeInBits,
+ LaneMask Lanes, RegisterBankID DefaultBank,
+ std::vector<SubRegIndex> SupportedSubRegs = {},
+ std::vector<RegisterClassID> SuperClasses = {}, bool Allocatable = true,
+ bool CoveredBySubRegs = false) {
+ return RegisterClassDesc{
+ ID,
+ Name,
+ std::move(Registers),
+ std::move(AllocationOrder),
+ SizeInBits,
+ SizeInBits,
+ SizeInBits,
+ Lanes,
+ std::move(SupportedSubRegs),
+ std::move(SuperClasses),
+ DefaultBank,
+ Allocatable,
+ 1,
+ CoveredBySubRegs,
+ };
+}
+
+} // namespace
+
+TargetRegisterInfoDesc createTestTargetRegisterInfoDescription() {
+ TargetRegisterInfoDesc Description;
+ Description.RegisterUnits = {
+ unit(1, "g0.lo16"), unit(2, "g0.bits16_31"), unit(3, "g0.bits32_47"), unit(4, "g0.hi16"),
+ unit(5, "g1.lo16"), unit(6, "g1.bits16_31"), unit(7, "g1.bits32_47"), unit(8, "g1.hi16"),
+ unit(9, "sp"), unit(10, "f0"), unit(11, "f1"),
+ };
+
+ Description.PhysicalRegisters = {
+ physicalRegister(TestRegister::G0, "g0", 0,
+ {unitLane(1, 0x1), unitLane(2, 0x2), unitLane(3, 0x4), unitLane(4, 0x8)}),
+ physicalRegister(TestRegister::G0Low32, "g0l", 0, {unitLane(1, 0x1), unitLane(2, 0x2)}),
+ physicalRegister(TestRegister::G0High32, "g0h", 0, {unitLane(3, 0x1), unitLane(4, 0x2)}),
+ physicalRegister(TestRegister::G0Low16, "g0w", 0, {unitLane(1, 0x1)}),
+ physicalRegister(TestRegister::G1, "g1", 1,
+ {unitLane(5, 0x1), unitLane(6, 0x2), unitLane(7, 0x4), unitLane(8, 0x8)}),
+ physicalRegister(TestRegister::G1Low32, "g1l", 1, {unitLane(5, 0x1), unitLane(6, 0x2)}),
+ physicalRegister(TestRegister::G1High32, "g1h", 1, {unitLane(7, 0x1), unitLane(8, 0x2)}),
+ physicalRegister(TestRegister::G1Low16, "g1w", 1, {unitLane(5, 0x1)}),
+ physicalRegister(TestRegister::StackPointer, "sp", 2, {unitLane(9, 0xF)}),
+ physicalRegister(TestRegister::F0, "f0", 0, {unitLane(10, 0x1)}),
+ physicalRegister(TestRegister::F1, "f1", 1, {unitLane(11, 0x1)}),
+ };
+
+ Description.SubRegIndices = {
+ SubRegIndexDesc{TestSubReg::Low32, "low32", LaneMask::fromRaw(0x3), 32, 0},
+ SubRegIndexDesc{TestSubReg::High32, "high32", LaneMask::fromRaw(0xC), 32, 32},
+ SubRegIndexDesc{TestSubReg::Low16, "low16", LaneMask::fromRaw(0x1), 16, 0},
+ };
+ Description.PhysicalSubRegisters = {
+ {phys(TestRegister::G0), TestSubReg::Low32, phys(TestRegister::G0Low32)},
+ {phys(TestRegister::G0), TestSubReg::High32, phys(TestRegister::G0High32)},
+ {phys(TestRegister::G0), TestSubReg::Low16, phys(TestRegister::G0Low16)},
+ {phys(TestRegister::G0Low32), TestSubReg::Low16, phys(TestRegister::G0Low16)},
+ {phys(TestRegister::G1), TestSubReg::Low32, phys(TestRegister::G1Low32)},
+ {phys(TestRegister::G1), TestSubReg::High32, phys(TestRegister::G1High32)},
+ {phys(TestRegister::G1), TestSubReg::Low16, phys(TestRegister::G1Low16)},
+ {phys(TestRegister::G1Low32), TestSubReg::Low16, phys(TestRegister::G1Low16)},
+ };
+ Description.SubRegCompositions = {
+ {TestSubReg::Low32, TestSubReg::Low16, TestSubReg::Low16},
+ };
+
+ const auto G0 = phys(TestRegister::G0);
+ const auto G0Low32 = phys(TestRegister::G0Low32);
+ const auto G0High32 = phys(TestRegister::G0High32);
+ const auto G0Low16 = phys(TestRegister::G0Low16);
+ const auto G1 = phys(TestRegister::G1);
+ const auto G1Low32 = phys(TestRegister::G1Low32);
+ const auto G1High32 = phys(TestRegister::G1High32);
+ const auto G1Low16 = phys(TestRegister::G1Low16);
+ const auto StackPointer = phys(TestRegister::StackPointer);
+ const auto F0 = phys(TestRegister::F0);
+ const auto F1 = phys(TestRegister::F1);
+
+ Description.RegisterClasses = {
+ registerClass(TestRegisterClass::GPR64, "GPR64", {G0, G1}, {G0, G1}, 64,
+ LaneMask::fromRaw(0xF), TestRegisterBank::GPR,
+ {TestSubReg::Low32, TestSubReg::High32, TestSubReg::Low16}, {}, true, true),
+ registerClass(TestRegisterClass::GPR64Alloc, "GPR64Alloc", {G0, G1}, {G1, G0}, 64,
+ LaneMask::fromRaw(0xF), TestRegisterBank::GPR, {}, {TestRegisterClass::GPR64}),
+ registerClass(TestRegisterClass::GPR32, "GPR32", {G0Low32, G0High32, G1Low32, G1High32},
+ {G0Low32, G1Low32, G0High32, G1High32}, 32, LaneMask::fromRaw(0x3),
+ TestRegisterBank::GPR),
+ registerClass(TestRegisterClass::GPR32Low, "GPR32Low", {G0Low32, G1Low32}, {G0Low32, G1Low32},
+ 32, LaneMask::fromRaw(0x3), TestRegisterBank::GPR, {TestSubReg::Low16},
+ {TestRegisterClass::GPR32}),
+ registerClass(TestRegisterClass::GPR32High, "GPR32High", {G0High32, G1High32},
+ {G0High32, G1High32}, 32, LaneMask::fromRaw(0x3), TestRegisterBank::GPR, {},
+ {TestRegisterClass::GPR32}),
+ registerClass(TestRegisterClass::GPR16, "GPR16", {G0Low16, G1Low16}, {G0Low16, G1Low16}, 16,
+ LaneMask::fromRaw(0x1), TestRegisterBank::GPR),
+ registerClass(TestRegisterClass::FPR64, "FPR64", {F0, F1}, {F0, F1}, 64,
+ LaneMask::fromRaw(0x1), TestRegisterBank::FPR),
+ registerClass(TestRegisterClass::SpecialGPR64, "SpecialGPR64", {StackPointer}, {}, 64,
+ LaneMask::fromRaw(0xF), TestRegisterBank::GPR, {}, {}, false),
+ };
+
+ Description.SubRegClassMappings = {
+ {TestRegisterClass::GPR64, TestSubReg::Low32, TestRegisterClass::GPR32Low},
+ {TestRegisterClass::GPR64, TestSubReg::High32, TestRegisterClass::GPR32High},
+ {TestRegisterClass::GPR64, TestSubReg::Low16, TestRegisterClass::GPR16},
+ {TestRegisterClass::GPR32Low, TestSubReg::Low16, TestRegisterClass::GPR16},
+ };
+ Description.RegisterBanks = {
+ {TestRegisterBank::GPR,
+ "GPR",
+ 64,
+ {TestRegisterClass::GPR64, TestRegisterClass::GPR64Alloc, TestRegisterClass::GPR32,
+ TestRegisterClass::GPR32Low, TestRegisterClass::GPR32High, TestRegisterClass::GPR16,
+ TestRegisterClass::SpecialGPR64}},
+ {TestRegisterBank::FPR, "FPR", 64, {TestRegisterClass::FPR64}},
+ };
+
+ return Description;
+}
+
+Expected<TargetRegisterInfo> createTestTargetRegisterInfo() {
+ return TargetRegisterInfo::create(createTestTargetRegisterInfoDescription());
+}
+
+Expected<TargetDescription> createTestTargetDescription(InstructionSet Instructions) {
+ auto Registers = createTestTargetRegisterInfo();
+ if (!Registers)
+ return std::unexpected(Registers.error());
+ return TargetDescription::create(std::move(Instructions), std::move(*Registers));
+}
+
+std::shared_ptr<const RegisterMask> createTestCallPreservedMask() {
+ std::uint64_t Preserved = 0;
+ Preserved |= std::uint64_t{1} << TestRegister::G1;
+ Preserved |= std::uint64_t{1} << TestRegister::G1Low32;
+ Preserved |= std::uint64_t{1} << TestRegister::G1High32;
+ Preserved |= std::uint64_t{1} << TestRegister::G1Low16;
+ Preserved |= std::uint64_t{1} << TestRegister::StackPointer;
+ Preserved |= std::uint64_t{1} << TestRegister::F1;
+ return std::make_shared<const RegisterMask>(std::vector<std::uint64_t>{Preserved});
+}
+
+} // namespace rcc::test
diff --git a/backend/unittests/Target/TestTarget.h b/backend/unittests/Target/TestTarget.h
new file mode 100644
index 0000000..3c2aa51
--- /dev/null
+++ b/backend/unittests/Target/TestTarget.h
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef HOME_RICHARD_CODE_RCC_PROJECT_BACKEND_UNITTESTS_TARGET_TESTTARGET_H
+#define HOME_RICHARD_CODE_RCC_PROJECT_BACKEND_UNITTESTS_TARGET_TESTTARGET_H
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <memory>
+
+namespace rcc::test {
+
+struct TestRegister final {
+
+ static constexpr std::uint32_t G0 = 1;
+ static constexpr std::uint32_t G0Low32 = 2;
+ static constexpr std::uint32_t G0High32 = 3;
+ static constexpr std::uint32_t G0Low16 = 4;
+ static constexpr std::uint32_t G1 = 5;
+ static constexpr std::uint32_t G1Low32 = 6;
+ static constexpr std::uint32_t G1High32 = 7;
+ static constexpr std::uint32_t G1Low16 = 8;
+ static constexpr std::uint32_t StackPointer = 9;
+ static constexpr std::uint32_t F0 = 10;
+ static constexpr std::uint32_t F1 = 11;
+};
+
+struct TestSubReg final {
+
+ static constexpr SubRegIndex Low32 = SubRegIndex::fromValue(1);
+ static constexpr SubRegIndex High32 = SubRegIndex::fromValue(2);
+ static constexpr SubRegIndex Low16 = SubRegIndex::fromValue(3);
+};
+
+struct TestRegisterBank final {
+
+ static constexpr RegisterBankID GPR = RegisterBankID::fromValue(1);
+ static constexpr RegisterBankID FPR = RegisterBankID::fromValue(2);
+};
+
+struct TestRegisterClass final {
+
+ static constexpr RegisterClassID GPR64 = RegisterClassID::fromValue(1);
+ static constexpr RegisterClassID GPR64Alloc = RegisterClassID::fromValue(2);
+ static constexpr RegisterClassID GPR32 = RegisterClassID::fromValue(3);
+ static constexpr RegisterClassID GPR32Low = RegisterClassID::fromValue(4);
+ static constexpr RegisterClassID GPR32High = RegisterClassID::fromValue(5);
+ static constexpr RegisterClassID GPR16 = RegisterClassID::fromValue(6);
+ static constexpr RegisterClassID FPR64 = RegisterClassID::fromValue(7);
+ static constexpr RegisterClassID SpecialGPR64 = RegisterClassID::fromValue(8);
+};
+
+[[nodiscard]] TargetRegisterInfoDesc createTestTargetRegisterInfoDescription();
+[[nodiscard]] Expected<TargetRegisterInfo> createTestTargetRegisterInfo();
+[[nodiscard]] Expected<TargetDescription> createTestTargetDescription(InstructionSet Instructions);
+[[nodiscard]] std::shared_ptr<const RegisterMask> createTestCallPreservedMask();
+
+} // namespace rcc::test
+
+#endif // HOME_RICHARD_CODE_RCC_PROJECT_BACKEND_UNITTESTS_TARGET_TESTTARGET_H