aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests')
-rw-r--r--backend/unittests/CodeGen/MachineConstantPoolTest.cpp4
-rw-r--r--backend/unittests/CodeGen/MachineFrameInfoTest.cpp3
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp5
-rw-r--r--backend/unittests/CodeGen/MachineIRBuilderTest.cpp3
-rw-r--r--backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp12
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp3
-rw-r--r--backend/unittests/MIR/CMakeLists.txt24
-rw-r--r--backend/unittests/MIR/EmbeddedIRTest.cpp14
-rw-r--r--backend/unittests/MIR/LLVMConstantSyntaxTest.cpp497
-rw-r--r--backend/unittests/MIR/MIParserTest.cpp62
-rw-r--r--backend/unittests/MIR/MIRDocumentTest.cpp953
11 files changed, 1566 insertions, 14 deletions
diff --git a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
index 7343fdb..08b69e2 100644
--- a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
+++ b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
@@ -128,8 +128,8 @@ struct PoolTargetConstant final {
[[nodiscard]] rcc::MachineConstantPoolEntryDesc
genericDescription(rcc::ConstantPoolMergeKind Merge) {
rcc::MachineConstantPoolEntryDesc Description;
- Description.Value =
- rcc::GenericConstantData{"i32", {std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}}};
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(32, 0x04030201U);
+ Description.Value = std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
Description.Size = 4;
Description.Align = *rcc::Alignment::create(4);
Description.Merge = Merge;
diff --git a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
index c67339d..78987c0 100644
--- a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
+++ b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
@@ -109,7 +109,8 @@ void testInvalidDescriptions(TestContext &Context) {
"fixed-size stack object must reject a missing size");
auto ZeroSize = fixedObject(0, 1);
- Context.expect(!Frame.createObject(ZeroSize), "fixed-size stack object must reject zero size");
+ Context.expect(Frame.createObject(ZeroSize).has_value(),
+ "fixed-size stack objects must preserve LLVM MIR zero-sized objects");
rcc::StackObjectDesc InvalidVariable;
InvalidVariable.Kind = rcc::StackObjectKind::DynamicAlloca;
diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
index 9c8a447..9d8ffd2 100644
--- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
+++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
@@ -335,8 +335,9 @@ struct SourceFixture final {
return false;
rcc::MachineConstantPoolEntryDesc HistoricalConstant;
+ auto HistoricalInteger = rcc::IntegerImmediate::fromUnsigned(32, 0x01010101U);
HistoricalConstant.Value =
- rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*HistoricalInteger)));
HistoricalConstant.Size = 4;
HistoricalConstant.Align = *rcc::Alignment::create(4);
auto HistoricalConstantIndex = Function.constantPool().addEntry(std::move(HistoricalConstant));
@@ -367,7 +368,7 @@ struct SourceFixture final {
"source jump table history must contain a tombstone");
rcc::MachineJumpTableDesc JumpTable;
- JumpTable.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase;
+ JumpTable.Encoding = rcc::JumpTableEntryEncoding::GPRelative32BlockAddress;
JumpTable.EntrySize = 4;
JumpTable.Align = *rcc::Alignment::create(4);
JumpTable.Targets = {Fixture.Exit, Fixture.Entry, Fixture.Exit};
diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
index 9f5cf5e..00ffffc 100644
--- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
+++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
@@ -426,8 +426,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
"builder must reject unknown constant-pool and jump-table indices before allocation");
rcc::MachineConstantPoolEntryDesc ConstantDescription;
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(64, 0x2a2a2a2a2a2a2a2aULL);
ConstantDescription.Value =
- rcc::GenericConstantData{"i64", std::vector<std::byte>(8, std::byte{0x2a})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
ConstantDescription.Size = 8;
ConstantDescription.Align = *rcc::Alignment::create(8);
auto ConstantIndex = Function->constantPool().addEntry(std::move(ConstantDescription));
diff --git a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
index 0273801..106a2a6 100644
--- a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
+++ b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
@@ -51,7 +51,7 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) {
[[nodiscard]] rcc::MachineJumpTableDesc tableDescription(rcc::MachineBasicBlock &First,
rcc::MachineBasicBlock &Second) {
rcc::MachineJumpTableDesc Description;
- Description.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase;
+ Description.Encoding = rcc::JumpTableEntryEncoding::GPRelative32BlockAddress;
Description.EntrySize = 4;
Description.Align = *rcc::Alignment::create(4);
Description.Targets = {&First, &Second, &Second};
@@ -90,14 +90,15 @@ void testTableLifecycle(TestContext &Context) {
Function->jumpTableInfo().table(*First) == nullptr,
"erasing a jump table must leave a tombstone");
auto CompressedDescription = tableDescription(*Entry, *Exit);
- CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
- CompressedDescription.EntrySize = 2;
+ CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::Custom32;
+ CompressedDescription.EntrySize = 4;
auto Compressed = Function->jumpTableInfo().createTable(std::move(CompressedDescription));
Context.expect(Compressed && Compressed->value() == 2,
"jump table tombstone indices must never be reused");
auto Updated = tableDescription(*Entry, *Exit);
- Updated.Encoding = rcc::JumpTableEntryEncoding::RelativeToEntry;
+ Updated.Encoding = rcc::JumpTableEntryEncoding::LabelDifference64;
+ Updated.EntrySize = 8;
Context.expect(Function->jumpTableInfo().updateTable(*Target, std::move(Updated)).has_value() &&
Function->jumpTableInfo().verify().has_value() &&
Function->jumpTableInfo().freeze().has_value() &&
@@ -145,7 +146,8 @@ void testInvalidTables(TestContext &Context) {
"generic jump table encoding must reject target encoding IDs");
auto OversizedCompressed = tableDescription(*Block, *Block);
- OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
+ OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::Custom32;
+ OversizedCompressed.EntrySize = 8;
OversizedCompressed.EntrySize = 8;
Context.expect(!Function->jumpTableInfo().createTable(std::move(OversizedCompressed)),
"compressed jump table must reject oversized entries");
diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp
index a803c08..ca265a7 100644
--- a/backend/unittests/CodeGen/MachineVerifierTest.cpp
+++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp
@@ -522,8 +522,9 @@ void testTableReferenceFailures(TestContext &Context) {
return;
rcc::MachineConstantPoolEntryDesc ConstantDescription;
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(32, 0x01010101U);
ConstantDescription.Value =
- rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
ConstantDescription.Size = 4;
ConstantDescription.Align = *rcc::Alignment::create(4);
auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription));
diff --git a/backend/unittests/MIR/CMakeLists.txt b/backend/unittests/MIR/CMakeLists.txt
index 1eb0965..6b41474 100644
--- a/backend/unittests/MIR/CMakeLists.txt
+++ b/backend/unittests/MIR/CMakeLists.txt
@@ -6,6 +6,15 @@ target_link_libraries(RCCBackendEmbeddedIRTests PRIVATE RCCBackend::MIR)
add_test(NAME RCCBackend.MIR.EmbeddedIR COMMAND RCCBackendEmbeddedIRTests)
+add_executable(RCCBackendLLVMConstantSyntaxTests LLVMConstantSyntaxTest.cpp)
+rcc_backend_configure_target(RCCBackendLLVMConstantSyntaxTests)
+target_link_libraries(RCCBackendLLVMConstantSyntaxTests PRIVATE RCCBackend::MIR)
+
+add_test(
+ NAME RCCBackend.MIR.LLVMConstantSyntax
+ COMMAND RCCBackendLLVMConstantSyntaxTests
+)
+
add_executable(RCCBackendMILexerTests MILexerTest.cpp)
rcc_backend_configure_target(RCCBackendMILexerTests)
target_link_libraries(RCCBackendMILexerTests PRIVATE RCCBackend::MIR)
@@ -23,13 +32,28 @@ target_link_libraries(
add_test(NAME RCCBackend.MIR.MIParser COMMAND RCCBackendMIParserTests)
+add_executable(RCCBackendMIRDocumentTests MIRDocumentTest.cpp)
+rcc_backend_configure_target(RCCBackendMIRDocumentTests)
+target_link_libraries(
+ RCCBackendMIRDocumentTests
+ PRIVATE
+ RCCBackend::MIR
+ RCCBackend::TestTarget
+)
+
+add_test(NAME RCCBackend.MIR.MIRDocument COMMAND RCCBackendMIRDocumentTests)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntaxTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexerTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParserTest.cpp"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntaxTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexerTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParserTest.cpp"
)
diff --git a/backend/unittests/MIR/EmbeddedIRTest.cpp b/backend/unittests/MIR/EmbeddedIRTest.cpp
index 58c6162..bbac841 100644
--- a/backend/unittests/MIR/EmbeddedIRTest.cpp
+++ b/backend/unittests/MIR/EmbeddedIRTest.cpp
@@ -17,6 +17,7 @@ int testReferenceIndex() {
const std::string Input = "; @commented = global i8 0\n"
"target datalayout = \"e-m:e-p:64:64\"\n"
"target triple = \"riscv64-unknown-linux-gnu\"\n"
+ "%pair = type { i32, i32 }\n"
"@ordinary = global i32 0\n"
"@\"space\\20global\" = external global i8\n"
"declare void @declared()\n"
@@ -25,12 +26,12 @@ int testReferenceIndex() {
") {\n"
"entry:\n"
" %slot = alloca i32, align 4\n"
- " %not_an_alloca = add i32 1, 2\n"
+ " %not_an_alloca = add i32 %argument, 2\n"
" br label %\"exit\\20block\"\n"
"\"exit\\20block\":\n"
" ret void\n"
"}\n"
- "!0 = !{}\n"
+ "!0 = !DILocation(line: 1, scope: !1)\n"
"!llvm.module.flags = !{!0}\n";
rcc::mir::EmbeddedIRIndexResult Result = rcc::mir::indexEmbeddedIR("embedded.ll", Input);
@@ -54,6 +55,9 @@ int testReferenceIndex() {
if (Function->allocas().size() != 1 || Function->alloca("slot") == nullptr ||
Function->alloca("not_an_alloca") != nullptr)
return fail("Embedded IR indexing must distinguish named allocas from other local values");
+ if (Function->values().size() != 3 || Function->value("argument") == nullptr ||
+ Function->value("slot") == nullptr || Function->value("not_an_alloca") == nullptr)
+ return fail("Embedded IR indexing must retain arguments and instruction results");
if (!Index->hasGlobalValue("ordinary") || !Index->hasGlobalValue("space global") ||
!Index->hasGlobalValue("declared") || Index->hasGlobalValue("commented"))
@@ -61,6 +65,12 @@ int testReferenceIndex() {
if (Index->metadata("0") == nullptr || Index->metadata("llvm.module.flags") == nullptr ||
Index->metadataDefinitions().size() != 2)
return fail("Embedded IR indexing must retain numeric and named metadata definitions");
+ if (Index->metadata("0")->DynamicType != "DILocation" ||
+ Index->metadata("llvm.module.flags")->DynamicType != "MDTuple")
+ return fail("Embedded IR indexing must classify metadata dynamic types");
+ if (Index->typeDefinitions().size() != 1 || Index->typeDefinition("pair") == nullptr ||
+ Index->typeDefinition("pair")->Definition != "{ i32, i32 }")
+ return fail("Embedded IR indexing must retain named type definitions");
return 0;
}
diff --git a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp
new file mode 100644
index 0000000..be5a67e
--- /dev/null
+++ b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace {
+
+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;
+};
+
+constexpr rcc::mir::LLVMConstantSyntaxOptions Options{
+ .DefaultPointerSizeInBits = 64,
+ .DataLayout = {},
+ .NamedTypes = {},
+};
+
+void expectRoundTrip(TestContext &Context, std::string_view Text, std::uint64_t ExpectedSize) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, Options);
+ Context.expect(Parsed.has_value(), "valid LLVM constant must parse");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ auto Size = rcc::mir::llvmConstantStorageSize(Parsed->Type, Options);
+ Context.expect(Size.has_value() && *Size == ExpectedSize,
+ "LLVM constant storage size must match its structured type");
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "structured LLVM constant must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, Options);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "LLVM constant parse-print-parse must preserve the value tree");
+ if (!Reparsing)
+ std::cerr << *Printed << ": " << Reparsing.error().message() << '\n';
+}
+
+void expectLayoutRoundTrip(TestContext &Context, std::string_view Text,
+ rcc::mir::LLVMConstantSyntaxOptions LayoutOptions,
+ std::uint64_t ExpectedSize, std::uint64_t ExpectedPreferredAlignment) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, LayoutOptions);
+ Context.expect(Parsed.has_value(), "DataLayout-aware LLVM constant must parse");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ auto Size = rcc::mir::llvmConstantStorageSize(Parsed->Type, LayoutOptions);
+ auto Alignment = rcc::mir::llvmConstantPreferredAlignment(Parsed->Type, LayoutOptions);
+ Context.expect(Size.has_value() && *Size == ExpectedSize,
+ "DataLayout must determine the exact allocation size");
+ if (!Size || *Size != ExpectedSize) {
+ std::cerr << Text << ": expected size " << ExpectedSize << ", got "
+ << (Size ? std::to_string(*Size) : Size.error().message()) << '\n';
+ }
+ Context.expect(Alignment.has_value() && Alignment->bytes() == ExpectedPreferredAlignment,
+ "DataLayout must determine the exact preferred alignment");
+ if (!Alignment || Alignment->bytes() != ExpectedPreferredAlignment) {
+ std::cerr << Text << ": expected preferred alignment " << ExpectedPreferredAlignment << ", got "
+ << (Alignment ? std::to_string(Alignment->bytes()) : Alignment.error().message())
+ << '\n';
+ }
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "DataLayout-aware LLVM constant must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, LayoutOptions);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "DataLayout-aware named constants must round-trip structurally");
+}
+
+void testScalarConstants(TestContext &Context) {
+ expectRoundTrip(Context, "i1 true", 1);
+ expectRoundTrip(Context, "i65 -1", 9);
+ expectRoundTrip(Context, "half 0xH3C00", 2);
+ expectRoundTrip(Context, "bfloat 0xR3F80", 2);
+ expectRoundTrip(Context, "float 1.250000e+00", 4);
+ expectRoundTrip(Context, "double 0x3FF0000000000000", 8);
+ expectRoundTrip(Context, "x86_fp80 f0x4000C000000000000000", 10);
+ expectRoundTrip(Context, "fp128 f0x3FFF0000000000000000000000000000", 16);
+ expectRoundTrip(Context, "ptr null", 8);
+ expectRoundTrip(Context, "i32 undef", 4);
+ expectRoundTrip(Context, "i32 poison", 4);
+ expectRoundTrip(Context, "i32 zeroinitializer", 4);
+}
+
+void expectIntegerWords(TestContext &Context, std::string_view Text,
+ std::span<const std::uint64_t> ExpectedWords) {
+ 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");
+ if (!Parsed)
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+}
+
+void expectFloatingWords(TestContext &Context, std::string_view Text,
+ std::span<const std::uint64_t> ExpectedWords) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, Options);
+ Context.expect(Parsed.has_value() && Parsed->FloatingValue.has_value() &&
+ std::ranges::equal(Parsed->FloatingValue->words(), ExpectedWords),
+ "LLVM floating special literal must preserve its exact bit pattern");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ if (!Parsed->FloatingValue ||
+ !std::ranges::equal(Parsed->FloatingValue->words(), ExpectedWords)) {
+ std::cerr << Text << ": expected words";
+ for (const std::uint64_t Word : ExpectedWords)
+ std::cerr << " 0x" << std::hex << Word;
+ std::cerr << ", got";
+ if (Parsed->FloatingValue) {
+ for (const std::uint64_t Word : Parsed->FloatingValue->words())
+ std::cerr << " 0x" << std::hex << Word;
+ }
+ std::cerr << std::dec << '\n';
+ }
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "LLVM floating special literal must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, Options);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "printed LLVM floating special literal must round-trip");
+}
+
+void testExplicitHexIntegerConstants(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 2> AllOnes65{std::numeric_limits<std::uint64_t>::max(), 1};
+ constexpr std::array<std::uint64_t, 2> SignBit65{0, 1};
+ constexpr std::array<std::uint64_t, 1> UnsignedFF{0xff};
+ constexpr std::array<std::uint64_t, 1> SignedFF{0xffff};
+ constexpr std::array<std::uint64_t, 1> SignedFF8{0xff};
+
+ expectIntegerWords(Context, "i65 u0x1FFFFFFFFFFFFFFFF", AllOnes65);
+ expectIntegerWords(Context, "i65 s0x1FFFFFFFFFFFFFFFF", AllOnes65);
+ expectIntegerWords(Context, "i65 s0x10000000000000000", SignBit65);
+ expectIntegerWords(Context, "i65 s0x01", AllOnes65);
+ expectIntegerWords(Context, "i16 u0xFF", UnsignedFF);
+ expectIntegerWords(Context, "i16 s0xFF", SignedFF);
+ expectIntegerWords(Context, "i8 s0x1FF", SignedFF8);
+
+ expectRoundTrip(Context, "i65 s0x01", 9);
+ Context.expect(!rcc::mir::parseLLVMConstant("i32 u0x", Options) &&
+ !rcc::mir::parseLLVMConstant("i32 u0X1", Options),
+ "explicit LLVM hexadecimal integers must use the fixed spelling");
+}
+
+void testSpecialFloatingLiterals(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 1> HalfInfinity{0x7c00};
+ constexpr std::array<std::uint64_t, 1> NegativeHalfInfinity{0xfc00};
+ constexpr std::array<std::uint64_t, 1> HalfQuietNaN{0x7e00};
+ constexpr std::array<std::uint64_t, 1> NegativeHalfQuietNaN{0xfe00};
+ constexpr std::array<std::uint64_t, 1> HalfSignalingNaN{0x7d00};
+ constexpr std::array<std::uint64_t, 1> HalfPayloadNaN{0x7fef};
+ constexpr std::array<std::uint64_t, 1> FloatSignalingNaN{0x7f800001};
+ constexpr std::array<std::uint64_t, 2> X87Infinity{0x8000000000000000, 0x7fff};
+ constexpr std::array<std::uint64_t, 2> X87QuietNaN{0xc000000000000000, 0x7fff};
+ constexpr std::array<std::uint64_t, 2> QuadOne{0, 0x3fff000000000000};
+ constexpr std::array<std::uint64_t, 2> QuadInfinity{0, 0x7fff000000000000};
+ constexpr std::array<std::uint64_t, 2> QuadQuietNaN{0, 0x7fff800000000000};
+ constexpr std::array<std::uint64_t, 2> PPCOne{0x3ff0000000000000, 0};
+ constexpr std::array<std::uint64_t, 2> PPCInfinity{0x7ff0000000000000, 0};
+ constexpr std::array<std::uint64_t, 2> PPCQuietNaN{0x7ff8000000000000, 0};
+
+ expectFloatingWords(Context, "half +inf", HalfInfinity);
+ expectFloatingWords(Context, "half -inf", NegativeHalfInfinity);
+ expectFloatingWords(Context, "half +qnan", HalfQuietNaN);
+ expectFloatingWords(Context, "half -qnan", NegativeHalfQuietNaN);
+ expectFloatingWords(Context, "half +snan(0x100)", HalfSignalingNaN);
+ expectFloatingWords(Context, "half +nan(0x123456789ABCDEF)", HalfPayloadNaN);
+ expectFloatingWords(Context, "float +snan(0x400001)", FloatSignalingNaN);
+ expectFloatingWords(Context, "x86_fp80 +inf", X87Infinity);
+ expectFloatingWords(Context, "x86_fp80 +qnan", X87QuietNaN);
+ expectFloatingWords(Context, "fp128 +inf", QuadInfinity);
+ expectFloatingWords(Context, "fp128 +qnan", QuadQuietNaN);
+ expectFloatingWords(Context, "fp128 0xL00000000000000003FFF000000000000", QuadOne);
+ expectFloatingWords(Context, "ppc_fp128 +inf", PPCInfinity);
+ expectFloatingWords(Context, "ppc_fp128 +qnan", PPCQuietNaN);
+ expectFloatingWords(Context, "ppc_fp128 0xM3FF00000000000000000000000000000", PPCOne);
+
+ for (const std::string_view Text : {
+ std::string_view{"float nan(0x)"},
+ std::string_view{"float snan(12)"},
+ std::string_view{"float qnan(0x1)"},
+ std::string_view{"float inf(0x1)"},
+ std::string_view{"float inf"},
+ std::string_view{"float +nan"},
+ std::string_view{"float +snan"},
+ std::string_view{"float +qnan(0x1)"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "malformed LLVM floating special literal must be rejected");
+ }
+}
+
+void testExactFloatingConversion(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 1> PositiveZero{0};
+ constexpr std::array<std::uint64_t, 1> NegativeFloatZero{0x80000000};
+ constexpr std::array<std::uint64_t, 1> HalfOne{0x3c00};
+ constexpr std::array<std::uint64_t, 1> HalfTwoUlps{0x3c02};
+ constexpr std::array<std::uint64_t, 1> HalfSmallestSubnormal{0x0001};
+ constexpr std::array<std::uint64_t, 1> BFloatOne{0x3f80};
+ constexpr std::array<std::uint64_t, 1> BFloatTwoUlps{0x3f82};
+ constexpr std::array<std::uint64_t, 1> FloatOne{0x3f800000};
+ constexpr std::array<std::uint64_t, 1> FloatTwoUlps{0x3f800002};
+ constexpr std::array<std::uint64_t, 1> FloatHexPower{0x2f800000};
+ constexpr std::array<std::uint64_t, 1> FloatSignalingNaN{0x7f800001};
+ constexpr std::array<std::uint64_t, 1> FloatQuietNaN{0x7fc00000};
+ constexpr std::array<std::uint64_t, 1> LargestDouble{0x7fefffffffffffff};
+ constexpr std::array<std::uint64_t, 2> X87One{0x8000000000000000, 0x3fff};
+ constexpr std::array<std::uint64_t, 2> QuadOne{0, 0x3fff000000000000};
+ constexpr std::array<std::uint64_t, 2> PPCOne{0x3ff0000000000000, 0};
+
+ expectFloatingWords(Context, "float +0.0", PositiveZero);
+ expectFloatingWords(Context, "float -0.0", NegativeFloatZero);
+ expectFloatingWords(Context, "half 1.00048828125", HalfOne);
+ expectFloatingWords(Context, "half 1.00146484375", HalfTwoUlps);
+ expectFloatingWords(Context, "half 0.0000000298023223876953126", HalfSmallestSubnormal);
+ expectFloatingWords(Context, "bfloat 1.00390625", BFloatOne);
+ expectFloatingWords(Context, "bfloat 1.01171875", BFloatTwoUlps);
+ expectFloatingWords(Context, "float 1.000000059604644775390625", FloatOne);
+ expectFloatingWords(Context, "float 1.000000178813934326171875", FloatTwoUlps);
+ expectFloatingWords(Context, "float 0x1.0p-32", FloatHexPower);
+ expectFloatingWords(Context, "double 1.79769313486231570815e+308", LargestDouble);
+ expectFloatingWords(Context, "x86_fp80 1.0", X87One);
+ expectFloatingWords(Context, "x86_fp80 0x1.0p0", X87One);
+ expectFloatingWords(Context, "fp128 1.0", QuadOne);
+ expectFloatingWords(Context, "fp128 0x1.0p0", QuadOne);
+ expectFloatingWords(Context, "ppc_fp128 1.0", PPCOne);
+ expectFloatingWords(Context, "ppc_fp128 0x1.0p0", PPCOne);
+ expectFloatingWords(Context, "float 0x3FF0000000000000", FloatOne);
+ expectFloatingWords(Context, "float 0x7FF0000020000000", FloatSignalingNaN);
+ expectFloatingWords(Context, "float 0x7FF8000000000000", FloatQuietNaN);
+ expectFloatingWords(Context, "float f0x3F800000", FloatOne);
+
+ for (const std::string_view Text : {
+ std::string_view{"half 0.0000000298023223876953125"},
+ std::string_view{"float 0x3FF0000000000001"},
+ std::string_view{"float 0x7FF8000000000001"},
+ std::string_view{"float f0x3F8000"},
+ std::string_view{"float f0X3F800000"},
+ std::string_view{"float 0X1.0p0"},
+ std::string_view{"float 0x1p0"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "noncanonical LLVM floating syntax must be rejected");
+ }
+}
+
+void testAggregateConstants(TestContext &Context) {
+ expectRoundTrip(Context, "[4 x i8] c\"ab\\00\\FF\"", 4);
+ expectRoundTrip(Context, "[3 x i16] [i16 1, i16 2, i16 3]", 6);
+ expectRoundTrip(Context, "<4 x i32> <i32 0, i32 1, i32 2, i32 3>", 16);
+ expectRoundTrip(Context, "<4 x float> <float 1.0, float -1.0, float 2.0, float -2.0>", 16);
+ expectRoundTrip(Context, "{ i32, ptr } { i32 7, ptr @global }", 16);
+ expectRoundTrip(Context, "<{ i8, i32 }> <{ i8 1, i32 2 }>", 5);
+ expectRoundTrip(Context, "<8 x i16> splat (i16 9)", 16);
+}
+
+void testAddressConstants(TestContext &Context) {
+ expectRoundTrip(Context, "ptr @global", 8);
+ expectRoundTrip(Context, "ptr @\"quoted name\"", 8);
+ expectRoundTrip(Context, "ptr blockaddress(@function, %entry)", 8);
+ expectRoundTrip(Context, "ptr dso_local_equivalent @function", 8);
+ expectRoundTrip(Context, "ptr no_cfi @function", 8);
+ expectRoundTrip(Context, "i64 ptrtoint (ptr @global to i64)", 8);
+ expectRoundTrip(Context, "ptr bitcast (ptr @global to ptr)", 8);
+ expectRoundTrip(Context, "i64 add nuw nsw (i64 1, i64 2)", 8);
+ expectRoundTrip(Context, "ptr getelementptr inbounds nusw nuw (i8, ptr @global, i64 4)", 8);
+ expectRoundTrip(Context,
+ "<2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, "
+ "<2 x i32> <i32 3, i32 4>, <2 x i32> <i32 0, i32 3>)",
+ 8);
+}
+
+void testConstantExpressionVerification(TestContext &Context) {
+ expectRoundTrip(Context, "i8 trunc (i16 257 to i8)", 1);
+ expectRoundTrip(Context, "i32 bitcast (float 1.0 to i32)", 4);
+ expectRoundTrip(Context, "ptr addrspacecast (ptr addrspace(1) @global to ptr)", 8);
+ expectRoundTrip(Context, "ptr inttoptr (i64 0 to ptr)", 8);
+ expectRoundTrip(Context, "i64 ptrtoaddr (ptr @global to i64)", 8);
+ expectRoundTrip(Context, "i64 ptrtoint (ptr @global to i64)", 8);
+ expectRoundTrip(Context,
+ "<2 x i32> insertelement (<2 x i32> <i32 1, i32 2>, "
+ "i32 3, i64 0)",
+ 8);
+ expectRoundTrip(Context, "i32 extractelement (<2 x i32> <i32 1, i32 2>, i64 0)", 4);
+ expectRoundTrip(Context, "ptr ptrauth (ptr @function, i32 1, i64 0, ptr null, ptr null)", 8);
+
+ for (const std::string_view Text : {
+ std::string_view{"i16 trunc (i8 1 to i16)"},
+ std::string_view{"i64 bitcast (float 1.0 to i64)"},
+ std::string_view{"ptr bitcast (ptr addrspace(1) @global to ptr)"},
+ std::string_view{"ptr addrspacecast (ptr @global to ptr)"},
+ std::string_view{"ptr inttoptr (float 1.0 to ptr)"},
+ std::string_view{"i64 ptrtoint (i64 0 to i64)"},
+ std::string_view{"i32 add nuw (i32 1, i64 2)"},
+ std::string_view{"ptr getelementptr (i8, ptr @global, float 1.0)"},
+ std::string_view{"<2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, "
+ "<2 x i32> <i32 3, i32 4>, <2 x i64> <i64 0, i64 3>)"},
+ std::string_view{"ptr ptrauth (ptr @function, i64 1)"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "constant expression with incompatible typed operands must be rejected");
+ }
+}
+
+void testDataLayoutAndNamedTypes(TestContext &Context) {
+ static constexpr std::array Definitions{
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "pair", .Definition = "{ i8, i64 }", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{.Name = "word", .Definition = "i32", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "node", .Definition = "{ i32, %node* }", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "loop", .Definition = "{ %loop }", .NumericName = false},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions LayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-p1:32:16:64-i64:64:128-i128:128-v96:128:256-a:0:64",
+ .NamedTypes = Definitions,
+ };
+
+ expectLayoutRoundTrip(Context, "i65 -1", LayoutOptions, 16, 16);
+ expectLayoutRoundTrip(Context, "[3 x i24] [i24 1, i24 2, i24 3]", LayoutOptions, 12, 4);
+ expectLayoutRoundTrip(Context, "<3 x i32> <i32 1, i32 2, i32 3>", LayoutOptions, 16, 32);
+ expectLayoutRoundTrip(Context, "ptr addrspace(1) null", LayoutOptions, 4, 8);
+ expectLayoutRoundTrip(Context, "%pair { i8 1, i64 2 }", LayoutOptions, 16, 8);
+ expectLayoutRoundTrip(Context, "%word 7", LayoutOptions, 4, 4);
+ expectLayoutRoundTrip(Context, "%node zeroinitializer", LayoutOptions, 16, 8);
+
+ Context.expect(!rcc::mir::parseLLVMType("%loop", LayoutOptions),
+ "directly recursive named aggregate types must be rejected as unsized");
+ auto NodeType = rcc::mir::parseLLVMType("%node", LayoutOptions);
+ Context.expect(NodeType.has_value(),
+ "named recursion through a pointer must remain sized and legal");
+}
+
+void testAddressSpacePointerWidths(TestContext &Context) {
+ constexpr rcc::mir::LLVMConstantSyntaxOptions HeterogeneousLayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-p1:32:16:64",
+ .NamedTypes = {},
+ };
+ auto DefaultPointer = rcc::mir::llvmPointerSizeInBits(0, HeterogeneousLayoutOptions);
+ auto AddressSpaceOnePointer = rcc::mir::llvmPointerSizeInBits(1, HeterogeneousLayoutOptions);
+ Context.expect(DefaultPointer.has_value() && *DefaultPointer == 64 &&
+ AddressSpaceOnePointer.has_value() && *AddressSpaceOnePointer == 32,
+ "LLVM data layout must provide pointer width by address space");
+
+ constexpr rcc::mir::LLVMConstantSyntaxOptions LegacyOptions{
+ .DefaultPointerSizeInBits = 64,
+ .DataLayout = {},
+ .NamedTypes = {},
+ };
+ auto LegacyPointer = rcc::mir::llvmPointerSizeInBits(7, LegacyOptions);
+ Context.expect(LegacyPointer.has_value() && *LegacyPointer == 64,
+ "legacy pointer-width fallback must apply without an explicit data layout");
+}
+
+void testDataLayoutBoundaryRules(TestContext &Context) {
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions IntegerAlignmentOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-i24:128:128",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions HistoricalAggregateOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-a0:64:128",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions ZeroAggregateOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-a:0:64",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions NonBytePointerOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:33:8:8:32",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions PointerFlagsOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-pue1(storage):33:8:8:32-po2(allones):64:64",
+ .NamedTypes = {},
+ };
+
+ expectLayoutRoundTrip(Context, "i20 1", IntegerAlignmentOptions, 16, 16);
+ expectLayoutRoundTrip(Context, "{ i8, i32 } { i8 1, i32 2 }", HistoricalAggregateOptions, 8, 16);
+ expectLayoutRoundTrip(Context, "{ i8, i32 } { i8 1, i32 2 }", ZeroAggregateOptions, 8, 8);
+ expectLayoutRoundTrip(Context, "<{ i8, i32 }> <{ i8 1, i32 2 }>", ZeroAggregateOptions, 5, 8);
+ expectLayoutRoundTrip(Context, "ptr null", NonBytePointerOptions, 5, 1);
+ expectLayoutRoundTrip(Context, "ptr addrspace(1) null", PointerFlagsOptions, 5, 1);
+
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions InvalidIndexSizeOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:33:8:8:34",
+ .NamedTypes = {},
+ };
+ auto Pointer = rcc::mir::parseLLVMType("ptr", InvalidIndexSizeOptions);
+ Context.expect(Pointer.has_value() &&
+ !rcc::mir::llvmConstantStorageSize(*Pointer, InvalidIndexSizeOptions),
+ "LLVM pointer index size larger than its representation must be rejected");
+
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions NonLayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-m:e-n8:16:32-S128-Fi64-P1-G2-A3-ni:1:2-ve-s-p:64:64",
+ .NamedTypes = {},
+ };
+ expectLayoutRoundTrip(Context, "ptr null", NonLayoutOptions, 8, 8);
+
+ const auto ExpectInvalidDataLayout = [&Context](std::string_view DataLayout) {
+ const rcc::mir::LLVMConstantSyntaxOptions InvalidOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = DataLayout,
+ .NamedTypes = {},
+ };
+ auto Type = rcc::mir::parseLLVMType("ptr", InvalidOptions);
+ Context.expect(Type.has_value() && !rcc::mir::llvmConstantStorageSize(*Type, InvalidOptions),
+ "malformed non-layout LLVM data-layout component must be rejected");
+ };
+ for (const std::string_view DataLayout : {
+ std::string_view{"ee-p:64:64"},
+ std::string_view{"m:q-p:64:64"},
+ std::string_view{"n64:0-p:64:64"},
+ std::string_view{"S-p:64:64"},
+ std::string_view{"Fx64-p:64:64"},
+ std::string_view{"P16777216-p:64:64"},
+ std::string_view{"ni:-p:64:64"},
+ std::string_view{"ni:0-p:64:64"},
+ std::string_view{"pe:64:64"},
+ std::string_view{"pu:64:64"},
+ std::string_view{"pzo1:64:64"},
+ std::string_view{"p1(P):64:64"},
+ std::string_view{"p1(foo):64:64-p2(foo):64:64"},
+ std::string_view{"p16777216:64:64"},
+ }) {
+ ExpectInvalidDataLayout(DataLayout);
+ }
+}
+
+void testInvalidConstants(TestContext &Context) {
+ for (const std::string_view Text : {
+ std::string_view{""},
+ std::string_view{"opaque 0"},
+ std::string_view{"i32 null"},
+ std::string_view{"ptr 42"},
+ std::string_view{"[2 x i8] [i8 1]"},
+ std::string_view{"<2 x i32> <i32 1, i64 2>"},
+ std::string_view{"i64 ptrtoint (ptr @global to i32)"},
+ std::string_view{"ptr getelementptr (i8, i64 0)"},
+ std::string_view{"i32 1 trailing"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "invalid LLVM constant must be rejected");
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testScalarConstants(Context);
+ testExplicitHexIntegerConstants(Context);
+ testSpecialFloatingLiterals(Context);
+ testExactFloatingConversion(Context);
+ testAggregateConstants(Context);
+ testAddressConstants(Context);
+ testConstantExpressionVerification(Context);
+ testDataLayoutAndNamedTypes(Context);
+ testAddressSpacePointerWidths(Context);
+ testDataLayoutBoundaryRules(Context);
+ testInvalidConstants(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp
index d037bef..552de25 100644
--- a/backend/unittests/MIR/MIParserTest.cpp
+++ b/backend/unittests/MIR/MIParserTest.cpp
@@ -345,6 +345,66 @@ bb.1."exit block" (landing-pad, bbsections Cold):
"complete MIR body parse-print-parse must be deterministic");
}
+void testAddressSpacePointerWidthResolver(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 3);
+ Context.expect(Target.has_value(), "pointer-width parser target must be valid");
+ if (!Target || !Function)
+ return;
+
+ rcc::mir::MIParserContext ParserContext;
+ ParserContext.PointerSizeInBits = [](std::uint32_t AddressSpace) -> rcc::Expected<std::uint32_t> {
+ if (AddressSpace == 1)
+ return 32;
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument,
+ "test data layout has no requested address space");
+ };
+ rcc::mir::MIParserOptions ParserOptions;
+ ParserOptions.VerifyAfterParse = false;
+ rcc::mir::MIParser Parser(*Function, *Target, "pointer-width.mir",
+ "bb.0:\n %0:_(p1) = TEST_COPY $g0\n", ParserContext, ParserOptions);
+ auto Parsed = Parser.parse();
+ const auto Registers = Function->registerInfo().virtualRegisters();
+ auto ExpectedType = rcc::LLT::makePointer(1, 32);
+ Context.expect(Parsed.has_value() && ExpectedType.has_value() && Registers.size() == 1 &&
+ Registers.front().Type == *ExpectedType,
+ "MI parser must use the address-space-specific pointer-width resolver");
+ if (!Parsed) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parser.diagnostics())
+ std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n';
+ std::cerr << "error: " << Parsed.error().message() << '\n';
+ }
+}
+
+void testUnserializableLowLevelType(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 4);
+ Context.expect(Target.has_value(), "unserializable-type target must be valid");
+ if (!Target || !Function)
+ return;
+
+ rcc::mir::MIParserOptions ParserOptions;
+ ParserOptions.VerifyAfterParse = false;
+ rcc::mir::MIParser Parser(*Function, *Target, "unserializable-type.mir",
+ "bb.0:\n %0:gpr64(f64) = TEST_COPY $g0\n", {}, ParserOptions);
+ auto Parsed = Parser.parse();
+ auto Register = rcc::Register::makeVirtual(0);
+ auto UnsupportedType = rcc::LLT::makeFloat(rcc::FloatSemantics::Float8E5M2);
+ Context.expect(Parsed.has_value() && Register.has_value() && UnsupportedType.has_value(),
+ "unserializable-type test setup must parse a typed virtual register");
+ if (!Parsed || !Register || !UnsupportedType)
+ return;
+ auto SetType = Function->registerInfo().setType(*Register, *UnsupportedType);
+ Context.expect(SetType.has_value(), "test setup must assign an extended floating low-level type");
+ if (!SetType)
+ return;
+
+ auto Printed = rcc::mir::printMachineFunctionBody(*Function, *Target);
+ Context.expect(!Printed && Printed.error().message().find("cannot spell this floating") !=
+ std::string_view::npos,
+ "printer must reject extended low-level types absent from fixed LLVM MIR grammar");
+}
+
void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body,
std::string_view Message) {
auto Function = createFunction(Context, 10);
@@ -391,6 +451,8 @@ void testDiagnostics(TestContext &Context) {
int main() {
TestContext Context;
testTwoPassBodyParsing(Context);
+ testAddressSpacePointerWidthResolver(Context);
+ testUnserializableLowLevelType(Context);
testDiagnostics(Context);
return Context.result();
}
diff --git a/backend/unittests/MIR/MIRDocumentTest.cpp b/backend/unittests/MIR/MIRDocumentTest.cpp
new file mode 100644
index 0000000..7ea82c1
--- /dev/null
+++ b/backend/unittests/MIR/MIRDocumentTest.cpp
@@ -0,0 +1,953 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MIR/MIRDocument.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineCompilationStatus.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetFunctionState.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+constexpr std::uint32_t CopyOpcode = 1;
+constexpr std::uint32_t BranchOpcode = 2;
+constexpr std::uint32_t LoadOpcode = 3;
+constexpr std::uint32_t GlobalOpcode = 4;
+
+class DocumentTargetState final {
+public:
+ DocumentTargetState(std::uint32_t FrameBase, std::string Mode)
+ : FrameBase(FrameBase), Mode(std::move(Mode)) {}
+
+ [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept {
+ return rcc::TargetFunctionStateTypeID::fromValue(0x5243434d49525354ULL);
+ }
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 1;
+ }
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ if (FrameBase == 0 || Mode.empty())
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "document target state is incomplete");
+ return {};
+ }
+ [[nodiscard]] rcc::Expected<DocumentTargetState>
+ clone(const rcc::MachineFunctionCloneContext &) const {
+ return DocumentTargetState(FrameBase, Mode);
+ }
+ [[nodiscard]] std::string print() const {
+ return Mode + ":" + std::to_string(FrameBase);
+ }
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Bytes(4);
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Bytes[Index] = static_cast<std::byte>((FrameBase >> (Index * 8U)) & 0xffU);
+ return Bytes;
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetState> decode(std::uint32_t Version,
+ std::span<const std::byte> Bytes) {
+ if (Version != schemaVersion() || Bytes.size() != 4)
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target state encoding");
+ std::uint32_t Value = 0;
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Value |= std::to_integer<std::uint32_t>(Bytes[Index]) << (Index * 8U);
+ return DocumentTargetState(Value, "test");
+ }
+
+ [[nodiscard]] std::uint32_t frameBase() const noexcept {
+ return FrameBase;
+ }
+ [[nodiscard]] std::string_view mode() const noexcept {
+ return Mode;
+ }
+
+ friend bool operator==(const DocumentTargetState &Left,
+ const DocumentTargetState &Right) = default;
+
+private:
+ std::uint32_t FrameBase = 0;
+ std::string Mode;
+};
+
+class DocumentTargetConstant final {
+public:
+ explicit DocumentTargetConstant(std::uint64_t Value) : Value(Value) {}
+
+ [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept {
+ return rcc::TargetConstantTypeID::fromValue(0x5243434d49524354ULL);
+ }
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 1;
+ }
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ return {};
+ }
+ [[nodiscard]] rcc::Expected<DocumentTargetConstant> clone() const {
+ return DocumentTargetConstant(Value);
+ }
+ [[nodiscard]] std::uint64_t stableHash() const noexcept {
+ return Value;
+ }
+ [[nodiscard]] std::string print() const {
+ return "test-constant(" + std::to_string(Value) + ")";
+ }
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Bytes(8);
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Bytes[Index] = static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU);
+ return Bytes;
+ }
+ [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const {
+ auto Bytes = encode();
+ if (!Bytes)
+ return std::unexpected(Bytes.error());
+ return rcc::TargetConstantEmission{.Bytes = std::move(*Bytes), .Fixups = {}};
+ }
+ [[nodiscard]] rcc::Alignment requiredAlignment() const {
+ return *rcc::Alignment::create(8);
+ }
+ [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const {
+ return std::nullopt;
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetConstant> parse(std::string_view Text) {
+ constexpr std::string_view Prefix = "test-constant(";
+ if (!Text.starts_with(Prefix) || !Text.ends_with(')'))
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "invalid document target constant");
+ Text.remove_prefix(Prefix.size());
+ Text.remove_suffix(1);
+ std::uint64_t Parsed = 0;
+ const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Parsed, 10);
+ if (Error != std::errc{} || End != Text.data() + Text.size())
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target constant value");
+ return DocumentTargetConstant(Parsed);
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetConstant>
+ decode(std::uint32_t Version, std::span<const std::byte> Bytes) {
+ if (Version != schemaVersion() || Bytes.size() != 8)
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target constant encoding");
+ std::uint64_t Parsed = 0;
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Parsed |= std::to_integer<std::uint64_t>(Bytes[Index]) << (Index * 8U);
+ return DocumentTargetConstant(Parsed);
+ }
+
+ [[nodiscard]] std::uint64_t value() const noexcept {
+ return Value;
+ }
+
+ friend bool operator==(const DocumentTargetConstant &Left,
+ const DocumentTargetConstant &Right) = default;
+
+private:
+ std::uint64_t Value = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetFunctionState>
+parseTargetState(const rcc::MIRTargetNode &Node) {
+ const rcc::MIRTargetNode *FrameBase = Node.mappingValue("frame-base");
+ const rcc::MIRTargetNode *Mode = Node.mappingValue("mode");
+ if (FrameBase == nullptr || Mode == nullptr ||
+ FrameBase->Kind != rcc::MIRTargetNodeKind::Scalar ||
+ Mode->Kind != rcc::MIRTargetNodeKind::Scalar) {
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "document target state requires frame-base and mode");
+ }
+ std::uint32_t Value = 0;
+ const auto [End, Error] = std::from_chars(
+ FrameBase->Scalar.data(), FrameBase->Scalar.data() + FrameBase->Scalar.size(), Value, 10);
+ if (Error != std::errc{} || End != FrameBase->Scalar.data() + FrameBase->Scalar.size())
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "document target frame-base is invalid");
+ return rcc::TargetFunctionState::create(DocumentTargetState(Value, Mode->Scalar));
+}
+
+[[nodiscard]] rcc::Expected<rcc::MIRTargetNode>
+printTargetState(const rcc::TargetFunctionState &State) {
+ const auto *Value = State.getIf<DocumentTargetState>();
+ if (Value == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument, "wrong document target state type");
+ std::vector<rcc::MIRTargetMappingEntry> Entries;
+ Entries.push_back({"frame-base", rcc::MIRTargetNode::scalar(std::to_string(Value->frameBase()))});
+ Entries.push_back({"mode", rcc::MIRTargetNode::scalar(std::string(Value->mode()))});
+ return rcc::MIRTargetNode::mapping(std::move(Entries));
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetConstant> parseTargetConstant(std::string_view Text) {
+ return rcc::TargetConstant::parse<DocumentTargetConstant>(Text);
+}
+
+[[nodiscard]] rcc::Expected<std::string> printTargetConstant(const rcc::TargetConstant &Constant) {
+ if (Constant.getIf<DocumentTargetConstant>() == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument,
+ "wrong document target constant type");
+ return Constant.print();
+}
+
+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::OperandDescriptor Operand;
+ Operand.Kinds = {rcc::MachineOperand::Kind::Register};
+ Operand.RegisterRole = Role;
+ Operand.RegClass = rcc::test::TestRegisterClass::GPR64;
+ return Operand;
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ rcc::InstructionDescriptor Copy;
+ Copy.InstructionOpcode = rcc::Opcode::fromValue(CopyOpcode);
+ Copy.Name = "TEST_COPY";
+ Copy.Operands = {
+ registerOperand(rcc::RegisterOperandRole::Def),
+ registerOperand(rcc::RegisterOperandRole::Use),
+ };
+ Copy.NumExplicitDefs = 1;
+ Copy.Flags.set(rcc::InstructionFlag::ProducesCode);
+
+ rcc::InstructionDescriptor Branch;
+ Branch.InstructionOpcode = rcc::Opcode::fromValue(BranchOpcode);
+ Branch.Name = "TEST_BRANCH";
+ rcc::OperandDescriptor Block;
+ Block.Kinds = {rcc::MachineOperand::Kind::BasicBlock};
+ Branch.Operands.push_back(Block);
+ Branch.Flags = {
+ rcc::InstructionFlag::ProducesCode,
+ rcc::InstructionFlag::Branch,
+ rcc::InstructionFlag::Terminator,
+ rcc::InstructionFlag::Barrier,
+ };
+
+ rcc::InstructionDescriptor Load;
+ Load.InstructionOpcode = rcc::Opcode::fromValue(LoadOpcode);
+ Load.Name = "TEST_LOAD";
+ Load.Operands = {
+ registerOperand(rcc::RegisterOperandRole::Def),
+ registerOperand(rcc::RegisterOperandRole::Use),
+ };
+ Load.NumExplicitDefs = 1;
+ Load.Flags = {
+ rcc::InstructionFlag::ProducesCode,
+ rcc::InstructionFlag::MayLoad,
+ };
+ Load.RequiresMemoryOperands = true;
+
+ rcc::InstructionDescriptor Global;
+ Global.InstructionOpcode = rcc::Opcode::fromValue(GlobalOpcode);
+ Global.Name = "TEST_GLOBAL";
+ rcc::OperandDescriptor GlobalOperand;
+ GlobalOperand.Kinds = {
+ rcc::MachineOperand::Kind::GlobalSymbol,
+ rcc::MachineOperand::Kind::ExternalSymbol,
+ };
+ Global.Operands.push_back(std::move(GlobalOperand));
+ Global.Flags.set(rcc::InstructionFlag::ProducesCode);
+
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Copy));
+ Descriptors.push_back(std::move(Branch));
+ Descriptors.push_back(std::move(Load));
+ Descriptors.push_back(std::move(Global));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ auto Registers = rcc::test::createTestTargetRegisterInfo();
+ if (!Registers)
+ return std::unexpected(Registers.error());
+ rcc::TargetMIRTextInfoDesc MIRText = rcc::test::createTestTargetMIRTextInfoDescription();
+ MIRText.FunctionStateType = DocumentTargetState::stableTypeID();
+ MIRText.ParseFunctionState = parseTargetState;
+ MIRText.PrintFunctionState = printTargetState;
+ MIRText.ParseTargetConstant = parseTargetConstant;
+ MIRText.PrintTargetConstant = printTargetConstant;
+ return rcc::TargetDescription::create(std::move(*Instructions), std::move(*Registers),
+ std::move(MIRText));
+}
+
+constexpr std::string_view ValidDocument = R"(--- |
+ target datalayout = "e-p:64:64"
+ target triple = "test64-unknown-linux-gnu"
+
+ %pair = type { i8, i64 }
+ @ordinary = external global i8
+
+ define void @documented() {
+ entry:
+ %slot = alloca i64, align 8
+ ret void
+ }
+
+ !0 = !DILocalVariable(name: "slot", scope: !1)
+ !1 = distinct !DISubprogram(name: "documented")
+ !2 = !DILocation(line: 4, column: 2, scope: !1)
+---
+name: documented
+alignment: 16
+exposesReturnsTwice: true
+tracksRegLiveness: true
+noPhis: true
+isSSA: true
+noVRegs: false
+registers:
+ - { id: 7, class: gpr64, preferred-register: '$g1', flags: [ test-vreg-one ] }
+liveins:
+ - { reg: '$g0', virtual-reg: '%7' }
+calleeSavedRegisters: [ '$g1' ]
+frameInfo:
+ isFrameAddressTaken: true
+ stackSize: 32
+ maxAlignment: 16
+ adjustsStack: true
+ hasCalls: true
+ framePointerPolicy: all
+ stackProtector: '%stack.4'
+ maxCallFrameSize: 24
+ localFrameSize: 8
+ savePoint:
+ - { point: '%bb.0', registers: [ '$g1' ] }
+fixedStack:
+ - { id: 3, type: spill-slot, offset: -16, size: 8, alignment: 8 }
+stack:
+ - { id: 4, name: slot, size: 8, alignment: 8, local-offset: 0,
+ debug-info-variable: '!0',
+ debug-info-expression: '!DIExpression(DW_OP_LLVM_arg, 0, 18446744073709551615)',
+ debug-info-location: '!DILocation(line: 4, column: 2, scope: !1, isImplicitCode: true)' }
+entry_values:
+ - { entry-value-register: '$g0', debug-info-variable: '!0',
+ debug-info-expression: '!DIExpression(DW_OP_stack_value)', debug-info-location: '!2' }
+constants:
+ - id: 5
+ value: 'i64 18446744073709551615'
+ alignment: 8
+ - id: 9
+ value: 'test-constant(1234)'
+ alignment: 8
+ isTargetSpecific: true
+ - id: 11
+ value: '%pair { i8 1, i64 2 }'
+machineFunctionInfo: { frame-base: 64, mode: test }
+jumpTable:
+ kind: block-address
+ entries:
+ - { id: 6, blocks: [ '%bb.0' ] }
+machineMetadataNodes:
+ - '!10 = distinct !{!0, !"machine", null}'
+body: |
+ bb.0.entry (%ir-block.entry):
+ %7:gpr64 = TEST_LOAD $g0 :: (load syncscope("agent") acquire 8 from %ir.slot, align 8)
+ TEST_GLOBAL @ordinary
+ TEST_BRANCH %bb.0.entry
+)";
+
+constexpr std::string_view HeterogeneousPointerDocument = R"(--- |
+ target datalayout = "e-p:64:64-p1:32:32"
+ target triple = "test64-unknown-linux-gnu"
+
+ define void @pointer_layout() {
+ ret void
+ }
+---
+name: pointer_layout
+constants:
+ - id: 0
+ value: 'ptr addrspace(1) null'
+body: |
+ bb.0:
+ %0:gpr64(p1) = TEST_COPY $g0
+)";
+
+void printDiagnostics(const rcc::mir::MIRDocumentParseResult &Parsed) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parsed.diagnostics())
+ std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n';
+}
+
+void testDocumentTransaction(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "MIR document target must be valid");
+ if (!Target)
+ return;
+
+ auto Parsed = rcc::mir::parseMIRDocument(*Target, "document.mir", std::string(ValidDocument));
+ Context.expect(Parsed.succeeded(), "complete MIR document must parse successfully");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+
+ const rcc::mir::MIRDocument *Document = Parsed.document();
+ Context.expect(
+ Document != nullptr && Document->embeddedIR() != nullptr &&
+ Document->unit().functionCount() == 1 && Document->functionDocumentInfo().size() == 1 &&
+ Document->syncScopes().size() == 1 && Document->syncScopes().front().Name == "agent",
+ "successful parsing must publish the complete document atomically");
+ if (Document == nullptr)
+ return;
+ const rcc::MachineFunction *Function = Document->unit().function("documented");
+ Context.expect(Function != nullptr, "parsed function must be queryable by name");
+ if (Function == nullptr)
+ return;
+
+ Context.expect(Function->registerInfo().virtualRegisters().size() == 1 &&
+ Function->registerInfo().liveIns().size() == 1 &&
+ Function->records().hasCalleeSavedRegisters(),
+ "register tables must publish into the common MachineIR model");
+ Context.expect(Function->frameInfo().liveObjectCount() == 2 &&
+ Function->frameInfo().state().StackSize == 32 &&
+ Function->frameInfo().state().SavePoints.size() == 1,
+ "frame records and stack objects must publish together");
+ const rcc::StackObjectDesc *StackDescription =
+ Function->frameInfo().object(rcc::FrameIndex::fromValue(1))->description();
+ Context.expect(StackDescription != nullptr && StackDescription->DebugInfo.has_value() &&
+ StackDescription->DebugInfo->Expression.has_value() &&
+ StackDescription->DebugInfo->Expression->expression() != nullptr &&
+ StackDescription->DebugInfo->Expression->expression()->elements().size() ==
+ 3 &&
+ StackDescription->DebugInfo->Location.has_value() &&
+ StackDescription->DebugInfo->Location->inlineLocation() != nullptr &&
+ Function->records().entryValues().size() == 1,
+ "stack and entry-value debug metadata must retain structured values");
+ const auto *TargetState = Function->getTargetInfo<DocumentTargetState>();
+ const rcc::MachineConstantPoolEntryDesc *TargetConstantDescription =
+ Function->constantPool().entry(rcc::ConstantPoolIndex::fromValue(1))->description();
+ const auto *TargetConstantValue =
+ TargetConstantDescription == nullptr
+ ? nullptr
+ : std::get_if<rcc::TargetConstant>(&TargetConstantDescription->Value);
+ Context.expect(Function->constantPool().liveEntryCount() == 3 &&
+ Function->jumpTableInfo().liveTableCount() == 1 &&
+ Function->metadata().nodes().size() == 1 && TargetState != nullptr &&
+ TargetState->frameBase() == 64 && TargetConstantValue != nullptr &&
+ TargetConstantValue->getIf<DocumentTargetConstant>() != nullptr &&
+ TargetConstantValue->getIf<DocumentTargetConstant>()->value() == 1234,
+ "constant, target-state, jump-table, and metadata tables must publish together");
+ Context.expect(Function->blockCount() == 1 && Function->allInstructions().size() == 3,
+ "the body must use the YAML predeclared text slots");
+
+ auto Printed = rcc::mir::printMIRDocument(*Document, *Target);
+ Context.expect(Printed.has_value(), "complete MIR document printing must succeed");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ return;
+ }
+ 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()) {
+ std::cerr << *Printed;
+ printDiagnostics(RoundTrip);
+ return;
+ }
+ auto PrintedAgain = rcc::mir::printMIRDocument(*RoundTrip.document(), *Target);
+ Context.expect(PrintedAgain.has_value() && *PrintedAgain == *Printed,
+ "complete MIR document printing must be deterministic");
+
+ auto Readable = rcc::mir::printMIRDocument(
+ *Document, *Target,
+ rcc::mir::MIRDocumentPrinterOptions{.Mode = rcc::mir::MIPrintMode::Readable,
+ .EmitEmbeddedIR = true,
+ .DataLayout = {},
+ .DefaultPointerSizeInBits = 0});
+ Context.expect(Readable.has_value(), "readable MIR document printing must succeed");
+ if (!Readable)
+ return;
+ auto ReadableRoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "readable.mir", std::string(*Readable));
+ Context.expect(ReadableRoundTrip.succeeded(), "readable MIR output must parse successfully");
+ if (!ReadableRoundTrip.succeeded())
+ printDiagnostics(ReadableRoundTrip);
+
+ auto WithoutEmbeddedIR = rcc::mir::printMIRDocument(
+ *Document, *Target,
+ rcc::mir::MIRDocumentPrinterOptions{.Mode = rcc::mir::MIPrintMode::Complete,
+ .EmitEmbeddedIR = false,
+ .DataLayout = {},
+ .DefaultPointerSizeInBits = 0});
+ Context.expect(!WithoutEmbeddedIR && WithoutEmbeddedIR.error().message().find(
+ "requires embedded LLVM IR") != std::string_view::npos,
+ "printer must reject output that omits required embedded LLVM IR");
+}
+
+void testHeterogeneousPointerLayout(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "heterogeneous-layout target must be valid");
+ if (!Target)
+ return;
+
+ rcc::mir::MIRDocumentParserOptions ParserOptions;
+ ParserOptions.MachineInstructions.VerifyAfterParse = false;
+ ParserOptions.VerifyMachineFunctions = false;
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "heterogeneous-layout.mir",
+ std::string(HeterogeneousPointerDocument), ParserOptions);
+ Context.expect(Parsed.succeeded(), "heterogeneous pointer-layout MIR must parse successfully");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("pointer_layout");
+ auto PointerType = rcc::LLT::makePointer(1, 32);
+ const rcc::MachineConstantPoolEntry *Constant =
+ Function == nullptr ? nullptr
+ : Function->constantPool().entry(rcc::ConstantPoolIndex::fromValue(0));
+ const rcc::MachineConstantPoolEntryDesc *ConstantDescription =
+ Constant == nullptr ? nullptr : Constant->description();
+ Context.expect(Function != nullptr && PointerType.has_value() &&
+ Function->registerInfo().virtualRegisters().size() == 1 &&
+ Function->registerInfo().virtualRegisters().front().Type == *PointerType &&
+ ConstantDescription != nullptr && ConstantDescription->Size == 4,
+ "embedded data layout must determine p1 register and generic-constant widths");
+
+ rcc::mir::MIRDocumentPrinterOptions PrinterOptions;
+ PrinterOptions.EmitEmbeddedIR = false;
+ auto WithoutLayout = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(!WithoutLayout && WithoutLayout.error().message().find("printer DataLayout") !=
+ std::string_view::npos,
+ "omitting embedded IR must reject layout-dependent MIR without DataLayout");
+
+ PrinterOptions.DataLayout = "e-p:64:64-p1:32:32";
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(Printed.has_value() && Printed->find("p1:32") == std::string_view::npos &&
+ Printed->find("p1)") != std::string_view::npos,
+ "matching explicit DataLayout must emit canonical LLVM p1 MIR syntax");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ return;
+ }
+
+ rcc::mir::MIRDocumentParserOptions ReparseOptions;
+ ReparseOptions.DataLayout = PrinterOptions.DataLayout;
+ ReparseOptions.MachineInstructions.VerifyAfterParse = false;
+ ReparseOptions.VerifyMachineFunctions = false;
+ auto RoundTrip = rcc::mir::parseMIRDocument(*Target, "heterogeneous-layout-round-trip.mir",
+ std::string(*Printed), ReparseOptions);
+ const rcc::MachineFunction *RoundTripFunction =
+ RoundTrip.succeeded() ? RoundTrip.document()->unit().function("pointer_layout") : nullptr;
+ Context.expect(RoundTrip.succeeded() && RoundTripFunction != nullptr &&
+ RoundTripFunction->registerInfo().virtualRegisters().size() == 1 &&
+ RoundTripFunction->registerInfo().virtualRegisters().front().Type ==
+ *PointerType,
+ "explicit DataLayout must preserve p1 type when embedded IR is omitted");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+
+ PrinterOptions.DataLayout = "e-p:64:64-p1:64:64";
+ auto Mismatched = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(!Mismatched &&
+ Mismatched.error().message().find("exactly match") != std::string_view::npos,
+ "mismatched output DataLayout must be rejected when embedded IR is omitted");
+}
+
+void expectReferenceFailure(TestContext &Context, const rcc::TargetDescription &Target,
+ std::string_view From, std::string_view To,
+ std::string_view ExpectedDiagnostic) {
+ std::string Invalid(ValidDocument);
+ const std::size_t Position = Invalid.find(From);
+ Context.expect(Position != std::string::npos,
+ "reference-validation test mutation must match the fixture");
+ if (Position == std::string::npos)
+ return;
+ Invalid.replace(Position, From.size(), To);
+ auto Parsed = rcc::mir::parseMIRDocument(Target, "reference.mir", std::move(Invalid));
+ const bool HasExpectedDiagnostic =
+ !Parsed.succeeded() && Parsed.document() == nullptr && Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find(ExpectedDiagnostic) != std::string::npos;
+ Context.expect(HasExpectedDiagnostic,
+ "invalid embedded IR references must fail with their semantic category");
+ if (!HasExpectedDiagnostic)
+ printDiagnostics(Parsed);
+}
+
+void testEmbeddedReferenceValidation(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "reference-validation target must be valid");
+ if (!Target)
+ return;
+
+ expectReferenceFailure(Context, *Target, "from %ir.slot", "from %ir.missing",
+ "undefined embedded LLVM IR value '%missing'");
+ expectReferenceFailure(Context, *Target, "TEST_GLOBAL @ordinary", "TEST_GLOBAL @missing",
+ "undefined embedded LLVM IR global 'missing'");
+ expectReferenceFailure(Context, *Target, "value: 'i64 18446744073709551615'",
+ "value: 'ptr @missing'",
+ "generic constant references an undefined embedded LLVM IR global");
+ expectReferenceFailure(Context, *Target, "value: 'i64 18446744073709551615'",
+ "value: 'ptr blockaddress(@documented, %missing)'",
+ "undefined embedded LLVM IR block 'missing'");
+ expectReferenceFailure(Context, *Target, "debug-info-location: '!2'", "debug-info-location: '!0'",
+ "debug location requires DILocation metadata");
+}
+
+void expectNoEmbeddedIRReferenceFailure(TestContext &Context, const rcc::TargetDescription &Target,
+ std::string_view DocumentText,
+ std::string_view ExpectedDiagnostic) {
+ auto Parsed =
+ rcc::mir::parseMIRDocument(Target, "no-embedded-reference.mir", std::string(DocumentText));
+ const bool Rejected =
+ !Parsed.succeeded() && Parsed.document() == nullptr && Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find(ExpectedDiagnostic) != std::string::npos;
+ Context.expect(Rejected, "IR-specific MIR references without embedded LLVM IR must be rejected");
+ if (!Rejected)
+ printDiagnostics(Parsed);
+}
+
+void testReferencesWithoutEmbeddedIR(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "no-embedded-reference target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view ExternalDocument = R"(---
+name: external_symbol
+body: |
+ bb.0:
+ TEST_GLOBAL &runtime_symbol
+)";
+ auto External =
+ rcc::mir::parseMIRDocument(*Target, "external-symbol.mir", std::string(ExternalDocument));
+ Context.expect(External.succeeded(),
+ "external symbols must remain usable without embedded LLVM IR");
+ if (!External.succeeded()) {
+ printDiagnostics(External);
+ return;
+ }
+ auto Printed = rcc::mir::printMIRDocument(*External.document(), *Target);
+ Context.expect(Printed.has_value(),
+ "external-symbol MIR without embedded IR must remain printable");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ } else {
+ auto RoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "external-round-trip.mir", std::move(*Printed));
+ Context.expect(RoundTrip.succeeded(),
+ "external-symbol MIR without embedded IR must remain reparseable");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+ }
+
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: global_reference
+body: |
+ bb.0:
+ TEST_GLOBAL @ordinary
+)",
+ "requires embedded LLVM IR to resolve global");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: ir_block_reference
+body: |
+ bb.0 (%ir-block.entry):
+)",
+ "machine basic block requires embedded LLVM IR");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: alloca_reference
+stack:
+ - { id: 0, name: slot, size: 8, alignment: 8 }
+body: |
+ bb.0:
+)",
+ "stack object requires embedded LLVM IR");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: ir_value_reference
+body: |
+ bb.0:
+ $g1 = TEST_LOAD $g0 :: (load 8 from %ir.slot, align 8)
+)",
+ "requires embedded LLVM IR to resolve IR value");
+ expectNoEmbeddedIRReferenceFailure(
+ Context, *Target, R"(---
+name: constant_reference
+constants:
+ - { id: 0, value: 'i64 ptrtoint (ptr @ordinary to i64)', alignment: 8 }
+body: |
+ bb.0:
+)",
+ "generic constant requires embedded LLVM IR to resolve global");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: block_address_reference
+constants:
+ - { id: 0, value: 'i64 ptrtoint (ptr blockaddress(@function, %entry) to i64)', alignment: 8 }
+body: |
+ bb.0:
+)",
+ "generic constant blockaddress requires embedded LLVM IR");
+}
+
+void testPrinterRejectsUnresolvedProgrammaticReferences(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "programmatic-reference target must be valid");
+ if (!Target)
+ return;
+
+ rcc::CompilationUnit Unit;
+ auto Function = Unit.createMachineFunction("programmatic_reference");
+ Context.expect(Function.has_value(), "programmatic document function creation must succeed");
+ if (!Function)
+ return;
+ auto Block = (*Function)->createBasicBlock();
+ auto Appended = Block ? (*Function)->appendBlock(**Block) : rcc::Expected<void>{};
+ Context.expect(Block && Appended.has_value(),
+ "programmatic document block creation must succeed");
+ if (!Block || !Appended)
+ return;
+
+ rcc::MachineIRBuilder Builder(**Function, *Target);
+ auto Insertion = Builder.setInsertPoint(**Block);
+ auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(GlobalOpcode));
+ auto Added = Pending ? Pending->addGlobalSymbol("ordinary") : rcc::Expected<void>{};
+ Context.expect(Insertion && Pending && Added.has_value(),
+ "programmatic global-reference instruction construction must succeed");
+ if (!Insertion || !Pending || !Added)
+ return;
+ auto Built = Pending->build();
+ Context.expect(Built.has_value(), "programmatic global-reference instruction must build");
+ if (!Built)
+ return;
+
+ rcc::mir::MIRDocument Document(std::move(Unit));
+ auto Printed = rcc::mir::printMIRDocument(Document, *Target);
+ const bool Rejected =
+ !Printed && Printed.error().message().find("requires embedded LLVM IR to resolve global") !=
+ std::string_view::npos;
+ Context.expect(Rejected,
+ "printer must reject programmatic IR-specific references without embedded IR");
+ if (!Rejected && !Printed)
+ std::cerr << "error: " << Printed.error().message() << '\n';
+}
+
+void testMetadataSlotPriorityAndCanonicalization(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "metadata-priority target must be valid");
+ if (!Target)
+ return;
+
+ std::string CollisionDocument(ValidDocument);
+ constexpr std::string_view Original = " - '!10 = distinct !{!0, !\"machine\", null}'";
+ constexpr std::string_view Replacement = " - '!0 = distinct !{!0, !\"machine\", null}'";
+ const std::size_t Position = CollisionDocument.find(Original);
+ Context.expect(Position != std::string::npos,
+ "metadata-priority test mutation must match the fixture");
+ if (Position == std::string::npos)
+ return;
+ CollisionDocument.replace(Position, Original.size(), Replacement);
+
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "metadata-priority.mir", std::move(CollisionDocument));
+ Context.expect(Parsed.succeeded(),
+ "machine metadata may reuse an embedded LLVM IR metadata slot");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("documented");
+ const auto Nodes = Function == nullptr ? std::span<const rcc::MachineMetadataNode>{}
+ : Function->metadata().nodes();
+ const rcc::MachineMetadataReference *Reference =
+ Nodes.size() == 1 && Nodes.front().Elements.size() == 3
+ ? Nodes.front().Elements.front().reference()
+ : nullptr;
+ Context.expect(Function != nullptr && Nodes.size() == 1 && Nodes.front().Slot == 0 &&
+ Reference != nullptr &&
+ *Reference == rcc::MachineMetadataReference::embeddedIR(0),
+ "bare metadata references must prefer embedded LLVM IR slots");
+
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target);
+ const bool Canonicalized =
+ Printed && Printed->find("'!3 = distinct !{!0, !\"machine\", null}'") != std::string::npos;
+ Context.expect(Canonicalized,
+ "printer must assign a noncolliding metadata slot for local metadata");
+ if (!Canonicalized)
+ return;
+ auto RoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "metadata-priority-round-trip.mir", std::move(*Printed));
+ Context.expect(RoundTrip.succeeded(),
+ "canonicalized metadata slots must survive a document round-trip");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+}
+
+void testFixedWidthDocumentFields(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "fixed-width field target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view Original = "maxCallFrameSize: 24";
+ std::string BoundaryDocument(ValidDocument);
+ const std::size_t BoundaryPosition = BoundaryDocument.find(Original);
+ Context.expect(BoundaryPosition != std::string::npos,
+ "fixed-width boundary test mutation must match the fixture");
+ if (BoundaryPosition == std::string::npos)
+ return;
+ BoundaryDocument.replace(BoundaryPosition, Original.size(), "maxCallFrameSize: 4294967295");
+ auto Boundary =
+ rcc::mir::parseMIRDocument(*Target, "frame-boundary.mir", std::move(BoundaryDocument));
+ Context.expect(Boundary.succeeded(),
+ "maximum 32-bit maxCallFrameSize must remain representable in MIR");
+ if (Boundary.succeeded()) {
+ auto Printed = rcc::mir::printMIRDocument(*Boundary.document(), *Target);
+ Context.expect(Printed && Printed->find("maxCallFrameSize: 4294967295") != std::string::npos,
+ "maximum 32-bit maxCallFrameSize must print without narrowing");
+ } else {
+ printDiagnostics(Boundary);
+ }
+
+ std::string OversizedDocument(ValidDocument);
+ const std::size_t OversizedPosition = OversizedDocument.find(Original);
+ if (OversizedPosition == std::string::npos)
+ return;
+ OversizedDocument.replace(OversizedPosition, Original.size(), "maxCallFrameSize: 4294967296");
+ auto Oversized =
+ rcc::mir::parseMIRDocument(*Target, "frame-oversized.mir", std::move(OversizedDocument));
+ const bool Rejected =
+ !Oversized.succeeded() && Oversized.document() == nullptr &&
+ Oversized.diagnostics().size() == 1 &&
+ Oversized.diagnostics().front().Message.find("maximum call-frame size") != std::string::npos;
+ Context.expect(Rejected,
+ "maxCallFrameSize values beyond the fixed 32-bit MIR range must be rejected");
+ if (!Rejected)
+ printDiagnostics(Oversized);
+}
+
+void testAtomicFailure(TestContext &Context) {
+ auto Target = createTarget();
+ if (!Target)
+ return;
+ std::string Invalid(ValidDocument);
+ Invalid.append("---\nname: second\nunknownField: true\n");
+ auto Parsed = rcc::mir::parseMIRDocument(*Target, "atomic.mir", std::move(Invalid));
+ Context.expect(!Parsed.succeeded() && Parsed.document() == nullptr &&
+ Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find("unknown machine function key") !=
+ std::string::npos,
+ "a later function failure must prevent publication of the entire document");
+}
+
+void testLLVMPropertySemantics(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "property-semantics target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view PropertyDocument = R"(---
+name: property_state
+failedISel: true
+failsVerification: true
+body: |
+ bb.0:
+)";
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "properties.mir", std::string(PropertyDocument));
+ Context.expect(Parsed.succeeded(), "LLVM MIR pipeline properties must parse");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("property_state");
+ Context.expect(Function != nullptr &&
+ Function->properties().has(rcc::MachineFunctionProperty::FailedISel) &&
+ Function->properties().has(rcc::MachineFunctionProperty::FailsVerification) &&
+ Function->compilationStatus().state() == rcc::MachineCompilationState::Success,
+ "failedISel and failsVerification must be pipeline properties, not run failures");
+
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target);
+ Context.expect(Printed.has_value() && Printed->find("failedISel: true") != std::string::npos &&
+ Printed->find("failsVerification: true") != std::string::npos,
+ "LLVM MIR pipeline properties must print deterministically");
+
+ constexpr std::string_view InvalidDocument = R"(---
+name: still_checked
+failsVerification: true
+noVRegs: true
+registers:
+ - { id: 1, class: gpr64 }
+body: |
+ bb.0:
+)";
+ auto Invalid =
+ 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;
+ Context.expect(Rejected, "failsVerification must not bypass RCC's mandatory input validation");
+ if (!Rejected)
+ printDiagnostics(Invalid);
+}
+
+void testBodyDiagnosticLocation(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "body-diagnostic target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view InvalidDocument = R"(---
+name: body_diagnostic
+body: |
+ bb.0:
+ UNKNOWN_OPCODE
+)";
+ const std::size_t ExpectedOffset = InvalidDocument.find("UNKNOWN_OPCODE");
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "body-diagnostic.mir", std::string(InvalidDocument));
+ const bool PointsAtToken = !Parsed.succeeded() && Parsed.document() == nullptr &&
+ Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Category == "mir-body" &&
+ Parsed.diagnostics().front().Range.Begin.Offset == ExpectedOffset;
+ Context.expect(PointsAtToken,
+ "body parser errors must retain the source position of the failing token");
+ if (!PointsAtToken)
+ printDiagnostics(Parsed);
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testDocumentTransaction(Context);
+ testHeterogeneousPointerLayout(Context);
+ testEmbeddedReferenceValidation(Context);
+ testReferencesWithoutEmbeddedIR(Context);
+ testPrinterRejectsUnresolvedProgrammaticReferences(Context);
+ testMetadataSlotPriorityAndCanonicalization(Context);
+ testFixedWidthDocumentFields(Context);
+ testAtomicFailure(Context);
+ testLLVMPropertySemantics(Context);
+ testBodyDiagnosticLocation(Context);
+ return Context.result();
+}