From 6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Tue, 11 Aug 2026 17:37:42 +0800 Subject: [MIR] Accept fixed-baseline LLVM syntax --- backend/include/rcc/CodeGen/InlineAsm.h | 314 +++++++++++++++ backend/include/rcc/CodeGen/MachineIRTypes.h | 2 +- backend/include/rcc/CodeGen/MachineMemOperand.h | 9 +- backend/include/rcc/CodeGen/MachineOperand.h | 1 + backend/include/rcc/CodeGen/MachineRegisterInfo.h | 2 + backend/include/rcc/CodeGen/TargetCodeGen.h | 5 + backend/include/rcc/MIR/EmbeddedIR.h | 9 + backend/include/rcc/MIR/MIDebugSyntax.h | 9 + backend/include/rcc/MIR/MIParser.h | 1 + backend/lib/AsmPrinter/AsmPrinter.cpp | 3 +- backend/lib/CodeGen/CMakeLists.txt | 1 + backend/lib/CodeGen/CompilationUnit.cpp | 39 +- backend/lib/CodeGen/InstructionDescriptor.cpp | 8 +- backend/lib/CodeGen/MachineFunction.cpp | 2 - backend/lib/CodeGen/MachineFunctionCloner.cpp | 8 + backend/lib/CodeGen/MachineMemOperand.cpp | 40 +- backend/lib/CodeGen/MachineOperand.cpp | 59 +-- backend/lib/CodeGen/MachinePseudoExpansion.cpp | 22 +- backend/lib/CodeGen/MachineRegisterInfo.cpp | 1 + backend/lib/CodeGen/MachineVerifier.cpp | 58 ++- backend/lib/MIR/EmbeddedIR.cpp | 121 +++++- backend/lib/MIR/LLVMConstantSyntax.cpp | 279 +++++++++++++- backend/lib/MIR/MIDebugSyntax.cpp | 179 ++++++++- backend/lib/MIR/MIParser.cpp | 425 ++++++++++----------- backend/lib/MIR/MIPrinter.cpp | 351 +++++++++++------ backend/lib/MIR/MIRDataParser.cpp | 2 +- backend/lib/MIR/MIRDocumentParser.cpp | 46 ++- backend/lib/MIR/MIRDocumentParser.h | 4 +- backend/lib/MIR/MIRDocumentPrinter.cpp | 261 +++++++++++-- backend/lib/MIR/MIRDocumentReferenceValidation.cpp | 18 +- backend/lib/MIR/MIRFrameParser.cpp | 12 +- backend/lib/MIR/MIRFunctionParser.cpp | 50 ++- backend/lib/MIR/MIRFunctionRecordsParser.cpp | 68 +++- backend/lib/MIR/MIRMetadataParser.cpp | 242 +++++++----- backend/lib/MIR/MIRMetadataTextParser.cpp | 10 +- backend/lib/MIR/MIRMetadataTextParser.h | 4 + backend/lib/Support/TextDiagnostic.cpp | 7 +- backend/lib/Support/YAML.cpp | 40 +- backend/lib/Tooling/PipelineDriver.cpp | 2 + backend/tools/MIRTool/MIRToolMain.cpp | 9 +- backend/tools/MIRTool/MIRToolOperationsTest.cmake | 17 +- backend/unittests/CodeGen/CompilationUnitTest.cpp | 26 +- backend/unittests/CodeGen/MachineDebugInfoTest.cpp | 35 +- .../CodeGen/MachineFunctionRecordsTest.cpp | 6 +- .../unittests/CodeGen/MachineValueTypesTest.cpp | 35 ++ backend/unittests/MIR/LLVMConstantSyntaxTest.cpp | 28 +- backend/unittests/MIR/MILexerTest.cpp | 2 +- backend/unittests/MIR/MIParserTest.cpp | 100 +++++ backend/unittests/MIR/MIRDocumentTest.cpp | 87 ++++- backend/unittests/Support/TextDiagnosticTest.cpp | 2 +- backend/unittests/Support/YAMLTest.cpp | 1 + backend/unittests/Tooling/MIRReducerFuzzerTest.cpp | 2 +- backend/unittests/Tooling/PipelineDriverTest.cpp | 15 + 53 files changed, 2429 insertions(+), 650 deletions(-) create mode 100644 backend/include/rcc/CodeGen/InlineAsm.h (limited to 'backend') diff --git a/backend/include/rcc/CodeGen/InlineAsm.h b/backend/include/rcc/CodeGen/InlineAsm.h new file mode 100644 index 0000000..492161f --- /dev/null +++ b/backend/include/rcc/CodeGen/InlineAsm.h @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_INLINEASM_H +#define RCC_CODEGEN_INLINEASM_H + +#include +#include +#include +#include +#include +#include + +namespace rcc { + +inline constexpr std::size_t InlineAsmStringOperandIndex = 0; +inline constexpr std::size_t InlineAsmExtraInfoOperandIndex = 1; +inline constexpr std::size_t InlineAsmFirstConstraintOperandIndex = 2; + +enum class InlineAsmExtraInfoFlag : std::uint32_t { + HasSideEffects = 1U, + AlignStack = 2U, + IntelDialect = 4U, + MayLoad = 8U, + MayStore = 16U, + Convergent = 32U, + MayUnwind = 64U, +}; + +class InlineAsmExtraInfo final { +public: + explicit constexpr InlineAsmExtraInfo(std::uint32_t RawValue = 0) noexcept + : Value(RawValue) {} + + [[nodiscard]] constexpr std::uint32_t rawValue() const noexcept { + return Value; + } + + [[nodiscard]] constexpr bool has(InlineAsmExtraInfoFlag Flag) const noexcept { + return (Value & static_cast(Flag)) != 0; + } + + constexpr void set(InlineAsmExtraInfoFlag Flag) noexcept { + Value |= static_cast(Flag); + } + + [[nodiscard]] static constexpr std::optional + namedFlagBits(std::string_view Name) noexcept { + if (Name == "sideeffect") + return static_cast(InlineAsmExtraInfoFlag::HasSideEffects); + if (Name == "mayload") + return static_cast(InlineAsmExtraInfoFlag::MayLoad); + if (Name == "maystore") + return static_cast(InlineAsmExtraInfoFlag::MayStore); + if (Name == "isconvergent") + return static_cast(InlineAsmExtraInfoFlag::Convergent); + if (Name == "alignstack") + return static_cast(InlineAsmExtraInfoFlag::AlignStack); + if (Name == "unwind") + return static_cast(InlineAsmExtraInfoFlag::MayUnwind); + if (Name == "attdialect") + return 0; + if (Name == "inteldialect") + return static_cast(InlineAsmExtraInfoFlag::IntelDialect); + return std::nullopt; + } + + [[nodiscard]] std::vector names() const { + std::vector Result; + Result.reserve(7); + if (has(InlineAsmExtraInfoFlag::HasSideEffects)) + Result.push_back("sideeffect"); + if (has(InlineAsmExtraInfoFlag::MayLoad)) + Result.push_back("mayload"); + if (has(InlineAsmExtraInfoFlag::MayStore)) + Result.push_back("maystore"); + if (has(InlineAsmExtraInfoFlag::Convergent)) + Result.push_back("isconvergent"); + if (has(InlineAsmExtraInfoFlag::AlignStack)) + Result.push_back("alignstack"); + if (has(InlineAsmExtraInfoFlag::MayUnwind)) + Result.push_back("unwind"); + Result.push_back(has(InlineAsmExtraInfoFlag::IntelDialect) ? "inteldialect" : "attdialect"); + return Result; + } + + friend constexpr bool operator==(InlineAsmExtraInfo Left, + InlineAsmExtraInfo Right) noexcept = default; + +private: + std::uint32_t Value = 0; +}; + +enum class InlineAsmOperandKind : std::uint8_t { + RegisterUse = 1, + RegisterDef = 2, + RegisterDefEarlyClobber = 3, + Clobber = 4, + Immediate = 5, + Memory = 6, + Function = 7, +}; + +enum class InlineAsmMemoryConstraint : std::uint8_t { + Unknown = 0, + ES, + I, + K, + M, + O, + V, + A, + Q, + R, + S, + T, + UM, + UN, + UQ, + US, + UT, + UV, + UY, + X, + Z, + ZB, + ZC, + ZY, + P, + ZQ, + ZR, + ZS, + ZT, +}; + +struct InlineAsmMemoryConstraintName final { + std::string_view Name; + InlineAsmMemoryConstraint Constraint = InlineAsmMemoryConstraint::Unknown; +}; + +inline constexpr std::array InlineAsmMemoryConstraintNames{ + InlineAsmMemoryConstraintName{"es", InlineAsmMemoryConstraint::ES}, + InlineAsmMemoryConstraintName{"i", InlineAsmMemoryConstraint::I}, + InlineAsmMemoryConstraintName{"k", InlineAsmMemoryConstraint::K}, + InlineAsmMemoryConstraintName{"m", InlineAsmMemoryConstraint::M}, + InlineAsmMemoryConstraintName{"o", InlineAsmMemoryConstraint::O}, + InlineAsmMemoryConstraintName{"v", InlineAsmMemoryConstraint::V}, + InlineAsmMemoryConstraintName{"A", InlineAsmMemoryConstraint::A}, + InlineAsmMemoryConstraintName{"Q", InlineAsmMemoryConstraint::Q}, + InlineAsmMemoryConstraintName{"R", InlineAsmMemoryConstraint::R}, + InlineAsmMemoryConstraintName{"S", InlineAsmMemoryConstraint::S}, + InlineAsmMemoryConstraintName{"T", InlineAsmMemoryConstraint::T}, + InlineAsmMemoryConstraintName{"Um", InlineAsmMemoryConstraint::UM}, + InlineAsmMemoryConstraintName{"Un", InlineAsmMemoryConstraint::UN}, + InlineAsmMemoryConstraintName{"Uq", InlineAsmMemoryConstraint::UQ}, + InlineAsmMemoryConstraintName{"Us", InlineAsmMemoryConstraint::US}, + InlineAsmMemoryConstraintName{"Ut", InlineAsmMemoryConstraint::UT}, + InlineAsmMemoryConstraintName{"Uv", InlineAsmMemoryConstraint::UV}, + InlineAsmMemoryConstraintName{"Uy", InlineAsmMemoryConstraint::UY}, + InlineAsmMemoryConstraintName{"X", InlineAsmMemoryConstraint::X}, + InlineAsmMemoryConstraintName{"Z", InlineAsmMemoryConstraint::Z}, + InlineAsmMemoryConstraintName{"ZB", InlineAsmMemoryConstraint::ZB}, + InlineAsmMemoryConstraintName{"ZC", InlineAsmMemoryConstraint::ZC}, + InlineAsmMemoryConstraintName{"Zy", InlineAsmMemoryConstraint::ZY}, + InlineAsmMemoryConstraintName{"p", InlineAsmMemoryConstraint::P}, + InlineAsmMemoryConstraintName{"ZQ", InlineAsmMemoryConstraint::ZQ}, + InlineAsmMemoryConstraintName{"ZR", InlineAsmMemoryConstraint::ZR}, + InlineAsmMemoryConstraintName{"ZS", InlineAsmMemoryConstraint::ZS}, + InlineAsmMemoryConstraintName{"ZT", InlineAsmMemoryConstraint::ZT}, +}; + +class InlineAsmOperandFlag final { +public: + constexpr InlineAsmOperandFlag(InlineAsmOperandKind Kind, std::uint32_t OperandCount = 1) noexcept + : Value(static_cast(Kind) | + ((OperandCount & OperandCountMask) << OperandCountShift)) {} + + explicit constexpr InlineAsmOperandFlag(std::uint32_t RawValue) noexcept : Value(RawValue) {} + + [[nodiscard]] constexpr std::uint32_t rawValue() const noexcept { + return Value; + } + + [[nodiscard]] constexpr InlineAsmOperandKind kind() const noexcept { + return static_cast(Value & KindMask); + } + + [[nodiscard]] constexpr std::uint32_t operandCount() const noexcept { + return (Value >> OperandCountShift) & OperandCountMask; + } + + [[nodiscard]] constexpr bool isMatched() const noexcept { + return (Value & MatchedMask) != 0; + } + + [[nodiscard]] constexpr std::optional matchingOperand() const noexcept { + return isMatched() ? std::optional((Value >> PayloadShift) & MatchedOperandMask) + : std::nullopt; + } + + [[nodiscard]] constexpr std::optional registerClass() const noexcept { + if (isMatched()) + return std::nullopt; + const std::uint32_t Encoded = (Value >> PayloadShift) & RegisterClassMask; + return Encoded == 0 ? std::nullopt : std::optional(Encoded); + } + + [[nodiscard]] constexpr InlineAsmMemoryConstraint memoryConstraint() const noexcept { + if (isMatched()) + return InlineAsmMemoryConstraint::Unknown; + return static_cast((Value >> PayloadShift) & MatchedOperandMask); + } + + [[nodiscard]] constexpr bool setMatchingOperand(std::uint32_t OperandNumber) noexcept { + if (OperandNumber > MatchedOperandMask) + return false; + Value &= ~(MatchedMask | (MatchedOperandMask << PayloadShift)); + Value |= MatchedMask | (OperandNumber << PayloadShift); + return true; + } + + [[nodiscard]] constexpr bool setRegisterClass(std::uint32_t RegisterClass) noexcept { + if (RegisterClass == 0 || RegisterClass > RegisterClassMask || isMatched()) + return false; + Value &= ~(RegisterClassMask << PayloadShift); + Value |= RegisterClass << PayloadShift; + return true; + } + + [[nodiscard]] constexpr bool + setMemoryConstraint(InlineAsmMemoryConstraint Constraint) noexcept { + const std::uint32_t Encoded = static_cast(Constraint); + if (Encoded == 0 || Encoded > MatchedOperandMask || isMatched()) + return false; + Value &= ~(MatchedOperandMask << PayloadShift); + Value |= Encoded << PayloadShift; + return true; + } + + [[nodiscard]] static constexpr std::optional + kindForName(std::string_view Name) noexcept { + if (Name == "regdef") + return InlineAsmOperandKind::RegisterDef; + if (Name == "reguse") + return InlineAsmOperandKind::RegisterUse; + if (Name == "regdef-ec") + return InlineAsmOperandKind::RegisterDefEarlyClobber; + if (Name == "clobber") + return InlineAsmOperandKind::Clobber; + if (Name == "imm") + return InlineAsmOperandKind::Immediate; + if (Name == "mem") + return InlineAsmOperandKind::Memory; + return std::nullopt; + } + + [[nodiscard]] static constexpr std::string_view + kindName(InlineAsmOperandKind Kind) noexcept { + switch (Kind) { + case InlineAsmOperandKind::RegisterUse: + return "reguse"; + case InlineAsmOperandKind::RegisterDef: + return "regdef"; + case InlineAsmOperandKind::RegisterDefEarlyClobber: + return "regdef-ec"; + case InlineAsmOperandKind::Clobber: + return "clobber"; + case InlineAsmOperandKind::Immediate: + return "imm"; + case InlineAsmOperandKind::Memory: + case InlineAsmOperandKind::Function: + return "mem"; + } + return {}; + } + + [[nodiscard]] static constexpr std::optional + memoryConstraintForName(std::string_view Name) noexcept { + for (const InlineAsmMemoryConstraintName &Entry : InlineAsmMemoryConstraintNames) { + if (Entry.Name == Name) + return Entry.Constraint; + } + return std::nullopt; + } + + [[nodiscard]] static constexpr std::optional + memoryConstraintName(InlineAsmMemoryConstraint Constraint) noexcept { + for (const InlineAsmMemoryConstraintName &Entry : InlineAsmMemoryConstraintNames) { + if (Entry.Constraint == Constraint) + return Entry.Name; + } + return std::nullopt; + } + + [[nodiscard]] constexpr bool isRegisterKind() const noexcept { + return kind() == InlineAsmOperandKind::RegisterUse || + kind() == InlineAsmOperandKind::RegisterDef || + kind() == InlineAsmOperandKind::RegisterDefEarlyClobber; + } + +private: + static constexpr std::uint32_t KindMask = 0x7U; + static constexpr std::uint32_t OperandCountShift = 3U; + static constexpr std::uint32_t OperandCountMask = 0x1FFFU; + static constexpr std::uint32_t PayloadShift = 16U; + static constexpr std::uint32_t MatchedOperandMask = 0x7FFFU; + static constexpr std::uint32_t RegisterClassMask = 0x3FFFU; + static constexpr std::uint32_t MatchedMask = 0x80000000U; + + std::uint32_t Value = 0; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_INLINEASM_H diff --git a/backend/include/rcc/CodeGen/MachineIRTypes.h b/backend/include/rcc/CodeGen/MachineIRTypes.h index 45b5ecf..e71b80e 100644 --- a/backend/include/rcc/CodeGen/MachineIRTypes.h +++ b/backend/include/rcc/CodeGen/MachineIRTypes.h @@ -218,7 +218,7 @@ public: BranchProbability Right) noexcept = default; private: - static constexpr std::uint32_t MaxRawValue = ~std::uint32_t{0}; + static constexpr std::uint32_t MaxRawValue = std::uint32_t{1} << 31U; explicit constexpr BranchProbability(std::uint32_t RawValue) noexcept : RawValue(RawValue) {} diff --git a/backend/include/rcc/CodeGen/MachineMemOperand.h b/backend/include/rcc/CodeGen/MachineMemOperand.h index a9e19a6..c5c1357 100644 --- a/backend/include/rcc/CodeGen/MachineMemOperand.h +++ b/backend/include/rcc/CodeGen/MachineMemOperand.h @@ -3,6 +3,7 @@ #ifndef RCC_CODEGEN_MACHINEMEMOPERAND_H #define RCC_CODEGEN_MACHINEMEMOPERAND_H +#include "rcc/CodeGen/GenericConstant.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineIRTypes.h" #include "rcc/CodeGen/MachineMetadata.h" @@ -140,6 +141,7 @@ public: GlobalSymbol, ExternalSymbol, IRValue, + IRConstant, Stack, GOT, ConstantPoolPseudo, @@ -157,6 +159,7 @@ public: [[nodiscard]] static MemoryBase globalSymbol(std::string Name); [[nodiscard]] static MemoryBase externalSymbol(std::string Name); [[nodiscard]] static MemoryBase irValue(std::string Name); + [[nodiscard]] static Expected irConstant(GenericConstantData Constant); [[nodiscard]] static MemoryBase stack(); [[nodiscard]] static MemoryBase got(); [[nodiscard]] static MemoryBase constantPoolPseudo(); @@ -173,12 +176,13 @@ public: [[nodiscard]] std::optional constantPoolIndex() const noexcept; [[nodiscard]] std::optional jumpTableIndex() const noexcept; [[nodiscard]] std::optional symbolName() const noexcept; + [[nodiscard]] const GenericConstantData *constantValue() const noexcept; friend bool operator==(const MemoryBase &Left, const MemoryBase &Right) = default; private: - using Storage = - std::variant; + using Storage = std::variant; MemoryBase(Kind BaseKind, Storage Value) : BaseKind(BaseKind), Value(std::move(Value)) {} @@ -193,6 +197,7 @@ struct MachineMemOperandDesc final { AtomicOrdering FailureOrdering = AtomicOrdering::NotAtomic; SyncScopeID SyncScope; MemorySize Size; + std::optional MemoryType; Alignment Align; std::optional BaseAlign; std::uint32_t AddressSpace = 0; diff --git a/backend/include/rcc/CodeGen/MachineOperand.h b/backend/include/rcc/CodeGen/MachineOperand.h index 986ba0e..a7feafa 100644 --- a/backend/include/rcc/CodeGen/MachineOperand.h +++ b/backend/include/rcc/CodeGen/MachineOperand.h @@ -83,6 +83,7 @@ public: [[nodiscard]] bool isZero() const noexcept; [[nodiscard]] bool isNegative() const noexcept; [[nodiscard]] std::string toUnsignedDecimal() const; + [[nodiscard]] std::string toSignedDecimal() const; [[nodiscard]] Expected verify() const; friend bool operator==(const IntegerImmediate &Left, const IntegerImmediate &Right) = default; diff --git a/backend/include/rcc/CodeGen/MachineRegisterInfo.h b/backend/include/rcc/CodeGen/MachineRegisterInfo.h index 14e50a3..cc9026e 100644 --- a/backend/include/rcc/CodeGen/MachineRegisterInfo.h +++ b/backend/include/rcc/CodeGen/MachineRegisterInfo.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,7 @@ private: }; struct VirtualRegisterInfo final { + std::optional Name; std::optional Type; std::optional Bank; std::optional RegClass; diff --git a/backend/include/rcc/CodeGen/TargetCodeGen.h b/backend/include/rcc/CodeGen/TargetCodeGen.h index f2aac5d..34a6de4 100644 --- a/backend/include/rcc/CodeGen/TargetCodeGen.h +++ b/backend/include/rcc/CodeGen/TargetCodeGen.h @@ -89,6 +89,11 @@ public: const TargetDescription &) const { return {}; } + + [[nodiscard]] virtual Expected + constrainSelectedInstruction(MachineInstr &, MachineFunction &, const TargetDescription &) const { + return {}; + } }; class TargetSSAInfo { diff --git a/backend/include/rcc/MIR/EmbeddedIR.h b/backend/include/rcc/MIR/EmbeddedIR.h index 522a217..99f1493 100644 --- a/backend/include/rcc/MIR/EmbeddedIR.h +++ b/backend/include/rcc/MIR/EmbeddedIR.h @@ -6,6 +6,7 @@ #include "rcc/Support/TextDiagnostic.h" #include +#include #include #include #include @@ -52,6 +53,10 @@ public: return IsDefinition; } + [[nodiscard]] bool isUnnamed() const noexcept { + return IsUnnamed; + } + [[nodiscard]] std::span blocks() const noexcept { return Blocks; } @@ -80,9 +85,12 @@ private: std::string Name; TextRange NameRange; bool IsDefinition = false; + bool IsUnnamed = false; + std::uint64_t NextUnnamedSlot = 0; std::vector Blocks; std::vector Allocas; std::vector Values; + std::vector LocalReferences; std::unordered_map> BlockNameToIndex; std::unordered_map> AllocaNameToIndex; std::unordered_map> ValueNameToIndex; @@ -131,6 +139,7 @@ public: } [[nodiscard]] const EmbeddedIRFunction *function(std::string_view Name) const noexcept; + [[nodiscard]] const EmbeddedIRFunction *unnamedFunction(std::size_t Ordinal) const noexcept; [[nodiscard]] const EmbeddedIRReference *globalObject(std::string_view Name) const noexcept; [[nodiscard]] const EmbeddedIRMetadataDefinition *metadata(std::string_view Name) const noexcept; [[nodiscard]] const EmbeddedIRTypeDefinition * diff --git a/backend/include/rcc/MIR/MIDebugSyntax.h b/backend/include/rcc/MIR/MIDebugSyntax.h index a8427dc..4952128 100644 --- a/backend/include/rcc/MIR/MIDebugSyntax.h +++ b/backend/include/rcc/MIR/MIDebugSyntax.h @@ -5,6 +5,8 @@ #include #include +#include +#include #include namespace rcc::mir { @@ -12,6 +14,13 @@ namespace rcc::mir { [[nodiscard]] std::optional parseDIExpressionOpcodeName(std::string_view Name) noexcept; +[[nodiscard]] std::optional +parseDIExpressionAttributeEncodingName(std::string_view Name) noexcept; + +[[nodiscard]] std::string invalidDIExpressionElementMessage(std::string_view Name); + +[[nodiscard]] std::string formatDIExpression(std::span Elements); + } // 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 116a97f..16af534 100644 --- a/backend/include/rcc/MIR/MIParser.h +++ b/backend/include/rcc/MIR/MIParser.h @@ -65,6 +65,7 @@ struct MIParserContext final { std::vector JumpTables; std::vector Metadata; std::vector SyncScopes; + std::function HasIRBlock; std::function(std::string_view)> InternSyncScope; std::function(std::uint32_t)> PointerSizeInBits; std::uint32_t DefaultPointerSizeInBits = 0; diff --git a/backend/lib/AsmPrinter/AsmPrinter.cpp b/backend/lib/AsmPrinter/AsmPrinter.cpp index 49388e1..1f237af 100644 --- a/backend/lib/AsmPrinter/AsmPrinter.cpp +++ b/backend/lib/AsmPrinter/AsmPrinter.cpp @@ -402,7 +402,8 @@ Expected AsmPrinter::emitFunction(const MachineFunction &Function) { if (!Type) return std::unexpected(Type.error()); } - if (const auto Alignment = Function.attributes().alignment()) { + if (const auto Alignment = Function.attributes().alignment(); + Alignment && Alignment->bytes() > 1) { auto Aligned = Streamer->emitAlignment(Alignment->bytes()); if (!Aligned) return std::unexpected(Aligned.error()); diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index 15f1772..d25d164 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -88,6 +88,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericConstant.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/FrameIndexElimination.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InlineAsm.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlockAttributes.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFIInfo.h" diff --git a/backend/lib/CodeGen/CompilationUnit.cpp b/backend/lib/CodeGen/CompilationUnit.cpp index 5ec54bc..a017851 100644 --- a/backend/lib/CodeGen/CompilationUnit.cpp +++ b/backend/lib/CodeGen/CompilationUnit.cpp @@ -22,13 +22,11 @@ CompilationUnit &CompilationUnit::operator=(CompilationUnit &&) noexcept = defau CompilationUnit::~CompilationUnit() = default; Expected CompilationUnit::createMachineFunction(std::string Name) { - if (Name.empty()) - return makeUnexpected(ErrorCode::InvalidArgument, "machine function name cannot be empty"); - if (FunctionNameToIndex.contains(Name)) { + if (!Name.empty() && FunctionNameToIndex.contains(Name)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine function name is already present in the compilation unit"); } - if (GlobalNameToIndex.contains(Name)) { + if (!Name.empty() && GlobalNameToIndex.contains(Name)) { return makeUnexpected(ErrorCode::InvalidArgument, "machine function name conflicts with a global object"); } @@ -42,7 +40,8 @@ Expected CompilationUnit::createMachineFunction(std::string N MachineFunction *Result = Function->get(); const std::size_t Index = Functions.size(); - FunctionNameToIndex.emplace(std::string(Result->name()), Index); + if (!Result->name().empty()) + FunctionNameToIndex.emplace(std::string(Result->name()), Index); Functions.push_back(std::move(*Function)); return Result; } @@ -129,23 +128,20 @@ const GlobalObjectDesc *CompilationUnit::globalObject(std::string_view Name) con } Expected CompilationUnit::verify() const { - if (FunctionNameToIndex.size() != Functions.size()) { - return makeUnexpected(ErrorCode::VerificationError, - "compilation unit function name index has an inconsistent size"); - } if (GlobalNameToIndex.size() != GlobalObjects.size()) { return makeUnexpected(ErrorCode::VerificationError, "compilation unit global object name index has an inconsistent size"); } std::unordered_set StableIDs; + std::size_t NamedFunctionCount = 0; for (std::size_t Index = 0; Index < Functions.size(); ++Index) { const std::unique_ptr &Function = Functions[Index]; if (Function == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "compilation unit contains a null machine function"); } - if (Function->name().empty() || !Function->stableID().isValid()) { + if (!Function->stableID().isValid()) { return makeUnexpected(ErrorCode::VerificationError, "compilation unit contains an invalid machine function identity"); } @@ -154,16 +150,23 @@ Expected CompilationUnit::verify() const { "compilation unit contains duplicate machine function StableIDs"); } - const auto Position = FunctionNameToIndex.find(Function->name()); - if (Position == FunctionNameToIndex.end() || Position->second != Index) { - return makeUnexpected(ErrorCode::VerificationError, - "compilation unit function name index is inconsistent"); - } - if (GlobalNameToIndex.contains(Function->name())) { - return makeUnexpected(ErrorCode::VerificationError, - "function name conflicts with a global object symbol"); + if (!Function->name().empty()) { + ++NamedFunctionCount; + const auto Position = FunctionNameToIndex.find(Function->name()); + if (Position == FunctionNameToIndex.end() || Position->second != Index) { + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit function name index is inconsistent"); + } + if (GlobalNameToIndex.contains(Function->name())) { + return makeUnexpected(ErrorCode::VerificationError, + "function name conflicts with a global object symbol"); + } } } + if (FunctionNameToIndex.size() != NamedFunctionCount) { + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit function name index has an inconsistent size"); + } for (std::size_t Index = 0; Index < GlobalObjects.size(); ++Index) { const std::unique_ptr &Object = GlobalObjects[Index]; diff --git a/backend/lib/CodeGen/InstructionDescriptor.cpp b/backend/lib/CodeGen/InstructionDescriptor.cpp index 48d4f10..fa00537 100644 --- a/backend/lib/CodeGen/InstructionDescriptor.cpp +++ b/backend/lib/CodeGen/InstructionDescriptor.cpp @@ -395,8 +395,12 @@ Expected InstructionSet::validateExplicitOperands(const InstructionDescrip } const OperandDescriptor &OperandDesc = descriptorForOperand(Descriptor, *Shape, Index); const auto Validation = validateOperand(OperandDesc, Operand, RegInfo); - if (!Validation) - return std::unexpected(Validation.error()); + if (!Validation) { + return makeUnexpected( + Validation.error().code(), + "operand " + std::to_string(Index) + " of '" + Descriptor.Name + "': " + + std::string(Validation.error().message())); + } if (Operand.isRegister()) { if (isDefRole(OperandDesc.RegisterRole)) { if (SawUse) { diff --git a/backend/lib/CodeGen/MachineFunction.cpp b/backend/lib/CodeGen/MachineFunction.cpp index 7f4c3a9..b870858 100644 --- a/backend/lib/CodeGen/MachineFunction.cpp +++ b/backend/lib/CodeGen/MachineFunction.cpp @@ -57,8 +57,6 @@ private: Expected> MachineFunction::create(std::string Name, MachineFunctionStableID StableID) { - if (Name.empty()) - return makeUnexpected(ErrorCode::InvalidArgument, "machine function name cannot be empty"); if (!StableID.isValid()) return makeUnexpected(ErrorCode::InvalidArgument, "machine function StableID must be valid"); return std::unique_ptr(new MachineFunction(std::move(Name), StableID)); diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp index 0048194..1a66e66 100644 --- a/backend/lib/CodeGen/MachineFunctionCloner.cpp +++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp @@ -202,6 +202,14 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { if (!DestinationReg) return std::unexpected(DestinationReg.error()); + if (SourceInfo.Name) { + auto DestinationInfo = + State.Destination.registerInfo().virtualRegisterInfo(*DestinationReg); + if (!DestinationInfo) + return std::unexpected(DestinationInfo.error()); + DestinationInfo->get().Name = SourceInfo.Name; + } + if (SourceInfo.Bank) { auto SetResult = State.Destination.registerInfo().setRegisterBank(*DestinationReg, *SourceInfo.Bank); diff --git a/backend/lib/CodeGen/MachineMemOperand.cpp b/backend/lib/CodeGen/MachineMemOperand.cpp index 95e7237..5f126fd 100644 --- a/backend/lib/CodeGen/MachineMemOperand.cpp +++ b/backend/lib/CodeGen/MachineMemOperand.cpp @@ -4,12 +4,22 @@ #include #include +#include #include #include namespace rcc { namespace { +[[nodiscard]] std::uint64_t effectiveAlignment(std::uint64_t BaseAlignment, + std::int64_t Offset) noexcept { + if (Offset == 0) + return BaseAlignment; + const auto OffsetBits = static_cast(Offset); + const std::uint64_t OffsetAlignment = std::uint64_t{1} << std::countr_zero(OffsetBits); + return std::min(BaseAlignment, OffsetAlignment); +} + [[nodiscard]] bool isValidAtomicLoadOrdering(AtomicOrdering Ordering) noexcept { return Ordering == AtomicOrdering::Unordered || Ordering == AtomicOrdering::Monotonic || Ordering == AtomicOrdering::Acquire || Ordering == AtomicOrdering::SequentiallyConsistent; @@ -167,6 +177,17 @@ MemoryBase MemoryBase::irValue(std::string Name) { return MemoryBase(Kind::IRValue, std::move(Name)); } +Expected MemoryBase::irConstant(GenericConstantData Constant) { + auto Verification = Constant.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Constant.Type.Kind != GenericConstantTypeKind::Pointer) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine memory IR constant base must have pointer type"); + } + return MemoryBase(Kind::IRConstant, std::move(Constant)); +} + MemoryBase MemoryBase::stack() { return MemoryBase(Kind::Stack, std::monostate{}); } @@ -222,6 +243,12 @@ std::optional MemoryBase::symbolName() const noexcept { return std::get(Value); } +const GenericConstantData *MemoryBase::constantValue() const noexcept { + if (BaseKind != Kind::IRConstant) + return nullptr; + return std::get_if(&Value); +} + Expected MachineMemOperand::create(MachineMemOperandDesc Description) { const bool IsLoad = Description.Accesses.has(MemoryAccessFlag::Load); const bool IsStore = Description.Accesses.has(MemoryAccessFlag::Store); @@ -233,9 +260,16 @@ Expected MachineMemOperand::create(MachineMemOperandDesc Desc return makeUnexpected(ErrorCode::InvalidArgument, "machine memory operand address space must fit in 24 bits"); } - if (Description.BaseAlign && Description.BaseAlign->bytes() < Description.Align.bytes()) { - return makeUnexpected(ErrorCode::InvalidArgument, - "machine memory operand base alignment cannot be smaller than alignment"); + if (Description.BaseAlign) { + const std::uint64_t Effective = + effectiveAlignment(Description.BaseAlign->bytes(), Description.Offset); + if (Effective != Description.Align.bytes()) { + return makeUnexpected( + ErrorCode::InvalidArgument, + "machine memory operand alignment is inconsistent with base alignment and offset"); + } + if (*Description.BaseAlign == Description.Align) + Description.BaseAlign.reset(); } if (Description.Accesses.has(MemoryAccessFlag::Invariant) && (!IsLoad || IsStore)) { return makeUnexpected(ErrorCode::InvalidArgument, diff --git a/backend/lib/CodeGen/MachineOperand.cpp b/backend/lib/CodeGen/MachineOperand.cpp index d1d152e..c81a7c3 100644 --- a/backend/lib/CodeGen/MachineOperand.cpp +++ b/backend/lib/CodeGen/MachineOperand.cpp @@ -121,31 +121,12 @@ Expected IntegerImmediate::parseDecimal(std::uint32_t BitWidth Word = static_cast(Product); Carry = static_cast(Product >> 64U); } - if (Carry != 0 || (Words.back() & ~integerHighWordMask(BitWidth)) != 0) { - return makeUnexpected(ErrorCode::ParseError, - "integer immediate does not fit its declared bit width"); - } } + // LLVM integer literals are arbitrary precision and are extended or + // truncated to the declared bit width before becoming a constant. + Words.back() &= integerHighWordMask(BitWidth); 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; @@ -186,6 +167,36 @@ std::string IntegerImmediate::toUnsignedDecimal() const { return Digits; } +std::string IntegerImmediate::toSignedDecimal() const { + if (!isNegative()) + return toUnsignedDecimal(); + + std::vector Magnitude = Words; + for (std::uint64_t &Word : Magnitude) + Word = ~Word; + std::uint64_t Carry = 1; + for (std::uint64_t &Word : Magnitude) { + const std::uint64_t Previous = Word; + Word += Carry; + Carry = Carry != 0 && Word < Previous ? 1 : 0; + } + Magnitude.back() &= integerHighWordMask(BitWidth); + + std::string Digits; + while (!std::ranges::all_of(Magnitude, [](std::uint64_t Word) { return Word == 0; })) { + unsigned __int128 Remainder = 0; + for (auto Iterator = Magnitude.rbegin(); Iterator != Magnitude.rend(); ++Iterator) { + const unsigned __int128 Dividend = (Remainder << 64U) | *Iterator; + *Iterator = static_cast(Dividend / 10U); + Remainder = Dividend % 10U; + } + Digits.push_back(static_cast('0' + static_cast(Remainder))); + } + std::ranges::reverse(Digits); + Digits.insert(Digits.begin(), '-'); + return Digits; +} + Expected IntegerImmediate::verify() const { return verifyIntegerImmediate(BitWidth, Words, ErrorCode::VerificationError); } @@ -324,10 +335,6 @@ Expected MachineOperand::globalSymbol(std::string Name, std::int Expected MachineOperand::externalSymbol(std::string Name, std::int64_t Offset, std::uint32_t TargetFlags) { - if (Name.empty()) { - return makeUnexpected(ErrorCode::InvalidArgument, - "external symbol operand name cannot be empty"); - } return MachineOperand(Kind::ExternalSymbol, ExternalSymbolOperand{std::move(Name), Offset}, TargetFlags); } diff --git a/backend/lib/CodeGen/MachinePseudoExpansion.cpp b/backend/lib/CodeGen/MachinePseudoExpansion.cpp index 676804a..924c515 100644 --- a/backend/lib/CodeGen/MachinePseudoExpansion.cpp +++ b/backend/lib/CodeGen/MachinePseudoExpansion.cpp @@ -200,13 +200,20 @@ using BlockSizeSnapshot = std::vector layoutInstructions(MachineFunction &Function) { + std::vector Result; + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) + for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) + Result.push_back(Instruction); + return Result; +} + [[nodiscard]] Expected prepareImplementation(MachineFunction &Function, const TargetDescription &Target, const TargetPseudoExpansionInfo &Info, MachineActionReplay &Actions) { bool Changed = false; - for (MachineInstr *Instruction : Function.allInstructions()) { - if (Instruction == nullptr || Instruction->parent() == nullptr) - continue; + for (MachineInstr *Instruction : layoutInstructions(Function)) { const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); if (Descriptor == nullptr) return makeUnexpected(ErrorCode::VerificationError, @@ -255,10 +262,7 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target, SizesBefore = std::move(*Snapshot); } std::vector Worklist; - for (MachineInstr *Instruction : Function.allInstructions()) { - if (Instruction != nullptr && Instruction->parent() != nullptr) - Worklist.push_back(Instruction); - } + Worklist = layoutInstructions(Function); bool Changed = false; for (MachineInstr *Instruction : Worklist) { if (Instruction->parent() == nullptr) @@ -325,9 +329,7 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target, } } } - for (MachineInstr *Instruction : Function.allInstructions()) { - if (Instruction == nullptr || Instruction->parent() == nullptr) - continue; + for (MachineInstr *Instruction : layoutInstructions(Function)) { const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); if (Descriptor != nullptr && Descriptor->Flags.has(InstructionFlag::Pseudo) && Descriptor->Owner == ownerFor(Phase)) { diff --git a/backend/lib/CodeGen/MachineRegisterInfo.cpp b/backend/lib/CodeGen/MachineRegisterInfo.cpp index 7bd0ef3..2fe74d0 100644 --- a/backend/lib/CodeGen/MachineRegisterInfo.cpp +++ b/backend/lib/CodeGen/MachineRegisterInfo.cpp @@ -90,6 +90,7 @@ Expected MachineRegisterInfo::eraseVirtualRegister(Register Reg) { } Info->get().IsTombstone = true; + Info->get().Name.reset(); Info->get().Type.reset(); Info->get().Bank.reset(); Info->get().RegClass.reset(); diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp index be7462b..3d9fbdb 100644 --- a/backend/lib/CodeGen/MachineVerifier.cpp +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -10,6 +10,7 @@ #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineLiveness.h" #include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineMetadata.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -28,6 +29,42 @@ namespace rcc { namespace { +[[nodiscard]] std::string formatDebugInstruction(const MachineInstr &Instruction, + const TargetDescription &Target) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + std::string Result = Descriptor == nullptr ? "" : Descriptor->Name; + for (std::size_t Index = 0; Index < Instruction.operandCount(); ++Index) { + if (Index == 0) + Result.push_back(' '); + else + Result.append(", "); + const MachineOperand &Operand = Instruction.operands()[Index]; + if (const auto *Immediate = Operand.getIf()) { + Result.append(std::to_string(*Immediate)); + continue; + } + if (const auto *RegisterValue = Operand.getIf()) { + if (RegisterValue->isNoRegister()) { + Result.append("$noreg"); + continue; + } + if (RegisterValue->isVirtual()) { + Result.push_back('%'); + Result.append(std::to_string(*RegisterValue->virtualIndex())); + continue; + } + const auto Physical = RegisterValue->physicalRegister(); + const PhysicalRegisterDesc *Register = + Physical ? Target.registers().physicalRegister(*Physical) : nullptr; + Result.push_back('$'); + Result.append(Register == nullptr ? RegisterValue->toString() : Register->Name); + continue; + } + Result.append(""); + } + return Result; +} + class FunctionVerifier final { public: FunctionVerifier(const MachineFunction &Function, const TargetDescription &Target, @@ -35,7 +72,7 @@ public: : Function(Function), Target(Target), Options(Options) {} [[nodiscard]] Expected run() { - if (Function.name().empty() || !Function.stableID().isValid()) + if (!Function.stableID().isValid()) return fail("function identity is invalid"); auto AttributeResult = Function.attributes().verify(); @@ -309,6 +346,14 @@ private: if (Instruction.sourceLocation() && !Instruction.sourceLocation()->isValid()) { return fail("instruction has an invalid source location"); } + const Opcode OpcodeValue = Instruction.opcode(); + const bool IsDebugValue = OpcodeValue == opcode(GenericOpcode::DBG_VALUE) || + OpcodeValue == opcode(GenericOpcode::DBG_VALUE_LIST); + if (IsDebugValue && Instruction.operandCount() == 4 && !Instruction.debugLocation()) { + return fail("Bad machine code: Missing DebugLoc for debug instruction\n" + "- instruction: " + + formatDebugInstruction(Instruction, Target)); + } if (Instruction.debugLocation()) { auto DebugLocationResult = Instruction.debugLocation()->verify(Function.metadata()); if (!DebugLocationResult) @@ -359,10 +404,6 @@ private: Global != nullptr && Global->Name.empty()) { return fail("global symbol operand has an empty name"); } - if (const auto *External = Operand.getIf(); - External != nullptr && External->Name.empty()) { - return fail("external symbol operand has an empty name"); - } if (const auto *Address = Operand.getIf(); Address != nullptr && (Address->FunctionName.empty() || Address->BlockName.empty())) { return fail("block-address operand has an incomplete name"); @@ -534,13 +575,6 @@ private: return std::unexpected(Call.error()); } - for (const MachinePrefetchTarget &Prefetch : Records.prefetchTargets()) { - const std::uint64_t Key = (static_cast(Prefetch.Block.BaseID) << 32U) | - static_cast(Prefetch.Block.CloneID); - if (!LinkedBlockProfileIDs.contains(Key)) - return fail("prefetch target references an unknown bb_id"); - } - if (!Records.debugValueSubstitutions().empty() && !Function.attributes().has(MachineFunctionAttribute::DebugInstrRef)) { return fail("debug value substitutions require DebugInstrRef"); diff --git a/backend/lib/MIR/EmbeddedIR.cpp b/backend/lib/MIR/EmbeddedIR.cpp index 662faac..93cd534 100644 --- a/backend/lib/MIR/EmbeddedIR.cpp +++ b/backend/lib/MIR/EmbeddedIR.cpp @@ -18,6 +18,7 @@ struct ParsedIdentifier final { std::string Name; TextRange Range; std::size_t End = 0; + bool IsNumericSlot = false; }; [[nodiscard]] bool isHorizontalSpace(char Character) noexcept { @@ -96,6 +97,18 @@ const EmbeddedIRFunction *EmbeddedIRReferenceIndex::function(std::string_view Na return &Functions[Position->second]; } +const EmbeddedIRFunction * +EmbeddedIRReferenceIndex::unnamedFunction(std::size_t Ordinal) const noexcept { + for (const EmbeddedIRFunction &Function : Functions) { + if (!Function.isUnnamed()) + continue; + if (Ordinal == 0) + return &Function; + --Ordinal; + } + return nullptr; +} + const EmbeddedIRReference * EmbeddedIRReferenceIndex::globalObject(std::string_view Name) const noexcept { const auto Position = GlobalNameToIndex.find(Name); @@ -152,6 +165,10 @@ public: return failureResult(); } + validateLocalReferences(); + if (Failed) + return failureResult(); + EmbeddedIRIndexResult Result; Result.Index.emplace(std::move(Index)); return Result; @@ -209,6 +226,7 @@ private: return std::nullopt; std::string Name; + bool IsNumericSlot = false; if (Line[Position] == '"') { ++Position; while (Position < End && Line[Position] != '"') { @@ -237,6 +255,7 @@ private: } ++Position; } else if (Line[Position] >= '0' && Line[Position] <= '9') { + IsNumericSlot = true; const std::size_t NameBegin = Position; while (Position < End && Line[Position] >= '0' && Line[Position] <= '9') ++Position; @@ -254,6 +273,7 @@ private: .Name = std::move(Name), .Range = Index.Source.range(LineOffset + Begin, LineOffset + Position), .End = Position, + .IsNumericSlot = IsNumericSlot, }; } @@ -464,6 +484,7 @@ private: Function.Name = Name.Name; Function.NameRange = Name.Range; Function.IsDefinition = IsDefinition; + Function.IsUnnamed = Name.IsNumericSlot; Index.Functions.push_back(std::move(Function)); Index.FunctionNameToIndex.emplace(Name.Name, IndexValue); return IndexValue; @@ -491,6 +512,9 @@ private: if (!Name || Failed) return; const std::size_t Following = skipHorizontalSpace(Line, Name->End, End); + recordLocalReference(*Name, Index.Functions[ActiveFunction.value()]); + if (Failed) + return; std::size_t Previous = IdentifierBegin; while (Previous > 0 && isHorizontalSpace(Line[Previous - 1])) --Previous; @@ -537,6 +561,7 @@ private: .Name = Name.Name, .Range = Name.Range, }); + observeNumericSlot(Name, Function); } void parseActiveFunctionLine(std::string_view Line, std::size_t LineOffset, std::size_t End) { @@ -587,10 +612,21 @@ private: return; } EmbeddedIRFunction &Function = Index.Functions[ActiveFunction.value()]; + const std::size_t PreviousBlockCount = Function.Blocks.size(); parseBlockLabel(Line, LineOffset, Begin, BodyEnd, Function); if (Failed) return; + const std::size_t FirstContent = skipHorizontalSpace(Line, Begin, BodyEnd); + if (Function.Blocks.empty() && Function.Blocks.size() == PreviousBlockCount && + FirstContent < BodyEnd) { + addImplicitEntryBlock(LineOffset + FirstContent, Function); + if (Failed) + return; + } parseLocalValueDefinition(Line, LineOffset, Begin, BodyEnd, Function); + if (Failed) + return; + parseLocalReferences(Line, LineOffset, Begin, BodyEnd, Function); if (Failed) return; @@ -624,9 +660,45 @@ private: const std::size_t BlockIndex = Function.Blocks.size(); Function.BlockNameToIndex.emplace(Name->Name, BlockIndex); Function.Blocks.push_back(EmbeddedIRReference{ - .Name = std::move(Name->Name), + .Name = Name->Name, .Range = Name->Range, }); + observeNumericSlot(*Name, Function); + } + + void addImplicitEntryBlock(std::size_t Offset, EmbeddedIRFunction &Function) { + const std::string Name = std::to_string(Function.NextUnnamedSlot++); + if (Function.BlockNameToIndex.contains(Name) || Function.ValueNameToIndex.contains(Name)) { + diagnose(Offset, Offset, "embedded LLVM IR unnamed entry block slot is already in use"); + return; + } + const std::size_t BlockIndex = Function.Blocks.size(); + Function.BlockNameToIndex.emplace(Name, BlockIndex); + Function.Blocks.push_back(EmbeddedIRReference{ + .Name = Name, + .Range = Index.Source.range(Offset, Offset), + }); + } + + void observeNumericSlot(const ParsedIdentifier &Name, EmbeddedIRFunction &Function) { + if (!Name.IsNumericSlot) + return; + std::uint64_t Slot = 0; + for (const char Digit : Name.Name) { + const std::uint64_t Value = static_cast(Digit - '0'); + if (Slot > (std::numeric_limits::max() - Value) / 10U) { + diagnose(Name.Range.Begin.Offset, Name.Range.End.Offset, + "embedded LLVM IR numeric slot is too large"); + return; + } + Slot = Slot * 10U + Value; + } + if (Slot == std::numeric_limits::max()) { + diagnose(Name.Range.Begin.Offset, Name.Range.End.Offset, + "embedded LLVM IR numeric slot is too large"); + return; + } + Function.NextUnnamedSlot = std::max(Function.NextUnnamedSlot, Slot + 1U); } void parseLocalValueDefinition(std::string_view Line, std::size_t LineOffset, std::size_t Begin, @@ -657,6 +729,53 @@ private: }); } + void recordLocalReference(const ParsedIdentifier &Name, EmbeddedIRFunction &Function) { + Function.LocalReferences.push_back(EmbeddedIRReference{ + .Name = Name.Name, + .Range = Name.Range, + }); + } + + void parseLocalReferences(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) { + const char Character = Line[Position]; + if (InQuote && Character == '\\' && Position + 2 < End && + isHexDigit(Line[Position + 1]) && isHexDigit(Line[Position + 2])) { + Position += 2; + continue; + } + if (Character == '"') { + InQuote = !InQuote; + continue; + } + if (InQuote || Character != '%') + continue; + auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); + if (!Name || Failed) + continue; + recordLocalReference(*Name, Function); + Position = Name->End - 1; + } + } + + void validateLocalReferences() { + for (const EmbeddedIRFunction &Function : Index.Functions) { + if (!Function.IsDefinition) + continue; + for (const EmbeddedIRReference &Reference : Function.LocalReferences) { + if (Function.ValueNameToIndex.contains(Reference.Name) || + Function.BlockNameToIndex.contains(Reference.Name) || + Index.TypeNameToIndex.contains(Reference.Name)) + continue; + diagnose(Reference.Range.Begin.Offset, Reference.Range.End.Offset, + "use of undefined value '%" + Reference.Name + "'"); + return; + } + } + } + void parseGlobalObject(std::string_view Line, std::size_t LineOffset, std::size_t Position, std::size_t End) { auto Name = parseIdentifier(Line, LineOffset, Position, End, '@'); diff --git a/backend/lib/MIR/LLVMConstantSyntax.cpp b/backend/lib/MIR/LLVMConstantSyntax.cpp index 8bc2c18..a2025dc 100644 --- a/backend/lib/MIR/LLVMConstantSyntax.cpp +++ b/backend/lib/MIR/LLVMConstantSyntax.cpp @@ -121,6 +121,17 @@ public: Limbs.push_back(static_cast(Carry)); } + [[nodiscard]] std::uint32_t divideSmall(std::uint32_t Divisor) { + std::uint64_t Remainder = 0; + for (std::size_t Index = Limbs.size(); Index != 0; --Index) { + const std::uint64_t Dividend = (Remainder << 32U) | Limbs[Index - 1U]; + Limbs[Index - 1U] = static_cast(Dividend / Divisor); + Remainder = Dividend % Divisor; + } + normalize(); + return static_cast(Remainder); + } + void addSmall(std::uint32_t Value) { std::uint64_t Carry = Value; std::size_t Index = 0; @@ -159,6 +170,51 @@ public: normalize(); } + [[nodiscard]] std::size_t countrZero() const noexcept { + std::size_t Result = 0; + for (const std::uint32_t Limb : Limbs) { + if (Limb == 0) { + Result += 32U; + continue; + } + return Result + static_cast(std::countr_zero(Limb)); + } + return Result; + } + + void shiftRight(std::size_t Amount) noexcept { + if (Amount == 0 || isZero()) + return; + const std::size_t LimbOffset = Amount / 32U; + if (LimbOffset >= Limbs.size()) { + Limbs.clear(); + return; + } + if (LimbOffset != 0) + Limbs.erase(Limbs.begin(), Limbs.begin() + static_cast(LimbOffset)); + const unsigned BitOffset = static_cast(Amount % 32U); + if (BitOffset != 0) { + 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 >> BitOffset) | Carry; + Carry = Current << (32U - BitOffset); + } + } + normalize(); + } + + [[nodiscard]] std::string toDecimal() const { + if (isZero()) + return "0"; + BigUnsigned Remaining = *this; + std::string Digits; + while (!Remaining.isZero()) + Digits.push_back(static_cast('0' + Remaining.divideSmall(10))); + std::ranges::reverse(Digits); + return Digits; + } + [[nodiscard]] BigUnsigned shiftedLeft(std::size_t Amount) const { if (isZero() || Amount == 0) return *this; @@ -2382,6 +2438,225 @@ Expected printType(const GenericConstantType &Type) { return makeUnexpected(ErrorCode::VerificationError, "generic constant type is invalid"); } +enum class PrintedFloatCategory : std::uint8_t { + Zero, + Finite, + Infinity, + QuietNaN, + SignalingNaN, +}; + +struct PrintedBinaryFloat final { + PrintedFloatCategory Category = PrintedFloatCategory::Zero; + bool Negative = false; + BigUnsigned Significand; + std::int32_t BinaryExponent = 0; + BigUnsigned NaNPayload; + std::uint32_t QuietNaNBit = 0; +}; + +[[nodiscard]] std::uint64_t extractFloatField(std::span Words, + std::uint32_t Begin, std::uint32_t Width) noexcept { + std::uint64_t Result = 0; + for (std::uint32_t Bit = 0; Bit < Width; ++Bit) { + if (hasFloatBit(Words, Begin + Bit)) + Result |= std::uint64_t{1} << Bit; + } + return Result; +} + +[[nodiscard]] BigUnsigned extractFloatBits(std::span Words, + std::uint32_t Begin, std::uint32_t Width) { + BigUnsigned Result; + for (std::uint32_t Bit = 0; Bit < Width; ++Bit) { + if (hasFloatBit(Words, Begin + Bit)) + Result.setBit(Bit); + } + return Result; +} + +[[nodiscard]] std::optional decodePrintedFloat(const FloatingPointBits &Bits) { + std::optional Format = binaryFloatFormat(Bits.semantics()); + std::span Words = Bits.words(); + if (!Format) + return std::nullopt; + + if (Bits.semantics() == FloatSemantics::PPCDoubleDouble || + Bits.semantics() == FloatSemantics::PPCDoubleDoubleLegacy) { + if (Words.size() != 2 || Words[1] != 0) + return std::nullopt; + Format = binaryFloatFormat(FloatSemantics::IEEEDouble); + Words = Words.first(1); + } + if (!Format || Format->ExponentBitWidth == 0) + return std::nullopt; + + PrintedBinaryFloat Result; + Result.Negative = hasFloatBit(Words, Format->StorageBitWidth - 1U); + const std::uint32_t StoredSignificandWidth = + Format->HasExplicitIntegerBit ? Format->Precision : Format->Precision - 1U; + const std::uint64_t Exponent = + extractFloatField(Words, StoredSignificandWidth, Format->ExponentBitWidth); + const std::uint64_t MaximumExponent = (std::uint64_t{1} << Format->ExponentBitWidth) - 1U; + + if (Exponent == MaximumExponent) { + const std::uint32_t PayloadWidth = Format->Precision - 1U; + Result.NaNPayload = extractFloatBits(Words, 0, PayloadWidth); + if (Result.NaNPayload.isZero()) { + Result.Category = PrintedFloatCategory::Infinity; + return Result; + } + Result.QuietNaNBit = PayloadWidth - 1U; + Result.Category = hasFloatBit(Words, Result.QuietNaNBit) ? PrintedFloatCategory::QuietNaN + : PrintedFloatCategory::SignalingNaN; + return Result; + } + + Result.Significand = extractFloatBits(Words, 0, StoredSignificandWidth); + if (Exponent != 0 && !Format->HasExplicitIntegerBit) + Result.Significand.setBit(Format->Precision - 1U); + if (Result.Significand.isZero()) { + Result.Category = PrintedFloatCategory::Zero; + return Result; + } + + const std::int64_t Bias = 1 - static_cast(Format->MinExponent); + const std::int64_t UnbiasedExponent = + Exponent == 0 ? Format->MinExponent : static_cast(Exponent) - Bias; + Result.BinaryExponent = static_cast(UnbiasedExponent - (Format->Precision - 1U)); + Result.Category = PrintedFloatCategory::Finite; + return Result; +} + +[[nodiscard]] std::string printNaNPayload(BigUnsigned Payload, std::uint32_t QuietNaNBit) { + Payload.clearBit(QuietNaNBit); + const auto Width = static_cast(std::max(Payload.bitWidth(), 1U)); + std::array Words{Payload.word(0), Payload.word(1)}; + std::string Digits = hexadecimalWords(Words, Width); + const std::size_t FirstNonzero = Digits.find_first_not_of('0'); + if (FirstNonzero == std::string::npos) + return "0x0"; + Digits.erase(0, FirstNonzero); + return "0x" + Digits; +} + +[[nodiscard]] std::string formatFiniteFloat(PrintedBinaryFloat Value) { + std::string Result; + if (Value.Negative) + Result.push_back('-'); + if (Value.Category == PrintedFloatCategory::Zero) { + Result.append("0.000000e+00"); + return Result; + } + + const std::size_t TrailingZeros = Value.Significand.countrZero(); + Value.Significand.shiftRight(TrailingZeros); + Value.BinaryExponent += static_cast(TrailingZeros); + + std::int32_t DecimalExponent = 0; + if (Value.BinaryExponent >= 0) { + Value.Significand = + Value.Significand.shiftedLeft(static_cast(Value.BinaryExponent)); + } else { + DecimalExponent = Value.BinaryExponent; + for (std::int32_t Power = 0; Power < -Value.BinaryExponent; ++Power) + Value.Significand.multiplySmall(5); + } + + std::string Digits = Value.Significand.toDecimal(); + while (Digits.size() > 1U && Digits.back() == '0') { + Digits.pop_back(); + ++DecimalExponent; + } + + constexpr std::size_t FormatPrecision = 6; + if (Digits.size() > FormatPrecision) { + std::size_t Removed = Digits.size() - FormatPrecision; + const bool RoundUp = Digits[FormatPrecision] >= '5'; + Digits.resize(FormatPrecision); + if (RoundUp) { + std::size_t Position = Digits.size(); + while (Position != 0 && Digits[Position - 1U] == '9') { + Digits[Position - 1U] = '0'; + --Position; + } + if (Position == 0) + Digits.insert(Digits.begin(), '1'); + else + ++Digits[Position - 1U]; + } + while (Digits.size() > 1U && Digits.back() == '0') { + Digits.pop_back(); + ++Removed; + } + DecimalExponent += static_cast(Removed); + } + + DecimalExponent += static_cast(Digits.size() - 1U); + Result.push_back(Digits.front()); + Result.push_back('.'); + Result.append(Digits.substr(1)); + Result.append(7U - Digits.size(), '0'); + Result.push_back('e'); + Result.push_back(DecimalExponent < 0 ? '-' : '+'); + const std::uint32_t Magnitude = static_cast( + DecimalExponent < 0 ? -static_cast(DecimalExponent) : DecimalExponent); + std::string Exponent = std::to_string(Magnitude); + if (Exponent.size() < 2U) + Result.push_back('0'); + Result.append(Exponent); + return Result; +} + +[[nodiscard]] std::string exactFloatingText(const FloatingPointBits &Bits) { + return "f0x" + hexadecimalWords(Bits.words(), Bits.bitWidth()); +} + +[[nodiscard]] std::string canonicalFloatingText(const FloatingPointBits &Bits) { + auto Decoded = decodePrintedFloat(Bits); + if (!Decoded) + return exactFloatingText(Bits); + + std::string Candidate; + switch (Decoded->Category) { + case PrintedFloatCategory::Zero: + case PrintedFloatCategory::Finite: + Candidate = formatFiniteFloat(std::move(*Decoded)); + break; + case PrintedFloatCategory::Infinity: + Candidate = Decoded->Negative ? "-inf" : "+inf"; + break; + case PrintedFloatCategory::QuietNaN: { + BigUnsigned Payload = Decoded->NaNPayload; + Payload.clearBit(Decoded->QuietNaNBit); + if (Payload.isZero()) { + Candidate = Decoded->Negative ? "-qnan" : "+qnan"; + break; + } + Candidate = Decoded->Negative ? "-nan(" : "+nan("; + Candidate.append(printNaNPayload(Decoded->NaNPayload, Decoded->QuietNaNBit)); + Candidate.push_back(')'); + break; + } + case PrintedFloatCategory::SignalingNaN: + Candidate = Decoded->Negative ? "-snan(" : "+snan("; + Candidate.append(printNaNPayload(Decoded->NaNPayload, Decoded->QuietNaNBit)); + Candidate.push_back(')'); + break; + } + + auto Type = GenericConstantType::floating(Bits.semantics()); + if (!Type) + return exactFloatingText(Bits); + auto TypeText = printType(*Type); + if (!TypeText) + return exactFloatingText(Bits); + auto Parsed = parseLLVMConstant(*TypeText + " " + Candidate); + if (!Parsed || !Parsed->FloatingValue || *Parsed->FloatingValue != Bits) + return exactFloatingText(Bits); + return Candidate; +} + [[nodiscard]] std::string_view expressionOpcodeName(GenericConstantExpressionOpcode Opcode) noexcept { switch (Opcode) { @@ -2464,9 +2739,7 @@ Expected printValue(const GenericConstantData &Value, bool IncludeT if (!Value.FloatingValue) return makeUnexpected(ErrorCode::VerificationError, "floating constant is missing its floating payload"); - Result.append("f0x"); - Result.append(hexadecimalWords(Value.FloatingValue.value().words(), - Value.FloatingValue.value().bitWidth())); + Result.append(canonicalFloatingText(*Value.FloatingValue)); return Result; case GenericConstantKind::NullPointer: Result.append("null"); diff --git a/backend/lib/MIR/MIDebugSyntax.cpp b/backend/lib/MIR/MIDebugSyntax.cpp index fc08df6..e614a21 100644 --- a/backend/lib/MIR/MIDebugSyntax.cpp +++ b/backend/lib/MIR/MIDebugSyntax.cpp @@ -5,7 +5,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -20,26 +23,8 @@ namespace { return Error == std::errc{} && End == Text.data() + Text.size(); } -} // namespace - -std::optional parseDIExpressionOpcodeName(std::string_view Name) noexcept { - const auto NumberedOpcode = [Name](std::string_view Prefix, - std::uint64_t Base) -> std::optional { - 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>({ +[[nodiscard]] const auto &namedOpcodes() { + static constexpr auto Opcodes = std::to_array>({ {"DW_OP_addr", 0x03}, {"DW_OP_deref", 0x06}, {"DW_OP_const1u", 0x08}, @@ -114,7 +99,104 @@ std::optional parseDIExpressionOpcodeName(std::string_view Name) {"DW_OP_LLVM_entry_value", 0x1003}, {"DW_OP_LLVM_implicit_pointer", 0x1004}, {"DW_OP_LLVM_arg", 0x1005}, + {"DW_OP_LLVM_extract_bits_sext", 0x1006}, + {"DW_OP_LLVM_extract_bits_zext", 0x1007}, }); + return Opcodes; +} + +[[nodiscard]] const auto &attributeEncodings() { + static constexpr auto Encodings = std::to_array>({ + {"DW_ATE_address", 0x01}, + {"DW_ATE_boolean", 0x02}, + {"DW_ATE_complex_float", 0x03}, + {"DW_ATE_float", 0x04}, + {"DW_ATE_signed", 0x05}, + {"DW_ATE_signed_char", 0x06}, + {"DW_ATE_unsigned", 0x07}, + {"DW_ATE_unsigned_char", 0x08}, + {"DW_ATE_imaginary_float", 0x09}, + {"DW_ATE_packed_decimal", 0x0A}, + {"DW_ATE_numeric_string", 0x0B}, + {"DW_ATE_edited", 0x0C}, + {"DW_ATE_signed_fixed", 0x0D}, + {"DW_ATE_unsigned_fixed", 0x0E}, + {"DW_ATE_decimal_float", 0x0F}, + {"DW_ATE_UTF", 0x10}, + {"DW_ATE_UCS", 0x11}, + {"DW_ATE_ASCII", 0x12}, + }); + return Encodings; +} + +[[nodiscard]] std::optional opcodeName(std::uint64_t Opcode) { + if (Opcode >= 0x30U && Opcode <= 0x4FU) + return "DW_OP_lit" + std::to_string(Opcode - 0x30U); + if (Opcode >= 0x50U && Opcode <= 0x6FU) + return "DW_OP_reg" + std::to_string(Opcode - 0x50U); + if (Opcode >= 0x70U && Opcode <= 0x8FU) + return "DW_OP_breg" + std::to_string(Opcode - 0x70U); + const auto &Opcodes = namedOpcodes(); + const auto Found = + std::ranges::find_if(Opcodes, [Opcode](const auto &Entry) { return Entry.second == Opcode; }); + if (Found == Opcodes.end()) + return std::nullopt; + return std::string(Found->first); +} + +[[nodiscard]] std::size_t opcodeSize(std::uint64_t Opcode) noexcept { + if (Opcode >= 0x70U && Opcode <= 0x8FU) + return 2; + switch (Opcode) { + case 0x92: + case 0x1000: + case 0x1001: + case 0x1006: + case 0x1007: + return 3; + case 0x10: + case 0x11: + case 0x23: + case 0x90: + case 0x94: + case 0x1002: + case 0x1003: + case 0x1005: + return 2; + default: + return 1; + } +} + +[[nodiscard]] std::optional attributeName(std::uint64_t Encoding) noexcept { + const auto &Encodings = attributeEncodings(); + const auto Found = std::ranges::find_if( + Encodings, [Encoding](const auto &Entry) { return Entry.second == Encoding; }); + if (Found == Encodings.end()) + return std::nullopt; + return Found->first; +} + +} // namespace + +std::optional parseDIExpressionOpcodeName(std::string_view Name) noexcept { + const auto NumberedOpcode = [Name](std::string_view Prefix, + std::uint64_t Base) -> std::optional { + 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; + + const auto &NamedOpcodes = namedOpcodes(); const auto *const Found = std::ranges::find_if(NamedOpcodes, [Name](const auto &Entry) { return Entry.first == Name; }); if (Found == NamedOpcodes.end()) @@ -122,4 +204,61 @@ std::optional parseDIExpressionOpcodeName(std::string_view Name) return Found->second; } +std::optional +parseDIExpressionAttributeEncodingName(std::string_view Name) noexcept { + const auto &Encodings = attributeEncodings(); + const auto Found = + std::ranges::find_if(Encodings, [Name](const auto &Entry) { return Entry.first == Name; }); + if (Found == Encodings.end()) + return std::nullopt; + return Found->second; +} + +std::string invalidDIExpressionElementMessage(std::string_view Name) { + if (Name.starts_with("DW_OP_")) + return "invalid DWARF op '" + std::string(Name) + "'"; + if (Name.starts_with("DW_ATE_")) + return "invalid DWARF attribute encoding '" + std::string(Name) + "'"; + return "expected unsigned integer"; +} + +std::string formatDIExpression(std::span Elements) { + std::string Result = "!DIExpression("; + std::size_t Position = 0; + bool First = true; + while (Position < Elements.size()) { + const std::uint64_t Opcode = Elements[Position]; + const auto Name = opcodeName(Opcode); + const std::size_t Size = opcodeSize(Opcode); + if (!Name || Position + Size > Elements.size()) { + Result = "!DIExpression("; + for (std::size_t Index = 0; Index < Elements.size(); ++Index) { + if (Index != 0) + Result.append(", "); + Result.append(std::to_string(Elements[Index])); + } + Result.push_back(')'); + return Result; + } + + if (!First) + Result.append(", "); + First = false; + Result.append(*Name); + for (std::size_t Argument = 1; Argument < Size; ++Argument) { + Result.append(", "); + if (Opcode == 0x1001U && Argument == 2U) { + if (const auto Encoding = attributeName(Elements[Position + Argument])) { + Result.append(*Encoding); + continue; + } + } + Result.append(std::to_string(Elements[Position + Argument])); + } + Position += Size; + } + Result.push_back(')'); + return Result; +} + } // namespace rcc::mir diff --git a/backend/lib/MIR/MIParser.cpp b/backend/lib/MIR/MIParser.cpp index 483c2f2..01ecbf5 100644 --- a/backend/lib/MIR/MIParser.cpp +++ b/backend/lib/MIR/MIParser.cpp @@ -2,6 +2,7 @@ #include "rcc/MIR/MIParser.h" +#include "rcc/CodeGen/InlineAsm.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" @@ -13,6 +14,7 @@ #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" @@ -113,60 +115,6 @@ namespace { Token.isKeyword("llvm_vector_register_mask"); } -[[nodiscard]] std::optional hexDigit(char Character) noexcept { - if (Character >= '0' && Character <= '9') - return static_cast(Character - '0'); - if (Character >= 'a' && Character <= 'f') - return static_cast(Character - 'a') + 10U; - if (Character >= 'A' && Character <= 'F') - return static_cast(Character - 'A') + 10U; - return std::nullopt; -} - -[[nodiscard]] bool parseHexWords(std::string_view Digits, std::uint32_t BitWidth, - std::vector &Words) { - if (Digits.empty() || Digits.size() > (static_cast(BitWidth) + 3U) / 4U) - return false; - Words.assign((static_cast(BitWidth) + 63U) / 64U, 0); - std::size_t BitOffset = 0; - for (auto Iterator = Digits.rbegin(); Iterator != Digits.rend(); ++Iterator) { - const auto Digit = hexDigit(*Iterator); - if (!Digit) - return false; - const std::size_t WordIndex = BitOffset / 64U; - const unsigned Shift = static_cast(BitOffset % 64U); - Words[WordIndex] |= static_cast(*Digit) << Shift; - BitOffset += 4U; - } - if (BitWidth % 64U != 0) - Words.back() &= (std::uint64_t{1} << (BitWidth % 64U)) - 1U; - return true; -} - -[[nodiscard]] bool isZeroWords(std::span Words) noexcept { - return std::ranges::all_of(Words, [](std::uint64_t Word) { return Word == 0; }); -} - -[[nodiscard]] bool magnitudeFitsSignedNegative(std::span Words, - std::uint32_t BitWidth) noexcept { - const std::size_t SignWord = (BitWidth - 1U) / 64U; - const unsigned SignBit = (BitWidth - 1U) % 64U; - for (std::size_t Index = SignWord + 1U; Index < Words.size(); ++Index) { - if (Words[Index] != 0) - return false; - } - const std::uint64_t SignMask = std::uint64_t{1} << SignBit; - if ((Words[SignWord] & ~SignMask) != 0) - return false; - if ((Words[SignWord] & SignMask) == 0) - return true; - for (std::size_t Index = 0; Index < SignWord; ++Index) { - if (Words[Index] != 0) - return false; - } - return true; -} - [[nodiscard]] Expected parseIntegerImmediateValue(std::uint32_t BitWidth, std::string_view Spelling) { if (BitWidth == 0 || BitWidth > IntegerImmediate::MaxBitWidth) @@ -188,17 +136,13 @@ namespace { Word = static_cast(Product); Carry = static_cast(Product >> 64U); } - if (Carry != 0) - return makeUnexpected(ErrorCode::ParseError, "typed integer does not fit its bit width"); - if (BitWidth % 64U != 0 && (Words.back() >> (BitWidth % 64U)) != 0) { - return makeUnexpected(ErrorCode::ParseError, "typed integer does not fit its bit width"); - } } + // LLVM parses the arbitrary-precision literal first and then truncates it to + // the declared integer width. Keep exactly that modulo-2^N bit pattern. + if (BitWidth % 64U != 0) + Words.back() &= (std::uint64_t{1} << (BitWidth % 64U)) - 1U; if (Negative) { - if (isZeroWords(Words) || !magnitudeFitsSignedNegative(Words, BitWidth)) - return makeUnexpected(ErrorCode::ParseError, - "negative typed integer does not fit its bit width"); for (std::uint64_t &Word : Words) Word = ~Word; std::uint64_t Carry = 1; @@ -213,51 +157,6 @@ namespace { return IntegerImmediate::create(BitWidth, Words); } -[[nodiscard]] std::uint16_t floatToHalfBits(float Value) noexcept { - const std::uint32_t Bits = std::bit_cast(Value); - const std::uint32_t Sign = (Bits >> 16U) & 0x8000U; - const std::uint32_t Exponent = (Bits >> 23U) & 0xFFU; - const std::uint32_t Fraction = Bits & 0x7FFFFFU; - if (Exponent == 0xFFU) { - const std::uint32_t Payload = Fraction == 0 ? 0 : std::max(1U, Fraction >> 13U); - return static_cast(Sign | 0x7C00U | Payload); - } - - const int HalfExponent = static_cast(Exponent) - 127 + 15; - if (HalfExponent >= 31) - return static_cast(Sign | 0x7C00U); - if (HalfExponent <= 0) { - if (HalfExponent < -10) - return static_cast(Sign); - const std::uint32_t Mantissa = Fraction | 0x800000U; - const unsigned Shift = static_cast(14 - HalfExponent); - std::uint32_t Rounded = Mantissa >> Shift; - const std::uint32_t Remainder = Mantissa & ((std::uint32_t{1} << Shift) - 1U); - const std::uint32_t Halfway = std::uint32_t{1} << (Shift - 1U); - if (Remainder > Halfway || (Remainder == Halfway && (Rounded & 1U) != 0)) - ++Rounded; - return static_cast(Sign | Rounded); - } - - std::uint32_t Rounded = Fraction >> 13U; - const std::uint32_t Remainder = Fraction & 0x1FFFU; - if (Remainder > 0x1000U || (Remainder == 0x1000U && (Rounded & 1U) != 0)) { - ++Rounded; - if (Rounded == 0x400U) - return static_cast(Sign | - (static_cast(HalfExponent + 1) << 10U)); - } - return static_cast(Sign | (static_cast(HalfExponent) << 10U) | - Rounded); -} - -[[nodiscard]] std::uint16_t floatToBFloatBits(float Value) noexcept { - std::uint32_t Bits = std::bit_cast(Value); - const std::uint32_t Bias = 0x7FFFU + ((Bits >> 16U) & 1U); - Bits += Bias; - return static_cast(Bits >> 16U); -} - [[nodiscard]] std::optional minimumTypeSizeInBits(LLT Type) noexcept { if (!Type.isValid() || Type.isToken()) return std::nullopt; @@ -493,6 +392,8 @@ private: [[nodiscard]] bool parseRegisterOperand(bool IsDef, ParsedOperand &Result); [[nodiscard]] bool parseTargetFlags(std::uint32_t &Flags); [[nodiscard]] bool parseOperand(bool IsDef, ParsedOperand &Result); + [[nodiscard]] bool parseInlineAsmExtraInfoOperand(ParsedOperand &Result); + [[nodiscard]] bool parseInlineAsmOperandFlag(ParsedOperand &Result); [[nodiscard]] bool parseTypedIntegerOperand(ParsedOperand &Result); [[nodiscard]] bool parseFloatingImmediateOperand(ParsedOperand &Result); [[nodiscard]] bool parseMetadataReference(MachineMetadataReference &Result); @@ -684,7 +585,8 @@ bool MIParser::Impl::parseScalarLowLevelType(const MIToken &TypeToken, LLT &Resu std::uint64_t Value = 0; if (!parseUnsignedDigits(Digits, Value)) - return fail(TypeToken, "low-level type suffix must be an unsigned decimal integer"); + return fail(TypeToken, + "expected integers after 's'/'i'/'f'/'bf'/'p' type identifier"); Expected Created = makeUnexpected(ErrorCode::ParseError, "invalid low-level type"); switch (Kind) { @@ -739,40 +641,67 @@ bool MIParser::Impl::parseScalarLowLevelType(const MIToken &TypeToken, LLT &Resu bool MIParser::Impl::parseLowLevelType(LLT &Result) { const MIToken Begin = token(); + const auto HasScalarPrefix = [](std::string_view Spelling) { + return Spelling.starts_with('s') || Spelling.starts_with('i') || + Spelling.starts_with('f') || Spelling.starts_with("bf") || + Spelling.starts_with('p'); + }; if (at(MITokenKind::Identifier)) { + if (!HasScalarPrefix(token().stringValue())) { + return fail(Begin, + "expected tN, pA, , , , or " + " for GlobalISel type, where t = {'s', 'i', 'f', 'bf'}"); + } if (parseScalarLowLevelType(token(), Result)) return true; advance(); return false; } - if (!consume(MITokenKind::Less)) - return fail(Begin, "expected a scalar, pointer, or vector low-level type"); + if (!consume(MITokenKind::Less)) { + return fail(Begin, + "expected tN, pA, , , , or " + " for GlobalISel type, where t = {'s', 'i', 'f', 'bf'}"); + } bool Scalable = false; if (at(MITokenKind::Identifier) && token().stringValue() == "vscale") { Scalable = true; advance(); - if (!at(MITokenKind::Identifier) || token().stringValue() != "x") - return fail("expected 'x' after 'vscale' in a scalable vector type"); + if (!at(MITokenKind::Identifier) || token().stringValue() != "x") { + return fail( + "expected , where t = {'s', 'i', 'f', 'bf', 'p'}"); + } advance(); } + const auto VectorError = [this, &Begin, Scalable] { + if (Scalable) { + return fail(Begin, + "expected for vector type, where t = " + "{'s', 'i', 'f', 'bf', 'p'}"); + } + return fail(Begin, + "expected for vector type, where t = {'s', 'i', 'f', 'bf', 'p'}"); + }; + std::uint64_t ElementCount = 0; if (!at(MITokenKind::IntegerLiteral) || parseUnsigned(token(), ElementCount)) - return fail("expected an unsigned vector element count"); + return VectorError(); advance(); if (!at(MITokenKind::Identifier) || token().stringValue() != "x") - return fail("expected 'x' after the vector element count"); + return VectorError(); advance(); + if (!at(MITokenKind::Identifier) || !HasScalarPrefix(token().stringValue())) + return VectorError(); LLT ElementType; if (parseScalarLowLevelType(token(), ElementType)) return true; if (ElementType.isToken()) return fail("token cannot be a vector element type"); advance(); - if (expect(MITokenKind::Greater, "'>' after the vector low-level type")) - return true; + if (!consume(MITokenKind::Greater)) + return VectorError(); Expected Created = Scalable ? LLT::makeScalableVector(ElementCount, ElementType) : LLT::makeFixedVector(ElementCount, ElementType); @@ -821,6 +750,8 @@ bool MIParser::Impl::parseBlockAttributes(MachineBasicBlock *Block) { } else if (at(MITokenKind::IRBlock) || at(MITokenKind::NamedIRBlock)) { if (!Seen.insert("ir-block").second) return fail("duplicate IR block attribute"); + if (Context.HasIRBlock && !Context.HasIRBlock(symbolName(token()))) + return fail(token(), "use of undefined IR block '" + std::string(token().spelling()) + "'"); if (Block != nullptr) { auto SetName = Block->attributes().setIRBlockName(symbolName(token())); if (!SetName) @@ -915,7 +846,14 @@ bool MIParser::Impl::parseBlockDefinition(bool Create, std::uint32_t &ID) { if (Blocks.size() >= Options.MaximumBlocks) return fail(Label, "machine basic block count exceeds parser limit"); if (Blocks.contains(ID)) - return fail(Label, "redefinition of machine basic block #" + std::to_string(ID)); + return fail(Label, + "redefinition of machine basic block with id #" + std::to_string(ID)); + if (!Label.stringValue().empty() && Context.HasIRBlock && + !Context.HasIRBlock(Label.stringValue())) { + return fail(Label, "basic block '" + std::string(Label.stringValue()) + + "' is not defined in the function '" + + std::string(Function.name()) + "'"); + } auto Created = Function.createBasicBlock(); if (!Created) return importError(Label, Created.error()); @@ -1000,6 +938,12 @@ Register MIParser::Impl::getOrCreateVirtualRegister(const MIToken &Token) { static_cast(importError(Token, Created.error())); return {}; } + auto Info = Function.registerInfo().virtualRegisterInfo(*Created); + if (!Info) { + static_cast(importError(Token, Info.error())); + return {}; + } + Info->get().Name = Name; NamedVirtualRegisters.emplace(Name, *Created); return *Created; } @@ -1345,96 +1289,22 @@ bool MIParser::Impl::parseFloatingImmediateOperand(ParsedOperand &Result) { const auto Semantics = floatSemanticsForKeyword(Begin.stringValue()); if (!Semantics) return fail(Begin, "unknown floating-point immediate type"); - const std::uint32_t BitWidth = getFloatSemanticsBitWidth(*Semantics).value_or(0); advance(); if (!at(MITokenKind::FloatingPointLiteral) && !at(MITokenKind::HexLiteral)) return fail("expected a floating-point literal"); const MIToken Literal = token(); - const std::string_view Spelling = Literal.spelling(); - std::vector Words; - if (Spelling.starts_with("f0x") || Spelling.starts_with("f0X")) { - if (!parseHexWords(Spelling.substr(3), BitWidth, Words)) - return fail(Literal, "raw floating-point literal does not fit its semantics"); - } else if (Spelling.starts_with("0x") || Spelling.starts_with("0X")) { - std::string_view Digits = Spelling.substr(2); - char Prefix = '\0'; - if (!Digits.empty() && !hexDigit(Digits.front())) { - Prefix = Digits.front(); - Digits.remove_prefix(1); - } - char ExpectedPrefix = '\0'; - switch (*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') { - if (Prefix != ExpectedPrefix || !parseHexWords(Digits, BitWidth, Words)) - return fail(Literal, "floating-point hexadecimal prefix does not match its semantics"); - } else if (Prefix != '\0') { - return fail(Literal, "unexpected floating-point hexadecimal prefix"); - } else if (*Semantics == FloatSemantics::IEEESingle) { - std::uint64_t DoubleBits = 0; - if (!parseHexadecimal(Spelling, DoubleBits)) - return fail(Literal, "invalid IEEE single hexadecimal literal"); - const float Value = static_cast(std::bit_cast(DoubleBits)); - Words = {std::bit_cast(Value)}; - } else if (*Semantics == FloatSemantics::IEEEDouble) { - std::uint64_t DoubleBits = 0; - if (!parseHexadecimal(Spelling, DoubleBits)) - return fail(Literal, "invalid IEEE double hexadecimal literal"); - Words = {DoubleBits}; - } else { - return fail(Literal, "floating-point literal requires its semantics prefix"); - } - } else { - const char *First = Spelling.data(); - const char *Last = First + Spelling.size(); - if (*Semantics == FloatSemantics::IEEEDouble) { - double Value = 0; - const auto Parsed = std::from_chars(First, Last, Value, std::chars_format::general); - if (Parsed.ec != std::errc{} || Parsed.ptr != Last) - return fail(Literal, "invalid IEEE double decimal literal"); - Words = {std::bit_cast(Value)}; - } else if (*Semantics == FloatSemantics::IEEESingle || *Semantics == FloatSemantics::IEEEHalf || - *Semantics == FloatSemantics::BFloat) { - float Value = 0; - const auto Parsed = std::from_chars(First, Last, Value, std::chars_format::general); - if (Parsed.ec != std::errc{} || Parsed.ptr != Last) - return fail(Literal, "invalid floating-point decimal literal"); - if (*Semantics == FloatSemantics::IEEESingle) - Words = {std::bit_cast(Value)}; - else if (*Semantics == FloatSemantics::IEEEHalf) - Words = {floatToHalfBits(Value)}; - else - Words = {floatToBFloatBits(Value)}; - } else { - return fail(Literal, "this floating-point semantics requires an exact hexadecimal literal"); - } - } - - auto Bits = FloatingPointBits::create(*Semantics, Words); - if (!Bits) - return importError(Literal, Bits.error()); + std::string ConstantText(Begin.stringValue()); + ConstantText.push_back(' '); + ConstantText.append(Literal.spelling()); + auto Constant = parseLLVMConstant(ConstantText); + if (!Constant) + return fail(Literal, std::string(Constant.error().message())); + if (!Constant->FloatingValue || Constant->FloatingValue->semantics() != *Semantics) + return fail(Literal, "floating-point literal produced the wrong semantics"); advance(); Result = ParsedOperand{ - MachineOperand::floatingImmediate(*Bits), + MachineOperand::floatingImmediate(*Constant->FloatingValue), std::nullopt, {Begin.range().Begin, token().range().Begin}, }; @@ -1485,10 +1355,10 @@ bool MIParser::Impl::parseMetadataOperand(ParsedOperand &Result) { while (!at(MITokenKind::RightParen)) { if (at(MITokenKind::Identifier)) { const auto Opcode = parseDIExpressionOpcodeName(token().stringValue()); - if (!Opcode) - return fail("unknown debug expression opcode '" + std::string(token().stringValue()) + - "'"); - Elements.push_back(*Opcode); + const auto Encoding = parseDIExpressionAttributeEncodingName(token().stringValue()); + if (!Opcode && !Encoding) + return fail(invalidDIExpressionElementMessage(token().stringValue())); + Elements.push_back(Opcode ? *Opcode : *Encoding); advance(); } else if (at(MITokenKind::IntegerLiteral)) { std::int64_t Signed = 0; @@ -1855,10 +1725,22 @@ bool MIParser::Impl::parseMemoryBase(MemoryBase &Result, std::int64_t &Offset) { break; case MITokenKind::IRValue: case MITokenKind::NamedIRValue: - case MITokenKind::QuotedIRValue: Result = MemoryBase::irValue(symbolName(BaseToken)); advance(); break; + case MITokenKind::QuotedIRValue: { + LLVMConstantSyntaxOptions ConstantOptions; + ConstantOptions.DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits; + auto Constant = parseLLVMConstant(BaseToken.stringValue(), ConstantOptions); + if (!Constant) + return importError(BaseToken, Constant.error()); + auto Base = MemoryBase::irConstant(std::move(*Constant)); + if (!Base) + return importError(BaseToken, Base.error()); + Result = std::move(*Base); + advance(); + break; + } case MITokenKind::Keyword: if (BaseToken.isKeyword("unknown-address")) { Result = MemoryBase(); @@ -2021,6 +1903,7 @@ bool MIParser::Impl::parseMemoryOperand(const MachineMemOperand *&Result) { if (!Size) return importError(Begin, Size.error()); Description.Size = *Size; + Description.MemoryType = Type; } else { return fail("expected a memory size, unknown-size, or memory low-level type"); } @@ -2044,21 +1927,27 @@ bool MIParser::Impl::parseMemoryOperand(const MachineMemOperand *&Result) { advance(); if (parseMemoryBase(Description.Base, Description.Offset)) return true; + if (const GenericConstantData *Constant = Description.Base.constantValue()) + Description.AddressSpace = Constant->Type.AddressSpace; } + Alignment BaseAlignment = Description.Align; std::unordered_set SeenOptions; while (consume(MITokenKind::Comma)) { const std::string Name(token().spelling()); if (!SeenOptions.insert(Name).second) return fail("duplicate machine memory operand option '" + Name + "'"); if (atKeyword("align")) { - if (parseAlignment(Description.Align)) + Alignment ParsedAlignment; + if (parseAlignment(ParsedAlignment)) return true; + if ((static_cast(Description.Offset) & (ParsedAlignment.bytes() - 1U)) != 0) { + return fail("specified alignment is more aligned than offset"); + } + BaseAlignment = ParsedAlignment; } else if (atKeyword("basealign")) { - Alignment BaseAlignment; if (parseAlignment(BaseAlignment)) return true; - Description.BaseAlign = BaseAlignment; } else if (atKeyword("addrspace")) { advance(); if (!at(MITokenKind::IntegerLiteral) || parseUnsigned32(token(), Description.AddressSpace) || @@ -2088,6 +1977,19 @@ bool MIParser::Impl::parseMemoryOperand(const MachineMemOperand *&Result) { return fail("unknown machine memory operand option '" + Name + "'"); } } + + std::uint64_t EffectiveAlignment = BaseAlignment.bytes(); + if (Description.Offset != 0) { + const auto OffsetBits = static_cast(Description.Offset); + const std::uint64_t OffsetAlignment = std::uint64_t{1} << std::countr_zero(OffsetBits); + EffectiveAlignment = std::min(EffectiveAlignment, OffsetAlignment); + } + Description.Align = *Alignment::create(EffectiveAlignment); + if (BaseAlignment != Description.Align) + Description.BaseAlign = BaseAlignment; + else + Description.BaseAlign.reset(); + if (expect(MITokenKind::RightParen, "')' after the machine memory operand")) return true; @@ -2527,6 +2429,78 @@ bool MIParser::Impl::parseOperand(bool IsDef, ParsedOperand &Result) { return false; } +bool MIParser::Impl::parseInlineAsmExtraInfoOperand(ParsedOperand &Result) { + const MIToken Begin = token(); + InlineAsmExtraInfo ExtraInfo; + while (at(MITokenKind::Identifier)) { + const auto Bits = InlineAsmExtraInfo::namedFlagBits(token().stringValue()); + if (!Bits) { + return fail("unknown inline asm extra info flag '" + std::string(token().stringValue()) + + "'"); + } + ExtraInfo = InlineAsmExtraInfo(ExtraInfo.rawValue() | *Bits); + advance(); + } + Result = ParsedOperand{ + MachineOperand::immediate(static_cast(ExtraInfo.rawValue())), + std::nullopt, + {Begin.range().Begin, token().range().Begin}, + }; + return false; +} + +bool MIParser::Impl::parseInlineAsmOperandFlag(ParsedOperand &Result) { + const MIToken Begin = token(); + const std::string KindName(token().stringValue()); + const auto Kind = InlineAsmOperandFlag::kindForName(KindName); + if (!Kind) + return fail("unknown inline asm operand kind '" + KindName + "'"); + advance(); + + InlineAsmOperandFlag Flag(*Kind); + if (at(MITokenKind::Identifier) && token().stringValue() == "tiedto") { + advance(); + if (!consume(MITokenKind::Colon)) + return fail("expected ':' after 'tiedto'"); + if (!at(MITokenKind::NamedRegister)) + return fail("expected '$N' operand number after 'tiedto:'"); + std::uint64_t OperandNumber = 0; + if (!parseUnsignedDecimal(token().stringValue(), OperandNumber) || + OperandNumber > std::numeric_limits::max() || + !Flag.setMatchingOperand(static_cast(OperandNumber))) { + return fail("invalid operand number in tiedto constraint"); + } + advance(); + } else if (consume(MITokenKind::Colon)) { + if (!at(MITokenKind::Identifier)) + return fail("expected register class or memory constraint name after ':'"); + const std::string Constraint(token().stringValue()); + if (*Kind == InlineAsmOperandKind::Memory) { + const auto MemoryConstraint = InlineAsmOperandFlag::memoryConstraintForName(Constraint); + if (!MemoryConstraint) + return fail("unknown memory constraint '" + Constraint + "'"); + if (!Flag.setMemoryConstraint(*MemoryConstraint)) + return fail("invalid memory constraint encoding"); + } else if (*Kind == InlineAsmOperandKind::RegisterDef || + *Kind == InlineAsmOperandKind::RegisterUse || + *Kind == InlineAsmOperandKind::RegisterDefEarlyClobber) { + const RegisterClassDesc *RegisterClass = Target.registers().registerClass(Constraint); + if (RegisterClass == nullptr) + return fail("unknown register class '" + Constraint + "'"); + if (!Flag.setRegisterClass(RegisterClass->ID.value())) + return fail("register class cannot be encoded in an inline asm operand flag"); + } + advance(); + } + + Result = ParsedOperand{ + MachineOperand::immediate(static_cast(Flag.rawValue())), + std::nullopt, + {Begin.range().Begin, token().range().Begin}, + }; + return false; +} + bool MIParser::Impl::parseBlockLiveIns(MachineBasicBlock &Block) { advance(); if (expect(MITokenKind::Colon, "':' after liveins")) @@ -2566,7 +2540,7 @@ bool MIParser::Impl::parseBlockSuccessors(MachineBasicBlock &Block) { return false; do { if (!at(MITokenKind::MachineBasicBlock)) - return fail("expected a machine basic block successor"); + return fail("expected a machine basic block reference"); MachineBasicBlock *Successor = blockFor(token()); if (Successor == nullptr) return true; @@ -2575,7 +2549,7 @@ bool MIParser::Impl::parseBlockSuccessors(MachineBasicBlock &Block) { if (consume(MITokenKind::LeftParen)) { std::uint32_t Weight = 0; if (parseUnsigned32(token(), Weight)) - return fail("invalid successor probability weight"); + return fail("expected unsigned integer"); Probability = BranchProbability::fromRaw(Weight); advance(); if (expect(MITokenKind::RightParen, "')' after successor weight")) @@ -2623,6 +2597,7 @@ bool MIParser::Impl::parseInstruction(MachineBasicBlock &Block, bool InBundle, if (Descriptor == nullptr) return fail("unknown machine instruction '" + std::string(OpcodeToken.stringValue()) + "'"); advance(); + const bool IsInlineAsm = Descriptor->Name == "INLINEASM" || Descriptor->Name == "INLINEASM_BR"; while (!token().isNewlineOrEnd() && !at(MITokenKind::DoubleColon) && !at(MITokenKind::LeftBrace) && !atKeyword("debug-instr-number") && @@ -2633,7 +2608,16 @@ bool MIParser::Impl::parseInstruction(MachineBasicBlock &Block, bool InBundle, if (Operands.size() >= Options.MaximumOperandsPerInstruction) return fail("instruction operand count exceeds parser limit"); ParsedOperand Operand{MachineOperand::immediate(0), std::nullopt, {}}; - if (parseOperand(false, Operand)) + const bool IsInlineAsmExtraInfo = + IsInlineAsm && Operands.size() == InlineAsmExtraInfoOperandIndex && + at(MITokenKind::Identifier); + const bool IsInlineAsmOperandFlag = + IsInlineAsm && Operands.size() >= InlineAsmFirstConstraintOperandIndex && + at(MITokenKind::Identifier); + if (IsInlineAsmExtraInfo + ? parseInlineAsmExtraInfoOperand(Operand) + : (IsInlineAsmOperandFlag ? parseInlineAsmOperandFlag(Operand) + : parseOperand(false, Operand))) return true; Operands.push_back(std::move(Operand)); if (token().isNewlineOrEnd() || at(MITokenKind::DoubleColon) || at(MITokenKind::LeftBrace) || @@ -2885,6 +2869,7 @@ bool MIParser::Impl::inferSuccessors() { continue; bool BlocksFallthrough = true; + std::vector Successors; for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; Instruction = Instruction->next()) { const InstructionDescriptor *Descriptor = @@ -2894,17 +2879,25 @@ bool MIParser::Impl::inferSuccessors() { if (Descriptor->Flags.has(InstructionFlag::Branch)) { for (const MachineOperand &Operand : Instruction->operands()) { if (const BasicBlockOperand *TargetBlock = Operand.getIf()) { - auto Added = Function.cfgUpdater().addSuccessor(*Block, *TargetBlock->Block); - if (!Added) - return importError(token(), Added.error()); + if (std::ranges::find(Successors, TargetBlock->Block) == Successors.end()) + Successors.push_back(TargetBlock->Block); } } } if (Descriptor->Flags.has(InstructionFlag::Barrier)) BlocksFallthrough = false; } - if (BlocksFallthrough && Block->next() != nullptr) { - auto Added = Function.cfgUpdater().addSuccessor(*Block, *Block->next()); + if (BlocksFallthrough && Block->next() != nullptr && + std::ranges::find(Successors, Block->next()) == Successors.end()) + Successors.push_back(Block->next()); + if (Successors.empty()) + continue; + const auto Probability = + BranchProbability::fromFraction(1U, static_cast(Successors.size())); + if (!Probability) + return importError(token(), Probability.error()); + for (MachineBasicBlock *Successor : Successors) { + auto Added = Function.cfgUpdater().addSuccessor(*Block, *Successor, *Probability); if (!Added) return importError(token(), Added.error()); } diff --git a/backend/lib/MIR/MIPrinter.cpp b/backend/lib/MIR/MIPrinter.cpp index 8ad6d6c..9e86b72 100644 --- a/backend/lib/MIR/MIPrinter.cpp +++ b/backend/lib/MIR/MIPrinter.cpp @@ -2,6 +2,7 @@ #include "rcc/MIR/MIPrinter.h" +#include "rcc/CodeGen/InlineAsm.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFIInfo.h" @@ -14,6 +15,8 @@ #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 #include @@ -83,52 +86,6 @@ namespace { return Result; } -[[nodiscard]] std::string unsignedDecimal(std::span InputWords) { - std::vector Words(InputWords.begin(), InputWords.end()); - while (!Words.empty() && Words.back() == 0) - Words.pop_back(); - if (Words.empty()) - return "0"; - - std::string Reversed; - while (!Words.empty()) { - std::uint64_t Remainder = 0; - for (std::size_t Index = Words.size(); Index != 0; --Index) { - const unsigned __int128 Dividend = - (static_cast(Remainder) << 64U) | Words[Index - 1U]; - Words[Index - 1U] = static_cast(Dividend / 10U); - Remainder = static_cast(Dividend % 10U); - } - Reversed.push_back(static_cast('0' + Remainder)); - while (!Words.empty() && Words.back() == 0) - Words.pop_back(); - } - std::ranges::reverse(Reversed); - return Reversed; -} - -[[nodiscard]] std::string_view floatKeyword(FloatSemantics Semantics) noexcept { - switch (Semantics) { - case FloatSemantics::IEEEHalf: - return "half"; - case FloatSemantics::BFloat: - return "bfloat"; - case FloatSemantics::IEEESingle: - return "float"; - case FloatSemantics::IEEEDouble: - return "double"; - case FloatSemantics::IEEEQuad: - return "fp128"; - case FloatSemantics::PPCDoubleDouble: - case FloatSemantics::PPCDoubleDoubleLegacy: - return "ppc_fp128"; - case FloatSemantics::X87DoubleExtended: - return "x86_fp80"; - default: - return {}; - } -} - [[nodiscard]] std::string_view integerPredicateName(IntegerPredicate Predicate) noexcept { switch (Predicate) { case IntegerPredicate::Equal: @@ -314,9 +271,13 @@ private: [[nodiscard]] Expected initialize(); [[nodiscard]] Expected printBlock(const MachineBasicBlock &Block); [[nodiscard]] Expected printInstruction(const MachineInstr &Instruction); - [[nodiscard]] Expected printOperand(const MachineOperand &Operand, bool IsExplicitDef); + [[nodiscard]] Expected printOperand(const MachineOperand &Operand, bool IsExplicitDef, + bool PrintVirtualType = true); + [[nodiscard]] Expected printInlineAsmExtraInfo(const MachineOperand &Operand); + [[nodiscard]] Expected + printInlineAsmOperandFlag(const MachineOperand &Operand); [[nodiscard]] Expected printRegisterOperand(const MachineOperand &Operand, - bool IsExplicitDef); + bool IsExplicitDef, bool PrintVirtualType); [[nodiscard]] Expected printTargetFlags(std::uint32_t Flags); [[nodiscard]] Expected printMetadataReference(MachineMetadataReference Reference); [[nodiscard]] Expected printDILocation(const MachineDILocation &Location, @@ -437,11 +398,6 @@ Expected BodyPrinter::appendPhysicalRegister(PhysicalRegister Register) { void BodyPrinter::appendBlockReference(const MachineBasicBlock &Block) { Output.append("%bb."); Output.append(std::to_string(BlockIDs.at(&Block))); - const auto &Name = Block.attributes().name(); - if (Name) { - Output.push_back('.'); - Output.append(quoteName(*Name)); - } } void BodyPrinter::appendOffset(std::int64_t Offset) { @@ -528,7 +484,7 @@ Expected BodyPrinter::printDILocation(const MachineDILocation &Location, } Expected BodyPrinter::printRegisterOperand(const MachineOperand &Operand, - bool IsExplicitDef) { + bool IsExplicitDef, bool PrintVirtualType) { const RegisterOperandFlags Flags = Operand.registerFlags(); if (Flags.has(RegisterOperandFlag::Implicit) && Flags.has(RegisterOperandFlag::Define)) { Output.append("implicit-def "); @@ -570,11 +526,19 @@ Expected BodyPrinter::printRegisterOperand(const MachineOperand &Operand, if (!Printed) return Printed; } else { - auto VirtualIndex = virtualRegisterID(Reg); - if (!VirtualIndex) - return std::unexpected(VirtualIndex.error()); + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + const VirtualRegisterInfo &RegisterInfo = Info->get(); Output.push_back('%'); - Output.append(std::to_string(*VirtualIndex)); + if (RegisterInfo.Name) { + Output.append(*RegisterInfo.Name); + } else { + auto VirtualIndex = virtualRegisterID(Reg); + if (!VirtualIndex) + return std::unexpected(VirtualIndex.error()); + Output.append(std::to_string(*VirtualIndex)); + } if (Operand.subReg().value() != 0) { const SubRegIndexDesc *SubReg = Target.registers().subRegIndex(Operand.subReg()); if (SubReg == nullptr) @@ -584,28 +548,26 @@ Expected BodyPrinter::printRegisterOperand(const MachineOperand &Operand, Output.append(SubReg->Name); } - auto Info = Function.registerInfo().virtualRegisterInfo(Reg); - if (!Info) - return std::unexpected(Info.error()); - const VirtualRegisterInfo &RegisterInfo = Info->get(); - if (RegisterInfo.RegClass) { + const bool PrintRegisterConstraint = + IsExplicitDef || Function.registerInfo().getUniqueVRegDef(Reg) == nullptr; + if (PrintRegisterConstraint && RegisterInfo.RegClass) { const auto Name = Target.registers().registerClassMIRName(*RegisterInfo.RegClass); if (!Name) return makeUnexpected(ErrorCode::VerificationError, "virtual register class has no MIR name"); Output.push_back(':'); Output.append(*Name); - } else if (RegisterInfo.Bank) { + } else if (PrintRegisterConstraint && RegisterInfo.Bank) { const auto Name = Target.registers().registerBankMIRName(*RegisterInfo.Bank); if (!Name) return makeUnexpected(ErrorCode::VerificationError, "virtual register bank has no MIR name"); Output.push_back(':'); Output.append(*Name); - } else if (RegisterInfo.Type) { + } else if (PrintRegisterConstraint && RegisterInfo.Type) { Output.append(":_"); } - if (RegisterInfo.Type) { + if (PrintVirtualType && RegisterInfo.Type) { auto Type = llvmMIRLowLevelType(*RegisterInfo.Type); if (!Type) return std::unexpected(Type.error()); @@ -801,9 +763,10 @@ Expected BodyPrinter::printCFIInstruction(CFIInstructionIndex Index) { return makeUnexpected(ErrorCode::InternalError, "CFI printer did not handle an operation"); } -Expected BodyPrinter::printOperand(const MachineOperand &Operand, bool IsExplicitDef) { +Expected BodyPrinter::printOperand(const MachineOperand &Operand, bool IsExplicitDef, + bool PrintVirtualType) { if (Operand.isRegister()) - return printRegisterOperand(Operand, IsExplicitDef); + return printRegisterOperand(Operand, IsExplicitDef, PrintVirtualType); if (auto Flags = printTargetFlags(Operand.targetFlags()); !Flags) return Flags; @@ -819,19 +782,21 @@ Expected BodyPrinter::printOperand(const MachineOperand &Operand, bool IsE Output.push_back('i'); Output.append(std::to_string(Value.bitWidth())); Output.push_back(' '); - Output.append(unsignedDecimal(Value.words())); + if (Value.bitWidth() == 1U) + Output.append(Value.isZero() ? "false" : "true"); + else + Output.append(Value.toSignedDecimal()); return {}; } case MachineOperand::Kind::FloatingImmediate: { const FloatingPointBits &Bits = Operand.getIf()->Bits; - const std::string_view Keyword = floatKeyword(Bits.semantics()); - if (Keyword.empty()) { - return makeUnexpected(ErrorCode::Unsupported, - "floating-point semantics has no LLVM MIR spelling"); - } - Output.append(Keyword); - Output.append(" f0x"); - Output.append(hexadecimalWords(Bits.words(), Bits.bitWidth())); + auto Constant = GenericConstantData::floating(Bits); + if (!Constant) + return std::unexpected(Constant.error()); + auto Text = printLLVMConstant(*Constant); + if (!Text) + return std::unexpected(Text.error()); + Output.append(*Text); return {}; } case MachineOperand::Kind::BasicBlock: { @@ -952,14 +917,7 @@ Expected BodyPrinter::printOperand(const MachineOperand &Operand, bool IsE return makeUnexpected(ErrorCode::VerificationError, "metadata operand has no printable value"); } - Output.append("!DIExpression("); - const auto Elements = Expression->elements(); - for (std::size_t Index = 0; Index < Elements.size(); ++Index) { - if (Index != 0) - Output.append(", "); - Output.append(std::to_string(Elements[Index])); - } - Output.push_back(')'); + Output.append(formatDIExpression(Expression->elements())); return {}; } case MachineOperand::Kind::TargetIndex: { @@ -1039,6 +997,68 @@ Expected BodyPrinter::printOperand(const MachineOperand &Operand, bool IsE return makeUnexpected(ErrorCode::InternalError, "machine operand printer did not handle a kind"); } +Expected BodyPrinter::printInlineAsmExtraInfo(const MachineOperand &Operand) { + const auto *Value = Operand.getIf(); + if (Value == nullptr || Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "inline assembly extra info must be an unflagged immediate"); + } + const InlineAsmExtraInfo ExtraInfo(static_cast(*Value)); + const std::vector Names = ExtraInfo.names(); + for (std::size_t Index = 0; Index < Names.size(); ++Index) { + if (Index != 0) + Output.push_back(' '); + Output.append(Names[Index]); + } + return {}; +} + +Expected BodyPrinter::printInlineAsmOperandFlag(const MachineOperand &Operand) { + const auto *Value = Operand.getIf(); + if (Value == nullptr || *Value < 0 || + static_cast(*Value) > std::numeric_limits::max() || + Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "inline assembly operand flag must be an unflagged 32-bit immediate"); + } + + const InlineAsmOperandFlag Flag(static_cast(*Value)); + const std::string_view KindName = InlineAsmOperandFlag::kindName(Flag.kind()); + if (KindName.empty() || Flag.operandCount() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "inline assembly operand flag has an invalid kind or operand count"); + } + Output.append(KindName); + + if (const auto MatchingOperand = Flag.matchingOperand()) { + Output.append(" tiedto:$"); + Output.append(std::to_string(*MatchingOperand)); + return Flag.operandCount(); + } + if (Flag.kind() == InlineAsmOperandKind::Memory || + Flag.kind() == InlineAsmOperandKind::Function) { + const auto Constraint = InlineAsmOperandFlag::memoryConstraintName(Flag.memoryConstraint()); + if (Constraint) { + Output.push_back(':'); + Output.append(*Constraint); + } + return Flag.operandCount(); + } + if (Flag.isRegisterKind()) { + if (const auto RegisterClass = Flag.registerClass()) { + const auto Name = + Target.registers().registerClassMIRName(RegisterClassID::fromValue(*RegisterClass)); + if (!Name) { + return makeUnexpected(ErrorCode::VerificationError, + "inline assembly operand flag references an unknown register class"); + } + Output.push_back(':'); + Output.append(*Name); + } + } + return Flag.operandCount(); +} + Expected BodyPrinter::printMemoryBase(const MemoryBase &Base, std::int64_t Offset) { switch (Base.kind()) { case MemoryBase::Kind::Unknown: @@ -1123,6 +1143,20 @@ Expected BodyPrinter::printMemoryBase(const MemoryBase &Base, std::int64_t Output.append(quoteName(*Name)); break; } + case MemoryBase::Kind::IRConstant: { + const GenericConstantData *Constant = Base.constantValue(); + if (Constant == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "IR-constant memory base has no constant value"); + } + auto Printed = printLLVMConstant(*Constant); + if (!Printed) + return std::unexpected(Printed.error()); + Output.push_back('`'); + Output.append(*Printed); + Output.push_back('`'); + break; + } case MemoryBase::Kind::Stack: Output.append("stack"); break; @@ -1219,18 +1253,27 @@ Expected BodyPrinter::printMemoryOperand(const MachineMemOperand &Operand) } Output.push_back(' '); - switch (Description.Size.kind()) { - case MemorySize::Kind::Unknown: - Output.append("unknown-size"); - break; - case MemorySize::Kind::Fixed: - Output.append(std::to_string(Description.Size.minimumBytes())); - break; - case MemorySize::Kind::Scalable: - Output.append("()"); - break; + if (Description.MemoryType) { + auto Type = llvmMIRLowLevelType(*Description.MemoryType); + if (!Type) + return std::unexpected(Type.error()); + Output.push_back('('); + Output.append(*Type); + Output.push_back(')'); + } else { + switch (Description.Size.kind()) { + case MemorySize::Kind::Unknown: + Output.append("unknown-size"); + break; + case MemorySize::Kind::Fixed: + Output.append(std::to_string(Description.Size.minimumBytes())); + break; + case MemorySize::Kind::Scalable: + Output.append("()"); + break; + } } const std::string_view Preposition = @@ -1240,9 +1283,13 @@ Expected BodyPrinter::printMemoryOperand(const MachineMemOperand &Operand) return Printed; } - Output.append(", align "); - Output.append(std::to_string(Description.Align.bytes())); - if (Description.BaseAlign) { + if (!Description.Size.isKnown() || + (Description.Size.minimumBytes() != 0 && + Description.Align.bytes() != Description.Size.minimumBytes())) { + Output.append(", align "); + Output.append(std::to_string(Description.Align.bytes())); + } + if (Description.BaseAlign && *Description.BaseAlign != Description.Align) { Output.append(", basealign "); Output.append(std::to_string(Description.BaseAlign->bytes())); } @@ -1337,13 +1384,74 @@ Expected BodyPrinter::printInstruction(const MachineInstr &Instruction) { Output.append(Descriptor->Name); bool HasPrintedTail = false; + const bool IsInlineAsm = Descriptor->Name == "INLINEASM" || Descriptor->Name == "INLINEASM_BR"; + std::size_t InlineAsmPayloadRemaining = 0; for (std::size_t Index = ExplicitDefs; Index < Instruction.operandCount(); ++Index) { Output.append(HasPrintedTail ? ", " : " "); HasPrintedTail = true; - if (auto Printed = printOperand(*Instruction.operandAt(Index), false); !Printed) { - return Printed; + const MachineOperand &Operand = *Instruction.operandAt(Index); + if (IsInlineAsm && Index == InlineAsmExtraInfoOperandIndex) { + auto Printed = printInlineAsmExtraInfo(Operand); + if (!Printed) + return Printed; + continue; } + if (IsInlineAsm && Index >= InlineAsmFirstConstraintOperandIndex && + InlineAsmPayloadRemaining == 0) { + auto OperandCount = printInlineAsmOperandFlag(Operand); + if (!OperandCount) + return std::unexpected(OperandCount.error()); + InlineAsmPayloadRemaining = *OperandCount; + continue; + } + bool PrintVirtualType = true; + if (Descriptor->Flags.has(InstructionFlag::Generic) && + !Descriptor->Flags.has(InstructionFlag::Phi) && + Index < Descriptor->Operands.size()) { + const std::optional &UseConstraint = + Descriptor->Operands[Index].TypeConstraint; + if (UseConstraint) { + for (std::size_t DefIndex = 0; DefIndex < ExplicitDefs; ++DefIndex) { + if (DefIndex >= Descriptor->Operands.size()) + break; + const std::optional &DefConstraint = + Descriptor->Operands[DefIndex].TypeConstraint; + if (DefConstraint && DefConstraint->EqualityGroup == UseConstraint->EqualityGroup) { + PrintVirtualType = false; + break; + } + } + } else if (const Register *UseRegister = Operand.getIf(); + UseRegister != nullptr && UseRegister->isVirtual()) { + const auto UseInfo = Function.registerInfo().virtualRegisterInfo(*UseRegister); + for (std::size_t DefIndex = 0; DefIndex < ExplicitDefs && UseInfo && + UseInfo->get().Type; + ++DefIndex) { + if (DefIndex >= Descriptor->Operands.size() || + Descriptor->Operands[DefIndex].TypeConstraint) + continue; + const MachineOperand *DefOperand = Instruction.operandAt(DefIndex); + const Register *DefRegister = + DefOperand == nullptr ? nullptr : DefOperand->getIf(); + if (DefRegister == nullptr || !DefRegister->isVirtual()) + continue; + const auto DefInfo = Function.registerInfo().virtualRegisterInfo(*DefRegister); + if (DefInfo && DefInfo->get().Type == UseInfo->get().Type) { + PrintVirtualType = false; + break; + } + } + } + } + auto Printed = printOperand(Operand, false, PrintVirtualType); + if (!Printed) + return Printed; + if (IsInlineAsm && InlineAsmPayloadRemaining != 0) + --InlineAsmPayloadRemaining; } + if (InlineAsmPayloadRemaining != 0) + return makeUnexpected(ErrorCode::VerificationError, + "inline assembly operand flag has missing payload operands"); const auto BeginAttachment = [this, &HasPrintedTail](std::string_view Name) { Output.append(HasPrintedTail ? ", " : " "); @@ -1490,6 +1598,26 @@ Expected BodyPrinter::printBlock(const MachineBasicBlock &Block) { } Output.append(":\n"); + bool HasLineAttributes = false; + if (!Block.successors().empty()) { + Output.append(" successors: "); + for (std::size_t Index = 0; Index < Block.successors().size(); ++Index) { + if (Index != 0) + Output.append(", "); + const MachineSuccessor &Successor = Block.successors()[Index]; + if (Successor.Block == nullptr || !BlockIDs.contains(Successor.Block)) { + return makeUnexpected(ErrorCode::VerificationError, + "successor references an unlinked block"); + } + appendBlockReference(*Successor.Block); + Output.append("(0x"); + Output.append(hexadecimalWords( + std::array{static_cast(Successor.Probability.rawValue())}, 32)); + Output.push_back(')'); + } + Output.push_back('\n'); + HasLineAttributes = true; + } if (!Block.liveIns().empty()) { Output.append(" liveins: "); for (std::size_t Index = 0; Index < Block.liveIns().size(); ++Index) { @@ -1504,24 +1632,11 @@ Expected BodyPrinter::printBlock(const MachineBasicBlock &Block) { } } Output.push_back('\n'); + HasLineAttributes = true; } - if (!Block.successors().empty()) { - Output.append(" successors: "); - for (std::size_t Index = 0; Index < Block.successors().size(); ++Index) { - if (Index != 0) - Output.append(", "); - const MachineSuccessor &Successor = Block.successors()[Index]; - if (Successor.Block == nullptr || !BlockIDs.contains(Successor.Block)) { - return makeUnexpected(ErrorCode::VerificationError, - "successor references an unlinked block"); - } - appendBlockReference(*Successor.Block); - Output.push_back('('); - Output.append(std::to_string(Successor.Probability.rawValue())); - Output.push_back(')'); - } + + if (HasLineAttributes && Block.firstInstruction() != nullptr) Output.push_back('\n'); - } bool InBundle = false; for (const MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; diff --git a/backend/lib/MIR/MIRDataParser.cpp b/backend/lib/MIR/MIRDataParser.cpp index fc70f0f..7e04ace 100644 --- a/backend/lib/MIR/MIRDataParser.cpp +++ b/backend/lib/MIR/MIRDataParser.cpp @@ -203,7 +203,7 @@ bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunctio Encoding = JumpTableEntryEncoding::Custom32; EntrySize = 4; } else { - return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'"); + return fail(KindNode->range(), "unknown enumerated scalar"); } const yaml::Node *Entries = mappingValue(*JumpTable, "entries"); diff --git a/backend/lib/MIR/MIRDocumentParser.cpp b/backend/lib/MIR/MIRDocumentParser.cpp index defcce9..ca2c6cd 100644 --- a/backend/lib/MIR/MIRDocumentParser.cpp +++ b/backend/lib/MIR/MIRDocumentParser.cpp @@ -49,8 +49,6 @@ using parser_detail::trim; 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), @@ -210,7 +208,14 @@ MIRDocumentParseResult MIRDocumentParser::run() { 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()); + if (Diagnostics.empty()) { + static_cast(fail(FirstRoot->range(), "embedded LLVM IR indexing failed", + "mir-embedded-ir")); + } else { + const TextDiagnostic &Diagnostic = Diagnostics.front(); + const TextRange Range = scalarDiagnosticRange(*FirstRoot, Diagnostic.Range); + static_cast(fail(Range, Diagnostic.Message, Diagnostic.Category)); + } return std::move(Result); } Staging.EmbeddedIR = std::move(Embedded).takeIndex(); @@ -301,22 +306,36 @@ bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Docum 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'"); + return fail(Root.range(), "missing required key 'name'"); + if (validateKeys(Root, Keys, "machine function")) + return true; std::string_view Name; - if (scalar(*NameNode, Name, "machine function name") || Name.empty()) - return fail(NameNode->range(), "machine function name cannot be empty"); + if (scalar(*NameNode, Name, "machine function name")) + return true; + const EmbeddedIRFunction *IRFunction = nullptr; if (Document.EmbeddedIR) { - const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name); + if (Name.empty()) { + std::size_t AnonymousOrdinal = 0; + for (std::size_t Index = 0; Index < Document.Unit.functionCount(); ++Index) { + const MachineFunction *Existing = Document.Unit.functionAt(Index); + if (Existing != nullptr && Existing->name().empty()) + ++AnonymousOrdinal; + } + IRFunction = Document.EmbeddedIR->unnamedFunction(AnonymousOrdinal); + } else { + IRFunction = Document.EmbeddedIR->function(Name); + } if (IRFunction == nullptr) - return fail(NameNode->range(), - "machine function has no corresponding embedded LLVM IR function"); + return fail(NameNode->range(), "function '" + std::string(Name) + + "' isn't defined in the provided LLVM IR"); } + if (!Name.empty() && Document.Unit.function(Name) != nullptr) + return fail(NameNode->range(), "redefinition of machine function '" + std::string(Name) + "'"); + auto Created = Document.Unit.createMachineFunction(std::string(Name)); if (!Created) return importError(NameNode->range(), Created.error()); @@ -332,6 +351,11 @@ bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Docum }; MIParserContext Context; Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; + if (IRFunction != nullptr) { + Context.HasIRBlock = [IRFunction](std::string_view BlockName) { + return IRFunction->block(BlockName) != nullptr; + }; + } std::vector PendingJumpTables; if (parseFunctionScalars(Root, Function, DocumentInfo) || diff --git a/backend/lib/MIR/MIRDocumentParser.h b/backend/lib/MIR/MIRDocumentParser.h index 53a31b4..3fade39 100644 --- a/backend/lib/MIR/MIRDocumentParser.h +++ b/backend/lib/MIR/MIRDocumentParser.h @@ -97,8 +97,8 @@ private: std::span 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]] TextRange scalarDiagnosticRange(const yaml::Node &Scalar, + const TextRange &ScalarRange) 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); diff --git a/backend/lib/MIR/MIRDocumentPrinter.cpp b/backend/lib/MIR/MIRDocumentPrinter.cpp index dba1ec7..5cc0173 100644 --- a/backend/lib/MIR/MIRDocumentPrinter.cpp +++ b/backend/lib/MIR/MIRDocumentPrinter.cpp @@ -17,6 +17,7 @@ #include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" #include "rcc/MIR/LLVMConstantSyntax.h" +#include "rcc/MIR/MIDebugSyntax.h" #include "rcc/MIR/MIRDocument.h" #include @@ -38,16 +39,206 @@ namespace rcc::mir { namespace { +enum class YAMLQuoting : std::uint8_t { + None, + Single, + Double, +}; + +[[nodiscard]] bool isASCIIAlphanumeric(unsigned char Character) noexcept { + return (Character >= '0' && Character <= '9') || (Character >= 'A' && Character <= 'Z') || + (Character >= 'a' && Character <= 'z'); +} + +[[nodiscard]] bool isYAMLNumeric(std::string_view Value) noexcept { + if (Value.empty() || Value == "+" || Value == "-") + return false; + if (Value == ".nan" || Value == ".NaN" || Value == ".NAN") + return true; + + std::string_view Tail = Value; + if (Tail.front() == '+' || Tail.front() == '-') + Tail.remove_prefix(1); + if (Tail == ".inf" || Tail == ".Inf" || Tail == ".INF") + return true; + if (Value.starts_with("0o")) { + return Value.size() > 2U && + Value.substr(2).find_first_not_of("01234567") == std::string_view::npos; + } + if (Value.starts_with("0x")) { + return Value.size() > 2U && + Value.substr(2).find_first_not_of("0123456789abcdefABCDEF") == std::string_view::npos; + } + + if (Tail.empty() || Tail.front() == 'e' || Tail.front() == 'E') + return false; + std::size_t Position = 0; + if (Tail.front() == '.') { + ++Position; + if (Position == Tail.size() || Tail[Position] < '0' || Tail[Position] > '9') + return false; + while (Position < Tail.size() && Tail[Position] >= '0' && Tail[Position] <= '9') + ++Position; + } else { + while (Position < Tail.size() && Tail[Position] >= '0' && Tail[Position] <= '9') + ++Position; + if (Position == 0) + return false; + } + + if (Position < Tail.size() && Tail[Position] == '.') { + ++Position; + while (Position < Tail.size() && Tail[Position] >= '0' && Tail[Position] <= '9') + ++Position; + } + if (Position == Tail.size()) + return true; + if (Tail[Position] != 'e' && Tail[Position] != 'E') + return false; + ++Position; + if (Position < Tail.size() && (Tail[Position] == '+' || Tail[Position] == '-')) + ++Position; + const std::size_t ExponentBegin = Position; + while (Position < Tail.size() && Tail[Position] >= '0' && Tail[Position] <= '9') + ++Position; + return Position == Tail.size() && Position != ExponentBegin; +} + +[[nodiscard]] YAMLQuoting yamlQuoting(std::string_view Value) noexcept { + if (Value.empty()) + return YAMLQuoting::Single; + + YAMLQuoting Result = YAMLQuoting::None; + const auto IsSpace = [](unsigned char Character) { + return Character == ' ' || Character == '\t' || Character == '\r' || Character == '\n' || + Character == '\f' || Character == '\v'; + }; + if (IsSpace(static_cast(Value.front())) || + IsSpace(static_cast(Value.back())) || Value == "null" || Value == "Null" || + Value == "NULL" || Value == "~" || Value == "true" || Value == "True" || Value == "TRUE" || + Value == "false" || Value == "False" || Value == "FALSE" || isYAMLNumeric(Value)) { + Result = YAMLQuoting::Single; + } + + switch (Value.front()) { + case '-': + case '?': + case ':': + case '\\': + case ',': + case '[': + case ']': + case '{': + case '}': + case '#': + case '&': + case '*': + case '!': + case '|': + case '>': + case '\'': + case '"': + case '%': + case '@': + case '`': + Result = YAMLQuoting::Single; + break; + default: + break; + } + + for (const char RawCharacter : Value) { + const auto Character = static_cast(RawCharacter); + if (isASCIIAlphanumeric(Character)) + continue; + switch (Character) { + case '_': + case '-': + case '^': + case '.': + case ',': + case ' ': + case '\t': + continue; + case '\n': + case '\r': + case 0x7f: + return YAMLQuoting::Double; + default: + if (Character <= 0x1fU || (Character & 0x80U) != 0) + return YAMLQuoting::Double; + Result = YAMLQuoting::Single; + break; + } + } + return Result; +} + [[nodiscard]] std::string yamlScalar(std::string_view Value) { + const YAMLQuoting Quoting = yamlQuoting(Value); + if (Quoting == YAMLQuoting::None) + return std::string(Value); + std::string Result; Result.reserve(Value.size() + 2U); - Result.push_back('\''); + const char Quote = Quoting == YAMLQuoting::Single ? '\'' : '"'; + Result.push_back(Quote); for (const char Character : Value) { - Result.push_back(Character); - if (Character == '\'') - Result.push_back('\''); + if (Quoting == YAMLQuoting::Single) { + Result.push_back(Character); + if (Character == '\'') + Result.push_back('\''); + continue; + } + switch (Character) { + case '\\': + Result.append("\\\\"); + break; + case '"': + Result.append("\\\""); + break; + case '\0': + Result.append("\\0"); + break; + case '\a': + Result.append("\\a"); + break; + case '\b': + Result.append("\\b"); + break; + case '\t': + Result.append("\\t"); + break; + case '\n': + Result.append("\\n"); + break; + case '\v': + Result.append("\\v"); + break; + case '\f': + Result.append("\\f"); + break; + case '\r': + Result.append("\\r"); + break; + case '\x1b': + Result.append("\\e"); + break; + default: { + const auto Byte = static_cast(Character); + if (Byte < 0x20U || Byte == 0x7fU) { + static constexpr std::string_view Hex = "0123456789ABCDEF"; + Result.append("\\x"); + Result.push_back(Hex[Byte >> 4U]); + Result.push_back(Hex[Byte & 0xfU]); + } else { + Result.push_back(Character); + } + break; + } + } } - Result.push_back('\''); + Result.push_back(Quote); return Result; } @@ -396,14 +587,7 @@ Expected MIRDocumentPrinter::metadataValue(const MachineMetadataVal 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; + return formatDIExpression(Expression->elements()); } Expected MIRDocumentPrinter::diLocation(const MachineDILocation &Location, @@ -679,24 +863,34 @@ Expected MIRDocumentPrinter::printFrameInfo(const MachineFunction &Functio 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()); + if (Options.Mode == MIPrintMode::Complete || State.StackProtector) { + std::string Reference; + if (State.StackProtector) { + auto PrintedReference = frameReference(*State.StackProtector, Context); + if (!PrintedReference) + return std::unexpected(PrintedReference.error()); + Reference = std::move(*PrintedReference); + } Output.append(" stackProtector: "); - Output.append(yamlScalar(*Reference)); + Output.append(yamlScalar(Reference)); Output.push_back('\n'); } - if (State.FunctionContext) { - auto Reference = frameReference(*State.FunctionContext, Context); - if (!Reference) - return std::unexpected(Reference.error()); + if (Options.Mode == MIPrintMode::Complete || State.FunctionContext) { + std::string Reference; + if (State.FunctionContext) { + auto PrintedReference = frameReference(*State.FunctionContext, Context); + if (!PrintedReference) + return std::unexpected(PrintedReference.error()); + Reference = std::move(*PrintedReference); + } Output.append(" functionContext: "); - Output.append(yamlScalar(*Reference)); + Output.append(yamlScalar(Reference)); Output.push_back('\n'); } - if (State.MaxCallFrameSize) - Unsigned("maxCallFrameSize", *State.MaxCallFrameSize); + if (Options.Mode == MIPrintMode::Complete || State.MaxCallFrameSize) { + Unsigned("maxCallFrameSize", + State.MaxCallFrameSize.value_or(std::numeric_limits::max())); + } Unsigned("cvBytesOfCalleeSavedRegisters", State.CVBytesOfCalleeSavedRegisters); Bool("hasOpaqueSPAdjustment", State.HasOpaqueSPAdjustment); Bool("hasVAStart", State.HasVAStart); @@ -1161,15 +1355,18 @@ Expected MIRDocumentPrinter::printFunctionRecords(const MachineFunction &F } if (!Records.prefetchTargets().empty()) { - Output.append("prefetch-targets:\n"); - for (const MachinePrefetchTarget &TargetValue : Records.prefetchTargets()) { + Output.append("prefetch-targets: [ "); + const auto Targets = Records.prefetchTargets(); + for (std::size_t Index = 0; Index < Targets.size(); ++Index) { + if (Index != 0) + Output.append(", "); + const MachinePrefetchTarget &TargetValue = Targets[Index]; 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'); } + Output.append(" ]\n"); } return {}; } @@ -1188,9 +1385,9 @@ Expected MIRDocumentPrinter::printFunction(const MachineFunction &Function Output.append(yamlScalar(Function.name())); Output.push_back('\n'); const auto Alignment = Function.attributes().alignment(); - if (Alignment) { + if (Options.Mode == MIPrintMode::Complete || Alignment) { Output.append("alignment: "); - Output.append(std::to_string(Alignment->bytes())); + Output.append(std::to_string(Alignment ? Alignment->bytes() : 1U)); Output.push_back('\n'); } @@ -1244,6 +1441,7 @@ Expected MIRDocumentPrinter::printFunction(const MachineFunction &Function return std::unexpected(Body.error()); Output.append("body: |\n"); appendBlockScalar(Output, *Body, 2); + Output.append("...\n"); return {}; } @@ -1281,6 +1479,7 @@ Expected MIRDocumentPrinter::run() { if (Options.EmitEmbeddedIR && Document.embeddedIR() != nullptr) { Output.append("--- |\n"); appendBlockScalar(Output, Document.embeddedIR()->source().text(), 2); + Output.append("...\n"); } for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { const MachineFunction *Function = Document.unit().functionAt(Index); diff --git a/backend/lib/MIR/MIRDocumentReferenceValidation.cpp b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp index a6dbb56..69b0dd1 100644 --- a/backend/lib/MIR/MIRDocumentReferenceValidation.cpp +++ b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp @@ -27,7 +27,23 @@ Expected verifyMIRDocumentReferences(const MIRDocument &Document, const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr; const EmbeddedIRFunction *IRFunction = nullptr; if (Embedded != nullptr) { - IRFunction = Embedded->function(Function.name()); + if (Function.name().empty()) { + std::size_t AnonymousOrdinal = 0; + bool FoundFunction = false; + for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { + const MachineFunction *Candidate = Document.unit().functionAt(Index); + if (Candidate == &Function) { + FoundFunction = true; + break; + } + if (Candidate != nullptr && Candidate->name().empty()) + ++AnonymousOrdinal; + } + if (FoundFunction) + IRFunction = Embedded->unnamedFunction(AnonymousOrdinal); + } else { + IRFunction = Embedded->function(Function.name()); + } if (IRFunction == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "machine function has no matching embedded LLVM IR function"); diff --git a/backend/lib/MIR/MIRFrameParser.cpp b/backend/lib/MIR/MIRFrameParser.cpp index 76ace8f..9e21dfc 100644 --- a/backend/lib/MIR/MIRFrameParser.cpp +++ b/backend/lib/MIR/MIRFrameParser.cpp @@ -408,6 +408,13 @@ bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector}, std::pair{"functionContext", &State.FunctionContext}}) { if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + std::string_view Reference; + if (scalar(*Value, Reference, "frame index")) + return true; + if (Reference.empty()) { + Destination->reset(); + continue; + } FrameIndex Index; if (parseFrameReference(*Value, Context, Index)) return true; @@ -420,7 +427,10 @@ bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction Size > std::numeric_limits::max()) { return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range"); } - State.MaxCallFrameSize = Size; + if (Size == std::numeric_limits::max()) + State.MaxCallFrameSize.reset(); + else + State.MaxCallFrameSize = Size; } for (const auto &[Key, Destination] : {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters}, diff --git a/backend/lib/MIR/MIRFunctionParser.cpp b/backend/lib/MIR/MIRFunctionParser.cpp index 07cb2b0..a59e4bd 100644 --- a/backend/lib/MIR/MIRFunctionParser.cpp +++ b/backend/lib/MIR/MIRFunctionParser.cpp @@ -165,10 +165,9 @@ bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunct const auto ApplyComputed = [this, &Root, &Function](std::optional Explicit, bool Computed, MachineFunctionProperty Property, - std::string_view Name) -> bool { + std::string_view Conflict) -> bool { if (Explicit && *Explicit && !Computed) { - return fail(Root.range(), "explicit property '" + std::string(Name) + - "' conflicts with the parsed machine function"); + return fail(Root.range(), std::string(Function.name()) + std::string(Conflict)); } if (Explicit.value_or(Computed)) Function.properties().set(Property); @@ -176,15 +175,18 @@ bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunct 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")) { + if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, + " has explicit property NoPhi, but contains at least one PHI") || + ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, + " has explicit property IsSSA, but is not valid SSA") || + ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, + " has explicit property NoVRegs, but contains virtual registers")) { return true; } if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) { - return fail(Root.range(), - "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction"); + return fail(Root.range(), std::string(Function.name()) + + " has explicit property hasFakeUses=false, but contains fake uses"); } if (DocumentInfo.HasFakeUses.value_or(HasFakeUse)) Function.attributes().set(MachineFunctionAttribute::HasFakeUses); @@ -302,7 +304,7 @@ bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction & const auto Flag = Target.mirText().virtualRegisterFlag(Name); if (!Flag) return fail(FlagNode->range(), - "unknown virtual-register flag '" + std::string(Name) + "'"); + "use of undefined register flag '" + std::string(Name) + "'"); if ((Mask & *Flag) != 0) return fail(FlagNode->range(), "duplicate virtual-register flag"); Mask = static_cast(Mask | *Flag); @@ -368,15 +370,23 @@ bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction & return false; } -TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, - const TextRange &BodyRange) const noexcept { - if (Body.scalarStyle() != yaml::ScalarStyle::Literal) - return Body.range(); +TextRange MIRDocumentParser::scalarDiagnosticRange(const yaml::Node &Scalar, + const TextRange &ScalarRange) const noexcept { + if (Scalar.scalarStyle() != yaml::ScalarStyle::Literal && + Scalar.scalarStyle() != yaml::ScalarStyle::Folded) { + std::size_t ContentOffset = Scalar.range().Begin.Offset; + if (Scalar.scalarStyle() == yaml::ScalarStyle::SingleQuoted || + Scalar.scalarStyle() == yaml::ScalarStyle::DoubleQuoted) { + ++ContentOffset; + } + return Result.Source.range(ContentOffset + ScalarRange.Begin.Offset, + ContentOffset + ScalarRange.End.Offset); + } - const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U; - const std::size_t LastBodyLine = Body.range().End.Line; + const std::size_t FirstBodyLine = Scalar.range().Begin.Line + 1U; + const std::size_t LastBodyLine = Scalar.range().End.Line; if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0) - return Body.range(); + return Scalar.range(); std::size_t Indent = 0; bool FoundContent = false; @@ -392,9 +402,9 @@ TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, break; } if (!FoundContent) - return Body.range(); + return Scalar.range(); - const TextPosition Fallback = Body.range().Begin; + const TextPosition Fallback = Scalar.range().Begin; const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent, Fallback](TextPosition Position) { const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U; @@ -407,7 +417,7 @@ TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, 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)}; + return {.Begin = MapPosition(ScalarRange.Begin), .End = MapPosition(ScalarRange.End)}; } bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function, @@ -460,7 +470,7 @@ bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Funct if (!Parser.diagnostics().empty()) { const TextRange Range = Body == nullptr ? BodyRange - : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range); + : scalarDiagnosticRange(*Body, Parser.diagnostics().front().Range); return fail(Range, Parser.diagnostics().front().Message, "mir-body"); } return importError(BodyRange, Parsed.error(), "mir-body"); diff --git a/backend/lib/MIR/MIRFunctionRecordsParser.cpp b/backend/lib/MIR/MIRFunctionRecordsParser.cpp index dcd486e..aed6947 100644 --- a/backend/lib/MIR/MIRFunctionRecordsParser.cpp +++ b/backend/lib/MIR/MIRFunctionRecordsParser.cpp @@ -234,21 +234,63 @@ bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunc 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::size_t ContentOffset = TargetNode->range().Begin.Offset; + if (TargetNode->scalarStyle() == yaml::ScalarStyle::SingleQuoted || + TargetNode->scalarStyle() == yaml::ScalarStyle::DoubleQuoted) { + ++ContentOffset; + } + const auto ErrorRange = [this, ContentOffset](std::size_t Begin, + std::size_t End = 1U) { + return Result.Source.range(ContentOffset + Begin, ContentOffset + Begin + End); + }; + const auto SkipSpace = [&Text](std::size_t &Cursor) { + while (Cursor < Text.size() && (Text[Cursor] == ' ' || Text[Cursor] == '\t')) + ++Cursor; + }; + const auto ParseNumber = [&Text, &SkipSpace](std::size_t &Cursor, + std::uint32_t &Value) { + SkipSpace(Cursor); + const std::size_t Begin = Cursor; + while (Cursor < Text.size() && Text[Cursor] >= '0' && Text[Cursor] <= '9') + ++Cursor; + return Begin != Cursor && parseUnsigned32(Text.substr(Begin, Cursor - Begin), Value); + }; + + std::size_t Cursor = 0; + SkipSpace(Cursor); + if (!Text.substr(Cursor).starts_with("bb_id") || + (Cursor + std::string_view("bb_id").size() < Text.size() && + Text[Cursor + std::string_view("bb_id").size()] != ' ' && + Text[Cursor + std::string_view("bb_id").size()] != '\t')) { + return fail(ErrorRange(Cursor), "expected 'bb_id'"); + } + Cursor += std::string_view("bb_id").size(); + std::array 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)); + if (!ParseNumber(Cursor, Values[0])) + return fail(ErrorRange(0), "expected unsigned integer"); + + SkipSpace(Cursor); + if (Cursor < Text.size() && (Text[Cursor] == ',' || Text[Cursor] == '.')) { + ++Cursor; + if (!ParseNumber(Cursor, Values[1])) + return fail(ErrorRange(0), "expected unsigned integer"); + } else if (Cursor < Text.size() && Text[Cursor] >= '0' && Text[Cursor] <= '9') { + if (!ParseNumber(Cursor, Values[1])) + return fail(ErrorRange(0), "expected unsigned integer"); } + + SkipSpace(Cursor); + if (Cursor >= Text.size() || Text[Cursor] != ',') + return fail(ErrorRange(Cursor), "expected ','"); + ++Cursor; + if (!ParseNumber(Cursor, Values[2])) + return fail(ErrorRange(0), "expected unsigned integer"); + SkipSpace(Cursor); + if (Cursor != Text.size()) + return fail(ErrorRange(0), "unexpected content after prefetch target"); + auto AddResult = Function.records().addPrefetchTarget( {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]}); if (!AddResult) diff --git a/backend/lib/MIR/MIRMetadataParser.cpp b/backend/lib/MIR/MIRMetadataParser.cpp index 6ce717b..911edb6 100644 --- a/backend/lib/MIR/MIRMetadataParser.cpp +++ b/backend/lib/MIR/MIRMetadataParser.cpp @@ -20,6 +20,7 @@ #include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" #include "rcc/MIR/LLVMConstantSyntax.h" +#include "rcc/MIR/MILexer.h" #include #include @@ -89,7 +90,8 @@ bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineF MetadataTextParser Parser(std::string(Text), Resolve); auto Value = Parser.parseValue(); if (!Value) - return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata"); + return importError(scalarDiagnosticRange(*ExpressionNode, Parser.diagnosticRange()), + Value.error(), "mir-debug-metadata"); if (Value->reference() != nullptr && Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) { return fail(ExpressionNode->range(), @@ -148,132 +150,180 @@ bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &F if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes")) return true; + enum class ParsedElementKind : std::uint8_t { + Null, + Reference, + String, + }; + struct ParsedElement final { + ParsedElementKind Kind = ParsedElementKind::Reference; + std::uint32_t Reference = 0; + std::string String; + TextRange Range; + }; struct Definition final { const yaml::Node *Node = nullptr; std::uint32_t Slot = 0; bool IsDistinct = false; - std::string_view Elements; + std::vector Elements; }; std::vector Definitions; std::unordered_set Slots; + const auto TranslateRange = [this](const yaml::Node &Node, const TextRange &Range) { + std::size_t ContentBegin = Node.range().Begin.Offset; + if (Node.scalarStyle() == yaml::ScalarStyle::SingleQuoted || + Node.scalarStyle() == yaml::ScalarStyle::DoubleQuoted) { + ++ContentBegin; + } + return Result.Source.range(ContentBegin + Range.Begin.Offset, ContentBegin + Range.End.Offset); + }; + 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")) { + if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) + return true; + + MILexer Lexer("machine-metadata", std::string(Text)); + MIToken Token = Lexer.next(); + const auto LexerFailed = [this, &Lexer, DefinitionNode, &TranslateRange, + &Token]() -> bool { + if (!Token.is(MITokenKind::Error)) + return false; + const auto Diagnostics = Lexer.diagnostics(); + const TextRange Range = Diagnostics.empty() + ? TranslateRange(*DefinitionNode, Token.range()) + : TranslateRange(*DefinitionNode, Diagnostics.front().Range); + return fail(Range, Diagnostics.empty() ? "invalid machine metadata node" + : Diagnostics.front().Message); + }; + const auto Advance = [&Lexer, &Token, &LexerFailed]() -> bool { + Token = Lexer.next(); + return LexerFailed(); + }; + const auto FailAtToken = [this, DefinitionNode, &TranslateRange](const MIToken &At, + std::string Message) { + return fail(TranslateRange(*DefinitionNode, At.range()), std::move(Message)); + }; + + if (LexerFailed()) + return true; + if (!Token.is(MITokenKind::Exclaim)) + return FailAtToken(Token, "expected a metadata node"); + if (Advance()) 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 (!Token.is(MITokenKind::IntegerLiteral) || + !parseUnsigned32(Token.integerValue(), Slot)) { + return FailAtToken(Token, "expected metadata id after '!'"); + } + if (Advance()) + return true; + if (!Token.is(MITokenKind::Equal)) + return FailAtToken(Token, "expected '='"); + if (Advance()) + return true; + + const bool IsDistinct = Token.isKeyword("distinct"); + if (IsDistinct && Advance()) + return true; + if (!Token.is(MITokenKind::Exclaim)) + return FailAtToken(Token, "expected a metadata node"); + if (Advance()) + return true; + if (!Token.is(MITokenKind::LeftBrace)) + return FailAtToken(Token, "expected '{' here"); + if (Advance()) + return true; + + std::vector Elements; + if (!Token.is(MITokenKind::RightBrace)) { + while (true) { + if (Token.hasStringValue() && Token.stringValue() == "null") { + Elements.push_back({.Kind = ParsedElementKind::Null, + .Reference = 0, + .String = {}, + .Range = TranslateRange(*DefinitionNode, Token.range())}); + } else { + if (!Token.is(MITokenKind::Exclaim)) + return FailAtToken(Token, "expected '!' here"); + const TextRange ReferenceRange = TranslateRange(*DefinitionNode, Token.range()); + if (Advance()) + return true; + + if (Token.is(MITokenKind::StringConstant)) { + Elements.push_back({.Kind = ParsedElementKind::String, + .Reference = 0, + .String = std::string(Token.stringValue()), + .Range = TranslateRange(*DefinitionNode, Token.range())}); + } else { + std::uint32_t Reference = 0; + if (!Token.is(MITokenKind::IntegerLiteral) || + !parseUnsigned32(Token.integerValue(), Reference)) { + return FailAtToken(Token, "expected metadata id after '!'"); + } + Elements.push_back({.Kind = ParsedElementKind::Reference, + .Reference = Reference, + .String = {}, + .Range = ReferenceRange}); + } + } + if (Advance()) + return true; + if (!Token.is(MITokenKind::Comma)) + break; + if (Advance()) + return true; + } } - if (!Right.starts_with("!{") || !Right.ends_with('}')) - return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple"); + if (!Token.is(MITokenKind::RightBrace)) + return FailAtToken(Token, "expected end of metadata node"); + if (Advance()) + return true; + if (!Token.is(MITokenKind::EndOfFile)) + return FailAtToken(Token, "expected end of metadata node"); + if (!Slots.insert(Slot).second) + return FailAtToken(Token, "Metadata id is already used"); + Definitions.push_back({.Node = DefinitionNode, .Slot = Slot, .IsDistinct = IsDistinct, - .Elements = Right.substr(2, Right.size() - 3)}); + .Elements = std::move(Elements)}); Context.Metadata.push_back( {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)}); } - auto ResolveReference = [this, - &Slots](std::uint32_t Slot, - const TextRange &Range) -> Expected { - 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; + for (const ParsedElement &Element : Definition.Elements) { + if (Element.Kind == ParsedElementKind::Null) { + NewNode.Elements.push_back(MachineMetadataElement::null()); + continue; + } + if (Element.Kind == ParsedElementKind::String) { + NewNode.Elements.push_back(MachineMetadataElement::string(Element.String)); + continue; } - 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))); + MachineMetadataReference Reference; + if (ActiveDocument->EmbeddedIR != std::nullopt && + ActiveDocument->EmbeddedIR->metadata(std::to_string(Element.Reference)) != nullptr) { + Reference = MachineMetadataReference::embeddedIR(Element.Reference); + } else if (Slots.contains(Element.Reference)) { + Reference = MachineMetadataReference::machineFunction(Element.Reference); } else { - return fail(Definition.Node->range(), "unsupported machine metadata tuple element"); + return fail(Element.Range, + "use of undefined metadata '!" + std::to_string(Element.Reference) + "'"); } - - if (End == Remaining.size()) - Remaining = {}; - else - Remaining = trim(Remaining.substr(End + 1)); + auto Resolved = MachineMetadataElement::reference(Reference); + if (!Resolved) + return importError(Element.Range, Resolved.error()); + NewNode.Elements.push_back(std::move(*Resolved)); } auto AddResult = Function.metadata().addNode(std::move(NewNode)); if (!AddResult) diff --git a/backend/lib/MIR/MIRMetadataTextParser.cpp b/backend/lib/MIR/MIRMetadataTextParser.cpp index 51b039f..7875b06 100644 --- a/backend/lib/MIR/MIRMetadataTextParser.cpp +++ b/backend/lib/MIR/MIRMetadataTextParser.cpp @@ -74,11 +74,11 @@ Expected MetadataTextParser::parseExpression() { 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); + const auto Encoding = parseDIExpressionAttributeEncodingName(token().stringValue()); + if (!Opcode && !Encoding) + return makeUnexpected(ErrorCode::ParseError, + invalidDIExpressionElementMessage(token().stringValue())); + Elements.push_back(Opcode ? *Opcode : *Encoding); advance(); } else if (at(MITokenKind::IntegerLiteral)) { std::int64_t Signed = 0; diff --git a/backend/lib/MIR/MIRMetadataTextParser.h b/backend/lib/MIR/MIRMetadataTextParser.h index b8ebb12..032523d 100644 --- a/backend/lib/MIR/MIRMetadataTextParser.h +++ b/backend/lib/MIR/MIRMetadataTextParser.h @@ -35,6 +35,10 @@ public: [[nodiscard]] Expected parseValue(); [[nodiscard]] Expected parseLocation(); + [[nodiscard]] const TextRange &diagnosticRange() const noexcept { + return token().range(); + } + private: [[nodiscard]] const MIToken &token() const noexcept { return Tokens[Position]; diff --git a/backend/lib/Support/TextDiagnostic.cpp b/backend/lib/Support/TextDiagnostic.cpp index 1c027c3..e4c2dec 100644 --- a/backend/lib/Support/TextDiagnostic.cpp +++ b/backend/lib/Support/TextDiagnostic.cpp @@ -65,11 +65,8 @@ std::string formatTextDiagnostic(const TextDiagnostic &Diagnostic, const TextSou const std::string_view SourceName = Diagnostic.SourceName.empty() ? Source.name() : std::string_view(Diagnostic.SourceName); - Output << SourceName << ':' << Position.Line << ':' << Position.Column << ": " - << getDiagnosticSeverityName(Diagnostic.Severity); - if (!Diagnostic.Category.empty()) - Output << '[' << Diagnostic.Category << ']'; - Output << ": " << Diagnostic.Message; + Output << getDiagnosticSeverityName(Diagnostic.Severity) << ": " << SourceName << ':' + << Position.Line << ':' << Position.Column << ": " << Diagnostic.Message; const std::string_view SourceLine = Source.line(Position.Line); if (SourceLine.empty()) diff --git a/backend/lib/Support/YAML.cpp b/backend/lib/Support/YAML.cpp index 1d3ed2f..1d67114 100644 --- a/backend/lib/Support/YAML.cpp +++ b/backend/lib/Support/YAML.cpp @@ -120,9 +120,16 @@ public: return failureResult(); } if (markerAt(LineIndex) == DocumentMarker::End) { - diagnose(Lines[LineIndex].Begin, Lines[LineIndex].ContentEnd, - "document end marker has no open document"); - return failureResult(); + const std::size_t EndRemainder = markerRemainder(LineIndex, 3); + if (EndRemainder < Lines[LineIndex].ContentEnd && + Parsed.Source.text()[EndRemainder] != '#') { + diagnose(EndRemainder, Lines[LineIndex].ContentEnd, + "document end marker may only be followed by a comment"); + return failureResult(); + } + ++LineIndex; + skipBlankLines(LineIndex, Lines.size()); + continue; } const std::size_t DocumentStartLine = LineIndex; @@ -239,7 +246,8 @@ private: return Result; } - void diagnose(std::size_t Begin, std::size_t End, std::string Message) { + void diagnose(std::size_t Begin, std::size_t End, std::string Message, + std::string SourceName = {}) { if (Failed) return; Failed = true; @@ -247,7 +255,8 @@ private: .Severity = DiagnosticSeverity::Error, .Category = "yaml", .Message = std::move(Message), - .SourceName = std::string(Parsed.Source.name()), + .SourceName = SourceName.empty() ? std::string(Parsed.Source.name()) + : std::move(SourceName), .Range = Parsed.Source.range(Begin, End), }); } @@ -321,6 +330,10 @@ private: } const std::size_t ContentStart = Lines[LineIndex].Begin + Indent; + if (isUnrecognizedPlainStart(Parsed.Source.text()[ContentStart])) { + diagnose(ContentStart, ContentStart + 1, "Unrecognized character while tokenizing.", "YAML"); + return {}; + } if (isSequenceIndicator(ContentStart, Lines[LineIndex].ContentEnd)) return parseBlockSequence(LineIndex, Indent, Depth, LimitLine); if (findBlockMappingColon(ContentStart, Lines[LineIndex].ContentEnd)) @@ -587,6 +600,10 @@ private: return parseFlowMapping(Cursor, End, Depth); if (Text[Cursor] == '\'' || Text[Cursor] == '"') return parseQuotedScalar(Cursor, End); + if (isUnrecognizedPlainStart(Text[Cursor])) { + diagnose(Cursor, Cursor + 1, "Unrecognized character while tokenizing.", "YAML"); + return {}; + } if (Text[Cursor] == '&' || Text[Cursor] == '*' || Text[Cursor] == '!' || Text[Cursor] == '%') { diagnose(Cursor, Cursor + 1, "YAML anchors, aliases, tags, and directives are not supported"); return {}; @@ -768,6 +785,10 @@ private: } const std::size_t Begin = Cursor; + if (isUnrecognizedPlainStart(Text[Cursor])) { + diagnose(Cursor, Cursor + 1, "Unrecognized character while tokenizing.", "YAML"); + return false; + } if (Text[Cursor] == '\'' || Text[Cursor] == '"') { ScalarStyle Style = ScalarStyle::Plain; return parseQuotedText(Cursor, End, Key, Style, Range); @@ -1049,6 +1070,11 @@ private: return false; } + if (isUnrecognizedPlainStart(Text[Begin])) { + diagnose(Begin, Begin + 1, "Unrecognized character while tokenizing.", "YAML"); + return false; + } + if (Text[Begin] == '\'' || Text[Begin] == '"') { std::size_t Cursor = Begin; ScalarStyle Style = ScalarStyle::Plain; @@ -1123,6 +1149,10 @@ private: Parsed.Source.text()[Offset + 1] == '#'; } + [[nodiscard]] static bool isUnrecognizedPlainStart(char Character) noexcept { + return Character == '@' || Character == '`'; + } + void skipFlowSpace(std::size_t &Cursor, std::size_t End) const noexcept { const std::string_view Text = Parsed.Source.text(); while (Cursor < End) { diff --git a/backend/lib/Tooling/PipelineDriver.cpp b/backend/lib/Tooling/PipelineDriver.cpp index 330730e..e9a3d9d 100644 --- a/backend/lib/Tooling/PipelineDriver.cpp +++ b/backend/lib/Tooling/PipelineDriver.cpp @@ -84,6 +84,8 @@ createPasses(const MachineFunction &Function, const BackendInvocation &Invocatio "the default pipeline marker cannot be mixed with named passes"); } for (const std::string &Name : Invocation.PassPipeline) { + if (Name == "none") + continue; auto Pass = riscv::createCanonicalPipelinePass(Name); if (!Pass) return std::unexpected(Pass.error()); diff --git a/backend/tools/MIRTool/MIRToolMain.cpp b/backend/tools/MIRTool/MIRToolMain.cpp index eeeaac5..f764cf0 100644 --- a/backend/tools/MIRTool/MIRToolMain.cpp +++ b/backend/tools/MIRTool/MIRToolMain.cpp @@ -592,7 +592,7 @@ template continue; } - if (!Argument.starts_with('-')) { + if (Argument == "-" || !Argument.starts_with('-')) { if (!Command.InputPath.empty()) return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "input path is specified twice"); Command.InputPath = std::string(Argument); @@ -780,7 +780,8 @@ runWithCache(const CommandLine &Command, CanonicalMIR Input, const rcc::TargetDe auto Source = readInput(Command.InputPath); if (!Source) return std::unexpected(Source.error()); - auto Input = rcc::tooling::parseAndCanonicalizeMIR(Command.InputPath, std::move(*Source), *Target, + const std::string SourceName = Command.InputPath == "-" ? "" : Command.InputPath; + auto Input = rcc::tooling::parseAndCanonicalizeMIR(SourceName, std::move(*Source), *Target, Command.Invocation); if (!Input) return std::unexpected(Input.error()); @@ -981,6 +982,10 @@ int main(int ArgumentCount, char *Arguments[]) { } auto Result = runCommand(*Command); if (!Result) { + if (Result.error().code() == ErrorCode::ParseError) { + std::cerr << Result.error().message() << '\n'; + return 1; + } std::cerr << "rcc-backend-mir: " << rcc::getErrorCodeName(Result.error().code()) << ": " << Result.error().message() << '\n'; return 1; diff --git a/backend/tools/MIRTool/MIRToolOperationsTest.cmake b/backend/tools/MIRTool/MIRToolOperationsTest.cmake index 097e571..003e868 100644 --- a/backend/tools/MIRTool/MIRToolOperationsTest.cmake +++ b/backend/tools/MIRTool/MIRToolOperationsTest.cmake @@ -7,6 +7,21 @@ endif() file(REMOVE_RECURSE "${WORK_DIRECTORY}") file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(StandardInputMIR "${WORK_DIRECTORY}/stdin.mir") +execute_process( + COMMAND "${TOOL}" --verify - --output "${StandardInputMIR}" + INPUT_FILE "${INPUT}" + RESULT_VARIABLE StandardInputResult + ERROR_VARIABLE StandardInputDiagnostics +) +if(NOT StandardInputResult EQUAL 0) + message(FATAL_ERROR "standard-input MIR parsing failed: ${StandardInputDiagnostics}") +endif() +file(SIZE "${StandardInputMIR}" StandardInputSize) +if(StandardInputSize EQUAL 0) + message(FATAL_ERROR "standard-input MIR parsing produced no canonical output") +endif() + set(CacheDirectory "${WORK_DIRECTORY}/cache") set(CachedAssembly "${WORK_DIRECTORY}/cached.s") set(ReloadedAssembly "${WORK_DIRECTORY}/reloaded.s") @@ -148,7 +163,7 @@ endif() file(READ "${INPUT}" ReducerSeedText) string(REPLACE " $x10 = COPY %2\n" - " %3:gpr(i64) = G_CONSTANT 99\n $x10 = COPY %2\n" + " %3:gprb(i64) = G_CONSTANT 99\n $x10 = COPY %2\n" ReducerSeedText "${ReducerSeedText}" ) diff --git a/backend/unittests/CodeGen/CompilationUnitTest.cpp b/backend/unittests/CodeGen/CompilationUnitTest.cpp index a31c9a8..141d6db 100644 --- a/backend/unittests/CodeGen/CompilationUnitTest.cpp +++ b/backend/unittests/CodeGen/CompilationUnitTest.cpp @@ -19,29 +19,34 @@ int fail(const char *Message) { int main() { rcc::CompilationUnit Unit; - auto Empty = Unit.createMachineFunction(""); - if (Empty || Unit.functionCount() != 0) - return fail("CompilationUnit must reject an empty function name atomically"); + auto FirstAnonymous = Unit.createMachineFunction(""); + auto SecondAnonymous = Unit.createMachineFunction(""); + if (!FirstAnonymous || !SecondAnonymous || Unit.functionCount() != 2 || + (*FirstAnonymous)->stableID().value() != 1 || (*SecondAnonymous)->stableID().value() != 2 || + Unit.function("") != nullptr) + return fail("CompilationUnit must retain anonymous functions by order and StableID"); auto First = Unit.createMachineFunction("first"); - if (!First || (*First)->stableID().value() != 1) + if (!First || (*First)->stableID().value() != 3) return fail("CompilationUnit must allocate the first stable function identity"); auto Duplicate = Unit.createMachineFunction("first"); - if (Duplicate || Unit.functionCount() != 1) + if (Duplicate || Unit.functionCount() != 3) return fail("CompilationUnit must reject duplicate function names atomically"); auto Second = Unit.createMachineFunction("second"); - if (!Second || (*Second)->stableID().value() != 2) + if (!Second || (*Second)->stableID().value() != 4) return fail("Rejected function creation must not consume a StableID"); - if (Unit.functionCount() != 2 || Unit.functionAt(0) != *First || Unit.functionAt(1) != *Second) + if (Unit.functionCount() != 4 || Unit.functionAt(0) != *FirstAnonymous || + Unit.functionAt(1) != *SecondAnonymous || Unit.functionAt(2) != *First || + Unit.functionAt(3) != *Second) return fail("CompilationUnit must preserve function document order"); if (Unit.function("first") != *First || Unit.function("missing") != nullptr) return fail("CompilationUnit must provide exact name lookup"); if (Unit.function((*Second)->stableID()) != *Second || Unit.function(rcc::MachineFunctionStableID::fromValue(99)) != nullptr) return fail("CompilationUnit must provide exact StableID lookup"); - if (Unit.functionAt(2) != nullptr) + if (Unit.functionAt(4) != nullptr) return fail("CompilationUnit must reject an out-of-range ordered lookup"); auto Zero = rcc::GlobalInitializer::zeroFill(4); @@ -58,7 +63,7 @@ int main() { return fail("CompilationUnit must own and index global objects"); auto ConflictingFunction = Unit.createMachineFunction("global"); - if (ConflictingFunction || Unit.functionCount() != 2) + if (ConflictingFunction || Unit.functionCount() != 4) return fail("CompilationUnit must reject function/global symbol conflicts atomically"); rcc::GlobalObjectDesc ConflictingGlobal; @@ -88,7 +93,8 @@ int main() { return fail("A valid CompilationUnit must pass verification"); rcc::CompilationUnit Moved = std::move(Unit); - if (Moved.functionCount() != 2 || Moved.function("first") != *First || + if (Moved.functionCount() != 4 || Moved.functionAt(0) != *FirstAnonymous || + Moved.functionAt(1) != *SecondAnonymous || Moved.function("first") != *First || Moved.function("second") != *Second || Moved.globalObject("global") != *Global) return fail("Moving a CompilationUnit must preserve owned object addresses and indices"); Verification = Moved.verify(); diff --git a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp index 80a83e0..ae4d539 100644 --- a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp +++ b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp @@ -3,6 +3,7 @@ #include "rcc/CodeGen/MachineDebugInfo.h" #include "Target/TestTarget.h" +#include "rcc/CodeGen/GenericOpcodes.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineFrameInfo.h" @@ -26,8 +27,8 @@ namespace { -constexpr std::uint32_t ValueOpcode = 1; -constexpr std::uint32_t MetaOpcode = 2; +constexpr std::uint32_t ValueOpcode = 1024; +constexpr std::uint32_t MetaOpcode = 1025; class TestContext final { public: @@ -64,7 +65,11 @@ private: std::vector Descriptors; Descriptors.push_back(std::move(Value)); Descriptors.push_back(std::move(Meta)); - auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + std::vector Predicates; + auto Generic = rcc::appendGenericInstructionInfo(Descriptors, Predicates); + if (!Generic) + return std::unexpected(Generic.error()); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates)); if (!Instructions) return std::unexpected(Instructions.error()); return rcc::test::createTestTargetDescription(std::move(*Instructions)); @@ -418,6 +423,30 @@ void testVerifierFailures(TestContext &Context) { if (!Target) return; + { + auto Function = createFunction(Context, 9, "missing-debug-location"); + if (Function == nullptr) + return; + rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); + if (Block == nullptr) + return; + auto Instruction = Function->createInstruction(rcc::opcode(rcc::GenericOpcode::DBG_VALUE)); + if (!Instruction) + return; + for (const std::int64_t Value : {1, 2, 3, 4}) { + if (!(*Instruction)->addOperand(rcc::MachineOperand::immediate(Value))) + return; + } + Context.expect(Block->appendInstruction(**Instruction).has_value(), + "four-operand DBG_VALUE must be linkable for verifier coverage"); + auto Verification = rcc::MachineVerifier::verify(*Function, *Target); + Context.expect(!Verification && + Verification.error().message().find( + "Bad machine code: Missing DebugLoc for debug instruction\n" + "- instruction: DBG_VALUE 1, 2, 3, 4") != std::string_view::npos, + "complete DBG_VALUE must require a DebugLoc with LLVM-compatible diagnostics"); + } + { auto Function = createFunction(Context, 10, "debug-meta"); if (Function == nullptr) diff --git a/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp b/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp index 8ca6bf3..621bd21 100644 --- a/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionRecordsTest.cpp @@ -301,7 +301,7 @@ void testCrossReferenceFailures(TestContext &Context) { } { - auto Function = createFunction(Context, 13, "unknown-bbid"); + auto Function = createFunction(Context, 13, "dangling-prefetch-target"); if (Function == nullptr) return; rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); @@ -313,8 +313,8 @@ void testCrossReferenceFailures(TestContext &Context) { .CallsiteIndex = 0, }) .has_value() && - !rcc::MachineVerifier::verify(*Function, *Target), - "prefetch targets must reference a live block bb_id"); + rcc::MachineVerifier::verify(*Function, *Target).has_value(), + "prefetch targets may reference a bb_id without a machine block"); } { diff --git a/backend/unittests/CodeGen/MachineValueTypesTest.cpp b/backend/unittests/CodeGen/MachineValueTypesTest.cpp index 40f121b..50482ba 100644 --- a/backend/unittests/CodeGen/MachineValueTypesTest.cpp +++ b/backend/unittests/CodeGen/MachineValueTypesTest.cpp @@ -119,6 +119,21 @@ void testMemoryValueTypes(TestContext &Context) { Context.expect(Symbol.kind() == rcc::MemoryBase::Kind::GlobalSymbol, "global memory base must preserve its kind"); Context.expect(Symbol.symbolName() == "global", "global memory base must preserve its symbol"); + + rcc::GenericConstantData PoisonPointer; + PoisonPointer.Type = rcc::GenericConstantType::pointer(); + PoisonPointer.Kind = rcc::GenericConstantKind::Poison; + auto ConstantBase = rcc::MemoryBase::irConstant(PoisonPointer); + Context.expect(ConstantBase && ConstantBase->kind() == rcc::MemoryBase::Kind::IRConstant && + ConstantBase->constantValue() != nullptr && + *ConstantBase->constantValue() == PoisonPointer, + "IR-constant memory base must preserve a structured pointer constant"); + + rcc::GenericConstantData InvalidIntegerBase; + InvalidIntegerBase.Type = *rcc::GenericConstantType::integer(8); + InvalidIntegerBase.Kind = rcc::GenericConstantKind::Poison; + Context.expect(!rcc::MemoryBase::irConstant(std::move(InvalidIntegerBase)), + "IR-constant memory base must reject a non-pointer constant"); } void testMachineMemOperand(TestContext &Context) { @@ -172,6 +187,26 @@ void testMachineMemOperand(TestContext &Context) { EmptySymbol.Base = rcc::MemoryBase::externalSymbol(""); Context.expect(!rcc::MachineMemOperand::create(EmptySymbol), "empty MMO symbol base must be rejected"); + + auto OffsetAlignment = makeBasicLoadDescription(); + OffsetAlignment.Offset = 12; + OffsetAlignment.Align = *rcc::Alignment::create(4); + OffsetAlignment.BaseAlign = *rcc::Alignment::create(8); + auto OffsetAligned = rcc::MachineMemOperand::create(OffsetAlignment); + Context.expect(OffsetAligned && OffsetAligned->description().BaseAlign && + OffsetAligned->description().BaseAlign->bytes() == 8, + "MMO base alignment must derive effective alignment through its offset"); + + OffsetAlignment.Align = *rcc::Alignment::create(8); + Context.expect(!rcc::MachineMemOperand::create(OffsetAlignment), + "MMO alignment inconsistent with its base and offset must be rejected"); + + OffsetAlignment.Offset = 16; + OffsetAlignment.Align = *rcc::Alignment::create(8); + OffsetAlignment.BaseAlign = *rcc::Alignment::create(8); + auto RedundantBase = rcc::MachineMemOperand::create(OffsetAlignment); + Context.expect(RedundantBase && !RedundantBase->description().BaseAlign, + "MMO base alignment equal to effective alignment must canonicalize away"); } } // namespace diff --git a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp index be5a67e..96346a0 100644 --- a/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp +++ b/backend/unittests/MIR/LLVMConstantSyntaxTest.cpp @@ -103,6 +103,19 @@ void testScalarConstants(TestContext &Context) { expectRoundTrip(Context, "i32 undef", 4); expectRoundTrip(Context, "i32 poison", 4); expectRoundTrip(Context, "i32 zeroinitializer", 4); + + auto BFloat = rcc::mir::parseLLVMConstant("bfloat 0xR3E80", Options); + auto PrintedBFloat = BFloat ? rcc::mir::printLLVMConstant(*BFloat) + : rcc::Expected(std::unexpected(BFloat.error())); + Context.expect(PrintedBFloat && *PrintedBFloat == "bfloat 2.500000e-01", + "LLVM-compatible floating printing must prefer an exact decimal spelling"); + + auto Pi = rcc::mir::parseLLVMConstant("double f0x400921FB54442D18", Options); + auto PrintedPi = Pi ? rcc::mir::printLLVMConstant(*Pi) + : rcc::Expected(std::unexpected(Pi.error())); + Context.expect( + PrintedPi && *PrintedPi == "double f0x400921FB54442D18", + "floating printing must retain exact bits when six decimal digits do not round-trip"); } void expectIntegerWords(TestContext &Context, std::string_view Text, @@ -110,11 +123,23 @@ void expectIntegerWords(TestContext &Context, std::string_view Text, auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); Context.expect(Parsed.has_value() && Parsed->IntegerValue.has_value() && std::ranges::equal(Parsed->IntegerValue->words(), ExpectedWords), - "explicit LLVM hexadecimal integer must preserve its exact bit pattern"); + "LLVM integer literal must preserve its exact bit pattern"); if (!Parsed) std::cerr << Text << ": " << Parsed.error().message() << '\n'; } +void testDecimalIntegerTruncation(TestContext &Context) { + constexpr std::array NegativeOne{std::numeric_limits::max()}; + constexpr std::array NarrowNegative{767}; + constexpr std::array NarrowMinimum{8}; + constexpr std::array WidePositive{1}; + + expectIntegerWords(Context, "i64 -1", NegativeOne); + expectIntegerWords(Context, "i16 -64769", NarrowNegative); + expectIntegerWords(Context, "i4 -8", NarrowMinimum); + expectIntegerWords(Context, "i64 18446744073709551617", WidePositive); +} + void expectFloatingWords(TestContext &Context, std::string_view Text, std::span ExpectedWords) { auto Parsed = rcc::mir::parseLLVMConstant(Text, Options); @@ -484,6 +509,7 @@ int main() { TestContext Context; testScalarConstants(Context); testExplicitHexIntegerConstants(Context); + testDecimalIntegerTruncation(Context); testSpecialFloatingLiterals(Context); testExactFloatingConversion(Context); testAggregateConstants(Context); diff --git a/backend/unittests/MIR/MILexerTest.cpp b/backend/unittests/MIR/MILexerTest.cpp index a130eb0..5ebd2ca 100644 --- a/backend/unittests/MIR/MILexerTest.cpp +++ b/backend/unittests/MIR/MILexerTest.cpp @@ -392,7 +392,7 @@ void testRangesAndClassifiers(TestContext &Context) { static_cast(Invalid.next()); const std::string Formatted = rcc::formatTextDiagnostic(Invalid.diagnostics().front(), Invalid.source()); - Context.expect(Formatted.starts_with("body.mir:2:3: error[mir-lexer]: unexpected character '#'"), + Context.expect(Formatted.starts_with("error: body.mir:2:3: unexpected character '#'"), "formatted MI diagnostics must report stable input line and column"); rcc::mir::MILexer Original("move.mir", R"("owned\20value")"); diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp index 07aef75..918de62 100644 --- a/backend/unittests/MIR/MIParserTest.cpp +++ b/backend/unittests/MIR/MIParserTest.cpp @@ -32,6 +32,7 @@ constexpr std::uint32_t ConstantOpcode = 5; constexpr std::uint32_t FloatingConstantOpcode = 6; constexpr std::uint32_t MetadataOpcode = 7; constexpr std::uint32_t LoadOpcode = 8; +constexpr std::uint32_t InlineAsmOpcode = 9; class TestContext final { public: @@ -144,6 +145,12 @@ registerOperand(rcc::RegisterOperandRole Role, Load.Flags.set(rcc::InstructionFlag::MayLoad); Load.RequiresMemoryOperands = true; + auto InlineAsm = descriptor(InlineAsmOpcode, "INLINEASM"); + InlineAsm.Operands = { + operand(rcc::MachineOperand::Kind::ExternalSymbol), + operand(rcc::MachineOperand::Kind::Immediate), + }; + std::vector Descriptors; Descriptors.push_back(std::move(Copy)); Descriptors.push_back(std::move(AddImmediate)); @@ -153,6 +160,7 @@ registerOperand(rcc::RegisterOperandRole Role, Descriptors.push_back(std::move(FloatingConstant)); Descriptors.push_back(std::move(Metadata)); Descriptors.push_back(std::move(Load)); + Descriptors.push_back(std::move(InlineAsm)); auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); if (!Instructions) return std::unexpected(Instructions.error()); @@ -319,6 +327,8 @@ bb.1."exit block" (landing-pad, bbsections Cold): std::cerr << "error: " << Printed.error().message() << '\n'; return; } + Context.expect(Printed->find("TEST_ADDI %0,") != std::string::npos, + "virtual-register uses must not repeat a definition constraint"); auto RoundTripFunction = createFunction(Context, 2); if (!RoundTripFunction) @@ -434,6 +444,94 @@ void testCompatibleRegisterClassAndBankAnnotations(TestContext &Context) { "conflicting virtual register bank"); } +void testTypedIntegerTruncation(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 12); + Context.expect(Target.has_value(), "typed-integer target description must be valid"); + if (!Target || !Function) + return; + + const std::string Body = R"(bb.0: + %0:_(s64) = TEST_CONSTANT i64 -1 + %1:_(s16) = TEST_CONSTANT i16 -64769 + %2:_(s4) = TEST_CONSTANT i4 -8 + %3:_(s64) = TEST_CONSTANT i64 18446744073709551617 +)"; + rcc::mir::MIParser Parser(*Function, *Target, "typed-integer-truncation.mir", Body); + const auto Parsed = Parser.parse(); + const auto Instructions = Function->allInstructions(); + const auto *MinusOne = Instructions.size() > 0 + ? Instructions[0]->operandAt(1)->getIf() + : nullptr; + const auto *NarrowNegative = + Instructions.size() > 1 + ? Instructions[1]->operandAt(1)->getIf() + : nullptr; + const auto *NarrowMinimum = + Instructions.size() > 2 + ? Instructions[2]->operandAt(1)->getIf() + : nullptr; + const auto *WidePositive = + Instructions.size() > 3 + ? Instructions[3]->operandAt(1)->getIf() + : nullptr; + Context.expect(Parsed.has_value() && MinusOne != nullptr && + MinusOne->Value.words()[0] == std::numeric_limits::max() && + NarrowNegative != nullptr && NarrowNegative->Value.bitWidth() == 16 && + NarrowNegative->Value.words()[0] == 767 && NarrowMinimum != nullptr && + NarrowMinimum->Value.bitWidth() == 4 && NarrowMinimum->Value.words()[0] == 8 && + WidePositive != nullptr && WidePositive->Value.words()[0] == 1, + "typed integer literals must use LLVM width truncation semantics"); + if (!Parsed) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + std::cerr << "error: " << Parsed.error().message() << '\n'; + } +} + +void testLLVMInlineAsmAndConstantMemorySyntax(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 11); + Context.expect(Target.has_value(), "LLVM MIR syntax target description must be valid"); + if (!Target || !Function) + return; + + const std::string Body = R"(bb.0: + INLINEASM &"", sideeffect mayload inteldialect + %0:gpr64 = TEST_LOAD $g0 :: (load (s64) from `ptr poison`) +)"; + rcc::mir::MIParserContext ParserContext; + ParserContext.DefaultPointerSizeInBits = 64; + rcc::mir::MIParser Parser(*Function, *Target, "llvm-compatible.mir", Body, ParserContext); + auto Parsed = Parser.parse(); + Context.expect(Parsed.has_value(), "LLVM inline-asm and constant-pointer MMO syntax must parse"); + if (!Parsed) { + for (const auto &Diagnostic : Parser.diagnostics()) + std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n'; + return; + } + + const rcc::MachineInstr *InlineAsm = Function->firstBlock()->firstInstruction(); + const auto *ExtraInfo = InlineAsm->operandAt(1)->getIf(); + const rcc::MachineInstr *Load = InlineAsm->next(); + const rcc::MachineMemOperandDesc &Memory = Load->memOperands().front()->description(); + const auto MemoryType = rcc::LLT::makeAnyScalar(64); + Context.expect(ExtraInfo != nullptr && *ExtraInfo == 13, + "inline-asm extra-info names must aggregate into the LLVM bit field"); + Context.expect(Memory.Base.kind() == rcc::MemoryBase::Kind::IRConstant && + Memory.Base.constantValue() != nullptr && + Memory.Base.constantValue()->Kind == rcc::GenericConstantKind::Poison && + MemoryType && Memory.MemoryType == *MemoryType, + "quoted LLVM pointer constants must remain structured MMO bases"); + + auto Printed = rcc::mir::printMachineFunctionBody(*Function, *Target, ParserContext); + Context.expect(Printed && + Printed->find("INLINEASM &\"\", sideeffect mayload inteldialect") != + std::string::npos && + Printed->find("from `ptr poison`") != std::string::npos, + "canonical MIR must print symbolic inline-asm flags and LLVM constant bases"); +} + void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body, std::string_view Message) { auto Function = createFunction(Context, 10); @@ -483,6 +581,8 @@ int main() { testAddressSpacePointerWidthResolver(Context); testUnserializableLowLevelType(Context); testCompatibleRegisterClassAndBankAnnotations(Context); + testTypedIntegerTruncation(Context); + testLLVMInlineAsmAndConstantMemorySyntax(Context); testDiagnostics(Context); return Context.result(); } diff --git a/backend/unittests/MIR/MIRDocumentTest.cpp b/backend/unittests/MIR/MIRDocumentTest.cpp index 1117bf1..b6ca0bb 100644 --- a/backend/unittests/MIR/MIRDocumentTest.cpp +++ b/backend/unittests/MIR/MIRDocumentTest.cpp @@ -468,6 +468,11 @@ void testDocumentTransaction(TestContext &Context) { std::cerr << "error: " << Printed.error().message() << '\n'; return; } + Context.expect(Printed->find("name: documented\nalignment: 16\n") != std::string::npos && + Printed->find("functionContext: ''") != std::string::npos && + Printed->find("...\n---\nname: documented") != std::string::npos && + Printed->ends_with("...\n"), + "complete MIR must use LLVM-compatible scalars, defaults, and document markers"); auto RoundTrip = rcc::mir::parseMIRDocument(*Target, "round-trip.mir", std::string(*Printed)); Context.expect(RoundTrip.succeeded(), "printed complete MIR must parse successfully"); if (!RoundTrip.succeeded()) { @@ -905,7 +910,7 @@ body: | rcc::mir::parseMIRDocument(*Target, "properties-invalid.mir", std::string(InvalidDocument)); const bool Rejected = !Invalid.succeeded() && Invalid.document() == nullptr && Invalid.diagnostics().size() == 1 && - Invalid.diagnostics().front().Message.find("noVRegs") != std::string::npos; + Invalid.diagnostics().front().Message.find("NoVRegs") != std::string::npos; Context.expect(Rejected, "failsVerification must not bypass RCC's mandatory input validation"); if (!Rejected) printDiagnostics(Invalid); @@ -936,6 +941,85 @@ body: | printDiagnostics(Parsed); } +void testAnonymousFunctionsAndIRBlocks(TestContext &Context) { + auto Target = createTarget(); + Context.expect(Target.has_value(), "anonymous-function target must be valid"); + if (!Target) + return; + + constexpr std::string_view DocumentText = R"(--- | + define void @0() { + ret void + } + + define void @named() { + ret void + } + + define void @1() { + ret void + } +... +--- +name: '' +body: | + bb.0 (%ir-block.0): +... +--- +name: named +body: | + bb.0 (%ir-block.0): +... +--- +name: '' +body: | + bb.0 (%ir-block.0): +... +)"; + auto Parsed = rcc::mir::parseMIRDocument(*Target, "anonymous.mir", std::string(DocumentText)); + const rcc::MachineFunction *First = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(0) : nullptr; + const rcc::MachineFunction *Named = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(1) : nullptr; + const rcc::MachineFunction *Second = + Parsed.succeeded() ? Parsed.document()->unit().functionAt(2) : nullptr; + Context.expect(Parsed.succeeded() && First != nullptr && First->name().empty() && + Named != nullptr && Named->name() == "named" && Second != nullptr && + Second->name().empty(), + "MIR must match empty names to embedded anonymous functions in document order"); + if (!Parsed.succeeded()) { + printDiagnostics(Parsed); + return; + } + + auto Printed = rcc::mir::printMIRDocument(*Parsed.document(), *Target); + Context.expect(Printed.has_value(), "anonymous MIR functions must remain printable"); + if (!Printed) + return; + const std::size_t FirstAnonymous = Printed->find("name: ''"); + const std::size_t SecondAnonymous = + FirstAnonymous == std::string::npos + ? std::string::npos + : Printed->find("name: ''", FirstAnonymous + std::string_view("name: ''").size()); + Context.expect(FirstAnonymous != std::string::npos && SecondAnonymous != std::string::npos && + Printed->find("%ir-block.0") != std::string::npos, + "anonymous function names and unnamed IR block slots must print canonically"); + + constexpr std::string_view NoIRText = R"(... +--- +name: '' +body: | + bb.0: +... +)"; + auto NoIR = rcc::mir::parseMIRDocument(*Target, "anonymous-no-ir.mir", std::string(NoIRText)); + Context.expect(NoIR.succeeded() && NoIR.document()->unit().functionAt(0) != nullptr && + NoIR.document()->unit().functionAt(0)->name().empty(), + "MIR without embedded IR must accept an anonymous dummy function"); + if (!NoIR.succeeded()) + printDiagnostics(NoIR); +} + } // namespace int main() { @@ -950,5 +1034,6 @@ int main() { testAtomicFailure(Context); testLLVMPropertySemantics(Context); testBodyDiagnosticLocation(Context); + testAnonymousFunctionsAndIRBlocks(Context); return Context.result(); } diff --git a/backend/unittests/Support/TextDiagnosticTest.cpp b/backend/unittests/Support/TextDiagnosticTest.cpp index cd28027..71d02b2 100644 --- a/backend/unittests/Support/TextDiagnosticTest.cpp +++ b/backend/unittests/Support/TextDiagnosticTest.cpp @@ -34,7 +34,7 @@ int main() { .Range = Range, }; const std::string Formatted = rcc::formatTextDiagnostic(Diagnostic, Source); - if (Formatted != "input.mir:2:3: error[yaml]: invalid scalar\nsecond line\n ^~~~~~") + if (Formatted != "error: input.mir:2:3: invalid scalar\nsecond line\n ^~~~~~") return fail("Text diagnostics must have deterministic source rendering"); if (rcc::getDiagnosticSeverityName(rcc::DiagnosticSeverity::Warning) != "warning") diff --git a/backend/unittests/Support/YAMLTest.cpp b/backend/unittests/Support/YAMLTest.cpp index 445159d..74f7bf2 100644 --- a/backend/unittests/Support/YAMLTest.cpp +++ b/backend/unittests/Support/YAMLTest.cpp @@ -21,6 +21,7 @@ const rcc::yaml::Node *mappingValue(const rcc::yaml::Stream &Stream, const rcc:: int testMIRShape() { const std::string Input = + "...\n" "--- |\n" " define void @f() {\n" " entry:\n" diff --git a/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp index 236f4e9..9c95d75 100644 --- a/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp +++ b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp @@ -36,7 +36,7 @@ namespace { return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "test MIR does not contain the expected COPY"); } - Source.insert(Position, " %3:gpr(i64) = G_CONSTANT 99\n"); + Source.insert(Position, " %3:gprb(i64) = G_CONSTANT 99\n"); return Source; } diff --git a/backend/unittests/Tooling/PipelineDriverTest.cpp b/backend/unittests/Tooling/PipelineDriverTest.cpp index 4a223b6..b9180ce 100644 --- a/backend/unittests/Tooling/PipelineDriverTest.cpp +++ b/backend/unittests/Tooling/PipelineDriverTest.cpp @@ -214,6 +214,21 @@ int main() { if (rcc::tooling::runRV64GCPipeline(MixedInput->Document, *Target, MixedPipeline)) return tooling_test::fail("named passes must not be mixed with the default pipeline marker"); + rcc::tooling::BackendInvocation NoPassInvocation = Invocation; + NoPassInvocation.PassPipeline = {"none"}; + auto NoPassInput = rcc::tooling::parseAndCanonicalizeMIR( + "no-pass.mir", *Source, *Target, NoPassInvocation); + if (!NoPassInput) + return tooling_test::fail(NoPassInput.error().message()); + const std::string NoPassExpected = NoPassInput->Text; + auto NoPassRun = rcc::tooling::runBackend( + std::move(*NoPassInput), *Target, NoPassInvocation, + rcc::tooling::BackendOutputKind::FinalMIR); + if (!NoPassRun || !NoPassRun->Pipeline.ExecutedPasses.empty() || + NoPassRun->text() != NoPassExpected) { + return tooling_test::fail("the LLVM-compatible none pass must preserve canonical MIR"); + } + if (!SelectedMIR) return tooling_test::fail("selected MIR stage was not captured"); auto LaterInput = -- cgit v1.2.3