From fde3e045d4dcc5c404cb4216389e18b0e720b8e0 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Thu, 13 Aug 2026 12:55:12 +0800 Subject: [IR] Split LL parsing, assembly writing, and data layout --- backend/unittests/AsmParser/CMakeLists.txt | 15 + backend/unittests/AsmParser/LLParserTest.cpp | 533 +++++++++++++++++++++++ backend/unittests/CMakeLists.txt | 1 + backend/unittests/MIR/CMakeLists.txt | 11 - backend/unittests/MIR/LLVMConstantSyntaxTest.cpp | 515 ---------------------- 5 files changed, 549 insertions(+), 526 deletions(-) create mode 100644 backend/unittests/AsmParser/CMakeLists.txt create mode 100644 backend/unittests/AsmParser/LLParserTest.cpp delete mode 100644 backend/unittests/MIR/LLVMConstantSyntaxTest.cpp (limited to 'backend/unittests') diff --git a/backend/unittests/AsmParser/CMakeLists.txt b/backend/unittests/AsmParser/CMakeLists.txt new file mode 100644 index 0000000..00e7429 --- /dev/null +++ b/backend/unittests/AsmParser/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +add_executable(RCCBackendLLParserTests LLParserTest.cpp) +rcc_backend_configure_target(RCCBackendLLParserTests) +target_link_libraries( + RCCBackendLLParserTests + PRIVATE + RCCBackend::AsmParser + RCCBackend::IR +) + +add_test(NAME RCCBackend.AsmParser.LLParser COMMAND RCCBackendLLParserTests) + +rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/LLParserTest.cpp") +rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/LLParserTest.cpp") diff --git a/backend/unittests/AsmParser/LLParserTest.cpp b/backend/unittests/AsmParser/LLParserTest.cpp new file mode 100644 index 0000000..75312e4 --- /dev/null +++ b/backend/unittests/AsmParser/LLParserTest.cpp @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/AsmParser/LLParser.h" + +#include "rcc/IR/DataLayout.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +}; + +struct TestOptions final { + std::string_view DataLayout; + std::span NamedTypes; +}; + +[[nodiscard]] rcc::Expected parseConstant(std::string_view Text, + TestOptions Options) { + return rcc::parseLLVMConstant(Text, {.NamedTypes = Options.NamedTypes}); +} + +[[nodiscard]] rcc::Expected parseType(std::string_view Text, + TestOptions Options) { + return rcc::parseLLVMType(Text, {.NamedTypes = Options.NamedTypes}); +} + +[[nodiscard]] rcc::Expected allocationSize(const rcc::GenericConstantType &Type, + TestOptions Options) { + auto Layout = rcc::DataLayout::parse(Options.DataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + return Layout->getTypeAllocSize(Type); +} + +[[nodiscard]] rcc::Expected preferredAlignment(const rcc::GenericConstantType &Type, + TestOptions Options) { + auto Layout = rcc::DataLayout::parse(Options.DataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + return Layout->getPrefTypeAlign(Type); +} + +[[nodiscard]] rcc::Expected pointerSizeInBits(std::uint32_t AddressSpace, + TestOptions Options = {}) { + auto Layout = rcc::DataLayout::parse(Options.DataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + return Layout->getPointerSizeInBits(AddressSpace); +} + +constexpr TestOptions Options{ + .DataLayout = "e-p:64:64", + .NamedTypes = {}, +}; + +void expectRoundTrip(TestContext &Context, std::string_view Text, std::uint64_t ExpectedSize) { + auto Parsed = parseConstant(Text, Options); + Context.expect(Parsed.has_value(), "valid LLVM constant must parse"); + if (!Parsed) { + std::cerr << Text << ": " << Parsed.error().message() << '\n'; + return; + } + auto Size = allocationSize(Parsed->Type, Options); + Context.expect(Size.has_value() && *Size == ExpectedSize, + "LLVM constant storage size must match its structured type"); + auto Printed = Parsed->toLLVMString(); + Context.expect(Printed.has_value(), "structured LLVM constant must print"); + if (!Printed) + return; + auto Reparsing = parseConstant(*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, TestOptions LayoutOptions, + std::uint64_t ExpectedSize, std::uint64_t ExpectedPreferredAlignment) { + auto Parsed = parseConstant(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 = allocationSize(Parsed->Type, LayoutOptions); + auto Alignment = preferredAlignment(Parsed->Type, LayoutOptions); + Context.expect(Size.has_value() && *Size == ExpectedSize, + "DataLayout must determine the exact allocation size"); + Context.expect(Alignment.has_value() && *Alignment == ExpectedPreferredAlignment, + "DataLayout must determine the exact preferred alignment"); + auto Printed = Parsed->toLLVMString(); + Context.expect(Printed.has_value(), "DataLayout-aware LLVM constant must print"); + if (!Printed) + return; + auto Reparsing = parseConstant(*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", 12); + 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", 16); + 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); + + auto BFloat = parseConstant("bfloat 0xR3E80", Options); + auto PrintedBFloat = + BFloat ? BFloat->toLLVMString() : rcc::Expected(std::unexpected(BFloat.error())); + Context.expect(PrintedBFloat && *PrintedBFloat == "bfloat 2.500000e-01", + "LLVM-compatible floating printing must prefer an exact decimal spelling"); + + auto Pi = parseConstant("double f0x400921FB54442D18", Options); + auto PrintedPi = + Pi ? Pi->toLLVMString() : rcc::Expected(std::unexpected(Pi.error())); + Context.expect( + PrintedPi && *PrintedPi == "double f0x400921FB54442D18", + "floating printing must retain exact bits when six decimal digits do not round-trip"); +} + +void expectIntegerWords(TestContext &Context, std::string_view Text, + std::span ExpectedWords) { + auto Parsed = parseConstant(Text, Options); + Context.expect(Parsed.has_value() && Parsed->IntegerValue.has_value() && + std::ranges::equal(Parsed->IntegerValue->words(), ExpectedWords), + "LLVM integer literal must preserve its exact bit pattern"); + if (!Parsed) + std::cerr << Text << ": " << Parsed.error().message() << '\n'; +} + +void testDecimalIntegerTruncation(TestContext &Context) { + constexpr std::array NegativeOne{std::numeric_limits::max()}; + constexpr std::array NarrowNegative{767}; + constexpr std::array NarrowMinimum{8}; + constexpr std::array WidePositive{1}; + + expectIntegerWords(Context, "i64 -1", NegativeOne); + expectIntegerWords(Context, "i16 -64769", NarrowNegative); + expectIntegerWords(Context, "i4 -8", NarrowMinimum); + expectIntegerWords(Context, "i64 18446744073709551617", WidePositive); +} + +void expectFloatingWords(TestContext &Context, std::string_view Text, + std::span ExpectedWords) { + auto Parsed = parseConstant(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 = Parsed->toLLVMString(); + Context.expect(Printed.has_value(), "LLVM floating special literal must print"); + if (!Printed) + return; + auto Reparsing = parseConstant(*Printed, Options); + Context.expect(Reparsing.has_value() && *Reparsing == *Parsed, + "printed LLVM floating special literal must round-trip"); +} + +void testExplicitHexIntegerConstants(TestContext &Context) { + constexpr std::array AllOnes65{std::numeric_limits::max(), 1}; + constexpr std::array SignBit65{0, 1}; + constexpr std::array UnsignedFF{0xff}; + constexpr std::array SignedFF{0xffff}; + constexpr std::array 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", 12); + Context.expect(!parseConstant("i32 u0x", Options) && !parseConstant("i32 u0X1", Options), + "explicit LLVM hexadecimal integers must use the fixed spelling"); +} + +void testSpecialFloatingLiterals(TestContext &Context) { + constexpr std::array HalfInfinity{0x7c00}; + constexpr std::array NegativeHalfInfinity{0xfc00}; + constexpr std::array HalfQuietNaN{0x7e00}; + constexpr std::array NegativeHalfQuietNaN{0xfe00}; + constexpr std::array HalfSignalingNaN{0x7d00}; + constexpr std::array HalfPayloadNaN{0x7fef}; + constexpr std::array FloatSignalingNaN{0x7f800001}; + constexpr std::array X87Infinity{0x8000000000000000, 0x7fff}; + constexpr std::array X87QuietNaN{0xc000000000000000, 0x7fff}; + constexpr std::array QuadOne{0, 0x3fff000000000000}; + constexpr std::array QuadInfinity{0, 0x7fff000000000000}; + constexpr std::array QuadQuietNaN{0, 0x7fff800000000000}; + constexpr std::array PPCOne{0x3ff0000000000000, 0}; + constexpr std::array PPCInfinity{0x7ff0000000000000, 0}; + constexpr std::array 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(!parseConstant(Text, Options), + "malformed LLVM floating special literal must be rejected"); + } +} + +void testExactFloatingConversion(TestContext &Context) { + constexpr std::array PositiveZero{0}; + constexpr std::array NegativeFloatZero{0x80000000}; + constexpr std::array HalfOne{0x3c00}; + constexpr std::array HalfTwoUlps{0x3c02}; + constexpr std::array HalfSmallestSubnormal{0x0001}; + constexpr std::array BFloatOne{0x3f80}; + constexpr std::array BFloatTwoUlps{0x3f82}; + constexpr std::array FloatOne{0x3f800000}; + constexpr std::array FloatTwoUlps{0x3f800002}; + constexpr std::array FloatHexPower{0x2f800000}; + constexpr std::array FloatSignalingNaN{0x7f800001}; + constexpr std::array FloatQuietNaN{0x7fc00000}; + constexpr std::array LargestDouble{0x7fefffffffffffff}; + constexpr std::array X87One{0x8000000000000000, 0x3fff}; + constexpr std::array QuadOne{0, 0x3fff000000000000}; + constexpr std::array 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(!parseConstant(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> ", 16); + expectRoundTrip(Context, "<4 x float> ", 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> , " + "<2 x i32> , <2 x i32> )", + 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 3, i64 0)", + 8); + expectRoundTrip(Context, "i32 extractelement (<2 x i32> , 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> , " + "<2 x i32> , <2 x i64> )"}, + std::string_view{"ptr ptrauth (ptr @function, i64 1)"}, + }) { + Context.expect(!parseConstant(Text, Options), + "constant expression with incompatible typed operands must be rejected"); + } +} + +void testDataLayoutAndNamedTypes(TestContext &Context) { + static constexpr std::array Definitions{ + rcc::LLNamedTypeDefinition{.Name = "pair", .Definition = "{ i8, i64 }", .NumericName = false}, + rcc::LLNamedTypeDefinition{.Name = "word", .Definition = "i32", .NumericName = false}, + rcc::LLNamedTypeDefinition{ + .Name = "node", .Definition = "{ i32, %node* }", .NumericName = false}, + rcc::LLNamedTypeDefinition{.Name = "loop", .Definition = "{ %loop }", .NumericName = false}, + }; + static constexpr TestOptions LayoutOptions{ + .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> ", 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(!parseType("%loop", LayoutOptions), + "directly recursive named aggregate types must be rejected as unsized"); + auto NodeType = parseType("%node", LayoutOptions); + Context.expect(NodeType.has_value(), + "named recursion through a pointer must remain sized and legal"); +} + +void testAddressSpacePointerWidths(TestContext &Context) { + constexpr TestOptions HeterogeneousLayoutOptions{ + .DataLayout = "e-p:64:64-p1:32:16:64", + .NamedTypes = {}, + }; + auto DefaultPointer = pointerSizeInBits(0, HeterogeneousLayoutOptions); + auto AddressSpaceOnePointer = pointerSizeInBits(1, HeterogeneousLayoutOptions); + Context.expect(DefaultPointer.has_value() && *DefaultPointer == 64 && + AddressSpaceOnePointer.has_value() && *AddressSpaceOnePointer == 32, + "LLVM data layout must provide pointer width by address space"); + + auto MissingPointer = pointerSizeInBits(7); + Context.expect(!MissingPointer, + "pointer width lookup must reject a missing explicit data layout"); +} + +void testDataLayoutBoundaryRules(TestContext &Context) { + static constexpr TestOptions IntegerAlignmentOptions{ + .DataLayout = "e-p:64:64-i24:128:128", + .NamedTypes = {}, + }; + static constexpr TestOptions HistoricalAggregateOptions{ + .DataLayout = "e-p:64:64-a0:64:128", + .NamedTypes = {}, + }; + static constexpr TestOptions ZeroAggregateOptions{ + .DataLayout = "e-p:64:64-a:0:64", + .NamedTypes = {}, + }; + static constexpr TestOptions NonBytePointerOptions{ + .DataLayout = "e-p:33:8:8:32", + .NamedTypes = {}, + }; + static constexpr TestOptions PointerFlagsOptions{ + .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 TestOptions InvalidIndexSizeOptions{ + .DataLayout = "e-p:33:8:8:34", + .NamedTypes = {}, + }; + auto Pointer = parseType("ptr", InvalidIndexSizeOptions); + Context.expect(Pointer.has_value() && !allocationSize(*Pointer, InvalidIndexSizeOptions), + "LLVM pointer index size larger than its representation must be rejected"); + + static constexpr TestOptions NonLayoutOptions{ + .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 TestOptions InvalidOptions{ + .DataLayout = DataLayout, + .NamedTypes = {}, + }; + auto Type = parseType("ptr", InvalidOptions); + Context.expect(Type.has_value() && !allocationSize(*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> "}, + 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(!parseConstant(Text, Options), "invalid LLVM constant must be rejected"); + } +} + +} // namespace + +int main() { + TestContext Context; + testScalarConstants(Context); + testExplicitHexIntegerConstants(Context); + testDecimalIntegerTruncation(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/CMakeLists.txt b/backend/unittests/CMakeLists.txt index 87e42b3..59e3c24 100644 --- a/backend/unittests/CMakeLists.txt +++ b/backend/unittests/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(Support) add_subdirectory(MC) +add_subdirectory(AsmParser) add_subdirectory(Target) add_subdirectory(CodeGen) add_subdirectory(MIR) diff --git a/backend/unittests/MIR/CMakeLists.txt b/backend/unittests/MIR/CMakeLists.txt index ae72452..8a8a8b4 100644 --- a/backend/unittests/MIR/CMakeLists.txt +++ b/backend/unittests/MIR/CMakeLists.txt @@ -6,15 +6,6 @@ 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_include_directories( @@ -49,14 +40,12 @@ 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/LLVMConstantSyntaxTest.cpp b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp deleted file mode 100644 index c10fc68..0000000 --- a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp +++ /dev/null @@ -1,515 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "rcc/MIR/LLVMConstantSyntax.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -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{ - .DataLayout = "e-p:64:64", - .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); - if (!Size || *Size != ExpectedSize) { - std::cerr << Text << ": expected storage size " << ExpectedSize << ", got "; - if (Size) - std::cerr << *Size; - else - std::cerr << Size.error().message(); - std::cerr << '\n'; - } - 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", 12); - 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", 16); - 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); - - auto BFloat = rcc::mir::parseLLVMConstant("bfloat 0xR3E80", Options); - auto PrintedBFloat = BFloat ? rcc::mir::printLLVMConstant(*BFloat) - : rcc::Expected(std::unexpected(BFloat.error())); - Context.expect(PrintedBFloat && *PrintedBFloat == "bfloat 2.500000e-01", - "LLVM-compatible floating printing must prefer an exact decimal spelling"); - - auto Pi = rcc::mir::parseLLVMConstant("double f0x400921FB54442D18", Options); - auto PrintedPi = Pi ? rcc::mir::printLLVMConstant(*Pi) - : rcc::Expected(std::unexpected(Pi.error())); - Context.expect( - PrintedPi && *PrintedPi == "double f0x400921FB54442D18", - "floating printing must retain exact bits when six decimal digits do not round-trip"); -} - -void expectIntegerWords(TestContext &Context, std::string_view Text, - std::span ExpectedWords) { - auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); - Context.expect(Parsed.has_value() && Parsed->IntegerValue.has_value() && - std::ranges::equal(Parsed->IntegerValue->words(), ExpectedWords), - "LLVM integer literal must preserve its exact bit pattern"); - if (!Parsed) - std::cerr << Text << ": " << Parsed.error().message() << '\n'; -} - -void testDecimalIntegerTruncation(TestContext &Context) { - constexpr std::array NegativeOne{std::numeric_limits::max()}; - constexpr std::array NarrowNegative{767}; - constexpr std::array NarrowMinimum{8}; - constexpr std::array WidePositive{1}; - - expectIntegerWords(Context, "i64 -1", NegativeOne); - expectIntegerWords(Context, "i16 -64769", NarrowNegative); - expectIntegerWords(Context, "i4 -8", NarrowMinimum); - expectIntegerWords(Context, "i64 18446744073709551617", WidePositive); -} - -void expectFloatingWords(TestContext &Context, std::string_view Text, - std::span ExpectedWords) { - auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); - 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 AllOnes65{std::numeric_limits::max(), 1}; - constexpr std::array SignBit65{0, 1}; - constexpr std::array UnsignedFF{0xff}; - constexpr std::array SignedFF{0xffff}; - constexpr std::array 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", 12); - 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 HalfInfinity{0x7c00}; - constexpr std::array NegativeHalfInfinity{0xfc00}; - constexpr std::array HalfQuietNaN{0x7e00}; - constexpr std::array NegativeHalfQuietNaN{0xfe00}; - constexpr std::array HalfSignalingNaN{0x7d00}; - constexpr std::array HalfPayloadNaN{0x7fef}; - constexpr std::array FloatSignalingNaN{0x7f800001}; - constexpr std::array X87Infinity{0x8000000000000000, 0x7fff}; - constexpr std::array X87QuietNaN{0xc000000000000000, 0x7fff}; - constexpr std::array QuadOne{0, 0x3fff000000000000}; - constexpr std::array QuadInfinity{0, 0x7fff000000000000}; - constexpr std::array QuadQuietNaN{0, 0x7fff800000000000}; - constexpr std::array PPCOne{0x3ff0000000000000, 0}; - constexpr std::array PPCInfinity{0x7ff0000000000000, 0}; - constexpr std::array 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 PositiveZero{0}; - constexpr std::array NegativeFloatZero{0x80000000}; - constexpr std::array HalfOne{0x3c00}; - constexpr std::array HalfTwoUlps{0x3c02}; - constexpr std::array HalfSmallestSubnormal{0x0001}; - constexpr std::array BFloatOne{0x3f80}; - constexpr std::array BFloatTwoUlps{0x3f82}; - constexpr std::array FloatOne{0x3f800000}; - constexpr std::array FloatTwoUlps{0x3f800002}; - constexpr std::array FloatHexPower{0x2f800000}; - constexpr std::array FloatSignalingNaN{0x7f800001}; - constexpr std::array FloatQuietNaN{0x7fc00000}; - constexpr std::array LargestDouble{0x7fefffffffffffff}; - constexpr std::array X87One{0x8000000000000000, 0x3fff}; - constexpr std::array QuadOne{0, 0x3fff000000000000}; - constexpr std::array 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> ", 16); - expectRoundTrip(Context, "<4 x float> ", 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> , " - "<2 x i32> , <2 x i32> )", - 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 3, i64 0)", - 8); - expectRoundTrip(Context, "i32 extractelement (<2 x i32> , 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> , " - "<2 x i32> , <2 x i64> )"}, - 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{ - .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> ", 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{ - .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"); - - auto MissingPointer = rcc::mir::llvmPointerSizeInBits(7); - Context.expect(!MissingPointer, - "pointer width lookup must reject a missing explicit data layout"); -} - -void testDataLayoutBoundaryRules(TestContext &Context) { - static constexpr rcc::mir::LLVMConstantSyntaxOptions IntegerAlignmentOptions{ - .DataLayout = "e-p:64:64-i24:128:128", - .NamedTypes = {}, - }; - static constexpr rcc::mir::LLVMConstantSyntaxOptions HistoricalAggregateOptions{ - .DataLayout = "e-p:64:64-a0:64:128", - .NamedTypes = {}, - }; - static constexpr rcc::mir::LLVMConstantSyntaxOptions ZeroAggregateOptions{ - .DataLayout = "e-p:64:64-a:0:64", - .NamedTypes = {}, - }; - static constexpr rcc::mir::LLVMConstantSyntaxOptions NonBytePointerOptions{ - .DataLayout = "e-p:33:8:8:32", - .NamedTypes = {}, - }; - static constexpr rcc::mir::LLVMConstantSyntaxOptions PointerFlagsOptions{ - .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{ - .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{ - .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{ - .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> "}, - 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); - testDecimalIntegerTruncation(Context); - testSpecialFloatingLiterals(Context); - testExactFloatingConversion(Context); - testAggregateConstants(Context); - testAddressConstants(Context); - testConstantExpressionVerification(Context); - testDataLayoutAndNamedTypes(Context); - testAddressSpacePointerWidths(Context); - testDataLayoutBoundaryRules(Context); - testInvalidConstants(Context); - return Context.result(); -} -- cgit v1.2.3