[llvm] [SelectionDAG] Verify SDTCisVT and SDTCVecEltisVT constraints (PR #150125)

Sergei Barannikov via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 22 18:35:21 PDT 2025


https://github.com/s-barannikov updated https://github.com/llvm/llvm-project/pull/150125

>From 50a82224c26055fc5726ca8cd40132442710dbd6 Mon Sep 17 00:00:00 2001
From: Sergei Barannikov <barannikov88 at gmail.com>
Date: Wed, 23 Jul 2025 01:06:57 +0300
Subject: [PATCH] [SelectionDAG] Verify SDTCisVT and SDTCVecEltisVT constraints

---
 llvm/include/llvm/CodeGen/SDNodeInfo.h        |  18 ++-
 llvm/lib/CodeGen/SelectionDAG/SDNodeInfo.cpp  | 105 ++++++++++++++++++
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |   2 +-
 .../AArch64/AArch64SelectionDAGInfo.cpp       |  44 +++++---
 llvm/lib/Target/M68k/M68kSelectionDAGInfo.cpp |  15 +++
 llvm/lib/Target/M68k/M68kSelectionDAGInfo.h   |   3 +
 .../Target/RISCV/RISCVSelectionDAGInfo.cpp    |  19 +---
 llvm/lib/Target/Sparc/SparcInstrInfo.td       |   2 +-
 .../ambiguous-constraints.td                  |  12 +-
 llvm/test/TableGen/SDNodeInfoEmitter/basic.td |  58 ++++++----
 .../TableGen/SDNodeInfoEmitter/namespace.td   |  18 +--
 .../SDNodeInfoEmitter/skipped-nodes.td        |   6 +-
 .../TableGen/Basic/SequenceToOffsetTable.h    |   3 +-
 llvm/utils/TableGen/Common/InfoByHwMode.h     |   2 +
 llvm/utils/TableGen/SDNodeInfoEmitter.cpp     |  72 ++++++++++--
 15 files changed, 295 insertions(+), 84 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/SDNodeInfo.h b/llvm/include/llvm/CodeGen/SDNodeInfo.h
index ba6c343ee1838..a1954645f210e 100644
--- a/llvm/include/llvm/CodeGen/SDNodeInfo.h
+++ b/llvm/include/llvm/CodeGen/SDNodeInfo.h
@@ -48,11 +48,21 @@ enum SDNF {
   SDNFIsStrictFP,
 };
 
+struct VTByHwModePair {
+  uint8_t Mode;
+  MVT::SimpleValueType VT;
+};
+
 struct SDTypeConstraint {
   SDTC Kind;
   uint8_t OpNo;
   uint8_t OtherOpNo;
-  MVT::SimpleValueType VT;
+  /// For Kind == SDTCisVT or SDTCVecEltisVT:
+  /// - if not using HwMode, NumHwModes == 0 and VT is MVT::SimpleValueType;
+  /// - otherwise, VT is offset into VTByHwModeTable and NumHwModes specifies
+  ///   the number of entries.
+  uint8_t NumHwModes;
+  uint16_t VT;
 };
 
 using SDNodeTSFlags = uint32_t;
@@ -76,13 +86,15 @@ class SDNodeInfo final {
   unsigned NumOpcodes;
   const SDNodeDesc *Descs;
   StringTable Names;
+  const VTByHwModePair *VTByHwModeTable;
   const SDTypeConstraint *Constraints;
 
 public:
   constexpr SDNodeInfo(unsigned NumOpcodes, const SDNodeDesc *Descs,
-                       StringTable Names, const SDTypeConstraint *Constraints)
+                       StringTable Names, const VTByHwModePair *VTByHwModeTable,
+                       const SDTypeConstraint *Constraints)
       : NumOpcodes(NumOpcodes), Descs(Descs), Names(Names),
-        Constraints(Constraints) {}
+        VTByHwModeTable(VTByHwModeTable), Constraints(Constraints) {}
 
   /// Returns true if there is a generated description for a node with the given
   /// target-specific opcode.
diff --git a/llvm/lib/CodeGen/SelectionDAG/SDNodeInfo.cpp b/llvm/lib/CodeGen/SelectionDAG/SDNodeInfo.cpp
index e3f6c98a9a90a..fc2bc64dc39c8 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SDNodeInfo.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SDNodeInfo.cpp
@@ -7,7 +7,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/CodeGen/SDNodeInfo.h"
+#include "llvm/CodeGen/SelectionDAG.h"
 #include "llvm/CodeGen/SelectionDAGNodes.h"
+#include "llvm/CodeGen/TargetLowering.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
 
 using namespace llvm;
 
@@ -40,6 +43,26 @@ static void checkOperandType(const SelectionDAG &DAG, const SDNode *N,
             ExpectedVT.getEVTString() + ", got " + ActualVT.getEVTString());
 }
 
+namespace {
+
+struct ConstraintOp {
+  const SDNode *N;
+  unsigned Idx;
+  bool IsRes;
+
+  SDValue getValue() const {
+    return IsRes ? SDValue(const_cast<SDNode *>(N), Idx) : N->getOperand(Idx);
+  }
+
+  EVT getValueType() const { return getValue().getValueType(); }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const ConstraintOp &Info) {
+  return OS << (Info.IsRes ? "result" : "operand") << " #" << Info.Idx;
+}
+
+} // namespace
+
 void SDNodeInfo::verifyNode(const SelectionDAG &DAG, const SDNode *N) const {
   const SDNodeDesc &Desc = getDesc(N->getOpcode());
   bool HasChain = Desc.hasProperty(SDNPHasChain);
@@ -125,4 +148,86 @@ void SDNodeInfo::verifyNode(const SelectionDAG &DAG, const SDNode *N) const {
                             " must be Register or RegisterMask");
     }
   }
+
+  unsigned VTHwMode =
+      DAG.getSubtarget().getHwMode(MCSubtargetInfo::HwMode_ValueType);
+
+  auto GetConstraintOp = [&](unsigned Idx) {
+    if (Idx < Desc.NumResults)
+      return ConstraintOp{N, Idx, /*IsRes=*/true};
+    return ConstraintOp{N, HasChain + (Idx - Desc.NumResults), /*IsRes=*/false};
+  };
+
+  auto GetConstraintVT = [&](const SDTypeConstraint &C) {
+    if (!C.NumHwModes)
+      return static_cast<MVT::SimpleValueType>(C.VT);
+    for (auto [Mode, VT] : ArrayRef(&VTByHwModeTable[C.VT], C.NumHwModes))
+      if (Mode == VTHwMode)
+        return VT;
+    llvm_unreachable("No value type for this HW mode");
+  };
+
+  SmallString<128> ES;
+  raw_svector_ostream SS(ES);
+
+  for (const SDTypeConstraint &C : getConstraints(N->getOpcode())) {
+    ConstraintOp Op = GetConstraintOp(C.OpNo);
+    EVT OpVT = Op.getValue().getValueType();
+
+    switch (C.Kind) {
+    case SDTCisVT: {
+      EVT ExpectedVT = GetConstraintVT(C);
+
+      bool IsPtr = ExpectedVT == MVT::iPTR;
+      if (IsPtr)
+        ExpectedVT =
+            DAG.getTargetLoweringInfo().getPointerTy(DAG.getDataLayout());
+
+      if (OpVT != ExpectedVT) {
+        SS << Op << " must have type " << ExpectedVT;
+        if (IsPtr)
+          SS << " (iPTR)";
+        SS << ", but has type " << OpVT;
+        reportNodeError(DAG, N, SS.str());
+      }
+      break;
+    }
+    case SDTCisPtrTy:
+      break;
+    case SDTCisInt:
+      break;
+    case SDTCisFP:
+      break;
+    case SDTCisVec:
+      break;
+    case SDTCisSameAs:
+      break;
+    case SDTCisVTSmallerThanOp:
+      break;
+    case SDTCisOpSmallerThanOp:
+      break;
+    case SDTCisEltOfVec:
+      break;
+    case SDTCisSubVecOfVec:
+      break;
+    case SDTCVecEltisVT: {
+      EVT ExpectedVT = GetConstraintVT(C);
+
+      if (!OpVT.isVector()) {
+        SS << Op << " must have vector type";
+        reportNodeError(DAG, N, SS.str());
+      }
+      if (OpVT.getVectorElementType() != ExpectedVT) {
+        SS << Op << " must have " << ExpectedVT << " element type, but has "
+           << OpVT.getVectorElementType() << " element type";
+        reportNodeError(DAG, N, SS.str());
+      }
+      break;
+    }
+    case SDTCisSameNumEltsAs:
+      break;
+    case SDTCisSameSizeAs:
+      break;
+    }
+  }
 }
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 9f8a2571b076e..ed255924b0eef 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -1144,7 +1144,7 @@ def AArch64msrr : SDNode<"AArch64ISD::MSRR",
                                                SDTCisVT<2, i64>]>,
                           [SDNPHasChain]>;
 
-def SD_AArch64rshrnb : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisVec<1>, SDTCisInt<2>]>;
+def SD_AArch64rshrnb : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisVec<1>, SDTCisVT<2, i32>]>;
 // Vector narrowing shift by immediate (bottom)
 def AArch64rshrnb : SDNode<"AArch64ISD::RSHRNB_I", SD_AArch64rshrnb>;
 def AArch64rshrnb_pf : PatFrags<(ops node:$rs, node:$i),
diff --git a/llvm/lib/Target/AArch64/AArch64SelectionDAGInfo.cpp b/llvm/lib/Target/AArch64/AArch64SelectionDAGInfo.cpp
index bafb8d0773232..35d0d9b79ae33 100644
--- a/llvm/lib/Target/AArch64/AArch64SelectionDAGInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64SelectionDAGInfo.cpp
@@ -32,16 +32,41 @@ AArch64SelectionDAGInfo::AArch64SelectionDAGInfo()
 
 void AArch64SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
                                                const SDNode *N) const {
+  switch (N->getOpcode()) {
+  case AArch64ISD::ADC:
+  case AArch64ISD::SBC:
+  case AArch64ISD::ADCS:
+  case AArch64ISD::SBCS:
+    // operand #2 must have type i32, but has type glue
+    return;
+  case AArch64ISD::SUBS:
+    // result #1 must have type i32, but has type glue
+    return;
+  case AArch64ISD::CSEL:
+  case AArch64ISD::CSINC:
+  case AArch64ISD::BRCOND:
+    // operand #3 must have type i32, but has type glue
+    return;
+  case AArch64ISD::WrapperLarge:
+    // operand #0 must have type i32, but has type i64
+    return;
+  case AArch64ISD::LDNP:
+    // result #0 must have type v4i32, but has type v2f64
+    return;
+  case AArch64ISD::STNP:
+    // operand #1 must have type v4i32, but has type v2i64
+    return;
+  }
+
+  SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
+
 #ifndef NDEBUG
+  // Some additional checks not yet implemented by verifyTargetNode.
   switch (N->getOpcode()) {
-  default:
-    return SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
   case AArch64ISD::SADDWT:
   case AArch64ISD::SADDWB:
   case AArch64ISD::UADDWT:
   case AArch64ISD::UADDWB: {
-    assert(N->getNumValues() == 1 && "Expected one result!");
-    assert(N->getNumOperands() == 2 && "Expected two operands!");
     EVT VT = N->getValueType(0);
     EVT Op0VT = N->getOperand(0).getValueType();
     EVT Op1VT = N->getOperand(1).getValueType();
@@ -61,8 +86,6 @@ void AArch64SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
   case AArch64ISD::SUNPKHI:
   case AArch64ISD::UUNPKLO:
   case AArch64ISD::UUNPKHI: {
-    assert(N->getNumValues() == 1 && "Expected one result!");
-    assert(N->getNumOperands() == 1 && "Expected one operand!");
     EVT VT = N->getValueType(0);
     EVT OpVT = N->getOperand(0).getValueType();
     assert(OpVT.isVector() && VT.isVector() && OpVT.isInteger() &&
@@ -79,8 +102,6 @@ void AArch64SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
   case AArch64ISD::UZP2:
   case AArch64ISD::ZIP1:
   case AArch64ISD::ZIP2: {
-    assert(N->getNumValues() == 1 && "Expected one result!");
-    assert(N->getNumOperands() == 2 && "Expected two operands!");
     EVT VT = N->getValueType(0);
     EVT Op0VT = N->getOperand(0).getValueType();
     EVT Op1VT = N->getOperand(1).getValueType();
@@ -90,11 +111,8 @@ void AArch64SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
     break;
   }
   case AArch64ISD::RSHRNB_I: {
-    assert(N->getNumValues() == 1 && "Expected one result!");
-    assert(N->getNumOperands() == 2 && "Expected two operands!");
     EVT VT = N->getValueType(0);
     EVT Op0VT = N->getOperand(0).getValueType();
-    EVT Op1VT = N->getOperand(1).getValueType();
     assert(VT.isVector() && VT.isInteger() &&
            "Expected integer vector result type!");
     assert(Op0VT.isVector() && Op0VT.isInteger() &&
@@ -103,8 +121,8 @@ void AArch64SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
            "Expected vectors of equal size!");
     assert(VT.getVectorElementCount() == Op0VT.getVectorElementCount() * 2 &&
            "Expected input vector with half the lanes of its result!");
-    assert(Op1VT == MVT::i32 && isa<ConstantSDNode>(N->getOperand(1)) &&
-           "Expected second operand to be a constant i32!");
+    assert(isa<ConstantSDNode>(N->getOperand(1)) &&
+           "Expected second operand to be a constant!");
     break;
   }
   }
diff --git a/llvm/lib/Target/M68k/M68kSelectionDAGInfo.cpp b/llvm/lib/Target/M68k/M68kSelectionDAGInfo.cpp
index dd1bfdf00af8c..a402c7721129c 100644
--- a/llvm/lib/Target/M68k/M68kSelectionDAGInfo.cpp
+++ b/llvm/lib/Target/M68k/M68kSelectionDAGInfo.cpp
@@ -16,4 +16,19 @@ using namespace llvm;
 M68kSelectionDAGInfo::M68kSelectionDAGInfo()
     : SelectionDAGGenTargetInfo(M68kGenSDNodeInfo) {}
 
+void M68kSelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
+                                            const SDNode *N) const {
+  switch (N->getOpcode()) {
+  case M68kISD::ADD:
+  case M68kISD::SUBX:
+    // result #1 must have type i8, but has type i32
+    return;
+  case M68kISD::SETCC:
+    // operand #1 must have type i8, but has type i32
+    return;
+  }
+
+  SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
+}
+
 M68kSelectionDAGInfo::~M68kSelectionDAGInfo() = default;
diff --git a/llvm/lib/Target/M68k/M68kSelectionDAGInfo.h b/llvm/lib/Target/M68k/M68kSelectionDAGInfo.h
index 87a8c08d2591e..de4667f830d41 100644
--- a/llvm/lib/Target/M68k/M68kSelectionDAGInfo.h
+++ b/llvm/lib/Target/M68k/M68kSelectionDAGInfo.h
@@ -21,6 +21,9 @@ class M68kSelectionDAGInfo : public SelectionDAGGenTargetInfo {
   M68kSelectionDAGInfo();
 
   ~M68kSelectionDAGInfo() override;
+
+  void verifyTargetNode(const SelectionDAG &DAG,
+                        const SDNode *N) const override;
 };
 
 } // namespace llvm
diff --git a/llvm/lib/Target/RISCV/RISCVSelectionDAGInfo.cpp b/llvm/lib/Target/RISCV/RISCVSelectionDAGInfo.cpp
index 6ecddad72c078..26a98637d0560 100644
--- a/llvm/lib/Target/RISCV/RISCVSelectionDAGInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVSelectionDAGInfo.cpp
@@ -20,27 +20,22 @@ RISCVSelectionDAGInfo::~RISCVSelectionDAGInfo() = default;
 
 void RISCVSelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
                                              const SDNode *N) const {
+  SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
+
 #ifndef NDEBUG
+  // Some additional checks not yet implemented by verifyTargetNode.
   switch (N->getOpcode()) {
-  default:
-    return SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
   case RISCVISD::TUPLE_EXTRACT:
-    assert(N->getNumOperands() == 2 && "Expected three operands!");
     assert(N->getOperand(1).getOpcode() == ISD::TargetConstant &&
-           N->getOperand(1).getValueType() == MVT::i32 &&
-           "Expected index to be an i32 target constant!");
+           "Expected index to be a target constant!");
     break;
   case RISCVISD::TUPLE_INSERT:
-    assert(N->getNumOperands() == 3 && "Expected three operands!");
     assert(N->getOperand(2).getOpcode() == ISD::TargetConstant &&
-           N->getOperand(2).getValueType() == MVT::i32 &&
-           "Expected index to be an i32 target constant!");
+           "Expected index to be a target constant!");
     break;
   case RISCVISD::VQDOT_VL:
   case RISCVISD::VQDOTU_VL:
   case RISCVISD::VQDOTSU_VL: {
-    assert(N->getNumValues() == 1 && "Expected one result!");
-    assert(N->getNumOperands() == 5 && "Expected five operands!");
     EVT VT = N->getValueType(0);
     assert(VT.isScalableVector() && VT.getVectorElementType() == MVT::i32 &&
            "Expected result to be an i32 scalable vector");
@@ -50,13 +45,9 @@ void RISCVSelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
            "Expected result and first 3 operands to have the same type!");
     EVT MaskVT = N->getOperand(3).getValueType();
     assert(MaskVT.isScalableVector() &&
-           MaskVT.getVectorElementType() == MVT::i1 &&
            MaskVT.getVectorElementCount() == VT.getVectorElementCount() &&
            "Expected mask VT to be an i1 scalable vector with same number of "
            "elements as the result");
-    assert((N->getOperand(4).getValueType() == MVT::i32 ||
-            N->getOperand(4).getValueType() == MVT::i64) &&
-           "Expect VL operand to be i32 or i64");
     break;
   }
   }
diff --git a/llvm/lib/Target/Sparc/SparcInstrInfo.td b/llvm/lib/Target/Sparc/SparcInstrInfo.td
index 1a32eafb0e83d..46181b3f2dd33 100644
--- a/llvm/lib/Target/Sparc/SparcInstrInfo.td
+++ b/llvm/lib/Target/Sparc/SparcInstrInfo.td
@@ -352,7 +352,7 @@ def callseq_start : SDNode<"ISD::CALLSEQ_START", SDT_SPCallSeqStart,
 def callseq_end   : SDNode<"ISD::CALLSEQ_END",   SDT_SPCallSeqEnd,
                            [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue]>;
 
-def SDT_SPCall    : SDTypeProfile<0, -1, [SDTCisVT<0, i32>]>;
+def SDT_SPCall    : SDTypeProfile<0, -1, [SDTCisVT<0, iPTR>]>;
 def call          : SDNode<"SPISD::CALL", SDT_SPCall,
                            [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue,
                             SDNPVariadic]>;
diff --git a/llvm/test/TableGen/SDNodeInfoEmitter/ambiguous-constraints.td b/llvm/test/TableGen/SDNodeInfoEmitter/ambiguous-constraints.td
index c09e2198dbeba..43b5561f0c694 100644
--- a/llvm/test/TableGen/SDNodeInfoEmitter/ambiguous-constraints.td
+++ b/llvm/test/TableGen/SDNodeInfoEmitter/ambiguous-constraints.td
@@ -20,7 +20,7 @@ def my_node_b : SDNode<"MyTargetISD::NODE", SDTypeProfile<1, 0, [SDTCisVT<0, f32
 // CHECK-NEXT:    ;
 
 // CHECK:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -28,8 +28,8 @@ def my_node_b : SDNode<"MyTargetISD::NODE", SDTypeProfile<1, 0, [SDTCisVT<0, f32
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs,
-// CHECK-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:      MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
 
 
 //--- test2.td
@@ -62,7 +62,7 @@ def my_node_2b : SDNode<"MyTargetISD::NODE_2", SDTypeProfile<1, 0, [SDTCisVT<0,
 // CHECK-NEXT:    ;
 
 // CHECK:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* 0 */ {SDTCisVT, 0, 0, MVT::i32},
+// CHECK-NEXT:    /* 0 */ {SDTCisVT, 0, 0, 0, MVT::i32},
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -71,5 +71,5 @@ def my_node_2b : SDNode<"MyTargetISD::NODE_2", SDTypeProfile<1, 0, [SDTCisVT<0,
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:      /*NumOpcodes=*/2, MyTargetSDNodeDescs,
-// CHECK-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:      /*NumOpcodes=*/2, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:      MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
diff --git a/llvm/test/TableGen/SDNodeInfoEmitter/basic.td b/llvm/test/TableGen/SDNodeInfoEmitter/basic.td
index 2b4c76a0d4543..bd8d927ce5655 100644
--- a/llvm/test/TableGen/SDNodeInfoEmitter/basic.td
+++ b/llvm/test/TableGen/SDNodeInfoEmitter/basic.td
@@ -38,16 +38,20 @@ def MyTarget : Target;
 // CHECK-NEXT:  static constexpr llvm::StringTable
 // CHECK-NEXT:  MyTargetSDNodeNames = MyTargetSDNodeNamesStorage;
 // CHECK-EMPTY:
+// CHECK-NEXT:  static const VTByHwModePair MyTargetVTByHwModeTable[] = {
+// CHECK-NEXT:    /* dummy */ {0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:  };
+// CHECK-EMPTY:
 // CHECK-NEXT:  static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:      /*NumOpcodes=*/0, MyTargetSDNodeDescs,
-// CHECK-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:      /*NumOpcodes=*/0, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:      MyTargetSDTypeConstraints);
 // CHECK-EMPTY:
 // CHECK-NEXT:  } // namespace llvm
 // CHECK-EMPTY:
@@ -79,8 +83,12 @@ def my_noop : SDNode<"MyTargetISD::NOOP", SDTypeProfile<0, 0, []>>;
 // CHECK-NEXT:    "MyTargetISD::NOOP\0"
 // CHECK-NEXT:    ;
 
+// CHECK:       static const VTByHwModePair MyTargetVTByHwModeTable[] = {
+// CHECK-NEXT:    /* dummy */ {0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:  };
+
 // CHECK:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -88,8 +96,8 @@ def my_noop : SDNode<"MyTargetISD::NOOP", SDTypeProfile<0, 0, []>>;
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs,
-// CHECK-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:      MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
 
 //--- advanced.td
 // RUN: llvm-tblgen -gen-sd-node-info -I %p/../../../include %t/advanced.td \
@@ -160,22 +168,26 @@ def my_node_3 : SDNode<
 // CHECK-NEXT:    "MyTargetISD::NODE_3\0"
 // CHECK-NEXT:    ;
 
+// CHECK:       static const VTByHwModePair MyTargetVTByHwModeTable[] = {
+// CHECK-NEXT:    /* dummy */ {0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:  };
+
 // CHECK:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* 0 */ {SDTCisVT, 1, 0, MVT::i2},
-// CHECK-SAME:            {SDTCisVT, 0, 0, MVT::i1},
-// CHECK-NEXT:    /* 2 */ {SDTCisSameSizeAs, 19, 18, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisSameNumEltsAs, 17, 16, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCVecEltisVT, 15, 0, MVT::i32},
-// CHECK-SAME:            {SDTCisSubVecOfVec, 14, 13, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisEltOfVec, 12, 11, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisOpSmallerThanOp, 10, 9, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisVTSmallerThanOp, 8, 7, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisSameAs, 6, 5, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisVec, 4, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisFP, 3, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisInt, 2, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisPtrTy, 1, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
-// CHECK-SAME:            {SDTCisVT, 0, 0, MVT::i1},
+// CHECK-NEXT:    /* 0 */ {SDTCisVT, 1, 0, 0, MVT::i2},
+// CHECK-SAME:            {SDTCisVT, 0, 0, 0, MVT::i1},
+// CHECK-NEXT:    /* 2 */ {SDTCisSameSizeAs, 19, 18, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisSameNumEltsAs, 17, 16, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCVecEltisVT, 15, 0, 0, MVT::i32},
+// CHECK-SAME:            {SDTCisSubVecOfVec, 14, 13, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisEltOfVec, 12, 11, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisOpSmallerThanOp, 10, 9, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisVTSmallerThanOp, 8, 7, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisSameAs, 6, 5, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisVec, 4, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisFP, 3, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisInt, 2, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisPtrTy, 1, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE},
+// CHECK-SAME:            {SDTCisVT, 0, 0, 0, MVT::i1},
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -185,5 +197,5 @@ def my_node_3 : SDNode<
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:     /*NumOpcodes=*/3, MyTargetSDNodeDescs,
-// CHECK-NEXT:     MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:     /*NumOpcodes=*/3, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:     MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
diff --git a/llvm/test/TableGen/SDNodeInfoEmitter/namespace.td b/llvm/test/TableGen/SDNodeInfoEmitter/namespace.td
index 217fb7c9fd475..578016469fccd 100644
--- a/llvm/test/TableGen/SDNodeInfoEmitter/namespace.td
+++ b/llvm/test/TableGen/SDNodeInfoEmitter/namespace.td
@@ -24,15 +24,15 @@ def node_2 : SDNode<"MyCustomISD::NODE", SDTypeProfile<0, 1, [SDTCisVT<0, i2>]>>
 // EMPTY-NEXT:     ;
 
 // EMPTY:        static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// EMPTY-NEXT:     /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// EMPTY-NEXT:     /* dummy */ {SDTCisVT, 0, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
 // EMPTY-NEXT:   };
 // EMPTY-EMPTY:
 // EMPTY-NEXT:   static const SDNodeDesc MyTargetSDNodeDescs[] = {
 // EMPTY-NEXT:   };
 // EMPTY-EMPTY:
 // EMPTY-NEXT:   static const SDNodeInfo MyTargetGenSDNodeInfo(
-// EMPTY-NEXT:       /*NumOpcodes=*/0, MyTargetSDNodeDescs,
-// EMPTY-NEXT:       MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// EMPTY-NEXT:       /*NumOpcodes=*/0, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// EMPTY-NEXT:       MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
 
 // COMMON:       namespace llvm::[[NS]] {
 // COMMON-EMPTY:
@@ -49,9 +49,13 @@ def node_2 : SDNode<"MyCustomISD::NODE", SDTypeProfile<0, 1, [SDTCisVT<0, i2>]>>
 // COMMON-NEXT:    "[[NS]]::NODE\0"
 // COMMON-NEXT:    ;
 
+// COMMON:       static const VTByHwModePair MyTargetVTByHwModeTable[] = {
+// COMMON-NEXT:    /* dummy */ {0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// COMMON-NEXT:  };
+
 // COMMON:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// TARGET-NEXT:    /* 0 */ {SDTCisVT, 0, 0, MVT::i1},
-// CUSTOM-NEXT:    /* 0 */ {SDTCisVT, 0, 0, MVT::i2},
+// TARGET-NEXT:    /* 0 */ {SDTCisVT, 0, 0, 0, MVT::i1},
+// CUSTOM-NEXT:    /* 0 */ {SDTCisVT, 0, 0, 0, MVT::i2},
 // COMMON-NEXT:  };
 // COMMON-EMPTY:
 // COMMON-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -60,5 +64,5 @@ def node_2 : SDNode<"MyCustomISD::NODE", SDTypeProfile<0, 1, [SDTCisVT<0, i2>]>>
 // COMMON-NEXT:  };
 // COMMON-EMPTY:
 // COMMON-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// COMMON-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs,
-// COMMON-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// COMMON-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// COMMON-NEXT:      MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
diff --git a/llvm/test/TableGen/SDNodeInfoEmitter/skipped-nodes.td b/llvm/test/TableGen/SDNodeInfoEmitter/skipped-nodes.td
index abd6ad3bda3bc..f6c2d174f636b 100644
--- a/llvm/test/TableGen/SDNodeInfoEmitter/skipped-nodes.td
+++ b/llvm/test/TableGen/SDNodeInfoEmitter/skipped-nodes.td
@@ -75,7 +75,7 @@ def node_5b : SDNode<"MyTargetISD::NODE_5", SDTypeProfile<0, 0, []>, [SDNPHasCha
 // CHECK-NEXT:    ;
 
 // CHECK:       static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
-// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
+// CHECK-NEXT:    /* dummy */ {SDTCisVT, 0, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeDesc MyTargetSDNodeDescs[] = {
@@ -83,8 +83,8 @@ def node_5b : SDNode<"MyTargetISD::NODE_5", SDTypeProfile<0, 0, []>, [SDNPHasCha
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT:  static const SDNodeInfo MyTargetGenSDNodeInfo(
-// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs,
-// CHECK-NEXT:      MyTargetSDNodeNames, MyTargetSDTypeConstraints);
+// CHECK-NEXT:      /*NumOpcodes=*/1, MyTargetSDNodeDescs, MyTargetSDNodeNames,
+// CHECK-NEXT:      MyTargetVTByHwModeTable, MyTargetSDTypeConstraints);
 
 def compat_a : SDNode<"MyTargetISD::COMPAT", SDTypeProfile<1, -1, []>>;
 def compat_b : SDNode<"MyTargetISD::COMPAT", SDTypeProfile<1, -1, [SDTCisVT<0, untyped>]>>;
diff --git a/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h b/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
index 8da6fbef0672e..a1c4dc09d3212 100644
--- a/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
+++ b/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
@@ -162,7 +162,8 @@ class SequenceToOffsetTable {
 
   /// emit - Print out the table as the body of an array initializer.
   /// Use the Print function to print elements.
-  void emit(raw_ostream &OS, void (*Print)(raw_ostream &, ElemT)) const {
+  void emit(raw_ostream &OS,
+            function_ref<void(raw_ostream &, ElemT)> Print) const {
     assert(IsLaidOut && "Call layout() before emit()");
     for (const auto &[Seq, Offset] : Seqs) {
       OS << "  /* " << Offset << " */ ";
diff --git a/llvm/utils/TableGen/Common/InfoByHwMode.h b/llvm/utils/TableGen/Common/InfoByHwMode.h
index 7925599a98a0c..16c5a36c135e6 100644
--- a/llvm/utils/TableGen/Common/InfoByHwMode.h
+++ b/llvm/utils/TableGen/Common/InfoByHwMode.h
@@ -102,6 +102,8 @@ template <typename InfoT> struct InfoByHwMode {
   LLVM_ATTRIBUTE_ALWAYS_INLINE
   const_iterator end() const { return Map.end(); }
   LLVM_ATTRIBUTE_ALWAYS_INLINE
+  size_t size() const { return Map.size(); }
+  LLVM_ATTRIBUTE_ALWAYS_INLINE
   bool empty() const { return Map.empty(); }
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
diff --git a/llvm/utils/TableGen/SDNodeInfoEmitter.cpp b/llvm/utils/TableGen/SDNodeInfoEmitter.cpp
index 64f03dae83e7d..ce74e7d1ebca0 100644
--- a/llvm/utils/TableGen/SDNodeInfoEmitter.cpp
+++ b/llvm/utils/TableGen/SDNodeInfoEmitter.cpp
@@ -198,15 +198,29 @@ static StringRef getTypeConstraintKindName(SDTypeConstraint::KindTy Kind) {
 #undef CASE
 }
 
-static void emitTypeConstraint(raw_ostream &OS, SDTypeConstraint C) {
+static void emitTypeConstraint(
+    raw_ostream &OS, SDTypeConstraint C,
+    const std::map<ValueTypeByHwMode, unsigned> &VTByHwModeTable) {
   unsigned OtherOpNo = 0;
-  MVT VT;
+  unsigned NumHwModes = 0;
+  unsigned VTByHwModeOffset = 0;
+  MVT::SimpleValueType 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();
+    if (C.VVT.isSimple()) {
+      VT = C.VVT.getSimple().SimpleTy;
+    } else {
+      NumHwModes = C.VVT.size();
+      assert(NumHwModes && "Empty type set?");
+      VTByHwModeOffset = VTByHwModeTable.at(C.VVT);
+    }
     break;
   case SDTypeConstraint::SDTCisPtrTy:
   case SDTypeConstraint::SDTCisInt:
@@ -224,15 +238,22 @@ static void emitTypeConstraint(raw_ostream &OS, SDTypeConstraint C) {
     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);
+  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);
@@ -261,6 +282,16 @@ SDNodeInfoEmitter::emitTypeConstraints(raw_ostream &OS) const {
     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.
@@ -269,9 +300,26 @@ SDNodeInfoEmitter::emitTypeConstraints(raw_ostream &OS) const {
 
   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, emitTypeConstraint);
+  ConstraintTable.emit(OS, std::bind(emitTypeConstraint, std::placeholders::_1,
+                                     std::placeholders::_2, VTByHwModeTable));
   OS << "};\n\n";
 
   for (const auto &[EnumName, Nodes] : NodesByName) {
@@ -342,8 +390,8 @@ void SDNodeInfoEmitter::emitDescs(raw_ostream &OS) const {
   OS << "};\n\n";
 
   OS << formatv("static const SDNodeInfo {0}GenSDNodeInfo(\n"
-                "    /*NumOpcodes=*/{1}, {0}SDNodeDescs,\n"
-                "    {0}SDNodeNames, {0}SDTypeConstraints);\n\n",
+                "    /*NumOpcodes=*/{1}, {0}SDNodeDescs, {0}SDNodeNames,\n"
+                "    {0}VTByHwModeTable, {0}SDTypeConstraints);\n\n",
                 TargetName, NodesByName.size());
 
   OS << "} // namespace llvm\n\n";



More information about the llvm-commits mailing list