diff options
Diffstat (limited to 'backend')
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 ®isters() 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 |
