aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-13 13:10:47 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-13 13:10:47 +0800
commit444db30751380e8fe459bfeae4c5bb2a93c596eb (patch)
tree5c60a23678f836113e75fb2526d3ec7b28b42f55
parentfde3e045d4dcc5c404cb4216389e18b0e720b8e0 (diff)
downloadrcc-project-444db30751380e8fe459bfeae4c5bb2a93c596eb.tar.gz
rcc-project-444db30751380e8fe459bfeae4c5bb2a93c596eb.tar.xz
[TableGen] Split the target description generator
-rw-r--r--backend/CMakeLists.txt1
-rw-r--r--backend/include/rcc/TableGen/Parser.h24
-rw-r--r--backend/include/rcc/TableGen/Record.h231
-rw-r--r--backend/lib/CMakeLists.txt1
-rw-r--r--backend/lib/TableGen/CMakeLists.txt23
-rw-r--r--backend/lib/TableGen/Parser.cpp1163
-rw-r--r--backend/lib/TableGen/Record.cpp92
-rw-r--r--backend/lib/Target/Description/TargetDescriptionGenerator.cpp3063
-rw-r--r--backend/lib/Target/Description/TargetDescriptionGenerator.h37
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt4
-rw-r--r--backend/tools/CMakeLists.txt1
-rw-r--r--backend/tools/DescriptionGenerator/CMakeLists.txt39
-rw-r--r--backend/unittests/Target/CMakeLists.txt15
-rw-r--r--backend/unittests/Target/TableGenTest.cpp (renamed from backend/unittests/Target/DescriptionGeneratorTest.cpp)2
-rw-r--r--backend/utils/TableGen/AsmWriterEmitter.cpp23
-rw-r--r--backend/utils/TableGen/CMakeLists.txt46
-rw-r--r--backend/utils/TableGen/CallingConvEmitter.cpp58
-rw-r--r--backend/utils/TableGen/CodeEmitterGen.cpp23
-rw-r--r--backend/utils/TableGen/Common/CodeGenInstruction.cpp564
-rw-r--r--backend/utils/TableGen/Common/CodeGenRegisters.cpp208
-rw-r--r--backend/utils/TableGen/Common/CodeGenSchedule.cpp92
-rw-r--r--backend/utils/TableGen/Common/CodeGenTarget.cpp222
-rw-r--r--backend/utils/TableGen/Common/CodeGenTarget.h245
-rw-r--r--backend/utils/TableGen/InstrInfoEmitter.cpp192
-rw-r--r--backend/utils/TableGen/RegisterBankEmitter.cpp27
-rw-r--r--backend/utils/TableGen/RegisterInfoEmitter.cpp158
-rw-r--r--backend/utils/TableGen/SearchableTableEmitter.cpp32
-rw-r--r--backend/utils/TableGen/SubtargetEmitter.cpp23
-rw-r--r--backend/utils/TableGen/TableGenBackends.h50
-rw-r--r--backend/utils/TableGen/rcc-tblgen.cpp (renamed from backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp)18
30 files changed, 3516 insertions, 3161 deletions
diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt
index aa4afe6..fcd75a8 100644
--- a/backend/CMakeLists.txt
+++ b/backend/CMakeLists.txt
@@ -54,6 +54,7 @@ endif()
rcc_backend_initialize_toolchain()
add_subdirectory(lib)
+add_subdirectory(utils/TableGen)
add_subdirectory(tools)
add_subdirectory(lib/Target)
diff --git a/backend/include/rcc/TableGen/Parser.h b/backend/include/rcc/TableGen/Parser.h
new file mode 100644
index 0000000..ff10aa7
--- /dev/null
+++ b/backend/include/rcc/TableGen/Parser.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TABLEGEN_PARSER_H
+#define RCC_TABLEGEN_PARSER_H
+
+#include "rcc/TableGen/Record.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace rcc::targetgen {
+
+struct DescriptionParseResult final {
+ std::optional<DescriptionAST> Records;
+ std::vector<TextDiagnostic> Diagnostics;
+};
+
+[[nodiscard]] DescriptionParseResult parseDescription(std::string SourceName,
+ std::string SourceText);
+
+} // namespace rcc::targetgen
+
+#endif // RCC_TABLEGEN_PARSER_H
diff --git a/backend/include/rcc/TableGen/Record.h b/backend/include/rcc/TableGen/Record.h
new file mode 100644
index 0000000..a3f8a5c
--- /dev/null
+++ b/backend/include/rcc/TableGen/Record.h
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TABLEGEN_RECORD_H
+#define RCC_TABLEGEN_RECORD_H
+
+#include "rcc/Support/TextDiagnostic.h"
+
+#include <cstdint>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc::targetgen {
+
+struct SourceTarget final {
+ std::string Namespace;
+ std::string Name;
+ std::string Triple;
+ std::string CPU;
+ std::string ABI;
+ std::string DataLayout;
+ std::vector<std::string> Features;
+ TextRange Range;
+};
+
+struct SourceRegisterUnit final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ TextRange Range;
+};
+
+struct SourceRegisterUnitLane final {
+ std::string UnitSymbol;
+ std::uint64_t Lanes = 0;
+ TextRange Range;
+};
+
+struct SourceRegister final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t Encoding = 0;
+ std::uint32_t DwarfRegister = 0;
+ std::vector<SourceRegisterUnitLane> Units;
+ bool IsConstant = false;
+ bool IsArtificial = false;
+ bool IsReserved = false;
+ std::vector<std::string> AlternativeNames;
+ TextRange Range;
+};
+
+struct SourceRegisterBank final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t MaximumSizeInBits = 0;
+ TextRange Range;
+};
+
+struct SourceRegisterClass final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::string BankSymbol;
+ std::uint32_t SizeInBits = 0;
+ std::uint32_t SpillSizeInBits = 0;
+ std::uint32_t SpillAlignmentInBits = 0;
+ std::uint64_t Lanes = 0;
+ std::vector<std::string> RegisterSymbols;
+ std::vector<std::string> AllocationOrderSymbols;
+ bool Allocatable = true;
+ std::uint32_t CopyCost = 1;
+ TextRange Range;
+};
+
+struct SourceRegisterMask final {
+ std::string Name;
+ std::vector<std::string> PreservedRegisterSymbols;
+ TextRange Range;
+};
+
+struct SourceMIROperandFlag final {
+ std::string Name;
+ std::uint32_t Value = 0;
+ TextRange Range;
+};
+
+struct SourceMIROperandFlags final {
+ std::uint32_t DirectMask = 0;
+ std::vector<SourceMIROperandFlag> Direct;
+ std::vector<SourceMIROperandFlag> Bitmask;
+ TextRange Range;
+};
+
+struct SourceCallingConvention final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::string IntegerRegisterClassSymbol;
+ std::string FloatingRegisterClassSymbol;
+ std::vector<std::string> IntegerArgumentRegisterSymbols;
+ std::vector<std::string> FloatingArgumentRegisterSymbols;
+ std::vector<std::string> IntegerReturnRegisterSymbols;
+ std::vector<std::string> FloatingReturnRegisterSymbols;
+ std::string PreservedMaskName;
+ std::uint32_t StackAlignmentBytes = 0;
+ std::uint32_t StackArgumentAlignmentBytes = 0;
+ std::uint32_t ShadowSpaceBytes = 0;
+ bool SupportsVarArgs = false;
+ bool SupportsTailCalls = false;
+ TextRange Range;
+};
+
+struct SourceNamedOperand final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ TextRange Range;
+};
+
+struct SourceImmediatePredicate final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::string Kind;
+ std::uint32_t Bits = 0;
+ std::uint64_t Alignment = 1;
+ bool NonZero = false;
+ std::vector<std::int64_t> AllowedValues;
+ TextRange Range;
+};
+
+struct SourceSchedulingClass final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t MicroOps = 0;
+ std::uint32_t Latency = 0;
+ TextRange Range;
+};
+
+struct SourceEncodingCategory final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t SizeInBytes = 0;
+ TextRange Range;
+};
+
+struct SourceAssemblyFormat final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ TextRange Range;
+};
+
+struct SourceOperand final {
+ std::vector<std::string> Kinds;
+ std::string RegisterRole = "none";
+ std::optional<std::string> RegisterClassSymbol;
+ std::optional<std::string> RegisterBankSymbol;
+ std::optional<std::string> ImmediatePredicateSymbol;
+ std::string MemoryRole = "none";
+ std::optional<std::uint32_t> DefaultTiedOperand;
+ bool EarlyClobber = false;
+ bool Optional = false;
+ bool VariadicGroup = false;
+ bool AllowSubReg = false;
+ std::optional<std::string> NameSymbol;
+ std::uint32_t AllowedTargetFlags = 0;
+ TextRange Range;
+};
+
+struct SourceImplicitRegister final {
+ std::string RegisterSymbol;
+ bool EarlyClobber = false;
+ bool Renamable = false;
+ TextRange Range;
+};
+
+struct SourceInstruction final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::string AssemblyMnemonic;
+ std::optional<std::uint32_t> BinaryEncoding;
+ std::vector<SourceOperand> Operands;
+ std::uint32_t NumExplicitDefs = 0;
+ bool Variadic = false;
+ std::vector<SourceImplicitRegister> ImplicitDefs;
+ std::vector<SourceImplicitRegister> ImplicitUses;
+ bool AllowExtraImplicitOperands = false;
+ std::vector<std::string> Flags;
+ std::string SchedulingClassSymbol;
+ std::string EncodingCategorySymbol;
+ std::string AssemblyFormatSymbol;
+ bool RequiresMemoryOperands = false;
+ std::string Owner = "none";
+ std::string Deadline = "emission";
+ TextRange Range;
+};
+
+struct DescriptionAST final {
+ SourceTarget Target;
+ std::vector<SourceRegisterUnit> RegisterUnits;
+ std::vector<SourceRegister> Registers;
+ std::vector<SourceRegisterBank> RegisterBanks;
+ std::vector<SourceRegisterClass> RegisterClasses;
+ std::vector<SourceRegisterMask> RegisterMasks;
+ SourceMIROperandFlags MIROperandFlags;
+ std::vector<SourceCallingConvention> CallingConventions;
+ std::vector<SourceNamedOperand> NamedOperands;
+ std::vector<SourceImmediatePredicate> ImmediatePredicates;
+ std::vector<SourceSchedulingClass> SchedulingClasses;
+ std::vector<SourceEncodingCategory> EncodingCategories;
+ std::vector<SourceAssemblyFormat> AssemblyFormats;
+ std::vector<SourceInstruction> Instructions;
+};
+
+[[nodiscard]] std::string lowerASCII(std::string_view Value);
+[[nodiscard]] bool isIdentifier(std::string_view Value) noexcept;
+[[nodiscard]] bool isAssemblyMnemonic(std::string_view Value) noexcept;
+[[nodiscard]] std::string upperIdentifier(std::string_view Value);
+void appendQuoted(std::ostream &Output, std::string_view Value);
+
+} // namespace rcc::targetgen
+
+#endif // RCC_TABLEGEN_RECORD_H
diff --git a/backend/lib/CMakeLists.txt b/backend/lib/CMakeLists.txt
index 350cbf2..184f120 100644
--- a/backend/lib/CMakeLists.txt
+++ b/backend/lib/CMakeLists.txt
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(Support)
+add_subdirectory(TableGen)
add_subdirectory(IR)
add_subdirectory(AsmParser)
add_subdirectory(CodeGen)
diff --git a/backend/lib/TableGen/CMakeLists.txt b/backend/lib/TableGen/CMakeLists.txt
new file mode 100644
index 0000000..4af1e2d
--- /dev/null
+++ b/backend/lib/TableGen/CMakeLists.txt
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(RCCBackendTableGenSources Parser.cpp Record.cpp)
+set(
+ RCCBackendTableGenHeaders
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/TableGen/Parser.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/TableGen/Record.h"
+)
+
+add_library(RCCBackendTableGen ${RCCBackendTableGenSources})
+add_library(RCCBackend::TableGen ALIAS RCCBackendTableGen)
+rcc_backend_configure_target(RCCBackendTableGen)
+target_include_directories(
+ RCCBackendTableGen
+ PUBLIC "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+)
+target_link_libraries(RCCBackendTableGen PUBLIC RCCBackend::Support)
+
+rcc_backend_register_format_files(
+ ${RCCBackendTableGenSources}
+ ${RCCBackendTableGenHeaders}
+)
+rcc_backend_register_tidy_files(${RCCBackendTableGenSources})
diff --git a/backend/lib/TableGen/Parser.cpp b/backend/lib/TableGen/Parser.cpp
new file mode 100644
index 0000000..a14385c
--- /dev/null
+++ b/backend/lib/TableGen/Parser.cpp
@@ -0,0 +1,1163 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/TableGen/Parser.h"
+
+#include "rcc/Support/YAML.h"
+
+#include <algorithm>
+#include <array>
+#include <charconv>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::targetgen {
+namespace {
+
+class ASTParser final {
+public:
+ explicit ASTParser(const yaml::Stream &Input) : Input(Input) {}
+
+ [[nodiscard]] std::optional<DescriptionAST> parse() {
+ const std::span<const yaml::Document> Documents = Input.documents();
+ if (Documents.size() != 1) {
+ fail(Input.source().range(0, Input.source().text().size()),
+ "target description must contain exactly one YAML document");
+ return std::nullopt;
+ }
+
+ const yaml::Node *Root = Input.node(Documents.front().Root);
+ if (!requireMapping(Root, "target description root"))
+ return std::nullopt;
+ if (!checkKeys(*Root,
+ {"schema", "target", "register_units", "registers", "register_banks",
+ "register_classes", "register_masks", "mir_operand_flags", "named_operands",
+ "immediate_predicates", "scheduling_classes", "encoding_categories",
+ "assembly_formats", "calling_conventions", "instructions"},
+ "target description root")) {
+ return std::nullopt;
+ }
+
+ const yaml::Node *SchemaNode = requiredNode(*Root, "schema", "target description root");
+ if (SchemaNode == nullptr)
+ return std::nullopt;
+ const auto Schema = scalar(*SchemaNode, "target description schema");
+ if (!Schema || *Schema != "rcc-target-description-v2") {
+ if (Schema)
+ fail(SchemaNode->range(), "unsupported target description schema");
+ return std::nullopt;
+ }
+
+ DescriptionAST Result;
+ const yaml::Node *TargetNode = requiredNode(*Root, "target", "target description root");
+ if (TargetNode == nullptr || !parseTarget(*TargetNode, Result.Target))
+ return std::nullopt;
+ const yaml::Node *UnitsNode = requiredNode(*Root, "register_units", "target description root");
+ const yaml::Node *RegistersNode = requiredNode(*Root, "registers", "target description root");
+ const yaml::Node *BanksNode = requiredNode(*Root, "register_banks", "target description root");
+ const yaml::Node *ClassesNode =
+ requiredNode(*Root, "register_classes", "target description root");
+ const yaml::Node *MasksNode = requiredNode(*Root, "register_masks", "target description root");
+ const yaml::Node *MIROperandFlagsNode = optionalNode(*Root, "mir_operand_flags");
+ const yaml::Node *NamedOperandsNode =
+ requiredNode(*Root, "named_operands", "target description root");
+ const yaml::Node *ImmediatePredicatesNode =
+ requiredNode(*Root, "immediate_predicates", "target description root");
+ const yaml::Node *SchedulingClassesNode =
+ requiredNode(*Root, "scheduling_classes", "target description root");
+ const yaml::Node *EncodingCategoriesNode =
+ requiredNode(*Root, "encoding_categories", "target description root");
+ const yaml::Node *AssemblyFormatsNode =
+ requiredNode(*Root, "assembly_formats", "target description root");
+ const yaml::Node *CallingConventionsNode =
+ requiredNode(*Root, "calling_conventions", "target description root");
+ const yaml::Node *InstructionsNode =
+ requiredNode(*Root, "instructions", "target description root");
+ if (UnitsNode == nullptr || RegistersNode == nullptr || BanksNode == nullptr ||
+ ClassesNode == nullptr || MasksNode == nullptr || NamedOperandsNode == nullptr ||
+ ImmediatePredicatesNode == nullptr || SchedulingClassesNode == nullptr ||
+ EncodingCategoriesNode == nullptr || AssemblyFormatsNode == nullptr ||
+ CallingConventionsNode == nullptr || InstructionsNode == nullptr ||
+ !parseRegisterUnits(*UnitsNode, Result.RegisterUnits) ||
+ !parseRegisters(*RegistersNode, Result.Registers) ||
+ !parseRegisterBanks(*BanksNode, Result.RegisterBanks) ||
+ !parseRegisterClasses(*ClassesNode, Result.RegisterClasses) ||
+ !parseRegisterMasks(*MasksNode, Result.RegisterMasks) ||
+ (MIROperandFlagsNode != nullptr &&
+ !parseMIROperandFlags(*MIROperandFlagsNode, Result.MIROperandFlags)) ||
+ !parseNamedOperands(*NamedOperandsNode, Result.NamedOperands) ||
+ !parseImmediatePredicates(*ImmediatePredicatesNode, Result.ImmediatePredicates) ||
+ !parseSchedulingClasses(*SchedulingClassesNode, Result.SchedulingClasses) ||
+ !parseEncodingCategories(*EncodingCategoriesNode, Result.EncodingCategories) ||
+ !parseAssemblyFormats(*AssemblyFormatsNode, Result.AssemblyFormats) ||
+ !parseCallingConventions(*CallingConventionsNode, Result.CallingConventions) ||
+ !parseInstructions(*InstructionsNode, Result.Instructions)) {
+ return std::nullopt;
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::vector<TextDiagnostic> takeDiagnostics() {
+ return std::move(Diagnostics);
+ }
+
+private:
+ void fail(TextRange Range, std::string Message) {
+ Diagnostics.push_back({
+ .Severity = DiagnosticSeverity::Error,
+ .Category = "target-description",
+ .Message = std::move(Message),
+ .SourceName = std::string(Input.source().name()),
+ .Range = Range,
+ });
+ }
+
+ [[nodiscard]] bool requireMapping(const yaml::Node *Node, std::string_view Context) {
+ if (Node != nullptr && Node->kind() == yaml::NodeKind::Mapping)
+ return true;
+ fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(),
+ std::string(Context) + " must be a mapping");
+ return false;
+ }
+
+ [[nodiscard]] bool requireSequence(const yaml::Node *Node, std::string_view Context) {
+ if (Node != nullptr && Node->kind() == yaml::NodeKind::Sequence)
+ return true;
+ fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(),
+ std::string(Context) + " must be a sequence");
+ return false;
+ }
+
+ [[nodiscard]] bool checkKeys(const yaml::Node &Mapping,
+ std::initializer_list<std::string_view> Allowed,
+ std::string_view Context) {
+ for (const yaml::MappingEntry &Entry : Mapping.mapping()) {
+ const bool IsAllowed =
+ std::ranges::any_of(Allowed, [&Entry](std::string_view Key) { return Entry.Key == Key; });
+ if (!IsAllowed) {
+ fail(Entry.KeyRange, std::string(Context) + " has unknown key '" + Entry.Key + "'");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ [[nodiscard]] const yaml::Node *requiredNode(const yaml::Node &Mapping, std::string_view Key,
+ std::string_view Context) {
+ const std::optional<yaml::NodeID> Value = Mapping.mappingValue(Key);
+ if (Value) {
+ const yaml::Node *Node = Input.node(*Value);
+ if (Node != nullptr)
+ return Node;
+ }
+ fail(Mapping.range(),
+ std::string(Context) + " is missing required key '" + std::string(Key) + "'");
+ return nullptr;
+ }
+
+ [[nodiscard]] std::optional<std::string>
+ requiredScalar(const yaml::Node &Mapping, std::string_view Key, std::string_view Context) {
+ const yaml::Node *Node = requiredNode(Mapping, Key, Context);
+ if (Node == nullptr)
+ return std::nullopt;
+ return scalar(*Node, std::string(Context) + " key '" + std::string(Key) + "'");
+ }
+
+ [[nodiscard]] std::optional<std::string> scalar(const yaml::Node &Node,
+ std::string_view Context) {
+ if (Node.kind() != yaml::NodeKind::Scalar) {
+ fail(Node.range(), std::string(Context) + " must be a scalar");
+ return std::nullopt;
+ }
+ return std::string(Node.scalar());
+ }
+
+ [[nodiscard]] std::optional<std::uint64_t> unsignedInteger(const yaml::Node &Node,
+ std::string_view Context) {
+ const auto Text = scalar(Node, Context);
+ if (!Text)
+ return std::nullopt;
+ std::uint64_t Result = 0;
+ const auto [End, Error] =
+ std::from_chars(Text->data(), Text->data() + Text->size(), Result, 10);
+ if (Error != std::errc{} || End != Text->data() + Text->size()) {
+ fail(Node.range(), std::string(Context) + " must be an unsigned decimal integer");
+ return std::nullopt;
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<std::uint32_t> unsignedInteger32(const yaml::Node &Node,
+ std::string_view Context) {
+ const auto Value = unsignedInteger(Node, Context);
+ if (!Value)
+ return std::nullopt;
+ if (*Value > std::numeric_limits<std::uint32_t>::max()) {
+ fail(Node.range(), std::string(Context) + " is outside the uint32 range");
+ return std::nullopt;
+ }
+ return static_cast<std::uint32_t>(*Value);
+ }
+
+ [[nodiscard]] std::optional<std::int64_t> signedInteger(const yaml::Node &Node,
+ std::string_view Context) {
+ const auto Text = scalar(Node, Context);
+ if (!Text)
+ return std::nullopt;
+ std::int64_t Result = 0;
+ const auto [End, Error] =
+ std::from_chars(Text->data(), Text->data() + Text->size(), Result, 10);
+ if (Error != std::errc{} || End != Text->data() + Text->size()) {
+ fail(Node.range(), std::string(Context) + " must be a signed decimal integer");
+ return std::nullopt;
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<bool> boolean(const yaml::Node &Node, std::string_view Context) {
+ const auto Text = scalar(Node, Context);
+ if (!Text)
+ return std::nullopt;
+ if (*Text == "true")
+ return true;
+ if (*Text == "false")
+ return false;
+ fail(Node.range(), std::string(Context) + " must be 'true' or 'false'");
+ return std::nullopt;
+ }
+
+ [[nodiscard]] std::optional<std::vector<std::string>> scalarSequence(const yaml::Node &Node,
+ std::string_view Context) {
+ if (!requireSequence(&Node, Context))
+ return std::nullopt;
+ std::vector<std::string> Result;
+ for (const yaml::NodeID Value : Node.sequence()) {
+ const yaml::Node *Element = Input.node(Value);
+ if (Element == nullptr)
+ return std::nullopt;
+ const auto Text = scalar(*Element, Context);
+ if (!Text)
+ return std::nullopt;
+ Result.push_back(*Text);
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<std::vector<std::int64_t>>
+ signedIntegerSequence(const yaml::Node &Node, std::string_view Context) {
+ if (!requireSequence(&Node, Context))
+ return std::nullopt;
+ std::vector<std::int64_t> Result;
+ for (const yaml::NodeID Value : Node.sequence()) {
+ const yaml::Node *Element = Input.node(Value);
+ if (Element == nullptr)
+ return std::nullopt;
+ const auto Parsed = signedInteger(*Element, Context);
+ if (!Parsed)
+ return std::nullopt;
+ Result.push_back(*Parsed);
+ }
+ return Result;
+ }
+
+ [[nodiscard]] bool parseTarget(const yaml::Node &Node, SourceTarget &Result) {
+ if (!requireMapping(&Node, "target"))
+ return false;
+ if (!checkKeys(Node, {"namespace", "name", "triple", "cpu", "abi", "data_layout", "features"},
+ "target")) {
+ return false;
+ }
+ const auto Namespace = requiredScalar(Node, "namespace", "target");
+ const auto Name = requiredScalar(Node, "name", "target");
+ const auto Triple = requiredScalar(Node, "triple", "target");
+ const auto CPU = requiredScalar(Node, "cpu", "target");
+ const auto ABI = requiredScalar(Node, "abi", "target");
+ const auto DataLayout = requiredScalar(Node, "data_layout", "target");
+ const yaml::Node *Features = requiredNode(Node, "features", "target");
+ if (!Namespace || !Name || !Triple || !CPU || !ABI || !DataLayout || Features == nullptr)
+ return false;
+ const auto ParsedFeatures = scalarSequence(*Features, "target features");
+ if (!ParsedFeatures)
+ return false;
+ Result = {
+ .Namespace = *Namespace,
+ .Name = *Name,
+ .Triple = *Triple,
+ .CPU = *CPU,
+ .ABI = *ABI,
+ .DataLayout = *DataLayout,
+ .Features = *ParsedFeatures,
+ .Range = Node.range(),
+ };
+ return true;
+ }
+
+ [[nodiscard]] bool parseRegisterUnits(const yaml::Node &Node,
+ std::vector<SourceRegisterUnit> &Result) {
+ if (!requireSequence(&Node, "register_units"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register unit"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name"}, "register unit"))
+ return false;
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "register unit");
+ const auto Symbol = requiredScalar(*Element, "symbol", "register unit");
+ const auto Name = requiredScalar(*Element, "name", "register unit");
+ if (IDNode == nullptr || !Symbol || !Name)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "register unit id");
+ if (!ID)
+ return false;
+ Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] std::optional<std::vector<SourceRegisterUnitLane>>
+ parseRegisterUnitLanes(const yaml::Node &Node) {
+ if (!requireSequence(&Node, "register units"))
+ return std::nullopt;
+ std::vector<SourceRegisterUnitLane> Result;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register unit lane"))
+ return std::nullopt;
+ if (!checkKeys(*Element, {"unit", "lanes"}, "register unit lane"))
+ return std::nullopt;
+ const auto Unit = requiredScalar(*Element, "unit", "register unit lane");
+ const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register unit lane");
+ if (!Unit || LanesNode == nullptr)
+ return std::nullopt;
+ const auto Lanes = unsignedInteger(*LanesNode, "register unit lane lanes");
+ if (!Lanes)
+ return std::nullopt;
+ Result.push_back({.UnitSymbol = *Unit, .Lanes = *Lanes, .Range = Element->range()});
+ }
+ return Result;
+ }
+
+ [[nodiscard]] bool parseRegisters(const yaml::Node &Node, std::vector<SourceRegister> &Result) {
+ if (!requireSequence(&Node, "registers"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register"))
+ return false;
+ if (!checkKeys(*Element,
+ {"id", "symbol", "name", "encoding", "dwarf_register", "units", "constant",
+ "artificial", "reserved", "aliases"},
+ "register")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "register");
+ const auto Symbol = requiredScalar(*Element, "symbol", "register");
+ const auto Name = requiredScalar(*Element, "name", "register");
+ const yaml::Node *EncodingNode = requiredNode(*Element, "encoding", "register");
+ const yaml::Node *DwarfNode = requiredNode(*Element, "dwarf_register", "register");
+ const yaml::Node *UnitsNode = requiredNode(*Element, "units", "register");
+ if (IDNode == nullptr || !Symbol || !Name || EncodingNode == nullptr ||
+ DwarfNode == nullptr || UnitsNode == nullptr) {
+ return false;
+ }
+ const auto ID = unsignedInteger32(*IDNode, "register id");
+ const auto Encoding = unsignedInteger32(*EncodingNode, "register encoding");
+ const auto DwarfRegister = unsignedInteger32(*DwarfNode, "register DWARF number");
+ const auto Units = parseRegisterUnitLanes(*UnitsNode);
+ if (!ID || !Encoding || !DwarfRegister || !Units)
+ return false;
+
+ SourceRegister Register{
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .Encoding = *Encoding,
+ .DwarfRegister = *DwarfRegister,
+ .Units = *Units,
+ .IsConstant = false,
+ .IsArtificial = false,
+ .IsReserved = false,
+ .AlternativeNames = {},
+ .Range = Element->range(),
+ };
+ if (const yaml::Node *Constant = optionalNode(*Element, "constant")) {
+ const auto Value = boolean(*Constant, "register constant");
+ if (!Value)
+ return false;
+ Register.IsConstant = *Value;
+ }
+ if (const yaml::Node *Artificial = optionalNode(*Element, "artificial")) {
+ const auto Value = boolean(*Artificial, "register artificial");
+ if (!Value)
+ return false;
+ Register.IsArtificial = *Value;
+ }
+ if (const yaml::Node *Reserved = optionalNode(*Element, "reserved")) {
+ const auto Value = boolean(*Reserved, "register reserved");
+ if (!Value)
+ return false;
+ Register.IsReserved = *Value;
+ }
+ if (const yaml::Node *Aliases = optionalNode(*Element, "aliases")) {
+ const auto Values = scalarSequence(*Aliases, "register aliases");
+ if (!Values)
+ return false;
+ Register.AlternativeNames = *Values;
+ }
+ Result.push_back(std::move(Register));
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseRegisterBanks(const yaml::Node &Node,
+ std::vector<SourceRegisterBank> &Result) {
+ if (!requireSequence(&Node, "register_banks"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register bank"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name", "maximum_size_bits"}, "register bank"))
+ return false;
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "register bank");
+ const auto Symbol = requiredScalar(*Element, "symbol", "register bank");
+ const auto Name = requiredScalar(*Element, "name", "register bank");
+ const yaml::Node *SizeNode = requiredNode(*Element, "maximum_size_bits", "register bank");
+ if (IDNode == nullptr || !Symbol || !Name || SizeNode == nullptr)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "register bank id");
+ const auto Size = unsignedInteger32(*SizeNode, "register bank maximum_size_bits");
+ if (!ID || !Size)
+ return false;
+ Result.push_back({.ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .MaximumSizeInBits = *Size,
+ .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseRegisterClasses(const yaml::Node &Node,
+ std::vector<SourceRegisterClass> &Result) {
+ if (!requireSequence(&Node, "register_classes"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register class"))
+ return false;
+ if (!checkKeys(*Element,
+ {"id", "symbol", "name", "bank", "size_bits", "spill_size_bits",
+ "spill_alignment_bits", "lanes", "registers", "allocation_order",
+ "allocatable", "copy_cost"},
+ "register class")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "register class");
+ const auto Symbol = requiredScalar(*Element, "symbol", "register class");
+ const auto Name = requiredScalar(*Element, "name", "register class");
+ const auto Bank = requiredScalar(*Element, "bank", "register class");
+ const yaml::Node *SizeNode = requiredNode(*Element, "size_bits", "register class");
+ const yaml::Node *SpillSizeNode = requiredNode(*Element, "spill_size_bits", "register class");
+ const yaml::Node *SpillAlignmentNode =
+ requiredNode(*Element, "spill_alignment_bits", "register class");
+ const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register class");
+ const yaml::Node *RegistersNode = requiredNode(*Element, "registers", "register class");
+ const yaml::Node *AllocationNode =
+ requiredNode(*Element, "allocation_order", "register class");
+ if (IDNode == nullptr || !Symbol || !Name || !Bank || SizeNode == nullptr ||
+ SpillSizeNode == nullptr || SpillAlignmentNode == nullptr || LanesNode == nullptr ||
+ RegistersNode == nullptr || AllocationNode == nullptr) {
+ return false;
+ }
+ const auto ID = unsignedInteger32(*IDNode, "register class id");
+ const auto Size = unsignedInteger32(*SizeNode, "register class size_bits");
+ const auto SpillSize = unsignedInteger32(*SpillSizeNode, "register class spill_size_bits");
+ const auto SpillAlignment =
+ unsignedInteger32(*SpillAlignmentNode, "register class spill_alignment_bits");
+ const auto Lanes = unsignedInteger(*LanesNode, "register class lanes");
+ const auto Registers = scalarSequence(*RegistersNode, "register class registers");
+ const auto AllocationOrder =
+ scalarSequence(*AllocationNode, "register class allocation_order");
+ if (!ID || !Size || !SpillSize || !SpillAlignment || !Lanes || !Registers ||
+ !AllocationOrder) {
+ return false;
+ }
+ SourceRegisterClass RegisterClass{
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .BankSymbol = *Bank,
+ .SizeInBits = *Size,
+ .SpillSizeInBits = *SpillSize,
+ .SpillAlignmentInBits = *SpillAlignment,
+ .Lanes = *Lanes,
+ .RegisterSymbols = *Registers,
+ .AllocationOrderSymbols = *AllocationOrder,
+ .Range = Element->range(),
+ };
+ if (const yaml::Node *Allocatable = optionalNode(*Element, "allocatable")) {
+ const auto Value = boolean(*Allocatable, "register class allocatable");
+ if (!Value)
+ return false;
+ RegisterClass.Allocatable = *Value;
+ }
+ if (const yaml::Node *CopyCost = optionalNode(*Element, "copy_cost")) {
+ const auto Value = unsignedInteger32(*CopyCost, "register class copy_cost");
+ if (!Value)
+ return false;
+ RegisterClass.CopyCost = *Value;
+ }
+ Result.push_back(std::move(RegisterClass));
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseRegisterMasks(const yaml::Node &Node,
+ std::vector<SourceRegisterMask> &Result) {
+ if (!requireSequence(&Node, "register_masks"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "register mask"))
+ return false;
+ if (!checkKeys(*Element, {"name", "preserved"}, "register mask"))
+ return false;
+ const auto Name = requiredScalar(*Element, "name", "register mask");
+ const yaml::Node *PreservedNode = requiredNode(*Element, "preserved", "register mask");
+ if (!Name || PreservedNode == nullptr)
+ return false;
+ const auto Preserved = scalarSequence(*PreservedNode, "register mask preserved");
+ if (!Preserved)
+ return false;
+ Result.push_back(
+ {.Name = *Name, .PreservedRegisterSymbols = *Preserved, .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseMIROperandFlagSequence(const yaml::Node &Node, std::string_view Context,
+ std::vector<SourceMIROperandFlag> &Result) {
+ if (!requireSequence(&Node, Context))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, Context) || !checkKeys(*Element, {"name", "value"}, Context)) {
+ return false;
+ }
+ const auto Name = requiredScalar(*Element, "name", Context);
+ const yaml::Node *ValueNode = requiredNode(*Element, "value", Context);
+ if (!Name || ValueNode == nullptr)
+ return false;
+ const auto Value = unsignedInteger32(*ValueNode, std::string(Context) + " value");
+ if (!Value)
+ return false;
+ Result.push_back({.Name = *Name, .Value = *Value, .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseMIROperandFlags(const yaml::Node &Node, SourceMIROperandFlags &Result) {
+ if (!requireMapping(&Node, "MIR operand flags") ||
+ !checkKeys(Node, {"direct_mask", "direct", "bitmask"}, "MIR operand flags")) {
+ return false;
+ }
+ const yaml::Node *DirectMaskNode = requiredNode(Node, "direct_mask", "MIR operand flags");
+ const yaml::Node *DirectNode = requiredNode(Node, "direct", "MIR operand flags");
+ const yaml::Node *BitmaskNode = requiredNode(Node, "bitmask", "MIR operand flags");
+ if (DirectMaskNode == nullptr || DirectNode == nullptr || BitmaskNode == nullptr)
+ return false;
+ const auto DirectMask = unsignedInteger32(*DirectMaskNode, "MIR operand flag direct_mask");
+ if (!DirectMask ||
+ !parseMIROperandFlagSequence(*DirectNode, "direct MIR operand flag", Result.Direct) ||
+ !parseMIROperandFlagSequence(*BitmaskNode, "bitmask MIR operand flag", Result.Bitmask)) {
+ return false;
+ }
+ Result.DirectMask = *DirectMask;
+ Result.Range = Node.range();
+ return true;
+ }
+
+ [[nodiscard]] bool parseNamedOperands(const yaml::Node &Node,
+ std::vector<SourceNamedOperand> &Result) {
+ if (!requireSequence(&Node, "named_operands"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "named operand"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name"}, "named operand"))
+ return false;
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "named operand");
+ const auto Symbol = requiredScalar(*Element, "symbol", "named operand");
+ const auto Name = requiredScalar(*Element, "name", "named operand");
+ if (IDNode == nullptr || !Symbol || !Name)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "named operand id");
+ if (!ID)
+ return false;
+ Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseImmediatePredicates(const yaml::Node &Node,
+ std::vector<SourceImmediatePredicate> &Result) {
+ if (!requireSequence(&Node, "immediate_predicates"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "immediate predicate"))
+ return false;
+ if (!checkKeys(
+ *Element,
+ {"id", "symbol", "name", "kind", "bits", "alignment", "nonzero", "allowed_values"},
+ "immediate predicate")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "immediate predicate");
+ const auto Symbol = requiredScalar(*Element, "symbol", "immediate predicate");
+ const auto Name = requiredScalar(*Element, "name", "immediate predicate");
+ const auto Kind = requiredScalar(*Element, "kind", "immediate predicate");
+ const yaml::Node *BitsNode = requiredNode(*Element, "bits", "immediate predicate");
+ if (IDNode == nullptr || !Symbol || !Name || !Kind || BitsNode == nullptr)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "immediate predicate id");
+ const auto Bits = unsignedInteger32(*BitsNode, "immediate predicate bits");
+ if (!ID || !Bits)
+ return false;
+ SourceImmediatePredicate Predicate{
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .Kind = *Kind,
+ .Bits = *Bits,
+ .Alignment = 1,
+ .NonZero = false,
+ .AllowedValues = {},
+ .Range = Element->range(),
+ };
+ if (const yaml::Node *Alignment = optionalNode(*Element, "alignment")) {
+ const auto Value = unsignedInteger(*Alignment, "immediate predicate alignment");
+ if (!Value)
+ return false;
+ Predicate.Alignment = *Value;
+ }
+ if (const yaml::Node *NonZero = optionalNode(*Element, "nonzero")) {
+ const auto Value = boolean(*NonZero, "immediate predicate nonzero");
+ if (!Value)
+ return false;
+ Predicate.NonZero = *Value;
+ }
+ if (const yaml::Node *AllowedValues = optionalNode(*Element, "allowed_values")) {
+ const auto Values =
+ signedIntegerSequence(*AllowedValues, "immediate predicate allowed_values");
+ if (!Values || Values->empty()) {
+ if (Values)
+ fail(AllowedValues->range(), "immediate predicate allowed_values must be nonempty");
+ return false;
+ }
+ Predicate.AllowedValues = *Values;
+ }
+ Result.push_back(std::move(Predicate));
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseSchedulingClasses(const yaml::Node &Node,
+ std::vector<SourceSchedulingClass> &Result) {
+ if (!requireSequence(&Node, "scheduling_classes"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "scheduling class"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name", "micro_ops", "latency"},
+ "scheduling class")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "scheduling class");
+ const auto Symbol = requiredScalar(*Element, "symbol", "scheduling class");
+ const auto Name = requiredScalar(*Element, "name", "scheduling class");
+ const yaml::Node *MicroOpsNode = requiredNode(*Element, "micro_ops", "scheduling class");
+ const yaml::Node *LatencyNode = requiredNode(*Element, "latency", "scheduling class");
+ if (IDNode == nullptr || !Symbol || !Name || MicroOpsNode == nullptr ||
+ LatencyNode == nullptr)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "scheduling class id");
+ const auto MicroOps = unsignedInteger32(*MicroOpsNode, "scheduling class micro_ops");
+ const auto Latency = unsignedInteger32(*LatencyNode, "scheduling class latency");
+ if (!ID || !MicroOps || !Latency)
+ return false;
+ Result.push_back({
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .MicroOps = *MicroOps,
+ .Latency = *Latency,
+ .Range = Element->range(),
+ });
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseEncodingCategories(const yaml::Node &Node,
+ std::vector<SourceEncodingCategory> &Result) {
+ if (!requireSequence(&Node, "encoding_categories"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "encoding category"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name", "size_bytes"}, "encoding category"))
+ return false;
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "encoding category");
+ const auto Symbol = requiredScalar(*Element, "symbol", "encoding category");
+ const auto Name = requiredScalar(*Element, "name", "encoding category");
+ const yaml::Node *SizeNode = requiredNode(*Element, "size_bytes", "encoding category");
+ if (IDNode == nullptr || !Symbol || !Name || SizeNode == nullptr)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "encoding category id");
+ const auto Size = unsignedInteger32(*SizeNode, "encoding category size_bytes");
+ if (!ID || !Size)
+ return false;
+ Result.push_back({
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .SizeInBytes = *Size,
+ .Range = Element->range(),
+ });
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseAssemblyFormats(const yaml::Node &Node,
+ std::vector<SourceAssemblyFormat> &Result) {
+ if (!requireSequence(&Node, "assembly_formats"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "assembly format"))
+ return false;
+ if (!checkKeys(*Element, {"id", "symbol", "name"}, "assembly format"))
+ return false;
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "assembly format");
+ const auto Symbol = requiredScalar(*Element, "symbol", "assembly format");
+ const auto Name = requiredScalar(*Element, "name", "assembly format");
+ if (IDNode == nullptr || !Symbol || !Name)
+ return false;
+ const auto ID = unsignedInteger32(*IDNode, "assembly format id");
+ if (!ID)
+ return false;
+ Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool parseCallingConventions(const yaml::Node &Node,
+ std::vector<SourceCallingConvention> &Result) {
+ if (!requireSequence(&Node, "calling_conventions"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "calling convention"))
+ return false;
+ if (!checkKeys(*Element,
+ {"id", "symbol", "name", "integer_register_class", "floating_register_class",
+ "integer_argument_registers", "floating_argument_registers",
+ "integer_return_registers", "floating_return_registers", "preserved_mask",
+ "stack_alignment_bytes", "stack_argument_alignment_bytes",
+ "shadow_space_bytes", "supports_varargs", "supports_tail_calls"},
+ "calling convention")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "calling convention");
+ const auto Symbol = requiredScalar(*Element, "symbol", "calling convention");
+ const auto Name = requiredScalar(*Element, "name", "calling convention");
+ const auto IntegerClass =
+ requiredScalar(*Element, "integer_register_class", "calling convention");
+ const auto FloatingClass =
+ requiredScalar(*Element, "floating_register_class", "calling convention");
+ const yaml::Node *IntegerArguments =
+ requiredNode(*Element, "integer_argument_registers", "calling convention");
+ const yaml::Node *FloatingArguments =
+ requiredNode(*Element, "floating_argument_registers", "calling convention");
+ const yaml::Node *IntegerReturns =
+ requiredNode(*Element, "integer_return_registers", "calling convention");
+ const yaml::Node *FloatingReturns =
+ requiredNode(*Element, "floating_return_registers", "calling convention");
+ const auto PreservedMask = requiredScalar(*Element, "preserved_mask", "calling convention");
+ const yaml::Node *StackAlignment =
+ requiredNode(*Element, "stack_alignment_bytes", "calling convention");
+ const yaml::Node *StackArgumentAlignment =
+ requiredNode(*Element, "stack_argument_alignment_bytes", "calling convention");
+ const yaml::Node *ShadowSpace =
+ requiredNode(*Element, "shadow_space_bytes", "calling convention");
+ const yaml::Node *SupportsVarArgs =
+ requiredNode(*Element, "supports_varargs", "calling convention");
+ const yaml::Node *SupportsTailCalls =
+ requiredNode(*Element, "supports_tail_calls", "calling convention");
+ if (IDNode == nullptr || !Symbol || !Name || !IntegerClass || !FloatingClass ||
+ IntegerArguments == nullptr || FloatingArguments == nullptr ||
+ IntegerReturns == nullptr || FloatingReturns == nullptr || !PreservedMask ||
+ StackAlignment == nullptr || StackArgumentAlignment == nullptr ||
+ ShadowSpace == nullptr || SupportsVarArgs == nullptr || SupportsTailCalls == nullptr) {
+ return false;
+ }
+ const auto ID = unsignedInteger32(*IDNode, "calling convention id");
+ const auto IntegerArgumentList =
+ scalarSequence(*IntegerArguments, "calling convention integer arguments");
+ const auto FloatingArgumentList =
+ scalarSequence(*FloatingArguments, "calling convention floating arguments");
+ const auto IntegerReturnList =
+ scalarSequence(*IntegerReturns, "calling convention integer returns");
+ const auto FloatingReturnList =
+ scalarSequence(*FloatingReturns, "calling convention floating returns");
+ const auto StackAlignmentValue =
+ unsignedInteger32(*StackAlignment, "calling convention stack alignment");
+ const auto StackArgumentAlignmentValue =
+ unsignedInteger32(*StackArgumentAlignment, "calling convention stack argument alignment");
+ const auto ShadowSpaceValue =
+ unsignedInteger32(*ShadowSpace, "calling convention shadow space");
+ const auto SupportsVarArgsValue = boolean(*SupportsVarArgs, "calling convention varargs");
+ const auto SupportsTailCallsValue =
+ boolean(*SupportsTailCalls, "calling convention tail calls");
+ if (!ID || !IntegerArgumentList || !FloatingArgumentList || !IntegerReturnList ||
+ !FloatingReturnList || !StackAlignmentValue || !StackArgumentAlignmentValue ||
+ !ShadowSpaceValue || !SupportsVarArgsValue || !SupportsTailCallsValue) {
+ return false;
+ }
+ Result.push_back({
+ .ID = *ID,
+ .Symbol = *Symbol,
+ .Name = *Name,
+ .IntegerRegisterClassSymbol = *IntegerClass,
+ .FloatingRegisterClassSymbol = *FloatingClass,
+ .IntegerArgumentRegisterSymbols = *IntegerArgumentList,
+ .FloatingArgumentRegisterSymbols = *FloatingArgumentList,
+ .IntegerReturnRegisterSymbols = *IntegerReturnList,
+ .FloatingReturnRegisterSymbols = *FloatingReturnList,
+ .PreservedMaskName = *PreservedMask,
+ .StackAlignmentBytes = *StackAlignmentValue,
+ .StackArgumentAlignmentBytes = *StackArgumentAlignmentValue,
+ .ShadowSpaceBytes = *ShadowSpaceValue,
+ .SupportsVarArgs = *SupportsVarArgsValue,
+ .SupportsTailCalls = *SupportsTailCallsValue,
+ .Range = Element->range(),
+ });
+ }
+ return true;
+ }
+
+ [[nodiscard]] std::optional<SourceOperand> parseOperand(const yaml::Node &Node) {
+ if (!requireMapping(&Node, "instruction operand"))
+ return std::nullopt;
+ if (!checkKeys(Node,
+ {"kinds", "role", "register_class", "register_bank", "immediate_predicate",
+ "memory_role", "default_tied_operand", "early_clobber", "optional",
+ "variadic_group", "allow_subreg", "name", "allowed_target_flags"},
+ "instruction operand")) {
+ return std::nullopt;
+ }
+ const yaml::Node *KindsNode = requiredNode(Node, "kinds", "instruction operand");
+ if (KindsNode == nullptr)
+ return std::nullopt;
+ const auto Kinds = scalarSequence(*KindsNode, "instruction operand kinds");
+ if (!Kinds)
+ return std::nullopt;
+
+ SourceOperand Result;
+ Result.Kinds = *Kinds;
+ Result.Range = Node.range();
+ if (const yaml::Node *Role = optionalNode(Node, "role")) {
+ const auto Value = scalar(*Role, "instruction operand role");
+ if (!Value)
+ return std::nullopt;
+ Result.RegisterRole = *Value;
+ }
+ if (const yaml::Node *RegisterClass = optionalNode(Node, "register_class")) {
+ const auto Value = scalar(*RegisterClass, "instruction operand register_class");
+ if (!Value)
+ return std::nullopt;
+ Result.RegisterClassSymbol = *Value;
+ }
+ if (const yaml::Node *RegisterBank = optionalNode(Node, "register_bank")) {
+ const auto Value = scalar(*RegisterBank, "instruction operand register_bank");
+ if (!Value)
+ return std::nullopt;
+ Result.RegisterBankSymbol = *Value;
+ }
+ if (const yaml::Node *Predicate = optionalNode(Node, "immediate_predicate")) {
+ const auto Value = scalar(*Predicate, "instruction operand immediate_predicate");
+ if (!Value)
+ return std::nullopt;
+ Result.ImmediatePredicateSymbol = *Value;
+ }
+ if (const yaml::Node *MemoryRole = optionalNode(Node, "memory_role")) {
+ const auto Value = scalar(*MemoryRole, "instruction operand memory_role");
+ if (!Value)
+ return std::nullopt;
+ Result.MemoryRole = *Value;
+ }
+ if (const yaml::Node *Tie = optionalNode(Node, "default_tied_operand")) {
+ const auto Value = unsignedInteger32(*Tie, "instruction operand default_tied_operand");
+ if (!Value)
+ return std::nullopt;
+ Result.DefaultTiedOperand = Value;
+ }
+ if (const yaml::Node *EarlyClobber = optionalNode(Node, "early_clobber")) {
+ const auto Value = boolean(*EarlyClobber, "instruction operand early_clobber");
+ if (!Value)
+ return std::nullopt;
+ Result.EarlyClobber = *Value;
+ }
+ if (const yaml::Node *Optional = optionalNode(Node, "optional")) {
+ const auto Value = boolean(*Optional, "instruction operand optional");
+ if (!Value)
+ return std::nullopt;
+ Result.Optional = *Value;
+ }
+ if (const yaml::Node *VariadicGroup = optionalNode(Node, "variadic_group")) {
+ const auto Value = boolean(*VariadicGroup, "instruction operand variadic_group");
+ if (!Value)
+ return std::nullopt;
+ Result.VariadicGroup = *Value;
+ }
+ if (const yaml::Node *AllowSubReg = optionalNode(Node, "allow_subreg")) {
+ const auto Value = boolean(*AllowSubReg, "instruction operand allow_subreg");
+ if (!Value)
+ return std::nullopt;
+ Result.AllowSubReg = *Value;
+ }
+ if (const yaml::Node *Name = optionalNode(Node, "name")) {
+ const auto Value = scalar(*Name, "instruction operand name");
+ if (!Value)
+ return std::nullopt;
+ Result.NameSymbol = *Value;
+ }
+ if (const yaml::Node *TargetFlags = optionalNode(Node, "allowed_target_flags")) {
+ const auto Value =
+ unsignedInteger32(*TargetFlags, "instruction operand allowed_target_flags");
+ if (!Value)
+ return std::nullopt;
+ Result.AllowedTargetFlags = *Value;
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<std::vector<SourceOperand>> parseOperands(const yaml::Node &Node) {
+ if (!requireSequence(&Node, "instruction operands"))
+ return std::nullopt;
+ std::vector<SourceOperand> Result;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (Element == nullptr)
+ return std::nullopt;
+ const auto Operand = parseOperand(*Element);
+ if (!Operand)
+ return std::nullopt;
+ Result.push_back(*Operand);
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<SourceImplicitRegister>
+ parseImplicitRegister(const yaml::Node &Node, std::string_view Context) {
+ if (!requireMapping(&Node, Context))
+ return std::nullopt;
+ if (!checkKeys(Node, {"register", "early_clobber", "renamable"}, Context))
+ return std::nullopt;
+ const auto Register = requiredScalar(Node, "register", Context);
+ if (!Register)
+ return std::nullopt;
+ SourceImplicitRegister Result{
+ .RegisterSymbol = *Register,
+ .Range = Node.range(),
+ };
+ if (const yaml::Node *EarlyClobber = optionalNode(Node, "early_clobber")) {
+ const auto Value = boolean(*EarlyClobber, std::string(Context) + " early_clobber");
+ if (!Value)
+ return std::nullopt;
+ Result.EarlyClobber = *Value;
+ }
+ if (const yaml::Node *Renamable = optionalNode(Node, "renamable")) {
+ const auto Value = boolean(*Renamable, std::string(Context) + " renamable");
+ if (!Value)
+ return std::nullopt;
+ Result.Renamable = *Value;
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<std::vector<SourceImplicitRegister>>
+ parseImplicitRegisters(const yaml::Node &Node, std::string_view Context) {
+ if (!requireSequence(&Node, Context))
+ return std::nullopt;
+ std::vector<SourceImplicitRegister> Result;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (Element == nullptr)
+ return std::nullopt;
+ const auto Register = parseImplicitRegister(*Element, Context);
+ if (!Register)
+ return std::nullopt;
+ Result.push_back(*Register);
+ }
+ return Result;
+ }
+
+ [[nodiscard]] bool parseInstructions(const yaml::Node &Node,
+ std::vector<SourceInstruction> &Result) {
+ if (!requireSequence(&Node, "instructions"))
+ return false;
+ for (const yaml::NodeID ElementID : Node.sequence()) {
+ const yaml::Node *Element = Input.node(ElementID);
+ if (!requireMapping(Element, "instruction"))
+ return false;
+ if (!checkKeys(*Element,
+ {"id", "symbol", "name", "assembly_mnemonic", "encoding", "operands",
+ "num_explicit_defs", "variadic", "implicit_defs", "implicit_uses",
+ "allow_extra_implicit_operands", "flags", "scheduling_class",
+ "encoding_category", "assembly_format", "requires_memory_operands", "owner",
+ "deadline"},
+ "instruction")) {
+ return false;
+ }
+ const yaml::Node *IDNode = requiredNode(*Element, "id", "instruction");
+ const auto Symbol = requiredScalar(*Element, "symbol", "instruction");
+ const auto Name = requiredScalar(*Element, "name", "instruction");
+ const yaml::Node *OperandsNode = requiredNode(*Element, "operands", "instruction");
+ const yaml::Node *DefsNode = requiredNode(*Element, "num_explicit_defs", "instruction");
+ const yaml::Node *FlagsNode = requiredNode(*Element, "flags", "instruction");
+ if (IDNode == nullptr || !Symbol || !Name || OperandsNode == nullptr || DefsNode == nullptr ||
+ FlagsNode == nullptr) {
+ return false;
+ }
+ const auto ID = unsignedInteger32(*IDNode, "instruction id");
+ const auto Defs = unsignedInteger32(*DefsNode, "instruction num_explicit_defs");
+ const auto Operands = parseOperands(*OperandsNode);
+ const auto Flags = scalarSequence(*FlagsNode, "instruction flags");
+ if (!ID || !Defs || !Operands || !Flags)
+ return false;
+ SourceInstruction Instruction;
+ Instruction.ID = *ID;
+ Instruction.Symbol = *Symbol;
+ Instruction.Name = *Name;
+ Instruction.Operands = *Operands;
+ Instruction.NumExplicitDefs = *Defs;
+ Instruction.Flags = *Flags;
+ Instruction.Range = Element->range();
+ if (const yaml::Node *Mnemonic = optionalNode(*Element, "assembly_mnemonic")) {
+ const auto Value = scalar(*Mnemonic, "instruction assembly_mnemonic");
+ if (!Value)
+ return false;
+ Instruction.AssemblyMnemonic = *Value;
+ }
+ if (const yaml::Node *Encoding = optionalNode(*Element, "encoding")) {
+ const auto Value = unsignedInteger32(*Encoding, "instruction encoding");
+ if (!Value)
+ return false;
+ Instruction.BinaryEncoding = Value;
+ }
+ if (const yaml::Node *SchedulingClass = optionalNode(*Element, "scheduling_class")) {
+ const auto Value = scalar(*SchedulingClass, "instruction scheduling_class");
+ if (!Value)
+ return false;
+ Instruction.SchedulingClassSymbol = *Value;
+ }
+ if (const yaml::Node *EncodingCategory = optionalNode(*Element, "encoding_category")) {
+ const auto Value = scalar(*EncodingCategory, "instruction encoding_category");
+ if (!Value)
+ return false;
+ Instruction.EncodingCategorySymbol = *Value;
+ }
+ if (const yaml::Node *AssemblyFormat = optionalNode(*Element, "assembly_format")) {
+ const auto Value = scalar(*AssemblyFormat, "instruction assembly_format");
+ if (!Value)
+ return false;
+ Instruction.AssemblyFormatSymbol = *Value;
+ }
+ if (const yaml::Node *Variadic = optionalNode(*Element, "variadic")) {
+ const auto Value = boolean(*Variadic, "instruction variadic");
+ if (!Value)
+ return false;
+ Instruction.Variadic = *Value;
+ }
+ if (const yaml::Node *ImplicitDefs = optionalNode(*Element, "implicit_defs")) {
+ const auto Values = parseImplicitRegisters(*ImplicitDefs, "instruction implicit_defs");
+ if (!Values)
+ return false;
+ Instruction.ImplicitDefs = *Values;
+ }
+ if (const yaml::Node *ImplicitUses = optionalNode(*Element, "implicit_uses")) {
+ const auto Values = parseImplicitRegisters(*ImplicitUses, "instruction implicit_uses");
+ if (!Values)
+ return false;
+ Instruction.ImplicitUses = *Values;
+ }
+ if (const yaml::Node *AllowExtra = optionalNode(*Element, "allow_extra_implicit_operands")) {
+ const auto Value = boolean(*AllowExtra, "instruction allow_extra_implicit_operands");
+ if (!Value)
+ return false;
+ Instruction.AllowExtraImplicitOperands = *Value;
+ }
+ if (const yaml::Node *MemoryOperands = optionalNode(*Element, "requires_memory_operands")) {
+ const auto Value = boolean(*MemoryOperands, "instruction requires_memory_operands");
+ if (!Value)
+ return false;
+ Instruction.RequiresMemoryOperands = *Value;
+ }
+ if (const yaml::Node *Owner = optionalNode(*Element, "owner")) {
+ const auto Value = scalar(*Owner, "instruction owner");
+ if (!Value)
+ return false;
+ Instruction.Owner = *Value;
+ }
+ if (const yaml::Node *Deadline = optionalNode(*Element, "deadline")) {
+ const auto Value = scalar(*Deadline, "instruction deadline");
+ if (!Value)
+ return false;
+ Instruction.Deadline = *Value;
+ }
+ Result.push_back(std::move(Instruction));
+ }
+ return true;
+ }
+
+ [[nodiscard]] const yaml::Node *optionalNode(const yaml::Node &Mapping,
+ std::string_view Key) const noexcept {
+ const std::optional<yaml::NodeID> Value = Mapping.mappingValue(Key);
+ return Value ? Input.node(*Value) : nullptr;
+ }
+
+ const yaml::Stream &Input;
+ std::vector<TextDiagnostic> Diagnostics;
+};
+
+} // namespace
+
+DescriptionParseResult parseDescription(std::string SourceName, std::string SourceText) {
+ yaml::ParseResult YAMLResult = yaml::parse(std::move(SourceName), std::move(SourceText));
+ if (!YAMLResult.succeeded()) {
+ DescriptionParseResult Result;
+ Result.Diagnostics.assign(YAMLResult.diagnostics().begin(), YAMLResult.diagnostics().end());
+ return Result;
+ }
+
+ ASTParser Parser(*YAMLResult.stream());
+ auto Records = Parser.parse();
+ if (!Records)
+ return {.Records = std::nullopt, .Diagnostics = Parser.takeDiagnostics()};
+ return {.Records = std::move(Records), .Diagnostics = {}};
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/lib/TableGen/Record.cpp b/backend/lib/TableGen/Record.cpp
new file mode 100644
index 0000000..c8981c6
--- /dev/null
+++ b/backend/lib/TableGen/Record.cpp
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/TableGen/Record.h"
+
+#include <algorithm>
+#include <cctype>
+#include <ranges>
+
+namespace rcc::targetgen {
+
+[[nodiscard]] std::string lowerASCII(std::string_view Value) {
+ std::string Result(Value);
+ for (char &Character : Result) {
+ if (Character >= 'A' && Character <= 'Z')
+ Character = static_cast<char>(Character - 'A' + 'a');
+ }
+ return Result;
+}
+
+[[nodiscard]] bool isIdentifier(std::string_view Value) noexcept {
+ if (Value.empty())
+ return false;
+ const auto IsHead = [](unsigned char Character) {
+ return std::isalpha(Character) != 0 || Character == '_';
+ };
+ const auto IsTail = [](unsigned char Character) {
+ return std::isalnum(Character) != 0 || Character == '_';
+ };
+ if (!IsHead(static_cast<unsigned char>(Value.front())))
+ return false;
+ return std::ranges::all_of(Value.substr(1), [IsTail](char Character) {
+ return IsTail(static_cast<unsigned char>(Character));
+ });
+}
+
+[[nodiscard]] bool isAssemblyMnemonic(std::string_view Value) noexcept {
+ if (Value.empty())
+ return false;
+ if (std::islower(static_cast<unsigned char>(Value.front())) == 0)
+ return false;
+ if (std::islower(static_cast<unsigned char>(Value.back())) == 0 &&
+ std::isdigit(static_cast<unsigned char>(Value.back())) == 0)
+ return false;
+ return std::ranges::all_of(Value, [](char Character) {
+ const unsigned char Code = static_cast<unsigned char>(Character);
+ return std::islower(Code) != 0 || std::isdigit(Code) != 0 || Character == '.' ||
+ Character == '_';
+ });
+}
+
+[[nodiscard]] std::string upperIdentifier(std::string_view Value) {
+ std::string Result;
+ Result.reserve(Value.size());
+ for (const char Character : Value) {
+ if (Character >= 'a' && Character <= 'z')
+ Result.push_back(static_cast<char>(Character - 'a' + 'A'));
+ else if ((Character >= 'A' && Character <= 'Z') || (Character >= '0' && Character <= '9'))
+ Result.push_back(Character);
+ else
+ Result.push_back('_');
+ }
+ return Result;
+}
+
+void appendQuoted(std::ostream &Output, std::string_view Value) {
+ Output << '"';
+ for (const char Character : Value) {
+ switch (Character) {
+ case '\\':
+ Output << "\\\\";
+ break;
+ case '"':
+ Output << "\\\"";
+ break;
+ case '\n':
+ Output << "\\n";
+ break;
+ case '\r':
+ Output << "\\r";
+ break;
+ case '\t':
+ Output << "\\t";
+ break;
+ default:
+ Output << Character;
+ break;
+ }
+ }
+ Output << '"';
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/lib/Target/Description/TargetDescriptionGenerator.cpp b/backend/lib/Target/Description/TargetDescriptionGenerator.cpp
deleted file mode 100644
index bd0e794..0000000
--- a/backend/lib/Target/Description/TargetDescriptionGenerator.cpp
+++ /dev/null
@@ -1,3063 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "TargetDescriptionGenerator.h"
-
-#include "rcc/Support/YAML.h"
-
-#include <algorithm>
-#include <array>
-#include <bit>
-#include <cctype>
-#include <charconv>
-#include <cstddef>
-#include <cstdint>
-#include <limits>
-#include <optional>
-#include <ranges>
-#include <span>
-#include <sstream>
-#include <string>
-#include <string_view>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-namespace rcc::targetgen {
-namespace {
-
-struct SourceTarget final {
- std::string Namespace;
- std::string Name;
- std::string Triple;
- std::string CPU;
- std::string ABI;
- std::string DataLayout;
- std::vector<std::string> Features;
- TextRange Range;
-};
-
-struct SourceRegisterUnit final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- TextRange Range;
-};
-
-struct SourceRegisterUnitLane final {
- std::string UnitSymbol;
- std::uint64_t Lanes = 0;
- TextRange Range;
-};
-
-struct SourceRegister final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t Encoding = 0;
- std::uint32_t DwarfRegister = 0;
- std::vector<SourceRegisterUnitLane> Units;
- bool IsConstant = false;
- bool IsArtificial = false;
- bool IsReserved = false;
- std::vector<std::string> AlternativeNames;
- TextRange Range;
-};
-
-struct SourceRegisterBank final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t MaximumSizeInBits = 0;
- TextRange Range;
-};
-
-struct SourceRegisterClass final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::string BankSymbol;
- std::uint32_t SizeInBits = 0;
- std::uint32_t SpillSizeInBits = 0;
- std::uint32_t SpillAlignmentInBits = 0;
- std::uint64_t Lanes = 0;
- std::vector<std::string> RegisterSymbols;
- std::vector<std::string> AllocationOrderSymbols;
- bool Allocatable = true;
- std::uint32_t CopyCost = 1;
- TextRange Range;
-};
-
-struct SourceRegisterMask final {
- std::string Name;
- std::vector<std::string> PreservedRegisterSymbols;
- TextRange Range;
-};
-
-struct SourceMIROperandFlag final {
- std::string Name;
- std::uint32_t Value = 0;
- TextRange Range;
-};
-
-struct SourceMIROperandFlags final {
- std::uint32_t DirectMask = 0;
- std::vector<SourceMIROperandFlag> Direct;
- std::vector<SourceMIROperandFlag> Bitmask;
- TextRange Range;
-};
-
-struct SourceCallingConvention final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::string IntegerRegisterClassSymbol;
- std::string FloatingRegisterClassSymbol;
- std::vector<std::string> IntegerArgumentRegisterSymbols;
- std::vector<std::string> FloatingArgumentRegisterSymbols;
- std::vector<std::string> IntegerReturnRegisterSymbols;
- std::vector<std::string> FloatingReturnRegisterSymbols;
- std::string PreservedMaskName;
- std::uint32_t StackAlignmentBytes = 0;
- std::uint32_t StackArgumentAlignmentBytes = 0;
- std::uint32_t ShadowSpaceBytes = 0;
- bool SupportsVarArgs = false;
- bool SupportsTailCalls = false;
- TextRange Range;
-};
-
-struct SourceNamedOperand final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- TextRange Range;
-};
-
-struct SourceImmediatePredicate final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::string Kind;
- std::uint32_t Bits = 0;
- std::uint64_t Alignment = 1;
- bool NonZero = false;
- std::vector<std::int64_t> AllowedValues;
- TextRange Range;
-};
-
-struct SourceSchedulingClass final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t MicroOps = 0;
- std::uint32_t Latency = 0;
- TextRange Range;
-};
-
-struct SourceEncodingCategory final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t SizeInBytes = 0;
- TextRange Range;
-};
-
-struct SourceAssemblyFormat final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- TextRange Range;
-};
-
-struct SourceOperand final {
- std::vector<std::string> Kinds;
- std::string RegisterRole = "none";
- std::optional<std::string> RegisterClassSymbol;
- std::optional<std::string> RegisterBankSymbol;
- std::optional<std::string> ImmediatePredicateSymbol;
- std::string MemoryRole = "none";
- std::optional<std::uint32_t> DefaultTiedOperand;
- bool EarlyClobber = false;
- bool Optional = false;
- bool VariadicGroup = false;
- bool AllowSubReg = false;
- std::optional<std::string> NameSymbol;
- std::uint32_t AllowedTargetFlags = 0;
- TextRange Range;
-};
-
-struct SourceImplicitRegister final {
- std::string RegisterSymbol;
- bool EarlyClobber = false;
- bool Renamable = false;
- TextRange Range;
-};
-
-struct SourceInstruction final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::string AssemblyMnemonic;
- std::optional<std::uint32_t> BinaryEncoding;
- std::vector<SourceOperand> Operands;
- std::uint32_t NumExplicitDefs = 0;
- bool Variadic = false;
- std::vector<SourceImplicitRegister> ImplicitDefs;
- std::vector<SourceImplicitRegister> ImplicitUses;
- bool AllowExtraImplicitOperands = false;
- std::vector<std::string> Flags;
- std::string SchedulingClassSymbol;
- std::string EncodingCategorySymbol;
- std::string AssemblyFormatSymbol;
- bool RequiresMemoryOperands = false;
- std::string Owner = "none";
- std::string Deadline = "emission";
- TextRange Range;
-};
-
-struct DescriptionAST final {
- SourceTarget Target;
- std::vector<SourceRegisterUnit> RegisterUnits;
- std::vector<SourceRegister> Registers;
- std::vector<SourceRegisterBank> RegisterBanks;
- std::vector<SourceRegisterClass> RegisterClasses;
- std::vector<SourceRegisterMask> RegisterMasks;
- SourceMIROperandFlags MIROperandFlags;
- std::vector<SourceCallingConvention> CallingConventions;
- std::vector<SourceNamedOperand> NamedOperands;
- std::vector<SourceImmediatePredicate> ImmediatePredicates;
- std::vector<SourceSchedulingClass> SchedulingClasses;
- std::vector<SourceEncodingCategory> EncodingCategories;
- std::vector<SourceAssemblyFormat> AssemblyFormats;
- std::vector<SourceInstruction> Instructions;
-};
-
-struct NormalizedRegisterUnitLane final {
- std::uint32_t UnitID = 0;
- std::uint64_t Lanes = 0;
-};
-
-struct NormalizedRegister final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t Encoding = 0;
- std::uint32_t DwarfRegister = 0;
- std::vector<NormalizedRegisterUnitLane> Units;
- bool IsConstant = false;
- bool IsArtificial = false;
- bool IsReserved = false;
- std::vector<std::string> AlternativeNames;
-};
-
-struct NormalizedRegisterBank final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t MaximumSizeInBits = 0;
- std::vector<std::uint32_t> RegisterClasses;
-};
-
-struct NormalizedRegisterClass final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t BankID = 0;
- std::uint32_t SizeInBits = 0;
- std::uint32_t SpillSizeInBits = 0;
- std::uint32_t SpillAlignmentInBits = 0;
- std::uint64_t Lanes = 0;
- std::vector<std::uint32_t> Registers;
- std::vector<std::uint32_t> AllocationOrder;
- bool Allocatable = true;
- std::uint32_t CopyCost = 1;
-};
-
-struct NormalizedRegisterMask final {
- std::string Name;
- std::vector<std::uint32_t> PreservedRegisters;
-};
-
-struct NormalizedCallingConvention final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t IntegerRegisterClass = 0;
- std::uint32_t FloatingRegisterClass = 0;
- std::vector<std::uint32_t> IntegerArgumentRegisters;
- std::vector<std::uint32_t> FloatingArgumentRegisters;
- std::vector<std::uint32_t> IntegerReturnRegisters;
- std::vector<std::uint32_t> FloatingReturnRegisters;
- std::string PreservedMaskName;
- std::uint32_t StackAlignmentBytes = 0;
- std::uint32_t StackArgumentAlignmentBytes = 0;
- std::uint32_t ShadowSpaceBytes = 0;
- bool SupportsVarArgs = false;
- bool SupportsTailCalls = false;
-};
-
-struct NormalizedNamedOperand final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
-};
-
-struct NormalizedImmediatePredicate final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- bool IsSigned = false;
- std::uint32_t Bits = 0;
- std::uint64_t Alignment = 1;
- bool NonZero = false;
- std::vector<std::int64_t> AllowedValues;
-};
-
-struct NormalizedSchedulingClass final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t MicroOps = 0;
- std::uint32_t Latency = 0;
-};
-
-struct NormalizedEncodingCategory final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::uint32_t SizeInBytes = 0;
-};
-
-struct NormalizedAssemblyFormat final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
-};
-
-struct NormalizedOperand final {
- std::vector<std::string> Kinds;
- std::string RegisterRole;
- std::optional<std::uint32_t> RegisterClass;
- std::optional<std::uint32_t> RegisterBank;
- std::optional<std::uint32_t> ImmediatePredicate;
- std::string MemoryRole;
- std::optional<std::uint32_t> DefaultTiedOperand;
- bool EarlyClobber = false;
- bool Optional = false;
- bool VariadicGroup = false;
- bool AllowSubReg = false;
- std::optional<std::uint32_t> Name;
- std::uint32_t AllowedTargetFlags = 0;
-};
-
-struct NormalizedImplicitRegister final {
- std::uint32_t Register = 0;
- bool EarlyClobber = false;
- bool Renamable = false;
-};
-
-struct NormalizedInstruction final {
- std::uint32_t ID = 0;
- std::string Symbol;
- std::string Name;
- std::string AssemblyMnemonic;
- std::uint32_t BinaryEncoding = 0;
- std::vector<NormalizedOperand> Operands;
- std::uint32_t NumExplicitDefs = 0;
- bool Variadic = false;
- std::vector<NormalizedImplicitRegister> ImplicitDefs;
- std::vector<NormalizedImplicitRegister> ImplicitUses;
- bool AllowExtraImplicitOperands = false;
- std::vector<std::string> Flags;
- std::uint32_t SchedulingClass = 0;
- std::uint32_t EncodingCategory = 0;
- std::uint32_t AssemblyFormat = 0;
- bool RequiresMemoryOperands = false;
- std::string Owner;
- std::string Deadline;
-};
-
-struct NormalizedTargetModel final {
- SourceTarget Target;
- std::vector<SourceRegisterUnit> RegisterUnits;
- std::vector<NormalizedRegister> Registers;
- std::vector<NormalizedRegisterBank> RegisterBanks;
- std::vector<NormalizedRegisterClass> RegisterClasses;
- std::vector<NormalizedRegisterMask> RegisterMasks;
- SourceMIROperandFlags MIROperandFlags;
- std::vector<NormalizedCallingConvention> CallingConventions;
- std::vector<NormalizedNamedOperand> NamedOperands;
- std::vector<NormalizedImmediatePredicate> ImmediatePredicates;
- std::vector<NormalizedSchedulingClass> SchedulingClasses;
- std::vector<NormalizedEncodingCategory> EncodingCategories;
- std::vector<NormalizedAssemblyFormat> AssemblyFormats;
- std::vector<NormalizedInstruction> Instructions;
-};
-
-[[nodiscard]] std::string lowerASCII(std::string_view Value) {
- std::string Result(Value);
- for (char &Character : Result) {
- if (Character >= 'A' && Character <= 'Z')
- Character = static_cast<char>(Character - 'A' + 'a');
- }
- return Result;
-}
-
-[[nodiscard]] bool isIdentifier(std::string_view Value) noexcept {
- if (Value.empty())
- return false;
- const auto IsHead = [](unsigned char Character) {
- return std::isalpha(Character) != 0 || Character == '_';
- };
- const auto IsTail = [](unsigned char Character) {
- return std::isalnum(Character) != 0 || Character == '_';
- };
- if (!IsHead(static_cast<unsigned char>(Value.front())))
- return false;
- return std::ranges::all_of(Value.substr(1), [IsTail](char Character) {
- return IsTail(static_cast<unsigned char>(Character));
- });
-}
-
-[[nodiscard]] bool isAssemblyMnemonic(std::string_view Value) noexcept {
- if (Value.empty())
- return false;
- if (std::islower(static_cast<unsigned char>(Value.front())) == 0)
- return false;
- if (std::islower(static_cast<unsigned char>(Value.back())) == 0 &&
- std::isdigit(static_cast<unsigned char>(Value.back())) == 0)
- return false;
- return std::ranges::all_of(Value, [](char Character) {
- const unsigned char Code = static_cast<unsigned char>(Character);
- return std::islower(Code) != 0 || std::isdigit(Code) != 0 || Character == '.' ||
- Character == '_';
- });
-}
-
-[[nodiscard]] std::string upperIdentifier(std::string_view Value) {
- std::string Result;
- Result.reserve(Value.size());
- for (const char Character : Value) {
- if (Character >= 'a' && Character <= 'z')
- Result.push_back(static_cast<char>(Character - 'a' + 'A'));
- else if ((Character >= 'A' && Character <= 'Z') || (Character >= '0' && Character <= '9'))
- Result.push_back(Character);
- else
- Result.push_back('_');
- }
- return Result;
-}
-
-void appendQuoted(std::ostream &Output, std::string_view Value) {
- Output << '"';
- for (const char Character : Value) {
- switch (Character) {
- case '\\':
- Output << "\\\\";
- break;
- case '"':
- Output << "\\\"";
- break;
- case '\n':
- Output << "\\n";
- break;
- case '\r':
- Output << "\\r";
- break;
- case '\t':
- Output << "\\t";
- break;
- default:
- Output << Character;
- break;
- }
- }
- Output << '"';
-}
-
-class ASTParser final {
-public:
- explicit ASTParser(const yaml::Stream &Input) : Input(Input) {}
-
- [[nodiscard]] std::optional<DescriptionAST> parse() {
- const std::span<const yaml::Document> Documents = Input.documents();
- if (Documents.size() != 1) {
- fail(Input.source().range(0, Input.source().text().size()),
- "target description must contain exactly one YAML document");
- return std::nullopt;
- }
-
- const yaml::Node *Root = Input.node(Documents.front().Root);
- if (!requireMapping(Root, "target description root"))
- return std::nullopt;
- if (!checkKeys(*Root,
- {"schema", "target", "register_units", "registers", "register_banks",
- "register_classes", "register_masks", "mir_operand_flags", "named_operands",
- "immediate_predicates", "scheduling_classes", "encoding_categories",
- "assembly_formats", "calling_conventions", "instructions"},
- "target description root")) {
- return std::nullopt;
- }
-
- const yaml::Node *SchemaNode = requiredNode(*Root, "schema", "target description root");
- if (SchemaNode == nullptr)
- return std::nullopt;
- const auto Schema = scalar(*SchemaNode, "target description schema");
- if (!Schema || *Schema != "rcc-target-description-v2") {
- if (Schema)
- fail(SchemaNode->range(), "unsupported target description schema");
- return std::nullopt;
- }
-
- DescriptionAST Result;
- const yaml::Node *TargetNode = requiredNode(*Root, "target", "target description root");
- if (TargetNode == nullptr || !parseTarget(*TargetNode, Result.Target))
- return std::nullopt;
- const yaml::Node *UnitsNode = requiredNode(*Root, "register_units", "target description root");
- const yaml::Node *RegistersNode = requiredNode(*Root, "registers", "target description root");
- const yaml::Node *BanksNode = requiredNode(*Root, "register_banks", "target description root");
- const yaml::Node *ClassesNode =
- requiredNode(*Root, "register_classes", "target description root");
- const yaml::Node *MasksNode = requiredNode(*Root, "register_masks", "target description root");
- const yaml::Node *MIROperandFlagsNode = optionalNode(*Root, "mir_operand_flags");
- const yaml::Node *NamedOperandsNode =
- requiredNode(*Root, "named_operands", "target description root");
- const yaml::Node *ImmediatePredicatesNode =
- requiredNode(*Root, "immediate_predicates", "target description root");
- const yaml::Node *SchedulingClassesNode =
- requiredNode(*Root, "scheduling_classes", "target description root");
- const yaml::Node *EncodingCategoriesNode =
- requiredNode(*Root, "encoding_categories", "target description root");
- const yaml::Node *AssemblyFormatsNode =
- requiredNode(*Root, "assembly_formats", "target description root");
- const yaml::Node *CallingConventionsNode =
- requiredNode(*Root, "calling_conventions", "target description root");
- const yaml::Node *InstructionsNode =
- requiredNode(*Root, "instructions", "target description root");
- if (UnitsNode == nullptr || RegistersNode == nullptr || BanksNode == nullptr ||
- ClassesNode == nullptr || MasksNode == nullptr || NamedOperandsNode == nullptr ||
- ImmediatePredicatesNode == nullptr || SchedulingClassesNode == nullptr ||
- EncodingCategoriesNode == nullptr || AssemblyFormatsNode == nullptr ||
- CallingConventionsNode == nullptr || InstructionsNode == nullptr ||
- !parseRegisterUnits(*UnitsNode, Result.RegisterUnits) ||
- !parseRegisters(*RegistersNode, Result.Registers) ||
- !parseRegisterBanks(*BanksNode, Result.RegisterBanks) ||
- !parseRegisterClasses(*ClassesNode, Result.RegisterClasses) ||
- !parseRegisterMasks(*MasksNode, Result.RegisterMasks) ||
- (MIROperandFlagsNode != nullptr &&
- !parseMIROperandFlags(*MIROperandFlagsNode, Result.MIROperandFlags)) ||
- !parseNamedOperands(*NamedOperandsNode, Result.NamedOperands) ||
- !parseImmediatePredicates(*ImmediatePredicatesNode, Result.ImmediatePredicates) ||
- !parseSchedulingClasses(*SchedulingClassesNode, Result.SchedulingClasses) ||
- !parseEncodingCategories(*EncodingCategoriesNode, Result.EncodingCategories) ||
- !parseAssemblyFormats(*AssemblyFormatsNode, Result.AssemblyFormats) ||
- !parseCallingConventions(*CallingConventionsNode, Result.CallingConventions) ||
- !parseInstructions(*InstructionsNode, Result.Instructions)) {
- return std::nullopt;
- }
- return Result;
- }
-
- [[nodiscard]] std::vector<TextDiagnostic> takeDiagnostics() {
- return std::move(Diagnostics);
- }
-
-private:
- void fail(TextRange Range, std::string Message) {
- Diagnostics.push_back({
- .Severity = DiagnosticSeverity::Error,
- .Category = "target-description",
- .Message = std::move(Message),
- .SourceName = std::string(Input.source().name()),
- .Range = Range,
- });
- }
-
- [[nodiscard]] bool requireMapping(const yaml::Node *Node, std::string_view Context) {
- if (Node != nullptr && Node->kind() == yaml::NodeKind::Mapping)
- return true;
- fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(),
- std::string(Context) + " must be a mapping");
- return false;
- }
-
- [[nodiscard]] bool requireSequence(const yaml::Node *Node, std::string_view Context) {
- if (Node != nullptr && Node->kind() == yaml::NodeKind::Sequence)
- return true;
- fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(),
- std::string(Context) + " must be a sequence");
- return false;
- }
-
- [[nodiscard]] bool checkKeys(const yaml::Node &Mapping,
- std::initializer_list<std::string_view> Allowed,
- std::string_view Context) {
- for (const yaml::MappingEntry &Entry : Mapping.mapping()) {
- const bool IsAllowed =
- std::ranges::any_of(Allowed, [&Entry](std::string_view Key) { return Entry.Key == Key; });
- if (!IsAllowed) {
- fail(Entry.KeyRange, std::string(Context) + " has unknown key '" + Entry.Key + "'");
- return false;
- }
- }
- return true;
- }
-
- [[nodiscard]] const yaml::Node *requiredNode(const yaml::Node &Mapping, std::string_view Key,
- std::string_view Context) {
- const std::optional<yaml::NodeID> Value = Mapping.mappingValue(Key);
- if (Value) {
- const yaml::Node *Node = Input.node(*Value);
- if (Node != nullptr)
- return Node;
- }
- fail(Mapping.range(),
- std::string(Context) + " is missing required key '" + std::string(Key) + "'");
- return nullptr;
- }
-
- [[nodiscard]] std::optional<std::string>
- requiredScalar(const yaml::Node &Mapping, std::string_view Key, std::string_view Context) {
- const yaml::Node *Node = requiredNode(Mapping, Key, Context);
- if (Node == nullptr)
- return std::nullopt;
- return scalar(*Node, std::string(Context) + " key '" + std::string(Key) + "'");
- }
-
- [[nodiscard]] std::optional<std::string> scalar(const yaml::Node &Node,
- std::string_view Context) {
- if (Node.kind() != yaml::NodeKind::Scalar) {
- fail(Node.range(), std::string(Context) + " must be a scalar");
- return std::nullopt;
- }
- return std::string(Node.scalar());
- }
-
- [[nodiscard]] std::optional<std::uint64_t> unsignedInteger(const yaml::Node &Node,
- std::string_view Context) {
- const auto Text = scalar(Node, Context);
- if (!Text)
- return std::nullopt;
- std::uint64_t Result = 0;
- const auto [End, Error] =
- std::from_chars(Text->data(), Text->data() + Text->size(), Result, 10);
- if (Error != std::errc{} || End != Text->data() + Text->size()) {
- fail(Node.range(), std::string(Context) + " must be an unsigned decimal integer");
- return std::nullopt;
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<std::uint32_t> unsignedInteger32(const yaml::Node &Node,
- std::string_view Context) {
- const auto Value = unsignedInteger(Node, Context);
- if (!Value)
- return std::nullopt;
- if (*Value > std::numeric_limits<std::uint32_t>::max()) {
- fail(Node.range(), std::string(Context) + " is outside the uint32 range");
- return std::nullopt;
- }
- return static_cast<std::uint32_t>(*Value);
- }
-
- [[nodiscard]] std::optional<std::int64_t> signedInteger(const yaml::Node &Node,
- std::string_view Context) {
- const auto Text = scalar(Node, Context);
- if (!Text)
- return std::nullopt;
- std::int64_t Result = 0;
- const auto [End, Error] =
- std::from_chars(Text->data(), Text->data() + Text->size(), Result, 10);
- if (Error != std::errc{} || End != Text->data() + Text->size()) {
- fail(Node.range(), std::string(Context) + " must be a signed decimal integer");
- return std::nullopt;
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<bool> boolean(const yaml::Node &Node, std::string_view Context) {
- const auto Text = scalar(Node, Context);
- if (!Text)
- return std::nullopt;
- if (*Text == "true")
- return true;
- if (*Text == "false")
- return false;
- fail(Node.range(), std::string(Context) + " must be 'true' or 'false'");
- return std::nullopt;
- }
-
- [[nodiscard]] std::optional<std::vector<std::string>> scalarSequence(const yaml::Node &Node,
- std::string_view Context) {
- if (!requireSequence(&Node, Context))
- return std::nullopt;
- std::vector<std::string> Result;
- for (const yaml::NodeID Value : Node.sequence()) {
- const yaml::Node *Element = Input.node(Value);
- if (Element == nullptr)
- return std::nullopt;
- const auto Text = scalar(*Element, Context);
- if (!Text)
- return std::nullopt;
- Result.push_back(*Text);
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<std::vector<std::int64_t>>
- signedIntegerSequence(const yaml::Node &Node, std::string_view Context) {
- if (!requireSequence(&Node, Context))
- return std::nullopt;
- std::vector<std::int64_t> Result;
- for (const yaml::NodeID Value : Node.sequence()) {
- const yaml::Node *Element = Input.node(Value);
- if (Element == nullptr)
- return std::nullopt;
- const auto Parsed = signedInteger(*Element, Context);
- if (!Parsed)
- return std::nullopt;
- Result.push_back(*Parsed);
- }
- return Result;
- }
-
- [[nodiscard]] bool parseTarget(const yaml::Node &Node, SourceTarget &Result) {
- if (!requireMapping(&Node, "target"))
- return false;
- if (!checkKeys(Node, {"namespace", "name", "triple", "cpu", "abi", "data_layout", "features"},
- "target")) {
- return false;
- }
- const auto Namespace = requiredScalar(Node, "namespace", "target");
- const auto Name = requiredScalar(Node, "name", "target");
- const auto Triple = requiredScalar(Node, "triple", "target");
- const auto CPU = requiredScalar(Node, "cpu", "target");
- const auto ABI = requiredScalar(Node, "abi", "target");
- const auto DataLayout = requiredScalar(Node, "data_layout", "target");
- const yaml::Node *Features = requiredNode(Node, "features", "target");
- if (!Namespace || !Name || !Triple || !CPU || !ABI || !DataLayout || Features == nullptr)
- return false;
- const auto ParsedFeatures = scalarSequence(*Features, "target features");
- if (!ParsedFeatures)
- return false;
- Result = {
- .Namespace = *Namespace,
- .Name = *Name,
- .Triple = *Triple,
- .CPU = *CPU,
- .ABI = *ABI,
- .DataLayout = *DataLayout,
- .Features = *ParsedFeatures,
- .Range = Node.range(),
- };
- return true;
- }
-
- [[nodiscard]] bool parseRegisterUnits(const yaml::Node &Node,
- std::vector<SourceRegisterUnit> &Result) {
- if (!requireSequence(&Node, "register_units"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register unit"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name"}, "register unit"))
- return false;
- const yaml::Node *IDNode = requiredNode(*Element, "id", "register unit");
- const auto Symbol = requiredScalar(*Element, "symbol", "register unit");
- const auto Name = requiredScalar(*Element, "name", "register unit");
- if (IDNode == nullptr || !Symbol || !Name)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "register unit id");
- if (!ID)
- return false;
- Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] std::optional<std::vector<SourceRegisterUnitLane>>
- parseRegisterUnitLanes(const yaml::Node &Node) {
- if (!requireSequence(&Node, "register units"))
- return std::nullopt;
- std::vector<SourceRegisterUnitLane> Result;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register unit lane"))
- return std::nullopt;
- if (!checkKeys(*Element, {"unit", "lanes"}, "register unit lane"))
- return std::nullopt;
- const auto Unit = requiredScalar(*Element, "unit", "register unit lane");
- const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register unit lane");
- if (!Unit || LanesNode == nullptr)
- return std::nullopt;
- const auto Lanes = unsignedInteger(*LanesNode, "register unit lane lanes");
- if (!Lanes)
- return std::nullopt;
- Result.push_back({.UnitSymbol = *Unit, .Lanes = *Lanes, .Range = Element->range()});
- }
- return Result;
- }
-
- [[nodiscard]] bool parseRegisters(const yaml::Node &Node, std::vector<SourceRegister> &Result) {
- if (!requireSequence(&Node, "registers"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register"))
- return false;
- if (!checkKeys(*Element,
- {"id", "symbol", "name", "encoding", "dwarf_register", "units", "constant",
- "artificial", "reserved", "aliases"},
- "register")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "register");
- const auto Symbol = requiredScalar(*Element, "symbol", "register");
- const auto Name = requiredScalar(*Element, "name", "register");
- const yaml::Node *EncodingNode = requiredNode(*Element, "encoding", "register");
- const yaml::Node *DwarfNode = requiredNode(*Element, "dwarf_register", "register");
- const yaml::Node *UnitsNode = requiredNode(*Element, "units", "register");
- if (IDNode == nullptr || !Symbol || !Name || EncodingNode == nullptr ||
- DwarfNode == nullptr || UnitsNode == nullptr) {
- return false;
- }
- const auto ID = unsignedInteger32(*IDNode, "register id");
- const auto Encoding = unsignedInteger32(*EncodingNode, "register encoding");
- const auto DwarfRegister = unsignedInteger32(*DwarfNode, "register DWARF number");
- const auto Units = parseRegisterUnitLanes(*UnitsNode);
- if (!ID || !Encoding || !DwarfRegister || !Units)
- return false;
-
- SourceRegister Register{
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .Encoding = *Encoding,
- .DwarfRegister = *DwarfRegister,
- .Units = *Units,
- .IsConstant = false,
- .IsArtificial = false,
- .IsReserved = false,
- .AlternativeNames = {},
- .Range = Element->range(),
- };
- if (const yaml::Node *Constant = optionalNode(*Element, "constant")) {
- const auto Value = boolean(*Constant, "register constant");
- if (!Value)
- return false;
- Register.IsConstant = *Value;
- }
- if (const yaml::Node *Artificial = optionalNode(*Element, "artificial")) {
- const auto Value = boolean(*Artificial, "register artificial");
- if (!Value)
- return false;
- Register.IsArtificial = *Value;
- }
- if (const yaml::Node *Reserved = optionalNode(*Element, "reserved")) {
- const auto Value = boolean(*Reserved, "register reserved");
- if (!Value)
- return false;
- Register.IsReserved = *Value;
- }
- if (const yaml::Node *Aliases = optionalNode(*Element, "aliases")) {
- const auto Values = scalarSequence(*Aliases, "register aliases");
- if (!Values)
- return false;
- Register.AlternativeNames = *Values;
- }
- Result.push_back(std::move(Register));
- }
- return true;
- }
-
- [[nodiscard]] bool parseRegisterBanks(const yaml::Node &Node,
- std::vector<SourceRegisterBank> &Result) {
- if (!requireSequence(&Node, "register_banks"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register bank"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name", "maximum_size_bits"}, "register bank"))
- return false;
- const yaml::Node *IDNode = requiredNode(*Element, "id", "register bank");
- const auto Symbol = requiredScalar(*Element, "symbol", "register bank");
- const auto Name = requiredScalar(*Element, "name", "register bank");
- const yaml::Node *SizeNode = requiredNode(*Element, "maximum_size_bits", "register bank");
- if (IDNode == nullptr || !Symbol || !Name || SizeNode == nullptr)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "register bank id");
- const auto Size = unsignedInteger32(*SizeNode, "register bank maximum_size_bits");
- if (!ID || !Size)
- return false;
- Result.push_back({.ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .MaximumSizeInBits = *Size,
- .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] bool parseRegisterClasses(const yaml::Node &Node,
- std::vector<SourceRegisterClass> &Result) {
- if (!requireSequence(&Node, "register_classes"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register class"))
- return false;
- if (!checkKeys(*Element,
- {"id", "symbol", "name", "bank", "size_bits", "spill_size_bits",
- "spill_alignment_bits", "lanes", "registers", "allocation_order",
- "allocatable", "copy_cost"},
- "register class")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "register class");
- const auto Symbol = requiredScalar(*Element, "symbol", "register class");
- const auto Name = requiredScalar(*Element, "name", "register class");
- const auto Bank = requiredScalar(*Element, "bank", "register class");
- const yaml::Node *SizeNode = requiredNode(*Element, "size_bits", "register class");
- const yaml::Node *SpillSizeNode = requiredNode(*Element, "spill_size_bits", "register class");
- const yaml::Node *SpillAlignmentNode =
- requiredNode(*Element, "spill_alignment_bits", "register class");
- const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register class");
- const yaml::Node *RegistersNode = requiredNode(*Element, "registers", "register class");
- const yaml::Node *AllocationNode =
- requiredNode(*Element, "allocation_order", "register class");
- if (IDNode == nullptr || !Symbol || !Name || !Bank || SizeNode == nullptr ||
- SpillSizeNode == nullptr || SpillAlignmentNode == nullptr || LanesNode == nullptr ||
- RegistersNode == nullptr || AllocationNode == nullptr) {
- return false;
- }
- const auto ID = unsignedInteger32(*IDNode, "register class id");
- const auto Size = unsignedInteger32(*SizeNode, "register class size_bits");
- const auto SpillSize = unsignedInteger32(*SpillSizeNode, "register class spill_size_bits");
- const auto SpillAlignment =
- unsignedInteger32(*SpillAlignmentNode, "register class spill_alignment_bits");
- const auto Lanes = unsignedInteger(*LanesNode, "register class lanes");
- const auto Registers = scalarSequence(*RegistersNode, "register class registers");
- const auto AllocationOrder =
- scalarSequence(*AllocationNode, "register class allocation_order");
- if (!ID || !Size || !SpillSize || !SpillAlignment || !Lanes || !Registers ||
- !AllocationOrder) {
- return false;
- }
- SourceRegisterClass RegisterClass{
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .BankSymbol = *Bank,
- .SizeInBits = *Size,
- .SpillSizeInBits = *SpillSize,
- .SpillAlignmentInBits = *SpillAlignment,
- .Lanes = *Lanes,
- .RegisterSymbols = *Registers,
- .AllocationOrderSymbols = *AllocationOrder,
- .Range = Element->range(),
- };
- if (const yaml::Node *Allocatable = optionalNode(*Element, "allocatable")) {
- const auto Value = boolean(*Allocatable, "register class allocatable");
- if (!Value)
- return false;
- RegisterClass.Allocatable = *Value;
- }
- if (const yaml::Node *CopyCost = optionalNode(*Element, "copy_cost")) {
- const auto Value = unsignedInteger32(*CopyCost, "register class copy_cost");
- if (!Value)
- return false;
- RegisterClass.CopyCost = *Value;
- }
- Result.push_back(std::move(RegisterClass));
- }
- return true;
- }
-
- [[nodiscard]] bool parseRegisterMasks(const yaml::Node &Node,
- std::vector<SourceRegisterMask> &Result) {
- if (!requireSequence(&Node, "register_masks"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "register mask"))
- return false;
- if (!checkKeys(*Element, {"name", "preserved"}, "register mask"))
- return false;
- const auto Name = requiredScalar(*Element, "name", "register mask");
- const yaml::Node *PreservedNode = requiredNode(*Element, "preserved", "register mask");
- if (!Name || PreservedNode == nullptr)
- return false;
- const auto Preserved = scalarSequence(*PreservedNode, "register mask preserved");
- if (!Preserved)
- return false;
- Result.push_back(
- {.Name = *Name, .PreservedRegisterSymbols = *Preserved, .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] bool parseMIROperandFlagSequence(const yaml::Node &Node, std::string_view Context,
- std::vector<SourceMIROperandFlag> &Result) {
- if (!requireSequence(&Node, Context))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, Context) || !checkKeys(*Element, {"name", "value"}, Context)) {
- return false;
- }
- const auto Name = requiredScalar(*Element, "name", Context);
- const yaml::Node *ValueNode = requiredNode(*Element, "value", Context);
- if (!Name || ValueNode == nullptr)
- return false;
- const auto Value = unsignedInteger32(*ValueNode, std::string(Context) + " value");
- if (!Value)
- return false;
- Result.push_back({.Name = *Name, .Value = *Value, .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] bool parseMIROperandFlags(const yaml::Node &Node, SourceMIROperandFlags &Result) {
- if (!requireMapping(&Node, "MIR operand flags") ||
- !checkKeys(Node, {"direct_mask", "direct", "bitmask"}, "MIR operand flags")) {
- return false;
- }
- const yaml::Node *DirectMaskNode = requiredNode(Node, "direct_mask", "MIR operand flags");
- const yaml::Node *DirectNode = requiredNode(Node, "direct", "MIR operand flags");
- const yaml::Node *BitmaskNode = requiredNode(Node, "bitmask", "MIR operand flags");
- if (DirectMaskNode == nullptr || DirectNode == nullptr || BitmaskNode == nullptr)
- return false;
- const auto DirectMask = unsignedInteger32(*DirectMaskNode, "MIR operand flag direct_mask");
- if (!DirectMask ||
- !parseMIROperandFlagSequence(*DirectNode, "direct MIR operand flag", Result.Direct) ||
- !parseMIROperandFlagSequence(*BitmaskNode, "bitmask MIR operand flag", Result.Bitmask)) {
- return false;
- }
- Result.DirectMask = *DirectMask;
- Result.Range = Node.range();
- return true;
- }
-
- [[nodiscard]] bool parseNamedOperands(const yaml::Node &Node,
- std::vector<SourceNamedOperand> &Result) {
- if (!requireSequence(&Node, "named_operands"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "named operand"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name"}, "named operand"))
- return false;
- const yaml::Node *IDNode = requiredNode(*Element, "id", "named operand");
- const auto Symbol = requiredScalar(*Element, "symbol", "named operand");
- const auto Name = requiredScalar(*Element, "name", "named operand");
- if (IDNode == nullptr || !Symbol || !Name)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "named operand id");
- if (!ID)
- return false;
- Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] bool parseImmediatePredicates(const yaml::Node &Node,
- std::vector<SourceImmediatePredicate> &Result) {
- if (!requireSequence(&Node, "immediate_predicates"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "immediate predicate"))
- return false;
- if (!checkKeys(
- *Element,
- {"id", "symbol", "name", "kind", "bits", "alignment", "nonzero", "allowed_values"},
- "immediate predicate")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "immediate predicate");
- const auto Symbol = requiredScalar(*Element, "symbol", "immediate predicate");
- const auto Name = requiredScalar(*Element, "name", "immediate predicate");
- const auto Kind = requiredScalar(*Element, "kind", "immediate predicate");
- const yaml::Node *BitsNode = requiredNode(*Element, "bits", "immediate predicate");
- if (IDNode == nullptr || !Symbol || !Name || !Kind || BitsNode == nullptr)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "immediate predicate id");
- const auto Bits = unsignedInteger32(*BitsNode, "immediate predicate bits");
- if (!ID || !Bits)
- return false;
- SourceImmediatePredicate Predicate{
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .Kind = *Kind,
- .Bits = *Bits,
- .Alignment = 1,
- .NonZero = false,
- .AllowedValues = {},
- .Range = Element->range(),
- };
- if (const yaml::Node *Alignment = optionalNode(*Element, "alignment")) {
- const auto Value = unsignedInteger(*Alignment, "immediate predicate alignment");
- if (!Value)
- return false;
- Predicate.Alignment = *Value;
- }
- if (const yaml::Node *NonZero = optionalNode(*Element, "nonzero")) {
- const auto Value = boolean(*NonZero, "immediate predicate nonzero");
- if (!Value)
- return false;
- Predicate.NonZero = *Value;
- }
- if (const yaml::Node *AllowedValues = optionalNode(*Element, "allowed_values")) {
- const auto Values =
- signedIntegerSequence(*AllowedValues, "immediate predicate allowed_values");
- if (!Values || Values->empty()) {
- if (Values)
- fail(AllowedValues->range(), "immediate predicate allowed_values must be nonempty");
- return false;
- }
- Predicate.AllowedValues = *Values;
- }
- Result.push_back(std::move(Predicate));
- }
- return true;
- }
-
- [[nodiscard]] bool parseSchedulingClasses(const yaml::Node &Node,
- std::vector<SourceSchedulingClass> &Result) {
- if (!requireSequence(&Node, "scheduling_classes"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "scheduling class"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name", "micro_ops", "latency"},
- "scheduling class")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "scheduling class");
- const auto Symbol = requiredScalar(*Element, "symbol", "scheduling class");
- const auto Name = requiredScalar(*Element, "name", "scheduling class");
- const yaml::Node *MicroOpsNode = requiredNode(*Element, "micro_ops", "scheduling class");
- const yaml::Node *LatencyNode = requiredNode(*Element, "latency", "scheduling class");
- if (IDNode == nullptr || !Symbol || !Name || MicroOpsNode == nullptr ||
- LatencyNode == nullptr)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "scheduling class id");
- const auto MicroOps = unsignedInteger32(*MicroOpsNode, "scheduling class micro_ops");
- const auto Latency = unsignedInteger32(*LatencyNode, "scheduling class latency");
- if (!ID || !MicroOps || !Latency)
- return false;
- Result.push_back({
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .MicroOps = *MicroOps,
- .Latency = *Latency,
- .Range = Element->range(),
- });
- }
- return true;
- }
-
- [[nodiscard]] bool parseEncodingCategories(const yaml::Node &Node,
- std::vector<SourceEncodingCategory> &Result) {
- if (!requireSequence(&Node, "encoding_categories"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "encoding category"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name", "size_bytes"}, "encoding category"))
- return false;
- const yaml::Node *IDNode = requiredNode(*Element, "id", "encoding category");
- const auto Symbol = requiredScalar(*Element, "symbol", "encoding category");
- const auto Name = requiredScalar(*Element, "name", "encoding category");
- const yaml::Node *SizeNode = requiredNode(*Element, "size_bytes", "encoding category");
- if (IDNode == nullptr || !Symbol || !Name || SizeNode == nullptr)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "encoding category id");
- const auto Size = unsignedInteger32(*SizeNode, "encoding category size_bytes");
- if (!ID || !Size)
- return false;
- Result.push_back({
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .SizeInBytes = *Size,
- .Range = Element->range(),
- });
- }
- return true;
- }
-
- [[nodiscard]] bool parseAssemblyFormats(const yaml::Node &Node,
- std::vector<SourceAssemblyFormat> &Result) {
- if (!requireSequence(&Node, "assembly_formats"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "assembly format"))
- return false;
- if (!checkKeys(*Element, {"id", "symbol", "name"}, "assembly format"))
- return false;
- const yaml::Node *IDNode = requiredNode(*Element, "id", "assembly format");
- const auto Symbol = requiredScalar(*Element, "symbol", "assembly format");
- const auto Name = requiredScalar(*Element, "name", "assembly format");
- if (IDNode == nullptr || !Symbol || !Name)
- return false;
- const auto ID = unsignedInteger32(*IDNode, "assembly format id");
- if (!ID)
- return false;
- Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()});
- }
- return true;
- }
-
- [[nodiscard]] bool parseCallingConventions(const yaml::Node &Node,
- std::vector<SourceCallingConvention> &Result) {
- if (!requireSequence(&Node, "calling_conventions"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "calling convention"))
- return false;
- if (!checkKeys(*Element,
- {"id", "symbol", "name", "integer_register_class", "floating_register_class",
- "integer_argument_registers", "floating_argument_registers",
- "integer_return_registers", "floating_return_registers", "preserved_mask",
- "stack_alignment_bytes", "stack_argument_alignment_bytes",
- "shadow_space_bytes", "supports_varargs", "supports_tail_calls"},
- "calling convention")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "calling convention");
- const auto Symbol = requiredScalar(*Element, "symbol", "calling convention");
- const auto Name = requiredScalar(*Element, "name", "calling convention");
- const auto IntegerClass =
- requiredScalar(*Element, "integer_register_class", "calling convention");
- const auto FloatingClass =
- requiredScalar(*Element, "floating_register_class", "calling convention");
- const yaml::Node *IntegerArguments =
- requiredNode(*Element, "integer_argument_registers", "calling convention");
- const yaml::Node *FloatingArguments =
- requiredNode(*Element, "floating_argument_registers", "calling convention");
- const yaml::Node *IntegerReturns =
- requiredNode(*Element, "integer_return_registers", "calling convention");
- const yaml::Node *FloatingReturns =
- requiredNode(*Element, "floating_return_registers", "calling convention");
- const auto PreservedMask = requiredScalar(*Element, "preserved_mask", "calling convention");
- const yaml::Node *StackAlignment =
- requiredNode(*Element, "stack_alignment_bytes", "calling convention");
- const yaml::Node *StackArgumentAlignment =
- requiredNode(*Element, "stack_argument_alignment_bytes", "calling convention");
- const yaml::Node *ShadowSpace =
- requiredNode(*Element, "shadow_space_bytes", "calling convention");
- const yaml::Node *SupportsVarArgs =
- requiredNode(*Element, "supports_varargs", "calling convention");
- const yaml::Node *SupportsTailCalls =
- requiredNode(*Element, "supports_tail_calls", "calling convention");
- if (IDNode == nullptr || !Symbol || !Name || !IntegerClass || !FloatingClass ||
- IntegerArguments == nullptr || FloatingArguments == nullptr ||
- IntegerReturns == nullptr || FloatingReturns == nullptr || !PreservedMask ||
- StackAlignment == nullptr || StackArgumentAlignment == nullptr ||
- ShadowSpace == nullptr || SupportsVarArgs == nullptr || SupportsTailCalls == nullptr) {
- return false;
- }
- const auto ID = unsignedInteger32(*IDNode, "calling convention id");
- const auto IntegerArgumentList =
- scalarSequence(*IntegerArguments, "calling convention integer arguments");
- const auto FloatingArgumentList =
- scalarSequence(*FloatingArguments, "calling convention floating arguments");
- const auto IntegerReturnList =
- scalarSequence(*IntegerReturns, "calling convention integer returns");
- const auto FloatingReturnList =
- scalarSequence(*FloatingReturns, "calling convention floating returns");
- const auto StackAlignmentValue =
- unsignedInteger32(*StackAlignment, "calling convention stack alignment");
- const auto StackArgumentAlignmentValue =
- unsignedInteger32(*StackArgumentAlignment, "calling convention stack argument alignment");
- const auto ShadowSpaceValue =
- unsignedInteger32(*ShadowSpace, "calling convention shadow space");
- const auto SupportsVarArgsValue = boolean(*SupportsVarArgs, "calling convention varargs");
- const auto SupportsTailCallsValue =
- boolean(*SupportsTailCalls, "calling convention tail calls");
- if (!ID || !IntegerArgumentList || !FloatingArgumentList || !IntegerReturnList ||
- !FloatingReturnList || !StackAlignmentValue || !StackArgumentAlignmentValue ||
- !ShadowSpaceValue || !SupportsVarArgsValue || !SupportsTailCallsValue) {
- return false;
- }
- Result.push_back({
- .ID = *ID,
- .Symbol = *Symbol,
- .Name = *Name,
- .IntegerRegisterClassSymbol = *IntegerClass,
- .FloatingRegisterClassSymbol = *FloatingClass,
- .IntegerArgumentRegisterSymbols = *IntegerArgumentList,
- .FloatingArgumentRegisterSymbols = *FloatingArgumentList,
- .IntegerReturnRegisterSymbols = *IntegerReturnList,
- .FloatingReturnRegisterSymbols = *FloatingReturnList,
- .PreservedMaskName = *PreservedMask,
- .StackAlignmentBytes = *StackAlignmentValue,
- .StackArgumentAlignmentBytes = *StackArgumentAlignmentValue,
- .ShadowSpaceBytes = *ShadowSpaceValue,
- .SupportsVarArgs = *SupportsVarArgsValue,
- .SupportsTailCalls = *SupportsTailCallsValue,
- .Range = Element->range(),
- });
- }
- return true;
- }
-
- [[nodiscard]] std::optional<SourceOperand> parseOperand(const yaml::Node &Node) {
- if (!requireMapping(&Node, "instruction operand"))
- return std::nullopt;
- if (!checkKeys(Node,
- {"kinds", "role", "register_class", "register_bank", "immediate_predicate",
- "memory_role", "default_tied_operand", "early_clobber", "optional",
- "variadic_group", "allow_subreg", "name", "allowed_target_flags"},
- "instruction operand")) {
- return std::nullopt;
- }
- const yaml::Node *KindsNode = requiredNode(Node, "kinds", "instruction operand");
- if (KindsNode == nullptr)
- return std::nullopt;
- const auto Kinds = scalarSequence(*KindsNode, "instruction operand kinds");
- if (!Kinds)
- return std::nullopt;
-
- SourceOperand Result;
- Result.Kinds = *Kinds;
- Result.Range = Node.range();
- if (const yaml::Node *Role = optionalNode(Node, "role")) {
- const auto Value = scalar(*Role, "instruction operand role");
- if (!Value)
- return std::nullopt;
- Result.RegisterRole = *Value;
- }
- if (const yaml::Node *RegisterClass = optionalNode(Node, "register_class")) {
- const auto Value = scalar(*RegisterClass, "instruction operand register_class");
- if (!Value)
- return std::nullopt;
- Result.RegisterClassSymbol = *Value;
- }
- if (const yaml::Node *RegisterBank = optionalNode(Node, "register_bank")) {
- const auto Value = scalar(*RegisterBank, "instruction operand register_bank");
- if (!Value)
- return std::nullopt;
- Result.RegisterBankSymbol = *Value;
- }
- if (const yaml::Node *Predicate = optionalNode(Node, "immediate_predicate")) {
- const auto Value = scalar(*Predicate, "instruction operand immediate_predicate");
- if (!Value)
- return std::nullopt;
- Result.ImmediatePredicateSymbol = *Value;
- }
- if (const yaml::Node *MemoryRole = optionalNode(Node, "memory_role")) {
- const auto Value = scalar(*MemoryRole, "instruction operand memory_role");
- if (!Value)
- return std::nullopt;
- Result.MemoryRole = *Value;
- }
- if (const yaml::Node *Tie = optionalNode(Node, "default_tied_operand")) {
- const auto Value = unsignedInteger32(*Tie, "instruction operand default_tied_operand");
- if (!Value)
- return std::nullopt;
- Result.DefaultTiedOperand = Value;
- }
- if (const yaml::Node *EarlyClobber = optionalNode(Node, "early_clobber")) {
- const auto Value = boolean(*EarlyClobber, "instruction operand early_clobber");
- if (!Value)
- return std::nullopt;
- Result.EarlyClobber = *Value;
- }
- if (const yaml::Node *Optional = optionalNode(Node, "optional")) {
- const auto Value = boolean(*Optional, "instruction operand optional");
- if (!Value)
- return std::nullopt;
- Result.Optional = *Value;
- }
- if (const yaml::Node *VariadicGroup = optionalNode(Node, "variadic_group")) {
- const auto Value = boolean(*VariadicGroup, "instruction operand variadic_group");
- if (!Value)
- return std::nullopt;
- Result.VariadicGroup = *Value;
- }
- if (const yaml::Node *AllowSubReg = optionalNode(Node, "allow_subreg")) {
- const auto Value = boolean(*AllowSubReg, "instruction operand allow_subreg");
- if (!Value)
- return std::nullopt;
- Result.AllowSubReg = *Value;
- }
- if (const yaml::Node *Name = optionalNode(Node, "name")) {
- const auto Value = scalar(*Name, "instruction operand name");
- if (!Value)
- return std::nullopt;
- Result.NameSymbol = *Value;
- }
- if (const yaml::Node *TargetFlags = optionalNode(Node, "allowed_target_flags")) {
- const auto Value =
- unsignedInteger32(*TargetFlags, "instruction operand allowed_target_flags");
- if (!Value)
- return std::nullopt;
- Result.AllowedTargetFlags = *Value;
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<std::vector<SourceOperand>> parseOperands(const yaml::Node &Node) {
- if (!requireSequence(&Node, "instruction operands"))
- return std::nullopt;
- std::vector<SourceOperand> Result;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (Element == nullptr)
- return std::nullopt;
- const auto Operand = parseOperand(*Element);
- if (!Operand)
- return std::nullopt;
- Result.push_back(*Operand);
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<SourceImplicitRegister>
- parseImplicitRegister(const yaml::Node &Node, std::string_view Context) {
- if (!requireMapping(&Node, Context))
- return std::nullopt;
- if (!checkKeys(Node, {"register", "early_clobber", "renamable"}, Context))
- return std::nullopt;
- const auto Register = requiredScalar(Node, "register", Context);
- if (!Register)
- return std::nullopt;
- SourceImplicitRegister Result{
- .RegisterSymbol = *Register,
- .Range = Node.range(),
- };
- if (const yaml::Node *EarlyClobber = optionalNode(Node, "early_clobber")) {
- const auto Value = boolean(*EarlyClobber, std::string(Context) + " early_clobber");
- if (!Value)
- return std::nullopt;
- Result.EarlyClobber = *Value;
- }
- if (const yaml::Node *Renamable = optionalNode(Node, "renamable")) {
- const auto Value = boolean(*Renamable, std::string(Context) + " renamable");
- if (!Value)
- return std::nullopt;
- Result.Renamable = *Value;
- }
- return Result;
- }
-
- [[nodiscard]] std::optional<std::vector<SourceImplicitRegister>>
- parseImplicitRegisters(const yaml::Node &Node, std::string_view Context) {
- if (!requireSequence(&Node, Context))
- return std::nullopt;
- std::vector<SourceImplicitRegister> Result;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (Element == nullptr)
- return std::nullopt;
- const auto Register = parseImplicitRegister(*Element, Context);
- if (!Register)
- return std::nullopt;
- Result.push_back(*Register);
- }
- return Result;
- }
-
- [[nodiscard]] bool parseInstructions(const yaml::Node &Node,
- std::vector<SourceInstruction> &Result) {
- if (!requireSequence(&Node, "instructions"))
- return false;
- for (const yaml::NodeID ElementID : Node.sequence()) {
- const yaml::Node *Element = Input.node(ElementID);
- if (!requireMapping(Element, "instruction"))
- return false;
- if (!checkKeys(*Element,
- {"id", "symbol", "name", "assembly_mnemonic", "encoding", "operands",
- "num_explicit_defs", "variadic", "implicit_defs", "implicit_uses",
- "allow_extra_implicit_operands", "flags", "scheduling_class",
- "encoding_category", "assembly_format", "requires_memory_operands", "owner",
- "deadline"},
- "instruction")) {
- return false;
- }
- const yaml::Node *IDNode = requiredNode(*Element, "id", "instruction");
- const auto Symbol = requiredScalar(*Element, "symbol", "instruction");
- const auto Name = requiredScalar(*Element, "name", "instruction");
- const yaml::Node *OperandsNode = requiredNode(*Element, "operands", "instruction");
- const yaml::Node *DefsNode = requiredNode(*Element, "num_explicit_defs", "instruction");
- const yaml::Node *FlagsNode = requiredNode(*Element, "flags", "instruction");
- if (IDNode == nullptr || !Symbol || !Name || OperandsNode == nullptr || DefsNode == nullptr ||
- FlagsNode == nullptr) {
- return false;
- }
- const auto ID = unsignedInteger32(*IDNode, "instruction id");
- const auto Defs = unsignedInteger32(*DefsNode, "instruction num_explicit_defs");
- const auto Operands = parseOperands(*OperandsNode);
- const auto Flags = scalarSequence(*FlagsNode, "instruction flags");
- if (!ID || !Defs || !Operands || !Flags)
- return false;
- SourceInstruction Instruction;
- Instruction.ID = *ID;
- Instruction.Symbol = *Symbol;
- Instruction.Name = *Name;
- Instruction.Operands = *Operands;
- Instruction.NumExplicitDefs = *Defs;
- Instruction.Flags = *Flags;
- Instruction.Range = Element->range();
- if (const yaml::Node *Mnemonic = optionalNode(*Element, "assembly_mnemonic")) {
- const auto Value = scalar(*Mnemonic, "instruction assembly_mnemonic");
- if (!Value)
- return false;
- Instruction.AssemblyMnemonic = *Value;
- }
- if (const yaml::Node *Encoding = optionalNode(*Element, "encoding")) {
- const auto Value = unsignedInteger32(*Encoding, "instruction encoding");
- if (!Value)
- return false;
- Instruction.BinaryEncoding = Value;
- }
- if (const yaml::Node *SchedulingClass = optionalNode(*Element, "scheduling_class")) {
- const auto Value = scalar(*SchedulingClass, "instruction scheduling_class");
- if (!Value)
- return false;
- Instruction.SchedulingClassSymbol = *Value;
- }
- if (const yaml::Node *EncodingCategory = optionalNode(*Element, "encoding_category")) {
- const auto Value = scalar(*EncodingCategory, "instruction encoding_category");
- if (!Value)
- return false;
- Instruction.EncodingCategorySymbol = *Value;
- }
- if (const yaml::Node *AssemblyFormat = optionalNode(*Element, "assembly_format")) {
- const auto Value = scalar(*AssemblyFormat, "instruction assembly_format");
- if (!Value)
- return false;
- Instruction.AssemblyFormatSymbol = *Value;
- }
- if (const yaml::Node *Variadic = optionalNode(*Element, "variadic")) {
- const auto Value = boolean(*Variadic, "instruction variadic");
- if (!Value)
- return false;
- Instruction.Variadic = *Value;
- }
- if (const yaml::Node *ImplicitDefs = optionalNode(*Element, "implicit_defs")) {
- const auto Values = parseImplicitRegisters(*ImplicitDefs, "instruction implicit_defs");
- if (!Values)
- return false;
- Instruction.ImplicitDefs = *Values;
- }
- if (const yaml::Node *ImplicitUses = optionalNode(*Element, "implicit_uses")) {
- const auto Values = parseImplicitRegisters(*ImplicitUses, "instruction implicit_uses");
- if (!Values)
- return false;
- Instruction.ImplicitUses = *Values;
- }
- if (const yaml::Node *AllowExtra = optionalNode(*Element, "allow_extra_implicit_operands")) {
- const auto Value = boolean(*AllowExtra, "instruction allow_extra_implicit_operands");
- if (!Value)
- return false;
- Instruction.AllowExtraImplicitOperands = *Value;
- }
- if (const yaml::Node *MemoryOperands = optionalNode(*Element, "requires_memory_operands")) {
- const auto Value = boolean(*MemoryOperands, "instruction requires_memory_operands");
- if (!Value)
- return false;
- Instruction.RequiresMemoryOperands = *Value;
- }
- if (const yaml::Node *Owner = optionalNode(*Element, "owner")) {
- const auto Value = scalar(*Owner, "instruction owner");
- if (!Value)
- return false;
- Instruction.Owner = *Value;
- }
- if (const yaml::Node *Deadline = optionalNode(*Element, "deadline")) {
- const auto Value = scalar(*Deadline, "instruction deadline");
- if (!Value)
- return false;
- Instruction.Deadline = *Value;
- }
- Result.push_back(std::move(Instruction));
- }
- return true;
- }
-
- [[nodiscard]] const yaml::Node *optionalNode(const yaml::Node &Mapping,
- std::string_view Key) const noexcept {
- const std::optional<yaml::NodeID> Value = Mapping.mappingValue(Key);
- return Value ? Input.node(*Value) : nullptr;
- }
-
- const yaml::Stream &Input;
- std::vector<TextDiagnostic> Diagnostics;
-};
-
-[[nodiscard]] std::optional<std::string_view>
-operandKindEnumerator(std::string_view Value) noexcept {
- static constexpr std::array<std::pair<std::string_view, std::string_view>, 22> Kinds = {{
- {"register", "Register"},
- {"immediate", "Immediate"},
- {"integer_immediate", "IntegerImmediate"},
- {"floating_immediate", "FloatingImmediate"},
- {"basic_block", "BasicBlock"},
- {"frame_index", "FrameIndex"},
- {"constant_pool_index", "ConstantPoolIndex"},
- {"jump_table_index", "JumpTableIndex"},
- {"global_symbol", "GlobalSymbol"},
- {"external_symbol", "ExternalSymbol"},
- {"block_address", "BlockAddress"},
- {"register_mask", "RegisterMask"},
- {"live_out_register_mask", "LiveOutRegisterMask"},
- {"metadata", "Metadata"},
- {"target_index", "TargetIndex"},
- {"mc_symbol", "MCSymbol"},
- {"cfi_index", "CFIIndex"},
- {"intrinsic", "Intrinsic"},
- {"predicate", "Predicate"},
- {"shuffle_mask", "ShuffleMask"},
- {"debug_instr_ref", "DebugInstrRef"},
- {"lane_mask", "LaneMask"},
- }};
- const auto *const Position = std::ranges::find(Kinds, Value, &decltype(Kinds)::value_type::first);
- if (Position == Kinds.end())
- return std::nullopt;
- return Position->second;
-}
-
-[[nodiscard]] std::optional<std::string_view>
-registerRoleEnumerator(std::string_view Value) noexcept {
- if (Value == "none")
- return "None";
- if (Value == "def")
- return "Def";
- if (Value == "use")
- return "Use";
- if (Value == "def_use")
- return "DefUse";
- return std::nullopt;
-}
-
-[[nodiscard]] std::optional<std::string_view>
-memoryRoleEnumerator(std::string_view Value) noexcept {
- if (Value == "none")
- return "None";
- if (Value == "address")
- return "Address";
- if (Value == "value")
- return "Value";
- if (Value == "size")
- return "Size";
- if (Value == "ordering")
- return "Ordering";
- return std::nullopt;
-}
-
-[[nodiscard]] std::optional<std::string_view>
-instructionFlagEnumerator(std::string_view Value) noexcept {
- static constexpr std::array<std::pair<std::string_view, std::string_view>, 23> Flags = {{
- {"terminator", "Terminator"},
- {"branch", "Branch"},
- {"indirect_branch", "IndirectBranch"},
- {"call", "Call"},
- {"return", "Return"},
- {"barrier", "Barrier"},
- {"may_load", "MayLoad"},
- {"may_store", "MayStore"},
- {"has_side_effects", "HasSideEffects"},
- {"pseudo", "Pseudo"},
- {"meta", "Meta"},
- {"allowed_pre_legalizer", "AllowedPreLegalizer"},
- {"produces_code", "ProducesCode"},
- {"commutable", "Commutable"},
- {"rematerializable", "Rematerializable"},
- {"predicable", "Predicable"},
- {"not_duplicable", "NotDuplicable"},
- {"convergent", "Convergent"},
- {"unmodeled_side_effects", "UnmodeledSideEffects"},
- {"phi", "Phi"},
- {"fake_use", "FakeUse"},
- {"generic", "Generic"},
- {"terminal_emission", "TerminalEmission"},
- }};
- const auto *const Position = std::ranges::find(Flags, Value, &decltype(Flags)::value_type::first);
- if (Position == Flags.end())
- return std::nullopt;
- return Position->second;
-}
-
-[[nodiscard]] std::optional<std::string_view>
-pseudoOwnerEnumerator(std::string_view Value) noexcept {
- static constexpr std::array<std::pair<std::string_view, std::string_view>, 12> Owners = {{
- {"none", "None"},
- {"legalizer", "Legalizer"},
- {"register_bank_select", "RegisterBankSelect"},
- {"instruction_select", "InstructionSelect"},
- {"phi_elimination", "PhiElimination"},
- {"two_address_rewrite", "TwoAddressRewrite"},
- {"register_allocator", "RegisterAllocator"},
- {"pre_pei_expansion", "PrePEIExpansion"},
- {"prologue_epilogue_insertion", "PrologueEpilogueInsertion"},
- {"post_ra_expansion", "PostRAExpansion"},
- {"finalization", "Finalization"},
- {"target_late_expansion", "TargetLateExpansion"},
- }};
- if (Value == "asm_printer")
- return "AsmPrinter";
- const auto *const Position =
- std::ranges::find(Owners, Value, &decltype(Owners)::value_type::first);
- if (Position == Owners.end())
- return std::nullopt;
- return Position->second;
-}
-
-[[nodiscard]] std::optional<std::string_view>
-pipelineStageEnumerator(std::string_view Value) noexcept {
- static constexpr std::array<std::pair<std::string_view, std::string_view>, 10> Stages = {{
- {"machine_ir_input", "MachineIRInput"},
- {"legalized", "Legalized"},
- {"reg_bank_selected", "RegBankSelected"},
- {"selected", "Selected"},
- {"ssa_eliminated", "SSAEliminated"},
- {"tied_ops_rewritten", "TiedOpsRewritten"},
- {"register_allocated", "RegisterAllocated"},
- {"frame_finalized", "FrameFinalized"},
- {"post_ra_finalized", "PostRAFinalized"},
- {"emission", "Emission"},
- }};
- const auto *const Position =
- std::ranges::find(Stages, Value, &decltype(Stages)::value_type::first);
- if (Position == Stages.end())
- return std::nullopt;
- return Position->second;
-}
-
-[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept {
- return Value != 0 && (Value & (Value - 1U)) == 0;
-}
-
-class ModelNormalizer final {
-public:
- ModelNormalizer(const DescriptionAST &Input, std::string SourceName)
- : Input(Input), SourceName(std::move(SourceName)) {}
-
- [[nodiscard]] std::optional<NormalizedTargetModel> normalize() {
- if (!validateTarget())
- return std::nullopt;
-
- NormalizedTargetModel Result;
- Result.Target = Input.Target;
- Result.RegisterUnits = Input.RegisterUnits;
- Result.MIROperandFlags = Input.MIROperandFlags;
- if (!normalizeRegisterUnits() || !normalizeRegisters(Result) ||
- !normalizeRegisterBanks(Result) || !normalizeRegisterClasses(Result) ||
- !normalizeRegisterMasks(Result) || !normalizeMIROperandFlags(Result) ||
- !normalizeCallingConventions(Result) || !normalizeNamedOperands(Result) ||
- !normalizeImmediatePredicates(Result) || !normalizeSchedulingClasses(Result) ||
- !normalizeEncodingCategories(Result) || !normalizeAssemblyFormats(Result) ||
- !normalizeInstructions(Result)) {
- return std::nullopt;
- }
- std::ranges::sort(Result.RegisterUnits, {}, &SourceRegisterUnit::ID);
- std::ranges::sort(Result.Registers, {}, &NormalizedRegister::ID);
- std::ranges::sort(Result.RegisterBanks, {}, &NormalizedRegisterBank::ID);
- std::ranges::sort(Result.RegisterClasses, {}, &NormalizedRegisterClass::ID);
- std::ranges::sort(Result.CallingConventions, {}, &NormalizedCallingConvention::ID);
- std::ranges::sort(Result.NamedOperands, {}, &NormalizedNamedOperand::ID);
- std::ranges::sort(Result.ImmediatePredicates, {}, &NormalizedImmediatePredicate::ID);
- std::ranges::sort(Result.SchedulingClasses, {}, &NormalizedSchedulingClass::ID);
- std::ranges::sort(Result.EncodingCategories, {}, &NormalizedEncodingCategory::ID);
- std::ranges::sort(Result.AssemblyFormats, {}, &NormalizedAssemblyFormat::ID);
- return Result;
- }
-
- [[nodiscard]] std::vector<TextDiagnostic> takeDiagnostics() {
- return std::move(Diagnostics);
- }
-
-private:
- void fail(TextRange Range, std::string Message) {
- Diagnostics.push_back({
- .Severity = DiagnosticSeverity::Error,
- .Category = "target-description",
- .Message = std::move(Message),
- .SourceName = SourceName,
- .Range = Range,
- });
- }
-
- [[nodiscard]] bool validateTarget() {
- if (!isIdentifier(Input.Target.Namespace)) {
- fail(Input.Target.Range, "target namespace must be a C++ identifier");
- return false;
- }
- if (Input.Target.Name.empty() || Input.Target.Triple.empty() || Input.Target.CPU.empty() ||
- Input.Target.ABI.empty() || Input.Target.DataLayout.empty() ||
- Input.Target.Features.empty()) {
- fail(Input.Target.Range, "target metadata fields and feature list must be nonempty");
- return false;
- }
- std::unordered_set<std::string> Features;
- for (const std::string &Feature : Input.Target.Features) {
- if (Feature.empty() || !Features.insert(Feature).second) {
- fail(Input.Target.Range, "target feature names must be nonempty and unique");
- return false;
- }
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeRegisterUnits() {
- if (Input.RegisterUnits.empty()) {
- fail(Input.Target.Range, "target description requires at least one register unit");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceRegisterUnit &Unit : Input.RegisterUnits) {
- if (Unit.ID == 0 || !isIdentifier(Unit.Symbol) || Unit.Name.empty() ||
- !IDs.insert(Unit.ID).second || !Symbols.insert(Unit.Symbol).second ||
- !Names.insert(lowerASCII(Unit.Name)).second) {
- fail(Unit.Range,
- "register units require nonzero unique IDs, unique symbols, and unique names");
- return false;
- }
- UnitBySymbol.emplace(Unit.Symbol, Unit.ID);
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeRegisters(NormalizedTargetModel &Result) {
- if (Input.Registers.empty()) {
- fail(Input.Target.Range, "target description requires at least one physical register");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceRegister &Register : Input.Registers) {
- if (Register.ID == 0 || !isIdentifier(Register.Symbol) || Register.Name.empty() ||
- !IDs.insert(Register.ID).second || !Symbols.insert(Register.Symbol).second ||
- !Names.insert(lowerASCII(Register.Name)).second || Register.Units.empty()) {
- fail(Register.Range,
- "registers require nonzero unique IDs, symbols, canonical names, and units");
- return false;
- }
- RegisterBySymbol.emplace(Register.Symbol, Register.ID);
- NormalizedRegister Normalized{
- .ID = Register.ID,
- .Symbol = Register.Symbol,
- .Name = Register.Name,
- .Encoding = Register.Encoding,
- .DwarfRegister = Register.DwarfRegister,
- .Units = {},
- .IsConstant = Register.IsConstant,
- .IsArtificial = Register.IsArtificial,
- .IsReserved = Register.IsReserved,
- .AlternativeNames = Register.AlternativeNames,
- };
- std::unordered_set<std::uint32_t> Units;
- for (const SourceRegisterUnitLane &Unit : Register.Units) {
- const auto Position = UnitBySymbol.find(Unit.UnitSymbol);
- if (Position == UnitBySymbol.end() || Unit.Lanes == 0 ||
- !Units.insert(Position->second).second) {
- fail(Unit.Range,
- "register units must name distinct declared units with nonzero lane masks");
- return false;
- }
- Normalized.Units.push_back({.UnitID = Position->second, .Lanes = Unit.Lanes});
- }
- for (const std::string &Alias : Register.AlternativeNames) {
- if (Alias.empty() || !Names.insert(lowerASCII(Alias)).second) {
- fail(Register.Range,
- "register ABI aliases must be nonempty and unique across all register names");
- return false;
- }
- }
- Result.Registers.push_back(std::move(Normalized));
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeRegisterBanks(NormalizedTargetModel &Result) {
- if (Input.RegisterBanks.empty()) {
- fail(Input.Target.Range, "target description requires at least one register bank");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceRegisterBank &Bank : Input.RegisterBanks) {
- if (Bank.ID == 0 || !isIdentifier(Bank.Symbol) || Bank.Name.empty() ||
- Bank.MaximumSizeInBits == 0 || !IDs.insert(Bank.ID).second ||
- !Symbols.insert(Bank.Symbol).second || !Names.insert(lowerASCII(Bank.Name)).second) {
- fail(Bank.Range,
- "register banks require nonzero unique IDs, symbols, names, and maximum sizes");
- return false;
- }
- BankBySymbol.emplace(Bank.Symbol, Bank.ID);
- Result.RegisterBanks.push_back({
- .ID = Bank.ID,
- .Symbol = Bank.Symbol,
- .Name = Bank.Name,
- .MaximumSizeInBits = Bank.MaximumSizeInBits,
- .RegisterClasses = {},
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeRegisterClasses(NormalizedTargetModel &Result) {
- if (Input.RegisterClasses.empty()) {
- fail(Input.Target.Range, "target description requires at least one register class");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceRegisterClass &Class : Input.RegisterClasses) {
- const auto Bank = BankBySymbol.find(Class.BankSymbol);
- if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() ||
- Bank == BankBySymbol.end() || Class.SizeInBits == 0 || Class.SpillSizeInBits == 0 ||
- Class.SpillAlignmentInBits == 0 || Class.Lanes == 0 || Class.RegisterSymbols.empty() ||
- !IDs.insert(Class.ID).second || !Symbols.insert(Class.Symbol).second ||
- !Names.insert(lowerASCII(Class.Name)).second) {
- fail(Class.Range, "register classes require unique IDs, symbols, names, a declared bank, "
- "sizes, lanes, and members");
- return false;
- }
- ClassBySymbol.emplace(Class.Symbol, Class.ID);
- NormalizedRegisterClass Normalized{
- .ID = Class.ID,
- .Symbol = Class.Symbol,
- .Name = Class.Name,
- .BankID = Bank->second,
- .SizeInBits = Class.SizeInBits,
- .SpillSizeInBits = Class.SpillSizeInBits,
- .SpillAlignmentInBits = Class.SpillAlignmentInBits,
- .Lanes = Class.Lanes,
- .Registers = {},
- .AllocationOrder = {},
- .Allocatable = Class.Allocatable,
- .CopyCost = Class.CopyCost,
- };
- std::unordered_set<std::uint32_t> Members;
- for (const std::string &Symbol : Class.RegisterSymbols) {
- const auto Register = RegisterBySymbol.find(Symbol);
- if (Register == RegisterBySymbol.end() || !Members.insert(Register->second).second) {
- fail(Class.Range, "register class members must reference distinct declared registers");
- return false;
- }
- Normalized.Registers.push_back(Register->second);
- }
- std::unordered_set<std::uint32_t> Allocation;
- for (const std::string &Symbol : Class.AllocationOrderSymbols) {
- const auto Register = RegisterBySymbol.find(Symbol);
- if (Register == RegisterBySymbol.end() || !Members.contains(Register->second) ||
- !Allocation.insert(Register->second).second) {
- fail(Class.Range,
- "register class allocation order must contain distinct members of the same class");
- return false;
- }
- Normalized.AllocationOrder.push_back(Register->second);
- }
- if ((Normalized.Allocatable && Normalized.AllocationOrder.empty()) ||
- (!Normalized.Allocatable && !Normalized.AllocationOrder.empty())) {
- fail(Class.Range, "allocatable register classes require an allocation order and "
- "nonallocatable classes forbid one");
- return false;
- }
- Result.RegisterClasses.push_back(std::move(Normalized));
- }
- for (NormalizedRegisterBank &Bank : Result.RegisterBanks) {
- for (const NormalizedRegisterClass &Class : Result.RegisterClasses) {
- if (Class.BankID == Bank.ID)
- Bank.RegisterClasses.push_back(Class.ID);
- }
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeRegisterMasks(NormalizedTargetModel &Result) {
- std::unordered_set<std::string> Names;
- for (const SourceRegisterMask &Mask : Input.RegisterMasks) {
- if (Mask.Name.empty() || !Names.insert(Mask.Name).second) {
- fail(Mask.Range, "register mask names must be nonempty and unique");
- return false;
- }
- NormalizedRegisterMask Normalized{.Name = Mask.Name, .PreservedRegisters = {}};
- std::unordered_set<std::uint32_t> Preserved;
- for (const std::string &Symbol : Mask.PreservedRegisterSymbols) {
- const auto Register = RegisterBySymbol.find(Symbol);
- if (Register == RegisterBySymbol.end() || !Preserved.insert(Register->second).second) {
- fail(Mask.Range, "register mask entries must name distinct declared registers");
- return false;
- }
- Normalized.PreservedRegisters.push_back(Register->second);
- }
- Result.RegisterMasks.push_back(std::move(Normalized));
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeMIROperandFlags(NormalizedTargetModel &Result) {
- const SourceMIROperandFlags &Flags = Result.MIROperandFlags;
- if (Flags.Direct.empty() != (Flags.DirectMask == 0)) {
- fail(Flags.Range, "direct MIR operand flags and their direct mask must be present together");
- return false;
- }
-
- std::unordered_set<std::string> Names;
- std::unordered_set<std::uint32_t> DirectValues;
- for (const SourceMIROperandFlag &Flag : Flags.Direct) {
- if (Flag.Name.empty() || Flag.Value == 0 || (Flag.Value & ~Flags.DirectMask) != 0 ||
- !Names.insert(Flag.Name).second || !DirectValues.insert(Flag.Value).second) {
- fail(Flag.Range,
- "direct MIR operand flags require unique names and nonzero values inside the mask");
- return false;
- }
- }
-
- std::uint32_t BitmaskValues = 0;
- for (const SourceMIROperandFlag &Flag : Flags.Bitmask) {
- if (Flag.Name.empty() || !std::has_single_bit(Flag.Value) ||
- (Flag.Value & Flags.DirectMask) != 0 || !Names.insert(Flag.Name).second ||
- (BitmaskValues & Flag.Value) != 0) {
- fail(Flag.Range,
- "bitmask MIR operand flags require unique names and distinct bits outside the "
- "direct mask");
- return false;
- }
- BitmaskValues |= Flag.Value;
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeCallingConventions(NormalizedTargetModel &Result) {
- if (Input.CallingConventions.empty()) {
- fail(Input.Target.Range, "target description requires at least one calling convention");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceCallingConvention &Convention : Input.CallingConventions) {
- const auto IntegerClass = ClassBySymbol.find(Convention.IntegerRegisterClassSymbol);
- const auto FloatingClass = ClassBySymbol.find(Convention.FloatingRegisterClassSymbol);
- const auto PreservedMask = std::ranges::find(
- Result.RegisterMasks, Convention.PreservedMaskName, &NormalizedRegisterMask::Name);
- if (Convention.ID == 0 || !isIdentifier(Convention.Symbol) || Convention.Name.empty() ||
- IntegerClass == ClassBySymbol.end() || FloatingClass == ClassBySymbol.end() ||
- PreservedMask == Result.RegisterMasks.end() || Convention.StackAlignmentBytes == 0 ||
- Convention.StackArgumentAlignmentBytes == 0 ||
- !isPowerOfTwo(Convention.StackAlignmentBytes) ||
- !isPowerOfTwo(Convention.StackArgumentAlignmentBytes) ||
- Convention.StackAlignmentBytes % Convention.StackArgumentAlignmentBytes != 0 ||
- Convention.ShadowSpaceBytes % Convention.StackArgumentAlignmentBytes != 0 ||
- !IDs.insert(Convention.ID).second || !Symbols.insert(Convention.Symbol).second ||
- !Names.insert(Convention.Name).second) {
- fail(
- Convention.Range,
- "calling conventions require unique IDs, symbols, names, declared register classes and "
- "preserved masks, compatible power-of-two stack alignments, and aligned shadow space");
- return false;
- }
- const auto IntegerClassDescription = std::ranges::find(
- Result.RegisterClasses, IntegerClass->second, &NormalizedRegisterClass::ID);
- const auto FloatingClassDescription = std::ranges::find(
- Result.RegisterClasses, FloatingClass->second, &NormalizedRegisterClass::ID);
- if (IntegerClassDescription == Result.RegisterClasses.end() ||
- FloatingClassDescription == Result.RegisterClasses.end()) {
- fail(Convention.Range, "calling convention register classes must resolve");
- return false;
- }
- const auto NormalizeRegisterList = [this,
- &Convention](const std::vector<std::string> &Symbols,
- const NormalizedRegisterClass &RegisterClass,
- std::vector<std::uint32_t> &Output) {
- std::unordered_set<std::uint32_t> Registers;
- for (const std::string &Symbol : Symbols) {
- const auto Register = RegisterBySymbol.find(Symbol);
- if (Register == RegisterBySymbol.end() ||
- !std::ranges::contains(RegisterClass.Registers, Register->second) ||
- !Registers.insert(Register->second).second) {
- fail(Convention.Range,
- "calling convention register lists must contain distinct members of their class");
- return false;
- }
- Output.push_back(Register->second);
- }
- return true;
- };
- NormalizedCallingConvention Normalized{
- .ID = Convention.ID,
- .Symbol = Convention.Symbol,
- .Name = Convention.Name,
- .IntegerRegisterClass = IntegerClass->second,
- .FloatingRegisterClass = FloatingClass->second,
- .IntegerArgumentRegisters = {},
- .FloatingArgumentRegisters = {},
- .IntegerReturnRegisters = {},
- .FloatingReturnRegisters = {},
- .PreservedMaskName = Convention.PreservedMaskName,
- .StackAlignmentBytes = Convention.StackAlignmentBytes,
- .StackArgumentAlignmentBytes = Convention.StackArgumentAlignmentBytes,
- .ShadowSpaceBytes = Convention.ShadowSpaceBytes,
- .SupportsVarArgs = Convention.SupportsVarArgs,
- .SupportsTailCalls = Convention.SupportsTailCalls,
- };
- if (!NormalizeRegisterList(Convention.IntegerArgumentRegisterSymbols,
- *IntegerClassDescription, Normalized.IntegerArgumentRegisters) ||
- !NormalizeRegisterList(Convention.FloatingArgumentRegisterSymbols,
- *FloatingClassDescription, Normalized.FloatingArgumentRegisters) ||
- !NormalizeRegisterList(Convention.IntegerReturnRegisterSymbols, *IntegerClassDescription,
- Normalized.IntegerReturnRegisters) ||
- !NormalizeRegisterList(Convention.FloatingReturnRegisterSymbols,
- *FloatingClassDescription, Normalized.FloatingReturnRegisters)) {
- return false;
- }
- CallingConventionBySymbol.emplace(Convention.Symbol, Convention.ID);
- Result.CallingConventions.push_back(std::move(Normalized));
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeNamedOperands(NormalizedTargetModel &Result) {
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceNamedOperand &Operand : Input.NamedOperands) {
- if (Operand.ID == 0 || !isIdentifier(Operand.Symbol) || Operand.Name.empty() ||
- !IDs.insert(Operand.ID).second || !Symbols.insert(Operand.Symbol).second ||
- !Names.insert(Operand.Name).second) {
- fail(Operand.Range, "named operands require nonzero unique IDs, symbols, and names");
- return false;
- }
- NamedOperandBySymbol.emplace(Operand.Symbol, Operand.ID);
- Result.NamedOperands.push_back({
- .ID = Operand.ID,
- .Symbol = Operand.Symbol,
- .Name = Operand.Name,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeImmediatePredicates(NormalizedTargetModel &Result) {
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceImmediatePredicate &Predicate : Input.ImmediatePredicates) {
- const bool IsSigned = Predicate.Kind == "signed";
- const bool IsUnsigned = Predicate.Kind == "unsigned";
- if (Predicate.ID == 0 || !isIdentifier(Predicate.Symbol) || Predicate.Name.empty() ||
- (!IsSigned && !IsUnsigned) || Predicate.Bits == 0 || Predicate.Bits > 63 ||
- !isPowerOfTwo(Predicate.Alignment) || !IDs.insert(Predicate.ID).second ||
- !Symbols.insert(Predicate.Symbol).second || !Names.insert(Predicate.Name).second) {
- fail(Predicate.Range,
- "immediate predicates require nonzero unique IDs, symbols, names, a signed or "
- "unsigned kind, a width in [1, 63], and power-of-two alignment");
- return false;
- }
- const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U);
- const std::int64_t Minimum = IsSigned ? -PositiveLimit : 0;
- const std::int64_t Maximum =
- IsSigned ? PositiveLimit - 1
- : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - 1U);
- std::unordered_set<std::int64_t> AllowedValues;
- for (const std::int64_t Value : Predicate.AllowedValues) {
- if (Value < Minimum || Value > Maximum || (Predicate.NonZero && Value == 0) ||
- !AllowedValues.insert(Value).second) {
- fail(Predicate.Range,
- "immediate predicate allowed_values must be unique, in range, and satisfy nonzero");
- return false;
- }
- }
- ImmediatePredicateBySymbol.emplace(Predicate.Symbol, Predicate.ID);
- Result.ImmediatePredicates.push_back({
- .ID = Predicate.ID,
- .Symbol = Predicate.Symbol,
- .Name = Predicate.Name,
- .IsSigned = IsSigned,
- .Bits = Predicate.Bits,
- .Alignment = Predicate.Alignment,
- .NonZero = Predicate.NonZero,
- .AllowedValues = Predicate.AllowedValues,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeSchedulingClasses(NormalizedTargetModel &Result) {
- if (Input.SchedulingClasses.empty()) {
- fail(Input.Target.Range, "target description requires at least one scheduling class");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceSchedulingClass &Class : Input.SchedulingClasses) {
- if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() ||
- Class.MicroOps == 0 || Class.Latency == 0 || !IDs.insert(Class.ID).second ||
- !Symbols.insert(Class.Symbol).second || !Names.insert(Class.Name).second) {
- fail(Class.Range,
- "scheduling classes require nonzero unique IDs, symbols, names, micro-op counts, "
- "and latencies");
- return false;
- }
- SchedulingClassBySymbol.emplace(Class.Symbol, Class.ID);
- Result.SchedulingClasses.push_back({
- .ID = Class.ID,
- .Symbol = Class.Symbol,
- .Name = Class.Name,
- .MicroOps = Class.MicroOps,
- .Latency = Class.Latency,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeEncodingCategories(NormalizedTargetModel &Result) {
- if (Input.EncodingCategories.empty()) {
- fail(Input.Target.Range, "target description requires at least one encoding category");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceEncodingCategory &Category : Input.EncodingCategories) {
- if (Category.ID == 0 || !isIdentifier(Category.Symbol) || Category.Name.empty() ||
- Category.SizeInBytes == 0 || !IDs.insert(Category.ID).second ||
- !Symbols.insert(Category.Symbol).second || !Names.insert(Category.Name).second) {
- fail(Category.Range,
- "encoding categories require nonzero unique IDs, symbols, names, and sizes");
- return false;
- }
- EncodingCategoryBySymbol.emplace(Category.Symbol, Category.ID);
- Result.EncodingCategories.push_back({
- .ID = Category.ID,
- .Symbol = Category.Symbol,
- .Name = Category.Name,
- .SizeInBytes = Category.SizeInBytes,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeAssemblyFormats(NormalizedTargetModel &Result) {
- if (Input.AssemblyFormats.empty()) {
- fail(Input.Target.Range, "target description requires at least one assembly format");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceAssemblyFormat &Format : Input.AssemblyFormats) {
- if (Format.ID == 0 || !isIdentifier(Format.Symbol) || Format.Name.empty() ||
- !IDs.insert(Format.ID).second || !Symbols.insert(Format.Symbol).second ||
- !Names.insert(Format.Name).second) {
- fail(Format.Range, "assembly formats require nonzero unique IDs, symbols, and names");
- return false;
- }
- AssemblyFormatBySymbol.emplace(Format.Symbol, Format.ID);
- Result.AssemblyFormats.push_back({
- .ID = Format.ID,
- .Symbol = Format.Symbol,
- .Name = Format.Name,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeOperand(const SourceOperand &InputOperand,
- NormalizedOperand &Result) {
- if (InputOperand.Kinds.empty()) {
- fail(InputOperand.Range, "instruction operand requires at least one kind");
- return false;
- }
- std::unordered_set<std::string> Kinds;
- bool HasRegisterKind = false;
- for (const std::string &Kind : InputOperand.Kinds) {
- const auto Enumerator = operandKindEnumerator(Kind);
- if (!Enumerator || !Kinds.insert(std::string(*Enumerator)).second) {
- fail(InputOperand.Range, "instruction operand kinds must be known and unique");
- return false;
- }
- HasRegisterKind |= *Enumerator == "Register";
- Result.Kinds.push_back(std::string(*Enumerator));
- }
-
- const auto Role = registerRoleEnumerator(InputOperand.RegisterRole);
- const auto MemoryRole = memoryRoleEnumerator(InputOperand.MemoryRole);
- if (!Role || !MemoryRole || HasRegisterKind != (*Role != "None")) {
- fail(InputOperand.Range,
- "instruction operand register role and memory role must match its declared kinds");
- return false;
- }
- Result.RegisterRole = std::string(*Role);
- Result.MemoryRole = std::string(*MemoryRole);
-
- const bool IsDefRole = *Role == "Def" || *Role == "DefUse";
- const bool IsRegisterOnly = Result.Kinds.size() == 1 && Result.Kinds.front() == "Register";
- if (IsDefRole && !IsRegisterOnly) {
- fail(InputOperand.Range, "register definition operands must accept only registers");
- return false;
- }
- if (!HasRegisterKind && (InputOperand.RegisterClassSymbol || InputOperand.RegisterBankSymbol ||
- InputOperand.EarlyClobber || InputOperand.AllowSubReg)) {
- fail(InputOperand.Range, "non-register operands cannot have register constraints");
- return false;
- }
- if (InputOperand.EarlyClobber && !IsDefRole) {
- fail(InputOperand.Range, "early-clobber operands must define a register");
- return false;
- }
-
- if (InputOperand.RegisterClassSymbol) {
- const auto Position = ClassBySymbol.find(*InputOperand.RegisterClassSymbol);
- if (Position == ClassBySymbol.end()) {
- fail(InputOperand.Range, "instruction operand names an unknown register class");
- return false;
- }
- Result.RegisterClass = Position->second;
- }
- if (InputOperand.RegisterBankSymbol) {
- const auto Position = BankBySymbol.find(*InputOperand.RegisterBankSymbol);
- if (Position == BankBySymbol.end()) {
- fail(InputOperand.Range, "instruction operand names an unknown register bank");
- return false;
- }
- Result.RegisterBank = Position->second;
- }
- if (InputOperand.ImmediatePredicateSymbol) {
- const auto Position = ImmediatePredicateBySymbol.find(*InputOperand.ImmediatePredicateSymbol);
- if (Position == ImmediatePredicateBySymbol.end() ||
- std::ranges::find(Result.Kinds, "Immediate") == Result.Kinds.end()) {
- fail(InputOperand.Range,
- "instruction immediate predicates must name a declaration and constrain an immediate");
- return false;
- }
- Result.ImmediatePredicate = Position->second;
- }
- if (InputOperand.NameSymbol) {
- const auto Position = NamedOperandBySymbol.find(*InputOperand.NameSymbol);
- if (Position == NamedOperandBySymbol.end()) {
- fail(InputOperand.Range, "instruction operand names an unknown named operand");
- return false;
- }
- Result.Name = Position->second;
- }
-
- Result.DefaultTiedOperand = InputOperand.DefaultTiedOperand;
- Result.EarlyClobber = InputOperand.EarlyClobber;
- Result.Optional = InputOperand.Optional;
- Result.VariadicGroup = InputOperand.VariadicGroup;
- Result.AllowSubReg = InputOperand.AllowSubReg;
- Result.AllowedTargetFlags = InputOperand.AllowedTargetFlags;
- return true;
- }
-
- [[nodiscard]] bool
- normalizeImplicitRegisters(const std::vector<SourceImplicitRegister> &InputRegisters, bool IsDef,
- std::vector<NormalizedImplicitRegister> &Result) {
- std::unordered_set<std::uint32_t> Registers;
- for (const SourceImplicitRegister &Register : InputRegisters) {
- const auto Position = RegisterBySymbol.find(Register.RegisterSymbol);
- if (Position == RegisterBySymbol.end() || !Registers.insert(Position->second).second ||
- (!IsDef && Register.EarlyClobber)) {
- fail(Register.Range,
- "implicit registers must be declared and unique; implicit uses cannot early-clobber");
- return false;
- }
- Result.push_back({
- .Register = Position->second,
- .EarlyClobber = Register.EarlyClobber,
- .Renamable = Register.Renamable,
- });
- }
- return true;
- }
-
- [[nodiscard]] bool normalizeInstructions(NormalizedTargetModel &Result) {
- if (Input.Instructions.empty()) {
- fail(Input.Target.Range, "target description requires at least one instruction");
- return false;
- }
- std::unordered_set<std::uint32_t> IDs;
- std::unordered_set<std::string> Symbols;
- std::unordered_set<std::string> Names;
- for (const SourceInstruction &Instruction : Input.Instructions) {
- const bool HasMnemonic = !Instruction.AssemblyMnemonic.empty();
- const bool HasBinaryEncoding = Instruction.BinaryEncoding.has_value();
- const bool HasSchedulingClass = !Instruction.SchedulingClassSymbol.empty();
- const bool HasEncodingCategory = !Instruction.EncodingCategorySymbol.empty();
- const bool HasAssemblyFormat = !Instruction.AssemblyFormatSymbol.empty();
- const bool HasAllMachineFacts = HasMnemonic && HasBinaryEncoding && HasSchedulingClass &&
- HasEncodingCategory && HasAssemblyFormat;
- const bool HasAnyMachineFact = HasMnemonic || HasBinaryEncoding || HasSchedulingClass ||
- HasEncodingCategory || HasAssemblyFormat;
- const bool IsPseudoSource =
- std::ranges::find(Instruction.Flags, "pseudo") != Instruction.Flags.end();
- const bool IsTerminalEmissionSource =
- std::ranges::find(Instruction.Flags, "terminal_emission") != Instruction.Flags.end();
- if (HasAnyMachineFact != HasAllMachineFacts ||
- ((!IsPseudoSource || IsTerminalEmissionSource) && !HasAllMachineFacts)) {
- fail(Instruction.Range,
- "encoded instructions and terminal-emission pseudos require complete scheduling, "
- "encoding, mnemonic, and assembly-format facts");
- return false;
- }
- const auto SchedulingClass = SchedulingClassBySymbol.find(Instruction.SchedulingClassSymbol);
- const auto EncodingCategory =
- EncodingCategoryBySymbol.find(Instruction.EncodingCategorySymbol);
- const auto AssemblyFormat = AssemblyFormatBySymbol.find(Instruction.AssemblyFormatSymbol);
- const auto Owner = pseudoOwnerEnumerator(Instruction.Owner);
- const auto Deadline = pipelineStageEnumerator(Instruction.Deadline);
- if (Instruction.ID == 0 || !isIdentifier(Instruction.Symbol) || Instruction.Name.empty() ||
- (HasMnemonic && !isAssemblyMnemonic(Instruction.AssemblyMnemonic)) ||
- (HasSchedulingClass && SchedulingClass == SchedulingClassBySymbol.end()) ||
- (HasEncodingCategory && EncodingCategory == EncodingCategoryBySymbol.end()) ||
- (HasAssemblyFormat && AssemblyFormat == AssemblyFormatBySymbol.end()) || !Owner ||
- !Deadline || !IDs.insert(Instruction.ID).second ||
- !Symbols.insert(Instruction.Symbol).second || !Names.insert(Instruction.Name).second) {
- fail(Instruction.Range,
- "instructions require nonzero unique IDs, symbols, lowercase assembly mnemonics, and "
- "declared format IDs");
- return false;
- }
- if (Instruction.NumExplicitDefs > Instruction.Operands.size()) {
- fail(Instruction.Range, "instruction explicit def count is outside its operand range");
- return false;
- }
-
- NormalizedInstruction Normalized{
- .ID = Instruction.ID,
- .Symbol = Instruction.Symbol,
- .Name = Instruction.Name,
- .AssemblyMnemonic = Instruction.AssemblyMnemonic,
- .BinaryEncoding = Instruction.BinaryEncoding.value_or(0),
- .Operands = {},
- .NumExplicitDefs = Instruction.NumExplicitDefs,
- .Variadic = Instruction.Variadic,
- .ImplicitDefs = {},
- .ImplicitUses = {},
- .AllowExtraImplicitOperands = Instruction.AllowExtraImplicitOperands,
- .Flags = {},
- .SchedulingClass = HasSchedulingClass ? SchedulingClass->second : 0,
- .EncodingCategory = HasEncodingCategory ? EncodingCategory->second : 0,
- .AssemblyFormat = HasAssemblyFormat ? AssemblyFormat->second : 0,
- .RequiresMemoryOperands = Instruction.RequiresMemoryOperands,
- .Owner = std::string(*Owner),
- .Deadline = std::string(*Deadline),
- };
- Normalized.Operands.reserve(Instruction.Operands.size());
- bool SawOptional = false;
- for (std::size_t Index = 0; Index < Instruction.Operands.size(); ++Index) {
- NormalizedOperand Operand;
- if (!normalizeOperand(Instruction.Operands[Index], Operand))
- return false;
- const bool IsDef = Operand.RegisterRole == "Def" || Operand.RegisterRole == "DefUse";
- if ((Index < Instruction.NumExplicitDefs) != IsDef) {
- fail(Instruction.Operands[Index].Range,
- "instruction explicit register definitions must form the leading operand prefix");
- return false;
- }
- if (Instruction.Variadic && Operand.Optional) {
- fail(Instruction.Operands[Index].Range,
- "variadic instructions cannot contain optional operands");
- return false;
- }
- if (Operand.Optional)
- SawOptional = true;
- else if (SawOptional && !Operand.VariadicGroup) {
- fail(Instruction.Operands[Index].Range,
- "optional instruction operands must form a trailing suffix");
- return false;
- }
- if (Operand.VariadicGroup &&
- (!Instruction.Variadic || Index + 1 != Instruction.Operands.size())) {
- fail(Instruction.Operands[Index].Range,
- "a variadic operand group must be the final instruction operand");
- return false;
- }
- if (Operand.VariadicGroup && (Operand.Optional || Operand.DefaultTiedOperand)) {
- fail(Instruction.Operands[Index].Range,
- "a variadic operand group cannot be optional or have a default tie");
- return false;
- }
- Normalized.Operands.push_back(std::move(Operand));
- }
- if (Instruction.Variadic &&
- (Normalized.Operands.empty() || !Normalized.Operands.back().VariadicGroup)) {
- fail(Instruction.Range, "variadic instructions require a final variadic operand group");
- return false;
- }
- if (!Instruction.Variadic &&
- std::ranges::any_of(Normalized.Operands, [](const NormalizedOperand &Operand) {
- return Operand.VariadicGroup;
- })) {
- fail(Instruction.Range, "non-variadic instructions cannot have a variadic operand group");
- return false;
- }
-
- std::unordered_set<std::uint32_t> NamedOperands;
- for (std::size_t Index = 0; Index < Normalized.Operands.size(); ++Index) {
- const NormalizedOperand &Operand = Normalized.Operands[Index];
- if (Operand.Name && !NamedOperands.insert(*Operand.Name).second) {
- fail(Instruction.Operands[Index].Range,
- "instruction operands must use distinct named operand IDs");
- return false;
- }
- if (!Operand.DefaultTiedOperand)
- continue;
- const std::uint32_t OtherIndex = *Operand.DefaultTiedOperand;
- if (OtherIndex >= Normalized.Operands.size() || OtherIndex == Index ||
- !Normalized.Operands[OtherIndex].DefaultTiedOperand ||
- *Normalized.Operands[OtherIndex].DefaultTiedOperand != Index) {
- fail(Instruction.Operands[Index].Range,
- "instruction default ties must be reciprocal and in range");
- return false;
- }
- const NormalizedOperand &Other = Normalized.Operands[OtherIndex];
- const bool IsRegisterOnly =
- Operand.Kinds.size() == 1 && Operand.Kinds.front() == "Register";
- const bool OtherIsRegisterOnly =
- Other.Kinds.size() == 1 && Other.Kinds.front() == "Register";
- const bool IsDefUsePair = (Operand.RegisterRole == "Def" && Other.RegisterRole == "Use") ||
- (Operand.RegisterRole == "Use" && Other.RegisterRole == "Def");
- if (Operand.Optional || Other.Optional || !IsRegisterOnly || !OtherIsRegisterOnly ||
- !IsDefUsePair) {
- fail(Instruction.Operands[Index].Range,
- "instruction default ties must connect required register defs and uses");
- return false;
- }
- }
-
- std::unordered_set<std::string> Flags;
- for (const std::string &Flag : Instruction.Flags) {
- const auto Enumerator = instructionFlagEnumerator(Flag);
- if (!Enumerator || !Flags.insert(std::string(*Enumerator)).second) {
- fail(Instruction.Range, "instruction flags must be known and unique");
- return false;
- }
- Normalized.Flags.push_back(std::string(*Enumerator));
- }
- const auto HasFlag = [&Normalized](std::string_view Flag) {
- return std::ranges::find(Normalized.Flags, Flag) != Normalized.Flags.end();
- };
- const bool IsTerminator = HasFlag("Terminator");
- if ((HasFlag("Branch") || HasFlag("Return") || HasFlag("Barrier") ||
- HasFlag("IndirectBranch")) &&
- !IsTerminator) {
- fail(Instruction.Range, "branch, return, and barrier instructions must be terminators");
- return false;
- }
- if (HasFlag("IndirectBranch") && !HasFlag("Branch")) {
- fail(Instruction.Range, "indirect branch instructions must also be branches");
- return false;
- }
- const bool IsPseudo = HasFlag("Pseudo");
- const bool IsMeta = HasFlag("Meta");
- if ((IsMeta && (!IsPseudo || HasFlag("ProducesCode"))) ||
- (!IsPseudo && Normalized.Owner != "None") ||
- (IsPseudo && !IsMeta && Normalized.Owner == "None") ||
- (HasFlag("TerminalEmission") &&
- (!IsPseudo || Normalized.Owner != "AsmPrinter" || !HasFlag("ProducesCode")))) {
- fail(Instruction.Range, "instruction pseudo flags, owner, and deadline are inconsistent");
- return false;
- }
- if (Normalized.RequiresMemoryOperands && !HasFlag("MayLoad") && !HasFlag("MayStore")) {
- fail(Instruction.Range,
- "memory operand requirements need a load or store instruction flag");
- return false;
- }
- if (!normalizeImplicitRegisters(Instruction.ImplicitDefs, true, Normalized.ImplicitDefs) ||
- !normalizeImplicitRegisters(Instruction.ImplicitUses, false, Normalized.ImplicitUses)) {
- return false;
- }
- Result.Instructions.push_back(std::move(Normalized));
- }
- std::ranges::sort(Result.Instructions, {}, &NormalizedInstruction::ID);
- return true;
- }
-
- const DescriptionAST &Input;
- std::string SourceName;
- std::unordered_map<std::string, std::uint32_t> UnitBySymbol;
- std::unordered_map<std::string, std::uint32_t> RegisterBySymbol;
- std::unordered_map<std::string, std::uint32_t> BankBySymbol;
- std::unordered_map<std::string, std::uint32_t> ClassBySymbol;
- std::unordered_map<std::string, std::uint32_t> NamedOperandBySymbol;
- std::unordered_map<std::string, std::uint32_t> ImmediatePredicateBySymbol;
- std::unordered_map<std::string, std::uint32_t> SchedulingClassBySymbol;
- std::unordered_map<std::string, std::uint32_t> EncodingCategoryBySymbol;
- std::unordered_map<std::string, std::uint32_t> AssemblyFormatBySymbol;
- std::unordered_map<std::string, std::uint32_t> CallingConventionBySymbol;
- std::vector<TextDiagnostic> Diagnostics;
-};
-
-[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model,
- std::uint32_t ID) {
- const auto Position = std::ranges::find(Model.Registers, ID, &NormalizedRegister::ID);
- return *Position;
-}
-
-[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model,
- std::uint32_t ID) {
- const auto Position = std::ranges::find(Model.RegisterUnits, ID, &SourceRegisterUnit::ID);
- return *Position;
-}
-
-[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model,
- std::uint32_t ID) {
- const auto Position = std::ranges::find(Model.RegisterBanks, ID, &NormalizedRegisterBank::ID);
- return *Position;
-}
-
-[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model,
- std::string_view SourceName) {
- const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" +
- upperIdentifier(Model.Target.Name) + "REGISTERS_H";
- std::ostringstream Output;
- Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
- Output << "// This file is generated by rcc-backend-descgen from ";
- appendQuoted(Output, SourceName);
- Output << ". Do not edit it manually.\n\n";
- Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n";
- Output << "#include <cstdint>\n#include <string_view>\n\n";
- Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n";
- Output << "inline constexpr std::string_view TargetName = ";
- appendQuoted(Output, Model.Target.Name);
- Output << ";\ninline constexpr std::string_view TargetTriple = ";
- appendQuoted(Output, Model.Target.Triple);
- Output << ";\ninline constexpr std::string_view TargetCPU = ";
- appendQuoted(Output, Model.Target.CPU);
- Output << ";\ninline constexpr std::string_view TargetABI = ";
- appendQuoted(Output, Model.Target.ABI);
- Output << ";\ninline constexpr std::string_view TargetDataLayout = ";
- appendQuoted(Output, Model.Target.DataLayout);
- Output << ";\n\ninline constexpr std::string_view TargetFeatures[] = {\n";
- for (const std::string &Feature : Model.Target.Features) {
- Output << " ";
- appendQuoted(Output, Feature);
- Output << ",\n";
- }
- Output << "};\n\n";
-
- Output << "enum class PhysicalRegisterID : std::uint32_t {\n NoRegister = 0,\n";
- for (const NormalizedRegister &Register : Model.Registers)
- Output << " " << Register.Symbol << " = " << Register.ID << ",\n";
- Output << "};\n\n";
- Output << "enum class RegisterUnit : std::uint32_t {\n";
- for (const SourceRegisterUnit &Unit : Model.RegisterUnits)
- Output << " " << Unit.Symbol << " = " << Unit.ID << ",\n";
- Output << "};\n\n";
- Output << "enum class RegisterBank : std::uint32_t {\n";
- for (const NormalizedRegisterBank &Bank : Model.RegisterBanks)
- Output << " " << Bank.Symbol << " = " << Bank.ID << ",\n";
- Output << "};\n\n";
- Output << "enum class RegisterClass : std::uint32_t {\n";
- for (const NormalizedRegisterClass &Class : Model.RegisterClasses)
- Output << " " << Class.Symbol << " = " << Class.ID << ",\n";
- Output << "};\n\n";
- Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n";
- Output << "#endif // " << Guard << "\n";
- return Output.str();
-}
-
-[[nodiscard]] std::string emitInstructionHeader(const NormalizedTargetModel &Model,
- std::string_view SourceName) {
- const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" +
- upperIdentifier(Model.Target.Name) + "INSTRUCTIONS_H";
- std::ostringstream Output;
- Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
- Output << "// This file is generated by rcc-backend-descgen from ";
- appendQuoted(Output, SourceName);
- Output << ". Do not edit it manually.\n\n";
- Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n";
- Output << "#include <cstdint>\n\n";
- Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n";
-
- Output << "enum class InstructionOpcode : std::uint32_t {\n Invalid = 0,\n";
- for (const NormalizedInstruction &Instruction : Model.Instructions)
- Output << " " << Instruction.Symbol << " = " << Instruction.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class ImmediatePredicate : std::uint32_t {\n None = 0,\n";
- for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates)
- Output << " " << Predicate.Symbol << " = " << Predicate.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class NamedOperand : std::uint32_t {\n None = 0,\n";
- for (const NormalizedNamedOperand &Operand : Model.NamedOperands)
- Output << " " << Operand.Symbol << " = " << Operand.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class SchedulingClass : std::uint32_t {\n None = 0,\n";
- for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses)
- Output << " " << Class.Symbol << " = " << Class.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class EncodingCategory : std::uint32_t {\n None = 0,\n";
- for (const NormalizedEncodingCategory &Category : Model.EncodingCategories)
- Output << " " << Category.Symbol << " = " << Category.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class AssemblyFormat : std::uint32_t {\n None = 0,\n";
- for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats)
- Output << " " << Format.Symbol << " = " << Format.ID << ",\n";
- Output << "};\n\n";
-
- Output << "enum class CallingConvention : std::uint32_t {\n None = 0,\n";
- for (const NormalizedCallingConvention &Convention : Model.CallingConventions)
- Output << " " << Convention.Symbol << " = " << Convention.ID << ",\n";
- Output << "};\n\n";
-
- Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n";
- Output << "#endif // " << Guard << "\n";
- return Output.str();
-}
-
-void emitRegisterIDList(std::ostream &Output, const NormalizedTargetModel &Model,
- std::span<const std::uint32_t> IDs) {
- for (const std::uint32_t ID : IDs)
- Output << "PhysicalRegisterID::" << findRegister(Model, ID).Symbol << ", ";
-}
-
-void emitPhysicalRegisterArray(std::ostream &Output, const NormalizedTargetModel &Model,
- std::string_view Name, std::span<const std::uint32_t> IDs) {
- Output << "const std::array<PhysicalRegister, " << IDs.size() << "> " << Name << " = {{\n";
- for (const std::uint32_t ID : IDs)
- Output << " physical(PhysicalRegisterID::" << findRegister(Model, ID).Symbol << "),\n";
- Output << "}};\n";
-}
-
-void emitOperandDescriptor(std::ostream &Output, const NormalizedOperand &Operand) {
- Output << " {.Kinds = OperandKindSet{";
- for (const std::string &Kind : Operand.Kinds)
- Output << "MachineOperand::Kind::" << Kind << ", ";
- Output << "}, .RegisterRole = RegisterOperandRole::" << Operand.RegisterRole;
- Output << ", .RegClass = ";
- if (Operand.RegisterClass)
- Output << "RegisterClassID::fromValue(" << *Operand.RegisterClass << ")";
- else
- Output << "std::nullopt";
- Output << ", .RegBank = ";
- if (Operand.RegisterBank)
- Output << "RegisterBankID::fromValue(" << *Operand.RegisterBank << ")";
- else
- Output << "std::nullopt";
- Output << ", .ImmediatePredicate = ";
- if (Operand.ImmediatePredicate)
- Output << "ImmediatePredicateID::fromValue(" << *Operand.ImmediatePredicate << ")";
- else
- Output << "std::nullopt";
- Output << ", .MemoryRole = MemoryOperandRole::" << Operand.MemoryRole;
- Output << ", .DefaultTiedOperand = ";
- if (Operand.DefaultTiedOperand)
- Output << "std::size_t{" << *Operand.DefaultTiedOperand << "}";
- else
- Output << "std::nullopt";
- Output << ", .EarlyClobber = " << (Operand.EarlyClobber ? "true" : "false")
- << ", .Optional = " << (Operand.Optional ? "true" : "false")
- << ", .VariadicGroup = " << (Operand.VariadicGroup ? "true" : "false")
- << ", .AllowSubReg = " << (Operand.AllowSubReg ? "true" : "false");
- Output << ", .Name = ";
- if (Operand.Name)
- Output << "NamedOperandID::fromValue(" << *Operand.Name << ")";
- else
- Output << "std::nullopt";
- Output << ", .TargetOperandType = std::nullopt, .AllowedTargetFlags = "
- << Operand.AllowedTargetFlags << ", .TypeConstraint = std::nullopt},\n";
-}
-
-void emitImplicitRegisterList(std::ostream &Output, const NormalizedTargetModel &Model,
- std::span<const NormalizedImplicitRegister> Registers) {
- Output << "{";
- for (const NormalizedImplicitRegister &Register : Registers) {
- Output << "{physical(PhysicalRegisterID::" << findRegister(Model, Register.Register).Symbol
- << "), " << (Register.EarlyClobber ? "true" : "false") << ", "
- << (Register.Renamable ? "true" : "false") << "}, ";
- }
- Output << "}";
-}
-
-void emitImmediatePredicate(std::ostream &Output, const NormalizedImmediatePredicate &Predicate) {
- const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U);
- const std::int64_t Minimum = Predicate.IsSigned ? -PositiveLimit : 0;
- const std::int64_t Maximum =
- Predicate.IsSigned
- ? PositiveLimit - 1
- : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - std::uint64_t{1});
- Output << " {.ID = ImmediatePredicateID::fromValue(" << Predicate.ID << "), .Name = ";
- appendQuoted(Output, Predicate.Name);
- Output << ", .Predicate = [](const MachineOperand &Operand) {\n"
- << " const auto *Value = Operand.getIf<std::int64_t>();\n"
- << " return Value != nullptr && *Value >= " << Minimum
- << " && *Value <= " << Maximum;
- if (Predicate.Alignment != 1)
- Output << " && (*Value % " << Predicate.Alignment << ") == 0";
- if (Predicate.NonZero)
- Output << " && *Value != 0";
- if (!Predicate.AllowedValues.empty()) {
- Output << " && (";
- for (std::size_t Index = 0; Index < Predicate.AllowedValues.size(); ++Index) {
- if (Index != 0)
- Output << " || ";
- Output << "*Value == " << Predicate.AllowedValues[Index];
- }
- Output << ")";
- }
- Output << ";\n }},\n";
-}
-
-[[nodiscard]] std::string emitTargetTables(const NormalizedTargetModel &Model,
- std::string_view SourceName) {
- std::ostringstream Output;
- Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
- Output << "// This file is generated by rcc-backend-descgen from ";
- appendQuoted(Output, SourceName);
- Output << ". Do not edit it manually.\n\n";
- Output << "void appendGeneratedRegisterInfo(TargetRegisterInfoDesc &Description) {\n";
- Output << " Description.RegisterUnits = {\n";
- for (const SourceRegisterUnit &Unit : Model.RegisterUnits) {
- Output << " {RegisterUnitID::fromValue(" << Unit.ID << "), ";
- appendQuoted(Output, Unit.Name);
- Output << "},\n";
- }
- Output << " };\n\n Description.PhysicalRegisters = {\n";
- for (const NormalizedRegister &Register : Model.Registers) {
- Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), ";
- appendQuoted(Output, Register.Name);
- Output << ", " << Register.Encoding << ", {";
- for (const NormalizedRegisterUnitLane &Unit : Register.Units) {
- Output << "unitLane(RegisterUnit::" << findRegisterUnit(Model, Unit.UnitID).Symbol << ", "
- << Unit.Lanes << "), ";
- }
- Output << "}, " << (Register.IsConstant ? "true" : "false") << ", "
- << (Register.IsArtificial ? "true" : "false") << ", "
- << (Register.IsReserved ? "true" : "false") << ", {";
- for (const std::string &Alias : Register.AlternativeNames) {
- appendQuoted(Output, Alias);
- Output << ", ";
- }
- Output << "}},\n";
- }
- Output << " };\n\n Description.RegisterBanks = {\n";
- for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) {
- Output << " {RegisterBankID::fromValue(" << Bank.ID << "), ";
- appendQuoted(Output, Bank.Name);
- Output << ", " << Bank.MaximumSizeInBits << ", {";
- for (const std::uint32_t ClassID : Bank.RegisterClasses) {
- const auto Position =
- std::ranges::find(Model.RegisterClasses, ClassID, &NormalizedRegisterClass::ID);
- Output << "RegisterClassID::fromValue(" << Position->ID << "), ";
- }
- Output << "}},\n";
- }
- Output << " };\n\n Description.RegisterClasses = {\n";
- for (const NormalizedRegisterClass &Class : Model.RegisterClasses) {
- Output << " registerClass(RegisterClass::" << Class.Symbol << ", ";
- appendQuoted(Output, Class.Name);
- Output << ", {";
- emitRegisterIDList(Output, Model, Class.Registers);
- Output << "}, {";
- emitRegisterIDList(Output, Model, Class.AllocationOrder);
- Output << "}, RegisterBank::" << findRegisterBank(Model, Class.BankID).Symbol << ", "
- << Class.SizeInBits << ", " << Class.SpillSizeInBits << ", "
- << Class.SpillAlignmentInBits << ", " << Class.Lanes << ", "
- << (Class.Allocatable ? "true" : "false") << ", " << Class.CopyCost << "),\n";
- }
- Output << " };\n}\n\n";
- Output << "void appendGeneratedMIRTextInfo(TargetMIRTextInfoDesc &Description) {\n";
- Output << " Description.DirectOperandFlagMask = " << Model.MIROperandFlags.DirectMask
- << ";\n Description.DirectOperandFlags = {\n";
- for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Direct) {
- Output << " {";
- appendQuoted(Output, Flag.Name);
- Output << ", " << Flag.Value << "},\n";
- }
- Output << " };\n Description.BitmaskOperandFlags = {\n";
- for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Bitmask) {
- Output << " {";
- appendQuoted(Output, Flag.Name);
- Output << ", " << Flag.Value << "},\n";
- }
- Output << " };\n";
- Output << " Description.RegisterMasks = {\n";
- for (const NormalizedRegisterMask &Mask : Model.RegisterMasks) {
- Output << " {";
- appendQuoted(Output, Mask.Name);
- Output << ", makePreservedMask({";
- emitRegisterIDList(Output, Model, Mask.PreservedRegisters);
- Output << "})},\n";
- }
- Output << " };\n Description.CFIRegisters = {\n";
- std::unordered_set<std::uint32_t> CanonicalDwarfRegisters;
- for (const NormalizedRegister &Register : Model.Registers) {
- const bool IsCanonical = CanonicalDwarfRegisters.insert(Register.DwarfRegister).second;
- Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), "
- << Register.DwarfRegister << ", " << (IsCanonical ? "true" : "false") << "},\n";
- }
- Output << " };\n}\n\n";
-
- Output
- << "void appendGeneratedInstructionInfo(std::vector<rcc::InstructionDescriptor> "
- "&Descriptors,\n"
- << " std::vector<rcc::ImmediatePredicate> &Predicates) {\n"
- << " Predicates = {\n";
- for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates)
- emitImmediatePredicate(Output, Predicate);
- Output << " };\n Descriptors = {\n";
- for (const NormalizedInstruction &Instruction : Model.Instructions) {
- Output << " {.InstructionOpcode = Opcode::fromValue(" << Instruction.ID << "), .Name = ";
- appendQuoted(Output, Instruction.Name);
- Output << ", .Operands = {\n";
- for (const NormalizedOperand &Operand : Instruction.Operands)
- emitOperandDescriptor(Output, Operand);
- Output << " }, .NumExplicitDefs = " << Instruction.NumExplicitDefs
- << ", .Variadic = " << (Instruction.Variadic ? "true" : "false")
- << ", .MinimumVariadicRepetitions = 0, .ImplicitDefs = ";
- emitImplicitRegisterList(Output, Model, Instruction.ImplicitDefs);
- Output << ", .ImplicitUses = ";
- emitImplicitRegisterList(Output, Model, Instruction.ImplicitUses);
- Output << ", .AllowExtraImplicitOperands = "
- << (Instruction.AllowExtraImplicitOperands ? "true" : "false")
- << ", .Flags = InstructionFlags{";
- for (const std::string &Flag : Instruction.Flags)
- Output << "InstructionFlag::" << Flag << ", ";
- Output << "}, .SchedulingClass = SchedulingClassID::fromValue(" << Instruction.SchedulingClass
- << "), .EncodingCategory = EncodingCategoryID::fromValue("
- << Instruction.EncodingCategory << "), .AssemblyFormat = AssemblyFormatID::fromValue("
- << Instruction.AssemblyFormat << "), .RequiresMemoryOperands = "
- << (Instruction.RequiresMemoryOperands ? "true" : "false")
- << ", .Owner = PseudoOwner::" << Instruction.Owner
- << ", .Deadline = PipelineStage::" << Instruction.Deadline
- << ", .VerifyExplicitOperands = nullptr, "
- ".VerifyInstructionSemantics = nullptr},\n";
- }
- Output << " };\n}\n\n";
-
- Output << "constexpr std::array<SchedulingClassInfo, " << Model.SchedulingClasses.size()
- << "> GeneratedSchedulingClasses = {{\n";
- for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses) {
- Output << " {SchedulingClassID::fromValue(" << Class.ID << "), ";
- appendQuoted(Output, Class.Name);
- Output << ", " << Class.MicroOps << ", " << Class.Latency << "},\n";
- }
- Output << "}};\n\n";
-
- Output << "constexpr std::array<EncodingCategoryInfo, " << Model.EncodingCategories.size()
- << "> GeneratedEncodingCategories = {{\n";
- for (const NormalizedEncodingCategory &Category : Model.EncodingCategories) {
- Output << " {EncodingCategoryID::fromValue(" << Category.ID << "), ";
- appendQuoted(Output, Category.Name);
- Output << ", " << Category.SizeInBytes << "},\n";
- }
- Output << "}};\n\n";
-
- Output << "constexpr std::array<AssemblyFormatInfo, " << Model.AssemblyFormats.size()
- << "> GeneratedAssemblyFormats = {{\n";
- for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats) {
- Output << " {AssemblyFormatID::fromValue(" << Format.ID << "), ";
- appendQuoted(Output, Format.Name);
- Output << "},\n";
- }
- Output << "}};\n\n";
-
- const auto AssemblyInstructionCount =
- std::ranges::count_if(Model.Instructions, [](const NormalizedInstruction &Instruction) {
- return !Instruction.AssemblyMnemonic.empty();
- });
- Output << "constexpr std::array<AssemblyInstructionInfo, " << AssemblyInstructionCount
- << "> GeneratedAssemblyInstructions = {{\n";
- for (const NormalizedInstruction &Instruction : Model.Instructions) {
- if (Instruction.AssemblyMnemonic.empty())
- continue;
- Output << " {Opcode::fromValue(" << Instruction.ID << "), ";
- appendQuoted(Output, Instruction.AssemblyMnemonic);
- Output << ", AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat << "), "
- << Instruction.BinaryEncoding << "},\n";
- }
- Output << "}};\n\n";
-
- for (const NormalizedCallingConvention &Convention : Model.CallingConventions) {
- const std::string Prefix = "CC" + std::to_string(Convention.ID);
- emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerArguments",
- Convention.IntegerArgumentRegisters);
- emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingArguments",
- Convention.FloatingArgumentRegisters);
- emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerReturns",
- Convention.IntegerReturnRegisters);
- emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingReturns",
- Convention.FloatingReturnRegisters);
- }
- Output << "const std::array<CallingConventionInfo, " << Model.CallingConventions.size()
- << "> GeneratedCallingConventions = {{\n";
- for (const NormalizedCallingConvention &Convention : Model.CallingConventions) {
- const std::string Prefix = "CC" + std::to_string(Convention.ID);
- Output << " {CallingConvention::" << Convention.Symbol << ", ";
- appendQuoted(Output, Convention.Name);
- Output << ", RegisterClassID::fromValue(" << Convention.IntegerRegisterClass
- << "), RegisterClassID::fromValue(" << Convention.FloatingRegisterClass << "), "
- << Prefix << "IntegerArguments, " << Prefix << "FloatingArguments, " << Prefix
- << "IntegerReturns, " << Prefix << "FloatingReturns, ";
- appendQuoted(Output, Convention.PreservedMaskName);
- Output << ", " << Convention.StackAlignmentBytes << ", "
- << Convention.StackArgumentAlignmentBytes << ", " << Convention.ShadowSpaceBytes << ", "
- << (Convention.SupportsVarArgs ? "true" : "false") << ", "
- << (Convention.SupportsTailCalls ? "true" : "false") << "},\n";
- }
- Output << "}};\n";
- return Output.str();
-}
-
-} // namespace
-
-DescriptionGenerationResult generateDescription(const std::string &SourceName,
- std::string SourceText) {
- yaml::ParseResult YAMLResult = yaml::parse(SourceName, std::move(SourceText));
- if (!YAMLResult.succeeded()) {
- DescriptionGenerationResult Result;
- for (const TextDiagnostic &Diagnostic : YAMLResult.diagnostics())
- Result.Diagnostics.push_back(Diagnostic);
- return Result;
- }
-
- ASTParser Parser(*YAMLResult.stream());
- const auto AST = Parser.parse();
- if (!AST) {
- DescriptionGenerationResult Result;
- Result.Diagnostics = Parser.takeDiagnostics();
- return Result;
- }
-
- ModelNormalizer Normalizer(*AST, SourceName);
- const auto Model = Normalizer.normalize();
- if (!Model) {
- DescriptionGenerationResult Result;
- Result.Diagnostics = Normalizer.takeDiagnostics();
- return Result;
- }
-
- const std::size_t Separator = SourceName.find_last_of("/\\");
- const std::string_view DisplayName = Separator == std::string::npos
- ? std::string_view(SourceName)
- : std::string_view(SourceName).substr(Separator + 1);
- return {
- .Output =
- GeneratedDescription{
- .RegisterHeader = emitRegisterHeader(*Model, DisplayName),
- .InstructionHeader = emitInstructionHeader(*Model, DisplayName),
- .TargetTables = emitTargetTables(*Model, DisplayName),
- },
- .Diagnostics = {},
- };
-}
-
-} // namespace rcc::targetgen
diff --git a/backend/lib/Target/Description/TargetDescriptionGenerator.h b/backend/lib/Target/Description/TargetDescriptionGenerator.h
deleted file mode 100644
index 9a03aa0..0000000
--- a/backend/lib/Target/Description/TargetDescriptionGenerator.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// The private header has no stable include-root-relative guard name.
-// NOLINTNEXTLINE(llvm-header-guard)
-#ifndef RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H
-#define RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H
-
-#include "rcc/Support/TextDiagnostic.h"
-
-#include <optional>
-#include <string>
-#include <string_view>
-#include <vector>
-
-namespace rcc::targetgen {
-
-struct GeneratedDescription final {
- std::string RegisterHeader;
- std::string InstructionHeader;
- std::string TargetTables;
-};
-
-struct DescriptionGenerationResult final {
- std::optional<GeneratedDescription> Output;
- std::vector<TextDiagnostic> Diagnostics;
-
- [[nodiscard]] bool succeeded() const noexcept {
- return Output.has_value() && Diagnostics.empty();
- }
-};
-
-[[nodiscard]] DescriptionGenerationResult generateDescription(const std::string &SourceName,
- std::string SourceText);
-
-} // namespace rcc::targetgen
-
-#endif // RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index 5783e91..c4dbdf7 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -20,12 +20,12 @@ add_custom_command(
"${RCCBackendRISCVGeneratedInstructionHeader}"
"${RCCBackendRISCVGeneratedTables}"
COMMAND
- "$<TARGET_FILE:RCCBackendDescriptionGenerator>"
+ "$<TARGET_FILE:RCCBackendTableGenTool>"
--input "${RCCBackendRISCVDescription}"
--register-header "${RCCBackendRISCVGeneratedRegisterHeader}"
--instruction-header "${RCCBackendRISCVGeneratedInstructionHeader}"
--tables "${RCCBackendRISCVGeneratedTables}"
- DEPENDS RCCBackendDescriptionGenerator "${RCCBackendRISCVDescription}"
+ DEPENDS RCCBackendTableGenTool "${RCCBackendRISCVDescription}"
COMMENT "Generating RCC RV64GC target tables"
VERBATIM
)
diff --git a/backend/tools/CMakeLists.txt b/backend/tools/CMakeLists.txt
index 009b0ae..da6b7f3 100644
--- a/backend/tools/CMakeLists.txt
+++ b/backend/tools/CMakeLists.txt
@@ -1,4 +1,3 @@
# SPDX-License-Identifier: GPL-3.0-or-later
-add_subdirectory(DescriptionGenerator)
add_subdirectory(MIRTool)
diff --git a/backend/tools/DescriptionGenerator/CMakeLists.txt b/backend/tools/DescriptionGenerator/CMakeLists.txt
deleted file mode 100644
index 9bc3f26..0000000
--- a/backend/tools/DescriptionGenerator/CMakeLists.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-set(
- RCCBackendDescriptionGeneratorCoreSources
- "${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description/TargetDescriptionGenerator.cpp"
-)
-set(
- RCCBackendDescriptionGeneratorCoreHeaders
- "${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description/TargetDescriptionGenerator.h"
-)
-
-add_library(
- RCCBackendDescriptionGeneratorCore
- STATIC
- ${RCCBackendDescriptionGeneratorCoreSources}
-)
-
-rcc_backend_configure_target(RCCBackendDescriptionGeneratorCore)
-
-target_include_directories(
- RCCBackendDescriptionGeneratorCore
- PUBLIC "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description>"
-)
-target_link_libraries(RCCBackendDescriptionGeneratorCore PUBLIC RCCBackend::Support)
-
-add_executable(RCCBackendDescriptionGenerator DescriptionGeneratorMain.cpp)
-rcc_backend_configure_target(RCCBackendDescriptionGenerator)
-target_link_libraries(RCCBackendDescriptionGenerator PRIVATE RCCBackendDescriptionGeneratorCore)
-set_target_properties(RCCBackendDescriptionGenerator PROPERTIES OUTPUT_NAME rcc-backend-descgen)
-
-rcc_backend_register_format_files(
- "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorMain.cpp"
- ${RCCBackendDescriptionGeneratorCoreSources}
- ${RCCBackendDescriptionGeneratorCoreHeaders}
-)
-rcc_backend_register_tidy_files(
- "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorMain.cpp"
- ${RCCBackendDescriptionGeneratorCoreSources}
-)
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
index 3377f82..36a6445 100644
--- a/backend/unittests/Target/CMakeLists.txt
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -12,13 +12,10 @@ target_include_directories(
)
target_link_libraries(RCCBackendTestTarget PUBLIC RCCBackend::CodeGen)
-add_executable(RCCBackendDescriptionGeneratorTests DescriptionGeneratorTest.cpp)
-rcc_backend_configure_target(RCCBackendDescriptionGeneratorTests)
-target_link_libraries(
- RCCBackendDescriptionGeneratorTests
- PRIVATE RCCBackendDescriptionGeneratorCore
-)
-add_test(NAME RCCBackendDescriptionGeneratorTests COMMAND RCCBackendDescriptionGeneratorTests)
+add_executable(RCCBackendTableGenTests TableGenTest.cpp)
+rcc_backend_configure_target(RCCBackendTableGenTests)
+target_link_libraries(RCCBackendTableGenTests PRIVATE RCCBackend::TableGenBackends)
+add_test(NAME RCCBackend.TableGen COMMAND RCCBackendTableGenTests)
add_executable(RCCBackendRISCVTargetTests RISCVTargetTest.cpp)
rcc_backend_configure_target(RCCBackendRISCVTargetTests)
@@ -109,7 +106,7 @@ target_link_libraries(
add_test(NAME RCCBackendRISCVObjectFileTests COMMAND RCCBackendRISCVObjectFileTests)
rcc_backend_register_format_files(
- "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TableGenTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGenTest.cpp"
@@ -125,7 +122,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
)
rcc_backend_register_tidy_files(
- "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TableGenTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGenTest.cpp"
diff --git a/backend/unittests/Target/DescriptionGeneratorTest.cpp b/backend/unittests/Target/TableGenTest.cpp
index e6679e8..af4d718 100644
--- a/backend/unittests/Target/DescriptionGeneratorTest.cpp
+++ b/backend/unittests/Target/TableGenTest.cpp
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "TargetDescriptionGenerator.h"
+#include "TableGenBackends.h"
#include <cstddef>
#include <iostream>
diff --git a/backend/utils/TableGen/AsmWriterEmitter.cpp b/backend/utils/TableGen/AsmWriterEmitter.cpp
new file mode 100644
index 0000000..f8452c5
--- /dev/null
+++ b/backend/utils/TableGen/AsmWriterEmitter.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <sstream>
+#include <string>
+
+namespace rcc::targetgen {
+
+std::string emitAssemblyWriter(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ Output << "constexpr std::array<AssemblyFormatInfo, " << Model.AssemblyFormats.size()
+ << "> GeneratedAssemblyFormats = {{\n";
+ for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats) {
+ Output << " {AssemblyFormatID::fromValue(" << Format.ID << "), ";
+ appendQuoted(Output, Format.Name);
+ Output << "},\n";
+ }
+ Output << "}};\n\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/CMakeLists.txt b/backend/utils/TableGen/CMakeLists.txt
new file mode 100644
index 0000000..6873cbc
--- /dev/null
+++ b/backend/utils/TableGen/CMakeLists.txt
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(
+ RCCBackendTableGenBackendSources
+ AsmWriterEmitter.cpp
+ CallingConvEmitter.cpp
+ CodeEmitterGen.cpp
+ Common/CodeGenInstruction.cpp
+ Common/CodeGenRegisters.cpp
+ Common/CodeGenSchedule.cpp
+ Common/CodeGenTarget.cpp
+ InstrInfoEmitter.cpp
+ RegisterBankEmitter.cpp
+ RegisterInfoEmitter.cpp
+ SearchableTableEmitter.cpp
+ SubtargetEmitter.cpp
+)
+set(
+ RCCBackendTableGenBackendHeaders
+ "${CMAKE_CURRENT_SOURCE_DIR}/Common/CodeGenTarget.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TableGenBackends.h"
+)
+
+add_library(RCCBackendTableGenBackends STATIC ${RCCBackendTableGenBackendSources})
+add_library(RCCBackend::TableGenBackends ALIAS RCCBackendTableGenBackends)
+rcc_backend_configure_target(RCCBackendTableGenBackends)
+target_include_directories(
+ RCCBackendTableGenBackends
+ PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
+)
+target_link_libraries(RCCBackendTableGenBackends PUBLIC RCCBackend::TableGen)
+
+add_executable(RCCBackendTableGenTool rcc-tblgen.cpp)
+rcc_backend_configure_target(RCCBackendTableGenTool)
+target_link_libraries(RCCBackendTableGenTool PRIVATE RCCBackend::TableGenBackends)
+set_target_properties(RCCBackendTableGenTool PROPERTIES OUTPUT_NAME rcc-tblgen)
+
+rcc_backend_register_format_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/rcc-tblgen.cpp"
+ ${RCCBackendTableGenBackendSources}
+ ${RCCBackendTableGenBackendHeaders}
+)
+rcc_backend_register_tidy_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/rcc-tblgen.cpp"
+ ${RCCBackendTableGenBackendSources}
+)
diff --git a/backend/utils/TableGen/CallingConvEmitter.cpp b/backend/utils/TableGen/CallingConvEmitter.cpp
new file mode 100644
index 0000000..d71241e
--- /dev/null
+++ b/backend/utils/TableGen/CallingConvEmitter.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstdint>
+#include <ostream>
+#include <span>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+namespace {
+
+void emitPhysicalRegisterArray(std::ostream &Output, const NormalizedTargetModel &Model,
+ std::string_view Name, std::span<const std::uint32_t> IDs) {
+ Output << "const std::array<PhysicalRegister, " << IDs.size() << "> " << Name << " = {{\n";
+ for (const std::uint32_t ID : IDs)
+ Output << " physical(PhysicalRegisterID::" << findRegister(Model, ID).Symbol << "),\n";
+ Output << "}};\n";
+}
+
+} // namespace
+
+std::string emitCallingConventions(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ for (const NormalizedCallingConvention &Convention : Model.CallingConventions) {
+ const std::string Prefix = "CC" + std::to_string(Convention.ID);
+ emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerArguments",
+ Convention.IntegerArgumentRegisters);
+ emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingArguments",
+ Convention.FloatingArgumentRegisters);
+ emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerReturns",
+ Convention.IntegerReturnRegisters);
+ emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingReturns",
+ Convention.FloatingReturnRegisters);
+ }
+ Output << "const std::array<CallingConventionInfo, " << Model.CallingConventions.size()
+ << "> GeneratedCallingConventions = {{\n";
+ for (const NormalizedCallingConvention &Convention : Model.CallingConventions) {
+ const std::string Prefix = "CC" + std::to_string(Convention.ID);
+ Output << " {CallingConvention::" << Convention.Symbol << ", ";
+ appendQuoted(Output, Convention.Name);
+ Output << ", RegisterClassID::fromValue(" << Convention.IntegerRegisterClass
+ << "), RegisterClassID::fromValue(" << Convention.FloatingRegisterClass << "), "
+ << Prefix << "IntegerArguments, " << Prefix << "FloatingArguments, " << Prefix
+ << "IntegerReturns, " << Prefix << "FloatingReturns, ";
+ appendQuoted(Output, Convention.PreservedMaskName);
+ Output << ", " << Convention.StackAlignmentBytes << ", "
+ << Convention.StackArgumentAlignmentBytes << ", " << Convention.ShadowSpaceBytes << ", "
+ << (Convention.SupportsVarArgs ? "true" : "false") << ", "
+ << (Convention.SupportsTailCalls ? "true" : "false") << "},\n";
+ }
+ Output << "}};\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/CodeEmitterGen.cpp b/backend/utils/TableGen/CodeEmitterGen.cpp
new file mode 100644
index 0000000..7d0d9e6
--- /dev/null
+++ b/backend/utils/TableGen/CodeEmitterGen.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <sstream>
+#include <string>
+
+namespace rcc::targetgen {
+
+std::string emitCodeEmitter(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ Output << "constexpr std::array<EncodingCategoryInfo, " << Model.EncodingCategories.size()
+ << "> GeneratedEncodingCategories = {{\n";
+ for (const NormalizedEncodingCategory &Category : Model.EncodingCategories) {
+ Output << " {EncodingCategoryID::fromValue(" << Category.ID << "), ";
+ appendQuoted(Output, Category.Name);
+ Output << ", " << Category.SizeInBytes << "},\n";
+ }
+ Output << "}};\n\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenInstruction.cpp b/backend/utils/TableGen/Common/CodeGenInstruction.cpp
new file mode 100644
index 0000000..fc6b400
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenInstruction.cpp
@@ -0,0 +1,564 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::targetgen {
+
+[[nodiscard]] std::optional<std::string_view>
+operandKindEnumerator(std::string_view Value) noexcept {
+ static constexpr std::array<std::pair<std::string_view, std::string_view>, 22> Kinds = {{
+ {"register", "Register"},
+ {"immediate", "Immediate"},
+ {"integer_immediate", "IntegerImmediate"},
+ {"floating_immediate", "FloatingImmediate"},
+ {"basic_block", "BasicBlock"},
+ {"frame_index", "FrameIndex"},
+ {"constant_pool_index", "ConstantPoolIndex"},
+ {"jump_table_index", "JumpTableIndex"},
+ {"global_symbol", "GlobalSymbol"},
+ {"external_symbol", "ExternalSymbol"},
+ {"block_address", "BlockAddress"},
+ {"register_mask", "RegisterMask"},
+ {"live_out_register_mask", "LiveOutRegisterMask"},
+ {"metadata", "Metadata"},
+ {"target_index", "TargetIndex"},
+ {"mc_symbol", "MCSymbol"},
+ {"cfi_index", "CFIIndex"},
+ {"intrinsic", "Intrinsic"},
+ {"predicate", "Predicate"},
+ {"shuffle_mask", "ShuffleMask"},
+ {"debug_instr_ref", "DebugInstrRef"},
+ {"lane_mask", "LaneMask"},
+ }};
+ const auto *const Position = std::ranges::find(Kinds, Value, &decltype(Kinds)::value_type::first);
+ if (Position == Kinds.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+[[nodiscard]] std::optional<std::string_view>
+registerRoleEnumerator(std::string_view Value) noexcept {
+ if (Value == "none")
+ return "None";
+ if (Value == "def")
+ return "Def";
+ if (Value == "use")
+ return "Use";
+ if (Value == "def_use")
+ return "DefUse";
+ return std::nullopt;
+}
+
+[[nodiscard]] std::optional<std::string_view>
+memoryRoleEnumerator(std::string_view Value) noexcept {
+ if (Value == "none")
+ return "None";
+ if (Value == "address")
+ return "Address";
+ if (Value == "value")
+ return "Value";
+ if (Value == "size")
+ return "Size";
+ if (Value == "ordering")
+ return "Ordering";
+ return std::nullopt;
+}
+
+[[nodiscard]] std::optional<std::string_view>
+instructionFlagEnumerator(std::string_view Value) noexcept {
+ static constexpr std::array<std::pair<std::string_view, std::string_view>, 23> Flags = {{
+ {"terminator", "Terminator"},
+ {"branch", "Branch"},
+ {"indirect_branch", "IndirectBranch"},
+ {"call", "Call"},
+ {"return", "Return"},
+ {"barrier", "Barrier"},
+ {"may_load", "MayLoad"},
+ {"may_store", "MayStore"},
+ {"has_side_effects", "HasSideEffects"},
+ {"pseudo", "Pseudo"},
+ {"meta", "Meta"},
+ {"allowed_pre_legalizer", "AllowedPreLegalizer"},
+ {"produces_code", "ProducesCode"},
+ {"commutable", "Commutable"},
+ {"rematerializable", "Rematerializable"},
+ {"predicable", "Predicable"},
+ {"not_duplicable", "NotDuplicable"},
+ {"convergent", "Convergent"},
+ {"unmodeled_side_effects", "UnmodeledSideEffects"},
+ {"phi", "Phi"},
+ {"fake_use", "FakeUse"},
+ {"generic", "Generic"},
+ {"terminal_emission", "TerminalEmission"},
+ }};
+ const auto *const Position = std::ranges::find(Flags, Value, &decltype(Flags)::value_type::first);
+ if (Position == Flags.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+[[nodiscard]] std::optional<std::string_view>
+pseudoOwnerEnumerator(std::string_view Value) noexcept {
+ static constexpr std::array<std::pair<std::string_view, std::string_view>, 12> Owners = {{
+ {"none", "None"},
+ {"legalizer", "Legalizer"},
+ {"register_bank_select", "RegisterBankSelect"},
+ {"instruction_select", "InstructionSelect"},
+ {"phi_elimination", "PhiElimination"},
+ {"two_address_rewrite", "TwoAddressRewrite"},
+ {"register_allocator", "RegisterAllocator"},
+ {"pre_pei_expansion", "PrePEIExpansion"},
+ {"prologue_epilogue_insertion", "PrologueEpilogueInsertion"},
+ {"post_ra_expansion", "PostRAExpansion"},
+ {"finalization", "Finalization"},
+ {"target_late_expansion", "TargetLateExpansion"},
+ }};
+ if (Value == "asm_printer")
+ return "AsmPrinter";
+ const auto *const Position =
+ std::ranges::find(Owners, Value, &decltype(Owners)::value_type::first);
+ if (Position == Owners.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+[[nodiscard]] std::optional<std::string_view>
+pipelineStageEnumerator(std::string_view Value) noexcept {
+ static constexpr std::array<std::pair<std::string_view, std::string_view>, 10> Stages = {{
+ {"machine_ir_input", "MachineIRInput"},
+ {"legalized", "Legalized"},
+ {"reg_bank_selected", "RegBankSelected"},
+ {"selected", "Selected"},
+ {"ssa_eliminated", "SSAEliminated"},
+ {"tied_ops_rewritten", "TiedOpsRewritten"},
+ {"register_allocated", "RegisterAllocated"},
+ {"frame_finalized", "FrameFinalized"},
+ {"post_ra_finalized", "PostRAFinalized"},
+ {"emission", "Emission"},
+ }};
+ const auto *const Position =
+ std::ranges::find(Stages, Value, &decltype(Stages)::value_type::first);
+ if (Position == Stages.end())
+ return std::nullopt;
+ return Position->second;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeMIROperandFlags(NormalizedTargetModel &Result) {
+ const SourceMIROperandFlags &Flags = Result.MIROperandFlags;
+ if (Flags.Direct.empty() != (Flags.DirectMask == 0)) {
+ fail(Flags.Range, "direct MIR operand flags and their direct mask must be present together");
+ return false;
+ }
+
+ std::unordered_set<std::string> Names;
+ std::unordered_set<std::uint32_t> DirectValues;
+ for (const SourceMIROperandFlag &Flag : Flags.Direct) {
+ if (Flag.Name.empty() || Flag.Value == 0 || (Flag.Value & ~Flags.DirectMask) != 0 ||
+ !Names.insert(Flag.Name).second || !DirectValues.insert(Flag.Value).second) {
+ fail(Flag.Range,
+ "direct MIR operand flags require unique names and nonzero values inside the mask");
+ return false;
+ }
+ }
+
+ std::uint32_t BitmaskValues = 0;
+ for (const SourceMIROperandFlag &Flag : Flags.Bitmask) {
+ if (Flag.Name.empty() || !std::has_single_bit(Flag.Value) ||
+ (Flag.Value & Flags.DirectMask) != 0 || !Names.insert(Flag.Name).second ||
+ (BitmaskValues & Flag.Value) != 0) {
+ fail(Flag.Range,
+ "bitmask MIR operand flags require unique names and distinct bits outside the "
+ "direct mask");
+ return false;
+ }
+ BitmaskValues |= Flag.Value;
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeNamedOperands(NormalizedTargetModel &Result) {
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceNamedOperand &Operand : Input.NamedOperands) {
+ if (Operand.ID == 0 || !isIdentifier(Operand.Symbol) || Operand.Name.empty() ||
+ !IDs.insert(Operand.ID).second || !Symbols.insert(Operand.Symbol).second ||
+ !Names.insert(Operand.Name).second) {
+ fail(Operand.Range, "named operands require nonzero unique IDs, symbols, and names");
+ return false;
+ }
+ NamedOperandBySymbol.emplace(Operand.Symbol, Operand.ID);
+ Result.NamedOperands.push_back({
+ .ID = Operand.ID,
+ .Symbol = Operand.Symbol,
+ .Name = Operand.Name,
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeImmediatePredicates(NormalizedTargetModel &Result) {
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceImmediatePredicate &Predicate : Input.ImmediatePredicates) {
+ const bool IsSigned = Predicate.Kind == "signed";
+ const bool IsUnsigned = Predicate.Kind == "unsigned";
+ if (Predicate.ID == 0 || !isIdentifier(Predicate.Symbol) || Predicate.Name.empty() ||
+ (!IsSigned && !IsUnsigned) || Predicate.Bits == 0 || Predicate.Bits > 63 ||
+ !isPowerOfTwo(Predicate.Alignment) || !IDs.insert(Predicate.ID).second ||
+ !Symbols.insert(Predicate.Symbol).second || !Names.insert(Predicate.Name).second) {
+ fail(Predicate.Range,
+ "immediate predicates require nonzero unique IDs, symbols, names, a signed or "
+ "unsigned kind, a width in [1, 63], and power-of-two alignment");
+ return false;
+ }
+ const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U);
+ const std::int64_t Minimum = IsSigned ? -PositiveLimit : 0;
+ const std::int64_t Maximum =
+ IsSigned ? PositiveLimit - 1
+ : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - 1U);
+ std::unordered_set<std::int64_t> AllowedValues;
+ for (const std::int64_t Value : Predicate.AllowedValues) {
+ if (Value < Minimum || Value > Maximum || (Predicate.NonZero && Value == 0) ||
+ !AllowedValues.insert(Value).second) {
+ fail(Predicate.Range,
+ "immediate predicate allowed_values must be unique, in range, and satisfy nonzero");
+ return false;
+ }
+ }
+ ImmediatePredicateBySymbol.emplace(Predicate.Symbol, Predicate.ID);
+ Result.ImmediatePredicates.push_back({
+ .ID = Predicate.ID,
+ .Symbol = Predicate.Symbol,
+ .Name = Predicate.Name,
+ .IsSigned = IsSigned,
+ .Bits = Predicate.Bits,
+ .Alignment = Predicate.Alignment,
+ .NonZero = Predicate.NonZero,
+ .AllowedValues = Predicate.AllowedValues,
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeOperand(const SourceOperand &InputOperand,
+ NormalizedOperand &Result) {
+ if (InputOperand.Kinds.empty()) {
+ fail(InputOperand.Range, "instruction operand requires at least one kind");
+ return false;
+ }
+ std::unordered_set<std::string> Kinds;
+ bool HasRegisterKind = false;
+ for (const std::string &Kind : InputOperand.Kinds) {
+ const auto Enumerator = operandKindEnumerator(Kind);
+ if (!Enumerator || !Kinds.insert(std::string(*Enumerator)).second) {
+ fail(InputOperand.Range, "instruction operand kinds must be known and unique");
+ return false;
+ }
+ HasRegisterKind |= *Enumerator == "Register";
+ Result.Kinds.push_back(std::string(*Enumerator));
+ }
+
+ const auto Role = registerRoleEnumerator(InputOperand.RegisterRole);
+ const auto MemoryRole = memoryRoleEnumerator(InputOperand.MemoryRole);
+ if (!Role || !MemoryRole || HasRegisterKind != (*Role != "None")) {
+ fail(InputOperand.Range,
+ "instruction operand register role and memory role must match its declared kinds");
+ return false;
+ }
+ Result.RegisterRole = std::string(*Role);
+ Result.MemoryRole = std::string(*MemoryRole);
+
+ const bool IsDefRole = *Role == "Def" || *Role == "DefUse";
+ const bool IsRegisterOnly = Result.Kinds.size() == 1 && Result.Kinds.front() == "Register";
+ if (IsDefRole && !IsRegisterOnly) {
+ fail(InputOperand.Range, "register definition operands must accept only registers");
+ return false;
+ }
+ if (!HasRegisterKind && (InputOperand.RegisterClassSymbol || InputOperand.RegisterBankSymbol ||
+ InputOperand.EarlyClobber || InputOperand.AllowSubReg)) {
+ fail(InputOperand.Range, "non-register operands cannot have register constraints");
+ return false;
+ }
+ if (InputOperand.EarlyClobber && !IsDefRole) {
+ fail(InputOperand.Range, "early-clobber operands must define a register");
+ return false;
+ }
+
+ if (InputOperand.RegisterClassSymbol) {
+ const auto Position = ClassBySymbol.find(*InputOperand.RegisterClassSymbol);
+ if (Position == ClassBySymbol.end()) {
+ fail(InputOperand.Range, "instruction operand names an unknown register class");
+ return false;
+ }
+ Result.RegisterClass = Position->second;
+ }
+ if (InputOperand.RegisterBankSymbol) {
+ const auto Position = BankBySymbol.find(*InputOperand.RegisterBankSymbol);
+ if (Position == BankBySymbol.end()) {
+ fail(InputOperand.Range, "instruction operand names an unknown register bank");
+ return false;
+ }
+ Result.RegisterBank = Position->second;
+ }
+ if (InputOperand.ImmediatePredicateSymbol) {
+ const auto Position = ImmediatePredicateBySymbol.find(*InputOperand.ImmediatePredicateSymbol);
+ if (Position == ImmediatePredicateBySymbol.end() ||
+ std::ranges::find(Result.Kinds, "Immediate") == Result.Kinds.end()) {
+ fail(InputOperand.Range,
+ "instruction immediate predicates must name a declaration and constrain an immediate");
+ return false;
+ }
+ Result.ImmediatePredicate = Position->second;
+ }
+ if (InputOperand.NameSymbol) {
+ const auto Position = NamedOperandBySymbol.find(*InputOperand.NameSymbol);
+ if (Position == NamedOperandBySymbol.end()) {
+ fail(InputOperand.Range, "instruction operand names an unknown named operand");
+ return false;
+ }
+ Result.Name = Position->second;
+ }
+
+ Result.DefaultTiedOperand = InputOperand.DefaultTiedOperand;
+ Result.EarlyClobber = InputOperand.EarlyClobber;
+ Result.Optional = InputOperand.Optional;
+ Result.VariadicGroup = InputOperand.VariadicGroup;
+ Result.AllowSubReg = InputOperand.AllowSubReg;
+ Result.AllowedTargetFlags = InputOperand.AllowedTargetFlags;
+ return true;
+}
+
+[[nodiscard]] bool
+CodeGenTarget::normalizeImplicitRegisters(const std::vector<SourceImplicitRegister> &InputRegisters,
+ bool IsDef,
+ std::vector<NormalizedImplicitRegister> &Result) {
+ std::unordered_set<std::uint32_t> Registers;
+ for (const SourceImplicitRegister &Register : InputRegisters) {
+ const auto Position = RegisterBySymbol.find(Register.RegisterSymbol);
+ if (Position == RegisterBySymbol.end() || !Registers.insert(Position->second).second ||
+ (!IsDef && Register.EarlyClobber)) {
+ fail(Register.Range,
+ "implicit registers must be declared and unique; implicit uses cannot early-clobber");
+ return false;
+ }
+ Result.push_back({
+ .Register = Position->second,
+ .EarlyClobber = Register.EarlyClobber,
+ .Renamable = Register.Renamable,
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeInstructions(NormalizedTargetModel &Result) {
+ if (Input.Instructions.empty()) {
+ fail(Input.Target.Range, "target description requires at least one instruction");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceInstruction &Instruction : Input.Instructions) {
+ const bool HasMnemonic = !Instruction.AssemblyMnemonic.empty();
+ const bool HasBinaryEncoding = Instruction.BinaryEncoding.has_value();
+ const bool HasSchedulingClass = !Instruction.SchedulingClassSymbol.empty();
+ const bool HasEncodingCategory = !Instruction.EncodingCategorySymbol.empty();
+ const bool HasAssemblyFormat = !Instruction.AssemblyFormatSymbol.empty();
+ const bool HasAllMachineFacts = HasMnemonic && HasBinaryEncoding && HasSchedulingClass &&
+ HasEncodingCategory && HasAssemblyFormat;
+ const bool HasAnyMachineFact = HasMnemonic || HasBinaryEncoding || HasSchedulingClass ||
+ HasEncodingCategory || HasAssemblyFormat;
+ const bool IsPseudoSource =
+ std::ranges::find(Instruction.Flags, "pseudo") != Instruction.Flags.end();
+ const bool IsTerminalEmissionSource =
+ std::ranges::find(Instruction.Flags, "terminal_emission") != Instruction.Flags.end();
+ if (HasAnyMachineFact != HasAllMachineFacts ||
+ ((!IsPseudoSource || IsTerminalEmissionSource) && !HasAllMachineFacts)) {
+ fail(Instruction.Range,
+ "encoded instructions and terminal-emission pseudos require complete scheduling, "
+ "encoding, mnemonic, and assembly-format facts");
+ return false;
+ }
+ const auto SchedulingClass = SchedulingClassBySymbol.find(Instruction.SchedulingClassSymbol);
+ const auto EncodingCategory = EncodingCategoryBySymbol.find(Instruction.EncodingCategorySymbol);
+ const auto AssemblyFormat = AssemblyFormatBySymbol.find(Instruction.AssemblyFormatSymbol);
+ const auto Owner = pseudoOwnerEnumerator(Instruction.Owner);
+ const auto Deadline = pipelineStageEnumerator(Instruction.Deadline);
+ if (Instruction.ID == 0 || !isIdentifier(Instruction.Symbol) || Instruction.Name.empty() ||
+ (HasMnemonic && !isAssemblyMnemonic(Instruction.AssemblyMnemonic)) ||
+ (HasSchedulingClass && SchedulingClass == SchedulingClassBySymbol.end()) ||
+ (HasEncodingCategory && EncodingCategory == EncodingCategoryBySymbol.end()) ||
+ (HasAssemblyFormat && AssemblyFormat == AssemblyFormatBySymbol.end()) || !Owner ||
+ !Deadline || !IDs.insert(Instruction.ID).second ||
+ !Symbols.insert(Instruction.Symbol).second || !Names.insert(Instruction.Name).second) {
+ fail(Instruction.Range,
+ "instructions require nonzero unique IDs, symbols, lowercase assembly mnemonics, and "
+ "declared format IDs");
+ return false;
+ }
+ if (Instruction.NumExplicitDefs > Instruction.Operands.size()) {
+ fail(Instruction.Range, "instruction explicit def count is outside its operand range");
+ return false;
+ }
+
+ NormalizedInstruction Normalized{
+ .ID = Instruction.ID,
+ .Symbol = Instruction.Symbol,
+ .Name = Instruction.Name,
+ .AssemblyMnemonic = Instruction.AssemblyMnemonic,
+ .BinaryEncoding = Instruction.BinaryEncoding.value_or(0),
+ .Operands = {},
+ .NumExplicitDefs = Instruction.NumExplicitDefs,
+ .Variadic = Instruction.Variadic,
+ .ImplicitDefs = {},
+ .ImplicitUses = {},
+ .AllowExtraImplicitOperands = Instruction.AllowExtraImplicitOperands,
+ .Flags = {},
+ .SchedulingClass = HasSchedulingClass ? SchedulingClass->second : 0,
+ .EncodingCategory = HasEncodingCategory ? EncodingCategory->second : 0,
+ .AssemblyFormat = HasAssemblyFormat ? AssemblyFormat->second : 0,
+ .RequiresMemoryOperands = Instruction.RequiresMemoryOperands,
+ .Owner = std::string(*Owner),
+ .Deadline = std::string(*Deadline),
+ };
+ Normalized.Operands.reserve(Instruction.Operands.size());
+ bool SawOptional = false;
+ for (std::size_t Index = 0; Index < Instruction.Operands.size(); ++Index) {
+ NormalizedOperand Operand;
+ if (!normalizeOperand(Instruction.Operands[Index], Operand))
+ return false;
+ const bool IsDef = Operand.RegisterRole == "Def" || Operand.RegisterRole == "DefUse";
+ if ((Index < Instruction.NumExplicitDefs) != IsDef) {
+ fail(Instruction.Operands[Index].Range,
+ "instruction explicit register definitions must form the leading operand prefix");
+ return false;
+ }
+ if (Instruction.Variadic && Operand.Optional) {
+ fail(Instruction.Operands[Index].Range,
+ "variadic instructions cannot contain optional operands");
+ return false;
+ }
+ if (Operand.Optional)
+ SawOptional = true;
+ else if (SawOptional && !Operand.VariadicGroup) {
+ fail(Instruction.Operands[Index].Range,
+ "optional instruction operands must form a trailing suffix");
+ return false;
+ }
+ if (Operand.VariadicGroup &&
+ (!Instruction.Variadic || Index + 1 != Instruction.Operands.size())) {
+ fail(Instruction.Operands[Index].Range,
+ "a variadic operand group must be the final instruction operand");
+ return false;
+ }
+ if (Operand.VariadicGroup && (Operand.Optional || Operand.DefaultTiedOperand)) {
+ fail(Instruction.Operands[Index].Range,
+ "a variadic operand group cannot be optional or have a default tie");
+ return false;
+ }
+ Normalized.Operands.push_back(std::move(Operand));
+ }
+ if (Instruction.Variadic &&
+ (Normalized.Operands.empty() || !Normalized.Operands.back().VariadicGroup)) {
+ fail(Instruction.Range, "variadic instructions require a final variadic operand group");
+ return false;
+ }
+ if (!Instruction.Variadic &&
+ std::ranges::any_of(Normalized.Operands, [](const NormalizedOperand &Operand) {
+ return Operand.VariadicGroup;
+ })) {
+ fail(Instruction.Range, "non-variadic instructions cannot have a variadic operand group");
+ return false;
+ }
+
+ std::unordered_set<std::uint32_t> NamedOperands;
+ for (std::size_t Index = 0; Index < Normalized.Operands.size(); ++Index) {
+ const NormalizedOperand &Operand = Normalized.Operands[Index];
+ if (Operand.Name && !NamedOperands.insert(*Operand.Name).second) {
+ fail(Instruction.Operands[Index].Range,
+ "instruction operands must use distinct named operand IDs");
+ return false;
+ }
+ if (!Operand.DefaultTiedOperand)
+ continue;
+ const std::uint32_t OtherIndex = *Operand.DefaultTiedOperand;
+ if (OtherIndex >= Normalized.Operands.size() || OtherIndex == Index ||
+ !Normalized.Operands[OtherIndex].DefaultTiedOperand ||
+ *Normalized.Operands[OtherIndex].DefaultTiedOperand != Index) {
+ fail(Instruction.Operands[Index].Range,
+ "instruction default ties must be reciprocal and in range");
+ return false;
+ }
+ const NormalizedOperand &Other = Normalized.Operands[OtherIndex];
+ const bool IsRegisterOnly = Operand.Kinds.size() == 1 && Operand.Kinds.front() == "Register";
+ const bool OtherIsRegisterOnly = Other.Kinds.size() == 1 && Other.Kinds.front() == "Register";
+ const bool IsDefUsePair = (Operand.RegisterRole == "Def" && Other.RegisterRole == "Use") ||
+ (Operand.RegisterRole == "Use" && Other.RegisterRole == "Def");
+ if (Operand.Optional || Other.Optional || !IsRegisterOnly || !OtherIsRegisterOnly ||
+ !IsDefUsePair) {
+ fail(Instruction.Operands[Index].Range,
+ "instruction default ties must connect required register defs and uses");
+ return false;
+ }
+ }
+
+ std::unordered_set<std::string> Flags;
+ for (const std::string &Flag : Instruction.Flags) {
+ const auto Enumerator = instructionFlagEnumerator(Flag);
+ if (!Enumerator || !Flags.insert(std::string(*Enumerator)).second) {
+ fail(Instruction.Range, "instruction flags must be known and unique");
+ return false;
+ }
+ Normalized.Flags.push_back(std::string(*Enumerator));
+ }
+ const auto HasFlag = [&Normalized](std::string_view Flag) {
+ return std::ranges::find(Normalized.Flags, Flag) != Normalized.Flags.end();
+ };
+ const bool IsTerminator = HasFlag("Terminator");
+ if ((HasFlag("Branch") || HasFlag("Return") || HasFlag("Barrier") ||
+ HasFlag("IndirectBranch")) &&
+ !IsTerminator) {
+ fail(Instruction.Range, "branch, return, and barrier instructions must be terminators");
+ return false;
+ }
+ if (HasFlag("IndirectBranch") && !HasFlag("Branch")) {
+ fail(Instruction.Range, "indirect branch instructions must also be branches");
+ return false;
+ }
+ const bool IsPseudo = HasFlag("Pseudo");
+ const bool IsMeta = HasFlag("Meta");
+ if ((IsMeta && (!IsPseudo || HasFlag("ProducesCode"))) ||
+ (!IsPseudo && Normalized.Owner != "None") ||
+ (IsPseudo && !IsMeta && Normalized.Owner == "None") ||
+ (HasFlag("TerminalEmission") &&
+ (!IsPseudo || Normalized.Owner != "AsmPrinter" || !HasFlag("ProducesCode")))) {
+ fail(Instruction.Range, "instruction pseudo flags, owner, and deadline are inconsistent");
+ return false;
+ }
+ if (Normalized.RequiresMemoryOperands && !HasFlag("MayLoad") && !HasFlag("MayStore")) {
+ fail(Instruction.Range, "memory operand requirements need a load or store instruction flag");
+ return false;
+ }
+ if (!normalizeImplicitRegisters(Instruction.ImplicitDefs, true, Normalized.ImplicitDefs) ||
+ !normalizeImplicitRegisters(Instruction.ImplicitUses, false, Normalized.ImplicitUses)) {
+ return false;
+ }
+ Result.Instructions.push_back(std::move(Normalized));
+ }
+ std::ranges::sort(Result.Instructions, {}, &NormalizedInstruction::ID);
+ return true;
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenRegisters.cpp b/backend/utils/TableGen/Common/CodeGenRegisters.cpp
new file mode 100644
index 0000000..839196e
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenRegisters.cpp
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include <algorithm>
+#include <limits>
+#include <ranges>
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::targetgen {
+
+[[nodiscard]] bool CodeGenTarget::normalizeRegisterUnits() {
+ if (Input.RegisterUnits.empty()) {
+ fail(Input.Target.Range, "target description requires at least one register unit");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceRegisterUnit &Unit : Input.RegisterUnits) {
+ if (Unit.ID == 0 || !isIdentifier(Unit.Symbol) || Unit.Name.empty() ||
+ !IDs.insert(Unit.ID).second || !Symbols.insert(Unit.Symbol).second ||
+ !Names.insert(lowerASCII(Unit.Name)).second) {
+ fail(Unit.Range,
+ "register units require nonzero unique IDs, unique symbols, and unique names");
+ return false;
+ }
+ UnitBySymbol.emplace(Unit.Symbol, Unit.ID);
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeRegisters(NormalizedTargetModel &Result) {
+ if (Input.Registers.empty()) {
+ fail(Input.Target.Range, "target description requires at least one physical register");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceRegister &Register : Input.Registers) {
+ if (Register.ID == 0 || !isIdentifier(Register.Symbol) || Register.Name.empty() ||
+ !IDs.insert(Register.ID).second || !Symbols.insert(Register.Symbol).second ||
+ !Names.insert(lowerASCII(Register.Name)).second || Register.Units.empty()) {
+ fail(Register.Range,
+ "registers require nonzero unique IDs, symbols, canonical names, and units");
+ return false;
+ }
+ RegisterBySymbol.emplace(Register.Symbol, Register.ID);
+ NormalizedRegister Normalized{
+ .ID = Register.ID,
+ .Symbol = Register.Symbol,
+ .Name = Register.Name,
+ .Encoding = Register.Encoding,
+ .DwarfRegister = Register.DwarfRegister,
+ .Units = {},
+ .IsConstant = Register.IsConstant,
+ .IsArtificial = Register.IsArtificial,
+ .IsReserved = Register.IsReserved,
+ .AlternativeNames = Register.AlternativeNames,
+ };
+ std::unordered_set<std::uint32_t> Units;
+ for (const SourceRegisterUnitLane &Unit : Register.Units) {
+ const auto Position = UnitBySymbol.find(Unit.UnitSymbol);
+ if (Position == UnitBySymbol.end() || Unit.Lanes == 0 ||
+ !Units.insert(Position->second).second) {
+ fail(Unit.Range,
+ "register units must name distinct declared units with nonzero lane masks");
+ return false;
+ }
+ Normalized.Units.push_back({.UnitID = Position->second, .Lanes = Unit.Lanes});
+ }
+ for (const std::string &Alias : Register.AlternativeNames) {
+ if (Alias.empty() || !Names.insert(lowerASCII(Alias)).second) {
+ fail(Register.Range,
+ "register ABI aliases must be nonempty and unique across all register names");
+ return false;
+ }
+ }
+ Result.Registers.push_back(std::move(Normalized));
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeRegisterBanks(NormalizedTargetModel &Result) {
+ if (Input.RegisterBanks.empty()) {
+ fail(Input.Target.Range, "target description requires at least one register bank");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceRegisterBank &Bank : Input.RegisterBanks) {
+ if (Bank.ID == 0 || !isIdentifier(Bank.Symbol) || Bank.Name.empty() ||
+ Bank.MaximumSizeInBits == 0 || !IDs.insert(Bank.ID).second ||
+ !Symbols.insert(Bank.Symbol).second || !Names.insert(lowerASCII(Bank.Name)).second) {
+ fail(Bank.Range,
+ "register banks require nonzero unique IDs, symbols, names, and maximum sizes");
+ return false;
+ }
+ BankBySymbol.emplace(Bank.Symbol, Bank.ID);
+ Result.RegisterBanks.push_back({
+ .ID = Bank.ID,
+ .Symbol = Bank.Symbol,
+ .Name = Bank.Name,
+ .MaximumSizeInBits = Bank.MaximumSizeInBits,
+ .RegisterClasses = {},
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeRegisterClasses(NormalizedTargetModel &Result) {
+ if (Input.RegisterClasses.empty()) {
+ fail(Input.Target.Range, "target description requires at least one register class");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceRegisterClass &Class : Input.RegisterClasses) {
+ const auto Bank = BankBySymbol.find(Class.BankSymbol);
+ if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() ||
+ Bank == BankBySymbol.end() || Class.SizeInBits == 0 || Class.SpillSizeInBits == 0 ||
+ Class.SpillAlignmentInBits == 0 || Class.Lanes == 0 || Class.RegisterSymbols.empty() ||
+ !IDs.insert(Class.ID).second || !Symbols.insert(Class.Symbol).second ||
+ !Names.insert(lowerASCII(Class.Name)).second) {
+ fail(Class.Range, "register classes require unique IDs, symbols, names, a declared bank, "
+ "sizes, lanes, and members");
+ return false;
+ }
+ ClassBySymbol.emplace(Class.Symbol, Class.ID);
+ NormalizedRegisterClass Normalized{
+ .ID = Class.ID,
+ .Symbol = Class.Symbol,
+ .Name = Class.Name,
+ .BankID = Bank->second,
+ .SizeInBits = Class.SizeInBits,
+ .SpillSizeInBits = Class.SpillSizeInBits,
+ .SpillAlignmentInBits = Class.SpillAlignmentInBits,
+ .Lanes = Class.Lanes,
+ .Registers = {},
+ .AllocationOrder = {},
+ .Allocatable = Class.Allocatable,
+ .CopyCost = Class.CopyCost,
+ };
+ std::unordered_set<std::uint32_t> Members;
+ for (const std::string &Symbol : Class.RegisterSymbols) {
+ const auto Register = RegisterBySymbol.find(Symbol);
+ if (Register == RegisterBySymbol.end() || !Members.insert(Register->second).second) {
+ fail(Class.Range, "register class members must reference distinct declared registers");
+ return false;
+ }
+ Normalized.Registers.push_back(Register->second);
+ }
+ std::unordered_set<std::uint32_t> Allocation;
+ for (const std::string &Symbol : Class.AllocationOrderSymbols) {
+ const auto Register = RegisterBySymbol.find(Symbol);
+ if (Register == RegisterBySymbol.end() || !Members.contains(Register->second) ||
+ !Allocation.insert(Register->second).second) {
+ fail(Class.Range,
+ "register class allocation order must contain distinct members of the same class");
+ return false;
+ }
+ Normalized.AllocationOrder.push_back(Register->second);
+ }
+ if ((Normalized.Allocatable && Normalized.AllocationOrder.empty()) ||
+ (!Normalized.Allocatable && !Normalized.AllocationOrder.empty())) {
+ fail(Class.Range, "allocatable register classes require an allocation order and "
+ "nonallocatable classes forbid one");
+ return false;
+ }
+ Result.RegisterClasses.push_back(std::move(Normalized));
+ }
+ for (NormalizedRegisterBank &Bank : Result.RegisterBanks) {
+ for (const NormalizedRegisterClass &Class : Result.RegisterClasses) {
+ if (Class.BankID == Bank.ID)
+ Bank.RegisterClasses.push_back(Class.ID);
+ }
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeRegisterMasks(NormalizedTargetModel &Result) {
+ std::unordered_set<std::string> Names;
+ for (const SourceRegisterMask &Mask : Input.RegisterMasks) {
+ if (Mask.Name.empty() || !Names.insert(Mask.Name).second) {
+ fail(Mask.Range, "register mask names must be nonempty and unique");
+ return false;
+ }
+ NormalizedRegisterMask Normalized{.Name = Mask.Name, .PreservedRegisters = {}};
+ std::unordered_set<std::uint32_t> Preserved;
+ for (const std::string &Symbol : Mask.PreservedRegisterSymbols) {
+ const auto Register = RegisterBySymbol.find(Symbol);
+ if (Register == RegisterBySymbol.end() || !Preserved.insert(Register->second).second) {
+ fail(Mask.Range, "register mask entries must name distinct declared registers");
+ return false;
+ }
+ Normalized.PreservedRegisters.push_back(Register->second);
+ }
+ Result.RegisterMasks.push_back(std::move(Normalized));
+ }
+ return true;
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenSchedule.cpp b/backend/utils/TableGen/Common/CodeGenSchedule.cpp
new file mode 100644
index 0000000..56dbb5d
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenSchedule.cpp
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include <ranges>
+#include <string>
+#include <unordered_set>
+
+namespace rcc::targetgen {
+
+[[nodiscard]] bool CodeGenTarget::normalizeSchedulingClasses(NormalizedTargetModel &Result) {
+ if (Input.SchedulingClasses.empty()) {
+ fail(Input.Target.Range, "target description requires at least one scheduling class");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceSchedulingClass &Class : Input.SchedulingClasses) {
+ if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() || Class.MicroOps == 0 ||
+ Class.Latency == 0 || !IDs.insert(Class.ID).second ||
+ !Symbols.insert(Class.Symbol).second || !Names.insert(Class.Name).second) {
+ fail(Class.Range,
+ "scheduling classes require nonzero unique IDs, symbols, names, micro-op counts, "
+ "and latencies");
+ return false;
+ }
+ SchedulingClassBySymbol.emplace(Class.Symbol, Class.ID);
+ Result.SchedulingClasses.push_back({
+ .ID = Class.ID,
+ .Symbol = Class.Symbol,
+ .Name = Class.Name,
+ .MicroOps = Class.MicroOps,
+ .Latency = Class.Latency,
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeEncodingCategories(NormalizedTargetModel &Result) {
+ if (Input.EncodingCategories.empty()) {
+ fail(Input.Target.Range, "target description requires at least one encoding category");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceEncodingCategory &Category : Input.EncodingCategories) {
+ if (Category.ID == 0 || !isIdentifier(Category.Symbol) || Category.Name.empty() ||
+ Category.SizeInBytes == 0 || !IDs.insert(Category.ID).second ||
+ !Symbols.insert(Category.Symbol).second || !Names.insert(Category.Name).second) {
+ fail(Category.Range,
+ "encoding categories require nonzero unique IDs, symbols, names, and sizes");
+ return false;
+ }
+ EncodingCategoryBySymbol.emplace(Category.Symbol, Category.ID);
+ Result.EncodingCategories.push_back({
+ .ID = Category.ID,
+ .Symbol = Category.Symbol,
+ .Name = Category.Name,
+ .SizeInBytes = Category.SizeInBytes,
+ });
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeAssemblyFormats(NormalizedTargetModel &Result) {
+ if (Input.AssemblyFormats.empty()) {
+ fail(Input.Target.Range, "target description requires at least one assembly format");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceAssemblyFormat &Format : Input.AssemblyFormats) {
+ if (Format.ID == 0 || !isIdentifier(Format.Symbol) || Format.Name.empty() ||
+ !IDs.insert(Format.ID).second || !Symbols.insert(Format.Symbol).second ||
+ !Names.insert(Format.Name).second) {
+ fail(Format.Range, "assembly formats require nonzero unique IDs, symbols, and names");
+ return false;
+ }
+ AssemblyFormatBySymbol.emplace(Format.Symbol, Format.ID);
+ Result.AssemblyFormats.push_back({
+ .ID = Format.ID,
+ .Symbol = Format.Symbol,
+ .Name = Format.Name,
+ });
+ }
+ return true;
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenTarget.cpp b/backend/utils/TableGen/Common/CodeGenTarget.cpp
new file mode 100644
index 0000000..89fea08
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenTarget.cpp
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include "TableGenBackends.h"
+#include "rcc/TableGen/Parser.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::targetgen {
+
+[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept {
+ return Value != 0 && (Value & (Value - 1U)) == 0;
+}
+
+CodeGenTarget::CodeGenTarget(const DescriptionAST &Input, std::string SourceName)
+ : Input(Input), SourceName(std::move(SourceName)) {}
+
+[[nodiscard]] std::optional<NormalizedTargetModel> CodeGenTarget::normalize() {
+ if (!validateTarget())
+ return std::nullopt;
+
+ NormalizedTargetModel Result;
+ Result.Target = Input.Target;
+ Result.RegisterUnits = Input.RegisterUnits;
+ Result.MIROperandFlags = Input.MIROperandFlags;
+ if (!normalizeRegisterUnits() || !normalizeRegisters(Result) || !normalizeRegisterBanks(Result) ||
+ !normalizeRegisterClasses(Result) || !normalizeRegisterMasks(Result) ||
+ !normalizeMIROperandFlags(Result) || !normalizeCallingConventions(Result) ||
+ !normalizeNamedOperands(Result) || !normalizeImmediatePredicates(Result) ||
+ !normalizeSchedulingClasses(Result) || !normalizeEncodingCategories(Result) ||
+ !normalizeAssemblyFormats(Result) || !normalizeInstructions(Result)) {
+ return std::nullopt;
+ }
+ std::ranges::sort(Result.RegisterUnits, {}, &SourceRegisterUnit::ID);
+ std::ranges::sort(Result.Registers, {}, &NormalizedRegister::ID);
+ std::ranges::sort(Result.RegisterBanks, {}, &NormalizedRegisterBank::ID);
+ std::ranges::sort(Result.RegisterClasses, {}, &NormalizedRegisterClass::ID);
+ std::ranges::sort(Result.CallingConventions, {}, &NormalizedCallingConvention::ID);
+ std::ranges::sort(Result.NamedOperands, {}, &NormalizedNamedOperand::ID);
+ std::ranges::sort(Result.ImmediatePredicates, {}, &NormalizedImmediatePredicate::ID);
+ std::ranges::sort(Result.SchedulingClasses, {}, &NormalizedSchedulingClass::ID);
+ std::ranges::sort(Result.EncodingCategories, {}, &NormalizedEncodingCategory::ID);
+ std::ranges::sort(Result.AssemblyFormats, {}, &NormalizedAssemblyFormat::ID);
+ return Result;
+}
+
+[[nodiscard]] std::vector<TextDiagnostic> CodeGenTarget::takeDiagnostics() {
+ return std::move(Diagnostics);
+}
+
+void CodeGenTarget::fail(TextRange Range, std::string Message) {
+ Diagnostics.push_back({
+ .Severity = DiagnosticSeverity::Error,
+ .Category = "target-description",
+ .Message = std::move(Message),
+ .SourceName = SourceName,
+ .Range = Range,
+ });
+}
+
+[[nodiscard]] bool CodeGenTarget::validateTarget() {
+ if (!isIdentifier(Input.Target.Namespace)) {
+ fail(Input.Target.Range, "target namespace must be a C++ identifier");
+ return false;
+ }
+ if (Input.Target.Name.empty() || Input.Target.Triple.empty() || Input.Target.CPU.empty() ||
+ Input.Target.ABI.empty() || Input.Target.DataLayout.empty() ||
+ Input.Target.Features.empty()) {
+ fail(Input.Target.Range, "target metadata fields and feature list must be nonempty");
+ return false;
+ }
+ std::unordered_set<std::string> Features;
+ for (const std::string &Feature : Input.Target.Features) {
+ if (Feature.empty() || !Features.insert(Feature).second) {
+ fail(Input.Target.Range, "target feature names must be nonempty and unique");
+ return false;
+ }
+ }
+ return true;
+}
+
+[[nodiscard]] bool CodeGenTarget::normalizeCallingConventions(NormalizedTargetModel &Result) {
+ if (Input.CallingConventions.empty()) {
+ fail(Input.Target.Range, "target description requires at least one calling convention");
+ return false;
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ for (const SourceCallingConvention &Convention : Input.CallingConventions) {
+ const auto IntegerClass = ClassBySymbol.find(Convention.IntegerRegisterClassSymbol);
+ const auto FloatingClass = ClassBySymbol.find(Convention.FloatingRegisterClassSymbol);
+ const auto PreservedMask = std::ranges::find(Result.RegisterMasks, Convention.PreservedMaskName,
+ &NormalizedRegisterMask::Name);
+ if (Convention.ID == 0 || !isIdentifier(Convention.Symbol) || Convention.Name.empty() ||
+ IntegerClass == ClassBySymbol.end() || FloatingClass == ClassBySymbol.end() ||
+ PreservedMask == Result.RegisterMasks.end() || Convention.StackAlignmentBytes == 0 ||
+ Convention.StackArgumentAlignmentBytes == 0 ||
+ !isPowerOfTwo(Convention.StackAlignmentBytes) ||
+ !isPowerOfTwo(Convention.StackArgumentAlignmentBytes) ||
+ Convention.StackAlignmentBytes % Convention.StackArgumentAlignmentBytes != 0 ||
+ Convention.ShadowSpaceBytes % Convention.StackArgumentAlignmentBytes != 0 ||
+ !IDs.insert(Convention.ID).second || !Symbols.insert(Convention.Symbol).second ||
+ !Names.insert(Convention.Name).second) {
+ fail(Convention.Range,
+ "calling conventions require unique IDs, symbols, names, declared register classes and "
+ "preserved masks, compatible power-of-two stack alignments, and aligned shadow space");
+ return false;
+ }
+ const auto IntegerClassDescription = std::ranges::find(
+ Result.RegisterClasses, IntegerClass->second, &NormalizedRegisterClass::ID);
+ const auto FloatingClassDescription = std::ranges::find(
+ Result.RegisterClasses, FloatingClass->second, &NormalizedRegisterClass::ID);
+ if (IntegerClassDescription == Result.RegisterClasses.end() ||
+ FloatingClassDescription == Result.RegisterClasses.end()) {
+ fail(Convention.Range, "calling convention register classes must resolve");
+ return false;
+ }
+ const auto NormalizeRegisterList = [this,
+ &Convention](const std::vector<std::string> &Symbols,
+ const NormalizedRegisterClass &RegisterClass,
+ std::vector<std::uint32_t> &Output) {
+ std::unordered_set<std::uint32_t> Registers;
+ for (const std::string &Symbol : Symbols) {
+ const auto Register = RegisterBySymbol.find(Symbol);
+ if (Register == RegisterBySymbol.end() ||
+ !std::ranges::contains(RegisterClass.Registers, Register->second) ||
+ !Registers.insert(Register->second).second) {
+ fail(Convention.Range,
+ "calling convention register lists must contain distinct members of their class");
+ return false;
+ }
+ Output.push_back(Register->second);
+ }
+ return true;
+ };
+ NormalizedCallingConvention Normalized{
+ .ID = Convention.ID,
+ .Symbol = Convention.Symbol,
+ .Name = Convention.Name,
+ .IntegerRegisterClass = IntegerClass->second,
+ .FloatingRegisterClass = FloatingClass->second,
+ .IntegerArgumentRegisters = {},
+ .FloatingArgumentRegisters = {},
+ .IntegerReturnRegisters = {},
+ .FloatingReturnRegisters = {},
+ .PreservedMaskName = Convention.PreservedMaskName,
+ .StackAlignmentBytes = Convention.StackAlignmentBytes,
+ .StackArgumentAlignmentBytes = Convention.StackArgumentAlignmentBytes,
+ .ShadowSpaceBytes = Convention.ShadowSpaceBytes,
+ .SupportsVarArgs = Convention.SupportsVarArgs,
+ .SupportsTailCalls = Convention.SupportsTailCalls,
+ };
+ if (!NormalizeRegisterList(Convention.IntegerArgumentRegisterSymbols, *IntegerClassDescription,
+ Normalized.IntegerArgumentRegisters) ||
+ !NormalizeRegisterList(Convention.FloatingArgumentRegisterSymbols,
+ *FloatingClassDescription, Normalized.FloatingArgumentRegisters) ||
+ !NormalizeRegisterList(Convention.IntegerReturnRegisterSymbols, *IntegerClassDescription,
+ Normalized.IntegerReturnRegisters) ||
+ !NormalizeRegisterList(Convention.FloatingReturnRegisterSymbols, *FloatingClassDescription,
+ Normalized.FloatingReturnRegisters)) {
+ return false;
+ }
+ CallingConventionBySymbol.emplace(Convention.Symbol, Convention.ID);
+ Result.CallingConventions.push_back(std::move(Normalized));
+ }
+ return true;
+}
+
+[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model,
+ std::uint32_t ID) {
+ const auto Position = std::ranges::find(Model.Registers, ID, &NormalizedRegister::ID);
+ return *Position;
+}
+[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model,
+ std::uint32_t ID) {
+ const auto Position = std::ranges::find(Model.RegisterUnits, ID, &SourceRegisterUnit::ID);
+ return *Position;
+}
+[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model,
+ std::uint32_t ID) {
+ const auto Position = std::ranges::find(Model.RegisterBanks, ID, &NormalizedRegisterBank::ID);
+ return *Position;
+}
+
+DescriptionGenerationResult generateDescription(const std::string &SourceName,
+ std::string SourceText) {
+ DescriptionParseResult ParseResult = parseDescription(SourceName, std::move(SourceText));
+ if (!ParseResult.Records)
+ return {.Output = std::nullopt, .Diagnostics = std::move(ParseResult.Diagnostics)};
+
+ CodeGenTarget Target(*ParseResult.Records, SourceName);
+ auto Model = Target.normalize();
+ if (!Model)
+ return {.Output = std::nullopt, .Diagnostics = Target.takeDiagnostics()};
+
+ const std::size_t Separator = SourceName.find_last_of("/\\");
+ const std::string_view DisplayName = Separator == std::string::npos
+ ? std::string_view(SourceName)
+ : std::string_view(SourceName).substr(Separator + 1);
+ return {
+ .Output =
+ GeneratedDescription{
+ .RegisterHeader = emitRegisterHeader(*Model, DisplayName),
+ .InstructionHeader = emitInstructionHeader(*Model, DisplayName),
+ .TargetTables = emitRegisterInfo(*Model, DisplayName) + emitInstructionInfo(*Model) +
+ emitSubtarget(*Model) + emitCodeEmitter(*Model) +
+ emitAssemblyWriter(*Model) + emitSearchableTables(*Model) +
+ emitCallingConventions(*Model),
+ },
+ .Diagnostics = {},
+ };
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenTarget.h b/backend/utils/TableGen/Common/CodeGenTarget.h
new file mode 100644
index 0000000..cc2697c
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenTarget.h
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// The private header follows the source-tree ownership of LLVM TableGen Common.
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H
+#define RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H
+
+#include "rcc/TableGen/Record.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+namespace rcc::targetgen {
+
+struct NormalizedRegisterUnitLane final {
+ std::uint32_t UnitID = 0;
+ std::uint64_t Lanes = 0;
+};
+
+struct NormalizedRegister final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t Encoding = 0;
+ std::uint32_t DwarfRegister = 0;
+ std::vector<NormalizedRegisterUnitLane> Units;
+ bool IsConstant = false;
+ bool IsArtificial = false;
+ bool IsReserved = false;
+ std::vector<std::string> AlternativeNames;
+};
+
+struct NormalizedRegisterBank final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t MaximumSizeInBits = 0;
+ std::vector<std::uint32_t> RegisterClasses;
+};
+
+struct NormalizedRegisterClass final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t BankID = 0;
+ std::uint32_t SizeInBits = 0;
+ std::uint32_t SpillSizeInBits = 0;
+ std::uint32_t SpillAlignmentInBits = 0;
+ std::uint64_t Lanes = 0;
+ std::vector<std::uint32_t> Registers;
+ std::vector<std::uint32_t> AllocationOrder;
+ bool Allocatable = true;
+ std::uint32_t CopyCost = 1;
+};
+
+struct NormalizedRegisterMask final {
+ std::string Name;
+ std::vector<std::uint32_t> PreservedRegisters;
+};
+
+struct NormalizedCallingConvention final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t IntegerRegisterClass = 0;
+ std::uint32_t FloatingRegisterClass = 0;
+ std::vector<std::uint32_t> IntegerArgumentRegisters;
+ std::vector<std::uint32_t> FloatingArgumentRegisters;
+ std::vector<std::uint32_t> IntegerReturnRegisters;
+ std::vector<std::uint32_t> FloatingReturnRegisters;
+ std::string PreservedMaskName;
+ std::uint32_t StackAlignmentBytes = 0;
+ std::uint32_t StackArgumentAlignmentBytes = 0;
+ std::uint32_t ShadowSpaceBytes = 0;
+ bool SupportsVarArgs = false;
+ bool SupportsTailCalls = false;
+};
+
+struct NormalizedNamedOperand final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+};
+
+struct NormalizedImmediatePredicate final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ bool IsSigned = false;
+ std::uint32_t Bits = 0;
+ std::uint64_t Alignment = 1;
+ bool NonZero = false;
+ std::vector<std::int64_t> AllowedValues;
+};
+
+struct NormalizedSchedulingClass final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t MicroOps = 0;
+ std::uint32_t Latency = 0;
+};
+
+struct NormalizedEncodingCategory final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t SizeInBytes = 0;
+};
+
+struct NormalizedAssemblyFormat final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+};
+
+struct NormalizedOperand final {
+ std::vector<std::string> Kinds;
+ std::string RegisterRole;
+ std::optional<std::uint32_t> RegisterClass;
+ std::optional<std::uint32_t> RegisterBank;
+ std::optional<std::uint32_t> ImmediatePredicate;
+ std::string MemoryRole;
+ std::optional<std::uint32_t> DefaultTiedOperand;
+ bool EarlyClobber = false;
+ bool Optional = false;
+ bool VariadicGroup = false;
+ bool AllowSubReg = false;
+ std::optional<std::uint32_t> Name;
+ std::uint32_t AllowedTargetFlags = 0;
+};
+
+struct NormalizedImplicitRegister final {
+ std::uint32_t Register = 0;
+ bool EarlyClobber = false;
+ bool Renamable = false;
+};
+
+struct NormalizedInstruction final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::string AssemblyMnemonic;
+ std::uint32_t BinaryEncoding = 0;
+ std::vector<NormalizedOperand> Operands;
+ std::uint32_t NumExplicitDefs = 0;
+ bool Variadic = false;
+ std::vector<NormalizedImplicitRegister> ImplicitDefs;
+ std::vector<NormalizedImplicitRegister> ImplicitUses;
+ bool AllowExtraImplicitOperands = false;
+ std::vector<std::string> Flags;
+ std::uint32_t SchedulingClass = 0;
+ std::uint32_t EncodingCategory = 0;
+ std::uint32_t AssemblyFormat = 0;
+ bool RequiresMemoryOperands = false;
+ std::string Owner;
+ std::string Deadline;
+};
+
+struct NormalizedTargetModel final {
+ SourceTarget Target;
+ std::vector<SourceRegisterUnit> RegisterUnits;
+ std::vector<NormalizedRegister> Registers;
+ std::vector<NormalizedRegisterBank> RegisterBanks;
+ std::vector<NormalizedRegisterClass> RegisterClasses;
+ std::vector<NormalizedRegisterMask> RegisterMasks;
+ SourceMIROperandFlags MIROperandFlags;
+ std::vector<NormalizedCallingConvention> CallingConventions;
+ std::vector<NormalizedNamedOperand> NamedOperands;
+ std::vector<NormalizedImmediatePredicate> ImmediatePredicates;
+ std::vector<NormalizedSchedulingClass> SchedulingClasses;
+ std::vector<NormalizedEncodingCategory> EncodingCategories;
+ std::vector<NormalizedAssemblyFormat> AssemblyFormats;
+ std::vector<NormalizedInstruction> Instructions;
+};
+
+[[nodiscard]] std::optional<std::string_view>
+operandKindEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] std::optional<std::string_view>
+registerRoleEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] std::optional<std::string_view> memoryRoleEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] std::optional<std::string_view>
+instructionFlagEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] std::optional<std::string_view>
+pseudoOwnerEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] std::optional<std::string_view>
+pipelineStageEnumerator(std::string_view Value) noexcept;
+[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept;
+
+class CodeGenTarget final {
+public:
+ CodeGenTarget(const DescriptionAST &Input, std::string SourceName);
+ [[nodiscard]] std::optional<NormalizedTargetModel> normalize();
+ [[nodiscard]] std::vector<TextDiagnostic> takeDiagnostics();
+
+private:
+ void fail(TextRange Range, std::string Message);
+ [[nodiscard]] bool validateTarget();
+ [[nodiscard]] bool normalizeRegisterUnits();
+ [[nodiscard]] bool normalizeRegisters(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeRegisterBanks(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeRegisterClasses(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeRegisterMasks(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeMIROperandFlags(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeCallingConventions(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeNamedOperands(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeImmediatePredicates(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeSchedulingClasses(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeEncodingCategories(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeAssemblyFormats(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeOperand(const SourceOperand &InputOperand, NormalizedOperand &Result);
+ [[nodiscard]] bool
+ normalizeImplicitRegisters(const std::vector<SourceImplicitRegister> &InputRegisters, bool IsDef,
+ std::vector<NormalizedImplicitRegister> &Result);
+ [[nodiscard]] bool normalizeInstructions(NormalizedTargetModel &Result);
+
+ const DescriptionAST &Input;
+ std::string SourceName;
+ std::unordered_map<std::string, std::uint32_t> UnitBySymbol;
+ std::unordered_map<std::string, std::uint32_t> RegisterBySymbol;
+ std::unordered_map<std::string, std::uint32_t> BankBySymbol;
+ std::unordered_map<std::string, std::uint32_t> ClassBySymbol;
+ std::unordered_map<std::string, std::uint32_t> NamedOperandBySymbol;
+ std::unordered_map<std::string, std::uint32_t> ImmediatePredicateBySymbol;
+ std::unordered_map<std::string, std::uint32_t> SchedulingClassBySymbol;
+ std::unordered_map<std::string, std::uint32_t> EncodingCategoryBySymbol;
+ std::unordered_map<std::string, std::uint32_t> AssemblyFormatBySymbol;
+ std::unordered_map<std::string, std::uint32_t> CallingConventionBySymbol;
+ std::vector<TextDiagnostic> Diagnostics;
+};
+
+[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model,
+ std::uint32_t ID);
+[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model,
+ std::uint32_t ID);
+[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model,
+ std::uint32_t ID);
+
+} // namespace rcc::targetgen
+
+#endif // RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H
diff --git a/backend/utils/TableGen/InstrInfoEmitter.cpp b/backend/utils/TableGen/InstrInfoEmitter.cpp
new file mode 100644
index 0000000..6c6426d
--- /dev/null
+++ b/backend/utils/TableGen/InstrInfoEmitter.cpp
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <ostream>
+#include <span>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+namespace {
+
+void emitOperandDescriptor(std::ostream &Output, const NormalizedOperand &Operand) {
+ Output << " {.Kinds = OperandKindSet{";
+ for (const std::string &Kind : Operand.Kinds)
+ Output << "MachineOperand::Kind::" << Kind << ", ";
+ Output << "}, .RegisterRole = RegisterOperandRole::" << Operand.RegisterRole;
+ Output << ", .RegClass = ";
+ if (Operand.RegisterClass)
+ Output << "RegisterClassID::fromValue(" << *Operand.RegisterClass << ")";
+ else
+ Output << "std::nullopt";
+ Output << ", .RegBank = ";
+ if (Operand.RegisterBank)
+ Output << "RegisterBankID::fromValue(" << *Operand.RegisterBank << ")";
+ else
+ Output << "std::nullopt";
+ Output << ", .ImmediatePredicate = ";
+ if (Operand.ImmediatePredicate)
+ Output << "ImmediatePredicateID::fromValue(" << *Operand.ImmediatePredicate << ")";
+ else
+ Output << "std::nullopt";
+ Output << ", .MemoryRole = MemoryOperandRole::" << Operand.MemoryRole;
+ Output << ", .DefaultTiedOperand = ";
+ if (Operand.DefaultTiedOperand)
+ Output << "std::size_t{" << *Operand.DefaultTiedOperand << "}";
+ else
+ Output << "std::nullopt";
+ Output << ", .EarlyClobber = " << (Operand.EarlyClobber ? "true" : "false")
+ << ", .Optional = " << (Operand.Optional ? "true" : "false")
+ << ", .VariadicGroup = " << (Operand.VariadicGroup ? "true" : "false")
+ << ", .AllowSubReg = " << (Operand.AllowSubReg ? "true" : "false");
+ Output << ", .Name = ";
+ if (Operand.Name)
+ Output << "NamedOperandID::fromValue(" << *Operand.Name << ")";
+ else
+ Output << "std::nullopt";
+ Output << ", .TargetOperandType = std::nullopt, .AllowedTargetFlags = "
+ << Operand.AllowedTargetFlags << ", .TypeConstraint = std::nullopt},\n";
+}
+
+void emitImplicitRegisterList(std::ostream &Output, const NormalizedTargetModel &Model,
+ std::span<const NormalizedImplicitRegister> Registers) {
+ Output << "{";
+ for (const NormalizedImplicitRegister &Register : Registers) {
+ Output << "{physical(PhysicalRegisterID::" << findRegister(Model, Register.Register).Symbol
+ << "), " << (Register.EarlyClobber ? "true" : "false") << ", "
+ << (Register.Renamable ? "true" : "false") << "}, ";
+ }
+ Output << "}";
+}
+
+void emitImmediatePredicate(std::ostream &Output, const NormalizedImmediatePredicate &Predicate) {
+ const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U);
+ const std::int64_t Minimum = Predicate.IsSigned ? -PositiveLimit : 0;
+ const std::int64_t Maximum =
+ Predicate.IsSigned
+ ? PositiveLimit - 1
+ : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - std::uint64_t{1});
+ Output << " {.ID = ImmediatePredicateID::fromValue(" << Predicate.ID << "), .Name = ";
+ appendQuoted(Output, Predicate.Name);
+ Output << ", .Predicate = [](const MachineOperand &Operand) {\n"
+ << " const auto *Value = Operand.getIf<std::int64_t>();\n"
+ << " return Value != nullptr && *Value >= " << Minimum
+ << " && *Value <= " << Maximum;
+ if (Predicate.Alignment != 1)
+ Output << " && (*Value % " << Predicate.Alignment << ") == 0";
+ if (Predicate.NonZero)
+ Output << " && *Value != 0";
+ if (!Predicate.AllowedValues.empty()) {
+ Output << " && (";
+ for (std::size_t Index = 0; Index < Predicate.AllowedValues.size(); ++Index) {
+ if (Index != 0)
+ Output << " || ";
+ Output << "*Value == " << Predicate.AllowedValues[Index];
+ }
+ Output << ")";
+ }
+ Output << ";\n }},\n";
+}
+
+} // namespace
+
+[[nodiscard]] std::string emitInstructionHeader(const NormalizedTargetModel &Model,
+ std::string_view SourceName) {
+ const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" +
+ upperIdentifier(Model.Target.Name) + "INSTRUCTIONS_H";
+ std::ostringstream Output;
+ Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
+ Output << "// This file is generated by rcc-tblgen from ";
+ appendQuoted(Output, SourceName);
+ Output << ". Do not edit it manually.\n\n";
+ Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n";
+ Output << "#include <cstdint>\n\n";
+ Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n";
+
+ Output << "enum class InstructionOpcode : std::uint32_t {\n Invalid = 0,\n";
+ for (const NormalizedInstruction &Instruction : Model.Instructions)
+ Output << " " << Instruction.Symbol << " = " << Instruction.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class ImmediatePredicate : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates)
+ Output << " " << Predicate.Symbol << " = " << Predicate.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class NamedOperand : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedNamedOperand &Operand : Model.NamedOperands)
+ Output << " " << Operand.Symbol << " = " << Operand.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class SchedulingClass : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses)
+ Output << " " << Class.Symbol << " = " << Class.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class EncodingCategory : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedEncodingCategory &Category : Model.EncodingCategories)
+ Output << " " << Category.Symbol << " = " << Category.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class AssemblyFormat : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats)
+ Output << " " << Format.Symbol << " = " << Format.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "enum class CallingConvention : std::uint32_t {\n None = 0,\n";
+ for (const NormalizedCallingConvention &Convention : Model.CallingConventions)
+ Output << " " << Convention.Symbol << " = " << Convention.ID << ",\n";
+ Output << "};\n\n";
+
+ Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n";
+ Output << "#endif // " << Guard << "\n";
+ return Output.str();
+}
+
+std::string emitInstructionInfo(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ Output
+ << "void appendGeneratedInstructionInfo(std::vector<rcc::InstructionDescriptor> "
+ "&Descriptors,\n"
+ << " std::vector<rcc::ImmediatePredicate> &Predicates) {\n"
+ << " Predicates = {\n";
+ for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates)
+ emitImmediatePredicate(Output, Predicate);
+ Output << " };\n Descriptors = {\n";
+ for (const NormalizedInstruction &Instruction : Model.Instructions) {
+ Output << " {.InstructionOpcode = Opcode::fromValue(" << Instruction.ID << "), .Name = ";
+ appendQuoted(Output, Instruction.Name);
+ Output << ", .Operands = {\n";
+ for (const NormalizedOperand &Operand : Instruction.Operands)
+ emitOperandDescriptor(Output, Operand);
+ Output << " }, .NumExplicitDefs = " << Instruction.NumExplicitDefs
+ << ", .Variadic = " << (Instruction.Variadic ? "true" : "false")
+ << ", .MinimumVariadicRepetitions = 0, .ImplicitDefs = ";
+ emitImplicitRegisterList(Output, Model, Instruction.ImplicitDefs);
+ Output << ", .ImplicitUses = ";
+ emitImplicitRegisterList(Output, Model, Instruction.ImplicitUses);
+ Output << ", .AllowExtraImplicitOperands = "
+ << (Instruction.AllowExtraImplicitOperands ? "true" : "false")
+ << ", .Flags = InstructionFlags{";
+ for (const std::string &Flag : Instruction.Flags)
+ Output << "InstructionFlag::" << Flag << ", ";
+ Output << "}, .SchedulingClass = SchedulingClassID::fromValue(" << Instruction.SchedulingClass
+ << "), .EncodingCategory = EncodingCategoryID::fromValue("
+ << Instruction.EncodingCategory << "), .AssemblyFormat = AssemblyFormatID::fromValue("
+ << Instruction.AssemblyFormat << "), .RequiresMemoryOperands = "
+ << (Instruction.RequiresMemoryOperands ? "true" : "false")
+ << ", .Owner = PseudoOwner::" << Instruction.Owner
+ << ", .Deadline = PipelineStage::" << Instruction.Deadline
+ << ", .VerifyExplicitOperands = nullptr, "
+ ".VerifyInstructionSemantics = nullptr},\n";
+ }
+ Output << " };\n}\n\n";
+
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/RegisterBankEmitter.cpp b/backend/utils/TableGen/RegisterBankEmitter.cpp
new file mode 100644
index 0000000..54deb63
--- /dev/null
+++ b/backend/utils/TableGen/RegisterBankEmitter.cpp
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <ranges>
+#include <sstream>
+#include <string>
+
+namespace rcc::targetgen {
+
+std::string emitRegisterBanks(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) {
+ Output << " {RegisterBankID::fromValue(" << Bank.ID << "), ";
+ appendQuoted(Output, Bank.Name);
+ Output << ", " << Bank.MaximumSizeInBits << ", {";
+ for (const std::uint32_t ClassID : Bank.RegisterClasses) {
+ const auto Position =
+ std::ranges::find(Model.RegisterClasses, ClassID, &NormalizedRegisterClass::ID);
+ Output << "RegisterClassID::fromValue(" << Position->ID << "), ";
+ }
+ Output << "}},\n";
+ }
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/RegisterInfoEmitter.cpp b/backend/utils/TableGen/RegisterInfoEmitter.cpp
new file mode 100644
index 0000000..a4caa69
--- /dev/null
+++ b/backend/utils/TableGen/RegisterInfoEmitter.cpp
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstdint>
+#include <ostream>
+#include <ranges>
+#include <span>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+
+namespace rcc::targetgen {
+namespace {
+
+void emitRegisterIDList(std::ostream &Output, const NormalizedTargetModel &Model,
+ std::span<const std::uint32_t> IDs) {
+ for (const std::uint32_t ID : IDs)
+ Output << "PhysicalRegisterID::" << findRegister(Model, ID).Symbol << ", ";
+}
+
+} // namespace
+
+[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model,
+ std::string_view SourceName) {
+ const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" +
+ upperIdentifier(Model.Target.Name) + "REGISTERS_H";
+ std::ostringstream Output;
+ Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
+ Output << "// This file is generated by rcc-tblgen from ";
+ appendQuoted(Output, SourceName);
+ Output << ". Do not edit it manually.\n\n";
+ Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n";
+ Output << "#include <cstdint>\n#include <string_view>\n\n";
+ Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n";
+ Output << "inline constexpr std::string_view TargetName = ";
+ appendQuoted(Output, Model.Target.Name);
+ Output << ";\ninline constexpr std::string_view TargetTriple = ";
+ appendQuoted(Output, Model.Target.Triple);
+ Output << ";\ninline constexpr std::string_view TargetCPU = ";
+ appendQuoted(Output, Model.Target.CPU);
+ Output << ";\ninline constexpr std::string_view TargetABI = ";
+ appendQuoted(Output, Model.Target.ABI);
+ Output << ";\ninline constexpr std::string_view TargetDataLayout = ";
+ appendQuoted(Output, Model.Target.DataLayout);
+ Output << ";\n\ninline constexpr std::string_view TargetFeatures[] = {\n";
+ for (const std::string &Feature : Model.Target.Features) {
+ Output << " ";
+ appendQuoted(Output, Feature);
+ Output << ",\n";
+ }
+ Output << "};\n\n";
+
+ Output << "enum class PhysicalRegisterID : std::uint32_t {\n NoRegister = 0,\n";
+ for (const NormalizedRegister &Register : Model.Registers)
+ Output << " " << Register.Symbol << " = " << Register.ID << ",\n";
+ Output << "};\n\n";
+ Output << "enum class RegisterUnit : std::uint32_t {\n";
+ for (const SourceRegisterUnit &Unit : Model.RegisterUnits)
+ Output << " " << Unit.Symbol << " = " << Unit.ID << ",\n";
+ Output << "};\n\n";
+ Output << "enum class RegisterBank : std::uint32_t {\n";
+ for (const NormalizedRegisterBank &Bank : Model.RegisterBanks)
+ Output << " " << Bank.Symbol << " = " << Bank.ID << ",\n";
+ Output << "};\n\n";
+ Output << "enum class RegisterClass : std::uint32_t {\n";
+ for (const NormalizedRegisterClass &Class : Model.RegisterClasses)
+ Output << " " << Class.Symbol << " = " << Class.ID << ",\n";
+ Output << "};\n\n";
+ Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n";
+ Output << "#endif // " << Guard << "\n";
+ return Output.str();
+}
+
+std::string emitRegisterInfo(const NormalizedTargetModel &Model, std::string_view SourceName) {
+ std::ostringstream Output;
+ Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n";
+ Output << "// This file is generated by rcc-tblgen from ";
+ appendQuoted(Output, SourceName);
+ Output << ". Do not edit it manually.\n\n";
+ Output << "void appendGeneratedRegisterInfo(TargetRegisterInfoDesc &Description) {\n";
+ Output << " Description.RegisterUnits = {\n";
+ for (const SourceRegisterUnit &Unit : Model.RegisterUnits) {
+ Output << " {RegisterUnitID::fromValue(" << Unit.ID << "), ";
+ appendQuoted(Output, Unit.Name);
+ Output << "},\n";
+ }
+ Output << " };\n\n Description.PhysicalRegisters = {\n";
+ for (const NormalizedRegister &Register : Model.Registers) {
+ Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), ";
+ appendQuoted(Output, Register.Name);
+ Output << ", " << Register.Encoding << ", {";
+ for (const NormalizedRegisterUnitLane &Unit : Register.Units) {
+ Output << "unitLane(RegisterUnit::" << findRegisterUnit(Model, Unit.UnitID).Symbol << ", "
+ << Unit.Lanes << "), ";
+ }
+ Output << "}, " << (Register.IsConstant ? "true" : "false") << ", "
+ << (Register.IsArtificial ? "true" : "false") << ", "
+ << (Register.IsReserved ? "true" : "false") << ", {";
+ for (const std::string &Alias : Register.AlternativeNames) {
+ appendQuoted(Output, Alias);
+ Output << ", ";
+ }
+ Output << "}},\n";
+ }
+ Output << " };\n\n Description.RegisterBanks = {\n";
+ Output << emitRegisterBanks(Model);
+ Output << " };\n\n Description.RegisterClasses = {\n";
+ for (const NormalizedRegisterClass &Class : Model.RegisterClasses) {
+ Output << " registerClass(RegisterClass::" << Class.Symbol << ", ";
+ appendQuoted(Output, Class.Name);
+ Output << ", {";
+ emitRegisterIDList(Output, Model, Class.Registers);
+ Output << "}, {";
+ emitRegisterIDList(Output, Model, Class.AllocationOrder);
+ Output << "}, RegisterBank::" << findRegisterBank(Model, Class.BankID).Symbol << ", "
+ << Class.SizeInBits << ", " << Class.SpillSizeInBits << ", "
+ << Class.SpillAlignmentInBits << ", " << Class.Lanes << ", "
+ << (Class.Allocatable ? "true" : "false") << ", " << Class.CopyCost << "),\n";
+ }
+ Output << " };\n}\n\n";
+ Output << "void appendGeneratedMIRTextInfo(TargetMIRTextInfoDesc &Description) {\n";
+ Output << " Description.DirectOperandFlagMask = " << Model.MIROperandFlags.DirectMask
+ << ";\n Description.DirectOperandFlags = {\n";
+ for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Direct) {
+ Output << " {";
+ appendQuoted(Output, Flag.Name);
+ Output << ", " << Flag.Value << "},\n";
+ }
+ Output << " };\n Description.BitmaskOperandFlags = {\n";
+ for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Bitmask) {
+ Output << " {";
+ appendQuoted(Output, Flag.Name);
+ Output << ", " << Flag.Value << "},\n";
+ }
+ Output << " };\n";
+ Output << " Description.RegisterMasks = {\n";
+ for (const NormalizedRegisterMask &Mask : Model.RegisterMasks) {
+ Output << " {";
+ appendQuoted(Output, Mask.Name);
+ Output << ", makePreservedMask({";
+ emitRegisterIDList(Output, Model, Mask.PreservedRegisters);
+ Output << "})},\n";
+ }
+ Output << " };\n Description.CFIRegisters = {\n";
+ std::unordered_set<std::uint32_t> CanonicalDwarfRegisters;
+ for (const NormalizedRegister &Register : Model.Registers) {
+ const bool IsCanonical = CanonicalDwarfRegisters.insert(Register.DwarfRegister).second;
+ Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), "
+ << Register.DwarfRegister << ", " << (IsCanonical ? "true" : "false") << "},\n";
+ }
+ Output << " };\n}\n\n";
+
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/SearchableTableEmitter.cpp b/backend/utils/TableGen/SearchableTableEmitter.cpp
new file mode 100644
index 0000000..3dfa534
--- /dev/null
+++ b/backend/utils/TableGen/SearchableTableEmitter.cpp
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <algorithm>
+#include <ranges>
+#include <sstream>
+#include <string>
+
+namespace rcc::targetgen {
+
+std::string emitSearchableTables(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ const auto AssemblyInstructionCount =
+ std::ranges::count_if(Model.Instructions, [](const NormalizedInstruction &Instruction) {
+ return !Instruction.AssemblyMnemonic.empty();
+ });
+ Output << "constexpr std::array<AssemblyInstructionInfo, " << AssemblyInstructionCount
+ << "> GeneratedAssemblyInstructions = {{\n";
+ for (const NormalizedInstruction &Instruction : Model.Instructions) {
+ if (Instruction.AssemblyMnemonic.empty())
+ continue;
+ Output << " {Opcode::fromValue(" << Instruction.ID << "), ";
+ appendQuoted(Output, Instruction.AssemblyMnemonic);
+ Output << ", AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat << "), "
+ << Instruction.BinaryEncoding << "},\n";
+ }
+ Output << "}};\n\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/SubtargetEmitter.cpp b/backend/utils/TableGen/SubtargetEmitter.cpp
new file mode 100644
index 0000000..f374d48
--- /dev/null
+++ b/backend/utils/TableGen/SubtargetEmitter.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <sstream>
+#include <string>
+
+namespace rcc::targetgen {
+
+std::string emitSubtarget(const NormalizedTargetModel &Model) {
+ std::ostringstream Output;
+ Output << "constexpr std::array<SchedulingClassInfo, " << Model.SchedulingClasses.size()
+ << "> GeneratedSchedulingClasses = {{\n";
+ for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses) {
+ Output << " {SchedulingClassID::fromValue(" << Class.ID << "), ";
+ appendQuoted(Output, Class.Name);
+ Output << ", " << Class.MicroOps << ", " << Class.Latency << "},\n";
+ }
+ Output << "}};\n\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/TableGenBackends.h b/backend/utils/TableGen/TableGenBackends.h
new file mode 100644
index 0000000..7835efb
--- /dev/null
+++ b/backend/utils/TableGen/TableGenBackends.h
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H
+#define RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H
+
+#include "Common/CodeGenTarget.h"
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc::targetgen {
+
+struct GeneratedDescription final {
+ std::string RegisterHeader;
+ std::string InstructionHeader;
+ std::string TargetTables;
+};
+
+struct DescriptionGenerationResult final {
+ std::optional<GeneratedDescription> Output;
+ std::vector<TextDiagnostic> Diagnostics;
+
+ [[nodiscard]] bool succeeded() const noexcept {
+ return Output.has_value() && Diagnostics.empty();
+ }
+};
+
+[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitRegisterInfo(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitRegisterBanks(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitInstructionHeader(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitInstructionInfo(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitCallingConventions(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitCodeEmitter(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitAssemblyWriter(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitSubtarget(const NormalizedTargetModel &Model);
+[[nodiscard]] std::string emitSearchableTables(const NormalizedTargetModel &Model);
+
+[[nodiscard]] DescriptionGenerationResult generateDescription(const std::string &SourceName,
+ std::string SourceText);
+
+} // namespace rcc::targetgen
+
+#endif // RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H
diff --git a/backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp b/backend/utils/TableGen/rcc-tblgen.cpp
index 13927e1..3e4e093 100644
--- a/backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp
+++ b/backend/utils/TableGen/rcc-tblgen.cpp
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "TargetDescriptionGenerator.h"
+#include "TableGenBackends.h"
#include "rcc/Support/TextDiagnostic.h"
#include <filesystem>
@@ -25,7 +25,7 @@ struct CommandLine final {
if (Argument == "--input" || Argument == "--register-header" ||
Argument == "--instruction-header" || Argument == "--tables") {
if (Index + 1 >= ArgumentCount) {
- std::cerr << "rcc-backend-descgen: missing value for " << Argument << '\n';
+ std::cerr << "rcc-tblgen: missing value for " << Argument << '\n';
return false;
}
std::string *Destination = nullptr;
@@ -40,14 +40,14 @@ struct CommandLine final {
*Destination = Arguments[++Index];
continue;
}
- std::cerr << "rcc-backend-descgen: unknown argument '" << Argument << "'\n";
+ std::cerr << "rcc-tblgen: unknown argument '" << Argument << "'\n";
return false;
}
if (!Result.InputPath.empty() && !Result.RegisterHeaderPath.empty() &&
!Result.InstructionHeaderPath.empty() && !Result.TablesPath.empty()) {
return true;
}
- std::cerr << "usage: rcc-backend-descgen --input <file> --register-header <file> "
+ std::cerr << "usage: rcc-tblgen --input <file> --register-header <file> "
"--instruction-header <file> --tables <file>\n";
return false;
}
@@ -56,13 +56,13 @@ struct CommandLine final {
std::error_code Error;
std::filesystem::create_directories(Path.parent_path(), Error);
if (Error) {
- std::cerr << "rcc-backend-descgen: cannot create output directory for '" << Path.string()
+ std::cerr << "rcc-tblgen: cannot create output directory for '" << Path.string()
<< "': " << Error.message() << '\n';
return false;
}
std::ofstream Output(Path, std::ios::binary | std::ios::trunc);
if (!Output) {
- std::cerr << "rcc-backend-descgen: cannot open output '" << Path.string() << "'\n";
+ std::cerr << "rcc-tblgen: cannot open output '" << Path.string() << "'\n";
return false;
}
Output.write(Contents.data(), static_cast<std::streamsize>(Contents.size()));
@@ -70,7 +70,7 @@ struct CommandLine final {
Output.close();
return true;
}
- std::cerr << "rcc-backend-descgen: cannot write output '" << Path.string() << "'\n";
+ std::cerr << "rcc-tblgen: cannot write output '" << Path.string() << "'\n";
return false;
}
@@ -83,7 +83,7 @@ int main(int ArgumentCount, char *Arguments[]) {
std::ifstream Input(Command.InputPath, std::ios::binary);
if (!Input) {
- std::cerr << "rcc-backend-descgen: cannot open input '" << Command.InputPath << "'\n";
+ std::cerr << "rcc-tblgen: cannot open input '" << Command.InputPath << "'\n";
return 1;
}
const std::string SourceText{std::istreambuf_iterator<char>(Input),
@@ -98,7 +98,7 @@ int main(int ArgumentCount, char *Arguments[]) {
}
if (!Result.Output) {
- std::cerr << "rcc-backend-descgen: successful generation produced no output\n";
+ std::cerr << "rcc-tblgen: successful generation produced no output\n";
return 1;
}
const rcc::targetgen::GeneratedDescription &Output = Result.Output.value();