diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 13:24:09 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 13:24:09 +0800 |
| commit | 19d8e4f562a8583b002dbaa837ec0083e7768a4f (patch) | |
| tree | d8525d05d65c69c81076b8cdafdf0bfd400d4053 /backend | |
| parent | 8553df97bbc6154dae9d0ad861857e71a0efa420 (diff) | |
| download | rcc-project-19d8e4f562a8583b002dbaa837ec0083e7768a4f.tar.gz rcc-project-19d8e4f562a8583b002dbaa837ec0083e7768a4f.tar.xz | |
[CodeGen] Add strict MachineIR verification
Verify arena catalogs, intrusive layouts, reciprocal CFG edges, operand ownership, exact def-use coverage, instruction descriptors, pseudo deadlines, and MachineFunctionProperties. Preserve monotonic-arena history while rejecting detached objects that retain live state.
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineRegisterInfo.h | 4 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineVerifier.h | 36 | ||||
| -rw-r--r-- | backend/lib/CodeGen/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineRegisterInfo.cpp | 22 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineVerifier.cpp | 504 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 459 |
7 files changed, 1044 insertions, 0 deletions
diff --git a/backend/include/rcc/CodeGen/MachineRegisterInfo.h b/backend/include/rcc/CodeGen/MachineRegisterInfo.h index 495853a..da0f530 100644 --- a/backend/include/rcc/CodeGen/MachineRegisterInfo.h +++ b/backend/include/rcc/CodeGen/MachineRegisterInfo.h @@ -108,6 +108,9 @@ public: [[nodiscard]] Expected<void> eraseVirtualRegister(Register Reg); [[nodiscard]] bool isKnownVirtualRegister(Register Reg) const noexcept; + [[nodiscard]] std::span<const VirtualRegisterInfo> virtualRegisters() const noexcept { + return VirtualRegisters; + } [[nodiscard]] Expected<std::reference_wrapper<VirtualRegisterInfo>> virtualRegisterInfo(Register Reg); [[nodiscard]] Expected<std::reference_wrapper<const VirtualRegisterInfo>> @@ -146,6 +149,7 @@ public: [[nodiscard]] Expected<std::size_t> replaceRegWith(Register OldReg, Register NewReg); [[nodiscard]] Expected<void> verifyDefUseChains() const; + [[nodiscard]] bool isLinkedOperand(const MachineOperand &Operand) const noexcept; private: struct RegOperandListHead final { diff --git a/backend/include/rcc/CodeGen/MachineVerifier.h b/backend/include/rcc/CodeGen/MachineVerifier.h new file mode 100644 index 0000000..b1035a2 --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineVerifier.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINEVERIFIER_H +#define RCC_CODEGEN_MACHINEVERIFIER_H + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <optional> + +namespace rcc { + +class MachineFunction; + +enum class MachineVerifierMode : std::uint8_t { + Generic, + PreLegalizer, +}; + +struct MachineVerifierOptions final { + MachineVerifierMode Mode = MachineVerifierMode::Generic; + std::optional<PipelineStage> Stage; + bool VerifyProperties = true; +}; + +class MachineVerifier final { +public: + [[nodiscard]] static Expected<void> verify(const MachineFunction &Function, + const InstructionSet &Instructions, + MachineVerifierOptions Options = {}); +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINEVERIFIER_H diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index deaacb1..273ef9c 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -13,6 +13,7 @@ set( MachineMemOperand.cpp MachineOperand.cpp MachineRegisterInfo.cpp + MachineVerifier.cpp MachineFunctionProperties.cpp Register.cpp RegisterMask.cpp @@ -30,6 +31,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMemOperand.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineOperand.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineRegisterInfo.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineVerifier.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunctionProperties.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/Register.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/RegisterMask.h" @@ -76,6 +78,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifier.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" @@ -93,6 +96,7 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifier.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" diff --git a/backend/lib/CodeGen/MachineRegisterInfo.cpp b/backend/lib/CodeGen/MachineRegisterInfo.cpp index cc0955e..c31b38b 100644 --- a/backend/lib/CodeGen/MachineRegisterInfo.cpp +++ b/backend/lib/CodeGen/MachineRegisterInfo.cpp @@ -2,6 +2,8 @@ #include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineOperand.h" #include <algorithm> @@ -312,6 +314,16 @@ Expected<void> MachineRegisterInfo::verifyDefUseChains() const { std::unordered_set<const MachineOperand *> SeenOperands; for (const auto &[Reg, HeadStorage] : DefUseHeads) { + if (!HeadStorage) { + return makeUnexpected(ErrorCode::VerificationError, + "register def-use table contains null head storage"); + } + if (HeadStorage->Head == nullptr) + continue; + if (Reg.isNoRegister() || (Reg.isVirtual() && !isKnownVirtualRegister(Reg))) { + return makeUnexpected(ErrorCode::VerificationError, + "register def-use chain is keyed by an invalid register"); + } MachineOperand *const *ExpectedPreviousLink = &HeadStorage->Head; for (MachineOperand *Operand = HeadStorage->Head; Operand != nullptr; Operand = Operand->NextRegOperand) { @@ -327,6 +339,12 @@ Expected<void> MachineRegisterInfo::verifyDefUseChains() const { return makeUnexpected(ErrorCode::VerificationError, "register def-use chain back-link is corrupt"); } + if (Operand->Parent == nullptr || !Operand->Parent->isLinked() || + &Operand->Parent->function().registerInfo() != this) { + return makeUnexpected( + ErrorCode::VerificationError, + "register def-use chain contains an operand outside its function layout"); + } ExpectedPreviousLink = &Operand->NextRegOperand; } } @@ -334,6 +352,10 @@ Expected<void> MachineRegisterInfo::verifyDefUseChains() const { return {}; } +bool MachineRegisterInfo::isLinkedOperand(const MachineOperand &Operand) const noexcept { + return Operand.LinkedRegInfo == this && Operand.PreviousRegLink != nullptr; +} + MachineOperand *MachineRegisterInfo::headFor(Register Reg) const noexcept { const auto Position = DefUseHeads.find(Reg); if (Position == DefUseHeads.end()) diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp new file mode 100644 index 0000000..450f8e3 --- /dev/null +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineVerifier.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <optional> +#include <ranges> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <utility> + +namespace rcc { +namespace { + +class FunctionVerifier final { +public: + FunctionVerifier(const MachineFunction &Function, const InstructionSet &Instructions, + MachineVerifierOptions Options) noexcept + : Function(Function), Instructions(Instructions), Options(Options) {} + + [[nodiscard]] Expected<void> run() { + if (Function.name().empty() || !Function.stableID().isValid()) + return fail("function identity is invalid"); + + auto CatalogResult = catalogArenaObjects(); + if (!CatalogResult) + return CatalogResult; + auto LayoutResult = verifyBlockLayout(); + if (!LayoutResult) + return LayoutResult; + auto CFGResult = verifyCFG(); + if (!CFGResult) + return CFGResult; + auto RegisterResult = verifyRegisterInfo(); + if (!RegisterResult) + return RegisterResult; + auto DefUseResult = Function.registerInfo().verifyDefUseChains(); + if (!DefUseResult) + return failWith("register info", DefUseResult.error()); + if (Options.VerifyProperties) { + auto PropertyResult = verifyProperties(); + if (!PropertyResult) + return PropertyResult; + } + return {}; + } + +private: + [[nodiscard]] Expected<void> fail(std::string Message) const { + return makeUnexpected(ErrorCode::VerificationError, "machine function '" + + std::string(Function.name()) + + "': " + std::move(Message)); + } + + [[nodiscard]] Expected<void> failWith(std::string_view Context, const Error &Nested) const { + return fail(std::string(Context) + ": " + std::string(Nested.message())); + } + + [[nodiscard]] static bool hasMatchingStorage(const MachineOperand &Operand) noexcept { + switch (Operand.kind()) { + case MachineOperand::Kind::Register: + return Operand.getIf<Register>() != nullptr; + case MachineOperand::Kind::Immediate: + return Operand.getIf<std::int64_t>() != nullptr; + case MachineOperand::Kind::FloatingImmediate: + return Operand.getIf<FloatingImmediateOperand>() != nullptr; + case MachineOperand::Kind::BasicBlock: + return Operand.getIf<BasicBlockOperand>() != nullptr; + case MachineOperand::Kind::FrameIndex: + return Operand.getIf<FrameIndexOperand>() != nullptr; + case MachineOperand::Kind::ConstantPoolIndex: + return Operand.getIf<ConstantPoolOperand>() != nullptr; + case MachineOperand::Kind::JumpTableIndex: + return Operand.getIf<JumpTableOperand>() != nullptr; + case MachineOperand::Kind::GlobalSymbol: + return Operand.getIf<GlobalSymbolOperand>() != nullptr; + case MachineOperand::Kind::ExternalSymbol: + return Operand.getIf<ExternalSymbolOperand>() != nullptr; + case MachineOperand::Kind::BlockAddress: + return Operand.getIf<BlockAddressOperand>() != nullptr; + case MachineOperand::Kind::RegisterMask: + return Operand.getIf<RegisterMaskOperand>() != nullptr; + case MachineOperand::Kind::Metadata: + return Operand.getIf<MetadataOperand>() != nullptr; + case MachineOperand::Kind::TargetIndex: + return Operand.getIf<TargetIndexOperand>() != nullptr; + case MachineOperand::Kind::MCSymbol: + return Operand.getIf<MCSymbolOperand>() != nullptr; + case MachineOperand::Kind::Count: + return false; + } + return false; + } + + [[nodiscard]] Expected<void> catalogArenaObjects() { + std::unordered_set<MachineBlockStableID> BlockIDs; + for (const MachineBasicBlock *Block : Function.allBlocks()) { + if (Block == nullptr || !RegisteredBlocks.insert(Block).second) + return fail("arena block registry contains a null or duplicate pointer"); + if (&Block->function() != &Function) + return fail("arena block registry contains a foreign block"); + if (!Block->stableID().isValid() || !BlockIDs.insert(Block->stableID()).second) { + return fail("arena block registry contains an invalid or duplicate StableID"); + } + } + + std::unordered_set<MachineInstrStableID> InstructionIDs; + for (const MachineInstr *Instruction : Function.allInstructions()) { + if (Instruction == nullptr || !RegisteredInstructions.insert(Instruction).second) { + return fail("arena instruction registry contains a null or duplicate pointer"); + } + if (&Instruction->function() != &Function) + return fail("arena instruction registry contains a foreign instruction"); + if (!Instruction->stableID().isValid() || + !InstructionIDs.insert(Instruction->stableID()).second) { + return fail("arena instruction registry contains an invalid or duplicate StableID"); + } + } + + for (const MachineMemOperand *MemOperand : Function.allMemOperands()) { + if (MemOperand == nullptr || !RegisteredMemOperands.insert(MemOperand).second) { + return fail("arena MMO registry contains a null or duplicate pointer"); + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyBlockLayout() { + const MachineBasicBlock *Previous = nullptr; + const MachineBasicBlock *Block = Function.firstBlock(); + std::uint32_t LayoutIndex = 0; + while (Block != nullptr) { + if (!RegisteredBlocks.contains(Block)) + return fail("block layout references an unregistered block"); + if (!LinkedBlocks.insert(Block).second) + return fail("block layout contains a cycle or duplicate block"); + if (!Block->isLinked() || Block->previous() != Previous) + return fail("block layout ownership or previous link is inconsistent"); + if (Block->denseNumber() != LayoutIndex || Block->layoutIndex() != LayoutIndex) { + return fail("block dense number or layout index is stale"); + } + + auto InstructionResult = verifyInstructionLayout(*Block); + if (!InstructionResult) + return InstructionResult; + Previous = Block; + Block = Block->next(); + ++LayoutIndex; + } + + if (Previous != Function.lastBlock()) + return fail("function last-block pointer is inconsistent"); + if ((Function.firstBlock() == nullptr) != (Function.lastBlock() == nullptr)) { + return fail("function has only one of its first/last block pointers"); + } + + for (const MachineBasicBlock *Registered : RegisteredBlocks) { + if (LinkedBlocks.contains(Registered)) { + if (!Registered->isLinked()) + return fail("reachable block is not marked linked"); + continue; + } + if (Registered->isLinked()) + return fail("unreachable arena block is marked linked"); + if (Registered->previous() != nullptr || Registered->next() != nullptr || + Registered->firstInstruction() != nullptr || Registered->lastInstruction() != nullptr || + !Registered->predecessors().empty() || !Registered->successors().empty() || + Registered->denseNumber() || Registered->layoutIndex()) { + return fail("detached block retains layout, instruction, or CFG state"); + } + } + + for (const MachineInstr *Instruction : RegisteredInstructions) { + auto LocalResult = verifyInstructionLocalState(*Instruction); + if (!LocalResult) + return LocalResult; + if (Instruction->parent() != nullptr) { + if (!LinkedInstructions.contains(Instruction)) + return fail("instruction has a parent but is absent from block layout"); + } else { + if (Instruction->previous() != nullptr || Instruction->next() != nullptr) { + return fail("detached instruction retains layout links"); + } + for (const MachineOperand &Operand : Instruction->operands()) { + if (Function.registerInfo().isLinkedOperand(Operand)) + return fail("detached instruction operand remains in a def-use chain"); + } + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyInstructionLayout(const MachineBasicBlock &Block) { + const MachineInstr *Previous = nullptr; + const MachineInstr *Instruction = Block.firstInstruction(); + while (Instruction != nullptr) { + if (!RegisteredInstructions.contains(Instruction)) + return fail("block layout references an unregistered instruction"); + if (!LinkedInstructions.insert(Instruction).second) + return fail("instruction layout contains a cycle or duplicate instruction"); + if (Instruction->parent() != &Block || Instruction->previous() != Previous) { + return fail("instruction parent or previous link is inconsistent"); + } + + auto LiveResult = verifyLiveInstruction(*Instruction); + if (!LiveResult) + return LiveResult; + Previous = Instruction; + Instruction = Instruction->next(); + } + if (Previous != Block.lastInstruction()) + return fail("block last-instruction pointer is inconsistent"); + if ((Block.firstInstruction() == nullptr) != (Block.lastInstruction() == nullptr)) { + return fail("block has only one of its first/last instruction pointers"); + } + return {}; + } + + [[nodiscard]] Expected<void> verifyInstructionLocalState(const MachineInstr &Instruction) { + if (Instruction.sourceLocation() && !Instruction.sourceLocation()->isValid()) { + return fail("instruction has an invalid source location"); + } + if (Instruction.origin() && Instruction.origin()->value() == 0) + return fail("instruction has an invalid origin ID"); + + for (const MachineOperand &Operand : Instruction.operands()) { + if (Operand.parent() != &Instruction || !hasMatchingStorage(Operand)) + return fail("instruction operand parent or tagged storage is inconsistent"); + if (const auto *Mask = Operand.getIf<RegisterMaskOperand>(); Mask != nullptr && !Mask->Mask) { + return fail("register-mask operand contains a null mask"); + } + if (const auto *Global = Operand.getIf<GlobalSymbolOperand>(); + Global != nullptr && Global->Name.empty()) { + return fail("global symbol operand has an empty name"); + } + if (const auto *External = Operand.getIf<ExternalSymbolOperand>(); + External != nullptr && External->Name.empty()) { + return fail("external symbol operand has an empty name"); + } + if (const auto *Address = Operand.getIf<BlockAddressOperand>(); + Address != nullptr && (Address->FunctionName.empty() || Address->BlockName.empty())) { + return fail("block-address operand has an incomplete name"); + } + if (const auto *Metadata = Operand.getIf<MetadataOperand>(); + Metadata != nullptr && Metadata->Marker.empty()) { + return fail("metadata operand has an empty marker"); + } + if (const auto *Symbol = Operand.getIf<MCSymbolOperand>(); + Symbol != nullptr && Symbol->Name.empty()) { + return fail("MC symbol operand has an empty name"); + } + } + + for (const MachineMemOperand *MemOperand : Instruction.memOperands()) { + if (MemOperand == nullptr || !RegisteredMemOperands.contains(MemOperand)) { + return fail("instruction references a foreign or null MMO"); + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyLiveInstruction(const MachineInstr &Instruction) { + auto LocalResult = verifyInstructionLocalState(Instruction); + if (!LocalResult) + return LocalResult; + + const InstructionDescriptor *Descriptor = Instructions.descriptor(Instruction.opcode()); + if (Descriptor == nullptr) + return fail("live instruction uses an unregistered opcode"); + auto DescriptorResult = Instructions.validateInstruction(Instruction, Function.registerInfo()); + if (!DescriptorResult) + return failWith("instruction descriptor", DescriptorResult.error()); + + if (Options.Stage && Descriptor->Flags.has(InstructionFlag::Pseudo) && + static_cast<std::uint8_t>(*Options.Stage) > + static_cast<std::uint8_t>(Descriptor->Deadline)) { + return fail("pseudo instruction survives beyond its expansion deadline"); + } + + HasPhi |= Descriptor->Flags.has(InstructionFlag::Phi); + HasGenericInstruction |= Descriptor->Flags.has(InstructionFlag::Generic); + HasExecutablePseudo |= Descriptor->Flags.has(InstructionFlag::Pseudo) && + !Descriptor->Flags.has(InstructionFlag::Meta) && + !Descriptor->Flags.has(InstructionFlag::TerminalEmission); + + const auto Operands = Instruction.operands(); + for (std::size_t Index = 0; Index < Operands.size(); ++Index) { + const MachineOperand &Operand = Operands[Index]; + if (Operand.kind() == MachineOperand::Kind::FrameIndex) + HasFrameIndex = true; + + if (const auto *Block = Operand.getIf<BasicBlockOperand>(); + Block != nullptr && + (Block->Block == nullptr || &Block->Block->function() != &Function || + !RegisteredBlocks.contains(Block->Block) || !Block->Block->isLinked())) { + return fail("basic-block operand references a foreign or detached block"); + } + + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr) + continue; + const bool ShouldBeLinked = !Reg->isNoRegister(); + if (Function.registerInfo().isLinkedOperand(Operand) != ShouldBeLinked) { + return fail("live register operand has incorrect def-use linkage"); + } + if (Reg->isVirtual()) { + if (!Function.registerInfo().isKnownVirtualRegister(*Reg)) + return fail("instruction references an unknown virtual register"); + if (Operand.isDef()) + ++VirtualDefCounts[*Reg]; + } + + if (Operand.tiedOperandIndex() && Index < *Operand.tiedOperandIndex()) { + const MachineOperand *Other = Instruction.operandAt(*Operand.tiedOperandIndex()); + const Register *OtherReg = Other == nullptr ? nullptr : Other->getIf<Register>(); + if (OtherReg == nullptr || *OtherReg != *Reg) + HasUnrewrittenTie = true; + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyCFG() { + for (const MachineBasicBlock *Block : LinkedBlocks) { + std::unordered_set<const MachineBasicBlock *> Successors; + for (const MachineSuccessor &Successor : Block->successors()) { + if (Successor.Block == nullptr || !LinkedBlocks.contains(Successor.Block) || + !Successors.insert(Successor.Block).second) { + return fail("CFG successor list contains a null, detached, or duplicate block"); + } + if (std::ranges::count(Successor.Block->predecessors(), Block) != 1) + return fail("CFG successor lacks one reciprocal predecessor"); + } + + std::unordered_set<const MachineBasicBlock *> Predecessors; + for (const MachineBasicBlock *Predecessor : Block->predecessors()) { + if (Predecessor == nullptr || !LinkedBlocks.contains(Predecessor) || + !Predecessors.insert(Predecessor).second) { + return fail("CFG predecessor list contains a null, detached, or duplicate block"); + } + const auto Matches = std::ranges::count_if( + Predecessor->successors(), + [Block](const MachineSuccessor &Successor) { return Successor.Block == Block; }); + if (Matches != 1) + return fail("CFG predecessor lacks one reciprocal successor"); + } + + std::unordered_set<PhysicalRegister> LiveIns; + for (const MachineBasicBlockLiveIn &LiveIn : Block->liveIns()) { + if (!LiveIn.PhysReg.isValid() || LiveIn.Lanes.empty() || + !LiveIns.insert(LiveIn.PhysReg).second) { + return fail("block live-in list contains invalid or duplicate entries"); + } + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyRegisterInfo() { + const MachineRegisterInfo &RegInfo = Function.registerInfo(); + std::unordered_set<PhysicalRegister> FunctionLiveIns; + for (const PhysicalRegisterLiveIn &LiveIn : RegInfo.liveIns()) { + if (!LiveIn.PhysReg.isValid() || !FunctionLiveIns.insert(LiveIn.PhysReg).second) { + return fail("function live-in list contains invalid or duplicate entries"); + } + if (LiveIn.InitialVirtualRegister && + !RegInfo.isKnownVirtualRegister(*LiveIn.InitialVirtualRegister)) { + return fail("function live-in references an unknown virtual register"); + } + } + + std::unordered_set<PhysicalRegister> Reserved; + for (const PhysicalRegister Reg : RegInfo.reservedRegisters()) { + if (!Reg.isValid() || !Reserved.insert(Reg).second) + return fail("reserved-register list contains invalid or duplicate entries"); + } + + for (const VirtualRegisterInfo &Info : RegInfo.virtualRegisters()) { + if (Info.IsTombstone) { + if (Info.Type || Info.Bank || Info.RegClass || !Info.AllocationHints.empty()) { + return fail("virtual-register tombstone retains semantic state"); + } + continue; + } + HasLiveVirtualRegister = true; + MissingVirtualType |= !Info.Type; + MissingVirtualBank |= !Info.Bank; + MissingVirtualClass |= !Info.RegClass; + if (Info.Type && !Info.Type->isValid()) + return fail("virtual register has an invalid LLT"); + + std::unordered_set<PhysicalRegister> Hints; + for (const PhysicalRegister Hint : Info.AllocationHints) { + if (!Hint.isValid() || !Hints.insert(Hint).second) + return fail("virtual register has an invalid or duplicate allocation hint"); + } + } + return {}; + } + + [[nodiscard]] Expected<void> verifyProperties() { + const MachineFunctionProperties &Properties = Function.properties(); + auto Has = [&Properties](MachineFunctionProperty Property) { return Properties.has(Property); }; + + if (Has(MachineFunctionProperty::RegBankSelected) && !Has(MachineFunctionProperty::Legalized)) { + return fail("RegBankSelected requires Legalized"); + } + if (Has(MachineFunctionProperty::Selected) && !Has(MachineFunctionProperty::RegBankSelected)) { + return fail("Selected requires RegBankSelected"); + } + if (Has(MachineFunctionProperty::TiedOpsRewritten) && !Has(MachineFunctionProperty::Selected)) { + return fail("TiedOpsRewritten requires Selected"); + } + if (Has(MachineFunctionProperty::FrameFinalized) && + (!Has(MachineFunctionProperty::NoVRegs) || !Has(MachineFunctionProperty::NoFrameIndices))) { + return fail("FrameFinalized requires NoVRegs and NoFrameIndices"); + } + + if (Has(MachineFunctionProperty::IsSSA)) { + for (const auto &[Reg, DefinitionCount] : VirtualDefCounts) { + if (DefinitionCount > 1) + return fail("IsSSA function has a multiply-defined virtual register"); + } + } + if (Has(MachineFunctionProperty::NoPHIs) && HasPhi) + return fail("NoPHIs function contains a PHI instruction"); + if (Has(MachineFunctionProperty::NoVRegs) && HasLiveVirtualRegister) { + return fail("NoVRegs function retains a live virtual register"); + } + if (Has(MachineFunctionProperty::NoFrameIndices) && HasFrameIndex) + return fail("NoFrameIndices function contains a frame-index operand"); + if (Has(MachineFunctionProperty::NoExecutablePseudos) && HasExecutablePseudo) { + return fail("NoExecutablePseudos function contains an executable pseudo"); + } + if (Has(MachineFunctionProperty::RegBankSelected) && MissingVirtualBank) { + return fail("RegBankSelected function has a virtual register without a bank"); + } + if (Has(MachineFunctionProperty::Selected) && (HasGenericInstruction || MissingVirtualClass)) { + return fail("Selected function contains generic code or an unclassified virtual register"); + } + if (Has(MachineFunctionProperty::TiedOpsRewritten) && HasUnrewrittenTie) { + return fail("TiedOpsRewritten function has unequal tied registers"); + } + + const bool NeedsTypes = Options.Mode == MachineVerifierMode::PreLegalizer || + Has(MachineFunctionProperty::Legalized) || + Has(MachineFunctionProperty::RegBankSelected); + if (NeedsTypes && MissingVirtualType) + return fail("typed pipeline stage has a virtual register without LLT"); + + if (Options.Mode == MachineVerifierMode::PreLegalizer) { + if (!Has(MachineFunctionProperty::IsSSA) || !Has(MachineFunctionProperty::TracksLiveness)) { + return fail("pre-legalizer input requires IsSSA and TracksLiveness"); + } + if (Has(MachineFunctionProperty::Legalized) || + Has(MachineFunctionProperty::RegBankSelected) || Has(MachineFunctionProperty::Selected) || + Has(MachineFunctionProperty::TiedOpsRewritten) || Has(MachineFunctionProperty::NoVRegs) || + Has(MachineFunctionProperty::FrameFinalized)) { + return fail("pre-legalizer input carries a later-stage property"); + } + } + return {}; + } + + const MachineFunction &Function; + const InstructionSet &Instructions; + MachineVerifierOptions Options; + std::unordered_set<const MachineBasicBlock *> RegisteredBlocks; + std::unordered_set<const MachineBasicBlock *> LinkedBlocks; + std::unordered_set<const MachineInstr *> RegisteredInstructions; + std::unordered_set<const MachineInstr *> LinkedInstructions; + std::unordered_set<const MachineMemOperand *> RegisteredMemOperands; + std::unordered_map<Register, std::size_t> VirtualDefCounts; + bool HasPhi = false; + bool HasGenericInstruction = false; + bool HasExecutablePseudo = false; + bool HasFrameIndex = false; + bool HasUnrewrittenTie = false; + bool HasLiveVirtualRegister = false; + bool MissingVirtualType = false; + bool MissingVirtualBank = false; + bool MissingVirtualClass = false; +}; + +} // namespace + +Expected<void> MachineVerifier::verify(const MachineFunction &Function, + const InstructionSet &Instructions, + MachineVerifierOptions Options) { + return FunctionVerifier(Function, Instructions, Options).run(); +} + +} // namespace rcc diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index ddba86a..6f17fc0 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -6,6 +6,7 @@ set( InstructionDescriptorTest.cpp MachineIRBuilderTest.cpp MachineIRCoreTest.cpp + MachineVerifierTest.cpp MachineValueTypesTest.cpp ) @@ -42,6 +43,18 @@ add_test( COMMAND RCCBackendCodeGenMachineIRBuilderTests ) +add_executable(RCCBackendCodeGenMachineVerifierTests MachineVerifierTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineVerifierTests) +target_link_libraries( + RCCBackendCodeGenMachineVerifierTests + PRIVATE RCCBackend::CodeGen +) + +add_test( + NAME RCCBackend.CodeGen.MachineVerifier + COMMAND RCCBackendCodeGenMachineVerifierTests +) + add_executable(RCCBackendCodeGenMachineValueTypesTests MachineValueTypesTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineValueTypesTests) target_link_libraries(RCCBackendCodeGenMachineValueTypesTests PRIVATE RCCBackend::CodeGen) @@ -65,6 +78,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) rcc_backend_register_tidy_files( @@ -72,5 +86,6 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp new file mode 100644 index 0000000..b8b985e --- /dev/null +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineVerifier.h" + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" + +#include <cstdint> +#include <iostream> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +constexpr std::uint32_t GenericDefOpcode = 1; +constexpr std::uint32_t GenericUseOpcode = 2; +constexpr std::uint32_t CopyOpcode = 3; +constexpr std::uint32_t BranchOpcode = 4; +constexpr std::uint32_t PhiOpcode = 5; +constexpr std::uint32_t FrameOpcode = 6; +constexpr std::uint32_t PseudoOpcode = 7; +constexpr std::uint32_t NopOpcode = 8; + +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 instructionDescriptor(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<rcc::InstructionSet> createInstructionSet() { + auto GenericDef = instructionDescriptor(GenericDefOpcode, "TEST_GENERIC_DEF"); + GenericDef.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); + GenericDef.NumExplicitDefs = 1; + GenericDef.Flags.set(rcc::InstructionFlag::Generic); + GenericDef.Flags.set(rcc::InstructionFlag::AllowedPreLegalizer); + + auto GenericUse = instructionDescriptor(GenericUseOpcode, "TEST_GENERIC_USE"); + GenericUse.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Use)); + GenericUse.Flags.set(rcc::InstructionFlag::Generic); + GenericUse.Flags.set(rcc::InstructionFlag::AllowedPreLegalizer); + + auto Copy = instructionDescriptor(CopyOpcode, "TEST_COPY"); + auto CopyDef = registerDescriptor(rcc::RegisterOperandRole::Def); + auto CopyUse = registerDescriptor(rcc::RegisterOperandRole::Use); + CopyDef.DefaultTiedOperand = 1; + CopyUse.DefaultTiedOperand = 0; + Copy.Operands.push_back(CopyDef); + Copy.Operands.push_back(CopyUse); + Copy.NumExplicitDefs = 1; + + auto Branch = instructionDescriptor(BranchOpcode, "TEST_BRANCH"); + rcc::OperandDescriptor Block; + Block.Kinds = {rcc::MachineOperand::Kind::BasicBlock}; + Branch.Operands.push_back(Block); + Branch.Flags.set(rcc::InstructionFlag::Terminator); + Branch.Flags.set(rcc::InstructionFlag::Branch); + + auto Phi = instructionDescriptor(PhiOpcode, "TEST_PHI"); + Phi.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); + Phi.NumExplicitDefs = 1; + Phi.Flags.set(rcc::InstructionFlag::Pseudo); + Phi.Flags.set(rcc::InstructionFlag::Phi); + Phi.Owner = rcc::PseudoOwner::PhiElimination; + Phi.Deadline = rcc::PipelineStage::SSAEliminated; + + auto Frame = instructionDescriptor(FrameOpcode, "TEST_FRAME"); + rcc::OperandDescriptor FrameIndex; + FrameIndex.Kinds = {rcc::MachineOperand::Kind::FrameIndex}; + Frame.Operands.push_back(FrameIndex); + + auto Pseudo = instructionDescriptor(PseudoOpcode, "TEST_PSEUDO"); + Pseudo.Flags.set(rcc::InstructionFlag::Pseudo); + Pseudo.Owner = rcc::PseudoOwner::InstructionSelect; + Pseudo.Deadline = rcc::PipelineStage::Selected; + + auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); + + std::vector<rcc::InstructionDescriptor> Descriptors; + Descriptors.push_back(std::move(GenericDef)); + Descriptors.push_back(std::move(GenericUse)); + Descriptors.push_back(std::move(Copy)); + Descriptors.push_back(std::move(Branch)); + Descriptors.push_back(std::move(Phi)); + Descriptors.push_back(std::move(Frame)); + Descriptors.push_back(std::move(Pseudo)); + Descriptors.push_back(std::move(Nop)); + return rcc::InstructionSet::create(std::move(Descriptors)); +} + +[[nodiscard]] std::unique_ptr<rcc::MachineFunction> +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(), "machine function creation must succeed"); + if (!Function) + return nullptr; + return std::move(*Function); +} + +[[nodiscard]] rcc::MachineBasicBlock *createBlock(TestContext &Context, + rcc::MachineFunction &Function) { + auto Block = Function.createBasicBlock(); + Context.expect(Block.has_value(), "machine basic block creation must succeed"); + if (!Block) + return nullptr; + Context.expect(Function.appendBlock(**Block).has_value(), + "machine basic block linking must succeed"); + return *Block; +} + +[[nodiscard]] rcc::MachineInstr * +buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32_t OpcodeValue) { + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(OpcodeValue)); + Context.expect(Pending.has_value(), "pending no-operand instruction must be created"); + if (!Pending) + return nullptr; + auto Result = Pending->build(); + Context.expect(Result.has_value(), "no-operand instruction must build"); + if (!Result) + return nullptr; + return *Result; +} + +[[nodiscard]] bool populateValidPreLegalizer(TestContext &Context, rcc::MachineFunction &Function, + const rcc::InstructionSet &InstructionSet) { + rcc::MachineBasicBlock *Entry = createBlock(Context, Function); + rcc::MachineBasicBlock *Exit = createBlock(Context, Function); + if (Entry == nullptr || Exit == nullptr) + return false; + + auto Type = rcc::LLT::makeInteger(64); + auto Value = Function.registerInfo().createVirtualRegister(*Type); + Context.expect(Value.has_value(), "typed verifier virtual register must be allocated"); + if (!Value) + return false; + + rcc::MachineIRBuilder Builder(Function, InstructionSet); + 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"); + if (!Def || !Def->build()) + return false; + + auto Branch = Builder.buildInstr(rcc::Opcode::fromValue(BranchOpcode)); + Context.expect(Branch && Branch->addBasicBlock(*Exit).has_value(), + "branch operand must be assembled"); + if (!Branch || !Branch->build()) + return false; + + Context.expect( + Function.cfgUpdater().addSuccessor(*Entry, *Exit, rcc::BranchProbability::one()).has_value(), + "valid verifier CFG edge must be created"); + + Context.expect(Builder.setInsertPoint(*Exit).has_value(), "exit insertion point must be valid"); + auto Use = Builder.buildInstr(rcc::Opcode::fromValue(GenericUseOpcode)); + Context.expect(Use && Use->addUse(*Value).has_value(), "generic use must be assembled"); + if (!Use || !Use->build()) + return false; + + Function.properties().set(rcc::MachineFunctionProperty::IsSSA); + Function.properties().set(rcc::MachineFunctionProperty::TracksLiveness); + return true; +} + +void testValidFunctionAndModes(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "valid", 1); + if (!InstructionSet || !Function || + !populateValidPreLegalizer(Context, *Function, *InstructionSet)) { + return; + } + + Context.expect(rcc::MachineVerifier::verify(*Function, *InstructionSet).has_value(), + "well-formed function must pass generic verification"); + Context.expect( + rcc::MachineVerifier::verify( + *Function, *InstructionSet, + rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true}) + .has_value(), + "typed SSA input must pass pre-legalizer verification"); + + auto DetachedBlock = Function->createBasicBlock(); + 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(), + "empty detached arena objects must not invalidate live layout"); + + Function->properties().set(rcc::MachineFunctionProperty::Legalized); + Context.expect( + !rcc::MachineVerifier::verify( + *Function, *InstructionSet, + 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) + return; + + auto Unknown = createFunction(Context, "unknown_opcode", 2); + rcc::MachineBasicBlock *UnknownBlock = Unknown ? createBlock(Context, *Unknown) : nullptr; + if (Unknown && UnknownBlock != nullptr) { + 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), + "live unregistered opcode must fail verification"); + } + + auto WrongOperands = createFunction(Context, "wrong_operands", 3); + rcc::MachineBasicBlock *WrongBlock = + WrongOperands ? createBlock(Context, *WrongOperands) : nullptr; + if (WrongOperands && WrongBlock != nullptr) { + 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), + "descriptor operand-count mismatch must fail verification"); + } + + auto DetachedChild = createFunction(Context, "detached_child", 4); + if (DetachedChild) { + auto Block = DetachedChild->createBasicBlock(); + 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), + "detached block retaining instructions must fail verification"); + } + + auto DetachedCFG = createFunction(Context, "detached_cfg", 5); + rcc::MachineBasicBlock *Linked = DetachedCFG ? createBlock(Context, *DetachedCFG) : nullptr; + if (DetachedCFG && Linked != nullptr) { + auto Detached = DetachedCFG->createBasicBlock(); + Context.expect(Detached && DetachedCFG->cfgUpdater() + .addSuccessor(**Detached, *Linked, rcc::BranchProbability::one()) + .has_value(), + "detached CFG negative test edge must be created"); + Context.expect(!rcc::MachineVerifier::verify(*DetachedCFG, *InstructionSet), + "detached block retaining CFG state must fail verification"); + } +} + +void testPropertyFailures(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + if (!InstructionSet) + return; + auto Type = rcc::LLT::makeInteger(64); + + auto SSA = createFunction(Context, "ssa", 6); + rcc::MachineBasicBlock *SSABlock = SSA ? createBlock(Context, *SSA) : nullptr; + if (SSA && SSABlock != nullptr) { + auto Value = SSA->registerInfo().createVirtualRegister(*Type); + rcc::MachineIRBuilder Builder(*SSA, *InstructionSet); + Context.expect(Value && Builder.setInsertPoint(*SSABlock).has_value(), + "SSA negative test setup must succeed"); + for (int Index = 0; Index != 2 && Value; ++Index) { + auto Def = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode)); + Context.expect(Def && Def->addDef(*Value).has_value() && Def->build(), + "SSA negative test definition must build"); + } + SSA->properties().set(rcc::MachineFunctionProperty::IsSSA); + Context.expect(!rcc::MachineVerifier::verify(*SSA, *InstructionSet), + "IsSSA must reject a multiply-defined virtual register"); + } + + auto PHI = createFunction(Context, "phi", 7); + rcc::MachineBasicBlock *PHIBlock = PHI ? createBlock(Context, *PHI) : nullptr; + if (PHI && PHIBlock != nullptr) { + auto Value = PHI->registerInfo().createVirtualRegister(*Type); + rcc::MachineIRBuilder Builder(*PHI, *InstructionSet); + 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), + "NoPHIs must reject a PHI instruction"); + } + + auto NoVRegs = createFunction(Context, "no_vregs", 8); + if (NoVRegs) { + rcc::MachineBasicBlock *Block = createBlock(Context, *NoVRegs); + auto Value = NoVRegs->registerInfo().createVirtualRegister(*Type); + rcc::MachineIRBuilder Builder(*NoVRegs, *InstructionSet); + Context.expect(Block != nullptr && Builder.setInsertPoint(*Block).has_value(), + "NoVRegs negative test setup must succeed"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode)); + Context.expect(Value && Pending && Pending->addDef(*Value).has_value(), + "NoVRegs linked history must be assembled"); + if (!Value || !Pending || Block == nullptr) + return; + auto Built = Pending->build(); + NoVRegs->properties().set(rcc::MachineFunctionProperty::NoVRegs); + Context.expect(Value && Built && !rcc::MachineVerifier::verify(*NoVRegs, *InstructionSet), + "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(), + "NoVRegs must accept tombstoned def-use history"); + } + + auto Frame = createFunction(Context, "frame_index", 9); + rcc::MachineBasicBlock *FrameBlock = Frame ? createBlock(Context, *Frame) : nullptr; + if (Frame && FrameBlock != nullptr) { + rcc::MachineIRBuilder Builder(*Frame, *InstructionSet); + Context.expect(Builder.setInsertPoint(*FrameBlock).has_value(), + "frame-index negative test setup must succeed"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Pending && Pending->addFrameIndex(rcc::FrameIndex::fromValue(0)).has_value() && + Pending->build(), + "frame-index negative test instruction must build"); + Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices); + Context.expect(!rcc::MachineVerifier::verify(*Frame, *InstructionSet), + "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); + 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), + "NoExecutablePseudos must reject an executable pseudo"); + } +} + +void testSelectedStateAndPseudoDeadline(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Type = rcc::LLT::makeInteger(64); + if (!InstructionSet) + return; + + auto Tied = createFunction(Context, "tied", 11); + rcc::MachineBasicBlock *TiedBlock = Tied ? createBlock(Context, *Tied) : nullptr; + if (Tied && TiedBlock != nullptr) { + auto Def = Tied->registerInfo().createVirtualRegister(*Type); + auto Use = Tied->registerInfo().createVirtualRegister(*Type); + if (!Def || !Use) + return; + for (const rcc::Register Reg : {*Def, *Use}) { + Context.expect(Tied->registerInfo() + .setRegisterBank(Reg, rcc::RegisterBankID::fromValue(1)) + .has_value() && + Tied->registerInfo() + .setRegisterClass(Reg, rcc::RegisterClassID::fromValue(1)) + .has_value(), + "selected-state register constraints must be recorded"); + } + + rcc::MachineIRBuilder Builder(*Tied, *InstructionSet); + Context.expect(Builder.setInsertPoint(*TiedBlock).has_value(), + "tied-state insertion point must be valid"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); + Context.expect(Pending && Pending->addDef(*Def).has_value() && + Pending->addUse(*Use).has_value() && Pending->build(), + "tied-state instruction must build"); + for (const rcc::MachineFunctionProperty Property : + {rcc::MachineFunctionProperty::Legalized, rcc::MachineFunctionProperty::RegBankSelected, + rcc::MachineFunctionProperty::Selected, rcc::MachineFunctionProperty::TiedOpsRewritten}) { + Tied->properties().set(Property); + } + Context.expect(!rcc::MachineVerifier::verify(*Tied, *InstructionSet), + "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(), + "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); + Context.expect(Builder.setInsertPoint(*Block).has_value() && + buildNoOperand(Context, Builder, PseudoOpcode) != nullptr, + "deadline pseudo must build"); + Context.expect( + rcc::MachineVerifier::verify(*Pseudo, *InstructionSet, + rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic, + rcc::PipelineStage::Selected}) + .has_value(), + "pseudo may remain at its declared deadline"); + Context.expect(!rcc::MachineVerifier::verify( + *Pseudo, *InstructionSet, + rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic, + rcc::PipelineStage::RegisterAllocated}), + "pseudo must not survive beyond its declared deadline"); + } + + auto Dependency = createFunction(Context, "property_dependency", 13); + if (Dependency) { + Dependency->properties().set(rcc::MachineFunctionProperty::Selected); + Context.expect(!rcc::MachineVerifier::verify(*Dependency, *InstructionSet), + "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), + "FrameFinalized must require NoVRegs and NoFrameIndices"); + } +} + +} // namespace + +int main() { + TestContext Context; + testValidFunctionAndModes(Context); + testReachableStructuralFailures(Context); + testPropertyFailures(Context); + testSelectedStateAndPseudoDeadline(Context); + return Context.result(); +} |
