aboutsummaryrefslogtreecommitdiff
path: root/backend/utils
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 /backend/utils
parentfde3e045d4dcc5c404cb4216389e18b0e720b8e0 (diff)
downloadrcc-project-444db30751380e8fe459bfeae4c5bb2a93c596eb.tar.gz
rcc-project-444db30751380e8fe459bfeae4c5bb2a93c596eb.tar.xz
[TableGen] Split the target description generator
Diffstat (limited to 'backend/utils')
-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.cpp110
16 files changed, 2073 insertions, 0 deletions
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/utils/TableGen/rcc-tblgen.cpp b/backend/utils/TableGen/rcc-tblgen.cpp
new file mode 100644
index 0000000..3e4e093
--- /dev/null
+++ b/backend/utils/TableGen/rcc-tblgen.cpp
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+#include "rcc/Support/TextDiagnostic.h"
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <string_view>
+
+namespace {
+
+struct CommandLine final {
+ std::string InputPath;
+ std::string RegisterHeaderPath;
+ std::string InstructionHeaderPath;
+ std::string TablesPath;
+};
+
+[[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) {
+ for (int Index = 1; Index < ArgumentCount; ++Index) {
+ const std::string_view Argument(Arguments[Index]);
+ if (Argument == "--input" || Argument == "--register-header" ||
+ Argument == "--instruction-header" || Argument == "--tables") {
+ if (Index + 1 >= ArgumentCount) {
+ std::cerr << "rcc-tblgen: missing value for " << Argument << '\n';
+ return false;
+ }
+ std::string *Destination = nullptr;
+ if (Argument == "--input")
+ Destination = &Result.InputPath;
+ else if (Argument == "--register-header")
+ Destination = &Result.RegisterHeaderPath;
+ else if (Argument == "--instruction-header")
+ Destination = &Result.InstructionHeaderPath;
+ else
+ Destination = &Result.TablesPath;
+ *Destination = Arguments[++Index];
+ continue;
+ }
+ 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-tblgen --input <file> --register-header <file> "
+ "--instruction-header <file> --tables <file>\n";
+ return false;
+}
+
+[[nodiscard]] bool writeFile(const std::filesystem::path &Path, std::string_view Contents) {
+ std::error_code Error;
+ std::filesystem::create_directories(Path.parent_path(), Error);
+ if (Error) {
+ 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-tblgen: cannot open output '" << Path.string() << "'\n";
+ return false;
+ }
+ Output.write(Contents.data(), static_cast<std::streamsize>(Contents.size()));
+ if (Output) {
+ Output.close();
+ return true;
+ }
+ std::cerr << "rcc-tblgen: cannot write output '" << Path.string() << "'\n";
+ return false;
+}
+
+} // namespace
+
+int main(int ArgumentCount, char *Arguments[]) {
+ CommandLine Command;
+ if (!parseCommandLine(ArgumentCount, Arguments, Command))
+ return 1;
+
+ std::ifstream Input(Command.InputPath, std::ios::binary);
+ if (!Input) {
+ std::cerr << "rcc-tblgen: cannot open input '" << Command.InputPath << "'\n";
+ return 1;
+ }
+ const std::string SourceText{std::istreambuf_iterator<char>(Input),
+ std::istreambuf_iterator<char>()};
+ const rcc::targetgen::DescriptionGenerationResult Result =
+ rcc::targetgen::generateDescription(Command.InputPath, SourceText);
+ if (!Result.succeeded()) {
+ const rcc::TextSource Source(Command.InputPath, SourceText);
+ for (const rcc::TextDiagnostic &Diagnostic : Result.Diagnostics)
+ std::cerr << rcc::formatTextDiagnostic(Diagnostic, Source) << '\n';
+ return 1;
+ }
+
+ if (!Result.Output) {
+ std::cerr << "rcc-tblgen: successful generation produced no output\n";
+ return 1;
+ }
+ const rcc::targetgen::GeneratedDescription &Output = Result.Output.value();
+ return writeFile(Command.RegisterHeaderPath, Output.RegisterHeader) &&
+ writeFile(Command.InstructionHeaderPath, Output.InstructionHeader) &&
+ writeFile(Command.TablesPath, Output.TargetTables)
+ ? 0
+ : 1;
+}