blob: 64f03dae83e7de44f61f439b1ec51c71e3d40215 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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/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 {
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<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) {
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<std::pair<unsigned, unsigned>>
SDNodeInfoEmitter::emitTypeConstraints(raw_ostream &OS) const {
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;
// 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<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();
OS << "#ifdef GET_SDNODE_DESC\n";
OS << "#undef GET_SDNODE_DESC\n\n";
OS << "namespace llvm {\n";
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,\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<SDNodeInfoEmitter>
X("gen-sd-node-info", "Generate target SDNode descriptions");