aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen/MachineVerifierTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/CodeGen/MachineVerifierTest.cpp')
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp738
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();
+}