aboutsummaryrefslogtreecommitdiff
path: root/backend/utils
diff options
context:
space:
mode:
Diffstat (limited to 'backend/utils')
-rw-r--r--backend/utils/TableGen/AsmWriterEmitter.cpp23
-rw-r--r--backend/utils/TableGen/CMakeLists.txt50
-rw-r--r--backend/utils/TableGen/CallingConvEmitter.cpp58
-rw-r--r--backend/utils/TableGen/CodeEmitterGen.cpp23
-rw-r--r--backend/utils/TableGen/Common/CodeGenInstruction.cpp582
-rw-r--r--backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp123
-rw-r--r--backend/utils/TableGen/Common/CodeGenMacroFusion.cpp175
-rw-r--r--backend/utils/TableGen/Common/CodeGenRegisters.cpp224
-rw-r--r--backend/utils/TableGen/Common/CodeGenSchedule.cpp92
-rw-r--r--backend/utils/TableGen/Common/CodeGenSelection.cpp1014
-rw-r--r--backend/utils/TableGen/Common/CodeGenTarget.cpp225
-rw-r--r--backend/utils/TableGen/Common/CodeGenTarget.h485
-rw-r--r--backend/utils/TableGen/GlobalISelCombinerEmitter.cpp154
-rw-r--r--backend/utils/TableGen/GlobalISelEmitter.cpp233
-rw-r--r--backend/utils/TableGen/InstrInfoEmitter.cpp205
-rw-r--r--backend/utils/TableGen/MCPseudoLoweringEmitter.cpp69
-rw-r--r--backend/utils/TableGen/MacroFusionEmitter.cpp100
-rw-r--r--backend/utils/TableGen/RegisterBankEmitter.cpp27
-rw-r--r--backend/utils/TableGen/RegisterInfoEmitter.cpp169
-rw-r--r--backend/utils/TableGen/SearchableTableEmitter.cpp32
-rw-r--r--backend/utils/TableGen/SubtargetEmitter.cpp23
-rw-r--r--backend/utils/TableGen/TableGenBackends.h62
-rw-r--r--backend/utils/TableGen/rcc-tblgen.cpp251
23 files changed, 4399 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..ae305b4
--- /dev/null
+++ b/backend/utils/TableGen/CMakeLists.txt
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(
+ RCCBackendTableGenBackendSources
+ AsmWriterEmitter.cpp
+ CallingConvEmitter.cpp
+ CodeEmitterGen.cpp
+ GlobalISelEmitter.cpp
+ GlobalISelCombinerEmitter.cpp
+ MacroFusionEmitter.cpp
+ MCPseudoLoweringEmitter.cpp
+ Common/CodeGenInstruction.cpp
+ Common/CodeGenMacroFusion.cpp
+ Common/CodeGenMCPseudoLowering.cpp
+ Common/CodeGenRegisters.cpp
+ Common/CodeGenSchedule.cpp
+ Common/CodeGenSelection.cpp
+ Common/CodeGenTarget.cpp
+ InstrInfoEmitter.cpp
+ RegisterBankEmitter.cpp
+ RegisterInfoEmitter.cpp
+ SearchableTableEmitter.cpp
+ SubtargetEmitter.cpp
+)
+list(TRANSFORM RCCBackendTableGenBackendSources PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/")
+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}
+)
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..e7f5ddc
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenInstruction.cpp
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#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>, 25> 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"},
+ {"may_raise_fp_exception", "MayRaiseFPException"},
+ {"phi", "Phi"},
+ {"fake_use", "FakeUse"},
+ {"generic", "Generic"},
+ {"terminal_emission", "TerminalEmission"},
+ {"uses_custom_inserter", "UsesCustomInserter"},
+ }};
+ 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"},
+ {"pre_ra_expansion", "PreRAExpansion"},
+ {"register_allocator", "RegisterAllocator"},
+ {"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 ||
+ !std::has_single_bit(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 (InputOperand.TypeIndex && !IsRegisterOnly) {
+ fail(InputOperand.Range, "instruction operand type_index requires exactly one register kind");
+ 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.TypeIndex = InputOperand.TypeIndex;
+ 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();
+ if ((!IsPseudoSource && !HasAllMachineFacts) || HasAnyMachineFact != HasAllMachineFacts) {
+ fail(Instruction.Range,
+ "encoded instructions 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,
+ .SizeInBytes = Instruction.SizeInBytes,
+ .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("Generic") &&
+ std::ranges::any_of(Normalized.Operands, [](const NormalizedOperand &Operand) {
+ return Operand.TypeIndex.has_value();
+ })) {
+ fail(Instruction.Range,
+ "instruction type_index operands require the generic instruction flag");
+ return false;
+ }
+ 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.SizeInBytes != 0 && !HasFlag("ProducesCode")) {
+ fail(Instruction.Range, "only code-producing instructions can declare a byte size");
+ 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/CodeGenMCPseudoLowering.cpp b/backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp
new file mode 100644
index 0000000..ca81085
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "CodeGenTarget.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <ranges>
+#include <string_view>
+#include <unordered_set>
+
+namespace rcc::targetgen {
+namespace {
+
+[[nodiscard]] const NormalizedInstruction *findInstruction(const NormalizedTargetModel &Model,
+ std::string_view Symbol) noexcept {
+ const auto Position =
+ std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol);
+ return Position == Model.Instructions.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] bool hasFlag(const NormalizedInstruction &Instruction,
+ std::string_view Flag) noexcept {
+ return std::ranges::contains(Instruction.Flags, Flag);
+}
+
+[[nodiscard]] bool hasCompatibleKind(const NormalizedOperand &Source,
+ const NormalizedOperand &Result) noexcept {
+ return std::ranges::any_of(Source.Kinds, [&Result](const std::string &Kind) {
+ return std::ranges::contains(Result.Kinds, Kind);
+ });
+}
+
+} // namespace
+
+bool CodeGenTarget::normalizeMCPseudoExpansions(NormalizedTargetModel &Result) {
+ std::unordered_set<std::uint32_t> PseudoOpcodes;
+ for (const SourceMCPseudoExpansion &Expansion : Input.MCPseudoExpansions) {
+ const NormalizedInstruction *Pseudo = findInstruction(Result, Expansion.PseudoOpcodeSymbol);
+ const NormalizedInstruction *ResultInstruction =
+ findInstruction(Result, Expansion.ResultOpcodeSymbol);
+ if (Pseudo == nullptr || ResultInstruction == nullptr ||
+ !PseudoOpcodes.insert(Pseudo == nullptr ? 0 : Pseudo->ID).second) {
+ fail(Expansion.Range,
+ "MC pseudo expansions require a unique declared pseudo and result instruction");
+ return false;
+ }
+ if (!hasFlag(*Pseudo, "Pseudo") || !hasFlag(*Pseudo, "TerminalEmission") ||
+ Pseudo->Owner != "AsmPrinter" || Pseudo->Deadline != "Emission" ||
+ hasFlag(*ResultInstruction, "Pseudo") || ResultInstruction->AssemblyMnemonic.empty()) {
+ fail(Expansion.Range,
+ "MC pseudo expansions require an AsmPrinter-owned terminal pseudo and encoded result");
+ return false;
+ }
+ if (Expansion.Operands.size() != ResultInstruction->Operands.size()) {
+ fail(Expansion.Range, "MC pseudo expansion operand count must match the result instruction");
+ return false;
+ }
+
+ NormalizedMCPseudoExpansion Normalized{
+ .PseudoOpcode = Pseudo->ID,
+ .ResultOpcode = ResultInstruction->ID,
+ .Operands = {},
+ };
+ Normalized.Operands.reserve(Expansion.Operands.size());
+ for (std::size_t Index = 0; Index < Expansion.Operands.size(); ++Index) {
+ const SourceMCPseudoOperand &Operand = Expansion.Operands[Index];
+ const unsigned SourceCount = static_cast<unsigned>(Operand.SourceOperandIndex.has_value()) +
+ static_cast<unsigned>(!Operand.RegisterSymbol.empty()) +
+ static_cast<unsigned>(Operand.Immediate.has_value());
+ if (SourceCount != 1) {
+ fail(Operand.Range,
+ "MC pseudo expansion operands require exactly one operand, register, or immediate");
+ return false;
+ }
+ const NormalizedOperand &ResultOperand = ResultInstruction->Operands[Index];
+ if (Operand.SourceOperandIndex) {
+ if (*Operand.SourceOperandIndex >= Pseudo->Operands.size() ||
+ !hasCompatibleKind(Pseudo->Operands[*Operand.SourceOperandIndex], ResultOperand)) {
+ fail(Operand.Range,
+ "MC pseudo source operand must exist and match the result operand kind");
+ return false;
+ }
+ Normalized.Operands.push_back({
+ .Kind = NormalizedMCPseudoOperandKind::SourceOperand,
+ .Value = *Operand.SourceOperandIndex,
+ .Immediate = 0,
+ });
+ continue;
+ }
+ if (!Operand.RegisterSymbol.empty()) {
+ const auto Register = RegisterBySymbol.find(Operand.RegisterSymbol);
+ if (Register == RegisterBySymbol.end() ||
+ !std::ranges::contains(ResultOperand.Kinds, "Register")) {
+ fail(Operand.Range,
+ "MC pseudo fixed register must be declared and target a register operand");
+ return false;
+ }
+ Normalized.Operands.push_back({
+ .Kind = NormalizedMCPseudoOperandKind::Register,
+ .Value = Register->second,
+ .Immediate = 0,
+ });
+ continue;
+ }
+ if (!std::ranges::contains(ResultOperand.Kinds, "Immediate")) {
+ fail(Operand.Range, "MC pseudo fixed immediate must target an immediate operand");
+ return false;
+ }
+ assert(Operand.Immediate && "validated MC pseudo operand must contain an immediate");
+ Normalized.Operands.push_back({
+ .Kind = NormalizedMCPseudoOperandKind::Immediate,
+ .Value = 0,
+ .Immediate = *Operand.Immediate,
+ });
+ }
+ Result.MCPseudoExpansions.push_back(std::move(Normalized));
+ }
+ std::ranges::sort(Result.MCPseudoExpansions, {}, &NormalizedMCPseudoExpansion::PseudoOpcode);
+ return true;
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp b/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp
new file mode 100644
index 0000000..7c4ea1c
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "CodeGenTarget.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <vector>
+
+namespace rcc::targetgen {
+namespace {
+
+[[nodiscard]] const NormalizedInstruction *findInstruction(const NormalizedTargetModel &Model,
+ std::string_view Symbol) noexcept {
+ const auto Position =
+ std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol);
+ return Position == Model.Instructions.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] bool isOperandKind(const NormalizedInstruction &Instruction,
+ std::uint32_t OperandIndex, std::string_view Kind) noexcept {
+ return OperandIndex < Instruction.Operands.size() &&
+ std::ranges::contains(Instruction.Operands[OperandIndex].Kinds, Kind);
+}
+
+[[nodiscard]] std::optional<NormalizedMacroFusionInstruction>
+normalizeInstructionSide(std::string_view Side) noexcept {
+ if (Side == "first")
+ return NormalizedMacroFusionInstruction::First;
+ if (Side == "second")
+ return NormalizedMacroFusionInstruction::Second;
+ return std::nullopt;
+}
+
+} // namespace
+
+bool CodeGenTarget::normalizeMacroFusionRules(NormalizedTargetModel &Result) {
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Features;
+ for (const SourceMacroFusionRule &Rule : Input.MacroFusionRules) {
+ if (!isIdentifier(Rule.Symbol) || Rule.Feature.empty() || Rule.FirstOpcodeSymbols.empty() ||
+ Rule.SecondOpcodeSymbols.empty() || !Symbols.insert(Rule.Symbol).second ||
+ !Features.insert(Rule.Feature).second) {
+ fail(Rule.Range,
+ "macro fusion rules require unique identifiers and features plus nonempty opcode sets");
+ return false;
+ }
+
+ NormalizedMacroFusionRule Normalized{
+ .Symbol = Rule.Symbol,
+ .Feature = Rule.Feature,
+ .FirstOpcodes = {},
+ .SecondOpcodes = {},
+ .OpcodePairs = {},
+ .ImmediatePredicates = {},
+ .RegisterInequalities = {},
+ .ImmediateRelations = {},
+ .FirstDefinitionOperand = Rule.FirstDefinitionOperand,
+ .SecondUseOperand = Rule.SecondUseOperand,
+ .SecondDefinitionOperand = Rule.SecondDefinitionOperand,
+ .CommutableSecondUseOperand = Rule.CommutableSecondUseOperand,
+ .RequireSingleNonDebugUse = Rule.RequireSingleNonDebugUse,
+ .RequireSecondDestinationTie = Rule.RequireSecondDestinationTie,
+ };
+ std::unordered_set<std::uint32_t> FirstOpcodes;
+ std::unordered_set<std::uint32_t> SecondOpcodes;
+ std::vector<const NormalizedInstruction *> FirstInstructions;
+ std::vector<const NormalizedInstruction *> SecondInstructions;
+ const auto NormalizeOpcodes =
+ [this, &Result](const std::vector<std::string> &OpcodeSymbols,
+ std::unordered_set<std::uint32_t> &Opcodes,
+ std::vector<std::uint32_t> &Output,
+ std::vector<const NormalizedInstruction *> &Instructions, TextRange Range) {
+ for (const std::string &Symbol : OpcodeSymbols) {
+ const NormalizedInstruction *Instruction = findInstruction(Result, Symbol);
+ if (Instruction == nullptr || !Opcodes.insert(Instruction->ID).second) {
+ fail(Range, "macro fusion opcodes must name distinct declared target instructions");
+ return false;
+ }
+ Output.push_back(Instruction->ID);
+ Instructions.push_back(Instruction);
+ }
+ return true;
+ };
+ if (!NormalizeOpcodes(Rule.FirstOpcodeSymbols, FirstOpcodes, Normalized.FirstOpcodes,
+ FirstInstructions, Rule.Range) ||
+ !NormalizeOpcodes(Rule.SecondOpcodeSymbols, SecondOpcodes, Normalized.SecondOpcodes,
+ SecondInstructions, Rule.Range)) {
+ return false;
+ }
+
+ const auto AllHaveOperandKind = [](const auto &Instructions, std::uint32_t Operand,
+ std::string_view Kind) {
+ return std::ranges::all_of(Instructions, [Operand, Kind](const auto *Instruction) {
+ return isOperandKind(*Instruction, Operand, Kind);
+ });
+ };
+ if (!AllHaveOperandKind(FirstInstructions, Rule.FirstDefinitionOperand, "Register") ||
+ !AllHaveOperandKind(SecondInstructions, Rule.SecondUseOperand, "Register") ||
+ (Rule.RequireSecondDestinationTie &&
+ !AllHaveOperandKind(SecondInstructions, Rule.SecondDefinitionOperand, "Register")) ||
+ (Rule.CommutableSecondUseOperand &&
+ !AllHaveOperandKind(SecondInstructions, *Rule.CommutableSecondUseOperand, "Register"))) {
+ fail(Rule.Range, "macro fusion linkage must reference register operands of every opcode");
+ return false;
+ }
+
+ std::unordered_set<std::uint64_t> OpcodePairs;
+ for (const SourceMacroFusionOpcodePair &Pair : Rule.OpcodePairs) {
+ const NormalizedInstruction *First = findInstruction(Result, Pair.FirstOpcodeSymbol);
+ const NormalizedInstruction *Second = findInstruction(Result, Pair.SecondOpcodeSymbol);
+ if (First == nullptr || Second == nullptr || !FirstOpcodes.contains(First->ID) ||
+ !SecondOpcodes.contains(Second->ID)) {
+ fail(Pair.Range, "macro fusion opcode pair must belong to the rule opcode sets");
+ return false;
+ }
+ const std::uint64_t Key = (static_cast<std::uint64_t>(First->ID) << 32U) | Second->ID;
+ if (!OpcodePairs.insert(Key).second) {
+ fail(Pair.Range, "macro fusion opcode pairs must be distinct");
+ return false;
+ }
+ Normalized.OpcodePairs.push_back({First->ID, Second->ID});
+ }
+
+ for (const SourceMacroFusionImmediatePredicate &Predicate : Rule.ImmediatePredicates) {
+ const auto Side = normalizeInstructionSide(Predicate.Instruction);
+ const auto &Instructions =
+ Side == NormalizedMacroFusionInstruction::First ? FirstInstructions : SecondInstructions;
+ if (!Side || Predicate.Minimum > Predicate.Maximum ||
+ !AllHaveOperandKind(Instructions, Predicate.OperandIndex, "Immediate")) {
+ fail(
+ Predicate.Range,
+ "macro fusion immediate predicate requires a valid side, range, and immediate operand");
+ return false;
+ }
+ Normalized.ImmediatePredicates.push_back(
+ {*Side, Predicate.OperandIndex, Predicate.Minimum, Predicate.Maximum});
+ }
+
+ for (const SourceMacroFusionRegisterInequality &Predicate : Rule.RegisterInequalities) {
+ const auto Side = normalizeInstructionSide(Predicate.Instruction);
+ const auto &Instructions =
+ Side == NormalizedMacroFusionInstruction::First ? FirstInstructions : SecondInstructions;
+ if (!Side || Predicate.LeftOperand == Predicate.RightOperand ||
+ !AllHaveOperandKind(Instructions, Predicate.LeftOperand, "Register") ||
+ !AllHaveOperandKind(Instructions, Predicate.RightOperand, "Register")) {
+ fail(
+ Predicate.Range,
+ "macro fusion register inequality requires distinct register operands on a valid side");
+ return false;
+ }
+ Normalized.RegisterInequalities.push_back(
+ {*Side, Predicate.LeftOperand, Predicate.RightOperand});
+ }
+
+ for (const SourceMacroFusionImmediateRelation &Relation : Rule.ImmediateRelations) {
+ if (Relation.Kind != "less_equal" ||
+ !AllHaveOperandKind(FirstInstructions, Relation.FirstOperand, "Immediate") ||
+ !AllHaveOperandKind(SecondInstructions, Relation.SecondOperand, "Immediate")) {
+ fail(Relation.Range,
+ "macro fusion immediate relation must be less_equal over immediate operands");
+ return false;
+ }
+ Normalized.ImmediateRelations.push_back({Relation.FirstOperand, Relation.SecondOperand});
+ }
+ Result.MacroFusionRules.push_back(std::move(Normalized));
+ }
+ 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..9fa8d7c
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenRegisters.cpp
@@ -0,0 +1,224 @@
+// 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);
+ }
+ for (const SourceRegisterClass &Class : Input.RegisterClasses) {
+ const auto Bank = BankBySymbol.find(Class.BankSymbol);
+ 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 = {},
+ .SuperClasses = {},
+ .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);
+ }
+ std::unordered_set<std::uint32_t> SuperClasses;
+ for (const std::string &Symbol : Class.SuperClassSymbols) {
+ const auto SuperClass = ClassBySymbol.find(Symbol);
+ if (SuperClass == ClassBySymbol.end() || SuperClass->second == Class.ID ||
+ !SuperClasses.insert(SuperClass->second).second) {
+ fail(Class.Range,
+ "register class superclasses must reference distinct declared classes other than "
+ "the class itself");
+ return false;
+ }
+ Normalized.SuperClasses.push_back(SuperClass->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/CodeGenSelection.cpp b/backend/utils/TableGen/Common/CodeGenSelection.cpp
new file mode 100644
index 0000000..b17d176
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenSelection.cpp
@@ -0,0 +1,1014 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "CodeGenTarget.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineOperand.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::targetgen {
+namespace {
+
+[[nodiscard]] std::optional<std::uint32_t> genericOpcodeValue(std::string_view Symbol) {
+#define RCC_CORE_OPCODE(Name) \
+ if (Symbol == #Name) \
+ return static_cast<std::uint32_t>(GenericOpcode::Name);
+#define RCC_GENERIC_OPCODE(Name) RCC_CORE_OPCODE(Name)
+#include "rcc/CodeGen/GenericOpcode.def"
+#undef RCC_GENERIC_OPCODE
+#undef RCC_CORE_OPCODE
+ return std::nullopt;
+}
+
+[[nodiscard]] const NormalizedInstruction *
+findInstructionBySymbol(const NormalizedTargetModel &Model, std::string_view Symbol) {
+ const auto Position =
+ std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol);
+ return Position == Model.Instructions.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] std::optional<std::uint32_t> resolveOpcode(const NormalizedTargetModel &Model,
+ std::string_view Symbol) {
+ if (const NormalizedInstruction *Instruction = findInstructionBySymbol(Model, Symbol))
+ return Instruction->ID;
+ return genericOpcodeValue(Symbol);
+}
+
+[[nodiscard]] bool isRegisterOperand(const NormalizedInstruction &Instruction,
+ std::uint32_t Index) {
+ return Index < Instruction.Operands.size() &&
+ std::ranges::contains(Instruction.Operands[Index].Kinds, "Register");
+}
+
+[[nodiscard]] std::optional<std::uint32_t> integerPredicateValue(std::string_view Symbol) noexcept {
+ static constexpr std::array<std::pair<std::string_view, IntegerPredicate>, 2> Predicates = {{
+ {"eq", IntegerPredicate::Equal},
+ {"ne", IntegerPredicate::NotEqual},
+ }};
+ if (Symbol == "sgt")
+ return static_cast<std::uint32_t>(IntegerPredicate::SignedGreaterThan);
+ if (Symbol == "sge")
+ return static_cast<std::uint32_t>(IntegerPredicate::SignedGreaterEqual);
+ if (Symbol == "sle")
+ return static_cast<std::uint32_t>(IntegerPredicate::SignedLessEqual);
+ if (Symbol == "slt")
+ return static_cast<std::uint32_t>(IntegerPredicate::SignedLessThan);
+ if (Symbol == "ugt")
+ return static_cast<std::uint32_t>(IntegerPredicate::UnsignedGreaterThan);
+ if (Symbol == "uge")
+ return static_cast<std::uint32_t>(IntegerPredicate::UnsignedGreaterEqual);
+ if (Symbol == "ult")
+ return static_cast<std::uint32_t>(IntegerPredicate::UnsignedLessThan);
+ if (Symbol == "ule")
+ return static_cast<std::uint32_t>(IntegerPredicate::UnsignedLessEqual);
+ const auto *const Position =
+ std::ranges::find(Predicates, Symbol, &decltype(Predicates)::value_type::first);
+ if (Position == Predicates.end())
+ return std::nullopt;
+ return static_cast<std::uint32_t>(Position->second);
+}
+
+[[nodiscard]] std::optional<std::uint32_t>
+floatingPredicateValue(std::string_view Symbol) noexcept {
+ static constexpr std::array<std::pair<std::string_view, FloatingPredicate>, 16> Predicates = {{
+ {"false", FloatingPredicate::AlwaysFalse},
+ {"oeq", FloatingPredicate::OrderedEqual},
+ {"ogt", FloatingPredicate::OrderedGreaterThan},
+ {"oge", FloatingPredicate::OrderedGreaterEqual},
+ {"olt", FloatingPredicate::OrderedLessThan},
+ {"ole", FloatingPredicate::OrderedLessEqual},
+ {"one", FloatingPredicate::OrderedNotEqual},
+ {"ord", FloatingPredicate::Ordered},
+ {"uno", FloatingPredicate::Unordered},
+ {"ueq", FloatingPredicate::UnorderedEqual},
+ {"ugt", FloatingPredicate::UnorderedGreaterThan},
+ {"uge", FloatingPredicate::UnorderedGreaterEqual},
+ {"ult", FloatingPredicate::UnorderedLessThan},
+ {"ule", FloatingPredicate::UnorderedLessEqual},
+ {"une", FloatingPredicate::UnorderedNotEqual},
+ {"true", FloatingPredicate::AlwaysTrue},
+ }};
+ const auto *const Position =
+ std::ranges::find(Predicates, Symbol, &decltype(Predicates)::value_type::first);
+ if (Position == Predicates.end())
+ return std::nullopt;
+ return static_cast<std::uint32_t>(Position->second);
+}
+
+[[nodiscard]] const NormalizedRegister *findRegisterBySymbol(const NormalizedTargetModel &Model,
+ std::string_view Symbol) {
+ const auto Position = std::ranges::find(Model.Registers, Symbol, &NormalizedRegister::Symbol);
+ return Position == Model.Registers.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] std::optional<RegisterOperandFlags>
+registerOperandFlags(std::span<const std::string> Names) noexcept {
+ RegisterOperandFlags Flags;
+ for (const std::string &Name : Names) {
+ const auto Set = [&Flags](RegisterOperandFlag Flag) {
+ Flags.set(Flag);
+ return true;
+ };
+ if (Name == "define")
+ Set(RegisterOperandFlag::Define);
+ else if (Name == "use")
+ Set(RegisterOperandFlag::Use);
+ else if (Name == "implicit")
+ Set(RegisterOperandFlag::Implicit);
+ else if (Name == "kill")
+ Set(RegisterOperandFlag::Kill);
+ else if (Name == "dead")
+ Set(RegisterOperandFlag::Dead);
+ else if (Name == "undef")
+ Set(RegisterOperandFlag::Undef);
+ else if (Name == "early_clobber")
+ Set(RegisterOperandFlag::EarlyClobber);
+ else if (Name == "internal_read")
+ Set(RegisterOperandFlag::InternalRead);
+ else if (Name == "renamable")
+ Set(RegisterOperandFlag::Renamable);
+ else if (Name == "debug")
+ Set(RegisterOperandFlag::Debug);
+ else
+ return std::nullopt;
+ }
+ return Flags;
+}
+
+[[nodiscard]] bool validRegisterOperandFlags(RegisterOperandFlags Flags) noexcept {
+ const bool IsDef = Flags.has(RegisterOperandFlag::Define);
+ const bool IsUse = Flags.has(RegisterOperandFlag::Use);
+ return (IsDef || IsUse) && (!Flags.has(RegisterOperandFlag::Kill) || IsUse) &&
+ (!Flags.has(RegisterOperandFlag::Dead) || IsDef) &&
+ (!Flags.has(RegisterOperandFlag::Undef) || IsUse) &&
+ (!Flags.has(RegisterOperandFlag::EarlyClobber) || IsDef) &&
+ (!Flags.has(RegisterOperandFlag::InternalRead) || (IsDef && IsUse)) &&
+ (!(IsDef && IsUse) || Flags.has(RegisterOperandFlag::InternalRead)) &&
+ (!Flags.has(RegisterOperandFlag::Debug) || (IsUse && !IsDef));
+}
+
+} // namespace
+
+bool CodeGenTarget::normalizeSelectionRules(NormalizedTargetModel &Result) {
+ std::unordered_set<std::string> Symbols;
+ for (const SourceSelectionRule &Rule : Input.SelectionRules) {
+ const auto MatchOpcode = resolveOpcode(Result, Rule.MatchOpcodeSymbol);
+ const NormalizedInstruction *MatchInstruction =
+ findInstructionBySymbol(Result, Rule.MatchOpcodeSymbol);
+ if (!isIdentifier(Rule.Symbol) || !Symbols.insert(Rule.Symbol).second || !MatchOpcode) {
+ fail(Rule.Range, "selection rules require a unique identifier and declared match opcode");
+ return false;
+ }
+ if (MatchInstruction != nullptr && !std::ranges::contains(MatchInstruction->Flags, "Generic")) {
+ fail(Rule.Range, "selection rule match opcode must be generic");
+ return false;
+ }
+ if (Rule.Actions.empty()) {
+ fail(Rule.Range, "selection rule requires at least one generated instruction");
+ return false;
+ }
+
+ const auto NormalizeType = [this](const SourceSelectionTypePredicate &Source)
+ -> std::optional<NormalizedSelectionTypePredicate> {
+ NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any;
+ if (Source.Kind == "scalar")
+ Kind = NormalizedSelectionTypeKind::Scalar;
+ else if (Source.Kind == "pointer")
+ Kind = NormalizedSelectionTypeKind::Pointer;
+ else if (Source.Kind != "any") {
+ fail(Source.Range, "selection type kind must be 'any', 'scalar', or 'pointer'");
+ return std::nullopt;
+ }
+ if (Source.Bits > LowLevelType::MaxBitWidth ||
+ (Kind != NormalizedSelectionTypeKind::Any && Source.Bits == 0)) {
+ fail(Source.Range, "selection type bit width is invalid for its kind");
+ return std::nullopt;
+ }
+ return NormalizedSelectionTypePredicate{.Kind = Kind, .Bits = Source.Bits};
+ };
+ const auto NormalizeRanges = [this](const std::vector<SourceSelectionConstantRange> &Ranges,
+ TextRange PredicateRange, std::string_view PredicateName)
+ -> std::optional<std::vector<NormalizedSelectionConstantRange>> {
+ if (Ranges.empty()) {
+ fail(PredicateRange, std::string(PredicateName) + " requires at least one range");
+ return std::nullopt;
+ }
+ std::vector<NormalizedSelectionConstantRange> NormalizedRanges;
+ NormalizedRanges.reserve(Ranges.size());
+ for (const SourceSelectionConstantRange &Range : Ranges) {
+ if (Range.Minimum > Range.Maximum) {
+ fail(Range.SourceRange, std::string(PredicateName) + " range is invalid");
+ return std::nullopt;
+ }
+ NormalizedRanges.push_back({
+ .Minimum = Range.Minimum,
+ .Maximum = Range.Maximum,
+ });
+ }
+ std::ranges::sort(NormalizedRanges, {}, &NormalizedSelectionConstantRange::Minimum);
+ for (std::size_t Index = 1; Index < NormalizedRanges.size(); ++Index) {
+ const auto &Previous = NormalizedRanges[Index - 1];
+ const auto &Current = NormalizedRanges[Index];
+ if (Current.Minimum <= Previous.Maximum) {
+ fail(PredicateRange, std::string(PredicateName) + " ranges overlap");
+ return std::nullopt;
+ }
+ }
+ return NormalizedRanges;
+ };
+
+ NormalizedSelectionRule Normalized;
+ Normalized.Symbol = Rule.Symbol;
+ Normalized.Priority = Rule.Priority;
+ Normalized.Specificity = 1;
+ Normalized.MatchOpcode = *MatchOpcode;
+ Normalized.EraseOperandDefinitions = Rule.EraseOperandDefinitions;
+ if (Rule.ResultType) {
+ auto Type = NormalizeType(*Rule.ResultType);
+ if (!Type)
+ return false;
+ if (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, 0)) {
+ fail(Rule.ResultType->Range,
+ "selection result type predicate requires register operand zero");
+ return false;
+ }
+ Normalized.ResultType = *Type;
+ ++Normalized.Specificity;
+ }
+
+ if (Rule.IntegerPredicate) {
+ const auto Predicate = integerPredicateValue(Rule.IntegerPredicate->Predicate);
+ if (!Predicate) {
+ fail(Rule.IntegerPredicate->Range,
+ "selection integer predicate is not a recognized integer predicate");
+ return false;
+ }
+ if (MatchInstruction != nullptr &&
+ (Rule.IntegerPredicate->OperandIndex >= MatchInstruction->Operands.size() ||
+ !std::ranges::contains(
+ MatchInstruction->Operands[Rule.IntegerPredicate->OperandIndex].Kinds,
+ "Predicate"))) {
+ fail(Rule.IntegerPredicate->Range,
+ "selection integer predicate requires a predicate operand");
+ return false;
+ }
+ Normalized.IntegerPredicate = {
+ .OperandIndex = Rule.IntegerPredicate->OperandIndex,
+ .Predicate = *Predicate,
+ };
+ ++Normalized.Specificity;
+ }
+
+ if (Rule.IntegerPredicate && Rule.FloatingPredicate) {
+ fail(Rule.Range, "selection rule cannot require both integer and floating predicates");
+ return false;
+ }
+ if (Rule.FloatingPredicate) {
+ const auto Predicate = floatingPredicateValue(Rule.FloatingPredicate->Predicate);
+ if (!Predicate) {
+ fail(Rule.FloatingPredicate->Range,
+ "selection floating predicate is not a recognized floating predicate");
+ return false;
+ }
+ if (MatchInstruction != nullptr &&
+ (Rule.FloatingPredicate->OperandIndex >= MatchInstruction->Operands.size() ||
+ !std::ranges::contains(
+ MatchInstruction->Operands[Rule.FloatingPredicate->OperandIndex].Kinds,
+ "Predicate"))) {
+ fail(Rule.FloatingPredicate->Range,
+ "selection floating predicate requires a predicate operand");
+ return false;
+ }
+ Normalized.FloatingPredicate = {
+ .OperandIndex = Rule.FloatingPredicate->OperandIndex,
+ .Predicate = *Predicate,
+ };
+ ++Normalized.Specificity;
+ }
+
+ if (Rule.MemoryPredicate) {
+ if (Rule.MemoryPredicate->Bytes == 0 ||
+ (MatchInstruction != nullptr && !MatchInstruction->RequiresMemoryOperands)) {
+ fail(Rule.MemoryPredicate->Range,
+ "selection memory predicate requires a nonzero size and a memory opcode");
+ return false;
+ }
+ Normalized.MemoryPredicate = {
+ .MemoryOperandIndex = Rule.MemoryPredicate->MemoryOperandIndex,
+ .Bytes = Rule.MemoryPredicate->Bytes,
+ };
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::string> TargetPredicateSymbols;
+ for (const SourceSelectionTargetPredicate &Predicate : Rule.TargetPredicates) {
+ if (!isIdentifier(Predicate.Symbol) ||
+ !TargetPredicateSymbols.insert(Predicate.Symbol).second) {
+ fail(Predicate.Range, "selection target predicates require distinct identifiers");
+ return false;
+ }
+ Normalized.TargetPredicates.push_back(Predicate.Symbol);
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> TypeOperands;
+ for (const SourceSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) {
+ auto Type = NormalizeType(Predicate.Type);
+ if (!Type)
+ return false;
+ if (!TypeOperands.insert(Predicate.OperandIndex).second ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) {
+ fail(Predicate.Range,
+ "selection operand type predicates require distinct register operands");
+ return false;
+ }
+ Normalized.OperandTypes.push_back({.OperandIndex = Predicate.OperandIndex, .Type = *Type});
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> BankOperands;
+ for (const SourceSelectionOperandBankPredicate &Predicate : Rule.OperandBanks) {
+ const auto Bank = BankBySymbol.find(Predicate.BankSymbol);
+ if (!BankOperands.insert(Predicate.OperandIndex).second || Bank == BankBySymbol.end() ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) {
+ fail(Predicate.Range,
+ "selection operand bank predicates require distinct register operands and declared "
+ "banks");
+ return false;
+ }
+ Normalized.OperandBanks.push_back(
+ {.OperandIndex = Predicate.OperandIndex, .Bank = Bank->second});
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> ClassOperands;
+ for (const SourceSelectionOperandClassPredicate &Predicate : Rule.OperandClasses) {
+ const auto RegClass = ClassBySymbol.find(Predicate.ClassSymbol);
+ if (!ClassOperands.insert(Predicate.OperandIndex).second || RegClass == ClassBySymbol.end() ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) {
+ fail(Predicate.Range,
+ "selection operand class predicates require distinct register operands and declared "
+ "classes");
+ return false;
+ }
+ Normalized.OperandClasses.push_back(
+ {.OperandIndex = Predicate.OperandIndex, .RegClass = RegClass->second});
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> ImmediateOperands;
+ for (const SourceSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) {
+ if (!ImmediateOperands.insert(Predicate.OperandIndex).second) {
+ fail(Predicate.Range, "selection operand immediate predicates require distinct operands");
+ return false;
+ }
+ auto Ranges = NormalizeRanges(Predicate.Ranges, Predicate.Range,
+ "selection operand immediate predicate");
+ if (!Ranges)
+ return false;
+ Normalized.OperandImmediates.push_back({
+ .OperandIndex = Predicate.OperandIndex,
+ .Ranges = std::move(*Ranges),
+ });
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> ConstantOperands;
+ for (const SourceSelectionOperandDefConstantPredicate &Predicate : Rule.OperandDefConstants) {
+ if (!ConstantOperands.insert(Predicate.OperandIndex).second ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) {
+ fail(Predicate.Range,
+ "selection operand definition constant predicates require distinct register operands");
+ return false;
+ }
+ auto Ranges = NormalizeRanges(Predicate.Ranges, Predicate.Range,
+ "selection operand definition constant predicate");
+ if (!Ranges)
+ return false;
+ Normalized.OperandDefConstants.push_back({
+ .OperandIndex = Predicate.OperandIndex,
+ .Ranges = std::move(*Ranges),
+ });
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_map<std::string, std::uint32_t> CaptureIDs;
+ std::unordered_set<std::uint32_t> DefUseRootOperands;
+ for (const SourceSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates) {
+ const auto DefOpcode = resolveOpcode(Result, Predicate.OpcodeSymbol);
+ if (!DefOpcode || !isGenericOpcode(Opcode::fromValue(*DefOpcode)) ||
+ !DefUseRootOperands.insert(Predicate.RootOperandIndex).second ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.RootOperandIndex))) {
+ fail(Predicate.Range,
+ "selection def-use predicates require distinct register operands and generic opcodes");
+ return false;
+ }
+
+ NormalizedSelectionDefUsePredicate NormalizedPredicate{
+ .RootOperandIndex = Predicate.RootOperandIndex,
+ .Opcode = *DefOpcode,
+ .IntegerPredicate = std::nullopt,
+ .Captures = {},
+ .OperandDefConstants = {},
+ };
+ if (Predicate.IntegerPredicate) {
+ const auto Integer = integerPredicateValue(Predicate.IntegerPredicate->Predicate);
+ if (!Integer) {
+ fail(Predicate.IntegerPredicate->Range,
+ "selection def-use integer predicate is not a recognized integer predicate");
+ return false;
+ }
+ NormalizedPredicate.IntegerPredicate = {
+ .OperandIndex = Predicate.IntegerPredicate->OperandIndex,
+ .Predicate = *Integer,
+ };
+ }
+ std::unordered_set<std::uint32_t> CapturedOperands;
+ for (const SourceSelectionDefUseCapture &Capture : Predicate.Captures) {
+ if (!isIdentifier(Capture.Symbol) ||
+ !CapturedOperands.insert(Capture.OperandIndex).second ||
+ !CaptureIDs.emplace(Capture.Symbol, CaptureIDs.size() + 1U).second) {
+ fail(Capture.Range,
+ "selection def-use captures require unique identifiers and distinct operands");
+ return false;
+ }
+ NormalizedPredicate.Captures.push_back({
+ .ID = CaptureIDs.at(Capture.Symbol),
+ .OperandIndex = Capture.OperandIndex,
+ });
+ }
+
+ std::unordered_set<std::uint32_t> ConstantOperandsInDef;
+ for (const SourceSelectionOperandDefConstantPredicate &Constant :
+ Predicate.OperandDefConstants) {
+ if (!ConstantOperandsInDef.insert(Constant.OperandIndex).second) {
+ fail(Constant.Range, "selection def-use constant predicates require distinct operands");
+ return false;
+ }
+ auto Ranges = NormalizeRanges(Constant.Ranges, Constant.Range,
+ "selection def-use constant predicate");
+ if (!Ranges)
+ return false;
+ NormalizedPredicate.OperandDefConstants.push_back({
+ .OperandIndex = Constant.OperandIndex,
+ .Ranges = std::move(*Ranges),
+ });
+ }
+ Normalized.DefUsePredicates.push_back(std::move(NormalizedPredicate));
+ Normalized.Specificity += 1U +
+ static_cast<std::uint32_t>(Predicate.IntegerPredicate.has_value()) +
+ static_cast<std::uint32_t>(Predicate.Captures.size()) +
+ static_cast<std::uint32_t>(Predicate.OperandDefConstants.size());
+ }
+
+ std::unordered_map<std::string, std::uint32_t> TemporaryIDs;
+ for (const SourceSelectionTemporary &Temporary : Rule.Temporaries) {
+ if (!isIdentifier(Temporary.Symbol) ||
+ !TemporaryIDs.emplace(Temporary.Symbol, TemporaryIDs.size() + 1U).second ||
+ (Temporary.TypeFromOperand.has_value() == Temporary.Type.has_value())) {
+ fail(Temporary.Range, "selection temporary requires a unique identifier and one type");
+ return false;
+ }
+ NormalizedSelectionTemporary NormalizedTemporary{
+ .ID = TemporaryIDs.at(Temporary.Symbol),
+ .TypeFromOperand = std::nullopt,
+ .Type = std::nullopt,
+ };
+ if (Temporary.TypeFromOperand) {
+ if (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, *Temporary.TypeFromOperand)) {
+ fail(Temporary.Range,
+ "selection temporary type source must be a matched register operand");
+ return false;
+ }
+ NormalizedTemporary.TypeFromOperand = Temporary.TypeFromOperand;
+ } else {
+ auto Type = NormalizeType(*Temporary.Type);
+ if (!Type || Type->Kind != NormalizedSelectionTypeKind::Scalar) {
+ fail(Temporary.Range, "selection temporary explicit type must be a scalar");
+ return false;
+ }
+ NormalizedTemporary.Type = *Type;
+ }
+ Normalized.Temporaries.push_back(NormalizedTemporary);
+ }
+
+ bool HasRootAction = false;
+ for (const SourceSelectionAction &Action : Rule.Actions) {
+ const auto ResultOpcode = resolveOpcode(Result, Action.ResultOpcodeSymbol);
+ const NormalizedInstruction *ResultInstruction =
+ findInstructionBySymbol(Result, Action.ResultOpcodeSymbol);
+ const bool IsCorePseudo =
+ ResultOpcode && isCorePseudoOpcode(Opcode::fromValue(*ResultOpcode));
+ if (!ResultOpcode || (ResultInstruction == nullptr && !IsCorePseudo) ||
+ (ResultInstruction != nullptr &&
+ (isGenericOpcode(Opcode::fromValue(*ResultOpcode)) ||
+ std::ranges::contains(ResultInstruction->Flags, "Generic")))) {
+ fail(Action.Range,
+ "selection instruction opcode must name a declared target instruction or core pseudo");
+ return false;
+ }
+ if (Action.ReplacesRoot && HasRootAction) {
+ fail(Action.Range, "selection instructions require exactly one root replacement");
+ return false;
+ }
+ HasRootAction |= Action.ReplacesRoot;
+ if (ResultInstruction != nullptr &&
+ ResultInstruction->Operands.size() != Action.OperandRenderers.size()) {
+ fail(Action.Range, "selection instruction has an incompatible operand count");
+ return false;
+ }
+
+ NormalizedSelectionAction NormalizedAction{
+ .ResultOpcode = *ResultOpcode,
+ .ReplacesRoot = Action.ReplacesRoot,
+ .OperandRenderers = {},
+ };
+ NormalizedAction.OperandRenderers.reserve(Action.OperandRenderers.size());
+ for (const SourceSelectionOperandRenderer &Renderer : Action.OperandRenderers) {
+ if (Renderer.ComplexOperandIndex) {
+ if (Renderer.ComplexRendererSymbol.empty() || !Renderer.Flags.empty() ||
+ Renderer.ImmediateTransform != "identity") {
+ fail(Renderer.Range,
+ "selection complex operand renderer requires a matcher and no flags or transform");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::ComplexOperand,
+ .Value = *Renderer.ComplexOperandIndex,
+ .ComplexRenderer = Renderer.ComplexRendererSymbol,
+ .Flags = 0,
+ });
+ continue;
+ }
+
+ if (Renderer.OperandIndex) {
+ if (MatchInstruction != nullptr &&
+ *Renderer.OperandIndex >= MatchInstruction->Operands.size()) {
+ fail(Renderer.Range, "selection operand renderer references an unknown operand");
+ return false;
+ }
+ if (!Renderer.Flags.empty()) {
+ fail(Renderer.Range, "selection operand renderer cannot replace source register flags");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::Operand,
+ .Value = *Renderer.OperandIndex,
+ .ComplexRenderer = {},
+ .Flags = 0,
+ });
+ continue;
+ }
+
+ if (!Renderer.PhysicalRegisterSymbol.empty()) {
+ const NormalizedRegister *Physical =
+ findRegisterBySymbol(Result, Renderer.PhysicalRegisterSymbol);
+ const auto Flags = registerOperandFlags(Renderer.Flags);
+ if (Physical == nullptr || Physical->ID == 0 || !Flags ||
+ !validRegisterOperandFlags(*Flags)) {
+ fail(Renderer.Range,
+ "selection physical register renderer requires a declared register and valid "
+ "register flags");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::PhysicalRegister,
+ .Value = Physical->ID,
+ .ComplexRenderer = {},
+ .Flags = Flags->rawValue(),
+ });
+ continue;
+ }
+
+ if (!Renderer.TemporarySymbol.empty()) {
+ const auto Temporary = TemporaryIDs.find(Renderer.TemporarySymbol);
+ const auto Flags = registerOperandFlags(Renderer.Flags);
+ if (Temporary == TemporaryIDs.end() || !Flags || !validRegisterOperandFlags(*Flags)) {
+ fail(Renderer.Range,
+ "selection temporary renderer requires a declared temporary and valid register "
+ "flags");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::Temporary,
+ .Value = Temporary->second,
+ .ComplexRenderer = {},
+ .Flags = Flags->rawValue(),
+ });
+ continue;
+ }
+
+ if (!Renderer.CapturedOperandSymbol.empty()) {
+ const auto Capture = CaptureIDs.find(Renderer.CapturedOperandSymbol);
+ if (Capture == CaptureIDs.end() || !Renderer.Flags.empty() ||
+ Renderer.ImmediateTransform != "identity") {
+ fail(Renderer.Range,
+ "selection captured operand renderer requires a matched capture and no flags");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::CapturedOperand,
+ .Value = Capture->second,
+ .ComplexRenderer = {},
+ .Flags = 0,
+ });
+ continue;
+ }
+
+ if (Renderer.CapturedImmediateOperand) {
+ const auto Transform =
+ [&Renderer]() -> std::optional<NormalizedSelectionImmediateTransform> {
+ if (Renderer.ImmediateTransform == "identity")
+ return NormalizedSelectionImmediateTransform::Identity;
+ if (Renderer.ImmediateTransform == "negate")
+ return NormalizedSelectionImmediateTransform::Negate;
+ if (Renderer.ImmediateTransform == "plus_one")
+ return NormalizedSelectionImmediateTransform::PlusOne;
+ return std::nullopt;
+ }();
+ if (!ConstantOperands.contains(*Renderer.CapturedImmediateOperand) || !Transform ||
+ !Renderer.Flags.empty()) {
+ fail(Renderer.Range,
+ "selection captured immediate renderer requires a matched constant and known "
+ "transform");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::CapturedImmediate,
+ .Value = *Renderer.CapturedImmediateOperand,
+ .ComplexRenderer = {},
+ .Flags = 0,
+ .Transform = *Transform,
+ });
+ continue;
+ }
+
+ if (Renderer.Immediate) {
+ if (!Renderer.Flags.empty() || Renderer.ImmediateTransform != "identity") {
+ fail(Renderer.Range, "selection immediate renderer cannot carry register flags");
+ return false;
+ }
+ NormalizedAction.OperandRenderers.push_back({
+ .SourceKind = NormalizedSelectionOperandRenderer::Kind::Immediate,
+ .Value = 0,
+ .ComplexRenderer = {},
+ .Flags = 0,
+ .Immediate = *Renderer.Immediate,
+ });
+ continue;
+ }
+
+ fail(Renderer.Range, "selection operand renderer has no normalized source");
+ return false;
+ }
+ Normalized.Actions.push_back(std::move(NormalizedAction));
+ }
+ if (!HasRootAction) {
+ fail(Rule.Range, "selection instructions require exactly one root replacement");
+ return false;
+ }
+
+ std::unordered_set<std::uint32_t> ErasedOperands;
+ for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) {
+ if (!ErasedOperands.insert(OperandIndex).second ||
+ (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, OperandIndex))) {
+ fail(Rule.Range, "selection erased definitions require distinct register operands");
+ return false;
+ }
+ if (!ConstantOperands.contains(OperandIndex) && !DefUseRootOperands.contains(OperandIndex)) {
+ fail(Rule.Range,
+ "selection erased definition requires a matched constant or def-use operand");
+ return false;
+ }
+ }
+ Result.SelectionRules.push_back(std::move(Normalized));
+ }
+
+ std::ranges::sort(Result.SelectionRules,
+ [](const NormalizedSelectionRule &Left, const NormalizedSelectionRule &Right) {
+ if (Left.Priority != Right.Priority)
+ return Left.Priority > Right.Priority;
+ if (Left.Specificity != Right.Specificity)
+ return Left.Specificity > Right.Specificity;
+ return Left.Symbol < Right.Symbol;
+ });
+ return true;
+}
+
+bool CodeGenTarget::normalizeCombinerRules(NormalizedTargetModel &Result) {
+ std::unordered_set<std::string> Symbols;
+ const auto NormalizeType = [this](const SourceSelectionTypePredicate &Source)
+ -> std::optional<NormalizedSelectionTypePredicate> {
+ NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any;
+ if (Source.Kind == "scalar")
+ Kind = NormalizedSelectionTypeKind::Scalar;
+ else if (Source.Kind == "pointer")
+ Kind = NormalizedSelectionTypeKind::Pointer;
+ else if (Source.Kind != "any") {
+ fail(Source.Range, "combiner type kind must be 'any', 'scalar', or 'pointer'");
+ return std::nullopt;
+ }
+ if (Source.Bits > LowLevelType::MaxBitWidth ||
+ (Kind != NormalizedSelectionTypeKind::Any && Source.Bits == 0)) {
+ fail(Source.Range, "combiner type bit width is invalid for its kind");
+ return std::nullopt;
+ }
+ return NormalizedSelectionTypePredicate{.Kind = Kind, .Bits = Source.Bits};
+ };
+ const auto NormalizeRanges =
+ [this](const std::vector<SourceSelectionConstantRange> &Ranges,
+ TextRange Range) -> std::optional<std::vector<NormalizedSelectionConstantRange>> {
+ if (Ranges.empty()) {
+ fail(Range, "combiner def-use constant predicate requires at least one range");
+ return std::nullopt;
+ }
+ std::vector<NormalizedSelectionConstantRange> ResultRanges;
+ ResultRanges.reserve(Ranges.size());
+ for (const SourceSelectionConstantRange &Source : Ranges) {
+ if (Source.Minimum > Source.Maximum) {
+ fail(Source.SourceRange, "combiner def-use constant range is inverted");
+ return std::nullopt;
+ }
+ ResultRanges.push_back({.Minimum = Source.Minimum, .Maximum = Source.Maximum});
+ }
+ std::ranges::sort(ResultRanges, {}, &NormalizedSelectionConstantRange::Minimum);
+ for (std::size_t Index = 1; Index < ResultRanges.size(); ++Index) {
+ if (ResultRanges[Index].Minimum <= ResultRanges[Index - 1].Maximum) {
+ fail(Range, "combiner def-use constant ranges overlap");
+ return std::nullopt;
+ }
+ }
+ return ResultRanges;
+ };
+ const auto FlagValue = [](std::string_view Name) -> std::optional<std::uint32_t> {
+ static constexpr std::array<std::pair<std::string_view, MachineInstrFlag>, 3> Flags = {{
+ {"nneg", MachineInstrFlag::NonNeg},
+ {"nsw", MachineInstrFlag::NoSignedWrap},
+ {"nuw", MachineInstrFlag::NoUnsignedWrap},
+ }};
+ const auto *const Position =
+ std::ranges::find(Flags, Name, &decltype(Flags)::value_type::first);
+ if (Position == Flags.end())
+ return std::nullopt;
+ return std::uint32_t{1} << static_cast<std::uint8_t>(Position->second);
+ };
+ const auto FlagMask = [&FlagValue](std::span<const std::string> Names,
+ TextRange Range) -> std::optional<std::uint32_t> {
+ std::uint32_t Mask = 0;
+ for (const std::string &Name : Names) {
+ const auto Value = FlagValue(Name);
+ if (!Value || (Mask & *Value) != 0)
+ return std::nullopt;
+ Mask |= *Value;
+ }
+ if (!Names.empty() && Mask == 0)
+ return std::nullopt;
+ (void)Range;
+ return Mask;
+ };
+
+ for (const SourceCombinerRule &Rule : Input.CombinerRules) {
+ const auto MatchOpcode = resolveOpcode(Result, Rule.MatchOpcodeSymbol);
+ const NormalizedInstruction *MatchInstruction =
+ findInstructionBySymbol(Result, Rule.MatchOpcodeSymbol);
+ if (!isIdentifier(Rule.Symbol) || !Symbols.insert(Rule.Symbol).second || !MatchOpcode) {
+ fail(Rule.Range, "combiner rules require a unique identifier and declared match opcode");
+ return false;
+ }
+ if (MatchInstruction != nullptr && !std::ranges::contains(MatchInstruction->Flags, "Generic")) {
+ fail(Rule.Range, "combiner rule match opcode must be generic");
+ return false;
+ }
+
+ NormalizedCombinerRule Normalized;
+ Normalized.Symbol = Rule.Symbol;
+ Normalized.Priority = Rule.Priority;
+ Normalized.Specificity = 1;
+ Normalized.MatchOpcode = *MatchOpcode;
+ if (Rule.ResultType) {
+ auto Type = NormalizeType(*Rule.ResultType);
+ if (!Type)
+ return false;
+ if (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, 0)) {
+ fail(Rule.ResultType->Range,
+ "combiner result type predicate requires register operand zero");
+ return false;
+ }
+ Normalized.ResultType = *Type;
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::uint32_t> TypeOperands;
+ for (const SourceSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) {
+ auto Type = NormalizeType(Predicate.Type);
+ if (!Type)
+ return false;
+ if (!TypeOperands.insert(Predicate.OperandIndex).second ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) {
+ fail(Predicate.Range,
+ "combiner operand type predicates require distinct register operands");
+ return false;
+ }
+ Normalized.OperandTypes.push_back({.OperandIndex = Predicate.OperandIndex, .Type = *Type});
+ ++Normalized.Specificity;
+ }
+
+ std::unordered_set<std::string> TargetPredicates;
+ for (const SourceSelectionTargetPredicate &Predicate : Rule.TargetPredicates) {
+ if (!isIdentifier(Predicate.Symbol) || !TargetPredicates.insert(Predicate.Symbol).second) {
+ fail(Predicate.Range, "combiner target predicates require distinct identifiers");
+ return false;
+ }
+ Normalized.TargetPredicates.push_back(Predicate.Symbol);
+ ++Normalized.Specificity;
+ }
+
+ std::uint32_t InstructionFlags = 0;
+ for (const std::string &Name : Rule.InstructionFlags) {
+ const auto Value = FlagValue(Name);
+ if (!Value) {
+ fail(Rule.Range, "combiner instruction flag is not supported");
+ return false;
+ }
+ if ((InstructionFlags & *Value) != 0) {
+ fail(Rule.Range, "combiner instruction flags must be unique");
+ return false;
+ }
+ InstructionFlags |= *Value;
+ ++Normalized.Specificity;
+ }
+ if (InstructionFlags != 0)
+ Normalized.InstructionFlags.push_back(InstructionFlags);
+
+ std::unordered_set<std::uint32_t> DefUseRoots;
+ std::unordered_set<std::uint32_t> CaptureIDs;
+ std::unordered_map<std::string, std::uint32_t> Captures;
+ std::uint32_t NextCaptureID = 1;
+ for (const SourceSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates) {
+ const auto DefOpcode = resolveOpcode(Result, Predicate.OpcodeSymbol);
+ const NormalizedInstruction *DefInstruction =
+ findInstructionBySymbol(Result, Predicate.OpcodeSymbol);
+ if (!DefOpcode || !DefUseRoots.insert(Predicate.RootOperandIndex).second ||
+ (MatchInstruction != nullptr &&
+ !isRegisterOperand(*MatchInstruction, Predicate.RootOperandIndex)) ||
+ (DefInstruction != nullptr && !std::ranges::contains(DefInstruction->Flags, "Generic"))) {
+ fail(Predicate.Range, "combiner def-use predicate has an invalid root or opcode");
+ return false;
+ }
+ NormalizedSelectionDefUsePredicate DefUse{
+ .RootOperandIndex = Predicate.RootOperandIndex,
+ .Opcode = *DefOpcode,
+ .IntegerPredicate = std::nullopt,
+ .Captures = {},
+ .OperandDefConstants = {},
+ };
+ for (const SourceSelectionDefUseCapture &Capture : Predicate.Captures) {
+ if (!isIdentifier(Capture.Symbol) || Captures.contains(Capture.Symbol) ||
+ !CaptureIDs.insert(NextCaptureID).second ||
+ (DefInstruction != nullptr &&
+ !isRegisterOperand(*DefInstruction, Capture.OperandIndex))) {
+ fail(Capture.Range, "combiner captures require unique identifiers and register operands");
+ return false;
+ }
+ Captures.emplace(Capture.Symbol, NextCaptureID);
+ DefUse.Captures.push_back({.ID = NextCaptureID, .OperandIndex = Capture.OperandIndex});
+ ++NextCaptureID;
+ }
+ for (const SourceSelectionOperandDefConstantPredicate &PredicateConstant :
+ Predicate.OperandDefConstants) {
+ auto Ranges = NormalizeRanges(PredicateConstant.Ranges, PredicateConstant.Range);
+ if (!Ranges)
+ return false;
+ if (DefInstruction != nullptr &&
+ (PredicateConstant.OperandIndex >= DefInstruction->Operands.size() ||
+ (!std::ranges::contains(DefInstruction->Operands[PredicateConstant.OperandIndex].Kinds,
+ "Immediate") &&
+ !std::ranges::contains(DefInstruction->Operands[PredicateConstant.OperandIndex].Kinds,
+ "IntegerImmediate")))) {
+ fail(PredicateConstant.Range, "combiner def-use constant operand is not immediate");
+ return false;
+ }
+ DefUse.OperandDefConstants.push_back({
+ .OperandIndex = PredicateConstant.OperandIndex,
+ .Ranges = std::move(*Ranges),
+ });
+ }
+ Normalized.DefUsePredicates.push_back(std::move(DefUse));
+ ++Normalized.Specificity;
+ }
+ if (Rule.DefUsePredicates.empty()) {
+ fail(Rule.Range, "combiner rule requires a def-use predicate");
+ return false;
+ }
+
+ NormalizedCombinerAction Action;
+ if (Rule.Action.Kind == SourceCombinerActionKind::ReplaceRootWithCapturedOperand) {
+ const auto Capture = Captures.find(Rule.Action.CapturedOperandSymbol);
+ if (Capture == Captures.end()) {
+ fail(Rule.Action.Range, "combiner replacement requires a matched capture");
+ return false;
+ }
+ Action.Kind = NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand;
+ Action.CapturedOperand = Capture->second;
+ } else {
+ const auto ResultOpcode = resolveOpcode(Result, Rule.Action.ResultOpcodeSymbol);
+ const NormalizedInstruction *ResultInstruction =
+ findInstructionBySymbol(Result, Rule.Action.ResultOpcodeSymbol);
+ if (!ResultOpcode || (ResultInstruction != nullptr &&
+ !std::ranges::contains(ResultInstruction->Flags, "Generic"))) {
+ fail(Rule.Action.Range, "combiner mutation requires a declared generic result opcode");
+ return false;
+ }
+ Action.Kind = NormalizedCombinerActionKind::MutateRoot;
+ Action.ResultOpcode = *ResultOpcode;
+ for (const SourceCombinerOperandRenderer &Renderer : Rule.Action.OperandRenderers) {
+ NormalizedCombinerOperandRenderer NormalizedRenderer;
+ switch (Renderer.SourceKind) {
+ case SourceCombinerOperandRenderer::Kind::RootOperand:
+ NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::RootOperand;
+ NormalizedRenderer.Value = Renderer.OperandIndex;
+ break;
+ case SourceCombinerOperandRenderer::Kind::CapturedOperand: {
+ const auto Capture = Captures.find(Renderer.CaptureSymbol);
+ if (Capture == Captures.end()) {
+ fail(Renderer.Range, "combiner renderer requires a matched capture");
+ return false;
+ }
+ NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::CapturedOperand;
+ NormalizedRenderer.Value = Capture->second;
+ break;
+ }
+ case SourceCombinerOperandRenderer::Kind::CapturedTypeBits: {
+ const auto Capture = Captures.find(Renderer.CaptureSymbol);
+ if (Capture == Captures.end()) {
+ fail(Renderer.Range, "combiner type-width renderer requires a matched capture");
+ return false;
+ }
+ NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::CapturedTypeBits;
+ NormalizedRenderer.Value = Capture->second;
+ break;
+ }
+ case SourceCombinerOperandRenderer::Kind::Immediate:
+ NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::Immediate;
+ NormalizedRenderer.Immediate = Renderer.Immediate;
+ break;
+ }
+ Action.OperandRenderers.push_back(NormalizedRenderer);
+ }
+ if (Action.OperandRenderers.empty()) {
+ fail(Rule.Action.Range, "combiner mutation requires operand renderers");
+ return false;
+ }
+ }
+ if (!Rule.Action.ClearInstructionFlags.empty()) {
+ const auto ClearFlags = FlagMask(Rule.Action.ClearInstructionFlags, Rule.Action.Range);
+ if (!ClearFlags) {
+ fail(Rule.Action.Range, "combiner action clear flags are invalid or duplicated");
+ return false;
+ }
+ Action.ClearInstructionFlags = *ClearFlags;
+ }
+ Normalized.Action = std::move(Action);
+ std::unordered_set<std::uint32_t> Erased;
+ for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) {
+ if (!Erased.insert(OperandIndex).second ||
+ (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, OperandIndex)) ||
+ !DefUseRoots.contains(OperandIndex)) {
+ fail(Rule.Range, "combiner erased definitions must be distinct def-use operands");
+ return false;
+ }
+ Normalized.EraseOperandDefinitions.push_back(OperandIndex);
+ }
+ Result.CombinerRules.push_back(std::move(Normalized));
+ }
+
+ std::ranges::sort(Result.CombinerRules,
+ [](const NormalizedCombinerRule &Left, const NormalizedCombinerRule &Right) {
+ if (Left.Priority != Right.Priority)
+ return Left.Priority > Right.Priority;
+ if (Left.Specificity != Right.Specificity)
+ return Left.Specificity > Right.Specificity;
+ return Left.Symbol < Right.Symbol;
+ });
+ 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..478457c
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenTarget.cpp
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Common/CodeGenTarget.h"
+
+#include "TableGenBackends.h"
+#include "rcc/TableGen/Parser.h"
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::targetgen {
+
+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) ||
+ !normalizeSelectionRules(Result) || !normalizeCombinerRules(Result) ||
+ !normalizeMacroFusionRules(Result) || !normalizeMCPseudoExpansions(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 ||
+ !std::has_single_bit(Convention.StackAlignmentBytes) ||
+ !std::has_single_bit(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),
+ .GlobalISel = emitGlobalISel(*Model, DisplayName),
+ .GlobalISelCombiner = emitGlobalISelCombiner(*Model, DisplayName),
+ .MacroFusion = emitMacroFusion(*Model, DisplayName),
+ .MCPseudoLowering = emitMCPseudoLowering(*Model, DisplayName),
+ },
+ .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..a2aed7c
--- /dev/null
+++ b/backend/utils/TableGen/Common/CodeGenTarget.h
@@ -0,0 +1,485 @@
+// 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;
+ std::vector<std::uint32_t> SuperClasses;
+ 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;
+};
+
+enum class NormalizedMacroFusionInstruction : std::uint8_t {
+ First,
+ Second,
+};
+
+struct NormalizedMacroFusionOpcodePair final {
+ std::uint32_t FirstOpcode = 0;
+ std::uint32_t SecondOpcode = 0;
+};
+
+struct NormalizedMacroFusionImmediatePredicate final {
+ NormalizedMacroFusionInstruction Instruction = NormalizedMacroFusionInstruction::First;
+ std::uint32_t OperandIndex = 0;
+ std::int64_t Minimum = 0;
+ std::int64_t Maximum = 0;
+};
+
+struct NormalizedMacroFusionRegisterInequality final {
+ NormalizedMacroFusionInstruction Instruction = NormalizedMacroFusionInstruction::First;
+ std::uint32_t LeftOperand = 0;
+ std::uint32_t RightOperand = 0;
+};
+
+struct NormalizedMacroFusionImmediateRelation final {
+ std::uint32_t FirstOperand = 0;
+ std::uint32_t SecondOperand = 0;
+};
+
+struct NormalizedMacroFusionRule final {
+ std::string Symbol;
+ std::string Feature;
+ std::vector<std::uint32_t> FirstOpcodes;
+ std::vector<std::uint32_t> SecondOpcodes;
+ std::vector<NormalizedMacroFusionOpcodePair> OpcodePairs;
+ std::vector<NormalizedMacroFusionImmediatePredicate> ImmediatePredicates;
+ std::vector<NormalizedMacroFusionRegisterInequality> RegisterInequalities;
+ std::vector<NormalizedMacroFusionImmediateRelation> ImmediateRelations;
+ std::uint32_t FirstDefinitionOperand = 0;
+ std::uint32_t SecondUseOperand = 0;
+ std::uint32_t SecondDefinitionOperand = 0;
+ std::optional<std::uint32_t> CommutableSecondUseOperand;
+ bool RequireSingleNonDebugUse = false;
+ bool RequireSecondDestinationTie = false;
+};
+
+enum class NormalizedMCPseudoOperandKind : std::uint8_t {
+ SourceOperand,
+ Register,
+ Immediate,
+};
+
+struct NormalizedMCPseudoOperand final {
+ NormalizedMCPseudoOperandKind Kind = NormalizedMCPseudoOperandKind::SourceOperand;
+ std::uint32_t Value = 0;
+ std::int64_t Immediate = 0;
+};
+
+struct NormalizedMCPseudoExpansion final {
+ std::uint32_t PseudoOpcode = 0;
+ std::uint32_t ResultOpcode = 0;
+ std::vector<NormalizedMCPseudoOperand> Operands;
+};
+
+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::optional<std::uint16_t> TypeIndex;
+ 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 SizeInBytes = 0;
+ std::uint32_t AssemblyFormat = 0;
+ bool RequiresMemoryOperands = false;
+ std::string Owner;
+ std::string Deadline;
+};
+
+enum class NormalizedSelectionTypeKind : std::uint8_t {
+ Any,
+ Scalar,
+ Pointer,
+};
+
+struct NormalizedSelectionTypePredicate final {
+ NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any;
+ std::uint32_t Bits = 0;
+};
+
+struct NormalizedSelectionOperandTypePredicate final {
+ std::uint32_t OperandIndex = 0;
+ NormalizedSelectionTypePredicate Type;
+};
+
+struct NormalizedSelectionOperandBankPredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::uint32_t Bank = 0;
+};
+
+struct NormalizedSelectionOperandClassPredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::uint32_t RegClass = 0;
+};
+
+struct NormalizedSelectionIntegerPredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::uint32_t Predicate = 0;
+};
+
+struct NormalizedSelectionFloatingPredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::uint32_t Predicate = 0;
+};
+
+struct NormalizedSelectionMemoryPredicate final {
+ std::uint32_t MemoryOperandIndex = 0;
+ std::uint32_t Bytes = 0;
+};
+
+struct NormalizedSelectionConstantRange final {
+ std::int64_t Minimum = 0;
+ std::int64_t Maximum = 0;
+};
+
+struct NormalizedSelectionOperandImmediatePredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::vector<NormalizedSelectionConstantRange> Ranges;
+};
+
+struct NormalizedSelectionOperandDefConstantPredicate final {
+ std::uint32_t OperandIndex = 0;
+ std::vector<NormalizedSelectionConstantRange> Ranges;
+};
+
+struct NormalizedSelectionDefUseCapture final {
+ std::uint32_t ID = 0;
+ std::uint32_t OperandIndex = 0;
+};
+
+struct NormalizedSelectionDefUsePredicate final {
+ std::uint32_t RootOperandIndex = 0;
+ std::uint32_t Opcode = 0;
+ std::optional<NormalizedSelectionIntegerPredicate> IntegerPredicate;
+ std::vector<NormalizedSelectionDefUseCapture> Captures;
+ std::vector<NormalizedSelectionOperandDefConstantPredicate> OperandDefConstants;
+};
+
+struct NormalizedSelectionTemporary final {
+ std::uint32_t ID = 0;
+ std::optional<std::uint32_t> TypeFromOperand;
+ std::optional<NormalizedSelectionTypePredicate> Type;
+};
+
+enum class NormalizedSelectionImmediateTransform : std::uint8_t {
+ Identity,
+ Negate,
+ PlusOne,
+};
+
+struct NormalizedSelectionOperandRenderer final {
+ enum class Kind : std::uint8_t {
+ Operand,
+ ComplexOperand,
+ PhysicalRegister,
+ Temporary,
+ CapturedOperand,
+ CapturedImmediate,
+ Immediate,
+ };
+
+ Kind SourceKind = Kind::Operand;
+ std::uint32_t Value = 0;
+ std::string ComplexRenderer;
+ std::uint16_t Flags = 0;
+ NormalizedSelectionImmediateTransform Transform = NormalizedSelectionImmediateTransform::Identity;
+ std::int64_t Immediate = 0;
+};
+
+struct NormalizedSelectionAction final {
+ std::uint32_t ResultOpcode = 0;
+ bool ReplacesRoot = false;
+ std::vector<NormalizedSelectionOperandRenderer> OperandRenderers;
+};
+
+struct NormalizedSelectionRule final {
+ std::string Symbol;
+ std::uint32_t Priority = 0;
+ std::uint32_t Specificity = 0;
+ std::uint32_t MatchOpcode = 0;
+ std::optional<NormalizedSelectionTypePredicate> ResultType;
+ std::optional<NormalizedSelectionIntegerPredicate> IntegerPredicate;
+ std::optional<NormalizedSelectionFloatingPredicate> FloatingPredicate;
+ std::optional<NormalizedSelectionMemoryPredicate> MemoryPredicate;
+ std::vector<std::string> TargetPredicates;
+ std::vector<NormalizedSelectionOperandTypePredicate> OperandTypes;
+ std::vector<NormalizedSelectionOperandBankPredicate> OperandBanks;
+ std::vector<NormalizedSelectionOperandClassPredicate> OperandClasses;
+ std::vector<NormalizedSelectionOperandImmediatePredicate> OperandImmediates;
+ std::vector<NormalizedSelectionOperandDefConstantPredicate> OperandDefConstants;
+ std::vector<NormalizedSelectionDefUsePredicate> DefUsePredicates;
+ std::vector<NormalizedSelectionTemporary> Temporaries;
+ std::vector<NormalizedSelectionAction> Actions;
+ std::vector<std::uint32_t> EraseOperandDefinitions;
+};
+
+enum class NormalizedCombinerActionKind : std::uint8_t {
+ ReplaceRootWithCapturedOperand,
+ MutateRoot,
+};
+
+struct NormalizedCombinerOperandRenderer final {
+ enum class Kind : std::uint8_t {
+ RootOperand,
+ CapturedOperand,
+ CapturedTypeBits,
+ Immediate,
+ };
+
+ Kind SourceKind = Kind::RootOperand;
+ std::uint32_t Value = 0;
+ std::int64_t Immediate = 0;
+};
+
+struct NormalizedCombinerAction final {
+ NormalizedCombinerActionKind Kind = NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand;
+ std::uint32_t CapturedOperand = 0;
+ std::uint32_t ResultOpcode = 0;
+ std::vector<NormalizedCombinerOperandRenderer> OperandRenderers;
+ std::uint32_t ClearInstructionFlags = 0;
+};
+
+struct NormalizedCombinerRule final {
+ std::string Symbol;
+ std::uint32_t Priority = 0;
+ std::uint32_t Specificity = 0;
+ std::uint32_t MatchOpcode = 0;
+ std::optional<NormalizedSelectionTypePredicate> ResultType;
+ std::vector<NormalizedSelectionOperandTypePredicate> OperandTypes;
+ std::vector<std::string> TargetPredicates;
+ std::vector<std::uint32_t> InstructionFlags;
+ std::vector<NormalizedSelectionDefUsePredicate> DefUsePredicates;
+ NormalizedCombinerAction Action;
+ std::vector<std::uint32_t> EraseOperandDefinitions;
+};
+
+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;
+ std::vector<NormalizedSelectionRule> SelectionRules;
+ std::vector<NormalizedCombinerRule> CombinerRules;
+ std::vector<NormalizedMacroFusionRule> MacroFusionRules;
+ std::vector<NormalizedMCPseudoExpansion> MCPseudoExpansions;
+};
+
+[[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;
+
+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);
+ [[nodiscard]] bool normalizeSelectionRules(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeCombinerRules(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeMacroFusionRules(NormalizedTargetModel &Result);
+ [[nodiscard]] bool normalizeMCPseudoExpansions(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/GlobalISelCombinerEmitter.cpp b/backend/utils/TableGen/GlobalISelCombinerEmitter.cpp
new file mode 100644
index 0000000..0c727e3
--- /dev/null
+++ b/backend/utils/TableGen/GlobalISelCombinerEmitter.cpp
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+namespace {
+
+void emitInstruction(std::ostream &Output, std::string_view Opcode, std::uint32_t OperandIndex = 0,
+ std::uint32_t Value = 0, std::uint32_t Auxiliary = 0,
+ std::int64_t Immediate = 0, std::string_view Symbol = {}) {
+ Output << " {GIMatchTableOpcode::" << Opcode << ", " << OperandIndex << ", " << Value << ", "
+ << Auxiliary << ", " << Immediate << ", ";
+ appendQuoted(Output, Symbol);
+ Output << "},\n";
+}
+
+[[nodiscard]] std::size_t instructionCount(const NormalizedCombinerRule &Rule) noexcept {
+ std::size_t Count = 2U + (Rule.ResultType ? 1U : 0U) + Rule.OperandTypes.size() +
+ Rule.TargetPredicates.size() + Rule.InstructionFlags.size() +
+ (Rule.Action.ClearInstructionFlags != 0 ? 1U : 0U) +
+ Rule.EraseOperandDefinitions.size();
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates)
+ Count += 1U + DefUse.Captures.size() + DefUse.OperandDefConstants.size();
+ Count += Rule.Action.Kind == NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand
+ ? 1U
+ : 1U + Rule.Action.OperandRenderers.size();
+ return Count;
+}
+
+} // namespace
+
+std::string emitGlobalISelCombiner(const NormalizedTargetModel &Model,
+ std::string_view SourceName) {
+ std::size_t InstructionCount = 0;
+ std::size_t ConstantRangeCount = 0;
+ for (const NormalizedCombinerRule &Rule : Model.CombinerRules) {
+ InstructionCount += instructionCount(Rule);
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates)
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants)
+ ConstantRangeCount += Predicate.Ranges.size();
+ }
+
+ 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 << "#ifdef GET_GLOBALISEL_COMBINER_TABLE\n\n";
+ Output << "constexpr std::array<GIMatchTableConstantRange, " << ConstantRangeCount
+ << "> GeneratedGlobalISelCombinerConstantRanges = {{\n";
+ for (const NormalizedCombinerRule &Rule : Model.CombinerRules) {
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) {
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants) {
+ for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges)
+ Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n";
+ }
+ }
+ }
+ Output << "}};\n\n";
+
+ Output << "constexpr std::array<GIMatchTableInstruction, " << InstructionCount
+ << "> GeneratedGlobalISelCombinerInstructions = {{\n";
+ std::size_t FirstConstantRange = 0;
+ for (const NormalizedCombinerRule &Rule : Model.CombinerRules) {
+ emitInstruction(Output, "CheckOpcode", 0, Rule.MatchOpcode);
+ if (Rule.ResultType) {
+ const char *Kind = "Any";
+ if (Rule.ResultType->Kind == NormalizedSelectionTypeKind::Scalar)
+ Kind = "Scalar";
+ else if (Rule.ResultType->Kind == NormalizedSelectionTypeKind::Pointer)
+ Kind = "Pointer";
+ emitInstruction(Output, "CheckOperandType", 0, Rule.ResultType->Bits,
+ static_cast<std::uint32_t>(Rule.ResultType->Kind));
+ (void)Kind;
+ }
+ for (const NormalizedSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) {
+ emitInstruction(Output, "CheckOperandType", Predicate.OperandIndex, Predicate.Type.Bits,
+ static_cast<std::uint32_t>(Predicate.Type.Kind));
+ }
+ for (const std::string &Predicate : Rule.TargetPredicates)
+ emitInstruction(Output, "CheckTargetPredicate", 0, 0, 0, 0, Predicate);
+ for (const std::uint32_t Flags : Rule.InstructionFlags)
+ emitInstruction(Output, "CheckInstructionFlag", 0, Flags);
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) {
+ emitInstruction(Output, "CheckOperandDefOpcode", DefUse.RootOperandIndex, DefUse.Opcode);
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants) {
+ emitInstruction(Output, "CheckOperandDefOperandConstantRanges", DefUse.RootOperandIndex,
+ static_cast<std::uint32_t>(FirstConstantRange), Predicate.OperandIndex,
+ static_cast<std::int64_t>(Predicate.Ranges.size()));
+ FirstConstantRange += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionDefUseCapture &Capture : DefUse.Captures)
+ emitInstruction(Output, "CaptureDefOperand", DefUse.RootOperandIndex, Capture.ID,
+ Capture.OperandIndex);
+ }
+ if (Rule.Action.Kind == NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand) {
+ emitInstruction(Output, "ReplaceRootWithCapturedOperand", 0, Rule.Action.CapturedOperand);
+ } else {
+ emitInstruction(Output, "MutateOpcode", 0, Rule.Action.ResultOpcode);
+ for (const NormalizedCombinerOperandRenderer &Renderer : Rule.Action.OperandRenderers) {
+ switch (Renderer.SourceKind) {
+ case NormalizedCombinerOperandRenderer::Kind::RootOperand:
+ emitInstruction(Output, "RenderOperand", Renderer.Value);
+ break;
+ case NormalizedCombinerOperandRenderer::Kind::CapturedOperand:
+ emitInstruction(Output, "RenderCapturedOperand", 0, Renderer.Value);
+ break;
+ case NormalizedCombinerOperandRenderer::Kind::CapturedTypeBits:
+ emitInstruction(Output, "RenderCapturedTypeBits", 0, Renderer.Value);
+ break;
+ case NormalizedCombinerOperandRenderer::Kind::Immediate:
+ emitInstruction(Output, "RenderImmediate", 0, 0, 0, Renderer.Immediate);
+ break;
+ }
+ }
+ }
+ if (Rule.Action.ClearInstructionFlags != 0)
+ emitInstruction(Output, "ClearInstructionFlags", 0, Rule.Action.ClearInstructionFlags);
+ for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions)
+ emitInstruction(Output, "EraseOperandDefinition", OperandIndex);
+ emitInstruction(Output, "Complete");
+ }
+ Output << "}};\n\n";
+
+ Output << "constexpr std::array<GIMatchTableRule, " << Model.CombinerRules.size()
+ << "> GeneratedGlobalISelCombinerRules = {{\n";
+ std::size_t FirstInstruction = 0;
+ for (const NormalizedCombinerRule &Rule : Model.CombinerRules) {
+ const std::size_t Count = instructionCount(Rule);
+ Output << " {";
+ appendQuoted(Output, Rule.Symbol);
+ Output << ", " << Rule.Priority << ", " << FirstInstruction << ", " << Count << "},\n";
+ FirstInstruction += Count;
+ }
+ Output << "}};\n\n";
+ Output
+ << "[[nodiscard]] constexpr GIMatchTable generatedGlobalISelCombinerMatchTable() noexcept {\n"
+ " return {GeneratedGlobalISelCombinerInstructions, GeneratedGlobalISelCombinerRules,\n"
+ " GeneratedGlobalISelCombinerConstantRanges, GIMatchTableKind::Combiner};\n"
+ "}\n\n";
+ Output << "#endif // GET_GLOBALISEL_COMBINER_TABLE\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/GlobalISelEmitter.cpp b/backend/utils/TableGen/GlobalISelEmitter.cpp
new file mode 100644
index 0000000..3ce9411
--- /dev/null
+++ b/backend/utils/TableGen/GlobalISelEmitter.cpp
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+namespace {
+
+[[nodiscard]] std::string_view typeKindName(NormalizedSelectionTypeKind Kind) noexcept {
+ switch (Kind) {
+ case NormalizedSelectionTypeKind::Any:
+ return "Any";
+ case NormalizedSelectionTypeKind::Scalar:
+ return "Scalar";
+ case NormalizedSelectionTypeKind::Pointer:
+ return "Pointer";
+ }
+ return "Any";
+}
+
+void emitInstruction(std::ostream &Output, std::string_view Opcode, std::uint32_t OperandIndex,
+ std::uint32_t Value = 0, std::string_view Auxiliary = "0",
+ std::int64_t Immediate = 0, std::string_view Symbol = {}) {
+ Output << " {GIMatchTableOpcode::" << Opcode << ", " << OperandIndex << ", " << Value << ", "
+ << Auxiliary << ", " << Immediate << ", ";
+ appendQuoted(Output, Symbol);
+ Output << "},\n";
+}
+
+void emitTypePredicate(std::ostream &Output, std::uint32_t OperandIndex,
+ const NormalizedSelectionTypePredicate &Predicate) {
+ emitInstruction(Output, "CheckOperandType", OperandIndex, Predicate.Bits,
+ "static_cast<std::uint32_t>(GIMatchTypeKind::" +
+ std::string(typeKindName(Predicate.Kind)) + ")");
+}
+
+[[nodiscard]] std::size_t instructionCount(const NormalizedSelectionRule &Rule) noexcept {
+ std::size_t Count = 2U + (Rule.ResultType ? 1U : 0U) + (Rule.IntegerPredicate ? 1U : 0U) +
+ (Rule.FloatingPredicate ? 1U : 0U) + (Rule.MemoryPredicate ? 1U : 0U) +
+ Rule.TargetPredicates.size() + Rule.OperandTypes.size() +
+ Rule.OperandBanks.size() + Rule.OperandClasses.size() +
+ Rule.OperandImmediates.size() + Rule.OperandDefConstants.size() +
+ Rule.Temporaries.size() + Rule.EraseOperandDefinitions.size();
+ for (const NormalizedSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates)
+ Count += 1U + static_cast<std::size_t>(Predicate.IntegerPredicate.has_value()) +
+ Predicate.Captures.size() + Predicate.OperandDefConstants.size();
+ for (const NormalizedSelectionAction &Action : Rule.Actions)
+ Count += 1U + Action.OperandRenderers.size();
+ return Count;
+}
+
+} // namespace
+
+std::string emitGlobalISel(const NormalizedTargetModel &Model, std::string_view SourceName) {
+ std::size_t InstructionCount = 0;
+ std::size_t ConstantRangeCount = 0;
+ for (const NormalizedSelectionRule &Rule : Model.SelectionRules)
+ InstructionCount += instructionCount(Rule);
+ for (const NormalizedSelectionRule &Rule : Model.SelectionRules) {
+ for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) {
+ ConstantRangeCount += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ Rule.OperandDefConstants) {
+ ConstantRangeCount += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) {
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants) {
+ ConstantRangeCount += Predicate.Ranges.size();
+ }
+ }
+ }
+
+ 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 << "#ifdef GET_GLOBALISEL_MATCH_TABLE\n\n";
+ Output << "constexpr std::array<GIMatchTableConstantRange, " << ConstantRangeCount
+ << "> GeneratedGlobalISelConstantRanges = {{\n";
+ for (const NormalizedSelectionRule &Rule : Model.SelectionRules) {
+ for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) {
+ for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges)
+ Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n";
+ }
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ Rule.OperandDefConstants) {
+ for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges)
+ Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n";
+ }
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) {
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants) {
+ for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges)
+ Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n";
+ }
+ }
+ }
+ Output << "}};\n\n";
+
+ Output << "constexpr std::array<GIMatchTableInstruction, " << InstructionCount
+ << "> GeneratedGlobalISelInstructions = {{\n";
+ std::size_t FirstConstantRange = 0;
+ for (const NormalizedSelectionRule &Rule : Model.SelectionRules) {
+ emitInstruction(Output, "CheckOpcode", 0, Rule.MatchOpcode);
+ if (Rule.ResultType)
+ emitTypePredicate(Output, 0, *Rule.ResultType);
+ if (Rule.IntegerPredicate)
+ emitInstruction(Output, "CheckIntegerPredicate", Rule.IntegerPredicate->OperandIndex,
+ Rule.IntegerPredicate->Predicate);
+ if (Rule.FloatingPredicate)
+ emitInstruction(Output, "CheckFloatingPredicate", Rule.FloatingPredicate->OperandIndex,
+ Rule.FloatingPredicate->Predicate);
+ if (Rule.MemoryPredicate)
+ emitInstruction(Output, "CheckMemorySize", Rule.MemoryPredicate->MemoryOperandIndex,
+ Rule.MemoryPredicate->Bytes);
+ for (const std::string &Predicate : Rule.TargetPredicates)
+ emitInstruction(Output, "CheckTargetPredicate", 0, 0, "0", 0, Predicate);
+ for (const NormalizedSelectionOperandTypePredicate &Predicate : Rule.OperandTypes)
+ emitTypePredicate(Output, Predicate.OperandIndex, Predicate.Type);
+ for (const NormalizedSelectionOperandBankPredicate &Predicate : Rule.OperandBanks)
+ emitInstruction(Output, "CheckOperandBank", Predicate.OperandIndex, Predicate.Bank);
+ for (const NormalizedSelectionOperandClassPredicate &Predicate : Rule.OperandClasses)
+ emitInstruction(Output, "CheckOperandClass", Predicate.OperandIndex, Predicate.RegClass);
+ for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) {
+ emitInstruction(Output, "CheckOperandImmediateRanges", Predicate.OperandIndex,
+ static_cast<std::uint32_t>(FirstConstantRange),
+ std::to_string(Predicate.Ranges.size()));
+ FirstConstantRange += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ Rule.OperandDefConstants) {
+ emitInstruction(Output, "CheckOperandDefConstantRanges", Predicate.OperandIndex,
+ static_cast<std::uint32_t>(FirstConstantRange),
+ std::to_string(Predicate.Ranges.size()));
+ FirstConstantRange += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) {
+ emitInstruction(Output, "CheckOperandDefOpcode", DefUse.RootOperandIndex, DefUse.Opcode);
+ if (DefUse.IntegerPredicate) {
+ emitInstruction(Output, "CheckOperandDefIntegerPredicate", DefUse.RootOperandIndex,
+ DefUse.IntegerPredicate->Predicate,
+ std::to_string(DefUse.IntegerPredicate->OperandIndex));
+ }
+ for (const NormalizedSelectionOperandDefConstantPredicate &Predicate :
+ DefUse.OperandDefConstants) {
+ emitInstruction(Output, "CheckOperandDefOperandConstantRanges", DefUse.RootOperandIndex,
+ static_cast<std::uint32_t>(FirstConstantRange),
+ std::to_string(Predicate.OperandIndex),
+ static_cast<std::int64_t>(Predicate.Ranges.size()));
+ FirstConstantRange += Predicate.Ranges.size();
+ }
+ for (const NormalizedSelectionDefUseCapture &Capture : DefUse.Captures)
+ emitInstruction(Output, "CaptureDefOperand", DefUse.RootOperandIndex, Capture.ID,
+ std::to_string(Capture.OperandIndex));
+ }
+ for (const NormalizedSelectionTemporary &Temporary : Rule.Temporaries) {
+ if (Temporary.TypeFromOperand) {
+ emitInstruction(Output, "CreateTemporary", *Temporary.TypeFromOperand, Temporary.ID);
+ continue;
+ }
+ emitInstruction(Output, "CreateTemporaryType", 0, Temporary.ID,
+ "static_cast<std::uint32_t>(GIMatchTypeKind::" +
+ std::string(typeKindName(Temporary.Type->Kind)) + ")",
+ Temporary.Type->Bits);
+ }
+ for (const NormalizedSelectionAction &Action : Rule.Actions) {
+ emitInstruction(Output, Action.ReplacesRoot ? "MutateOpcode" : "BuildInstruction", 0,
+ Action.ResultOpcode);
+ for (const NormalizedSelectionOperandRenderer &Renderer : Action.OperandRenderers) {
+ switch (Renderer.SourceKind) {
+ case NormalizedSelectionOperandRenderer::Kind::Operand:
+ emitInstruction(Output, "RenderOperand", Renderer.Value);
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::ComplexOperand:
+ emitInstruction(Output, "RenderComplexOperand", Renderer.Value, 0, "0", 0,
+ Renderer.ComplexRenderer);
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::PhysicalRegister:
+ emitInstruction(Output, "RenderPhysicalRegister", 0, Renderer.Value,
+ std::to_string(Renderer.Flags));
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::Temporary:
+ emitInstruction(Output, "RenderTemporary", 0, Renderer.Value,
+ std::to_string(Renderer.Flags));
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::CapturedOperand:
+ emitInstruction(Output, "RenderCapturedOperand", 0, Renderer.Value);
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::CapturedImmediate:
+ emitInstruction(Output, "RenderCapturedImmediate", Renderer.Value,
+ static_cast<std::uint32_t>(Renderer.Transform));
+ break;
+ case NormalizedSelectionOperandRenderer::Kind::Immediate:
+ emitInstruction(Output, "RenderImmediate", 0, 0, "0", Renderer.Immediate);
+ break;
+ }
+ }
+ }
+ for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions)
+ emitInstruction(Output, "EraseOperandDefinition", OperandIndex);
+ emitInstruction(Output, "Complete", 0);
+ }
+ Output << "}};\n\n";
+
+ Output << "constexpr std::array<GIMatchTableRule, " << Model.SelectionRules.size()
+ << "> GeneratedGlobalISelRules = {{\n";
+ std::size_t FirstInstruction = 0;
+ for (const NormalizedSelectionRule &Rule : Model.SelectionRules) {
+ const std::size_t Count = instructionCount(Rule);
+ Output << " {";
+ appendQuoted(Output, Rule.Symbol);
+ Output << ", " << Rule.Priority << ", " << FirstInstruction << ", " << Count << "},\n";
+ FirstInstruction += Count;
+ }
+ Output << "}};\n\n";
+ Output << "[[nodiscard]] constexpr GIMatchTable generatedGlobalISelMatchTable() noexcept {\n"
+ " return {GeneratedGlobalISelInstructions, GeneratedGlobalISelRules,\n"
+ " GeneratedGlobalISelConstantRanges};\n"
+ "}\n\n";
+ Output << "#endif // GET_GLOBALISEL_MATCH_TABLE\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/InstrInfoEmitter.cpp b/backend/utils/TableGen/InstrInfoEmitter.cpp
new file mode 100644
index 0000000..1f42d4a
--- /dev/null
+++ b/backend/utils/TableGen/InstrInfoEmitter.cpp
@@ -0,0 +1,205 @@
+// 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 = ";
+ if (Operand.TypeIndex) {
+ Output << "OperandTypeConstraint{LowLevelTypeKindSet{"
+ << "LowLevelType::Kind::CategoryNeutralScalar, LowLevelType::Kind::Pointer, "
+ << "LowLevelType::Kind::Vector, LowLevelType::Kind::VectorPointer}, std::uint16_t{"
+ << *Operand.TypeIndex << "}}";
+ } else {
+ Output << "std::nullopt";
+ }
+ Output << "},\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 \"rcc/CodeGen/MachineIRTypes.h\"\n\n#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 << "[[nodiscard]] constexpr Opcode instructionOpcode(InstructionOpcode Value) noexcept {\n"
+ " return Opcode::fromValue(static_cast<std::uint32_t>(Value));\n"
+ "}\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 << "), .SizeInBytes = " << Instruction.SizeInBytes
+ << ", .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/MCPseudoLoweringEmitter.cpp b/backend/utils/TableGen/MCPseudoLoweringEmitter.cpp
new file mode 100644
index 0000000..5afa911
--- /dev/null
+++ b/backend/utils/TableGen/MCPseudoLoweringEmitter.cpp
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstddef>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+
+std::string emitMCPseudoLowering(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 << "#ifdef GET_MC_PSEUDO_LOWERING\n\n";
+ Output << "[[nodiscard]] Expected<std::optional<mc::MCInst>>\n"
+ "lowerGeneratedMCPseudo(const MachineInstr &Instruction,\n"
+ " MachineMCContext &Context) {\n"
+ " switch (Instruction.opcode().value()) {\n"
+ " default:\n"
+ " return std::optional<mc::MCInst>{};\n";
+ for (const NormalizedMCPseudoExpansion &Expansion : Model.MCPseudoExpansions) {
+ Output << " case " << Expansion.PseudoOpcode << ": {\n"
+ << " mc::MCInst Result(mc::MCOpcode::fromValue(" << Expansion.ResultOpcode
+ << "));\n"
+ " Result.setFlags(Instruction.flags().rawValue());\n"
+ " Result.setSourceIdentity(Instruction.stableID().value());\n"
+ " Result.reserveOperands("
+ << Expansion.Operands.size() << ");\n";
+ for (std::size_t Index = 0; Index < Expansion.Operands.size(); ++Index) {
+ const NormalizedMCPseudoOperand &Operand = Expansion.Operands[Index];
+ switch (Operand.Kind) {
+ case NormalizedMCPseudoOperandKind::SourceOperand:
+ Output << " const MachineOperand *Operand" << Index << " = Instruction.operandAt("
+ << Operand.Value << ");\n"
+ << " if (Operand" << Index << " == nullptr)\n"
+ << " return makeUnexpected(ErrorCode::AssemblyLoweringFailed,\n"
+ " \"MC pseudo has too few operands\");\n"
+ << " auto Lowered" << Index << " =\n"
+ << " lowerOperand(*Operand" << Index
+ << ", Instruction.function(), Context);\n"
+ << " if (!Lowered" << Index << ")\n"
+ << " return std::unexpected(Lowered" << Index << ".error());\n"
+ << " Result.addOperand(std::move(*Lowered" << Index << "));\n";
+ break;
+ case NormalizedMCPseudoOperandKind::Register:
+ Output << " Result.addOperand(mc::MCOperand::registerOperand(\n"
+ " mc::MCRegister::fromValue("
+ << Operand.Value << ")));\n";
+ break;
+ case NormalizedMCPseudoOperandKind::Immediate:
+ Output << " Result.addOperand(mc::MCOperand::immediate(" << Operand.Immediate << "));\n";
+ break;
+ }
+ }
+ Output << " return std::optional<mc::MCInst>{std::move(Result)};\n"
+ " }\n";
+ }
+ Output << " }\n"
+ "}\n\n"
+ "#endif // GET_MC_PSEUDO_LOWERING\n";
+ return Output.str();
+}
+
+} // namespace rcc::targetgen
diff --git a/backend/utils/TableGen/MacroFusionEmitter.cpp b/backend/utils/TableGen/MacroFusionEmitter.cpp
new file mode 100644
index 0000000..2018a43
--- /dev/null
+++ b/backend/utils/TableGen/MacroFusionEmitter.cpp
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+namespace rcc::targetgen {
+namespace {
+
+[[nodiscard]] std::string_view
+instructionSide(NormalizedMacroFusionInstruction Instruction) noexcept {
+ return Instruction == NormalizedMacroFusionInstruction::First ? "First" : "Second";
+}
+
+void emitOpcodes(std::ostream &Output, const NormalizedMacroFusionRule &Rule,
+ std::string_view Suffix, const std::vector<std::uint32_t> &Opcodes) {
+ Output << "constexpr std::array<Opcode, " << Opcodes.size() << "> " << Rule.Symbol << Suffix
+ << " = {{\n";
+ for (const std::uint32_t Opcode : Opcodes)
+ Output << " Opcode::fromValue(" << Opcode << "),\n";
+ Output << "}};\n";
+}
+
+} // namespace
+
+std::string emitMacroFusion(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 << "#ifdef GET_MACRO_FUSION_RULES\n\n";
+ for (const NormalizedMacroFusionRule &Rule : Model.MacroFusionRules) {
+ emitOpcodes(Output, Rule, "FirstOpcodes", Rule.FirstOpcodes);
+ emitOpcodes(Output, Rule, "SecondOpcodes", Rule.SecondOpcodes);
+ Output << "constexpr std::array<MacroFusionOpcodePair, " << Rule.OpcodePairs.size() << "> "
+ << Rule.Symbol << "OpcodePairs = {{\n";
+ for (const NormalizedMacroFusionOpcodePair &Pair : Rule.OpcodePairs) {
+ Output << " {Opcode::fromValue(" << Pair.FirstOpcode << "), Opcode::fromValue("
+ << Pair.SecondOpcode << ")},\n";
+ }
+ Output << "}};\n";
+ Output << "constexpr std::array<MacroFusionImmediatePredicate, "
+ << Rule.ImmediatePredicates.size() << "> " << Rule.Symbol
+ << "ImmediatePredicates = {{\n";
+ for (const NormalizedMacroFusionImmediatePredicate &Predicate : Rule.ImmediatePredicates) {
+ Output << " {MacroFusionInstruction::" << instructionSide(Predicate.Instruction) << ", "
+ << Predicate.OperandIndex << ", " << Predicate.Minimum << ", " << Predicate.Maximum
+ << "},\n";
+ }
+ Output << "}};\n";
+ Output << "constexpr std::array<MacroFusionRegisterInequality, "
+ << Rule.RegisterInequalities.size() << "> " << Rule.Symbol
+ << "RegisterInequalities = {{\n";
+ for (const NormalizedMacroFusionRegisterInequality &Predicate : Rule.RegisterInequalities) {
+ Output << " {MacroFusionInstruction::" << instructionSide(Predicate.Instruction) << ", "
+ << Predicate.LeftOperand << ", " << Predicate.RightOperand << "},\n";
+ }
+ Output << "}};\n";
+ Output << "constexpr std::array<MacroFusionImmediateRelation, "
+ << Rule.ImmediateRelations.size() << "> " << Rule.Symbol << "ImmediateRelations = {{\n";
+ for (const NormalizedMacroFusionImmediateRelation &Relation : Rule.ImmediateRelations)
+ Output << " {" << Relation.FirstOperand << ", " << Relation.SecondOperand << "},\n";
+ Output << "}};\n\n";
+ }
+
+ Output << "constexpr std::array<MacroFusionRule, " << Model.MacroFusionRules.size()
+ << "> GeneratedMacroFusionRules = {{\n";
+ for (const NormalizedMacroFusionRule &Rule : Model.MacroFusionRules) {
+ Output << " {";
+ appendQuoted(Output, Rule.Symbol);
+ Output << ", ";
+ appendQuoted(Output, Rule.Feature);
+ Output << ", " << Rule.Symbol << "FirstOpcodes, " << Rule.Symbol << "SecondOpcodes, "
+ << Rule.Symbol << "OpcodePairs, " << Rule.Symbol << "ImmediatePredicates, "
+ << Rule.Symbol << "RegisterInequalities, " << Rule.Symbol << "ImmediateRelations, "
+ << Rule.FirstDefinitionOperand << ", " << Rule.SecondUseOperand << ", "
+ << Rule.SecondDefinitionOperand << ", ";
+ if (Rule.CommutableSecondUseOperand)
+ Output << *Rule.CommutableSecondUseOperand;
+ else
+ Output << "MacroFusionRule::NoOperand";
+ Output << ", " << (Rule.RequireSingleNonDebugUse ? "true" : "false") << ", "
+ << (Rule.RequireSecondDestinationTie ? "true" : "false") << "},\n";
+ }
+ Output << "}};\n\n";
+ Output << "[[nodiscard]] constexpr std::span<const MacroFusionRule> "
+ "generatedMacroFusionRules() noexcept {\n"
+ " return GeneratedMacroFusionRules;\n"
+ "}\n\n";
+ Output << "#endif // GET_MACRO_FUSION_RULES\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..0eee15c
--- /dev/null
+++ b/backend/utils/TableGen/RegisterInfoEmitter.cpp
@@ -0,0 +1,169 @@
+// 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 << ", ";
+}
+
+void emitRegisterClassIDList(std::ostream &Output, const NormalizedTargetModel &Model,
+ std::span<const std::uint32_t> IDs) {
+ for (const std::uint32_t ID : IDs) {
+ const auto Position =
+ std::ranges::find(Model.RegisterClasses, ID, &NormalizedRegisterClass::ID);
+ Output << "RegisterClass::" << Position->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 << "}, {";
+ emitRegisterClassIDList(Output, Model, Class.SuperClasses);
+ 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..dff0333
--- /dev/null
+++ b/backend/utils/TableGen/TableGenBackends.h
@@ -0,0 +1,62 @@
+// 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;
+ std::string GlobalISel;
+ std::string GlobalISelCombiner;
+ std::string MacroFusion;
+ std::string MCPseudoLowering;
+};
+
+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]] std::string emitGlobalISel(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitGlobalISelCombiner(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitMacroFusion(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+[[nodiscard]] std::string emitMCPseudoLowering(const NormalizedTargetModel &Model,
+ std::string_view SourceName);
+
+[[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..cf10665
--- /dev/null
+++ b/backend/utils/TableGen/rcc-tblgen.cpp
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "TableGenBackends.h"
+#include "rcc/Support/TextDiagnostic.h"
+
+#include <cctype>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+namespace {
+
+struct CommandLine final {
+ std::vector<std::string> InputPaths;
+ std::string RegisterHeaderPath;
+ std::string InstructionHeaderPath;
+ std::string TablesPath;
+ std::string GlobalISelPath;
+ std::string GlobalISelCombinerPath;
+ std::string MacroFusionPath;
+ std::string MCPseudoLoweringPath;
+};
+
+[[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") {
+ if (Index + 1 >= ArgumentCount) {
+ std::cerr << "rcc-tblgen: missing value for " << Argument << '\n';
+ return false;
+ }
+ Result.InputPaths.emplace_back(Arguments[++Index]);
+ continue;
+ }
+ if (Argument == "--register-header" || Argument == "--instruction-header" ||
+ Argument == "--tables" || Argument == "--global-isel" ||
+ Argument == "--global-isel-combiner" || Argument == "--macro-fusion" ||
+ Argument == "--mc-pseudo-lowering") {
+ if (Index + 1 >= ArgumentCount) {
+ std::cerr << "rcc-tblgen: missing value for " << Argument << '\n';
+ return false;
+ }
+ std::string *Destination = nullptr;
+ if (Argument == "--register-header")
+ Destination = &Result.RegisterHeaderPath;
+ else if (Argument == "--instruction-header")
+ Destination = &Result.InstructionHeaderPath;
+ else if (Argument == "--global-isel")
+ Destination = &Result.GlobalISelPath;
+ else if (Argument == "--global-isel-combiner")
+ Destination = &Result.GlobalISelCombinerPath;
+ else if (Argument == "--macro-fusion")
+ Destination = &Result.MacroFusionPath;
+ else if (Argument == "--mc-pseudo-lowering")
+ Destination = &Result.MCPseudoLoweringPath;
+ else
+ Destination = &Result.TablesPath;
+ *Destination = Arguments[++Index];
+ continue;
+ }
+ std::cerr << "rcc-tblgen: unknown argument '" << Argument << "'\n";
+ return false;
+ }
+ if (!Result.InputPaths.empty() && !Result.RegisterHeaderPath.empty() &&
+ !Result.InstructionHeaderPath.empty() && !Result.TablesPath.empty() &&
+ !Result.GlobalISelPath.empty() && !Result.GlobalISelCombinerPath.empty() &&
+ !Result.MacroFusionPath.empty() && !Result.MCPseudoLoweringPath.empty()) {
+ return true;
+ }
+ std::cerr << "usage: rcc-tblgen --input <file> [--input <fragment>]... --register-header <file> "
+ "--instruction-header <file> --tables <file> --global-isel <file> "
+ "--global-isel-combiner <file> --macro-fusion <file> "
+ "--mc-pseudo-lowering <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;
+}
+
+struct SourceSection final {
+ std::string Name;
+ std::string Contents;
+ std::size_t HeaderSize = 0;
+};
+
+[[nodiscard]] bool isIdentifierCharacter(char Character) noexcept {
+ return std::isalnum(static_cast<unsigned char>(Character)) != 0 || Character == '_';
+}
+
+[[nodiscard]] std::optional<std::string_view> topLevelKey(std::string_view Line) noexcept {
+ if (Line.empty() || Line.front() == '#' ||
+ (!std::isalpha(static_cast<unsigned char>(Line.front())) && Line.front() != '_')) {
+ return std::nullopt;
+ }
+ std::size_t End = 1;
+ while (End < Line.size() && isIdentifierCharacter(Line[End]))
+ ++End;
+ if (End == Line.size() || Line[End] != ':')
+ return std::nullopt;
+ return Line.substr(0, End);
+}
+
+[[nodiscard]] std::vector<SourceSection> splitSections(std::string_view SourceText) {
+ std::vector<SourceSection> Result;
+ std::size_t SectionStart = 0;
+ std::size_t HeaderEnd = 0;
+ std::optional<std::string> SectionName;
+ std::size_t LineStart = 0;
+ while (LineStart < SourceText.size()) {
+ const std::size_t LineEnd = SourceText.find('\n', LineStart);
+ const std::size_t ContentEnd = LineEnd == std::string_view::npos ? SourceText.size() : LineEnd;
+ const std::string_view Line = SourceText.substr(LineStart, ContentEnd - LineStart);
+ if (const auto Key = topLevelKey(Line)) {
+ if (SectionName) {
+ Result.push_back({
+ .Name = std::move(*SectionName),
+ .Contents = std::string(SourceText.substr(SectionStart, LineStart - SectionStart)),
+ .HeaderSize = HeaderEnd - SectionStart,
+ });
+ }
+ SectionStart = LineStart;
+ HeaderEnd = LineEnd == std::string_view::npos ? SourceText.size() : LineEnd + 1U;
+ SectionName = std::string(*Key);
+ }
+ if (LineEnd == std::string_view::npos)
+ break;
+ LineStart = LineEnd + 1U;
+ }
+ if (SectionName) {
+ Result.push_back({
+ .Name = std::move(*SectionName),
+ .Contents = std::string(SourceText.substr(SectionStart)),
+ .HeaderSize = HeaderEnd - SectionStart,
+ });
+ }
+ return Result;
+}
+
+[[nodiscard]] std::optional<std::string>
+combineDescriptionSources(const std::vector<std::string> &Paths,
+ const std::vector<std::string> &Sources) {
+ std::vector<SourceSection> Result;
+ std::unordered_map<std::string, std::size_t> SectionIndex;
+ for (std::size_t Index = 0; Index < Sources.size(); ++Index) {
+ const std::vector<SourceSection> Sections = splitSections(Sources[Index]);
+ if (Sections.empty()) {
+ std::cerr << "rcc-tblgen: input '" << Paths[Index]
+ << "' does not contain a top-level description section\n";
+ return std::nullopt;
+ }
+ for (const SourceSection &Section : Sections) {
+ const auto Position = SectionIndex.find(Section.Name);
+ if (Position == SectionIndex.end()) {
+ SectionIndex.emplace(Section.Name, Result.size());
+ Result.push_back(Section);
+ continue;
+ }
+ if (Section.Name != "instructions" && Section.Name != "selection_rules" &&
+ Section.Name != "combiner_rules" && Section.Name != "macro_fusion_rules" &&
+ Section.Name != "mc_pseudo_expansions") {
+ std::cerr << "rcc-tblgen: duplicate description section '" << Section.Name << "' in '"
+ << Paths[Index] << "'\n";
+ return std::nullopt;
+ }
+ SourceSection &Existing = Result[Position->second];
+ const std::string_view Body(Section.Contents.data() + Section.HeaderSize,
+ Section.Contents.size() - Section.HeaderSize);
+ if (!Existing.Contents.empty() && Existing.Contents.back() != '\n')
+ Existing.Contents.push_back('\n');
+ Existing.Contents.append(Body);
+ }
+ }
+
+ std::string Combined;
+ for (const SourceSection &Section : Result) {
+ if (!Combined.empty() && Combined.back() != '\n')
+ Combined.push_back('\n');
+ Combined.append(Section.Contents);
+ }
+ return Combined;
+}
+
+} // namespace
+
+int main(int ArgumentCount, char *Arguments[]) {
+ CommandLine Command;
+ if (!parseCommandLine(ArgumentCount, Arguments, Command))
+ return 1;
+
+ std::vector<std::string> Sources;
+ Sources.reserve(Command.InputPaths.size());
+ for (const std::string &InputPath : Command.InputPaths) {
+ std::ifstream Input(InputPath, std::ios::binary);
+ if (!Input) {
+ std::cerr << "rcc-tblgen: cannot open input '" << InputPath << "'\n";
+ return 1;
+ }
+ Sources.emplace_back(std::istreambuf_iterator<char>(Input), std::istreambuf_iterator<char>());
+ }
+ const auto SourceText = combineDescriptionSources(Command.InputPaths, Sources);
+ if (!SourceText)
+ return 1;
+ const rcc::targetgen::DescriptionGenerationResult Result =
+ rcc::targetgen::generateDescription(Command.InputPaths.front(), *SourceText);
+ if (!Result.succeeded()) {
+ const rcc::TextSource Source(Command.InputPaths.front(), *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) &&
+ writeFile(Command.GlobalISelPath, Output.GlobalISel) &&
+ writeFile(Command.GlobalISelCombinerPath, Output.GlobalISelCombiner) &&
+ writeFile(Command.MacroFusionPath, Output.MacroFusion) &&
+ writeFile(Command.MCPseudoLoweringPath, Output.MCPseudoLowering)
+ ? 0
+ : 1;
+}