diff options
Diffstat (limited to 'backend/unittests/CodeGen/MachineVerifierTest.cpp')
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp new file mode 100644 index 0000000..5327148 --- /dev/null +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineVerifier.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineCompilationStatus.h" +#include "rcc/CodeGen/MachineConstantPool.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.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; +constexpr std::uint32_t ConstantPoolOpcode = 9; +constexpr std::uint32_t JumpTableOpcode = 10; + +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::TargetDescription> createTarget() { + 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)); + auto PhiValue = registerDescriptor(rcc::RegisterOperandRole::Use); + PhiValue.VariadicGroup = true; + Phi.Operands.push_back(PhiValue); + rcc::OperandDescriptor PhiBlock; + PhiBlock.Kinds = {rcc::MachineOperand::Kind::BasicBlock}; + PhiBlock.VariadicGroup = true; + Phi.Operands.push_back(PhiBlock); + Phi.NumExplicitDefs = 1; + Phi.Variadic = true; + 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"); + + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + + 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)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); +} + +[[nodiscard]] std::unique_ptr<rcc::MachineFunction> +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::TargetDescription &Target) { + rcc::MachineBasicBlock *Entry = createBlock(Context, Function); + rcc::MachineBasicBlock *Exit = createBlock(Context, Function); + if (Entry == nullptr || Exit == nullptr) + return false; + + auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(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, Target); + Context.expect(Builder.setInsertPoint(*Entry).has_value(), "entry insertion point must be valid"); + auto Def = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode)); + Context.expect(Def && Def->addDef(*Value).has_value(), "generic definition must be assembled"); + 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 Target = createTarget(); + auto Function = createFunction(Context, "valid", 1); + if (!Target || !Function || !populateValidPreLegalizer(Context, *Function, *Target)) { + return; + } + + Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(), + "well-formed function must pass generic verification"); + Context.expect( + rcc::MachineVerifier::verify( + *Function, *Target, + 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, *Target).has_value(), + "empty detached arena objects must not invalidate live layout"); + + Function->properties().set(rcc::MachineFunctionProperty::Legalized); + Context.expect( + !rcc::MachineVerifier::verify( + *Function, *Target, + rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true}), + "pre-legalizer mode must reject later-stage properties"); +} + +void testReachableStructuralFailures(TestContext &Context) { + auto Target = createTarget(); + if (!Target) + 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, *Target), + "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, *Target), + "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, *Target), + "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, *Target), + "detached block retaining CFG state must fail verification"); + } +} + +enum class PhiTestShape : std::uint8_t { + Valid, + MissingPredecessor, + DuplicatePredecessor, + NonPredecessor, + AfterInstruction, +}; + +[[nodiscard]] std::unique_ptr<rcc::MachineFunction> +createPhiTestFunction(TestContext &Context, const rcc::TargetDescription &Target, std::string Name, + std::uint64_t ID, PhiTestShape Shape) { + auto Function = createFunction(Context, std::move(Name), ID); + if (!Function) + return nullptr; + rcc::MachineBasicBlock *Left = createBlock(Context, *Function); + rcc::MachineBasicBlock *Right = createBlock(Context, *Function); + rcc::MachineBasicBlock *Merge = createBlock(Context, *Function); + rcc::MachineBasicBlock *Other = createBlock(Context, *Function); + if (Left == nullptr || Right == nullptr || Merge == nullptr || Other == nullptr) + return nullptr; + + auto CFG = Function->cfgUpdater(); + if (!CFG.addSuccessor(*Left, *Merge, rcc::BranchProbability::one()) || + !CFG.addSuccessor(*Right, *Merge, rcc::BranchProbability::one())) { + Context.expect(false, "PHI verifier test CFG must be constructed"); + return nullptr; + } + + auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(64); + auto Destination = Type ? Function->registerInfo().createVirtualRegister(*Type) + : rcc::Expected<rcc::Register>{std::unexpected(Type.error())}; + auto LeftValue = Type ? Function->registerInfo().createVirtualRegister(*Type) + : rcc::Expected<rcc::Register>{std::unexpected(Type.error())}; + auto RightValue = Type ? Function->registerInfo().createVirtualRegister(*Type) + : rcc::Expected<rcc::Register>{std::unexpected(Type.error())}; + if (!Destination || !LeftValue || !RightValue) { + Context.expect(false, "PHI verifier test registers must be created"); + return nullptr; + } + + rcc::MachineIRBuilder Builder(*Function, Target); + if (!Builder.setInsertPoint(*Merge)) { + Context.expect(false, "PHI verifier insertion point must be valid"); + return nullptr; + } + if (Shape == PhiTestShape::AfterInstruction && + buildNoOperand(Context, Builder, NopOpcode) == nullptr) + return nullptr; + + auto Phi = Builder.buildInstr(rcc::Opcode::fromValue(PhiOpcode)); + if (!Phi || !Phi->addDef(*Destination) || !Phi->addUse(*LeftValue) || + !Phi->addBasicBlock(*Left)) { + Context.expect(false, "PHI verifier instruction prefix must build"); + return nullptr; + } + if (Shape == PhiTestShape::DuplicatePredecessor) { + if (!Phi->addUse(*RightValue) || !Phi->addBasicBlock(*Left)) + return nullptr; + } else if (Shape == PhiTestShape::NonPredecessor) { + if (!Phi->addUse(*RightValue) || !Phi->addBasicBlock(*Other)) + return nullptr; + } else if (Shape != PhiTestShape::MissingPredecessor) { + if (!Phi->addUse(*RightValue) || !Phi->addBasicBlock(*Right)) + return nullptr; + } + if (!Phi->build()) { + Context.expect(false, "PHI verifier test instruction must publish"); + return nullptr; + } + return Function; +} + +void testPHIStructure(TestContext &Context) { + auto Target = createTarget(); + if (!Target) + return; + auto Valid = createPhiTestFunction(Context, *Target, "valid_phi", 60, PhiTestShape::Valid); + auto Missing = + createPhiTestFunction(Context, *Target, "missing_phi", 61, PhiTestShape::MissingPredecessor); + auto Duplicate = createPhiTestFunction(Context, *Target, "duplicate_phi", 62, + PhiTestShape::DuplicatePredecessor); + auto NonPredecessor = + createPhiTestFunction(Context, *Target, "foreign_phi", 63, PhiTestShape::NonPredecessor); + auto AfterInstruction = + createPhiTestFunction(Context, *Target, "late_phi", 64, PhiTestShape::AfterInstruction); + Context.expect(Valid && rcc::MachineVerifier::verify(*Valid, *Target), + "PHI with one incoming value per predecessor must verify"); + Context.expect(Missing && !rcc::MachineVerifier::verify(*Missing, *Target), + "PHI missing a predecessor must fail verification"); + Context.expect(Duplicate && !rcc::MachineVerifier::verify(*Duplicate, *Target), + "PHI with duplicate incoming blocks must fail verification"); + Context.expect(NonPredecessor && !rcc::MachineVerifier::verify(*NonPredecessor, *Target), + "PHI naming a non-predecessor must fail verification"); + Context.expect(AfterInstruction && !rcc::MachineVerifier::verify(*AfterInstruction, *Target), + "PHI after a code instruction must fail verification"); +} + +void testPropertyFailures(TestContext &Context) { + auto Target = createTarget(); + if (!Target) + return; + auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(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, *Target); + 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, *Target), + "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, *Target); + Context.expect(Value && Builder.setInsertPoint(*PHIBlock).has_value(), + "PHI negative test setup must succeed"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(PhiOpcode)); + Context.expect(Pending && Pending->addDef(*Value).has_value() && Pending->build(), + "PHI negative test instruction must build"); + PHI->properties().set(rcc::MachineFunctionProperty::NoPHIs); + Context.expect(!rcc::MachineVerifier::verify(*PHI, *Target), + "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, *Target); + 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, *Target), + "NoVRegs must reject a live virtual register"); + Context.expect(Value && Built && Block != nullptr && + Block->removeInstruction(**Built).has_value() && + NoVRegs->registerInfo().eraseVirtualRegister(*Value).has_value() && + rcc::MachineVerifier::verify(*NoVRegs, *Target).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::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Frame->frameInfo().createObject(StackSlot); + rcc::MachineIRBuilder Builder(*Frame, *Target); + Context.expect(FrameIndex && Builder.setInsertPoint(*FrameBlock).has_value(), + "frame-index negative test setup must succeed"); + if (!FrameIndex) + return; + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Pending && Pending->addFrameIndex(*FrameIndex).has_value() && Pending->build(), + "frame-index negative test instruction must build"); + Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices); + Context.expect(!rcc::MachineVerifier::verify(*Frame, *Target), + "NoFrameIndices must reject a frame-index operand"); + } + + auto Pseudo = createFunction(Context, "pseudo", 10); + rcc::MachineBasicBlock *PseudoBlock = Pseudo ? createBlock(Context, *Pseudo) : nullptr; + if (Pseudo && PseudoBlock != nullptr) { + rcc::MachineIRBuilder Builder(*Pseudo, *Target); + Context.expect(Builder.setInsertPoint(*PseudoBlock).has_value() && + buildNoOperand(Context, Builder, PseudoOpcode) != nullptr, + "pseudo negative test instruction must build"); + Pseudo->properties().set(rcc::MachineFunctionProperty::NoExecutablePseudos); + Context.expect(!rcc::MachineVerifier::verify(*Pseudo, *Target), + "NoExecutablePseudos must reject an executable pseudo"); + } +} + +void testSelectedStateAndPseudoDeadline(TestContext &Context) { + auto Target = createTarget(); + auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(64); + if (!Target) + 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, *Target); + 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::NoPHIs, + rcc::MachineFunctionProperty::TiedOpsRewritten}) { + Tied->properties().set(Property); + } + Context.expect(!rcc::MachineVerifier::verify(*Tied, *Target), + "TiedOpsRewritten must reject unequal tied registers"); + rcc::MachineInstr *Instruction = TiedBlock->firstInstruction(); + Context.expect(Instruction != nullptr && + Instruction->operandAt(1)->setRegister(*Def).has_value() && + rcc::MachineVerifier::verify(*Tied, *Target).has_value(), + "equal tied registers must satisfy TiedOpsRewritten"); + } + + auto Pseudo = createFunction(Context, "pseudo_deadline", 12); + rcc::MachineBasicBlock *Block = Pseudo ? createBlock(Context, *Pseudo) : nullptr; + if (Pseudo && Block != nullptr) { + rcc::MachineIRBuilder Builder(*Pseudo, *Target); + Context.expect(Builder.setInsertPoint(*Block).has_value() && + buildNoOperand(Context, Builder, PseudoOpcode) != nullptr, + "deadline pseudo must build"); + Context.expect( + rcc::MachineVerifier::verify(*Pseudo, *Target, + rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic, + rcc::PipelineStage::Selected}) + .has_value(), + "pseudo may remain at its declared deadline"); + Context.expect(!rcc::MachineVerifier::verify( + *Pseudo, *Target, + 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, *Target), + "Selected must require prior pipeline properties"); + Dependency->properties().clear(rcc::MachineFunctionProperty::Selected); + Dependency->properties().set(rcc::MachineFunctionProperty::FrameFinalized); + Context.expect(!rcc::MachineVerifier::verify(*Dependency, *Target), + "FrameFinalized must require NoVRegs and NoFrameIndices"); + } + + auto SSAExitDependency = createFunction(Context, "ssa_exit_property_dependency", 65); + if (SSAExitDependency) { + for (const rcc::MachineFunctionProperty Property : + {rcc::MachineFunctionProperty::Legalized, rcc::MachineFunctionProperty::RegBankSelected, + rcc::MachineFunctionProperty::Selected, rcc::MachineFunctionProperty::TiedOpsRewritten}) { + SSAExitDependency->properties().set(Property); + } + Context.expect(!rcc::MachineVerifier::verify(*SSAExitDependency, *Target), + "TiedOpsRewritten must require NoPHIs"); + SSAExitDependency->properties().set(rcc::MachineFunctionProperty::NoPHIs); + Context.expect(rcc::MachineVerifier::verify(*SSAExitDependency, *Target).has_value(), + "TiedOpsRewritten dependency must be satisfied by NoPHIs"); + } +} + +void testFrameStateFailures(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, "frame_references", 14); + rcc::MachineBasicBlock *Block = Function ? createBlock(Context, *Function) : nullptr; + if (!Target || !Function || Block == nullptr) + return; + + rcc::MachineIRBuilder Builder(*Function, *Target); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "frame reference insertion point must be valid"); + auto Unknown = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Unknown && Unknown->addFrameIndex(rcc::FrameIndex::fromValue(99)).has_value() && + !Unknown->build() && Function->allInstructions().empty(), + "builder must reject an unknown frame index before instruction allocation"); + + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Function->frameInfo().createObject(StackSlot); + if (!FrameIndex) + return; + auto Live = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Live && Live->addFrameIndex(*FrameIndex).has_value() && Live->build(), + "known live frame index must build"); + Context.expect(Function->frameInfo().markObjectDead(*FrameIndex).has_value() && + !rcc::MachineVerifier::verify(*Function, *Target), + "verifier must reject an instruction that references a dead stack object"); + + auto Lifetime = createFunction(Context, "frame_lifetime", 15); + if (!Lifetime) + return; + rcc::StackObjectDesc LifetimeSlot; + LifetimeSlot.Size = 8; + LifetimeSlot.Align = *rcc::Alignment::create(8); + LifetimeSlot.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(1), rcc::MachineInstrStableID::fromValue(2)}); + Context.expect(Lifetime->frameInfo().createObject(LifetimeSlot).has_value() && + !rcc::MachineVerifier::verify(*Lifetime, *Target), + "verifier must reject stack lifetimes that reference unknown instructions"); +} + +void testTableReferenceFailures(TestContext &Context) { + auto Target = createTarget(); + auto ConstantFunction = createFunction(Context, "constant_reference", 16); + rcc::MachineBasicBlock *ConstantBlock = + ConstantFunction ? createBlock(Context, *ConstantFunction) : nullptr; + if (!Target || !ConstantFunction || ConstantBlock == nullptr) + return; + + rcc::MachineConstantPoolEntryDesc ConstantDescription; + auto Integer = rcc::APInt::fromUnsigned(32, 0x01010101U); + ConstantDescription.Value = std::move(*rcc::GenericConstantData::integer(std::move(*Integer))); + ConstantDescription.Size = 4; + ConstantDescription.Align = *rcc::Alignment::create(4); + auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription)); + rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *Target); + Context.expect(ConstantIndex && ConstantBuilder.setInsertPoint(*ConstantBlock).has_value(), + "constant-pool verifier setup must succeed"); + if (!ConstantIndex) + return; + auto ConstantInstruction = ConstantBuilder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + Context.expect(ConstantInstruction && + ConstantInstruction->addConstantPool(*ConstantIndex).has_value() && + ConstantInstruction->build() && + ConstantFunction->constantPool().eraseEntry(*ConstantIndex).has_value() && + !rcc::MachineVerifier::verify(*ConstantFunction, *Target), + "verifier must reject a tombstoned constant-pool reference"); + + auto JumpFunction = createFunction(Context, "jump_reference", 17); + rcc::MachineBasicBlock *JumpBlock = JumpFunction ? createBlock(Context, *JumpFunction) : nullptr; + if (!JumpFunction || JumpBlock == nullptr) + return; + rcc::MachineJumpTableDesc JumpDescription; + JumpDescription.Targets = {JumpBlock}; + auto JumpIndex = JumpFunction->jumpTableInfo().createTable(std::move(JumpDescription)); + rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *Target); + Context.expect(JumpIndex && JumpBuilder.setInsertPoint(*JumpBlock).has_value(), + "jump-table verifier setup must succeed"); + if (!JumpIndex) + return; + auto JumpInstruction = JumpBuilder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect(JumpInstruction && JumpInstruction->addJumpTable(*JumpIndex).has_value() && + JumpInstruction->build() && + JumpFunction->jumpTableInfo().eraseTable(*JumpIndex).has_value() && + !rcc::MachineVerifier::verify(*JumpFunction, *Target), + "verifier must reject a tombstoned jump-table reference"); +} + +void testCompilationStatusReferences(TestContext &Context) { + auto Target = createTarget(); + auto Unknown = createFunction(Context, "unknown_failure_instruction", 18); + if (!Target || !Unknown) + return; + + rcc::MachineCompilationFailure UnknownFailure; + UnknownFailure.Diagnostic.Code = rcc::ErrorCode::Unsupported; + UnknownFailure.Diagnostic.Message = "selection failed"; + UnknownFailure.FailureInstruction = rcc::MachineInstrStableID::fromValue(99); + UnknownFailure.FailureStage = rcc::PipelineStage::Selected; + Context.expect( + Unknown->compilationStatus() + .fail(rcc::MachineCompilationState::SelectionFailed, std::move(UnknownFailure)) + .has_value() && + !rcc::MachineVerifier::verify(*Unknown, *Target), + "verifier must reject compilation status that references an unknown instruction"); + + auto Live = createFunction(Context, "live_failure_instruction", 19); + rcc::MachineBasicBlock *Block = Live ? createBlock(Context, *Live) : nullptr; + if (!Live || Block == nullptr) + return; + rcc::MachineIRBuilder Builder(*Live, *Target); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "live failure instruction insertion point must be valid"); + rcc::MachineInstr *Instruction = buildNoOperand(Context, Builder, NopOpcode); + if (Instruction == nullptr) + return; + + rcc::MachineCompilationFailure LiveFailure; + LiveFailure.Diagnostic.Code = rcc::ErrorCode::Unsupported; + LiveFailure.Diagnostic.Message = "finalization failed"; + LiveFailure.FailureInstruction = Instruction->stableID(); + LiveFailure.FailureStage = rcc::PipelineStage::PostRAFinalized; + Context.expect( + Live->compilationStatus() + .fail(rcc::MachineCompilationState::FinalizationFailed, std::move(LiveFailure)) + .has_value() && + rcc::MachineVerifier::verify(*Live, *Target).has_value(), + "verifier must accept a structurally valid failed function"); +} + +} // namespace + +int main() { + TestContext Context; + testValidFunctionAndModes(Context); + testReachableStructuralFailures(Context); + testPHIStructure(Context); + testPropertyFailures(Context); + testSelectedStateAndPseudoDeadline(Context); + testFrameStateFailures(Context); + testTableReferenceFailures(Context); + testCompilationStatusReferences(Context); + return Context.result(); +} |
