aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/include/rcc/CodeGen/CompilationUnit.h61
-rw-r--r--backend/include/rcc/MIR/EmbeddedIR.h175
-rw-r--r--backend/lib/CMakeLists.txt1
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt4
-rw-r--r--backend/lib/CodeGen/CompilationUnit.cpp119
-rw-r--r--backend/lib/MIR/CMakeLists.txt41
-rw-r--r--backend/lib/MIR/EmbeddedIR.cpp636
-rw-r--r--backend/unittests/CMakeLists.txt1
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt12
-rw-r--r--backend/unittests/CodeGen/CompilationUnitTest.cpp58
-rw-r--r--backend/unittests/MIR/CMakeLists.txt10
-rw-r--r--backend/unittests/MIR/EmbeddedIRTest.cpp133
12 files changed, 1251 insertions, 0 deletions
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 <cstddef>
+#include <functional>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+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<MachineFunction *> 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<void> 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<std::string_view>{}(Value);
+ }
+ };
+
+ std::vector<std::unique_ptr<MachineFunction>> Functions;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> FunctionNameToIndex;
+ StableIDGenerator<MachineFunctionStableID> 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 <cstddef>
+#include <functional>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+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<const EmbeddedIRReference> blocks() const noexcept {
+ return Blocks;
+ }
+
+ [[nodiscard]] std::span<const EmbeddedIRReference> 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<std::string_view>{}(Value);
+ }
+ };
+
+ std::string Name;
+ TextRange NameRange;
+ bool IsDefinition = false;
+ std::vector<EmbeddedIRReference> Blocks;
+ std::vector<EmbeddedIRReference> Allocas;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> BlockNameToIndex;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> AllocaNameToIndex;
+
+ 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<std::string_view> targetTriple() const noexcept {
+ if (!TargetTriple)
+ return std::nullopt;
+ return *TargetTriple;
+ }
+
+ [[nodiscard]] std::optional<std::string_view> dataLayout() const noexcept {
+ if (!DataLayout)
+ return std::nullopt;
+ return *DataLayout;
+ }
+
+ [[nodiscard]] std::span<const EmbeddedIRFunction> functions() const noexcept {
+ return Functions;
+ }
+
+ [[nodiscard]] std::span<const EmbeddedIRReference> globalObjects() const noexcept {
+ return GlobalObjects;
+ }
+
+ [[nodiscard]] std::span<const EmbeddedIRReference> 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<std::string_view>{}(Value);
+ }
+ };
+
+ EmbeddedIRReferenceIndex(std::string SourceName, std::string SourceText)
+ : Source(std::move(SourceName), std::move(SourceText)) {}
+
+ TextSource Source;
+ std::optional<std::string> TargetTriple;
+ std::optional<TextRange> TargetTripleRange;
+ std::optional<std::string> DataLayout;
+ std::optional<TextRange> DataLayoutRange;
+ std::vector<EmbeddedIRFunction> Functions;
+ std::vector<EmbeddedIRReference> GlobalObjects;
+ std::vector<EmbeddedIRReference> MetadataDefinitions;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> FunctionNameToIndex;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> GlobalNameToIndex;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> MetadataNameToIndex;
+
+ 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<const TextDiagnostic> diagnostics() const noexcept {
+ return Diagnostics;
+ }
+
+private:
+ EmbeddedIRIndexResult() = default;
+
+ std::optional<EmbeddedIRReferenceIndex> Index;
+ std::vector<TextDiagnostic> 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 <cstddef>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc {
+
+CompilationUnit::CompilationUnit() = default;
+
+CompilationUnit::CompilationUnit(CompilationUnit &&) noexcept = default;
+
+CompilationUnit &CompilationUnit::operator=(CompilationUnit &&) noexcept = default;
+
+CompilationUnit::~CompilationUnit() = default;
+
+Expected<MachineFunction *> 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<MachineFunction> &Function : Functions) {
+ if (Function->stableID() == ID)
+ return Function.get();
+ }
+ return nullptr;
+}
+
+const MachineFunction *CompilationUnit::function(MachineFunctionStableID ID) const noexcept {
+ for (const std::unique_ptr<MachineFunction> &Function : Functions) {
+ if (Function->stableID() == ID)
+ return Function.get();
+ }
+ return nullptr;
+}
+
+Expected<void> CompilationUnit::verify() const {
+ if (FunctionNameToIndex.size() != Functions.size()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "compilation unit function name index has an inconsistent size");
+ }
+
+ std::unordered_set<MachineFunctionStableID> StableIDs;
+ for (std::size_t Index = 0; Index < Functions.size(); ++Index) {
+ const std::unique_ptr<MachineFunction> &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
+ "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+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 <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+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<unsigned>(Character - '0');
+ if (Character >= 'a' && Character <= 'f')
+ return static_cast<unsigned>(Character - 'a') + 10U;
+ return static_cast<unsigned>(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<ParsedIdentifier>
+ parseIdentifier(std::string_view Line, std::size_t LineOffset, std::size_t Position,
+ std::size_t End, std::optional<char> 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<unsigned char>(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<char>(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<std::pair<std::string, TextRange>>
+ 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<unsigned char>(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<char>(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::string, TextRange>(
+ 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<std::string> &Storage = IsTriple ? Index.TargetTriple : Index.DataLayout;
+ std::optional<TextRange> &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<ParsedIdentifier> 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<std::size_t> 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<TextDiagnostic> Diagnostics;
+ std::optional<std::size_t> 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 <iostream>
+#include <utility>
+
+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 <iostream>
+#include <string>
+#include <string_view>
+
+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<std::string_view>("riscv64-unknown-linux-gnu") ||
+ Index->dataLayout() != std::optional<std::string_view>("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();
+}