diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-11 17:37:42 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-11 17:37:42 +0800 |
| commit | 6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0 (patch) | |
| tree | f363c502f0e2b9cda2f12928049275a433db6ad4 /backend/unittests | |
| parent | adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f (diff) | |
| download | rcc-project-6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0.tar.gz rcc-project-6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0.tar.xz | |
[MIR] Accept fixed-baseline LLVM syntax
Diffstat (limited to 'backend/unittests')
| -rw-r--r-- | backend/unittests/CodeGen/CompilationUnitTest.cpp | 26 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineDebugInfoTest.cpp | 35 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp | 6 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineValueTypesTest.cpp | 35 | ||||
| -rw-r--r-- | backend/unittests/MIR/LLVMConstantSyntaxTest.cpp | 28 | ||||
| -rw-r--r-- | backend/unittests/MIR/MILexerTest.cpp | 2 | ||||
| -rw-r--r-- | backend/unittests/MIR/MIParserTest.cpp | 100 | ||||
| -rw-r--r-- | backend/unittests/MIR/MIRDocumentTest.cpp | 87 | ||||
| -rw-r--r-- | backend/unittests/Support/TextDiagnosticTest.cpp | 2 | ||||
| -rw-r--r-- | backend/unittests/Support/YAMLTest.cpp | 1 | ||||
| -rw-r--r-- | backend/unittests/Tooling/MIRReducerFuzzerTest.cpp | 2 | ||||
| -rw-r--r-- | backend/unittests/Tooling/PipelineDriverTest.cpp | 15 |
12 files changed, 318 insertions, 21 deletions
diff --git a/backend/unittests/CodeGen/CompilationUnitTest.cpp b/backend/unittests/CodeGen/CompilationUnitTest.cpp index a31c9a8..141d6db 100644 --- a/backend/unittests/CodeGen/CompilationUnitTest.cpp +++ b/backend/unittests/CodeGen/CompilationUnitTest.cpp @@ -19,29 +19,34 @@ int fail(const char *Message) { int main() { rcc::CompilationUnit Unit; - auto Empty = Unit.createMachineFunction(""); - if (Empty || Unit.functionCount() != 0) - return fail("CompilationUnit must reject an empty function name atomically"); + auto FirstAnonymous = Unit.createMachineFunction(""); + auto SecondAnonymous = Unit.createMachineFunction(""); + if (!FirstAnonymous || !SecondAnonymous || Unit.functionCount() != 2 || + (*FirstAnonymous)->stableID().value() != 1 || (*SecondAnonymous)->stableID().value() != 2 || + Unit.function("") != nullptr) + return fail("CompilationUnit must retain anonymous functions by order and StableID"); auto First = Unit.createMachineFunction("first"); - if (!First || (*First)->stableID().value() != 1) + if (!First || (*First)->stableID().value() != 3) return fail("CompilationUnit must allocate the first stable function identity"); auto Duplicate = Unit.createMachineFunction("first"); - if (Duplicate || Unit.functionCount() != 1) + if (Duplicate || Unit.functionCount() != 3) return fail("CompilationUnit must reject duplicate function names atomically"); auto Second = Unit.createMachineFunction("second"); - if (!Second || (*Second)->stableID().value() != 2) + if (!Second || (*Second)->stableID().value() != 4) return fail("Rejected function creation must not consume a StableID"); - if (Unit.functionCount() != 2 || Unit.functionAt(0) != *First || Unit.functionAt(1) != *Second) + if (Unit.functionCount() != 4 || Unit.functionAt(0) != *FirstAnonymous || + Unit.functionAt(1) != *SecondAnonymous || Unit.functionAt(2) != *First || + Unit.functionAt(3) != *Second) return fail("CompilationUnit must preserve function document order"); if (Unit.function("first") != *First || Unit.function("missing") != nullptr) return fail("CompilationUnit must provide exact name lookup"); if (Unit.function((*Second)->stableID()) != *Second || Unit.function(rcc::MachineFunctionStableID::fromValue(99)) != nullptr) return fail("CompilationUnit must provide exact StableID lookup"); - if (Unit.functionAt(2) != nullptr) + if (Unit.functionAt(4) != nullptr) return fail("CompilationUnit must reject an out-of-range ordered lookup"); auto Zero = rcc::GlobalInitializer::zeroFill(4); @@ -58,7 +63,7 @@ int main() { return fail("CompilationUnit must own and index global objects"); auto ConflictingFunction = Unit.createMachineFunction("global"); - if (ConflictingFunction || Unit.functionCount() != 2) + if (ConflictingFunction || Unit.functionCount() != 4) return fail("CompilationUnit must reject function/global symbol conflicts atomically"); rcc::GlobalObjectDesc ConflictingGlobal; @@ -88,7 +93,8 @@ int main() { return fail("A valid CompilationUnit must pass verification"); rcc::CompilationUnit Moved = std::move(Unit); - if (Moved.functionCount() != 2 || Moved.function("first") != *First || + if (Moved.functionCount() != 4 || Moved.functionAt(0) != *FirstAnonymous || + Moved.functionAt(1) != *SecondAnonymous || Moved.function("first") != *First || Moved.function("second") != *Second || Moved.globalObject("global") != *Global) return fail("Moving a CompilationUnit must preserve owned object addresses and indices"); Verification = Moved.verify(); diff --git a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp index 80a83e0..ae4d539 100644 --- a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp +++ b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp @@ -3,6 +3,7 @@ #include "rcc/CodeGen/MachineDebugInfo.h" #include "Target/TestTarget.h" +#include "rcc/CodeGen/GenericOpcodes.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineFrameInfo.h" @@ -26,8 +27,8 @@ namespace { -constexpr std::uint32_t ValueOpcode = 1; -constexpr std::uint32_t MetaOpcode = 2; +constexpr std::uint32_t ValueOpcode = 1024; +constexpr std::uint32_t MetaOpcode = 1025; class TestContext final { public: @@ -64,7 +65,11 @@ private: std::vector<rcc::InstructionDescriptor> Descriptors; Descriptors.push_back(std::move(Value)); Descriptors.push_back(std::move(Meta)); - auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + std::vector<rcc::ImmediatePredicate> Predicates; + auto Generic = rcc::appendGenericInstructionInfo(Descriptors, Predicates); + if (!Generic) + return std::unexpected(Generic.error()); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates)); if (!Instructions) return std::unexpected(Instructions.error()); return rcc::test::createTestTargetDescription(std::move(*Instructions)); @@ -419,6 +424,30 @@ void testVerifierFailures(TestContext &Context) { return; { + auto Function = createFunction(Context, 9, "missing-debug-location"); + if (Function == nullptr) + return; + rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); + if (Block == nullptr) + return; + auto Instruction = Function->createInstruction(rcc::opcode(rcc::GenericOpcode::DBG_VALUE)); + if (!Instruction) + return; + for (const std::int64_t Value : {1, 2, 3, 4}) { + if (!(*Instruction)->addOperand(rcc::MachineOperand::immediate(Value))) + return; + } + Context.expect(Block->appendInstruction(**Instruction).has_value(), + "four-operand DBG_VALUE must be linkable for verifier coverage"); + auto Verification = rcc::MachineVerifier::verify(*Function, *Target); + Context.expect(!Verification && + Verification.error().message().find( + "Bad machine code: Missing DebugLoc for debug instruction\n" + "- instruction: DBG_VALUE 1, 2, 3, 4") != std::string_view::npos, + "complete DBG_VALUE must require a DebugLoc with LLVM-compatible diagnostics"); + } + + { auto Function = createFunction(Context, 10, "debug-meta"); if (Function == nullptr) return; diff --git a/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp b/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp index 8ca6bf3..621bd21 100644 --- a/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp @@ -301,7 +301,7 @@ void testCrossReferenceFailures(TestContext &Context) { } { - auto Function = createFunction(Context, 13, "unknown-bbid"); + auto Function = createFunction(Context, 13, "dangling-prefetch-target"); if (Function == nullptr) return; rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); @@ -313,8 +313,8 @@ void testCrossReferenceFailures(TestContext &Context) { .CallsiteIndex = 0, }) .has_value() && - !rcc::MachineVerifier::verify(*Function, *Target), - "prefetch targets must reference a live block bb_id"); + rcc::MachineVerifier::verify(*Function, *Target).has_value(), + "prefetch targets may reference a bb_id without a machine block"); } { diff --git a/backend/unittests/CodeGen/MachineValueTypesTest.cpp b/backend/unittests/CodeGen/MachineValueTypesTest.cpp index 40f121b..50482ba 100644 --- a/backend/unittests/CodeGen/MachineValueTypesTest.cpp +++ b/backend/unittests/CodeGen/MachineValueTypesTest.cpp @@ -119,6 +119,21 @@ void testMemoryValueTypes(TestContext &Context) { Context.expect(Symbol.kind() == rcc::MemoryBase::Kind::GlobalSymbol, "global memory base must preserve its kind"); Context.expect(Symbol.symbolName() == "global", "global memory base must preserve its symbol"); + + rcc::GenericConstantData PoisonPointer; + PoisonPointer.Type = rcc::GenericConstantType::pointer(); + PoisonPointer.Kind = rcc::GenericConstantKind::Poison; + auto ConstantBase = rcc::MemoryBase::irConstant(PoisonPointer); + Context.expect(ConstantBase && ConstantBase->kind() == rcc::MemoryBase::Kind::IRConstant && + ConstantBase->constantValue() != nullptr && + *ConstantBase->constantValue() == PoisonPointer, + "IR-constant memory base must preserve a structured pointer constant"); + + rcc::GenericConstantData InvalidIntegerBase; + InvalidIntegerBase.Type = *rcc::GenericConstantType::integer(8); + InvalidIntegerBase.Kind = rcc::GenericConstantKind::Poison; + Context.expect(!rcc::MemoryBase::irConstant(std::move(InvalidIntegerBase)), + "IR-constant memory base must reject a non-pointer constant"); } void testMachineMemOperand(TestContext &Context) { @@ -172,6 +187,26 @@ void testMachineMemOperand(TestContext &Context) { EmptySymbol.Base = rcc::MemoryBase::externalSymbol(""); Context.expect(!rcc::MachineMemOperand::create(EmptySymbol), "empty MMO symbol base must be rejected"); + + auto OffsetAlignment = makeBasicLoadDescription(); + OffsetAlignment.Offset = 12; + OffsetAlignment.Align = *rcc::Alignment::create(4); + OffsetAlignment.BaseAlign = *rcc::Alignment::create(8); + auto OffsetAligned = rcc::MachineMemOperand::create(OffsetAlignment); + Context.expect(OffsetAligned && OffsetAligned->description().BaseAlign && + OffsetAligned->description().BaseAlign->bytes() == 8, + "MMO base alignment must derive effective alignment through its offset"); + + OffsetAlignment.Align = *rcc::Alignment::create(8); + Context.expect(!rcc::MachineMemOperand::create(OffsetAlignment), + "MMO alignment inconsistent with its base and offset must be rejected"); + + OffsetAlignment.Offset = 16; + OffsetAlignment.Align = *rcc::Alignment::create(8); + OffsetAlignment.BaseAlign = *rcc::Alignment::create(8); + auto RedundantBase = rcc::MachineMemOperand::create(OffsetAlignment); + Context.expect(RedundantBase && !RedundantBase->description().BaseAlign, + "MMO base alignment equal to effective alignment must canonicalize away"); } } // namespace diff --git a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp index be5a67e..96346a0 100644 --- a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp +++ b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp @@ -103,6 +103,19 @@ void testScalarConstants(TestContext &Context) { expectRoundTrip(Context, "i32 undef", 4); expectRoundTrip(Context, "i32 poison", 4); expectRoundTrip(Context, "i32 zeroinitializer", 4); + + auto BFloat = rcc::mir::parseLLVMConstant("bfloat 0xR3E80", Options); + auto PrintedBFloat = BFloat ? rcc::mir::printLLVMConstant(*BFloat) + : rcc::Expected<std::string>(std::unexpected(BFloat.error())); + Context.expect(PrintedBFloat && *PrintedBFloat == "bfloat 2.500000e-01", + "LLVM-compatible floating printing must prefer an exact decimal spelling"); + + auto Pi = rcc::mir::parseLLVMConstant("double f0x400921FB54442D18", Options); + auto PrintedPi = Pi ? rcc::mir::printLLVMConstant(*Pi) + : rcc::Expected<std::string>(std::unexpected(Pi.error())); + Context.expect( + PrintedPi && *PrintedPi == "double f0x400921FB54442D18", + "floating printing must retain exact bits when six decimal digits do not round-trip"); } void expectIntegerWords(TestContext &Context, std::string_view Text, @@ -110,11 +123,23 @@ void expectIntegerWords(TestContext &Context, std::string_view Text, auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); Context.expect(Parsed.has_value() && Parsed->IntegerValue.has_value() && std::ranges::equal(Parsed->IntegerValue->words(), ExpectedWords), - "explicit LLVM hexadecimal integer must preserve its exact bit pattern"); + "LLVM integer literal must preserve its exact bit pattern"); if (!Parsed) std::cerr << Text << ": " << Parsed.error().message() << '\n'; } +void testDecimalIntegerTruncation(TestContext &Context) { + constexpr std::array<std::uint64_t, 1> NegativeOne{std::numeric_limits<std::uint64_t>::max()}; + constexpr std::array<std::uint64_t, 1> NarrowNegative{767}; + constexpr std::array<std::uint64_t, 1> NarrowMinimum{8}; + constexpr std::array<std::uint64_t, 1> WidePositive{1}; + + expectIntegerWords(Context, "i64 -1", NegativeOne); + expectIntegerWords(Context, "i16 -64769", NarrowNegative); + expectIntegerWords(Context, "i4 -8", NarrowMinimum); + expectIntegerWords(Context, "i64 18446744073709551617", WidePositive); +} + void expectFloatingWords(TestContext &Context, std::string_view Text, std::span<const std::uint64_t> ExpectedWords) { auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); @@ -484,6 +509,7 @@ int main() { TestContext Context; testScalarConstants(Context); testExplicitHexIntegerConstants(Context); + testDecimalIntegerTruncation(Context); testSpecialFloatingLiterals(Context); testExactFloatingConversion(Context); testAggregateConstants(Context); diff --git a/backend/unittests/MIR/MILexerTest.cpp b/backend/unittests/MIR/MILexerTest.cpp index a130eb0..5ebd2ca 100644 --- a/backend/unittests/MIR/MILexerTest.cpp +++ b/backend/unittests/MIR/MILexerTest.cpp @@ -392,7 +392,7 @@ void testRangesAndClassifiers(TestContext &Context) { static_cast<void>(Invalid.next()); const std::string Formatted = rcc::formatTextDiagnostic(Invalid.diagnostics().front(), Invalid.source()); - Context.expect(Formatted.starts_with("body.mir:2:3: error[mir-lexer]: unexpected character '#'"), + Context.expect(Formatted.starts_with("error: body.mir:2:3: unexpected character '#'"), "formatted MI diagnostics must report stable input line and column"); rcc::mir::MILexer Original("move.mir", R"("owned\20value")"); diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp index 07aef75..918de62 100644 --- a/backend/unittests/MIR/MIParserTest.cpp +++ b/backend/unittests/MIR/MIParserTest.cpp @@ -32,6 +32,7 @@ constexpr std::uint32_t ConstantOpcode = 5; constexpr std::uint32_t FloatingConstantOpcode = 6; constexpr std::uint32_t MetadataOpcode = 7; constexpr std::uint32_t LoadOpcode = 8; +constexpr std::uint32_t InlineAsmOpcode = 9; class TestContext final { public: @@ -144,6 +145,12 @@ registerOperand(rcc::RegisterOperandRole Role, Load.Flags.set(rcc::InstructionFlag::MayLoad); Load.RequiresMemoryOperands = true; + auto InlineAsm = descriptor(InlineAsmOpcode, "INLINEASM"); + InlineAsm.Operands = { + operand(rcc::MachineOperand::Kind::ExternalSymbol), + operand(rcc::MachineOperand::Kind::Immediate), + }; + std::vector<rcc::InstructionDescriptor> Descriptors; Descriptors.push_back(std::move(Copy)); Descriptors.push_back(std::move(AddImmediate)); @@ -153,6 +160,7 @@ registerOperand(rcc::RegisterOperandRole Role, Descriptors.push_back(std::move(FloatingConstant)); Descriptors.push_back(std::move(Metadata)); Descriptors.push_back(std::move(Load)); + Descriptors.push_back(std::move(InlineAsm)); auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); if (!Instructions) return std::unexpected(Instructions.error()); @@ -319,6 +327,8 @@ bb.1."exit block" (landing-pad, bbsections Cold): std::cerr << "error: " << Printed.error().message() << '\n'; return; } + Context.expect(Printed->find("TEST_ADDI %0,") != std::string::npos, + "virtual-register uses must not repeat a definition constraint"); auto RoundTripFunction = createFunction(Context, 2); if (!RoundTripFunction) @@ -434,6 +444,94 @@ void testCompatibleRegisterClassAndBankAnnotations(TestContext &Context) { "conflicting virtual register bank"); } +void testTypedIntegerTruncation(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 12); + Context.expect(Target.has_value(), "typed-integer target description must be valid"); + if (!Target || !Function) + return; + + const std::string Body = R"(bb.0: + %0:_(s64) = TEST_CONSTANT i64 -1 + %1:_(s16) = TEST_CONSTANT i16 -64769 + %2:_(s4) = TEST_CONSTANT i4 -8 + %3:_(s64) = TEST_CONSTANT i64 18446744073709551617 +)"; + rcc::mir::MIParser Parser(*Function, *Target, "typed-integer-truncation.mir", Body); + const auto Parsed = Parser.parse(); + const auto Instructions = Function->allInstructions(); + const auto *MinusOne = Instructions.size() > 0 + ? Instructions[0]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>() + : nullptr; + const auto *NarrowNegative = + Instructions.size() > 1 + ? Instructions[1]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>() + : nullptr; + const auto *NarrowMinimum = + Instructions.size() > 2 + ? Instructions[2]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>() + : nullptr; + const auto *WidePositive = + Instructions.size() > 3 + ? Instructions[3]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>() + : nullptr; + Context.expect(Parsed.has_value() && MinusOne != nullptr && + MinusOne->Value.words()[0] == std::numeric_limits<std::uint64_t>::max() && + NarrowNegative != nullptr && NarrowNegative->Value.bitWidth() == 16 && + NarrowNegative->Value.words()[0] == 767 && NarrowMinimum != nullptr && + NarrowMinimum->Value.bitWidth() == 4 && NarrowMinimum->Value.words()[0] == 8 && + WidePositive != nullptr && WidePositive->Value.words()[0] == 1, + "typed integer literals must use LLVM width truncation semantics"); + if (!Parsed) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + std::cerr << "error: " << Parsed.error().message() << '\n'; + } +} + +void testLLVMInlineAsmAndConstantMemorySyntax(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 11); + Context.expect(Target.has_value(), "LLVM MIR syntax target description must be valid"); + if (!Target || !Function) + return; + + const std::string Body = R"(bb.0: + INLINEASM &"", sideeffect mayload inteldialect + %0:gpr64 = TEST_LOAD $g0 :: (load (s64) from `ptr poison`) +)"; + rcc::mir::MIParserContext ParserContext; + ParserContext.DefaultPointerSizeInBits = 64; + rcc::mir::MIParser Parser(*Function, *Target, "llvm-compatible.mir", Body, ParserContext); + auto Parsed = Parser.parse(); + Context.expect(Parsed.has_value(), "LLVM inline-asm and constant-pointer MMO syntax must parse"); + if (!Parsed) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + return; + } + + const rcc::MachineInstr *InlineAsm = Function->firstBlock()->firstInstruction(); + const auto *ExtraInfo = InlineAsm->operandAt(1)->getIf<std::int64_t>(); + const rcc::MachineInstr *Load = InlineAsm->next(); + const rcc::MachineMemOperandDesc &Memory = Load->memOperands().front()->description(); + const auto MemoryType = rcc::LLT::makeAnyScalar(64); + Context.expect(ExtraInfo != nullptr && *ExtraInfo == 13, + "inline-asm extra-info names must aggregate into the LLVM bit field"); + Context.expect(Memory.Base.kind() == rcc::MemoryBase::Kind::IRConstant && + Memory.Base.constantValue() != nullptr && + Memory.Base.constantValue()->Kind == rcc::GenericConstantKind::Poison && + MemoryType && Memory.MemoryType == *MemoryType, + "quoted LLVM pointer constants must remain structured MMO bases"); + + auto Printed = rcc::mir::printMachineFunctionBody(*Function, *Target, ParserContext); + Context.expect(Printed && + Printed->find("INLINEASM &\"\", sideeffect mayload inteldialect") != + std::string::npos && + Printed->find("from `ptr poison`") != std::string::npos, + "canonical MIR must print symbolic inline-asm flags and LLVM constant bases"); +} + void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body, std::string_view Message) { auto Function = createFunction(Context, 10); @@ -483,6 +581,8 @@ int main() { testAddressSpacePointerWidthResolver(Context); testUnserializableLowLevelType(Context); testCompatibleRegisterClassAndBankAnnotations(Context); + testTypedIntegerTruncation(Context); + testLLVMInlineAsmAndConstantMemorySyntax(Context); testDiagnostics(Context); return Context.result(); } diff --git a/backend/unittests/MIR/MIRDocumentTest.cpp b/backend/unittests/MIR/MIRDocumentTest.cpp index 1117bf1..b6ca0bb 100644 --- a/backend/unittests/MIR/MIRDocumentTest.cpp +++ b/backend/unittests/MIR/MIRDocumentTest.cpp @@ -468,6 +468,11 @@ void testDocumentTransaction(TestContext &Context) { std::cerr << "error: " << Printed.error().message() << '\n'; return; } + Context.expect(Printed->find("name: documented\nalignment: 16\n") != std::string::npos && + Printed->find("functionContext: ''") != std::string::npos && + Printed->find("...\n---\nname: documented") != std::string::npos && + Printed->ends_with("...\n"), + "complete MIR must use LLVM-compatible scalars, defaults, and document markers"); auto RoundTrip = rcc::mir::parseMIRDocument(*Target, "round-trip.mir", std::string(*Printed)); Context.expect(RoundTrip.succeeded(), "printed complete MIR must parse successfully"); if (!RoundTrip.succeeded()) { @@ -905,7 +910,7 @@ body: | rcc::mir::parseMIRDocument(*Target, "properties-invalid.mir", std::string(InvalidDocument)); const bool Rejected = !Invalid.succeeded() && Invalid.document() == nullptr && Invalid.diagnostics().size() == 1 && - Invalid.diagnostics().front().Message.find("noVRegs") != std::string::npos; + Invalid.diagnostics().front().Message.find("NoVRegs") != std::string::npos; Context.expect(Rejected, "failsVerification must not bypass RCC's mandatory input validation"); if (!Rejected) printDiagnostics(Invalid); @@ -936,6 +941,85 @@ body: | printDiagnostics(Parsed); } +void testAnonymousFunctionsAndIRBlocks(TestContext &Context) { + auto Target = createTarget(); + Context.expect(Target.has_value(), "anonymous-function target must be valid"); + if (!Target) + return; + + constexpr std::string_view DocumentText = R"(--- | + define void @0() { + ret void + } + + define void @named() { + ret void + } + + define void @1() { + ret void + } +... +--- +name: '' +body: | + bb.0 (%ir-block.0): +... +--- +name: named +body: | + bb.0 (%ir-block.0): +... +--- +name: '' +body: | + bb.0 (%ir-block.0): +... +)"; + auto Parsed = rcc::mir::parseMIRDocument(*Target, "anonymous.mir", std::string(DocumentText)); + const rcc::MachineFunction *First = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(0) : nullptr; + const rcc::MachineFunction *Named = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(1) : nullptr; + const rcc::MachineFunction *Second = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(2) : nullptr; + Context.expect(Parsed.succeeded() && First != nullptr && First->name().empty() && + Named != nullptr && Named->name() == "named" && Second != nullptr && + Second->name().empty(), + "MIR must match empty names to embedded anonymous functions in document order"); + if (!Parsed.succeeded()) { + printDiagnostics(Parsed); + return; + } + + auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target); + Context.expect(Printed.has_value(), "anonymous MIR functions must remain printable"); + if (!Printed) + return; + const std::size_t FirstAnonymous = Printed->find("name: ''"); + const std::size_t SecondAnonymous = + FirstAnonymous == std::string::npos + ? std::string::npos + : Printed->find("name: ''", FirstAnonymous + std::string_view("name: ''").size()); + Context.expect(FirstAnonymous != std::string::npos && SecondAnonymous != std::string::npos && + Printed->find("%ir-block.0") != std::string::npos, + "anonymous function names and unnamed IR block slots must print canonically"); + + constexpr std::string_view NoIRText = R"(... +--- +name: '' +body: | + bb.0: +... +)"; + auto NoIR = rcc::mir::parseMIRDocument(*Target, "anonymous-no-ir.mir", std::string(NoIRText)); + Context.expect(NoIR.succeeded() && NoIR.document()->unit().functionAt(0) != nullptr && + NoIR.document()->unit().functionAt(0)->name().empty(), + "MIR without embedded IR must accept an anonymous dummy function"); + if (!NoIR.succeeded()) + printDiagnostics(NoIR); +} + } // namespace int main() { @@ -950,5 +1034,6 @@ int main() { testAtomicFailure(Context); testLLVMPropertySemantics(Context); testBodyDiagnosticLocation(Context); + testAnonymousFunctionsAndIRBlocks(Context); return Context.result(); } diff --git a/backend/unittests/Support/TextDiagnosticTest.cpp b/backend/unittests/Support/TextDiagnosticTest.cpp index cd28027..71d02b2 100644 --- a/backend/unittests/Support/TextDiagnosticTest.cpp +++ b/backend/unittests/Support/TextDiagnosticTest.cpp @@ -34,7 +34,7 @@ int main() { .Range = Range, }; const std::string Formatted = rcc::formatTextDiagnostic(Diagnostic, Source); - if (Formatted != "input.mir:2:3: error[yaml]: invalid scalar\nsecond line\n ^~~~~~") + if (Formatted != "error: input.mir:2:3: invalid scalar\nsecond line\n ^~~~~~") return fail("Text diagnostics must have deterministic source rendering"); if (rcc::getDiagnosticSeverityName(rcc::DiagnosticSeverity::Warning) != "warning") diff --git a/backend/unittests/Support/YAMLTest.cpp b/backend/unittests/Support/YAMLTest.cpp index 445159d..74f7bf2 100644 --- a/backend/unittests/Support/YAMLTest.cpp +++ b/backend/unittests/Support/YAMLTest.cpp @@ -21,6 +21,7 @@ const rcc::yaml::Node *mappingValue(const rcc::yaml::Stream &Stream, const rcc:: int testMIRShape() { const std::string Input = + "...\n" "--- |\n" " define void @f() {\n" " entry:\n" diff --git a/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp index 236f4e9..9c95d75 100644 --- a/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp +++ b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp @@ -36,7 +36,7 @@ namespace { return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "test MIR does not contain the expected COPY"); } - Source.insert(Position, " %3:gpr(i64) = G_CONSTANT 99\n"); + Source.insert(Position, " %3:gprb(i64) = G_CONSTANT 99\n"); return Source; } diff --git a/backend/unittests/Tooling/PipelineDriverTest.cpp b/backend/unittests/Tooling/PipelineDriverTest.cpp index 4a223b6..b9180ce 100644 --- a/backend/unittests/Tooling/PipelineDriverTest.cpp +++ b/backend/unittests/Tooling/PipelineDriverTest.cpp @@ -214,6 +214,21 @@ int main() { if (rcc::tooling::runRV64GCPipeline(MixedInput->Document, *Target, MixedPipeline)) return tooling_test::fail("named passes must not be mixed with the default pipeline marker"); + rcc::tooling::BackendInvocation NoPassInvocation = Invocation; + NoPassInvocation.PassPipeline = {"none"}; + auto NoPassInput = rcc::tooling::parseAndCanonicalizeMIR( + "no-pass.mir", *Source, *Target, NoPassInvocation); + if (!NoPassInput) + return tooling_test::fail(NoPassInput.error().message()); + const std::string NoPassExpected = NoPassInput->Text; + auto NoPassRun = rcc::tooling::runBackend( + std::move(*NoPassInput), *Target, NoPassInvocation, + rcc::tooling::BackendOutputKind::FinalMIR); + if (!NoPassRun || !NoPassRun->Pipeline.ExecutedPasses.empty() || + NoPassRun->text() != NoPassExpected) { + return tooling_test::fail("the LLVM-compatible none pass must preserve canonical MIR"); + } + if (!SelectedMIR) return tooling_test::fail("selected MIR stage was not captured"); auto LaterInput = |
