From 6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Tue, 11 Aug 2026 17:37:42 +0800 Subject: [MIR] Accept fixed-baseline LLVM syntax --- backend/unittests/MIR/LLVMConstantSyntaxTest.cpp | 28 ++++++- backend/unittests/MIR/MILexerTest.cpp | 2 +- backend/unittests/MIR/MIParserTest.cpp | 100 +++++++++++++++++++++++ backend/unittests/MIR/MIRDocumentTest.cpp | 87 +++++++++++++++++++- 4 files changed, 214 insertions(+), 3 deletions(-) (limited to 'backend/unittests/MIR') 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::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::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 NegativeOne{std::numeric_limits::max()}; + constexpr std::array NarrowNegative{767}; + constexpr std::array NarrowMinimum{8}; + constexpr std::array 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 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(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 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() + : nullptr; + const auto *NarrowNegative = + Instructions.size() > 1 + ? Instructions[1]->operandAt(1)->getIf() + : nullptr; + const auto *NarrowMinimum = + Instructions.size() > 2 + ? Instructions[2]->operandAt(1)->getIf() + : nullptr; + const auto *WidePositive = + Instructions.size() > 3 + ? Instructions[3]->operandAt(1)->getIf() + : nullptr; + Context.expect(Parsed.has_value() && MinusOne != nullptr && + MinusOne->Value.words()[0] == std::numeric_limits::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(); + 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(); } -- cgit v1.2.3