diff options
Diffstat (limited to 'backend/lib/MIR')
| -rw-r--r-- | backend/lib/MIR/CMakeLists.txt | 16 | ||||
| -rw-r--r-- | backend/lib/MIR/EmbeddedIR.cpp | 188 | ||||
| -rw-r--r-- | backend/lib/MIR/LLVMConstantSyntax.cpp | 3291 | ||||
| -rw-r--r-- | backend/lib/MIR/MIDebugSyntax.cpp | 127 | ||||
| -rw-r--r-- | backend/lib/MIR/MIParser.cpp | 154 | ||||
| -rw-r--r-- | backend/lib/MIR/MIPrinter.cpp | 122 | ||||
| -rw-r--r-- | backend/lib/MIR/MIRDocument.cpp | 2771 | ||||
| -rw-r--r-- | backend/lib/MIR/MIRDocumentPrinter.cpp | 1302 | ||||
| -rw-r--r-- | backend/lib/MIR/MIRDocumentReferenceValidation.h | 23 |
9 files changed, 7839 insertions, 155 deletions
diff --git a/backend/lib/MIR/CMakeLists.txt b/backend/lib/MIR/CMakeLists.txt index 7f66685..2ef63ed 100644 --- a/backend/lib/MIR/CMakeLists.txt +++ b/backend/lib/MIR/CMakeLists.txt @@ -3,14 +3,21 @@ set( RCCBackendMIRSources EmbeddedIR.cpp + LLVMConstantSyntax.cpp + MIDebugSyntax.cpp MILexer.cpp + MIRDocument.cpp + MIRDocumentPrinter.cpp MIParser.cpp MIPrinter.cpp ) set( RCCBackendMIRHeaders "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/EmbeddedIR.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/LLVMConstantSyntax.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/MIDebugSyntax.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/MILexer.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/MIRDocument.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/MIParser.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/MIPrinter.h" ) @@ -45,14 +52,23 @@ install( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIR.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.h" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp" ${RCCBackendMIRHeaders} ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIR.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp" ) diff --git a/backend/lib/MIR/EmbeddedIR.cpp b/backend/lib/MIR/EmbeddedIR.cpp index d3d5456..b19748e 100644 --- a/backend/lib/MIR/EmbeddedIR.cpp +++ b/backend/lib/MIR/EmbeddedIR.cpp @@ -82,6 +82,13 @@ const EmbeddedIRReference *EmbeddedIRFunction::alloca(std::string_view QueryName return &Allocas[Position->second]; } +const EmbeddedIRReference *EmbeddedIRFunction::value(std::string_view QueryName) const noexcept { + const auto Position = ValueNameToIndex.find(QueryName); + if (Position == ValueNameToIndex.end()) + return nullptr; + return &Values[Position->second]; +} + const EmbeddedIRFunction *EmbeddedIRReferenceIndex::function(std::string_view Name) const noexcept { const auto Position = FunctionNameToIndex.find(Name); if (Position == FunctionNameToIndex.end()) @@ -97,7 +104,7 @@ EmbeddedIRReferenceIndex::globalObject(std::string_view Name) const noexcept { return &GlobalObjects[Position->second]; } -const EmbeddedIRReference * +const EmbeddedIRMetadataDefinition * EmbeddedIRReferenceIndex::metadata(std::string_view Name) const noexcept { const auto Position = MetadataNameToIndex.find(Name); if (Position == MetadataNameToIndex.end()) @@ -105,6 +112,14 @@ EmbeddedIRReferenceIndex::metadata(std::string_view Name) const noexcept { return &MetadataDefinitions[Position->second]; } +const EmbeddedIRTypeDefinition * +EmbeddedIRReferenceIndex::typeDefinition(std::string_view Name) const noexcept { + const auto Position = TypeNameToIndex.find(Name); + if (Position == TypeNameToIndex.end()) + return nullptr; + return &TypeDefinitions[Position->second]; +} + class EmbeddedIRIndexer final { public: EmbeddedIRIndexer(std::string SourceName, std::string SourceText) @@ -299,10 +314,55 @@ private: parseGlobalObject(Line, LineOffset, Position, End); return; } + if (Line[Position] == '%') { + parseTypeDefinition(Line, LineOffset, Position, End); + return; + } if (Line[Position] == '!') parseMetadataDefinition(Line, LineOffset, Position, End); } + void parseTypeDefinition(std::string_view Line, std::size_t LineOffset, + std::size_t Position, std::size_t End) { + auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != '=') + return; + ++Position; + Position = skipHorizontalSpace(Line, Position, End); + if (!startsWord(Line, Position, End, "type")) + return; + Position += std::string_view("type").size(); + Position = skipHorizontalSpace(Line, Position, End); + std::size_t DefinitionEnd = End; + while (DefinitionEnd > Position && isHorizontalSpace(Line[DefinitionEnd - 1])) + --DefinitionEnd; + if (Position == DefinitionEnd) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "embedded LLVM IR type definition is empty"); + return; + } + if (Index.TypeNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR type definition"); + return; + } + const std::size_t TypeIndex = Index.TypeDefinitions.size(); + const bool NumericName = !Name->Name.empty() && Name->Name.front() >= '0' && + Name->Name.front() <= '9'; + Index.TypeNameToIndex.emplace(Name->Name, TypeIndex); + Index.TypeDefinitions.push_back(EmbeddedIRTypeDefinition{ + .Name = std::move(Name->Name), + .Range = Name->Range, + .Definition = std::string(Line.substr(Position, DefinitionEnd - Position)), + .DefinitionRange = + Index.Source.range(LineOffset + Position, LineOffset + DefinitionEnd), + .NumericName = NumericName, + }); + } + void parseTargetFact(std::string_view Line, std::size_t LineOffset, std::size_t Position, std::size_t End) { Position += std::string_view("target").size(); @@ -426,6 +486,24 @@ private: } if (InQuote) continue; + if (Character == '%' && HeaderParenthesisDepth != 0 && ActiveFunction) { + const std::size_t IdentifierBegin = Position; + auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); + if (!Name || Failed) + return; + const std::size_t Following = skipHorizontalSpace(Line, Name->End, End); + std::size_t Previous = IdentifierBegin; + while (Previous > 0 && isHorizontalSpace(Line[Previous - 1])) + --Previous; + const bool HasTypeBefore = Previous != 0 && Line[Previous - 1] != '(' && + Line[Previous - 1] != ','; + const bool EndsParameter = Following == End || Line[Following] == ',' || + Line[Following] == ')'; + if (HasTypeBefore && EndsParameter) + addLocalValue(*Name, Index.Functions[ActiveFunction.value()]); + Position = Name->End - 1; + continue; + } if (Character == '(') { ++HeaderParenthesisDepth; continue; @@ -447,6 +525,21 @@ private: } } + void addLocalValue(const ParsedIdentifier &Name, EmbeddedIRFunction &Function) { + if (Function.ValueNameToIndex.contains(Name.Name) || + Function.BlockNameToIndex.contains(Name.Name)) { + diagnose(Name.Range.Begin.Offset, Name.Range.End.Offset, + "duplicate embedded LLVM IR local value or basic block name"); + return; + } + const std::size_t ValueIndex = Function.Values.size(); + Function.ValueNameToIndex.emplace(Name.Name, ValueIndex); + Function.Values.push_back(EmbeddedIRReference{ + .Name = Name.Name, + .Range = Name.Range, + }); + } + void parseActiveFunctionLine(std::string_view Line, std::size_t LineOffset, std::size_t End) { if (FunctionBraceDepth == 0) { parseFunctionHeaderRemainder(Line, LineOffset, 0, End); @@ -498,7 +591,7 @@ private: parseBlockLabel(Line, LineOffset, Begin, BodyEnd, Function); if (Failed) return; - parseAlloca(Line, LineOffset, Begin, BodyEnd, Function); + parseLocalValueDefinition(Line, LineOffset, Begin, BodyEnd, Function); if (Failed) return; @@ -524,6 +617,11 @@ private: "duplicate embedded LLVM IR basic block label"); return; } + if (Function.ValueNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR local value or basic block name"); + return; + } const std::size_t BlockIndex = Function.Blocks.size(); Function.BlockNameToIndex.emplace(Name->Name, BlockIndex); Function.Blocks.push_back(EmbeddedIRReference{ @@ -532,46 +630,33 @@ private: }); } - void parseAlloca(std::string_view Line, std::size_t LineOffset, std::size_t Begin, - std::size_t End, EmbeddedIRFunction &Function) { - bool InQuote = false; - for (std::size_t Position = Begin; Position < End; ++Position) { - if (InQuote && Line[Position] == '\\' && Position + 2 < End && - isHexDigit(Line[Position + 1]) && isHexDigit(Line[Position + 2])) { - Position += 2; - continue; - } - if (Line[Position] == '"') { - InQuote = !InQuote; - continue; - } - if (InQuote || Line[Position] != '%') - continue; + void parseLocalValueDefinition(std::string_view Line, std::size_t LineOffset, + std::size_t Begin, std::size_t End, + EmbeddedIRFunction &Function) { + std::size_t Position = skipHorizontalSpace(Line, Begin, End); + if (Position >= End || Line[Position] != '%') + return; + auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != '=') + return; + ++Position; + Position = skipHorizontalSpace(Line, Position, End); - auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); - if (!Name || Failed) - return; - Position = skipHorizontalSpace(Line, Name->End, End); - if (Position >= End || Line[Position] != '=') - continue; - ++Position; - Position = skipHorizontalSpace(Line, Position, End); - if (!startsWord(Line, Position, End, "alloca")) - continue; + addLocalValue(*Name, Function); + if (Failed) + return; - if (Function.AllocaNameToIndex.contains(Name->Name)) { - diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, - "duplicate embedded LLVM IR alloca name"); - return; - } - const std::size_t AllocaIndex = Function.Allocas.size(); - Function.AllocaNameToIndex.emplace(Name->Name, AllocaIndex); - Function.Allocas.push_back(EmbeddedIRReference{ - .Name = std::move(Name->Name), - .Range = Name->Range, - }); + if (!startsWord(Line, Position, End, "alloca")) return; - } + const std::size_t AllocaIndex = Function.Allocas.size(); + Function.AllocaNameToIndex.emplace(Name->Name, AllocaIndex); + Function.Allocas.push_back(EmbeddedIRReference{ + .Name = std::move(Name->Name), + .Range = Name->Range, + }); } void parseGlobalObject(std::string_view Line, std::size_t LineOffset, std::size_t Position, @@ -608,6 +693,28 @@ private: Position = skipHorizontalSpace(Line, Name->End, End); if (Position >= End || Line[Position] != '=') return; + ++Position; + Position = skipHorizontalSpace(Line, Position, End); + if (startsWord(Line, Position, End, "distinct")) { + Position += std::string_view("distinct").size(); + Position = skipHorizontalSpace(Line, Position, End); + } + + std::string DynamicType; + if (Position < End && Line[Position] == '!') { + ++Position; + if (Position < End && Line[Position] == '{') { + DynamicType = "MDTuple"; + } else if (Position < End && Line[Position] == '"') { + DynamicType = "MDString"; + } else { + const std::size_t TypeBegin = Position; + while (Position < End && isIdentifierBody(Line[Position])) + ++Position; + if (Position != TypeBegin) + DynamicType = std::string(Line.substr(TypeBegin, Position - TypeBegin)); + } + } if (Index.MetadataNameToIndex.contains(Name->Name)) { diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, "duplicate embedded LLVM IR metadata definition"); @@ -615,9 +722,10 @@ private: } const std::size_t MetadataIndex = Index.MetadataDefinitions.size(); Index.MetadataNameToIndex.emplace(Name->Name, MetadataIndex); - Index.MetadataDefinitions.push_back(EmbeddedIRReference{ + Index.MetadataDefinitions.push_back(EmbeddedIRMetadataDefinition{ .Name = std::move(Name->Name), .Range = Name->Range, + .DynamicType = std::move(DynamicType), }); } diff --git a/backend/lib/MIR/LLVMConstantSyntax.cpp b/backend/lib/MIR/LLVMConstantSyntax.cpp new file mode 100644 index 0000000..543cc1a --- /dev/null +++ b/backend/lib/MIR/LLVMConstantSyntax.cpp @@ -0,0 +1,3291 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include <algorithm> +#include <array> +#include <bit> +#include <charconv> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <ranges> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc::mir { +namespace { + +struct ParsedName final { + std::string Value; + bool Numeric = false; +}; + +[[nodiscard]] bool isSpace(char Character) noexcept { + return Character == ' ' || Character == '\t' || Character == '\r' || Character == '\n'; +} + +[[nodiscard]] bool isIdentifierStart(char Character) noexcept { + return (Character >= 'A' && Character <= 'Z') || (Character >= 'a' && Character <= 'z') || + Character == '-' || Character == '$' || Character == '.' || Character == '_'; +} + +[[nodiscard]] bool isIdentifierBody(char Character) noexcept { + return isIdentifierStart(Character) || (Character >= '0' && Character <= '9'); +} + +[[nodiscard]] bool isHexDigit(char Character) noexcept { + return (Character >= '0' && Character <= '9') || (Character >= 'a' && Character <= 'f') || + (Character >= 'A' && Character <= 'F'); +} + +[[nodiscard]] unsigned hexDigitValue(char Character) noexcept { + if (Character >= '0' && Character <= '9') + return static_cast<unsigned>(Character - '0'); + if (Character >= 'a' && Character <= 'f') + return static_cast<unsigned>(Character - 'a') + 10U; + return static_cast<unsigned>(Character - 'A') + 10U; +} + +class BigUnsigned final { +public: + BigUnsigned() = default; + + explicit BigUnsigned(std::uint64_t Value) { + if (Value == 0) + return; + Limbs.push_back(static_cast<std::uint32_t>(Value)); + if ((Value >> 32U) != 0) + Limbs.push_back(static_cast<std::uint32_t>(Value >> 32U)); + } + + [[nodiscard]] bool isZero() const noexcept { + return Limbs.empty(); + } + + [[nodiscard]] bool isOdd() const noexcept { + return !Limbs.empty() && (Limbs.front() & 1U) != 0; + } + + [[nodiscard]] std::size_t bitWidth() const noexcept { + if (Limbs.empty()) + return 0; + std::uint32_t MostSignificant = Limbs.back(); + std::size_t Bits = 0; + while (MostSignificant != 0) { + ++Bits; + MostSignificant >>= 1U; + } + return (Limbs.size() - 1U) * 32U + Bits; + } + + [[nodiscard]] std::uint64_t word(std::size_t Index) const noexcept { + const std::size_t First = Index * 2U; + if (First >= Limbs.size()) + return 0; + std::uint64_t Result = Limbs[First]; + if (First + 1U < Limbs.size()) + Result |= static_cast<std::uint64_t>(Limbs[First + 1U]) << 32U; + return Result; + } + + [[nodiscard]] int compare(const BigUnsigned &Other) const noexcept { + if (Limbs.size() != Other.Limbs.size()) + return Limbs.size() < Other.Limbs.size() ? -1 : 1; + for (std::size_t Index = Limbs.size(); Index != 0; --Index) { + const std::uint32_t Left = Limbs[Index - 1U]; + const std::uint32_t Right = Other.Limbs[Index - 1U]; + if (Left != Right) + return Left < Right ? -1 : 1; + } + return 0; + } + + void multiplySmall(std::uint32_t Multiplier) { + if (Multiplier == 0 || isZero()) { + Limbs.clear(); + return; + } + std::uint64_t Carry = 0; + for (std::uint32_t &Limb : Limbs) { + const std::uint64_t Product = static_cast<std::uint64_t>(Limb) * Multiplier + Carry; + Limb = static_cast<std::uint32_t>(Product); + Carry = Product >> 32U; + } + if (Carry != 0) + Limbs.push_back(static_cast<std::uint32_t>(Carry)); + } + + void addSmall(std::uint32_t Value) { + std::uint64_t Carry = Value; + std::size_t Index = 0; + while (Carry != 0 && Index < Limbs.size()) { + const std::uint64_t Sum = static_cast<std::uint64_t>(Limbs[Index]) + Carry; + Limbs[Index] = static_cast<std::uint32_t>(Sum); + Carry = Sum >> 32U; + ++Index; + } + if (Carry != 0) + Limbs.push_back(static_cast<std::uint32_t>(Carry)); + } + + void setBit(std::size_t Bit) { + const std::size_t Limb = Bit / 32U; + if (Limb >= Limbs.size()) + Limbs.resize(Limb + 1U, 0); + Limbs[Limb] |= std::uint32_t{1} << (Bit % 32U); + } + + void clearBit(std::size_t Bit) noexcept { + const std::size_t Limb = Bit / 32U; + if (Limb >= Limbs.size()) + return; + Limbs[Limb] &= ~(std::uint32_t{1} << (Bit % 32U)); + normalize(); + } + + void shiftRightOne() noexcept { + std::uint32_t Carry = 0; + for (std::size_t Index = Limbs.size(); Index != 0; --Index) { + const std::uint32_t Current = Limbs[Index - 1U]; + Limbs[Index - 1U] = (Current >> 1U) | (Carry << 31U); + Carry = Current & 1U; + } + normalize(); + } + + [[nodiscard]] BigUnsigned shiftedLeft(std::size_t Amount) const { + if (isZero() || Amount == 0) + return *this; + const std::size_t LimbOffset = Amount / 32U; + const unsigned BitOffset = static_cast<unsigned>(Amount % 32U); + BigUnsigned Result; + Result.Limbs.assign(Limbs.size() + LimbOffset + (BitOffset == 0 ? 0U : 1U), 0); + for (std::size_t Index = 0; Index < Limbs.size(); ++Index) { + const std::uint64_t Shifted = static_cast<std::uint64_t>(Limbs[Index]) << BitOffset; + Result.Limbs[Index + LimbOffset] |= static_cast<std::uint32_t>(Shifted); + if (BitOffset != 0) + Result.Limbs[Index + LimbOffset + 1U] |= static_cast<std::uint32_t>(Shifted >> 32U); + } + Result.normalize(); + return Result; + } + + void subtract(const BigUnsigned &Other) noexcept { + std::uint64_t Borrow = 0; + for (std::size_t Index = 0; Index < Limbs.size(); ++Index) { + const std::uint64_t Subtrahend = + (Index < Other.Limbs.size() ? static_cast<std::uint64_t>(Other.Limbs[Index]) : 0) + + Borrow; + const std::uint64_t Current = Limbs[Index]; + Limbs[Index] = static_cast<std::uint32_t>(Current - Subtrahend); + Borrow = Current < Subtrahend ? 1U : 0U; + } + normalize(); + } + +private: + friend int compareToShifted(const BigUnsigned &Left, const BigUnsigned &Right, + std::size_t RightShift) noexcept; + + void normalize() noexcept { + while (!Limbs.empty() && Limbs.back() == 0) + Limbs.pop_back(); + } + + std::vector<std::uint32_t> Limbs; +}; + +[[nodiscard]] int compareToShifted(const BigUnsigned &Left, const BigUnsigned &Right, + std::size_t RightShift) noexcept { + if (Right.isZero()) + return Left.isZero() ? 0 : 1; + if (Left.isZero()) + return -1; + + const std::size_t LeftBits = Left.bitWidth(); + const std::size_t RightBits = Right.bitWidth(); + if (RightShift > std::numeric_limits<std::size_t>::max() - RightBits) + return -1; + const std::size_t ShiftedBits = RightBits + RightShift; + if (LeftBits != ShiftedBits) + return LeftBits < ShiftedBits ? -1 : 1; + + const std::size_t LimbOffset = RightShift / 32U; + const unsigned BitOffset = static_cast<unsigned>(RightShift % 32U); + const std::size_t WordCount = (ShiftedBits + 31U) / 32U; + for (std::size_t Index = WordCount; Index != 0; --Index) { + const std::size_t Destination = Index - 1U; + std::uint32_t ShiftedWord = 0; + if (Destination >= LimbOffset) { + const std::size_t Source = Destination - LimbOffset; + if (Source < Right.Limbs.size()) + ShiftedWord = static_cast<std::uint32_t>(Right.Limbs[Source] << BitOffset); + if (BitOffset != 0 && Source != 0 && Source - 1U < Right.Limbs.size()) + ShiftedWord |= Right.Limbs[Source - 1U] >> (32U - BitOffset); + } + const std::uint32_t LeftWord = Destination < Left.Limbs.size() ? Left.Limbs[Destination] : 0; + if (LeftWord != ShiftedWord) + return LeftWord < ShiftedWord ? -1 : 1; + } + return 0; +} + +[[nodiscard]] bool parseHexWords(std::string_view Digits, std::uint32_t BitWidth, + std::vector<std::uint64_t> &Words) { + if (Digits.empty() || Digits.size() > (static_cast<std::size_t>(BitWidth) + 3U) / 4U) + return false; + Words.assign((BitWidth + 63U) / 64U, 0); + std::size_t Nibble = 0; + for (auto Iterator = Digits.rbegin(); Iterator != Digits.rend(); ++Iterator, ++Nibble) { + if (!isHexDigit(*Iterator)) + return false; + Words[Nibble / 16U] |= static_cast<std::uint64_t>(hexDigitValue(*Iterator)) + << ((Nibble % 16U) * 4U); + } + if ((BitWidth % 64U) != 0 && (Words.back() >> (BitWidth % 64U)) != 0) + return false; + return true; +} + +[[nodiscard]] bool parseLegacyWideFloatHexWords(std::string_view Digits, + std::vector<std::uint64_t> &Words) { + if (Digits.empty() || Digits.size() > 32U || !std::ranges::all_of(Digits, isHexDigit)) + return false; + Words.assign(2, 0); + const auto ParseWord = [](std::string_view Text, std::uint64_t &Word) { + const auto Parsed = std::from_chars(Text.data(), Text.data() + Text.size(), Word, 16); + return Parsed.ec == std::errc{} && Parsed.ptr == Text.data() + Text.size(); + }; + const std::size_t FirstWordLength = std::min<std::size_t>(Digits.size(), 16U); + if (!ParseWord(Digits.substr(0, FirstWordLength), Words[0])) + return false; + if (Digits.size() == FirstWordLength) + return true; + return ParseWord(Digits.substr(FirstWordLength), Words[1]); +} + +enum class SpecialFloatLiteralKind : std::uint8_t { + Infinity, + QuietNaN, + SignalingNaN, +}; + +struct SpecialFloatLiteral final { + SpecialFloatLiteralKind Kind = SpecialFloatLiteralKind::Infinity; + bool Negative = false; + std::array<std::uint64_t, 2> Payload{}; +}; + +[[nodiscard]] Expected<std::optional<SpecialFloatLiteral>> +parseSpecialFloatLiteral(std::string_view Token) { + if (!Token.starts_with('+') && !Token.starts_with('-')) + return std::optional<SpecialFloatLiteral>{}; + + SpecialFloatLiteral Result; + Result.Negative = Token.front() == '-'; + Token.remove_prefix(1); + if (Token == "inf") + return Result; + if (Token == "qnan") { + Result.Kind = SpecialFloatLiteralKind::QuietNaN; + return Result; + } + + if (!Token.starts_with("nan") && !Token.starts_with("snan")) + return std::optional<SpecialFloatLiteral>{}; + + const bool IsSignaling = Token.starts_with("snan"); + const std::size_t NameLength = IsSignaling ? 4U : 3U; + const std::string_view Suffix = Token.substr(NameLength); + Result.Kind = + IsSignaling ? SpecialFloatLiteralKind::SignalingNaN : SpecialFloatLiteralKind::QuietNaN; + if (Suffix.size() < 5 || Suffix.front() != '(' || Suffix.back() != ')' || + !Suffix.substr(1).starts_with("0x")) { + return makeUnexpected(ErrorCode::ParseError, "invalid floating-point NaN payload"); + } + + const std::string_view Digits = Suffix.substr(3, Suffix.size() - 4U); + if (Digits.empty() || !std::ranges::all_of(Digits, isHexDigit)) { + return makeUnexpected(ErrorCode::ParseError, "invalid floating-point NaN payload"); + } + std::size_t Nibble = 0; + for (auto Iterator = Digits.rbegin(); Iterator != Digits.rend(); ++Iterator, ++Nibble) { + if (Nibble >= 32U) + break; + Result.Payload[Nibble / 16U] |= static_cast<std::uint64_t>(hexDigitValue(*Iterator)) + << ((Nibble % 16U) * 4U); + } + return Result; +} + +void setFloatBit(std::vector<std::uint64_t> &Words, std::uint32_t Bit) { + Words[Bit / 64U] |= std::uint64_t{1} << (Bit % 64U); +} + +void clearFloatBit(std::vector<std::uint64_t> &Words, std::uint32_t Bit) { + Words[Bit / 64U] &= ~(std::uint64_t{1} << (Bit % 64U)); +} + +[[nodiscard]] bool hasFloatBit(std::span<const std::uint64_t> Words, std::uint32_t Bit) noexcept { + return (Words[Bit / 64U] & (std::uint64_t{1} << (Bit % 64U))) != 0; +} + +void copyNaNPayload(std::vector<std::uint64_t> &Words, const std::array<std::uint64_t, 2> &Payload, + std::uint32_t PayloadBitWidth) { + for (std::uint32_t Bit = 0; Bit < PayloadBitWidth; ++Bit) { + if ((Payload[Bit / 64U] & (std::uint64_t{1} << (Bit % 64U))) != 0) + setFloatBit(Words, Bit); + } +} + +void makeBinarySpecialFloat(std::vector<std::uint64_t> &Words, std::uint32_t BitWidth, + std::uint32_t ExponentBitWidth, const SpecialFloatLiteral &Literal) { + const std::uint32_t FractionBitWidth = BitWidth - ExponentBitWidth - 1U; + if (Literal.Negative) + setFloatBit(Words, BitWidth - 1U); + for (std::uint32_t Bit = FractionBitWidth; Bit < FractionBitWidth + ExponentBitWidth; ++Bit) { + setFloatBit(Words, Bit); + } + if (Literal.Kind == SpecialFloatLiteralKind::Infinity) + return; + + copyNaNPayload(Words, Literal.Payload, FractionBitWidth); + const std::uint32_t QuietNaNBit = FractionBitWidth - 1U; + if (Literal.Kind == SpecialFloatLiteralKind::QuietNaN) { + setFloatBit(Words, QuietNaNBit); + return; + } + clearFloatBit(Words, QuietNaNBit); + bool HasPayload = false; + for (std::uint32_t Bit = 0; Bit < FractionBitWidth; ++Bit) + HasPayload = HasPayload || hasFloatBit(Words, Bit); + if (!HasPayload) + setFloatBit(Words, QuietNaNBit - 1U); +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +makeSpecialFloatWords(FloatSemantics Semantics, const SpecialFloatLiteral &Literal) { + const auto BitWidth = getFloatSemanticsBitWidth(Semantics); + if (!BitWidth) + return makeUnexpected(ErrorCode::InvalidArgument, "floating-point semantics is unknown"); + std::vector<std::uint64_t> Words((*BitWidth + 63U) / 64U, 0); + switch (Semantics) { + case FloatSemantics::IEEEHalf: + makeBinarySpecialFloat(Words, *BitWidth, 5, Literal); + return Words; + case FloatSemantics::BFloat: + case FloatSemantics::IEEESingle: + makeBinarySpecialFloat(Words, *BitWidth, 8, Literal); + return Words; + case FloatSemantics::IEEEDouble: + makeBinarySpecialFloat(Words, *BitWidth, 11, Literal); + return Words; + case FloatSemantics::IEEEQuad: + makeBinarySpecialFloat(Words, *BitWidth, 15, Literal); + return Words; + case FloatSemantics::X87DoubleExtended: { + if (Literal.Negative) + setFloatBit(Words, 79); + for (std::uint32_t Bit = 64; Bit < 79; ++Bit) + setFloatBit(Words, Bit); + if (Literal.Kind == SpecialFloatLiteralKind::Infinity) { + setFloatBit(Words, 63); + return Words; + } + copyNaNPayload(Words, Literal.Payload, 63); + setFloatBit(Words, 63); + if (Literal.Kind == SpecialFloatLiteralKind::QuietNaN) { + setFloatBit(Words, 62); + return Words; + } + clearFloatBit(Words, 62); + bool HasPayload = false; + for (std::uint32_t Bit = 0; Bit < 63; ++Bit) + HasPayload = HasPayload || hasFloatBit(Words, Bit); + if (!HasPayload) + setFloatBit(Words, 61); + return Words; + } + case FloatSemantics::PPCDoubleDouble: + case FloatSemantics::PPCDoubleDoubleLegacy: { + std::vector<std::uint64_t> HighPart(1, 0); + makeBinarySpecialFloat(HighPart, 64, 11, Literal); + Words[0] = HighPart.front(); + return Words; + } + case FloatSemantics::Float8E5M2: + case FloatSemantics::Float8E5M2FNUZ: + case FloatSemantics::Float8E4M3: + case FloatSemantics::Float8E4M3FN: + case FloatSemantics::Float8E4M3FNUZ: + case FloatSemantics::Float8E4M3B11FNUZ: + case FloatSemantics::Float8E3M4: + case FloatSemantics::FloatTF32: + case FloatSemantics::Float8E8M0FNU: + case FloatSemantics::Float6E3M2FN: + case FloatSemantics::Float6E2M3FN: + case FloatSemantics::Float4E2M1FN: + case FloatSemantics::Count: + break; + } + return makeUnexpected(ErrorCode::Unsupported, + "floating-point special literal uses unsupported semantics"); +} + +struct BinaryFloatFormat final { + std::uint32_t Precision = 0; + std::int32_t MinExponent = 0; + std::int32_t MaxExponent = 0; + std::uint32_t ExponentBitWidth = 0; + std::uint32_t StorageBitWidth = 0; + bool HasExplicitIntegerBit = false; +}; + +[[nodiscard]] std::optional<BinaryFloatFormat> +binaryFloatFormat(FloatSemantics Semantics) noexcept { + switch (Semantics) { + case FloatSemantics::IEEEHalf: + return BinaryFloatFormat{.Precision = 11, + .MinExponent = -14, + .MaxExponent = 15, + .ExponentBitWidth = 5, + .StorageBitWidth = 16}; + case FloatSemantics::BFloat: + return BinaryFloatFormat{.Precision = 8, + .MinExponent = -126, + .MaxExponent = 127, + .ExponentBitWidth = 8, + .StorageBitWidth = 16}; + case FloatSemantics::IEEESingle: + return BinaryFloatFormat{.Precision = 24, + .MinExponent = -126, + .MaxExponent = 127, + .ExponentBitWidth = 8, + .StorageBitWidth = 32}; + case FloatSemantics::IEEEDouble: + return BinaryFloatFormat{.Precision = 53, + .MinExponent = -1022, + .MaxExponent = 1023, + .ExponentBitWidth = 11, + .StorageBitWidth = 64}; + case FloatSemantics::IEEEQuad: + return BinaryFloatFormat{.Precision = 113, + .MinExponent = -16382, + .MaxExponent = 16383, + .ExponentBitWidth = 15, + .StorageBitWidth = 128}; + case FloatSemantics::X87DoubleExtended: + return BinaryFloatFormat{.Precision = 64, + .MinExponent = -16382, + .MaxExponent = 16383, + .ExponentBitWidth = 15, + .StorageBitWidth = 80, + .HasExplicitIntegerBit = true}; + case FloatSemantics::PPCDoubleDouble: + case FloatSemantics::PPCDoubleDoubleLegacy: + // LLVM parses a decimal PPC double-double through its documented legacy + // 106-bit format and then materializes the canonical pair of doubles. + return BinaryFloatFormat{.Precision = 106, + .MinExponent = -969, + .MaxExponent = 1023, + .ExponentBitWidth = 0, + .StorageBitWidth = 128}; + case FloatSemantics::Float8E5M2: + case FloatSemantics::Float8E5M2FNUZ: + case FloatSemantics::Float8E4M3: + case FloatSemantics::Float8E4M3FN: + case FloatSemantics::Float8E4M3FNUZ: + case FloatSemantics::Float8E4M3B11FNUZ: + case FloatSemantics::Float8E3M4: + case FloatSemantics::FloatTF32: + case FloatSemantics::Float8E8M0FNU: + case FloatSemantics::Float6E3M2FN: + case FloatSemantics::Float6E2M3FN: + case FloatSemantics::Float4E2M1FN: + case FloatSemantics::Count: + return std::nullopt; + } + return std::nullopt; +} + +struct RoundedBinaryFloat final { + bool IsZero = false; + bool IsSubnormal = false; + std::int64_t Exponent = 0; + std::int64_t Scale = 0; + BigUnsigned Significand; +}; + +[[nodiscard]] RoundedBinaryFloat makeZeroRoundedBinaryFloat() { + RoundedBinaryFloat Result; + Result.IsZero = true; + return Result; +} + +[[nodiscard]] bool checkedAddExponent(std::int64_t Left, std::int64_t Right, + std::int64_t &Result) noexcept { + if (Right > 0 && Left > std::numeric_limits<std::int64_t>::max() - Right) + return false; + if (Right < 0 && Left < std::numeric_limits<std::int64_t>::min() - Right) + return false; + Result = Left + Right; + return true; +} + +[[nodiscard]] bool checkedSubtractExponent(std::int64_t Left, std::int64_t Right, + std::int64_t &Result) noexcept { + if (Right == std::numeric_limits<std::int64_t>::min()) { + if (Left >= 0) + return false; + Result = Left - Right; + return true; + } + return checkedAddExponent(Left, -Right, Result); +} + +[[nodiscard]] Expected<BigUnsigned> divideAndRoundNearest(BigUnsigned Numerator, + const BigUnsigned &Denominator) { + if (Denominator.isZero()) + return makeUnexpected(ErrorCode::VerificationError, + "floating-point conversion has a zero denominator"); + + BigUnsigned Quotient; + const std::size_t NumeratorBits = Numerator.bitWidth(); + const std::size_t DenominatorBits = Denominator.bitWidth(); + if (NumeratorBits >= DenominatorBits) { + for (std::size_t Shift = NumeratorBits - DenominatorBits + 1U; Shift != 0; --Shift) { + const std::size_t ActualShift = Shift - 1U; + if (compareToShifted(Numerator, Denominator, ActualShift) < 0) + continue; + Numerator.subtract(Denominator.shiftedLeft(ActualShift)); + Quotient.setBit(ActualShift); + } + } + + const int HalfComparison = Numerator.shiftedLeft(1).compare(Denominator); + if (HalfComparison > 0 || (HalfComparison == 0 && Quotient.isOdd())) + Quotient.addSmall(1); + return Quotient; +} + +[[nodiscard]] Expected<BigUnsigned> +roundScaledRational(BigUnsigned Numerator, BigUnsigned Denominator, std::int64_t Shift) { + const auto AbsoluteShift = [Shift]() -> std::uint64_t { + if (Shift >= 0) + return static_cast<std::uint64_t>(Shift); + return static_cast<std::uint64_t>(-(Shift + 1)) + 1U; + }(); + if (AbsoluteShift > std::numeric_limits<std::size_t>::max()) { + return makeUnexpected(ErrorCode::ParseError, + "floating-point literal has an unrepresentable binary exponent"); + } + if (Shift >= 0) + Numerator = Numerator.shiftedLeft(static_cast<std::size_t>(AbsoluteShift)); + else + Denominator = Denominator.shiftedLeft(static_cast<std::size_t>(AbsoluteShift)); + return divideAndRoundNearest(std::move(Numerator), Denominator); +} + +[[nodiscard]] Expected<std::int64_t> floorLog2OfRational(const BigUnsigned &Numerator, + const BigUnsigned &Denominator, + std::int64_t BinaryScale) { + if (Numerator.isZero() || Denominator.isZero()) { + return makeUnexpected(ErrorCode::VerificationError, + "floating-point conversion requires a nonzero rational value"); + } + const std::size_t NumeratorBits = Numerator.bitWidth(); + const std::size_t DenominatorBits = Denominator.bitWidth(); + if (NumeratorBits > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max()) || + DenominatorBits > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "floating-point literal significand is too large"); + } + const std::int64_t BitDelta = + static_cast<std::int64_t>(NumeratorBits) - static_cast<std::int64_t>(DenominatorBits); + std::int64_t Candidate = 0; + if (!checkedAddExponent(BinaryScale, BitDelta, Candidate)) { + return makeUnexpected(BinaryScale >= 0 ? ErrorCode::ParseError : ErrorCode::Unsupported, + BinaryScale >= 0 ? "floating-point constant overflowed type" + : "floating-point constant underflowed type"); + } + + const std::int64_t RelativeShift = -BitDelta; + int Comparison = 0; + if (RelativeShift >= 0) { + if (static_cast<std::uint64_t>(RelativeShift) > std::numeric_limits<std::size_t>::max()) { + Comparison = -1; + } else { + Comparison = + -compareToShifted(Denominator, Numerator, static_cast<std::size_t>(RelativeShift)); + } + } else { + const std::uint64_t AbsoluteShift = static_cast<std::uint64_t>(-(RelativeShift + 1)) + 1U; + if (AbsoluteShift > std::numeric_limits<std::size_t>::max()) + Comparison = 1; + else + Comparison = + compareToShifted(Numerator, Denominator, static_cast<std::size_t>(AbsoluteShift)); + } + if (Comparison >= 0) + return Candidate; + if (Candidate == std::numeric_limits<std::int64_t>::min()) { + return makeUnexpected(ErrorCode::ParseError, "floating-point constant underflowed type"); + } + return Candidate - 1; +} + +[[nodiscard]] Expected<RoundedBinaryFloat> roundBinaryRational(const BinaryFloatFormat &Format, + const BigUnsigned &Numerator, + const BigUnsigned &Denominator, + std::int64_t BinaryScale, + bool PermitZeroResult = false) { + if (Numerator.isZero()) + return makeZeroRoundedBinaryFloat(); + + auto FloorExponent = floorLog2OfRational(Numerator, Denominator, BinaryScale); + if (!FloorExponent) + return std::unexpected(FloorExponent.error()); + if (*FloorExponent > Format.MaxExponent) { + return makeUnexpected(ErrorCode::ParseError, "floating-point constant overflowed type"); + } + + const std::int64_t SmallestUnit = + static_cast<std::int64_t>(Format.MinExponent) - (Format.Precision - 1U); + if (*FloorExponent < SmallestUnit - 1) { + if (PermitZeroResult) + return makeZeroRoundedBinaryFloat(); + return makeUnexpected(ErrorCode::ParseError, "floating-point constant underflowed type"); + } + + const bool StartsNormal = *FloorExponent >= Format.MinExponent; + const std::int64_t Unit = StartsNormal ? *FloorExponent - (Format.Precision - 1U) : SmallestUnit; + std::int64_t Shift = 0; + if (!checkedSubtractExponent(BinaryScale, Unit, Shift)) { + return makeUnexpected(BinaryScale >= 0 ? ErrorCode::ParseError : ErrorCode::Unsupported, + BinaryScale >= 0 ? "floating-point constant overflowed type" + : "floating-point constant underflowed type"); + } + auto Significand = roundScaledRational(Numerator, Denominator, Shift); + if (!Significand) + return std::unexpected(Significand.error()); + if (Significand->isZero()) { + if (PermitZeroResult) + return makeZeroRoundedBinaryFloat(); + return makeUnexpected(ErrorCode::ParseError, "floating-point constant underflowed type"); + } + + RoundedBinaryFloat Result; + Result.Significand = std::move(*Significand); + if (StartsNormal) { + Result.Exponent = *FloorExponent; + if (Result.Significand.bitWidth() > Format.Precision) { + Result.Significand.shiftRightOne(); + if (Result.Exponent == Format.MaxExponent) { + return makeUnexpected(ErrorCode::ParseError, "floating-point constant overflowed type"); + } + ++Result.Exponent; + } + Result.Scale = Result.Exponent - (Format.Precision - 1U); + return Result; + } + + if (Result.Significand.bitWidth() >= Format.Precision) { + Result.IsSubnormal = false; + Result.Exponent = Format.MinExponent; + Result.Scale = Result.Exponent - (Format.Precision - 1U); + return Result; + } + Result.IsSubnormal = true; + Result.Exponent = Format.MinExponent; + Result.Scale = SmallestUnit; + return Result; +} + +void appendSignificandWords(std::vector<std::uint64_t> &Words, const BigUnsigned &Significand) { + for (std::size_t Index = 0; Index < Words.size(); ++Index) + Words[Index] = Significand.word(Index); +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +packIEEEBinaryFloat(const BinaryFloatFormat &Format, const RoundedBinaryFloat &Value, + bool Negative) { + if (Format.HasExplicitIntegerBit || Format.ExponentBitWidth == 0 || Format.StorageBitWidth == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "floating-point format does not use IEEE binary storage"); + } + std::vector<std::uint64_t> Words((Format.StorageBitWidth + 63U) / 64U, 0); + if (Value.IsZero) { + if (Negative) + setFloatBit(Words, Format.StorageBitWidth - 1U); + return Words; + } + + appendSignificandWords(Words, Value.Significand); + const std::uint32_t FractionBitWidth = Format.Precision - 1U; + if (!Value.IsSubnormal) + clearFloatBit(Words, FractionBitWidth); + const std::int64_t Bias = 1 - static_cast<std::int64_t>(Format.MinExponent); + const std::uint64_t Exponent = + Value.IsSubnormal ? 0 : static_cast<std::uint64_t>(Value.Exponent + Bias); + for (std::uint32_t Bit = 0; Bit < Format.ExponentBitWidth; ++Bit) { + if ((Exponent & (std::uint64_t{1} << Bit)) != 0) + setFloatBit(Words, FractionBitWidth + Bit); + } + if (Negative) + setFloatBit(Words, Format.StorageBitWidth - 1U); + return Words; +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +packX87Float(const BinaryFloatFormat &Format, const RoundedBinaryFloat &Value, bool Negative) { + if (!Format.HasExplicitIntegerBit || Format.StorageBitWidth != 80 || Format.Precision != 64) { + return makeUnexpected(ErrorCode::VerificationError, "x87 floating-point format is invalid"); + } + std::vector<std::uint64_t> Words(2, 0); + if (Value.IsZero) { + if (Negative) + setFloatBit(Words, 79); + return Words; + } + Words[0] = Value.Significand.word(0); + if (!Value.IsSubnormal) { + const std::int64_t Bias = 1 - static_cast<std::int64_t>(Format.MinExponent); + Words[1] = static_cast<std::uint64_t>(Value.Exponent + Bias); + } + if (Negative) + setFloatBit(Words, 79); + return Words; +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +packPPCDoubleDouble(const RoundedBinaryFloat &Value, bool Negative) { + const auto DoubleFormat = binaryFloatFormat(FloatSemantics::IEEEDouble); + if (!DoubleFormat) + return makeUnexpected(ErrorCode::VerificationError, "IEEE double format is unavailable"); + if (Value.IsZero) { + RoundedBinaryFloat Zero = makeZeroRoundedBinaryFloat(); + auto High = packIEEEBinaryFloat(*DoubleFormat, Zero, Negative); + if (!High) + return std::unexpected(High.error()); + return std::vector<std::uint64_t>{High->front(), 0}; + } + + BigUnsigned One(1); + auto High = roundBinaryRational(*DoubleFormat, Value.Significand, One, Value.Scale, true); + if (!High) + return std::unexpected(High.error()); + auto HighWords = packIEEEBinaryFloat(*DoubleFormat, *High, Negative); + if (!HighWords) + return std::unexpected(HighWords.error()); + + BigUnsigned SourceMagnitude = Value.Significand; + BigUnsigned HighMagnitude = High->Significand; + const std::int64_t CommonScale = std::min(Value.Scale, High->Scale); + if (!SourceMagnitude.isZero()) { + const std::int64_t SourceShift = Value.Scale - CommonScale; + if (SourceShift > 0) + SourceMagnitude = SourceMagnitude.shiftedLeft(static_cast<std::size_t>(SourceShift)); + } + if (!HighMagnitude.isZero()) { + const std::int64_t HighShift = High->Scale - CommonScale; + if (HighShift > 0) + HighMagnitude = HighMagnitude.shiftedLeft(static_cast<std::size_t>(HighShift)); + } + + bool LowNegative = false; + BigUnsigned LowMagnitude; + if (SourceMagnitude.compare(HighMagnitude) >= 0) { + LowMagnitude = std::move(SourceMagnitude); + LowMagnitude.subtract(HighMagnitude); + LowNegative = Negative; + } else { + LowMagnitude = std::move(HighMagnitude); + LowMagnitude.subtract(SourceMagnitude); + LowNegative = !Negative; + } + + auto Low = roundBinaryRational(*DoubleFormat, LowMagnitude, One, CommonScale, true); + if (!Low) + return std::unexpected(Low.error()); + auto LowWords = packIEEEBinaryFloat(*DoubleFormat, *Low, LowNegative); + if (!LowWords) + return std::unexpected(LowWords.error()); + return std::vector<std::uint64_t>{HighWords->front(), LowWords->front()}; +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +convertRationalToFloatWords(FloatSemantics Semantics, bool Negative, const BigUnsigned &Numerator, + const BigUnsigned &Denominator, std::int64_t BinaryScale) { + const auto Format = binaryFloatFormat(Semantics); + if (!Format) { + return makeUnexpected(ErrorCode::Unsupported, + "floating-point literal uses unsupported semantics"); + } + auto Rounded = roundBinaryRational(*Format, Numerator, Denominator, BinaryScale); + if (!Rounded) + return std::unexpected(Rounded.error()); + if (Semantics == FloatSemantics::PPCDoubleDouble || + Semantics == FloatSemantics::PPCDoubleDoubleLegacy) { + return packPPCDoubleDouble(*Rounded, Negative); + } + if (Format->HasExplicitIntegerBit) + return packX87Float(*Format, *Rounded, Negative); + return packIEEEBinaryFloat(*Format, *Rounded, Negative); +} + +[[nodiscard]] bool isDecimalDigit(char Character) noexcept { + return Character >= '0' && Character <= '9'; +} + +[[nodiscard]] Expected<std::int32_t> parseFloatingExponent(std::string_view Text, + std::int32_t Limit) { + if (Text.empty()) + return makeUnexpected(ErrorCode::ParseError, "floating-point exponent has no digits"); + bool Negative = false; + if (Text.front() == '+' || Text.front() == '-') { + Negative = Text.front() == '-'; + Text.remove_prefix(1); + } + if (Text.empty()) + return makeUnexpected(ErrorCode::ParseError, "floating-point exponent has no digits"); + + std::int32_t Value = 0; + for (const char Character : Text) { + if (!isDecimalDigit(Character)) { + return makeUnexpected(ErrorCode::ParseError, + "floating-point exponent contains a non-decimal digit"); + } + const std::int32_t Digit = Character - '0'; + if (Value >= Limit || Value > (Limit - Digit) / 10) { + Value = Limit; + continue; + } + Value = Value * 10 + Digit; + } + return Negative ? -Value : Value; +} + +[[nodiscard]] BigUnsigned parseDecimalSignificand(std::string_view Digits) { + BigUnsigned Result; + std::size_t Position = 0; + std::size_t FirstChunkLength = Digits.size() % 9U; + if (FirstChunkLength == 0) + FirstChunkLength = std::min<std::size_t>(Digits.size(), 9U); + while (Position < Digits.size()) { + const std::size_t Length = Position == 0 ? FirstChunkLength : 9U; + std::uint32_t Chunk = 0; + for (std::size_t Index = 0; Index < Length; ++Index) + Chunk = Chunk * 10U + static_cast<std::uint32_t>(Digits[Position + Index] - '0'); + Result.multiplySmall(Position == 0 ? 1U : 1000000000U); + Result.addSmall(Chunk); + Position += Length; + } + return Result; +} + +[[nodiscard]] BigUnsigned parseHexSignificand(std::string_view Digits) { + BigUnsigned Result; + for (const char Character : Digits) { + Result.multiplySmall(16U); + Result.addSmall(hexDigitValue(Character)); + } + return Result; +} + +void multiplyByPowerOfFive(BigUnsigned &Value, std::uint64_t Exponent) { + for (std::uint64_t Index = 0; Index < Exponent; ++Index) + Value.multiplySmall(5U); +} + +[[nodiscard]] BigUnsigned powerOfFive(std::uint64_t Exponent) { + BigUnsigned Result(1); + multiplyByPowerOfFive(Result, Exponent); + return Result; +} + +[[nodiscard]] bool decimalDefinitelyOverflows(const BinaryFloatFormat &Format, + std::int64_t NormalizedExponent) noexcept { + const __int128 Left = (static_cast<__int128>(NormalizedExponent) - 1) * 42039; + const __int128 Right = static_cast<__int128>(12655) * Format.MaxExponent; + return Left >= Right; +} + +[[nodiscard]] bool decimalDefinitelyUnderflows(const BinaryFloatFormat &Format, + std::int64_t NormalizedExponent) noexcept { + const __int128 Left = (static_cast<__int128>(NormalizedExponent) + 1) * 28738; + const __int128 Right = static_cast<__int128>(8651) * + (static_cast<std::int64_t>(Format.MinExponent) - Format.Precision); + return Left <= Right; +} + +struct ParsedFiniteFloatLiteral final { + bool Negative = false; + BigUnsigned Numerator; + BigUnsigned Denominator{1}; + std::int64_t BinaryScale = 0; +}; + +[[nodiscard]] Expected<ParsedFiniteFloatLiteral> +parseDecimalFloatLiteral(std::string_view Token, const BinaryFloatFormat &Format) { + ParsedFiniteFloatLiteral Result; + if (Token.starts_with('+') || Token.starts_with('-')) { + Result.Negative = Token.front() == '-'; + Token.remove_prefix(1); + } + if (Token.empty()) + return makeUnexpected(ErrorCode::ParseError, "floating-point literal has no digits"); + + std::size_t Position = 0; + const std::size_t IntegerBegin = Position; + while (Position < Token.size() && isDecimalDigit(Token[Position])) + ++Position; + if (IntegerBegin == Position || Position == Token.size() || Token[Position] != '.') { + return makeUnexpected(ErrorCode::ParseError, + "decimal floating-point literal requires a decimal point"); + } + + std::string Digits(Token.substr(IntegerBegin, Position - IntegerBegin)); + ++Position; + const std::size_t FractionBegin = Position; + while (Position < Token.size() && isDecimalDigit(Token[Position])) + ++Position; + Digits.append(Token.substr(FractionBegin, Position - FractionBegin)); + const std::size_t FractionDigits = Position - FractionBegin; + + std::int32_t Exponent = 0; + if (Position < Token.size() && (Token[Position] == 'e' || Token[Position] == 'E')) { + auto ParsedExponent = parseFloatingExponent(Token.substr(Position + 1U), 24000); + if (!ParsedExponent) + return std::unexpected(ParsedExponent.error()); + Exponent = *ParsedExponent; + Position = Token.size(); + } + if (Position != Token.size()) { + return makeUnexpected(ErrorCode::ParseError, + "decimal floating-point literal has an invalid suffix"); + } + + const std::size_t FirstSignificant = Digits.find_first_not_of('0'); + if (FirstSignificant == std::string::npos) + return Result; + const std::size_t LastSignificant = Digits.find_last_not_of('0'); + const std::string_view Significant(Digits.data() + FirstSignificant, + LastSignificant - FirstSignificant + 1U); + const std::size_t TrailingZeroCount = Digits.size() - LastSignificant - 1U; + std::int64_t DecimalScale = static_cast<std::int64_t>(Exponent) - + static_cast<std::int64_t>(FractionDigits) + + static_cast<std::int64_t>(TrailingZeroCount); + const std::int64_t NormalizedExponent = + DecimalScale + static_cast<std::int64_t>(Significant.size() - 1U); + if (decimalDefinitelyOverflows(Format, NormalizedExponent)) { + return makeUnexpected(ErrorCode::ParseError, "floating-point constant overflowed type"); + } + if (decimalDefinitelyUnderflows(Format, NormalizedExponent)) { + return makeUnexpected(ErrorCode::ParseError, "floating-point constant underflowed type"); + } + + Result.Numerator = parseDecimalSignificand(Significant); + Result.BinaryScale = DecimalScale; + if (DecimalScale >= 0) { + multiplyByPowerOfFive(Result.Numerator, static_cast<std::uint64_t>(DecimalScale)); + } else { + Result.Denominator = powerOfFive(static_cast<std::uint64_t>(-(DecimalScale + 1)) + 1U); + } + return Result; +} + +[[nodiscard]] Expected<ParsedFiniteFloatLiteral> parseHexFloatLiteral(std::string_view Token) { + ParsedFiniteFloatLiteral Result; + if (Token.starts_with('+') || Token.starts_with('-')) { + Result.Negative = Token.front() == '-'; + Token.remove_prefix(1); + } + if (!Token.starts_with("0x")) { + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal must use the 0x prefix"); + } + std::size_t Position = 2; + const std::size_t IntegerBegin = Position; + while (Position < Token.size() && isHexDigit(Token[Position])) + ++Position; + if (IntegerBegin == Position || Position == Token.size() || Token[Position] != '.') { + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal requires a hexadecimal point"); + } + std::string Digits(Token.substr(IntegerBegin, Position - IntegerBegin)); + ++Position; + const std::size_t FractionBegin = Position; + while (Position < Token.size() && isHexDigit(Token[Position])) + ++Position; + Digits.append(Token.substr(FractionBegin, Position - FractionBegin)); + const std::size_t FractionDigits = Position - FractionBegin; + if (Position == Token.size() || (Token[Position] != 'p' && Token[Position] != 'P')) { + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal requires a binary exponent"); + } + auto Exponent = parseFloatingExponent(Token.substr(Position + 1U), 32767); + if (!Exponent) + return std::unexpected(Exponent.error()); + + const std::size_t FirstSignificant = Digits.find_first_not_of('0'); + if (FirstSignificant == std::string::npos) + return Result; + const std::string_view Significant(Digits.data() + FirstSignificant, + Digits.size() - FirstSignificant); + Result.Numerator = parseHexSignificand(Significant); + Result.BinaryScale = + static_cast<std::int64_t>(*Exponent) - static_cast<std::int64_t>(FractionDigits) * 4; + return Result; +} + +[[nodiscard]] Expected<std::uint64_t> parseRawDoubleBits(std::string_view Digits) { + if (Digits.empty()) + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal has no digits"); + std::uint64_t Result = 0; + for (const char Character : Digits) { + if (!isHexDigit(Character)) { + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal contains a non-hexadecimal digit"); + } + const std::uint64_t Digit = hexDigitValue(Character); + if (Result > (std::numeric_limits<std::uint64_t>::max() - Digit) / 16U) { + return makeUnexpected(ErrorCode::ParseError, + "hexadecimal floating-point literal does not fit IEEE double"); + } + Result = Result * 16U + Digit; + } + return Result; +} + +[[nodiscard]] int compareScaledBinaryIntegers(const BigUnsigned &Left, std::int64_t LeftScale, + const BigUnsigned &Right, std::int64_t RightScale) { + if (Left.isZero()) + return Right.isZero() ? 0 : -1; + if (Right.isZero()) + return 1; + if (LeftScale == RightScale) + return Left.compare(Right); + if (LeftScale > RightScale) + return -compareToShifted(Right, Left, static_cast<std::size_t>(LeftScale - RightScale)); + return compareToShifted(Left, Right, static_cast<std::size_t>(RightScale - LeftScale)); +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> +convertRawDoubleNaN(FloatSemantics Semantics, bool Negative, std::uint64_t Fraction) { + if (Semantics == FloatSemantics::IEEEDouble) { + return std::vector<std::uint64_t>{(Negative ? std::uint64_t{1} << 63U : 0) | + 0x7ff0000000000000ULL | Fraction}; + } + const auto Format = binaryFloatFormat(Semantics); + if (!Format || Format->HasExplicitIntegerBit || Format->StorageBitWidth > 64 || + (Semantics != FloatSemantics::IEEEHalf && Semantics != FloatSemantics::BFloat && + Semantics != FloatSemantics::IEEESingle)) { + return makeUnexpected(ErrorCode::ParseError, + "unprefixed hexadecimal literal is invalid for type"); + } + const std::uint32_t TargetFractionBits = Format->Precision - 1U; + const std::uint32_t DroppedBits = 53U - Format->Precision; + const std::uint64_t DroppedMask = (std::uint64_t{1} << DroppedBits) - 1U; + if ((Fraction & DroppedMask) != 0) { + return makeUnexpected( + ErrorCode::ParseError, + "hexadecimal floating-point literal is not exactly representable by its type"); + } + + const bool IsSignaling = (Fraction & (std::uint64_t{1} << 51U)) == 0; + std::uint64_t TargetFraction = Fraction >> DroppedBits; + const std::uint64_t QuietBit = std::uint64_t{1} << (TargetFractionBits - 1U); + if (IsSignaling) { + TargetFraction &= ~QuietBit; + if ((TargetFraction & (QuietBit - 1U)) == 0) + TargetFraction |= QuietBit >> 1U; + } else { + TargetFraction |= QuietBit; + } + const std::uint64_t Sign = Negative ? std::uint64_t{1} << (Format->StorageBitWidth - 1U) : 0; + const std::uint64_t Exponent = (std::uint64_t{1} << Format->ExponentBitWidth) - 1U; + return std::vector<std::uint64_t>{Sign | (Exponent << TargetFractionBits) | TargetFraction}; +} + +[[nodiscard]] Expected<std::vector<std::uint64_t>> convertRawDoubleLiteral(FloatSemantics Semantics, + std::uint64_t Bits) { + if (Semantics != FloatSemantics::IEEEHalf && Semantics != FloatSemantics::BFloat && + Semantics != FloatSemantics::IEEESingle && Semantics != FloatSemantics::IEEEDouble) { + return makeUnexpected(ErrorCode::ParseError, + "unprefixed hexadecimal literal is invalid for type"); + } + if (Semantics == FloatSemantics::IEEEDouble) + return std::vector<std::uint64_t>{Bits}; + + const bool Negative = (Bits >> 63U) != 0; + const std::uint64_t Exponent = (Bits >> 52U) & 0x7ffU; + const std::uint64_t Fraction = Bits & 0x000fffffffffffffULL; + if (Exponent == 0x7ffU) { + if (Fraction != 0) + return convertRawDoubleNaN(Semantics, Negative, Fraction); + SpecialFloatLiteral Infinity{.Kind = SpecialFloatLiteralKind::Infinity, .Negative = Negative}; + return makeSpecialFloatWords(Semantics, Infinity); + } + + const auto Format = binaryFloatFormat(Semantics); + if (!Format) + return makeUnexpected(ErrorCode::VerificationError, "IEEE target format is unavailable"); + if (Exponent == 0 && Fraction == 0) { + RoundedBinaryFloat Zero = makeZeroRoundedBinaryFloat(); + return packIEEEBinaryFloat(*Format, Zero, Negative); + } + + const bool IsSubnormal = Exponent == 0; + const std::uint64_t Significand = IsSubnormal ? Fraction : (std::uint64_t{1} << 52U) | Fraction; + const std::int64_t SourceScale = + (IsSubnormal ? -1022 : static_cast<std::int64_t>(Exponent) - 1023) - 52; + const BigUnsigned Source(Significand); + const BigUnsigned One(1); + auto Rounded = roundBinaryRational(*Format, Source, One, SourceScale); + if (!Rounded) + return std::unexpected(Rounded.error()); + if (compareScaledBinaryIntegers(Source, SourceScale, Rounded->Significand, Rounded->Scale) != 0) { + return makeUnexpected( + ErrorCode::ParseError, + "hexadecimal floating-point literal is not exactly representable by its type"); + } + return packIEEEBinaryFloat(*Format, *Rounded, Negative); +} + +[[nodiscard]] Expected<IntegerImmediate> parseExplicitHexInteger(std::uint32_t BitWidth, + std::string_view Spelling) { + if (Spelling.size() < 4 || (Spelling[0] != 'u' && Spelling[0] != 's') || Spelling[1] != '0' || + Spelling[2] != 'x') { + return makeUnexpected(ErrorCode::ParseError, + "integer hexadecimal spelling must use u0x or s0x"); + } + const std::string_view Digits = Spelling.substr(3); + if (Digits.empty() || !std::ranges::all_of(Digits, isHexDigit)) { + return makeUnexpected(ErrorCode::ParseError, + "integer hexadecimal spelling contains a non-hexadecimal digit"); + } + + const auto FirstNonZero = + std::ranges::find_if(Digits, [](char Character) { return hexDigitValue(Character) != 0; }); + std::size_t ActiveBits = 0; + if (FirstNonZero != Digits.end()) { + const unsigned MostSignificantNibble = hexDigitValue(*FirstNonZero); + unsigned MostSignificantBits = 0; + for (unsigned Value = MostSignificantNibble; Value != 0; Value >>= 1U) + ++MostSignificantBits; + ActiveBits = + static_cast<std::size_t>(Digits.end() - FirstNonZero - 1U) * 4U + MostSignificantBits; + } + + std::vector<std::uint64_t> Words((BitWidth + 63U) / 64U, 0); + std::size_t Nibble = 0; + for (auto Iterator = Digits.rbegin(); Iterator != Digits.rend(); ++Iterator, ++Nibble) { + const std::size_t BitOffset = Nibble * 4U; + if (BitOffset >= BitWidth) + break; + Words[BitOffset / 64U] |= static_cast<std::uint64_t>(hexDigitValue(*Iterator)) + << (BitOffset % 64U); + } + + // LLVM discards leading zero bits before it assigns signedness to an s0x + // literal. A nonzero signed spelling is therefore sign-extended from its + // active width, rather than from the number of written hexadecimal digits. + if (Spelling[0] == 's' && ActiveBits != 0 && ActiveBits < BitWidth) { + const std::size_t FirstExtendedBit = ActiveBits; + const std::size_t FirstWord = FirstExtendedBit / 64U; + const unsigned FirstBit = static_cast<unsigned>(FirstExtendedBit % 64U); + Words[FirstWord] |= std::numeric_limits<std::uint64_t>::max() << FirstBit; + for (std::size_t Index = FirstWord + 1U; Index < Words.size(); ++Index) + Words[Index] = std::numeric_limits<std::uint64_t>::max(); + } + if ((BitWidth % 64U) != 0) + Words.back() &= (std::uint64_t{1} << (BitWidth % 64U)) - 1U; + return IntegerImmediate::create(BitWidth, Words); +} + +[[nodiscard]] std::optional<FloatSemantics> floatingSemantics(std::string_view Name) noexcept { + if (Name == "half") + return FloatSemantics::IEEEHalf; + if (Name == "bfloat") + return FloatSemantics::BFloat; + if (Name == "float") + return FloatSemantics::IEEESingle; + if (Name == "double") + return FloatSemantics::IEEEDouble; + if (Name == "fp128") + return FloatSemantics::IEEEQuad; + if (Name == "ppc_fp128") + return FloatSemantics::PPCDoubleDouble; + if (Name == "x86_fp80") + return FloatSemantics::X87DoubleExtended; + return std::nullopt; +} + +[[nodiscard]] Expected<std::uint64_t> checkedMultiply(std::uint64_t Left, std::uint64_t Right, + std::string_view Description) { + if (Left != 0 && Right > std::numeric_limits<std::uint64_t>::max() / Left) + return makeUnexpected(ErrorCode::Unsupported, std::string(Description) + " is too large"); + return Left * Right; +} + +[[nodiscard]] Expected<std::uint64_t> checkedAdd(std::uint64_t Left, std::uint64_t Right, + std::string_view Description) { + if (Right > std::numeric_limits<std::uint64_t>::max() - Left) + return makeUnexpected(ErrorCode::Unsupported, std::string(Description) + " is too large"); + return Left + Right; +} + +[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t AlignmentValue) { + if (AlignmentValue == 0 || !std::has_single_bit(AlignmentValue)) + return makeUnexpected(ErrorCode::InvalidArgument, "LLVM type alignment is invalid"); + const std::uint64_t Mask = AlignmentValue - 1U; + if (Value > std::numeric_limits<std::uint64_t>::max() - Mask) + return makeUnexpected(ErrorCode::Unsupported, "LLVM type allocation size is too large"); + return (Value + Mask) & ~Mask; +} + +[[nodiscard]] const GenericConstantType &resolvedType(const GenericConstantType &Type) noexcept { + const GenericConstantType *Resolved = &Type; + std::size_t Depth = 0; + while (Resolved->Kind == GenericConstantTypeKind::Named && Resolved->Elements.size() == 1 && + Depth++ < 1024) { + Resolved = &Resolved->Elements.front(); + } + return *Resolved; +} + +class ConstantParser final { +public: + ConstantParser(std::string_view SourceText, LLVMConstantSyntaxOptions ParserOptions, + std::vector<std::string> ResolvingTypes = {}) + : Text(SourceText), Options(ParserOptions), ResolvingNamedTypes(std::move(ResolvingTypes)) {} + + [[nodiscard]] Expected<GenericConstantData> parse(); + [[nodiscard]] Expected<GenericConstantType> parseTypeOnly(); + +private: + template <typename T> [[nodiscard]] Expected<T> fail(std::string_view Message) const { + return makeUnexpected(ErrorCode::ParseError, "LLVM constant at byte " + + std::to_string(Position) + ": " + + std::string(Message)); + } + + void skipSpace() noexcept { + while (Position < Text.size() && isSpace(Text[Position])) + ++Position; + } + + [[nodiscard]] bool atEnd() { + skipSpace(); + return Position == Text.size(); + } + + [[nodiscard]] bool consume(char Character) { + skipSpace(); + if (Position >= Text.size() || Text[Position] != Character) + return false; + ++Position; + return true; + } + + [[nodiscard]] bool consumeWord(std::string_view Word) { + skipSpace(); + if (!Text.substr(Position).starts_with(Word)) + return false; + const std::size_t End = Position + Word.size(); + if (End < Text.size() && isIdentifierBody(Text[End])) + return false; + Position = End; + return true; + } + + [[nodiscard]] std::optional<std::string_view> peekWord() { + skipSpace(); + if (Position >= Text.size() || !isIdentifierStart(Text[Position])) + return std::nullopt; + std::size_t End = Position + 1; + while (End < Text.size() && isIdentifierBody(Text[End])) + ++End; + return Text.substr(Position, End - Position); + } + + [[nodiscard]] Expected<void> expect(char Character, std::string_view Description) { + if (!consume(Character)) + return fail<void>("expected " + std::string(Description)); + return {}; + } + + [[nodiscard]] Expected<void> expectWord(std::string_view Word) { + if (!consumeWord(Word)) + return fail<void>("expected '" + std::string(Word) + "'"); + return {}; + } + + [[nodiscard]] Expected<std::uint64_t> parseUnsigned(); + [[nodiscard]] Expected<ParsedName> parseName(char Prefix); + [[nodiscard]] Expected<std::vector<std::byte>> parseByteString(); + [[nodiscard]] Expected<GenericConstantType> parseType(std::size_t Depth = 0); + [[nodiscard]] Expected<void> resolveNamedType(GenericConstantType &Type, std::size_t Depth); + [[nodiscard]] Expected<GenericConstantType> parsePrimaryType(std::size_t Depth); + [[nodiscard]] Expected<GenericConstantData> parseTypedValue(std::size_t Depth); + [[nodiscard]] Expected<GenericConstantData> parseValue(GenericConstantType Type, + std::size_t Depth); + [[nodiscard]] Expected<GenericConstantData> + parseAggregate(GenericConstantType Type, char Open, char Close, bool Packed, std::size_t Depth); + [[nodiscard]] Expected<GenericConstantData> parseFloatingValue(GenericConstantType Type, + std::size_t Depth); + [[nodiscard]] Expected<GenericConstantData> + parseExpression(GenericConstantType Type, std::string_view Opcode, std::size_t Depth); + + [[nodiscard]] Expected<void> accountNode(std::size_t Depth) { + if (Depth > Options.MaximumDepth || ++Nodes > Options.MaximumNodes) + return fail<void>("constant exceeds configured structural limits"); + return {}; + } + + [[nodiscard]] Expected<std::string_view> parseValueToken(); + + std::string_view Text; + LLVMConstantSyntaxOptions Options; + std::size_t Position = 0; + std::size_t Nodes = 0; + std::vector<std::string> ResolvingNamedTypes; +}; + +Expected<std::uint64_t> ConstantParser::parseUnsigned() { + skipSpace(); + const std::size_t Begin = Position; + while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') + ++Position; + if (Begin == Position) + return fail<std::uint64_t>("expected an unsigned integer"); + std::uint64_t Value = 0; + const auto Parsed = std::from_chars(Text.data() + Begin, Text.data() + Position, Value, 10); + if (Parsed.ec != std::errc{}) + return fail<std::uint64_t>("unsigned integer is out of range"); + return Value; +} + +Expected<ParsedName> ConstantParser::parseName(char Prefix) { + skipSpace(); + if (Position >= Text.size() || Text[Position] != Prefix) + return fail<ParsedName>(std::string("expected '") + Prefix + "' identifier"); + ++Position; + if (Position >= Text.size()) + return fail<ParsedName>("identifier is empty"); + + ParsedName Result; + if (Text[Position] == '"') { + ++Position; + while (Position < Text.size() && Text[Position] != '"') { + if (Text[Position] != '\\') { + if (static_cast<unsigned char>(Text[Position]) < 0x20U) + return fail<ParsedName>("identifier contains a control character"); + Result.Value.push_back(Text[Position++]); + continue; + } + ++Position; + if (Position + 1 >= Text.size() || !isHexDigit(Text[Position]) || + !isHexDigit(Text[Position + 1])) { + return fail<ParsedName>("identifier escape requires two hexadecimal digits"); + } + Result.Value.push_back(static_cast<char>((hexDigitValue(Text[Position]) << 4U) | + hexDigitValue(Text[Position + 1]))); + Position += 2; + } + if (Position >= Text.size()) + return fail<ParsedName>("quoted identifier is unterminated"); + ++Position; + return Result; + } + + if (Text[Position] >= '0' && Text[Position] <= '9') { + Result.Numeric = true; + const std::size_t Begin = Position; + while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') + ++Position; + Result.Value = std::string(Text.substr(Begin, Position - Begin)); + return Result; + } + if (!isIdentifierStart(Text[Position])) + return fail<ParsedName>("identifier has an invalid first character"); + const std::size_t Begin = Position++; + while (Position < Text.size() && isIdentifierBody(Text[Position])) + ++Position; + Result.Value = std::string(Text.substr(Begin, Position - Begin)); + return Result; +} + +Expected<std::vector<std::byte>> ConstantParser::parseByteString() { + skipSpace(); + if (Position >= Text.size() || Text[Position] != '"') + return fail<std::vector<std::byte>>("expected an LLVM byte string"); + ++Position; + std::vector<std::byte> Bytes; + while (Position < Text.size() && Text[Position] != '"') { + const char Character = Text[Position++]; + if (Character != '\\') { + if (static_cast<unsigned char>(Character) < 0x20U) + return fail<std::vector<std::byte>>("byte string contains a control character"); + Bytes.push_back(static_cast<std::byte>(static_cast<unsigned char>(Character))); + continue; + } + if (Position + 1 >= Text.size() || !isHexDigit(Text[Position]) || + !isHexDigit(Text[Position + 1])) { + return fail<std::vector<std::byte>>("byte-string escape requires two hexadecimal digits"); + } + Bytes.push_back(static_cast<std::byte>((hexDigitValue(Text[Position]) << 4U) | + hexDigitValue(Text[Position + 1]))); + Position += 2; + } + if (Position >= Text.size()) + return fail<std::vector<std::byte>>("LLVM byte string is unterminated"); + ++Position; + return Bytes; +} + +Expected<GenericConstantType> ConstantParser::parsePrimaryType(std::size_t Depth) { + if (auto Limit = accountNode(Depth); !Limit) + return std::unexpected(Limit.error()); + skipSpace(); + + if (consume('[')) { + auto Count = parseUnsigned(); + if (!Count) + return std::unexpected(Count.error()); + if (auto Separator = expectWord("x"); !Separator) + return std::unexpected(Separator.error()); + auto Element = parseType(Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + if (auto End = expect(']', "']' after array type"); !End) + return std::unexpected(End.error()); + return GenericConstantType::array(*Count, std::move(*Element)); + } + + if (consume('<')) { + if (consume('{')) { + std::vector<GenericConstantType> Elements; + if (!consume('}')) { + while (true) { + auto Element = parseType(Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + Elements.push_back(std::move(*Element)); + if (consume('}')) + break; + if (auto Comma = expect(',', "',' between packed struct members"); !Comma) + return std::unexpected(Comma.error()); + } + } + if (auto End = expect('>', "'>' after packed struct type"); !End) + return std::unexpected(End.error()); + return GenericConstantType::structure(std::move(Elements), true); + } + + const bool Scalable = consumeWord("vscale"); + if (Scalable) { + if (auto Separator = expectWord("x"); !Separator) + return std::unexpected(Separator.error()); + } + auto Count = parseUnsigned(); + if (!Count) + return std::unexpected(Count.error()); + if (auto Separator = expectWord("x"); !Separator) + return std::unexpected(Separator.error()); + auto Element = parseType(Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + if (auto End = expect('>', "'>' after vector type"); !End) + return std::unexpected(End.error()); + return GenericConstantType::vector(*Count, std::move(*Element), Scalable); + } + + if (consume('{')) { + std::vector<GenericConstantType> Elements; + if (!consume('}')) { + while (true) { + auto Element = parseType(Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + Elements.push_back(std::move(*Element)); + if (consume('}')) + break; + if (auto Comma = expect(',', "',' between struct members"); !Comma) + return std::unexpected(Comma.error()); + } + } + return GenericConstantType::structure(std::move(Elements), false); + } + + skipSpace(); + if (Position < Text.size() && Text[Position] == '%') { + auto Name = parseName('%'); + if (!Name) + return std::unexpected(Name.error()); + return GenericConstantType::named(std::move(Name->Value), Name->Numeric); + } + + const auto Word = peekWord(); + if (!Word) + return fail<GenericConstantType>("expected an LLVM IR type"); + Position += Word->size(); + if (*Word == "void") + return GenericConstantType::primitive(GenericConstantTypeKind::Void); + if (*Word == "label") + return GenericConstantType::primitive(GenericConstantTypeKind::Label); + if (*Word == "token") + return GenericConstantType::primitive(GenericConstantTypeKind::Token); + if (*Word == "metadata") + return GenericConstantType::primitive(GenericConstantTypeKind::Metadata); + if (*Word == "byte") + return GenericConstantType::byte(); + if (Word->starts_with('i')) { + std::uint64_t Width = 0; + const auto Parsed = std::from_chars(Word->data() + 1, Word->data() + Word->size(), Width, 10); + if (Word->size() == 1 || Parsed.ec != std::errc{} || + Parsed.ptr != Word->data() + Word->size() || + Width > std::numeric_limits<std::uint32_t>::max()) { + return fail<GenericConstantType>("integer type has an invalid bit width"); + } + return GenericConstantType::integer(static_cast<std::uint32_t>(Width)); + } + if (const auto Semantics = floatingSemantics(*Word)) + return GenericConstantType::floating(*Semantics); + if (*Word == "ptr") { + std::uint32_t AddressSpace = 0; + if (consumeWord("addrspace")) { + if (auto Open = expect('(', "'(' after addrspace"); !Open) + return std::unexpected(Open.error()); + auto ParsedAddressSpace = parseUnsigned(); + if (!ParsedAddressSpace || *ParsedAddressSpace > std::numeric_limits<std::uint32_t>::max()) + return fail<GenericConstantType>("pointer address space is out of range"); + AddressSpace = static_cast<std::uint32_t>(*ParsedAddressSpace); + if (auto Close = expect(')', "')' after address space"); !Close) + return std::unexpected(Close.error()); + } + return GenericConstantType::pointer(AddressSpace); + } + return fail<GenericConstantType>("unsupported LLVM IR type '" + std::string(*Word) + "'"); +} + +Expected<GenericConstantType> ConstantParser::parseType(std::size_t Depth) { + auto Type = parsePrimaryType(Depth); + if (!Type) + return std::unexpected(Type.error()); + + while (true) { + skipSpace(); + if (consume('(')) { + std::vector<GenericConstantType> Parameters; + bool VarArg = false; + if (!consume(')')) { + while (true) { + skipSpace(); + if (Text.substr(Position).starts_with("...")) { + Position += 3; + VarArg = true; + if (auto End = expect(')', "')' after variadic function type"); !End) + return std::unexpected(End.error()); + break; + } + auto Parameter = parseType(Depth + 1); + if (!Parameter) + return std::unexpected(Parameter.error()); + Parameters.push_back(std::move(*Parameter)); + if (consume(')')) + break; + if (auto Comma = expect(',', "',' between function parameters"); !Comma) + return std::unexpected(Comma.error()); + } + } + Type = GenericConstantType::function(std::move(*Type), std::move(Parameters), VarArg); + if (!Type) + return std::unexpected(Type.error()); + continue; + } + + std::uint32_t AddressSpace = 0; + const std::size_t BeforeAddressSpace = Position; + if (consumeWord("addrspace")) { + if (auto Open = expect('(', "'(' after addrspace"); !Open) + return std::unexpected(Open.error()); + auto ParsedAddressSpace = parseUnsigned(); + if (!ParsedAddressSpace || *ParsedAddressSpace > std::numeric_limits<std::uint32_t>::max()) + return fail<GenericConstantType>("pointer address space is out of range"); + AddressSpace = static_cast<std::uint32_t>(*ParsedAddressSpace); + if (auto Close = expect(')', "')' after address space"); !Close) + return std::unexpected(Close.error()); + } + if (consume('*')) { + Type = GenericConstantType::pointer(AddressSpace); + continue; + } + Position = BeforeAddressSpace; + break; + } + if (Type->Kind == GenericConstantTypeKind::Named) { + if (auto Resolved = resolveNamedType(*Type, Depth + 1); !Resolved) + return std::unexpected(Resolved.error()); + } + return Type; +} + +Expected<void> ConstantParser::resolveNamedType(GenericConstantType &Type, std::size_t Depth) { + if (Type.Kind != GenericConstantTypeKind::Named || !Type.Elements.empty()) + return {}; + const auto Definition = + std::ranges::find_if(Options.NamedTypes, [&Type](const LLVMNamedTypeDefinition &Candidate) { + return Candidate.Name == Type.Name && Candidate.NumericName == Type.NumericName; + }); + if (Definition == Options.NamedTypes.end()) + return {}; + if (Depth > Options.MaximumDepth || + std::ranges::find(ResolvingNamedTypes, Type.Name) != ResolvingNamedTypes.end()) { + return fail<void>("named LLVM type definition is recursively unsized"); + } + std::vector<std::string> Stack = ResolvingNamedTypes; + Stack.push_back(Type.Name); + ConstantParser DefinitionParser(Definition->Definition, Options, std::move(Stack)); + auto Resolved = DefinitionParser.parseTypeOnly(); + if (!Resolved) + return std::unexpected(Resolved.error()); + Type.Elements.push_back(std::move(*Resolved)); + return {}; +} + +Expected<std::string_view> ConstantParser::parseValueToken() { + skipSpace(); + const std::size_t Begin = Position; + unsigned ParenthesisDepth = 0; + while (Position < Text.size()) { + const char Character = Text[Position]; + if (Character == '(') { + ++ParenthesisDepth; + ++Position; + continue; + } + if (Character == ')') { + if (ParenthesisDepth == 0) + break; + --ParenthesisDepth; + ++Position; + continue; + } + if (ParenthesisDepth == 0 && (isSpace(Character) || Character == ',' || Character == ']' || + Character == '}' || Character == '>')) { + break; + } + ++Position; + if (Position - Begin > Options.MaximumTokenLength) + return fail<std::string_view>("value token exceeds the configured length limit"); + } + if (Begin == Position) + return fail<std::string_view>("expected an LLVM constant value"); + return Text.substr(Begin, Position - Begin); +} + +Expected<GenericConstantData> ConstantParser::parseFloatingValue(GenericConstantType Type, + std::size_t Depth) { + const GenericConstantType &EffectiveType = resolvedType(Type); + if (EffectiveType.Kind != GenericConstantTypeKind::Floating) + return fail<GenericConstantData>("floating literal requires a floating-point type"); + auto Token = parseValueToken(); + if (!Token) + return std::unexpected(Token.error()); + const std::uint32_t BitWidth = getFloatSemanticsBitWidth(EffectiveType.Semantics).value_or(0); + const auto Format = binaryFloatFormat(EffectiveType.Semantics); + if (!Format) + return fail<GenericConstantData>("floating-point type has unsupported semantics"); + std::vector<std::uint64_t> Words; + + auto SpecialLiteral = parseSpecialFloatLiteral(*Token); + if (!SpecialLiteral) + return fail<GenericConstantData>(SpecialLiteral.error().message()); + if (*SpecialLiteral) { + auto SpecialWords = makeSpecialFloatWords(EffectiveType.Semantics, **SpecialLiteral); + if (!SpecialWords) + return fail<GenericConstantData>(SpecialWords.error().message()); + Words = std::move(*SpecialWords); + } else if (Token->starts_with("f0x")) { + const std::string_view Digits = Token->substr(3); + if (Digits.size() != (static_cast<std::size_t>(BitWidth) + 3U) / 4U || + !parseHexWords(Digits, BitWidth, Words)) { + return fail<GenericConstantData>("exact floating-point bit pattern does not fit its type"); + } + } else if (Token->starts_with("0x") && Token->find('.') != std::string_view::npos) { + auto Literal = parseHexFloatLiteral(*Token); + if (!Literal) + return fail<GenericConstantData>(Literal.error().message()); + auto Converted = + convertRationalToFloatWords(EffectiveType.Semantics, Literal->Negative, Literal->Numerator, + Literal->Denominator, Literal->BinaryScale); + if (!Converted) + return fail<GenericConstantData>(Converted.error().message()); + Words = std::move(*Converted); + } else if (Token->starts_with("0x")) { + std::string_view Digits = Token->substr(2); + char Prefix = '\0'; + if (!Digits.empty() && !isHexDigit(Digits.front())) { + Prefix = Digits.front(); + Digits.remove_prefix(1); + } + char ExpectedPrefix = '\0'; + switch (EffectiveType.Semantics) { + case FloatSemantics::IEEEHalf: + ExpectedPrefix = 'H'; + break; + case FloatSemantics::BFloat: + ExpectedPrefix = 'R'; + break; + case FloatSemantics::X87DoubleExtended: + ExpectedPrefix = 'K'; + break; + case FloatSemantics::IEEEQuad: + ExpectedPrefix = 'L'; + break; + case FloatSemantics::PPCDoubleDouble: + case FloatSemantics::PPCDoubleDoubleLegacy: + ExpectedPrefix = 'M'; + break; + default: + break; + } + if (ExpectedPrefix != '\0') { + const bool IsLegacyWideFormat = + EffectiveType.Semantics == FloatSemantics::IEEEQuad || + EffectiveType.Semantics == FloatSemantics::PPCDoubleDouble || + EffectiveType.Semantics == FloatSemantics::PPCDoubleDoubleLegacy; + const bool Parsed = IsLegacyWideFormat ? parseLegacyWideFloatHexWords(Digits, Words) + : parseHexWords(Digits, BitWidth, Words); + if (Prefix != ExpectedPrefix || !Parsed) { + return fail<GenericConstantData>( + "floating-point hexadecimal prefix does not match its type"); + } + } else if (Prefix != '\0') { + return fail<GenericConstantData>("unexpected floating-point hexadecimal prefix"); + } else { + auto DoubleBits = parseRawDoubleBits(Digits); + if (!DoubleBits) + return fail<GenericConstantData>(DoubleBits.error().message()); + auto Converted = convertRawDoubleLiteral(EffectiveType.Semantics, *DoubleBits); + if (!Converted) + return fail<GenericConstantData>(Converted.error().message()); + Words = std::move(*Converted); + } + } else { + auto Literal = parseDecimalFloatLiteral(*Token, *Format); + if (!Literal) + return fail<GenericConstantData>(Literal.error().message()); + auto Converted = + convertRationalToFloatWords(EffectiveType.Semantics, Literal->Negative, Literal->Numerator, + Literal->Denominator, Literal->BinaryScale); + if (!Converted) + return fail<GenericConstantData>(Converted.error().message()); + Words = std::move(*Converted); + } + + auto Bits = FloatingPointBits::create(EffectiveType.Semantics, Words); + if (!Bits) + return std::unexpected(Bits.error()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::Floating; + Value.FloatingValue = *Bits; + if (auto Limit = accountNode(Depth); !Limit) + return std::unexpected(Limit.error()); + return Value; +} + +Expected<GenericConstantData> ConstantParser::parseAggregate(GenericConstantType Type, char Open, + char Close, bool Packed, + std::size_t Depth) { + if (!consume(Open)) + return fail<GenericConstantData>(std::string("expected '") + Open + "' for aggregate"); + if (Packed && !consume('{')) + return fail<GenericConstantData>("expected '{' in packed struct constant"); + + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::Aggregate; + const char InnerClose = Packed ? '}' : Close; + if (!consume(InnerClose)) { + while (true) { + auto Element = parseTypedValue(Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + Value.Elements.push_back(std::move(*Element)); + if (consume(InnerClose)) + break; + if (auto Comma = expect(',', "',' between aggregate elements"); !Comma) + return std::unexpected(Comma.error()); + } + } + if (Packed) { + if (auto End = expect('>', "'>' after packed struct constant"); !End) + return std::unexpected(End.error()); + } + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; +} + +Expected<GenericConstantData> ConstantParser::parseTypedValue(std::size_t Depth) { + auto Type = parseType(Depth + 1); + if (!Type) + return std::unexpected(Type.error()); + return parseValue(std::move(*Type), Depth + 1); +} + +Expected<GenericConstantData> ConstantParser::parseExpression(GenericConstantType Type, + std::string_view Opcode, + std::size_t Depth) { + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::Expression; + + const auto SetOpcode = [&](GenericConstantExpressionOpcode ParsedOpcode) { + Value.ExpressionOpcode = ParsedOpcode; + }; + if (Opcode == "trunc") + SetOpcode(GenericConstantExpressionOpcode::Trunc); + else if (Opcode == "bitcast") + SetOpcode(GenericConstantExpressionOpcode::BitCast); + else if (Opcode == "addrspacecast") + SetOpcode(GenericConstantExpressionOpcode::AddressSpaceCast); + else if (Opcode == "inttoptr") + SetOpcode(GenericConstantExpressionOpcode::IntToPtr); + else if (Opcode == "ptrtoaddr") + SetOpcode(GenericConstantExpressionOpcode::PtrToAddr); + else if (Opcode == "ptrtoint") + SetOpcode(GenericConstantExpressionOpcode::PtrToInt); + else if (Opcode == "add") + SetOpcode(GenericConstantExpressionOpcode::Add); + else if (Opcode == "sub") + SetOpcode(GenericConstantExpressionOpcode::Sub); + else if (Opcode == "xor") + SetOpcode(GenericConstantExpressionOpcode::Xor); + else if (Opcode == "getelementptr") + SetOpcode(GenericConstantExpressionOpcode::GetElementPtr); + else if (Opcode == "shufflevector") + SetOpcode(GenericConstantExpressionOpcode::ShuffleVector); + else if (Opcode == "insertelement") + SetOpcode(GenericConstantExpressionOpcode::InsertElement); + else if (Opcode == "extractelement") + SetOpcode(GenericConstantExpressionOpcode::ExtractElement); + else if (Opcode == "splat") + SetOpcode(GenericConstantExpressionOpcode::Splat); + else + return fail<GenericConstantData>("unsupported constant expression '" + std::string(Opcode) + + "'"); + + const bool IsCast = Value.ExpressionOpcode == GenericConstantExpressionOpcode::Trunc || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::BitCast || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::AddressSpaceCast || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::IntToPtr || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::PtrToAddr || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::PtrToInt; + const bool IsBinary = Value.ExpressionOpcode == GenericConstantExpressionOpcode::Add || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::Sub || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::Xor; + + if (IsBinary) { + while (true) { + if (consumeWord("nuw")) { + Value.ExpressionFlags.set(GenericConstantExpressionFlag::NoUnsignedWrap); + continue; + } + if (consumeWord("nsw")) { + Value.ExpressionFlags.set(GenericConstantExpressionFlag::NoSignedWrap); + continue; + } + break; + } + } else if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::GetElementPtr) { + while (true) { + if (consumeWord("inbounds")) { + Value.ExpressionFlags.set(GenericConstantExpressionFlag::InBounds); + continue; + } + if (consumeWord("nusw")) { + Value.ExpressionFlags.set(GenericConstantExpressionFlag::NoUnsignedSignedWrap); + continue; + } + if (consumeWord("nuw")) { + Value.ExpressionFlags.set(GenericConstantExpressionFlag::GEPNoUnsignedWrap); + continue; + } + break; + } + if (consumeWord("inrange")) { + if (auto Open = expect('(', "'(' after inrange"); !Open) + return std::unexpected(Open.error()); + auto StartToken = parseValueToken(); + if (!StartToken) + return std::unexpected(StartToken.error()); + auto Start = IntegerImmediate::parseDecimal(64, *StartToken); + if (!Start) + return fail<GenericConstantData>("invalid inrange start"); + if (auto Comma = expect(',', "',' in inrange interval"); !Comma) + return std::unexpected(Comma.error()); + auto EndToken = parseValueToken(); + if (!EndToken) + return std::unexpected(EndToken.error()); + auto End = IntegerImmediate::parseDecimal(64, *EndToken); + if (!End) + return fail<GenericConstantData>("invalid inrange end"); + if (auto Close = expect(')', "')' after inrange interval"); !Close) + return std::unexpected(Close.error()); + Value.InRangeStart = std::move(*Start); + Value.InRangeEnd = std::move(*End); + } + } + + if (auto Open = expect('(', "'(' after constant-expression opcode"); !Open) + return std::unexpected(Open.error()); + if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::GetElementPtr) { + auto SourceType = parseType(Depth + 1); + if (!SourceType) + return std::unexpected(SourceType.error()); + Value.SourceElementType = std::move(*SourceType); + if (auto Comma = expect(',', "',' after getelementptr source type"); !Comma) + return std::unexpected(Comma.error()); + } + + auto First = parseTypedValue(Depth + 1); + if (!First) + return std::unexpected(First.error()); + Value.Elements.push_back(std::move(*First)); + if (IsCast) { + if (auto To = expectWord("to"); !To) + return std::unexpected(To.error()); + auto Destination = parseType(Depth + 1); + if (!Destination) + return std::unexpected(Destination.error()); + if (*Destination != Value.Type) + return fail<GenericConstantData>("constant-expression destination type does not match value"); + } else { + while (consume(',')) { + auto Operand = parseTypedValue(Depth + 1); + if (!Operand) + return std::unexpected(Operand.error()); + Value.Elements.push_back(std::move(*Operand)); + } + } + if (auto Close = expect(')', "')' after constant expression"); !Close) + return std::unexpected(Close.error()); + + const std::size_t RequiredOperands = [&]() -> std::size_t { + switch (Value.ExpressionOpcode) { + case GenericConstantExpressionOpcode::Trunc: + case GenericConstantExpressionOpcode::BitCast: + case GenericConstantExpressionOpcode::AddressSpaceCast: + case GenericConstantExpressionOpcode::IntToPtr: + case GenericConstantExpressionOpcode::PtrToAddr: + case GenericConstantExpressionOpcode::PtrToInt: + case GenericConstantExpressionOpcode::Splat: + return 1; + case GenericConstantExpressionOpcode::Add: + case GenericConstantExpressionOpcode::Sub: + case GenericConstantExpressionOpcode::Xor: + case GenericConstantExpressionOpcode::ExtractElement: + return 2; + case GenericConstantExpressionOpcode::InsertElement: + return 3; + case GenericConstantExpressionOpcode::ShuffleVector: + return 3; + case GenericConstantExpressionOpcode::GetElementPtr: + return 1; + case GenericConstantExpressionOpcode::Invalid: + return 0; + } + return 0; + }(); + if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::GetElementPtr) { + if (Value.Elements.empty() || + resolvedType(Value.Elements.front().Type).Kind != GenericConstantTypeKind::Pointer || + resolvedType(Value.Type).Kind != GenericConstantTypeKind::Pointer) { + return fail<GenericConstantData>("getelementptr constant has no base pointer"); + } + } else if (Value.Elements.size() != RequiredOperands) { + return fail<GenericConstantData>("constant expression has the wrong operand count"); + } + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; +} + +Expected<GenericConstantData> ConstantParser::parseValue(GenericConstantType Type, + std::size_t Depth) { + if (auto Limit = accountNode(Depth); !Limit) + return std::unexpected(Limit.error()); + skipSpace(); + + const auto Marker = [&](GenericConstantKind Kind) -> Expected<GenericConstantData> { + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = Kind; + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; + }; + if (consumeWord("zeroinitializer")) + return Marker(GenericConstantKind::ZeroInitializer); + if (consumeWord("undef")) + return Marker(GenericConstantKind::Undef); + if (consumeWord("poison")) + return Marker(GenericConstantKind::Poison); + if (consumeWord("null")) + return Marker(GenericConstantKind::NullPointer); + + const GenericConstantType &EffectiveType = resolvedType(Type); + + if (consumeWord("c")) { + auto Bytes = parseByteString(); + if (!Bytes) + return std::unexpected(Bytes.error()); + return GenericConstantData::byteString(std::move(Type), std::move(*Bytes)); + } + + if (EffectiveType.Kind == GenericConstantTypeKind::Array) + return parseAggregate(std::move(Type), '[', ']', false, Depth + 1); + if (EffectiveType.Kind == GenericConstantTypeKind::Struct) + return parseAggregate(std::move(Type), EffectiveType.Packed ? '<' : '{', '}', + EffectiveType.Packed, Depth + 1); + if (EffectiveType.Kind == GenericConstantTypeKind::Vector) { + if (consumeWord("splat")) + return parseExpression(std::move(Type), "splat", Depth + 1); + skipSpace(); + if (Position < Text.size() && Text[Position] == '<') + return parseAggregate(std::move(Type), '<', '>', false, Depth + 1); + } + + skipSpace(); + if (Position < Text.size() && Text[Position] == '@') { + auto Name = parseName('@'); + if (!Name) + return std::unexpected(Name.error()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::GlobalAddress; + Value.Symbol = std::move(Name->Value); + Value.NumericSymbol = Name->Numeric; + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; + } + + if (consumeWord("blockaddress")) { + if (auto Open = expect('(', "'(' after blockaddress"); !Open) + return std::unexpected(Open.error()); + auto Function = parseName('@'); + if (!Function) + return std::unexpected(Function.error()); + if (auto Comma = expect(',', "',' in blockaddress"); !Comma) + return std::unexpected(Comma.error()); + auto Block = parseName('%'); + if (!Block) + return std::unexpected(Block.error()); + if (auto Close = expect(')', "')' after blockaddress"); !Close) + return std::unexpected(Close.error()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::BlockAddress; + Value.Symbol = std::move(Function->Value); + Value.NumericSymbol = Function->Numeric; + Value.Block = std::move(Block->Value); + Value.NumericBlock = Block->Numeric; + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; + } + + for (const auto &[Keyword, Kind] : + {std::pair{std::string_view{"dso_local_equivalent"}, + GenericConstantKind::DSOLocalEquivalent}, + std::pair{std::string_view{"no_cfi"}, GenericConstantKind::NoCFI}}) { + if (!consumeWord(Keyword)) + continue; + auto Name = parseName('@'); + if (!Name) + return std::unexpected(Name.error()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = Kind; + Value.Symbol = std::move(Name->Value); + Value.NumericSymbol = Name->Numeric; + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; + } + + if (consumeWord("ptrauth")) { + if (auto Open = expect('(', "'(' after ptrauth"); !Open) + return std::unexpected(Open.error()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::PointerAuth; + while (true) { + auto Operand = parseTypedValue(Depth + 1); + if (!Operand) + return std::unexpected(Operand.error()); + Value.Elements.push_back(std::move(*Operand)); + if (consume(')')) + break; + if (auto Comma = expect(',', "',' in ptrauth constant"); !Comma) + return std::unexpected(Comma.error()); + } + auto Verification = Value.verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; + } + + const auto Word = peekWord(); + if (Word) { + constexpr std::array ExpressionNames{ + std::string_view{"trunc"}, + std::string_view{"bitcast"}, + std::string_view{"addrspacecast"}, + std::string_view{"inttoptr"}, + std::string_view{"ptrtoaddr"}, + std::string_view{"ptrtoint"}, + std::string_view{"add"}, + std::string_view{"sub"}, + std::string_view{"xor"}, + std::string_view{"getelementptr"}, + std::string_view{"shufflevector"}, + std::string_view{"insertelement"}, + std::string_view{"extractelement"}, + }; + if (std::ranges::find(ExpressionNames, *Word) != ExpressionNames.end()) { + Position += Word->size(); + return parseExpression(std::move(Type), *Word, Depth + 1); + } + } + + if (EffectiveType.Kind == GenericConstantTypeKind::Floating) + return parseFloatingValue(std::move(Type), Depth + 1); + if (EffectiveType.Kind == GenericConstantTypeKind::Integer || + EffectiveType.Kind == GenericConstantTypeKind::Byte) { + auto Token = parseValueToken(); + if (!Token) + return std::unexpected(Token.error()); + std::string_view Spelling = *Token; + if (Spelling == "true") + Spelling = "1"; + else if (Spelling == "false") + Spelling = "0"; + const std::uint32_t BitWidth = + EffectiveType.Kind == GenericConstantTypeKind::Byte ? 8U : EffectiveType.BitWidth; + const bool IsExplicitHex = Spelling.starts_with("u0x") || Spelling.starts_with("s0x"); + auto Integer = IsExplicitHex ? parseExplicitHexInteger(BitWidth, Spelling) + : IntegerImmediate::parseDecimal(BitWidth, Spelling); + if (!Integer) + return fail<GenericConstantData>(Integer.error().message()); + GenericConstantData Value; + Value.Type = std::move(Type); + Value.Kind = GenericConstantKind::Integer; + Value.IntegerValue = std::move(*Integer); + return Value; + } + return fail<GenericConstantData>("constant value is not valid for its LLVM IR type"); +} + +Expected<GenericConstantData> ConstantParser::parse() { + if (Text.empty()) + return fail<GenericConstantData>("constant text is empty"); + auto Value = parseTypedValue(0); + if (!Value) + return Value; + if (!atEnd()) + return fail<GenericConstantData>("unexpected trailing text"); + auto Verification = Value->verify(); + if (!Verification) + return fail<GenericConstantData>(Verification.error().message()); + return Value; +} + +Expected<GenericConstantType> ConstantParser::parseTypeOnly() { + if (Text.empty()) + return fail<GenericConstantType>("type text is empty"); + auto Type = parseType(0); + if (!Type) + return Type; + if (!atEnd()) + return fail<GenericConstantType>("unexpected trailing text after LLVM type"); + auto Verification = Type->verify(); + if (!Verification) + return fail<GenericConstantType>(Verification.error().message()); + return Type; +} + +[[nodiscard]] std::string hexadecimalWords(std::span<const std::uint64_t> Words, + std::uint32_t BitWidth) { + static constexpr std::string_view Digits = "0123456789ABCDEF"; + const std::size_t DigitCount = std::max<std::size_t>((BitWidth + 3U) / 4U, 1); + std::string Result(DigitCount, '0'); + for (std::size_t DigitIndex = 0; DigitIndex < DigitCount; ++DigitIndex) { + const std::size_t SourceNibble = DigitCount - 1U - DigitIndex; + const std::size_t WordIndex = SourceNibble / 16U; + const unsigned Shift = static_cast<unsigned>((SourceNibble % 16U) * 4U); + if (WordIndex < Words.size()) + Result[DigitIndex] = Digits[(Words[WordIndex] >> Shift) & 0xfU]; + } + return Result; +} + +[[nodiscard]] bool canUseBareName(std::string_view Name) noexcept { + if (Name.empty() || !isIdentifierStart(Name.front())) + return false; + return std::ranges::all_of(Name.substr(1), isIdentifierBody); +} + +[[nodiscard]] std::string printName(char Prefix, std::string_view Name, bool Numeric) { + std::string Result(1, Prefix); + if (Numeric || canUseBareName(Name)) { + Result.append(Name); + return Result; + } + static constexpr std::string_view HexDigits = "0123456789ABCDEF"; + Result.push_back('"'); + for (const char RawCharacter : Name) { + const auto Character = static_cast<unsigned char>(RawCharacter); + if (Character >= 0x20U && Character <= 0x7eU && Character != '"' && Character != '\\') { + Result.push_back(static_cast<char>(Character)); + continue; + } + Result.push_back('\\'); + Result.push_back(HexDigits[Character >> 4U]); + Result.push_back(HexDigits[Character & 0xfU]); + } + Result.push_back('"'); + return Result; +} + +[[nodiscard]] Expected<std::string> printType(const GenericConstantType &Type); + +[[nodiscard]] Expected<std::string> printTypeList(std::span<const GenericConstantType> Types, + std::string_view Open, std::string_view Close) { + std::string Result(Open); + for (std::size_t Index = 0; Index < Types.size(); ++Index) { + if (Index != 0) + Result.append(", "); + auto Element = printType(Types[Index]); + if (!Element) + return std::unexpected(Element.error()); + Result.append(*Element); + } + Result.append(Close); + return Result; +} + +Expected<std::string> printType(const GenericConstantType &Type) { + auto Verification = Type.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + switch (Type.Kind) { + case GenericConstantTypeKind::Invalid: + break; + case GenericConstantTypeKind::Void: + return std::string("void"); + case GenericConstantTypeKind::Label: + return std::string("label"); + case GenericConstantTypeKind::Token: + return std::string("token"); + case GenericConstantTypeKind::Metadata: + return std::string("metadata"); + case GenericConstantTypeKind::Integer: + return "i" + std::to_string(Type.BitWidth); + case GenericConstantTypeKind::Byte: + return std::string("byte"); + case GenericConstantTypeKind::Floating: + switch (Type.Semantics) { + case FloatSemantics::IEEEHalf: + return std::string("half"); + case FloatSemantics::BFloat: + return std::string("bfloat"); + case FloatSemantics::IEEESingle: + return std::string("float"); + case FloatSemantics::IEEEDouble: + return std::string("double"); + case FloatSemantics::IEEEQuad: + return std::string("fp128"); + case FloatSemantics::PPCDoubleDouble: + case FloatSemantics::PPCDoubleDoubleLegacy: + return std::string("ppc_fp128"); + case FloatSemantics::X87DoubleExtended: + return std::string("x86_fp80"); + default: + return makeUnexpected(ErrorCode::Unsupported, + "floating semantics has no LLVM IR type spelling"); + } + case GenericConstantTypeKind::Pointer: + if (Type.AddressSpace == 0) + return std::string("ptr"); + return "ptr addrspace(" + std::to_string(Type.AddressSpace) + ")"; + case GenericConstantTypeKind::Array: { + auto Element = printType(Type.Elements.front()); + if (!Element) + return std::unexpected(Element.error()); + return "[" + std::to_string(Type.ElementCount) + " x " + *Element + "]"; + } + case GenericConstantTypeKind::Vector: { + auto Element = printType(Type.Elements.front()); + if (!Element) + return std::unexpected(Element.error()); + std::string Result = "<"; + if (Type.Scalable) + Result.append("vscale x "); + Result.append(std::to_string(Type.ElementCount)); + Result.append(" x "); + Result.append(*Element); + Result.push_back('>'); + return Result; + } + case GenericConstantTypeKind::Struct: + return printTypeList(Type.Elements, Type.Packed ? "<{ " : "{ ", Type.Packed ? " }>" : " }"); + case GenericConstantTypeKind::Function: { + auto Return = printType(Type.Elements.front()); + if (!Return) + return std::unexpected(Return.error()); + std::string Result = *Return + " ("; + for (std::size_t Index = 1; Index < Type.Elements.size(); ++Index) { + if (Index != 1) + Result.append(", "); + auto Parameter = printType(Type.Elements[Index]); + if (!Parameter) + return std::unexpected(Parameter.error()); + Result.append(*Parameter); + } + if (Type.VarArg) { + if (Type.Elements.size() != 1) + Result.append(", "); + Result.append("..."); + } + Result.push_back(')'); + return Result; + } + case GenericConstantTypeKind::Named: + return printName('%', Type.Name, Type.NumericName); + } + return makeUnexpected(ErrorCode::VerificationError, "generic constant type is invalid"); +} + +[[nodiscard]] std::string_view +expressionOpcodeName(GenericConstantExpressionOpcode Opcode) noexcept { + switch (Opcode) { + case GenericConstantExpressionOpcode::Trunc: + return "trunc"; + case GenericConstantExpressionOpcode::BitCast: + return "bitcast"; + case GenericConstantExpressionOpcode::AddressSpaceCast: + return "addrspacecast"; + case GenericConstantExpressionOpcode::IntToPtr: + return "inttoptr"; + case GenericConstantExpressionOpcode::PtrToAddr: + return "ptrtoaddr"; + case GenericConstantExpressionOpcode::PtrToInt: + return "ptrtoint"; + case GenericConstantExpressionOpcode::Add: + return "add"; + case GenericConstantExpressionOpcode::Sub: + return "sub"; + case GenericConstantExpressionOpcode::Xor: + return "xor"; + case GenericConstantExpressionOpcode::GetElementPtr: + return "getelementptr"; + case GenericConstantExpressionOpcode::ShuffleVector: + return "shufflevector"; + case GenericConstantExpressionOpcode::InsertElement: + return "insertelement"; + case GenericConstantExpressionOpcode::ExtractElement: + return "extractelement"; + case GenericConstantExpressionOpcode::Splat: + return "splat"; + case GenericConstantExpressionOpcode::Invalid: + return {}; + } + return {}; +} + +[[nodiscard]] Expected<std::string> printValue(const GenericConstantData &Value, bool IncludeType); + +[[nodiscard]] Expected<void> appendTypedOperands(std::string &Output, + std::span<const GenericConstantData> Operands) { + for (std::size_t Index = 0; Index < Operands.size(); ++Index) { + if (Index != 0) + Output.append(", "); + auto Operand = printValue(Operands[Index], true); + if (!Operand) + return std::unexpected(Operand.error()); + Output.append(*Operand); + } + return {}; +} + +Expected<std::string> printValue(const GenericConstantData &Value, bool IncludeType) { + auto Verification = Value.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + std::string Result; + if (IncludeType) { + auto Type = printType(Value.Type); + if (!Type) + return std::unexpected(Type.error()); + Result.append(*Type); + Result.push_back(' '); + } + + switch (Value.Kind) { + case GenericConstantKind::Invalid: + break; + case GenericConstantKind::Integer: + if (resolvedType(Value.Type).Kind == GenericConstantTypeKind::Integer && + resolvedType(Value.Type).BitWidth == 1) + Result.append(Value.IntegerValue->isZero() ? "false" : "true"); + else + Result.append(Value.IntegerValue->toUnsignedDecimal()); + return Result; + case GenericConstantKind::Floating: + Result.append("f0x"); + Result.append(hexadecimalWords(Value.FloatingValue->words(), Value.FloatingValue->bitWidth())); + return Result; + case GenericConstantKind::NullPointer: + Result.append("null"); + return Result; + case GenericConstantKind::ZeroInitializer: + Result.append("zeroinitializer"); + return Result; + case GenericConstantKind::Undef: + Result.append("undef"); + return Result; + case GenericConstantKind::Poison: + Result.append("poison"); + return Result; + case GenericConstantKind::Aggregate: { + const GenericConstantType &EffectiveType = resolvedType(Value.Type); + const bool Packed = + EffectiveType.Kind == GenericConstantTypeKind::Struct && EffectiveType.Packed; + Result.append(Packed ? "<{ " + : EffectiveType.Kind == GenericConstantTypeKind::Array ? "[ " + : EffectiveType.Kind == GenericConstantTypeKind::Struct ? "{ " + : "< "); + auto Appended = appendTypedOperands(Result, Value.Elements); + if (!Appended) + return std::unexpected(Appended.error()); + Result.append(Packed ? " }>" + : EffectiveType.Kind == GenericConstantTypeKind::Array ? " ]" + : EffectiveType.Kind == GenericConstantTypeKind::Struct ? " }" + : " >"); + return Result; + } + case GenericConstantKind::ByteString: { + static constexpr std::string_view Digits = "0123456789ABCDEF"; + Result.append("c\""); + for (const std::byte Byte : Value.ByteValue) { + const unsigned Character = std::to_integer<unsigned>(Byte); + if (Character >= 0x20U && Character <= 0x7eU && Character != '"' && Character != '\\') { + Result.push_back(static_cast<char>(Character)); + } else { + Result.push_back('\\'); + Result.push_back(Digits[Character >> 4U]); + Result.push_back(Digits[Character & 0xfU]); + } + } + Result.push_back('"'); + return Result; + } + case GenericConstantKind::GlobalAddress: + Result.append(printName('@', Value.Symbol, Value.NumericSymbol)); + return Result; + case GenericConstantKind::BlockAddress: + Result.append("blockaddress("); + Result.append(printName('@', Value.Symbol, Value.NumericSymbol)); + Result.append(", "); + Result.append(printName('%', Value.Block, Value.NumericBlock)); + Result.push_back(')'); + return Result; + case GenericConstantKind::DSOLocalEquivalent: + Result.append("dso_local_equivalent "); + Result.append(printName('@', Value.Symbol, Value.NumericSymbol)); + return Result; + case GenericConstantKind::NoCFI: + Result.append("no_cfi "); + Result.append(printName('@', Value.Symbol, Value.NumericSymbol)); + return Result; + case GenericConstantKind::PointerAuth: + Result.append("ptrauth ("); + if (auto Appended = appendTypedOperands(Result, Value.Elements); !Appended) + return std::unexpected(Appended.error()); + Result.push_back(')'); + return Result; + case GenericConstantKind::Expression: { + const std::string_view Opcode = expressionOpcodeName(Value.ExpressionOpcode); + if (Opcode.empty()) + return makeUnexpected(ErrorCode::VerificationError, + "constant expression has an invalid opcode"); + Result.append(Opcode); + if (Value.ExpressionFlags.has(GenericConstantExpressionFlag::InBounds)) + Result.append(" inbounds"); + if (Value.ExpressionFlags.has(GenericConstantExpressionFlag::NoUnsignedSignedWrap)) + Result.append(" nusw"); + if (Value.ExpressionFlags.has(GenericConstantExpressionFlag::GEPNoUnsignedWrap) || + Value.ExpressionFlags.has(GenericConstantExpressionFlag::NoUnsignedWrap)) + Result.append(" nuw"); + if (Value.ExpressionFlags.has(GenericConstantExpressionFlag::NoSignedWrap)) + Result.append(" nsw"); + if (Value.InRangeStart) { + Result.append(" inrange("); + Result.append(std::to_string(std::bit_cast<std::int64_t>(Value.InRangeStart->words()[0]))); + Result.append(", "); + Result.append(std::to_string(std::bit_cast<std::int64_t>(Value.InRangeEnd->words()[0]))); + Result.push_back(')'); + } + Result.append(" ("); + if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::GetElementPtr) { + auto SourceType = printType(*Value.SourceElementType); + if (!SourceType) + return std::unexpected(SourceType.error()); + Result.append(*SourceType); + Result.append(", "); + } + auto First = printValue(Value.Elements.front(), true); + if (!First) + return std::unexpected(First.error()); + Result.append(*First); + const bool IsCast = + Value.ExpressionOpcode == GenericConstantExpressionOpcode::Trunc || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::BitCast || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::AddressSpaceCast || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::IntToPtr || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::PtrToAddr || + Value.ExpressionOpcode == GenericConstantExpressionOpcode::PtrToInt; + if (IsCast) { + auto Destination = printType(Value.Type); + if (!Destination) + return std::unexpected(Destination.error()); + Result.append(" to "); + Result.append(*Destination); + } else { + for (std::size_t Index = 1; Index < Value.Elements.size(); ++Index) { + Result.append(", "); + auto Operand = printValue(Value.Elements[Index], true); + if (!Operand) + return std::unexpected(Operand.error()); + Result.append(*Operand); + } + } + Result.push_back(')'); + return Result; + } + } + return makeUnexpected(ErrorCode::VerificationError, "generic constant value is invalid"); +} + +struct LayoutAlignment final { + std::uint64_t ABI = 1; + std::uint64_t Preferred = 1; +}; + +struct LayoutAlignmentEntry final { + char Kind = '\0'; + std::uint32_t BitWidth = 0; + LayoutAlignment Alignment; +}; + +struct LayoutPointerEntry final { + std::uint32_t AddressSpace = 0; + std::uint32_t SizeInBits = 0; + std::uint32_t IndexSizeInBits = 0; + LayoutAlignment Alignment; + bool HasExternalState = false; + bool HasUnstableRepresentation = false; + bool NullIsAllOnes = false; + std::string AddressSpaceName; +}; + +constexpr std::uint64_t MaximumDataLayoutSize = (std::uint64_t{1} << 24U) - 1U; +constexpr std::uint64_t MaximumDataLayoutAlignment = (std::uint64_t{1} << 16U) - 1U; + +class DataLayoutInfo final { +public: + [[nodiscard]] static Expected<DataLayoutInfo> parse(LLVMConstantSyntaxOptions Options); + + [[nodiscard]] bool isExplicit() const noexcept { + return Explicit; + } + + [[nodiscard]] Expected<LayoutPointerEntry> pointer(std::uint32_t AddressSpace) const; + [[nodiscard]] LayoutAlignment integer(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] LayoutAlignment floating(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + [[nodiscard]] LayoutAlignment vector(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + + [[nodiscard]] LayoutAlignment aggregate() const noexcept { + return Aggregate; + } + +private: + [[nodiscard]] static Expected<std::uint64_t> parseUnsignedField(std::string_view Text, + std::string_view Description); + [[nodiscard]] static Expected<std::uint64_t> alignmentBytes(std::string_view Text, bool AllowZero, + std::string_view Description); + [[nodiscard]] static std::vector<std::string_view> split(std::string_view Text, char Separator); + + void setAlignment(LayoutAlignmentEntry Entry); + void setPointer(LayoutPointerEntry Entry); + [[nodiscard]] LayoutAlignment natural(std::uint64_t StoreSize) const noexcept; + [[nodiscard]] LayoutAlignment scalarAlignment(char Kind, std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept; + + bool Explicit = false; + std::uint32_t LegacyPointerSizeInBits = 0; + std::vector<LayoutAlignmentEntry> Alignments; + std::vector<LayoutPointerEntry> Pointers; + LayoutAlignment Aggregate{1, 8}; +}; + +Expected<std::uint64_t> DataLayoutInfo::parseUnsignedField(std::string_view Text, + std::string_view Description) { + std::uint64_t Value = 0; + const auto Parsed = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); + if (Text.empty() || Parsed.ec != std::errc{} || Parsed.ptr != Text.data() + Text.size()) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM data layout has an invalid " + std::string(Description)); + } + return Value; +} + +Expected<std::uint64_t> DataLayoutInfo::alignmentBytes(std::string_view Text, bool AllowZero, + std::string_view Description) { + auto Bits = parseUnsignedField(Text, Description); + if (!Bits) + return std::unexpected(Bits.error()); + if (*Bits == 0 && AllowZero) + return 1; + if (*Bits > MaximumDataLayoutAlignment || *Bits < 8 || (*Bits % 8U) != 0 || + !std::has_single_bit(*Bits)) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM data layout " + std::string(Description) + + " must be a nonzero power-of-two byte alignment"); + } + return *Bits / 8U; +} + +std::vector<std::string_view> DataLayoutInfo::split(std::string_view Text, char Separator) { + std::vector<std::string_view> Parts; + while (true) { + const std::size_t Position = Text.find(Separator); + Parts.push_back(Text.substr(0, Position)); + if (Position == std::string_view::npos) + return Parts; + Text.remove_prefix(Position + 1U); + } +} + +void DataLayoutInfo::setAlignment(LayoutAlignmentEntry Entry) { + const auto Existing = + std::ranges::find_if(Alignments, [&Entry](const LayoutAlignmentEntry &Candidate) { + return Candidate.Kind == Entry.Kind && Candidate.BitWidth == Entry.BitWidth; + }); + if (Existing == Alignments.end()) + Alignments.push_back(Entry); + else + *Existing = Entry; +} + +void DataLayoutInfo::setPointer(LayoutPointerEntry Entry) { + const auto Existing = + std::ranges::find_if(Pointers, [&Entry](const LayoutPointerEntry &Candidate) { + return Candidate.AddressSpace == Entry.AddressSpace; + }); + if (Existing == Pointers.end()) + Pointers.push_back(Entry); + else + *Existing = Entry; +} + +Expected<DataLayoutInfo> DataLayoutInfo::parse(LLVMConstantSyntaxOptions Options) { + DataLayoutInfo Result; + Result.LegacyPointerSizeInBits = Options.DefaultPointerSizeInBits; + if (Options.DataLayout.empty()) + return Result; + if (Options.DataLayout.size() > Options.MaximumTokenLength) { + return makeUnexpected(ErrorCode::Unsupported, + "LLVM data layout exceeds the configured token length limit"); + } + Result.Explicit = true; + Result.setPointer({.AddressSpace = 0, + .SizeInBits = 64, + .IndexSizeInBits = 64, + .Alignment = {.ABI = 8, .Preferred = 8}, + .HasExternalState = false, + .HasUnstableRepresentation = false, + .NullIsAllOnes = false, + .AddressSpaceName = {}}); + for (const LayoutAlignmentEntry Entry : { + LayoutAlignmentEntry{'i', 1, {1, 1}}, + LayoutAlignmentEntry{'i', 8, {1, 1}}, + LayoutAlignmentEntry{'i', 16, {2, 2}}, + LayoutAlignmentEntry{'i', 32, {4, 4}}, + LayoutAlignmentEntry{'i', 64, {4, 8}}, + LayoutAlignmentEntry{'f', 16, {2, 2}}, + LayoutAlignmentEntry{'f', 32, {4, 4}}, + LayoutAlignmentEntry{'f', 64, {8, 8}}, + LayoutAlignmentEntry{'f', 128, {16, 16}}, + LayoutAlignmentEntry{'v', 64, {8, 8}}, + LayoutAlignmentEntry{'v', 128, {16, 16}}, + }) { + Result.setAlignment(Entry); + } + + for (const std::string_view Component : split(Options.DataLayout, '-')) { + if (Component.empty()) + return makeUnexpected(ErrorCode::ParseError, "LLVM data layout contains an empty component"); + if (Component.front() == 'p') { + const std::size_t Colon = Component.find(':'); + if (Colon == std::string_view::npos) + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer data layout component has no size"); + std::string_view AddressSpaceText = Component.substr(1, Colon - 1U); + bool HasExternalState = false; + bool HasUnstableRepresentation = false; + bool NullIsAllOnes = false; + bool HasExplicitNullValue = false; + while (!AddressSpaceText.empty()) { + const char Flag = AddressSpaceText.front(); + if (Flag == 'e') { + HasExternalState = true; + } else if (Flag == 'u') { + HasUnstableRepresentation = true; + } else if (Flag == 'z' || Flag == 'o') { + if (HasExplicitNullValue) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer data layout has conflicting null-pointer flags"); + } + HasExplicitNullValue = true; + NullIsAllOnes = Flag == 'o'; + } else { + break; + } + AddressSpaceText.remove_prefix(1); + } + + std::uint64_t AddressSpace = 0; + std::string AddressSpaceName; + if (!AddressSpaceText.empty()) { + std::size_t NumberLength = 0; + while (NumberLength < AddressSpaceText.size() && AddressSpaceText[NumberLength] >= '0' && + AddressSpaceText[NumberLength] <= '9') { + ++NumberLength; + } + if (NumberLength != 0) { + auto Parsed = + parseUnsignedField(AddressSpaceText.substr(0, NumberLength), "pointer address space"); + if (!Parsed) + return std::unexpected(Parsed.error()); + AddressSpace = *Parsed; + AddressSpaceText.remove_prefix(NumberLength); + } + if (!AddressSpaceText.empty()) { + if (AddressSpaceText.size() <= 2 || AddressSpaceText.front() != '(' || + AddressSpaceText.back() != ')') { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer address space or name is invalid"); + } + AddressSpaceName = std::string(AddressSpaceText.substr(1, AddressSpaceText.size() - 2)); + if (AddressSpaceName.size() == 1 && + (AddressSpaceName.front() == 'P' || AddressSpaceName.front() == 'G' || + AddressSpaceName.front() == 'A')) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer address space uses a reserved name"); + } + } + } + if (AddressSpace == 0 && (HasExternalState || HasUnstableRepresentation)) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM address space zero cannot be unstable or external"); + } + if (!AddressSpaceName.empty() && + std::ranges::any_of(Result.Pointers, + [&AddressSpaceName](const LayoutPointerEntry &Entry) { + return Entry.AddressSpaceName == AddressSpaceName; + })) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer address-space name is duplicated"); + } + const auto Fields = split(Component.substr(Colon + 1U), ':'); + if (Fields.size() < 2 || Fields.size() > 4 || AddressSpace > MaximumDataLayoutSize) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer data layout component has invalid fields"); + } + auto Size = parseUnsignedField(Fields[0], "pointer size"); + auto ABI = alignmentBytes(Fields[1], false, "pointer ABI alignment"); + auto Preferred = Fields.size() >= 3 + ? alignmentBytes(Fields[2], false, "pointer preferred alignment") + : ABI; + if (!Size || !ABI || !Preferred || *Size == 0 || *Size > MaximumDataLayoutSize || + *Preferred < *ABI) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM pointer data layout component is invalid"); + } + std::uint64_t IndexSize = *Size; + if (Fields.size() == 4) { + auto ParsedIndexSize = parseUnsignedField(Fields[3], "pointer index size"); + if (!ParsedIndexSize || *ParsedIndexSize == 0 || *ParsedIndexSize > MaximumDataLayoutSize || + *ParsedIndexSize > *Size) { + return makeUnexpected(ErrorCode::ParseError, "LLVM pointer index size is invalid"); + } + IndexSize = *ParsedIndexSize; + } + Result.setPointer({.AddressSpace = static_cast<std::uint32_t>(AddressSpace), + .SizeInBits = static_cast<std::uint32_t>(*Size), + .IndexSizeInBits = static_cast<std::uint32_t>(IndexSize), + .Alignment = {.ABI = *ABI, .Preferred = *Preferred}, + .HasExternalState = HasExternalState, + .HasUnstableRepresentation = HasUnstableRepresentation, + .NullIsAllOnes = NullIsAllOnes, + .AddressSpaceName = std::move(AddressSpaceName)}); + continue; + } + + if (Component == "ve") + continue; + + if (Component.front() == 'i' || Component.front() == 'f' || Component.front() == 'v') { + const std::size_t Colon = Component.find(':'); + if (Colon == std::string_view::npos) + return makeUnexpected(ErrorCode::ParseError, + "LLVM scalar data layout component has no alignment"); + auto Width = parseUnsignedField(Component.substr(1, Colon - 1U), "type alignment bit width"); + const auto Fields = split(Component.substr(Colon + 1U), ':'); + if (!Width || *Width == 0 || *Width > MaximumDataLayoutSize || Fields.empty() || + Fields.size() > 2) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM scalar data layout component has invalid fields"); + } + auto ABI = alignmentBytes(Fields[0], false, "type ABI alignment"); + auto Preferred = + Fields.size() == 2 ? alignmentBytes(Fields[1], false, "type preferred alignment") : ABI; + if (!ABI || !Preferred || *Preferred < *ABI) + return makeUnexpected(ErrorCode::ParseError, + "LLVM scalar data layout alignment is invalid"); + Result.setAlignment( + {Component.front(), static_cast<std::uint32_t>(*Width), {*ABI, *Preferred}}); + continue; + } + + if (Component.starts_with('a')) { + const auto Fields = split(Component.substr(1), ':'); + if (Fields.size() < 2 || Fields.size() > 3 || (!Fields[0].empty() && Fields[0] != "0")) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM aggregate data layout component has invalid fields"); + } + auto ABI = alignmentBytes(Fields[1], true, "aggregate ABI alignment"); + auto Preferred = Fields.size() == 3 + ? alignmentBytes(Fields[2], false, "aggregate preferred alignment") + : ABI; + if (!ABI || !Preferred || *Preferred < *ABI) + return makeUnexpected(ErrorCode::ParseError, + "LLVM aggregate data layout alignment is invalid"); + Result.Aggregate = {.ABI = *ABI, .Preferred = *Preferred}; + continue; + } + + if (Component == "e" || Component == "E" || Component == "s" || Component == "ve") + continue; + + if (Component.starts_with("ni:")) { + const auto AddressSpaces = split(Component.substr(3), ':'); + if (AddressSpaces.empty()) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM non-integral address-space component is empty"); + } + for (const std::string_view AddressSpace : AddressSpaces) { + auto Parsed = parseUnsignedField(AddressSpace, "non-integral address space"); + if (!Parsed || *Parsed == 0 || *Parsed > MaximumDataLayoutSize) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM non-integral address space is invalid"); + } + } + continue; + } + + if (Component.starts_with("m:")) { + const std::string_view Mode = Component.substr(2); + if (Mode.size() != 1 || (Mode.front() != 'e' && Mode.front() != 'l' && Mode.front() != 'o' && + Mode.front() != 'm' && Mode.front() != 'w' && Mode.front() != 'x' && + Mode.front() != 'a')) { + return makeUnexpected(ErrorCode::ParseError, "LLVM mangling mode is invalid"); + } + continue; + } + + if (Component.starts_with('n')) { + const auto Widths = split(Component.substr(1), ':'); + if (Widths.empty()) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM native integer-width component is empty"); + } + for (const std::string_view Width : Widths) { + auto Parsed = parseUnsignedField(Width, "native integer bit width"); + if (!Parsed || *Parsed == 0 || *Parsed > MaximumDataLayoutSize) { + return makeUnexpected(ErrorCode::ParseError, "LLVM native integer bit width is invalid"); + } + } + continue; + } + + if (Component.starts_with('S')) { + auto AlignmentValue = alignmentBytes(Component.substr(1), false, "stack natural alignment"); + if (!AlignmentValue) + return std::unexpected(AlignmentValue.error()); + continue; + } + + if (Component.starts_with('F')) { + const std::string_view Rest = Component.substr(1); + if (Rest.size() < 2 || (Rest.front() != 'i' && Rest.front() != 'n')) { + return makeUnexpected(ErrorCode::ParseError, + "LLVM function-pointer alignment component is invalid"); + } + auto AlignmentValue = alignmentBytes(Rest.substr(1), false, "function-pointer ABI alignment"); + if (!AlignmentValue) + return std::unexpected(AlignmentValue.error()); + continue; + } + + if (Component.starts_with('P') || Component.starts_with('G') || Component.starts_with('A')) { + auto AddressSpace = parseUnsignedField(Component.substr(1), "default address space"); + if (!AddressSpace || *AddressSpace > MaximumDataLayoutSize) { + return makeUnexpected(ErrorCode::ParseError, "LLVM default address space is invalid"); + } + continue; + } + + return makeUnexpected(ErrorCode::ParseError, "unsupported LLVM data layout component '" + + std::string(Component) + "'"); + } + return Result; +} + +LayoutAlignment DataLayoutInfo::natural(std::uint64_t StoreSize) const noexcept { + if (StoreSize == 0) + return {1, 1}; + constexpr std::uint64_t MaximumPowerOfTwo = std::uint64_t{1} << 63U; + const std::uint64_t LimitedSize = + std::min<std::uint64_t>(StoreSize, Explicit ? MaximumPowerOfTwo : 16U); + const std::uint64_t Alignment = std::bit_ceil(LimitedSize); + return {Alignment, Alignment}; +} + +LayoutAlignment DataLayoutInfo::scalarAlignment(char Kind, std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept { + if (!Explicit) + return natural(StoreSize); + const LayoutAlignmentEntry *Selected = nullptr; + for (const LayoutAlignmentEntry &Entry : Alignments) { + if (Entry.Kind != Kind) + continue; + if (Kind == 'i') { + if (Entry.BitWidth >= BitWidth && + (Selected == nullptr || Entry.BitWidth < Selected->BitWidth)) { + Selected = &Entry; + } else if (Selected == nullptr) { + Selected = &Entry; + } else if (Selected->BitWidth < BitWidth && Entry.BitWidth > Selected->BitWidth) { + Selected = &Entry; + } + } else if (Entry.BitWidth == BitWidth) { + Selected = &Entry; + break; + } + } + return Selected == nullptr ? natural(StoreSize) : Selected->Alignment; +} + +LayoutAlignment DataLayoutInfo::integer(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept { + return scalarAlignment('i', BitWidth, StoreSize); +} + +LayoutAlignment DataLayoutInfo::floating(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept { + return scalarAlignment('f', BitWidth, StoreSize); +} + +LayoutAlignment DataLayoutInfo::vector(std::uint32_t BitWidth, + std::uint64_t StoreSize) const noexcept { + return scalarAlignment('v', BitWidth, StoreSize); +} + +Expected<LayoutPointerEntry> DataLayoutInfo::pointer(std::uint32_t AddressSpace) const { + if (!Explicit) { + if (LegacyPointerSizeInBits == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "pointer size is required to lay out pointer constants"); + } + const std::uint64_t Bytes = (static_cast<std::uint64_t>(LegacyPointerSizeInBits) + 7U) / 8U; + return LayoutPointerEntry{ + .AddressSpace = AddressSpace, + .SizeInBits = LegacyPointerSizeInBits, + .IndexSizeInBits = LegacyPointerSizeInBits, + .Alignment = natural(Bytes), + .HasExternalState = false, + .HasUnstableRepresentation = false, + .NullIsAllOnes = false, + .AddressSpaceName = {}, + }; + } + auto Entry = std::ranges::find_if(Pointers, [AddressSpace](const LayoutPointerEntry &Candidate) { + return Candidate.AddressSpace == AddressSpace; + }); + if (Entry == Pointers.end()) { + Entry = std::ranges::find_if( + Pointers, [](const LayoutPointerEntry &Candidate) { return Candidate.AddressSpace == 0; }); + } + if (Entry == Pointers.end()) + return makeUnexpected(ErrorCode::InvalidArgument, "LLVM data layout has no pointer layout"); + return *Entry; +} + +struct TypeLayout final { + std::uint64_t StoreSize = 0; + std::uint64_t AllocationSize = 0; + std::uint64_t ABIAlignment = 1; + std::uint64_t PreferredAlignment = 1; +}; + +[[nodiscard]] Expected<TypeLayout> layoutType(const GenericConstantType &Type, + LLVMConstantSyntaxOptions Options, + const DataLayoutInfo &Layout, + std::vector<std::string> &NamedTypeStack, + std::size_t Depth) { + if (Depth > Options.MaximumDepth) + return makeUnexpected(ErrorCode::Unsupported, "LLVM type exceeds the layout depth limit"); + auto Verification = Type.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + const auto ScalarLayout = [&Layout](std::uint64_t StoreSize, + LayoutAlignment TypeAlignment) -> Expected<TypeLayout> { + std::uint64_t AllocationSize = StoreSize; + if (Layout.isExplicit()) { + auto Aligned = alignTo(StoreSize, TypeAlignment.ABI); + if (!Aligned) + return std::unexpected(Aligned.error()); + AllocationSize = *Aligned; + } + return TypeLayout{StoreSize, AllocationSize, TypeAlignment.ABI, TypeAlignment.Preferred}; + }; + + switch (Type.Kind) { + case GenericConstantTypeKind::Integer: + case GenericConstantTypeKind::Byte: { + const std::uint32_t Bits = Type.Kind == GenericConstantTypeKind::Byte ? 8U : Type.BitWidth; + const std::uint64_t StoreSize = (static_cast<std::uint64_t>(Bits) + 7U) / 8U; + return ScalarLayout(StoreSize, Layout.integer(Bits, StoreSize)); + } + case GenericConstantTypeKind::Floating: { + const std::uint32_t Bits = getFloatSemanticsBitWidth(Type.Semantics).value_or(0); + const std::uint64_t StoreSize = (static_cast<std::uint64_t>(Bits) + 7U) / 8U; + return ScalarLayout(StoreSize, Layout.floating(Bits, StoreSize)); + } + case GenericConstantTypeKind::Pointer: { + auto Pointer = Layout.pointer(Type.AddressSpace); + if (!Pointer) + return std::unexpected(Pointer.error()); + const std::uint64_t StoreSize = (static_cast<std::uint64_t>(Pointer->SizeInBits) + 7U) / 8U; + return ScalarLayout(StoreSize, Pointer->Alignment); + } + case GenericConstantTypeKind::Array: { + auto Element = layoutType(Type.Elements.front(), Options, Layout, NamedTypeStack, Depth + 1); + if (!Element) + return std::unexpected(Element.error()); + auto Stride = alignTo(Element->AllocationSize, Element->ABIAlignment); + if (!Stride) + return std::unexpected(Stride.error()); + auto Size = checkedMultiply(*Stride, Type.ElementCount, "LLVM array allocation size"); + if (!Size) + return std::unexpected(Size.error()); + return TypeLayout{*Size, *Size, Element->ABIAlignment, Element->PreferredAlignment}; + } + case GenericConstantTypeKind::Vector: { + if (Type.Scalable) + return makeUnexpected(ErrorCode::Unsupported, + "scalable vector has no fixed constant-pool size"); + const GenericConstantType &ElementType = resolvedType(Type.Elements.front()); + std::uint64_t ElementBits = 0; + if (ElementType.Kind == GenericConstantTypeKind::Integer) + ElementBits = ElementType.BitWidth; + else if (ElementType.Kind == GenericConstantTypeKind::Byte) + ElementBits = 8; + else if (ElementType.Kind == GenericConstantTypeKind::Floating) + ElementBits = getFloatSemanticsBitWidth(ElementType.Semantics).value_or(0); + else if (ElementType.Kind == GenericConstantTypeKind::Pointer) { + auto Pointer = Layout.pointer(ElementType.AddressSpace); + if (!Pointer) + return std::unexpected(Pointer.error()); + ElementBits = Pointer->SizeInBits; + } else { + return makeUnexpected(ErrorCode::Unsupported, + "LLVM vector element type has no fixed bit size"); + } + auto TotalBits = checkedMultiply(ElementBits, Type.ElementCount, "LLVM vector bit size"); + if (!TotalBits || *TotalBits > std::numeric_limits<std::uint64_t>::max() - 7U) + return makeUnexpected(ErrorCode::Unsupported, "LLVM vector bit size is too large"); + const std::uint64_t StoreSize = (*TotalBits + 7U) / 8U; + return ScalarLayout(StoreSize, + Layout.vector(static_cast<std::uint32_t>(std::min<std::uint64_t>( + *TotalBits, std::numeric_limits<std::uint32_t>::max())), + StoreSize)); + } + case GenericConstantTypeKind::Struct: { + if (Type.Packed) { + std::uint64_t Size = 0; + for (const GenericConstantType &MemberType : Type.Elements) { + auto Member = layoutType(MemberType, Options, Layout, NamedTypeStack, Depth + 1); + if (!Member) + return std::unexpected(Member.error()); + auto Next = checkedAdd(Size, Member->AllocationSize, "LLVM packed struct allocation size"); + if (!Next) + return std::unexpected(Next.error()); + Size = *Next; + } + return TypeLayout{Size, Size, 1, std::max<std::uint64_t>(Layout.aggregate().Preferred, 1)}; + } + std::uint64_t Offset = 0; + std::uint64_t ABIAlignment = std::max<std::uint64_t>(Layout.aggregate().ABI, 1); + for (const GenericConstantType &MemberType : Type.Elements) { + auto Member = layoutType(MemberType, Options, Layout, NamedTypeStack, Depth + 1); + if (!Member) + return std::unexpected(Member.error()); + auto AlignedOffset = alignTo(Offset, Member->ABIAlignment); + if (!AlignedOffset) + return std::unexpected(AlignedOffset.error()); + auto Next = checkedAdd(*AlignedOffset, Member->AllocationSize, "LLVM struct allocation size"); + if (!Next) + return std::unexpected(Next.error()); + Offset = *Next; + ABIAlignment = std::max(ABIAlignment, Member->ABIAlignment); + } + auto Size = alignTo(Offset, ABIAlignment); + if (!Size) + return std::unexpected(Size.error()); + return TypeLayout{*Size, *Size, ABIAlignment, + std::max(Layout.aggregate().Preferred, ABIAlignment)}; + } + case GenericConstantTypeKind::Named: { + if (std::ranges::find(NamedTypeStack, Type.Name) != NamedTypeStack.end()) { + return makeUnexpected(ErrorCode::Unsupported, + "named LLVM type definition is recursively unsized"); + } + NamedTypeStack.push_back(Type.Name); + Expected<TypeLayout> Result = + makeUnexpected(ErrorCode::Unsupported, "named LLVM type has no embedded type definition"); + if (Type.Elements.size() == 1) { + Result = layoutType(Type.Elements.front(), Options, Layout, NamedTypeStack, Depth + 1); + } else { + const auto Definition = std::ranges::find_if( + Options.NamedTypes, [&Type](const LLVMNamedTypeDefinition &Candidate) { + return Candidate.Name == Type.Name && Candidate.NumericName == Type.NumericName; + }); + if (Definition != Options.NamedTypes.end()) { + auto Resolved = parseLLVMType(Definition->Definition, Options); + if (!Resolved) + Result = std::unexpected(Resolved.error()); + else + Result = layoutType(*Resolved, Options, Layout, NamedTypeStack, Depth + 1); + } + } + NamedTypeStack.pop_back(); + return Result; + } + case GenericConstantTypeKind::Invalid: + case GenericConstantTypeKind::Void: + case GenericConstantTypeKind::Label: + case GenericConstantTypeKind::Token: + case GenericConstantTypeKind::Metadata: + case GenericConstantTypeKind::Function: + return makeUnexpected(ErrorCode::Unsupported, + "LLVM type is not sized for a machine constant pool"); + } + return makeUnexpected(ErrorCode::InternalError, "LLVM constant type layout is unhandled"); +} + +[[nodiscard]] Expected<TypeLayout> layoutType(const GenericConstantType &Type, + LLVMConstantSyntaxOptions Options) { + auto Layout = DataLayoutInfo::parse(Options); + if (!Layout) + return std::unexpected(Layout.error()); + std::vector<std::string> NamedTypeStack; + return layoutType(Type, Options, *Layout, NamedTypeStack, 0); +} + +} // namespace + +Expected<GenericConstantData> parseLLVMConstant(std::string_view Text, + LLVMConstantSyntaxOptions Options) { + if (Options.MaximumDepth == 0 || Options.MaximumNodes == 0 || Options.MaximumTokenLength == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "LLVM constant parser limits must be nonzero"); + } + return ConstantParser(Text, Options).parse(); +} + +Expected<GenericConstantType> parseLLVMType(std::string_view Text, + LLVMConstantSyntaxOptions Options) { + if (Options.MaximumDepth == 0 || Options.MaximumNodes == 0 || Options.MaximumTokenLength == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, "LLVM type parser limits must be nonzero"); + } + return ConstantParser(Text, Options).parseTypeOnly(); +} + +Expected<std::string> printLLVMConstant(const GenericConstantData &Constant) { + return printValue(Constant, true); +} + +Expected<std::uint64_t> llvmConstantStorageSize(const GenericConstantType &Type, + LLVMConstantSyntaxOptions Options) { + auto Layout = layoutType(Type, Options); + if (!Layout) + return std::unexpected(Layout.error()); + return Layout->AllocationSize; +} + +Expected<Alignment> llvmConstantPreferredAlignment(const GenericConstantType &Type, + LLVMConstantSyntaxOptions Options) { + auto Layout = layoutType(Type, Options); + if (!Layout) + return std::unexpected(Layout.error()); + return Alignment::create(Layout->PreferredAlignment); +} + +Expected<std::uint32_t> llvmPointerSizeInBits(std::uint32_t AddressSpace, + LLVMConstantSyntaxOptions Options) { + auto Layout = DataLayoutInfo::parse(Options); + if (!Layout) + return std::unexpected(Layout.error()); + auto Pointer = Layout->pointer(AddressSpace); + if (!Pointer) + return std::unexpected(Pointer.error()); + return Pointer->SizeInBits; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIDebugSyntax.cpp b/backend/lib/MIR/MIDebugSyntax.cpp new file mode 100644 index 0000000..bee82c4 --- /dev/null +++ b/backend/lib/MIR/MIDebugSyntax.cpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/MIDebugSyntax.h" + +#include <array> +#include <charconv> +#include <cstdint> +#include <ranges> +#include <string_view> +#include <system_error> +#include <utility> + +namespace rcc::mir { +namespace { + +[[nodiscard]] bool parseIndex(std::string_view Text, std::uint64_t &Value) noexcept { + if (Text.empty()) + return false; + const auto [End, Error] = + std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); + return Error == std::errc{} && End == Text.data() + Text.size(); +} + +} // namespace + +std::optional<std::uint64_t> +parseDIExpressionOpcodeName(std::string_view Name) noexcept { + const auto NumberedOpcode = [Name](std::string_view Prefix, + std::uint64_t Base) -> std::optional<std::uint64_t> { + if (!Name.starts_with(Prefix)) + return std::nullopt; + std::uint64_t Index = 0; + if (!parseIndex(Name.substr(Prefix.size()), Index) || Index > 31) + return std::nullopt; + return Base + Index; + }; + if (auto Opcode = NumberedOpcode("DW_OP_lit", 0x30U)) + return Opcode; + if (auto Opcode = NumberedOpcode("DW_OP_reg", 0x50U)) + return Opcode; + if (auto Opcode = NumberedOpcode("DW_OP_breg", 0x70U)) + return Opcode; + + constexpr auto NamedOpcodes = std::to_array<std::pair<std::string_view, std::uint64_t>>({ + {"DW_OP_addr", 0x03}, + {"DW_OP_deref", 0x06}, + {"DW_OP_const1u", 0x08}, + {"DW_OP_const1s", 0x09}, + {"DW_OP_const2u", 0x0A}, + {"DW_OP_const2s", 0x0B}, + {"DW_OP_const4u", 0x0C}, + {"DW_OP_const4s", 0x0D}, + {"DW_OP_const8u", 0x0E}, + {"DW_OP_const8s", 0x0F}, + {"DW_OP_constu", 0x10}, + {"DW_OP_consts", 0x11}, + {"DW_OP_dup", 0x12}, + {"DW_OP_drop", 0x13}, + {"DW_OP_over", 0x14}, + {"DW_OP_pick", 0x15}, + {"DW_OP_swap", 0x16}, + {"DW_OP_rot", 0x17}, + {"DW_OP_xderef", 0x18}, + {"DW_OP_abs", 0x19}, + {"DW_OP_and", 0x1A}, + {"DW_OP_div", 0x1B}, + {"DW_OP_minus", 0x1C}, + {"DW_OP_mod", 0x1D}, + {"DW_OP_mul", 0x1E}, + {"DW_OP_neg", 0x1F}, + {"DW_OP_not", 0x20}, + {"DW_OP_or", 0x21}, + {"DW_OP_plus", 0x22}, + {"DW_OP_plus_uconst", 0x23}, + {"DW_OP_shl", 0x24}, + {"DW_OP_shr", 0x25}, + {"DW_OP_shra", 0x26}, + {"DW_OP_xor", 0x27}, + {"DW_OP_bra", 0x28}, + {"DW_OP_eq", 0x29}, + {"DW_OP_ge", 0x2A}, + {"DW_OP_gt", 0x2B}, + {"DW_OP_le", 0x2C}, + {"DW_OP_lt", 0x2D}, + {"DW_OP_ne", 0x2E}, + {"DW_OP_skip", 0x2F}, + {"DW_OP_regx", 0x90}, + {"DW_OP_fbreg", 0x91}, + {"DW_OP_bregx", 0x92}, + {"DW_OP_piece", 0x93}, + {"DW_OP_deref_size", 0x94}, + {"DW_OP_xderef_size", 0x95}, + {"DW_OP_nop", 0x96}, + {"DW_OP_push_object_address", 0x97}, + {"DW_OP_call2", 0x98}, + {"DW_OP_call4", 0x99}, + {"DW_OP_call_ref", 0x9A}, + {"DW_OP_form_tls_address", 0x9B}, + {"DW_OP_call_frame_cfa", 0x9C}, + {"DW_OP_bit_piece", 0x9D}, + {"DW_OP_implicit_value", 0x9E}, + {"DW_OP_stack_value", 0x9F}, + {"DW_OP_implicit_pointer", 0xA0}, + {"DW_OP_addrx", 0xA1}, + {"DW_OP_constx", 0xA2}, + {"DW_OP_entry_value", 0xA3}, + {"DW_OP_const_type", 0xA4}, + {"DW_OP_regval_type", 0xA5}, + {"DW_OP_deref_type", 0xA6}, + {"DW_OP_xderef_type", 0xA7}, + {"DW_OP_convert", 0xA8}, + {"DW_OP_reinterpret", 0xA9}, + {"DW_OP_LLVM_fragment", 0x1000}, + {"DW_OP_LLVM_convert", 0x1001}, + {"DW_OP_LLVM_tag_offset", 0x1002}, + {"DW_OP_LLVM_entry_value", 0x1003}, + {"DW_OP_LLVM_implicit_pointer", 0x1004}, + {"DW_OP_LLVM_arg", 0x1005}, + }); + const auto Found = + std::ranges::find_if(NamedOpcodes, [Name](const auto &Entry) { return Entry.first == Name; }); + if (Found == NamedOpcodes.end()) + return std::nullopt; + return Found->second; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIParser.cpp b/backend/lib/MIR/MIParser.cpp index b632415..ecf4986 100644 --- a/backend/lib/MIR/MIParser.cpp +++ b/backend/lib/MIR/MIParser.cpp @@ -13,6 +13,7 @@ #include "rcc/CodeGen/TargetDescription.h" #include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/MIDebugSyntax.h" #include "rcc/MIR/MILexer.h" #include <algorithm> @@ -23,6 +24,7 @@ #include <cstddef> #include <cstdint> #include <cstring> +#include <iterator> #include <limits> #include <memory> #include <optional> @@ -256,106 +258,6 @@ namespace { return static_cast<std::uint16_t>(Bits >> 16U); } -[[nodiscard]] std::optional<std::uint64_t> debugExpressionOpcode(std::string_view Name) noexcept { - if (Name.starts_with("DW_OP_lit")) { - std::uint64_t Index = 0; - if (parseUnsignedDigits(Name.substr(9), Index) && Index <= 31) - return 0x30U + Index; - } - if (Name.starts_with("DW_OP_reg")) { - std::uint64_t Index = 0; - if (parseUnsignedDigits(Name.substr(9), Index) && Index <= 31) - return 0x50U + Index; - } - if (Name.starts_with("DW_OP_breg")) { - std::uint64_t Index = 0; - if (parseUnsignedDigits(Name.substr(10), Index) && Index <= 31) - return 0x70U + Index; - } - - constexpr auto NamedOpcodes = std::to_array<std::pair<std::string_view, std::uint64_t>>({ - {"DW_OP_addr", 0x03}, - {"DW_OP_deref", 0x06}, - {"DW_OP_const1u", 0x08}, - {"DW_OP_const1s", 0x09}, - {"DW_OP_const2u", 0x0A}, - {"DW_OP_const2s", 0x0B}, - {"DW_OP_const4u", 0x0C}, - {"DW_OP_const4s", 0x0D}, - {"DW_OP_const8u", 0x0E}, - {"DW_OP_const8s", 0x0F}, - {"DW_OP_constu", 0x10}, - {"DW_OP_consts", 0x11}, - {"DW_OP_dup", 0x12}, - {"DW_OP_drop", 0x13}, - {"DW_OP_over", 0x14}, - {"DW_OP_pick", 0x15}, - {"DW_OP_swap", 0x16}, - {"DW_OP_rot", 0x17}, - {"DW_OP_xderef", 0x18}, - {"DW_OP_abs", 0x19}, - {"DW_OP_and", 0x1A}, - {"DW_OP_div", 0x1B}, - {"DW_OP_minus", 0x1C}, - {"DW_OP_mod", 0x1D}, - {"DW_OP_mul", 0x1E}, - {"DW_OP_neg", 0x1F}, - {"DW_OP_not", 0x20}, - {"DW_OP_or", 0x21}, - {"DW_OP_plus", 0x22}, - {"DW_OP_plus_uconst", 0x23}, - {"DW_OP_shl", 0x24}, - {"DW_OP_shr", 0x25}, - {"DW_OP_shra", 0x26}, - {"DW_OP_xor", 0x27}, - {"DW_OP_bra", 0x28}, - {"DW_OP_eq", 0x29}, - {"DW_OP_ge", 0x2A}, - {"DW_OP_gt", 0x2B}, - {"DW_OP_le", 0x2C}, - {"DW_OP_lt", 0x2D}, - {"DW_OP_ne", 0x2E}, - {"DW_OP_skip", 0x2F}, - {"DW_OP_regx", 0x90}, - {"DW_OP_fbreg", 0x91}, - {"DW_OP_bregx", 0x92}, - {"DW_OP_piece", 0x93}, - {"DW_OP_deref_size", 0x94}, - {"DW_OP_xderef_size", 0x95}, - {"DW_OP_nop", 0x96}, - {"DW_OP_push_object_address", 0x97}, - {"DW_OP_call2", 0x98}, - {"DW_OP_call4", 0x99}, - {"DW_OP_call_ref", 0x9A}, - {"DW_OP_form_tls_address", 0x9B}, - {"DW_OP_call_frame_cfa", 0x9C}, - {"DW_OP_bit_piece", 0x9D}, - {"DW_OP_implicit_value", 0x9E}, - {"DW_OP_stack_value", 0x9F}, - {"DW_OP_implicit_pointer", 0xA0}, - {"DW_OP_addrx", 0xA1}, - {"DW_OP_constx", 0xA2}, - {"DW_OP_entry_value", 0xA3}, - {"DW_OP_const_type", 0xA4}, - {"DW_OP_regval_type", 0xA5}, - {"DW_OP_deref_type", 0xA6}, - {"DW_OP_xderef_type", 0xA7}, - {"DW_OP_convert", 0xA8}, - {"DW_OP_reinterpret", 0xA9}, - {"DW_OP_LLVM_fragment", 0x1000}, - {"DW_OP_LLVM_convert", 0x1001}, - {"DW_OP_LLVM_tag_offset", 0x1002}, - {"DW_OP_LLVM_entry_value", 0x1003}, - {"DW_OP_LLVM_implicit_pointer", 0x1004}, - {"DW_OP_LLVM_arg", 0x1005}, - }); - const auto *const Found = - std::ranges::find_if(NamedOpcodes, [Name](const auto &Entry) { return Entry.first == Name; }); - if (Found == NamedOpcodes.end()) - return std::nullopt; - return Found->second; -} - [[nodiscard]] std::optional<std::uint64_t> minimumTypeSizeInBits(LLT Type) noexcept { if (!Type.isValid() || Type.isToken()) return std::nullopt; @@ -817,6 +719,13 @@ bool MIParser::Impl::parseScalarLowLevelType(const MIToken &TypeToken, LLT &Resu Created = LLT::makeFloat(FloatSemantics::BFloat); break; case ScalarKind::Pointer: + if (Context.PointerSizeInBits) { + auto PointerSize = Context.PointerSizeInBits(static_cast<std::uint32_t>(Value)); + if (!PointerSize) + return importError(TypeToken, PointerSize.error()); + Created = LLT::makePointer(Value, *PointerSize); + break; + } if (Context.DefaultPointerSizeInBits == 0) return fail(TypeToken, "pointer low-level type requires an explicit pointer size"); Created = LLT::makePointer(Value, Context.DefaultPointerSizeInBits); @@ -1335,7 +1244,9 @@ std::optional<JumpTableIndex> MIParser::Impl::jumpTableIndexFor(const MIToken &T return std::nullopt; } for (const MIJumpTableSlot &Slot : Context.JumpTables) { - if (Slot.TextID == ID && Function.jumpTableInfo().table(Slot.Index) != nullptr) + if (Slot.TextID == ID && (Function.jumpTableInfo().table(Slot.Index) != nullptr || + (Slot.Index.value() < Function.jumpTableInfo().tables().size() && + Function.jumpTableInfo().tables()[Slot.Index.value()].isPending()))) return Slot.Index; } static_cast<void>(fail(Token, "use of undefined jump table #" + std::to_string(ID))); @@ -1566,7 +1477,7 @@ bool MIParser::Impl::parseMetadataOperand(ParsedOperand &Result) { std::vector<std::uint64_t> Elements; while (!at(MITokenKind::RightParen)) { if (at(MITokenKind::Identifier)) { - const auto Opcode = debugExpressionOpcode(token().stringValue()); + const auto Opcode = parseDIExpressionOpcodeName(token().stringValue()); if (!Opcode) return fail("unknown debug expression opcode '" + std::string(token().stringValue()) + "'"); @@ -2036,8 +1947,16 @@ bool MIParser::Impl::parseMemoryOperand(const MachineMemOperand *&Result) { if (!at(MITokenKind::StringConstant)) return fail("syncscope requires a quoted scope name"); const std::string_view Name = token().stringValue(); - const auto Scope = std::ranges::find_if( + auto Scope = std::ranges::find_if( Context.SyncScopes, [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; }); + if (Scope == Context.SyncScopes.end() && Context.InternSyncScope) { + const MIToken NameToken = token(); + auto ID = Context.InternSyncScope(Name); + if (!ID) + return importError(NameToken, ID.error()); + Context.SyncScopes.push_back({.Name = std::string(Name), .ID = *ID}); + Scope = std::prev(Context.SyncScopes.end()); + } if (Scope == Context.SyncScopes.end()) return fail("unknown synchronization scope '" + std::string(Name) + "'"); Description.SyncScope = Scope->ID; @@ -2994,20 +2913,33 @@ Expected<void> MIParser::Impl::parse() { return makeUnexpected(ErrorCode::InvalidArgument, "MI parser requires a staging machine function with an empty body"); } + for (const MIVirtualRegisterSlot &Slot : Context.VirtualRegisters) { + if (!Slot.Reg.isVirtual() || !Function.registerInfo().isKnownVirtualRegister(Slot.Reg)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "MI parser virtual-register slot maps to an unknown register"); + } + if (!NumericVirtualRegisters.emplace(Slot.TextID, Slot.Reg).second) { + return makeUnexpected(ErrorCode::InvalidArgument, + "MI parser virtual-register text IDs must be unique"); + } + } if (tokenize()) return failureResult(); if (parseFirstPass() || parseSecondPass() || inferSuccessors()) return failureResult(); Function.renumberBlocks(); - auto Verified = MachineVerifier::verify( - Function, Target, - MachineVerifierOptions{ - .Mode = MachineVerifierMode::Generic, .Stage = std::nullopt, .VerifyProperties = false}); - if (!Verified) { - static_cast<void>(fail(token(), "parsed machine function failed verification: " + - std::string(Verified.error().message()))); - return failureResult(); + if (Options.VerifyAfterParse) { + auto Verified = + MachineVerifier::verify(Function, Target, + MachineVerifierOptions{.Mode = MachineVerifierMode::Generic, + .Stage = std::nullopt, + .VerifyProperties = false}); + if (!Verified) { + static_cast<void>(fail(token(), "parsed machine function failed verification: " + + std::string(Verified.error().message()))); + return failureResult(); + } } return {}; } diff --git a/backend/lib/MIR/MIPrinter.cpp b/backend/lib/MIR/MIPrinter.cpp index e3f504b..83fce40 100644 --- a/backend/lib/MIR/MIPrinter.cpp +++ b/backend/lib/MIR/MIPrinter.cpp @@ -61,6 +61,15 @@ namespace { return Result; } +[[nodiscard]] std::string quoteString(std::string_view Value) { + std::string Result = quoteName(Value); + if (!Result.empty() && Result.front() == '"') + return Result; + Result.insert(Result.begin(), '"'); + Result.push_back('"'); + return Result; +} + [[nodiscard]] std::string hexadecimalWords(std::span<const std::uint64_t> Words, std::uint32_t BitWidth) { constexpr std::string_view Hex = "0123456789ABCDEF"; @@ -208,6 +217,91 @@ namespace { return {}; } +[[nodiscard]] Expected<std::string> llvmMIRScalarLowLevelType(LLT Type) { + if (Type.isToken()) + return "s0"; + if (Type.isAnyScalar()) + return "s" + std::to_string(Type.scalarBitWidth()); + if (Type.isInteger()) + return "i" + std::to_string(Type.scalarBitWidth()); + if (Type.isPointer()) { + const auto AddressSpace = Type.addressSpace(); + if (!AddressSpace) { + return makeUnexpected(ErrorCode::VerificationError, + "pointer low-level type has no address space"); + } + return "p" + std::to_string(*AddressSpace); + } + if (!Type.isFloat()) { + return makeUnexpected(ErrorCode::VerificationError, + "invalid scalar low-level type cannot be printed as LLVM MIR"); + } + + const auto Semantics = Type.floatSemantics(); + if (!Semantics) { + return makeUnexpected(ErrorCode::VerificationError, + "floating low-level type has no floating-point semantics"); + } + switch (*Semantics) { + case FloatSemantics::IEEEHalf: + return "f16"; + case FloatSemantics::BFloat: + return "bf16"; + case FloatSemantics::IEEESingle: + return "f32"; + case FloatSemantics::IEEEDouble: + return "f64"; + case FloatSemantics::IEEEQuad: + return "f128"; + case FloatSemantics::PPCDoubleDouble: + case FloatSemantics::PPCDoubleDoubleLegacy: + case FloatSemantics::Float8E5M2: + case FloatSemantics::Float8E5M2FNUZ: + case FloatSemantics::Float8E4M3: + case FloatSemantics::Float8E4M3FN: + case FloatSemantics::Float8E4M3FNUZ: + case FloatSemantics::Float8E4M3B11FNUZ: + case FloatSemantics::Float8E3M4: + case FloatSemantics::FloatTF32: + case FloatSemantics::Float8E8M0FNU: + case FloatSemantics::Float6E3M2FN: + case FloatSemantics::Float6E2M3FN: + case FloatSemantics::Float4E2M1FN: + case FloatSemantics::X87DoubleExtended: + case FloatSemantics::Count: + return makeUnexpected(ErrorCode::Unsupported, + "fixed LLVM MIR cannot spell this floating low-level type"); + } + return makeUnexpected(ErrorCode::InternalError, "floating low-level type is unhandled"); +} + +[[nodiscard]] Expected<std::string> llvmMIRLowLevelType(LLT Type) { + if (!Type.isValid()) { + return makeUnexpected(ErrorCode::VerificationError, + "invalid low-level type cannot be printed as LLVM MIR"); + } + if (!Type.isVector()) + return llvmMIRScalarLowLevelType(Type); + + const auto ElementType = Type.scalarElementType(); + if (!ElementType) { + return makeUnexpected(ErrorCode::VerificationError, + "vector low-level type has no scalar element type"); + } + auto Scalar = llvmMIRScalarLowLevelType(*ElementType); + if (!Scalar) + return std::unexpected(Scalar.error()); + + std::string Result = "<"; + if (Type.isScalableVector()) + Result.append("vscale x "); + Result.append(std::to_string(Type.minimumElementCount())); + Result.append(" x "); + Result.append(*Scalar); + Result.push_back('>'); + return Result; +} + class BodyPrinter final { public: BodyPrinter(const MachineFunction &Function, const TargetDescription &Target, @@ -232,6 +326,7 @@ private: [[nodiscard]] Expected<void> printMemoryBase(const MemoryBase &Base, std::int64_t Offset); [[nodiscard]] Expected<std::uint32_t> blockID(const MachineBasicBlock &Block) const; + [[nodiscard]] Expected<std::uint32_t> virtualRegisterID(Register Reg) const; [[nodiscard]] Expected<std::uint32_t> frameID(FrameIndex Index) const; [[nodiscard]] Expected<std::uint32_t> constantPoolID(ConstantPoolIndex Index) const; [[nodiscard]] Expected<std::uint32_t> jumpTableID(JumpTableIndex Index) const; @@ -270,6 +365,22 @@ Expected<std::uint32_t> BodyPrinter::blockID(const MachineBasicBlock &Block) con return Found->second; } +Expected<std::uint32_t> BodyPrinter::virtualRegisterID(Register Reg) const { + if (!Context.VirtualRegisters.empty()) { + const auto Found = + std::ranges::find_if(Context.VirtualRegisters, + [Reg](const MIVirtualRegisterSlot &Slot) { return Slot.Reg == Reg; }); + if (Found == Context.VirtualRegisters.end()) { + return makeUnexpected(ErrorCode::VerificationError, "virtual register has no MIR text slot"); + } + return Found->TextID; + } + const auto VirtualIndex = Reg.virtualIndex(); + if (!VirtualIndex) + return makeUnexpected(ErrorCode::VerificationError, "virtual register has no numeric index"); + return *VirtualIndex; +} + Expected<std::uint32_t> BodyPrinter::frameID(FrameIndex Index) const { const auto InStack = std::ranges::find_if( Context.StackObjects, [Index](const MIFrameObjectSlot &Slot) { return Slot.Index == Index; }); @@ -459,9 +570,9 @@ Expected<void> BodyPrinter::printRegisterOperand(const MachineOperand &Operand, if (!Printed) return Printed; } else { - const auto VirtualIndex = Reg.virtualIndex(); + auto VirtualIndex = virtualRegisterID(Reg); if (!VirtualIndex) - return makeUnexpected(ErrorCode::VerificationError, "virtual register has no numeric index"); + return std::unexpected(VirtualIndex.error()); Output.push_back('%'); Output.append(std::to_string(*VirtualIndex)); if (Operand.subReg().value() != 0) { @@ -495,8 +606,11 @@ Expected<void> BodyPrinter::printRegisterOperand(const MachineOperand &Operand, Output.append(":_"); } if (RegisterInfo.Type) { + auto Type = llvmMIRLowLevelType(*RegisterInfo.Type); + if (!Type) + return std::unexpected(Type.error()); Output.push_back('('); - Output.append(RegisterInfo.Type->toString()); + Output.append(*Type); Output.push_back(')'); } } @@ -1092,7 +1206,7 @@ Expected<void> BodyPrinter::printMemoryOperand(const MachineMemOperand &Operand) return makeUnexpected(ErrorCode::VerificationError, "synchronization scope has no MIR name"); } Output.append(" syncscope("); - Output.append(quoteName(Scope->Name)); + Output.append(quoteString(Scope->Name)); Output.push_back(')'); } if (Description.SuccessOrdering != AtomicOrdering::NotAtomic) { diff --git a/backend/lib/MIR/MIRDocument.cpp b/backend/lib/MIR/MIRDocument.cpp new file mode 100644 index 0000000..4808df7 --- /dev/null +++ b/backend/lib/MIR/MIRDocument.cpp @@ -0,0 +1,2771 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/MIRDocument.h" + +#include "MIRDocumentReferenceValidation.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineConstantPool.h" +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionAttributes.h" +#include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineFunctionRecords.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMetadata.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" +#include "rcc/MIR/MIDebugSyntax.h" +#include "rcc/MIR/MILexer.h" + +#include <algorithm> +#include <array> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <functional> +#include <initializer_list> +#include <limits> +#include <optional> +#include <ranges> +#include <span> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc::mir { +namespace { + +[[nodiscard]] std::string_view trim(std::string_view Text) noexcept { + while (!Text.empty() && (Text.front() == ' ' || Text.front() == '\t' || Text.front() == '\r' || + Text.front() == '\n')) { + Text.remove_prefix(1); + } + while (!Text.empty() && (Text.back() == ' ' || Text.back() == '\t' || Text.back() == '\r' || + Text.back() == '\n')) { + Text.remove_suffix(1); + } + return Text; +} + +[[nodiscard]] bool parseUnsigned(std::string_view Text, std::uint64_t &Value) noexcept { + Text = trim(Text); + int Base = 10; + if (Text.starts_with("0x") || Text.starts_with("0X")) { + Text.remove_prefix(2); + Base = 16; + } + if (Text.empty()) + return false; + const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, Base); + return Error == std::errc{} && End == Text.data() + Text.size(); +} + +[[nodiscard]] bool parseSigned(std::string_view Text, std::int64_t &Value) noexcept { + Text = trim(Text); + if (Text.empty()) + return false; + const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); + return Error == std::errc{} && End == Text.data() + Text.size(); +} + +[[nodiscard]] bool parseUnsigned32(std::string_view Text, std::uint32_t &Value) noexcept { + std::uint64_t Wide = 0; + if (!parseUnsigned(Text, Wide) || Wide > std::numeric_limits<std::uint32_t>::max()) + return false; + Value = static_cast<std::uint32_t>(Wide); + return true; +} + +[[nodiscard]] bool parseBool(std::string_view Text, bool &Value) noexcept { + if (Text == "true") { + Value = true; + return true; + } + if (Text == "false") { + Value = false; + return true; + } + return false; +} + +[[nodiscard]] std::optional<std::uint32_t> parsePrefixedID(std::string_view Text, + std::string_view Prefix) noexcept { + Text = trim(Text); + if (!Text.starts_with(Prefix)) + return std::nullopt; + Text.remove_prefix(Prefix.size()); + const std::size_t Suffix = Text.find_first_of(". \t"); + if (Suffix != std::string_view::npos) + Text = Text.substr(0, Suffix); + std::uint32_t ID = 0; + if (!parseUnsigned32(Text, ID)) + return std::nullopt; + return ID; +} + +struct PendingJumpTable final { + JumpTableIndex Index; + std::uint32_t TextID = 0; + JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress; + std::uint8_t EntrySize = 8; + std::vector<std::string> Blocks; +}; + +class MetadataTextParser final { +public: + using Resolver = std::function<Expected<MachineMetadataReference>(std::uint32_t Slot)>; + + MetadataTextParser(std::string Text, Resolver Resolve) + : Lexer("MIR metadata scalar", std::move(Text)), Resolve(std::move(Resolve)) { + do { + Tokens.push_back(Lexer.next()); + } while (!Tokens.back().isErrorOrEnd()); + } + + [[nodiscard]] Expected<MachineMetadataReference> parseReferenceOnly(); + [[nodiscard]] Expected<MachineMetadataValue> parseValue(); + [[nodiscard]] Expected<MachineDebugLocation> parseLocation(); + +private: + [[nodiscard]] const MIToken &token() const noexcept { + return Tokens[Position]; + } + [[nodiscard]] bool at(MITokenKind Kind) const noexcept { + return token().is(Kind); + } + void advance() noexcept { + if (Position + 1U < Tokens.size()) + ++Position; + } + [[nodiscard]] bool consume(MITokenKind Kind) noexcept { + if (!at(Kind)) + return false; + advance(); + return true; + } + [[nodiscard]] Expected<void> expect(MITokenKind Kind, std::string_view Description); + [[nodiscard]] Expected<void> finish(); + [[nodiscard]] Expected<MachineMetadataReference> parseReference(); + [[nodiscard]] Expected<MachineDIExpression> parseExpression(); + [[nodiscard]] Expected<void> parseDILocation(std::vector<MachineDILocationFrame> &Frames, + std::optional<MachineMetadataReference> &Terminal); + + MILexer Lexer; + Resolver Resolve; + std::vector<MIToken> Tokens; + std::size_t Position = 0; +}; + +Expected<void> MetadataTextParser::expect(MITokenKind Kind, std::string_view Description) { + if (consume(Kind)) + return {}; + return makeUnexpected(ErrorCode::ParseError, + "expected " + std::string(Description) + " in MIR metadata scalar"); +} + +Expected<void> MetadataTextParser::finish() { + while (consume(MITokenKind::Newline)) { + } + if (Lexer.failed()) { + if (!Lexer.diagnostics().empty()) { + return makeUnexpected(ErrorCode::ParseError, + std::string(Lexer.diagnostics().front().Message)); + } + return makeUnexpected(ErrorCode::ParseError, "invalid MIR metadata scalar"); + } + if (!at(MITokenKind::EndOfFile)) + return makeUnexpected(ErrorCode::ParseError, "unexpected trailing MIR metadata tokens"); + return {}; +} + +Expected<MachineMetadataReference> MetadataTextParser::parseReference() { + if (auto Exclaim = expect(MITokenKind::Exclaim, "'!' before a metadata slot"); !Exclaim) + return std::unexpected(Exclaim.error()); + if (!at(MITokenKind::IntegerLiteral)) + return makeUnexpected(ErrorCode::ParseError, "expected a 32-bit metadata slot"); + std::uint32_t Slot = 0; + if (!parseUnsigned32(token().integerValue(), Slot)) + return makeUnexpected(ErrorCode::ParseError, "metadata slot is outside the 32-bit range"); + advance(); + return Resolve(Slot); +} + +Expected<MachineMetadataReference> MetadataTextParser::parseReferenceOnly() { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Reference; +} + +Expected<MachineDIExpression> MetadataTextParser::parseExpression() { + if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DIExpression")) { + return makeUnexpected(ErrorCode::ParseError, "expected an inline !DIExpression"); + } + advance(); + if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DIExpression"); !Left) + return std::unexpected(Left.error()); + std::vector<std::uint64_t> Elements; + while (!at(MITokenKind::RightParen)) { + if (at(MITokenKind::Identifier)) { + const auto Opcode = parseDIExpressionOpcodeName(token().stringValue()); + if (!Opcode) { + return makeUnexpected(ErrorCode::ParseError, "unknown DIExpression opcode '" + + std::string(token().stringValue()) + "'"); + } + Elements.push_back(*Opcode); + advance(); + } else if (at(MITokenKind::IntegerLiteral)) { + std::int64_t Signed = 0; + std::uint64_t Unsigned = 0; + if (parseSigned(token().integerValue(), Signed)) + Elements.push_back(static_cast<std::uint64_t>(Signed)); + else if (parseUnsigned(token().integerValue(), Unsigned)) + Elements.push_back(Unsigned); + else + return makeUnexpected(ErrorCode::ParseError, "DIExpression integer exceeds 64 bits"); + advance(); + } else { + return makeUnexpected(ErrorCode::ParseError, "expected a DIExpression opcode or integer"); + } + if (!consume(MITokenKind::Comma)) + break; + } + if (auto Right = expect(MITokenKind::RightParen, "')' after !DIExpression"); !Right) + return std::unexpected(Right.error()); + return MachineDIExpression(std::move(Elements)); +} + +Expected<MachineMetadataValue> MetadataTextParser::parseValue() { + MachineMetadataValue Value; + if (at(MITokenKind::Exclaim)) { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + auto Created = MachineMetadataValue::reference(*Reference); + if (!Created) + return std::unexpected(Created.error()); + Value = std::move(*Created); + } else { + auto Expression = parseExpression(); + if (!Expression) + return std::unexpected(Expression.error()); + Value = MachineMetadataValue::expression(std::move(*Expression)); + } + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Value; +} + +Expected<void> +MetadataTextParser::parseDILocation(std::vector<MachineDILocationFrame> &Frames, + std::optional<MachineMetadataReference> &Terminal) { + if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DILocation")) { + return makeUnexpected(ErrorCode::ParseError, "expected an inline !DILocation"); + } + advance(); + if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DILocation"); !Left) + return Left; + + MachineDILocationFrame Frame; + bool HasLine = false; + bool HasScope = false; + std::vector<MachineDILocationFrame> NestedFrames; + std::optional<MachineMetadataReference> NestedTerminal; + std::unordered_set<std::string> Seen; + while (!at(MITokenKind::RightParen)) { + if (!at(MITokenKind::Identifier)) + return makeUnexpected(ErrorCode::ParseError, "expected a DILocation field name"); + const std::string Field(token().stringValue()); + if (!Seen.insert(Field).second) { + return makeUnexpected(ErrorCode::ParseError, "duplicate DILocation field '" + Field + "'"); + } + advance(); + if (auto Colon = expect(MITokenKind::Colon, "':' after a DILocation field"); !Colon) + return Colon; + + if (Field == "line") { + std::uint32_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned32(token().integerValue(), Value)) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation line must be an unsigned 32-bit integer"); + } + Frame.Line = Value; + HasLine = true; + advance(); + } else if (Field == "column") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > std::numeric_limits<std::uint16_t>::max()) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation column must be an unsigned 16-bit integer"); + } + Frame.Column = static_cast<std::uint16_t>(Value); + advance(); + } else if (Field == "scope") { + auto Scope = parseReference(); + if (!Scope) + return std::unexpected(Scope.error()); + if (Scope->scope() != MachineMetadataScope::EmbeddedIR) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation scope must reference embedded LLVM IR metadata"); + } + Frame.Scope = *Scope; + HasScope = true; + } else if (Field == "inlinedAt") { + if (at(MITokenKind::MetadataKeyword) && token().isMetadataKeyword("!DILocation")) { + if (auto Nested = parseDILocation(NestedFrames, NestedTerminal); !Nested) + return Nested; + } else { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation inlinedAt must reference embedded LLVM IR metadata"); + } + NestedTerminal = *Reference; + } + } else if (Field == "isImplicitCode") { + if (!at(MITokenKind::Identifier) || + (token().stringValue() != "true" && token().stringValue() != "false")) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation isImplicitCode must be true or false"); + } + Frame.IsImplicitCode = token().stringValue() == "true"; + advance(); + } else if (Field == "atomGroup") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > MachineDILocation::MaxAtomGroup) { + return makeUnexpected(ErrorCode::ParseError, "DILocation atomGroup exceeds 61 bits"); + } + Frame.AtomGroup = Value; + advance(); + } else if (Field == "atomRank") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > 7) { + return makeUnexpected(ErrorCode::ParseError, "DILocation atomRank exceeds three bits"); + } + Frame.AtomRank = static_cast<std::uint8_t>(Value); + advance(); + } else { + return makeUnexpected(ErrorCode::ParseError, "unknown DILocation field '" + Field + "'"); + } + if (!consume(MITokenKind::Comma)) + break; + } + if (auto Right = expect(MITokenKind::RightParen, "')' after !DILocation"); !Right) + return Right; + if (!HasLine || !HasScope) + return makeUnexpected(ErrorCode::ParseError, "DILocation requires line and scope fields"); + Frames.push_back(Frame); + Frames.insert(Frames.end(), NestedFrames.begin(), NestedFrames.end()); + Terminal = NestedTerminal; + return {}; +} + +Expected<MachineDebugLocation> MetadataTextParser::parseLocation() { + MachineDebugLocation Location; + if (at(MITokenKind::Exclaim)) { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + auto Created = MachineDebugLocation::reference(*Reference); + if (!Created) + return std::unexpected(Created.error()); + Location = std::move(*Created); + } else { + std::vector<MachineDILocationFrame> Frames; + std::optional<MachineMetadataReference> Terminal; + if (auto Parsed = parseDILocation(Frames, Terminal); !Parsed) + return std::unexpected(Parsed.error()); + auto Created = MachineDILocation::create(std::move(Frames), Terminal); + if (!Created) + return std::unexpected(Created.error()); + Location = MachineDebugLocation::inlineLocation(std::move(*Created)); + } + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Location; +} + +} // namespace + +class MIRDocumentParser final { +public: + MIRDocumentParser(const TargetDescription &Target, std::string SourceName, std::string SourceText, + MIRDocumentParserOptions Options) + : Target(Target), Options(Options), Result(std::move(SourceName), std::move(SourceText)) {} + + [[nodiscard]] MIRDocumentParseResult run(); + +private: + [[nodiscard]] bool fail(const TextRange &Range, std::string Message, + std::string Category = "mir-yaml"); + [[nodiscard]] bool importError(const TextRange &Range, const Error &Failure, + std::string Category = "mir-yaml"); + [[nodiscard]] const yaml::Node *node(yaml::NodeID ID) const noexcept { + return Stream->node(ID); + } + [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Mapping, + std::string_view Key) const noexcept; + [[nodiscard]] bool requireKind(const yaml::Node &Node, yaml::NodeKind Kind, + std::string_view Description); + [[nodiscard]] bool validateKeys(const yaml::Node &Mapping, std::span<const std::string_view> Keys, + std::string_view Description); + [[nodiscard]] bool scalar(const yaml::Node &Node, std::string_view &Value, + std::string_view Description); + [[nodiscard]] bool boolean(const yaml::Node &Node, bool &Value); + [[nodiscard]] bool unsignedValue(const yaml::Node &Node, std::uint64_t &Value, + std::string_view Description); + [[nodiscard]] bool signedValue(const yaml::Node &Node, std::int64_t &Value, + std::string_view Description); + [[nodiscard]] bool alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero); + [[nodiscard]] bool physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg); + [[nodiscard]] bool physicalRegister(std::string_view Text, const TextRange &Range, + PhysicalRegister &Reg); + [[nodiscard]] bool virtualRegister(std::string_view Text, const TextRange &Range, + const MIParserContext &Context, Register &Reg); + [[nodiscard]] Expected<MachineMetadataReference> + resolveMetadataReference(const MachineFunction &Function, std::uint32_t Slot) const; + [[nodiscard]] bool parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, + MachineVariableDebugInfo &DebugInfo); + [[nodiscard]] bool parseFunction(const yaml::Node &Root, MIRDocument &Document); + [[nodiscard]] bool parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, + MIRFunctionDocumentInfo &DocumentInfo); + [[nodiscard]] bool recomputeProperties(const yaml::Node &Root, MachineFunction &Function, + const MIRFunctionDocumentInfo &DocumentInfo); + [[nodiscard]] bool parseRegisters(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseMetadata(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseStackObjects(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseConstants(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseFrameState(const yaml::Node &Root, MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] bool reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context, + std::vector<PendingJumpTable> &Pending); + [[nodiscard]] bool defineJumpTables(MachineFunction &Function, + std::span<const PendingJumpTable> Pending); + [[nodiscard]] bool parseBody(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] TextRange bodyDiagnosticRange(const yaml::Node &Body, + const TextRange &BodyRange) const noexcept; + [[nodiscard]] bool parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function); + [[nodiscard]] bool parseTargetState(const yaml::Node &Root, MachineFunction &Function); + [[nodiscard]] bool convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination); + [[nodiscard]] bool validateDocumentReferences(const MachineFunction &Function); + [[nodiscard]] std::string_view effectiveDataLayout() const noexcept; + [[nodiscard]] LLVMConstantSyntaxOptions pointerLayoutOptions() const noexcept; + + [[nodiscard]] MachineBasicBlock *blockAt(MachineFunction &Function, + std::uint32_t Number) const noexcept; + [[nodiscard]] MachineInstr *instructionAt(MachineFunction &Function, std::uint32_t Block, + std::uint32_t Offset) const noexcept; + [[nodiscard]] bool parseBlockReference(std::string_view Text, const TextRange &Range, + MachineFunction &Function, MachineBasicBlock *&Block); + [[nodiscard]] bool parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, + FrameIndex &Index); + [[nodiscard]] bool parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, + std::vector<MachineFrameSaveRestorePoint> &Points); + + const TargetDescription &Target; + MIRDocumentParserOptions Options; + MIRDocumentParseResult Result; + const yaml::Stream *Stream = nullptr; + MIRDocument *ActiveDocument = nullptr; + MachineFunction *ActiveFunction = nullptr; +}; + +bool MIRDocumentParser::fail(const TextRange &Range, std::string Message, std::string Category) { + if (Result.Diagnostics.empty()) { + if (ActiveFunction != nullptr) + Message = "function '" + std::string(ActiveFunction->name()) + "': " + std::move(Message); + Result.Diagnostics.push_back(TextDiagnostic{ + .Severity = DiagnosticSeverity::Error, + .Category = std::move(Category), + .Message = std::move(Message), + .SourceName = std::string(Result.Source.name()), + .Range = Range, + }); + } + return true; +} + +bool MIRDocumentParser::importError(const TextRange &Range, const Error &Failure, + std::string Category) { + return fail(Range, std::string(Failure.message()), std::move(Category)); +} + +const yaml::Node *MIRDocumentParser::mappingValue(const yaml::Node &Mapping, + std::string_view Key) const noexcept { + const auto Value = Mapping.mappingValue(Key); + return Value ? node(*Value) : nullptr; +} + +std::string_view MIRDocumentParser::effectiveDataLayout() const noexcept { + if (ActiveDocument != nullptr) { + if (const EmbeddedIRReferenceIndex *Embedded = ActiveDocument->embeddedIR()) { + if (const auto DataLayout = Embedded->dataLayout()) + return *DataLayout; + } + } + return Options.DataLayout; +} + +LLVMConstantSyntaxOptions MIRDocumentParser::pointerLayoutOptions() const noexcept { + return { + .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits, + .DataLayout = effectiveDataLayout(), + .NamedTypes = {}, + }; +} + +bool MIRDocumentParser::requireKind(const yaml::Node &Node, yaml::NodeKind Kind, + std::string_view Description) { + if (Node.kind() == Kind) + return false; + return fail(Node.range(), std::string(Description) + " has the wrong YAML node kind"); +} + +bool MIRDocumentParser::validateKeys(const yaml::Node &Mapping, + std::span<const std::string_view> Keys, + std::string_view Description) { + for (const yaml::MappingEntry &Entry : Mapping.mapping()) { + if (std::ranges::find(Keys, Entry.Key) == Keys.end()) { + return fail(Entry.KeyRange, + "unknown " + std::string(Description) + " key '" + Entry.Key + "'"); + } + } + return false; +} + +bool MIRDocumentParser::scalar(const yaml::Node &Node, std::string_view &Value, + std::string_view Description) { + if (requireKind(Node, yaml::NodeKind::Scalar, Description)) + return true; + Value = Node.scalar(); + return false; +} + +bool MIRDocumentParser::boolean(const yaml::Node &Node, bool &Value) { + std::string_view Text; + if (scalar(Node, Text, "boolean") || !parseBool(Text, Value)) + return fail(Node.range(), "expected 'true' or 'false'"); + return false; +} + +bool MIRDocumentParser::unsignedValue(const yaml::Node &Node, std::uint64_t &Value, + std::string_view Description) { + std::string_view Text; + if (scalar(Node, Text, Description) || !parseUnsigned(Text, Value)) + return fail(Node.range(), "expected " + std::string(Description)); + return false; +} + +bool MIRDocumentParser::signedValue(const yaml::Node &Node, std::int64_t &Value, + std::string_view Description) { + std::string_view Text; + if (scalar(Node, Text, Description) || !parseSigned(Text, Value)) + return fail(Node.range(), "expected " + std::string(Description)); + return false; +} + +bool MIRDocumentParser::alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero) { + std::uint64_t Bytes = 0; + if (unsignedValue(Node, Bytes, "an unsigned alignment")) + return true; + if (Bytes == 0 && AllowZero) { + Value = Alignment{}; + return false; + } + auto Parsed = Alignment::create(Bytes); + if (!Parsed) + return importError(Node.range(), Parsed.error()); + Value = *Parsed; + return false; +} + +bool MIRDocumentParser::physicalRegister(std::string_view Text, const TextRange &Range, + PhysicalRegister &Reg) { + Text = trim(Text); + if (!Text.starts_with('$') || Text.size() == 1) + return fail(Range, "physical register must use the '$name' MIR spelling"); + const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(Text.substr(1)); + if (Description == nullptr) + return fail(Range, "unknown physical register '" + std::string(Text) + "'"); + Reg = Description->Reg; + return false; +} + +bool MIRDocumentParser::physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg) { + std::string_view Text; + return scalar(Node, Text, "physical register") || physicalRegister(Text, Node.range(), Reg); +} + +bool MIRDocumentParser::virtualRegister(std::string_view Text, const TextRange &Range, + const MIParserContext &Context, Register &Reg) { + const auto ID = parsePrefixedID(Text, "%"); + if (!ID) + return fail(Range, "virtual register must use the '%N' MIR spelling"); + const auto Found = + std::ranges::find_if(Context.VirtualRegisters, + [ID](const MIVirtualRegisterSlot &Slot) { return Slot.TextID == *ID; }); + if (Found == Context.VirtualRegisters.end()) + return fail(Range, "reference to undefined virtual register '%" + std::to_string(*ID) + "'"); + Reg = Found->Reg; + return false; +} + +Expected<MachineMetadataReference> +MIRDocumentParser::resolveMetadataReference(const MachineFunction &Function, + std::uint32_t Slot) const { + if (ActiveDocument != nullptr && ActiveDocument->EmbeddedIR && + ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { + return MachineMetadataReference::embeddedIR(Slot); + } + if (Function.metadata().node(Slot) != nullptr) + return MachineMetadataReference::machineFunction(Slot); + return makeUnexpected(ErrorCode::ParseError, + "metadata reference uses undefined slot '!" + std::to_string(Slot) + "'"); +} + +bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, + MachineVariableDebugInfo &DebugInfo) { + const auto Resolve = [this, &Function](std::uint32_t Slot) { + return resolveMetadataReference(Function, Slot); + }; + if (const yaml::Node *VariableNode = mappingValue(Entry, "debug-info-variable")) { + std::string_view Text; + if (scalar(*VariableNode, Text, "debug variable")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Reference = Parser.parseReferenceOnly(); + if (!Reference) + return importError(VariableNode->range(), Reference.error(), "mir-debug-metadata"); + if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(VariableNode->range(), + "debug variable must reference embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Variable = *Reference; + } + } + if (const yaml::Node *ExpressionNode = mappingValue(Entry, "debug-info-expression")) { + std::string_view Text; + if (scalar(*ExpressionNode, Text, "debug expression")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Value = Parser.parseValue(); + if (!Value) + return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata"); + if (Value->reference() != nullptr && + Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(ExpressionNode->range(), + "debug expression reference must use embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Expression = std::move(*Value); + } + } + if (const yaml::Node *LocationNode = mappingValue(Entry, "debug-info-location")) { + std::string_view Text; + if (scalar(*LocationNode, Text, "debug location")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Location = Parser.parseLocation(); + if (!Location) + return importError(LocationNode->range(), Location.error(), "mir-debug-metadata"); + if (Location->reference() != nullptr && + Location->reference()->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(LocationNode->range(), + "variable debug location must reference embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Location = std::move(*Location); + } + } + if (!DebugInfo.Variable && !DebugInfo.Expression && !DebugInfo.Location) + return false; + auto Verification = DebugInfo.verifyStructure(); + if (!Verification) + return importError(Entry.range(), Verification.error(), "mir-debug-metadata"); + return false; +} + +MIRDocumentParseResult MIRDocumentParser::run() { + yaml::ParseResult YAMLResult = yaml::parse(std::string(Result.Source.name()), + std::string(Result.Source.text()), Options.YAML); + if (!YAMLResult.succeeded()) { + const auto Diagnostics = YAMLResult.diagnostics(); + Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); + return std::move(Result); + } + Stream = YAMLResult.stream(); + if (Stream == nullptr || Stream->documents().empty()) { + static_cast<void>(fail(Result.Source.range(0, 0), "MIR stream contains no YAML documents")); + return std::move(Result); + } + + MIRDocument Staging; + ActiveDocument = &Staging; + std::size_t FirstFunction = 0; + const yaml::Node *FirstRoot = node(Stream->documents().front().Root); + if (FirstRoot != nullptr && FirstRoot->kind() == yaml::NodeKind::Scalar) { + EmbeddedIRIndexResult Embedded = indexEmbeddedIR( + std::string(Result.Source.name()) + ":embedded-ir", std::string(FirstRoot->scalar())); + if (!Embedded.succeeded()) { + const auto Diagnostics = Embedded.diagnostics(); + Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); + return std::move(Result); + } + Staging.EmbeddedIR = std::move(Embedded).takeIndex(); + FirstFunction = 1; + } + + if (const EmbeddedIRReferenceIndex *Embedded = Staging.embeddedIR()) { + if (const auto DataLayout = Embedded->dataLayout(); + DataLayout && !Options.DataLayout.empty() && *DataLayout != Options.DataLayout) { + static_cast<void>(fail(Stream->documents().front().Range, + "explicit DataLayout does not match embedded LLVM IR data layout")); + return std::move(Result); + } + } + if (!effectiveDataLayout().empty()) { + auto PointerSize = llvmPointerSizeInBits(0, pointerLayoutOptions()); + if (!PointerSize) { + static_cast<void>( + importError(Stream->documents().front().Range, PointerSize.error(), "mir-data-layout")); + return std::move(Result); + } + } + + if (FirstFunction == Stream->documents().size()) { + static_cast<void>(fail(Stream->documents().front().Range, + "MIR stream must contain at least one machine function document")); + return std::move(Result); + } + + for (std::size_t Index = FirstFunction; Index < Stream->documents().size(); ++Index) { + const yaml::Node *Root = node(Stream->documents()[Index].Root); + if (Root == nullptr || requireKind(*Root, yaml::NodeKind::Mapping, "machine function") || + parseFunction(*Root, Staging)) { + return std::move(Result); + } + } + + auto UnitResult = Staging.Unit.verify(); + if (!UnitResult) { + static_cast<void>( + fail(Stream->documents().back().Range, "parsed compilation unit failed verification: " + + std::string(UnitResult.error().message()))); + return std::move(Result); + } + ActiveDocument = nullptr; + Result.Document = std::move(Staging); + return std::move(Result); +} + +bool MIRDocumentParser::parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, + MIRFunctionDocumentInfo &DocumentInfo) { + if (const yaml::Node *AlignNode = mappingValue(Root, "alignment")) { + Alignment Align; + if (alignment(*AlignNode, Align, true)) + return true; + if (Align.bytes() != 1 || AlignNode->scalar() != "0") + Function.attributes().setAlignment(Align); + } + + constexpr std::array PropertyBindings{ + std::pair{"legalized", MachineFunctionProperty::Legalized}, + std::pair{"regBankSelected", MachineFunctionProperty::RegBankSelected}, + std::pair{"selected", MachineFunctionProperty::Selected}, + std::pair{"failedISel", MachineFunctionProperty::FailedISel}, + std::pair{"tracksRegLiveness", MachineFunctionProperty::TracksLiveness}, + std::pair{"noPhis", MachineFunctionProperty::NoPHIs}, + std::pair{"isSSA", MachineFunctionProperty::IsSSA}, + std::pair{"noVRegs", MachineFunctionProperty::NoVRegs}, + std::pair{"failsVerification", MachineFunctionProperty::FailsVerification}, + }; + for (const auto &[Key, Property] : PropertyBindings) { + const yaml::Node *ValueNode = mappingValue(Root, Key); + if (ValueNode == nullptr) + continue; + bool Value = false; + if (boolean(*ValueNode, Value)) + return true; + if (Value) + Function.properties().set(Property); + else + Function.properties().clear(Property); + if (Key == std::string_view("noPhis")) + DocumentInfo.NoPHIs = Value; + else if (Key == std::string_view("isSSA")) + DocumentInfo.IsSSA = Value; + else if (Key == std::string_view("noVRegs")) + DocumentInfo.NoVRegs = Value; + } + + constexpr std::array AttributeBindings{ + std::pair{"exposesReturnsTwice", MachineFunctionAttribute::ExposesReturnsTwice}, + std::pair{"hasWinCFI", MachineFunctionAttribute::HasWinCFI}, + std::pair{"hasFakeUses", MachineFunctionAttribute::HasFakeUses}, + std::pair{"callsEHReturn", MachineFunctionAttribute::CallsEHReturn}, + std::pair{"callsUnwindInit", MachineFunctionAttribute::CallsUnwindInit}, + std::pair{"hasEHContTarget", MachineFunctionAttribute::HasEHContTarget}, + std::pair{"hasEHScopes", MachineFunctionAttribute::HasEHScopes}, + std::pair{"hasEHFunclets", MachineFunctionAttribute::HasEHFunclets}, + std::pair{"isOutlined", MachineFunctionAttribute::IsOutlined}, + std::pair{"debugInstrRef", MachineFunctionAttribute::DebugInstrRef}, + std::pair{"tracksDebugUserValues", MachineFunctionAttribute::TracksDebugUserValues}, + }; + for (const auto &[Key, Attribute] : AttributeBindings) { + const yaml::Node *ValueNode = mappingValue(Root, Key); + if (ValueNode == nullptr) + continue; + bool Value = false; + if (boolean(*ValueNode, Value)) + return true; + if (Value) + Function.attributes().set(Attribute); + else + Function.attributes().clear(Attribute); + if (Key == std::string_view("hasFakeUses")) + DocumentInfo.HasFakeUses = Value; + } + + return false; +} + +bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunction &Function, + const MIRFunctionDocumentInfo &DocumentInfo) { + bool HasPHI = false; + bool HasFakeUse = false; + bool IsSSA = true; + bool HasTiedOperands = false; + bool AllTiedOperandsRewritten = true; + std::unordered_set<Register> Definitions; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return fail(Root.range(), "instruction has no target descriptor"); + HasPHI |= Descriptor->Flags.has(InstructionFlag::Phi); + HasFakeUse |= Descriptor->Flags.has(InstructionFlag::FakeUse); + const auto Operands = Instruction->operands(); + for (std::size_t Index = 0; Index < Operands.size(); ++Index) { + const MachineOperand &Operand = Operands[Index]; + const auto Reg = Operand.registerValue(); + if (Reg && Reg->isVirtual() && Operand.isDef() && !Definitions.insert(*Reg).second) { + IsSSA = false; + } + if (!Operand.tiedOperandIndex() || !Operand.isUse()) + continue; + HasTiedOperands = true; + const MachineOperand *Definition = Instruction->operandAt(*Operand.tiedOperandIndex()); + if (Definition == nullptr || Definition->registerValue() != Reg) + AllTiedOperandsRewritten = false; + } + } + } + const bool NoPHIs = !HasPHI; + const bool NoVRegs = + std::ranges::none_of(Function.registerInfo().virtualRegisters(), + [](const VirtualRegisterInfo &Info) { return !Info.IsTombstone; }); + + const auto ApplyComputed = [this, &Root, &Function](std::optional<bool> Explicit, bool Computed, + MachineFunctionProperty Property, + std::string_view Name) -> bool { + if (Explicit && *Explicit && !Computed) { + return fail(Root.range(), "explicit property '" + std::string(Name) + + "' conflicts with the parsed machine function"); + } + if (Explicit.value_or(Computed)) + Function.properties().set(Property); + else + Function.properties().clear(Property); + return false; + }; + if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, "noPhis") || + ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, "isSSA") || + ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, "noVRegs")) { + return true; + } + + if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) { + return fail(Root.range(), + "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction"); + } + if (DocumentInfo.HasFakeUses.value_or(HasFakeUse)) + Function.attributes().set(MachineFunctionAttribute::HasFakeUses); + else + Function.attributes().clear(MachineFunctionAttribute::HasFakeUses); + + if (HasTiedOperands && AllTiedOperandsRewritten) + Function.properties().set(MachineFunctionProperty::TiedOpsRewritten); + else + Function.properties().clear(MachineFunctionProperty::TiedOpsRewritten); + return false; +} + +bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Registers = mappingValue(Root, "registers"); + if (Registers != nullptr) { + if (requireKind(*Registers, yaml::NodeKind::Sequence, "registers")) + return true; + constexpr std::array RegisterKeys{ + std::string_view{"id"}, + std::string_view{"class"}, + std::string_view{"preferred-register"}, + std::string_view{"flags"}, + }; + std::unordered_set<std::uint32_t> IDs; + for (const yaml::NodeID ElementID : Registers->sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "register entry") || + validateKeys(*Element, RegisterKeys, "register")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Element, "id"); + const yaml::Node *ClassNode = mappingValue(*Element, "class"); + if (IDNode == nullptr || ClassNode == nullptr) + return fail(Element->range(), "register entry requires 'id' and 'class'"); + std::uint64_t WideID = 0; + std::string_view ClassName; + if (unsignedValue(*IDNode, WideID, "a 32-bit virtual-register ID") || + WideID > std::numeric_limits<std::uint32_t>::max() || + scalar(*ClassNode, ClassName, "register class or bank")) { + return true; + } + const auto TextID = static_cast<std::uint32_t>(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate virtual register ID"); + auto Created = Function.registerInfo().createVirtualRegister(); + if (!Created) + return importError(IDNode->range(), Created.error()); + Context.VirtualRegisters.push_back({TextID, *Created}); + + if (ClassName != "_") { + if (const RegisterClassDesc *Class = Target.registers().registerClass(ClassName)) { + auto SetResult = Function.registerInfo().setRegisterClass(*Created, Class->ID); + if (!SetResult) + return importError(ClassNode->range(), SetResult.error()); + } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(ClassName)) { + auto SetResult = Function.registerInfo().setRegisterBank(*Created, Bank->ID); + if (!SetResult) + return importError(ClassNode->range(), SetResult.error()); + } else { + return fail(ClassNode->range(), + "unknown register class or bank '" + std::string(ClassName) + "'"); + } + } + + if (const yaml::Node *Preferred = mappingValue(*Element, "preferred-register")) { + PhysicalRegister Reg; + if (physicalRegister(*Preferred, Reg)) + return true; + auto AddResult = Function.registerInfo().addAllocationHint(*Created, Reg); + if (!AddResult) + return importError(Preferred->range(), AddResult.error()); + } + if (const yaml::Node *Flags = mappingValue(*Element, "flags")) { + if (requireKind(*Flags, yaml::NodeKind::Sequence, "virtual-register flags")) + return true; + std::uint8_t Mask = 0; + for (const yaml::NodeID FlagID : Flags->sequence()) { + const yaml::Node *FlagNode = node(FlagID); + std::string_view Name; + if (FlagNode == nullptr || scalar(*FlagNode, Name, "virtual-register flag")) + return true; + const auto Flag = Target.mirText().virtualRegisterFlag(Name); + if (!Flag) + return fail(FlagNode->range(), + "unknown virtual-register flag '" + std::string(Name) + "'"); + if ((Mask & *Flag) != 0) + return fail(FlagNode->range(), "duplicate virtual-register flag"); + Mask = static_cast<std::uint8_t>(Mask | *Flag); + } + auto SetResult = Function.registerInfo().setTargetRegisterFlags(*Created, Mask); + if (!SetResult) + return importError(Flags->range(), SetResult.error()); + } + } + } + + const yaml::Node *LiveIns = mappingValue(Root, "liveins"); + if (LiveIns != nullptr) { + if (requireKind(*LiveIns, yaml::NodeKind::Sequence, "liveins")) + return true; + constexpr std::array LiveInKeys{ + std::string_view{"reg"}, + std::string_view{"virtual-reg"}, + }; + for (const yaml::NodeID ElementID : LiveIns->sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "live-in entry") || + validateKeys(*Element, LiveInKeys, "live-in")) { + return true; + } + const yaml::Node *RegNode = mappingValue(*Element, "reg"); + if (RegNode == nullptr) + return fail(Element->range(), "live-in entry requires 'reg'"); + PhysicalRegister Reg; + if (physicalRegister(*RegNode, Reg)) + return true; + std::optional<Register> Initial; + if (const yaml::Node *VirtualNode = mappingValue(*Element, "virtual-reg")) { + std::string_view Text; + Register Virtual; + if (scalar(*VirtualNode, Text, "virtual register") || + virtualRegister(Text, VirtualNode->range(), Context, Virtual)) { + return true; + } + Initial = Virtual; + } + auto AddResult = Function.registerInfo().addLiveIn(Reg, Initial); + if (!AddResult) + return importError(Element->range(), AddResult.error()); + } + } + + if (const yaml::Node *CalleeSaved = mappingValue(Root, "calleeSavedRegisters")) { + if (requireKind(*CalleeSaved, yaml::NodeKind::Sequence, "callee-saved registers")) + return true; + std::vector<PhysicalRegister> RegistersValue; + for (const yaml::NodeID RegID : CalleeSaved->sequence()) { + const yaml::Node *RegNode = node(RegID); + PhysicalRegister Reg; + if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) + return true; + RegistersValue.push_back(Reg); + } + auto SetResult = Function.records().setCalleeSavedRegisters(std::move(RegistersValue)); + if (!SetResult) + return importError(CalleeSaved->range(), SetResult.error()); + } + return false; +} + +bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + if (ActiveDocument->EmbeddedIR) { + for (const EmbeddedIRMetadataDefinition &Reference : + ActiveDocument->EmbeddedIR->metadataDefinitions()) { + std::uint32_t ID = 0; + if (!parseUnsigned32(Reference.Name, ID)) + continue; + if (std::ranges::any_of(Context.Metadata, + [ID](const MIMetadataSlot &Slot) { return Slot.TextID == ID; })) { + return fail(Root.range(), "embedded LLVM IR metadata slots are not unique"); + } + Context.Metadata.push_back( + {.TextID = ID, .Reference = MachineMetadataReference::embeddedIR(ID)}); + } + } + + const yaml::Node *Metadata = mappingValue(Root, "machineMetadataNodes"); + if (Metadata == nullptr) + return false; + if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes")) + return true; + + struct Definition final { + const yaml::Node *Node = nullptr; + std::uint32_t Slot = 0; + bool IsDistinct = false; + std::string_view Elements; + }; + std::vector<Definition> Definitions; + std::unordered_set<std::uint32_t> Slots; + + for (const yaml::NodeID DefinitionID : Metadata->sequence()) { + const yaml::Node *DefinitionNode = node(DefinitionID); + std::string_view Text; + if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) { + return true; + } + Text = trim(Text); + if (!Text.starts_with('!')) + return fail(DefinitionNode->range(), "machine metadata definition must start with '!N'"); + const std::size_t Equals = Text.find('='); + if (Equals == std::string_view::npos) + return fail(DefinitionNode->range(), "machine metadata definition requires '='"); + std::uint32_t Slot = 0; + if (!parseUnsigned32(trim(Text.substr(1, Equals - 1)), Slot)) + return fail(DefinitionNode->range(), "machine metadata definition has an invalid slot"); + if (!Slots.insert(Slot).second) + return fail(DefinitionNode->range(), "duplicate machine metadata slot"); + std::string_view Right = trim(Text.substr(Equals + 1)); + bool IsDistinct = false; + if (Right.starts_with("distinct")) { + Right = trim(Right.substr(std::string_view("distinct").size())); + IsDistinct = true; + } + if (!Right.starts_with("!{") || !Right.ends_with('}')) + return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple"); + Definitions.push_back({.Node = DefinitionNode, + .Slot = Slot, + .IsDistinct = IsDistinct, + .Elements = Right.substr(2, Right.size() - 3)}); + Context.Metadata.push_back( + {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)}); + } + + auto ResolveReference = [this, + &Slots](std::uint32_t Slot, + const TextRange &Range) -> Expected<MachineMetadataElement> { + MachineMetadataReference Reference; + if (ActiveDocument->EmbeddedIR != std::nullopt && + ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { + Reference = MachineMetadataReference::embeddedIR(Slot); + } else if (Slots.contains(Slot)) { + Reference = MachineMetadataReference::machineFunction(Slot); + } else { + return makeUnexpected(ErrorCode::ParseError, "metadata tuple references undefined slot '!" + + std::to_string(Slot) + "' at line " + + std::to_string(Range.Begin.Line)); + } + return MachineMetadataElement::reference(Reference); + }; + + for (const Definition &Definition : Definitions) { + MachineMetadataNode NewNode{ + .Slot = Definition.Slot, + .IsDistinct = Definition.IsDistinct, + .Elements = {}, + }; + std::string_view Remaining = trim(Definition.Elements); + while (!Remaining.empty()) { + std::size_t End = 0; + bool InString = false; + bool Escaped = false; + for (; End < Remaining.size(); ++End) { + const char Character = Remaining[End]; + if (InString && Escaped) { + Escaped = false; + continue; + } + if (InString && Character == '\\') { + Escaped = true; + continue; + } + if (Character == '"') { + InString = !InString; + continue; + } + if (!InString && Character == ',') + break; + } + if (InString) + return fail(Definition.Node->range(), "unterminated machine metadata string"); + std::string_view Element = trim(Remaining.substr(0, End)); + if (Element.empty()) + return fail(Definition.Node->range(), "empty machine metadata tuple element"); + + if (Element == "null") { + NewNode.Elements.push_back(MachineMetadataElement::null()); + } else if (Element.starts_with('!') && Element.size() > 1 && Element[1] >= '0' && + Element[1] <= '9') { + std::uint32_t ReferenceSlot = 0; + if (!parseUnsigned32(Element.substr(1), ReferenceSlot)) + return fail(Definition.Node->range(), "invalid metadata tuple reference"); + auto Reference = ResolveReference(ReferenceSlot, Definition.Node->range()); + if (!Reference) + return importError(Definition.Node->range(), Reference.error()); + NewNode.Elements.push_back(std::move(*Reference)); + } else if (Element.starts_with("!\"") && Element.ends_with('"')) { + std::string Value; + std::string_view Encoded = Element.substr(2, Element.size() - 3); + for (std::size_t Index = 0; Index < Encoded.size(); ++Index) { + if (Encoded[Index] == '\\' && Index + 1 < Encoded.size()) { + const char EscapedCharacter = Encoded[++Index]; + if (EscapedCharacter != '\\' && EscapedCharacter != '"') + return fail(Definition.Node->range(), "unsupported machine metadata string escape"); + Value.push_back(EscapedCharacter); + } else { + Value.push_back(Encoded[Index]); + } + } + NewNode.Elements.push_back(MachineMetadataElement::string(std::move(Value))); + } else { + return fail(Definition.Node->range(), "unsupported machine metadata tuple element"); + } + + if (End == Remaining.size()) + Remaining = {}; + else + Remaining = trim(Remaining.substr(End + 1)); + } + auto AddResult = Function.metadata().addNode(std::move(NewNode)); + if (!AddResult) + return importError(Definition.Node->range(), AddResult.error()); + } + return false; +} + +bool MIRDocumentParser::parseStackObjects(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + auto ParseList = [this, &Function, &Context](const yaml::Node &List, bool Fixed) -> bool { + if (requireKind(List, yaml::NodeKind::Sequence, + Fixed ? "fixed stack objects" : "stack objects")) { + return true; + } + constexpr std::array FixedKeys{ + std::string_view{"id"}, + std::string_view{"type"}, + std::string_view{"offset"}, + std::string_view{"size"}, + std::string_view{"alignment"}, + std::string_view{"stack-id"}, + std::string_view{"isImmutable"}, + std::string_view{"isAliased"}, + std::string_view{"callee-saved-register"}, + std::string_view{"callee-saved-restored"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + constexpr std::array StackKeys{ + std::string_view{"id"}, + std::string_view{"name"}, + std::string_view{"type"}, + std::string_view{"offset"}, + std::string_view{"size"}, + std::string_view{"alignment"}, + std::string_view{"stack-id"}, + std::string_view{"callee-saved-register"}, + std::string_view{"callee-saved-restored"}, + std::string_view{"local-offset"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + std::unordered_set<std::uint32_t> IDs; + for (const yaml::NodeID ElementID : List.sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "stack object") || + (Fixed ? validateKeys(*Element, FixedKeys, "fixed stack object") + : validateKeys(*Element, StackKeys, "stack object"))) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Element, "id"); + if (IDNode == nullptr) + return fail(Element->range(), "stack object requires 'id'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit stack object ID") || + WideID > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + const auto TextID = static_cast<std::uint32_t>(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate stack object ID"); + + StackObjectDesc Description; + Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; + Description.Offset = 0; + if (const yaml::Node *NameNode = mappingValue(*Element, "name")) { + std::string_view Name; + if (scalar(*NameNode, Name, "stack object name") || Name.empty()) + return fail(NameNode->range(), "stack object name cannot be empty"); + Description.AllocaName = std::string(Name); + } + if (const yaml::Node *TypeNode = mappingValue(*Element, "type")) { + std::string_view Type; + if (scalar(*TypeNode, Type, "stack object type")) + return true; + if (Type == "default") + Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; + else if (Type == "spill-slot") + Description.Kind = Fixed ? StackObjectKind::FixedSpillSlot : StackObjectKind::SpillSlot; + else if (!Fixed && Type == "variable-sized") + Description.Kind = StackObjectKind::VariableSized; + else + return fail(TypeNode->range(), "unknown stack object type '" + std::string(Type) + "'"); + } + if (const yaml::Node *OffsetNode = mappingValue(*Element, "offset")) { + std::int64_t Offset = 0; + if (signedValue(*OffsetNode, Offset, "a signed stack object offset")) + return true; + Description.Offset = Offset; + } else if (Fixed) { + Description.Offset = 0; + } + if (Description.Kind != StackObjectKind::VariableSized) { + const yaml::Node *SizeNode = mappingValue(*Element, "size"); + if (SizeNode == nullptr) { + if (!Fixed) + return fail(Element->range(), "fixed-size stack object requires 'size'"); + Description.Size = 0; + } else { + std::uint64_t Size = 0; + if (unsignedValue(*SizeNode, Size, "an unsigned stack object size")) + return true; + Description.Size = Size; + } + } + if (const yaml::Node *AlignmentNode = mappingValue(*Element, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } + if (const yaml::Node *StackIDNode = mappingValue(*Element, "stack-id")) { + std::string_view Name; + if (scalar(*StackIDNode, Name, "stack ID")) + return true; + const auto Stack = parseStackIDName(Name); + if (!Stack) + return fail(StackIDNode->range(), "unknown stack ID '" + std::string(Name) + "'"); + Description.Stack = *Stack; + } + if (const yaml::Node *ImmutableNode = mappingValue(*Element, "isImmutable")) { + if (boolean(*ImmutableNode, Description.IsImmutable)) + return true; + } + if (const yaml::Node *AliasedNode = mappingValue(*Element, "isAliased")) { + if (boolean(*AliasedNode, Description.IsAliased)) + return true; + } + if (const yaml::Node *SavedNode = mappingValue(*Element, "callee-saved-register")) { + PhysicalRegister Reg; + if (physicalRegister(*SavedNode, Reg)) + return true; + Description.CalleeSavedRegister = Reg; + } + if (const yaml::Node *RestoredNode = mappingValue(*Element, "callee-saved-restored")) { + if (boolean(*RestoredNode, Description.CalleeSavedRestored)) + return true; + } + if (const yaml::Node *LocalNode = mappingValue(*Element, "local-offset")) { + std::int64_t Offset = 0; + if (signedValue(*LocalNode, Offset, "a signed local frame offset")) + return true; + Description.LocalOffset = Offset; + } + MachineVariableDebugInfo DebugInfo; + if (parseVariableDebugInfo(*Element, Function, DebugInfo)) + return true; + if (DebugInfo.Variable || DebugInfo.Expression || DebugInfo.Location) + Description.DebugInfo = std::move(DebugInfo); + + auto Created = Function.frameInfo().createObject(std::move(Description)); + if (!Created) + return importError(Element->range(), Created.error()); + MIFrameObjectSlot Slot{ + .TextID = TextID, + .Index = *Created, + .Name = std::nullopt, + }; + if (const StackObjectDesc *CreatedDescription = + Function.frameInfo().object(*Created)->description(); + CreatedDescription != nullptr && CreatedDescription->AllocaName) { + Slot.Name = *CreatedDescription->AllocaName; + } + (Fixed ? Context.FixedStackObjects : Context.StackObjects).push_back(std::move(Slot)); + } + return false; + }; + + if (const yaml::Node *Fixed = mappingValue(Root, "fixedStack")) { + if (ParseList(*Fixed, true)) + return true; + } + if (const yaml::Node *Stack = mappingValue(Root, "stack")) { + if (ParseList(*Stack, false)) + return true; + } + return false; +} + +bool MIRDocumentParser::parseConstants(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Constants = mappingValue(Root, "constants"); + if (Constants == nullptr) + return false; + if (requireKind(*Constants, yaml::NodeKind::Sequence, "constant pool")) + return true; + constexpr std::array Keys{ + std::string_view{"id"}, + std::string_view{"value"}, + std::string_view{"alignment"}, + std::string_view{"isTargetSpecific"}, + }; + std::vector<LLVMNamedTypeDefinition> NamedTypes; + const std::string_view DataLayout = effectiveDataLayout(); + if (ActiveDocument->EmbeddedIR) { + const auto Definitions = ActiveDocument->EmbeddedIR->typeDefinitions(); + NamedTypes.reserve(Definitions.size()); + for (const EmbeddedIRTypeDefinition &Definition : Definitions) { + NamedTypes.push_back({.Name = Definition.Name, + .Definition = Definition.Definition, + .NumericName = Definition.NumericName}); + } + } + std::unordered_set<std::uint32_t> IDs; + for (const yaml::NodeID ConstantID : Constants->sequence()) { + const yaml::Node *Constant = node(ConstantID); + if (Constant == nullptr || + requireKind(*Constant, yaml::NodeKind::Mapping, "constant pool entry") || + validateKeys(*Constant, Keys, "constant pool entry")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Constant, "id"); + const yaml::Node *ValueNode = mappingValue(*Constant, "value"); + if (IDNode == nullptr || ValueNode == nullptr) + return fail(Constant->range(), "constant pool entry requires 'id' and 'value'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit constant pool ID") || + WideID > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + const auto TextID = static_cast<std::uint32_t>(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate constant pool ID"); + + bool IsTargetSpecific = false; + if (const yaml::Node *TargetNode = mappingValue(*Constant, "isTargetSpecific")) { + if (boolean(*TargetNode, IsTargetSpecific)) + return true; + } + if (IsTargetSpecific) { + std::string_view ValueText; + if (scalar(*ValueNode, ValueText, "target constant")) + return true; + auto TargetConstantValue = Target.mirText().parseTargetConstant(ValueText); + if (!TargetConstantValue) + return importError(ValueNode->range(), TargetConstantValue.error(), "mir-target-constant"); + auto Emission = TargetConstantValue->emit(); + if (!Emission) + return importError(ValueNode->range(), Emission.error(), "mir-target-constant"); + auto RequiredAlignment = TargetConstantValue->requiredAlignment(); + if (!RequiredAlignment) + return importError(ValueNode->range(), RequiredAlignment.error(), "mir-target-constant"); + + MachineConstantPoolEntryDesc Description; + Description.Size = Emission->Bytes.size(); + Description.Align = *RequiredAlignment; + Description.Value = std::move(*TargetConstantValue); + if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } + auto Added = Function.constantPool().addEntry(std::move(Description)); + if (!Added) + return importError(Constant->range(), Added.error()); + Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); + continue; + } + + std::string_view ValueText; + if (scalar(*ValueNode, ValueText, "LLVM constant")) + return true; + const LLVMConstantSyntaxOptions ConstantOptions{ + .DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits, + .DataLayout = DataLayout, + .NamedTypes = NamedTypes, + }; + auto Data = parseLLVMConstant(trim(ValueText), ConstantOptions); + if (!Data) + return importError(ValueNode->range(), Data.error(), "mir-llvm-constant"); + auto Size = llvmConstantStorageSize(Data->Type, ConstantOptions); + if (!Size) + return importError(ValueNode->range(), Size.error(), "mir-llvm-constant"); + + MachineConstantPoolEntryDesc Description; + Description.Size = *Size; + Description.Value = std::move(*Data); + if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } else { + auto Preferred = llvmConstantPreferredAlignment( + std::get<GenericConstantData>(Description.Value).Type, ConstantOptions); + if (!Preferred) + return importError(ValueNode->range(), Preferred.error(), "mir-llvm-constant"); + Description.Align = *Preferred; + } + auto Added = Function.constantPool().addEntry(std::move(Description)); + if (!Added) + return importError(Constant->range(), Added.error()); + Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); + } + return false; +} + +bool MIRDocumentParser::parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, + FrameIndex &Index) { + std::string_view Text; + if (scalar(Node, Text, "frame index")) + return true; + bool Fixed = false; + std::optional<std::uint32_t> ID = parsePrefixedID(Text, "%stack."); + if (!ID) { + ID = parsePrefixedID(Text, "%fixed-stack."); + Fixed = true; + } + if (!ID) + return fail(Node.range(), "frame index must use '%stack.N' or '%fixed-stack.N'"); + const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects; + const auto Found = std::ranges::find_if( + Slots, [ID](const MIFrameObjectSlot &Slot) { return Slot.TextID == *ID; }); + if (Found == Slots.end()) + return fail(Node.range(), "reference to undefined frame object"); + Index = Found->Index; + return false; +} + +MachineBasicBlock *MIRDocumentParser::blockAt(MachineFunction &Function, + std::uint32_t Number) const noexcept { + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { + if (Block->denseNumber() == Number) + return Block; + } + return nullptr; +} + +MachineInstr *MIRDocumentParser::instructionAt(MachineFunction &Function, std::uint32_t BlockNumber, + std::uint32_t Offset) const noexcept { + MachineBasicBlock *Block = blockAt(Function, BlockNumber); + if (Block == nullptr) + return nullptr; + MachineInstr *Instruction = Block->firstInstruction(); + for (std::uint32_t Index = 0; Instruction != nullptr && Index < Offset; ++Index) + Instruction = Instruction->next(); + return Instruction; +} + +bool MIRDocumentParser::parseBlockReference(std::string_view Text, const TextRange &Range, + MachineFunction &Function, MachineBasicBlock *&Block) { + const auto Number = parsePrefixedID(Text, "%bb."); + if (!Number) + return fail(Range, "basic block reference must use the '%bb.N' MIR spelling"); + Block = blockAt(Function, *Number); + if (Block == nullptr) + return fail(Range, "reference to undefined machine basic block"); + return false; +} + +bool MIRDocumentParser::parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, + std::vector<MachineFrameSaveRestorePoint> &Points) { + if (requireKind(Node, yaml::NodeKind::Sequence, "frame save/restore points")) + return true; + constexpr std::array Keys{ + std::string_view{"point"}, + std::string_view{"registers"}, + }; + for (const yaml::NodeID PointID : Node.sequence()) { + const yaml::Node *PointNode = node(PointID); + if (PointNode == nullptr || + requireKind(*PointNode, yaml::NodeKind::Mapping, "frame save/restore point") || + validateKeys(*PointNode, Keys, "frame save/restore point")) { + return true; + } + const yaml::Node *BlockNode = mappingValue(*PointNode, "point"); + if (BlockNode == nullptr) + return fail(PointNode->range(), "frame save/restore point requires 'point'"); + std::string_view BlockText; + MachineBasicBlock *Block = nullptr; + if (scalar(*BlockNode, BlockText, "basic block reference") || + parseBlockReference(BlockText, BlockNode->range(), Function, Block)) { + return true; + } + MachineFrameSaveRestorePoint Point{.Block = Block->stableID(), .Registers = {}}; + if (const yaml::Node *RegistersNode = mappingValue(*PointNode, "registers")) { + if (requireKind(*RegistersNode, yaml::NodeKind::Sequence, "save/restore point registers")) { + return true; + } + for (const yaml::NodeID RegID : RegistersNode->sequence()) { + const yaml::Node *RegNode = node(RegID); + PhysicalRegister Reg; + if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) + return true; + Point.Registers.push_back(Reg); + } + } + Points.push_back(std::move(Point)); + } + return false; +} + +bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction &Function, + const MIParserContext &Context) { + const yaml::Node *Frame = mappingValue(Root, "frameInfo"); + if (Frame == nullptr) + return false; + if (requireKind(*Frame, yaml::NodeKind::Mapping, "frameInfo")) + return true; + constexpr std::array Keys{ + std::string_view{"isFrameAddressTaken"}, + std::string_view{"isReturnAddressTaken"}, + std::string_view{"hasStackMap"}, + std::string_view{"hasPatchPoint"}, + std::string_view{"stackSize"}, + std::string_view{"offsetAdjustment"}, + std::string_view{"maxAlignment"}, + std::string_view{"adjustsStack"}, + std::string_view{"hasCalls"}, + std::string_view{"framePointerPolicy"}, + std::string_view{"stackProtector"}, + std::string_view{"functionContext"}, + std::string_view{"maxCallFrameSize"}, + std::string_view{"cvBytesOfCalleeSavedRegisters"}, + std::string_view{"hasOpaqueSPAdjustment"}, + std::string_view{"hasVAStart"}, + std::string_view{"hasMustTailInVarArgFunc"}, + std::string_view{"hasTailCall"}, + std::string_view{"isCalleeSavedInfoValid"}, + std::string_view{"localFrameSize"}, + std::string_view{"savePoint"}, + std::string_view{"restorePoint"}, + }; + if (validateKeys(*Frame, Keys, "frameInfo")) + return true; + + MachineFrameState State = Function.frameInfo().state(); + const std::array BoolBindings{ + std::pair{"isFrameAddressTaken", &State.IsFrameAddressTaken}, + std::pair{"isReturnAddressTaken", &State.IsReturnAddressTaken}, + std::pair{"hasStackMap", &State.HasStackMap}, + std::pair{"hasPatchPoint", &State.HasPatchPoint}, + std::pair{"adjustsStack", &State.AdjustsStack}, + std::pair{"hasCalls", &State.HasCalls}, + std::pair{"hasOpaqueSPAdjustment", &State.HasOpaqueSPAdjustment}, + std::pair{"hasVAStart", &State.HasVAStart}, + std::pair{"hasMustTailInVarArgFunc", &State.HasMustTailInVarArgFunc}, + std::pair{"hasTailCall", &State.HasTailCall}, + std::pair{"isCalleeSavedInfoValid", &State.IsCalleeSavedInfoValid}, + }; + for (const auto &[Key, Destination] : BoolBindings) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + if (boolean(*Value, *Destination)) + return true; + } + } + + if (const yaml::Node *Value = mappingValue(*Frame, "stackSize")) { + if (unsignedValue(*Value, State.StackSize, "an unsigned stack size")) + return true; + } + if (const yaml::Node *Value = mappingValue(*Frame, "offsetAdjustment")) { + std::int64_t Wide = 0; + if (signedValue(*Value, Wide, "a 32-bit signed offset adjustment") || + Wide < std::numeric_limits<std::int32_t>::min() || + Wide > std::numeric_limits<std::int32_t>::max()) { + return fail(Value->range(), "offset adjustment is outside the 32-bit signed range"); + } + State.OffsetAdjustment = static_cast<std::int32_t>(Wide); + } + if (const yaml::Node *Value = mappingValue(*Frame, "maxAlignment")) { + std::uint64_t Bytes = 0; + if (unsignedValue(*Value, Bytes, "an unsigned maximum alignment")) + return true; + if (Bytes == 0) { + State.MaxAlignment.reset(); + } else { + auto Parsed = Alignment::create(Bytes); + if (!Parsed) + return importError(Value->range(), Parsed.error()); + State.MaxAlignment = *Parsed; + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "framePointerPolicy")) { + std::string_view Name; + if (scalar(*Value, Name, "frame-pointer policy")) + return true; + const auto Policy = parseMachineFramePointerPolicy(Name); + if (!Policy) + return fail(Value->range(), "unknown frame-pointer policy '" + std::string(Name) + "'"); + State.FramePointerPolicy = *Policy; + } + for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector}, + std::pair{"functionContext", &State.FunctionContext}}) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + FrameIndex Index; + if (parseFrameReference(*Value, Context, Index)) + return true; + *Destination = Index; + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "maxCallFrameSize")) { + std::uint64_t Size = 0; + if (unsignedValue(*Value, Size, "a 32-bit unsigned maximum call-frame size") || + Size > std::numeric_limits<std::uint32_t>::max()) { + return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range"); + } + State.MaxCallFrameSize = Size; + } + for (const auto &[Key, Destination] : + {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters}, + std::pair{"localFrameSize", &State.LocalFrameSize}}) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + std::uint64_t Wide = 0; + if (unsignedValue(*Value, Wide, "a 32-bit unsigned frame value") || + Wide > std::numeric_limits<std::uint32_t>::max()) { + return fail(Value->range(), "frame value is outside the 32-bit unsigned range"); + } + *Destination = static_cast<std::uint32_t>(Wide); + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "savePoint")) { + if (parseSaveRestorePoints(*Value, Function, State.SavePoints)) + return true; + } + if (const yaml::Node *Value = mappingValue(*Frame, "restorePoint")) { + if (parseSaveRestorePoints(*Value, Function, State.RestorePoints)) + return true; + } + auto StateResult = Function.frameInfo().setState(std::move(State)); + if (!StateResult) + return importError(Frame->range(), StateResult.error()); + return false; +} + +bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context, + std::vector<PendingJumpTable> &Pending) { + const yaml::Node *JumpTable = mappingValue(Root, "jumpTable"); + if (JumpTable == nullptr) + return false; + if (requireKind(*JumpTable, yaml::NodeKind::Mapping, "jump table")) + return true; + constexpr std::array JumpTableKeys{ + std::string_view{"kind"}, + std::string_view{"entries"}, + }; + if (validateKeys(*JumpTable, JumpTableKeys, "jump table")) + return true; + const yaml::Node *KindNode = mappingValue(*JumpTable, "kind"); + if (KindNode == nullptr) + return fail(JumpTable->range(), "jump table requires 'kind'"); + std::string_view KindName; + if (scalar(*KindNode, KindName, "jump table kind")) + return true; + + JumpTableEntryEncoding Encoding; + std::uint8_t EntrySize = 8; + if (KindName == "block-address") + Encoding = JumpTableEntryEncoding::BlockAddress; + else if (KindName == "gp-rel64-block-address") + Encoding = JumpTableEntryEncoding::GPRelative64BlockAddress; + else if (KindName == "gp-rel32-block-address") { + Encoding = JumpTableEntryEncoding::GPRelative32BlockAddress; + EntrySize = 4; + } else if (KindName == "label-difference32") { + Encoding = JumpTableEntryEncoding::LabelDifference32; + EntrySize = 4; + } else if (KindName == "label-difference64") + Encoding = JumpTableEntryEncoding::LabelDifference64; + else if (KindName == "inline") { + Encoding = JumpTableEntryEncoding::Inline; + EntrySize = 4; + } else if (KindName == "custom32") { + Encoding = JumpTableEntryEncoding::Custom32; + EntrySize = 4; + } else { + return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'"); + } + + const yaml::Node *Entries = mappingValue(*JumpTable, "entries"); + if (Entries == nullptr) + return false; + if (requireKind(*Entries, yaml::NodeKind::Sequence, "jump table entries")) + return true; + constexpr std::array EntryKeys{ + std::string_view{"id"}, + std::string_view{"blocks"}, + }; + std::unordered_set<std::uint32_t> IDs; + for (const yaml::NodeID EntryID : Entries->sequence()) { + const yaml::Node *Entry = node(EntryID); + if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "jump table entry") || + validateKeys(*Entry, EntryKeys, "jump table entry")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Entry, "id"); + if (IDNode == nullptr) + return fail(Entry->range(), "jump table entry requires 'id'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit jump table ID") || + WideID > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + const auto TextID = static_cast<std::uint32_t>(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate jump table ID"); + auto Reserved = Function.jumpTableInfo().reserveTable(); + if (!Reserved) + return importError(Entry->range(), Reserved.error()); + PendingJumpTable NewPending{ + .Index = *Reserved, + .TextID = TextID, + .Encoding = Encoding, + .EntrySize = EntrySize, + .Blocks = {}, + }; + if (const yaml::Node *Blocks = mappingValue(*Entry, "blocks")) { + if (requireKind(*Blocks, yaml::NodeKind::Sequence, "jump table blocks")) + return true; + for (const yaml::NodeID BlockID : Blocks->sequence()) { + const yaml::Node *BlockNode = node(BlockID); + std::string_view Text; + if (BlockNode == nullptr || scalar(*BlockNode, Text, "basic block reference")) + return true; + NewPending.Blocks.emplace_back(Text); + } + } + if (NewPending.Blocks.empty()) + return fail(Entry->range(), "jump table entry must contain at least one block"); + Context.JumpTables.push_back({.TextID = TextID, .Index = *Reserved}); + Pending.push_back(std::move(NewPending)); + } + return false; +} + +bool MIRDocumentParser::defineJumpTables(MachineFunction &Function, + std::span<const PendingJumpTable> Pending) { + for (const PendingJumpTable &Table : Pending) { + MachineJumpTableDesc Description; + Description.Encoding = Table.Encoding; + Description.EntrySize = Table.EntrySize; + Description.Align = *Alignment::create(Table.EntrySize); + for (const std::string &Text : Table.Blocks) { + MachineBasicBlock *Block = nullptr; + if (parseBlockReference(Text, Result.Source.range(0, 0), Function, Block)) + return true; + Description.Targets.push_back(Block); + } + auto DefineResult = + Function.jumpTableInfo().defineReservedTable(Table.Index, std::move(Description)); + if (!DefineResult) + return importError(Result.Source.range(0, 0), DefineResult.error()); + } + return false; +} + +TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, + const TextRange &BodyRange) const noexcept { + if (Body.scalarStyle() != yaml::ScalarStyle::Literal) + return Body.range(); + + const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U; + const std::size_t LastBodyLine = Body.range().End.Line; + if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0) + return Body.range(); + + std::size_t Indent = 0; + bool FoundContent = false; + for (std::size_t Line = FirstBodyLine; Line <= LastBodyLine; ++Line) { + const std::string_view Text = Result.Source.line(Line); + std::size_t CurrentIndent = 0; + while (CurrentIndent < Text.size() && Text[CurrentIndent] == ' ') + ++CurrentIndent; + if (CurrentIndent == Text.size()) + continue; + Indent = CurrentIndent; + FoundContent = true; + break; + } + if (!FoundContent) + return Body.range(); + + const TextPosition Fallback = Body.range().Begin; + const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent, + Fallback](TextPosition Position) { + const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U; + if (Position.Line == 0 || SourceLine > LastBodyLine || SourceLine > Result.Source.lineCount()) { + return Fallback; + } + const std::string_view SourceLineText = Result.Source.line(SourceLine); + const std::size_t BodyColumn = Position.Column == 0 ? 0 : Position.Column - 1U; + const std::size_t SourceColumn = std::min(Indent + BodyColumn, SourceLineText.size()); + const std::size_t Offset = Result.Source.lineOffsets()[SourceLine - 1U] + SourceColumn; + return Result.Source.positionAt(Offset); + }; + return {.Begin = MapPosition(BodyRange.Begin), .End = MapPosition(BodyRange.End)}; +} + +bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Body = mappingValue(Root, "body"); + std::string BodyText; + TextRange BodyRange = Root.range(); + if (Body != nullptr) { + std::string_view Text; + if (scalar(*Body, Text, "machine function body")) + return true; + BodyText = Text; + BodyRange = Body->range(); + } + + if (Options.DefaultPointerSizeInBits != 0) + Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; + if (!effectiveDataLayout().empty()) { + const LLVMConstantSyntaxOptions PointerOptions = pointerLayoutOptions(); + Context.PointerSizeInBits = [PointerOptions](std::uint32_t AddressSpace) { + return llvmPointerSizeInBits(AddressSpace, PointerOptions); + }; + } + Context.SyncScopes = ActiveDocument->SyncScopes; + Context.InternSyncScope = [this](std::string_view Name) -> Expected<SyncScopeID> { + if (Name.empty()) + return makeUnexpected(ErrorCode::ParseError, "synchronization scope name cannot be empty"); + const auto Existing = + std::ranges::find_if(ActiveDocument->SyncScopes, + [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; }); + if (Existing != ActiveDocument->SyncScopes.end()) + return Existing->ID; + if (ActiveDocument->SyncScopes.size() >= + static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "MIR document contains too many synchronization scopes"); + } + const auto ID = + SyncScopeID::fromValue(static_cast<std::uint32_t>(ActiveDocument->SyncScopes.size() + 1U)); + ActiveDocument->SyncScopes.push_back({.Name = std::string(Name), .ID = ID}); + return ID; + }; + MIParserOptions BodyOptions = Options.MachineInstructions; + BodyOptions.VerifyAfterParse = false; + MIParser Parser(Function, Target, + std::string(Result.Source.name()) + ":" + std::string(Function.name()), + std::move(BodyText), Context, BodyOptions); + auto Parsed = Parser.parse(); + if (!Parsed) { + if (!Parser.diagnostics().empty()) { + const TextRange Range = Body == nullptr + ? BodyRange + : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range); + return fail(Range, Parser.diagnostics().front().Message, "mir-body"); + } + return importError(BodyRange, Parsed.error(), "mir-body"); + } + return false; +} + +bool MIRDocumentParser::convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination) { + switch (Source.kind()) { + case yaml::NodeKind::Null: + Destination = MIRTargetNode::null(); + return false; + case yaml::NodeKind::Scalar: + Destination = MIRTargetNode::scalar(std::string(Source.scalar())); + return false; + case yaml::NodeKind::Sequence: { + std::vector<MIRTargetNode> Values; + Values.reserve(Source.sequence().size()); + for (const yaml::NodeID ElementID : Source.sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr) + return fail(Source.range(), "target MIR sequence contains an invalid node"); + MIRTargetNode Value; + if (convertTargetNode(*Element, Value)) + return true; + Values.push_back(std::move(Value)); + } + Destination = MIRTargetNode::sequence(std::move(Values)); + return false; + } + case yaml::NodeKind::Mapping: { + std::vector<MIRTargetMappingEntry> Entries; + Entries.reserve(Source.mapping().size()); + for (const yaml::MappingEntry &SourceEntry : Source.mapping()) { + const yaml::Node *ValueNode = node(SourceEntry.Value); + if (ValueNode == nullptr) + return fail(SourceEntry.KeyRange, "target MIR mapping contains an invalid node"); + MIRTargetNode Value; + if (convertTargetNode(*ValueNode, Value)) + return true; + Entries.push_back({SourceEntry.Key, std::move(Value)}); + } + Destination = MIRTargetNode::mapping(std::move(Entries)); + return false; + } + } + return fail(Source.range(), "target MIR node has an unknown kind"); +} + +bool MIRDocumentParser::parseTargetState(const yaml::Node &Root, MachineFunction &Function) { + const yaml::Node *StateNode = mappingValue(Root, "machineFunctionInfo"); + if (StateNode == nullptr) + return false; + MIRTargetNode TargetNode; + if (convertTargetNode(*StateNode, TargetNode)) + return true; + auto State = Target.mirText().parseFunctionState(TargetNode); + if (!State) + return importError(StateNode->range(), State.error(), "mir-target-state"); + auto SetResult = Function.setTargetState(std::move(*State)); + if (!SetResult) + return importError(StateNode->range(), SetResult.error(), "mir-target-state"); + return false; +} + +bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function) { + auto ParseLocation = [this, &Function](const yaml::Node &Entry, + MachineInstr *&Instruction) -> bool { + const yaml::Node *BlockNode = mappingValue(Entry, "bb"); + const yaml::Node *OffsetNode = mappingValue(Entry, "offset"); + if (BlockNode == nullptr || OffsetNode == nullptr) + return fail(Entry.range(), "instruction location requires 'bb' and 'offset'"); + std::uint64_t Block = 0; + std::uint64_t Offset = 0; + if (unsignedValue(*BlockNode, Block, "a 32-bit basic block number") || + unsignedValue(*OffsetNode, Offset, "a 32-bit instruction offset") || + Block > std::numeric_limits<std::uint32_t>::max() || + Offset > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + Instruction = instructionAt(Function, static_cast<std::uint32_t>(Block), + static_cast<std::uint32_t>(Offset)); + if (Instruction == nullptr) + return fail(Entry.range(), "instruction location is outside the machine function body"); + return false; + }; + + if (const yaml::Node *CallSites = mappingValue(Root, "callSites")) { + if (requireKind(*CallSites, yaml::NodeKind::Sequence, "call-site records")) + return true; + constexpr std::array CallSiteKeys{ + std::string_view{"bb"}, + std::string_view{"offset"}, + std::string_view{"fwdArgRegs"}, + std::string_view{"calleeTypeIds"}, + }; + constexpr std::array ArgumentKeys{ + std::string_view{"arg"}, + std::string_view{"reg"}, + }; + for (const yaml::NodeID CallSiteID : CallSites->sequence()) { + const yaml::Node *CallSiteNode = node(CallSiteID); + if (CallSiteNode == nullptr || + requireKind(*CallSiteNode, yaml::NodeKind::Mapping, "call-site record") || + validateKeys(*CallSiteNode, CallSiteKeys, "call-site record")) { + return true; + } + MachineInstr *Call = nullptr; + if (ParseLocation(*CallSiteNode, Call)) + return true; + MachineCallSiteInfo Info{ + .CallInstruction = Call->stableID(), + .ForwardedArguments = {}, + .CalleeTypeIDs = {}, + }; + if (const yaml::Node *Arguments = mappingValue(*CallSiteNode, "fwdArgRegs")) { + if (requireKind(*Arguments, yaml::NodeKind::Sequence, "forwarded call arguments")) { + return true; + } + for (const yaml::NodeID ArgumentID : Arguments->sequence()) { + const yaml::Node *Argument = node(ArgumentID); + if (Argument == nullptr || + requireKind(*Argument, yaml::NodeKind::Mapping, "forwarded call argument") || + validateKeys(*Argument, ArgumentKeys, "forwarded call argument")) { + return true; + } + const yaml::Node *NumberNode = mappingValue(*Argument, "arg"); + const yaml::Node *RegNode = mappingValue(*Argument, "reg"); + if (NumberNode == nullptr || RegNode == nullptr) + return fail(Argument->range(), "forwarded call argument requires 'arg' and 'reg'"); + std::uint16_t Number = 0; + std::uint64_t WideNumber = 0; + PhysicalRegister Reg; + if (unsignedValue(*NumberNode, WideNumber, "a 16-bit argument number") || + WideNumber > std::numeric_limits<std::uint16_t>::max() || + physicalRegister(*RegNode, Reg)) { + return true; + } + Number = static_cast<std::uint16_t>(WideNumber); + Info.ForwardedArguments.push_back({Reg, Number}); + } + } + if (const yaml::Node *TypeIDs = mappingValue(*CallSiteNode, "calleeTypeIds")) { + if (requireKind(*TypeIDs, yaml::NodeKind::Sequence, "callee type IDs")) + return true; + for (const yaml::NodeID TypeID : TypeIDs->sequence()) { + const yaml::Node *TypeNode = node(TypeID); + std::uint64_t Value = 0; + if (TypeNode == nullptr || + unsignedValue(*TypeNode, Value, "an unsigned callee type ID")) { + return true; + } + Info.CalleeTypeIDs.push_back(Value); + } + } + auto AddResult = Function.records().addCallSite(std::move(Info)); + if (!AddResult) + return importError(CallSiteNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *CalledGlobals = mappingValue(Root, "calledGlobals")) { + if (requireKind(*CalledGlobals, yaml::NodeKind::Sequence, "called-global records")) + return true; + constexpr std::array Keys{ + std::string_view{"bb"}, + std::string_view{"offset"}, + std::string_view{"callee"}, + std::string_view{"flags"}, + }; + for (const yaml::NodeID CalledID : CalledGlobals->sequence()) { + const yaml::Node *CalledNode = node(CalledID); + if (CalledNode == nullptr || + requireKind(*CalledNode, yaml::NodeKind::Mapping, "called-global record") || + validateKeys(*CalledNode, Keys, "called-global record")) { + return true; + } + MachineInstr *Call = nullptr; + if (ParseLocation(*CalledNode, Call)) + return true; + const yaml::Node *CalleeNode = mappingValue(*CalledNode, "callee"); + const yaml::Node *FlagsNode = mappingValue(*CalledNode, "flags"); + if (CalleeNode == nullptr || FlagsNode == nullptr) + return fail(CalledNode->range(), "called-global record requires 'callee' and 'flags'"); + std::string_view Callee; + std::uint64_t WideFlags = 0; + if (scalar(*CalleeNode, Callee, "callee name") || + unsignedValue(*FlagsNode, WideFlags, "32-bit called-global flags") || + WideFlags > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + if (ActiveDocument->EmbeddedIR && + ActiveDocument->EmbeddedIR->hasGlobalValue(Callee) == false) { + return fail(CalleeNode->range(), "called global references an undefined embedded global"); + } + auto AddResult = + Function.records().addCalledGlobal({.CallInstruction = Call->stableID(), + .Callee = std::string(Callee), + .Flags = static_cast<std::uint32_t>(WideFlags)}); + if (!AddResult) + return importError(CalledNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *Substitutions = mappingValue(Root, "debugValueSubstitutions")) { + if (requireKind(*Substitutions, yaml::NodeKind::Sequence, "debug value substitutions")) { + return true; + } + constexpr std::array Keys{ + std::string_view{"srcinst"}, std::string_view{"srcop"}, std::string_view{"dstinst"}, + std::string_view{"dstop"}, std::string_view{"subreg"}, + }; + for (const yaml::NodeID SubstitutionID : Substitutions->sequence()) { + const yaml::Node *Substitution = node(SubstitutionID); + if (Substitution == nullptr || + requireKind(*Substitution, yaml::NodeKind::Mapping, "debug value substitution") || + validateKeys(*Substitution, Keys, "debug value substitution")) { + return true; + } + std::array<std::uint32_t, 5> Values{}; + for (std::size_t Index = 0; Index < Keys.size(); ++Index) { + const yaml::Node *ValueNode = mappingValue(*Substitution, Keys[Index]); + if (ValueNode == nullptr) + return fail(Substitution->range(), "debug value substitution requires every fixed field"); + std::uint64_t Wide = 0; + if (unsignedValue(*ValueNode, Wide, "a 32-bit debug substitution value") || + Wide > std::numeric_limits<std::uint32_t>::max()) { + return true; + } + Values[Index] = static_cast<std::uint32_t>(Wide); + } + auto AddResult = Function.records().addDebugValueSubstitution( + {.Source = {Values[0], Values[1]}, + .Destination = {Values[2], Values[3]}, + .Subregister = SubRegIndex::fromValue(Values[4])}); + if (!AddResult) + return importError(Substitution->range(), AddResult.error()); + } + } + + if (const yaml::Node *PrefetchTargets = mappingValue(Root, "prefetch-targets")) { + if (requireKind(*PrefetchTargets, yaml::NodeKind::Sequence, "prefetch targets")) + return true; + for (const yaml::NodeID TargetID : PrefetchTargets->sequence()) { + const yaml::Node *TargetNode = node(TargetID); + std::string_view Text; + if (TargetNode == nullptr || scalar(*TargetNode, Text, "prefetch target")) + return true; + Text = trim(Text); + if (!Text.starts_with("bb_id")) + return fail(TargetNode->range(), "prefetch target must start with 'bb_id'"); + Text = trim(Text.substr(std::string_view("bb_id").size())); + std::array<std::uint32_t, 3> Values{}; + for (std::size_t Index = 0; Index < Values.size(); ++Index) { + const std::size_t Comma = Text.find(','); + const std::string_view Part = Index + 1 == Values.size() ? Text : Text.substr(0, Comma); + if ((Index + 1 != Values.size() && Comma == std::string_view::npos) || + !parseUnsigned32(trim(Part), Values[Index])) { + return fail(TargetNode->range(), "prefetch target has invalid numeric fields"); + } + if (Index + 1 != Values.size()) + Text = trim(Text.substr(Comma + 1)); + } + auto AddResult = Function.records().addPrefetchTarget( + {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]}); + if (!AddResult) + return importError(TargetNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *EntryValues = mappingValue(Root, "entry_values")) { + if (requireKind(*EntryValues, yaml::NodeKind::Sequence, "entry values")) + return true; + constexpr std::array Keys{ + std::string_view{"entry-value-register"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + for (const yaml::NodeID EntryID : EntryValues->sequence()) { + const yaml::Node *Entry = node(EntryID); + if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "entry value") || + validateKeys(*Entry, Keys, "entry value")) { + return true; + } + const yaml::Node *RegNode = mappingValue(*Entry, "entry-value-register"); + const yaml::Node *VariableNode = mappingValue(*Entry, "debug-info-variable"); + const yaml::Node *ExpressionNode = mappingValue(*Entry, "debug-info-expression"); + const yaml::Node *LocationNode = mappingValue(*Entry, "debug-info-location"); + if (RegNode == nullptr || VariableNode == nullptr || ExpressionNode == nullptr || + LocationNode == nullptr) { + return fail(Entry->range(), "entry value requires all four fixed fields"); + } + PhysicalRegister Reg; + if (physicalRegister(*RegNode, Reg)) + return true; + MachineVariableDebugInfo DebugInfo; + if (parseVariableDebugInfo(*Entry, Function, DebugInfo)) + return true; + if (!DebugInfo.Variable || !DebugInfo.Expression || !DebugInfo.Location) { + return fail(Entry->range(), "entry value debug metadata cannot be empty", + "mir-debug-metadata"); + } + auto AddResult = + Function.records().addEntryValue({.Reg = Reg, .DebugInfo = std::move(DebugInfo)}); + if (!AddResult) + return importError(Entry->range(), AddResult.error()); + } + } + return false; +} + +Expected<void> verifyMIRDocumentReferences(const MIRDocument &Document, + const MachineFunction &Function, bool UseEmbeddedIR) { + const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr; + const EmbeddedIRFunction *IRFunction = nullptr; + if (Embedded != nullptr) { + IRFunction = Embedded->function(Function.name()); + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "machine function has no matching embedded LLVM IR function"); + } + } + + const auto ValidateGlobal = [Embedded](std::string_view Name, + std::string_view Description) -> Expected<void> { + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve global '" + + std::string(Name) + "'"); + } + if (Embedded->hasGlobalValue(Name)) + return {}; + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR global '" + + std::string(Name) + "'"); + }; + const auto ValidateBlockAddress = [Embedded](std::string_view FunctionName, + std::string_view BlockName, + std::string_view Description) -> Expected<void> { + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve a block address"); + } + const EmbeddedIRFunction *AddressFunction = Embedded->function(FunctionName); + if (AddressFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR function '" + + std::string(FunctionName) + "'"); + } + if (AddressFunction->block(BlockName) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + std::string(Description) + " references an undefined embedded LLVM IR block '" + + std::string(BlockName) + "' in function '" + std::string(FunctionName) + "'"); + } + return {}; + }; + const auto ValidateIRValue = [IRFunction](std::string_view Name, + std::string_view Description) -> Expected<void> { + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve IR value '%" + + std::string(Name) + "'"); + } + if (IRFunction->value(Name) != nullptr) + return {}; + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR value '%" + + std::string(Name) + "'"); + }; + const auto ValidateMetadata = [Embedded](MachineMetadataReference Reference, + std::initializer_list<std::string_view> ExpectedTypes, + std::string_view Description) -> Expected<void> { + if (!Reference.isValid()) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " has an invalid metadata reference"); + } + if (Reference.scope() != MachineMetadataScope::EmbeddedIR) + return {}; + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " requires embedded LLVM IR metadata !" + + std::to_string(Reference.slot())); + } + const EmbeddedIRMetadataDefinition *Definition = + Embedded->metadata(std::to_string(Reference.slot())); + if (Definition == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references undefined embedded LLVM IR metadata !" + + std::to_string(Reference.slot())); + } + if (ExpectedTypes.size() == 0 || + std::ranges::find(ExpectedTypes, Definition->DynamicType) != ExpectedTypes.end()) { + return {}; + } + std::string Expected; + for (const std::string_view Type : ExpectedTypes) { + if (!Expected.empty()) + Expected.append(" or "); + Expected.append(Type); + } + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " requires " + Expected + " metadata, but !" + + std::to_string(Reference.slot()) + " is " + + (Definition->DynamicType.empty() + ? std::string("an unclassified metadata node") + : Definition->DynamicType)); + }; + const auto ValidateMetadataValue = + [&ValidateMetadata](const MachineMetadataValue &Value, + std::string_view Description) -> Expected<void> { + const MachineMetadataReference *Reference = Value.reference(); + return Reference == nullptr ? Expected<void>{} : ValidateMetadata(*Reference, {}, Description); + }; + const auto ValidateLocation = + [&ValidateMetadata](const MachineDebugLocation &Location, + std::string_view Description) -> Expected<void> { + if (const MachineMetadataReference *Reference = Location.reference()) + return ValidateMetadata(*Reference, {"DILocation"}, Description); + const MachineDILocation *Inline = Location.inlineLocation(); + if (Inline == nullptr) + return {}; + for (const MachineDILocationFrame &Frame : Inline->inlineChain()) { + auto Verification = + ValidateMetadata(Frame.Scope, {"DISubprogram", "DILexicalBlock", "DILexicalBlockFile"}, + "inline DILocation scope"); + if (!Verification) + return Verification; + } + if (Inline->terminalInlinedAt()) { + return ValidateMetadata(*Inline->terminalInlinedAt(), {"DILocation"}, + "inline DILocation inlinedAt"); + } + return {}; + }; + const auto ValidateVariableDebugInfo = + [&ValidateMetadata, &ValidateMetadataValue, + &ValidateLocation](const MachineVariableDebugInfo &DebugInfo) -> Expected<void> { + if (DebugInfo.Variable) { + auto Verification = + ValidateMetadata(*DebugInfo.Variable, {"DILocalVariable"}, "debug variable"); + if (!Verification) + return Verification; + } + if (DebugInfo.Expression) { + if (const MachineMetadataReference *Reference = DebugInfo.Expression->reference(); + Reference != nullptr) { + auto Verification = ValidateMetadata(*Reference, {"DIExpression"}, "debug expression"); + if (!Verification) + return Verification; + } + auto Verification = ValidateMetadataValue(*DebugInfo.Expression, "debug expression"); + if (!Verification) + return Verification; + } + if (DebugInfo.Location) + return ValidateLocation(*DebugInfo.Location, "debug location"); + return {}; + }; + const auto ValidateGenericType = [Embedded](const auto &Self, + const GenericConstantType &Type) -> Expected<void> { + if (Type.Kind == GenericConstantTypeKind::Named) { + if (Embedded == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "generic constant type requires embedded LLVM IR to resolve named type '%" + Type.Name + + "'"); + } + if (Embedded->typeDefinition(Type.Name) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "generic constant type references an undefined embedded LLVM IR named type '%" + + Type.Name + "'"); + } + } + for (const GenericConstantType &Element : Type.Elements) { + auto Verification = Self(Self, Element); + if (!Verification) + return Verification; + } + return {}; + }; + const auto ValidateGenericConstant = + [&ValidateBlockAddress, &ValidateGenericType, + &ValidateGlobal](const auto &Self, const GenericConstantData &Constant) -> Expected<void> { + auto TypeVerification = ValidateGenericType(ValidateGenericType, Constant.Type); + if (!TypeVerification) + return TypeVerification; + if (Constant.SourceElementType) { + TypeVerification = ValidateGenericType(ValidateGenericType, *Constant.SourceElementType); + if (!TypeVerification) + return TypeVerification; + } + switch (Constant.Kind) { + case GenericConstantKind::GlobalAddress: + case GenericConstantKind::DSOLocalEquivalent: + case GenericConstantKind::NoCFI: + if (auto Verification = ValidateGlobal(Constant.Symbol, "generic constant"); !Verification) + return Verification; + break; + case GenericConstantKind::BlockAddress: + if (auto Verification = ValidateBlockAddress(Constant.Symbol, Constant.Block, + "generic constant blockaddress"); + !Verification) { + return Verification; + } + break; + default: + break; + } + for (const GenericConstantData &Element : Constant.Elements) { + auto Verification = Self(Self, Element); + if (!Verification) + return Verification; + } + return {}; + }; + + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + if (const auto Name = Block->attributes().irBlockName(); Name) { + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "machine basic block requires embedded LLVM IR to resolve block '" + + std::string(*Name) + "'"); + } + if (IRFunction->block(*Name) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "machine basic block references an undefined embedded LLVM IR block '" + + std::string(*Name) + "'"); + } + } + } + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr || !Description->AllocaName) + continue; + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object requires embedded LLVM IR to resolve alloca '" + + *Description->AllocaName + "'"); + } + if (IRFunction->alloca(*Description->AllocaName) == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object references an undefined embedded LLVM IR alloca '" + + *Description->AllocaName + "'"); + } + } + + for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description == nullptr) + continue; + if (const auto *Generic = std::get_if<GenericConstantData>(&Description->Value)) { + auto Verification = ValidateGenericConstant(ValidateGenericConstant, *Generic); + if (!Verification) + return Verification; + } else if (const auto *Address = std::get_if<BlockAddressConstant>(&Description->Value)) { + auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, + "constant-pool block address"); + if (!Verification) + return Verification; + } + } + + for (const MachineMetadataNode &Node : Function.metadata().nodes()) { + for (const MachineMetadataElement &Element : Node.Elements) { + const MachineMetadataReference *Reference = Element.reference(); + if (Reference == nullptr) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "machine metadata node"); + if (!Verification) + return Verification; + } + } + + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr || !Description->DebugInfo) + continue; + auto Verification = ValidateVariableDebugInfo(*Description->DebugInfo); + if (!Verification) + return Verification; + } + for (const MachineEntryValueInfo &Entry : Function.records().entryValues()) { + auto Verification = ValidateVariableDebugInfo(Entry.DebugInfo); + if (!Verification) + return Verification; + } + for (const MachineCalledGlobal &Called : Function.records().calledGlobals()) { + auto Verification = ValidateGlobal(Called.Callee, "called-global record"); + if (!Verification) + return Verification; + } + + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->debugLocation()) { + auto Verification = + ValidateLocation(*Instruction->debugLocation(), "instruction debug location"); + if (!Verification) + return Verification; + } + const std::array InstructionMetadata{ + Instruction->heapAllocMarker(), + Instruction->pcSections(), + Instruction->mmra(), + }; + for (const auto &Reference : InstructionMetadata) { + if (!Reference) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "instruction metadata attachment"); + if (!Verification) + return Verification; + } + + for (const MachineOperand &Operand : Instruction->operands()) { + if (const auto *Global = Operand.getIf<GlobalSymbolOperand>(); Global != nullptr) { + auto Verification = ValidateGlobal(Global->Name, "global-symbol operand"); + if (!Verification) + return Verification; + } + if (const auto *Address = Operand.getIf<BlockAddressOperand>(); Address != nullptr) { + auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, + "block-address operand"); + if (!Verification) + return Verification; + } + if (const auto *Metadata = Operand.getIf<MetadataOperand>(); Metadata != nullptr) { + auto Verification = ValidateMetadataValue(Metadata->Value, "metadata operand"); + if (!Verification) + return Verification; + } + } + + for (const MachineMemOperand *Memory : Instruction->memOperands()) { + const MachineMemOperandDesc &Description = Memory->description(); + const std::optional<std::string_view> BaseName = Description.Base.symbolName(); + switch (Description.Base.kind()) { + case MemoryBase::Kind::GlobalSymbol: + case MemoryBase::Kind::CallEntryGlobal: + if (BaseName) { + auto Verification = ValidateGlobal(*BaseName, "machine memory base"); + if (!Verification) + return Verification; + } + break; + case MemoryBase::Kind::IRValue: + if (BaseName) { + auto Verification = ValidateIRValue(*BaseName, "machine memory base"); + if (!Verification) + return Verification; + } + break; + default: + break; + } + const std::array MetadataReferences{ + Description.TBAA, Description.AliasScope, + Description.NoAlias, Description.NoAliasAddressSpace, + Description.RangeMetadata, + }; + for (const auto &Reference : MetadataReferences) { + if (!Reference) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "machine memory operand metadata"); + if (!Verification) + return Verification; + } + if (Description.SyncScope.value() == 0) + continue; + const auto Scope = + std::ranges::find_if(Document.syncScopes(), [&Description](const auto &Slot) { + return Slot.ID == Description.SyncScope; + }); + if (Scope == Document.syncScopes().end()) { + return makeUnexpected( + ErrorCode::VerificationError, + "machine memory operand synchronization scope has no document name"); + } + } + } + } + return {}; +} + +bool MIRDocumentParser::validateDocumentReferences(const MachineFunction &Function) { + auto Verification = verifyMIRDocumentReferences(*ActiveDocument, Function, true); + if (Verification) + return false; + return importError(Result.Source.range(0, 0), Verification.error()); +} + +bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Document) { + constexpr std::array Keys{ + std::string_view{"name"}, + std::string_view{"alignment"}, + std::string_view{"exposesReturnsTwice"}, + std::string_view{"legalized"}, + std::string_view{"regBankSelected"}, + std::string_view{"selected"}, + std::string_view{"failedISel"}, + std::string_view{"tracksRegLiveness"}, + std::string_view{"hasWinCFI"}, + std::string_view{"noPhis"}, + std::string_view{"isSSA"}, + std::string_view{"noVRegs"}, + std::string_view{"hasFakeUses"}, + std::string_view{"callsEHReturn"}, + std::string_view{"callsUnwindInit"}, + std::string_view{"hasEHContTarget"}, + std::string_view{"hasEHScopes"}, + std::string_view{"hasEHFunclets"}, + std::string_view{"isOutlined"}, + std::string_view{"debugInstrRef"}, + std::string_view{"failsVerification"}, + std::string_view{"tracksDebugUserValues"}, + std::string_view{"registers"}, + std::string_view{"liveins"}, + std::string_view{"calleeSavedRegisters"}, + std::string_view{"frameInfo"}, + std::string_view{"fixedStack"}, + std::string_view{"stack"}, + std::string_view{"entry_values"}, + std::string_view{"callSites"}, + std::string_view{"debugValueSubstitutions"}, + std::string_view{"constants"}, + std::string_view{"machineFunctionInfo"}, + std::string_view{"jumpTable"}, + std::string_view{"machineMetadataNodes"}, + std::string_view{"calledGlobals"}, + std::string_view{"prefetch-targets"}, + std::string_view{"body"}, + }; + if (validateKeys(Root, Keys, "machine function")) + return true; + const yaml::Node *NameNode = mappingValue(Root, "name"); + if (NameNode == nullptr) + return fail(Root.range(), "machine function requires 'name'"); + std::string_view Name; + if (scalar(*NameNode, Name, "machine function name") || Name.empty()) + return fail(NameNode->range(), "machine function name cannot be empty"); + + if (Document.EmbeddedIR) { + const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name); + if (IRFunction == nullptr) + return fail(NameNode->range(), + "machine function has no corresponding embedded LLVM IR function"); + } + + auto Created = Document.Unit.createMachineFunction(std::string(Name)); + if (!Created) + return importError(NameNode->range(), Created.error()); + MachineFunction &Function = **Created; + ActiveFunction = &Function; + + MIRFunctionDocumentInfo DocumentInfo{ + .Function = Function.stableID(), + .NoPHIs = std::nullopt, + .IsSSA = std::nullopt, + .NoVRegs = std::nullopt, + .HasFakeUses = std::nullopt, + }; + MIParserContext Context; + Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; + + std::vector<PendingJumpTable> PendingJumpTables; + if (parseFunctionScalars(Root, Function, DocumentInfo) || + parseMetadata(Root, Function, Context) || parseRegisters(Root, Function, Context) || + parseStackObjects(Root, Function, Context) || parseConstants(Root, Function, Context) || + reserveJumpTables(Root, Function, Context, PendingJumpTables) || + parseBody(Root, Function, Context) || defineJumpTables(Function, PendingJumpTables) || + recomputeProperties(Root, Function, DocumentInfo) || + parseFrameState(Root, Function, Context) || parseTargetState(Root, Function) || + parseFunctionRecords(Root, Function) || validateDocumentReferences(Function)) { + return true; + } + + if (Options.VerifyMachineFunctions) { + auto Verified = MachineVerifier::verify(Function, Target); + if (!Verified) + return importError(Root.range(), Verified.error(), "mir-verifier"); + } + Document.FunctionInfo.push_back(DocumentInfo); + ActiveFunction = nullptr; + return false; +} + +MIRDocument::MIRDocument() = default; +MIRDocument::MIRDocument(CompilationUnit Unit) : Unit(std::move(Unit)) {} +MIRDocument::MIRDocument(MIRDocument &&) noexcept = default; +MIRDocument &MIRDocument::operator=(MIRDocument &&) noexcept = default; +MIRDocument::~MIRDocument() = default; + +MIRDocumentParseResult::MIRDocumentParseResult(MIRDocumentParseResult &&) noexcept = default; +MIRDocumentParseResult & +MIRDocumentParseResult::operator=(MIRDocumentParseResult &&) noexcept = default; +MIRDocumentParseResult::~MIRDocumentParseResult() = default; + +MIRDocumentParseResult parseMIRDocument(const TargetDescription &Target, std::string SourceName, + std::string SourceText, MIRDocumentParserOptions Options) { + return MIRDocumentParser(Target, std::move(SourceName), std::move(SourceText), Options).run(); +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRDocumentPrinter.cpp b/backend/lib/MIR/MIRDocumentPrinter.cpp new file mode 100644 index 0000000..e9f9e1c --- /dev/null +++ b/backend/lib/MIR/MIRDocumentPrinter.cpp @@ -0,0 +1,1302 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentReferenceValidation.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/MachineFunctionAttributes.h" +#include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineFunctionRecords.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMetadata.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" +#include "rcc/MIR/MIRDocument.h" + +#include <algorithm> +#include <array> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <ranges> +#include <span> +#include <string> +#include <string_view> +#include <unordered_set> +#include <utility> +#include <variant> +#include <vector> + +namespace rcc::mir { +namespace { + +[[nodiscard]] std::string yamlScalar(std::string_view Value) { + std::string Result; + Result.reserve(Value.size() + 2U); + Result.push_back('\''); + for (const char Character : Value) { + Result.push_back(Character); + if (Character == '\'') + Result.push_back('\''); + } + Result.push_back('\''); + return Result; +} + +void appendBlockScalar(std::string &Output, std::string_view Text, std::size_t Indent) { + std::size_t Position = 0; + do { + Output.append(Indent, ' '); + const std::size_t End = Text.find('\n', Position); + if (End == std::string_view::npos) { + Output.append(Text.substr(Position)); + Output.push_back('\n'); + return; + } + Output.append(Text.substr(Position, End - Position)); + Output.push_back('\n'); + Position = End + 1U; + } while (Position < Text.size()); +} + +[[nodiscard]] std::optional<std::uint32_t> parseDecimal32(std::string_view Text) noexcept { + std::uint32_t Value = 0; + if (Text.empty()) + return std::nullopt; + const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); + if (Error != std::errc{} || End != Text.data() + Text.size()) + return std::nullopt; + return Value; +} + +[[nodiscard]] bool isDefaultFrameState(const MachineFrameState &State) { + return State == MachineFrameState{}; +} + +class MIRDocumentPrinter final { +public: + MIRDocumentPrinter(const MIRDocument &Document, const TargetDescription &Target, + MIRDocumentPrinterOptions Options) + : Document(Document), Target(Target), Options(Options) {} + + [[nodiscard]] Expected<std::string> run(); + +private: + struct InstructionLocation final { + std::uint32_t Block = 0; + std::uint32_t Offset = 0; + }; + + [[nodiscard]] Expected<void> printFunction(const MachineFunction &Function, + const MIRFunctionDocumentInfo *DocumentInfo); + [[nodiscard]] Expected<MIParserContext> createBodyContext(const MachineFunction &Function) const; + [[nodiscard]] Expected<void> printRegisters(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> printFrameInfo(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> printStackObjects(const MachineFunction &Function, + const MIParserContext &Context, bool Fixed); + [[nodiscard]] Expected<void> printConstants(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> printJumpTable(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> printMetadata(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> printTargetState(const MachineFunction &Function); + [[nodiscard]] Expected<void> printFunctionRecords(const MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] Expected<void> verifyOmittedEmbeddedIRLayout(const MachineFunction &Function) const; + + [[nodiscard]] Expected<std::string> physicalRegister(PhysicalRegister Register) const; + [[nodiscard]] Expected<std::string> frameReference(FrameIndex Index, + const MIParserContext &Context) const; + [[nodiscard]] Expected<std::string> metadataReference(MachineMetadataReference Reference, + const MIParserContext &Context) const; + [[nodiscard]] Expected<std::string> metadataValue(const MachineMetadataValue &Value, + const MIParserContext &Context) const; + [[nodiscard]] Expected<std::string> debugLocation(const MachineDebugLocation &Location, + const MIParserContext &Context) const; + [[nodiscard]] Expected<std::string> diLocation(const MachineDILocation &Location, + std::size_t FrameIndex, + const MIParserContext &Context) const; + [[nodiscard]] Expected<InstructionLocation> instructionLocation(const MachineFunction &Function, + MachineInstrStableID ID) const; + [[nodiscard]] Expected<std::string> blockReference(const MachineFunction &Function, + MachineBlockStableID ID) const; + [[nodiscard]] Expected<std::string> targetNode(const MIRTargetNode &Node) const; + [[nodiscard]] const MIRFunctionDocumentInfo * + documentInfo(const MachineFunction &Function) const noexcept; + + void printBoolean(std::string_view Key, bool Value, bool Always = false); + void printUnsigned(std::string_view Key, std::uint64_t Value, bool Always = false); + void printSigned(std::string_view Key, std::int64_t Value, bool Always = false); + + const MIRDocument &Document; + const TargetDescription &Target; + MIRDocumentPrinterOptions Options; + std::string Output; +}; + +void MIRDocumentPrinter::printBoolean(std::string_view Key, bool Value, bool Always) { + if (!Always && Options.Mode == MIPrintMode::Readable && !Value) + return; + Output.append(Key); + Output.append(Value ? ": true\n" : ": false\n"); +} + +void MIRDocumentPrinter::printUnsigned(std::string_view Key, std::uint64_t Value, bool Always) { + if (!Always && Options.Mode == MIPrintMode::Readable && Value == 0) + return; + Output.append(Key); + Output.append(": "); + Output.append(std::to_string(Value)); + Output.push_back('\n'); +} + +void MIRDocumentPrinter::printSigned(std::string_view Key, std::int64_t Value, bool Always) { + if (!Always && Options.Mode == MIPrintMode::Readable && Value == 0) + return; + Output.append(Key); + Output.append(": "); + Output.append(std::to_string(Value)); + Output.push_back('\n'); +} + +Expected<std::string> MIRDocumentPrinter::physicalRegister(PhysicalRegister Register) const { + const auto Name = Target.registers().physicalRegisterMIRName(Register); + if (!Name) { + return makeUnexpected(ErrorCode::VerificationError, + "physical register has no canonical MIR name"); + } + return "$" + std::string(*Name); +} + +const MIRFunctionDocumentInfo * +MIRDocumentPrinter::documentInfo(const MachineFunction &Function) const noexcept { + const auto Infos = Document.functionDocumentInfo(); + const auto Found = std::ranges::find_if(Infos, [&Function](const MIRFunctionDocumentInfo &Info) { + return Info.Function == Function.stableID(); + }); + return Found == Infos.end() ? nullptr : &*Found; +} + +Expected<void> +MIRDocumentPrinter::verifyOmittedEmbeddedIRLayout(const MachineFunction &Function) const { + if (Options.EmitEmbeddedIR) + return {}; + + const EmbeddedIRReferenceIndex *Embedded = Document.embeddedIR(); + if (Embedded == nullptr) + return {}; + const auto EmbeddedDataLayout = Embedded->dataLayout(); + if (!EmbeddedDataLayout) + return {}; + + bool HasLayoutDependentContent = false; + for (const VirtualRegisterInfo &RegisterInfo : Function.registerInfo().virtualRegisters()) { + if (!RegisterInfo.IsTombstone && RegisterInfo.Type && + RegisterInfo.Type->isPointerOrPointerVector()) { + HasLayoutDependentContent = true; + break; + } + } + if (!HasLayoutDependentContent) { + for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description != nullptr && + std::holds_alternative<GenericConstantData>(Description->Value)) { + HasLayoutDependentContent = true; + break; + } + } + } + if (!HasLayoutDependentContent) + return {}; + + if (Options.DataLayout != *EmbeddedDataLayout) { + return makeUnexpected( + ErrorCode::InvalidArgument, + "omitting embedded LLVM IR requires printer DataLayout to exactly match its data layout"); + } + + const LLVMConstantSyntaxOptions LayoutOptions{ + .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits, + .DataLayout = Options.DataLayout, + .NamedTypes = {}, + }; + for (const VirtualRegisterInfo &RegisterInfo : Function.registerInfo().virtualRegisters()) { + if (RegisterInfo.IsTombstone || !RegisterInfo.Type || + !RegisterInfo.Type->isPointerOrPointerVector()) { + continue; + } + const auto AddressSpace = RegisterInfo.Type->addressSpace(); + if (!AddressSpace) { + return makeUnexpected(ErrorCode::VerificationError, + "pointer virtual register has no address space"); + } + auto PointerSize = llvmPointerSizeInBits(*AddressSpace, LayoutOptions); + if (!PointerSize) { + return makeUnexpected(ErrorCode::InvalidArgument, + "printer DataLayout cannot determine pointer width: " + + std::string(PointerSize.error().message())); + } + if (*PointerSize != RegisterInfo.Type->scalarBitWidth()) { + return makeUnexpected(ErrorCode::VerificationError, + "printer DataLayout does not match pointer virtual-register width"); + } + } + + for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description == nullptr) + continue; + const auto *Generic = std::get_if<GenericConstantData>(&Description->Value); + if (Generic == nullptr) + continue; + auto StorageSize = llvmConstantStorageSize(Generic->Type, LayoutOptions); + if (!StorageSize) { + return makeUnexpected(ErrorCode::InvalidArgument, + "printer DataLayout cannot lay out generic constant: " + + std::string(StorageSize.error().message())); + } + if (*StorageSize != Description->Size) { + return makeUnexpected(ErrorCode::VerificationError, + "printer DataLayout does not match generic constant storage size"); + } + } + return {}; +} + +Expected<MIParserContext> +MIRDocumentPrinter::createBodyContext(const MachineFunction &Function) const { + MIParserContext Context; + Context.SyncScopes.assign(Document.syncScopes().begin(), Document.syncScopes().end()); + const auto VirtualRegisters = Function.registerInfo().virtualRegisters(); + for (std::size_t Index = 0; Index < VirtualRegisters.size(); ++Index) { + if (VirtualRegisters[Index].IsTombstone) + continue; + auto Reg = Register::makeVirtual(Index); + if (!Reg) + return std::unexpected(Reg.error()); + if (Index > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, + "MIR cannot represent more than 2^32 virtual registers"); + } + Context.VirtualRegisters.push_back({.TextID = static_cast<std::uint32_t>(Index), .Reg = *Reg}); + } + + std::uint32_t StackID = 0; + std::uint32_t FixedID = 0; + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr) + continue; + MIFrameObjectSlot Slot{ + .TextID = Object.isFixed() ? FixedID++ : StackID++, + .Index = Object.index(), + .Name = Description->AllocaName, + }; + (Object.isFixed() ? Context.FixedStackObjects : Context.StackObjects) + .push_back(std::move(Slot)); + } + + std::uint32_t ConstantID = 0; + for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { + if (Entry.description() != nullptr) + Context.ConstantPoolItems.push_back({.TextID = ConstantID++, .Index = Entry.index()}); + } + std::uint32_t JumpTableID = 0; + for (const MachineJumpTableEntry &Entry : Function.jumpTableInfo().tables()) { + if (Entry.description() != nullptr) + Context.JumpTables.push_back({.TextID = JumpTableID++, .Index = Entry.index()}); + } + + std::unordered_set<std::uint32_t> EmbeddedMetadataSlots; + if (const EmbeddedIRReferenceIndex *Embedded = Document.embeddedIR()) { + for (const EmbeddedIRMetadataDefinition &Definition : Embedded->metadataDefinitions()) { + const auto Slot = parseDecimal32(Definition.Name); + if (Slot) { + if (!EmbeddedMetadataSlots.insert(*Slot).second) { + return makeUnexpected(ErrorCode::VerificationError, + "embedded LLVM IR metadata slots are not unique"); + } + Context.Metadata.push_back( + {.TextID = *Slot, .Reference = MachineMetadataReference::embeddedIR(*Slot)}); + } + } + } + + std::unordered_set<std::uint32_t> UsedMetadataSlots = EmbeddedMetadataSlots; + for (const MachineMetadataNode &Node : Function.metadata().nodes()) { + if (!EmbeddedMetadataSlots.contains(Node.Slot)) + UsedMetadataSlots.insert(Node.Slot); + } + std::uint64_t NextMetadataSlot = 0; + for (const MachineMetadataNode &Node : Function.metadata().nodes()) { + std::uint32_t TextID = Node.Slot; + if (EmbeddedMetadataSlots.contains(TextID)) { + while (NextMetadataSlot <= std::numeric_limits<std::uint32_t>::max() && + UsedMetadataSlots.contains(static_cast<std::uint32_t>(NextMetadataSlot))) { + ++NextMetadataSlot; + } + if (NextMetadataSlot > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "MIR metadata text slot space is exhausted"); + } + TextID = static_cast<std::uint32_t>(NextMetadataSlot); + UsedMetadataSlots.insert(TextID); + ++NextMetadataSlot; + } + Context.Metadata.push_back( + {.TextID = TextID, .Reference = MachineMetadataReference::machineFunction(Node.Slot)}); + } + return Context; +} + +Expected<std::string> MIRDocumentPrinter::frameReference(FrameIndex Index, + const MIParserContext &Context) const { + const auto Stack = std::ranges::find_if( + Context.StackObjects, [Index](const MIFrameObjectSlot &Slot) { return Slot.Index == Index; }); + if (Stack != Context.StackObjects.end()) + return "%stack." + std::to_string(Stack->TextID); + const auto Fixed = + std::ranges::find_if(Context.FixedStackObjects, + [Index](const MIFrameObjectSlot &Slot) { return Slot.Index == Index; }); + if (Fixed != Context.FixedStackObjects.end()) + return "%fixed-stack." + std::to_string(Fixed->TextID); + return makeUnexpected(ErrorCode::VerificationError, + "frame state references an unknown stack object"); +} + +Expected<std::string> MIRDocumentPrinter::metadataReference(MachineMetadataReference Reference, + const MIParserContext &Context) const { + if (!Reference.isValid()) + return makeUnexpected(ErrorCode::VerificationError, "invalid metadata reference"); + const auto Slot = + std::ranges::find_if(Context.Metadata, [Reference](const MIMetadataSlot &Candidate) { + return Candidate.Reference == Reference; + }); + if (Slot == Context.Metadata.end()) { + return makeUnexpected(ErrorCode::VerificationError, "metadata reference has no MIR text slot"); + } + return "!" + std::to_string(Slot->TextID); +} + +Expected<std::string> MIRDocumentPrinter::metadataValue(const MachineMetadataValue &Value, + const MIParserContext &Context) const { + if (const MachineMetadataReference *Reference = Value.reference()) + return metadataReference(*Reference, Context); + const MachineDIExpression *Expression = Value.expression(); + if (Expression == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "metadata value is not printable"); + std::string Text = "!DIExpression("; + for (std::size_t Index = 0; Index < Expression->elements().size(); ++Index) { + if (Index != 0) + Text.append(", "); + Text.append(std::to_string(Expression->elements()[Index])); + } + Text.push_back(')'); + return Text; +} + +Expected<std::string> MIRDocumentPrinter::diLocation(const MachineDILocation &Location, + std::size_t FrameIndex, + const MIParserContext &Context) const { + if (FrameIndex >= Location.inlineChain().size()) { + return makeUnexpected(ErrorCode::VerificationError, + "inline DILocation has an invalid frame chain"); + } + const MachineDILocationFrame &Frame = Location.inlineChain()[FrameIndex]; + auto Scope = metadataReference(Frame.Scope, Context); + if (!Scope) + return std::unexpected(Scope.error()); + std::string Text = "!DILocation(line: " + std::to_string(Frame.Line); + if (Options.Mode == MIPrintMode::Complete || Frame.Column != 0) + Text.append(", column: " + std::to_string(Frame.Column)); + Text.append(", scope: " + *Scope); + if (FrameIndex + 1U < Location.inlineChain().size()) { + auto Nested = diLocation(Location, FrameIndex + 1U, Context); + if (!Nested) + return std::unexpected(Nested.error()); + Text.append(", inlinedAt: " + *Nested); + } else if (Location.terminalInlinedAt()) { + auto Terminal = metadataReference(*Location.terminalInlinedAt(), Context); + if (!Terminal) + return std::unexpected(Terminal.error()); + Text.append(", inlinedAt: " + *Terminal); + } + if (Options.Mode == MIPrintMode::Complete || Frame.IsImplicitCode) + Text.append(Frame.IsImplicitCode ? ", isImplicitCode: true" : ", isImplicitCode: false"); + if (Options.Mode == MIPrintMode::Complete || Frame.AtomGroup != 0) + Text.append(", atomGroup: " + std::to_string(Frame.AtomGroup)); + if (Options.Mode == MIPrintMode::Complete || Frame.AtomRank != 0) + Text.append(", atomRank: " + std::to_string(Frame.AtomRank)); + Text.push_back(')'); + return Text; +} + +Expected<std::string> MIRDocumentPrinter::debugLocation(const MachineDebugLocation &Location, + const MIParserContext &Context) const { + if (const MachineMetadataReference *Reference = Location.reference()) + return metadataReference(*Reference, Context); + if (const MachineDILocation *Inline = Location.inlineLocation()) + return diLocation(*Inline, 0, Context); + return makeUnexpected(ErrorCode::VerificationError, "debug location is not printable"); +} + +Expected<MIRDocumentPrinter::InstructionLocation> +MIRDocumentPrinter::instructionLocation(const MachineFunction &Function, + MachineInstrStableID ID) const { + std::uint64_t BlockNumber = 0; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next(), ++BlockNumber) { + if (BlockNumber > std::numeric_limits<std::uint32_t>::max()) + break; + std::uint64_t Offset = 0; + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next(), ++Offset) { + if (Instruction->stableID() != ID) + continue; + if (Offset > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "MIR instruction offset exceeds 32 bits"); + } + return InstructionLocation{static_cast<std::uint32_t>(BlockNumber), + static_cast<std::uint32_t>(Offset)}; + } + } + return makeUnexpected(ErrorCode::VerificationError, + "function record references an unlinked instruction"); +} + +Expected<std::string> MIRDocumentPrinter::blockReference(const MachineFunction &Function, + MachineBlockStableID ID) const { + std::uint64_t Number = 0; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next(), ++Number) { + if (Block->stableID() == ID) { + if (Number > std::numeric_limits<std::uint32_t>::max()) + break; + return "%bb." + std::to_string(Number); + } + } + return makeUnexpected(ErrorCode::VerificationError, + "frame record references an unlinked basic block"); +} + +Expected<std::string> MIRDocumentPrinter::targetNode(const MIRTargetNode &Node) const { + switch (Node.Kind) { + case MIRTargetNodeKind::Null: + return std::string("null"); + case MIRTargetNodeKind::Scalar: + return yamlScalar(Node.Scalar); + case MIRTargetNodeKind::Sequence: { + std::string Text = "[ "; + for (std::size_t Index = 0; Index < Node.Sequence.size(); ++Index) { + if (Index != 0) + Text.append(", "); + auto Element = targetNode(Node.Sequence[Index]); + if (!Element) + return std::unexpected(Element.error()); + Text.append(*Element); + } + Text.append(" ]"); + return Text; + } + case MIRTargetNodeKind::Mapping: { + std::string Text = "{ "; + for (std::size_t Index = 0; Index < Node.Mapping.size(); ++Index) { + if (Index != 0) + Text.append(", "); + Text.append(yamlScalar(Node.Mapping[Index].Key)); + Text.append(": "); + auto Value = targetNode(Node.Mapping[Index].Value); + if (!Value) + return std::unexpected(Value.error()); + Text.append(*Value); + } + Text.append(" }"); + return Text; + } + case MIRTargetNodeKind::Count: + break; + } + return makeUnexpected(ErrorCode::VerificationError, "target MIR node has an invalid kind"); +} + +Expected<void> MIRDocumentPrinter::printRegisters(const MachineFunction &Function, + const MIParserContext &Context) { + if (Context.VirtualRegisters.empty()) { + if (Options.Mode == MIPrintMode::Complete) + Output.append("registers: []\n"); + } else { + Output.append("registers:\n"); + for (const MIVirtualRegisterSlot &Slot : Context.VirtualRegisters) { + auto InfoResult = Function.registerInfo().virtualRegisterInfo(Slot.Reg); + if (!InfoResult) + return std::unexpected(InfoResult.error()); + const VirtualRegisterInfo &Info = InfoResult->get(); + std::string Class = "_"; + if (Info.RegClass) { + const auto Name = Target.registers().registerClassMIRName(*Info.RegClass); + if (!Name) + return makeUnexpected(ErrorCode::VerificationError, + "virtual register class has no canonical MIR name"); + Class = *Name; + } else if (Info.Bank) { + const auto Name = Target.registers().registerBankMIRName(*Info.Bank); + if (!Name) + return makeUnexpected(ErrorCode::VerificationError, + "virtual register bank has no canonical MIR name"); + Class = *Name; + } + if (Info.AllocationHints.size() > 1) { + return makeUnexpected( + ErrorCode::Unsupported, + "fixed LLVM MIR can serialize only one simple preferred-register hint"); + } + + Output.append(" - { id: "); + Output.append(std::to_string(Slot.TextID)); + Output.append(", class: "); + Output.append(yamlScalar(Class)); + if (!Info.AllocationHints.empty()) { + auto Preferred = physicalRegister(Info.AllocationHints.front()); + if (!Preferred) + return std::unexpected(Preferred.error()); + Output.append(", preferred-register: "); + Output.append(yamlScalar(*Preferred)); + } + if (Info.TargetFlags != 0) { + auto Names = Target.mirText().virtualRegisterFlagNames(Info.TargetFlags); + if (!Names) + return std::unexpected(Names.error()); + Output.append(", flags: [ "); + for (std::size_t Index = 0; Index < Names->size(); ++Index) { + if (Index != 0) + Output.append(", "); + Output.append(yamlScalar((*Names)[Index])); + } + Output.append(" ]"); + } + Output.append(" }\n"); + } + } + + if (Function.registerInfo().liveIns().empty()) { + if (Options.Mode == MIPrintMode::Complete) + Output.append("liveins: []\n"); + } else { + Output.append("liveins:\n"); + for (const PhysicalRegisterLiveIn &LiveIn : Function.registerInfo().liveIns()) { + auto Name = physicalRegister(LiveIn.PhysReg); + if (!Name) + return std::unexpected(Name.error()); + Output.append(" - { reg: "); + Output.append(yamlScalar(*Name)); + if (LiveIn.InitialVirtualRegister) { + const auto Slot = std::ranges::find_if( + Context.VirtualRegisters, [&LiveIn](const MIVirtualRegisterSlot &Candidate) { + return Candidate.Reg == *LiveIn.InitialVirtualRegister; + }); + if (Slot == Context.VirtualRegisters.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "live-in references an unknown virtual register"); + } + Output.append(", virtual-reg: "); + Output.append(yamlScalar("%" + std::to_string(Slot->TextID))); + } + Output.append(" }\n"); + } + } + + if (Function.records().hasCalleeSavedRegisters()) { + Output.append("calleeSavedRegisters: [ "); + const auto Registers = Function.records().calleeSavedRegisters(); + for (std::size_t Index = 0; Index < Registers.size(); ++Index) { + if (Index != 0) + Output.append(", "); + auto Name = physicalRegister(Registers[Index]); + if (!Name) + return std::unexpected(Name.error()); + Output.append(yamlScalar(*Name)); + } + Output.append(" ]\n"); + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printFrameInfo(const MachineFunction &Function, + const MIParserContext &Context) { + const MachineFrameState &State = Function.frameInfo().state(); + if (Options.Mode == MIPrintMode::Readable && isDefaultFrameState(State)) + return {}; + if (State.MaxCallFrameSize && + *State.MaxCallFrameSize > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, + "fixed LLVM MIR maxCallFrameSize is limited to 32 bits"); + } + Output.append("frameInfo:\n"); + const auto Bool = [this](std::string_view Key, bool Value) { + if (Options.Mode == MIPrintMode::Readable && !Value) + return; + Output.append(" "); + Output.append(Key); + Output.append(Value ? ": true\n" : ": false\n"); + }; + const auto Unsigned = [this](std::string_view Key, std::uint64_t Value) { + if (Options.Mode == MIPrintMode::Readable && Value == 0) + return; + Output.append(" "); + Output.append(Key); + Output.append(": "); + Output.append(std::to_string(Value)); + Output.push_back('\n'); + }; + + Bool("isFrameAddressTaken", State.IsFrameAddressTaken); + Bool("isReturnAddressTaken", State.IsReturnAddressTaken); + Bool("hasStackMap", State.HasStackMap); + Bool("hasPatchPoint", State.HasPatchPoint); + Unsigned("stackSize", State.StackSize); + if (Options.Mode == MIPrintMode::Complete || State.OffsetAdjustment != 0) { + Output.append(" offsetAdjustment: "); + Output.append(std::to_string(State.OffsetAdjustment)); + Output.push_back('\n'); + } + Unsigned("maxAlignment", State.MaxAlignment ? State.MaxAlignment->bytes() : 0); + Bool("adjustsStack", State.AdjustsStack); + Bool("hasCalls", State.HasCalls); + if (Options.Mode == MIPrintMode::Complete || + State.FramePointerPolicy != MachineFramePointerPolicy::None) { + Output.append(" framePointerPolicy: "); + Output.append(getMachineFramePointerPolicyName(State.FramePointerPolicy)); + Output.push_back('\n'); + } + if (State.StackProtector) { + auto Reference = frameReference(*State.StackProtector, Context); + if (!Reference) + return std::unexpected(Reference.error()); + Output.append(" stackProtector: "); + Output.append(yamlScalar(*Reference)); + Output.push_back('\n'); + } + if (State.FunctionContext) { + auto Reference = frameReference(*State.FunctionContext, Context); + if (!Reference) + return std::unexpected(Reference.error()); + Output.append(" functionContext: "); + Output.append(yamlScalar(*Reference)); + Output.push_back('\n'); + } + if (State.MaxCallFrameSize) + Unsigned("maxCallFrameSize", *State.MaxCallFrameSize); + Unsigned("cvBytesOfCalleeSavedRegisters", State.CVBytesOfCalleeSavedRegisters); + Bool("hasOpaqueSPAdjustment", State.HasOpaqueSPAdjustment); + Bool("hasVAStart", State.HasVAStart); + Bool("hasMustTailInVarArgFunc", State.HasMustTailInVarArgFunc); + Bool("hasTailCall", State.HasTailCall); + Bool("isCalleeSavedInfoValid", State.IsCalleeSavedInfoValid); + Unsigned("localFrameSize", State.LocalFrameSize); + + const auto PrintPoints = + [this, &Function](std::string_view Key, + std::span<const MachineFrameSaveRestorePoint> Points) -> Expected<void> { + if (Points.empty()) + return {}; + Output.append(" "); + Output.append(Key); + Output.append(":\n"); + for (const MachineFrameSaveRestorePoint &Point : Points) { + auto Block = blockReference(Function, Point.Block); + if (!Block) + return std::unexpected(Block.error()); + Output.append(" - { point: "); + Output.append(yamlScalar(*Block)); + if (!Point.Registers.empty()) { + Output.append(", registers: [ "); + for (std::size_t Index = 0; Index < Point.Registers.size(); ++Index) { + if (Index != 0) + Output.append(", "); + auto Name = physicalRegister(Point.Registers[Index]); + if (!Name) + return std::unexpected(Name.error()); + Output.append(yamlScalar(*Name)); + } + Output.append(" ]"); + } + Output.append(" }\n"); + } + return {}; + }; + if (auto Printed = PrintPoints("savePoint", State.SavePoints); !Printed) + return Printed; + return PrintPoints("restorePoint", State.RestorePoints); +} + +Expected<void> MIRDocumentPrinter::printStackObjects(const MachineFunction &Function, + const MIParserContext &Context, bool Fixed) { + const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects; + if (Slots.empty()) { + if (Options.Mode == MIPrintMode::Complete) + Output.append(Fixed ? "fixedStack: []\n" : "stack: []\n"); + return {}; + } + Output.append(Fixed ? "fixedStack:\n" : "stack:\n"); + for (const MIFrameObjectSlot &Slot : Slots) { + const StackObject *Object = Function.frameInfo().object(Slot.Index); + const StackObjectDesc *Description = Object == nullptr ? nullptr : Object->description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "MIR stack slot is a tombstone"); + if (Description->IsDead || Description->AddressTaken || !Description->Lifetime.empty() || + Description->TargetFlags != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "stack object contains state not represented by fixed LLVM MIR"); + } + + std::string_view Type; + switch (Description->Kind) { + case StackObjectKind::Default: + case StackObjectKind::FixedABI: + Type = "default"; + break; + case StackObjectKind::FixedSpillSlot: + case StackObjectKind::SpillSlot: + Type = "spill-slot"; + break; + case StackObjectKind::VariableSized: + Type = "variable-sized"; + break; + case StackObjectKind::DynamicAlloca: + case StackObjectKind::CalleeSaved: + case StackObjectKind::EmergencySpill: + case StackObjectKind::Target: + case StackObjectKind::Count: + return makeUnexpected(ErrorCode::Unsupported, + "stack object kind has no fixed LLVM MIR representation"); + } + + Output.append(" - { id: "); + Output.append(std::to_string(Slot.TextID)); + if (!Fixed && Description->AllocaName) { + Output.append(", name: "); + Output.append(yamlScalar(*Description->AllocaName)); + } + if (Options.Mode == MIPrintMode::Complete || Type != "default") { + Output.append(", type: "); + Output.append(Type); + } + if (Options.Mode == MIPrintMode::Complete || Description->Offset.value_or(0) != 0) { + Output.append(", offset: "); + Output.append(std::to_string(Description->Offset.value_or(0))); + } + if (Description->Kind != StackObjectKind::VariableSized) { + Output.append(", size: "); + Output.append(std::to_string(Description->Size.value_or(0))); + } + if (Options.Mode == MIPrintMode::Complete || Description->Align.bytes() != 1) { + Output.append(", alignment: "); + Output.append(std::to_string(Description->Align.bytes())); + } + if (Options.Mode == MIPrintMode::Complete || Description->Stack != StackID::defaultStack()) { + Output.append(", stack-id: "); + Output.append(getStackIDName(Description->Stack)); + } + if (Fixed && Description->Kind != StackObjectKind::FixedSpillSlot) { + if (Options.Mode == MIPrintMode::Complete || Description->IsImmutable) + Output.append(Description->IsImmutable ? ", isImmutable: true" : ", isImmutable: false"); + if (Options.Mode == MIPrintMode::Complete || Description->IsAliased) + Output.append(Description->IsAliased ? ", isAliased: true" : ", isAliased: false"); + } else if (!Fixed && (Description->IsImmutable || Description->IsAliased)) { + return makeUnexpected(ErrorCode::Unsupported, + "ordinary stack object immutable/aliased state is not serialized"); + } + if (Description->CalleeSavedRegister) { + auto Name = physicalRegister(*Description->CalleeSavedRegister); + if (!Name) + return std::unexpected(Name.error()); + Output.append(", callee-saved-register: "); + Output.append(yamlScalar(*Name)); + } + if (Options.Mode == MIPrintMode::Complete || !Description->CalleeSavedRestored) { + Output.append(Description->CalleeSavedRestored ? ", callee-saved-restored: true" + : ", callee-saved-restored: false"); + } + if (!Fixed && Description->LocalOffset) { + Output.append(", local-offset: "); + Output.append(std::to_string(*Description->LocalOffset)); + } + if (Description->DebugInfo) { + const MachineVariableDebugInfo &Debug = *Description->DebugInfo; + if (Debug.Variable) { + auto Value = metadataReference(*Debug.Variable, Context); + if (!Value) + return std::unexpected(Value.error()); + Output.append(", debug-info-variable: "); + Output.append(yamlScalar(*Value)); + } + if (Debug.Expression) { + auto Value = metadataValue(*Debug.Expression, Context); + if (!Value) + return std::unexpected(Value.error()); + Output.append(", debug-info-expression: "); + Output.append(yamlScalar(*Value)); + } + if (Debug.Location) { + auto Value = debugLocation(*Debug.Location, Context); + if (!Value) + return std::unexpected(Value.error()); + Output.append(", debug-info-location: "); + Output.append(yamlScalar(*Value)); + } + } + Output.append(" }\n"); + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printConstants(const MachineFunction &Function, + const MIParserContext &Context) { + if (Context.ConstantPoolItems.empty()) { + if (Options.Mode == MIPrintMode::Complete) + Output.append("constants: []\n"); + return {}; + } + Output.append("constants:\n"); + for (const MIConstantPoolSlot &Slot : Context.ConstantPoolItems) { + const MachineConstantPoolEntry *Entry = Function.constantPool().entry(Slot.Index); + const MachineConstantPoolEntryDesc *Description = + Entry == nullptr ? nullptr : Entry->description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "constant-pool slot is a tombstone"); + if (Description->Merge != ConstantPoolMergeKind::Never || + Description->Relocation != ConstantPoolRelocationMode::None || + Description->Placement != ConstantPoolPlacement{}) { + return makeUnexpected(ErrorCode::Unsupported, + "constant-pool placement state is not represented by fixed LLVM MIR"); + } + + bool IsTargetSpecific = false; + Expected<std::string> Value = + makeUnexpected(ErrorCode::Unsupported, "constant-pool kind is not MIR serializable"); + if (const auto *Generic = std::get_if<GenericConstantData>(&Description->Value)) { + Value = printLLVMConstant(*Generic); + } else if (const auto *TargetConstantValue = std::get_if<TargetConstant>(&Description->Value)) { + IsTargetSpecific = true; + Value = Target.mirText().printTargetConstant(*TargetConstantValue); + } + if (!Value) + return std::unexpected(Value.error()); + + Output.append(" - id: "); + Output.append(std::to_string(Slot.TextID)); + Output.append("\n value: "); + Output.append(yamlScalar(*Value)); + Output.append("\n alignment: "); + Output.append(std::to_string(Description->Align.bytes())); + Output.push_back('\n'); + if (IsTargetSpecific) + Output.append(" isTargetSpecific: true\n"); + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printJumpTable(const MachineFunction &Function, + const MIParserContext &Context) { + if (Context.JumpTables.empty()) + return {}; + const MachineJumpTableEntry *FirstEntry = + Function.jumpTableInfo().table(Context.JumpTables.front().Index); + const MachineJumpTableDesc *First = FirstEntry == nullptr ? nullptr : FirstEntry->description(); + if (First == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "jump-table slot is not defined"); + + std::string_view Kind; + std::uint8_t ExpectedSize = 8; + switch (First->Encoding) { + case JumpTableEntryEncoding::BlockAddress: + Kind = "block-address"; + break; + case JumpTableEntryEncoding::GPRelative64BlockAddress: + Kind = "gp-rel64-block-address"; + break; + case JumpTableEntryEncoding::GPRelative32BlockAddress: + Kind = "gp-rel32-block-address"; + ExpectedSize = 4; + break; + case JumpTableEntryEncoding::LabelDifference32: + Kind = "label-difference32"; + ExpectedSize = 4; + break; + case JumpTableEntryEncoding::LabelDifference64: + Kind = "label-difference64"; + break; + case JumpTableEntryEncoding::Inline: + Kind = "inline"; + ExpectedSize = 4; + break; + case JumpTableEntryEncoding::Custom32: + Kind = "custom32"; + ExpectedSize = 4; + break; + case JumpTableEntryEncoding::Target: + case JumpTableEntryEncoding::Count: + return makeUnexpected(ErrorCode::Unsupported, + "target jump-table encoding has no fixed LLVM MIR spelling"); + } + + Output.append("jumpTable:\n kind: "); + Output.append(Kind); + Output.append("\n entries:\n"); + for (const MIJumpTableSlot &Slot : Context.JumpTables) { + const MachineJumpTableEntry *Entry = Function.jumpTableInfo().table(Slot.Index); + const MachineJumpTableDesc *Description = Entry == nullptr ? nullptr : Entry->description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "jump-table slot is not defined"); + if (Description->Encoding != First->Encoding || Description->EntrySize != ExpectedSize || + Description->Align.bytes() != ExpectedSize || + Description->Placement != JumpTablePlacement{} || Description->TargetEncoding != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "jump-table entries cannot share one fixed LLVM MIR kind"); + } + Output.append(" - { id: "); + Output.append(std::to_string(Slot.TextID)); + Output.append(", blocks: [ "); + for (std::size_t Index = 0; Index < Description->Targets.size(); ++Index) { + if (Index != 0) + Output.append(", "); + if (Description->Targets[Index] == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "jump-table entry contains a null block"); + auto Block = blockReference(Function, Description->Targets[Index]->stableID()); + if (!Block) + return std::unexpected(Block.error()); + Output.append(yamlScalar(*Block)); + } + Output.append(" ] }\n"); + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printMetadata(const MachineFunction &Function, + const MIParserContext &Context) { + if (Function.metadata().nodes().empty()) + return {}; + Output.append("machineMetadataNodes:\n"); + for (const MachineMetadataNode &Node : Function.metadata().nodes()) { + auto NodeReference = + metadataReference(MachineMetadataReference::machineFunction(Node.Slot), Context); + if (!NodeReference) + return std::unexpected(NodeReference.error()); + std::string Text = *NodeReference + " = "; + if (Node.IsDistinct) + Text.append("distinct "); + Text.append("!{"); + for (std::size_t Index = 0; Index < Node.Elements.size(); ++Index) { + if (Index != 0) + Text.append(", "); + const MachineMetadataElement &Element = Node.Elements[Index]; + if (Element.isNull()) { + Text.append("null"); + } else if (const MachineMetadataReference *Reference = Element.reference()) { + auto Printed = metadataReference(*Reference, Context); + if (!Printed) + return std::unexpected(Printed.error()); + Text.append(*Printed); + } else if (const std::string *String = Element.string()) { + Text.append("!\""); + for (const char Character : *String) { + if (Character == '\\' || Character == '"') + Text.push_back('\\'); + Text.push_back(Character); + } + Text.push_back('"'); + } else { + return makeUnexpected(ErrorCode::VerificationError, + "machine metadata tuple contains an invalid element"); + } + } + Text.push_back('}'); + Output.append(" - "); + Output.append(yamlScalar(Text)); + Output.push_back('\n'); + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printTargetState(const MachineFunction &Function) { + if (!Function.targetState().hasValue()) + return {}; + auto Node = Target.mirText().printFunctionState(Function.targetState()); + if (!Node) + return std::unexpected(Node.error()); + auto Text = targetNode(*Node); + if (!Text) + return std::unexpected(Text.error()); + Output.append("machineFunctionInfo: "); + Output.append(*Text); + Output.push_back('\n'); + return {}; +} + +Expected<void> MIRDocumentPrinter::printFunctionRecords(const MachineFunction &Function, + const MIParserContext &Context) { + const MachineFunctionRecords &Records = Function.records(); + if (!Records.entryValues().empty()) { + Output.append("entry_values:\n"); + for (const MachineEntryValueInfo &Entry : Records.entryValues()) { + auto RegisterName = physicalRegister(Entry.Reg); + if (!RegisterName) + return std::unexpected(RegisterName.error()); + if (!Entry.DebugInfo.Variable || !Entry.DebugInfo.Expression || !Entry.DebugInfo.Location) { + return makeUnexpected(ErrorCode::Unsupported, + "MIR entry value requires complete debug metadata"); + } + auto Variable = metadataReference(*Entry.DebugInfo.Variable, Context); + auto Expression = metadataValue(*Entry.DebugInfo.Expression, Context); + auto Location = debugLocation(*Entry.DebugInfo.Location, Context); + if (!Variable) + return std::unexpected(Variable.error()); + if (!Expression) + return std::unexpected(Expression.error()); + if (!Location) + return std::unexpected(Location.error()); + Output.append(" - { entry-value-register: "); + Output.append(yamlScalar(*RegisterName)); + Output.append(", debug-info-variable: "); + Output.append(yamlScalar(*Variable)); + Output.append(", debug-info-expression: "); + Output.append(yamlScalar(*Expression)); + Output.append(", debug-info-location: "); + Output.append(yamlScalar(*Location)); + Output.append(" }\n"); + } + } else if (Options.Mode == MIPrintMode::Complete) { + Output.append("entry_values: []\n"); + } + + if (!Records.callSites().empty()) { + Output.append("callSites:\n"); + for (const MachineCallSiteInfo &CallSite : Records.callSites()) { + auto Location = instructionLocation(Function, CallSite.CallInstruction); + if (!Location) + return std::unexpected(Location.error()); + Output.append(" - { bb: "); + Output.append(std::to_string(Location->Block)); + Output.append(", offset: "); + Output.append(std::to_string(Location->Offset)); + if (!CallSite.ForwardedArguments.empty()) { + Output.append(", fwdArgRegs: [ "); + for (std::size_t Index = 0; Index < CallSite.ForwardedArguments.size(); ++Index) { + if (Index != 0) + Output.append(", "); + const MachineCallSiteArgument &Argument = CallSite.ForwardedArguments[Index]; + auto RegisterName = physicalRegister(Argument.Reg); + if (!RegisterName) + return std::unexpected(RegisterName.error()); + Output.append("{ arg: "); + Output.append(std::to_string(Argument.ArgumentNumber)); + Output.append(", reg: "); + Output.append(yamlScalar(*RegisterName)); + Output.append(" }"); + } + Output.append(" ]"); + } + if (!CallSite.CalleeTypeIDs.empty()) { + Output.append(", calleeTypeIds: [ "); + for (std::size_t Index = 0; Index < CallSite.CalleeTypeIDs.size(); ++Index) { + if (Index != 0) + Output.append(", "); + Output.append(std::to_string(CallSite.CalleeTypeIDs[Index])); + } + Output.append(" ]"); + } + Output.append(" }\n"); + } + } else if (Options.Mode == MIPrintMode::Complete) { + Output.append("callSites: []\n"); + } + + if (!Records.debugValueSubstitutions().empty()) { + Output.append("debugValueSubstitutions:\n"); + for (const MachineDebugValueSubstitution &Substitution : Records.debugValueSubstitutions()) { + Output.append(" - { srcinst: "); + Output.append(std::to_string(Substitution.Source.InstructionNumber)); + Output.append(", srcop: "); + Output.append(std::to_string(Substitution.Source.OperandNumber)); + Output.append(", dstinst: "); + Output.append(std::to_string(Substitution.Destination.InstructionNumber)); + Output.append(", dstop: "); + Output.append(std::to_string(Substitution.Destination.OperandNumber)); + Output.append(", subreg: "); + Output.append(std::to_string(Substitution.Subregister.value())); + Output.append(" }\n"); + } + } else if (Options.Mode == MIPrintMode::Complete) { + Output.append("debugValueSubstitutions: []\n"); + } + + if (!Records.calledGlobals().empty()) { + Output.append("calledGlobals:\n"); + for (const MachineCalledGlobal &Called : Records.calledGlobals()) { + auto Location = instructionLocation(Function, Called.CallInstruction); + if (!Location) + return std::unexpected(Location.error()); + Output.append(" - { bb: "); + Output.append(std::to_string(Location->Block)); + Output.append(", offset: "); + Output.append(std::to_string(Location->Offset)); + Output.append(", callee: "); + Output.append(yamlScalar(Called.Callee)); + Output.append(", flags: "); + Output.append(std::to_string(Called.Flags)); + Output.append(" }\n"); + } + } + + if (!Records.prefetchTargets().empty()) { + Output.append("prefetch-targets:\n"); + for (const MachinePrefetchTarget &TargetValue : Records.prefetchTargets()) { + std::string Text = "bb_id " + std::to_string(TargetValue.Block.BaseID) + ", " + + std::to_string(TargetValue.Block.CloneID) + ", " + + std::to_string(TargetValue.CallsiteIndex); + Output.append(" - "); + Output.append(yamlScalar(Text)); + Output.push_back('\n'); + } + } + return {}; +} + +Expected<void> MIRDocumentPrinter::printFunction(const MachineFunction &Function, + const MIRFunctionDocumentInfo *) { + if (Function.compilationStatus().state() != MachineCompilationState::Success) { + return makeUnexpected(ErrorCode::Unsupported, + "fixed LLVM MIR cannot serialize this compilation failure state"); + } + auto Context = createBodyContext(Function); + if (!Context) + return std::unexpected(Context.error()); + + Output.append("---\nname: "); + Output.append(yamlScalar(Function.name())); + Output.push_back('\n'); + if (Function.attributes().alignment()) { + Output.append("alignment: "); + Output.append(std::to_string(Function.attributes().alignment()->bytes())); + Output.push_back('\n'); + } + + const MachineFunctionProperties &Properties = Function.properties(); + const MachineFunctionAttributes &Attributes = Function.attributes(); + printBoolean("exposesReturnsTwice", + Attributes.has(MachineFunctionAttribute::ExposesReturnsTwice)); + printBoolean("legalized", Properties.has(MachineFunctionProperty::Legalized)); + printBoolean("regBankSelected", Properties.has(MachineFunctionProperty::RegBankSelected)); + printBoolean("selected", Properties.has(MachineFunctionProperty::Selected)); + printBoolean("failedISel", Properties.has(MachineFunctionProperty::FailedISel)); + printBoolean("tracksRegLiveness", Properties.has(MachineFunctionProperty::TracksLiveness)); + printBoolean("hasWinCFI", Attributes.has(MachineFunctionAttribute::HasWinCFI)); + printBoolean("noPhis", Properties.has(MachineFunctionProperty::NoPHIs), true); + printBoolean("isSSA", Properties.has(MachineFunctionProperty::IsSSA), true); + printBoolean("noVRegs", Properties.has(MachineFunctionProperty::NoVRegs), true); + printBoolean("hasFakeUses", Attributes.has(MachineFunctionAttribute::HasFakeUses), true); + printBoolean("callsEHReturn", Attributes.has(MachineFunctionAttribute::CallsEHReturn)); + printBoolean("callsUnwindInit", Attributes.has(MachineFunctionAttribute::CallsUnwindInit)); + printBoolean("hasEHContTarget", Attributes.has(MachineFunctionAttribute::HasEHContTarget)); + printBoolean("hasEHScopes", Attributes.has(MachineFunctionAttribute::HasEHScopes)); + printBoolean("hasEHFunclets", Attributes.has(MachineFunctionAttribute::HasEHFunclets)); + printBoolean("isOutlined", Attributes.has(MachineFunctionAttribute::IsOutlined)); + printBoolean("debugInstrRef", Attributes.has(MachineFunctionAttribute::DebugInstrRef)); + printBoolean("failsVerification", Properties.has(MachineFunctionProperty::FailsVerification)); + printBoolean("tracksDebugUserValues", + Attributes.has(MachineFunctionAttribute::TracksDebugUserValues)); + + if (auto Printed = printRegisters(Function, *Context); !Printed) + return Printed; + if (auto Printed = printFrameInfo(Function, *Context); !Printed) + return Printed; + if (auto Printed = printStackObjects(Function, *Context, true); !Printed) + return Printed; + if (auto Printed = printStackObjects(Function, *Context, false); !Printed) + return Printed; + if (auto Printed = printFunctionRecords(Function, *Context); !Printed) + return Printed; + if (auto Printed = printConstants(Function, *Context); !Printed) + return Printed; + if (auto Printed = printTargetState(Function); !Printed) + return Printed; + if (auto Printed = printJumpTable(Function, *Context); !Printed) + return Printed; + if (auto Printed = printMetadata(Function, *Context); !Printed) + return Printed; + + auto Body = + printMachineFunctionBody(Function, Target, *Context, MIPrinterOptions{.Mode = Options.Mode}); + if (!Body) + return std::unexpected(Body.error()); + Output.append("body: |\n"); + appendBlockScalar(Output, *Body, 2); + return {}; +} + +Expected<std::string> MIRDocumentPrinter::run() { + auto UnitVerification = Document.unit().verify(); + if (!UnitVerification) + return std::unexpected(UnitVerification.error()); + if (Document.unit().functionCount() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "MIR document must contain at least one machine function"); + } + + for (const MIRFunctionDocumentInfo &Info : Document.functionDocumentInfo()) { + if (Document.unit().function(Info.Function) == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "MIR document info references an unknown machine function"); + } + } + + for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { + const MachineFunction *Function = Document.unit().functionAt(Index); + if (Function == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit contains a null machine function"); + } + auto ReferenceVerification = + verifyMIRDocumentReferences(Document, *Function, Options.EmitEmbeddedIR); + if (!ReferenceVerification) + return std::unexpected(ReferenceVerification.error()); + auto LayoutVerification = verifyOmittedEmbeddedIRLayout(*Function); + if (!LayoutVerification) + return std::unexpected(LayoutVerification.error()); + } + + if (Options.EmitEmbeddedIR && Document.embeddedIR() != nullptr) { + Output.append("--- |\n"); + appendBlockScalar(Output, Document.embeddedIR()->source().text(), 2); + } + for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { + const MachineFunction *Function = Document.unit().functionAt(Index); + if (Function == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit contains a null machine function"); + if (auto Printed = printFunction(*Function, documentInfo(*Function)); !Printed) + return std::unexpected(Printed.error()); + } + return std::move(Output); +} + +} // namespace + +Expected<std::string> printMIRDocument(const MIRDocument &Document, const TargetDescription &Target, + MIRDocumentPrinterOptions Options) { + return MIRDocumentPrinter(Document, Target, Options).run(); +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRDocumentReferenceValidation.h b/backend/lib/MIR/MIRDocumentReferenceValidation.h new file mode 100644 index 0000000..0992d84 --- /dev/null +++ b/backend/lib/MIR/MIRDocumentReferenceValidation.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_MIR_MIRDOCUMENTREFERENCEVALIDATION_H +#define RCC_LIB_MIR_MIRDOCUMENTREFERENCEVALIDATION_H + +#include "rcc/Support/Error.h" + +namespace rcc { + +class MachineFunction; + +namespace mir { + +class MIRDocument; + +[[nodiscard]] Expected<void> verifyMIRDocumentReferences(const MIRDocument &Document, + const MachineFunction &Function, + bool UseEmbeddedIR); + +} // namespace mir +} // namespace rcc + +#endif // RCC_LIB_MIR_MIRDOCUMENTREFERENCEVALIDATION_H |
