From fd8770b01a7a3ae85ba85fb43b21803c88593ad7 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 12:05:36 +0800 Subject: [CodeGen] Add machine instruction descriptors Introduce an immutable instruction registry with exact operand, implicit register, tie, pseudo lifecycle, and memory constraints. Validate descriptor tables before publication and use the same rules to verify MachineInstr instances. --- .../include/rcc/CodeGen/InstructionDescriptor.h | 264 ++++++++++ backend/include/rcc/CodeGen/MachineIRTypes.h | 10 + backend/include/rcc/CodeGen/MachineOperand.h | 1 + backend/lib/CodeGen/CMakeLists.txt | 4 + backend/lib/CodeGen/InstructionDescriptor.cpp | 531 +++++++++++++++++++++ backend/unittests/CodeGen/CMakeLists.txt | 18 + .../CodeGen/InstructionDescriptorTest.cpp | 358 ++++++++++++++ 7 files changed, 1186 insertions(+) create mode 100644 backend/include/rcc/CodeGen/InstructionDescriptor.h create mode 100644 backend/lib/CodeGen/InstructionDescriptor.cpp create mode 100644 backend/unittests/CodeGen/InstructionDescriptorTest.cpp (limited to 'backend') diff --git a/backend/include/rcc/CodeGen/InstructionDescriptor.h b/backend/include/rcc/CodeGen/InstructionDescriptor.h new file mode 100644 index 0000000..c6049b9 --- /dev/null +++ b/backend/include/rcc/CodeGen/InstructionDescriptor.h @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_INSTRUCTIONDESCRIPTOR_H +#define RCC_CODEGEN_INSTRUCTIONDESCRIPTOR_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/Register.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineInstr; +class MachineRegisterInfo; + +class OperandKindSet final { +public: + static_assert(static_cast(MachineOperand::Kind::Count) <= 32); + + constexpr OperandKindSet() noexcept = default; + + OperandKindSet(std::initializer_list Kinds) noexcept; + + [[nodiscard]] constexpr bool empty() const noexcept { + return Mask == 0; + } + + [[nodiscard]] constexpr bool contains(MachineOperand::Kind Kind) const noexcept { + const auto Index = static_cast(Kind); + if (Index >= static_cast(MachineOperand::Kind::Count)) + return false; + return (Mask & (std::uint32_t{1} << Index)) != 0; + } + + constexpr void add(MachineOperand::Kind Kind) noexcept { + const auto Index = static_cast(Kind); + if (Index < static_cast(MachineOperand::Kind::Count)) + Mask |= std::uint32_t{1} << Index; + } + + [[nodiscard]] constexpr std::uint32_t rawValue() const noexcept { + return Mask; + } + + friend constexpr bool operator==(OperandKindSet Left, OperandKindSet Right) noexcept = default; + +private: + std::uint32_t Mask = 0; +}; + +enum class RegisterOperandRole : std::uint8_t { + None, + Def, + Use, + DefUse, +}; + +enum class MemoryOperandRole : std::uint8_t { + None, + Address, + Value, + Size, + Ordering, +}; + +enum class InstructionFlag : std::uint8_t { + Terminator, + Branch, + IndirectBranch, + Call, + Return, + Barrier, + MayLoad, + MayStore, + HasSideEffects, + Pseudo, + Meta, + AllowedPreLegalizer, + ProducesCode, + Commutable, + Rematerializable, + Predicable, + NotDuplicable, + Convergent, + UnmodeledSideEffects, + Phi, + Generic, + TerminalEmission, + Count, +}; + +class InstructionFlags final { +public: + static_assert(static_cast(InstructionFlag::Count) <= 32); + + constexpr InstructionFlags() noexcept = default; + + InstructionFlags(std::initializer_list Flags) noexcept; + + [[nodiscard]] constexpr bool has(InstructionFlag Flag) const noexcept { + const auto Index = static_cast(Flag); + if (Index >= static_cast(InstructionFlag::Count)) + return false; + return (Mask & (std::uint32_t{1} << Index)) != 0; + } + + constexpr void set(InstructionFlag Flag) noexcept { + const auto Index = static_cast(Flag); + if (Index < static_cast(InstructionFlag::Count)) + Mask |= std::uint32_t{1} << Index; + } + + constexpr void clear(InstructionFlag Flag) noexcept { + const auto Index = static_cast(Flag); + if (Index < static_cast(InstructionFlag::Count)) + Mask &= ~(std::uint32_t{1} << Index); + } + + [[nodiscard]] constexpr std::uint32_t rawValue() const noexcept { + return Mask; + } + + friend constexpr bool operator==(InstructionFlags Left, + InstructionFlags Right) noexcept = default; + +private: + std::uint32_t Mask = 0; +}; + +enum class PseudoOwner : std::uint8_t { + None, + Legalizer, + RegisterBankSelect, + InstructionSelect, + PhiElimination, + TwoAddressRewrite, + RegisterAllocator, + PrePEIExpansion, + PrologueEpilogueInsertion, + PostRAExpansion, + Finalization, + AsmPrinter, +}; + +enum class PipelineStage : std::uint8_t { + MachineIRInput, + Legalized, + RegBankSelected, + Selected, + SSAEliminated, + TiedOpsRewritten, + RegisterAllocated, + FrameFinalized, + PostRAFinalized, + Emission, +}; + +struct OperandDescriptor final { + OperandKindSet Kinds; + RegisterOperandRole RegisterRole = RegisterOperandRole::None; + std::optional RegClass; + std::optional RegBank; + std::optional ImmediatePredicate; + MemoryOperandRole MemoryRole = MemoryOperandRole::None; + std::optional DefaultTiedOperand; + bool EarlyClobber = false; + bool Optional = false; + bool VariadicGroup = false; + bool AllowSubReg = false; + std::optional Name; + std::optional TargetOperandType; + std::uint32_t AllowedTargetFlags = 0; +}; + +struct ImplicitRegisterDescriptor final { + PhysicalRegister Reg; + bool EarlyClobber = false; + bool Renamable = false; + + friend bool operator==(const ImplicitRegisterDescriptor &Left, + const ImplicitRegisterDescriptor &Right) = default; +}; + +struct InstructionDescriptor final { + Opcode InstructionOpcode; + std::string Name; + std::vector Operands; + std::size_t NumExplicitDefs = 0; + bool Variadic = false; + std::vector ImplicitDefs; + std::vector ImplicitUses; + bool AllowExtraImplicitOperands = false; + InstructionFlags Flags; + SchedulingClassID SchedulingClass; + EncodingCategoryID EncodingCategory; + AssemblyFormatID AssemblyFormat; + bool RequiresMemoryOperands = false; + PseudoOwner Owner = PseudoOwner::None; + PipelineStage Deadline = PipelineStage::Emission; +}; + +struct ImmediatePredicate final { + ImmediatePredicateID ID; + std::string Name; + std::function Predicate; +}; + +class InstructionSet final { +public: + [[nodiscard]] static Expected + create(std::vector Descriptors, + std::vector ImmediatePredicates = {}); + + [[nodiscard]] const InstructionDescriptor *descriptor(Opcode InstructionOpcode) const noexcept; + [[nodiscard]] const InstructionDescriptor *descriptor(std::string_view Name) const noexcept; + [[nodiscard]] std::span descriptors() const noexcept { + return Descriptors; + } + + [[nodiscard]] Expected validateExplicitOperands(Opcode InstructionOpcode, + std::span Operands, + const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected validateInstruction(const MachineInstr &Instruction, + const MachineRegisterInfo &RegInfo) const; + + [[nodiscard]] Expected> + createDefaultImplicitOperands(const InstructionDescriptor &Descriptor) const; + +private: + InstructionSet(std::vector Descriptors, + std::vector ImmediatePredicates) + : Descriptors(std::move(Descriptors)), ImmediatePredicates(std::move(ImmediatePredicates)) {} + + [[nodiscard]] const ImmediatePredicate * + immediatePredicate(ImmediatePredicateID ID) const noexcept; + [[nodiscard]] Expected validateExplicitOperands(const InstructionDescriptor &Descriptor, + std::span Operands, + const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected validateOperand(const OperandDescriptor &Descriptor, + const MachineOperand &Operand, + const MachineRegisterInfo &RegInfo) const; + + std::vector Descriptors; + std::vector ImmediatePredicates; + std::unordered_map OpcodeToIndex; + std::unordered_map NameToIndex; + std::unordered_map PredicateToIndex; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_INSTRUCTIONDESCRIPTOR_H diff --git a/backend/include/rcc/CodeGen/MachineIRTypes.h b/backend/include/rcc/CodeGen/MachineIRTypes.h index 6d2a710..de6be5a 100644 --- a/backend/include/rcc/CodeGen/MachineIRTypes.h +++ b/backend/include/rcc/CodeGen/MachineIRTypes.h @@ -47,6 +47,11 @@ struct SubRegIndexTag; struct RegisterBankIDTag; struct RegisterClassIDTag; struct SchedulingClassIDTag; +struct ImmediatePredicateIDTag; +struct NamedOperandIDTag; +struct TargetOperandTypeIDTag; +struct EncodingCategoryIDTag; +struct AssemblyFormatIDTag; struct SyncScopeIDTag; struct MemoryAliasSourceIDTag; struct OriginIDTag; @@ -61,6 +66,11 @@ using SubRegIndex = MachineIndex; using RegisterBankID = MachineIndex; using RegisterClassID = MachineIndex; using SchedulingClassID = MachineIndex; +using ImmediatePredicateID = MachineIndex; +using NamedOperandID = MachineIndex; +using TargetOperandTypeID = MachineIndex; +using EncodingCategoryID = MachineIndex; +using AssemblyFormatID = MachineIndex; using SyncScopeID = MachineIndex; using MemoryAliasSourceID = MachineIndex; using OriginID = MachineIndex; diff --git a/backend/include/rcc/CodeGen/MachineOperand.h b/backend/include/rcc/CodeGen/MachineOperand.h index 0da9f25..35cf45b 100644 --- a/backend/include/rcc/CodeGen/MachineOperand.h +++ b/backend/include/rcc/CodeGen/MachineOperand.h @@ -213,6 +213,7 @@ public: Metadata, TargetIndex, MCSymbol, + Count, }; MachineOperand(const MachineOperand &) = delete; diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index ffa5e82..d700d54 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -2,6 +2,7 @@ set( RCCBackendCodeGenSources + InstructionDescriptor.cpp LowLevelType.cpp MachineBasicBlock.cpp MachineCFGUpdater.cpp @@ -17,6 +18,7 @@ set( ) set( RCCBackendCodeGenHeaders + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlock.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFGUpdater.h" @@ -61,6 +63,7 @@ install( ) rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" @@ -76,6 +79,7 @@ rcc_backend_register_format_files( ${RCCBackendCodeGenHeaders} ) rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" diff --git a/backend/lib/CodeGen/InstructionDescriptor.cpp b/backend/lib/CodeGen/InstructionDescriptor.cpp new file mode 100644 index 0000000..e5efad7 --- /dev/null +++ b/backend/lib/CodeGen/InstructionDescriptor.cpp @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/InstructionDescriptor.h" + +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { +namespace { + +[[nodiscard]] bool isRegisterOnly(const OperandKindSet &Kinds) noexcept { + OperandKindSet RegisterKind{MachineOperand::Kind::Register}; + return Kinds == RegisterKind; +} + +[[nodiscard]] bool isDefRole(RegisterOperandRole Role) noexcept { + return Role == RegisterOperandRole::Def || Role == RegisterOperandRole::DefUse; +} + +[[nodiscard]] bool isUseRole(RegisterOperandRole Role) noexcept { + return Role == RegisterOperandRole::Use || Role == RegisterOperandRole::DefUse; +} + +[[nodiscard]] bool formsDefUsePair(RegisterOperandRole Left, RegisterOperandRole Right) noexcept { + return (Left == RegisterOperandRole::Def && Right == RegisterOperandRole::Use) || + (Left == RegisterOperandRole::Use && Right == RegisterOperandRole::Def); +} + +[[nodiscard]] Expected validateDescriptorStructure(const InstructionDescriptor &Descriptor) { + if (!Descriptor.InstructionOpcode.isValid()) + return makeUnexpected(ErrorCode::InvalidArgument, "instruction descriptor opcode is invalid"); + if (Descriptor.Name.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, "instruction descriptor name is empty"); + if (Descriptor.NumExplicitDefs > Descriptor.Operands.size()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "instruction descriptor explicit def count is out of range"); + } + + bool SawOptional = false; + for (std::size_t Index = 0; Index < Descriptor.Operands.size(); ++Index) { + const OperandDescriptor &Operand = Descriptor.Operands[Index]; + if (Operand.Kinds.empty()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "operand descriptor must accept at least one operand kind"); + } + + const bool IsRegister = Operand.Kinds.contains(MachineOperand::Kind::Register); + if (IsRegister != (Operand.RegisterRole != RegisterOperandRole::None)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "register-capable operand descriptor must declare a register role"); + } + if (isDefRole(Operand.RegisterRole) && !isRegisterOnly(Operand.Kinds)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "register definition descriptor must accept only registers"); + } + if (!IsRegister && + (Operand.RegClass || Operand.RegBank || Operand.EarlyClobber || Operand.AllowSubReg)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "non-register operand descriptor has register constraints"); + } + if (Operand.EarlyClobber && !isDefRole(Operand.RegisterRole)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "early-clobber operand descriptor must define a register"); + } + if (Index < Descriptor.NumExplicitDefs && !isDefRole(Operand.RegisterRole)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "canonical explicit defs must precede instruction uses"); + } + if (Index >= Descriptor.NumExplicitDefs && isDefRole(Operand.RegisterRole)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "register definition appears after the explicit def prefix"); + } + + if (Descriptor.Variadic && Operand.Optional) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variadic instruction descriptor cannot contain optional operands"); + } + if (Operand.Optional) + SawOptional = true; + else if (SawOptional && !Operand.VariadicGroup) { + return makeUnexpected(ErrorCode::InvalidArgument, + "optional operand descriptors must form a trailing suffix"); + } + + if (Operand.VariadicGroup && + (!Descriptor.Variadic || Index + 1 != Descriptor.Operands.size())) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variadic operand group must be the final descriptor"); + } + if (Operand.VariadicGroup && (Operand.Optional || Operand.DefaultTiedOperand)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variadic operand descriptor cannot be optional or have a default tie"); + } + } + + if (Descriptor.Variadic && + (Descriptor.Operands.empty() || !Descriptor.Operands.back().VariadicGroup)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variadic instruction descriptor requires a final variadic group"); + } + if (!Descriptor.Variadic && + std::ranges::any_of(Descriptor.Operands, + [](const OperandDescriptor &Operand) { return Operand.VariadicGroup; })) { + return makeUnexpected(ErrorCode::InvalidArgument, + "non-variadic instruction descriptor has a variadic group"); + } + + for (std::size_t Index = 0; Index < Descriptor.Operands.size(); ++Index) { + const OperandDescriptor &Operand = Descriptor.Operands[Index]; + if (!Operand.DefaultTiedOperand) + continue; + const std::size_t OtherIndex = *Operand.DefaultTiedOperand; + if (OtherIndex >= Descriptor.Operands.size() || OtherIndex == Index || + Descriptor.Operands[OtherIndex].DefaultTiedOperand != Index) { + return makeUnexpected(ErrorCode::InvalidArgument, + "default operand tie must be reciprocal and in range"); + } + if (Operand.Optional || Descriptor.Operands[OtherIndex].Optional || + !isRegisterOnly(Operand.Kinds) || !isRegisterOnly(Descriptor.Operands[OtherIndex].Kinds) || + !formsDefUsePair(Operand.RegisterRole, Descriptor.Operands[OtherIndex].RegisterRole)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "default operand tie must connect required register def and use"); + } + } + + std::unordered_set OperandNames; + for (const OperandDescriptor &Operand : Descriptor.Operands) { + if (Operand.Name && !OperandNames.insert(*Operand.Name).second) { + return makeUnexpected(ErrorCode::InvalidArgument, + "instruction descriptor has duplicate named operands"); + } + } + + std::unordered_set ImplicitDefs; + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitDefs) { + if (!Implicit.Reg.isValid() || !ImplicitDefs.insert(Implicit.Reg).second) { + return makeUnexpected(ErrorCode::InvalidArgument, + "implicit definitions must name unique physical registers"); + } + } + std::unordered_set ImplicitUses; + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitUses) { + if (!Implicit.Reg.isValid() || Implicit.EarlyClobber || + !ImplicitUses.insert(Implicit.Reg).second) { + return makeUnexpected(ErrorCode::InvalidArgument, + "implicit uses must name unique physical registers and cannot " + "early-clobber"); + } + } + + const bool IsBranch = Descriptor.Flags.has(InstructionFlag::Branch); + const bool IsTerminator = Descriptor.Flags.has(InstructionFlag::Terminator); + if ((IsBranch || Descriptor.Flags.has(InstructionFlag::Return) || + Descriptor.Flags.has(InstructionFlag::Barrier) || + Descriptor.Flags.has(InstructionFlag::IndirectBranch)) && + !IsTerminator) { + return makeUnexpected(ErrorCode::InvalidArgument, + "branch, return, and barrier descriptors must be terminators"); + } + if (Descriptor.Flags.has(InstructionFlag::IndirectBranch) && !IsBranch) { + return makeUnexpected(ErrorCode::InvalidArgument, + "indirect branch descriptor must also be a branch"); + } + + const bool IsPseudo = Descriptor.Flags.has(InstructionFlag::Pseudo); + const bool IsMeta = Descriptor.Flags.has(InstructionFlag::Meta); + if (IsMeta && (!IsPseudo || Descriptor.Flags.has(InstructionFlag::ProducesCode))) { + return makeUnexpected(ErrorCode::InvalidArgument, + "meta instruction must be a non-emitting pseudo"); + } + if (!IsPseudo && Descriptor.Owner != PseudoOwner::None) { + return makeUnexpected(ErrorCode::InvalidArgument, + "non-pseudo instruction cannot have a pseudo owner"); + } + if (IsPseudo && !IsMeta && Descriptor.Owner == PseudoOwner::None) { + return makeUnexpected(ErrorCode::InvalidArgument, + "executable pseudo must declare its expansion owner"); + } + if (Descriptor.Flags.has(InstructionFlag::TerminalEmission) && + (!IsPseudo || Descriptor.Owner != PseudoOwner::AsmPrinter || + !Descriptor.Flags.has(InstructionFlag::ProducesCode))) { + return makeUnexpected(ErrorCode::InvalidArgument, + "terminal emission pseudo must produce code and be owned by AsmPrinter"); + } + if (Descriptor.RequiresMemoryOperands && !Descriptor.Flags.has(InstructionFlag::MayLoad) && + !Descriptor.Flags.has(InstructionFlag::MayStore)) { + return makeUnexpected(ErrorCode::InvalidArgument, "MMO requirement needs mayLoad or mayStore"); + } + + return {}; +} + +[[nodiscard]] const OperandDescriptor &descriptorForOperand(const InstructionDescriptor &Descriptor, + std::size_t Index) { + if (Index < Descriptor.Operands.size()) + return Descriptor.Operands[Index]; + return Descriptor.Operands.back(); +} + +} // namespace + +OperandKindSet::OperandKindSet(std::initializer_list Kinds) noexcept { + for (const MachineOperand::Kind Kind : Kinds) + add(Kind); +} + +InstructionFlags::InstructionFlags(std::initializer_list Flags) noexcept { + for (const InstructionFlag Flag : Flags) + set(Flag); +} + +Expected +InstructionSet::create(std::vector Descriptors, + std::vector ImmediatePredicates) { + if (Descriptors.empty()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "instruction set requires at least one descriptor"); + } + + InstructionSet Result(std::move(Descriptors), std::move(ImmediatePredicates)); + + std::unordered_set PredicateNames; + for (std::size_t Index = 0; Index < Result.ImmediatePredicates.size(); ++Index) { + const ImmediatePredicate &Predicate = Result.ImmediatePredicates[Index]; + if (Predicate.Name.empty() || !Predicate.Predicate) { + return makeUnexpected(ErrorCode::InvalidArgument, + "immediate predicate requires a name and callable"); + } + if (!Result.PredicateToIndex.emplace(Predicate.ID, Index).second) { + return makeUnexpected(ErrorCode::InvalidArgument, "duplicate immediate predicate ID"); + } + if (!PredicateNames.insert(Predicate.Name).second) { + return makeUnexpected(ErrorCode::InvalidArgument, "duplicate immediate predicate name"); + } + } + + for (std::size_t Index = 0; Index < Result.Descriptors.size(); ++Index) { + const InstructionDescriptor &Descriptor = Result.Descriptors[Index]; + const auto Validation = validateDescriptorStructure(Descriptor); + if (!Validation) + return std::unexpected(Validation.error()); + if (!Result.OpcodeToIndex.emplace(Descriptor.InstructionOpcode, Index).second) + return makeUnexpected(ErrorCode::InvalidArgument, "duplicate instruction opcode"); + if (!Result.NameToIndex.emplace(Descriptor.Name, Index).second) + return makeUnexpected(ErrorCode::InvalidArgument, "duplicate instruction name"); + + for (const OperandDescriptor &Operand : Descriptor.Operands) { + if (Operand.ImmediatePredicate && + Result.immediatePredicate(*Operand.ImmediatePredicate) == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "operand descriptor references an unknown immediate predicate"); + } + } + } + + return Result; +} + +const InstructionDescriptor *InstructionSet::descriptor(Opcode InstructionOpcode) const noexcept { + const auto Position = OpcodeToIndex.find(InstructionOpcode); + if (Position == OpcodeToIndex.end()) + return nullptr; + return &Descriptors[Position->second]; +} + +const InstructionDescriptor *InstructionSet::descriptor(std::string_view Name) const noexcept { + const auto Position = NameToIndex.find(std::string(Name)); + if (Position == NameToIndex.end()) + return nullptr; + return &Descriptors[Position->second]; +} + +Expected InstructionSet::validateExplicitOperands(const InstructionDescriptor &Descriptor, + std::span Operands, + const MachineRegisterInfo &RegInfo) const { + std::size_t RequiredCount = Descriptor.Operands.size(); + if (Descriptor.Variadic) + --RequiredCount; + else { + while (RequiredCount != 0 && Descriptor.Operands[RequiredCount - 1].Optional) + --RequiredCount; + } + + if (Operands.size() < RequiredCount || + (!Descriptor.Variadic && Operands.size() > Descriptor.Operands.size())) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction has an invalid explicit operand count"); + } + + for (std::size_t Index = 0; Index < Operands.size(); ++Index) { + const MachineOperand &Operand = Operands[Index]; + if (Operand.isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "explicit operand sequence contains an implicit register"); + } + const auto Validation = + validateOperand(descriptorForOperand(Descriptor, Index), Operand, RegInfo); + if (!Validation) + return std::unexpected(Validation.error()); + } + return {}; +} + +Expected InstructionSet::validateExplicitOperands(Opcode InstructionOpcode, + std::span Operands, + const MachineRegisterInfo &RegInfo) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateExplicitOperands(*Descriptor, Operands, RegInfo); +} + +Expected InstructionSet::validateOperand(const OperandDescriptor &Descriptor, + const MachineOperand &Operand, + const MachineRegisterInfo &RegInfo) const { + if (!Descriptor.Kinds.contains(Operand.kind())) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction operand kind does not match its descriptor"); + } + if ((Operand.targetFlags() & ~Descriptor.AllowedTargetFlags) != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction operand uses unregistered target flags"); + } + + if (Operand.isRegister()) { + const bool IsDef = Operand.isDef(); + const bool IsUse = Operand.isUse(); + const bool ExpectedDef = isDefRole(Descriptor.RegisterRole); + const bool ExpectedUse = isUseRole(Descriptor.RegisterRole); + if (IsDef != ExpectedDef || IsUse != ExpectedUse) { + return makeUnexpected(ErrorCode::VerificationError, + "register operand def/use role does not match its descriptor"); + } + if (Operand.registerFlags().has(RegisterOperandFlag::EarlyClobber) != Descriptor.EarlyClobber) { + return makeUnexpected(ErrorCode::VerificationError, + "register operand early-clobber does not match its descriptor"); + } + if (!Descriptor.AllowSubReg && Operand.subReg().value() != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "register operand has a disallowed subregister index"); + } + + const Register *Reg = Operand.getIf(); + if (Reg == nullptr) { + return makeUnexpected(ErrorCode::InternalError, "register operand has inconsistent storage"); + } + if (Reg->isVirtual()) { + 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"); + } + } + } + + if (Descriptor.ImmediatePredicate) { + const ImmediatePredicate *Predicate = immediatePredicate(*Descriptor.ImmediatePredicate); + if (Predicate == nullptr || !Predicate->Predicate(Operand)) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction operand fails its immediate predicate"); + } + } + + return {}; +} + +Expected InstructionSet::validateInstruction(const MachineInstr &Instruction, + const MachineRegisterInfo &RegInfo) const { + const InstructionDescriptor *Descriptor = descriptor(Instruction.opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + + const auto AllOperands = Instruction.operands(); + std::size_t ExplicitCount = 0; + while (ExplicitCount < AllOperands.size() && !AllOperands[ExplicitCount].isImplicit()) + ++ExplicitCount; + for (std::size_t Index = ExplicitCount; Index < AllOperands.size(); ++Index) { + if (!AllOperands[Index].isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "explicit operand appears after implicit operands"); + } + } + + const auto ExplicitValidation = + validateExplicitOperands(*Descriptor, AllOperands.first(ExplicitCount), RegInfo); + if (!ExplicitValidation) + return std::unexpected(ExplicitValidation.error()); + + for (std::size_t Index = 0; Index < ExplicitCount; ++Index) { + const MachineOperand &Operand = AllOperands[Index]; + const OperandDescriptor &OperandDesc = descriptorForOperand(*Descriptor, Index); + if (OperandDesc.DefaultTiedOperand && + Operand.tiedOperandIndex() != OperandDesc.DefaultTiedOperand) { + return makeUnexpected(ErrorCode::VerificationError, + "required descriptor tie is not materialized"); + } + if (!Operand.tiedOperandIndex()) + continue; + const std::size_t OtherIndex = *Operand.tiedOperandIndex(); + if (OtherIndex >= ExplicitCount || AllOperands[OtherIndex].tiedOperandIndex() != Index || + !Operand.isRegister() || !AllOperands[OtherIndex].isRegister() || + Operand.isDef() == AllOperands[OtherIndex].isDef() || + Operand.isUse() == AllOperands[OtherIndex].isUse()) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction operand tie is invalid or not reciprocal"); + } + } + + const std::size_t DefaultImplicitCount = + Descriptor->ImplicitDefs.size() + Descriptor->ImplicitUses.size(); + const std::size_t ActualImplicitCount = AllOperands.size() - ExplicitCount; + if (ActualImplicitCount < DefaultImplicitCount || + (!Descriptor->AllowExtraImplicitOperands && ActualImplicitCount != DefaultImplicitCount)) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction implicit operand count does not match its descriptor"); + } + + std::size_t ImplicitIndex = ExplicitCount; + auto ValidateImplicit = [&](const ImplicitRegisterDescriptor &Implicit, + bool IsDef) -> Expected { + const MachineOperand &Operand = AllOperands[ImplicitIndex++]; + const auto Reg = Operand.registerValue(); + if (!Reg || Reg->physicalRegister() != Implicit.Reg || Operand.isDef() != IsDef || + Operand.isUse() == IsDef || !Operand.isImplicit() || + Operand.registerFlags().has(RegisterOperandFlag::EarlyClobber) != Implicit.EarlyClobber || + Operand.registerFlags().has(RegisterOperandFlag::Renamable) != Implicit.Renamable) { + return makeUnexpected(ErrorCode::VerificationError, + "default implicit register operand does not match its descriptor"); + } + return {}; + }; + + for (const ImplicitRegisterDescriptor &Implicit : Descriptor->ImplicitDefs) { + auto Validation = ValidateImplicit(Implicit, true); + if (!Validation) + return std::unexpected(Validation.error()); + } + for (const ImplicitRegisterDescriptor &Implicit : Descriptor->ImplicitUses) { + auto Validation = ValidateImplicit(Implicit, false); + if (!Validation) + return std::unexpected(Validation.error()); + } + for (; ImplicitIndex < AllOperands.size(); ++ImplicitIndex) { + if (!AllOperands[ImplicitIndex].isRegister() || !AllOperands[ImplicitIndex].isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "dynamic implicit operand must be an implicit register"); + } + } + + const bool MayLoad = Descriptor->Flags.has(InstructionFlag::MayLoad); + const bool MayStore = Descriptor->Flags.has(InstructionFlag::MayStore); + if (Descriptor->RequiresMemoryOperands && Instruction.memOperands().empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "memory instruction is missing MachineMemOperand"); + } + for (const MachineMemOperand *MemOperand : Instruction.memOperands()) { + if (MemOperand == nullptr || (MemOperand->isLoad() && !MayLoad) || + (MemOperand->isStore() && !MayStore)) { + return makeUnexpected(ErrorCode::VerificationError, + "MachineMemOperand access conflicts with instruction descriptor"); + } + } + if (!MayLoad && !MayStore && !Instruction.memOperands().empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "non-memory instruction carries MachineMemOperand"); + } + + return {}; +} + +Expected> +InstructionSet::createDefaultImplicitOperands(const InstructionDescriptor &Descriptor) const { + std::vector Result; + Result.reserve(Descriptor.ImplicitDefs.size() + Descriptor.ImplicitUses.size()); + + auto AppendImplicit = [&Result](const ImplicitRegisterDescriptor &Implicit, + bool IsDef) -> Expected { + RegisterOperandFlags Flags{ + IsDef ? RegisterOperandFlag::Define : RegisterOperandFlag::Use, + RegisterOperandFlag::Implicit, + }; + if (Implicit.EarlyClobber) + Flags.set(RegisterOperandFlag::EarlyClobber); + if (Implicit.Renamable) + Flags.set(RegisterOperandFlag::Renamable); + + auto Operand = MachineOperand::registerOperand(Register::fromPhysical(Implicit.Reg), Flags); + if (!Operand) + return std::unexpected(Operand.error()); + Result.push_back(std::move(*Operand)); + return {}; + }; + + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitDefs) { + auto AppendResult = AppendImplicit(Implicit, true); + if (!AppendResult) + return std::unexpected(AppendResult.error()); + } + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitUses) { + auto AppendResult = AppendImplicit(Implicit, false); + if (!AppendResult) + return std::unexpected(AppendResult.error()); + } + return Result; +} + +const ImmediatePredicate * +InstructionSet::immediatePredicate(ImmediatePredicateID ID) const noexcept { + const auto Position = PredicateToIndex.find(ID); + if (Position == PredicateToIndex.end()) + return nullptr; + return &ImmediatePredicates[Position->second]; +} + +} // namespace rcc diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index cad2a44..a3aefde 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -3,6 +3,7 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp + InstructionDescriptorTest.cpp MachineIRCoreTest.cpp MachineValueTypesTest.cpp ) @@ -13,6 +14,21 @@ target_link_libraries(RCCBackendCodeGenCoreTypesTests PRIVATE RCCBackend::CodeGe add_test(NAME RCCBackend.CodeGen.CoreTypes COMMAND RCCBackendCodeGenCoreTypesTests) +add_executable( + RCCBackendCodeGenInstructionDescriptorTests + InstructionDescriptorTest.cpp +) +rcc_backend_configure_target(RCCBackendCodeGenInstructionDescriptorTests) +target_link_libraries( + RCCBackendCodeGenInstructionDescriptorTests + PRIVATE RCCBackend::CodeGen +) + +add_test( + NAME RCCBackend.CodeGen.InstructionDescriptor + COMMAND RCCBackendCodeGenInstructionDescriptorTests +) + add_executable(RCCBackendCodeGenMachineValueTypesTests MachineValueTypesTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineValueTypesTests) target_link_libraries(RCCBackendCodeGenMachineValueTypesTests PRIVATE RCCBackend::CodeGen) @@ -33,11 +49,13 @@ add_test( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) diff --git a/backend/unittests/CodeGen/InstructionDescriptorTest.cpp b/backend/unittests/CodeGen/InstructionDescriptorTest.cpp new file mode 100644 index 0000000..424d2e4 --- /dev/null +++ b/backend/unittests/CodeGen/InstructionDescriptorTest.cpp @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/InstructionDescriptor.h" + +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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::OperandDescriptor registerDescriptor(rcc::RegisterOperandRole Role) { + rcc::OperandDescriptor Descriptor; + Descriptor.Kinds = {rcc::MachineOperand::Kind::Register}; + Descriptor.RegisterRole = Role; + return Descriptor; +} + +[[nodiscard]] rcc::InstructionDescriptor minimalDescriptor(std::uint32_t OpcodeValue, + std::string Name) { + rcc::InstructionDescriptor Descriptor; + Descriptor.InstructionOpcode = rcc::Opcode::fromValue(OpcodeValue); + Descriptor.Name = std::move(Name); + Descriptor.Flags = {rcc::InstructionFlag::ProducesCode}; + return Descriptor; +} + +[[nodiscard]] rcc::Expected registerOperand(rcc::Register Reg, + rcc::RegisterOperandFlags Flags, + rcc::SubRegIndex SubReg = {}, + std::uint32_t TargetFlags = 0) { + return rcc::MachineOperand::registerOperand(Reg, Flags, SubReg, TargetFlags); +} + +[[nodiscard]] rcc::MachineMemOperandDesc loadDescription() { + rcc::MachineMemOperandDesc Description; + Description.Accesses = {rcc::MemoryAccessFlag::Load}; + Description.Size = *rcc::MemorySize::fixed(8); + Description.Align = *rcc::Alignment::create(8); + Description.Base = rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)); + return Description; +} + +[[nodiscard]] rcc::Expected createValidInstructionSet() { + constexpr std::uint32_t AddOpcode = 1; + constexpr std::uint32_t TwoAddressOpcode = 2; + constexpr std::uint32_t LoadOpcode = 3; + + rcc::InstructionDescriptor Add = minimalDescriptor(AddOpcode, "TEST_ADDri"); + auto Def = registerDescriptor(rcc::RegisterOperandRole::Def); + Def.RegBank = rcc::RegisterBankID::fromValue(7); + Def.RegClass = rcc::RegisterClassID::fromValue(8); + Def.Name = rcc::NamedOperandID::fromValue(1); + + auto Source = registerDescriptor(rcc::RegisterOperandRole::Use); + Source.Name = rcc::NamedOperandID::fromValue(2); + + rcc::OperandDescriptor RegisterOrImmediate; + RegisterOrImmediate.Kinds = { + rcc::MachineOperand::Kind::Register, + rcc::MachineOperand::Kind::Immediate, + }; + RegisterOrImmediate.RegisterRole = rcc::RegisterOperandRole::Use; + RegisterOrImmediate.ImmediatePredicate = rcc::ImmediatePredicateID::fromValue(1); + RegisterOrImmediate.Name = rcc::NamedOperandID::fromValue(3); + RegisterOrImmediate.AllowedTargetFlags = 0x3; + + Add.Operands.push_back(Def); + Add.Operands.push_back(Source); + Add.Operands.push_back(RegisterOrImmediate); + Add.NumExplicitDefs = 1; + Add.ImplicitDefs.push_back( + rcc::ImplicitRegisterDescriptor{*rcc::PhysicalRegister::create(1), false, true}); + Add.ImplicitUses.push_back(rcc::ImplicitRegisterDescriptor{*rcc::PhysicalRegister::create(2)}); + + rcc::InstructionDescriptor TwoAddress = minimalDescriptor(TwoAddressOpcode, "TEST_TWOADDR"); + auto TiedDef = registerDescriptor(rcc::RegisterOperandRole::Def); + auto TiedUse = registerDescriptor(rcc::RegisterOperandRole::Use); + TiedDef.DefaultTiedOperand = 1; + TiedUse.DefaultTiedOperand = 0; + TwoAddress.Operands.push_back(TiedDef); + TwoAddress.Operands.push_back(TiedUse); + TwoAddress.NumExplicitDefs = 1; + + rcc::InstructionDescriptor Load = minimalDescriptor(LoadOpcode, "TEST_LOAD"); + Load.Flags.set(rcc::InstructionFlag::MayLoad); + Load.RequiresMemoryOperands = true; + + rcc::ImmediatePredicate Signed12; + Signed12.ID = rcc::ImmediatePredicateID::fromValue(1); + Signed12.Name = "register-or-simm12"; + Signed12.Predicate = [](const rcc::MachineOperand &Operand) { + if (Operand.isRegister()) + return true; + const auto *Value = Operand.getIf(); + return Value != nullptr && *Value >= -2048 && *Value <= 2047; + }; + + std::vector Descriptors; + Descriptors.push_back(std::move(Add)); + Descriptors.push_back(std::move(TwoAddress)); + Descriptors.push_back(std::move(Load)); + std::vector Predicates; + Predicates.push_back(std::move(Signed12)); + return rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates)); +} + +void testDescriptorStructure(TestContext &Context) { + Context.expect(!rcc::InstructionSet::create({}), "an empty instruction set must be rejected"); + + auto DefAfterUses = minimalDescriptor(10, "BAD_DEF_ORDER"); + DefAfterUses.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); + Context.expect(!rcc::InstructionSet::create({DefAfterUses}), + "a definition outside the explicit def prefix must be rejected"); + + auto AmbiguousVariadic = minimalDescriptor(11, "BAD_VARIADIC"); + auto Optional = registerDescriptor(rcc::RegisterOperandRole::Use); + Optional.Optional = true; + auto Variadic = registerDescriptor(rcc::RegisterOperandRole::Use); + Variadic.VariadicGroup = true; + AmbiguousVariadic.Operands.push_back(Optional); + AmbiguousVariadic.Operands.push_back(Variadic); + AmbiguousVariadic.Variadic = true; + Context.expect(!rcc::InstructionSet::create({AmbiguousVariadic}), + "optional and variadic operand tails must not be combined"); + + auto BadTie = minimalDescriptor(12, "BAD_TIE"); + auto Def = registerDescriptor(rcc::RegisterOperandRole::Def); + auto Use = registerDescriptor(rcc::RegisterOperandRole::Use); + Def.DefaultTiedOperand = 1; + BadTie.Operands.push_back(Def); + BadTie.Operands.push_back(Use); + BadTie.NumExplicitDefs = 1; + Context.expect(!rcc::InstructionSet::create({BadTie}), "default operand ties must be reciprocal"); + + auto DuplicateNames = minimalDescriptor(13, "BAD_NAMES"); + auto FirstUse = registerDescriptor(rcc::RegisterOperandRole::Use); + auto SecondUse = registerDescriptor(rcc::RegisterOperandRole::Use); + FirstUse.Name = rcc::NamedOperandID::fromValue(9); + SecondUse.Name = rcc::NamedOperandID::fromValue(9); + DuplicateNames.Operands.push_back(FirstUse); + DuplicateNames.Operands.push_back(SecondUse); + Context.expect(!rcc::InstructionSet::create({DuplicateNames}), + "named operand IDs must be unique within an instruction"); + + auto BadBranch = minimalDescriptor(14, "BAD_BRANCH"); + BadBranch.Flags.set(rcc::InstructionFlag::Branch); + Context.expect(!rcc::InstructionSet::create({BadBranch}), + "branch descriptors must also be terminators"); + + auto UnknownPredicate = minimalDescriptor(15, "BAD_PREDICATE"); + rcc::OperandDescriptor Immediate; + Immediate.Kinds = {rcc::MachineOperand::Kind::Immediate}; + Immediate.ImmediatePredicate = rcc::ImmediatePredicateID::fromValue(99); + UnknownPredicate.Operands.push_back(Immediate); + Context.expect(!rcc::InstructionSet::create({UnknownPredicate}), + "operand descriptors must not reference unknown predicates"); +} + +void testOperandValidation(TestContext &Context) { + auto InstructionSet = createValidInstructionSet(); + Context.expect(InstructionSet.has_value(), "the complete test instruction set must be valid"); + if (!InstructionSet) + return; + + auto Function = rcc::MachineFunction::create("descriptor_operands", + rcc::MachineFunctionStableID::fromValue(1)); + Context.expect(Function.has_value(), "descriptor test function must be valid"); + if (!Function) + return; + + auto I64 = rcc::LLT::makeInteger(64); + auto Destination = (*Function)->registerInfo().createVirtualRegister(*I64); + auto Source = (*Function)->registerInfo().createVirtualRegister(*I64); + Context.expect(Destination && Source, "descriptor test virtual registers must be allocated"); + if (!Destination || !Source) + return; + + auto &RegInfo = (*Function)->registerInfo(); + Context.expect( + RegInfo.setRegisterBank(*Destination, rcc::RegisterBankID::fromValue(7)).has_value() && + RegInfo.setRegisterClass(*Destination, rcc::RegisterClassID::fromValue(8)).has_value(), + "descriptor test destination constraints must be recorded"); + + std::vector ValidOperands; + ValidOperands.push_back(*registerOperand(*Destination, {rcc::RegisterOperandFlag::Define})); + ValidOperands.push_back(*registerOperand(*Source, {rcc::RegisterOperandFlag::Use})); + ValidOperands.push_back(rcc::MachineOperand::immediate(42, 0x1)); + Context.expect( + InstructionSet->validateExplicitOperands(rcc::Opcode::fromValue(1), ValidOperands, RegInfo) + .has_value(), + "matching roles, constraints, flags, and predicate must validate"); + + std::vector WrongRole; + WrongRole.push_back(*registerOperand(*Destination, {rcc::RegisterOperandFlag::Use})); + WrongRole.push_back(*registerOperand(*Source, {rcc::RegisterOperandFlag::Use})); + WrongRole.push_back(rcc::MachineOperand::immediate(1)); + Context.expect( + !InstructionSet->validateExplicitOperands(rcc::Opcode::fromValue(1), WrongRole, RegInfo), + "a register role mismatch must be rejected"); + + std::vector BadImmediate; + BadImmediate.push_back(*registerOperand(*Destination, {rcc::RegisterOperandFlag::Define})); + BadImmediate.push_back(*registerOperand(*Source, {rcc::RegisterOperandFlag::Use})); + BadImmediate.push_back(rcc::MachineOperand::immediate(4096)); + Context.expect( + !InstructionSet->validateExplicitOperands(rcc::Opcode::fromValue(1), BadImmediate, RegInfo), + "an operand outside its immediate predicate must be rejected"); + + std::vector BadTargetFlags; + BadTargetFlags.push_back(*registerOperand(*Destination, {rcc::RegisterOperandFlag::Define})); + BadTargetFlags.push_back(*registerOperand(*Source, {rcc::RegisterOperandFlag::Use})); + BadTargetFlags.push_back(rcc::MachineOperand::immediate(1, 0x4)); + Context.expect( + !InstructionSet->validateExplicitOperands(rcc::Opcode::fromValue(1), BadTargetFlags, RegInfo), + "unregistered target operand flags must be rejected"); +} + +void testInstructionValidation(TestContext &Context) { + auto InstructionSet = createValidInstructionSet(); + auto Function = rcc::MachineFunction::create("descriptor_instructions", + rcc::MachineFunctionStableID::fromValue(2)); + Context.expect(InstructionSet && Function, "instruction validation setup must succeed"); + if (!InstructionSet || !Function) + return; + + auto I64 = rcc::LLT::makeInteger(64); + auto Reg = (*Function)->registerInfo().createVirtualRegister(*I64); + Context.expect(Reg.has_value(), "instruction validation register must be allocated"); + if (!Reg) + return; + + auto Tied = (*Function)->createInstruction(rcc::Opcode::fromValue(2)); + Context.expect(Tied.has_value(), "tied instruction must be allocated"); + if (!Tied) + return; + Context.expect( + (*Tied)->addOperand(*registerOperand(*Reg, {rcc::RegisterOperandFlag::Define})).has_value() && + (*Tied) + ->addOperand(*registerOperand(*Reg, {rcc::RegisterOperandFlag::Use})) + .has_value() && + (*Tied)->tieOperands(0, 1).has_value(), + "tied instruction operands must be materialized"); + Context.expect( + InstructionSet->validateInstruction(**Tied, (*Function)->registerInfo()).has_value(), + "a reciprocal descriptor-required tie must validate"); + + auto Untied = (*Function)->createInstruction(rcc::Opcode::fromValue(2)); + Context.expect(Untied.has_value(), "untied instruction must be allocated"); + if (Untied) { + Context.expect((*Untied) + ->addOperand(*registerOperand(*Reg, {rcc::RegisterOperandFlag::Define})) + .has_value() && + (*Untied) + ->addOperand(*registerOperand(*Reg, {rcc::RegisterOperandFlag::Use})) + .has_value(), + "untied instruction operands must be added"); + Context.expect(!InstructionSet->validateInstruction(**Untied, (*Function)->registerInfo()), + "a missing descriptor-required tie must be rejected"); + } + + auto Destination = (*Function)->registerInfo().createVirtualRegister(*I64); + auto Source = (*Function)->registerInfo().createVirtualRegister(*I64); + Context.expect(Destination && Source, "implicit operand validation registers must be allocated"); + if (!Destination || !Source) + return; + auto &RegInfo = (*Function)->registerInfo(); + Context.expect( + RegInfo.setRegisterBank(*Destination, rcc::RegisterBankID::fromValue(7)).has_value() && + RegInfo.setRegisterClass(*Destination, rcc::RegisterClassID::fromValue(8)).has_value(), + "implicit validation destination constraints must be recorded"); + + auto WithImplicit = (*Function)->createInstruction(rcc::Opcode::fromValue(1)); + const rcc::InstructionDescriptor *AddDescriptor = + InstructionSet->descriptor(rcc::Opcode::fromValue(1)); + Context.expect(WithImplicit && AddDescriptor != nullptr, + "implicit instruction setup must find its descriptor"); + if (!WithImplicit || AddDescriptor == nullptr) + return; + Context.expect( + (*WithImplicit) + ->addOperand(*registerOperand(*Destination, {rcc::RegisterOperandFlag::Define})) + .has_value() && + (*WithImplicit) + ->addOperand(*registerOperand(*Source, {rcc::RegisterOperandFlag::Use})) + .has_value() && + (*WithImplicit)->addOperand(rcc::MachineOperand::immediate(7)).has_value(), + "explicit operands for implicit validation must be added"); + auto DefaultImplicit = InstructionSet->createDefaultImplicitOperands(*AddDescriptor); + Context.expect(DefaultImplicit.has_value(), "default implicit operands must be constructible"); + if (!DefaultImplicit) + return; + for (rcc::MachineOperand &Operand : *DefaultImplicit) { + Context.expect((*WithImplicit)->addOperand(std::move(Operand)).has_value(), + "default implicit operand insertion must succeed"); + } + Context.expect(InstructionSet->validateInstruction(**WithImplicit, RegInfo).has_value(), + "exact default implicit operands must validate"); + + rcc::MachineOperand *ImplicitDef = (*WithImplicit)->operandAt(3); + Context.expect(ImplicitDef != nullptr && + ImplicitDef + ->setRegisterFlags( + {rcc::RegisterOperandFlag::Define, rcc::RegisterOperandFlag::Implicit}) + .has_value(), + "implicit definition flags must be mutable for a negative test"); + Context.expect(!InstructionSet->validateInstruction(**WithImplicit, RegInfo), + "implicit renamable state must match the descriptor exactly"); + + auto Load = (*Function)->createInstruction(rcc::Opcode::fromValue(3)); + Context.expect(Load.has_value(), "load instruction must be allocated"); + if (!Load) + return; + Context.expect(!InstructionSet->validateInstruction(**Load, RegInfo), + "an MMO-required load without an MMO must be rejected"); + auto MemOperand = (*Function)->createMemOperand(loadDescription()); + Context.expect(MemOperand.has_value() && (*Load)->addMemOperand(**MemOperand).has_value(), + "a function-owned load MMO must attach"); + Context.expect(InstructionSet->validateInstruction(**Load, RegInfo).has_value(), + "an MMO matching mayLoad must validate"); +} + +} // namespace + +int main() { + TestContext Context; + testDescriptorStructure(Context); + testOperandValidation(Context); + testInstructionValidation(Context); + return Context.result(); +} -- cgit v1.2.3