aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 01:27:41 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 01:27:41 +0800
commit76d89700953ddd81cf31ab8f260eddf67e93aa85 (patch)
treec96a453164154b2d1559cea36910df52103f0f19
parent183e2354e9fdd107be420cbb185c1d239434aa48 (diff)
downloadrcc-project-76d89700953ddd81cf31ab8f260eddf67e93aa85.tar.gz
rcc-project-76d89700953ddd81cf31ab8f260eddf67e93aa85.tar.xz
[MIR] Complete document parsing and printing
Implement the single LLVM-compatible MIR document path with structured\nembedded-IR references, YAML mappings, constants, and deterministic printing.\n\nKeep pointer LLT widths DataLayout-derived, reject layout-dependent output\nwhen embedded IR is omitted, and record the fixed-baseline compatibility\ncontract and regression coverage.
-rw-r--r--backend/include/rcc/CodeGen/GenericConstant.h176
-rw-r--r--backend/include/rcc/CodeGen/InstructionDescriptor.h1
-rw-r--r--backend/include/rcc/CodeGen/MachineConstantPool.h9
-rw-r--r--backend/include/rcc/CodeGen/MachineFrameInfo.h79
-rw-r--r--backend/include/rcc/CodeGen/MachineFunctionProperties.h4
-rw-r--r--backend/include/rcc/CodeGen/MachineJumpTableInfo.h26
-rw-r--r--backend/include/rcc/CodeGen/MachineMetadata.h12
-rw-r--r--backend/include/rcc/CodeGen/MachineOperand.h3
-rw-r--r--backend/include/rcc/CodeGen/TargetMIRTextInfo.h63
-rw-r--r--backend/include/rcc/MIR/EmbeddedIR.h39
-rw-r--r--backend/include/rcc/MIR/LLVMConstantSyntax.h52
-rw-r--r--backend/include/rcc/MIR/MIDebugSyntax.h17
-rw-r--r--backend/include/rcc/MIR/MIParser.h12
-rw-r--r--backend/include/rcc/MIR/MIRDocument.h143
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt4
-rw-r--r--backend/lib/CodeGen/GenericConstant.cpp875
-rw-r--r--backend/lib/CodeGen/MachineConstantPool.cpp44
-rw-r--r--backend/lib/CodeGen/MachineFrameInfo.cpp143
-rw-r--r--backend/lib/CodeGen/MachineFunctionCloner.cpp55
-rw-r--r--backend/lib/CodeGen/MachineFunctionProperties.cpp2
-rw-r--r--backend/lib/CodeGen/MachineJumpTableInfo.cpp55
-rw-r--r--backend/lib/CodeGen/MachineMetadata.cpp4
-rw-r--r--backend/lib/CodeGen/MachineOperand.cpp83
-rw-r--r--backend/lib/CodeGen/MachineVerifier.cpp30
-rw-r--r--backend/lib/CodeGen/TargetMIRTextInfo.cpp178
-rw-r--r--backend/lib/MIR/CMakeLists.txt16
-rw-r--r--backend/lib/MIR/EmbeddedIR.cpp188
-rw-r--r--backend/lib/MIR/LLVMConstantSyntax.cpp3291
-rw-r--r--backend/lib/MIR/MIDebugSyntax.cpp127
-rw-r--r--backend/lib/MIR/MIParser.cpp154
-rw-r--r--backend/lib/MIR/MIPrinter.cpp122
-rw-r--r--backend/lib/MIR/MIRDocument.cpp2771
-rw-r--r--backend/lib/MIR/MIRDocumentPrinter.cpp1302
-rw-r--r--backend/lib/MIR/MIRDocumentReferenceValidation.h23
-rw-r--r--backend/unittests/CodeGen/MachineConstantPoolTest.cpp4
-rw-r--r--backend/unittests/CodeGen/MachineFrameInfoTest.cpp3
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp5
-rw-r--r--backend/unittests/CodeGen/MachineIRBuilderTest.cpp3
-rw-r--r--backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp12
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp3
-rw-r--r--backend/unittests/MIR/CMakeLists.txt24
-rw-r--r--backend/unittests/MIR/EmbeddedIRTest.cpp14
-rw-r--r--backend/unittests/MIR/LLVMConstantSyntaxTest.cpp497
-rw-r--r--backend/unittests/MIR/MIParserTest.cpp62
-rw-r--r--backend/unittests/MIR/MIRDocumentTest.cpp953
45 files changed, 11465 insertions, 218 deletions
diff --git a/backend/include/rcc/CodeGen/GenericConstant.h b/backend/include/rcc/CodeGen/GenericConstant.h
new file mode 100644
index 0000000..11c5068
--- /dev/null
+++ b/backend/include/rcc/CodeGen/GenericConstant.h
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_GENERICCONSTANT_H
+#define RCC_CODEGEN_GENERICCONSTANT_H
+
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace rcc {
+
+enum class GenericConstantTypeKind : std::uint8_t {
+ Invalid,
+ Void,
+ Label,
+ Token,
+ Metadata,
+ Integer,
+ Byte,
+ Floating,
+ Pointer,
+ Array,
+ Vector,
+ Struct,
+ Function,
+ Named,
+};
+
+struct GenericConstantType final {
+ GenericConstantTypeKind Kind = GenericConstantTypeKind::Invalid;
+ std::uint32_t BitWidth = 0;
+ FloatSemantics Semantics = FloatSemantics::IEEEHalf;
+ std::uint32_t AddressSpace = 0;
+ std::uint64_t ElementCount = 0;
+ bool Scalable = false;
+ bool Packed = false;
+ bool VarArg = false;
+ bool NumericName = false;
+ std::string Name;
+ std::vector<GenericConstantType> Elements;
+
+ [[nodiscard]] static Expected<GenericConstantType> integer(std::uint32_t BitWidth);
+ [[nodiscard]] static GenericConstantType primitive(GenericConstantTypeKind Kind) noexcept;
+ [[nodiscard]] static GenericConstantType byte() noexcept;
+ [[nodiscard]] static Expected<GenericConstantType> floating(FloatSemantics Semantics);
+ [[nodiscard]] static GenericConstantType pointer(std::uint32_t AddressSpace = 0) noexcept;
+ [[nodiscard]] static Expected<GenericConstantType>
+ array(std::uint64_t ElementCount, GenericConstantType Element);
+ [[nodiscard]] static Expected<GenericConstantType>
+ vector(std::uint64_t ElementCount, GenericConstantType Element, bool Scalable = false);
+ [[nodiscard]] static Expected<GenericConstantType>
+ structure(std::vector<GenericConstantType> Elements, bool Packed = false);
+ [[nodiscard]] static Expected<GenericConstantType>
+ function(GenericConstantType Return, std::vector<GenericConstantType> Parameters,
+ bool VarArg = false);
+ [[nodiscard]] static Expected<GenericConstantType> named(std::string Name,
+ bool NumericName = false);
+
+ [[nodiscard]] Expected<void> verify() const;
+
+ friend bool operator==(const GenericConstantType &Left,
+ const GenericConstantType &Right) = default;
+};
+
+enum class GenericConstantKind : std::uint8_t {
+ Invalid,
+ Integer,
+ Floating,
+ NullPointer,
+ ZeroInitializer,
+ Undef,
+ Poison,
+ Aggregate,
+ ByteString,
+ GlobalAddress,
+ BlockAddress,
+ DSOLocalEquivalent,
+ NoCFI,
+ PointerAuth,
+ Expression,
+};
+
+enum class GenericConstantExpressionOpcode : std::uint8_t {
+ Invalid,
+ Trunc,
+ BitCast,
+ AddressSpaceCast,
+ IntToPtr,
+ PtrToAddr,
+ PtrToInt,
+ Add,
+ Sub,
+ Xor,
+ GetElementPtr,
+ ShuffleVector,
+ InsertElement,
+ ExtractElement,
+ Splat,
+};
+
+enum class GenericConstantExpressionFlag : std::uint8_t {
+ NoUnsignedWrap,
+ NoSignedWrap,
+ InBounds,
+ NoUnsignedSignedWrap,
+ GEPNoUnsignedWrap,
+ Count,
+};
+
+class GenericConstantExpressionFlags final {
+public:
+ constexpr GenericConstantExpressionFlags() noexcept = default;
+
+ [[nodiscard]] constexpr bool has(GenericConstantExpressionFlag Flag) const noexcept {
+ const auto Index = static_cast<std::uint8_t>(Flag);
+ if (Index >= static_cast<std::uint8_t>(GenericConstantExpressionFlag::Count))
+ return false;
+ return (Mask & (std::uint8_t{1} << Index)) != 0;
+ }
+
+ constexpr void set(GenericConstantExpressionFlag Flag) noexcept {
+ const auto Index = static_cast<std::uint8_t>(Flag);
+ if (Index < static_cast<std::uint8_t>(GenericConstantExpressionFlag::Count))
+ Mask |= std::uint8_t{1} << Index;
+ }
+
+ [[nodiscard]] constexpr std::uint8_t rawValue() const noexcept {
+ return Mask;
+ }
+
+ friend constexpr bool operator==(GenericConstantExpressionFlags Left,
+ GenericConstantExpressionFlags Right) noexcept = default;
+
+private:
+ std::uint8_t Mask = 0;
+};
+
+struct GenericConstantData final {
+ GenericConstantType Type;
+ GenericConstantKind Kind = GenericConstantKind::Invalid;
+ std::optional<IntegerImmediate> IntegerValue;
+ std::optional<FloatingPointBits> FloatingValue;
+ std::vector<std::byte> ByteValue;
+ std::vector<GenericConstantData> Elements;
+ std::string Symbol;
+ std::string Block;
+ bool NumericSymbol = false;
+ bool NumericBlock = false;
+ GenericConstantExpressionOpcode ExpressionOpcode =
+ GenericConstantExpressionOpcode::Invalid;
+ GenericConstantExpressionFlags ExpressionFlags;
+ std::optional<GenericConstantType> SourceElementType;
+ std::optional<IntegerImmediate> InRangeStart;
+ std::optional<IntegerImmediate> InRangeEnd;
+
+ [[nodiscard]] static Expected<GenericConstantData> integer(IntegerImmediate Value);
+ [[nodiscard]] static Expected<GenericConstantData> floating(FloatingPointBits Value);
+ [[nodiscard]] static Expected<GenericConstantData> byteString(GenericConstantType Type,
+ std::vector<std::byte> Bytes);
+
+ [[nodiscard]] Expected<void> verify() const;
+ [[nodiscard]] bool referencesSymbol() const noexcept;
+
+ friend bool operator==(const GenericConstantData &Left,
+ const GenericConstantData &Right) = default;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_GENERICCONSTANT_H
diff --git a/backend/include/rcc/CodeGen/InstructionDescriptor.h b/backend/include/rcc/CodeGen/InstructionDescriptor.h
index 94ea374..06e37d3 100644
--- a/backend/include/rcc/CodeGen/InstructionDescriptor.h
+++ b/backend/include/rcc/CodeGen/InstructionDescriptor.h
@@ -97,6 +97,7 @@ enum class InstructionFlag : std::uint8_t {
Convergent,
UnmodeledSideEffects,
Phi,
+ FakeUse,
Generic,
TerminalEmission,
Count,
diff --git a/backend/include/rcc/CodeGen/MachineConstantPool.h b/backend/include/rcc/CodeGen/MachineConstantPool.h
index cc9ae04..a0c269b 100644
--- a/backend/include/rcc/CodeGen/MachineConstantPool.h
+++ b/backend/include/rcc/CodeGen/MachineConstantPool.h
@@ -3,6 +3,7 @@
#ifndef RCC_CODEGEN_MACHINECONSTANTPOOL_H
#define RCC_CODEGEN_MACHINECONSTANTPOOL_H
+#include "rcc/CodeGen/GenericConstant.h"
#include "rcc/CodeGen/MachineIRTypes.h"
#include "rcc/CodeGen/MachineMemOperand.h"
#include "rcc/CodeGen/TargetConstant.h"
@@ -59,14 +60,6 @@ struct ConstantPoolPlacement final {
const ConstantPoolPlacement &Right) = default;
};
-struct GenericConstantData final {
- std::string TypeIdentity;
- std::vector<std::byte> Bytes;
-
- friend bool operator==(const GenericConstantData &Left,
- const GenericConstantData &Right) = default;
-};
-
struct SymbolDifferenceConstant final {
std::string Minuend;
std::string Subtrahend;
diff --git a/backend/include/rcc/CodeGen/MachineFrameInfo.h b/backend/include/rcc/CodeGen/MachineFrameInfo.h
index 7c42bc2..48e3d7a 100644
--- a/backend/include/rcc/CodeGen/MachineFrameInfo.h
+++ b/backend/include/rcc/CodeGen/MachineFrameInfo.h
@@ -6,12 +6,14 @@
#include "rcc/CodeGen/MachineDebugInfo.h"
#include "rcc/CodeGen/MachineIRTypes.h"
#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/Register.h"
#include "rcc/CodeGen/StableID.h"
#include "rcc/Support/Error.h"
#include <cstdint>
#include <optional>
#include <span>
+#include <string>
#include <string_view>
#include <utility>
#include <variant>
@@ -22,6 +24,7 @@ namespace rcc {
enum class StackObjectKind : std::uint8_t {
Default,
FixedABI,
+ FixedSpillSlot,
SpillSlot,
VariableSized,
DynamicAlloca,
@@ -101,13 +104,18 @@ struct StackObjectLifetimeRange final {
struct StackObjectDesc final {
StackObjectKind Kind = StackObjectKind::Default;
+ std::optional<std::string> AllocaName;
std::optional<std::uint64_t> Size;
Alignment Align;
std::optional<std::int64_t> Offset;
StackID Stack;
bool IsDead = false;
+ bool IsImmutable = false;
bool IsAliased = false;
bool AddressTaken = false;
+ std::optional<PhysicalRegister> CalleeSavedRegister;
+ bool CalleeSavedRestored = true;
+ std::optional<std::int64_t> LocalOffset;
std::vector<StackObjectLifetimeRange> Lifetime;
std::optional<MachineVariableDebugInfo> DebugInfo;
std::uint64_t TargetFlags = 0;
@@ -143,6 +151,56 @@ private:
friend class MachineFrameInfo;
};
+enum class MachineFramePointerPolicy : std::uint8_t {
+ None,
+ NonLeaf,
+ All,
+ Reserved,
+ NonLeafNoReserve,
+ Count,
+};
+
+[[nodiscard]] std::optional<MachineFramePointerPolicy>
+parseMachineFramePointerPolicy(std::string_view Name) noexcept;
+[[nodiscard]] std::string_view
+getMachineFramePointerPolicyName(MachineFramePointerPolicy Policy) noexcept;
+
+struct MachineFrameSaveRestorePoint final {
+ MachineBlockStableID Block;
+ std::vector<PhysicalRegister> Registers;
+
+ friend bool operator==(const MachineFrameSaveRestorePoint &Left,
+ const MachineFrameSaveRestorePoint &Right) = default;
+};
+
+struct MachineFrameState final {
+ bool IsFrameAddressTaken = false;
+ bool IsReturnAddressTaken = false;
+ bool HasStackMap = false;
+ bool HasPatchPoint = false;
+ std::uint64_t StackSize = 0;
+ std::int32_t OffsetAdjustment = 0;
+ std::optional<Alignment> MaxAlignment;
+ bool AdjustsStack = false;
+ bool HasCalls = false;
+ MachineFramePointerPolicy FramePointerPolicy = MachineFramePointerPolicy::None;
+ std::optional<FrameIndex> StackProtector;
+ std::optional<FrameIndex> FunctionContext;
+ std::optional<std::uint64_t> MaxCallFrameSize;
+ std::uint32_t CVBytesOfCalleeSavedRegisters = 0;
+ bool HasOpaqueSPAdjustment = false;
+ bool HasVAStart = false;
+ bool HasMustTailInVarArgFunc = false;
+ bool HasTailCall = false;
+ bool IsCalleeSavedInfoValid = false;
+ std::uint32_t LocalFrameSize = 0;
+ std::vector<MachineFrameSaveRestorePoint> SavePoints;
+ std::vector<MachineFrameSaveRestorePoint> RestorePoints;
+
+ friend bool operator==(const MachineFrameState &Left,
+ const MachineFrameState &Right) = default;
+};
+
class MachineFrameInfo final {
public:
MachineFrameInfo() = default;
@@ -169,31 +227,38 @@ public:
return Frozen;
}
+ [[nodiscard]] const MachineFrameState &state() const noexcept {
+ return State;
+ }
+
[[nodiscard]] std::optional<std::uint64_t> staticStackSize() const noexcept {
- return StaticStackSize;
+ if (!Frozen)
+ return std::nullopt;
+ return State.StackSize;
}
[[nodiscard]] Alignment stackAlignment() const noexcept {
- return StackAlign;
+ return State.MaxAlignment.value_or(Alignment{});
}
- [[nodiscard]] std::uint64_t maxCallFrameSize() const noexcept {
- return MaxCallFrameSize;
+ [[nodiscard]] std::optional<std::uint64_t> maxCallFrameSize() const noexcept {
+ return State.MaxCallFrameSize;
}
+ [[nodiscard]] Expected<void> setState(MachineFrameState NewState);
[[nodiscard]] Expected<void> setMaxCallFrameSize(std::uint64_t Size);
+ [[nodiscard]] Expected<void> clearMaxCallFrameSize();
[[nodiscard]] Expected<void> freeze(std::uint64_t Size, Alignment Align);
[[nodiscard]] Expected<void> verify() const;
private:
[[nodiscard]] static Expected<void> verifyDescription(const StackObjectDesc &Description);
+ [[nodiscard]] static Expected<void> verifyStateStructure(const MachineFrameState &State);
[[nodiscard]] Expected<StackObject *> mutableObject(FrameIndex Index);
std::vector<StackObject> Objects;
+ MachineFrameState State;
bool Frozen = false;
- std::optional<std::uint64_t> StaticStackSize;
- Alignment StackAlign;
- std::uint64_t MaxCallFrameSize = 0;
};
} // namespace rcc
diff --git a/backend/include/rcc/CodeGen/MachineFunctionProperties.h b/backend/include/rcc/CodeGen/MachineFunctionProperties.h
index 7d1a5ad..39a4fce 100644
--- a/backend/include/rcc/CodeGen/MachineFunctionProperties.h
+++ b/backend/include/rcc/CodeGen/MachineFunctionProperties.h
@@ -27,6 +27,10 @@ enum class MachineFunctionProperty : std::uint8_t {
FrameFinalized,
NoFrameIndices,
NoExecutablePseudos,
+ // These two properties mirror LLVM MIR pipeline state. They are deliberately
+ // separate from RCC's MachineCompilationStatus, which records a concrete run.
+ FailedISel,
+ FailsVerification,
Count,
};
diff --git a/backend/include/rcc/CodeGen/MachineJumpTableInfo.h b/backend/include/rcc/CodeGen/MachineJumpTableInfo.h
index 6342281..b22ea50 100644
--- a/backend/include/rcc/CodeGen/MachineJumpTableInfo.h
+++ b/backend/include/rcc/CodeGen/MachineJumpTableInfo.h
@@ -21,10 +21,13 @@ class MachineBasicBlock;
class MachineFunction;
enum class JumpTableEntryEncoding : std::uint8_t {
- AbsoluteAddress,
- RelativeToTableBase,
- RelativeToEntry,
- CompressedOffset,
+ BlockAddress,
+ GPRelative64BlockAddress,
+ GPRelative32BlockAddress,
+ LabelDifference32,
+ LabelDifference64,
+ Inline,
+ Custom32,
Target,
Count,
};
@@ -39,7 +42,7 @@ struct JumpTablePlacement final {
};
struct MachineJumpTableDesc final {
- JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::AbsoluteAddress;
+ JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress;
std::uint8_t EntrySize = 8;
Alignment Align;
std::vector<MachineBasicBlock *> Targets;
@@ -60,16 +63,24 @@ public:
return std::holds_alternative<std::monostate>(Description);
}
+ [[nodiscard]] bool isPending() const noexcept {
+ return std::holds_alternative<PendingDescription>(Description);
+ }
+
[[nodiscard]] const MachineJumpTableDesc *description() const noexcept {
return std::get_if<MachineJumpTableDesc>(&Description);
}
private:
+ struct PendingDescription final {};
+
MachineJumpTableEntry(JumpTableIndex Index, MachineJumpTableDesc Description)
: Index(Index), Description(std::move(Description)) {}
+ explicit MachineJumpTableEntry(JumpTableIndex Index)
+ : Index(Index), Description(PendingDescription{}) {}
JumpTableIndex Index;
- std::variant<std::monostate, MachineJumpTableDesc> Description;
+ std::variant<std::monostate, PendingDescription, MachineJumpTableDesc> Description;
friend class MachineJumpTableInfo;
};
@@ -85,6 +96,9 @@ public:
~MachineJumpTableInfo() = default;
[[nodiscard]] Expected<JumpTableIndex> createTable(MachineJumpTableDesc Description);
+ [[nodiscard]] Expected<JumpTableIndex> reserveTable();
+ [[nodiscard]] Expected<void> defineReservedTable(JumpTableIndex Index,
+ MachineJumpTableDesc Description);
[[nodiscard]] Expected<void> updateTable(JumpTableIndex Index, MachineJumpTableDesc Description);
[[nodiscard]] Expected<void> eraseTable(JumpTableIndex Index);
diff --git a/backend/include/rcc/CodeGen/MachineMetadata.h b/backend/include/rcc/CodeGen/MachineMetadata.h
index 95033f6..df56b76 100644
--- a/backend/include/rcc/CodeGen/MachineMetadata.h
+++ b/backend/include/rcc/CodeGen/MachineMetadata.h
@@ -62,6 +62,11 @@ class MachineMetadataElement final {
public:
MachineMetadataElement() = default;
+ struct NullValue final {
+ friend constexpr bool operator==(NullValue Left, NullValue Right) noexcept = default;
+ };
+
+ [[nodiscard]] static MachineMetadataElement null();
[[nodiscard]] static Expected<MachineMetadataElement>
reference(MachineMetadataReference Reference);
[[nodiscard]] static MachineMetadataElement string(std::string Value);
@@ -78,14 +83,19 @@ public:
return std::get_if<std::string>(&Storage);
}
+ [[nodiscard]] bool isNull() const noexcept {
+ return std::holds_alternative<NullValue>(Storage);
+ }
+
friend bool operator==(const MachineMetadataElement &Left,
const MachineMetadataElement &Right) = default;
private:
+ explicit MachineMetadataElement(NullValue Value) : Storage(Value) {}
explicit MachineMetadataElement(MachineMetadataReference Reference) : Storage(Reference) {}
explicit MachineMetadataElement(std::string Value) : Storage(std::move(Value)) {}
- std::variant<std::monostate, MachineMetadataReference, std::string> Storage;
+ std::variant<std::monostate, NullValue, MachineMetadataReference, std::string> Storage;
};
struct MachineMetadataNode final {
diff --git a/backend/include/rcc/CodeGen/MachineOperand.h b/backend/include/rcc/CodeGen/MachineOperand.h
index 7344932..b8a6dcc 100644
--- a/backend/include/rcc/CodeGen/MachineOperand.h
+++ b/backend/include/rcc/CodeGen/MachineOperand.h
@@ -69,6 +69,8 @@ public:
std::uint64_t Value);
[[nodiscard]] static Expected<IntegerImmediate> fromSigned(std::uint32_t BitWidth,
std::int64_t Value);
+ [[nodiscard]] static Expected<IntegerImmediate> parseDecimal(std::uint32_t BitWidth,
+ std::string_view Text);
[[nodiscard]] std::uint32_t bitWidth() const noexcept {
return BitWidth;
@@ -80,6 +82,7 @@ public:
[[nodiscard]] bool isZero() const noexcept;
[[nodiscard]] bool isNegative() const noexcept;
+ [[nodiscard]] std::string toUnsignedDecimal() const;
[[nodiscard]] Expected<void> verify() const;
friend bool operator==(const IntegerImmediate &Left, const IntegerImmediate &Right) = default;
diff --git a/backend/include/rcc/CodeGen/TargetMIRTextInfo.h b/backend/include/rcc/CodeGen/TargetMIRTextInfo.h
index 4fdbb3a..b8eb4ee 100644
--- a/backend/include/rcc/CodeGen/TargetMIRTextInfo.h
+++ b/backend/include/rcc/CodeGen/TargetMIRTextInfo.h
@@ -7,6 +7,8 @@
#include "rcc/CodeGen/MachineIRTypes.h"
#include "rcc/CodeGen/Register.h"
#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/CodeGen/TargetFunctionState.h"
#include "rcc/Support/Error.h"
#include <cstdint>
@@ -24,6 +26,55 @@ namespace rcc {
class TargetRegisterInfo;
+enum class MIRTargetNodeKind : std::uint8_t {
+ Null,
+ Scalar,
+ Sequence,
+ Mapping,
+ Count,
+};
+
+struct MIRTargetMappingEntry;
+
+struct MIRTargetNode final {
+ MIRTargetNode();
+ MIRTargetNode(const MIRTargetNode &);
+ MIRTargetNode &operator=(const MIRTargetNode &);
+ MIRTargetNode(MIRTargetNode &&) noexcept;
+ MIRTargetNode &operator=(MIRTargetNode &&) noexcept;
+ ~MIRTargetNode();
+
+ MIRTargetNodeKind Kind = MIRTargetNodeKind::Null;
+ std::string Scalar;
+ std::vector<MIRTargetNode> Sequence;
+ std::vector<MIRTargetMappingEntry> Mapping;
+
+ [[nodiscard]] static MIRTargetNode null();
+ [[nodiscard]] static MIRTargetNode scalar(std::string Value);
+ [[nodiscard]] static MIRTargetNode sequence(std::vector<MIRTargetNode> Values);
+ [[nodiscard]] static MIRTargetNode mapping(std::vector<MIRTargetMappingEntry> Entries);
+
+ [[nodiscard]] const MIRTargetNode *mappingValue(std::string_view Key) const noexcept;
+ [[nodiscard]] Expected<void> verify() const;
+
+ friend bool operator==(const MIRTargetNode &Left, const MIRTargetNode &Right);
+};
+
+struct MIRTargetMappingEntry final {
+ std::string Key;
+ MIRTargetNode Value;
+
+ friend bool operator==(const MIRTargetMappingEntry &Left,
+ const MIRTargetMappingEntry &Right) = default;
+};
+
+using MIRTargetFunctionStateParser =
+ Expected<TargetFunctionState> (*)(const MIRTargetNode &Node);
+using MIRTargetFunctionStatePrinter =
+ Expected<MIRTargetNode> (*)(const TargetFunctionState &State);
+using MIRTargetConstantParser = Expected<TargetConstant> (*)(std::string_view Text);
+using MIRTargetConstantPrinter = Expected<std::string> (*)(const TargetConstant &Constant);
+
struct MIRRegisterMaskDesc final {
std::string Name;
std::shared_ptr<const RegisterMask> Mask;
@@ -75,6 +126,10 @@ struct TargetMIRTextInfoDesc final {
StackID::noAllocStack(),
};
std::optional<TargetFunctionStateTypeID> FunctionStateType;
+ MIRTargetFunctionStateParser ParseFunctionState = nullptr;
+ MIRTargetFunctionStatePrinter PrintFunctionState = nullptr;
+ MIRTargetConstantParser ParseTargetConstant = nullptr;
+ MIRTargetConstantPrinter PrintTargetConstant = nullptr;
};
class TargetMIRTextInfo final {
@@ -180,6 +235,14 @@ public:
return Description.FunctionStateType;
}
+ [[nodiscard]] Expected<TargetFunctionState>
+ parseFunctionState(const MIRTargetNode &Node) const;
+ [[nodiscard]] Expected<MIRTargetNode>
+ printFunctionState(const TargetFunctionState &State) const;
+ [[nodiscard]] Expected<TargetConstant> parseTargetConstant(std::string_view Text) const;
+ [[nodiscard]] Expected<std::string>
+ printTargetConstant(const TargetConstant &Constant) const;
+
private:
explicit TargetMIRTextInfo(TargetMIRTextInfoDesc Description)
: Description(std::move(Description)) {}
diff --git a/backend/include/rcc/MIR/EmbeddedIR.h b/backend/include/rcc/MIR/EmbeddedIR.h
index 8e9e0f6..50567de 100644
--- a/backend/include/rcc/MIR/EmbeddedIR.h
+++ b/backend/include/rcc/MIR/EmbeddedIR.h
@@ -24,6 +24,20 @@ struct EmbeddedIRReference final {
TextRange Range;
};
+struct EmbeddedIRMetadataDefinition final {
+ std::string Name;
+ TextRange Range;
+ std::string DynamicType;
+};
+
+struct EmbeddedIRTypeDefinition final {
+ std::string Name;
+ TextRange Range;
+ std::string Definition;
+ TextRange DefinitionRange;
+ bool NumericName = false;
+};
+
class EmbeddedIRFunction final {
public:
[[nodiscard]] std::string_view name() const noexcept {
@@ -46,8 +60,13 @@ public:
return Allocas;
}
+ [[nodiscard]] std::span<const EmbeddedIRReference> values() const noexcept {
+ return Values;
+ }
+
[[nodiscard]] const EmbeddedIRReference *block(std::string_view QueryName) const noexcept;
[[nodiscard]] const EmbeddedIRReference *alloca(std::string_view QueryName) const noexcept;
+ [[nodiscard]] const EmbeddedIRReference *value(std::string_view QueryName) const noexcept;
private:
struct StringHash final {
@@ -63,8 +82,10 @@ private:
bool IsDefinition = false;
std::vector<EmbeddedIRReference> Blocks;
std::vector<EmbeddedIRReference> Allocas;
+ std::vector<EmbeddedIRReference> Values;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> BlockNameToIndex;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> AllocaNameToIndex;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> ValueNameToIndex;
friend class EmbeddedIRIndexer;
};
@@ -101,13 +122,19 @@ public:
return GlobalObjects;
}
- [[nodiscard]] std::span<const EmbeddedIRReference> metadataDefinitions() const noexcept {
+ [[nodiscard]] std::span<const EmbeddedIRMetadataDefinition>
+ metadataDefinitions() const noexcept {
return MetadataDefinitions;
}
+ [[nodiscard]] std::span<const EmbeddedIRTypeDefinition> typeDefinitions() const noexcept {
+ return TypeDefinitions;
+ }
+
[[nodiscard]] const EmbeddedIRFunction *function(std::string_view Name) const noexcept;
[[nodiscard]] const EmbeddedIRReference *globalObject(std::string_view Name) const noexcept;
- [[nodiscard]] const EmbeddedIRReference *metadata(std::string_view Name) const noexcept;
+ [[nodiscard]] const EmbeddedIRMetadataDefinition *metadata(std::string_view Name) const noexcept;
+ [[nodiscard]] const EmbeddedIRTypeDefinition *typeDefinition(std::string_view Name) const noexcept;
[[nodiscard]] bool hasGlobalValue(std::string_view Name) const noexcept {
return function(Name) != nullptr || globalObject(Name) != nullptr;
}
@@ -131,10 +158,12 @@ private:
std::optional<TextRange> DataLayoutRange;
std::vector<EmbeddedIRFunction> Functions;
std::vector<EmbeddedIRReference> GlobalObjects;
- std::vector<EmbeddedIRReference> MetadataDefinitions;
+ std::vector<EmbeddedIRMetadataDefinition> MetadataDefinitions;
+ std::vector<EmbeddedIRTypeDefinition> TypeDefinitions;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> FunctionNameToIndex;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> GlobalNameToIndex;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> MetadataNameToIndex;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> TypeNameToIndex;
friend class EmbeddedIRIndexer;
};
@@ -159,6 +188,10 @@ public:
return Diagnostics;
}
+ [[nodiscard]] std::optional<EmbeddedIRReferenceIndex> takeIndex() && {
+ return std::move(Index);
+ }
+
private:
EmbeddedIRIndexResult() = default;
diff --git a/backend/include/rcc/MIR/LLVMConstantSyntax.h b/backend/include/rcc/MIR/LLVMConstantSyntax.h
new file mode 100644
index 0000000..9d7a7d4
--- /dev/null
+++ b/backend/include/rcc/MIR/LLVMConstantSyntax.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MIR_LLVMCONSTANTSYNTAX_H
+#define RCC_MIR_LLVMCONSTANTSYNTAX_H
+
+#include "rcc/CodeGen/GenericConstant.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace rcc::mir {
+
+struct LLVMNamedTypeDefinition final {
+ std::string_view Name;
+ std::string_view Definition;
+ bool NumericName = false;
+};
+
+struct LLVMConstantSyntaxOptions final {
+ std::uint32_t DefaultPointerSizeInBits = 0;
+ std::string_view DataLayout;
+ std::span<const LLVMNamedTypeDefinition> NamedTypes;
+ std::size_t MaximumDepth = 1024;
+ std::size_t MaximumNodes = std::size_t{1} << 24U;
+ std::size_t MaximumTokenLength = std::size_t{1} << 24U;
+};
+
+[[nodiscard]] Expected<GenericConstantData>
+parseLLVMConstant(std::string_view Text, LLVMConstantSyntaxOptions Options = {});
+
+[[nodiscard]] Expected<GenericConstantType> parseLLVMType(std::string_view Text,
+ LLVMConstantSyntaxOptions Options = {});
+
+[[nodiscard]] Expected<std::string> printLLVMConstant(const GenericConstantData &Constant);
+
+[[nodiscard]] Expected<std::uint64_t>
+llvmConstantStorageSize(const GenericConstantType &Type, LLVMConstantSyntaxOptions Options = {});
+
+[[nodiscard]] Expected<Alignment>
+llvmConstantPreferredAlignment(const GenericConstantType &Type,
+ LLVMConstantSyntaxOptions Options = {});
+
+[[nodiscard]] Expected<std::uint32_t> llvmPointerSizeInBits(std::uint32_t AddressSpace,
+ LLVMConstantSyntaxOptions Options = {});
+
+} // namespace rcc::mir
+
+#endif // RCC_MIR_LLVMCONSTANTSYNTAX_H
diff --git a/backend/include/rcc/MIR/MIDebugSyntax.h b/backend/include/rcc/MIR/MIDebugSyntax.h
new file mode 100644
index 0000000..a8427dc
--- /dev/null
+++ b/backend/include/rcc/MIR/MIDebugSyntax.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MIR_MIDEBUGSYNTAX_H
+#define RCC_MIR_MIDEBUGSYNTAX_H
+
+#include <cstdint>
+#include <optional>
+#include <string_view>
+
+namespace rcc::mir {
+
+[[nodiscard]] std::optional<std::uint64_t>
+parseDIExpressionOpcodeName(std::string_view Name) noexcept;
+
+} // namespace rcc::mir
+
+#endif // RCC_MIR_MIDEBUGSYNTAX_H
diff --git a/backend/include/rcc/MIR/MIParser.h b/backend/include/rcc/MIR/MIParser.h
index 978e928..116a97f 100644
--- a/backend/include/rcc/MIR/MIParser.h
+++ b/backend/include/rcc/MIR/MIParser.h
@@ -5,15 +5,18 @@
#include "rcc/CodeGen/MachineIRTypes.h"
#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/Register.h"
#include "rcc/Support/Error.h"
#include "rcc/Support/TextDiagnostic.h"
#include <cstddef>
#include <cstdint>
+#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
+#include <string_view>
#include <vector>
namespace rcc {
@@ -23,6 +26,11 @@ class TargetDescription;
namespace mir {
+struct MIVirtualRegisterSlot final {
+ std::uint32_t TextID = 0;
+ Register Reg;
+};
+
struct MIFrameObjectSlot final {
std::uint32_t TextID = 0;
FrameIndex Index;
@@ -50,12 +58,15 @@ struct MISyncScopeSlot final {
};
struct MIParserContext final {
+ std::vector<MIVirtualRegisterSlot> VirtualRegisters;
std::vector<MIFrameObjectSlot> StackObjects;
std::vector<MIFrameObjectSlot> FixedStackObjects;
std::vector<MIConstantPoolSlot> ConstantPoolItems;
std::vector<MIJumpTableSlot> JumpTables;
std::vector<MIMetadataSlot> Metadata;
std::vector<MISyncScopeSlot> SyncScopes;
+ std::function<Expected<SyncScopeID>(std::string_view)> InternSyncScope;
+ std::function<Expected<std::uint32_t>(std::uint32_t)> PointerSizeInBits;
std::uint32_t DefaultPointerSizeInBits = 0;
};
@@ -66,6 +77,7 @@ struct MIParserOptions final {
std::size_t MaximumInstructions = std::size_t{1} << 24U;
std::size_t MaximumOperandsPerInstruction = std::size_t{1} << 20U;
std::size_t MaximumMemoryOperandsPerInstruction = std::size_t{1} << 20U;
+ bool VerifyAfterParse = true;
};
class MIParser final {
diff --git a/backend/include/rcc/MIR/MIRDocument.h b/backend/include/rcc/MIR/MIRDocument.h
new file mode 100644
index 0000000..7980ca1
--- /dev/null
+++ b/backend/include/rcc/MIR/MIRDocument.h
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MIR_MIRDOCUMENT_H
+#define RCC_MIR_MIRDOCUMENT_H
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/StableID.h"
+#include "rcc/MIR/EmbeddedIR.h"
+#include "rcc/MIR/MIParser.h"
+#include "rcc/MIR/MIPrinter.h"
+#include "rcc/Support/TextDiagnostic.h"
+#include "rcc/Support/YAML.h"
+
+#include <cstdint>
+#include <optional>
+#include <span>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace mir {
+
+struct MIRFunctionDocumentInfo final {
+ MachineFunctionStableID Function;
+ std::optional<bool> NoPHIs;
+ std::optional<bool> IsSSA;
+ std::optional<bool> NoVRegs;
+ std::optional<bool> HasFakeUses;
+
+ friend bool operator==(const MIRFunctionDocumentInfo &Left,
+ const MIRFunctionDocumentInfo &Right) = default;
+};
+
+class MIRDocument final {
+public:
+ MIRDocument();
+ explicit MIRDocument(CompilationUnit Unit);
+ MIRDocument(const MIRDocument &) = delete;
+ MIRDocument &operator=(const MIRDocument &) = delete;
+ MIRDocument(MIRDocument &&) noexcept;
+ MIRDocument &operator=(MIRDocument &&) noexcept;
+ ~MIRDocument();
+
+ [[nodiscard]] CompilationUnit &unit() noexcept {
+ return Unit;
+ }
+
+ [[nodiscard]] const CompilationUnit &unit() const noexcept {
+ return Unit;
+ }
+
+ [[nodiscard]] const EmbeddedIRReferenceIndex *embeddedIR() const noexcept {
+ return EmbeddedIR ? &*EmbeddedIR : nullptr;
+ }
+
+ [[nodiscard]] std::span<const MIRFunctionDocumentInfo> functionDocumentInfo() const noexcept {
+ return FunctionInfo;
+ }
+
+ [[nodiscard]] std::span<const MISyncScopeSlot> syncScopes() const noexcept {
+ return SyncScopes;
+ }
+
+private:
+ CompilationUnit Unit;
+ std::optional<EmbeddedIRReferenceIndex> EmbeddedIR;
+ std::vector<MIRFunctionDocumentInfo> FunctionInfo;
+ std::vector<MISyncScopeSlot> SyncScopes;
+
+ friend class MIRDocumentParser;
+};
+
+struct MIRDocumentParserOptions final {
+ yaml::ParseOptions YAML;
+ MIParserOptions MachineInstructions;
+ std::string DataLayout;
+ std::uint32_t DefaultPointerSizeInBits = 0;
+ bool VerifyMachineFunctions = true;
+};
+
+class MIRDocumentParseResult final {
+public:
+ MIRDocumentParseResult(const MIRDocumentParseResult &) = delete;
+ MIRDocumentParseResult &operator=(const MIRDocumentParseResult &) = delete;
+ MIRDocumentParseResult(MIRDocumentParseResult &&) noexcept;
+ MIRDocumentParseResult &operator=(MIRDocumentParseResult &&) noexcept;
+ ~MIRDocumentParseResult();
+
+ [[nodiscard]] bool succeeded() const noexcept {
+ return Document.has_value() && Diagnostics.empty();
+ }
+
+ [[nodiscard]] MIRDocument *document() noexcept {
+ return Document ? &*Document : nullptr;
+ }
+
+ [[nodiscard]] const MIRDocument *document() const noexcept {
+ return Document ? &*Document : nullptr;
+ }
+
+ [[nodiscard]] const TextSource &source() const noexcept {
+ return Source;
+ }
+
+ [[nodiscard]] std::span<const TextDiagnostic> diagnostics() const noexcept {
+ return Diagnostics;
+ }
+
+private:
+ MIRDocumentParseResult(std::string SourceName, std::string SourceText)
+ : Source(std::move(SourceName), std::move(SourceText)) {}
+
+ TextSource Source;
+ std::optional<MIRDocument> Document;
+ std::vector<TextDiagnostic> Diagnostics;
+
+ friend class MIRDocumentParser;
+};
+
+[[nodiscard]] MIRDocumentParseResult parseMIRDocument(const TargetDescription &Target,
+ std::string SourceName,
+ std::string SourceText,
+ MIRDocumentParserOptions Options = {});
+
+struct MIRDocumentPrinterOptions final {
+ MIPrintMode Mode = MIPrintMode::Complete;
+ bool EmitEmbeddedIR = true;
+ std::string DataLayout;
+ std::uint32_t DefaultPointerSizeInBits = 0;
+};
+
+[[nodiscard]] Expected<std::string> printMIRDocument(const MIRDocument &Document,
+ const TargetDescription &Target,
+ MIRDocumentPrinterOptions Options = {});
+
+} // namespace mir
+} // namespace rcc
+
+#endif // RCC_MIR_MIRDOCUMENT_H
diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt
index ce53cd2..34e9ef6 100644
--- a/backend/lib/CodeGen/CMakeLists.txt
+++ b/backend/lib/CodeGen/CMakeLists.txt
@@ -3,6 +3,7 @@
set(
RCCBackendCodeGenSources
CompilationUnit.cpp
+ GenericConstant.cpp
InstructionDescriptor.cpp
LowLevelType.cpp
MachineBasicBlockAttributes.cpp
@@ -38,6 +39,7 @@ set(
set(
RCCBackendCodeGenHeaders
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/CompilationUnit.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericConstant.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlockAttributes.h"
@@ -102,6 +104,7 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlockAttributes.cpp"
@@ -137,6 +140,7 @@ rcc_backend_register_format_files(
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlockAttributes.cpp"
diff --git a/backend/lib/CodeGen/GenericConstant.cpp b/backend/lib/CodeGen/GenericConstant.cpp
new file mode 100644
index 0000000..2ef24bf
--- /dev/null
+++ b/backend/lib/CodeGen/GenericConstant.cpp
@@ -0,0 +1,875 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/GenericConstant.h"
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <utility>
+
+namespace rcc {
+namespace {
+
+constexpr std::size_t MaximumVerificationDepth = 1024;
+constexpr std::size_t MaximumVerificationNodes = std::size_t{1} << 24U;
+
+[[nodiscard]] bool isAggregateType(GenericConstantTypeKind Kind) noexcept {
+ return Kind == GenericConstantTypeKind::Array || Kind == GenericConstantTypeKind::Vector ||
+ Kind == GenericConstantTypeKind::Struct;
+}
+
+[[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++ <= MaximumVerificationDepth) {
+ Resolved = &Resolved->Elements.front();
+ }
+ return *Resolved;
+}
+
+[[nodiscard]] bool sameResolvedType(const GenericConstantType &Left,
+ const GenericConstantType &Right) noexcept {
+ return resolvedType(Left) == resolvedType(Right);
+}
+
+[[nodiscard]] bool isIntegerScalarType(const GenericConstantType &Type) noexcept {
+ const GenericConstantTypeKind Kind = resolvedType(Type).Kind;
+ return Kind == GenericConstantTypeKind::Integer || Kind == GenericConstantTypeKind::Byte;
+}
+
+[[nodiscard]] std::optional<std::uint32_t>
+integerScalarBitWidth(const GenericConstantType &Type) noexcept {
+ const GenericConstantType &Resolved = resolvedType(Type);
+ if (Resolved.Kind == GenericConstantTypeKind::Integer)
+ return Resolved.BitWidth;
+ if (Resolved.Kind == GenericConstantTypeKind::Byte)
+ return 8;
+ return std::nullopt;
+}
+
+[[nodiscard]] bool isPointerScalarType(const GenericConstantType &Type) noexcept {
+ return resolvedType(Type).Kind == GenericConstantTypeKind::Pointer;
+}
+
+[[nodiscard]] bool isFloatingScalarType(const GenericConstantType &Type) noexcept {
+ return resolvedType(Type).Kind == GenericConstantTypeKind::Floating;
+}
+
+struct VectorShape final {
+ std::uint64_t ElementCount = 0;
+ bool Scalable = false;
+
+ friend constexpr bool operator==(VectorShape Left, VectorShape Right) noexcept = default;
+};
+
+[[nodiscard]] std::optional<VectorShape>
+vectorShape(const GenericConstantType &Type) noexcept {
+ const GenericConstantType &Resolved = resolvedType(Type);
+ if (Resolved.Kind != GenericConstantTypeKind::Vector)
+ return std::nullopt;
+ return VectorShape{.ElementCount = Resolved.ElementCount, .Scalable = Resolved.Scalable};
+}
+
+[[nodiscard]] const GenericConstantType *
+vectorElementType(const GenericConstantType &Type) noexcept {
+ const GenericConstantType &Resolved = resolvedType(Type);
+ if (Resolved.Kind != GenericConstantTypeKind::Vector || Resolved.Elements.size() != 1)
+ return nullptr;
+ return &Resolved.Elements.front();
+}
+
+[[nodiscard]] bool hasSameVectorShape(const GenericConstantType &Left,
+ const GenericConstantType &Right) noexcept {
+ const auto LeftShape = vectorShape(Left);
+ const auto RightShape = vectorShape(Right);
+ return LeftShape == RightShape;
+}
+
+[[nodiscard]] bool isIntegerOrIntegerVectorType(const GenericConstantType &Type) noexcept {
+ if (isIntegerScalarType(Type))
+ return true;
+ const GenericConstantType *Element = vectorElementType(Type);
+ return Element != nullptr && isIntegerScalarType(*Element);
+}
+
+[[nodiscard]] bool isPointerOrPointerVectorType(const GenericConstantType &Type) noexcept {
+ if (isPointerScalarType(Type))
+ return true;
+ const GenericConstantType *Element = vectorElementType(Type);
+ return Element != nullptr && isPointerScalarType(*Element);
+}
+
+[[nodiscard]] std::optional<std::uint32_t>
+pointerAddressSpace(const GenericConstantType &Type) noexcept {
+ const GenericConstantType &Resolved = resolvedType(Type);
+ if (Resolved.Kind == GenericConstantTypeKind::Pointer)
+ return Resolved.AddressSpace;
+ const GenericConstantType *Element = vectorElementType(Resolved);
+ if (Element == nullptr)
+ return std::nullopt;
+ const GenericConstantType &ResolvedElement = resolvedType(*Element);
+ if (ResolvedElement.Kind != GenericConstantTypeKind::Pointer)
+ return std::nullopt;
+ return ResolvedElement.AddressSpace;
+}
+
+[[nodiscard]] bool isNonPointerBitCastType(const GenericConstantType &Type) noexcept {
+ if (isIntegerScalarType(Type) || isFloatingScalarType(Type))
+ return true;
+ const GenericConstantType *Element = vectorElementType(Type);
+ return Element != nullptr &&
+ (isIntegerScalarType(*Element) || isFloatingScalarType(*Element));
+}
+
+[[nodiscard]] std::optional<std::uint64_t>
+nonPointerFixedBitWidth(const GenericConstantType &Type) noexcept {
+ const GenericConstantType &Resolved = resolvedType(Type);
+ if (const auto Width = integerScalarBitWidth(Resolved))
+ return *Width;
+ if (Resolved.Kind == GenericConstantTypeKind::Floating)
+ return getFloatSemanticsBitWidth(Resolved.Semantics);
+ const GenericConstantType *Element = vectorElementType(Resolved);
+ if (Element == nullptr || Resolved.Scalable)
+ return std::nullopt;
+ const auto ElementWidth = nonPointerFixedBitWidth(*Element);
+ if (!ElementWidth || *ElementWidth == 0 ||
+ Resolved.ElementCount > std::numeric_limits<std::uint64_t>::max() / *ElementWidth) {
+ return std::nullopt;
+ }
+ return Resolved.ElementCount * *ElementWidth;
+}
+
+[[nodiscard]] bool hasIntegerCastShape(const GenericConstantType &Source,
+ const GenericConstantType &Destination,
+ bool RequireNarrowing) noexcept {
+ const GenericConstantType &ResolvedSource = resolvedType(Source);
+ const GenericConstantType &ResolvedDestination = resolvedType(Destination);
+ if (const auto SourceWidth = integerScalarBitWidth(ResolvedSource)) {
+ const auto DestinationWidth = integerScalarBitWidth(ResolvedDestination);
+ return DestinationWidth && (RequireNarrowing ? *SourceWidth > *DestinationWidth
+ : *SourceWidth == *DestinationWidth);
+ }
+ const GenericConstantType *SourceElement = vectorElementType(ResolvedSource);
+ const GenericConstantType *DestinationElement = vectorElementType(ResolvedDestination);
+ if (SourceElement == nullptr || DestinationElement == nullptr ||
+ !hasSameVectorShape(ResolvedSource, ResolvedDestination)) {
+ return false;
+ }
+ return hasIntegerCastShape(*SourceElement, *DestinationElement, RequireNarrowing);
+}
+
+[[nodiscard]] bool hasPointerIntegerCastShape(const GenericConstantType &Pointer,
+ const GenericConstantType &Integer) noexcept {
+ const GenericConstantType &ResolvedPointer = resolvedType(Pointer);
+ const GenericConstantType &ResolvedInteger = resolvedType(Integer);
+ if (isPointerScalarType(ResolvedPointer))
+ return isIntegerScalarType(ResolvedInteger);
+ const GenericConstantType *PointerElement = vectorElementType(ResolvedPointer);
+ const GenericConstantType *IntegerElement = vectorElementType(ResolvedInteger);
+ return PointerElement != nullptr && IntegerElement != nullptr &&
+ hasSameVectorShape(ResolvedPointer, ResolvedInteger) &&
+ isPointerScalarType(*PointerElement) && isIntegerScalarType(*IntegerElement);
+}
+
+[[nodiscard]] bool isSizedType(const GenericConstantType &Type,
+ std::size_t Depth = 0) noexcept {
+ if (Depth > MaximumVerificationDepth)
+ return false;
+ const GenericConstantType &Resolved = resolvedType(Type);
+ switch (Resolved.Kind) {
+ case GenericConstantTypeKind::Integer:
+ case GenericConstantTypeKind::Byte:
+ case GenericConstantTypeKind::Floating:
+ case GenericConstantTypeKind::Pointer:
+ return true;
+ case GenericConstantTypeKind::Array:
+ case GenericConstantTypeKind::Vector:
+ return Resolved.Elements.size() == 1 && isSizedType(Resolved.Elements.front(), Depth + 1);
+ case GenericConstantTypeKind::Struct:
+ return std::ranges::all_of(Resolved.Elements, [Depth](const GenericConstantType &Element) {
+ return isSizedType(Element, Depth + 1);
+ });
+ case GenericConstantTypeKind::Named:
+ return Resolved.Elements.size() == 1 && isSizedType(Resolved.Elements.front(), Depth + 1);
+ case GenericConstantTypeKind::Invalid:
+ case GenericConstantTypeKind::Void:
+ case GenericConstantTypeKind::Label:
+ case GenericConstantTypeKind::Token:
+ case GenericConstantTypeKind::Metadata:
+ case GenericConstantTypeKind::Function:
+ return false;
+ }
+ return false;
+}
+
+[[nodiscard]] bool signedIntegerLess(const IntegerImmediate &Left,
+ const IntegerImmediate &Right) noexcept {
+ if (Left.bitWidth() != Right.bitWidth())
+ return false;
+ if (Left.isNegative() != Right.isNegative())
+ return Left.isNegative();
+ const auto LeftWords = Left.words();
+ const auto RightWords = Right.words();
+ for (std::size_t Index = LeftWords.size(); Index != 0; --Index) {
+ if (LeftWords[Index - 1U] != RightWords[Index - 1U])
+ return LeftWords[Index - 1U] < RightWords[Index - 1U];
+ }
+ return false;
+}
+
+[[nodiscard]] bool hasOnlyValuePayload(const GenericConstantData &Value) noexcept {
+ return Value.Symbol.empty() && Value.Block.empty() &&
+ Value.ExpressionOpcode == GenericConstantExpressionOpcode::Invalid &&
+ Value.ExpressionFlags.rawValue() == 0 && !Value.SourceElementType &&
+ !Value.InRangeStart && !Value.InRangeEnd;
+}
+
+[[nodiscard]] Expected<void> verifyType(const GenericConstantType &Type, std::size_t Depth,
+ std::size_t &Nodes) {
+ if (Depth > MaximumVerificationDepth || ++Nodes > MaximumVerificationNodes) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant type exceeds structural limits");
+ }
+
+ const auto RequireNoElements = [&]() -> Expected<void> {
+ if (!Type.Elements.empty()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "scalar generic constant type cannot contain child types");
+ }
+ return {};
+ };
+
+ switch (Type.Kind) {
+ case GenericConstantTypeKind::Invalid:
+ return makeUnexpected(ErrorCode::VerificationError, "generic constant type is invalid");
+ case GenericConstantTypeKind::Void:
+ case GenericConstantTypeKind::Label:
+ case GenericConstantTypeKind::Token:
+ case GenericConstantTypeKind::Metadata:
+ return RequireNoElements();
+ case GenericConstantTypeKind::Integer:
+ if (Type.BitWidth == 0 || Type.BitWidth > IntegerImmediate::MaxBitWidth) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic integer type has an invalid bit width");
+ }
+ return RequireNoElements();
+ case GenericConstantTypeKind::Byte:
+ return RequireNoElements();
+ case GenericConstantTypeKind::Floating:
+ if (!isKnownFloatSemantics(Type.Semantics)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic floating type has unknown semantics");
+ }
+ return RequireNoElements();
+ case GenericConstantTypeKind::Pointer:
+ return RequireNoElements();
+ case GenericConstantTypeKind::Array:
+ case GenericConstantTypeKind::Vector:
+ if (Type.Elements.size() != 1 ||
+ (Type.Kind == GenericConstantTypeKind::Vector && Type.ElementCount == 0)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic sequential type has an invalid element description");
+ }
+ return verifyType(Type.Elements.front(), Depth + 1, Nodes);
+ case GenericConstantTypeKind::Struct:
+ for (const GenericConstantType &Element : Type.Elements) {
+ auto Result = verifyType(Element, Depth + 1, Nodes);
+ if (!Result)
+ return Result;
+ }
+ return {};
+ case GenericConstantTypeKind::Function:
+ if (Type.Elements.empty()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic function type has no return type");
+ }
+ for (const GenericConstantType &Element : Type.Elements) {
+ auto Result = verifyType(Element, Depth + 1, Nodes);
+ if (!Result)
+ return Result;
+ }
+ return {};
+ case GenericConstantTypeKind::Named:
+ if (Type.Name.empty() || Type.Elements.size() > 1) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic named type has an invalid identity");
+ }
+ if (!Type.Elements.empty())
+ return verifyType(Type.Elements.front(), Depth + 1, Nodes);
+ return {};
+ }
+ return makeUnexpected(ErrorCode::InternalError, "generic constant type kind is unhandled");
+}
+
+[[nodiscard]] Expected<void> verifyConstant(const GenericConstantData &Value, std::size_t Depth,
+ std::size_t &Nodes) {
+ if (Depth > MaximumVerificationDepth || ++Nodes > MaximumVerificationNodes) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant value exceeds structural limits");
+ }
+ std::size_t TypeNodes = 0;
+ auto TypeResult = verifyType(Value.Type, 0, TypeNodes);
+ if (!TypeResult)
+ return TypeResult;
+ const GenericConstantType &EffectiveType = resolvedType(Value.Type);
+
+ const auto RequireEmptyScalarPayload = [&]() -> Expected<void> {
+ if (Value.IntegerValue || Value.FloatingValue || !Value.ByteValue.empty() ||
+ !Value.Elements.empty() || !hasOnlyValuePayload(Value)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant kind contains an unrelated payload");
+ }
+ return {};
+ };
+
+ switch (Value.Kind) {
+ case GenericConstantKind::Invalid:
+ return makeUnexpected(ErrorCode::VerificationError, "generic constant value is invalid");
+ case GenericConstantKind::Integer:
+ if (!Value.IntegerValue || Value.FloatingValue || !Value.ByteValue.empty() ||
+ !Value.Elements.empty() || !hasOnlyValuePayload(Value) ||
+ (EffectiveType.Kind != GenericConstantTypeKind::Integer &&
+ EffectiveType.Kind != GenericConstantTypeKind::Byte) ||
+ Value.IntegerValue->bitWidth() !=
+ (EffectiveType.Kind == GenericConstantTypeKind::Byte ? 8U
+ : EffectiveType.BitWidth)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic integer constant has an inconsistent payload");
+ }
+ return Value.IntegerValue->verify();
+ case GenericConstantKind::Floating:
+ if (!Value.FloatingValue || Value.IntegerValue || !Value.ByteValue.empty() ||
+ !Value.Elements.empty() || !hasOnlyValuePayload(Value) ||
+ EffectiveType.Kind != GenericConstantTypeKind::Floating ||
+ Value.FloatingValue->semantics() != EffectiveType.Semantics) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic floating constant has an inconsistent payload");
+ }
+ return {};
+ case GenericConstantKind::NullPointer:
+ if (EffectiveType.Kind != GenericConstantTypeKind::Pointer) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic null constant must have pointer type");
+ }
+ return RequireEmptyScalarPayload();
+ case GenericConstantKind::ZeroInitializer:
+ case GenericConstantKind::Undef:
+ case GenericConstantKind::Poison:
+ if (EffectiveType.Kind == GenericConstantTypeKind::Invalid ||
+ EffectiveType.Kind == GenericConstantTypeKind::Function) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic marker constant has an invalid type");
+ }
+ return RequireEmptyScalarPayload();
+ case GenericConstantKind::Aggregate: {
+ if (!isAggregateType(EffectiveType.Kind) || Value.IntegerValue || Value.FloatingValue ||
+ !Value.ByteValue.empty() || !hasOnlyValuePayload(Value)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic aggregate constant has an inconsistent payload");
+ }
+ if ((EffectiveType.Kind == GenericConstantTypeKind::Array ||
+ EffectiveType.Kind == GenericConstantTypeKind::Vector) &&
+ Value.Elements.size() != EffectiveType.ElementCount) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic aggregate constant has the wrong element count");
+ }
+ if (EffectiveType.Kind == GenericConstantTypeKind::Struct &&
+ Value.Elements.size() != EffectiveType.Elements.size()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic struct constant has the wrong element count");
+ }
+ for (std::size_t Index = 0; Index < Value.Elements.size(); ++Index) {
+ const GenericConstantType &ExpectedType =
+ EffectiveType.Kind == GenericConstantTypeKind::Struct
+ ? EffectiveType.Elements[Index]
+ : EffectiveType.Elements.front();
+ if (Value.Elements[Index].Type != ExpectedType) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic aggregate element type does not match its container");
+ }
+ auto Result = verifyConstant(Value.Elements[Index], Depth + 1, Nodes);
+ if (!Result)
+ return Result;
+ }
+ return {};
+ }
+ case GenericConstantKind::ByteString:
+ if (EffectiveType.Kind != GenericConstantTypeKind::Array ||
+ EffectiveType.Elements.size() != 1 ||
+ (EffectiveType.Elements.front().Kind != GenericConstantTypeKind::Integer &&
+ EffectiveType.Elements.front().Kind != GenericConstantTypeKind::Byte) ||
+ (EffectiveType.Elements.front().Kind == GenericConstantTypeKind::Integer &&
+ EffectiveType.Elements.front().BitWidth != 8) ||
+ EffectiveType.ElementCount != Value.ByteValue.size() || Value.IntegerValue ||
+ Value.FloatingValue || !Value.Elements.empty() || !hasOnlyValuePayload(Value)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic byte-string constant has an inconsistent type or payload");
+ }
+ return {};
+ case GenericConstantKind::GlobalAddress:
+ case GenericConstantKind::DSOLocalEquivalent:
+ case GenericConstantKind::NoCFI:
+ if (EffectiveType.Kind != GenericConstantTypeKind::Pointer || Value.Symbol.empty() ||
+ Value.IntegerValue || Value.FloatingValue || !Value.ByteValue.empty() ||
+ !Value.Elements.empty() || !Value.Block.empty() ||
+ Value.ExpressionOpcode != GenericConstantExpressionOpcode::Invalid ||
+ Value.ExpressionFlags.rawValue() != 0 || Value.SourceElementType ||
+ Value.InRangeStart || Value.InRangeEnd) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic global-address constant has an inconsistent payload");
+ }
+ return {};
+ case GenericConstantKind::BlockAddress:
+ if (EffectiveType.Kind != GenericConstantTypeKind::Pointer || Value.Symbol.empty() ||
+ Value.Block.empty() || Value.IntegerValue || Value.FloatingValue ||
+ !Value.ByteValue.empty() || !Value.Elements.empty() ||
+ Value.ExpressionOpcode != GenericConstantExpressionOpcode::Invalid ||
+ Value.ExpressionFlags.rawValue() != 0 || Value.SourceElementType ||
+ Value.InRangeStart || Value.InRangeEnd) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic block-address constant has an inconsistent payload");
+ }
+ return {};
+ case GenericConstantKind::PointerAuth:
+ if (EffectiveType.Kind != GenericConstantTypeKind::Pointer || Value.Elements.size() < 2 ||
+ Value.Elements.size() > 5 || Value.IntegerValue || Value.FloatingValue ||
+ !Value.ByteValue.empty() || !hasOnlyValuePayload(Value)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic pointer-auth constant has an inconsistent payload");
+ }
+ for (const GenericConstantData &Element : Value.Elements) {
+ auto Result = verifyConstant(Element, Depth + 1, Nodes);
+ if (!Result)
+ return Result;
+ }
+ if (!isPointerScalarType(Value.Elements[0].Type) ||
+ !sameResolvedType(Value.Type, Value.Elements[0].Type) ||
+ !integerScalarBitWidth(Value.Elements[1].Type) ||
+ *integerScalarBitWidth(Value.Elements[1].Type) != 32) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic pointer-auth constant has invalid pointer or key operands");
+ }
+ if (Value.Elements.size() >= 3 &&
+ (!integerScalarBitWidth(Value.Elements[2].Type) ||
+ *integerScalarBitWidth(Value.Elements[2].Type) != 64)) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "generic pointer-auth constant integer discriminator must be a scalar i64");
+ }
+ for (std::size_t Index = 3; Index < Value.Elements.size(); ++Index) {
+ if (!isPointerScalarType(Value.Elements[Index].Type)) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "generic pointer-auth constant address operands must be scalar pointers");
+ }
+ }
+ return {};
+ case GenericConstantKind::Expression:
+ if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::Invalid ||
+ Value.Elements.empty() || Value.IntegerValue || Value.FloatingValue ||
+ !Value.ByteValue.empty() || !Value.Symbol.empty() || !Value.Block.empty()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant expression has an inconsistent payload");
+ }
+ if ((Value.InRangeStart.has_value() != Value.InRangeEnd.has_value()) ||
+ ((Value.InRangeStart || Value.InRangeEnd || Value.SourceElementType) &&
+ Value.ExpressionOpcode != GenericConstantExpressionOpcode::GetElementPtr)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant expression has misplaced GEP state");
+ }
+ if (Value.ExpressionOpcode == GenericConstantExpressionOpcode::GetElementPtr &&
+ !Value.SourceElementType) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr constant has no source element type");
+ }
+ for (const GenericConstantData &Element : Value.Elements) {
+ auto Result = verifyConstant(Element, Depth + 1, Nodes);
+ if (!Result)
+ return Result;
+ }
+
+ const bool IsBinary = Value.ExpressionOpcode == GenericConstantExpressionOpcode::Add ||
+ Value.ExpressionOpcode == GenericConstantExpressionOpcode::Sub ||
+ Value.ExpressionOpcode == GenericConstantExpressionOpcode::Xor;
+ const bool IsGEP = Value.ExpressionOpcode ==
+ GenericConstantExpressionOpcode::GetElementPtr;
+ const std::uint8_t Flags = Value.ExpressionFlags.rawValue();
+ const std::uint8_t BinaryFlags =
+ (std::uint8_t{1} << static_cast<std::uint8_t>(GenericConstantExpressionFlag::NoUnsignedWrap)) |
+ (std::uint8_t{1} << static_cast<std::uint8_t>(GenericConstantExpressionFlag::NoSignedWrap));
+ const std::uint8_t GEPFlags =
+ (std::uint8_t{1} << static_cast<std::uint8_t>(GenericConstantExpressionFlag::InBounds)) |
+ (std::uint8_t{1} <<
+ static_cast<std::uint8_t>(GenericConstantExpressionFlag::NoUnsignedSignedWrap)) |
+ (std::uint8_t{1} <<
+ static_cast<std::uint8_t>(GenericConstantExpressionFlag::GEPNoUnsignedWrap));
+ if ((!IsBinary && !IsGEP && Flags != 0) || (IsBinary && (Flags & ~BinaryFlags) != 0) ||
+ (IsGEP && (Flags & ~GEPFlags) != 0)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant expression has invalid opcode flags");
+ }
+
+ const auto RequireExactOperandCount = [&Value](std::size_t Count) -> Expected<void> {
+ if (Value.Elements.size() != Count) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant expression has the wrong operand count");
+ }
+ return {};
+ };
+ const GenericConstantType &FirstType = Value.Elements.front().Type;
+ switch (Value.ExpressionOpcode) {
+ case GenericConstantExpressionOpcode::Trunc:
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ if (!hasIntegerCastShape(FirstType, Value.Type, true)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic trunc constant has incompatible integer types");
+ }
+ return {};
+ case GenericConstantExpressionOpcode::BitCast: {
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ const bool SourceIsPointer = isPointerOrPointerVectorType(FirstType);
+ const bool DestinationIsPointer = isPointerOrPointerVectorType(Value.Type);
+ if (SourceIsPointer || DestinationIsPointer) {
+ const auto SourceAddressSpace = pointerAddressSpace(FirstType);
+ const auto DestinationAddressSpace = pointerAddressSpace(Value.Type);
+ if (!SourceIsPointer || !DestinationIsPointer || !SourceAddressSpace ||
+ !DestinationAddressSpace || *SourceAddressSpace != *DestinationAddressSpace ||
+ !hasSameVectorShape(FirstType, Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic bitcast constant has incompatible pointer types");
+ }
+ return {};
+ }
+ const auto SourceWidth = nonPointerFixedBitWidth(FirstType);
+ const auto DestinationWidth = nonPointerFixedBitWidth(Value.Type);
+ if (!isNonPointerBitCastType(FirstType) || !isNonPointerBitCastType(Value.Type) ||
+ !SourceWidth || !DestinationWidth || *SourceWidth != *DestinationWidth ||
+ !hasSameVectorShape(FirstType, Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic bitcast constant has incompatible fixed-width types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::AddressSpaceCast: {
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ const auto SourceAddressSpace = pointerAddressSpace(FirstType);
+ const auto DestinationAddressSpace = pointerAddressSpace(Value.Type);
+ if (!SourceAddressSpace || !DestinationAddressSpace ||
+ *SourceAddressSpace == *DestinationAddressSpace ||
+ !hasSameVectorShape(FirstType, Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic addrspacecast constant has incompatible pointer types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::IntToPtr:
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ if (!hasPointerIntegerCastShape(Value.Type, FirstType)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic inttoptr constant has incompatible operand types");
+ }
+ return {};
+ case GenericConstantExpressionOpcode::PtrToAddr:
+ case GenericConstantExpressionOpcode::PtrToInt:
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ if (!hasPointerIntegerCastShape(FirstType, Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic pointer-to-integer constant has incompatible operand types");
+ }
+ return {};
+ case GenericConstantExpressionOpcode::Add:
+ case GenericConstantExpressionOpcode::Sub:
+ case GenericConstantExpressionOpcode::Xor:
+ if (auto Count = RequireExactOperandCount(2); !Count)
+ return Count;
+ if (!isIntegerOrIntegerVectorType(Value.Type) ||
+ !sameResolvedType(Value.Elements[0].Type, Value.Type) ||
+ !sameResolvedType(Value.Elements[1].Type, Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic binary constant requires equal integer operand and result types");
+ }
+ return {};
+ case GenericConstantExpressionOpcode::GetElementPtr: {
+ if (!Value.SourceElementType || !isSizedType(*Value.SourceElementType) ||
+ !isPointerOrPointerVectorType(FirstType) ||
+ !isPointerOrPointerVectorType(Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr constant has invalid source or pointer types");
+ }
+ const auto BaseAddressSpace = pointerAddressSpace(FirstType);
+ const auto ResultAddressSpace = pointerAddressSpace(Value.Type);
+ if (!BaseAddressSpace || !ResultAddressSpace || *BaseAddressSpace != *ResultAddressSpace) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr constant changes pointer address space");
+ }
+ std::optional<VectorShape> RequiredResultShape = vectorShape(FirstType);
+ for (std::size_t Index = 1; Index < Value.Elements.size(); ++Index) {
+ const GenericConstantType &IndexType = Value.Elements[Index].Type;
+ if (!isIntegerOrIntegerVectorType(IndexType)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr index must be an integer or integer vector");
+ }
+ if (const auto IndexShape = vectorShape(IndexType)) {
+ if (RequiredResultShape && *RequiredResultShape != *IndexShape) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "generic getelementptr vector index has an incompatible element count");
+ }
+ RequiredResultShape = *IndexShape;
+ }
+ }
+ if (RequiredResultShape != vectorShape(Value.Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr result has an incompatible vector shape");
+ }
+ if (Value.InRangeStart &&
+ (Value.InRangeStart->bitWidth() != Value.InRangeEnd->bitWidth() ||
+ !signedIntegerLess(*Value.InRangeStart, *Value.InRangeEnd))) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic getelementptr inrange interval is invalid");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::ShuffleVector: {
+ if (auto Count = RequireExactOperandCount(3); !Count)
+ return Count;
+ const GenericConstantType *InputElement = vectorElementType(FirstType);
+ const GenericConstantType *MaskElement = vectorElementType(Value.Elements[2].Type);
+ const GenericConstantType *ResultElement = vectorElementType(Value.Type);
+ const auto InputShape = vectorShape(FirstType);
+ const auto MaskShape = vectorShape(Value.Elements[2].Type);
+ const auto ResultShape = vectorShape(Value.Type);
+ if (InputElement == nullptr || MaskElement == nullptr || ResultElement == nullptr ||
+ !InputShape || !MaskShape || !ResultShape ||
+ !sameResolvedType(FirstType, Value.Elements[1].Type) ||
+ !integerScalarBitWidth(*MaskElement) || *integerScalarBitWidth(*MaskElement) != 32 ||
+ InputShape->Scalable != MaskShape->Scalable ||
+ !sameResolvedType(*InputElement, *ResultElement) ||
+ ResultShape->ElementCount != MaskShape->ElementCount ||
+ ResultShape->Scalable != MaskShape->Scalable) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic shufflevector constant has incompatible operand types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::InsertElement: {
+ if (auto Count = RequireExactOperandCount(3); !Count)
+ return Count;
+ const GenericConstantType *Element = vectorElementType(FirstType);
+ if (Element == nullptr || !sameResolvedType(Value.Type, FirstType) ||
+ !sameResolvedType(Value.Elements[1].Type, *Element) ||
+ !isIntegerScalarType(Value.Elements[2].Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic insertelement constant has incompatible operand types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::ExtractElement: {
+ if (auto Count = RequireExactOperandCount(2); !Count)
+ return Count;
+ const GenericConstantType *Element = vectorElementType(FirstType);
+ if (Element == nullptr || !sameResolvedType(Value.Type, *Element) ||
+ !isIntegerScalarType(Value.Elements[1].Type)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic extractelement constant has incompatible operand types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::Splat: {
+ if (auto Count = RequireExactOperandCount(1); !Count)
+ return Count;
+ const GenericConstantType *Element = vectorElementType(Value.Type);
+ if (Element == nullptr || !sameResolvedType(FirstType, *Element)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic splat constant has incompatible operand types");
+ }
+ return {};
+ }
+ case GenericConstantExpressionOpcode::Invalid:
+ break;
+ }
+ return makeUnexpected(ErrorCode::InternalError,
+ "generic constant expression opcode is unhandled");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "generic constant value kind is unhandled");
+}
+
+} // namespace
+
+Expected<GenericConstantType> GenericConstantType::integer(std::uint32_t Width) {
+ if (Width == 0 || Width > IntegerImmediate::MaxBitWidth)
+ return makeUnexpected(ErrorCode::InvalidArgument, "generic integer type width is invalid");
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Integer;
+ Type.BitWidth = Width;
+ return Type;
+}
+
+GenericConstantType GenericConstantType::primitive(GenericConstantTypeKind PrimitiveKind) noexcept {
+ GenericConstantType Type;
+ Type.Kind = PrimitiveKind;
+ return Type;
+}
+
+GenericConstantType GenericConstantType::byte() noexcept {
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Byte;
+ Type.BitWidth = 8;
+ return Type;
+}
+
+Expected<GenericConstantType> GenericConstantType::floating(FloatSemantics ValueSemantics) {
+ if (!isKnownFloatSemantics(ValueSemantics))
+ return makeUnexpected(ErrorCode::InvalidArgument, "generic floating semantics is unknown");
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Floating;
+ Type.Semantics = ValueSemantics;
+ Type.BitWidth = getFloatSemanticsBitWidth(ValueSemantics).value_or(0);
+ return Type;
+}
+
+GenericConstantType GenericConstantType::pointer(std::uint32_t PointerAddressSpace) noexcept {
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Pointer;
+ Type.AddressSpace = PointerAddressSpace;
+ return Type;
+}
+
+Expected<GenericConstantType> GenericConstantType::array(std::uint64_t Count,
+ GenericConstantType Element) {
+ auto Verification = Element.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Array;
+ Type.ElementCount = Count;
+ Type.Elements.push_back(std::move(Element));
+ return Type;
+}
+
+Expected<GenericConstantType> GenericConstantType::vector(std::uint64_t Count,
+ GenericConstantType Element,
+ bool IsScalable) {
+ if (Count == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "generic vector type cannot be empty");
+ auto Verification = Element.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Vector;
+ Type.ElementCount = Count;
+ Type.Scalable = IsScalable;
+ Type.Elements.push_back(std::move(Element));
+ return Type;
+}
+
+Expected<GenericConstantType>
+GenericConstantType::structure(std::vector<GenericConstantType> MemberTypes, bool IsPacked) {
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Struct;
+ Type.Packed = IsPacked;
+ Type.Elements = std::move(MemberTypes);
+ auto Verification = Type.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Type;
+}
+
+Expected<GenericConstantType>
+GenericConstantType::function(GenericConstantType Return,
+ std::vector<GenericConstantType> Parameters, bool IsVarArg) {
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Function;
+ Type.VarArg = IsVarArg;
+ Type.Elements.reserve(Parameters.size() + 1);
+ Type.Elements.push_back(std::move(Return));
+ for (GenericConstantType &Parameter : Parameters)
+ Type.Elements.push_back(std::move(Parameter));
+ auto Verification = Type.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Type;
+}
+
+Expected<GenericConstantType> GenericConstantType::named(std::string TypeName,
+ bool IsNumericName) {
+ if (TypeName.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "generic named type cannot be empty");
+ GenericConstantType Type;
+ Type.Kind = GenericConstantTypeKind::Named;
+ Type.Name = std::move(TypeName);
+ Type.NumericName = IsNumericName;
+ return Type;
+}
+
+Expected<void> GenericConstantType::verify() const {
+ std::size_t Nodes = 0;
+ return verifyType(*this, 0, Nodes);
+}
+
+Expected<GenericConstantData> GenericConstantData::integer(IntegerImmediate Value) {
+ auto Type = GenericConstantType::integer(Value.bitWidth());
+ if (!Type)
+ return std::unexpected(Type.error());
+ GenericConstantData Result;
+ Result.Type = std::move(*Type);
+ Result.Kind = GenericConstantKind::Integer;
+ Result.IntegerValue = std::move(Value);
+ return Result;
+}
+
+Expected<GenericConstantData> GenericConstantData::floating(FloatingPointBits Value) {
+ auto Type = GenericConstantType::floating(Value.semantics());
+ if (!Type)
+ return std::unexpected(Type.error());
+ GenericConstantData Result;
+ Result.Type = std::move(*Type);
+ Result.Kind = GenericConstantKind::Floating;
+ Result.FloatingValue = Value;
+ return Result;
+}
+
+Expected<GenericConstantData> GenericConstantData::byteString(GenericConstantType ValueType,
+ std::vector<std::byte> Bytes) {
+ GenericConstantData Result;
+ Result.Type = std::move(ValueType);
+ Result.Kind = GenericConstantKind::ByteString;
+ Result.ByteValue = std::move(Bytes);
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+Expected<void> GenericConstantData::verify() const {
+ std::size_t Nodes = 0;
+ return verifyConstant(*this, 0, Nodes);
+}
+
+bool GenericConstantData::referencesSymbol() const noexcept {
+ switch (Kind) {
+ case GenericConstantKind::GlobalAddress:
+ case GenericConstantKind::BlockAddress:
+ case GenericConstantKind::DSOLocalEquivalent:
+ case GenericConstantKind::NoCFI:
+ return true;
+ default:
+ break;
+ }
+ for (const GenericConstantData &Element : Elements) {
+ if (Element.referencesSymbol())
+ return true;
+ }
+ return false;
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineConstantPool.cpp b/backend/lib/CodeGen/MachineConstantPool.cpp
index 43f2620..bd46f2d 100644
--- a/backend/lib/CodeGen/MachineConstantPool.cpp
+++ b/backend/lib/CodeGen/MachineConstantPool.cpp
@@ -53,6 +53,34 @@ getTargetConstant(const MachineConstantPoolValue &Value) noexcept {
return {};
}
+[[nodiscard]] std::optional<std::uint64_t>
+fixedGenericStoreSize(const GenericConstantType &Type) noexcept {
+ switch (Type.Kind) {
+ case GenericConstantTypeKind::Integer:
+ return (static_cast<std::uint64_t>(Type.BitWidth) + 7U) / 8U;
+ case GenericConstantTypeKind::Byte:
+ return 1;
+ case GenericConstantTypeKind::Floating:
+ return (static_cast<std::uint64_t>(getFloatSemanticsBitWidth(Type.Semantics).value_or(0)) +
+ 7U) /
+ 8U;
+ case GenericConstantTypeKind::Array:
+ case GenericConstantTypeKind::Vector: {
+ if (Type.Scalable || Type.Elements.size() != 1)
+ return std::nullopt;
+ const auto ElementSize = fixedGenericStoreSize(Type.Elements.front());
+ if (!ElementSize ||
+ (*ElementSize != 0 &&
+ Type.ElementCount > std::numeric_limits<std::uint64_t>::max() / *ElementSize)) {
+ return std::nullopt;
+ }
+ return *ElementSize * Type.ElementCount;
+ }
+ default:
+ return std::nullopt;
+ }
+}
+
} // namespace
ConstantPoolEntryKind MachineConstantPoolEntryDesc::kind() const noexcept {
@@ -95,13 +123,21 @@ Expected<void> MachineConstantPoolEntryDesc::verify() const {
switch (kind()) {
case ConstantPoolEntryKind::Generic: {
const auto *Generic = std::get_if<GenericConstantData>(&Value);
- if (Generic == nullptr || Generic->TypeIdentity.empty() || Generic->Bytes.size() != Size) {
+ if (Generic == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "generic constant pool entry has an invalid payload");
+ }
+ auto Verification = Generic->verify();
+ if (!Verification)
+ return Verification;
+ if (const auto FixedSize = fixedGenericStoreSize(Generic->Type);
+ FixedSize && *FixedSize != Size) {
return makeUnexpected(ErrorCode::VerificationError,
- "generic constant pool entry has invalid type or byte size");
+ "generic constant store size does not match its pool entry size");
}
- if (Relocation != ConstantPoolRelocationMode::None) {
+ if (!Generic->referencesSymbol() && Relocation != ConstantPoolRelocationMode::None) {
return makeUnexpected(ErrorCode::VerificationError,
- "raw generic constant pool bytes cannot carry relocations");
+ "symbol-free generic constant cannot require relocation");
}
return {};
}
diff --git a/backend/lib/CodeGen/MachineFrameInfo.cpp b/backend/lib/CodeGen/MachineFrameInfo.cpp
index 23f9852..21a4b78 100644
--- a/backend/lib/CodeGen/MachineFrameInfo.cpp
+++ b/backend/lib/CodeGen/MachineFrameInfo.cpp
@@ -5,6 +5,7 @@
#include <cstddef>
#include <cstdint>
#include <limits>
+#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
@@ -50,17 +51,53 @@ std::string_view getStackIDName(StackID ID) noexcept {
return "unknown";
}
+std::optional<MachineFramePointerPolicy>
+parseMachineFramePointerPolicy(std::string_view Name) noexcept {
+ if (Name == "none")
+ return MachineFramePointerPolicy::None;
+ if (Name == "non-leaf")
+ return MachineFramePointerPolicy::NonLeaf;
+ if (Name == "all")
+ return MachineFramePointerPolicy::All;
+ if (Name == "reserved")
+ return MachineFramePointerPolicy::Reserved;
+ if (Name == "non-leaf-no-reserve")
+ return MachineFramePointerPolicy::NonLeafNoReserve;
+ return std::nullopt;
+}
+
+std::string_view
+getMachineFramePointerPolicyName(MachineFramePointerPolicy Policy) noexcept {
+ switch (Policy) {
+ case MachineFramePointerPolicy::None:
+ return "none";
+ case MachineFramePointerPolicy::NonLeaf:
+ return "non-leaf";
+ case MachineFramePointerPolicy::All:
+ return "all";
+ case MachineFramePointerPolicy::Reserved:
+ return "reserved";
+ case MachineFramePointerPolicy::NonLeafNoReserve:
+ return "non-leaf-no-reserve";
+ case MachineFramePointerPolicy::Count:
+ return "unknown";
+ }
+ return "unknown";
+}
+
bool StackObject::isSpill() const noexcept {
const StackObjectDesc *Desc = description();
if (Desc == nullptr)
return false;
return Desc->Kind == StackObjectKind::SpillSlot || Desc->Kind == StackObjectKind::CalleeSaved ||
- Desc->Kind == StackObjectKind::EmergencySpill;
+ Desc->Kind == StackObjectKind::EmergencySpill ||
+ Desc->Kind == StackObjectKind::FixedSpillSlot;
}
bool StackObject::isFixed() const noexcept {
const StackObjectDesc *Desc = description();
- return Desc != nullptr && Desc->Kind == StackObjectKind::FixedABI;
+ return Desc != nullptr && (Desc->Kind == StackObjectKind::FixedABI ||
+ Desc->Kind == StackObjectKind::FixedSpillSlot);
}
bool StackObject::isVariableSized() const noexcept {
@@ -82,15 +119,35 @@ Expected<void> MachineFrameInfo::verifyDescription(const StackObjectDesc &Descri
return makeUnexpected(ErrorCode::InvalidArgument,
"variable-sized stack objects cannot have a fixed size or offset");
}
- } else if (!Description.Size || *Description.Size == 0) {
+ } else if (!Description.Size) {
return makeUnexpected(ErrorCode::InvalidArgument,
- "fixed-size stack objects require a nonzero size");
+ "fixed-size stack objects require an explicit size");
}
- if (Description.Kind == StackObjectKind::FixedABI && !Description.Offset) {
+ if ((Description.Kind == StackObjectKind::FixedABI ||
+ Description.Kind == StackObjectKind::FixedSpillSlot) &&
+ !Description.Offset) {
return makeUnexpected(ErrorCode::InvalidArgument,
"fixed ABI stack objects require a fixed offset");
}
+ if (Description.AllocaName && Description.AllocaName->empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "named stack objects require a nonempty alloca name");
+ }
+ if (Description.IsImmutable && Description.Kind != StackObjectKind::FixedABI) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "only fixed ABI stack objects may be immutable");
+ }
+ if (Description.CalleeSavedRegister && Description.CalleeSavedRegister->number() == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "callee-saved stack objects require a nonzero physical register");
+ }
+ if (Description.LocalOffset &&
+ (Description.Kind == StackObjectKind::FixedABI ||
+ Description.Kind == StackObjectKind::FixedSpillSlot)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "fixed ABI stack objects cannot have a local frame offset");
+ }
if (Description.Kind == StackObjectKind::Target && !Description.Stack.isSpecialStack() &&
Description.TargetFlags == 0) {
return makeUnexpected(
@@ -118,6 +175,41 @@ Expected<void> MachineFrameInfo::verifyDescription(const StackObjectDesc &Descri
return {};
}
+Expected<void> MachineFrameInfo::verifyStateStructure(const MachineFrameState &State) {
+ if (State.FramePointerPolicy >= MachineFramePointerPolicy::Count) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "frame-pointer policy is invalid");
+ }
+
+ auto VerifyPoints = [](std::span<const MachineFrameSaveRestorePoint> Points,
+ std::string_view Kind) -> Expected<void> {
+ std::unordered_set<MachineBlockStableID> Blocks;
+ for (const MachineFrameSaveRestorePoint &Point : Points) {
+ if (!Point.Block.isValid()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ std::string(Kind) + " point requires a valid basic-block identity");
+ }
+ if (!Blocks.insert(Point.Block).second) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ std::string(Kind) + " points must use unique basic blocks");
+ }
+ std::unordered_set<PhysicalRegister> Registers;
+ for (const PhysicalRegister Reg : Point.Registers) {
+ if (Reg.number() == 0 || !Registers.insert(Reg).second) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ std::string(Kind) +
+ " point registers must be nonzero and unique");
+ }
+ }
+ }
+ return {};
+ };
+
+ auto SaveResult = VerifyPoints(State.SavePoints, "save");
+ if (!SaveResult)
+ return SaveResult;
+ return VerifyPoints(State.RestorePoints, "restore");
+}
+
Expected<FrameIndex> MachineFrameInfo::createObject(StackObjectDesc Description) {
if (Frozen)
return makeUnexpected(ErrorCode::InvalidArgument, "frozen frame cannot create stack objects");
@@ -214,7 +306,26 @@ Expected<void> MachineFrameInfo::setMaxCallFrameSize(std::uint64_t Size) {
if (Frozen)
return makeUnexpected(ErrorCode::InvalidArgument,
"frozen frame cannot change its maximum call-frame size");
- MaxCallFrameSize = Size;
+ State.MaxCallFrameSize = Size;
+ return {};
+}
+
+Expected<void> MachineFrameInfo::clearMaxCallFrameSize() {
+ if (Frozen) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "frozen frame cannot change its maximum call-frame size");
+ }
+ State.MaxCallFrameSize.reset();
+ return {};
+}
+
+Expected<void> MachineFrameInfo::setState(MachineFrameState NewState) {
+ if (Frozen)
+ return makeUnexpected(ErrorCode::InvalidArgument, "frozen frame cannot replace its state");
+ auto Verification = verifyStateStructure(NewState);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ State = std::move(NewState);
return {};
}
@@ -234,13 +345,17 @@ Expected<void> MachineFrameInfo::freeze(std::uint64_t Size, Alignment Align) {
"static stack size must be aligned to the stack alignment");
}
- StaticStackSize = Size;
- StackAlign = Align;
+ State.StackSize = Size;
+ State.MaxAlignment = Align;
Frozen = true;
return {};
}
Expected<void> MachineFrameInfo::verify() const {
+ auto StateResult = verifyStateStructure(State);
+ if (!StateResult)
+ return StateResult;
+
for (std::size_t Position = 0; Position < Objects.size(); ++Position) {
const StackObject &Object = Objects[Position];
if (Object.index() != FrameIndex::fromValue(static_cast<std::uint32_t>(Position))) {
@@ -258,11 +373,17 @@ Expected<void> MachineFrameInfo::verify() const {
}
}
- if (Frozen != StaticStackSize.has_value()) {
+ for (const std::optional<FrameIndex> Index : {State.StackProtector, State.FunctionContext}) {
+ if (Index && object(*Index) == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "frame state references an unknown stack object");
+ }
+ }
+ if (Frozen && !State.MaxAlignment) {
return makeUnexpected(ErrorCode::VerificationError,
- "frame frozen state and static stack size are inconsistent");
+ "frozen frame is missing its maximum alignment");
}
- if (Frozen && *StaticStackSize % StackAlign.bytes() != 0) {
+ if (Frozen && State.StackSize % State.MaxAlignment->bytes() != 0) {
return makeUnexpected(ErrorCode::VerificationError,
"frozen static stack size violates stack alignment");
}
diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp
index 9342c5a..ced3b1a 100644
--- a/backend/lib/CodeGen/MachineFunctionCloner.cpp
+++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp
@@ -263,10 +263,29 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) {
return std::unexpected(DestinationIndex.error());
State.Result.FrameIndices.emplace(SourceObject.index(), *DestinationIndex);
}
- auto CallFrameResult = State.Destination.frameInfo().setMaxCallFrameSize(
- State.Source.frameInfo().maxCallFrameSize());
- if (!CallFrameResult)
- return std::unexpected(CallFrameResult.error());
+ MachineFrameState FrameState = State.Source.frameInfo().state();
+ FrameState.SavePoints.clear();
+ FrameState.RestorePoints.clear();
+ auto MapFrameReference = [&State](std::optional<FrameIndex> &Reference) -> Expected<void> {
+ if (!Reference)
+ return {};
+ const auto Position = State.Result.FrameIndices.find(*Reference);
+ if (Position == State.Result.FrameIndices.end()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "source frame state references an unmapped stack object");
+ }
+ Reference = Position->second;
+ return {};
+ };
+ auto StackProtectorResult = MapFrameReference(FrameState.StackProtector);
+ if (!StackProtectorResult)
+ return StackProtectorResult;
+ auto FunctionContextResult = MapFrameReference(FrameState.FunctionContext);
+ if (!FunctionContextResult)
+ return FunctionContextResult;
+ auto StateResult = State.Destination.frameInfo().setState(std::move(FrameState));
+ if (!StateResult)
+ return std::unexpected(StateResult.error());
return {};
}
@@ -491,6 +510,34 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) {
return std::unexpected(UpdateResult.error());
}
+ MachineFrameState FrameState = State.Destination.frameInfo().state();
+ auto ClonePoints =
+ [&State](std::span<const MachineFrameSaveRestorePoint> SourcePoints,
+ std::vector<MachineFrameSaveRestorePoint> &DestinationPoints) -> Expected<void> {
+ DestinationPoints.clear();
+ DestinationPoints.reserve(SourcePoints.size());
+ for (const MachineFrameSaveRestorePoint &Point : SourcePoints) {
+ const auto Position = State.Result.BlockStableIDs.find(Point.Block);
+ if (Position == State.Result.BlockStableIDs.end()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "source frame save/restore point references an unmapped block");
+ }
+ DestinationPoints.push_back({Position->second, Point.Registers});
+ }
+ return {};
+ };
+ auto SaveResult =
+ ClonePoints(State.Source.frameInfo().state().SavePoints, FrameState.SavePoints);
+ if (!SaveResult)
+ return SaveResult;
+ auto RestoreResult =
+ ClonePoints(State.Source.frameInfo().state().RestorePoints, FrameState.RestorePoints);
+ if (!RestoreResult)
+ return RestoreResult;
+ auto StateResult = State.Destination.frameInfo().setState(std::move(FrameState));
+ if (!StateResult)
+ return std::unexpected(StateResult.error());
+
if (State.Source.frameInfo().isFrozen()) {
const auto StackSize = State.Source.frameInfo().staticStackSize();
if (!StackSize) {
diff --git a/backend/lib/CodeGen/MachineFunctionProperties.cpp b/backend/lib/CodeGen/MachineFunctionProperties.cpp
index 55a4ca3..d3dfdac 100644
--- a/backend/lib/CodeGen/MachineFunctionProperties.cpp
+++ b/backend/lib/CodeGen/MachineFunctionProperties.cpp
@@ -20,6 +20,8 @@ constexpr std::array PropertyNames{
std::string_view{"FrameFinalized"},
std::string_view{"NoFrameIndices"},
std::string_view{"NoExecutablePseudos"},
+ std::string_view{"FailedISel"},
+ std::string_view{"FailsVerification"},
};
static_assert(PropertyNames.size() == static_cast<std::size_t>(MachineFunctionProperty::Count));
diff --git a/backend/lib/CodeGen/MachineJumpTableInfo.cpp b/backend/lib/CodeGen/MachineJumpTableInfo.cpp
index 4ae0c15..5ba0853 100644
--- a/backend/lib/CodeGen/MachineJumpTableInfo.cpp
+++ b/backend/lib/CodeGen/MachineJumpTableInfo.cpp
@@ -33,10 +33,18 @@ MachineJumpTableInfo::verifyDescription(const MachineJumpTableDesc &Description)
return makeUnexpected(ErrorCode::VerificationError,
"target jump table encoding and target encoding ID are inconsistent");
}
- if (Description.Encoding == JumpTableEntryEncoding::CompressedOffset &&
- Description.EntrySize > 4) {
+ if ((Description.Encoding == JumpTableEntryEncoding::GPRelative32BlockAddress ||
+ Description.Encoding == JumpTableEntryEncoding::LabelDifference32 ||
+ Description.Encoding == JumpTableEntryEncoding::Custom32) &&
+ Description.EntrySize != 4) {
return makeUnexpected(ErrorCode::VerificationError,
- "compressed jump table entries cannot exceed four bytes");
+ "32-bit jump table encodings require four-byte entries");
+ }
+ if ((Description.Encoding == JumpTableEntryEncoding::GPRelative64BlockAddress ||
+ Description.Encoding == JumpTableEntryEncoding::LabelDifference64) &&
+ Description.EntrySize != 8) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "64-bit jump table encodings require eight-byte entries");
}
for (const MachineBasicBlock *Target : Description.Targets) {
@@ -63,6 +71,37 @@ Expected<JumpTableIndex> MachineJumpTableInfo::createTable(MachineJumpTableDesc
return Index;
}
+Expected<JumpTableIndex> MachineJumpTableInfo::reserveTable() {
+ if (Frozen) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "frozen jump table info cannot reserve tables");
+ }
+ if (Tables.size() > std::numeric_limits<std::uint32_t>::max())
+ return makeUnexpected(ErrorCode::Unsupported, "jump table index space is exhausted");
+
+ const JumpTableIndex Index = JumpTableIndex::fromValue(static_cast<std::uint32_t>(Tables.size()));
+ Tables.push_back(MachineJumpTableEntry(Index));
+ return Index;
+}
+
+Expected<void>
+MachineJumpTableInfo::defineReservedTable(JumpTableIndex Index,
+ MachineJumpTableDesc Description) {
+ if (Frozen) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "frozen jump table info cannot define reserved tables");
+ }
+ if (Index.value() >= Tables.size() || !Tables[Index.value()].isPending()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "jump table index is not an unresolved reservation");
+ }
+ auto Verification = verifyDescription(Description);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ Tables[Index.value()].Description.emplace<MachineJumpTableDesc>(std::move(Description));
+ return {};
+}
+
Expected<MachineJumpTableEntry *> MachineJumpTableInfo::mutableTable(JumpTableIndex Index) {
if (Frozen)
return makeUnexpected(ErrorCode::InvalidArgument,
@@ -70,8 +109,10 @@ Expected<MachineJumpTableEntry *> MachineJumpTableInfo::mutableTable(JumpTableIn
if (Index.value() >= Tables.size())
return makeUnexpected(ErrorCode::InvalidArgument, "jump table index is out of range");
MachineJumpTableEntry &Entry = Tables[Index.value()];
- if (Entry.isTombstone())
- return makeUnexpected(ErrorCode::InvalidArgument, "jump table index is a tombstone");
+ if (Entry.isTombstone() || Entry.isPending()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "jump table index has no defined description");
+ }
return &Entry;
}
@@ -130,6 +171,10 @@ Expected<void> MachineJumpTableInfo::verify() const {
"jump table index does not match its stable table position");
}
const MachineJumpTableDesc *Description = Entry.description();
+ if (Entry.isPending()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "jump table contains an unresolved reservation");
+ }
if (Description == nullptr)
continue;
auto Verification = verifyDescription(*Description);
diff --git a/backend/lib/CodeGen/MachineMetadata.cpp b/backend/lib/CodeGen/MachineMetadata.cpp
index f789a74..756124c 100644
--- a/backend/lib/CodeGen/MachineMetadata.cpp
+++ b/backend/lib/CodeGen/MachineMetadata.cpp
@@ -27,6 +27,10 @@ namespace {
} // namespace
+MachineMetadataElement MachineMetadataElement::null() {
+ return MachineMetadataElement(NullValue{});
+}
+
Expected<MachineMetadataElement>
MachineMetadataElement::reference(MachineMetadataReference Reference) {
if (!Reference.isValid()) {
diff --git a/backend/lib/CodeGen/MachineOperand.cpp b/backend/lib/CodeGen/MachineOperand.cpp
index 6cce188..ae46505 100644
--- a/backend/lib/CodeGen/MachineOperand.cpp
+++ b/backend/lib/CodeGen/MachineOperand.cpp
@@ -12,6 +12,8 @@
#include <limits>
#include <memory>
#include <span>
+#include <string>
+#include <string_view>
#include <utility>
#include <vector>
@@ -94,6 +96,68 @@ Expected<IntegerImmediate> IntegerImmediate::fromSigned(std::uint32_t BitWidth,
return create(BitWidth, Words);
}
+Expected<IntegerImmediate> IntegerImmediate::parseDecimal(std::uint32_t BitWidth,
+ std::string_view Text) {
+ if (BitWidth == 0 || BitWidth > MaxBitWidth) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "integer immediate bit width is outside the supported range");
+ }
+ const bool Negative = Text.starts_with('-');
+ if (Negative)
+ Text.remove_prefix(1);
+ if (Text.empty())
+ return makeUnexpected(ErrorCode::ParseError, "integer immediate has no decimal digits");
+
+ std::vector<std::uint64_t> Words(integerWordCount(BitWidth), 0);
+ for (const char Character : Text) {
+ if (Character < '0' || Character > '9') {
+ return makeUnexpected(ErrorCode::ParseError,
+ "integer immediate contains a non-decimal digit");
+ }
+ std::uint64_t Carry = static_cast<unsigned>(Character - '0');
+ for (std::uint64_t &Word : Words) {
+ const unsigned __int128 Product = static_cast<unsigned __int128>(Word) * 10U + Carry;
+ Word = static_cast<std::uint64_t>(Product);
+ Carry = static_cast<std::uint64_t>(Product >> 64U);
+ }
+ if (Carry != 0 || (Words.back() & ~integerHighWordMask(BitWidth)) != 0) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "integer immediate does not fit its declared bit width");
+ }
+ }
+
+ if (Negative) {
+ if (std::ranges::all_of(Words, [](std::uint64_t Word) { return Word == 0; })) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "negative zero is not a canonical integer immediate");
+ }
+ const std::uint32_t SignBit = BitWidth - 1U;
+ const std::size_t SignWord = SignBit / 64U;
+ const std::uint64_t SignMask = std::uint64_t{1} << (SignBit % 64U);
+ bool Fits = Words[SignWord] <= SignMask;
+ for (std::size_t Index = SignWord + 1; Index < Words.size(); ++Index)
+ Fits = Fits && Words[Index] == 0;
+ if (Words[SignWord] == SignMask) {
+ for (std::size_t Index = 0; Index < SignWord; ++Index)
+ Fits = Fits && Words[Index] == 0;
+ }
+ if (!Fits) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "negative integer immediate does not fit its declared bit width");
+ }
+ for (std::uint64_t &Word : Words)
+ Word = ~Word;
+ std::uint64_t Carry = 1;
+ for (std::uint64_t &Word : Words) {
+ const std::uint64_t Previous = Word;
+ Word += Carry;
+ Carry = Carry != 0 && Word < Previous ? 1 : 0;
+ }
+ Words.back() &= integerHighWordMask(BitWidth);
+ }
+ return create(BitWidth, Words);
+}
+
bool IntegerImmediate::isZero() const noexcept {
return std::ranges::all_of(Words, [](std::uint64_t Word) { return Word == 0; });
}
@@ -103,6 +167,25 @@ bool IntegerImmediate::isNegative() const noexcept {
return (Words[SignBit / 64U] & (std::uint64_t{1} << (SignBit % 64U))) != 0;
}
+std::string IntegerImmediate::toUnsignedDecimal() const {
+ if (isZero())
+ return "0";
+ std::vector<std::uint64_t> Remaining = Words;
+ std::string Digits;
+ while (!std::ranges::all_of(Remaining,
+ [](std::uint64_t Word) { return Word == 0; })) {
+ unsigned __int128 Remainder = 0;
+ for (auto Iterator = Remaining.rbegin(); Iterator != Remaining.rend(); ++Iterator) {
+ const unsigned __int128 Dividend = (Remainder << 64U) | *Iterator;
+ *Iterator = static_cast<std::uint64_t>(Dividend / 10U);
+ Remainder = Dividend % 10U;
+ }
+ Digits.push_back(static_cast<char>('0' + static_cast<unsigned>(Remainder)));
+ }
+ std::ranges::reverse(Digits);
+ return Digits;
+}
+
Expected<void> IntegerImmediate::verify() const {
return verifyIntegerImmediate(BitWidth, Words, ErrorCode::VerificationError);
}
diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp
index 3ebe683..f6f5640 100644
--- a/backend/lib/CodeGen/MachineVerifier.cpp
+++ b/backend/lib/CodeGen/MachineVerifier.cpp
@@ -211,6 +211,7 @@ private:
return fail("block layout contains a cycle or duplicate block");
if (!Block->isLinked() || Block->previous() != Previous)
return fail("block layout ownership or previous link is inconsistent");
+ LinkedBlockIDs.insert(Block->stableID());
if (Block->denseNumber() != LayoutIndex || Block->layoutIndex() != LayoutIndex) {
return fail("block dense number or layout index is stale");
}
@@ -427,6 +428,30 @@ private:
}
[[nodiscard]] Expected<void> verifyFrameLifetimes() const {
+ auto VerifyPoints =
+ [this](std::span<const MachineFrameSaveRestorePoint> Points,
+ std::string_view Kind) -> Expected<void> {
+ for (const MachineFrameSaveRestorePoint &Point : Points) {
+ if (!LinkedBlockIDs.contains(Point.Block)) {
+ return fail(std::string("frame ") + std::string(Kind) +
+ " point references a detached or unknown block");
+ }
+ for (const PhysicalRegister Reg : Point.Registers) {
+ if (!Target.registers().isKnownPhysicalRegister(Reg)) {
+ return fail(std::string("frame ") + std::string(Kind) +
+ " point references an unknown physical register");
+ }
+ }
+ }
+ return {};
+ };
+ auto SaveResult = VerifyPoints(Function.frameInfo().state().SavePoints, "save");
+ if (!SaveResult)
+ return SaveResult;
+ auto RestoreResult = VerifyPoints(Function.frameInfo().state().RestorePoints, "restore");
+ if (!RestoreResult)
+ return RestoreResult;
+
for (const StackObject &Object : Function.frameInfo().objects()) {
const StackObjectDesc *Description = Object.description();
if (Description == nullptr)
@@ -442,6 +467,10 @@ private:
if (!DebugInfoResult)
return failWith("stack object debug information", DebugInfoResult.error());
}
+ if (Description->CalleeSavedRegister &&
+ !Target.registers().isKnownPhysicalRegister(*Description->CalleeSavedRegister)) {
+ return fail("callee-saved stack object references an unknown physical register");
+ }
}
return {};
}
@@ -794,6 +823,7 @@ private:
MachineVerifierOptions Options;
std::unordered_set<const MachineBasicBlock *> RegisteredBlocks;
std::unordered_set<const MachineBasicBlock *> LinkedBlocks;
+ std::unordered_set<MachineBlockStableID> LinkedBlockIDs;
std::unordered_set<const MachineInstr *> RegisteredInstructions;
std::unordered_set<const MachineInstr *> LinkedInstructions;
std::unordered_set<MachineInstrStableID> LinkedInstructionIDs;
diff --git a/backend/lib/CodeGen/TargetMIRTextInfo.cpp b/backend/lib/CodeGen/TargetMIRTextInfo.cpp
index a349432..65e3c65 100644
--- a/backend/lib/CodeGen/TargetMIRTextInfo.cpp
+++ b/backend/lib/CodeGen/TargetMIRTextInfo.cpp
@@ -8,6 +8,7 @@
#include <cstddef>
#include <cstdint>
#include <string>
+#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -51,6 +52,89 @@ namespace {
} // namespace
+MIRTargetNode::MIRTargetNode() = default;
+MIRTargetNode::MIRTargetNode(const MIRTargetNode &) = default;
+MIRTargetNode &MIRTargetNode::operator=(const MIRTargetNode &) = default;
+MIRTargetNode::MIRTargetNode(MIRTargetNode &&) noexcept = default;
+MIRTargetNode &MIRTargetNode::operator=(MIRTargetNode &&) noexcept = default;
+MIRTargetNode::~MIRTargetNode() = default;
+
+MIRTargetNode MIRTargetNode::null() {
+ return {};
+}
+
+MIRTargetNode MIRTargetNode::scalar(std::string Value) {
+ MIRTargetNode Result;
+ Result.Kind = MIRTargetNodeKind::Scalar;
+ Result.Scalar = std::move(Value);
+ return Result;
+}
+
+MIRTargetNode MIRTargetNode::sequence(std::vector<MIRTargetNode> Values) {
+ MIRTargetNode Result;
+ Result.Kind = MIRTargetNodeKind::Sequence;
+ Result.Sequence = std::move(Values);
+ return Result;
+}
+
+MIRTargetNode MIRTargetNode::mapping(std::vector<MIRTargetMappingEntry> Entries) {
+ MIRTargetNode Result;
+ Result.Kind = MIRTargetNodeKind::Mapping;
+ Result.Mapping = std::move(Entries);
+ return Result;
+}
+
+bool operator==(const MIRTargetNode &Left, const MIRTargetNode &Right) {
+ return Left.Kind == Right.Kind && Left.Scalar == Right.Scalar &&
+ Left.Sequence == Right.Sequence && Left.Mapping == Right.Mapping;
+}
+
+const MIRTargetNode *MIRTargetNode::mappingValue(std::string_view Key) const noexcept {
+ if (Kind != MIRTargetNodeKind::Mapping)
+ return nullptr;
+ for (const MIRTargetMappingEntry &Entry : Mapping) {
+ if (Entry.Key == Key)
+ return &Entry.Value;
+ }
+ return nullptr;
+}
+
+Expected<void> MIRTargetNode::verify() const {
+ if (Kind >= MIRTargetNodeKind::Count)
+ return makeUnexpected(ErrorCode::InvalidArgument, "target MIR node kind is invalid");
+
+ const bool HasScalar = !Scalar.empty();
+ const bool HasSequence = !Sequence.empty();
+ const bool HasMapping = !Mapping.empty();
+ if ((Kind != MIRTargetNodeKind::Scalar && HasScalar) ||
+ (Kind != MIRTargetNodeKind::Sequence && HasSequence) ||
+ (Kind != MIRTargetNodeKind::Mapping && HasMapping)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target MIR node contains storage for a different node kind");
+ }
+
+ if (Kind == MIRTargetNodeKind::Sequence) {
+ for (const MIRTargetNode &Element : Sequence) {
+ auto ElementResult = Element.verify();
+ if (!ElementResult)
+ return ElementResult;
+ }
+ }
+ if (Kind == MIRTargetNodeKind::Mapping) {
+ std::unordered_set<std::string_view> Keys;
+ for (const MIRTargetMappingEntry &Entry : Mapping) {
+ if (Entry.Key.empty() || !Keys.insert(Entry.Key).second) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target MIR mapping keys must be nonempty and unique");
+ }
+ auto ValueResult = Entry.Value.verify();
+ if (!ValueResult)
+ return ValueResult;
+ }
+ }
+ return {};
+}
+
Expected<TargetMIRTextInfo> TargetMIRTextInfo::create(TargetMIRTextInfoDesc Description,
const TargetRegisterInfo &Registers) {
TargetMIRTextInfo Result(std::move(Description));
@@ -192,6 +276,18 @@ Expected<TargetMIRTextInfo> TargetMIRTextInfo::create(TargetMIRTextInfoDesc Desc
if (Result.Description.FunctionStateType && Result.Description.FunctionStateType->value() == 0) {
return invalidDescription("target function state TypeID must be nonzero");
}
+ const bool HasStateType = Result.Description.FunctionStateType.has_value();
+ const bool HasParser = Result.Description.ParseFunctionState != nullptr;
+ const bool HasPrinter = Result.Description.PrintFunctionState != nullptr;
+ if (HasStateType != HasParser || HasStateType != HasPrinter) {
+ return invalidDescription(
+ "target function state TypeID, parser, and printer must be provided together");
+ }
+ const bool HasConstantParser = Result.Description.ParseTargetConstant != nullptr;
+ const bool HasConstantPrinter = Result.Description.PrintTargetConstant != nullptr;
+ if (HasConstantParser != HasConstantPrinter) {
+ return invalidDescription("target constant parser and printer must be provided together");
+ }
return Result;
}
@@ -365,4 +461,86 @@ TargetMIRTextInfo::physicalRegister(std::uint32_t DwarfRegister) const noexcept
return Position->second;
}
+Expected<TargetFunctionState>
+TargetMIRTextInfo::parseFunctionState(const MIRTargetNode &Node) const {
+ if (Description.ParseFunctionState == nullptr || !Description.FunctionStateType) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not define a machineFunctionInfo MIR schema");
+ }
+ auto NodeResult = Node.verify();
+ if (!NodeResult)
+ return std::unexpected(NodeResult.error());
+ auto State = Description.ParseFunctionState(Node);
+ if (!State)
+ return std::unexpected(State.error());
+ auto StateResult = State->verify();
+ if (!StateResult)
+ return std::unexpected(StateResult.error());
+ if (State->typeID() != Description.FunctionStateType) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "parsed target function state has the wrong schema TypeID");
+ }
+ return State;
+}
+
+Expected<MIRTargetNode>
+TargetMIRTextInfo::printFunctionState(const TargetFunctionState &State) const {
+ if (Description.PrintFunctionState == nullptr || !Description.FunctionStateType) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not define a machineFunctionInfo MIR schema");
+ }
+ auto StateResult = State.verify();
+ if (!StateResult)
+ return std::unexpected(StateResult.error());
+ if (State.typeID() != Description.FunctionStateType) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target function state has the wrong MIR schema TypeID");
+ }
+ auto Node = Description.PrintFunctionState(State);
+ if (!Node)
+ return std::unexpected(Node.error());
+ auto NodeResult = Node->verify();
+ if (!NodeResult)
+ return std::unexpected(NodeResult.error());
+ if (Node->Kind != MIRTargetNodeKind::Mapping) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machineFunctionInfo printer must produce a mapping");
+ }
+ return Node;
+}
+
+Expected<TargetConstant>
+TargetMIRTextInfo::parseTargetConstant(std::string_view Text) const {
+ if (Description.ParseTargetConstant == nullptr) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not define a target constant MIR schema");
+ }
+ auto Constant = Description.ParseTargetConstant(Text);
+ if (!Constant)
+ return std::unexpected(Constant.error());
+ auto Verification = Constant->verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Constant;
+}
+
+Expected<std::string>
+TargetMIRTextInfo::printTargetConstant(const TargetConstant &Constant) const {
+ if (Description.PrintTargetConstant == nullptr) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not define a target constant MIR schema");
+ }
+ auto Verification = Constant.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ auto Text = Description.PrintTargetConstant(Constant);
+ if (!Text)
+ return std::unexpected(Text.error());
+ if (Text->empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target constant printer produced an empty MIR value");
+ }
+ return Text;
+}
+
} // namespace rcc
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
diff --git a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
index 7343fdb..08b69e2 100644
--- a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
+++ b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp
@@ -128,8 +128,8 @@ struct PoolTargetConstant final {
[[nodiscard]] rcc::MachineConstantPoolEntryDesc
genericDescription(rcc::ConstantPoolMergeKind Merge) {
rcc::MachineConstantPoolEntryDesc Description;
- Description.Value =
- rcc::GenericConstantData{"i32", {std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}}};
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(32, 0x04030201U);
+ Description.Value = std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
Description.Size = 4;
Description.Align = *rcc::Alignment::create(4);
Description.Merge = Merge;
diff --git a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
index c67339d..78987c0 100644
--- a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
+++ b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp
@@ -109,7 +109,8 @@ void testInvalidDescriptions(TestContext &Context) {
"fixed-size stack object must reject a missing size");
auto ZeroSize = fixedObject(0, 1);
- Context.expect(!Frame.createObject(ZeroSize), "fixed-size stack object must reject zero size");
+ Context.expect(Frame.createObject(ZeroSize).has_value(),
+ "fixed-size stack objects must preserve LLVM MIR zero-sized objects");
rcc::StackObjectDesc InvalidVariable;
InvalidVariable.Kind = rcc::StackObjectKind::DynamicAlloca;
diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
index 9c8a447..9d8ffd2 100644
--- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
+++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
@@ -335,8 +335,9 @@ struct SourceFixture final {
return false;
rcc::MachineConstantPoolEntryDesc HistoricalConstant;
+ auto HistoricalInteger = rcc::IntegerImmediate::fromUnsigned(32, 0x01010101U);
HistoricalConstant.Value =
- rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*HistoricalInteger)));
HistoricalConstant.Size = 4;
HistoricalConstant.Align = *rcc::Alignment::create(4);
auto HistoricalConstantIndex = Function.constantPool().addEntry(std::move(HistoricalConstant));
@@ -367,7 +368,7 @@ struct SourceFixture final {
"source jump table history must contain a tombstone");
rcc::MachineJumpTableDesc JumpTable;
- JumpTable.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase;
+ JumpTable.Encoding = rcc::JumpTableEntryEncoding::GPRelative32BlockAddress;
JumpTable.EntrySize = 4;
JumpTable.Align = *rcc::Alignment::create(4);
JumpTable.Targets = {Fixture.Exit, Fixture.Entry, Fixture.Exit};
diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
index 9f5cf5e..00ffffc 100644
--- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
+++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp
@@ -426,8 +426,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) {
"builder must reject unknown constant-pool and jump-table indices before allocation");
rcc::MachineConstantPoolEntryDesc ConstantDescription;
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(64, 0x2a2a2a2a2a2a2a2aULL);
ConstantDescription.Value =
- rcc::GenericConstantData{"i64", std::vector<std::byte>(8, std::byte{0x2a})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
ConstantDescription.Size = 8;
ConstantDescription.Align = *rcc::Alignment::create(8);
auto ConstantIndex = Function->constantPool().addEntry(std::move(ConstantDescription));
diff --git a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
index 0273801..106a2a6 100644
--- a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
+++ b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
@@ -51,7 +51,7 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) {
[[nodiscard]] rcc::MachineJumpTableDesc tableDescription(rcc::MachineBasicBlock &First,
rcc::MachineBasicBlock &Second) {
rcc::MachineJumpTableDesc Description;
- Description.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase;
+ Description.Encoding = rcc::JumpTableEntryEncoding::GPRelative32BlockAddress;
Description.EntrySize = 4;
Description.Align = *rcc::Alignment::create(4);
Description.Targets = {&First, &Second, &Second};
@@ -90,14 +90,15 @@ void testTableLifecycle(TestContext &Context) {
Function->jumpTableInfo().table(*First) == nullptr,
"erasing a jump table must leave a tombstone");
auto CompressedDescription = tableDescription(*Entry, *Exit);
- CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
- CompressedDescription.EntrySize = 2;
+ CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::Custom32;
+ CompressedDescription.EntrySize = 4;
auto Compressed = Function->jumpTableInfo().createTable(std::move(CompressedDescription));
Context.expect(Compressed && Compressed->value() == 2,
"jump table tombstone indices must never be reused");
auto Updated = tableDescription(*Entry, *Exit);
- Updated.Encoding = rcc::JumpTableEntryEncoding::RelativeToEntry;
+ Updated.Encoding = rcc::JumpTableEntryEncoding::LabelDifference64;
+ Updated.EntrySize = 8;
Context.expect(Function->jumpTableInfo().updateTable(*Target, std::move(Updated)).has_value() &&
Function->jumpTableInfo().verify().has_value() &&
Function->jumpTableInfo().freeze().has_value() &&
@@ -145,7 +146,8 @@ void testInvalidTables(TestContext &Context) {
"generic jump table encoding must reject target encoding IDs");
auto OversizedCompressed = tableDescription(*Block, *Block);
- OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
+ OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::Custom32;
+ OversizedCompressed.EntrySize = 8;
OversizedCompressed.EntrySize = 8;
Context.expect(!Function->jumpTableInfo().createTable(std::move(OversizedCompressed)),
"compressed jump table must reject oversized entries");
diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp
index a803c08..ca265a7 100644
--- a/backend/unittests/CodeGen/MachineVerifierTest.cpp
+++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp
@@ -522,8 +522,9 @@ void testTableReferenceFailures(TestContext &Context) {
return;
rcc::MachineConstantPoolEntryDesc ConstantDescription;
+ auto Integer = rcc::IntegerImmediate::fromUnsigned(32, 0x01010101U);
ConstantDescription.Value =
- rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})};
+ std::move(*rcc::GenericConstantData::integer(std::move(*Integer)));
ConstantDescription.Size = 4;
ConstantDescription.Align = *rcc::Alignment::create(4);
auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription));
diff --git a/backend/unittests/MIR/CMakeLists.txt b/backend/unittests/MIR/CMakeLists.txt
index 1eb0965..6b41474 100644
--- a/backend/unittests/MIR/CMakeLists.txt
+++ b/backend/unittests/MIR/CMakeLists.txt
@@ -6,6 +6,15 @@ target_link_libraries(RCCBackendEmbeddedIRTests PRIVATE RCCBackend::MIR)
add_test(NAME RCCBackend.MIR.EmbeddedIR COMMAND RCCBackendEmbeddedIRTests)
+add_executable(RCCBackendLLVMConstantSyntaxTests LLVMConstantSyntaxTest.cpp)
+rcc_backend_configure_target(RCCBackendLLVMConstantSyntaxTests)
+target_link_libraries(RCCBackendLLVMConstantSyntaxTests PRIVATE RCCBackend::MIR)
+
+add_test(
+ NAME RCCBackend.MIR.LLVMConstantSyntax
+ COMMAND RCCBackendLLVMConstantSyntaxTests
+)
+
add_executable(RCCBackendMILexerTests MILexerTest.cpp)
rcc_backend_configure_target(RCCBackendMILexerTests)
target_link_libraries(RCCBackendMILexerTests PRIVATE RCCBackend::MIR)
@@ -23,13 +32,28 @@ target_link_libraries(
add_test(NAME RCCBackend.MIR.MIParser COMMAND RCCBackendMIParserTests)
+add_executable(RCCBackendMIRDocumentTests MIRDocumentTest.cpp)
+rcc_backend_configure_target(RCCBackendMIRDocumentTests)
+target_link_libraries(
+ RCCBackendMIRDocumentTests
+ PRIVATE
+ RCCBackend::MIR
+ RCCBackend::TestTarget
+)
+
+add_test(NAME RCCBackend.MIR.MIRDocument COMMAND RCCBackendMIRDocumentTests)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntaxTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexerTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParserTest.cpp"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntaxTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexerTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParserTest.cpp"
)
diff --git a/backend/unittests/MIR/EmbeddedIRTest.cpp b/backend/unittests/MIR/EmbeddedIRTest.cpp
index 58c6162..bbac841 100644
--- a/backend/unittests/MIR/EmbeddedIRTest.cpp
+++ b/backend/unittests/MIR/EmbeddedIRTest.cpp
@@ -17,6 +17,7 @@ int testReferenceIndex() {
const std::string Input = "; @commented = global i8 0\n"
"target datalayout = \"e-m:e-p:64:64\"\n"
"target triple = \"riscv64-unknown-linux-gnu\"\n"
+ "%pair = type { i32, i32 }\n"
"@ordinary = global i32 0\n"
"@\"space\\20global\" = external global i8\n"
"declare void @declared()\n"
@@ -25,12 +26,12 @@ int testReferenceIndex() {
") {\n"
"entry:\n"
" %slot = alloca i32, align 4\n"
- " %not_an_alloca = add i32 1, 2\n"
+ " %not_an_alloca = add i32 %argument, 2\n"
" br label %\"exit\\20block\"\n"
"\"exit\\20block\":\n"
" ret void\n"
"}\n"
- "!0 = !{}\n"
+ "!0 = !DILocation(line: 1, scope: !1)\n"
"!llvm.module.flags = !{!0}\n";
rcc::mir::EmbeddedIRIndexResult Result = rcc::mir::indexEmbeddedIR("embedded.ll", Input);
@@ -54,6 +55,9 @@ int testReferenceIndex() {
if (Function->allocas().size() != 1 || Function->alloca("slot") == nullptr ||
Function->alloca("not_an_alloca") != nullptr)
return fail("Embedded IR indexing must distinguish named allocas from other local values");
+ if (Function->values().size() != 3 || Function->value("argument") == nullptr ||
+ Function->value("slot") == nullptr || Function->value("not_an_alloca") == nullptr)
+ return fail("Embedded IR indexing must retain arguments and instruction results");
if (!Index->hasGlobalValue("ordinary") || !Index->hasGlobalValue("space global") ||
!Index->hasGlobalValue("declared") || Index->hasGlobalValue("commented"))
@@ -61,6 +65,12 @@ int testReferenceIndex() {
if (Index->metadata("0") == nullptr || Index->metadata("llvm.module.flags") == nullptr ||
Index->metadataDefinitions().size() != 2)
return fail("Embedded IR indexing must retain numeric and named metadata definitions");
+ if (Index->metadata("0")->DynamicType != "DILocation" ||
+ Index->metadata("llvm.module.flags")->DynamicType != "MDTuple")
+ return fail("Embedded IR indexing must classify metadata dynamic types");
+ if (Index->typeDefinitions().size() != 1 || Index->typeDefinition("pair") == nullptr ||
+ Index->typeDefinition("pair")->Definition != "{ i32, i32 }")
+ return fail("Embedded IR indexing must retain named type definitions");
return 0;
}
diff --git a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp
new file mode 100644
index 0000000..be5a67e
--- /dev/null
+++ b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+constexpr rcc::mir::LLVMConstantSyntaxOptions Options{
+ .DefaultPointerSizeInBits = 64,
+ .DataLayout = {},
+ .NamedTypes = {},
+};
+
+void expectRoundTrip(TestContext &Context, std::string_view Text, std::uint64_t ExpectedSize) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, Options);
+ Context.expect(Parsed.has_value(), "valid LLVM constant must parse");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ auto Size = rcc::mir::llvmConstantStorageSize(Parsed->Type, Options);
+ Context.expect(Size.has_value() && *Size == ExpectedSize,
+ "LLVM constant storage size must match its structured type");
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "structured LLVM constant must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, Options);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "LLVM constant parse-print-parse must preserve the value tree");
+ if (!Reparsing)
+ std::cerr << *Printed << ": " << Reparsing.error().message() << '\n';
+}
+
+void expectLayoutRoundTrip(TestContext &Context, std::string_view Text,
+ rcc::mir::LLVMConstantSyntaxOptions LayoutOptions,
+ std::uint64_t ExpectedSize, std::uint64_t ExpectedPreferredAlignment) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, LayoutOptions);
+ Context.expect(Parsed.has_value(), "DataLayout-aware LLVM constant must parse");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ auto Size = rcc::mir::llvmConstantStorageSize(Parsed->Type, LayoutOptions);
+ auto Alignment = rcc::mir::llvmConstantPreferredAlignment(Parsed->Type, LayoutOptions);
+ Context.expect(Size.has_value() && *Size == ExpectedSize,
+ "DataLayout must determine the exact allocation size");
+ if (!Size || *Size != ExpectedSize) {
+ std::cerr << Text << ": expected size " << ExpectedSize << ", got "
+ << (Size ? std::to_string(*Size) : Size.error().message()) << '\n';
+ }
+ Context.expect(Alignment.has_value() && Alignment->bytes() == ExpectedPreferredAlignment,
+ "DataLayout must determine the exact preferred alignment");
+ if (!Alignment || Alignment->bytes() != ExpectedPreferredAlignment) {
+ std::cerr << Text << ": expected preferred alignment " << ExpectedPreferredAlignment << ", got "
+ << (Alignment ? std::to_string(Alignment->bytes()) : Alignment.error().message())
+ << '\n';
+ }
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "DataLayout-aware LLVM constant must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, LayoutOptions);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "DataLayout-aware named constants must round-trip structurally");
+}
+
+void testScalarConstants(TestContext &Context) {
+ expectRoundTrip(Context, "i1 true", 1);
+ expectRoundTrip(Context, "i65 -1", 9);
+ expectRoundTrip(Context, "half 0xH3C00", 2);
+ expectRoundTrip(Context, "bfloat 0xR3F80", 2);
+ expectRoundTrip(Context, "float 1.250000e+00", 4);
+ expectRoundTrip(Context, "double 0x3FF0000000000000", 8);
+ expectRoundTrip(Context, "x86_fp80 f0x4000C000000000000000", 10);
+ expectRoundTrip(Context, "fp128 f0x3FFF0000000000000000000000000000", 16);
+ expectRoundTrip(Context, "ptr null", 8);
+ expectRoundTrip(Context, "i32 undef", 4);
+ expectRoundTrip(Context, "i32 poison", 4);
+ expectRoundTrip(Context, "i32 zeroinitializer", 4);
+}
+
+void expectIntegerWords(TestContext &Context, std::string_view Text,
+ std::span<const std::uint64_t> ExpectedWords) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, Options);
+ Context.expect(Parsed.has_value() && Parsed->IntegerValue.has_value() &&
+ std::ranges::equal(Parsed->IntegerValue->words(), ExpectedWords),
+ "explicit LLVM hexadecimal integer must preserve its exact bit pattern");
+ if (!Parsed)
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+}
+
+void expectFloatingWords(TestContext &Context, std::string_view Text,
+ std::span<const std::uint64_t> ExpectedWords) {
+ auto Parsed = rcc::mir::parseLLVMConstant(Text, Options);
+ Context.expect(Parsed.has_value() && Parsed->FloatingValue.has_value() &&
+ std::ranges::equal(Parsed->FloatingValue->words(), ExpectedWords),
+ "LLVM floating special literal must preserve its exact bit pattern");
+ if (!Parsed) {
+ std::cerr << Text << ": " << Parsed.error().message() << '\n';
+ return;
+ }
+ if (!Parsed->FloatingValue ||
+ !std::ranges::equal(Parsed->FloatingValue->words(), ExpectedWords)) {
+ std::cerr << Text << ": expected words";
+ for (const std::uint64_t Word : ExpectedWords)
+ std::cerr << " 0x" << std::hex << Word;
+ std::cerr << ", got";
+ if (Parsed->FloatingValue) {
+ for (const std::uint64_t Word : Parsed->FloatingValue->words())
+ std::cerr << " 0x" << std::hex << Word;
+ }
+ std::cerr << std::dec << '\n';
+ }
+ auto Printed = rcc::mir::printLLVMConstant(*Parsed);
+ Context.expect(Printed.has_value(), "LLVM floating special literal must print");
+ if (!Printed)
+ return;
+ auto Reparsing = rcc::mir::parseLLVMConstant(*Printed, Options);
+ Context.expect(Reparsing.has_value() && *Reparsing == *Parsed,
+ "printed LLVM floating special literal must round-trip");
+}
+
+void testExplicitHexIntegerConstants(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 2> AllOnes65{std::numeric_limits<std::uint64_t>::max(), 1};
+ constexpr std::array<std::uint64_t, 2> SignBit65{0, 1};
+ constexpr std::array<std::uint64_t, 1> UnsignedFF{0xff};
+ constexpr std::array<std::uint64_t, 1> SignedFF{0xffff};
+ constexpr std::array<std::uint64_t, 1> SignedFF8{0xff};
+
+ expectIntegerWords(Context, "i65 u0x1FFFFFFFFFFFFFFFF", AllOnes65);
+ expectIntegerWords(Context, "i65 s0x1FFFFFFFFFFFFFFFF", AllOnes65);
+ expectIntegerWords(Context, "i65 s0x10000000000000000", SignBit65);
+ expectIntegerWords(Context, "i65 s0x01", AllOnes65);
+ expectIntegerWords(Context, "i16 u0xFF", UnsignedFF);
+ expectIntegerWords(Context, "i16 s0xFF", SignedFF);
+ expectIntegerWords(Context, "i8 s0x1FF", SignedFF8);
+
+ expectRoundTrip(Context, "i65 s0x01", 9);
+ Context.expect(!rcc::mir::parseLLVMConstant("i32 u0x", Options) &&
+ !rcc::mir::parseLLVMConstant("i32 u0X1", Options),
+ "explicit LLVM hexadecimal integers must use the fixed spelling");
+}
+
+void testSpecialFloatingLiterals(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 1> HalfInfinity{0x7c00};
+ constexpr std::array<std::uint64_t, 1> NegativeHalfInfinity{0xfc00};
+ constexpr std::array<std::uint64_t, 1> HalfQuietNaN{0x7e00};
+ constexpr std::array<std::uint64_t, 1> NegativeHalfQuietNaN{0xfe00};
+ constexpr std::array<std::uint64_t, 1> HalfSignalingNaN{0x7d00};
+ constexpr std::array<std::uint64_t, 1> HalfPayloadNaN{0x7fef};
+ constexpr std::array<std::uint64_t, 1> FloatSignalingNaN{0x7f800001};
+ constexpr std::array<std::uint64_t, 2> X87Infinity{0x8000000000000000, 0x7fff};
+ constexpr std::array<std::uint64_t, 2> X87QuietNaN{0xc000000000000000, 0x7fff};
+ constexpr std::array<std::uint64_t, 2> QuadOne{0, 0x3fff000000000000};
+ constexpr std::array<std::uint64_t, 2> QuadInfinity{0, 0x7fff000000000000};
+ constexpr std::array<std::uint64_t, 2> QuadQuietNaN{0, 0x7fff800000000000};
+ constexpr std::array<std::uint64_t, 2> PPCOne{0x3ff0000000000000, 0};
+ constexpr std::array<std::uint64_t, 2> PPCInfinity{0x7ff0000000000000, 0};
+ constexpr std::array<std::uint64_t, 2> PPCQuietNaN{0x7ff8000000000000, 0};
+
+ expectFloatingWords(Context, "half +inf", HalfInfinity);
+ expectFloatingWords(Context, "half -inf", NegativeHalfInfinity);
+ expectFloatingWords(Context, "half +qnan", HalfQuietNaN);
+ expectFloatingWords(Context, "half -qnan", NegativeHalfQuietNaN);
+ expectFloatingWords(Context, "half +snan(0x100)", HalfSignalingNaN);
+ expectFloatingWords(Context, "half +nan(0x123456789ABCDEF)", HalfPayloadNaN);
+ expectFloatingWords(Context, "float +snan(0x400001)", FloatSignalingNaN);
+ expectFloatingWords(Context, "x86_fp80 +inf", X87Infinity);
+ expectFloatingWords(Context, "x86_fp80 +qnan", X87QuietNaN);
+ expectFloatingWords(Context, "fp128 +inf", QuadInfinity);
+ expectFloatingWords(Context, "fp128 +qnan", QuadQuietNaN);
+ expectFloatingWords(Context, "fp128 0xL00000000000000003FFF000000000000", QuadOne);
+ expectFloatingWords(Context, "ppc_fp128 +inf", PPCInfinity);
+ expectFloatingWords(Context, "ppc_fp128 +qnan", PPCQuietNaN);
+ expectFloatingWords(Context, "ppc_fp128 0xM3FF00000000000000000000000000000", PPCOne);
+
+ for (const std::string_view Text : {
+ std::string_view{"float nan(0x)"},
+ std::string_view{"float snan(12)"},
+ std::string_view{"float qnan(0x1)"},
+ std::string_view{"float inf(0x1)"},
+ std::string_view{"float inf"},
+ std::string_view{"float +nan"},
+ std::string_view{"float +snan"},
+ std::string_view{"float +qnan(0x1)"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "malformed LLVM floating special literal must be rejected");
+ }
+}
+
+void testExactFloatingConversion(TestContext &Context) {
+ constexpr std::array<std::uint64_t, 1> PositiveZero{0};
+ constexpr std::array<std::uint64_t, 1> NegativeFloatZero{0x80000000};
+ constexpr std::array<std::uint64_t, 1> HalfOne{0x3c00};
+ constexpr std::array<std::uint64_t, 1> HalfTwoUlps{0x3c02};
+ constexpr std::array<std::uint64_t, 1> HalfSmallestSubnormal{0x0001};
+ constexpr std::array<std::uint64_t, 1> BFloatOne{0x3f80};
+ constexpr std::array<std::uint64_t, 1> BFloatTwoUlps{0x3f82};
+ constexpr std::array<std::uint64_t, 1> FloatOne{0x3f800000};
+ constexpr std::array<std::uint64_t, 1> FloatTwoUlps{0x3f800002};
+ constexpr std::array<std::uint64_t, 1> FloatHexPower{0x2f800000};
+ constexpr std::array<std::uint64_t, 1> FloatSignalingNaN{0x7f800001};
+ constexpr std::array<std::uint64_t, 1> FloatQuietNaN{0x7fc00000};
+ constexpr std::array<std::uint64_t, 1> LargestDouble{0x7fefffffffffffff};
+ constexpr std::array<std::uint64_t, 2> X87One{0x8000000000000000, 0x3fff};
+ constexpr std::array<std::uint64_t, 2> QuadOne{0, 0x3fff000000000000};
+ constexpr std::array<std::uint64_t, 2> PPCOne{0x3ff0000000000000, 0};
+
+ expectFloatingWords(Context, "float +0.0", PositiveZero);
+ expectFloatingWords(Context, "float -0.0", NegativeFloatZero);
+ expectFloatingWords(Context, "half 1.00048828125", HalfOne);
+ expectFloatingWords(Context, "half 1.00146484375", HalfTwoUlps);
+ expectFloatingWords(Context, "half 0.0000000298023223876953126", HalfSmallestSubnormal);
+ expectFloatingWords(Context, "bfloat 1.00390625", BFloatOne);
+ expectFloatingWords(Context, "bfloat 1.01171875", BFloatTwoUlps);
+ expectFloatingWords(Context, "float 1.000000059604644775390625", FloatOne);
+ expectFloatingWords(Context, "float 1.000000178813934326171875", FloatTwoUlps);
+ expectFloatingWords(Context, "float 0x1.0p-32", FloatHexPower);
+ expectFloatingWords(Context, "double 1.79769313486231570815e+308", LargestDouble);
+ expectFloatingWords(Context, "x86_fp80 1.0", X87One);
+ expectFloatingWords(Context, "x86_fp80 0x1.0p0", X87One);
+ expectFloatingWords(Context, "fp128 1.0", QuadOne);
+ expectFloatingWords(Context, "fp128 0x1.0p0", QuadOne);
+ expectFloatingWords(Context, "ppc_fp128 1.0", PPCOne);
+ expectFloatingWords(Context, "ppc_fp128 0x1.0p0", PPCOne);
+ expectFloatingWords(Context, "float 0x3FF0000000000000", FloatOne);
+ expectFloatingWords(Context, "float 0x7FF0000020000000", FloatSignalingNaN);
+ expectFloatingWords(Context, "float 0x7FF8000000000000", FloatQuietNaN);
+ expectFloatingWords(Context, "float f0x3F800000", FloatOne);
+
+ for (const std::string_view Text : {
+ std::string_view{"half 0.0000000298023223876953125"},
+ std::string_view{"float 0x3FF0000000000001"},
+ std::string_view{"float 0x7FF8000000000001"},
+ std::string_view{"float f0x3F8000"},
+ std::string_view{"float f0X3F800000"},
+ std::string_view{"float 0X1.0p0"},
+ std::string_view{"float 0x1p0"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "noncanonical LLVM floating syntax must be rejected");
+ }
+}
+
+void testAggregateConstants(TestContext &Context) {
+ expectRoundTrip(Context, "[4 x i8] c\"ab\\00\\FF\"", 4);
+ expectRoundTrip(Context, "[3 x i16] [i16 1, i16 2, i16 3]", 6);
+ expectRoundTrip(Context, "<4 x i32> <i32 0, i32 1, i32 2, i32 3>", 16);
+ expectRoundTrip(Context, "<4 x float> <float 1.0, float -1.0, float 2.0, float -2.0>", 16);
+ expectRoundTrip(Context, "{ i32, ptr } { i32 7, ptr @global }", 16);
+ expectRoundTrip(Context, "<{ i8, i32 }> <{ i8 1, i32 2 }>", 5);
+ expectRoundTrip(Context, "<8 x i16> splat (i16 9)", 16);
+}
+
+void testAddressConstants(TestContext &Context) {
+ expectRoundTrip(Context, "ptr @global", 8);
+ expectRoundTrip(Context, "ptr @\"quoted name\"", 8);
+ expectRoundTrip(Context, "ptr blockaddress(@function, %entry)", 8);
+ expectRoundTrip(Context, "ptr dso_local_equivalent @function", 8);
+ expectRoundTrip(Context, "ptr no_cfi @function", 8);
+ expectRoundTrip(Context, "i64 ptrtoint (ptr @global to i64)", 8);
+ expectRoundTrip(Context, "ptr bitcast (ptr @global to ptr)", 8);
+ expectRoundTrip(Context, "i64 add nuw nsw (i64 1, i64 2)", 8);
+ expectRoundTrip(Context, "ptr getelementptr inbounds nusw nuw (i8, ptr @global, i64 4)", 8);
+ expectRoundTrip(Context,
+ "<2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, "
+ "<2 x i32> <i32 3, i32 4>, <2 x i32> <i32 0, i32 3>)",
+ 8);
+}
+
+void testConstantExpressionVerification(TestContext &Context) {
+ expectRoundTrip(Context, "i8 trunc (i16 257 to i8)", 1);
+ expectRoundTrip(Context, "i32 bitcast (float 1.0 to i32)", 4);
+ expectRoundTrip(Context, "ptr addrspacecast (ptr addrspace(1) @global to ptr)", 8);
+ expectRoundTrip(Context, "ptr inttoptr (i64 0 to ptr)", 8);
+ expectRoundTrip(Context, "i64 ptrtoaddr (ptr @global to i64)", 8);
+ expectRoundTrip(Context, "i64 ptrtoint (ptr @global to i64)", 8);
+ expectRoundTrip(Context,
+ "<2 x i32> insertelement (<2 x i32> <i32 1, i32 2>, "
+ "i32 3, i64 0)",
+ 8);
+ expectRoundTrip(Context, "i32 extractelement (<2 x i32> <i32 1, i32 2>, i64 0)", 4);
+ expectRoundTrip(Context, "ptr ptrauth (ptr @function, i32 1, i64 0, ptr null, ptr null)", 8);
+
+ for (const std::string_view Text : {
+ std::string_view{"i16 trunc (i8 1 to i16)"},
+ std::string_view{"i64 bitcast (float 1.0 to i64)"},
+ std::string_view{"ptr bitcast (ptr addrspace(1) @global to ptr)"},
+ std::string_view{"ptr addrspacecast (ptr @global to ptr)"},
+ std::string_view{"ptr inttoptr (float 1.0 to ptr)"},
+ std::string_view{"i64 ptrtoint (i64 0 to i64)"},
+ std::string_view{"i32 add nuw (i32 1, i64 2)"},
+ std::string_view{"ptr getelementptr (i8, ptr @global, float 1.0)"},
+ std::string_view{"<2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, "
+ "<2 x i32> <i32 3, i32 4>, <2 x i64> <i64 0, i64 3>)"},
+ std::string_view{"ptr ptrauth (ptr @function, i64 1)"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "constant expression with incompatible typed operands must be rejected");
+ }
+}
+
+void testDataLayoutAndNamedTypes(TestContext &Context) {
+ static constexpr std::array Definitions{
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "pair", .Definition = "{ i8, i64 }", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{.Name = "word", .Definition = "i32", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "node", .Definition = "{ i32, %node* }", .NumericName = false},
+ rcc::mir::LLVMNamedTypeDefinition{
+ .Name = "loop", .Definition = "{ %loop }", .NumericName = false},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions LayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-p1:32:16:64-i64:64:128-i128:128-v96:128:256-a:0:64",
+ .NamedTypes = Definitions,
+ };
+
+ expectLayoutRoundTrip(Context, "i65 -1", LayoutOptions, 16, 16);
+ expectLayoutRoundTrip(Context, "[3 x i24] [i24 1, i24 2, i24 3]", LayoutOptions, 12, 4);
+ expectLayoutRoundTrip(Context, "<3 x i32> <i32 1, i32 2, i32 3>", LayoutOptions, 16, 32);
+ expectLayoutRoundTrip(Context, "ptr addrspace(1) null", LayoutOptions, 4, 8);
+ expectLayoutRoundTrip(Context, "%pair { i8 1, i64 2 }", LayoutOptions, 16, 8);
+ expectLayoutRoundTrip(Context, "%word 7", LayoutOptions, 4, 4);
+ expectLayoutRoundTrip(Context, "%node zeroinitializer", LayoutOptions, 16, 8);
+
+ Context.expect(!rcc::mir::parseLLVMType("%loop", LayoutOptions),
+ "directly recursive named aggregate types must be rejected as unsized");
+ auto NodeType = rcc::mir::parseLLVMType("%node", LayoutOptions);
+ Context.expect(NodeType.has_value(),
+ "named recursion through a pointer must remain sized and legal");
+}
+
+void testAddressSpacePointerWidths(TestContext &Context) {
+ constexpr rcc::mir::LLVMConstantSyntaxOptions HeterogeneousLayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-p1:32:16:64",
+ .NamedTypes = {},
+ };
+ auto DefaultPointer = rcc::mir::llvmPointerSizeInBits(0, HeterogeneousLayoutOptions);
+ auto AddressSpaceOnePointer = rcc::mir::llvmPointerSizeInBits(1, HeterogeneousLayoutOptions);
+ Context.expect(DefaultPointer.has_value() && *DefaultPointer == 64 &&
+ AddressSpaceOnePointer.has_value() && *AddressSpaceOnePointer == 32,
+ "LLVM data layout must provide pointer width by address space");
+
+ constexpr rcc::mir::LLVMConstantSyntaxOptions LegacyOptions{
+ .DefaultPointerSizeInBits = 64,
+ .DataLayout = {},
+ .NamedTypes = {},
+ };
+ auto LegacyPointer = rcc::mir::llvmPointerSizeInBits(7, LegacyOptions);
+ Context.expect(LegacyPointer.has_value() && *LegacyPointer == 64,
+ "legacy pointer-width fallback must apply without an explicit data layout");
+}
+
+void testDataLayoutBoundaryRules(TestContext &Context) {
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions IntegerAlignmentOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-i24:128:128",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions HistoricalAggregateOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-a0:64:128",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions ZeroAggregateOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:64:64-a:0:64",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions NonBytePointerOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:33:8:8:32",
+ .NamedTypes = {},
+ };
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions PointerFlagsOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-pue1(storage):33:8:8:32-po2(allones):64:64",
+ .NamedTypes = {},
+ };
+
+ expectLayoutRoundTrip(Context, "i20 1", IntegerAlignmentOptions, 16, 16);
+ expectLayoutRoundTrip(Context, "{ i8, i32 } { i8 1, i32 2 }", HistoricalAggregateOptions, 8, 16);
+ expectLayoutRoundTrip(Context, "{ i8, i32 } { i8 1, i32 2 }", ZeroAggregateOptions, 8, 8);
+ expectLayoutRoundTrip(Context, "<{ i8, i32 }> <{ i8 1, i32 2 }>", ZeroAggregateOptions, 5, 8);
+ expectLayoutRoundTrip(Context, "ptr null", NonBytePointerOptions, 5, 1);
+ expectLayoutRoundTrip(Context, "ptr addrspace(1) null", PointerFlagsOptions, 5, 1);
+
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions InvalidIndexSizeOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-p:33:8:8:34",
+ .NamedTypes = {},
+ };
+ auto Pointer = rcc::mir::parseLLVMType("ptr", InvalidIndexSizeOptions);
+ Context.expect(Pointer.has_value() &&
+ !rcc::mir::llvmConstantStorageSize(*Pointer, InvalidIndexSizeOptions),
+ "LLVM pointer index size larger than its representation must be rejected");
+
+ static constexpr rcc::mir::LLVMConstantSyntaxOptions NonLayoutOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = "e-m:e-n8:16:32-S128-Fi64-P1-G2-A3-ni:1:2-ve-s-p:64:64",
+ .NamedTypes = {},
+ };
+ expectLayoutRoundTrip(Context, "ptr null", NonLayoutOptions, 8, 8);
+
+ const auto ExpectInvalidDataLayout = [&Context](std::string_view DataLayout) {
+ const rcc::mir::LLVMConstantSyntaxOptions InvalidOptions{
+ .DefaultPointerSizeInBits = 0,
+ .DataLayout = DataLayout,
+ .NamedTypes = {},
+ };
+ auto Type = rcc::mir::parseLLVMType("ptr", InvalidOptions);
+ Context.expect(Type.has_value() && !rcc::mir::llvmConstantStorageSize(*Type, InvalidOptions),
+ "malformed non-layout LLVM data-layout component must be rejected");
+ };
+ for (const std::string_view DataLayout : {
+ std::string_view{"ee-p:64:64"},
+ std::string_view{"m:q-p:64:64"},
+ std::string_view{"n64:0-p:64:64"},
+ std::string_view{"S-p:64:64"},
+ std::string_view{"Fx64-p:64:64"},
+ std::string_view{"P16777216-p:64:64"},
+ std::string_view{"ni:-p:64:64"},
+ std::string_view{"ni:0-p:64:64"},
+ std::string_view{"pe:64:64"},
+ std::string_view{"pu:64:64"},
+ std::string_view{"pzo1:64:64"},
+ std::string_view{"p1(P):64:64"},
+ std::string_view{"p1(foo):64:64-p2(foo):64:64"},
+ std::string_view{"p16777216:64:64"},
+ }) {
+ ExpectInvalidDataLayout(DataLayout);
+ }
+}
+
+void testInvalidConstants(TestContext &Context) {
+ for (const std::string_view Text : {
+ std::string_view{""},
+ std::string_view{"opaque 0"},
+ std::string_view{"i32 null"},
+ std::string_view{"ptr 42"},
+ std::string_view{"[2 x i8] [i8 1]"},
+ std::string_view{"<2 x i32> <i32 1, i64 2>"},
+ std::string_view{"i64 ptrtoint (ptr @global to i32)"},
+ std::string_view{"ptr getelementptr (i8, i64 0)"},
+ std::string_view{"i32 1 trailing"},
+ }) {
+ Context.expect(!rcc::mir::parseLLVMConstant(Text, Options),
+ "invalid LLVM constant must be rejected");
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testScalarConstants(Context);
+ testExplicitHexIntegerConstants(Context);
+ testSpecialFloatingLiterals(Context);
+ testExactFloatingConversion(Context);
+ testAggregateConstants(Context);
+ testAddressConstants(Context);
+ testConstantExpressionVerification(Context);
+ testDataLayoutAndNamedTypes(Context);
+ testAddressSpacePointerWidths(Context);
+ testDataLayoutBoundaryRules(Context);
+ testInvalidConstants(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp
index d037bef..552de25 100644
--- a/backend/unittests/MIR/MIParserTest.cpp
+++ b/backend/unittests/MIR/MIParserTest.cpp
@@ -345,6 +345,66 @@ bb.1."exit block" (landing-pad, bbsections Cold):
"complete MIR body parse-print-parse must be deterministic");
}
+void testAddressSpacePointerWidthResolver(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 3);
+ Context.expect(Target.has_value(), "pointer-width parser target must be valid");
+ if (!Target || !Function)
+ return;
+
+ rcc::mir::MIParserContext ParserContext;
+ ParserContext.PointerSizeInBits = [](std::uint32_t AddressSpace) -> rcc::Expected<std::uint32_t> {
+ if (AddressSpace == 1)
+ return 32;
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument,
+ "test data layout has no requested address space");
+ };
+ rcc::mir::MIParserOptions ParserOptions;
+ ParserOptions.VerifyAfterParse = false;
+ rcc::mir::MIParser Parser(*Function, *Target, "pointer-width.mir",
+ "bb.0:\n %0:_(p1) = TEST_COPY $g0\n", ParserContext, ParserOptions);
+ auto Parsed = Parser.parse();
+ const auto Registers = Function->registerInfo().virtualRegisters();
+ auto ExpectedType = rcc::LLT::makePointer(1, 32);
+ Context.expect(Parsed.has_value() && ExpectedType.has_value() && Registers.size() == 1 &&
+ Registers.front().Type == *ExpectedType,
+ "MI parser must use the address-space-specific pointer-width resolver");
+ if (!Parsed) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parser.diagnostics())
+ std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n';
+ std::cerr << "error: " << Parsed.error().message() << '\n';
+ }
+}
+
+void testUnserializableLowLevelType(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 4);
+ Context.expect(Target.has_value(), "unserializable-type target must be valid");
+ if (!Target || !Function)
+ return;
+
+ rcc::mir::MIParserOptions ParserOptions;
+ ParserOptions.VerifyAfterParse = false;
+ rcc::mir::MIParser Parser(*Function, *Target, "unserializable-type.mir",
+ "bb.0:\n %0:gpr64(f64) = TEST_COPY $g0\n", {}, ParserOptions);
+ auto Parsed = Parser.parse();
+ auto Register = rcc::Register::makeVirtual(0);
+ auto UnsupportedType = rcc::LLT::makeFloat(rcc::FloatSemantics::Float8E5M2);
+ Context.expect(Parsed.has_value() && Register.has_value() && UnsupportedType.has_value(),
+ "unserializable-type test setup must parse a typed virtual register");
+ if (!Parsed || !Register || !UnsupportedType)
+ return;
+ auto SetType = Function->registerInfo().setType(*Register, *UnsupportedType);
+ Context.expect(SetType.has_value(), "test setup must assign an extended floating low-level type");
+ if (!SetType)
+ return;
+
+ auto Printed = rcc::mir::printMachineFunctionBody(*Function, *Target);
+ Context.expect(!Printed && Printed.error().message().find("cannot spell this floating") !=
+ std::string_view::npos,
+ "printer must reject extended low-level types absent from fixed LLVM MIR grammar");
+}
+
void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body,
std::string_view Message) {
auto Function = createFunction(Context, 10);
@@ -391,6 +451,8 @@ void testDiagnostics(TestContext &Context) {
int main() {
TestContext Context;
testTwoPassBodyParsing(Context);
+ testAddressSpacePointerWidthResolver(Context);
+ testUnserializableLowLevelType(Context);
testDiagnostics(Context);
return Context.result();
}
diff --git a/backend/unittests/MIR/MIRDocumentTest.cpp b/backend/unittests/MIR/MIRDocumentTest.cpp
new file mode 100644
index 0000000..7ea82c1
--- /dev/null
+++ b/backend/unittests/MIR/MIRDocumentTest.cpp
@@ -0,0 +1,953 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MIR/MIRDocument.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineCompilationStatus.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetFunctionState.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+constexpr std::uint32_t CopyOpcode = 1;
+constexpr std::uint32_t BranchOpcode = 2;
+constexpr std::uint32_t LoadOpcode = 3;
+constexpr std::uint32_t GlobalOpcode = 4;
+
+class DocumentTargetState final {
+public:
+ DocumentTargetState(std::uint32_t FrameBase, std::string Mode)
+ : FrameBase(FrameBase), Mode(std::move(Mode)) {}
+
+ [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept {
+ return rcc::TargetFunctionStateTypeID::fromValue(0x5243434d49525354ULL);
+ }
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 1;
+ }
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ if (FrameBase == 0 || Mode.empty())
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "document target state is incomplete");
+ return {};
+ }
+ [[nodiscard]] rcc::Expected<DocumentTargetState>
+ clone(const rcc::MachineFunctionCloneContext &) const {
+ return DocumentTargetState(FrameBase, Mode);
+ }
+ [[nodiscard]] std::string print() const {
+ return Mode + ":" + std::to_string(FrameBase);
+ }
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Bytes(4);
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Bytes[Index] = static_cast<std::byte>((FrameBase >> (Index * 8U)) & 0xffU);
+ return Bytes;
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetState> decode(std::uint32_t Version,
+ std::span<const std::byte> Bytes) {
+ if (Version != schemaVersion() || Bytes.size() != 4)
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target state encoding");
+ std::uint32_t Value = 0;
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Value |= std::to_integer<std::uint32_t>(Bytes[Index]) << (Index * 8U);
+ return DocumentTargetState(Value, "test");
+ }
+
+ [[nodiscard]] std::uint32_t frameBase() const noexcept {
+ return FrameBase;
+ }
+ [[nodiscard]] std::string_view mode() const noexcept {
+ return Mode;
+ }
+
+ friend bool operator==(const DocumentTargetState &Left,
+ const DocumentTargetState &Right) = default;
+
+private:
+ std::uint32_t FrameBase = 0;
+ std::string Mode;
+};
+
+class DocumentTargetConstant final {
+public:
+ explicit DocumentTargetConstant(std::uint64_t Value) : Value(Value) {}
+
+ [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept {
+ return rcc::TargetConstantTypeID::fromValue(0x5243434d49524354ULL);
+ }
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 1;
+ }
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ return {};
+ }
+ [[nodiscard]] rcc::Expected<DocumentTargetConstant> clone() const {
+ return DocumentTargetConstant(Value);
+ }
+ [[nodiscard]] std::uint64_t stableHash() const noexcept {
+ return Value;
+ }
+ [[nodiscard]] std::string print() const {
+ return "test-constant(" + std::to_string(Value) + ")";
+ }
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Bytes(8);
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Bytes[Index] = static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU);
+ return Bytes;
+ }
+ [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const {
+ auto Bytes = encode();
+ if (!Bytes)
+ return std::unexpected(Bytes.error());
+ return rcc::TargetConstantEmission{.Bytes = std::move(*Bytes), .Fixups = {}};
+ }
+ [[nodiscard]] rcc::Alignment requiredAlignment() const {
+ return *rcc::Alignment::create(8);
+ }
+ [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const {
+ return std::nullopt;
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetConstant> parse(std::string_view Text) {
+ constexpr std::string_view Prefix = "test-constant(";
+ if (!Text.starts_with(Prefix) || !Text.ends_with(')'))
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "invalid document target constant");
+ Text.remove_prefix(Prefix.size());
+ Text.remove_suffix(1);
+ std::uint64_t Parsed = 0;
+ const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Parsed, 10);
+ if (Error != std::errc{} || End != Text.data() + Text.size())
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target constant value");
+ return DocumentTargetConstant(Parsed);
+ }
+ [[nodiscard]] static rcc::Expected<DocumentTargetConstant>
+ decode(std::uint32_t Version, std::span<const std::byte> Bytes) {
+ if (Version != schemaVersion() || Bytes.size() != 8)
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "invalid document target constant encoding");
+ std::uint64_t Parsed = 0;
+ for (std::size_t Index = 0; Index < Bytes.size(); ++Index)
+ Parsed |= std::to_integer<std::uint64_t>(Bytes[Index]) << (Index * 8U);
+ return DocumentTargetConstant(Parsed);
+ }
+
+ [[nodiscard]] std::uint64_t value() const noexcept {
+ return Value;
+ }
+
+ friend bool operator==(const DocumentTargetConstant &Left,
+ const DocumentTargetConstant &Right) = default;
+
+private:
+ std::uint64_t Value = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetFunctionState>
+parseTargetState(const rcc::MIRTargetNode &Node) {
+ const rcc::MIRTargetNode *FrameBase = Node.mappingValue("frame-base");
+ const rcc::MIRTargetNode *Mode = Node.mappingValue("mode");
+ if (FrameBase == nullptr || Mode == nullptr ||
+ FrameBase->Kind != rcc::MIRTargetNodeKind::Scalar ||
+ Mode->Kind != rcc::MIRTargetNodeKind::Scalar) {
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "document target state requires frame-base and mode");
+ }
+ std::uint32_t Value = 0;
+ const auto [End, Error] = std::from_chars(
+ FrameBase->Scalar.data(), FrameBase->Scalar.data() + FrameBase->Scalar.size(), Value, 10);
+ if (Error != std::errc{} || End != FrameBase->Scalar.data() + FrameBase->Scalar.size())
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "document target frame-base is invalid");
+ return rcc::TargetFunctionState::create(DocumentTargetState(Value, Mode->Scalar));
+}
+
+[[nodiscard]] rcc::Expected<rcc::MIRTargetNode>
+printTargetState(const rcc::TargetFunctionState &State) {
+ const auto *Value = State.getIf<DocumentTargetState>();
+ if (Value == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument, "wrong document target state type");
+ std::vector<rcc::MIRTargetMappingEntry> Entries;
+ Entries.push_back({"frame-base", rcc::MIRTargetNode::scalar(std::to_string(Value->frameBase()))});
+ Entries.push_back({"mode", rcc::MIRTargetNode::scalar(std::string(Value->mode()))});
+ return rcc::MIRTargetNode::mapping(std::move(Entries));
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetConstant> parseTargetConstant(std::string_view Text) {
+ return rcc::TargetConstant::parse<DocumentTargetConstant>(Text);
+}
+
+[[nodiscard]] rcc::Expected<std::string> printTargetConstant(const rcc::TargetConstant &Constant) {
+ if (Constant.getIf<DocumentTargetConstant>() == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument,
+ "wrong document target constant type");
+ return Constant.print();
+}
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+[[nodiscard]] rcc::OperandDescriptor registerOperand(rcc::RegisterOperandRole Role) {
+ rcc::OperandDescriptor Operand;
+ Operand.Kinds = {rcc::MachineOperand::Kind::Register};
+ Operand.RegisterRole = Role;
+ Operand.RegClass = rcc::test::TestRegisterClass::GPR64;
+ return Operand;
+}
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ rcc::InstructionDescriptor Copy;
+ Copy.InstructionOpcode = rcc::Opcode::fromValue(CopyOpcode);
+ Copy.Name = "TEST_COPY";
+ Copy.Operands = {
+ registerOperand(rcc::RegisterOperandRole::Def),
+ registerOperand(rcc::RegisterOperandRole::Use),
+ };
+ Copy.NumExplicitDefs = 1;
+ Copy.Flags.set(rcc::InstructionFlag::ProducesCode);
+
+ rcc::InstructionDescriptor Branch;
+ Branch.InstructionOpcode = rcc::Opcode::fromValue(BranchOpcode);
+ Branch.Name = "TEST_BRANCH";
+ rcc::OperandDescriptor Block;
+ Block.Kinds = {rcc::MachineOperand::Kind::BasicBlock};
+ Branch.Operands.push_back(Block);
+ Branch.Flags = {
+ rcc::InstructionFlag::ProducesCode,
+ rcc::InstructionFlag::Branch,
+ rcc::InstructionFlag::Terminator,
+ rcc::InstructionFlag::Barrier,
+ };
+
+ rcc::InstructionDescriptor Load;
+ Load.InstructionOpcode = rcc::Opcode::fromValue(LoadOpcode);
+ Load.Name = "TEST_LOAD";
+ Load.Operands = {
+ registerOperand(rcc::RegisterOperandRole::Def),
+ registerOperand(rcc::RegisterOperandRole::Use),
+ };
+ Load.NumExplicitDefs = 1;
+ Load.Flags = {
+ rcc::InstructionFlag::ProducesCode,
+ rcc::InstructionFlag::MayLoad,
+ };
+ Load.RequiresMemoryOperands = true;
+
+ rcc::InstructionDescriptor Global;
+ Global.InstructionOpcode = rcc::Opcode::fromValue(GlobalOpcode);
+ Global.Name = "TEST_GLOBAL";
+ rcc::OperandDescriptor GlobalOperand;
+ GlobalOperand.Kinds = {
+ rcc::MachineOperand::Kind::GlobalSymbol,
+ rcc::MachineOperand::Kind::ExternalSymbol,
+ };
+ Global.Operands.push_back(std::move(GlobalOperand));
+ Global.Flags.set(rcc::InstructionFlag::ProducesCode);
+
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Copy));
+ Descriptors.push_back(std::move(Branch));
+ Descriptors.push_back(std::move(Load));
+ Descriptors.push_back(std::move(Global));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ auto Registers = rcc::test::createTestTargetRegisterInfo();
+ if (!Registers)
+ return std::unexpected(Registers.error());
+ rcc::TargetMIRTextInfoDesc MIRText = rcc::test::createTestTargetMIRTextInfoDescription();
+ MIRText.FunctionStateType = DocumentTargetState::stableTypeID();
+ MIRText.ParseFunctionState = parseTargetState;
+ MIRText.PrintFunctionState = printTargetState;
+ MIRText.ParseTargetConstant = parseTargetConstant;
+ MIRText.PrintTargetConstant = printTargetConstant;
+ return rcc::TargetDescription::create(std::move(*Instructions), std::move(*Registers),
+ std::move(MIRText));
+}
+
+constexpr std::string_view ValidDocument = R"(--- |
+ target datalayout = "e-p:64:64"
+ target triple = "test64-unknown-linux-gnu"
+
+ %pair = type { i8, i64 }
+ @ordinary = external global i8
+
+ define void @documented() {
+ entry:
+ %slot = alloca i64, align 8
+ ret void
+ }
+
+ !0 = !DILocalVariable(name: "slot", scope: !1)
+ !1 = distinct !DISubprogram(name: "documented")
+ !2 = !DILocation(line: 4, column: 2, scope: !1)
+---
+name: documented
+alignment: 16
+exposesReturnsTwice: true
+tracksRegLiveness: true
+noPhis: true
+isSSA: true
+noVRegs: false
+registers:
+ - { id: 7, class: gpr64, preferred-register: '$g1', flags: [ test-vreg-one ] }
+liveins:
+ - { reg: '$g0', virtual-reg: '%7' }
+calleeSavedRegisters: [ '$g1' ]
+frameInfo:
+ isFrameAddressTaken: true
+ stackSize: 32
+ maxAlignment: 16
+ adjustsStack: true
+ hasCalls: true
+ framePointerPolicy: all
+ stackProtector: '%stack.4'
+ maxCallFrameSize: 24
+ localFrameSize: 8
+ savePoint:
+ - { point: '%bb.0', registers: [ '$g1' ] }
+fixedStack:
+ - { id: 3, type: spill-slot, offset: -16, size: 8, alignment: 8 }
+stack:
+ - { id: 4, name: slot, size: 8, alignment: 8, local-offset: 0,
+ debug-info-variable: '!0',
+ debug-info-expression: '!DIExpression(DW_OP_LLVM_arg, 0, 18446744073709551615)',
+ debug-info-location: '!DILocation(line: 4, column: 2, scope: !1, isImplicitCode: true)' }
+entry_values:
+ - { entry-value-register: '$g0', debug-info-variable: '!0',
+ debug-info-expression: '!DIExpression(DW_OP_stack_value)', debug-info-location: '!2' }
+constants:
+ - id: 5
+ value: 'i64 18446744073709551615'
+ alignment: 8
+ - id: 9
+ value: 'test-constant(1234)'
+ alignment: 8
+ isTargetSpecific: true
+ - id: 11
+ value: '%pair { i8 1, i64 2 }'
+machineFunctionInfo: { frame-base: 64, mode: test }
+jumpTable:
+ kind: block-address
+ entries:
+ - { id: 6, blocks: [ '%bb.0' ] }
+machineMetadataNodes:
+ - '!10 = distinct !{!0, !"machine", null}'
+body: |
+ bb.0.entry (%ir-block.entry):
+ %7:gpr64 = TEST_LOAD $g0 :: (load syncscope("agent") acquire 8 from %ir.slot, align 8)
+ TEST_GLOBAL @ordinary
+ TEST_BRANCH %bb.0.entry
+)";
+
+constexpr std::string_view HeterogeneousPointerDocument = R"(--- |
+ target datalayout = "e-p:64:64-p1:32:32"
+ target triple = "test64-unknown-linux-gnu"
+
+ define void @pointer_layout() {
+ ret void
+ }
+---
+name: pointer_layout
+constants:
+ - id: 0
+ value: 'ptr addrspace(1) null'
+body: |
+ bb.0:
+ %0:gpr64(p1) = TEST_COPY $g0
+)";
+
+void printDiagnostics(const rcc::mir::MIRDocumentParseResult &Parsed) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parsed.diagnostics())
+ std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n';
+}
+
+void testDocumentTransaction(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "MIR document target must be valid");
+ if (!Target)
+ return;
+
+ auto Parsed = rcc::mir::parseMIRDocument(*Target, "document.mir", std::string(ValidDocument));
+ Context.expect(Parsed.succeeded(), "complete MIR document must parse successfully");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+
+ const rcc::mir::MIRDocument *Document = Parsed.document();
+ Context.expect(
+ Document != nullptr && Document->embeddedIR() != nullptr &&
+ Document->unit().functionCount() == 1 && Document->functionDocumentInfo().size() == 1 &&
+ Document->syncScopes().size() == 1 && Document->syncScopes().front().Name == "agent",
+ "successful parsing must publish the complete document atomically");
+ if (Document == nullptr)
+ return;
+ const rcc::MachineFunction *Function = Document->unit().function("documented");
+ Context.expect(Function != nullptr, "parsed function must be queryable by name");
+ if (Function == nullptr)
+ return;
+
+ Context.expect(Function->registerInfo().virtualRegisters().size() == 1 &&
+ Function->registerInfo().liveIns().size() == 1 &&
+ Function->records().hasCalleeSavedRegisters(),
+ "register tables must publish into the common MachineIR model");
+ Context.expect(Function->frameInfo().liveObjectCount() == 2 &&
+ Function->frameInfo().state().StackSize == 32 &&
+ Function->frameInfo().state().SavePoints.size() == 1,
+ "frame records and stack objects must publish together");
+ const rcc::StackObjectDesc *StackDescription =
+ Function->frameInfo().object(rcc::FrameIndex::fromValue(1))->description();
+ Context.expect(StackDescription != nullptr && StackDescription->DebugInfo.has_value() &&
+ StackDescription->DebugInfo->Expression.has_value() &&
+ StackDescription->DebugInfo->Expression->expression() != nullptr &&
+ StackDescription->DebugInfo->Expression->expression()->elements().size() ==
+ 3 &&
+ StackDescription->DebugInfo->Location.has_value() &&
+ StackDescription->DebugInfo->Location->inlineLocation() != nullptr &&
+ Function->records().entryValues().size() == 1,
+ "stack and entry-value debug metadata must retain structured values");
+ const auto *TargetState = Function->getTargetInfo<DocumentTargetState>();
+ const rcc::MachineConstantPoolEntryDesc *TargetConstantDescription =
+ Function->constantPool().entry(rcc::ConstantPoolIndex::fromValue(1))->description();
+ const auto *TargetConstantValue =
+ TargetConstantDescription == nullptr
+ ? nullptr
+ : std::get_if<rcc::TargetConstant>(&TargetConstantDescription->Value);
+ Context.expect(Function->constantPool().liveEntryCount() == 3 &&
+ Function->jumpTableInfo().liveTableCount() == 1 &&
+ Function->metadata().nodes().size() == 1 && TargetState != nullptr &&
+ TargetState->frameBase() == 64 && TargetConstantValue != nullptr &&
+ TargetConstantValue->getIf<DocumentTargetConstant>() != nullptr &&
+ TargetConstantValue->getIf<DocumentTargetConstant>()->value() == 1234,
+ "constant, target-state, jump-table, and metadata tables must publish together");
+ Context.expect(Function->blockCount() == 1 && Function->allInstructions().size() == 3,
+ "the body must use the YAML predeclared text slots");
+
+ auto Printed = rcc::mir::printMIRDocument(*Document, *Target);
+ Context.expect(Printed.has_value(), "complete MIR document printing must succeed");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ return;
+ }
+ auto RoundTrip = rcc::mir::parseMIRDocument(*Target, "round-trip.mir", std::string(*Printed));
+ Context.expect(RoundTrip.succeeded(), "printed complete MIR must parse successfully");
+ if (!RoundTrip.succeeded()) {
+ std::cerr << *Printed;
+ printDiagnostics(RoundTrip);
+ return;
+ }
+ auto PrintedAgain = rcc::mir::printMIRDocument(*RoundTrip.document(), *Target);
+ Context.expect(PrintedAgain.has_value() && *PrintedAgain == *Printed,
+ "complete MIR document printing must be deterministic");
+
+ auto Readable = rcc::mir::printMIRDocument(
+ *Document, *Target,
+ rcc::mir::MIRDocumentPrinterOptions{.Mode = rcc::mir::MIPrintMode::Readable,
+ .EmitEmbeddedIR = true,
+ .DataLayout = {},
+ .DefaultPointerSizeInBits = 0});
+ Context.expect(Readable.has_value(), "readable MIR document printing must succeed");
+ if (!Readable)
+ return;
+ auto ReadableRoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "readable.mir", std::string(*Readable));
+ Context.expect(ReadableRoundTrip.succeeded(), "readable MIR output must parse successfully");
+ if (!ReadableRoundTrip.succeeded())
+ printDiagnostics(ReadableRoundTrip);
+
+ auto WithoutEmbeddedIR = rcc::mir::printMIRDocument(
+ *Document, *Target,
+ rcc::mir::MIRDocumentPrinterOptions{.Mode = rcc::mir::MIPrintMode::Complete,
+ .EmitEmbeddedIR = false,
+ .DataLayout = {},
+ .DefaultPointerSizeInBits = 0});
+ Context.expect(!WithoutEmbeddedIR && WithoutEmbeddedIR.error().message().find(
+ "requires embedded LLVM IR") != std::string_view::npos,
+ "printer must reject output that omits required embedded LLVM IR");
+}
+
+void testHeterogeneousPointerLayout(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "heterogeneous-layout target must be valid");
+ if (!Target)
+ return;
+
+ rcc::mir::MIRDocumentParserOptions ParserOptions;
+ ParserOptions.MachineInstructions.VerifyAfterParse = false;
+ ParserOptions.VerifyMachineFunctions = false;
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "heterogeneous-layout.mir",
+ std::string(HeterogeneousPointerDocument), ParserOptions);
+ Context.expect(Parsed.succeeded(), "heterogeneous pointer-layout MIR must parse successfully");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("pointer_layout");
+ auto PointerType = rcc::LLT::makePointer(1, 32);
+ const rcc::MachineConstantPoolEntry *Constant =
+ Function == nullptr ? nullptr
+ : Function->constantPool().entry(rcc::ConstantPoolIndex::fromValue(0));
+ const rcc::MachineConstantPoolEntryDesc *ConstantDescription =
+ Constant == nullptr ? nullptr : Constant->description();
+ Context.expect(Function != nullptr && PointerType.has_value() &&
+ Function->registerInfo().virtualRegisters().size() == 1 &&
+ Function->registerInfo().virtualRegisters().front().Type == *PointerType &&
+ ConstantDescription != nullptr && ConstantDescription->Size == 4,
+ "embedded data layout must determine p1 register and generic-constant widths");
+
+ rcc::mir::MIRDocumentPrinterOptions PrinterOptions;
+ PrinterOptions.EmitEmbeddedIR = false;
+ auto WithoutLayout = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(!WithoutLayout && WithoutLayout.error().message().find("printer DataLayout") !=
+ std::string_view::npos,
+ "omitting embedded IR must reject layout-dependent MIR without DataLayout");
+
+ PrinterOptions.DataLayout = "e-p:64:64-p1:32:32";
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(Printed.has_value() && Printed->find("p1:32") == std::string_view::npos &&
+ Printed->find("p1)") != std::string_view::npos,
+ "matching explicit DataLayout must emit canonical LLVM p1 MIR syntax");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ return;
+ }
+
+ rcc::mir::MIRDocumentParserOptions ReparseOptions;
+ ReparseOptions.DataLayout = PrinterOptions.DataLayout;
+ ReparseOptions.MachineInstructions.VerifyAfterParse = false;
+ ReparseOptions.VerifyMachineFunctions = false;
+ auto RoundTrip = rcc::mir::parseMIRDocument(*Target, "heterogeneous-layout-round-trip.mir",
+ std::string(*Printed), ReparseOptions);
+ const rcc::MachineFunction *RoundTripFunction =
+ RoundTrip.succeeded() ? RoundTrip.document()->unit().function("pointer_layout") : nullptr;
+ Context.expect(RoundTrip.succeeded() && RoundTripFunction != nullptr &&
+ RoundTripFunction->registerInfo().virtualRegisters().size() == 1 &&
+ RoundTripFunction->registerInfo().virtualRegisters().front().Type ==
+ *PointerType,
+ "explicit DataLayout must preserve p1 type when embedded IR is omitted");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+
+ PrinterOptions.DataLayout = "e-p:64:64-p1:64:64";
+ auto Mismatched = rcc::mir::printMIRDocument(*Parsed.document(), *Target, PrinterOptions);
+ Context.expect(!Mismatched &&
+ Mismatched.error().message().find("exactly match") != std::string_view::npos,
+ "mismatched output DataLayout must be rejected when embedded IR is omitted");
+}
+
+void expectReferenceFailure(TestContext &Context, const rcc::TargetDescription &Target,
+ std::string_view From, std::string_view To,
+ std::string_view ExpectedDiagnostic) {
+ std::string Invalid(ValidDocument);
+ const std::size_t Position = Invalid.find(From);
+ Context.expect(Position != std::string::npos,
+ "reference-validation test mutation must match the fixture");
+ if (Position == std::string::npos)
+ return;
+ Invalid.replace(Position, From.size(), To);
+ auto Parsed = rcc::mir::parseMIRDocument(Target, "reference.mir", std::move(Invalid));
+ const bool HasExpectedDiagnostic =
+ !Parsed.succeeded() && Parsed.document() == nullptr && Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find(ExpectedDiagnostic) != std::string::npos;
+ Context.expect(HasExpectedDiagnostic,
+ "invalid embedded IR references must fail with their semantic category");
+ if (!HasExpectedDiagnostic)
+ printDiagnostics(Parsed);
+}
+
+void testEmbeddedReferenceValidation(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "reference-validation target must be valid");
+ if (!Target)
+ return;
+
+ expectReferenceFailure(Context, *Target, "from %ir.slot", "from %ir.missing",
+ "undefined embedded LLVM IR value '%missing'");
+ expectReferenceFailure(Context, *Target, "TEST_GLOBAL @ordinary", "TEST_GLOBAL @missing",
+ "undefined embedded LLVM IR global 'missing'");
+ expectReferenceFailure(Context, *Target, "value: 'i64 18446744073709551615'",
+ "value: 'ptr @missing'",
+ "generic constant references an undefined embedded LLVM IR global");
+ expectReferenceFailure(Context, *Target, "value: 'i64 18446744073709551615'",
+ "value: 'ptr blockaddress(@documented, %missing)'",
+ "undefined embedded LLVM IR block 'missing'");
+ expectReferenceFailure(Context, *Target, "debug-info-location: '!2'", "debug-info-location: '!0'",
+ "debug location requires DILocation metadata");
+}
+
+void expectNoEmbeddedIRReferenceFailure(TestContext &Context, const rcc::TargetDescription &Target,
+ std::string_view DocumentText,
+ std::string_view ExpectedDiagnostic) {
+ auto Parsed =
+ rcc::mir::parseMIRDocument(Target, "no-embedded-reference.mir", std::string(DocumentText));
+ const bool Rejected =
+ !Parsed.succeeded() && Parsed.document() == nullptr && Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find(ExpectedDiagnostic) != std::string::npos;
+ Context.expect(Rejected, "IR-specific MIR references without embedded LLVM IR must be rejected");
+ if (!Rejected)
+ printDiagnostics(Parsed);
+}
+
+void testReferencesWithoutEmbeddedIR(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "no-embedded-reference target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view ExternalDocument = R"(---
+name: external_symbol
+body: |
+ bb.0:
+ TEST_GLOBAL &runtime_symbol
+)";
+ auto External =
+ rcc::mir::parseMIRDocument(*Target, "external-symbol.mir", std::string(ExternalDocument));
+ Context.expect(External.succeeded(),
+ "external symbols must remain usable without embedded LLVM IR");
+ if (!External.succeeded()) {
+ printDiagnostics(External);
+ return;
+ }
+ auto Printed = rcc::mir::printMIRDocument(*External.document(), *Target);
+ Context.expect(Printed.has_value(),
+ "external-symbol MIR without embedded IR must remain printable");
+ if (!Printed) {
+ std::cerr << "error: " << Printed.error().message() << '\n';
+ } else {
+ auto RoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "external-round-trip.mir", std::move(*Printed));
+ Context.expect(RoundTrip.succeeded(),
+ "external-symbol MIR without embedded IR must remain reparseable");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+ }
+
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: global_reference
+body: |
+ bb.0:
+ TEST_GLOBAL @ordinary
+)",
+ "requires embedded LLVM IR to resolve global");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: ir_block_reference
+body: |
+ bb.0 (%ir-block.entry):
+)",
+ "machine basic block requires embedded LLVM IR");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: alloca_reference
+stack:
+ - { id: 0, name: slot, size: 8, alignment: 8 }
+body: |
+ bb.0:
+)",
+ "stack object requires embedded LLVM IR");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: ir_value_reference
+body: |
+ bb.0:
+ $g1 = TEST_LOAD $g0 :: (load 8 from %ir.slot, align 8)
+)",
+ "requires embedded LLVM IR to resolve IR value");
+ expectNoEmbeddedIRReferenceFailure(
+ Context, *Target, R"(---
+name: constant_reference
+constants:
+ - { id: 0, value: 'i64 ptrtoint (ptr @ordinary to i64)', alignment: 8 }
+body: |
+ bb.0:
+)",
+ "generic constant requires embedded LLVM IR to resolve global");
+ expectNoEmbeddedIRReferenceFailure(Context, *Target, R"(---
+name: block_address_reference
+constants:
+ - { id: 0, value: 'i64 ptrtoint (ptr blockaddress(@function, %entry) to i64)', alignment: 8 }
+body: |
+ bb.0:
+)",
+ "generic constant blockaddress requires embedded LLVM IR");
+}
+
+void testPrinterRejectsUnresolvedProgrammaticReferences(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "programmatic-reference target must be valid");
+ if (!Target)
+ return;
+
+ rcc::CompilationUnit Unit;
+ auto Function = Unit.createMachineFunction("programmatic_reference");
+ Context.expect(Function.has_value(), "programmatic document function creation must succeed");
+ if (!Function)
+ return;
+ auto Block = (*Function)->createBasicBlock();
+ auto Appended = Block ? (*Function)->appendBlock(**Block) : rcc::Expected<void>{};
+ Context.expect(Block && Appended.has_value(),
+ "programmatic document block creation must succeed");
+ if (!Block || !Appended)
+ return;
+
+ rcc::MachineIRBuilder Builder(**Function, *Target);
+ auto Insertion = Builder.setInsertPoint(**Block);
+ auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(GlobalOpcode));
+ auto Added = Pending ? Pending->addGlobalSymbol("ordinary") : rcc::Expected<void>{};
+ Context.expect(Insertion && Pending && Added.has_value(),
+ "programmatic global-reference instruction construction must succeed");
+ if (!Insertion || !Pending || !Added)
+ return;
+ auto Built = Pending->build();
+ Context.expect(Built.has_value(), "programmatic global-reference instruction must build");
+ if (!Built)
+ return;
+
+ rcc::mir::MIRDocument Document(std::move(Unit));
+ auto Printed = rcc::mir::printMIRDocument(Document, *Target);
+ const bool Rejected =
+ !Printed && Printed.error().message().find("requires embedded LLVM IR to resolve global") !=
+ std::string_view::npos;
+ Context.expect(Rejected,
+ "printer must reject programmatic IR-specific references without embedded IR");
+ if (!Rejected && !Printed)
+ std::cerr << "error: " << Printed.error().message() << '\n';
+}
+
+void testMetadataSlotPriorityAndCanonicalization(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "metadata-priority target must be valid");
+ if (!Target)
+ return;
+
+ std::string CollisionDocument(ValidDocument);
+ constexpr std::string_view Original = " - '!10 = distinct !{!0, !\"machine\", null}'";
+ constexpr std::string_view Replacement = " - '!0 = distinct !{!0, !\"machine\", null}'";
+ const std::size_t Position = CollisionDocument.find(Original);
+ Context.expect(Position != std::string::npos,
+ "metadata-priority test mutation must match the fixture");
+ if (Position == std::string::npos)
+ return;
+ CollisionDocument.replace(Position, Original.size(), Replacement);
+
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "metadata-priority.mir", std::move(CollisionDocument));
+ Context.expect(Parsed.succeeded(),
+ "machine metadata may reuse an embedded LLVM IR metadata slot");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("documented");
+ const auto Nodes = Function == nullptr ? std::span<const rcc::MachineMetadataNode>{}
+ : Function->metadata().nodes();
+ const rcc::MachineMetadataReference *Reference =
+ Nodes.size() == 1 && Nodes.front().Elements.size() == 3
+ ? Nodes.front().Elements.front().reference()
+ : nullptr;
+ Context.expect(Function != nullptr && Nodes.size() == 1 && Nodes.front().Slot == 0 &&
+ Reference != nullptr &&
+ *Reference == rcc::MachineMetadataReference::embeddedIR(0),
+ "bare metadata references must prefer embedded LLVM IR slots");
+
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target);
+ const bool Canonicalized =
+ Printed && Printed->find("'!3 = distinct !{!0, !\"machine\", null}'") != std::string::npos;
+ Context.expect(Canonicalized,
+ "printer must assign a noncolliding metadata slot for local metadata");
+ if (!Canonicalized)
+ return;
+ auto RoundTrip =
+ rcc::mir::parseMIRDocument(*Target, "metadata-priority-round-trip.mir", std::move(*Printed));
+ Context.expect(RoundTrip.succeeded(),
+ "canonicalized metadata slots must survive a document round-trip");
+ if (!RoundTrip.succeeded())
+ printDiagnostics(RoundTrip);
+}
+
+void testFixedWidthDocumentFields(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "fixed-width field target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view Original = "maxCallFrameSize: 24";
+ std::string BoundaryDocument(ValidDocument);
+ const std::size_t BoundaryPosition = BoundaryDocument.find(Original);
+ Context.expect(BoundaryPosition != std::string::npos,
+ "fixed-width boundary test mutation must match the fixture");
+ if (BoundaryPosition == std::string::npos)
+ return;
+ BoundaryDocument.replace(BoundaryPosition, Original.size(), "maxCallFrameSize: 4294967295");
+ auto Boundary =
+ rcc::mir::parseMIRDocument(*Target, "frame-boundary.mir", std::move(BoundaryDocument));
+ Context.expect(Boundary.succeeded(),
+ "maximum 32-bit maxCallFrameSize must remain representable in MIR");
+ if (Boundary.succeeded()) {
+ auto Printed = rcc::mir::printMIRDocument(*Boundary.document(), *Target);
+ Context.expect(Printed && Printed->find("maxCallFrameSize: 4294967295") != std::string::npos,
+ "maximum 32-bit maxCallFrameSize must print without narrowing");
+ } else {
+ printDiagnostics(Boundary);
+ }
+
+ std::string OversizedDocument(ValidDocument);
+ const std::size_t OversizedPosition = OversizedDocument.find(Original);
+ if (OversizedPosition == std::string::npos)
+ return;
+ OversizedDocument.replace(OversizedPosition, Original.size(), "maxCallFrameSize: 4294967296");
+ auto Oversized =
+ rcc::mir::parseMIRDocument(*Target, "frame-oversized.mir", std::move(OversizedDocument));
+ const bool Rejected =
+ !Oversized.succeeded() && Oversized.document() == nullptr &&
+ Oversized.diagnostics().size() == 1 &&
+ Oversized.diagnostics().front().Message.find("maximum call-frame size") != std::string::npos;
+ Context.expect(Rejected,
+ "maxCallFrameSize values beyond the fixed 32-bit MIR range must be rejected");
+ if (!Rejected)
+ printDiagnostics(Oversized);
+}
+
+void testAtomicFailure(TestContext &Context) {
+ auto Target = createTarget();
+ if (!Target)
+ return;
+ std::string Invalid(ValidDocument);
+ Invalid.append("---\nname: second\nunknownField: true\n");
+ auto Parsed = rcc::mir::parseMIRDocument(*Target, "atomic.mir", std::move(Invalid));
+ Context.expect(!Parsed.succeeded() && Parsed.document() == nullptr &&
+ Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Message.find("unknown machine function key") !=
+ std::string::npos,
+ "a later function failure must prevent publication of the entire document");
+}
+
+void testLLVMPropertySemantics(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "property-semantics target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view PropertyDocument = R"(---
+name: property_state
+failedISel: true
+failsVerification: true
+body: |
+ bb.0:
+)";
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "properties.mir", std::string(PropertyDocument));
+ Context.expect(Parsed.succeeded(), "LLVM MIR pipeline properties must parse");
+ if (!Parsed.succeeded()) {
+ printDiagnostics(Parsed);
+ return;
+ }
+ const rcc::MachineFunction *Function = Parsed.document()->unit().function("property_state");
+ Context.expect(Function != nullptr &&
+ Function->properties().has(rcc::MachineFunctionProperty::FailedISel) &&
+ Function->properties().has(rcc::MachineFunctionProperty::FailsVerification) &&
+ Function->compilationStatus().state() == rcc::MachineCompilationState::Success,
+ "failedISel and failsVerification must be pipeline properties, not run failures");
+
+ auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target);
+ Context.expect(Printed.has_value() && Printed->find("failedISel: true") != std::string::npos &&
+ Printed->find("failsVerification: true") != std::string::npos,
+ "LLVM MIR pipeline properties must print deterministically");
+
+ constexpr std::string_view InvalidDocument = R"(---
+name: still_checked
+failsVerification: true
+noVRegs: true
+registers:
+ - { id: 1, class: gpr64 }
+body: |
+ bb.0:
+)";
+ auto Invalid =
+ rcc::mir::parseMIRDocument(*Target, "properties-invalid.mir", std::string(InvalidDocument));
+ const bool Rejected = !Invalid.succeeded() && Invalid.document() == nullptr &&
+ Invalid.diagnostics().size() == 1 &&
+ Invalid.diagnostics().front().Message.find("noVRegs") != std::string::npos;
+ Context.expect(Rejected, "failsVerification must not bypass RCC's mandatory input validation");
+ if (!Rejected)
+ printDiagnostics(Invalid);
+}
+
+void testBodyDiagnosticLocation(TestContext &Context) {
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "body-diagnostic target must be valid");
+ if (!Target)
+ return;
+
+ constexpr std::string_view InvalidDocument = R"(---
+name: body_diagnostic
+body: |
+ bb.0:
+ UNKNOWN_OPCODE
+)";
+ const std::size_t ExpectedOffset = InvalidDocument.find("UNKNOWN_OPCODE");
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, "body-diagnostic.mir", std::string(InvalidDocument));
+ const bool PointsAtToken = !Parsed.succeeded() && Parsed.document() == nullptr &&
+ Parsed.diagnostics().size() == 1 &&
+ Parsed.diagnostics().front().Category == "mir-body" &&
+ Parsed.diagnostics().front().Range.Begin.Offset == ExpectedOffset;
+ Context.expect(PointsAtToken,
+ "body parser errors must retain the source position of the failing token");
+ if (!PointsAtToken)
+ printDiagnostics(Parsed);
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testDocumentTransaction(Context);
+ testHeterogeneousPointerLayout(Context);
+ testEmbeddedReferenceValidation(Context);
+ testReferencesWithoutEmbeddedIR(Context);
+ testPrinterRejectsUnresolvedProgrammaticReferences(Context);
+ testMetadataSlotPriorityAndCanonicalization(Context);
+ testFixedWidthDocumentFields(Context);
+ testAtomicFailure(Context);
+ testLLVMPropertySemantics(Context);
+ testBodyDiagnosticLocation(Context);
+ return Context.result();
+}