| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "Basic/SequenceToOffsetTable.h" | |
| #include "Common/CodeGenDAGPatterns.h" // For SDNodeInfo. | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/TableGen/CodeGenHelpers.h" | |
| #include "llvm/TableGen/Error.h" | |
| #include "llvm/TableGen/StringToOffsetTable.h" | |
| #include "llvm/TableGen/TableGenBackend.h" | |
| using namespace llvm; | |
| static cl::OptionCategory SDNodeInfoEmitterCat("Options for -gen-sdnode-info"); | |
| static cl::opt<std::string> TargetSDNodeNamespace( | |
| "sdnode-namespace", cl::cat(SDNodeInfoEmitterCat), | |
| cl::desc("Specify target SDNode namespace (default=<Target>ISD)")); | |
| static cl::opt<bool> WarnOnSkippedNodes( | |
| "warn-on-skipped-nodes", cl::cat(SDNodeInfoEmitterCat), | |
| cl::desc("Explain why a node was skipped (default=true)"), cl::init(true)); | |
| namespace { | |
| class SDNodeInfoEmitter { | |
| const RecordKeeper &RK; | |
| const CodeGenTarget Target; | |
| std::map<StringRef, SmallVector<SDNodeInfo, 2>> NodesByName; | |
| public: | |
| explicit SDNodeInfoEmitter(const RecordKeeper &RK); | |
| void run(raw_ostream &OS) const; | |
| private: | |
| void emitEnum(raw_ostream &OS) const; | |
| std::vector<unsigned> emitNodeNames(raw_ostream &OS) const; | |
| std::vector<std::pair<unsigned, unsigned>> | |
| emitTypeConstraints(raw_ostream &OS) const; | |
| void emitDescs(raw_ostream &OS) const; | |
| }; | |
| } // namespace | |
| static bool haveCompatibleDescriptions(const SDNodeInfo &N1, | |
| const SDNodeInfo &N2) { | |
| // Number of results/operands must match. | |
| if (N1.getNumResults() != N2.getNumResults() || | |
| N1.getNumOperands() != N2.getNumOperands()) | |
| return false; | |
| // Flags must match. | |
| if (N1.isStrictFP() != N2.isStrictFP() || N1.getTSFlags() != N2.getTSFlags()) | |
| return false; | |
| // We're only interested in a subset of node properties. Properties like | |
| // SDNPAssociative and SDNPCommutative do not impose constraints on nodes, | |
| // and sometimes differ between nodes sharing the same enum name. | |
| constexpr unsigned PropMask = (1 << SDNPHasChain) | (1 << SDNPOutGlue) | | |
| (1 << SDNPInGlue) | (1 << SDNPOptInGlue) | | |
| (1 << SDNPMemOperand) | (1 << SDNPVariadic); | |
| return (N1.getProperties() & PropMask) == (N2.getProperties() & PropMask); | |
| } | |
| static bool haveCompatibleDescriptions(ArrayRef<SDNodeInfo> Nodes) { | |
| const SDNodeInfo &N = Nodes.front(); | |
| return all_of(drop_begin(Nodes), [&](const SDNodeInfo &Other) { | |
| return haveCompatibleDescriptions(Other, N); | |
| }); | |
| } | |
| static void warnOnSkippedNode(const SDNodeInfo &N, const Twine &Reason) { | |
| PrintWarning(N.getRecord()->getLoc(), "skipped node: " + Reason); | |
| } | |
| SDNodeInfoEmitter::SDNodeInfoEmitter(const RecordKeeper &RK) | |
| : RK(RK), Target(RK) { | |
| const CodeGenHwModes &HwModes = Target.getHwModes(); | |
| // Figure out target SDNode namespace. | |
| if (!TargetSDNodeNamespace.getNumOccurrences()) | |
| TargetSDNodeNamespace = Target.getName().str() + "ISD"; | |
| // Filter nodes by the target SDNode namespace and create a mapping | |
| // from an enum name to a list of nodes that have that name. | |
| // The mapping is usually 1:1, but in rare cases it can be 1:N. | |
| for (const Record *R : RK.getAllDerivedDefinitions("SDNode")) { | |
| SDNodeInfo Node(R, HwModes); | |
| auto [NS, EnumName] = Node.getEnumName().split("::"); | |
| if (NS.empty() || EnumName.empty()) { | |
| if (WarnOnSkippedNodes) | |
| warnOnSkippedNode(Node, "invalid enum name"); | |
| continue; | |
| } | |
| if (NS != TargetSDNodeNamespace) | |
| continue; | |
| NodesByName[EnumName].push_back(std::move(Node)); | |
| } | |
| // Filter out nodes that have different "prototypes" and/or flags. | |
| // Don't look at type constraints though, we will simply skip emitting | |
| // the constraints if they differ. | |
| decltype(NodesByName)::iterator Next; | |
| for (auto I = NodesByName.begin(), E = NodesByName.end(); I != E; I = Next) { | |
| Next = std::next(I); | |
| if (haveCompatibleDescriptions(I->second)) | |
| continue; | |
| if (WarnOnSkippedNodes) | |
| for (const SDNodeInfo &N : I->second) | |
| warnOnSkippedNode(N, "incompatible description"); | |
| NodesByName.erase(I); | |
| } | |
| } | |
| void SDNodeInfoEmitter::emitEnum(raw_ostream &OS) const { | |
| IfDefEmitter IfDef(OS, "GET_SDNODE_ENUM"); | |
| NamespaceEmitter NS(OS, "llvm::" + TargetSDNodeNamespace); | |
| if (!NodesByName.empty()) { | |
| StringRef FirstName = NodesByName.begin()->first; | |
| StringRef LastName = NodesByName.rbegin()->first; | |
| OS << "enum GenNodeType : unsigned {\n"; | |
| OS << " " << FirstName << " = ISD::BUILTIN_OP_END,\n"; | |
| for (StringRef EnumName : make_first_range(drop_begin(NodesByName))) | |
| OS << " " << EnumName << ",\n"; | |
| OS << "};\n\n"; | |
| OS << "static constexpr unsigned GENERATED_OPCODE_END = " << LastName | |
| << " + 1;\n"; | |
| } else { | |
| OS << "static constexpr unsigned GENERATED_OPCODE_END = " | |
| "ISD::BUILTIN_OP_END;\n"; | |
| } | |
| } | |
| std::vector<unsigned> SDNodeInfoEmitter::emitNodeNames(raw_ostream &OS) const { | |
| StringToOffsetTable NameTable; | |
| std::vector<unsigned> NameOffsets; | |
| NameOffsets.reserve(NodesByName.size()); | |
| for (StringRef EnumName : make_first_range(NodesByName)) { | |
| SmallString<64> DebugName; | |
| raw_svector_ostream SS(DebugName); | |
| SS << TargetSDNodeNamespace << "::" << EnumName; | |
| NameOffsets.push_back(NameTable.GetOrAddStringOffset(DebugName)); | |
| } | |
| NameTable.EmitStringTableDef(OS, Target.getName() + "SDNodeNames"); | |
| OS << '\n'; | |
| return NameOffsets; | |
| } | |
| static StringRef getTypeConstraintKindName(SDTypeConstraint::KindTy Kind) { | |
| #define CASE(NAME) \ | |
| case SDTypeConstraint::NAME: \ | |
| return #NAME | |
| switch (Kind) { | |
| CASE(SDTCisVT); | |
| CASE(SDTCisPtrTy); | |
| CASE(SDTCisInt); | |
| CASE(SDTCisFP); | |
| CASE(SDTCisVec); | |
| CASE(SDTCisSameAs); | |
| CASE(SDTCisVTSmallerThanOp); | |
| CASE(SDTCisOpSmallerThanOp); | |
| CASE(SDTCisEltOfVec); | |
| CASE(SDTCisSubVecOfVec); | |
| CASE(SDTCVecEltisVT); | |
| CASE(SDTCisSameNumEltsAs); | |
| CASE(SDTCisSameSizeAs); | |
| } | |
| llvm_unreachable("Unknown constraint kind"); // Make MSVC happy. | |
| #undef CASE | |
| } | |
| static void emitTypeConstraint( | |
| raw_ostream &OS, SDTypeConstraint C, | |
| const std::map<ValueTypeByHwMode, unsigned> &VTByHwModeTable) { | |
| unsigned OtherOpNo = 0; | |
| unsigned NumHwModes = 0; | |
| unsigned VTByHwModeOffset = 0; | |
| MVT VT = MVT::INVALID_SIMPLE_VALUE_TYPE; | |
| switch (C.ConstraintType) { | |
| case SDTypeConstraint::SDTCisVT: | |
| // SequenceToOffsetTable::emit() prints a "dummy" (default-constructed) | |
| // element if the table would otherwise be empty. VVT is empty in this case. | |
| if (C.VVT.empty()) | |
| break; | |
| [[fallthrough]]; | |
| case SDTypeConstraint::SDTCVecEltisVT: | |
| if (C.VVT.isSimple()) { | |
| VT = C.VVT.getSimple(); | |
| } else { | |
| NumHwModes = C.VVT.size(); | |
| assert(NumHwModes && "Empty type set?"); | |
| VTByHwModeOffset = VTByHwModeTable.at(C.VVT); | |
| } | |
| break; | |
| case SDTypeConstraint::SDTCisPtrTy: | |
| case SDTypeConstraint::SDTCisInt: | |
| case SDTypeConstraint::SDTCisFP: | |
| case SDTypeConstraint::SDTCisVec: | |
| break; | |
| case SDTypeConstraint::SDTCisSameAs: | |
| case SDTypeConstraint::SDTCisVTSmallerThanOp: | |
| case SDTypeConstraint::SDTCisOpSmallerThanOp: | |
| case SDTypeConstraint::SDTCisEltOfVec: | |
| case SDTypeConstraint::SDTCisSubVecOfVec: | |
| case SDTypeConstraint::SDTCisSameNumEltsAs: | |
| case SDTypeConstraint::SDTCisSameSizeAs: | |
| OtherOpNo = C.OtherOperandNo; | |
| break; | |
| } | |
| OS << '{' << getTypeConstraintKindName(C.ConstraintType) << ", " | |
| << C.OperandNo << ", " << OtherOpNo << ", " << NumHwModes << ", "; | |
| if (NumHwModes) { | |
| OS << VTByHwModeOffset; | |
| } else { | |
| OS << (VT == MVT::INVALID_SIMPLE_VALUE_TYPE | |
| ? "MVT::INVALID_SIMPLE_VALUE_TYPE" | |
| : getEnumName(VT)); | |
| } | |
| OS << '}'; | |
| } | |
| std::vector<std::pair<unsigned, unsigned>> | |
| SDNodeInfoEmitter::emitTypeConstraints(raw_ostream &OS) const { | |
| std::map<ValueTypeByHwMode, unsigned> VTByHwModeTable; | |
| using ConstraintsVecTy = SmallVector<SDTypeConstraint, 0>; | |
| SequenceToOffsetTable<ConstraintsVecTy> ConstraintTable( | |
| /*Terminator=*/std::nullopt); | |
| std::vector<std::pair<unsigned, unsigned>> ConstraintOffsetsAndCounts; | |
| ConstraintOffsetsAndCounts.reserve(NodesByName.size()); | |
| SmallVector<StringRef> SkippedNodes; | |
| for (const auto &[EnumName, Nodes] : NodesByName) { | |
| ArrayRef<SDTypeConstraint> Constraints = Nodes.front().getTypeConstraints(); | |
| bool IsAmbiguous = any_of(drop_begin(Nodes), [&](const SDNodeInfo &Other) { | |
| return ArrayRef(Other.getTypeConstraints()) != Constraints; | |
| }); | |
| // If nodes with the same enum name have different constraints, | |
| // treat them as if they had no constraints at all. | |
| if (IsAmbiguous) { | |
| SkippedNodes.push_back(EnumName); | |
| continue; | |
| } | |
| // Don't add empty sequences to the table. This slightly simplifies | |
| // the implementation and makes the output less confusing if the table | |
| // ends up empty. | |
| if (Constraints.empty()) | |
| continue; | |
| for (const SDTypeConstraint &C : Constraints) { | |
| if (C.ConstraintType == SDTypeConstraint::SDTCisVT || | |
| C.ConstraintType == SDTypeConstraint::SDTCVecEltisVT) { | |
| if (!C.VVT.isSimple()) { | |
| assert(!C.VVT.empty() && "Unexpected empty type set"); | |
| VTByHwModeTable.try_emplace(C.VVT); | |
| } | |
| } | |
| } | |
| // SequenceToOffsetTable reuses the storage if a sequence matches another | |
| // sequence's *suffix*. It is more likely that we have a matching *prefix*, | |
| // so reverse the order to increase the likelihood of a match. | |
| ConstraintTable.add(ConstraintsVecTy(reverse(Constraints))); | |
| } | |
| ConstraintTable.layout(); | |
| OS << "static const VTByHwModePair " << Target.getName() | |
| << "VTByHwModeTable[] = {\n"; | |
| unsigned VTByHwModeOffset = 0; | |
| for (auto &[VTByHwMode, Offset] : VTByHwModeTable) { | |
| OS << " /* " << VTByHwModeOffset << " */ "; | |
| for (auto [Mode, VT] : VTByHwMode) | |
| OS << '{' << Mode << ", " << getEnumName(VT.SimpleTy) << "}, "; | |
| OS << '\n'; | |
| Offset = VTByHwModeOffset; | |
| VTByHwModeOffset += VTByHwMode.size(); | |
| } | |
| // Avoid "zero size arrays are an extension" warning. | |
| if (VTByHwModeTable.empty()) | |
| OS << " /* dummy */ {0, MVT::INVALID_SIMPLE_VALUE_TYPE}\n"; | |
| OS << "};\n\n"; | |
| OS << "static const SDTypeConstraint " << Target.getName() | |
| << "SDTypeConstraints[] = {\n"; | |
| ConstraintTable.emit(OS, std::bind(emitTypeConstraint, std::placeholders::_1, | |
| std::placeholders::_2, VTByHwModeTable)); | |
| OS << "};\n\n"; | |
| for (const auto &[EnumName, Nodes] : NodesByName) { | |
| ArrayRef<SDTypeConstraint> Constraints = Nodes.front().getTypeConstraints(); | |
| if (Constraints.empty() || is_contained(SkippedNodes, EnumName)) { | |
| ConstraintOffsetsAndCounts.emplace_back(/*Offset=*/0, /*Size=*/0); | |
| continue; | |
| } | |
| unsigned ConstraintsOffset = | |
| ConstraintTable.get(ConstraintsVecTy(reverse(Constraints))); | |
| ConstraintOffsetsAndCounts.emplace_back(ConstraintsOffset, | |
| Constraints.size()); | |
| } | |
| return ConstraintOffsetsAndCounts; | |
| } | |
| static void emitDesc(raw_ostream &OS, StringRef EnumName, | |
| ArrayRef<SDNodeInfo> Nodes, unsigned NameOffset, | |
| unsigned ConstraintsOffset, unsigned ConstraintCount) { | |
| assert(haveCompatibleDescriptions(Nodes)); | |
| const SDNodeInfo &N = Nodes.front(); | |
| OS << " {" << N.getNumResults() << ", " << N.getNumOperands() << ", 0"; | |
| // Emitted properties must be kept in sync with haveCompatibleDescriptions. | |
| unsigned Properties = N.getProperties(); | |
| if (Properties & (1 << SDNPHasChain)) | |
| OS << "|1<<SDNPHasChain"; | |
| if (Properties & (1 << SDNPOutGlue)) | |
| OS << "|1<<SDNPOutGlue"; | |
| if (Properties & (1 << SDNPInGlue)) | |
| OS << "|1<<SDNPInGlue"; | |
| if (Properties & (1 << SDNPOptInGlue)) | |
| OS << "|1<<SDNPOptInGlue"; | |
| if (Properties & (1 << SDNPVariadic)) | |
| OS << "|1<<SDNPVariadic"; | |
| if (Properties & (1 << SDNPMemOperand)) | |
| OS << "|1<<SDNPMemOperand"; | |
| OS << ", 0"; | |
| if (N.isStrictFP()) | |
| OS << "|1<<SDNFIsStrictFP"; | |
| OS << formatv(", {}, {}, {}, {}}, // {}\n", N.getTSFlags(), NameOffset, | |
| ConstraintsOffset, ConstraintCount, EnumName); | |
| } | |
| void SDNodeInfoEmitter::emitDescs(raw_ostream &OS) const { | |
| StringRef TargetName = Target.getName(); | |
| IfDefEmitter IfDef(OS, "GET_SDNODE_DESC"); | |
| NamespaceEmitter NS(OS, "llvm"); | |
| std::vector<unsigned> NameOffsets = emitNodeNames(OS); | |
| std::vector<std::pair<unsigned, unsigned>> ConstraintOffsetsAndCounts = | |
| emitTypeConstraints(OS); | |
| OS << "static const SDNodeDesc " << TargetName << "SDNodeDescs[] = {\n"; | |
| for (const auto &[NameAndNodes, NameOffset, ConstraintOffsetAndCount] : | |
| zip_equal(NodesByName, NameOffsets, ConstraintOffsetsAndCounts)) | |
| emitDesc(OS, NameAndNodes.first, NameAndNodes.second, NameOffset, | |
| ConstraintOffsetAndCount.first, ConstraintOffsetAndCount.second); | |
| OS << "};\n\n"; | |
| OS << formatv("static const SDNodeInfo {0}GenSDNodeInfo(\n" | |
| " /*NumOpcodes=*/{1}, {0}SDNodeDescs, {0}SDNodeNames,\n" | |
| " {0}VTByHwModeTable, {0}SDTypeConstraints);\n", | |
| TargetName, NodesByName.size()); | |
| } | |
| void SDNodeInfoEmitter::run(raw_ostream &OS) const { | |
| emitSourceFileHeader("Target SDNode descriptions", OS, RK); | |
| emitEnum(OS); | |
| emitDescs(OS); | |
| } | |
| static TableGen::Emitter::OptClass<SDNodeInfoEmitter> | |
| X("gen-sd-node-info", "Generate target SDNode descriptions"); |