diff options
Diffstat (limited to 'backend/unittests/MIR/MIParserTest.cpp')
| -rw-r--r-- | backend/unittests/MIR/MIParserTest.cpp | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp new file mode 100644 index 0000000..d037bef --- /dev/null +++ b/backend/unittests/MIR/MIParserTest.cpp @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/MIParser.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/MIR/MIPrinter.h" + +#include <cstdint> +#include <iostream> +#include <limits> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +constexpr std::uint32_t CopyOpcode = 1; +constexpr std::uint32_t AddImmediateOpcode = 2; +constexpr std::uint32_t BranchOpcode = 3; +constexpr std::uint32_t NopOpcode = 4; +constexpr std::uint32_t ConstantOpcode = 5; +constexpr std::uint32_t FloatingConstantOpcode = 6; +constexpr std::uint32_t MetadataOpcode = 7; +constexpr std::uint32_t LoadOpcode = 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 +registerOperand(rcc::RegisterOperandRole Role, + rcc::RegisterClassID Class = rcc::test::TestRegisterClass::GPR64) { + rcc::OperandDescriptor Descriptor; + Descriptor.Kinds = {rcc::MachineOperand::Kind::Register}; + Descriptor.RegisterRole = Role; + Descriptor.RegClass = Class; + return Descriptor; +} + +[[nodiscard]] rcc::InstructionDescriptor descriptor(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::OperandDescriptor genericRegisterOperand(rcc::RegisterOperandRole Role) { + rcc::OperandDescriptor Descriptor; + Descriptor.Kinds = {rcc::MachineOperand::Kind::Register}; + Descriptor.RegisterRole = Role; + return Descriptor; +} + +[[nodiscard]] rcc::OperandDescriptor operand(rcc::MachineOperand::Kind Kind) { + rcc::OperandDescriptor Descriptor; + Descriptor.Kinds = {Kind}; + return Descriptor; +} + +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { + auto Copy = descriptor(CopyOpcode, "TEST_COPY"); + Copy.Operands = { + registerOperand(rcc::RegisterOperandRole::Def), + registerOperand(rcc::RegisterOperandRole::Use), + }; + Copy.NumExplicitDefs = 1; + + auto AddImmediate = descriptor(AddImmediateOpcode, "TEST_ADDI"); + AddImmediate.Operands = { + registerOperand(rcc::RegisterOperandRole::Def), + registerOperand(rcc::RegisterOperandRole::Use), + }; + rcc::OperandDescriptor Immediate; + Immediate.Kinds = {rcc::MachineOperand::Kind::Immediate}; + Immediate.AllowedTargetFlags = 0xFU; + AddImmediate.Operands.push_back(Immediate); + AddImmediate.NumExplicitDefs = 1; + + auto Branch = descriptor(BranchOpcode, "TEST_BRANCH"); + rcc::OperandDescriptor Block; + Block.Kinds = {rcc::MachineOperand::Kind::BasicBlock}; + Branch.Operands.push_back(Block); + Branch.Flags.set(rcc::InstructionFlag::Branch); + Branch.Flags.set(rcc::InstructionFlag::Terminator); + Branch.Flags.set(rcc::InstructionFlag::Barrier); + + auto Nop = descriptor(NopOpcode, "TEST_NOP"); + + auto Constant = descriptor(ConstantOpcode, "TEST_CONSTANT"); + Constant.Operands = { + genericRegisterOperand(rcc::RegisterOperandRole::Def), + operand(rcc::MachineOperand::Kind::IntegerImmediate), + }; + Constant.NumExplicitDefs = 1; + Constant.Flags.set(rcc::InstructionFlag::Generic); + Constant.Flags.set(rcc::InstructionFlag::AllowedPreLegalizer); + + auto FloatingConstant = descriptor(FloatingConstantOpcode, "TEST_FCONSTANT"); + FloatingConstant.Operands = { + genericRegisterOperand(rcc::RegisterOperandRole::Def), + operand(rcc::MachineOperand::Kind::FloatingImmediate), + }; + FloatingConstant.NumExplicitDefs = 1; + FloatingConstant.Flags.set(rcc::InstructionFlag::Generic); + FloatingConstant.Flags.set(rcc::InstructionFlag::AllowedPreLegalizer); + + auto Metadata = descriptor(MetadataOpcode, "TEST_METADATA"); + Metadata.Operands = { + operand(rcc::MachineOperand::Kind::Metadata), + operand(rcc::MachineOperand::Kind::Metadata), + operand(rcc::MachineOperand::Kind::CFIIndex), + }; + + auto Load = descriptor(LoadOpcode, "TEST_LOAD"); + Load.Operands = { + registerOperand(rcc::RegisterOperandRole::Def), + registerOperand(rcc::RegisterOperandRole::Use), + }; + Load.NumExplicitDefs = 1; + Load.Flags.set(rcc::InstructionFlag::MayLoad); + Load.RequiresMemoryOperands = true; + + std::vector<rcc::InstructionDescriptor> Descriptors; + Descriptors.push_back(std::move(Copy)); + Descriptors.push_back(std::move(AddImmediate)); + Descriptors.push_back(std::move(Branch)); + Descriptors.push_back(std::move(Nop)); + Descriptors.push_back(std::move(Constant)); + Descriptors.push_back(std::move(FloatingConstant)); + Descriptors.push_back(std::move(Metadata)); + Descriptors.push_back(std::move(Load)); + 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::uint64_t ID) { + auto Function = + rcc::MachineFunction::create("parser", rcc::MachineFunctionStableID::fromValue(ID)); + Context.expect(Function.has_value(), "machine function creation must succeed"); + if (!Function) + return nullptr; + return std::move(*Function); +} + +void testTwoPassBodyParsing(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 1); + Context.expect(Target.has_value(), "parser target description must be valid"); + if (!Target) + std::cerr << "error: " << Target.error().message() << '\n'; + if (!Target || !Function) + return; + auto MetadataNode = + Function->metadata().addNode({.Slot = 100, .IsDistinct = false, .Elements = {}}); + Context.expect(MetadataNode.has_value(), "metadata fixture must be valid"); + if (!MetadataNode) + return; + + const std::string Body = R"( +bb.0."entry block" (align 16, bb_id 7.1, call-frame-size 32): + liveins: $g0 + successors: %bb.1."exit block"(2147483648) + %0:gpr64 = TEST_COPY $g0 + %1:gpr64 = frame-setup TEST_ADDI %0, target-flags(test-direct-one, test-bit-four) 4 + %2:_(s128) = TEST_CONSTANT i128 340282366920938463463374607431768211455 + %3:_(f64) = TEST_FCONSTANT double f0x400921FB54442D18 + TEST_METADATA !100, !DIExpression(DW_OP_LLVM_arg, 0), def_cfa $g0, 16, pre-instr-symbol <mcsymbol before>, post-instr-symbol <mcsymbol after>, heap-alloc-marker !0, pcsections !0, mmra !0, cfi-type 17, deactivation-symbol @guard, debug-instr-number 9, debug-location !DILocation(line: 42, column: 7, scope: !0, inlinedAt: !DILocation(line: 8, scope: !0, inlinedAt: !0), isImplicitCode: true, atomGroup: 3, atomRank: 2) + %4:gpr64 = TEST_LOAD %0 :: (load 8 from unknown-address, align 8, addrspace 0, !tbaa !0) + TEST_BRANCH %bb.1."exit block" + +bb.1."exit block" (landing-pad, bbsections Cold): + $g1 = TEST_COPY %1 + TEST_NOP { + TEST_NOP + } +)"; + + rcc::mir::MIParserContext ParserContext; + ParserContext.Metadata.push_back( + {.TextID = 0, .Reference = rcc::MachineMetadataReference::embeddedIR(0)}); + ParserContext.DefaultPointerSizeInBits = 64; + rcc::mir::MIParser Parser(*Function, *Target, "body.mir", Body, ParserContext); + const auto Parsed = Parser.parse(); + const bool ParseSucceeded = + Parsed.has_value() && !Parser.failed() && Parser.diagnostics().empty(); + Context.expect(ParseSucceeded, + "valid MIR body must pass both parsing passes and final verification"); + if (!ParseSucceeded) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + if (!Parsed) + std::cerr << "error: " << Parsed.error().message() << '\n'; + } + if (!Parsed) + return; + + Context.expect(Function->blockCount() == 2 && Function->allInstructions().size() == 10, + "parser must publish every declared block and instruction"); + rcc::MachineBasicBlock *Entry = Function->firstBlock(); + rcc::MachineBasicBlock *Exit = Entry == nullptr ? nullptr : Entry->next(); + Context.expect(Entry != nullptr && Exit != nullptr && !Exit->next(), + "parsed block layout must retain source order"); + if (Entry == nullptr || Exit == nullptr) + return; + + Context.expect(Entry->attributes().name() == "entry block" && + Entry->attributes().alignment().bytes() == 16 && + Entry->attributes().profileID() == rcc::MachineBasicBlockProfileID{7, 1} && + Entry->attributes().callFrameSize() == 32, + "first-pass block attributes must be retained"); + Context.expect(Exit->attributes().has(rcc::MachineBasicBlockAttribute::LandingPad) && + Exit->attributes().sectionID() == rcc::MachineBasicBlockSectionID::cold(), + "named block flags and section IDs must be retained"); + Context.expect(Entry->liveIns().size() == 1 && Entry->successors().size() == 1 && + Entry->successors().front().Block == Exit && + Entry->successors().front().Probability.rawValue() == 2147483648U, + "live-ins and explicitly weighted successors must be published"); + + const auto Registers = Function->registerInfo().virtualRegisters(); + Context.expect(Registers.size() == 5 && + Registers[0].RegClass == rcc::test::TestRegisterClass::GPR64 && + Registers[1].RegClass == rcc::test::TestRegisterClass::GPR64 && + Registers[2].Type == *rcc::LLT::makeAnyScalar(128) && + Registers[3].Type == *rcc::LLT::makeFloat(rcc::FloatSemantics::IEEEDouble) && + Registers[4].RegClass == rcc::test::TestRegisterClass::GPR64, + "textual virtual registers must map deterministically to classed MachineIR vregs"); + + const rcc::MachineInstr *Add = Entry->firstInstruction()->next(); + Context.expect(Add->flags().has(rcc::MachineInstrFlag::FrameSetup) && + Add->operandAt(2)->targetFlags() == 0x5U, + "instruction and target operand flags must be preserved"); + + const rcc::MachineInstr *Constant = Add->next(); + const auto *Integer = Constant->operandAt(1)->getIf<rcc::IntegerImmediateOperand>(); + const auto *Floating = Constant->next()->operandAt(1)->getIf<rcc::FloatingImmediateOperand>(); + Context.expect(Integer != nullptr && Integer->Value.bitWidth() == 128 && + Integer->Value.words().size() == 2 && + Integer->Value.words()[0] == std::numeric_limits<std::uint64_t>::max() && + Integer->Value.words()[1] == std::numeric_limits<std::uint64_t>::max() && + Floating != nullptr && + Floating->Bits.semantics() == rcc::FloatSemantics::IEEEDouble && + Floating->Bits.words()[0] == 0x400921FB54442D18ULL, + "typed integer and exact floating-point operands must retain their bit patterns"); + + const rcc::MachineInstr *Metadata = Constant->next()->next(); + const auto *Reference = Metadata->operandAt(0)->getIf<rcc::MetadataOperand>(); + const auto *Expression = Metadata->operandAt(1)->getIf<rcc::MetadataOperand>(); + const auto *CFI = Metadata->operandAt(2)->getIf<rcc::CFIIndexOperand>(); + Context.expect( + Reference != nullptr && Reference->Value.reference() != nullptr && + Reference->Value.reference()->scope() == rcc::MachineMetadataScope::MachineFunction && + Reference->Value.reference()->slot() == 100 && Expression != nullptr && + Expression->Value.expression() != nullptr && + Expression->Value.expression()->elements().size() == 2 && + Expression->Value.expression()->elements()[0] == 0x1005U && CFI != nullptr && + Function->cfiInfo().instruction(CFI->Index) != nullptr && + Function->cfiInfo().instruction(CFI->Index)->operation() == + rcc::MachineCFIOperation::DefCFA && + Metadata->preInstrSymbol() == "before" && Metadata->postInstrSymbol() == "after" && + Metadata->heapAllocMarker().has_value() && Metadata->pcSections().has_value() && + Metadata->mmra().has_value() && Metadata->cfiType() == 17 && + Metadata->deactivationSymbol() == "guard" && Metadata->debugInstrNumber() == 9 && + Metadata->debugLocation().has_value() && + Metadata->debugLocation()->inlineLocation() != nullptr && + Metadata->debugLocation()->inlineLocation()->inlineChain().size() == 2 && + Metadata->debugLocation()->inlineLocation()->inlineChain()[0].Line == 42 && + Metadata->debugLocation()->inlineLocation()->inlineChain()[0].Column == 7 && + Metadata->debugLocation()->inlineLocation()->inlineChain()[0].IsImplicitCode && + Metadata->debugLocation()->inlineLocation()->inlineChain()[0].AtomGroup == 3 && + Metadata->debugLocation()->inlineLocation()->inlineChain()[0].AtomRank == 2 && + Metadata->debugLocation()->inlineLocation()->terminalInlinedAt() == + rcc::MachineMetadataReference::embeddedIR(0), + "metadata, debug expressions, and CFI operands must retain structured semantics"); + + const rcc::MachineInstr *Load = Metadata->next(); + Context.expect(Load->memOperands().size() == 1 && Load->memOperands().front()->isLoad() && + Load->memOperands().front()->description().Size == + *rcc::MemorySize::fixed(8) && + Load->memOperands().front()->description().Align.bytes() == 8 && + Load->memOperands().front()->description().TBAA.has_value(), + "machine memory operands must retain access, size, alignment, and metadata"); + + const rcc::MachineInstr *BundleHeader = Exit->firstInstruction()->next(); + const rcc::MachineInstr *BundleMember = BundleHeader->next(); + Context.expect(BundleHeader->flags().has(rcc::MachineInstrFlag::BundledWithSucc) && + BundleMember->flags().has(rcc::MachineInstrFlag::BundledWithPred), + "instruction bundle braces must produce reciprocal layout flags"); + + auto Printed = rcc::mir::printMachineFunctionBody(*Function, *Target, ParserContext); + Context.expect(Printed.has_value(), "complete MIR body printing must accept the parsed function"); + if (!Printed) { + std::cerr << "error: " << Printed.error().message() << '\n'; + return; + } + + auto RoundTripFunction = createFunction(Context, 2); + if (!RoundTripFunction) + return; + auto RoundTripMetadata = + RoundTripFunction->metadata().addNode({.Slot = 100, .IsDistinct = false, .Elements = {}}); + Context.expect(RoundTripMetadata.has_value(), "round-trip metadata fixture must be valid"); + if (!RoundTripMetadata) + return; + rcc::mir::MIParser RoundTripParser(*RoundTripFunction, *Target, "round-trip.mir", *Printed, + ParserContext); + const auto RoundTrip = RoundTripParser.parse(); + Context.expect(RoundTrip.has_value() && !RoundTripParser.failed(), + "the complete body printer output must parse successfully"); + if (!RoundTrip) { + std::cerr << *Printed << '\n'; + for (const auto &Diagnostic : RoundTripParser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + return; + } + auto PrintedAgain = + rcc::mir::printMachineFunctionBody(*RoundTripFunction, *Target, ParserContext); + Context.expect(PrintedAgain.has_value() && *PrintedAgain == *Printed, + "complete MIR body parse-print-parse must be deterministic"); +} + +void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body, + std::string_view Message) { + auto Function = createFunction(Context, 10); + if (!Function) + return; + rcc::mir::MIParser Parser(*Function, Target, "invalid.mir", std::move(Body)); + const auto Parsed = Parser.parse(); + const bool IsExpectedDiagnostic = + !Parsed && Parser.failed() && Parser.diagnostics().size() == 1 && + Parser.diagnostics().front().Category == "mir-parser" && + Parser.diagnostics().front().Message.find(Message) != std::string::npos; + Context.expect(IsExpectedDiagnostic, + "invalid MIR body must produce one stable parser diagnostic"); + if (!IsExpectedDiagnostic) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + if (!Parsed) + std::cerr << "error: " << Parsed.error().message() << '\n'; + } +} + +void testDiagnostics(TestContext &Context) { + auto Target = createTarget(); + Context.expect(Target.has_value(), "diagnostic target description must be valid"); + if (!Target) + std::cerr << "error: " << Target.error().message() << '\n'; + if (!Target) + return; + + expectParseError(Context, *Target, "bb.0:\n TEST_BRANCH %bb.9\n", + "undefined machine basic block"); + expectParseError(Context, *Target, "bb.0:\n TEST_NOP\nbb.0:\n TEST_NOP\n", + "redefinition of machine basic block"); + expectParseError(Context, *Target, + "bb.0:\n %0:gpr64 = TEST_ADDI $g0, " + "target-flags(no-such-flag) 1\n", + "unknown target operand flag"); + expectParseError(Context, *Target, " bb.0:\n TEST_NOP\n", + "basic block definition must be at the start of a line"); +} + +} // namespace + +int main() { + TestContext Context; + testTwoPassBodyParsing(Context); + testDiagnostics(Context); + return Context.result(); +} |
