From 77d76b73a614820bf45024b053db376f35b06d10 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 17:10:33 +0800 Subject: [MIR] Add compilation unit reference context --- backend/include/rcc/CodeGen/CompilationUnit.h | 61 +++ backend/include/rcc/MIR/EmbeddedIR.h | 175 ++++++ backend/lib/CMakeLists.txt | 1 + backend/lib/CodeGen/CMakeLists.txt | 4 + backend/lib/CodeGen/CompilationUnit.cpp | 119 ++++ backend/lib/MIR/CMakeLists.txt | 41 ++ backend/lib/MIR/EmbeddedIR.cpp | 636 ++++++++++++++++++++++ backend/unittests/CMakeLists.txt | 1 + backend/unittests/CodeGen/CMakeLists.txt | 12 + backend/unittests/CodeGen/CompilationUnitTest.cpp | 58 ++ backend/unittests/MIR/CMakeLists.txt | 10 + backend/unittests/MIR/EmbeddedIRTest.cpp | 133 +++++ 12 files changed, 1251 insertions(+) create mode 100644 backend/include/rcc/CodeGen/CompilationUnit.h create mode 100644 backend/include/rcc/MIR/EmbeddedIR.h create mode 100644 backend/lib/CodeGen/CompilationUnit.cpp create mode 100644 backend/lib/MIR/CMakeLists.txt create mode 100644 backend/lib/MIR/EmbeddedIR.cpp create mode 100644 backend/unittests/CodeGen/CompilationUnitTest.cpp create mode 100644 backend/unittests/MIR/CMakeLists.txt create mode 100644 backend/unittests/MIR/EmbeddedIRTest.cpp diff --git a/backend/include/rcc/CodeGen/CompilationUnit.h b/backend/include/rcc/CodeGen/CompilationUnit.h new file mode 100644 index 0000000..d798438 --- /dev/null +++ b/backend/include/rcc/CodeGen/CompilationUnit.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_COMPILATIONUNIT_H +#define RCC_CODEGEN_COMPILATIONUNIT_H + +#include "rcc/CodeGen/StableID.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineFunction; + +class CompilationUnit final { +public: + CompilationUnit(); + CompilationUnit(const CompilationUnit &) = delete; + CompilationUnit &operator=(const CompilationUnit &) = delete; + CompilationUnit(CompilationUnit &&) noexcept; + CompilationUnit &operator=(CompilationUnit &&) noexcept; + ~CompilationUnit(); + + [[nodiscard]] Expected createMachineFunction(std::string Name); + + [[nodiscard]] std::size_t functionCount() const noexcept { + return Functions.size(); + } + + [[nodiscard]] MachineFunction *functionAt(std::size_t Index) noexcept; + [[nodiscard]] const MachineFunction *functionAt(std::size_t Index) const noexcept; + [[nodiscard]] MachineFunction *function(std::string_view Name) noexcept; + [[nodiscard]] const MachineFunction *function(std::string_view Name) const noexcept; + [[nodiscard]] MachineFunction *function(MachineFunctionStableID ID) noexcept; + [[nodiscard]] const MachineFunction *function(MachineFunctionStableID ID) const noexcept; + + [[nodiscard]] Expected verify() const; + +private: + struct StringHash final { + using is_transparent = void; // NOLINT(readability-identifier-naming) + + [[nodiscard]] std::size_t operator()(std::string_view Value) const noexcept { + return std::hash{}(Value); + } + }; + + std::vector> Functions; + std::unordered_map> FunctionNameToIndex; + StableIDGenerator FunctionIDGenerator; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_COMPILATIONUNIT_H diff --git a/backend/include/rcc/MIR/EmbeddedIR.h b/backend/include/rcc/MIR/EmbeddedIR.h new file mode 100644 index 0000000..8e9e0f6 --- /dev/null +++ b/backend/include/rcc/MIR/EmbeddedIR.h @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MIR_EMBEDDEDIR_H +#define RCC_MIR_EMBEDDEDIR_H + +#include "rcc/Support/TextDiagnostic.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +class EmbeddedIRIndexer; + +struct EmbeddedIRReference final { + std::string Name; + TextRange Range; +}; + +class EmbeddedIRFunction final { +public: + [[nodiscard]] std::string_view name() const noexcept { + return Name; + } + + [[nodiscard]] const TextRange &nameRange() const noexcept { + return NameRange; + } + + [[nodiscard]] bool isDefinition() const noexcept { + return IsDefinition; + } + + [[nodiscard]] std::span blocks() const noexcept { + return Blocks; + } + + [[nodiscard]] std::span allocas() const noexcept { + return Allocas; + } + + [[nodiscard]] const EmbeddedIRReference *block(std::string_view QueryName) const noexcept; + [[nodiscard]] const EmbeddedIRReference *alloca(std::string_view QueryName) const noexcept; + +private: + struct StringHash final { + using is_transparent = void; // NOLINT(readability-identifier-naming) + + [[nodiscard]] std::size_t operator()(std::string_view Value) const noexcept { + return std::hash{}(Value); + } + }; + + std::string Name; + TextRange NameRange; + bool IsDefinition = false; + std::vector Blocks; + std::vector Allocas; + std::unordered_map> BlockNameToIndex; + std::unordered_map> AllocaNameToIndex; + + friend class EmbeddedIRIndexer; +}; + +class EmbeddedIRReferenceIndex final { +public: + EmbeddedIRReferenceIndex(const EmbeddedIRReferenceIndex &) = delete; + EmbeddedIRReferenceIndex &operator=(const EmbeddedIRReferenceIndex &) = delete; + EmbeddedIRReferenceIndex(EmbeddedIRReferenceIndex &&) noexcept = default; + EmbeddedIRReferenceIndex &operator=(EmbeddedIRReferenceIndex &&) noexcept = default; + ~EmbeddedIRReferenceIndex() = default; + + [[nodiscard]] const TextSource &source() const noexcept { + return Source; + } + + [[nodiscard]] std::optional targetTriple() const noexcept { + if (!TargetTriple) + return std::nullopt; + return *TargetTriple; + } + + [[nodiscard]] std::optional dataLayout() const noexcept { + if (!DataLayout) + return std::nullopt; + return *DataLayout; + } + + [[nodiscard]] std::span functions() const noexcept { + return Functions; + } + + [[nodiscard]] std::span globalObjects() const noexcept { + return GlobalObjects; + } + + [[nodiscard]] std::span metadataDefinitions() const noexcept { + return MetadataDefinitions; + } + + [[nodiscard]] const EmbeddedIRFunction *function(std::string_view Name) const noexcept; + [[nodiscard]] const EmbeddedIRReference *globalObject(std::string_view Name) const noexcept; + [[nodiscard]] const EmbeddedIRReference *metadata(std::string_view Name) const noexcept; + [[nodiscard]] bool hasGlobalValue(std::string_view Name) const noexcept { + return function(Name) != nullptr || globalObject(Name) != nullptr; + } + +private: + struct StringHash final { + using is_transparent = void; // NOLINT(readability-identifier-naming) + + [[nodiscard]] std::size_t operator()(std::string_view Value) const noexcept { + return std::hash{}(Value); + } + }; + + EmbeddedIRReferenceIndex(std::string SourceName, std::string SourceText) + : Source(std::move(SourceName), std::move(SourceText)) {} + + TextSource Source; + std::optional TargetTriple; + std::optional TargetTripleRange; + std::optional DataLayout; + std::optional DataLayoutRange; + std::vector Functions; + std::vector GlobalObjects; + std::vector MetadataDefinitions; + std::unordered_map> FunctionNameToIndex; + std::unordered_map> GlobalNameToIndex; + std::unordered_map> MetadataNameToIndex; + + friend class EmbeddedIRIndexer; +}; + +class EmbeddedIRIndexResult final { +public: + EmbeddedIRIndexResult(const EmbeddedIRIndexResult &) = delete; + EmbeddedIRIndexResult &operator=(const EmbeddedIRIndexResult &) = delete; + EmbeddedIRIndexResult(EmbeddedIRIndexResult &&) noexcept = default; + EmbeddedIRIndexResult &operator=(EmbeddedIRIndexResult &&) noexcept = default; + ~EmbeddedIRIndexResult() = default; + + [[nodiscard]] bool succeeded() const noexcept { + return Index.has_value() && Diagnostics.empty(); + } + + [[nodiscard]] const EmbeddedIRReferenceIndex *index() const noexcept { + return Index.has_value() ? Index.operator->() : nullptr; + } + + [[nodiscard]] std::span diagnostics() const noexcept { + return Diagnostics; + } + +private: + EmbeddedIRIndexResult() = default; + + std::optional Index; + std::vector Diagnostics; + + friend class EmbeddedIRIndexer; +}; + +[[nodiscard]] EmbeddedIRIndexResult indexEmbeddedIR(std::string SourceName, std::string SourceText); + +} // namespace rcc::mir + +#endif // RCC_MIR_EMBEDDEDIR_H diff --git a/backend/lib/CMakeLists.txt b/backend/lib/CMakeLists.txt index dfaff97..f6460e5 100644 --- a/backend/lib/CMakeLists.txt +++ b/backend/lib/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(Support) add_subdirectory(CodeGen) +add_subdirectory(MIR) diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index 3a45cec..2c4f6f8 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -2,6 +2,7 @@ set( RCCBackendCodeGenSources + CompilationUnit.cpp InstructionDescriptor.cpp LowLevelType.cpp MachineCompilationStatus.cpp @@ -29,6 +30,7 @@ set( ) set( RCCBackendCodeGenHeaders + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/CompilationUnit.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCompilationStatus.h" @@ -85,6 +87,7 @@ install( ) rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatus.cpp" @@ -112,6 +115,7 @@ rcc_backend_register_format_files( ${RCCBackendCodeGenHeaders} ) rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatus.cpp" diff --git a/backend/lib/CodeGen/CompilationUnit.cpp b/backend/lib/CodeGen/CompilationUnit.cpp new file mode 100644 index 0000000..1de6487 --- /dev/null +++ b/backend/lib/CodeGen/CompilationUnit.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/CompilationUnit.h" + +#include "rcc/CodeGen/MachineFunction.h" + +#include +#include +#include +#include +#include +#include + +namespace rcc { + +CompilationUnit::CompilationUnit() = default; + +CompilationUnit::CompilationUnit(CompilationUnit &&) noexcept = default; + +CompilationUnit &CompilationUnit::operator=(CompilationUnit &&) noexcept = default; + +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)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine function name is already present in the compilation unit"); + } + + auto StableID = FunctionIDGenerator.allocate(); + if (!StableID) + return std::unexpected(StableID.error()); + auto Function = MachineFunction::create(std::move(Name), *StableID); + if (!Function) + return std::unexpected(Function.error()); + + MachineFunction *Result = Function->get(); + const std::size_t Index = Functions.size(); + FunctionNameToIndex.emplace(std::string(Result->name()), Index); + Functions.push_back(std::move(*Function)); + return Result; +} + +MachineFunction *CompilationUnit::functionAt(std::size_t Index) noexcept { + if (Index >= Functions.size()) + return nullptr; + return Functions[Index].get(); +} + +const MachineFunction *CompilationUnit::functionAt(std::size_t Index) const noexcept { + if (Index >= Functions.size()) + return nullptr; + return Functions[Index].get(); +} + +MachineFunction *CompilationUnit::function(std::string_view Name) noexcept { + const auto Position = FunctionNameToIndex.find(Name); + if (Position == FunctionNameToIndex.end()) + return nullptr; + return Functions[Position->second].get(); +} + +const MachineFunction *CompilationUnit::function(std::string_view Name) const noexcept { + const auto Position = FunctionNameToIndex.find(Name); + if (Position == FunctionNameToIndex.end()) + return nullptr; + return Functions[Position->second].get(); +} + +MachineFunction *CompilationUnit::function(MachineFunctionStableID ID) noexcept { + for (const std::unique_ptr &Function : Functions) { + if (Function->stableID() == ID) + return Function.get(); + } + return nullptr; +} + +const MachineFunction *CompilationUnit::function(MachineFunctionStableID ID) const noexcept { + for (const std::unique_ptr &Function : Functions) { + if (Function->stableID() == ID) + return Function.get(); + } + return nullptr; +} + +Expected CompilationUnit::verify() const { + if (FunctionNameToIndex.size() != Functions.size()) { + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit function name index has an inconsistent size"); + } + + std::unordered_set StableIDs; + 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()) { + return makeUnexpected(ErrorCode::VerificationError, + "compilation unit contains an invalid machine function identity"); + } + if (!StableIDs.insert(Function->stableID()).second) { + return makeUnexpected(ErrorCode::VerificationError, + "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"); + } + } + return {}; +} + +} // namespace rcc diff --git a/backend/lib/MIR/CMakeLists.txt b/backend/lib/MIR/CMakeLists.txt new file mode 100644 index 0000000..882e488 --- /dev/null +++ b/backend/lib/MIR/CMakeLists.txt @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set(RCCBackendMIRSources EmbeddedIR.cpp) +set( + RCCBackendMIRHeaders + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MIR/EmbeddedIR.h" +) + +add_library(RCCBackendMIR ${RCCBackendMIRSources}) +add_library(RCCBackend::MIR ALIAS RCCBackendMIR) + +rcc_backend_configure_target(RCCBackendMIR) + +target_include_directories( + RCCBackendMIR + PUBLIC + "$" + "$" +) +target_link_libraries(RCCBackendMIR PUBLIC RCCBackend::CodeGen) + +set_target_properties( + RCCBackendMIR + PROPERTIES + EXPORT_NAME MIR + OUTPUT_NAME RCCBackendMIR +) + +install( + TARGETS RCCBackendMIR + EXPORT RCCBackendTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIR.cpp" + ${RCCBackendMIRHeaders} +) +rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIR.cpp") diff --git a/backend/lib/MIR/EmbeddedIR.cpp b/backend/lib/MIR/EmbeddedIR.cpp new file mode 100644 index 0000000..d3d5456 --- /dev/null +++ b/backend/lib/MIR/EmbeddedIR.cpp @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/EmbeddedIR.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { +namespace { + +struct ParsedIdentifier final { + std::string Name; + TextRange Range; + std::size_t End = 0; +}; + +[[nodiscard]] bool isHorizontalSpace(char Character) noexcept { + return Character == ' ' || Character == '\t'; +} + +[[nodiscard]] bool isHexDigit(char Character) noexcept { + return (Character >= '0' && Character <= '9') || (Character >= 'a' && Character <= 'f') || + (Character >= 'A' && Character <= 'F'); +} + +[[nodiscard]] unsigned hexValue(char Character) noexcept { + if (Character >= '0' && Character <= '9') + return static_cast(Character - '0'); + if (Character >= 'a' && Character <= 'f') + return static_cast(Character - 'a') + 10U; + return static_cast(Character - 'A') + 10U; +} + +[[nodiscard]] bool isIdentifierStart(char Character) noexcept { + return (Character >= 'a' && Character <= 'z') || (Character >= 'A' && Character <= 'Z') || + Character == '-' || Character == '$' || Character == '.' || Character == '_'; +} + +[[nodiscard]] bool isIdentifierBody(char Character) noexcept { + return isIdentifierStart(Character) || (Character >= '0' && Character <= '9'); +} + +[[nodiscard]] bool isWordBody(char Character) noexcept { + return (Character >= 'a' && Character <= 'z') || (Character >= 'A' && Character <= 'Z') || + (Character >= '0' && Character <= '9') || Character == '_'; +} + +[[nodiscard]] std::size_t skipHorizontalSpace(std::string_view Line, std::size_t Position, + std::size_t End) noexcept { + while (Position < End && isHorizontalSpace(Line[Position])) + ++Position; + return Position; +} + +[[nodiscard]] bool startsWord(std::string_view Line, std::size_t Position, std::size_t End, + std::string_view Word) noexcept { + if (End - Position < Word.size() || Line.substr(Position, Word.size()) != Word) + return false; + const std::size_t WordEnd = Position + Word.size(); + return WordEnd == End || !isWordBody(Line[WordEnd]); +} + +} // namespace + +const EmbeddedIRReference *EmbeddedIRFunction::block(std::string_view QueryName) const noexcept { + const auto Position = BlockNameToIndex.find(QueryName); + if (Position == BlockNameToIndex.end()) + return nullptr; + return &Blocks[Position->second]; +} + +const EmbeddedIRReference *EmbeddedIRFunction::alloca(std::string_view QueryName) const noexcept { + const auto Position = AllocaNameToIndex.find(QueryName); + if (Position == AllocaNameToIndex.end()) + return nullptr; + return &Allocas[Position->second]; +} + +const EmbeddedIRFunction *EmbeddedIRReferenceIndex::function(std::string_view Name) const noexcept { + const auto Position = FunctionNameToIndex.find(Name); + if (Position == FunctionNameToIndex.end()) + return nullptr; + return &Functions[Position->second]; +} + +const EmbeddedIRReference * +EmbeddedIRReferenceIndex::globalObject(std::string_view Name) const noexcept { + const auto Position = GlobalNameToIndex.find(Name); + if (Position == GlobalNameToIndex.end()) + return nullptr; + return &GlobalObjects[Position->second]; +} + +const EmbeddedIRReference * +EmbeddedIRReferenceIndex::metadata(std::string_view Name) const noexcept { + const auto Position = MetadataNameToIndex.find(Name); + if (Position == MetadataNameToIndex.end()) + return nullptr; + return &MetadataDefinitions[Position->second]; +} + +class EmbeddedIRIndexer final { +public: + EmbeddedIRIndexer(std::string SourceName, std::string SourceText) + : Index(std::move(SourceName), std::move(SourceText)) {} + + [[nodiscard]] EmbeddedIRIndexResult run() { + const std::size_t NulOffset = Index.Source.text().find('\0'); + if (NulOffset != std::string_view::npos) { + diagnose(NulOffset, NulOffset + 1, "NUL bytes are not allowed in embedded LLVM IR"); + return failureResult(); + } + + for (std::size_t LineNumber = 1; LineNumber <= Index.Source.lineCount(); ++LineNumber) { + const std::string_view Line = Index.Source.line(LineNumber); + const std::size_t LineOffset = Index.Source.lineOffsets()[LineNumber - 1]; + const std::size_t ContentEnd = findCommentStart(Line); + if (ActiveFunction) { + parseActiveFunctionLine(Line, LineOffset, ContentEnd); + } else { + parseTopLevelLine(Line, LineOffset, ContentEnd); + } + if (Failed) + return failureResult(); + } + + if (ActiveFunction) { + const EmbeddedIRFunction &Function = Index.Functions[ActiveFunction.value()]; + diagnose(Function.nameRange().Begin.Offset, Function.nameRange().End.Offset, + "embedded LLVM IR function definition has no closing brace"); + return failureResult(); + } + + EmbeddedIRIndexResult Result; + Result.Index.emplace(std::move(Index)); + return Result; + } + +private: + [[nodiscard]] EmbeddedIRIndexResult failureResult() { + EmbeddedIRIndexResult Result; + Result.Diagnostics = std::move(Diagnostics); + return Result; + } + + void diagnose(std::size_t Begin, std::size_t End, std::string Message) { + if (Failed) + return; + Failed = true; + Diagnostics.push_back(TextDiagnostic{ + .Severity = DiagnosticSeverity::Error, + .Category = "mir-embedded-ir", + .Message = std::move(Message), + .SourceName = std::string(Index.Source.name()), + .Range = Index.Source.range(Begin, End), + }); + } + + [[nodiscard]] std::size_t findCommentStart(std::string_view Line) const noexcept { + bool InQuote = false; + for (std::size_t Position = 0; Position < Line.size(); ++Position) { + const char Character = Line[Position]; + if (InQuote && Character == '\\' && Position + 2 < Line.size() && + isHexDigit(Line[Position + 1]) && isHexDigit(Line[Position + 2])) { + Position += 2; + continue; + } + if (Character == '"') { + InQuote = !InQuote; + continue; + } + if (!InQuote && Character == ';') + return Position; + } + return Line.size(); + } + + [[nodiscard]] std::optional + parseIdentifier(std::string_view Line, std::size_t LineOffset, std::size_t Position, + std::size_t End, std::optional Prefix) { + const std::size_t Begin = Position; + if (Prefix) { + if (Position >= End || Line[Position] != *Prefix) + return std::nullopt; + ++Position; + } + if (Position >= End) + return std::nullopt; + + std::string Name; + if (Line[Position] == '"') { + ++Position; + while (Position < End && Line[Position] != '"') { + const char Character = Line[Position++]; + if (Character != '\\') { + if (static_cast(Character) < 0x20U) { + diagnose(LineOffset + Position - 1, LineOffset + Position, + "control character is not allowed in quoted LLVM IR identifier"); + return std::nullopt; + } + Name.push_back(Character); + continue; + } + if (End - Position < 2 || !isHexDigit(Line[Position]) || !isHexDigit(Line[Position + 1])) { + diagnose(LineOffset + Position - 1, LineOffset + std::min(Position + 2, End), + "LLVM IR identifier escape must contain two hexadecimal digits"); + return std::nullopt; + } + const unsigned Value = (hexValue(Line[Position]) << 4U) | hexValue(Line[Position + 1]); + Name.push_back(static_cast(Value)); + Position += 2; + } + if (Position >= End || Line[Position] != '"') { + diagnose(LineOffset + Begin, LineOffset + End, "unterminated quoted LLVM IR identifier"); + return std::nullopt; + } + ++Position; + } else if (Line[Position] >= '0' && Line[Position] <= '9') { + const std::size_t NameBegin = Position; + while (Position < End && Line[Position] >= '0' && Line[Position] <= '9') + ++Position; + Name = std::string(Line.substr(NameBegin, Position - NameBegin)); + } else if (isIdentifierStart(Line[Position])) { + const std::size_t NameBegin = Position; + while (Position < End && isIdentifierBody(Line[Position])) + ++Position; + Name = std::string(Line.substr(NameBegin, Position - NameBegin)); + } else { + return std::nullopt; + } + + return ParsedIdentifier{ + .Name = std::move(Name), + .Range = Index.Source.range(LineOffset + Begin, LineOffset + Position), + .End = Position, + }; + } + + [[nodiscard]] std::optional> + parseStringLiteral(std::string_view Line, std::size_t LineOffset, std::size_t &Position, + std::size_t End) { + if (Position >= End || Line[Position] != '"') + return std::nullopt; + const std::size_t Begin = Position++; + std::string Value; + while (Position < End && Line[Position] != '"') { + const char Character = Line[Position++]; + if (Character != '\\') { + if (static_cast(Character) < 0x20U) { + diagnose(LineOffset + Position - 1, LineOffset + Position, + "control character is not allowed in LLVM IR string"); + return std::nullopt; + } + Value.push_back(Character); + continue; + } + if (End - Position < 2 || !isHexDigit(Line[Position]) || !isHexDigit(Line[Position + 1])) { + diagnose(LineOffset + Position - 1, LineOffset + std::min(Position + 2, End), + "LLVM IR string escape must contain two hexadecimal digits"); + return std::nullopt; + } + const unsigned Decoded = (hexValue(Line[Position]) << 4U) | hexValue(Line[Position + 1]); + Value.push_back(static_cast(Decoded)); + Position += 2; + } + if (Position >= End || Line[Position] != '"') { + diagnose(LineOffset + Begin, LineOffset + End, "unterminated LLVM IR string"); + return std::nullopt; + } + ++Position; + return std::pair( + std::move(Value), Index.Source.range(LineOffset + Begin, LineOffset + Position)); + } + + void parseTopLevelLine(std::string_view Line, std::size_t LineOffset, std::size_t End) { + std::size_t Position = skipHorizontalSpace(Line, 0, End); + if (Position == End) + return; + + if (startsWord(Line, Position, End, "target")) { + parseTargetFact(Line, LineOffset, Position, End); + return; + } + if (startsWord(Line, Position, End, "define")) { + parseFunctionHeader(Line, LineOffset, Position, End, true); + return; + } + if (startsWord(Line, Position, End, "declare")) { + parseFunctionHeader(Line, LineOffset, Position, End, false); + return; + } + if (Line[Position] == '@') { + parseGlobalObject(Line, LineOffset, Position, End); + return; + } + if (Line[Position] == '!') + parseMetadataDefinition(Line, LineOffset, Position, End); + } + + void parseTargetFact(std::string_view Line, std::size_t LineOffset, std::size_t Position, + std::size_t End) { + Position += std::string_view("target").size(); + Position = skipHorizontalSpace(Line, Position, End); + const bool IsTriple = startsWord(Line, Position, End, "triple"); + const bool IsDataLayout = startsWord(Line, Position, End, "datalayout"); + if (!IsTriple && !IsDataLayout) + return; + Position += + IsTriple ? std::string_view("triple").size() : std::string_view("datalayout").size(); + Position = skipHorizontalSpace(Line, Position, End); + if (Position >= End || Line[Position] != '=') { + diagnose(LineOffset + Position, LineOffset + std::min(Position + 1, End), + "expected '=' in embedded LLVM IR target fact"); + return; + } + ++Position; + Position = skipHorizontalSpace(Line, Position, End); + auto Value = parseStringLiteral(Line, LineOffset, Position, End); + if (!Value) + return; + Position = skipHorizontalSpace(Line, Position, End); + if (Position != End) { + diagnose(LineOffset + Position, LineOffset + End, + "unexpected content after embedded LLVM IR target fact"); + return; + } + + std::optional &Storage = IsTriple ? Index.TargetTriple : Index.DataLayout; + std::optional &Range = IsTriple ? Index.TargetTripleRange : Index.DataLayoutRange; + if (Storage) { + diagnose(Value->second.Begin.Offset, Value->second.End.Offset, + IsTriple ? "duplicate embedded LLVM IR target triple" + : "duplicate embedded LLVM IR data layout"); + return; + } + Storage = std::move(Value->first); + Range = Value->second; + } + + void parseFunctionHeader(std::string_view Line, std::size_t LineOffset, std::size_t Position, + std::size_t End, bool IsDefinition) { + Position += + IsDefinition ? std::string_view("define").size() : std::string_view("declare").size(); + std::optional Name; + while (Position < End) { + if (Line[Position] == '"') { + std::size_t StringPosition = Position; + auto Ignored = parseStringLiteral(Line, LineOffset, StringPosition, End); + if (!Ignored) + return; + Position = StringPosition; + continue; + } + if (Line[Position] == '@') { + Name = parseIdentifier(Line, LineOffset, Position, End, '@'); + break; + } + ++Position; + } + if (!Name) { + diagnose(LineOffset, LineOffset + End, + "embedded LLVM IR function header has no global identifier"); + return; + } + + auto FunctionIndex = addFunction(*Name, IsDefinition); + if (!FunctionIndex || Failed || !IsDefinition) + return; + ActiveFunction = FunctionIndex; + HeaderParenthesisDepth = 0; + parseFunctionHeaderRemainder(Line, LineOffset, Name->End, End); + } + + [[nodiscard]] std::optional addFunction(const ParsedIdentifier &Name, + bool IsDefinition) { + if (Index.GlobalNameToIndex.contains(Name.Name)) { + diagnose(Name.Range.Begin.Offset, Name.Range.End.Offset, + "embedded LLVM IR name is used by both a function and a global object"); + return std::nullopt; + } + + const auto Existing = Index.FunctionNameToIndex.find(Name.Name); + if (Existing != Index.FunctionNameToIndex.end()) { + EmbeddedIRFunction &Function = Index.Functions[Existing->second]; + if (IsDefinition && Function.IsDefinition) { + diagnose(Name.Range.Begin.Offset, Name.Range.End.Offset, + "duplicate embedded LLVM IR function definition"); + return std::nullopt; + } + if (IsDefinition) { + Function.IsDefinition = true; + Function.NameRange = Name.Range; + } + return Existing->second; + } + + const std::size_t IndexValue = Index.Functions.size(); + EmbeddedIRFunction Function; + Function.Name = Name.Name; + Function.NameRange = Name.Range; + Function.IsDefinition = IsDefinition; + Index.Functions.push_back(std::move(Function)); + Index.FunctionNameToIndex.emplace(Name.Name, IndexValue); + return IndexValue; + } + + void parseFunctionHeaderRemainder(std::string_view Line, std::size_t LineOffset, + std::size_t Position, std::size_t End) { + bool InQuote = false; + for (; 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) + continue; + if (Character == '(') { + ++HeaderParenthesisDepth; + continue; + } + if (Character == ')') { + if (HeaderParenthesisDepth == 0) { + diagnose(LineOffset + Position, LineOffset + Position + 1, + "unmatched ')' in embedded LLVM IR function header"); + return; + } + --HeaderParenthesisDepth; + continue; + } + if (Character == '{' && HeaderParenthesisDepth == 0) { + FunctionBraceDepth = 1; + parseFunctionBodySegment(Line, LineOffset, Position + 1, End); + return; + } + } + } + + void parseActiveFunctionLine(std::string_view Line, std::size_t LineOffset, std::size_t End) { + if (FunctionBraceDepth == 0) { + parseFunctionHeaderRemainder(Line, LineOffset, 0, End); + return; + } + parseFunctionBodySegment(Line, LineOffset, 0, End); + } + + void parseFunctionBodySegment(std::string_view Line, std::size_t LineOffset, std::size_t Begin, + std::size_t End) { + std::size_t BodyEnd = End; + bool InQuote = false; + std::size_t Depth = FunctionBraceDepth; + 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) + continue; + if (Character == '{') { + ++Depth; + } else if (Character == '}') { + if (Depth == 0) { + diagnose(LineOffset + Position, LineOffset + Position + 1, + "unmatched '}' in embedded LLVM IR function"); + return; + } + --Depth; + if (Depth == 0) { + BodyEnd = Position; + break; + } + } + } + + if (!ActiveFunction) { + diagnose(LineOffset + Begin, LineOffset + BodyEnd, + "internal embedded LLVM IR function indexing state is invalid"); + return; + } + EmbeddedIRFunction &Function = Index.Functions[ActiveFunction.value()]; + parseBlockLabel(Line, LineOffset, Begin, BodyEnd, Function); + if (Failed) + return; + parseAlloca(Line, LineOffset, Begin, BodyEnd, Function); + if (Failed) + return; + + FunctionBraceDepth = Depth; + if (FunctionBraceDepth == 0) + ActiveFunction.reset(); + } + + void parseBlockLabel(std::string_view Line, std::size_t LineOffset, std::size_t Begin, + std::size_t End, EmbeddedIRFunction &Function) { + std::size_t Position = skipHorizontalSpace(Line, Begin, End); + if (Position >= End) + return; + auto Name = parseIdentifier(Line, LineOffset, Position, End, std::nullopt); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != ':') + return; + + if (Function.BlockNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR basic block label"); + return; + } + const std::size_t BlockIndex = Function.Blocks.size(); + Function.BlockNameToIndex.emplace(Name->Name, BlockIndex); + Function.Blocks.push_back(EmbeddedIRReference{ + .Name = std::move(Name->Name), + .Range = Name->Range, + }); + } + + void parseAlloca(std::string_view Line, std::size_t LineOffset, std::size_t Begin, + std::size_t End, EmbeddedIRFunction &Function) { + bool InQuote = false; + for (std::size_t Position = Begin; Position < End; ++Position) { + if (InQuote && Line[Position] == '\\' && Position + 2 < End && + isHexDigit(Line[Position + 1]) && isHexDigit(Line[Position + 2])) { + Position += 2; + continue; + } + if (Line[Position] == '"') { + InQuote = !InQuote; + continue; + } + if (InQuote || Line[Position] != '%') + continue; + + auto Name = parseIdentifier(Line, LineOffset, Position, End, '%'); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != '=') + continue; + ++Position; + Position = skipHorizontalSpace(Line, Position, End); + if (!startsWord(Line, Position, End, "alloca")) + continue; + + if (Function.AllocaNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR alloca name"); + return; + } + const std::size_t AllocaIndex = Function.Allocas.size(); + Function.AllocaNameToIndex.emplace(Name->Name, AllocaIndex); + Function.Allocas.push_back(EmbeddedIRReference{ + .Name = std::move(Name->Name), + .Range = Name->Range, + }); + 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, '@'); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != '=') + return; + if (Index.FunctionNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "embedded LLVM IR name is used by both a global object and a function"); + return; + } + if (Index.GlobalNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR global object"); + return; + } + const std::size_t GlobalIndex = Index.GlobalObjects.size(); + Index.GlobalNameToIndex.emplace(Name->Name, GlobalIndex); + Index.GlobalObjects.push_back(EmbeddedIRReference{ + .Name = std::move(Name->Name), + .Range = Name->Range, + }); + } + + void parseMetadataDefinition(std::string_view Line, std::size_t LineOffset, std::size_t Position, + std::size_t End) { + auto Name = parseIdentifier(Line, LineOffset, Position, End, '!'); + if (!Name || Failed) + return; + Position = skipHorizontalSpace(Line, Name->End, End); + if (Position >= End || Line[Position] != '=') + return; + if (Index.MetadataNameToIndex.contains(Name->Name)) { + diagnose(Name->Range.Begin.Offset, Name->Range.End.Offset, + "duplicate embedded LLVM IR metadata definition"); + return; + } + const std::size_t MetadataIndex = Index.MetadataDefinitions.size(); + Index.MetadataNameToIndex.emplace(Name->Name, MetadataIndex); + Index.MetadataDefinitions.push_back(EmbeddedIRReference{ + .Name = std::move(Name->Name), + .Range = Name->Range, + }); + } + + EmbeddedIRReferenceIndex Index; + std::vector Diagnostics; + std::optional ActiveFunction; + std::size_t HeaderParenthesisDepth = 0; + std::size_t FunctionBraceDepth = 0; + bool Failed = false; +}; + +EmbeddedIRIndexResult indexEmbeddedIR(std::string SourceName, std::string SourceText) { + return EmbeddedIRIndexer(std::move(SourceName), std::move(SourceText)).run(); +} + +} // namespace rcc::mir diff --git a/backend/unittests/CMakeLists.txt b/backend/unittests/CMakeLists.txt index e612449..b93373d 100644 --- a/backend/unittests/CMakeLists.txt +++ b/backend/unittests/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(Support) add_subdirectory(Target) add_subdirectory(CodeGen) +add_subdirectory(MIR) diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index b0bdd13..e128d0b 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -2,6 +2,7 @@ set( RCCBackendCodeGenTestSources + CompilationUnitTest.cpp CoreTypesTest.cpp InstructionDescriptorTest.cpp MachineCompilationStatusTest.cpp @@ -19,6 +20,15 @@ set( TargetRegisterInfoTest.cpp ) +add_executable(RCCBackendCodeGenCompilationUnitTests CompilationUnitTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenCompilationUnitTests) +target_link_libraries(RCCBackendCodeGenCompilationUnitTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.CompilationUnit + COMMAND RCCBackendCodeGenCompilationUnitTests +) + add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenCoreTypesTests) target_link_libraries(RCCBackendCodeGenCoreTypesTests PRIVATE RCCBackend::CodeGen) @@ -188,6 +198,7 @@ add_test( ) rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnitTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp" @@ -205,6 +216,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp" ) rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnitTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp" diff --git a/backend/unittests/CodeGen/CompilationUnitTest.cpp b/backend/unittests/CodeGen/CompilationUnitTest.cpp new file mode 100644 index 0000000..39cd175 --- /dev/null +++ b/backend/unittests/CodeGen/CompilationUnitTest.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/CompilationUnit.h" + +#include "rcc/CodeGen/MachineFunction.h" + +#include +#include + +namespace { + +int fail(const char *Message) { + std::cerr << Message << '\n'; + return 1; +} + +} // namespace + +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 First = Unit.createMachineFunction("first"); + if (!First || (*First)->stableID().value() != 1) + return fail("CompilationUnit must allocate the first stable function identity"); + + auto Duplicate = Unit.createMachineFunction("first"); + if (Duplicate || Unit.functionCount() != 1) + return fail("CompilationUnit must reject duplicate function names atomically"); + + auto Second = Unit.createMachineFunction("second"); + if (!Second || (*Second)->stableID().value() != 2) + return fail("Rejected function creation must not consume a StableID"); + if (Unit.functionCount() != 2 || Unit.functionAt(0) != *First || Unit.functionAt(1) != *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) + return fail("CompilationUnit must reject an out-of-range ordered lookup"); + + auto Verification = Unit.verify(); + if (!Verification) + return fail("A valid CompilationUnit must pass verification"); + + rcc::CompilationUnit Moved = std::move(Unit); + if (Moved.functionCount() != 2 || Moved.function("first") != *First || + Moved.function("second") != *Second) + return fail("Moving a CompilationUnit must preserve owned function addresses and indices"); + Verification = Moved.verify(); + if (!Verification) + return fail("A moved CompilationUnit must pass verification"); + return 0; +} diff --git a/backend/unittests/MIR/CMakeLists.txt b/backend/unittests/MIR/CMakeLists.txt new file mode 100644 index 0000000..7a60ba2 --- /dev/null +++ b/backend/unittests/MIR/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +add_executable(RCCBackendEmbeddedIRTests EmbeddedIRTest.cpp) +rcc_backend_configure_target(RCCBackendEmbeddedIRTests) +target_link_libraries(RCCBackendEmbeddedIRTests PRIVATE RCCBackend::MIR) + +add_test(NAME RCCBackend.MIR.EmbeddedIR COMMAND RCCBackendEmbeddedIRTests) + +rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp") +rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/EmbeddedIRTest.cpp") diff --git a/backend/unittests/MIR/EmbeddedIRTest.cpp b/backend/unittests/MIR/EmbeddedIRTest.cpp new file mode 100644 index 0000000..58c6162 --- /dev/null +++ b/backend/unittests/MIR/EmbeddedIRTest.cpp @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MIR/EmbeddedIR.h" + +#include +#include +#include + +namespace { + +int fail(const char *Message) { + std::cerr << Message << '\n'; + return 1; +} + +int testReferenceIndex() { + const std::string Input = "; @commented = global i8 0\n" + "target datalayout = \"e-m:e-p:64:64\"\n" + "target triple = \"riscv64-unknown-linux-gnu\"\n" + "@ordinary = global i32 0\n" + "@\"space\\20global\" = external global i8\n" + "declare void @declared()\n" + "define void @\"function\\20name\"(\n" + " { i32, i32 } %argument\n" + ") {\n" + "entry:\n" + " %slot = alloca i32, align 4\n" + " %not_an_alloca = add i32 1, 2\n" + " br label %\"exit\\20block\"\n" + "\"exit\\20block\":\n" + " ret void\n" + "}\n" + "!0 = !{}\n" + "!llvm.module.flags = !{!0}\n"; + + rcc::mir::EmbeddedIRIndexResult Result = rcc::mir::indexEmbeddedIR("embedded.ll", Input); + const rcc::mir::EmbeddedIRReferenceIndex *Index = Result.index(); + if (!Result.succeeded() || Index == nullptr) + return fail("Embedded IR indexing must accept the MIR reference subset"); + if (Index->source().text() != Input) + return fail("Embedded IR indexing must preserve the original text"); + if (Index->targetTriple() != std::optional("riscv64-unknown-linux-gnu") || + Index->dataLayout() != std::optional("e-m:e-p:64:64")) + return fail("Embedded IR indexing must preserve target facts"); + if (Index->functions().size() != 2 || Index->function("declared") == nullptr || + Index->function("declared")->isDefinition()) + return fail("Embedded IR indexing must retain function declarations"); + + const rcc::mir::EmbeddedIRFunction *Function = Index->function("function name"); + if (Function == nullptr || !Function->isDefinition() || Function->blocks().size() != 2) + return fail("Embedded IR indexing must decode function and block identifiers"); + if (Function->block("entry") == nullptr || Function->block("exit block") == nullptr) + return fail("Embedded IR block lookup must use decoded names"); + if (Function->allocas().size() != 1 || Function->alloca("slot") == nullptr || + Function->alloca("not_an_alloca") != nullptr) + return fail("Embedded IR indexing must distinguish named allocas from other local values"); + + if (!Index->hasGlobalValue("ordinary") || !Index->hasGlobalValue("space global") || + !Index->hasGlobalValue("declared") || Index->hasGlobalValue("commented")) + return fail("Embedded IR global lookup must include objects and functions but ignore comments"); + if (Index->metadata("0") == nullptr || Index->metadata("llvm.module.flags") == nullptr || + Index->metadataDefinitions().size() != 2) + return fail("Embedded IR indexing must retain numeric and named metadata definitions"); + return 0; +} + +int testDeclarationDefinitionMerge() { + const std::string Input = "declare void @shared()\n" + "define void @shared() {\n" + "0:\n" + " ret void\n" + "}\n"; + rcc::mir::EmbeddedIRIndexResult Result = rcc::mir::indexEmbeddedIR("merge.ll", Input); + const rcc::mir::EmbeddedIRReferenceIndex *Index = Result.index(); + if (!Result.succeeded() || Index == nullptr || Index->functions().size() != 1) + return fail("Embedded IR indexing must merge a declaration with its definition"); + const rcc::mir::EmbeddedIRFunction *Function = Index->function("shared"); + if (Function == nullptr || !Function->isDefinition() || Function->block("0") == nullptr) + return fail("A later definition must complete its indexed declaration"); + return 0; +} + +bool hasSingleDiagnostic(const rcc::mir::EmbeddedIRIndexResult &Result, std::string_view Message) { + return !Result.succeeded() && Result.diagnostics().size() == 1 && + Result.diagnostics()[0].Message == Message; +} + +int testDiagnostics() { + auto DuplicateBlock = + rcc::mir::indexEmbeddedIR("blocks.ll", "define void @f() {\nentry:\nentry:\n ret void\n}\n"); + if (!hasSingleDiagnostic(DuplicateBlock, "duplicate embedded LLVM IR basic block label") || + DuplicateBlock.diagnostics()[0].Range.Begin.Line != 3) + return fail("Embedded IR indexing must reject a duplicate block at its second label"); + + auto BadEscape = rcc::mir::indexEmbeddedIR("escape.ll", "@\"bad\\q0\" = global i8 0\n"); + if (!hasSingleDiagnostic(BadEscape, + "LLVM IR identifier escape must contain two hexadecimal digits")) + return fail("Embedded IR indexing must reject malformed identifier escapes"); + + auto Unterminated = + rcc::mir::indexEmbeddedIR("function.ll", "define void @f() {\nentry:\n ret void\n"); + if (!hasSingleDiagnostic(Unterminated, + "embedded LLVM IR function definition has no closing brace")) + return fail("Embedded IR indexing must reject an unterminated function definition"); + + auto DuplicateTriple = + rcc::mir::indexEmbeddedIR("triple.ll", "target triple = \"riscv64-unknown-linux-gnu\"\n" + "target triple = \"riscv64-unknown-linux-gnu\"\n"); + if (!hasSingleDiagnostic(DuplicateTriple, "duplicate embedded LLVM IR target triple")) + return fail("Embedded IR indexing must reject duplicate target facts"); + + auto NameConflict = + rcc::mir::indexEmbeddedIR("names.ll", "@same = global i32 0\ndeclare void @same()\n"); + if (!hasSingleDiagnostic(NameConflict, + "embedded LLVM IR name is used by both a function and a global object")) + return fail("Embedded IR indexing must reject conflicting global value kinds"); + + const std::string NulInput("target triple = \"x\"\0", 20); + auto Nul = rcc::mir::indexEmbeddedIR("nul.ll", NulInput); + if (!hasSingleDiagnostic(Nul, "NUL bytes are not allowed in embedded LLVM IR")) + return fail("Embedded IR indexing must reject embedded NUL bytes"); + return 0; +} + +} // namespace + +int main() { + if (const int Result = testReferenceIndex(); Result != 0) + return Result; + if (const int Result = testDeclarationDefinitionMerge(); Result != 0) + return Result; + return testDiagnostics(); +} -- cgit v1.2.3