aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/MIR
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-11 17:37:42 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-11 17:37:42 +0800
commit6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0 (patch)
treef363c502f0e2b9cda2f12928049275a433db6ad4 /backend/unittests/MIR
parentadcf8c9b1bd1b47e00bf859693112cea1f7e8c9f (diff)
downloadrcc-project-6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0.tar.gz
rcc-project-6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0.tar.xz
[MIR] Accept fixed-baseline LLVM syntax
Diffstat (limited to 'backend/unittests/MIR')
-rw-r--r--backend/unittests/MIR/LLVMConstantSyntaxTest.cpp28
-rw-r--r--backend/unittests/MIR/MILexerTest.cpp2
-rw-r--r--backend/unittests/MIR/MIParserTest.cpp100
-rw-r--r--backend/unittests/MIR/MIRDocumentTest.cpp87
4 files changed, 214 insertions, 3 deletions
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();
}