//===----------------------------------------------------------------------===// // // 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/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 TargetSDNodeNamespace( "sdnode-namespace", cl::cat(SDNodeInfoEmitterCat), cl::desc("Specify target SDNode namespace (default=ISD)")); static cl::opt 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> NodesByName; public: explicit SDNodeInfoEmitter(const RecordKeeper &RK); void run(raw_ostream &OS) const; private: void emitEnum(raw_ostream &OS) const; std::vector emitNodeNames(raw_ostream &OS) const; std::vector> 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 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 { OS << "#ifdef GET_SDNODE_ENUM\n"; OS << "#undef GET_SDNODE_ENUM\n\n"; OS << "namespace llvm::" << TargetSDNodeNamespace << " {\n\n"; 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\n"; } else { OS << "static constexpr unsigned GENERATED_OPCODE_END = " "ISD::BUILTIN_OP_END;\n\n"; } OS << "} // namespace llvm::" << TargetSDNodeNamespace << "\n\n"; OS << "#endif // GET_SDNODE_ENUM\n\n"; } std::vector SDNodeInfoEmitter::emitNodeNames(raw_ostream &OS) const { StringToOffsetTable NameTable; std::vector 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) { unsigned OtherOpNo = 0; MVT VT; switch (C.ConstraintType) { case SDTypeConstraint::SDTCisVT: case SDTypeConstraint::SDTCVecEltisVT: if (C.VVT.isSimple()) VT = C.VVT.getSimple(); 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; } StringRef KindName = getTypeConstraintKindName(C.ConstraintType); StringRef VTName = VT.SimpleTy == MVT::INVALID_SIMPLE_VALUE_TYPE ? "MVT::INVALID_SIMPLE_VALUE_TYPE" : getEnumName(VT.SimpleTy); OS << formatv("{{{}, {}, {}, {}}", KindName, C.OperandNo, OtherOpNo, VTName); } std::vector> SDNodeInfoEmitter::emitTypeConstraints(raw_ostream &OS) const { using ConstraintsVecTy = SmallVector; SequenceToOffsetTable ConstraintTable( /*Terminator=*/std::nullopt); std::vector> ConstraintOffsetsAndCounts; ConstraintOffsetsAndCounts.reserve(NodesByName.size()); SmallVector SkippedNodes; for (const auto &[EnumName, Nodes] : NodesByName) { ArrayRef 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; // 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 SDTypeConstraint " << Target.getName() << "SDTypeConstraints[] = {\n"; ConstraintTable.emit(OS, emitTypeConstraint); OS << "};\n\n"; for (const auto &[EnumName, Nodes] : NodesByName) { ArrayRef 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 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< NameOffsets = emitNodeNames(OS); std::vector> 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,\n" " {0}SDNodeNames, {0}SDTypeConstraints);\n\n", TargetName, NodesByName.size()); OS << "} // namespace llvm\n\n"; OS << "#endif // GET_SDNODE_DESC\n\n"; } void SDNodeInfoEmitter::run(raw_ostream &OS) const { emitSourceFileHeader("Target SDNode descriptions", OS, RK); emitEnum(OS); emitDescs(OS); } static TableGen::Emitter::OptClass X("gen-sd-node-info", "Generate target SDNode descriptions");