aboutsummaryrefslogtreecommitdiff
path: root/backend/utils/TableGen/Common
diff options
context:
space:
mode:
Diffstat (limited to 'backend/utils/TableGen/Common')
-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
8 files changed, 2920 insertions, 0 deletions
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