aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 16:07:24 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 16:07:24 +0800
commit924dcd36ebed84f04729de05ec08b311a36a04c1 (patch)
tree300af742968d4d48915ef178855741f7087fc979 /backend
parentcfdbcc469f9691bd9dce3b0b86982a63d86ca6b7 (diff)
downloadrcc-project-924dcd36ebed84f04729de05ec08b311a36a04c1.tar.gz
rcc-project-924dcd36ebed84f04729de05ec08b311a36a04c1.tar.xz
[CodeGen] Add target register descriptions
Introduce an immutable register topology for units, subregisters, classes, banks, and preserved masks. Bind instruction descriptors to this topology through TargetDescription so builders, cloning, and verification share one target-aware contract. Add the test-only TestTarget model and positive and negative target fact coverage.
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