aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests')
-rw-r--r--backend/unittests/CodeGen/CompilationUnitTest.cpp26
-rw-r--r--backend/unittests/CodeGen/MachineDebugInfoTest.cpp35
-rw-r--r--backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp6
-rw-r--r--backend/unittests/CodeGen/MachineValueTypesTest.cpp35
-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
-rw-r--r--backend/unittests/Support/TextDiagnosticTest.cpp2
-rw-r--r--backend/unittests/Support/YAMLTest.cpp1
-rw-r--r--backend/unittests/Tooling/MIRReducerFuzzerTest.cpp2
-rw-r--r--backend/unittests/Tooling/PipelineDriverTest.cpp15
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 =