diff options
Diffstat (limited to 'backend/utils')
23 files changed, 4399 insertions, 0 deletions
diff --git a/backend/utils/TableGen/AsmWriterEmitter.cpp b/backend/utils/TableGen/AsmWriterEmitter.cpp new file mode 100644 index 0000000..f8452c5 --- /dev/null +++ b/backend/utils/TableGen/AsmWriterEmitter.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <sstream> +#include <string> + +namespace rcc::targetgen { + +std::string emitAssemblyWriter(const NormalizedTargetModel &Model) { + std::ostringstream Output; + Output << "constexpr std::array<AssemblyFormatInfo, " << Model.AssemblyFormats.size() + << "> GeneratedAssemblyFormats = {{\n"; + for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats) { + Output << " {AssemblyFormatID::fromValue(" << Format.ID << "), "; + appendQuoted(Output, Format.Name); + Output << "},\n"; + } + Output << "}};\n\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/CMakeLists.txt b/backend/utils/TableGen/CMakeLists.txt new file mode 100644 index 0000000..ae305b4 --- /dev/null +++ b/backend/utils/TableGen/CMakeLists.txt @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set( + RCCBackendTableGenBackendSources + AsmWriterEmitter.cpp + CallingConvEmitter.cpp + CodeEmitterGen.cpp + GlobalISelEmitter.cpp + GlobalISelCombinerEmitter.cpp + MacroFusionEmitter.cpp + MCPseudoLoweringEmitter.cpp + Common/CodeGenInstruction.cpp + Common/CodeGenMacroFusion.cpp + Common/CodeGenMCPseudoLowering.cpp + Common/CodeGenRegisters.cpp + Common/CodeGenSchedule.cpp + Common/CodeGenSelection.cpp + Common/CodeGenTarget.cpp + InstrInfoEmitter.cpp + RegisterBankEmitter.cpp + RegisterInfoEmitter.cpp + SearchableTableEmitter.cpp + SubtargetEmitter.cpp +) +list(TRANSFORM RCCBackendTableGenBackendSources PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") +set( + RCCBackendTableGenBackendHeaders + "${CMAKE_CURRENT_SOURCE_DIR}/Common/CodeGenTarget.h" + "${CMAKE_CURRENT_SOURCE_DIR}/TableGenBackends.h" +) + +add_library(RCCBackendTableGenBackends STATIC ${RCCBackendTableGenBackendSources}) +add_library(RCCBackend::TableGenBackends ALIAS RCCBackendTableGenBackends) +rcc_backend_configure_target(RCCBackendTableGenBackends) +target_include_directories( + RCCBackendTableGenBackends + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>" +) +target_link_libraries(RCCBackendTableGenBackends PUBLIC RCCBackend::TableGen) + +add_executable(RCCBackendTableGenTool rcc-tblgen.cpp) +rcc_backend_configure_target(RCCBackendTableGenTool) +target_link_libraries(RCCBackendTableGenTool PRIVATE RCCBackend::TableGenBackends) +set_target_properties(RCCBackendTableGenTool PROPERTIES OUTPUT_NAME rcc-tblgen) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/rcc-tblgen.cpp" + ${RCCBackendTableGenBackendSources} + ${RCCBackendTableGenBackendHeaders} +) diff --git a/backend/utils/TableGen/CallingConvEmitter.cpp b/backend/utils/TableGen/CallingConvEmitter.cpp new file mode 100644 index 0000000..d71241e --- /dev/null +++ b/backend/utils/TableGen/CallingConvEmitter.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstdint> +#include <ostream> +#include <span> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { +namespace { + +void emitPhysicalRegisterArray(std::ostream &Output, const NormalizedTargetModel &Model, + std::string_view Name, std::span<const std::uint32_t> IDs) { + Output << "const std::array<PhysicalRegister, " << IDs.size() << "> " << Name << " = {{\n"; + for (const std::uint32_t ID : IDs) + Output << " physical(PhysicalRegisterID::" << findRegister(Model, ID).Symbol << "),\n"; + Output << "}};\n"; +} + +} // namespace + +std::string emitCallingConventions(const NormalizedTargetModel &Model) { + std::ostringstream Output; + for (const NormalizedCallingConvention &Convention : Model.CallingConventions) { + const std::string Prefix = "CC" + std::to_string(Convention.ID); + emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerArguments", + Convention.IntegerArgumentRegisters); + emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingArguments", + Convention.FloatingArgumentRegisters); + emitPhysicalRegisterArray(Output, Model, Prefix + "IntegerReturns", + Convention.IntegerReturnRegisters); + emitPhysicalRegisterArray(Output, Model, Prefix + "FloatingReturns", + Convention.FloatingReturnRegisters); + } + Output << "const std::array<CallingConventionInfo, " << Model.CallingConventions.size() + << "> GeneratedCallingConventions = {{\n"; + for (const NormalizedCallingConvention &Convention : Model.CallingConventions) { + const std::string Prefix = "CC" + std::to_string(Convention.ID); + Output << " {CallingConvention::" << Convention.Symbol << ", "; + appendQuoted(Output, Convention.Name); + Output << ", RegisterClassID::fromValue(" << Convention.IntegerRegisterClass + << "), RegisterClassID::fromValue(" << Convention.FloatingRegisterClass << "), " + << Prefix << "IntegerArguments, " << Prefix << "FloatingArguments, " << Prefix + << "IntegerReturns, " << Prefix << "FloatingReturns, "; + appendQuoted(Output, Convention.PreservedMaskName); + Output << ", " << Convention.StackAlignmentBytes << ", " + << Convention.StackArgumentAlignmentBytes << ", " << Convention.ShadowSpaceBytes << ", " + << (Convention.SupportsVarArgs ? "true" : "false") << ", " + << (Convention.SupportsTailCalls ? "true" : "false") << "},\n"; + } + Output << "}};\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/CodeEmitterGen.cpp b/backend/utils/TableGen/CodeEmitterGen.cpp new file mode 100644 index 0000000..7d0d9e6 --- /dev/null +++ b/backend/utils/TableGen/CodeEmitterGen.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <sstream> +#include <string> + +namespace rcc::targetgen { + +std::string emitCodeEmitter(const NormalizedTargetModel &Model) { + std::ostringstream Output; + Output << "constexpr std::array<EncodingCategoryInfo, " << Model.EncodingCategories.size() + << "> GeneratedEncodingCategories = {{\n"; + for (const NormalizedEncodingCategory &Category : Model.EncodingCategories) { + Output << " {EncodingCategoryID::fromValue(" << Category.ID << "), "; + appendQuoted(Output, Category.Name); + Output << ", " << Category.SizeInBytes << "},\n"; + } + Output << "}};\n\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenInstruction.cpp b/backend/utils/TableGen/Common/CodeGenInstruction.cpp new file mode 100644 index 0000000..e7f5ddc --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenInstruction.cpp @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Common/CodeGenTarget.h" + +#include <algorithm> +#include <array> +#include <bit> +#include <cstdint> +#include <limits> +#include <ranges> +#include <string> +#include <string_view> +#include <unordered_set> +#include <utility> + +namespace rcc::targetgen { + +[[nodiscard]] std::optional<std::string_view> +operandKindEnumerator(std::string_view Value) noexcept { + static constexpr std::array<std::pair<std::string_view, std::string_view>, 22> Kinds = {{ + {"register", "Register"}, + {"immediate", "Immediate"}, + {"integer_immediate", "IntegerImmediate"}, + {"floating_immediate", "FloatingImmediate"}, + {"basic_block", "BasicBlock"}, + {"frame_index", "FrameIndex"}, + {"constant_pool_index", "ConstantPoolIndex"}, + {"jump_table_index", "JumpTableIndex"}, + {"global_symbol", "GlobalSymbol"}, + {"external_symbol", "ExternalSymbol"}, + {"block_address", "BlockAddress"}, + {"register_mask", "RegisterMask"}, + {"live_out_register_mask", "LiveOutRegisterMask"}, + {"metadata", "Metadata"}, + {"target_index", "TargetIndex"}, + {"mc_symbol", "MCSymbol"}, + {"cfi_index", "CFIIndex"}, + {"intrinsic", "Intrinsic"}, + {"predicate", "Predicate"}, + {"shuffle_mask", "ShuffleMask"}, + {"debug_instr_ref", "DebugInstrRef"}, + {"lane_mask", "LaneMask"}, + }}; + const auto *const Position = std::ranges::find(Kinds, Value, &decltype(Kinds)::value_type::first); + if (Position == Kinds.end()) + return std::nullopt; + return Position->second; +} + +[[nodiscard]] std::optional<std::string_view> +registerRoleEnumerator(std::string_view Value) noexcept { + if (Value == "none") + return "None"; + if (Value == "def") + return "Def"; + if (Value == "use") + return "Use"; + if (Value == "def_use") + return "DefUse"; + return std::nullopt; +} + +[[nodiscard]] std::optional<std::string_view> +memoryRoleEnumerator(std::string_view Value) noexcept { + if (Value == "none") + return "None"; + if (Value == "address") + return "Address"; + if (Value == "value") + return "Value"; + if (Value == "size") + return "Size"; + if (Value == "ordering") + return "Ordering"; + return std::nullopt; +} + +[[nodiscard]] std::optional<std::string_view> +instructionFlagEnumerator(std::string_view Value) noexcept { + static constexpr std::array<std::pair<std::string_view, std::string_view>, 25> Flags = {{ + {"terminator", "Terminator"}, + {"branch", "Branch"}, + {"indirect_branch", "IndirectBranch"}, + {"call", "Call"}, + {"return", "Return"}, + {"barrier", "Barrier"}, + {"may_load", "MayLoad"}, + {"may_store", "MayStore"}, + {"has_side_effects", "HasSideEffects"}, + {"pseudo", "Pseudo"}, + {"meta", "Meta"}, + {"allowed_pre_legalizer", "AllowedPreLegalizer"}, + {"produces_code", "ProducesCode"}, + {"commutable", "Commutable"}, + {"rematerializable", "Rematerializable"}, + {"predicable", "Predicable"}, + {"not_duplicable", "NotDuplicable"}, + {"convergent", "Convergent"}, + {"unmodeled_side_effects", "UnmodeledSideEffects"}, + {"may_raise_fp_exception", "MayRaiseFPException"}, + {"phi", "Phi"}, + {"fake_use", "FakeUse"}, + {"generic", "Generic"}, + {"terminal_emission", "TerminalEmission"}, + {"uses_custom_inserter", "UsesCustomInserter"}, + }}; + const auto *const Position = std::ranges::find(Flags, Value, &decltype(Flags)::value_type::first); + if (Position == Flags.end()) + return std::nullopt; + return Position->second; +} + +[[nodiscard]] std::optional<std::string_view> +pseudoOwnerEnumerator(std::string_view Value) noexcept { + static constexpr std::array<std::pair<std::string_view, std::string_view>, 12> Owners = {{ + {"none", "None"}, + {"legalizer", "Legalizer"}, + {"register_bank_select", "RegisterBankSelect"}, + {"instruction_select", "InstructionSelect"}, + {"phi_elimination", "PhiElimination"}, + {"two_address_rewrite", "TwoAddressRewrite"}, + {"pre_ra_expansion", "PreRAExpansion"}, + {"register_allocator", "RegisterAllocator"}, + {"prologue_epilogue_insertion", "PrologueEpilogueInsertion"}, + {"post_ra_expansion", "PostRAExpansion"}, + {"finalization", "Finalization"}, + {"target_late_expansion", "TargetLateExpansion"}, + }}; + if (Value == "asm_printer") + return "AsmPrinter"; + const auto *const Position = + std::ranges::find(Owners, Value, &decltype(Owners)::value_type::first); + if (Position == Owners.end()) + return std::nullopt; + return Position->second; +} + +[[nodiscard]] std::optional<std::string_view> +pipelineStageEnumerator(std::string_view Value) noexcept { + static constexpr std::array<std::pair<std::string_view, std::string_view>, 10> Stages = {{ + {"machine_ir_input", "MachineIRInput"}, + {"legalized", "Legalized"}, + {"reg_bank_selected", "RegBankSelected"}, + {"selected", "Selected"}, + {"ssa_eliminated", "SSAEliminated"}, + {"tied_ops_rewritten", "TiedOpsRewritten"}, + {"register_allocated", "RegisterAllocated"}, + {"frame_finalized", "FrameFinalized"}, + {"post_ra_finalized", "PostRAFinalized"}, + {"emission", "Emission"}, + }}; + const auto *const Position = + std::ranges::find(Stages, Value, &decltype(Stages)::value_type::first); + if (Position == Stages.end()) + return std::nullopt; + return Position->second; +} + +[[nodiscard]] bool CodeGenTarget::normalizeMIROperandFlags(NormalizedTargetModel &Result) { + const SourceMIROperandFlags &Flags = Result.MIROperandFlags; + if (Flags.Direct.empty() != (Flags.DirectMask == 0)) { + fail(Flags.Range, "direct MIR operand flags and their direct mask must be present together"); + return false; + } + + std::unordered_set<std::string> Names; + std::unordered_set<std::uint32_t> DirectValues; + for (const SourceMIROperandFlag &Flag : Flags.Direct) { + if (Flag.Name.empty() || Flag.Value == 0 || (Flag.Value & ~Flags.DirectMask) != 0 || + !Names.insert(Flag.Name).second || !DirectValues.insert(Flag.Value).second) { + fail(Flag.Range, + "direct MIR operand flags require unique names and nonzero values inside the mask"); + return false; + } + } + + std::uint32_t BitmaskValues = 0; + for (const SourceMIROperandFlag &Flag : Flags.Bitmask) { + if (Flag.Name.empty() || !std::has_single_bit(Flag.Value) || + (Flag.Value & Flags.DirectMask) != 0 || !Names.insert(Flag.Name).second || + (BitmaskValues & Flag.Value) != 0) { + fail(Flag.Range, + "bitmask MIR operand flags require unique names and distinct bits outside the " + "direct mask"); + return false; + } + BitmaskValues |= Flag.Value; + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeNamedOperands(NormalizedTargetModel &Result) { + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceNamedOperand &Operand : Input.NamedOperands) { + if (Operand.ID == 0 || !isIdentifier(Operand.Symbol) || Operand.Name.empty() || + !IDs.insert(Operand.ID).second || !Symbols.insert(Operand.Symbol).second || + !Names.insert(Operand.Name).second) { + fail(Operand.Range, "named operands require nonzero unique IDs, symbols, and names"); + return false; + } + NamedOperandBySymbol.emplace(Operand.Symbol, Operand.ID); + Result.NamedOperands.push_back({ + .ID = Operand.ID, + .Symbol = Operand.Symbol, + .Name = Operand.Name, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeImmediatePredicates(NormalizedTargetModel &Result) { + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceImmediatePredicate &Predicate : Input.ImmediatePredicates) { + const bool IsSigned = Predicate.Kind == "signed"; + const bool IsUnsigned = Predicate.Kind == "unsigned"; + if (Predicate.ID == 0 || !isIdentifier(Predicate.Symbol) || Predicate.Name.empty() || + (!IsSigned && !IsUnsigned) || Predicate.Bits == 0 || Predicate.Bits > 63 || + !std::has_single_bit(Predicate.Alignment) || !IDs.insert(Predicate.ID).second || + !Symbols.insert(Predicate.Symbol).second || !Names.insert(Predicate.Name).second) { + fail(Predicate.Range, + "immediate predicates require nonzero unique IDs, symbols, names, a signed or " + "unsigned kind, a width in [1, 63], and power-of-two alignment"); + return false; + } + const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U); + const std::int64_t Minimum = IsSigned ? -PositiveLimit : 0; + const std::int64_t Maximum = + IsSigned ? PositiveLimit - 1 + : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - 1U); + std::unordered_set<std::int64_t> AllowedValues; + for (const std::int64_t Value : Predicate.AllowedValues) { + if (Value < Minimum || Value > Maximum || (Predicate.NonZero && Value == 0) || + !AllowedValues.insert(Value).second) { + fail(Predicate.Range, + "immediate predicate allowed_values must be unique, in range, and satisfy nonzero"); + return false; + } + } + ImmediatePredicateBySymbol.emplace(Predicate.Symbol, Predicate.ID); + Result.ImmediatePredicates.push_back({ + .ID = Predicate.ID, + .Symbol = Predicate.Symbol, + .Name = Predicate.Name, + .IsSigned = IsSigned, + .Bits = Predicate.Bits, + .Alignment = Predicate.Alignment, + .NonZero = Predicate.NonZero, + .AllowedValues = Predicate.AllowedValues, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeOperand(const SourceOperand &InputOperand, + NormalizedOperand &Result) { + if (InputOperand.Kinds.empty()) { + fail(InputOperand.Range, "instruction operand requires at least one kind"); + return false; + } + std::unordered_set<std::string> Kinds; + bool HasRegisterKind = false; + for (const std::string &Kind : InputOperand.Kinds) { + const auto Enumerator = operandKindEnumerator(Kind); + if (!Enumerator || !Kinds.insert(std::string(*Enumerator)).second) { + fail(InputOperand.Range, "instruction operand kinds must be known and unique"); + return false; + } + HasRegisterKind |= *Enumerator == "Register"; + Result.Kinds.push_back(std::string(*Enumerator)); + } + + const auto Role = registerRoleEnumerator(InputOperand.RegisterRole); + const auto MemoryRole = memoryRoleEnumerator(InputOperand.MemoryRole); + if (!Role || !MemoryRole || HasRegisterKind != (*Role != "None")) { + fail(InputOperand.Range, + "instruction operand register role and memory role must match its declared kinds"); + return false; + } + Result.RegisterRole = std::string(*Role); + Result.MemoryRole = std::string(*MemoryRole); + + const bool IsDefRole = *Role == "Def" || *Role == "DefUse"; + const bool IsRegisterOnly = Result.Kinds.size() == 1 && Result.Kinds.front() == "Register"; + if (IsDefRole && !IsRegisterOnly) { + fail(InputOperand.Range, "register definition operands must accept only registers"); + return false; + } + if (InputOperand.TypeIndex && !IsRegisterOnly) { + fail(InputOperand.Range, "instruction operand type_index requires exactly one register kind"); + return false; + } + if (!HasRegisterKind && (InputOperand.RegisterClassSymbol || InputOperand.RegisterBankSymbol || + InputOperand.EarlyClobber || InputOperand.AllowSubReg)) { + fail(InputOperand.Range, "non-register operands cannot have register constraints"); + return false; + } + if (InputOperand.EarlyClobber && !IsDefRole) { + fail(InputOperand.Range, "early-clobber operands must define a register"); + return false; + } + + if (InputOperand.RegisterClassSymbol) { + const auto Position = ClassBySymbol.find(*InputOperand.RegisterClassSymbol); + if (Position == ClassBySymbol.end()) { + fail(InputOperand.Range, "instruction operand names an unknown register class"); + return false; + } + Result.RegisterClass = Position->second; + } + if (InputOperand.RegisterBankSymbol) { + const auto Position = BankBySymbol.find(*InputOperand.RegisterBankSymbol); + if (Position == BankBySymbol.end()) { + fail(InputOperand.Range, "instruction operand names an unknown register bank"); + return false; + } + Result.RegisterBank = Position->second; + } + if (InputOperand.ImmediatePredicateSymbol) { + const auto Position = ImmediatePredicateBySymbol.find(*InputOperand.ImmediatePredicateSymbol); + if (Position == ImmediatePredicateBySymbol.end() || + std::ranges::find(Result.Kinds, "Immediate") == Result.Kinds.end()) { + fail(InputOperand.Range, + "instruction immediate predicates must name a declaration and constrain an immediate"); + return false; + } + Result.ImmediatePredicate = Position->second; + } + if (InputOperand.NameSymbol) { + const auto Position = NamedOperandBySymbol.find(*InputOperand.NameSymbol); + if (Position == NamedOperandBySymbol.end()) { + fail(InputOperand.Range, "instruction operand names an unknown named operand"); + return false; + } + Result.Name = Position->second; + } + + Result.DefaultTiedOperand = InputOperand.DefaultTiedOperand; + Result.EarlyClobber = InputOperand.EarlyClobber; + Result.Optional = InputOperand.Optional; + Result.VariadicGroup = InputOperand.VariadicGroup; + Result.AllowSubReg = InputOperand.AllowSubReg; + Result.TypeIndex = InputOperand.TypeIndex; + Result.AllowedTargetFlags = InputOperand.AllowedTargetFlags; + return true; +} + +[[nodiscard]] bool +CodeGenTarget::normalizeImplicitRegisters(const std::vector<SourceImplicitRegister> &InputRegisters, + bool IsDef, + std::vector<NormalizedImplicitRegister> &Result) { + std::unordered_set<std::uint32_t> Registers; + for (const SourceImplicitRegister &Register : InputRegisters) { + const auto Position = RegisterBySymbol.find(Register.RegisterSymbol); + if (Position == RegisterBySymbol.end() || !Registers.insert(Position->second).second || + (!IsDef && Register.EarlyClobber)) { + fail(Register.Range, + "implicit registers must be declared and unique; implicit uses cannot early-clobber"); + return false; + } + Result.push_back({ + .Register = Position->second, + .EarlyClobber = Register.EarlyClobber, + .Renamable = Register.Renamable, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeInstructions(NormalizedTargetModel &Result) { + if (Input.Instructions.empty()) { + fail(Input.Target.Range, "target description requires at least one instruction"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceInstruction &Instruction : Input.Instructions) { + const bool HasMnemonic = !Instruction.AssemblyMnemonic.empty(); + const bool HasBinaryEncoding = Instruction.BinaryEncoding.has_value(); + const bool HasSchedulingClass = !Instruction.SchedulingClassSymbol.empty(); + const bool HasEncodingCategory = !Instruction.EncodingCategorySymbol.empty(); + const bool HasAssemblyFormat = !Instruction.AssemblyFormatSymbol.empty(); + const bool HasAllMachineFacts = HasMnemonic && HasBinaryEncoding && HasSchedulingClass && + HasEncodingCategory && HasAssemblyFormat; + const bool HasAnyMachineFact = HasMnemonic || HasBinaryEncoding || HasSchedulingClass || + HasEncodingCategory || HasAssemblyFormat; + const bool IsPseudoSource = + std::ranges::find(Instruction.Flags, "pseudo") != Instruction.Flags.end(); + if ((!IsPseudoSource && !HasAllMachineFacts) || HasAnyMachineFact != HasAllMachineFacts) { + fail(Instruction.Range, + "encoded instructions require complete scheduling, encoding, mnemonic, and " + "assembly-format facts"); + return false; + } + const auto SchedulingClass = SchedulingClassBySymbol.find(Instruction.SchedulingClassSymbol); + const auto EncodingCategory = EncodingCategoryBySymbol.find(Instruction.EncodingCategorySymbol); + const auto AssemblyFormat = AssemblyFormatBySymbol.find(Instruction.AssemblyFormatSymbol); + const auto Owner = pseudoOwnerEnumerator(Instruction.Owner); + const auto Deadline = pipelineStageEnumerator(Instruction.Deadline); + if (Instruction.ID == 0 || !isIdentifier(Instruction.Symbol) || Instruction.Name.empty() || + (HasMnemonic && !isAssemblyMnemonic(Instruction.AssemblyMnemonic)) || + (HasSchedulingClass && SchedulingClass == SchedulingClassBySymbol.end()) || + (HasEncodingCategory && EncodingCategory == EncodingCategoryBySymbol.end()) || + (HasAssemblyFormat && AssemblyFormat == AssemblyFormatBySymbol.end()) || !Owner || + !Deadline || !IDs.insert(Instruction.ID).second || + !Symbols.insert(Instruction.Symbol).second || !Names.insert(Instruction.Name).second) { + fail(Instruction.Range, + "instructions require nonzero unique IDs, symbols, lowercase assembly mnemonics, and " + "declared format IDs"); + return false; + } + if (Instruction.NumExplicitDefs > Instruction.Operands.size()) { + fail(Instruction.Range, "instruction explicit def count is outside its operand range"); + return false; + } + + NormalizedInstruction Normalized{ + .ID = Instruction.ID, + .Symbol = Instruction.Symbol, + .Name = Instruction.Name, + .AssemblyMnemonic = Instruction.AssemblyMnemonic, + .BinaryEncoding = Instruction.BinaryEncoding.value_or(0), + .Operands = {}, + .NumExplicitDefs = Instruction.NumExplicitDefs, + .Variadic = Instruction.Variadic, + .ImplicitDefs = {}, + .ImplicitUses = {}, + .AllowExtraImplicitOperands = Instruction.AllowExtraImplicitOperands, + .Flags = {}, + .SchedulingClass = HasSchedulingClass ? SchedulingClass->second : 0, + .EncodingCategory = HasEncodingCategory ? EncodingCategory->second : 0, + .SizeInBytes = Instruction.SizeInBytes, + .AssemblyFormat = HasAssemblyFormat ? AssemblyFormat->second : 0, + .RequiresMemoryOperands = Instruction.RequiresMemoryOperands, + .Owner = std::string(*Owner), + .Deadline = std::string(*Deadline), + }; + Normalized.Operands.reserve(Instruction.Operands.size()); + bool SawOptional = false; + for (std::size_t Index = 0; Index < Instruction.Operands.size(); ++Index) { + NormalizedOperand Operand; + if (!normalizeOperand(Instruction.Operands[Index], Operand)) + return false; + const bool IsDef = Operand.RegisterRole == "Def" || Operand.RegisterRole == "DefUse"; + if ((Index < Instruction.NumExplicitDefs) != IsDef) { + fail(Instruction.Operands[Index].Range, + "instruction explicit register definitions must form the leading operand prefix"); + return false; + } + if (Instruction.Variadic && Operand.Optional) { + fail(Instruction.Operands[Index].Range, + "variadic instructions cannot contain optional operands"); + return false; + } + if (Operand.Optional) + SawOptional = true; + else if (SawOptional && !Operand.VariadicGroup) { + fail(Instruction.Operands[Index].Range, + "optional instruction operands must form a trailing suffix"); + return false; + } + if (Operand.VariadicGroup && + (!Instruction.Variadic || Index + 1 != Instruction.Operands.size())) { + fail(Instruction.Operands[Index].Range, + "a variadic operand group must be the final instruction operand"); + return false; + } + if (Operand.VariadicGroup && (Operand.Optional || Operand.DefaultTiedOperand)) { + fail(Instruction.Operands[Index].Range, + "a variadic operand group cannot be optional or have a default tie"); + return false; + } + Normalized.Operands.push_back(std::move(Operand)); + } + if (Instruction.Variadic && + (Normalized.Operands.empty() || !Normalized.Operands.back().VariadicGroup)) { + fail(Instruction.Range, "variadic instructions require a final variadic operand group"); + return false; + } + if (!Instruction.Variadic && + std::ranges::any_of(Normalized.Operands, [](const NormalizedOperand &Operand) { + return Operand.VariadicGroup; + })) { + fail(Instruction.Range, "non-variadic instructions cannot have a variadic operand group"); + return false; + } + + std::unordered_set<std::uint32_t> NamedOperands; + for (std::size_t Index = 0; Index < Normalized.Operands.size(); ++Index) { + const NormalizedOperand &Operand = Normalized.Operands[Index]; + if (Operand.Name && !NamedOperands.insert(*Operand.Name).second) { + fail(Instruction.Operands[Index].Range, + "instruction operands must use distinct named operand IDs"); + return false; + } + if (!Operand.DefaultTiedOperand) + continue; + const std::uint32_t OtherIndex = *Operand.DefaultTiedOperand; + if (OtherIndex >= Normalized.Operands.size() || OtherIndex == Index || + !Normalized.Operands[OtherIndex].DefaultTiedOperand || + *Normalized.Operands[OtherIndex].DefaultTiedOperand != Index) { + fail(Instruction.Operands[Index].Range, + "instruction default ties must be reciprocal and in range"); + return false; + } + const NormalizedOperand &Other = Normalized.Operands[OtherIndex]; + const bool IsRegisterOnly = Operand.Kinds.size() == 1 && Operand.Kinds.front() == "Register"; + const bool OtherIsRegisterOnly = Other.Kinds.size() == 1 && Other.Kinds.front() == "Register"; + const bool IsDefUsePair = (Operand.RegisterRole == "Def" && Other.RegisterRole == "Use") || + (Operand.RegisterRole == "Use" && Other.RegisterRole == "Def"); + if (Operand.Optional || Other.Optional || !IsRegisterOnly || !OtherIsRegisterOnly || + !IsDefUsePair) { + fail(Instruction.Operands[Index].Range, + "instruction default ties must connect required register defs and uses"); + return false; + } + } + + std::unordered_set<std::string> Flags; + for (const std::string &Flag : Instruction.Flags) { + const auto Enumerator = instructionFlagEnumerator(Flag); + if (!Enumerator || !Flags.insert(std::string(*Enumerator)).second) { + fail(Instruction.Range, "instruction flags must be known and unique"); + return false; + } + Normalized.Flags.push_back(std::string(*Enumerator)); + } + const auto HasFlag = [&Normalized](std::string_view Flag) { + return std::ranges::find(Normalized.Flags, Flag) != Normalized.Flags.end(); + }; + const bool IsTerminator = HasFlag("Terminator"); + if (!HasFlag("Generic") && + std::ranges::any_of(Normalized.Operands, [](const NormalizedOperand &Operand) { + return Operand.TypeIndex.has_value(); + })) { + fail(Instruction.Range, + "instruction type_index operands require the generic instruction flag"); + return false; + } + if ((HasFlag("Branch") || HasFlag("Return") || HasFlag("Barrier") || + HasFlag("IndirectBranch")) && + !IsTerminator) { + fail(Instruction.Range, "branch, return, and barrier instructions must be terminators"); + return false; + } + if (HasFlag("IndirectBranch") && !HasFlag("Branch")) { + fail(Instruction.Range, "indirect branch instructions must also be branches"); + return false; + } + const bool IsPseudo = HasFlag("Pseudo"); + const bool IsMeta = HasFlag("Meta"); + if ((IsMeta && (!IsPseudo || HasFlag("ProducesCode"))) || + (!IsPseudo && Normalized.Owner != "None") || + (IsPseudo && !IsMeta && Normalized.Owner == "None") || + (HasFlag("TerminalEmission") && + (!IsPseudo || Normalized.Owner != "AsmPrinter" || !HasFlag("ProducesCode")))) { + fail(Instruction.Range, "instruction pseudo flags, owner, and deadline are inconsistent"); + return false; + } + if (Normalized.SizeInBytes != 0 && !HasFlag("ProducesCode")) { + fail(Instruction.Range, "only code-producing instructions can declare a byte size"); + return false; + } + if (Normalized.RequiresMemoryOperands && !HasFlag("MayLoad") && !HasFlag("MayStore")) { + fail(Instruction.Range, "memory operand requirements need a load or store instruction flag"); + return false; + } + if (!normalizeImplicitRegisters(Instruction.ImplicitDefs, true, Normalized.ImplicitDefs) || + !normalizeImplicitRegisters(Instruction.ImplicitUses, false, Normalized.ImplicitUses)) { + return false; + } + Result.Instructions.push_back(std::move(Normalized)); + } + std::ranges::sort(Result.Instructions, {}, &NormalizedInstruction::ID); + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp b/backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp new file mode 100644 index 0000000..ca81085 --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenMCPseudoLowering.cpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CodeGenTarget.h" + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <ranges> +#include <string_view> +#include <unordered_set> + +namespace rcc::targetgen { +namespace { + +[[nodiscard]] const NormalizedInstruction *findInstruction(const NormalizedTargetModel &Model, + std::string_view Symbol) noexcept { + const auto Position = + std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol); + return Position == Model.Instructions.end() ? nullptr : &*Position; +} + +[[nodiscard]] bool hasFlag(const NormalizedInstruction &Instruction, + std::string_view Flag) noexcept { + return std::ranges::contains(Instruction.Flags, Flag); +} + +[[nodiscard]] bool hasCompatibleKind(const NormalizedOperand &Source, + const NormalizedOperand &Result) noexcept { + return std::ranges::any_of(Source.Kinds, [&Result](const std::string &Kind) { + return std::ranges::contains(Result.Kinds, Kind); + }); +} + +} // namespace + +bool CodeGenTarget::normalizeMCPseudoExpansions(NormalizedTargetModel &Result) { + std::unordered_set<std::uint32_t> PseudoOpcodes; + for (const SourceMCPseudoExpansion &Expansion : Input.MCPseudoExpansions) { + const NormalizedInstruction *Pseudo = findInstruction(Result, Expansion.PseudoOpcodeSymbol); + const NormalizedInstruction *ResultInstruction = + findInstruction(Result, Expansion.ResultOpcodeSymbol); + if (Pseudo == nullptr || ResultInstruction == nullptr || + !PseudoOpcodes.insert(Pseudo == nullptr ? 0 : Pseudo->ID).second) { + fail(Expansion.Range, + "MC pseudo expansions require a unique declared pseudo and result instruction"); + return false; + } + if (!hasFlag(*Pseudo, "Pseudo") || !hasFlag(*Pseudo, "TerminalEmission") || + Pseudo->Owner != "AsmPrinter" || Pseudo->Deadline != "Emission" || + hasFlag(*ResultInstruction, "Pseudo") || ResultInstruction->AssemblyMnemonic.empty()) { + fail(Expansion.Range, + "MC pseudo expansions require an AsmPrinter-owned terminal pseudo and encoded result"); + return false; + } + if (Expansion.Operands.size() != ResultInstruction->Operands.size()) { + fail(Expansion.Range, "MC pseudo expansion operand count must match the result instruction"); + return false; + } + + NormalizedMCPseudoExpansion Normalized{ + .PseudoOpcode = Pseudo->ID, + .ResultOpcode = ResultInstruction->ID, + .Operands = {}, + }; + Normalized.Operands.reserve(Expansion.Operands.size()); + for (std::size_t Index = 0; Index < Expansion.Operands.size(); ++Index) { + const SourceMCPseudoOperand &Operand = Expansion.Operands[Index]; + const unsigned SourceCount = static_cast<unsigned>(Operand.SourceOperandIndex.has_value()) + + static_cast<unsigned>(!Operand.RegisterSymbol.empty()) + + static_cast<unsigned>(Operand.Immediate.has_value()); + if (SourceCount != 1) { + fail(Operand.Range, + "MC pseudo expansion operands require exactly one operand, register, or immediate"); + return false; + } + const NormalizedOperand &ResultOperand = ResultInstruction->Operands[Index]; + if (Operand.SourceOperandIndex) { + if (*Operand.SourceOperandIndex >= Pseudo->Operands.size() || + !hasCompatibleKind(Pseudo->Operands[*Operand.SourceOperandIndex], ResultOperand)) { + fail(Operand.Range, + "MC pseudo source operand must exist and match the result operand kind"); + return false; + } + Normalized.Operands.push_back({ + .Kind = NormalizedMCPseudoOperandKind::SourceOperand, + .Value = *Operand.SourceOperandIndex, + .Immediate = 0, + }); + continue; + } + if (!Operand.RegisterSymbol.empty()) { + const auto Register = RegisterBySymbol.find(Operand.RegisterSymbol); + if (Register == RegisterBySymbol.end() || + !std::ranges::contains(ResultOperand.Kinds, "Register")) { + fail(Operand.Range, + "MC pseudo fixed register must be declared and target a register operand"); + return false; + } + Normalized.Operands.push_back({ + .Kind = NormalizedMCPseudoOperandKind::Register, + .Value = Register->second, + .Immediate = 0, + }); + continue; + } + if (!std::ranges::contains(ResultOperand.Kinds, "Immediate")) { + fail(Operand.Range, "MC pseudo fixed immediate must target an immediate operand"); + return false; + } + assert(Operand.Immediate && "validated MC pseudo operand must contain an immediate"); + Normalized.Operands.push_back({ + .Kind = NormalizedMCPseudoOperandKind::Immediate, + .Value = 0, + .Immediate = *Operand.Immediate, + }); + } + Result.MCPseudoExpansions.push_back(std::move(Normalized)); + } + std::ranges::sort(Result.MCPseudoExpansions, {}, &NormalizedMCPseudoExpansion::PseudoOpcode); + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp b/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp new file mode 100644 index 0000000..7c4ea1c --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenMacroFusion.cpp @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CodeGenTarget.h" + +#include <algorithm> +#include <cstdint> +#include <optional> +#include <ranges> +#include <string> +#include <string_view> +#include <unordered_set> +#include <vector> + +namespace rcc::targetgen { +namespace { + +[[nodiscard]] const NormalizedInstruction *findInstruction(const NormalizedTargetModel &Model, + std::string_view Symbol) noexcept { + const auto Position = + std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol); + return Position == Model.Instructions.end() ? nullptr : &*Position; +} + +[[nodiscard]] bool isOperandKind(const NormalizedInstruction &Instruction, + std::uint32_t OperandIndex, std::string_view Kind) noexcept { + return OperandIndex < Instruction.Operands.size() && + std::ranges::contains(Instruction.Operands[OperandIndex].Kinds, Kind); +} + +[[nodiscard]] std::optional<NormalizedMacroFusionInstruction> +normalizeInstructionSide(std::string_view Side) noexcept { + if (Side == "first") + return NormalizedMacroFusionInstruction::First; + if (Side == "second") + return NormalizedMacroFusionInstruction::Second; + return std::nullopt; +} + +} // namespace + +bool CodeGenTarget::normalizeMacroFusionRules(NormalizedTargetModel &Result) { + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Features; + for (const SourceMacroFusionRule &Rule : Input.MacroFusionRules) { + if (!isIdentifier(Rule.Symbol) || Rule.Feature.empty() || Rule.FirstOpcodeSymbols.empty() || + Rule.SecondOpcodeSymbols.empty() || !Symbols.insert(Rule.Symbol).second || + !Features.insert(Rule.Feature).second) { + fail(Rule.Range, + "macro fusion rules require unique identifiers and features plus nonempty opcode sets"); + return false; + } + + NormalizedMacroFusionRule Normalized{ + .Symbol = Rule.Symbol, + .Feature = Rule.Feature, + .FirstOpcodes = {}, + .SecondOpcodes = {}, + .OpcodePairs = {}, + .ImmediatePredicates = {}, + .RegisterInequalities = {}, + .ImmediateRelations = {}, + .FirstDefinitionOperand = Rule.FirstDefinitionOperand, + .SecondUseOperand = Rule.SecondUseOperand, + .SecondDefinitionOperand = Rule.SecondDefinitionOperand, + .CommutableSecondUseOperand = Rule.CommutableSecondUseOperand, + .RequireSingleNonDebugUse = Rule.RequireSingleNonDebugUse, + .RequireSecondDestinationTie = Rule.RequireSecondDestinationTie, + }; + std::unordered_set<std::uint32_t> FirstOpcodes; + std::unordered_set<std::uint32_t> SecondOpcodes; + std::vector<const NormalizedInstruction *> FirstInstructions; + std::vector<const NormalizedInstruction *> SecondInstructions; + const auto NormalizeOpcodes = + [this, &Result](const std::vector<std::string> &OpcodeSymbols, + std::unordered_set<std::uint32_t> &Opcodes, + std::vector<std::uint32_t> &Output, + std::vector<const NormalizedInstruction *> &Instructions, TextRange Range) { + for (const std::string &Symbol : OpcodeSymbols) { + const NormalizedInstruction *Instruction = findInstruction(Result, Symbol); + if (Instruction == nullptr || !Opcodes.insert(Instruction->ID).second) { + fail(Range, "macro fusion opcodes must name distinct declared target instructions"); + return false; + } + Output.push_back(Instruction->ID); + Instructions.push_back(Instruction); + } + return true; + }; + if (!NormalizeOpcodes(Rule.FirstOpcodeSymbols, FirstOpcodes, Normalized.FirstOpcodes, + FirstInstructions, Rule.Range) || + !NormalizeOpcodes(Rule.SecondOpcodeSymbols, SecondOpcodes, Normalized.SecondOpcodes, + SecondInstructions, Rule.Range)) { + return false; + } + + const auto AllHaveOperandKind = [](const auto &Instructions, std::uint32_t Operand, + std::string_view Kind) { + return std::ranges::all_of(Instructions, [Operand, Kind](const auto *Instruction) { + return isOperandKind(*Instruction, Operand, Kind); + }); + }; + if (!AllHaveOperandKind(FirstInstructions, Rule.FirstDefinitionOperand, "Register") || + !AllHaveOperandKind(SecondInstructions, Rule.SecondUseOperand, "Register") || + (Rule.RequireSecondDestinationTie && + !AllHaveOperandKind(SecondInstructions, Rule.SecondDefinitionOperand, "Register")) || + (Rule.CommutableSecondUseOperand && + !AllHaveOperandKind(SecondInstructions, *Rule.CommutableSecondUseOperand, "Register"))) { + fail(Rule.Range, "macro fusion linkage must reference register operands of every opcode"); + return false; + } + + std::unordered_set<std::uint64_t> OpcodePairs; + for (const SourceMacroFusionOpcodePair &Pair : Rule.OpcodePairs) { + const NormalizedInstruction *First = findInstruction(Result, Pair.FirstOpcodeSymbol); + const NormalizedInstruction *Second = findInstruction(Result, Pair.SecondOpcodeSymbol); + if (First == nullptr || Second == nullptr || !FirstOpcodes.contains(First->ID) || + !SecondOpcodes.contains(Second->ID)) { + fail(Pair.Range, "macro fusion opcode pair must belong to the rule opcode sets"); + return false; + } + const std::uint64_t Key = (static_cast<std::uint64_t>(First->ID) << 32U) | Second->ID; + if (!OpcodePairs.insert(Key).second) { + fail(Pair.Range, "macro fusion opcode pairs must be distinct"); + return false; + } + Normalized.OpcodePairs.push_back({First->ID, Second->ID}); + } + + for (const SourceMacroFusionImmediatePredicate &Predicate : Rule.ImmediatePredicates) { + const auto Side = normalizeInstructionSide(Predicate.Instruction); + const auto &Instructions = + Side == NormalizedMacroFusionInstruction::First ? FirstInstructions : SecondInstructions; + if (!Side || Predicate.Minimum > Predicate.Maximum || + !AllHaveOperandKind(Instructions, Predicate.OperandIndex, "Immediate")) { + fail( + Predicate.Range, + "macro fusion immediate predicate requires a valid side, range, and immediate operand"); + return false; + } + Normalized.ImmediatePredicates.push_back( + {*Side, Predicate.OperandIndex, Predicate.Minimum, Predicate.Maximum}); + } + + for (const SourceMacroFusionRegisterInequality &Predicate : Rule.RegisterInequalities) { + const auto Side = normalizeInstructionSide(Predicate.Instruction); + const auto &Instructions = + Side == NormalizedMacroFusionInstruction::First ? FirstInstructions : SecondInstructions; + if (!Side || Predicate.LeftOperand == Predicate.RightOperand || + !AllHaveOperandKind(Instructions, Predicate.LeftOperand, "Register") || + !AllHaveOperandKind(Instructions, Predicate.RightOperand, "Register")) { + fail( + Predicate.Range, + "macro fusion register inequality requires distinct register operands on a valid side"); + return false; + } + Normalized.RegisterInequalities.push_back( + {*Side, Predicate.LeftOperand, Predicate.RightOperand}); + } + + for (const SourceMacroFusionImmediateRelation &Relation : Rule.ImmediateRelations) { + if (Relation.Kind != "less_equal" || + !AllHaveOperandKind(FirstInstructions, Relation.FirstOperand, "Immediate") || + !AllHaveOperandKind(SecondInstructions, Relation.SecondOperand, "Immediate")) { + fail(Relation.Range, + "macro fusion immediate relation must be less_equal over immediate operands"); + return false; + } + Normalized.ImmediateRelations.push_back({Relation.FirstOperand, Relation.SecondOperand}); + } + Result.MacroFusionRules.push_back(std::move(Normalized)); + } + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenRegisters.cpp b/backend/utils/TableGen/Common/CodeGenRegisters.cpp new file mode 100644 index 0000000..9fa8d7c --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenRegisters.cpp @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Common/CodeGenTarget.h" + +#include <algorithm> +#include <limits> +#include <ranges> +#include <string> +#include <unordered_set> +#include <utility> + +namespace rcc::targetgen { + +[[nodiscard]] bool CodeGenTarget::normalizeRegisterUnits() { + if (Input.RegisterUnits.empty()) { + fail(Input.Target.Range, "target description requires at least one register unit"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceRegisterUnit &Unit : Input.RegisterUnits) { + if (Unit.ID == 0 || !isIdentifier(Unit.Symbol) || Unit.Name.empty() || + !IDs.insert(Unit.ID).second || !Symbols.insert(Unit.Symbol).second || + !Names.insert(lowerASCII(Unit.Name)).second) { + fail(Unit.Range, + "register units require nonzero unique IDs, unique symbols, and unique names"); + return false; + } + UnitBySymbol.emplace(Unit.Symbol, Unit.ID); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeRegisters(NormalizedTargetModel &Result) { + if (Input.Registers.empty()) { + fail(Input.Target.Range, "target description requires at least one physical register"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceRegister &Register : Input.Registers) { + if (Register.ID == 0 || !isIdentifier(Register.Symbol) || Register.Name.empty() || + !IDs.insert(Register.ID).second || !Symbols.insert(Register.Symbol).second || + !Names.insert(lowerASCII(Register.Name)).second || Register.Units.empty()) { + fail(Register.Range, + "registers require nonzero unique IDs, symbols, canonical names, and units"); + return false; + } + RegisterBySymbol.emplace(Register.Symbol, Register.ID); + NormalizedRegister Normalized{ + .ID = Register.ID, + .Symbol = Register.Symbol, + .Name = Register.Name, + .Encoding = Register.Encoding, + .DwarfRegister = Register.DwarfRegister, + .Units = {}, + .IsConstant = Register.IsConstant, + .IsArtificial = Register.IsArtificial, + .IsReserved = Register.IsReserved, + .AlternativeNames = Register.AlternativeNames, + }; + std::unordered_set<std::uint32_t> Units; + for (const SourceRegisterUnitLane &Unit : Register.Units) { + const auto Position = UnitBySymbol.find(Unit.UnitSymbol); + if (Position == UnitBySymbol.end() || Unit.Lanes == 0 || + !Units.insert(Position->second).second) { + fail(Unit.Range, + "register units must name distinct declared units with nonzero lane masks"); + return false; + } + Normalized.Units.push_back({.UnitID = Position->second, .Lanes = Unit.Lanes}); + } + for (const std::string &Alias : Register.AlternativeNames) { + if (Alias.empty() || !Names.insert(lowerASCII(Alias)).second) { + fail(Register.Range, + "register ABI aliases must be nonempty and unique across all register names"); + return false; + } + } + Result.Registers.push_back(std::move(Normalized)); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeRegisterBanks(NormalizedTargetModel &Result) { + if (Input.RegisterBanks.empty()) { + fail(Input.Target.Range, "target description requires at least one register bank"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceRegisterBank &Bank : Input.RegisterBanks) { + if (Bank.ID == 0 || !isIdentifier(Bank.Symbol) || Bank.Name.empty() || + Bank.MaximumSizeInBits == 0 || !IDs.insert(Bank.ID).second || + !Symbols.insert(Bank.Symbol).second || !Names.insert(lowerASCII(Bank.Name)).second) { + fail(Bank.Range, + "register banks require nonzero unique IDs, symbols, names, and maximum sizes"); + return false; + } + BankBySymbol.emplace(Bank.Symbol, Bank.ID); + Result.RegisterBanks.push_back({ + .ID = Bank.ID, + .Symbol = Bank.Symbol, + .Name = Bank.Name, + .MaximumSizeInBits = Bank.MaximumSizeInBits, + .RegisterClasses = {}, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeRegisterClasses(NormalizedTargetModel &Result) { + if (Input.RegisterClasses.empty()) { + fail(Input.Target.Range, "target description requires at least one register class"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceRegisterClass &Class : Input.RegisterClasses) { + const auto Bank = BankBySymbol.find(Class.BankSymbol); + if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() || + Bank == BankBySymbol.end() || Class.SizeInBits == 0 || Class.SpillSizeInBits == 0 || + Class.SpillAlignmentInBits == 0 || Class.Lanes == 0 || Class.RegisterSymbols.empty() || + !IDs.insert(Class.ID).second || !Symbols.insert(Class.Symbol).second || + !Names.insert(lowerASCII(Class.Name)).second) { + fail(Class.Range, "register classes require unique IDs, symbols, names, a declared bank, " + "sizes, lanes, and members"); + return false; + } + ClassBySymbol.emplace(Class.Symbol, Class.ID); + } + for (const SourceRegisterClass &Class : Input.RegisterClasses) { + const auto Bank = BankBySymbol.find(Class.BankSymbol); + NormalizedRegisterClass Normalized{ + .ID = Class.ID, + .Symbol = Class.Symbol, + .Name = Class.Name, + .BankID = Bank->second, + .SizeInBits = Class.SizeInBits, + .SpillSizeInBits = Class.SpillSizeInBits, + .SpillAlignmentInBits = Class.SpillAlignmentInBits, + .Lanes = Class.Lanes, + .Registers = {}, + .AllocationOrder = {}, + .SuperClasses = {}, + .Allocatable = Class.Allocatable, + .CopyCost = Class.CopyCost, + }; + std::unordered_set<std::uint32_t> Members; + for (const std::string &Symbol : Class.RegisterSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Members.insert(Register->second).second) { + fail(Class.Range, "register class members must reference distinct declared registers"); + return false; + } + Normalized.Registers.push_back(Register->second); + } + std::unordered_set<std::uint32_t> Allocation; + for (const std::string &Symbol : Class.AllocationOrderSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Members.contains(Register->second) || + !Allocation.insert(Register->second).second) { + fail(Class.Range, + "register class allocation order must contain distinct members of the same class"); + return false; + } + Normalized.AllocationOrder.push_back(Register->second); + } + std::unordered_set<std::uint32_t> SuperClasses; + for (const std::string &Symbol : Class.SuperClassSymbols) { + const auto SuperClass = ClassBySymbol.find(Symbol); + if (SuperClass == ClassBySymbol.end() || SuperClass->second == Class.ID || + !SuperClasses.insert(SuperClass->second).second) { + fail(Class.Range, + "register class superclasses must reference distinct declared classes other than " + "the class itself"); + return false; + } + Normalized.SuperClasses.push_back(SuperClass->second); + } + if ((Normalized.Allocatable && Normalized.AllocationOrder.empty()) || + (!Normalized.Allocatable && !Normalized.AllocationOrder.empty())) { + fail(Class.Range, "allocatable register classes require an allocation order and " + "nonallocatable classes forbid one"); + return false; + } + Result.RegisterClasses.push_back(std::move(Normalized)); + } + for (NormalizedRegisterBank &Bank : Result.RegisterBanks) { + for (const NormalizedRegisterClass &Class : Result.RegisterClasses) { + if (Class.BankID == Bank.ID) + Bank.RegisterClasses.push_back(Class.ID); + } + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeRegisterMasks(NormalizedTargetModel &Result) { + std::unordered_set<std::string> Names; + for (const SourceRegisterMask &Mask : Input.RegisterMasks) { + if (Mask.Name.empty() || !Names.insert(Mask.Name).second) { + fail(Mask.Range, "register mask names must be nonempty and unique"); + return false; + } + NormalizedRegisterMask Normalized{.Name = Mask.Name, .PreservedRegisters = {}}; + std::unordered_set<std::uint32_t> Preserved; + for (const std::string &Symbol : Mask.PreservedRegisterSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Preserved.insert(Register->second).second) { + fail(Mask.Range, "register mask entries must name distinct declared registers"); + return false; + } + Normalized.PreservedRegisters.push_back(Register->second); + } + Result.RegisterMasks.push_back(std::move(Normalized)); + } + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenSchedule.cpp b/backend/utils/TableGen/Common/CodeGenSchedule.cpp new file mode 100644 index 0000000..56dbb5d --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenSchedule.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Common/CodeGenTarget.h" + +#include <ranges> +#include <string> +#include <unordered_set> + +namespace rcc::targetgen { + +[[nodiscard]] bool CodeGenTarget::normalizeSchedulingClasses(NormalizedTargetModel &Result) { + if (Input.SchedulingClasses.empty()) { + fail(Input.Target.Range, "target description requires at least one scheduling class"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceSchedulingClass &Class : Input.SchedulingClasses) { + if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() || Class.MicroOps == 0 || + Class.Latency == 0 || !IDs.insert(Class.ID).second || + !Symbols.insert(Class.Symbol).second || !Names.insert(Class.Name).second) { + fail(Class.Range, + "scheduling classes require nonzero unique IDs, symbols, names, micro-op counts, " + "and latencies"); + return false; + } + SchedulingClassBySymbol.emplace(Class.Symbol, Class.ID); + Result.SchedulingClasses.push_back({ + .ID = Class.ID, + .Symbol = Class.Symbol, + .Name = Class.Name, + .MicroOps = Class.MicroOps, + .Latency = Class.Latency, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeEncodingCategories(NormalizedTargetModel &Result) { + if (Input.EncodingCategories.empty()) { + fail(Input.Target.Range, "target description requires at least one encoding category"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceEncodingCategory &Category : Input.EncodingCategories) { + if (Category.ID == 0 || !isIdentifier(Category.Symbol) || Category.Name.empty() || + Category.SizeInBytes == 0 || !IDs.insert(Category.ID).second || + !Symbols.insert(Category.Symbol).second || !Names.insert(Category.Name).second) { + fail(Category.Range, + "encoding categories require nonzero unique IDs, symbols, names, and sizes"); + return false; + } + EncodingCategoryBySymbol.emplace(Category.Symbol, Category.ID); + Result.EncodingCategories.push_back({ + .ID = Category.ID, + .Symbol = Category.Symbol, + .Name = Category.Name, + .SizeInBytes = Category.SizeInBytes, + }); + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeAssemblyFormats(NormalizedTargetModel &Result) { + if (Input.AssemblyFormats.empty()) { + fail(Input.Target.Range, "target description requires at least one assembly format"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceAssemblyFormat &Format : Input.AssemblyFormats) { + if (Format.ID == 0 || !isIdentifier(Format.Symbol) || Format.Name.empty() || + !IDs.insert(Format.ID).second || !Symbols.insert(Format.Symbol).second || + !Names.insert(Format.Name).second) { + fail(Format.Range, "assembly formats require nonzero unique IDs, symbols, and names"); + return false; + } + AssemblyFormatBySymbol.emplace(Format.Symbol, Format.ID); + Result.AssemblyFormats.push_back({ + .ID = Format.ID, + .Symbol = Format.Symbol, + .Name = Format.Name, + }); + } + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenSelection.cpp b/backend/utils/TableGen/Common/CodeGenSelection.cpp new file mode 100644 index 0000000..b17d176 --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenSelection.cpp @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CodeGenTarget.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineOperand.h" + +#include <algorithm> +#include <array> +#include <cstdint> +#include <optional> +#include <ranges> +#include <span> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> +#include <utility> + +namespace rcc::targetgen { +namespace { + +[[nodiscard]] std::optional<std::uint32_t> genericOpcodeValue(std::string_view Symbol) { +#define RCC_CORE_OPCODE(Name) \ + if (Symbol == #Name) \ + return static_cast<std::uint32_t>(GenericOpcode::Name); +#define RCC_GENERIC_OPCODE(Name) RCC_CORE_OPCODE(Name) +#include "rcc/CodeGen/GenericOpcode.def" +#undef RCC_GENERIC_OPCODE +#undef RCC_CORE_OPCODE + return std::nullopt; +} + +[[nodiscard]] const NormalizedInstruction * +findInstructionBySymbol(const NormalizedTargetModel &Model, std::string_view Symbol) { + const auto Position = + std::ranges::find(Model.Instructions, Symbol, &NormalizedInstruction::Symbol); + return Position == Model.Instructions.end() ? nullptr : &*Position; +} + +[[nodiscard]] std::optional<std::uint32_t> resolveOpcode(const NormalizedTargetModel &Model, + std::string_view Symbol) { + if (const NormalizedInstruction *Instruction = findInstructionBySymbol(Model, Symbol)) + return Instruction->ID; + return genericOpcodeValue(Symbol); +} + +[[nodiscard]] bool isRegisterOperand(const NormalizedInstruction &Instruction, + std::uint32_t Index) { + return Index < Instruction.Operands.size() && + std::ranges::contains(Instruction.Operands[Index].Kinds, "Register"); +} + +[[nodiscard]] std::optional<std::uint32_t> integerPredicateValue(std::string_view Symbol) noexcept { + static constexpr std::array<std::pair<std::string_view, IntegerPredicate>, 2> Predicates = {{ + {"eq", IntegerPredicate::Equal}, + {"ne", IntegerPredicate::NotEqual}, + }}; + if (Symbol == "sgt") + return static_cast<std::uint32_t>(IntegerPredicate::SignedGreaterThan); + if (Symbol == "sge") + return static_cast<std::uint32_t>(IntegerPredicate::SignedGreaterEqual); + if (Symbol == "sle") + return static_cast<std::uint32_t>(IntegerPredicate::SignedLessEqual); + if (Symbol == "slt") + return static_cast<std::uint32_t>(IntegerPredicate::SignedLessThan); + if (Symbol == "ugt") + return static_cast<std::uint32_t>(IntegerPredicate::UnsignedGreaterThan); + if (Symbol == "uge") + return static_cast<std::uint32_t>(IntegerPredicate::UnsignedGreaterEqual); + if (Symbol == "ult") + return static_cast<std::uint32_t>(IntegerPredicate::UnsignedLessThan); + if (Symbol == "ule") + return static_cast<std::uint32_t>(IntegerPredicate::UnsignedLessEqual); + const auto *const Position = + std::ranges::find(Predicates, Symbol, &decltype(Predicates)::value_type::first); + if (Position == Predicates.end()) + return std::nullopt; + return static_cast<std::uint32_t>(Position->second); +} + +[[nodiscard]] std::optional<std::uint32_t> +floatingPredicateValue(std::string_view Symbol) noexcept { + static constexpr std::array<std::pair<std::string_view, FloatingPredicate>, 16> Predicates = {{ + {"false", FloatingPredicate::AlwaysFalse}, + {"oeq", FloatingPredicate::OrderedEqual}, + {"ogt", FloatingPredicate::OrderedGreaterThan}, + {"oge", FloatingPredicate::OrderedGreaterEqual}, + {"olt", FloatingPredicate::OrderedLessThan}, + {"ole", FloatingPredicate::OrderedLessEqual}, + {"one", FloatingPredicate::OrderedNotEqual}, + {"ord", FloatingPredicate::Ordered}, + {"uno", FloatingPredicate::Unordered}, + {"ueq", FloatingPredicate::UnorderedEqual}, + {"ugt", FloatingPredicate::UnorderedGreaterThan}, + {"uge", FloatingPredicate::UnorderedGreaterEqual}, + {"ult", FloatingPredicate::UnorderedLessThan}, + {"ule", FloatingPredicate::UnorderedLessEqual}, + {"une", FloatingPredicate::UnorderedNotEqual}, + {"true", FloatingPredicate::AlwaysTrue}, + }}; + const auto *const Position = + std::ranges::find(Predicates, Symbol, &decltype(Predicates)::value_type::first); + if (Position == Predicates.end()) + return std::nullopt; + return static_cast<std::uint32_t>(Position->second); +} + +[[nodiscard]] const NormalizedRegister *findRegisterBySymbol(const NormalizedTargetModel &Model, + std::string_view Symbol) { + const auto Position = std::ranges::find(Model.Registers, Symbol, &NormalizedRegister::Symbol); + return Position == Model.Registers.end() ? nullptr : &*Position; +} + +[[nodiscard]] std::optional<RegisterOperandFlags> +registerOperandFlags(std::span<const std::string> Names) noexcept { + RegisterOperandFlags Flags; + for (const std::string &Name : Names) { + const auto Set = [&Flags](RegisterOperandFlag Flag) { + Flags.set(Flag); + return true; + }; + if (Name == "define") + Set(RegisterOperandFlag::Define); + else if (Name == "use") + Set(RegisterOperandFlag::Use); + else if (Name == "implicit") + Set(RegisterOperandFlag::Implicit); + else if (Name == "kill") + Set(RegisterOperandFlag::Kill); + else if (Name == "dead") + Set(RegisterOperandFlag::Dead); + else if (Name == "undef") + Set(RegisterOperandFlag::Undef); + else if (Name == "early_clobber") + Set(RegisterOperandFlag::EarlyClobber); + else if (Name == "internal_read") + Set(RegisterOperandFlag::InternalRead); + else if (Name == "renamable") + Set(RegisterOperandFlag::Renamable); + else if (Name == "debug") + Set(RegisterOperandFlag::Debug); + else + return std::nullopt; + } + return Flags; +} + +[[nodiscard]] bool validRegisterOperandFlags(RegisterOperandFlags Flags) noexcept { + const bool IsDef = Flags.has(RegisterOperandFlag::Define); + const bool IsUse = Flags.has(RegisterOperandFlag::Use); + return (IsDef || IsUse) && (!Flags.has(RegisterOperandFlag::Kill) || IsUse) && + (!Flags.has(RegisterOperandFlag::Dead) || IsDef) && + (!Flags.has(RegisterOperandFlag::Undef) || IsUse) && + (!Flags.has(RegisterOperandFlag::EarlyClobber) || IsDef) && + (!Flags.has(RegisterOperandFlag::InternalRead) || (IsDef && IsUse)) && + (!(IsDef && IsUse) || Flags.has(RegisterOperandFlag::InternalRead)) && + (!Flags.has(RegisterOperandFlag::Debug) || (IsUse && !IsDef)); +} + +} // namespace + +bool CodeGenTarget::normalizeSelectionRules(NormalizedTargetModel &Result) { + std::unordered_set<std::string> Symbols; + for (const SourceSelectionRule &Rule : Input.SelectionRules) { + const auto MatchOpcode = resolveOpcode(Result, Rule.MatchOpcodeSymbol); + const NormalizedInstruction *MatchInstruction = + findInstructionBySymbol(Result, Rule.MatchOpcodeSymbol); + if (!isIdentifier(Rule.Symbol) || !Symbols.insert(Rule.Symbol).second || !MatchOpcode) { + fail(Rule.Range, "selection rules require a unique identifier and declared match opcode"); + return false; + } + if (MatchInstruction != nullptr && !std::ranges::contains(MatchInstruction->Flags, "Generic")) { + fail(Rule.Range, "selection rule match opcode must be generic"); + return false; + } + if (Rule.Actions.empty()) { + fail(Rule.Range, "selection rule requires at least one generated instruction"); + return false; + } + + const auto NormalizeType = [this](const SourceSelectionTypePredicate &Source) + -> std::optional<NormalizedSelectionTypePredicate> { + NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any; + if (Source.Kind == "scalar") + Kind = NormalizedSelectionTypeKind::Scalar; + else if (Source.Kind == "pointer") + Kind = NormalizedSelectionTypeKind::Pointer; + else if (Source.Kind != "any") { + fail(Source.Range, "selection type kind must be 'any', 'scalar', or 'pointer'"); + return std::nullopt; + } + if (Source.Bits > LowLevelType::MaxBitWidth || + (Kind != NormalizedSelectionTypeKind::Any && Source.Bits == 0)) { + fail(Source.Range, "selection type bit width is invalid for its kind"); + return std::nullopt; + } + return NormalizedSelectionTypePredicate{.Kind = Kind, .Bits = Source.Bits}; + }; + const auto NormalizeRanges = [this](const std::vector<SourceSelectionConstantRange> &Ranges, + TextRange PredicateRange, std::string_view PredicateName) + -> std::optional<std::vector<NormalizedSelectionConstantRange>> { + if (Ranges.empty()) { + fail(PredicateRange, std::string(PredicateName) + " requires at least one range"); + return std::nullopt; + } + std::vector<NormalizedSelectionConstantRange> NormalizedRanges; + NormalizedRanges.reserve(Ranges.size()); + for (const SourceSelectionConstantRange &Range : Ranges) { + if (Range.Minimum > Range.Maximum) { + fail(Range.SourceRange, std::string(PredicateName) + " range is invalid"); + return std::nullopt; + } + NormalizedRanges.push_back({ + .Minimum = Range.Minimum, + .Maximum = Range.Maximum, + }); + } + std::ranges::sort(NormalizedRanges, {}, &NormalizedSelectionConstantRange::Minimum); + for (std::size_t Index = 1; Index < NormalizedRanges.size(); ++Index) { + const auto &Previous = NormalizedRanges[Index - 1]; + const auto &Current = NormalizedRanges[Index]; + if (Current.Minimum <= Previous.Maximum) { + fail(PredicateRange, std::string(PredicateName) + " ranges overlap"); + return std::nullopt; + } + } + return NormalizedRanges; + }; + + NormalizedSelectionRule Normalized; + Normalized.Symbol = Rule.Symbol; + Normalized.Priority = Rule.Priority; + Normalized.Specificity = 1; + Normalized.MatchOpcode = *MatchOpcode; + Normalized.EraseOperandDefinitions = Rule.EraseOperandDefinitions; + if (Rule.ResultType) { + auto Type = NormalizeType(*Rule.ResultType); + if (!Type) + return false; + if (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, 0)) { + fail(Rule.ResultType->Range, + "selection result type predicate requires register operand zero"); + return false; + } + Normalized.ResultType = *Type; + ++Normalized.Specificity; + } + + if (Rule.IntegerPredicate) { + const auto Predicate = integerPredicateValue(Rule.IntegerPredicate->Predicate); + if (!Predicate) { + fail(Rule.IntegerPredicate->Range, + "selection integer predicate is not a recognized integer predicate"); + return false; + } + if (MatchInstruction != nullptr && + (Rule.IntegerPredicate->OperandIndex >= MatchInstruction->Operands.size() || + !std::ranges::contains( + MatchInstruction->Operands[Rule.IntegerPredicate->OperandIndex].Kinds, + "Predicate"))) { + fail(Rule.IntegerPredicate->Range, + "selection integer predicate requires a predicate operand"); + return false; + } + Normalized.IntegerPredicate = { + .OperandIndex = Rule.IntegerPredicate->OperandIndex, + .Predicate = *Predicate, + }; + ++Normalized.Specificity; + } + + if (Rule.IntegerPredicate && Rule.FloatingPredicate) { + fail(Rule.Range, "selection rule cannot require both integer and floating predicates"); + return false; + } + if (Rule.FloatingPredicate) { + const auto Predicate = floatingPredicateValue(Rule.FloatingPredicate->Predicate); + if (!Predicate) { + fail(Rule.FloatingPredicate->Range, + "selection floating predicate is not a recognized floating predicate"); + return false; + } + if (MatchInstruction != nullptr && + (Rule.FloatingPredicate->OperandIndex >= MatchInstruction->Operands.size() || + !std::ranges::contains( + MatchInstruction->Operands[Rule.FloatingPredicate->OperandIndex].Kinds, + "Predicate"))) { + fail(Rule.FloatingPredicate->Range, + "selection floating predicate requires a predicate operand"); + return false; + } + Normalized.FloatingPredicate = { + .OperandIndex = Rule.FloatingPredicate->OperandIndex, + .Predicate = *Predicate, + }; + ++Normalized.Specificity; + } + + if (Rule.MemoryPredicate) { + if (Rule.MemoryPredicate->Bytes == 0 || + (MatchInstruction != nullptr && !MatchInstruction->RequiresMemoryOperands)) { + fail(Rule.MemoryPredicate->Range, + "selection memory predicate requires a nonzero size and a memory opcode"); + return false; + } + Normalized.MemoryPredicate = { + .MemoryOperandIndex = Rule.MemoryPredicate->MemoryOperandIndex, + .Bytes = Rule.MemoryPredicate->Bytes, + }; + ++Normalized.Specificity; + } + + std::unordered_set<std::string> TargetPredicateSymbols; + for (const SourceSelectionTargetPredicate &Predicate : Rule.TargetPredicates) { + if (!isIdentifier(Predicate.Symbol) || + !TargetPredicateSymbols.insert(Predicate.Symbol).second) { + fail(Predicate.Range, "selection target predicates require distinct identifiers"); + return false; + } + Normalized.TargetPredicates.push_back(Predicate.Symbol); + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> TypeOperands; + for (const SourceSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) { + auto Type = NormalizeType(Predicate.Type); + if (!Type) + return false; + if (!TypeOperands.insert(Predicate.OperandIndex).second || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) { + fail(Predicate.Range, + "selection operand type predicates require distinct register operands"); + return false; + } + Normalized.OperandTypes.push_back({.OperandIndex = Predicate.OperandIndex, .Type = *Type}); + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> BankOperands; + for (const SourceSelectionOperandBankPredicate &Predicate : Rule.OperandBanks) { + const auto Bank = BankBySymbol.find(Predicate.BankSymbol); + if (!BankOperands.insert(Predicate.OperandIndex).second || Bank == BankBySymbol.end() || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) { + fail(Predicate.Range, + "selection operand bank predicates require distinct register operands and declared " + "banks"); + return false; + } + Normalized.OperandBanks.push_back( + {.OperandIndex = Predicate.OperandIndex, .Bank = Bank->second}); + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> ClassOperands; + for (const SourceSelectionOperandClassPredicate &Predicate : Rule.OperandClasses) { + const auto RegClass = ClassBySymbol.find(Predicate.ClassSymbol); + if (!ClassOperands.insert(Predicate.OperandIndex).second || RegClass == ClassBySymbol.end() || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) { + fail(Predicate.Range, + "selection operand class predicates require distinct register operands and declared " + "classes"); + return false; + } + Normalized.OperandClasses.push_back( + {.OperandIndex = Predicate.OperandIndex, .RegClass = RegClass->second}); + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> ImmediateOperands; + for (const SourceSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) { + if (!ImmediateOperands.insert(Predicate.OperandIndex).second) { + fail(Predicate.Range, "selection operand immediate predicates require distinct operands"); + return false; + } + auto Ranges = NormalizeRanges(Predicate.Ranges, Predicate.Range, + "selection operand immediate predicate"); + if (!Ranges) + return false; + Normalized.OperandImmediates.push_back({ + .OperandIndex = Predicate.OperandIndex, + .Ranges = std::move(*Ranges), + }); + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> ConstantOperands; + for (const SourceSelectionOperandDefConstantPredicate &Predicate : Rule.OperandDefConstants) { + if (!ConstantOperands.insert(Predicate.OperandIndex).second || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) { + fail(Predicate.Range, + "selection operand definition constant predicates require distinct register operands"); + return false; + } + auto Ranges = NormalizeRanges(Predicate.Ranges, Predicate.Range, + "selection operand definition constant predicate"); + if (!Ranges) + return false; + Normalized.OperandDefConstants.push_back({ + .OperandIndex = Predicate.OperandIndex, + .Ranges = std::move(*Ranges), + }); + ++Normalized.Specificity; + } + + std::unordered_map<std::string, std::uint32_t> CaptureIDs; + std::unordered_set<std::uint32_t> DefUseRootOperands; + for (const SourceSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates) { + const auto DefOpcode = resolveOpcode(Result, Predicate.OpcodeSymbol); + if (!DefOpcode || !isGenericOpcode(Opcode::fromValue(*DefOpcode)) || + !DefUseRootOperands.insert(Predicate.RootOperandIndex).second || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.RootOperandIndex))) { + fail(Predicate.Range, + "selection def-use predicates require distinct register operands and generic opcodes"); + return false; + } + + NormalizedSelectionDefUsePredicate NormalizedPredicate{ + .RootOperandIndex = Predicate.RootOperandIndex, + .Opcode = *DefOpcode, + .IntegerPredicate = std::nullopt, + .Captures = {}, + .OperandDefConstants = {}, + }; + if (Predicate.IntegerPredicate) { + const auto Integer = integerPredicateValue(Predicate.IntegerPredicate->Predicate); + if (!Integer) { + fail(Predicate.IntegerPredicate->Range, + "selection def-use integer predicate is not a recognized integer predicate"); + return false; + } + NormalizedPredicate.IntegerPredicate = { + .OperandIndex = Predicate.IntegerPredicate->OperandIndex, + .Predicate = *Integer, + }; + } + std::unordered_set<std::uint32_t> CapturedOperands; + for (const SourceSelectionDefUseCapture &Capture : Predicate.Captures) { + if (!isIdentifier(Capture.Symbol) || + !CapturedOperands.insert(Capture.OperandIndex).second || + !CaptureIDs.emplace(Capture.Symbol, CaptureIDs.size() + 1U).second) { + fail(Capture.Range, + "selection def-use captures require unique identifiers and distinct operands"); + return false; + } + NormalizedPredicate.Captures.push_back({ + .ID = CaptureIDs.at(Capture.Symbol), + .OperandIndex = Capture.OperandIndex, + }); + } + + std::unordered_set<std::uint32_t> ConstantOperandsInDef; + for (const SourceSelectionOperandDefConstantPredicate &Constant : + Predicate.OperandDefConstants) { + if (!ConstantOperandsInDef.insert(Constant.OperandIndex).second) { + fail(Constant.Range, "selection def-use constant predicates require distinct operands"); + return false; + } + auto Ranges = NormalizeRanges(Constant.Ranges, Constant.Range, + "selection def-use constant predicate"); + if (!Ranges) + return false; + NormalizedPredicate.OperandDefConstants.push_back({ + .OperandIndex = Constant.OperandIndex, + .Ranges = std::move(*Ranges), + }); + } + Normalized.DefUsePredicates.push_back(std::move(NormalizedPredicate)); + Normalized.Specificity += 1U + + static_cast<std::uint32_t>(Predicate.IntegerPredicate.has_value()) + + static_cast<std::uint32_t>(Predicate.Captures.size()) + + static_cast<std::uint32_t>(Predicate.OperandDefConstants.size()); + } + + std::unordered_map<std::string, std::uint32_t> TemporaryIDs; + for (const SourceSelectionTemporary &Temporary : Rule.Temporaries) { + if (!isIdentifier(Temporary.Symbol) || + !TemporaryIDs.emplace(Temporary.Symbol, TemporaryIDs.size() + 1U).second || + (Temporary.TypeFromOperand.has_value() == Temporary.Type.has_value())) { + fail(Temporary.Range, "selection temporary requires a unique identifier and one type"); + return false; + } + NormalizedSelectionTemporary NormalizedTemporary{ + .ID = TemporaryIDs.at(Temporary.Symbol), + .TypeFromOperand = std::nullopt, + .Type = std::nullopt, + }; + if (Temporary.TypeFromOperand) { + if (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, *Temporary.TypeFromOperand)) { + fail(Temporary.Range, + "selection temporary type source must be a matched register operand"); + return false; + } + NormalizedTemporary.TypeFromOperand = Temporary.TypeFromOperand; + } else { + auto Type = NormalizeType(*Temporary.Type); + if (!Type || Type->Kind != NormalizedSelectionTypeKind::Scalar) { + fail(Temporary.Range, "selection temporary explicit type must be a scalar"); + return false; + } + NormalizedTemporary.Type = *Type; + } + Normalized.Temporaries.push_back(NormalizedTemporary); + } + + bool HasRootAction = false; + for (const SourceSelectionAction &Action : Rule.Actions) { + const auto ResultOpcode = resolveOpcode(Result, Action.ResultOpcodeSymbol); + const NormalizedInstruction *ResultInstruction = + findInstructionBySymbol(Result, Action.ResultOpcodeSymbol); + const bool IsCorePseudo = + ResultOpcode && isCorePseudoOpcode(Opcode::fromValue(*ResultOpcode)); + if (!ResultOpcode || (ResultInstruction == nullptr && !IsCorePseudo) || + (ResultInstruction != nullptr && + (isGenericOpcode(Opcode::fromValue(*ResultOpcode)) || + std::ranges::contains(ResultInstruction->Flags, "Generic")))) { + fail(Action.Range, + "selection instruction opcode must name a declared target instruction or core pseudo"); + return false; + } + if (Action.ReplacesRoot && HasRootAction) { + fail(Action.Range, "selection instructions require exactly one root replacement"); + return false; + } + HasRootAction |= Action.ReplacesRoot; + if (ResultInstruction != nullptr && + ResultInstruction->Operands.size() != Action.OperandRenderers.size()) { + fail(Action.Range, "selection instruction has an incompatible operand count"); + return false; + } + + NormalizedSelectionAction NormalizedAction{ + .ResultOpcode = *ResultOpcode, + .ReplacesRoot = Action.ReplacesRoot, + .OperandRenderers = {}, + }; + NormalizedAction.OperandRenderers.reserve(Action.OperandRenderers.size()); + for (const SourceSelectionOperandRenderer &Renderer : Action.OperandRenderers) { + if (Renderer.ComplexOperandIndex) { + if (Renderer.ComplexRendererSymbol.empty() || !Renderer.Flags.empty() || + Renderer.ImmediateTransform != "identity") { + fail(Renderer.Range, + "selection complex operand renderer requires a matcher and no flags or transform"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::ComplexOperand, + .Value = *Renderer.ComplexOperandIndex, + .ComplexRenderer = Renderer.ComplexRendererSymbol, + .Flags = 0, + }); + continue; + } + + if (Renderer.OperandIndex) { + if (MatchInstruction != nullptr && + *Renderer.OperandIndex >= MatchInstruction->Operands.size()) { + fail(Renderer.Range, "selection operand renderer references an unknown operand"); + return false; + } + if (!Renderer.Flags.empty()) { + fail(Renderer.Range, "selection operand renderer cannot replace source register flags"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::Operand, + .Value = *Renderer.OperandIndex, + .ComplexRenderer = {}, + .Flags = 0, + }); + continue; + } + + if (!Renderer.PhysicalRegisterSymbol.empty()) { + const NormalizedRegister *Physical = + findRegisterBySymbol(Result, Renderer.PhysicalRegisterSymbol); + const auto Flags = registerOperandFlags(Renderer.Flags); + if (Physical == nullptr || Physical->ID == 0 || !Flags || + !validRegisterOperandFlags(*Flags)) { + fail(Renderer.Range, + "selection physical register renderer requires a declared register and valid " + "register flags"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::PhysicalRegister, + .Value = Physical->ID, + .ComplexRenderer = {}, + .Flags = Flags->rawValue(), + }); + continue; + } + + if (!Renderer.TemporarySymbol.empty()) { + const auto Temporary = TemporaryIDs.find(Renderer.TemporarySymbol); + const auto Flags = registerOperandFlags(Renderer.Flags); + if (Temporary == TemporaryIDs.end() || !Flags || !validRegisterOperandFlags(*Flags)) { + fail(Renderer.Range, + "selection temporary renderer requires a declared temporary and valid register " + "flags"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::Temporary, + .Value = Temporary->second, + .ComplexRenderer = {}, + .Flags = Flags->rawValue(), + }); + continue; + } + + if (!Renderer.CapturedOperandSymbol.empty()) { + const auto Capture = CaptureIDs.find(Renderer.CapturedOperandSymbol); + if (Capture == CaptureIDs.end() || !Renderer.Flags.empty() || + Renderer.ImmediateTransform != "identity") { + fail(Renderer.Range, + "selection captured operand renderer requires a matched capture and no flags"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::CapturedOperand, + .Value = Capture->second, + .ComplexRenderer = {}, + .Flags = 0, + }); + continue; + } + + if (Renderer.CapturedImmediateOperand) { + const auto Transform = + [&Renderer]() -> std::optional<NormalizedSelectionImmediateTransform> { + if (Renderer.ImmediateTransform == "identity") + return NormalizedSelectionImmediateTransform::Identity; + if (Renderer.ImmediateTransform == "negate") + return NormalizedSelectionImmediateTransform::Negate; + if (Renderer.ImmediateTransform == "plus_one") + return NormalizedSelectionImmediateTransform::PlusOne; + return std::nullopt; + }(); + if (!ConstantOperands.contains(*Renderer.CapturedImmediateOperand) || !Transform || + !Renderer.Flags.empty()) { + fail(Renderer.Range, + "selection captured immediate renderer requires a matched constant and known " + "transform"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::CapturedImmediate, + .Value = *Renderer.CapturedImmediateOperand, + .ComplexRenderer = {}, + .Flags = 0, + .Transform = *Transform, + }); + continue; + } + + if (Renderer.Immediate) { + if (!Renderer.Flags.empty() || Renderer.ImmediateTransform != "identity") { + fail(Renderer.Range, "selection immediate renderer cannot carry register flags"); + return false; + } + NormalizedAction.OperandRenderers.push_back({ + .SourceKind = NormalizedSelectionOperandRenderer::Kind::Immediate, + .Value = 0, + .ComplexRenderer = {}, + .Flags = 0, + .Immediate = *Renderer.Immediate, + }); + continue; + } + + fail(Renderer.Range, "selection operand renderer has no normalized source"); + return false; + } + Normalized.Actions.push_back(std::move(NormalizedAction)); + } + if (!HasRootAction) { + fail(Rule.Range, "selection instructions require exactly one root replacement"); + return false; + } + + std::unordered_set<std::uint32_t> ErasedOperands; + for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) { + if (!ErasedOperands.insert(OperandIndex).second || + (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, OperandIndex))) { + fail(Rule.Range, "selection erased definitions require distinct register operands"); + return false; + } + if (!ConstantOperands.contains(OperandIndex) && !DefUseRootOperands.contains(OperandIndex)) { + fail(Rule.Range, + "selection erased definition requires a matched constant or def-use operand"); + return false; + } + } + Result.SelectionRules.push_back(std::move(Normalized)); + } + + std::ranges::sort(Result.SelectionRules, + [](const NormalizedSelectionRule &Left, const NormalizedSelectionRule &Right) { + if (Left.Priority != Right.Priority) + return Left.Priority > Right.Priority; + if (Left.Specificity != Right.Specificity) + return Left.Specificity > Right.Specificity; + return Left.Symbol < Right.Symbol; + }); + return true; +} + +bool CodeGenTarget::normalizeCombinerRules(NormalizedTargetModel &Result) { + std::unordered_set<std::string> Symbols; + const auto NormalizeType = [this](const SourceSelectionTypePredicate &Source) + -> std::optional<NormalizedSelectionTypePredicate> { + NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any; + if (Source.Kind == "scalar") + Kind = NormalizedSelectionTypeKind::Scalar; + else if (Source.Kind == "pointer") + Kind = NormalizedSelectionTypeKind::Pointer; + else if (Source.Kind != "any") { + fail(Source.Range, "combiner type kind must be 'any', 'scalar', or 'pointer'"); + return std::nullopt; + } + if (Source.Bits > LowLevelType::MaxBitWidth || + (Kind != NormalizedSelectionTypeKind::Any && Source.Bits == 0)) { + fail(Source.Range, "combiner type bit width is invalid for its kind"); + return std::nullopt; + } + return NormalizedSelectionTypePredicate{.Kind = Kind, .Bits = Source.Bits}; + }; + const auto NormalizeRanges = + [this](const std::vector<SourceSelectionConstantRange> &Ranges, + TextRange Range) -> std::optional<std::vector<NormalizedSelectionConstantRange>> { + if (Ranges.empty()) { + fail(Range, "combiner def-use constant predicate requires at least one range"); + return std::nullopt; + } + std::vector<NormalizedSelectionConstantRange> ResultRanges; + ResultRanges.reserve(Ranges.size()); + for (const SourceSelectionConstantRange &Source : Ranges) { + if (Source.Minimum > Source.Maximum) { + fail(Source.SourceRange, "combiner def-use constant range is inverted"); + return std::nullopt; + } + ResultRanges.push_back({.Minimum = Source.Minimum, .Maximum = Source.Maximum}); + } + std::ranges::sort(ResultRanges, {}, &NormalizedSelectionConstantRange::Minimum); + for (std::size_t Index = 1; Index < ResultRanges.size(); ++Index) { + if (ResultRanges[Index].Minimum <= ResultRanges[Index - 1].Maximum) { + fail(Range, "combiner def-use constant ranges overlap"); + return std::nullopt; + } + } + return ResultRanges; + }; + const auto FlagValue = [](std::string_view Name) -> std::optional<std::uint32_t> { + static constexpr std::array<std::pair<std::string_view, MachineInstrFlag>, 3> Flags = {{ + {"nneg", MachineInstrFlag::NonNeg}, + {"nsw", MachineInstrFlag::NoSignedWrap}, + {"nuw", MachineInstrFlag::NoUnsignedWrap}, + }}; + const auto *const Position = + std::ranges::find(Flags, Name, &decltype(Flags)::value_type::first); + if (Position == Flags.end()) + return std::nullopt; + return std::uint32_t{1} << static_cast<std::uint8_t>(Position->second); + }; + const auto FlagMask = [&FlagValue](std::span<const std::string> Names, + TextRange Range) -> std::optional<std::uint32_t> { + std::uint32_t Mask = 0; + for (const std::string &Name : Names) { + const auto Value = FlagValue(Name); + if (!Value || (Mask & *Value) != 0) + return std::nullopt; + Mask |= *Value; + } + if (!Names.empty() && Mask == 0) + return std::nullopt; + (void)Range; + return Mask; + }; + + for (const SourceCombinerRule &Rule : Input.CombinerRules) { + const auto MatchOpcode = resolveOpcode(Result, Rule.MatchOpcodeSymbol); + const NormalizedInstruction *MatchInstruction = + findInstructionBySymbol(Result, Rule.MatchOpcodeSymbol); + if (!isIdentifier(Rule.Symbol) || !Symbols.insert(Rule.Symbol).second || !MatchOpcode) { + fail(Rule.Range, "combiner rules require a unique identifier and declared match opcode"); + return false; + } + if (MatchInstruction != nullptr && !std::ranges::contains(MatchInstruction->Flags, "Generic")) { + fail(Rule.Range, "combiner rule match opcode must be generic"); + return false; + } + + NormalizedCombinerRule Normalized; + Normalized.Symbol = Rule.Symbol; + Normalized.Priority = Rule.Priority; + Normalized.Specificity = 1; + Normalized.MatchOpcode = *MatchOpcode; + if (Rule.ResultType) { + auto Type = NormalizeType(*Rule.ResultType); + if (!Type) + return false; + if (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, 0)) { + fail(Rule.ResultType->Range, + "combiner result type predicate requires register operand zero"); + return false; + } + Normalized.ResultType = *Type; + ++Normalized.Specificity; + } + + std::unordered_set<std::uint32_t> TypeOperands; + for (const SourceSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) { + auto Type = NormalizeType(Predicate.Type); + if (!Type) + return false; + if (!TypeOperands.insert(Predicate.OperandIndex).second || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.OperandIndex))) { + fail(Predicate.Range, + "combiner operand type predicates require distinct register operands"); + return false; + } + Normalized.OperandTypes.push_back({.OperandIndex = Predicate.OperandIndex, .Type = *Type}); + ++Normalized.Specificity; + } + + std::unordered_set<std::string> TargetPredicates; + for (const SourceSelectionTargetPredicate &Predicate : Rule.TargetPredicates) { + if (!isIdentifier(Predicate.Symbol) || !TargetPredicates.insert(Predicate.Symbol).second) { + fail(Predicate.Range, "combiner target predicates require distinct identifiers"); + return false; + } + Normalized.TargetPredicates.push_back(Predicate.Symbol); + ++Normalized.Specificity; + } + + std::uint32_t InstructionFlags = 0; + for (const std::string &Name : Rule.InstructionFlags) { + const auto Value = FlagValue(Name); + if (!Value) { + fail(Rule.Range, "combiner instruction flag is not supported"); + return false; + } + if ((InstructionFlags & *Value) != 0) { + fail(Rule.Range, "combiner instruction flags must be unique"); + return false; + } + InstructionFlags |= *Value; + ++Normalized.Specificity; + } + if (InstructionFlags != 0) + Normalized.InstructionFlags.push_back(InstructionFlags); + + std::unordered_set<std::uint32_t> DefUseRoots; + std::unordered_set<std::uint32_t> CaptureIDs; + std::unordered_map<std::string, std::uint32_t> Captures; + std::uint32_t NextCaptureID = 1; + for (const SourceSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates) { + const auto DefOpcode = resolveOpcode(Result, Predicate.OpcodeSymbol); + const NormalizedInstruction *DefInstruction = + findInstructionBySymbol(Result, Predicate.OpcodeSymbol); + if (!DefOpcode || !DefUseRoots.insert(Predicate.RootOperandIndex).second || + (MatchInstruction != nullptr && + !isRegisterOperand(*MatchInstruction, Predicate.RootOperandIndex)) || + (DefInstruction != nullptr && !std::ranges::contains(DefInstruction->Flags, "Generic"))) { + fail(Predicate.Range, "combiner def-use predicate has an invalid root or opcode"); + return false; + } + NormalizedSelectionDefUsePredicate DefUse{ + .RootOperandIndex = Predicate.RootOperandIndex, + .Opcode = *DefOpcode, + .IntegerPredicate = std::nullopt, + .Captures = {}, + .OperandDefConstants = {}, + }; + for (const SourceSelectionDefUseCapture &Capture : Predicate.Captures) { + if (!isIdentifier(Capture.Symbol) || Captures.contains(Capture.Symbol) || + !CaptureIDs.insert(NextCaptureID).second || + (DefInstruction != nullptr && + !isRegisterOperand(*DefInstruction, Capture.OperandIndex))) { + fail(Capture.Range, "combiner captures require unique identifiers and register operands"); + return false; + } + Captures.emplace(Capture.Symbol, NextCaptureID); + DefUse.Captures.push_back({.ID = NextCaptureID, .OperandIndex = Capture.OperandIndex}); + ++NextCaptureID; + } + for (const SourceSelectionOperandDefConstantPredicate &PredicateConstant : + Predicate.OperandDefConstants) { + auto Ranges = NormalizeRanges(PredicateConstant.Ranges, PredicateConstant.Range); + if (!Ranges) + return false; + if (DefInstruction != nullptr && + (PredicateConstant.OperandIndex >= DefInstruction->Operands.size() || + (!std::ranges::contains(DefInstruction->Operands[PredicateConstant.OperandIndex].Kinds, + "Immediate") && + !std::ranges::contains(DefInstruction->Operands[PredicateConstant.OperandIndex].Kinds, + "IntegerImmediate")))) { + fail(PredicateConstant.Range, "combiner def-use constant operand is not immediate"); + return false; + } + DefUse.OperandDefConstants.push_back({ + .OperandIndex = PredicateConstant.OperandIndex, + .Ranges = std::move(*Ranges), + }); + } + Normalized.DefUsePredicates.push_back(std::move(DefUse)); + ++Normalized.Specificity; + } + if (Rule.DefUsePredicates.empty()) { + fail(Rule.Range, "combiner rule requires a def-use predicate"); + return false; + } + + NormalizedCombinerAction Action; + if (Rule.Action.Kind == SourceCombinerActionKind::ReplaceRootWithCapturedOperand) { + const auto Capture = Captures.find(Rule.Action.CapturedOperandSymbol); + if (Capture == Captures.end()) { + fail(Rule.Action.Range, "combiner replacement requires a matched capture"); + return false; + } + Action.Kind = NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand; + Action.CapturedOperand = Capture->second; + } else { + const auto ResultOpcode = resolveOpcode(Result, Rule.Action.ResultOpcodeSymbol); + const NormalizedInstruction *ResultInstruction = + findInstructionBySymbol(Result, Rule.Action.ResultOpcodeSymbol); + if (!ResultOpcode || (ResultInstruction != nullptr && + !std::ranges::contains(ResultInstruction->Flags, "Generic"))) { + fail(Rule.Action.Range, "combiner mutation requires a declared generic result opcode"); + return false; + } + Action.Kind = NormalizedCombinerActionKind::MutateRoot; + Action.ResultOpcode = *ResultOpcode; + for (const SourceCombinerOperandRenderer &Renderer : Rule.Action.OperandRenderers) { + NormalizedCombinerOperandRenderer NormalizedRenderer; + switch (Renderer.SourceKind) { + case SourceCombinerOperandRenderer::Kind::RootOperand: + NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::RootOperand; + NormalizedRenderer.Value = Renderer.OperandIndex; + break; + case SourceCombinerOperandRenderer::Kind::CapturedOperand: { + const auto Capture = Captures.find(Renderer.CaptureSymbol); + if (Capture == Captures.end()) { + fail(Renderer.Range, "combiner renderer requires a matched capture"); + return false; + } + NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::CapturedOperand; + NormalizedRenderer.Value = Capture->second; + break; + } + case SourceCombinerOperandRenderer::Kind::CapturedTypeBits: { + const auto Capture = Captures.find(Renderer.CaptureSymbol); + if (Capture == Captures.end()) { + fail(Renderer.Range, "combiner type-width renderer requires a matched capture"); + return false; + } + NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::CapturedTypeBits; + NormalizedRenderer.Value = Capture->second; + break; + } + case SourceCombinerOperandRenderer::Kind::Immediate: + NormalizedRenderer.SourceKind = NormalizedCombinerOperandRenderer::Kind::Immediate; + NormalizedRenderer.Immediate = Renderer.Immediate; + break; + } + Action.OperandRenderers.push_back(NormalizedRenderer); + } + if (Action.OperandRenderers.empty()) { + fail(Rule.Action.Range, "combiner mutation requires operand renderers"); + return false; + } + } + if (!Rule.Action.ClearInstructionFlags.empty()) { + const auto ClearFlags = FlagMask(Rule.Action.ClearInstructionFlags, Rule.Action.Range); + if (!ClearFlags) { + fail(Rule.Action.Range, "combiner action clear flags are invalid or duplicated"); + return false; + } + Action.ClearInstructionFlags = *ClearFlags; + } + Normalized.Action = std::move(Action); + std::unordered_set<std::uint32_t> Erased; + for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) { + if (!Erased.insert(OperandIndex).second || + (MatchInstruction != nullptr && !isRegisterOperand(*MatchInstruction, OperandIndex)) || + !DefUseRoots.contains(OperandIndex)) { + fail(Rule.Range, "combiner erased definitions must be distinct def-use operands"); + return false; + } + Normalized.EraseOperandDefinitions.push_back(OperandIndex); + } + Result.CombinerRules.push_back(std::move(Normalized)); + } + + std::ranges::sort(Result.CombinerRules, + [](const NormalizedCombinerRule &Left, const NormalizedCombinerRule &Right) { + if (Left.Priority != Right.Priority) + return Left.Priority > Right.Priority; + if (Left.Specificity != Right.Specificity) + return Left.Specificity > Right.Specificity; + return Left.Symbol < Right.Symbol; + }); + return true; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenTarget.cpp b/backend/utils/TableGen/Common/CodeGenTarget.cpp new file mode 100644 index 0000000..478457c --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenTarget.cpp @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Common/CodeGenTarget.h" + +#include "TableGenBackends.h" +#include "rcc/TableGen/Parser.h" + +#include <algorithm> +#include <bit> +#include <cstddef> +#include <ranges> +#include <string> +#include <string_view> +#include <unordered_set> +#include <utility> + +namespace rcc::targetgen { + +CodeGenTarget::CodeGenTarget(const DescriptionAST &Input, std::string SourceName) + : Input(Input), SourceName(std::move(SourceName)) {} + +[[nodiscard]] std::optional<NormalizedTargetModel> CodeGenTarget::normalize() { + if (!validateTarget()) + return std::nullopt; + + NormalizedTargetModel Result; + Result.Target = Input.Target; + Result.RegisterUnits = Input.RegisterUnits; + Result.MIROperandFlags = Input.MIROperandFlags; + if (!normalizeRegisterUnits() || !normalizeRegisters(Result) || !normalizeRegisterBanks(Result) || + !normalizeRegisterClasses(Result) || !normalizeRegisterMasks(Result) || + !normalizeMIROperandFlags(Result) || !normalizeCallingConventions(Result) || + !normalizeNamedOperands(Result) || !normalizeImmediatePredicates(Result) || + !normalizeSchedulingClasses(Result) || !normalizeEncodingCategories(Result) || + !normalizeAssemblyFormats(Result) || !normalizeInstructions(Result) || + !normalizeSelectionRules(Result) || !normalizeCombinerRules(Result) || + !normalizeMacroFusionRules(Result) || !normalizeMCPseudoExpansions(Result)) { + return std::nullopt; + } + std::ranges::sort(Result.RegisterUnits, {}, &SourceRegisterUnit::ID); + std::ranges::sort(Result.Registers, {}, &NormalizedRegister::ID); + std::ranges::sort(Result.RegisterBanks, {}, &NormalizedRegisterBank::ID); + std::ranges::sort(Result.RegisterClasses, {}, &NormalizedRegisterClass::ID); + std::ranges::sort(Result.CallingConventions, {}, &NormalizedCallingConvention::ID); + std::ranges::sort(Result.NamedOperands, {}, &NormalizedNamedOperand::ID); + std::ranges::sort(Result.ImmediatePredicates, {}, &NormalizedImmediatePredicate::ID); + std::ranges::sort(Result.SchedulingClasses, {}, &NormalizedSchedulingClass::ID); + std::ranges::sort(Result.EncodingCategories, {}, &NormalizedEncodingCategory::ID); + std::ranges::sort(Result.AssemblyFormats, {}, &NormalizedAssemblyFormat::ID); + return Result; +} + +[[nodiscard]] std::vector<TextDiagnostic> CodeGenTarget::takeDiagnostics() { + return std::move(Diagnostics); +} + +void CodeGenTarget::fail(TextRange Range, std::string Message) { + Diagnostics.push_back({ + .Severity = DiagnosticSeverity::Error, + .Category = "target-description", + .Message = std::move(Message), + .SourceName = SourceName, + .Range = Range, + }); +} + +[[nodiscard]] bool CodeGenTarget::validateTarget() { + if (!isIdentifier(Input.Target.Namespace)) { + fail(Input.Target.Range, "target namespace must be a C++ identifier"); + return false; + } + if (Input.Target.Name.empty() || Input.Target.Triple.empty() || Input.Target.CPU.empty() || + Input.Target.ABI.empty() || Input.Target.DataLayout.empty() || + Input.Target.Features.empty()) { + fail(Input.Target.Range, "target metadata fields and feature list must be nonempty"); + return false; + } + std::unordered_set<std::string> Features; + for (const std::string &Feature : Input.Target.Features) { + if (Feature.empty() || !Features.insert(Feature).second) { + fail(Input.Target.Range, "target feature names must be nonempty and unique"); + return false; + } + } + return true; +} + +[[nodiscard]] bool CodeGenTarget::normalizeCallingConventions(NormalizedTargetModel &Result) { + if (Input.CallingConventions.empty()) { + fail(Input.Target.Range, "target description requires at least one calling convention"); + return false; + } + std::unordered_set<std::uint32_t> IDs; + std::unordered_set<std::string> Symbols; + std::unordered_set<std::string> Names; + for (const SourceCallingConvention &Convention : Input.CallingConventions) { + const auto IntegerClass = ClassBySymbol.find(Convention.IntegerRegisterClassSymbol); + const auto FloatingClass = ClassBySymbol.find(Convention.FloatingRegisterClassSymbol); + const auto PreservedMask = std::ranges::find(Result.RegisterMasks, Convention.PreservedMaskName, + &NormalizedRegisterMask::Name); + if (Convention.ID == 0 || !isIdentifier(Convention.Symbol) || Convention.Name.empty() || + IntegerClass == ClassBySymbol.end() || FloatingClass == ClassBySymbol.end() || + PreservedMask == Result.RegisterMasks.end() || Convention.StackAlignmentBytes == 0 || + Convention.StackArgumentAlignmentBytes == 0 || + !std::has_single_bit(Convention.StackAlignmentBytes) || + !std::has_single_bit(Convention.StackArgumentAlignmentBytes) || + Convention.StackAlignmentBytes % Convention.StackArgumentAlignmentBytes != 0 || + Convention.ShadowSpaceBytes % Convention.StackArgumentAlignmentBytes != 0 || + !IDs.insert(Convention.ID).second || !Symbols.insert(Convention.Symbol).second || + !Names.insert(Convention.Name).second) { + fail(Convention.Range, + "calling conventions require unique IDs, symbols, names, declared register classes and " + "preserved masks, compatible power-of-two stack alignments, and aligned shadow space"); + return false; + } + const auto IntegerClassDescription = std::ranges::find( + Result.RegisterClasses, IntegerClass->second, &NormalizedRegisterClass::ID); + const auto FloatingClassDescription = std::ranges::find( + Result.RegisterClasses, FloatingClass->second, &NormalizedRegisterClass::ID); + if (IntegerClassDescription == Result.RegisterClasses.end() || + FloatingClassDescription == Result.RegisterClasses.end()) { + fail(Convention.Range, "calling convention register classes must resolve"); + return false; + } + const auto NormalizeRegisterList = [this, + &Convention](const std::vector<std::string> &Symbols, + const NormalizedRegisterClass &RegisterClass, + std::vector<std::uint32_t> &Output) { + std::unordered_set<std::uint32_t> Registers; + for (const std::string &Symbol : Symbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || + !std::ranges::contains(RegisterClass.Registers, Register->second) || + !Registers.insert(Register->second).second) { + fail(Convention.Range, + "calling convention register lists must contain distinct members of their class"); + return false; + } + Output.push_back(Register->second); + } + return true; + }; + NormalizedCallingConvention Normalized{ + .ID = Convention.ID, + .Symbol = Convention.Symbol, + .Name = Convention.Name, + .IntegerRegisterClass = IntegerClass->second, + .FloatingRegisterClass = FloatingClass->second, + .IntegerArgumentRegisters = {}, + .FloatingArgumentRegisters = {}, + .IntegerReturnRegisters = {}, + .FloatingReturnRegisters = {}, + .PreservedMaskName = Convention.PreservedMaskName, + .StackAlignmentBytes = Convention.StackAlignmentBytes, + .StackArgumentAlignmentBytes = Convention.StackArgumentAlignmentBytes, + .ShadowSpaceBytes = Convention.ShadowSpaceBytes, + .SupportsVarArgs = Convention.SupportsVarArgs, + .SupportsTailCalls = Convention.SupportsTailCalls, + }; + if (!NormalizeRegisterList(Convention.IntegerArgumentRegisterSymbols, *IntegerClassDescription, + Normalized.IntegerArgumentRegisters) || + !NormalizeRegisterList(Convention.FloatingArgumentRegisterSymbols, + *FloatingClassDescription, Normalized.FloatingArgumentRegisters) || + !NormalizeRegisterList(Convention.IntegerReturnRegisterSymbols, *IntegerClassDescription, + Normalized.IntegerReturnRegisters) || + !NormalizeRegisterList(Convention.FloatingReturnRegisterSymbols, *FloatingClassDescription, + Normalized.FloatingReturnRegisters)) { + return false; + } + CallingConventionBySymbol.emplace(Convention.Symbol, Convention.ID); + Result.CallingConventions.push_back(std::move(Normalized)); + } + return true; +} + +[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.Registers, ID, &NormalizedRegister::ID); + return *Position; +} +[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.RegisterUnits, ID, &SourceRegisterUnit::ID); + return *Position; +} +[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.RegisterBanks, ID, &NormalizedRegisterBank::ID); + return *Position; +} + +DescriptionGenerationResult generateDescription(const std::string &SourceName, + std::string SourceText) { + DescriptionParseResult ParseResult = parseDescription(SourceName, std::move(SourceText)); + if (!ParseResult.Records) + return {.Output = std::nullopt, .Diagnostics = std::move(ParseResult.Diagnostics)}; + + CodeGenTarget Target(*ParseResult.Records, SourceName); + auto Model = Target.normalize(); + if (!Model) + return {.Output = std::nullopt, .Diagnostics = Target.takeDiagnostics()}; + + const std::size_t Separator = SourceName.find_last_of("/\\"); + const std::string_view DisplayName = Separator == std::string::npos + ? std::string_view(SourceName) + : std::string_view(SourceName).substr(Separator + 1); + return { + .Output = + GeneratedDescription{ + .RegisterHeader = emitRegisterHeader(*Model, DisplayName), + .InstructionHeader = emitInstructionHeader(*Model, DisplayName), + .TargetTables = emitRegisterInfo(*Model, DisplayName) + emitInstructionInfo(*Model) + + emitSubtarget(*Model) + emitCodeEmitter(*Model) + + emitAssemblyWriter(*Model) + emitSearchableTables(*Model) + + emitCallingConventions(*Model), + .GlobalISel = emitGlobalISel(*Model, DisplayName), + .GlobalISelCombiner = emitGlobalISelCombiner(*Model, DisplayName), + .MacroFusion = emitMacroFusion(*Model, DisplayName), + .MCPseudoLowering = emitMCPseudoLowering(*Model, DisplayName), + }, + .Diagnostics = {}, + }; +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/Common/CodeGenTarget.h b/backend/utils/TableGen/Common/CodeGenTarget.h new file mode 100644 index 0000000..a2aed7c --- /dev/null +++ b/backend/utils/TableGen/Common/CodeGenTarget.h @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// The private header follows the source-tree ownership of LLVM TableGen Common. +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H +#define RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H + +#include "rcc/TableGen/Record.h" + +#include <cstdint> +#include <optional> +#include <string> +#include <string_view> +#include <unordered_map> +#include <vector> + +namespace rcc::targetgen { + +struct NormalizedRegisterUnitLane final { + std::uint32_t UnitID = 0; + std::uint64_t Lanes = 0; +}; + +struct NormalizedRegister final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t Encoding = 0; + std::uint32_t DwarfRegister = 0; + std::vector<NormalizedRegisterUnitLane> Units; + bool IsConstant = false; + bool IsArtificial = false; + bool IsReserved = false; + std::vector<std::string> AlternativeNames; +}; + +struct NormalizedRegisterBank final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t MaximumSizeInBits = 0; + std::vector<std::uint32_t> RegisterClasses; +}; + +struct NormalizedRegisterClass final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t BankID = 0; + std::uint32_t SizeInBits = 0; + std::uint32_t SpillSizeInBits = 0; + std::uint32_t SpillAlignmentInBits = 0; + std::uint64_t Lanes = 0; + std::vector<std::uint32_t> Registers; + std::vector<std::uint32_t> AllocationOrder; + std::vector<std::uint32_t> SuperClasses; + bool Allocatable = true; + std::uint32_t CopyCost = 1; +}; + +struct NormalizedRegisterMask final { + std::string Name; + std::vector<std::uint32_t> PreservedRegisters; +}; + +struct NormalizedCallingConvention final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t IntegerRegisterClass = 0; + std::uint32_t FloatingRegisterClass = 0; + std::vector<std::uint32_t> IntegerArgumentRegisters; + std::vector<std::uint32_t> FloatingArgumentRegisters; + std::vector<std::uint32_t> IntegerReturnRegisters; + std::vector<std::uint32_t> FloatingReturnRegisters; + std::string PreservedMaskName; + std::uint32_t StackAlignmentBytes = 0; + std::uint32_t StackArgumentAlignmentBytes = 0; + std::uint32_t ShadowSpaceBytes = 0; + bool SupportsVarArgs = false; + bool SupportsTailCalls = false; +}; + +struct NormalizedNamedOperand final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; +}; + +struct NormalizedImmediatePredicate final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + bool IsSigned = false; + std::uint32_t Bits = 0; + std::uint64_t Alignment = 1; + bool NonZero = false; + std::vector<std::int64_t> AllowedValues; +}; + +struct NormalizedSchedulingClass final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t MicroOps = 0; + std::uint32_t Latency = 0; +}; + +enum class NormalizedMacroFusionInstruction : std::uint8_t { + First, + Second, +}; + +struct NormalizedMacroFusionOpcodePair final { + std::uint32_t FirstOpcode = 0; + std::uint32_t SecondOpcode = 0; +}; + +struct NormalizedMacroFusionImmediatePredicate final { + NormalizedMacroFusionInstruction Instruction = NormalizedMacroFusionInstruction::First; + std::uint32_t OperandIndex = 0; + std::int64_t Minimum = 0; + std::int64_t Maximum = 0; +}; + +struct NormalizedMacroFusionRegisterInequality final { + NormalizedMacroFusionInstruction Instruction = NormalizedMacroFusionInstruction::First; + std::uint32_t LeftOperand = 0; + std::uint32_t RightOperand = 0; +}; + +struct NormalizedMacroFusionImmediateRelation final { + std::uint32_t FirstOperand = 0; + std::uint32_t SecondOperand = 0; +}; + +struct NormalizedMacroFusionRule final { + std::string Symbol; + std::string Feature; + std::vector<std::uint32_t> FirstOpcodes; + std::vector<std::uint32_t> SecondOpcodes; + std::vector<NormalizedMacroFusionOpcodePair> OpcodePairs; + std::vector<NormalizedMacroFusionImmediatePredicate> ImmediatePredicates; + std::vector<NormalizedMacroFusionRegisterInequality> RegisterInequalities; + std::vector<NormalizedMacroFusionImmediateRelation> ImmediateRelations; + std::uint32_t FirstDefinitionOperand = 0; + std::uint32_t SecondUseOperand = 0; + std::uint32_t SecondDefinitionOperand = 0; + std::optional<std::uint32_t> CommutableSecondUseOperand; + bool RequireSingleNonDebugUse = false; + bool RequireSecondDestinationTie = false; +}; + +enum class NormalizedMCPseudoOperandKind : std::uint8_t { + SourceOperand, + Register, + Immediate, +}; + +struct NormalizedMCPseudoOperand final { + NormalizedMCPseudoOperandKind Kind = NormalizedMCPseudoOperandKind::SourceOperand; + std::uint32_t Value = 0; + std::int64_t Immediate = 0; +}; + +struct NormalizedMCPseudoExpansion final { + std::uint32_t PseudoOpcode = 0; + std::uint32_t ResultOpcode = 0; + std::vector<NormalizedMCPseudoOperand> Operands; +}; + +struct NormalizedEncodingCategory final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t SizeInBytes = 0; +}; + +struct NormalizedAssemblyFormat final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; +}; + +struct NormalizedOperand final { + std::vector<std::string> Kinds; + std::string RegisterRole; + std::optional<std::uint32_t> RegisterClass; + std::optional<std::uint32_t> RegisterBank; + std::optional<std::uint32_t> ImmediatePredicate; + std::string MemoryRole; + std::optional<std::uint32_t> DefaultTiedOperand; + bool EarlyClobber = false; + bool Optional = false; + bool VariadicGroup = false; + bool AllowSubReg = false; + std::optional<std::uint32_t> Name; + std::optional<std::uint16_t> TypeIndex; + std::uint32_t AllowedTargetFlags = 0; +}; + +struct NormalizedImplicitRegister final { + std::uint32_t Register = 0; + bool EarlyClobber = false; + bool Renamable = false; +}; + +struct NormalizedInstruction final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::string AssemblyMnemonic; + std::uint32_t BinaryEncoding = 0; + std::vector<NormalizedOperand> Operands; + std::uint32_t NumExplicitDefs = 0; + bool Variadic = false; + std::vector<NormalizedImplicitRegister> ImplicitDefs; + std::vector<NormalizedImplicitRegister> ImplicitUses; + bool AllowExtraImplicitOperands = false; + std::vector<std::string> Flags; + std::uint32_t SchedulingClass = 0; + std::uint32_t EncodingCategory = 0; + std::uint32_t SizeInBytes = 0; + std::uint32_t AssemblyFormat = 0; + bool RequiresMemoryOperands = false; + std::string Owner; + std::string Deadline; +}; + +enum class NormalizedSelectionTypeKind : std::uint8_t { + Any, + Scalar, + Pointer, +}; + +struct NormalizedSelectionTypePredicate final { + NormalizedSelectionTypeKind Kind = NormalizedSelectionTypeKind::Any; + std::uint32_t Bits = 0; +}; + +struct NormalizedSelectionOperandTypePredicate final { + std::uint32_t OperandIndex = 0; + NormalizedSelectionTypePredicate Type; +}; + +struct NormalizedSelectionOperandBankPredicate final { + std::uint32_t OperandIndex = 0; + std::uint32_t Bank = 0; +}; + +struct NormalizedSelectionOperandClassPredicate final { + std::uint32_t OperandIndex = 0; + std::uint32_t RegClass = 0; +}; + +struct NormalizedSelectionIntegerPredicate final { + std::uint32_t OperandIndex = 0; + std::uint32_t Predicate = 0; +}; + +struct NormalizedSelectionFloatingPredicate final { + std::uint32_t OperandIndex = 0; + std::uint32_t Predicate = 0; +}; + +struct NormalizedSelectionMemoryPredicate final { + std::uint32_t MemoryOperandIndex = 0; + std::uint32_t Bytes = 0; +}; + +struct NormalizedSelectionConstantRange final { + std::int64_t Minimum = 0; + std::int64_t Maximum = 0; +}; + +struct NormalizedSelectionOperandImmediatePredicate final { + std::uint32_t OperandIndex = 0; + std::vector<NormalizedSelectionConstantRange> Ranges; +}; + +struct NormalizedSelectionOperandDefConstantPredicate final { + std::uint32_t OperandIndex = 0; + std::vector<NormalizedSelectionConstantRange> Ranges; +}; + +struct NormalizedSelectionDefUseCapture final { + std::uint32_t ID = 0; + std::uint32_t OperandIndex = 0; +}; + +struct NormalizedSelectionDefUsePredicate final { + std::uint32_t RootOperandIndex = 0; + std::uint32_t Opcode = 0; + std::optional<NormalizedSelectionIntegerPredicate> IntegerPredicate; + std::vector<NormalizedSelectionDefUseCapture> Captures; + std::vector<NormalizedSelectionOperandDefConstantPredicate> OperandDefConstants; +}; + +struct NormalizedSelectionTemporary final { + std::uint32_t ID = 0; + std::optional<std::uint32_t> TypeFromOperand; + std::optional<NormalizedSelectionTypePredicate> Type; +}; + +enum class NormalizedSelectionImmediateTransform : std::uint8_t { + Identity, + Negate, + PlusOne, +}; + +struct NormalizedSelectionOperandRenderer final { + enum class Kind : std::uint8_t { + Operand, + ComplexOperand, + PhysicalRegister, + Temporary, + CapturedOperand, + CapturedImmediate, + Immediate, + }; + + Kind SourceKind = Kind::Operand; + std::uint32_t Value = 0; + std::string ComplexRenderer; + std::uint16_t Flags = 0; + NormalizedSelectionImmediateTransform Transform = NormalizedSelectionImmediateTransform::Identity; + std::int64_t Immediate = 0; +}; + +struct NormalizedSelectionAction final { + std::uint32_t ResultOpcode = 0; + bool ReplacesRoot = false; + std::vector<NormalizedSelectionOperandRenderer> OperandRenderers; +}; + +struct NormalizedSelectionRule final { + std::string Symbol; + std::uint32_t Priority = 0; + std::uint32_t Specificity = 0; + std::uint32_t MatchOpcode = 0; + std::optional<NormalizedSelectionTypePredicate> ResultType; + std::optional<NormalizedSelectionIntegerPredicate> IntegerPredicate; + std::optional<NormalizedSelectionFloatingPredicate> FloatingPredicate; + std::optional<NormalizedSelectionMemoryPredicate> MemoryPredicate; + std::vector<std::string> TargetPredicates; + std::vector<NormalizedSelectionOperandTypePredicate> OperandTypes; + std::vector<NormalizedSelectionOperandBankPredicate> OperandBanks; + std::vector<NormalizedSelectionOperandClassPredicate> OperandClasses; + std::vector<NormalizedSelectionOperandImmediatePredicate> OperandImmediates; + std::vector<NormalizedSelectionOperandDefConstantPredicate> OperandDefConstants; + std::vector<NormalizedSelectionDefUsePredicate> DefUsePredicates; + std::vector<NormalizedSelectionTemporary> Temporaries; + std::vector<NormalizedSelectionAction> Actions; + std::vector<std::uint32_t> EraseOperandDefinitions; +}; + +enum class NormalizedCombinerActionKind : std::uint8_t { + ReplaceRootWithCapturedOperand, + MutateRoot, +}; + +struct NormalizedCombinerOperandRenderer final { + enum class Kind : std::uint8_t { + RootOperand, + CapturedOperand, + CapturedTypeBits, + Immediate, + }; + + Kind SourceKind = Kind::RootOperand; + std::uint32_t Value = 0; + std::int64_t Immediate = 0; +}; + +struct NormalizedCombinerAction final { + NormalizedCombinerActionKind Kind = NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand; + std::uint32_t CapturedOperand = 0; + std::uint32_t ResultOpcode = 0; + std::vector<NormalizedCombinerOperandRenderer> OperandRenderers; + std::uint32_t ClearInstructionFlags = 0; +}; + +struct NormalizedCombinerRule final { + std::string Symbol; + std::uint32_t Priority = 0; + std::uint32_t Specificity = 0; + std::uint32_t MatchOpcode = 0; + std::optional<NormalizedSelectionTypePredicate> ResultType; + std::vector<NormalizedSelectionOperandTypePredicate> OperandTypes; + std::vector<std::string> TargetPredicates; + std::vector<std::uint32_t> InstructionFlags; + std::vector<NormalizedSelectionDefUsePredicate> DefUsePredicates; + NormalizedCombinerAction Action; + std::vector<std::uint32_t> EraseOperandDefinitions; +}; + +struct NormalizedTargetModel final { + SourceTarget Target; + std::vector<SourceRegisterUnit> RegisterUnits; + std::vector<NormalizedRegister> Registers; + std::vector<NormalizedRegisterBank> RegisterBanks; + std::vector<NormalizedRegisterClass> RegisterClasses; + std::vector<NormalizedRegisterMask> RegisterMasks; + SourceMIROperandFlags MIROperandFlags; + std::vector<NormalizedCallingConvention> CallingConventions; + std::vector<NormalizedNamedOperand> NamedOperands; + std::vector<NormalizedImmediatePredicate> ImmediatePredicates; + std::vector<NormalizedSchedulingClass> SchedulingClasses; + std::vector<NormalizedEncodingCategory> EncodingCategories; + std::vector<NormalizedAssemblyFormat> AssemblyFormats; + std::vector<NormalizedInstruction> Instructions; + std::vector<NormalizedSelectionRule> SelectionRules; + std::vector<NormalizedCombinerRule> CombinerRules; + std::vector<NormalizedMacroFusionRule> MacroFusionRules; + std::vector<NormalizedMCPseudoExpansion> MCPseudoExpansions; +}; + +[[nodiscard]] std::optional<std::string_view> +operandKindEnumerator(std::string_view Value) noexcept; +[[nodiscard]] std::optional<std::string_view> +registerRoleEnumerator(std::string_view Value) noexcept; +[[nodiscard]] std::optional<std::string_view> memoryRoleEnumerator(std::string_view Value) noexcept; +[[nodiscard]] std::optional<std::string_view> +instructionFlagEnumerator(std::string_view Value) noexcept; +[[nodiscard]] std::optional<std::string_view> +pseudoOwnerEnumerator(std::string_view Value) noexcept; +[[nodiscard]] std::optional<std::string_view> +pipelineStageEnumerator(std::string_view Value) noexcept; + +class CodeGenTarget final { +public: + CodeGenTarget(const DescriptionAST &Input, std::string SourceName); + [[nodiscard]] std::optional<NormalizedTargetModel> normalize(); + [[nodiscard]] std::vector<TextDiagnostic> takeDiagnostics(); + +private: + void fail(TextRange Range, std::string Message); + [[nodiscard]] bool validateTarget(); + [[nodiscard]] bool normalizeRegisterUnits(); + [[nodiscard]] bool normalizeRegisters(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeRegisterBanks(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeRegisterClasses(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeRegisterMasks(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeMIROperandFlags(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeCallingConventions(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeNamedOperands(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeImmediatePredicates(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeSchedulingClasses(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeEncodingCategories(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeAssemblyFormats(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeOperand(const SourceOperand &InputOperand, NormalizedOperand &Result); + [[nodiscard]] bool + normalizeImplicitRegisters(const std::vector<SourceImplicitRegister> &InputRegisters, bool IsDef, + std::vector<NormalizedImplicitRegister> &Result); + [[nodiscard]] bool normalizeInstructions(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeSelectionRules(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeCombinerRules(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeMacroFusionRules(NormalizedTargetModel &Result); + [[nodiscard]] bool normalizeMCPseudoExpansions(NormalizedTargetModel &Result); + + const DescriptionAST &Input; + std::string SourceName; + std::unordered_map<std::string, std::uint32_t> UnitBySymbol; + std::unordered_map<std::string, std::uint32_t> RegisterBySymbol; + std::unordered_map<std::string, std::uint32_t> BankBySymbol; + std::unordered_map<std::string, std::uint32_t> ClassBySymbol; + std::unordered_map<std::string, std::uint32_t> NamedOperandBySymbol; + std::unordered_map<std::string, std::uint32_t> ImmediatePredicateBySymbol; + std::unordered_map<std::string, std::uint32_t> SchedulingClassBySymbol; + std::unordered_map<std::string, std::uint32_t> EncodingCategoryBySymbol; + std::unordered_map<std::string, std::uint32_t> AssemblyFormatBySymbol; + std::unordered_map<std::string, std::uint32_t> CallingConventionBySymbol; + std::vector<TextDiagnostic> Diagnostics; +}; + +[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model, + std::uint32_t ID); +[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model, + std::uint32_t ID); +[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model, + std::uint32_t ID); + +} // namespace rcc::targetgen + +#endif // RCC_UTILS_TABLEGEN_COMMON_CODEGENTARGET_H diff --git a/backend/utils/TableGen/GlobalISelCombinerEmitter.cpp b/backend/utils/TableGen/GlobalISelCombinerEmitter.cpp new file mode 100644 index 0000000..0c727e3 --- /dev/null +++ b/backend/utils/TableGen/GlobalISelCombinerEmitter.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstddef> +#include <cstdint> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { +namespace { + +void emitInstruction(std::ostream &Output, std::string_view Opcode, std::uint32_t OperandIndex = 0, + std::uint32_t Value = 0, std::uint32_t Auxiliary = 0, + std::int64_t Immediate = 0, std::string_view Symbol = {}) { + Output << " {GIMatchTableOpcode::" << Opcode << ", " << OperandIndex << ", " << Value << ", " + << Auxiliary << ", " << Immediate << ", "; + appendQuoted(Output, Symbol); + Output << "},\n"; +} + +[[nodiscard]] std::size_t instructionCount(const NormalizedCombinerRule &Rule) noexcept { + std::size_t Count = 2U + (Rule.ResultType ? 1U : 0U) + Rule.OperandTypes.size() + + Rule.TargetPredicates.size() + Rule.InstructionFlags.size() + + (Rule.Action.ClearInstructionFlags != 0 ? 1U : 0U) + + Rule.EraseOperandDefinitions.size(); + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) + Count += 1U + DefUse.Captures.size() + DefUse.OperandDefConstants.size(); + Count += Rule.Action.Kind == NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand + ? 1U + : 1U + Rule.Action.OperandRenderers.size(); + return Count; +} + +} // namespace + +std::string emitGlobalISelCombiner(const NormalizedTargetModel &Model, + std::string_view SourceName) { + std::size_t InstructionCount = 0; + std::size_t ConstantRangeCount = 0; + for (const NormalizedCombinerRule &Rule : Model.CombinerRules) { + InstructionCount += instructionCount(Rule); + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) + ConstantRangeCount += Predicate.Ranges.size(); + } + + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifdef GET_GLOBALISEL_COMBINER_TABLE\n\n"; + Output << "constexpr std::array<GIMatchTableConstantRange, " << ConstantRangeCount + << "> GeneratedGlobalISelCombinerConstantRanges = {{\n"; + for (const NormalizedCombinerRule &Rule : Model.CombinerRules) { + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) { + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) { + for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges) + Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n"; + } + } + } + Output << "}};\n\n"; + + Output << "constexpr std::array<GIMatchTableInstruction, " << InstructionCount + << "> GeneratedGlobalISelCombinerInstructions = {{\n"; + std::size_t FirstConstantRange = 0; + for (const NormalizedCombinerRule &Rule : Model.CombinerRules) { + emitInstruction(Output, "CheckOpcode", 0, Rule.MatchOpcode); + if (Rule.ResultType) { + const char *Kind = "Any"; + if (Rule.ResultType->Kind == NormalizedSelectionTypeKind::Scalar) + Kind = "Scalar"; + else if (Rule.ResultType->Kind == NormalizedSelectionTypeKind::Pointer) + Kind = "Pointer"; + emitInstruction(Output, "CheckOperandType", 0, Rule.ResultType->Bits, + static_cast<std::uint32_t>(Rule.ResultType->Kind)); + (void)Kind; + } + for (const NormalizedSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) { + emitInstruction(Output, "CheckOperandType", Predicate.OperandIndex, Predicate.Type.Bits, + static_cast<std::uint32_t>(Predicate.Type.Kind)); + } + for (const std::string &Predicate : Rule.TargetPredicates) + emitInstruction(Output, "CheckTargetPredicate", 0, 0, 0, 0, Predicate); + for (const std::uint32_t Flags : Rule.InstructionFlags) + emitInstruction(Output, "CheckInstructionFlag", 0, Flags); + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) { + emitInstruction(Output, "CheckOperandDefOpcode", DefUse.RootOperandIndex, DefUse.Opcode); + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) { + emitInstruction(Output, "CheckOperandDefOperandConstantRanges", DefUse.RootOperandIndex, + static_cast<std::uint32_t>(FirstConstantRange), Predicate.OperandIndex, + static_cast<std::int64_t>(Predicate.Ranges.size())); + FirstConstantRange += Predicate.Ranges.size(); + } + for (const NormalizedSelectionDefUseCapture &Capture : DefUse.Captures) + emitInstruction(Output, "CaptureDefOperand", DefUse.RootOperandIndex, Capture.ID, + Capture.OperandIndex); + } + if (Rule.Action.Kind == NormalizedCombinerActionKind::ReplaceRootWithCapturedOperand) { + emitInstruction(Output, "ReplaceRootWithCapturedOperand", 0, Rule.Action.CapturedOperand); + } else { + emitInstruction(Output, "MutateOpcode", 0, Rule.Action.ResultOpcode); + for (const NormalizedCombinerOperandRenderer &Renderer : Rule.Action.OperandRenderers) { + switch (Renderer.SourceKind) { + case NormalizedCombinerOperandRenderer::Kind::RootOperand: + emitInstruction(Output, "RenderOperand", Renderer.Value); + break; + case NormalizedCombinerOperandRenderer::Kind::CapturedOperand: + emitInstruction(Output, "RenderCapturedOperand", 0, Renderer.Value); + break; + case NormalizedCombinerOperandRenderer::Kind::CapturedTypeBits: + emitInstruction(Output, "RenderCapturedTypeBits", 0, Renderer.Value); + break; + case NormalizedCombinerOperandRenderer::Kind::Immediate: + emitInstruction(Output, "RenderImmediate", 0, 0, 0, Renderer.Immediate); + break; + } + } + } + if (Rule.Action.ClearInstructionFlags != 0) + emitInstruction(Output, "ClearInstructionFlags", 0, Rule.Action.ClearInstructionFlags); + for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) + emitInstruction(Output, "EraseOperandDefinition", OperandIndex); + emitInstruction(Output, "Complete"); + } + Output << "}};\n\n"; + + Output << "constexpr std::array<GIMatchTableRule, " << Model.CombinerRules.size() + << "> GeneratedGlobalISelCombinerRules = {{\n"; + std::size_t FirstInstruction = 0; + for (const NormalizedCombinerRule &Rule : Model.CombinerRules) { + const std::size_t Count = instructionCount(Rule); + Output << " {"; + appendQuoted(Output, Rule.Symbol); + Output << ", " << Rule.Priority << ", " << FirstInstruction << ", " << Count << "},\n"; + FirstInstruction += Count; + } + Output << "}};\n\n"; + Output + << "[[nodiscard]] constexpr GIMatchTable generatedGlobalISelCombinerMatchTable() noexcept {\n" + " return {GeneratedGlobalISelCombinerInstructions, GeneratedGlobalISelCombinerRules,\n" + " GeneratedGlobalISelCombinerConstantRanges, GIMatchTableKind::Combiner};\n" + "}\n\n"; + Output << "#endif // GET_GLOBALISEL_COMBINER_TABLE\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/GlobalISelEmitter.cpp b/backend/utils/TableGen/GlobalISelEmitter.cpp new file mode 100644 index 0000000..3ce9411 --- /dev/null +++ b/backend/utils/TableGen/GlobalISelEmitter.cpp @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstddef> +#include <cstdint> +#include <ostream> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { +namespace { + +[[nodiscard]] std::string_view typeKindName(NormalizedSelectionTypeKind Kind) noexcept { + switch (Kind) { + case NormalizedSelectionTypeKind::Any: + return "Any"; + case NormalizedSelectionTypeKind::Scalar: + return "Scalar"; + case NormalizedSelectionTypeKind::Pointer: + return "Pointer"; + } + return "Any"; +} + +void emitInstruction(std::ostream &Output, std::string_view Opcode, std::uint32_t OperandIndex, + std::uint32_t Value = 0, std::string_view Auxiliary = "0", + std::int64_t Immediate = 0, std::string_view Symbol = {}) { + Output << " {GIMatchTableOpcode::" << Opcode << ", " << OperandIndex << ", " << Value << ", " + << Auxiliary << ", " << Immediate << ", "; + appendQuoted(Output, Symbol); + Output << "},\n"; +} + +void emitTypePredicate(std::ostream &Output, std::uint32_t OperandIndex, + const NormalizedSelectionTypePredicate &Predicate) { + emitInstruction(Output, "CheckOperandType", OperandIndex, Predicate.Bits, + "static_cast<std::uint32_t>(GIMatchTypeKind::" + + std::string(typeKindName(Predicate.Kind)) + ")"); +} + +[[nodiscard]] std::size_t instructionCount(const NormalizedSelectionRule &Rule) noexcept { + std::size_t Count = 2U + (Rule.ResultType ? 1U : 0U) + (Rule.IntegerPredicate ? 1U : 0U) + + (Rule.FloatingPredicate ? 1U : 0U) + (Rule.MemoryPredicate ? 1U : 0U) + + Rule.TargetPredicates.size() + Rule.OperandTypes.size() + + Rule.OperandBanks.size() + Rule.OperandClasses.size() + + Rule.OperandImmediates.size() + Rule.OperandDefConstants.size() + + Rule.Temporaries.size() + Rule.EraseOperandDefinitions.size(); + for (const NormalizedSelectionDefUsePredicate &Predicate : Rule.DefUsePredicates) + Count += 1U + static_cast<std::size_t>(Predicate.IntegerPredicate.has_value()) + + Predicate.Captures.size() + Predicate.OperandDefConstants.size(); + for (const NormalizedSelectionAction &Action : Rule.Actions) + Count += 1U + Action.OperandRenderers.size(); + return Count; +} + +} // namespace + +std::string emitGlobalISel(const NormalizedTargetModel &Model, std::string_view SourceName) { + std::size_t InstructionCount = 0; + std::size_t ConstantRangeCount = 0; + for (const NormalizedSelectionRule &Rule : Model.SelectionRules) + InstructionCount += instructionCount(Rule); + for (const NormalizedSelectionRule &Rule : Model.SelectionRules) { + for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) { + ConstantRangeCount += Predicate.Ranges.size(); + } + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + Rule.OperandDefConstants) { + ConstantRangeCount += Predicate.Ranges.size(); + } + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) { + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) { + ConstantRangeCount += Predicate.Ranges.size(); + } + } + } + + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifdef GET_GLOBALISEL_MATCH_TABLE\n\n"; + Output << "constexpr std::array<GIMatchTableConstantRange, " << ConstantRangeCount + << "> GeneratedGlobalISelConstantRanges = {{\n"; + for (const NormalizedSelectionRule &Rule : Model.SelectionRules) { + for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) { + for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges) + Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n"; + } + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + Rule.OperandDefConstants) { + for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges) + Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n"; + } + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) { + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) { + for (const NormalizedSelectionConstantRange &Range : Predicate.Ranges) + Output << " {" << Range.Minimum << ", " << Range.Maximum << "},\n"; + } + } + } + Output << "}};\n\n"; + + Output << "constexpr std::array<GIMatchTableInstruction, " << InstructionCount + << "> GeneratedGlobalISelInstructions = {{\n"; + std::size_t FirstConstantRange = 0; + for (const NormalizedSelectionRule &Rule : Model.SelectionRules) { + emitInstruction(Output, "CheckOpcode", 0, Rule.MatchOpcode); + if (Rule.ResultType) + emitTypePredicate(Output, 0, *Rule.ResultType); + if (Rule.IntegerPredicate) + emitInstruction(Output, "CheckIntegerPredicate", Rule.IntegerPredicate->OperandIndex, + Rule.IntegerPredicate->Predicate); + if (Rule.FloatingPredicate) + emitInstruction(Output, "CheckFloatingPredicate", Rule.FloatingPredicate->OperandIndex, + Rule.FloatingPredicate->Predicate); + if (Rule.MemoryPredicate) + emitInstruction(Output, "CheckMemorySize", Rule.MemoryPredicate->MemoryOperandIndex, + Rule.MemoryPredicate->Bytes); + for (const std::string &Predicate : Rule.TargetPredicates) + emitInstruction(Output, "CheckTargetPredicate", 0, 0, "0", 0, Predicate); + for (const NormalizedSelectionOperandTypePredicate &Predicate : Rule.OperandTypes) + emitTypePredicate(Output, Predicate.OperandIndex, Predicate.Type); + for (const NormalizedSelectionOperandBankPredicate &Predicate : Rule.OperandBanks) + emitInstruction(Output, "CheckOperandBank", Predicate.OperandIndex, Predicate.Bank); + for (const NormalizedSelectionOperandClassPredicate &Predicate : Rule.OperandClasses) + emitInstruction(Output, "CheckOperandClass", Predicate.OperandIndex, Predicate.RegClass); + for (const NormalizedSelectionOperandImmediatePredicate &Predicate : Rule.OperandImmediates) { + emitInstruction(Output, "CheckOperandImmediateRanges", Predicate.OperandIndex, + static_cast<std::uint32_t>(FirstConstantRange), + std::to_string(Predicate.Ranges.size())); + FirstConstantRange += Predicate.Ranges.size(); + } + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + Rule.OperandDefConstants) { + emitInstruction(Output, "CheckOperandDefConstantRanges", Predicate.OperandIndex, + static_cast<std::uint32_t>(FirstConstantRange), + std::to_string(Predicate.Ranges.size())); + FirstConstantRange += Predicate.Ranges.size(); + } + for (const NormalizedSelectionDefUsePredicate &DefUse : Rule.DefUsePredicates) { + emitInstruction(Output, "CheckOperandDefOpcode", DefUse.RootOperandIndex, DefUse.Opcode); + if (DefUse.IntegerPredicate) { + emitInstruction(Output, "CheckOperandDefIntegerPredicate", DefUse.RootOperandIndex, + DefUse.IntegerPredicate->Predicate, + std::to_string(DefUse.IntegerPredicate->OperandIndex)); + } + for (const NormalizedSelectionOperandDefConstantPredicate &Predicate : + DefUse.OperandDefConstants) { + emitInstruction(Output, "CheckOperandDefOperandConstantRanges", DefUse.RootOperandIndex, + static_cast<std::uint32_t>(FirstConstantRange), + std::to_string(Predicate.OperandIndex), + static_cast<std::int64_t>(Predicate.Ranges.size())); + FirstConstantRange += Predicate.Ranges.size(); + } + for (const NormalizedSelectionDefUseCapture &Capture : DefUse.Captures) + emitInstruction(Output, "CaptureDefOperand", DefUse.RootOperandIndex, Capture.ID, + std::to_string(Capture.OperandIndex)); + } + for (const NormalizedSelectionTemporary &Temporary : Rule.Temporaries) { + if (Temporary.TypeFromOperand) { + emitInstruction(Output, "CreateTemporary", *Temporary.TypeFromOperand, Temporary.ID); + continue; + } + emitInstruction(Output, "CreateTemporaryType", 0, Temporary.ID, + "static_cast<std::uint32_t>(GIMatchTypeKind::" + + std::string(typeKindName(Temporary.Type->Kind)) + ")", + Temporary.Type->Bits); + } + for (const NormalizedSelectionAction &Action : Rule.Actions) { + emitInstruction(Output, Action.ReplacesRoot ? "MutateOpcode" : "BuildInstruction", 0, + Action.ResultOpcode); + for (const NormalizedSelectionOperandRenderer &Renderer : Action.OperandRenderers) { + switch (Renderer.SourceKind) { + case NormalizedSelectionOperandRenderer::Kind::Operand: + emitInstruction(Output, "RenderOperand", Renderer.Value); + break; + case NormalizedSelectionOperandRenderer::Kind::ComplexOperand: + emitInstruction(Output, "RenderComplexOperand", Renderer.Value, 0, "0", 0, + Renderer.ComplexRenderer); + break; + case NormalizedSelectionOperandRenderer::Kind::PhysicalRegister: + emitInstruction(Output, "RenderPhysicalRegister", 0, Renderer.Value, + std::to_string(Renderer.Flags)); + break; + case NormalizedSelectionOperandRenderer::Kind::Temporary: + emitInstruction(Output, "RenderTemporary", 0, Renderer.Value, + std::to_string(Renderer.Flags)); + break; + case NormalizedSelectionOperandRenderer::Kind::CapturedOperand: + emitInstruction(Output, "RenderCapturedOperand", 0, Renderer.Value); + break; + case NormalizedSelectionOperandRenderer::Kind::CapturedImmediate: + emitInstruction(Output, "RenderCapturedImmediate", Renderer.Value, + static_cast<std::uint32_t>(Renderer.Transform)); + break; + case NormalizedSelectionOperandRenderer::Kind::Immediate: + emitInstruction(Output, "RenderImmediate", 0, 0, "0", Renderer.Immediate); + break; + } + } + } + for (const std::uint32_t OperandIndex : Rule.EraseOperandDefinitions) + emitInstruction(Output, "EraseOperandDefinition", OperandIndex); + emitInstruction(Output, "Complete", 0); + } + Output << "}};\n\n"; + + Output << "constexpr std::array<GIMatchTableRule, " << Model.SelectionRules.size() + << "> GeneratedGlobalISelRules = {{\n"; + std::size_t FirstInstruction = 0; + for (const NormalizedSelectionRule &Rule : Model.SelectionRules) { + const std::size_t Count = instructionCount(Rule); + Output << " {"; + appendQuoted(Output, Rule.Symbol); + Output << ", " << Rule.Priority << ", " << FirstInstruction << ", " << Count << "},\n"; + FirstInstruction += Count; + } + Output << "}};\n\n"; + Output << "[[nodiscard]] constexpr GIMatchTable generatedGlobalISelMatchTable() noexcept {\n" + " return {GeneratedGlobalISelInstructions, GeneratedGlobalISelRules,\n" + " GeneratedGlobalISelConstantRanges};\n" + "}\n\n"; + Output << "#endif // GET_GLOBALISEL_MATCH_TABLE\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/InstrInfoEmitter.cpp b/backend/utils/TableGen/InstrInfoEmitter.cpp new file mode 100644 index 0000000..1f42d4a --- /dev/null +++ b/backend/utils/TableGen/InstrInfoEmitter.cpp @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstddef> +#include <cstdint> +#include <ostream> +#include <span> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { +namespace { + +void emitOperandDescriptor(std::ostream &Output, const NormalizedOperand &Operand) { + Output << " {.Kinds = OperandKindSet{"; + for (const std::string &Kind : Operand.Kinds) + Output << "MachineOperand::Kind::" << Kind << ", "; + Output << "}, .RegisterRole = RegisterOperandRole::" << Operand.RegisterRole; + Output << ", .RegClass = "; + if (Operand.RegisterClass) + Output << "RegisterClassID::fromValue(" << *Operand.RegisterClass << ")"; + else + Output << "std::nullopt"; + Output << ", .RegBank = "; + if (Operand.RegisterBank) + Output << "RegisterBankID::fromValue(" << *Operand.RegisterBank << ")"; + else + Output << "std::nullopt"; + Output << ", .ImmediatePredicate = "; + if (Operand.ImmediatePredicate) + Output << "ImmediatePredicateID::fromValue(" << *Operand.ImmediatePredicate << ")"; + else + Output << "std::nullopt"; + Output << ", .MemoryRole = MemoryOperandRole::" << Operand.MemoryRole; + Output << ", .DefaultTiedOperand = "; + if (Operand.DefaultTiedOperand) + Output << "std::size_t{" << *Operand.DefaultTiedOperand << "}"; + else + Output << "std::nullopt"; + Output << ", .EarlyClobber = " << (Operand.EarlyClobber ? "true" : "false") + << ", .Optional = " << (Operand.Optional ? "true" : "false") + << ", .VariadicGroup = " << (Operand.VariadicGroup ? "true" : "false") + << ", .AllowSubReg = " << (Operand.AllowSubReg ? "true" : "false"); + Output << ", .Name = "; + if (Operand.Name) + Output << "NamedOperandID::fromValue(" << *Operand.Name << ")"; + else + Output << "std::nullopt"; + Output << ", .TargetOperandType = std::nullopt, .AllowedTargetFlags = " + << Operand.AllowedTargetFlags << ", .TypeConstraint = "; + if (Operand.TypeIndex) { + Output << "OperandTypeConstraint{LowLevelTypeKindSet{" + << "LowLevelType::Kind::CategoryNeutralScalar, LowLevelType::Kind::Pointer, " + << "LowLevelType::Kind::Vector, LowLevelType::Kind::VectorPointer}, std::uint16_t{" + << *Operand.TypeIndex << "}}"; + } else { + Output << "std::nullopt"; + } + Output << "},\n"; +} + +void emitImplicitRegisterList(std::ostream &Output, const NormalizedTargetModel &Model, + std::span<const NormalizedImplicitRegister> Registers) { + Output << "{"; + for (const NormalizedImplicitRegister &Register : Registers) { + Output << "{physical(PhysicalRegisterID::" << findRegister(Model, Register.Register).Symbol + << "), " << (Register.EarlyClobber ? "true" : "false") << ", " + << (Register.Renamable ? "true" : "false") << "}, "; + } + Output << "}"; +} + +void emitImmediatePredicate(std::ostream &Output, const NormalizedImmediatePredicate &Predicate) { + const std::int64_t PositiveLimit = std::int64_t{1} << (Predicate.Bits - 1U); + const std::int64_t Minimum = Predicate.IsSigned ? -PositiveLimit : 0; + const std::int64_t Maximum = + Predicate.IsSigned + ? PositiveLimit - 1 + : static_cast<std::int64_t>((std::uint64_t{1} << Predicate.Bits) - std::uint64_t{1}); + Output << " {.ID = ImmediatePredicateID::fromValue(" << Predicate.ID << "), .Name = "; + appendQuoted(Output, Predicate.Name); + Output << ", .Predicate = [](const MachineOperand &Operand) {\n" + << " const auto *Value = Operand.getIf<std::int64_t>();\n" + << " return Value != nullptr && *Value >= " << Minimum + << " && *Value <= " << Maximum; + if (Predicate.Alignment != 1) + Output << " && (*Value % " << Predicate.Alignment << ") == 0"; + if (Predicate.NonZero) + Output << " && *Value != 0"; + if (!Predicate.AllowedValues.empty()) { + Output << " && ("; + for (std::size_t Index = 0; Index < Predicate.AllowedValues.size(); ++Index) { + if (Index != 0) + Output << " || "; + Output << "*Value == " << Predicate.AllowedValues[Index]; + } + Output << ")"; + } + Output << ";\n }},\n"; +} + +} // namespace + +[[nodiscard]] std::string emitInstructionHeader(const NormalizedTargetModel &Model, + std::string_view SourceName) { + const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" + + upperIdentifier(Model.Target.Name) + "INSTRUCTIONS_H"; + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n"; + Output << "#include \"rcc/CodeGen/MachineIRTypes.h\"\n\n#include <cstdint>\n\n"; + Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n"; + + Output << "enum class InstructionOpcode : std::uint32_t {\n Invalid = 0,\n"; + for (const NormalizedInstruction &Instruction : Model.Instructions) + Output << " " << Instruction.Symbol << " = " << Instruction.ID << ",\n"; + Output << "};\n\n"; + Output << "[[nodiscard]] constexpr Opcode instructionOpcode(InstructionOpcode Value) noexcept {\n" + " return Opcode::fromValue(static_cast<std::uint32_t>(Value));\n" + "}\n\n"; + + Output << "enum class ImmediatePredicate : std::uint32_t {\n None = 0,\n"; + for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates) + Output << " " << Predicate.Symbol << " = " << Predicate.ID << ",\n"; + Output << "};\n\n"; + + Output << "enum class NamedOperand : std::uint32_t {\n None = 0,\n"; + for (const NormalizedNamedOperand &Operand : Model.NamedOperands) + Output << " " << Operand.Symbol << " = " << Operand.ID << ",\n"; + Output << "};\n\n"; + + Output << "enum class SchedulingClass : std::uint32_t {\n None = 0,\n"; + for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses) + Output << " " << Class.Symbol << " = " << Class.ID << ",\n"; + Output << "};\n\n"; + + Output << "enum class EncodingCategory : std::uint32_t {\n None = 0,\n"; + for (const NormalizedEncodingCategory &Category : Model.EncodingCategories) + Output << " " << Category.Symbol << " = " << Category.ID << ",\n"; + Output << "};\n\n"; + + Output << "enum class AssemblyFormat : std::uint32_t {\n None = 0,\n"; + for (const NormalizedAssemblyFormat &Format : Model.AssemblyFormats) + Output << " " << Format.Symbol << " = " << Format.ID << ",\n"; + Output << "};\n\n"; + + Output << "enum class CallingConvention : std::uint32_t {\n None = 0,\n"; + for (const NormalizedCallingConvention &Convention : Model.CallingConventions) + Output << " " << Convention.Symbol << " = " << Convention.ID << ",\n"; + Output << "};\n\n"; + + Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n"; + Output << "#endif // " << Guard << "\n"; + return Output.str(); +} + +std::string emitInstructionInfo(const NormalizedTargetModel &Model) { + std::ostringstream Output; + Output + << "void appendGeneratedInstructionInfo(std::vector<rcc::InstructionDescriptor> " + "&Descriptors,\n" + << " std::vector<rcc::ImmediatePredicate> &Predicates) {\n" + << " Predicates = {\n"; + for (const NormalizedImmediatePredicate &Predicate : Model.ImmediatePredicates) + emitImmediatePredicate(Output, Predicate); + Output << " };\n Descriptors = {\n"; + for (const NormalizedInstruction &Instruction : Model.Instructions) { + Output << " {.InstructionOpcode = Opcode::fromValue(" << Instruction.ID << "), .Name = "; + appendQuoted(Output, Instruction.Name); + Output << ", .Operands = {\n"; + for (const NormalizedOperand &Operand : Instruction.Operands) + emitOperandDescriptor(Output, Operand); + Output << " }, .NumExplicitDefs = " << Instruction.NumExplicitDefs + << ", .Variadic = " << (Instruction.Variadic ? "true" : "false") + << ", .MinimumVariadicRepetitions = 0, .ImplicitDefs = "; + emitImplicitRegisterList(Output, Model, Instruction.ImplicitDefs); + Output << ", .ImplicitUses = "; + emitImplicitRegisterList(Output, Model, Instruction.ImplicitUses); + Output << ", .AllowExtraImplicitOperands = " + << (Instruction.AllowExtraImplicitOperands ? "true" : "false") + << ", .Flags = InstructionFlags{"; + for (const std::string &Flag : Instruction.Flags) + Output << "InstructionFlag::" << Flag << ", "; + Output << "}, .SchedulingClass = SchedulingClassID::fromValue(" << Instruction.SchedulingClass + << "), .EncodingCategory = EncodingCategoryID::fromValue(" + << Instruction.EncodingCategory << "), .SizeInBytes = " << Instruction.SizeInBytes + << ", .AssemblyFormat = AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat + << "), .RequiresMemoryOperands = " + << (Instruction.RequiresMemoryOperands ? "true" : "false") + << ", .Owner = PseudoOwner::" << Instruction.Owner + << ", .Deadline = PipelineStage::" << Instruction.Deadline + << ", .VerifyExplicitOperands = nullptr, " + ".VerifyInstructionSemantics = nullptr},\n"; + } + Output << " };\n}\n\n"; + + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/MCPseudoLoweringEmitter.cpp b/backend/utils/TableGen/MCPseudoLoweringEmitter.cpp new file mode 100644 index 0000000..5afa911 --- /dev/null +++ b/backend/utils/TableGen/MCPseudoLoweringEmitter.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstddef> +#include <ostream> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { + +std::string emitMCPseudoLowering(const NormalizedTargetModel &Model, std::string_view SourceName) { + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifdef GET_MC_PSEUDO_LOWERING\n\n"; + Output << "[[nodiscard]] Expected<std::optional<mc::MCInst>>\n" + "lowerGeneratedMCPseudo(const MachineInstr &Instruction,\n" + " MachineMCContext &Context) {\n" + " switch (Instruction.opcode().value()) {\n" + " default:\n" + " return std::optional<mc::MCInst>{};\n"; + for (const NormalizedMCPseudoExpansion &Expansion : Model.MCPseudoExpansions) { + Output << " case " << Expansion.PseudoOpcode << ": {\n" + << " mc::MCInst Result(mc::MCOpcode::fromValue(" << Expansion.ResultOpcode + << "));\n" + " Result.setFlags(Instruction.flags().rawValue());\n" + " Result.setSourceIdentity(Instruction.stableID().value());\n" + " Result.reserveOperands(" + << Expansion.Operands.size() << ");\n"; + for (std::size_t Index = 0; Index < Expansion.Operands.size(); ++Index) { + const NormalizedMCPseudoOperand &Operand = Expansion.Operands[Index]; + switch (Operand.Kind) { + case NormalizedMCPseudoOperandKind::SourceOperand: + Output << " const MachineOperand *Operand" << Index << " = Instruction.operandAt(" + << Operand.Value << ");\n" + << " if (Operand" << Index << " == nullptr)\n" + << " return makeUnexpected(ErrorCode::AssemblyLoweringFailed,\n" + " \"MC pseudo has too few operands\");\n" + << " auto Lowered" << Index << " =\n" + << " lowerOperand(*Operand" << Index + << ", Instruction.function(), Context);\n" + << " if (!Lowered" << Index << ")\n" + << " return std::unexpected(Lowered" << Index << ".error());\n" + << " Result.addOperand(std::move(*Lowered" << Index << "));\n"; + break; + case NormalizedMCPseudoOperandKind::Register: + Output << " Result.addOperand(mc::MCOperand::registerOperand(\n" + " mc::MCRegister::fromValue(" + << Operand.Value << ")));\n"; + break; + case NormalizedMCPseudoOperandKind::Immediate: + Output << " Result.addOperand(mc::MCOperand::immediate(" << Operand.Immediate << "));\n"; + break; + } + } + Output << " return std::optional<mc::MCInst>{std::move(Result)};\n" + " }\n"; + } + Output << " }\n" + "}\n\n" + "#endif // GET_MC_PSEUDO_LOWERING\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/MacroFusionEmitter.cpp b/backend/utils/TableGen/MacroFusionEmitter.cpp new file mode 100644 index 0000000..2018a43 --- /dev/null +++ b/backend/utils/TableGen/MacroFusionEmitter.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstddef> +#include <cstdint> +#include <ostream> +#include <sstream> +#include <string> +#include <string_view> + +namespace rcc::targetgen { +namespace { + +[[nodiscard]] std::string_view +instructionSide(NormalizedMacroFusionInstruction Instruction) noexcept { + return Instruction == NormalizedMacroFusionInstruction::First ? "First" : "Second"; +} + +void emitOpcodes(std::ostream &Output, const NormalizedMacroFusionRule &Rule, + std::string_view Suffix, const std::vector<std::uint32_t> &Opcodes) { + Output << "constexpr std::array<Opcode, " << Opcodes.size() << "> " << Rule.Symbol << Suffix + << " = {{\n"; + for (const std::uint32_t Opcode : Opcodes) + Output << " Opcode::fromValue(" << Opcode << "),\n"; + Output << "}};\n"; +} + +} // namespace + +std::string emitMacroFusion(const NormalizedTargetModel &Model, std::string_view SourceName) { + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifdef GET_MACRO_FUSION_RULES\n\n"; + for (const NormalizedMacroFusionRule &Rule : Model.MacroFusionRules) { + emitOpcodes(Output, Rule, "FirstOpcodes", Rule.FirstOpcodes); + emitOpcodes(Output, Rule, "SecondOpcodes", Rule.SecondOpcodes); + Output << "constexpr std::array<MacroFusionOpcodePair, " << Rule.OpcodePairs.size() << "> " + << Rule.Symbol << "OpcodePairs = {{\n"; + for (const NormalizedMacroFusionOpcodePair &Pair : Rule.OpcodePairs) { + Output << " {Opcode::fromValue(" << Pair.FirstOpcode << "), Opcode::fromValue(" + << Pair.SecondOpcode << ")},\n"; + } + Output << "}};\n"; + Output << "constexpr std::array<MacroFusionImmediatePredicate, " + << Rule.ImmediatePredicates.size() << "> " << Rule.Symbol + << "ImmediatePredicates = {{\n"; + for (const NormalizedMacroFusionImmediatePredicate &Predicate : Rule.ImmediatePredicates) { + Output << " {MacroFusionInstruction::" << instructionSide(Predicate.Instruction) << ", " + << Predicate.OperandIndex << ", " << Predicate.Minimum << ", " << Predicate.Maximum + << "},\n"; + } + Output << "}};\n"; + Output << "constexpr std::array<MacroFusionRegisterInequality, " + << Rule.RegisterInequalities.size() << "> " << Rule.Symbol + << "RegisterInequalities = {{\n"; + for (const NormalizedMacroFusionRegisterInequality &Predicate : Rule.RegisterInequalities) { + Output << " {MacroFusionInstruction::" << instructionSide(Predicate.Instruction) << ", " + << Predicate.LeftOperand << ", " << Predicate.RightOperand << "},\n"; + } + Output << "}};\n"; + Output << "constexpr std::array<MacroFusionImmediateRelation, " + << Rule.ImmediateRelations.size() << "> " << Rule.Symbol << "ImmediateRelations = {{\n"; + for (const NormalizedMacroFusionImmediateRelation &Relation : Rule.ImmediateRelations) + Output << " {" << Relation.FirstOperand << ", " << Relation.SecondOperand << "},\n"; + Output << "}};\n\n"; + } + + Output << "constexpr std::array<MacroFusionRule, " << Model.MacroFusionRules.size() + << "> GeneratedMacroFusionRules = {{\n"; + for (const NormalizedMacroFusionRule &Rule : Model.MacroFusionRules) { + Output << " {"; + appendQuoted(Output, Rule.Symbol); + Output << ", "; + appendQuoted(Output, Rule.Feature); + Output << ", " << Rule.Symbol << "FirstOpcodes, " << Rule.Symbol << "SecondOpcodes, " + << Rule.Symbol << "OpcodePairs, " << Rule.Symbol << "ImmediatePredicates, " + << Rule.Symbol << "RegisterInequalities, " << Rule.Symbol << "ImmediateRelations, " + << Rule.FirstDefinitionOperand << ", " << Rule.SecondUseOperand << ", " + << Rule.SecondDefinitionOperand << ", "; + if (Rule.CommutableSecondUseOperand) + Output << *Rule.CommutableSecondUseOperand; + else + Output << "MacroFusionRule::NoOperand"; + Output << ", " << (Rule.RequireSingleNonDebugUse ? "true" : "false") << ", " + << (Rule.RequireSecondDestinationTie ? "true" : "false") << "},\n"; + } + Output << "}};\n\n"; + Output << "[[nodiscard]] constexpr std::span<const MacroFusionRule> " + "generatedMacroFusionRules() noexcept {\n" + " return GeneratedMacroFusionRules;\n" + "}\n\n"; + Output << "#endif // GET_MACRO_FUSION_RULES\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/RegisterBankEmitter.cpp b/backend/utils/TableGen/RegisterBankEmitter.cpp new file mode 100644 index 0000000..54deb63 --- /dev/null +++ b/backend/utils/TableGen/RegisterBankEmitter.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <ranges> +#include <sstream> +#include <string> + +namespace rcc::targetgen { + +std::string emitRegisterBanks(const NormalizedTargetModel &Model) { + std::ostringstream Output; + for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) { + Output << " {RegisterBankID::fromValue(" << Bank.ID << "), "; + appendQuoted(Output, Bank.Name); + Output << ", " << Bank.MaximumSizeInBits << ", {"; + for (const std::uint32_t ClassID : Bank.RegisterClasses) { + const auto Position = + std::ranges::find(Model.RegisterClasses, ClassID, &NormalizedRegisterClass::ID); + Output << "RegisterClassID::fromValue(" << Position->ID << "), "; + } + Output << "}},\n"; + } + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/RegisterInfoEmitter.cpp b/backend/utils/TableGen/RegisterInfoEmitter.cpp new file mode 100644 index 0000000..0eee15c --- /dev/null +++ b/backend/utils/TableGen/RegisterInfoEmitter.cpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <cstdint> +#include <ostream> +#include <ranges> +#include <span> +#include <sstream> +#include <string> +#include <string_view> +#include <unordered_set> + +namespace rcc::targetgen { +namespace { + +void emitRegisterIDList(std::ostream &Output, const NormalizedTargetModel &Model, + std::span<const std::uint32_t> IDs) { + for (const std::uint32_t ID : IDs) + Output << "PhysicalRegisterID::" << findRegister(Model, ID).Symbol << ", "; +} + +void emitRegisterClassIDList(std::ostream &Output, const NormalizedTargetModel &Model, + std::span<const std::uint32_t> IDs) { + for (const std::uint32_t ID : IDs) { + const auto Position = + std::ranges::find(Model.RegisterClasses, ID, &NormalizedRegisterClass::ID); + Output << "RegisterClass::" << Position->Symbol << ", "; + } +} + +} // namespace + +[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model, + std::string_view SourceName) { + const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" + + upperIdentifier(Model.Target.Name) + "REGISTERS_H"; + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n"; + Output << "#include <cstdint>\n#include <string_view>\n\n"; + Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n"; + Output << "inline constexpr std::string_view TargetName = "; + appendQuoted(Output, Model.Target.Name); + Output << ";\ninline constexpr std::string_view TargetTriple = "; + appendQuoted(Output, Model.Target.Triple); + Output << ";\ninline constexpr std::string_view TargetCPU = "; + appendQuoted(Output, Model.Target.CPU); + Output << ";\ninline constexpr std::string_view TargetABI = "; + appendQuoted(Output, Model.Target.ABI); + Output << ";\ninline constexpr std::string_view TargetDataLayout = "; + appendQuoted(Output, Model.Target.DataLayout); + Output << ";\n\ninline constexpr std::string_view TargetFeatures[] = {\n"; + for (const std::string &Feature : Model.Target.Features) { + Output << " "; + appendQuoted(Output, Feature); + Output << ",\n"; + } + Output << "};\n\n"; + + Output << "enum class PhysicalRegisterID : std::uint32_t {\n NoRegister = 0,\n"; + for (const NormalizedRegister &Register : Model.Registers) + Output << " " << Register.Symbol << " = " << Register.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterUnit : std::uint32_t {\n"; + for (const SourceRegisterUnit &Unit : Model.RegisterUnits) + Output << " " << Unit.Symbol << " = " << Unit.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterBank : std::uint32_t {\n"; + for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) + Output << " " << Bank.Symbol << " = " << Bank.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterClass : std::uint32_t {\n"; + for (const NormalizedRegisterClass &Class : Model.RegisterClasses) + Output << " " << Class.Symbol << " = " << Class.ID << ",\n"; + Output << "};\n\n"; + Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n"; + Output << "#endif // " << Guard << "\n"; + return Output.str(); +} + +std::string emitRegisterInfo(const NormalizedTargetModel &Model, std::string_view SourceName) { + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-tblgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "void appendGeneratedRegisterInfo(TargetRegisterInfoDesc &Description) {\n"; + Output << " Description.RegisterUnits = {\n"; + for (const SourceRegisterUnit &Unit : Model.RegisterUnits) { + Output << " {RegisterUnitID::fromValue(" << Unit.ID << "), "; + appendQuoted(Output, Unit.Name); + Output << "},\n"; + } + Output << " };\n\n Description.PhysicalRegisters = {\n"; + for (const NormalizedRegister &Register : Model.Registers) { + Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), "; + appendQuoted(Output, Register.Name); + Output << ", " << Register.Encoding << ", {"; + for (const NormalizedRegisterUnitLane &Unit : Register.Units) { + Output << "unitLane(RegisterUnit::" << findRegisterUnit(Model, Unit.UnitID).Symbol << ", " + << Unit.Lanes << "), "; + } + Output << "}, " << (Register.IsConstant ? "true" : "false") << ", " + << (Register.IsArtificial ? "true" : "false") << ", " + << (Register.IsReserved ? "true" : "false") << ", {"; + for (const std::string &Alias : Register.AlternativeNames) { + appendQuoted(Output, Alias); + Output << ", "; + } + Output << "}},\n"; + } + Output << " };\n\n Description.RegisterBanks = {\n"; + Output << emitRegisterBanks(Model); + Output << " };\n\n Description.RegisterClasses = {\n"; + for (const NormalizedRegisterClass &Class : Model.RegisterClasses) { + Output << " registerClass(RegisterClass::" << Class.Symbol << ", "; + appendQuoted(Output, Class.Name); + Output << ", {"; + emitRegisterIDList(Output, Model, Class.Registers); + Output << "}, {"; + emitRegisterIDList(Output, Model, Class.AllocationOrder); + Output << "}, {"; + emitRegisterClassIDList(Output, Model, Class.SuperClasses); + Output << "}, RegisterBank::" << findRegisterBank(Model, Class.BankID).Symbol << ", " + << Class.SizeInBits << ", " << Class.SpillSizeInBits << ", " + << Class.SpillAlignmentInBits << ", " << Class.Lanes << ", " + << (Class.Allocatable ? "true" : "false") << ", " << Class.CopyCost << "),\n"; + } + Output << " };\n}\n\n"; + Output << "void appendGeneratedMIRTextInfo(TargetMIRTextInfoDesc &Description) {\n"; + Output << " Description.DirectOperandFlagMask = " << Model.MIROperandFlags.DirectMask + << ";\n Description.DirectOperandFlags = {\n"; + for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Direct) { + Output << " {"; + appendQuoted(Output, Flag.Name); + Output << ", " << Flag.Value << "},\n"; + } + Output << " };\n Description.BitmaskOperandFlags = {\n"; + for (const SourceMIROperandFlag &Flag : Model.MIROperandFlags.Bitmask) { + Output << " {"; + appendQuoted(Output, Flag.Name); + Output << ", " << Flag.Value << "},\n"; + } + Output << " };\n"; + Output << " Description.RegisterMasks = {\n"; + for (const NormalizedRegisterMask &Mask : Model.RegisterMasks) { + Output << " {"; + appendQuoted(Output, Mask.Name); + Output << ", makePreservedMask({"; + emitRegisterIDList(Output, Model, Mask.PreservedRegisters); + Output << "})},\n"; + } + Output << " };\n Description.CFIRegisters = {\n"; + std::unordered_set<std::uint32_t> CanonicalDwarfRegisters; + for (const NormalizedRegister &Register : Model.Registers) { + const bool IsCanonical = CanonicalDwarfRegisters.insert(Register.DwarfRegister).second; + Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), " + << Register.DwarfRegister << ", " << (IsCanonical ? "true" : "false") << "},\n"; + } + Output << " };\n}\n\n"; + + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/SearchableTableEmitter.cpp b/backend/utils/TableGen/SearchableTableEmitter.cpp new file mode 100644 index 0000000..3dfa534 --- /dev/null +++ b/backend/utils/TableGen/SearchableTableEmitter.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <algorithm> +#include <ranges> +#include <sstream> +#include <string> + +namespace rcc::targetgen { + +std::string emitSearchableTables(const NormalizedTargetModel &Model) { + std::ostringstream Output; + const auto AssemblyInstructionCount = + std::ranges::count_if(Model.Instructions, [](const NormalizedInstruction &Instruction) { + return !Instruction.AssemblyMnemonic.empty(); + }); + Output << "constexpr std::array<AssemblyInstructionInfo, " << AssemblyInstructionCount + << "> GeneratedAssemblyInstructions = {{\n"; + for (const NormalizedInstruction &Instruction : Model.Instructions) { + if (Instruction.AssemblyMnemonic.empty()) + continue; + Output << " {Opcode::fromValue(" << Instruction.ID << "), "; + appendQuoted(Output, Instruction.AssemblyMnemonic); + Output << ", AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat << "), " + << Instruction.BinaryEncoding << "},\n"; + } + Output << "}};\n\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/SubtargetEmitter.cpp b/backend/utils/TableGen/SubtargetEmitter.cpp new file mode 100644 index 0000000..f374d48 --- /dev/null +++ b/backend/utils/TableGen/SubtargetEmitter.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" + +#include <sstream> +#include <string> + +namespace rcc::targetgen { + +std::string emitSubtarget(const NormalizedTargetModel &Model) { + std::ostringstream Output; + Output << "constexpr std::array<SchedulingClassInfo, " << Model.SchedulingClasses.size() + << "> GeneratedSchedulingClasses = {{\n"; + for (const NormalizedSchedulingClass &Class : Model.SchedulingClasses) { + Output << " {SchedulingClassID::fromValue(" << Class.ID << "), "; + appendQuoted(Output, Class.Name); + Output << ", " << Class.MicroOps << ", " << Class.Latency << "},\n"; + } + Output << "}};\n\n"; + return Output.str(); +} + +} // namespace rcc::targetgen diff --git a/backend/utils/TableGen/TableGenBackends.h b/backend/utils/TableGen/TableGenBackends.h new file mode 100644 index 0000000..dff0333 --- /dev/null +++ b/backend/utils/TableGen/TableGenBackends.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H +#define RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H + +#include "Common/CodeGenTarget.h" + +#include <optional> +#include <string> +#include <string_view> +#include <vector> + +namespace rcc::targetgen { + +struct GeneratedDescription final { + std::string RegisterHeader; + std::string InstructionHeader; + std::string TargetTables; + std::string GlobalISel; + std::string GlobalISelCombiner; + std::string MacroFusion; + std::string MCPseudoLowering; +}; + +struct DescriptionGenerationResult final { + std::optional<GeneratedDescription> Output; + std::vector<TextDiagnostic> Diagnostics; + + [[nodiscard]] bool succeeded() const noexcept { + return Output.has_value() && Diagnostics.empty(); + } +}; + +[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitRegisterInfo(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitRegisterBanks(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitInstructionHeader(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitInstructionInfo(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitCallingConventions(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitCodeEmitter(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitAssemblyWriter(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitSubtarget(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitSearchableTables(const NormalizedTargetModel &Model); +[[nodiscard]] std::string emitGlobalISel(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitGlobalISelCombiner(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitMacroFusion(const NormalizedTargetModel &Model, + std::string_view SourceName); +[[nodiscard]] std::string emitMCPseudoLowering(const NormalizedTargetModel &Model, + std::string_view SourceName); + +[[nodiscard]] DescriptionGenerationResult generateDescription(const std::string &SourceName, + std::string SourceText); + +} // namespace rcc::targetgen + +#endif // RCC_UTILS_TABLEGEN_TABLEGENBACKENDS_H diff --git a/backend/utils/TableGen/rcc-tblgen.cpp b/backend/utils/TableGen/rcc-tblgen.cpp new file mode 100644 index 0000000..cf10665 --- /dev/null +++ b/backend/utils/TableGen/rcc-tblgen.cpp @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TableGenBackends.h" +#include "rcc/Support/TextDiagnostic.h" + +#include <cctype> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <iterator> +#include <optional> +#include <string> +#include <string_view> +#include <unordered_map> +#include <vector> + +namespace { + +struct CommandLine final { + std::vector<std::string> InputPaths; + std::string RegisterHeaderPath; + std::string InstructionHeaderPath; + std::string TablesPath; + std::string GlobalISelPath; + std::string GlobalISelCombinerPath; + std::string MacroFusionPath; + std::string MCPseudoLoweringPath; +}; + +[[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) { + for (int Index = 1; Index < ArgumentCount; ++Index) { + const std::string_view Argument(Arguments[Index]); + if (Argument == "--input") { + if (Index + 1 >= ArgumentCount) { + std::cerr << "rcc-tblgen: missing value for " << Argument << '\n'; + return false; + } + Result.InputPaths.emplace_back(Arguments[++Index]); + continue; + } + if (Argument == "--register-header" || Argument == "--instruction-header" || + Argument == "--tables" || Argument == "--global-isel" || + Argument == "--global-isel-combiner" || Argument == "--macro-fusion" || + Argument == "--mc-pseudo-lowering") { + if (Index + 1 >= ArgumentCount) { + std::cerr << "rcc-tblgen: missing value for " << Argument << '\n'; + return false; + } + std::string *Destination = nullptr; + if (Argument == "--register-header") + Destination = &Result.RegisterHeaderPath; + else if (Argument == "--instruction-header") + Destination = &Result.InstructionHeaderPath; + else if (Argument == "--global-isel") + Destination = &Result.GlobalISelPath; + else if (Argument == "--global-isel-combiner") + Destination = &Result.GlobalISelCombinerPath; + else if (Argument == "--macro-fusion") + Destination = &Result.MacroFusionPath; + else if (Argument == "--mc-pseudo-lowering") + Destination = &Result.MCPseudoLoweringPath; + else + Destination = &Result.TablesPath; + *Destination = Arguments[++Index]; + continue; + } + std::cerr << "rcc-tblgen: unknown argument '" << Argument << "'\n"; + return false; + } + if (!Result.InputPaths.empty() && !Result.RegisterHeaderPath.empty() && + !Result.InstructionHeaderPath.empty() && !Result.TablesPath.empty() && + !Result.GlobalISelPath.empty() && !Result.GlobalISelCombinerPath.empty() && + !Result.MacroFusionPath.empty() && !Result.MCPseudoLoweringPath.empty()) { + return true; + } + std::cerr << "usage: rcc-tblgen --input <file> [--input <fragment>]... --register-header <file> " + "--instruction-header <file> --tables <file> --global-isel <file> " + "--global-isel-combiner <file> --macro-fusion <file> " + "--mc-pseudo-lowering <file>\n"; + return false; +} + +[[nodiscard]] bool writeFile(const std::filesystem::path &Path, std::string_view Contents) { + std::error_code Error; + std::filesystem::create_directories(Path.parent_path(), Error); + if (Error) { + std::cerr << "rcc-tblgen: cannot create output directory for '" << Path.string() + << "': " << Error.message() << '\n'; + return false; + } + std::ofstream Output(Path, std::ios::binary | std::ios::trunc); + if (!Output) { + std::cerr << "rcc-tblgen: cannot open output '" << Path.string() << "'\n"; + return false; + } + Output.write(Contents.data(), static_cast<std::streamsize>(Contents.size())); + if (Output) { + Output.close(); + return true; + } + std::cerr << "rcc-tblgen: cannot write output '" << Path.string() << "'\n"; + return false; +} + +struct SourceSection final { + std::string Name; + std::string Contents; + std::size_t HeaderSize = 0; +}; + +[[nodiscard]] bool isIdentifierCharacter(char Character) noexcept { + return std::isalnum(static_cast<unsigned char>(Character)) != 0 || Character == '_'; +} + +[[nodiscard]] std::optional<std::string_view> topLevelKey(std::string_view Line) noexcept { + if (Line.empty() || Line.front() == '#' || + (!std::isalpha(static_cast<unsigned char>(Line.front())) && Line.front() != '_')) { + return std::nullopt; + } + std::size_t End = 1; + while (End < Line.size() && isIdentifierCharacter(Line[End])) + ++End; + if (End == Line.size() || Line[End] != ':') + return std::nullopt; + return Line.substr(0, End); +} + +[[nodiscard]] std::vector<SourceSection> splitSections(std::string_view SourceText) { + std::vector<SourceSection> Result; + std::size_t SectionStart = 0; + std::size_t HeaderEnd = 0; + std::optional<std::string> SectionName; + std::size_t LineStart = 0; + while (LineStart < SourceText.size()) { + const std::size_t LineEnd = SourceText.find('\n', LineStart); + const std::size_t ContentEnd = LineEnd == std::string_view::npos ? SourceText.size() : LineEnd; + const std::string_view Line = SourceText.substr(LineStart, ContentEnd - LineStart); + if (const auto Key = topLevelKey(Line)) { + if (SectionName) { + Result.push_back({ + .Name = std::move(*SectionName), + .Contents = std::string(SourceText.substr(SectionStart, LineStart - SectionStart)), + .HeaderSize = HeaderEnd - SectionStart, + }); + } + SectionStart = LineStart; + HeaderEnd = LineEnd == std::string_view::npos ? SourceText.size() : LineEnd + 1U; + SectionName = std::string(*Key); + } + if (LineEnd == std::string_view::npos) + break; + LineStart = LineEnd + 1U; + } + if (SectionName) { + Result.push_back({ + .Name = std::move(*SectionName), + .Contents = std::string(SourceText.substr(SectionStart)), + .HeaderSize = HeaderEnd - SectionStart, + }); + } + return Result; +} + +[[nodiscard]] std::optional<std::string> +combineDescriptionSources(const std::vector<std::string> &Paths, + const std::vector<std::string> &Sources) { + std::vector<SourceSection> Result; + std::unordered_map<std::string, std::size_t> SectionIndex; + for (std::size_t Index = 0; Index < Sources.size(); ++Index) { + const std::vector<SourceSection> Sections = splitSections(Sources[Index]); + if (Sections.empty()) { + std::cerr << "rcc-tblgen: input '" << Paths[Index] + << "' does not contain a top-level description section\n"; + return std::nullopt; + } + for (const SourceSection &Section : Sections) { + const auto Position = SectionIndex.find(Section.Name); + if (Position == SectionIndex.end()) { + SectionIndex.emplace(Section.Name, Result.size()); + Result.push_back(Section); + continue; + } + if (Section.Name != "instructions" && Section.Name != "selection_rules" && + Section.Name != "combiner_rules" && Section.Name != "macro_fusion_rules" && + Section.Name != "mc_pseudo_expansions") { + std::cerr << "rcc-tblgen: duplicate description section '" << Section.Name << "' in '" + << Paths[Index] << "'\n"; + return std::nullopt; + } + SourceSection &Existing = Result[Position->second]; + const std::string_view Body(Section.Contents.data() + Section.HeaderSize, + Section.Contents.size() - Section.HeaderSize); + if (!Existing.Contents.empty() && Existing.Contents.back() != '\n') + Existing.Contents.push_back('\n'); + Existing.Contents.append(Body); + } + } + + std::string Combined; + for (const SourceSection &Section : Result) { + if (!Combined.empty() && Combined.back() != '\n') + Combined.push_back('\n'); + Combined.append(Section.Contents); + } + return Combined; +} + +} // namespace + +int main(int ArgumentCount, char *Arguments[]) { + CommandLine Command; + if (!parseCommandLine(ArgumentCount, Arguments, Command)) + return 1; + + std::vector<std::string> Sources; + Sources.reserve(Command.InputPaths.size()); + for (const std::string &InputPath : Command.InputPaths) { + std::ifstream Input(InputPath, std::ios::binary); + if (!Input) { + std::cerr << "rcc-tblgen: cannot open input '" << InputPath << "'\n"; + return 1; + } + Sources.emplace_back(std::istreambuf_iterator<char>(Input), std::istreambuf_iterator<char>()); + } + const auto SourceText = combineDescriptionSources(Command.InputPaths, Sources); + if (!SourceText) + return 1; + const rcc::targetgen::DescriptionGenerationResult Result = + rcc::targetgen::generateDescription(Command.InputPaths.front(), *SourceText); + if (!Result.succeeded()) { + const rcc::TextSource Source(Command.InputPaths.front(), *SourceText); + for (const rcc::TextDiagnostic &Diagnostic : Result.Diagnostics) + std::cerr << rcc::formatTextDiagnostic(Diagnostic, Source) << '\n'; + return 1; + } + + if (!Result.Output) { + std::cerr << "rcc-tblgen: successful generation produced no output\n"; + return 1; + } + const rcc::targetgen::GeneratedDescription &Output = Result.Output.value(); + return writeFile(Command.RegisterHeaderPath, Output.RegisterHeader) && + writeFile(Command.InstructionHeaderPath, Output.InstructionHeader) && + writeFile(Command.TablesPath, Output.TargetTables) && + writeFile(Command.GlobalISelPath, Output.GlobalISel) && + writeFile(Command.GlobalISelCombinerPath, Output.GlobalISelCombiner) && + writeFile(Command.MacroFusionPath, Output.MacroFusion) && + writeFile(Command.MCPseudoLoweringPath, Output.MCPseudoLowering) + ? 0 + : 1; +} |
