[llvm] [GlobalISel][TableGen] MIR Pattern Variadics (PR #100563)

Pierre van Houtryve via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 25 05:29:43 PDT 2024


https://github.com/Pierre-vh created https://github.com/llvm/llvm-project/pull/100563

Allow for matching & rewriting a variable number of arguments in an instructions.

Solves #87459

>From de9e0f1e7bf6cc616b1135b0aaf1cf924b916d44 Mon Sep 17 00:00:00 2001
From: pvanhout <pierre.vanhoutryve at amd.com>
Date: Thu, 25 Jul 2024 14:28:52 +0200
Subject: [PATCH] [GlobalISel][TableGen] MIR Pattern Variadics

Allow for matching & rewriting a variable number of arguments in an instructions.

Solves #87459
---
 llvm/docs/GlobalISel/MIRPatterns.rst          |  55 +++++-
 .../CodeGen/GlobalISel/GIMatchTableExecutor.h |  16 ++
 .../GlobalISel/GIMatchTableExecutorImpl.h     |  33 ++++
 .../include/llvm/Target/GlobalISel/Combine.td |  10 +
 .../match-table-variadics.td                  |  54 ++++-
 .../operand-types.td                          |  26 ++-
 .../typeof-errors.td                          |  14 +-
 .../variadic-errors.td                        |  75 +++++++
 .../TableGen/Common/GlobalISel/CodeExpander.h |   2 +-
 .../GlobalISel/GlobalISelMatchTable.cpp       |  65 +++---
 .../Common/GlobalISel/GlobalISelMatchTable.h  |  65 ++++--
 .../TableGen/Common/GlobalISel/Patterns.cpp   |  39 ++++
 .../TableGen/Common/GlobalISel/Patterns.h     |  28 ++-
 .../TableGen/GlobalISelCombinerEmitter.cpp    | 185 +++++++++++++-----
 llvm/utils/TableGen/GlobalISelEmitter.cpp     |   4 +-
 15 files changed, 567 insertions(+), 104 deletions(-)
 create mode 100644 llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td

diff --git a/llvm/docs/GlobalISel/MIRPatterns.rst b/llvm/docs/GlobalISel/MIRPatterns.rst
index 7e6d88683d491..abc23985a6fac 100644
--- a/llvm/docs/GlobalISel/MIRPatterns.rst
+++ b/llvm/docs/GlobalISel/MIRPatterns.rst
@@ -115,7 +115,7 @@ GITypeOf
 ``GITypeOf<"$x">`` is a ``GISpecialType`` that allows for the creation of a
 register or immediate with the same type as another (register) operand.
 
-Operand:
+Type Parameters:
 
 * An operand name as a string, prefixed by ``$``.
 
@@ -143,6 +143,57 @@ Semantics:
     (apply (G_FSUB $dst, $src, $tmp),
            (G_FNEG GITypeOf<"$dst">:$tmp, $src))>;
 
+GIVariadic
+~~~~~~~~~~
+
+``GIVariadic<>`` is a ``GISpecialType`` that allows for matching 1 or
+more operands remaining on an instruction.
+
+Type Parameters:
+
+* The minimum number of additional operands to match. Must be greater than zero.
+
+  * Default is 1.
+
+* The maximum number of additional operands to match. Must be strictly greater
+  than the minimum.
+
+  * 0 can be used to indicate there is no upper limit.
+  * Default is 0.
+
+Semantics:
+
+* ``GIVariadic<>`` operands can not be defs.
+* ``GIVariadic<>`` operands can only appear as the last operand in a 'match' pattern.
+* Each instance within a 'match' pattern must be uniquely named.
+* Re-using a ``GIVariadic<>`` operand in an 'apply' pattern will result in all
+  the matched operands being copied from the original instruction.
+* The min/max operands will result in the matcher checking that the number of operands
+  falls within that range.
+* ``GIVariadic<>`` operands can be used in C++ code within a rule, which will
+  result in the operand name being expanded to an iterator range containing all
+  the matched operands, similar to the one returned by ``MachineInstr::operands()``.
+
+.. code-block:: text
+
+  def build_vector_to_unmerge: GICombineRule <
+    (defs root:$root),
+    (match (G_BUILD_VECTOR $root, GIVariadic<>:$args),
+           [{ return checkBuildVectorToUnmerge(${args}); }]),
+    (apply (G_UNMERGE_VALUES $root, $args))
+  >;
+
+.. code-block:: text
+
+  // Will additionally check the number of operands is >= 3 and <= 5.
+  // ($root is one operand, then 2 to 4 variadic operands).
+  def build_vector_to_unmerge: GICombineRule <
+    (defs root:$root),
+    (match (G_BUILD_VECTOR $root, GIVariadic<2, 4>:$two_to_four),
+           [{ return checkBuildVectorToUnmerge(${two_to_four}); }]),
+    (apply (G_UNMERGE_VALUES $root, $two_to_four))
+  >;
+
 Builtin Operations
 ------------------
 
@@ -240,6 +291,8 @@ This a non-exhaustive list of known issues with MIR patterns at this time.
   match. e.g. if a pattern needs to work on both i32 and i64, you either
   need to leave it untyped and check the type in C++, or duplicate the
   pattern.
+* ``GISpecialType`` operands are not allowed within a ``GICombinePatFrag``.
+* ``GIVariadic<>`` matched operands must each have a unique name.
 
 GICombineRule
 -------------
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
index cc2dd2f4e489c..d7b9c96f1c01e 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutor.h
@@ -133,6 +133,12 @@ enum {
   /// - Ops(ULEB128) - Expected number of operands
   GIM_CheckNumOperands,
 
+  /// Check the instruction has a number of operands <= or >= than given number.
+  /// - InsnID(ULEB128) - Instruction ID
+  /// - Ops(ULEB128) - Number of operands
+  GIM_CheckNumOperandsLE,
+  GIM_CheckNumOperandsGE,
+
   /// Check an immediate predicate on the specified instruction
   /// - InsnID(ULEB128) - Instruction ID
   /// - Pred(2) - The predicate to test
@@ -294,12 +300,15 @@ enum {
   /// Check the specified operands are identical.
   /// The IgnoreCopies variant looks through COPY instructions before
   /// comparing the operands.
+  /// The "All" variants check all operands starting from the index.
   /// - InsnID(ULEB128) - Instruction ID
   /// - OpIdx(ULEB128) - Operand index
   /// - OtherInsnID(ULEB128) - Other instruction ID
   /// - OtherOpIdx(ULEB128) - Other operand index
   GIM_CheckIsSameOperand,
   GIM_CheckIsSameOperandIgnoreCopies,
+  GIM_CheckAllSameOperand,
+  GIM_CheckAllSameOperandIgnoreCopies,
 
   /// Check we can replace all uses of a register with another.
   /// - OldInsnID(ULEB128)
@@ -362,6 +371,13 @@ enum {
   /// GIR_Copy but with both New/OldInsnIDs omitted and defaulting to zero.
   GIR_RootToRootCopy,
 
+  /// Copies all operand starting from OpIdx in OldInsnID into the new
+  /// instruction NewInsnID.
+  /// - NewInsnID(ULEB128) - Instruction ID to modify
+  /// - OldInsnID(ULEB128) - Instruction ID to copy from
+  /// - OpIdx(ULEB128) - The first operand to copy
+  GIR_CopyRemaining,
+
   /// Copy an operand to the specified instruction or add a zero register if the
   /// operand is a zero immediate.
   /// - NewInsnID(ULEB128) - Instruction ID to modify
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
index 90b4fe5518c87..5a5a750ac6b4a 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h
@@ -309,6 +309,23 @@ bool GIMatchTableExecutor::executeMatchTable(
       break;
     }
 
+    case GIM_CheckNumOperandsGE:
+    case GIM_CheckNumOperandsLE: {
+      uint64_t InsnID = readULEB();
+      uint64_t Expected = readULEB();
+      const bool IsLE = (MatcherOpcode == GIM_CheckNumOperandsLE);
+      DEBUG_WITH_TYPE(TgtExecutor::getName(),
+                      dbgs() << CurrentIdx << ": GIM_CheckNumOperands"
+                             << (IsLE ? "LE" : "GE") << "(MIs[" << InsnID
+                             << "], Expected=" << Expected << ")\n");
+      assert(State.MIs[InsnID] != nullptr && "Used insn before defined");
+      const unsigned NumOps = State.MIs[InsnID]->getNumOperands();
+      if (IsLE ? (NumOps <= Expected) : (NumOps >= Expected)) {
+        if (handleReject() == RejectAndGiveUp)
+          return false;
+      }
+      break;
+    }
     case GIM_CheckNumOperands: {
       uint64_t InsnID = readULEB();
       uint64_t Expected = readULEB();
@@ -1081,6 +1098,22 @@ bool GIMatchTableExecutor::executeMatchTable(
       break;
     }
 
+    case GIR_CopyRemaining: {
+      uint64_t NewInsnID = readULEB();
+      uint64_t OldInsnID = readULEB();
+      uint64_t OpIdx = readULEB();
+      assert(OutMIs[NewInsnID] && "Attempted to add to undefined instruction");
+      MachineInstr &OldMI = *State.MIs[OldInsnID];
+      MachineInstrBuilder &NewMI = OutMIs[NewInsnID];
+      for (const auto &Op : drop_begin(OldMI.operands(), OpIdx))
+        NewMI.add(Op);
+      DEBUG_WITH_TYPE(TgtExecutor::getName(),
+                      dbgs() << CurrentIdx << ": GIR_CopyRemaining(OutMIs["
+                             << NewInsnID << "], MIs[" << OldInsnID
+                             << "], /*start=*/" << OpIdx << ")\n");
+      break;
+    }
+
     case GIR_CopyOrAddZeroReg: {
       uint64_t NewInsnID = readULEB();
       uint64_t OldInsnID = readULEB();
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index 3ef0636ebf1c7..5c5ad1f6077a6 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -128,6 +128,16 @@ class GITypeOf<string opName> : GISpecialType {
   string OpName = opName;
 }
 
+// The type of an operand that can match a variable amount of operands.
+// This type contains a minimum and maximum number of operands to match.
+// The minimum must be 1 or more, as we cannot have an operand representing
+// zero operands, and the max can be zero (which means "unlimited") or a value
+// greater than the minimum.
+class GIVariadic<int min = 1, int max = 0> : GISpecialType {
+  int MinArgs = min;
+  int MaxArgs = max;
+}
+
 //===----------------------------------------------------------------------===//
 // Pattern Builtins
 //===----------------------------------------------------------------------===//
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
index 86ae031caecb5..e3061e2f4d3e5 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-variadics.td
@@ -28,19 +28,31 @@ def InstTest3 : GICombineRule<
   (match (G_UNMERGE_VALUES $a, $b, $c, $d)),
   (apply [{ APPLY }])>;
 
+def VariadicTypeTestCxx : GICombineRule<
+  (defs root:$a),
+  (match (G_BUILD_VECTOR $a, GIVariadic<2, 4>:$b)),
+  (apply [{ ${b} }])>;
+
+def VariadicTypeTestReuse : GICombineRule<
+  (defs root:$a),
+  (match (G_BUILD_VECTOR $a, $c, GIVariadic<2, 4>:$b)),
+  (apply (G_MERGE_VALUES $a, $b, $c))>;
+
 def MyCombiner: GICombiner<"GenMyCombiner", [
   InstTest0,
   InstTest1,
   InstTest2,
-  InstTest3
+  InstTest3,
+  VariadicTypeTestCxx,
+  VariadicTypeTestReuse
 ]>;
 
 // CHECK:      const uint8_t *GenMyCombiner::getMatchTable() const {
 // CHECK-NEXT:   constexpr static uint8_t MatchTable0[] = {
-// CHECK-NEXT:     GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2([[#LOWER:]]), GIMT_Encode2([[#UPPER:]]), /*)*//*default:*//*Label 2*/ GIMT_Encode4([[#DEFAULT:]]),
+// CHECK-NEXT:     GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2(70), GIMT_Encode2(74), /*)*//*default:*//*Label 2*/ GIMT_Encode4(127),
 // CHECK-NEXT:     /*TargetOpcode::G_UNMERGE_VALUES*//*Label 0*/ GIMT_Encode4(26), GIMT_Encode4(0), GIMT_Encode4(0),
 // CHECK-NEXT:     /*TargetOpcode::G_BUILD_VECTOR*//*Label 1*/ GIMT_Encode4(55),
-// CHECK-NEXT:     // Label 0: @[[#%u, mul(UPPER-LOWER, 4) + 10]]
+// CHECK-NEXT:     // Label 0: @26
 // CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 3*/ GIMT_Encode4(40), // Rule ID 2 //
 // CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule2Enabled),
 // CHECK-NEXT:       GIM_CheckNumOperands, /*MI*/0, /*Expected*/2,
@@ -77,7 +89,35 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
 // CHECK-NEXT:       // Combiner Rule #1: InstTest1
 // CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
 // CHECK-NEXT:     // Label 5: @69
-// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4(83), // Rule ID 0 //
+// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4(86), // Rule ID 4 //
+// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule4Enabled),
+// CHECK-NEXT:       GIM_CheckNumOperandsGE, /*MI*/0, /*Expected*/3,
+// CHECK-NEXT:       GIM_CheckNumOperandsLE, /*MI*/0, /*Expected*/5,
+// CHECK-NEXT:       // MIs[0] a
+// CHECK-NEXT:       // No operand predicates
+// CHECK-NEXT:       // MIs[0] b
+// CHECK-NEXT:       // No operand predicates
+// CHECK-NEXT:       // Combiner Rule #4: VariadicTypeTestCxx
+// CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner1),
+// CHECK-NEXT:     // Label 6: @86
+// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 7*/ GIMT_Encode4(112), // Rule ID 5 //
+// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule5Enabled),
+// CHECK-NEXT:       GIM_CheckNumOperandsGE, /*MI*/0, /*Expected*/4,
+// CHECK-NEXT:       GIM_CheckNumOperandsLE, /*MI*/0, /*Expected*/6,
+// CHECK-NEXT:       // MIs[0] a
+// CHECK-NEXT:       // No operand predicates
+// CHECK-NEXT:       // MIs[0] c
+// CHECK-NEXT:       // No operand predicates
+// CHECK-NEXT:       // MIs[0] b
+// CHECK-NEXT:       // No operand predicates
+// CHECK-NEXT:       // Combiner Rule #5: VariadicTypeTestReuse
+// CHECK-NEXT:       GIR_BuildRootMI, /*Opcode*/GIMT_Encode2(TargetOpcode::G_MERGE_VALUES),
+// CHECK-NEXT:       GIR_RootToRootCopy, /*OpIdx*/0, // a
+// CHECK-NEXT:       GIR_CopyRemaining, /*NewInsnID*/0, /*OldInsnID*/0, /*OpIdx*/2, // b
+// CHECK-NEXT:       GIR_RootToRootCopy, /*OpIdx*/1, // c
+// CHECK-NEXT:       GIR_EraseRootFromParent_Done,
+// CHECK-NEXT:     // Label 7: @112
+// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 8*/ GIMT_Encode4(126), // Rule ID 0 //
 // CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule0Enabled),
 // CHECK-NEXT:       GIM_CheckNumOperands, /*MI*/0, /*Expected*/4,
 // CHECK-NEXT:       // MIs[0] a
@@ -90,10 +130,10 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
 // CHECK-NEXT:       // No operand predicates
 // CHECK-NEXT:       // Combiner Rule #0: InstTest0
 // CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
-// CHECK-NEXT:     // Label 6: @83
+// CHECK-NEXT:     // Label 8: @126
 // CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     // Label 2: @[[#%u, DEFAULT]]
+// CHECK-NEXT:     // Label 2: @127
 // CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     }; // Size: [[#%u, DEFAULT + 1]] bytes
+// CHECK-NEXT:     }; // Size: 128 bytes
 // CHECK-NEXT:   return MatchTable0;
 // CHECK-NEXT: }
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
index 4769bed972401..9b5598661c8de 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/operand-types.td
@@ -104,8 +104,32 @@ def TypeOfProp : GICombineRule<
   (apply (G_ANYEXT $x, GITypeOf<"$y">:$tmp),
          (G_ANYEXT $tmp, $y))>;
 
+// CHECK:      (CombineRule name:VariadicTypeTest id:3 root:a
+// CHECK-NEXT:   (MatchPats
+// CHECK-NEXT:     <match_root>__VariadicTypeTest_match_0:(CodeGenInstructionPattern G_UNMERGE_VALUES operands:[<def>$a, <def>$b, GIVariadic<1,0>:$z])
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (ApplyPats
+// CHECK-NEXT:     <apply_root>__VariadicTypeTest_apply_0:(CodeGenInstructionPattern G_UNMERGE_VALUES operands:[<def>$a, <def>$b, GIVariadic<1,0>:$z])
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (OperandTable MatchPats
+// CHECK-NEXT:     a -> __VariadicTypeTest_match_0
+// CHECK-NEXT:     b -> __VariadicTypeTest_match_0
+// CHECK-NEXT:     z -> <live-in>
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (OperandTable ApplyPats
+// CHECK-NEXT:     a -> __VariadicTypeTest_apply_0
+// CHECK-NEXT:     b -> __VariadicTypeTest_apply_0
+// CHECK-NEXT:     z -> <live-in>
+// CHECK-NEXT:   )
+// CHECK-NEXT: )
+def VariadicTypeTest: GICombineRule<
+  (defs root:$a),
+  (match (G_UNMERGE_VALUES $a, $b, GIVariadic<>:$z)),
+  (apply (G_UNMERGE_VALUES $a, $b, $z))>;
+
 def MyCombiner: GICombiner<"GenMyCombiner", [
   InstTest0,
   PatFragTest0,
-  TypeOfProp
+  TypeOfProp,
+  VariadicTypeTest,
 ]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
index ee7b8f5f3a39c..900a4bb902f7c 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/typeof-errors.td
@@ -21,7 +21,7 @@ def UnknownOperand : GICombineRule<
   (match (G_ZEXT $dst, $src)),
   (apply (G_ANYEXT $dst, (GITypeOf<"$unknown"> 0)))>;
 
-// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GISpecialType is not supported in 'match' patterns
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GITypeOf is not supported in 'match' patterns
 // CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: operand 1 of '__UseInMatch_match_0' has type 'GITypeOf<$dst>'
 def UseInMatch : GICombineRule<
   (defs root:$dst),
@@ -41,7 +41,7 @@ def UseInPF: GICombineRule<
   (match (PFWithTypeOF $dst)),
   (apply (G_ANYEXT $dst, (i32 0)))>;
 
-// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GISpecialType is not supported in 'match' patterns
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: GITypeOf is not supported in 'match' patterns
 // CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: operand 1 of '__InferredUseInMatch_match_0' has type 'GITypeOf<$dst>'
 def InferredUseInMatch : GICombineRule<
   (defs root:$dst),
@@ -63,6 +63,13 @@ def TypeOfApplyTmp : GICombineRule<
   (apply (G_ANYEXT $dst, i32:$tmp),
          (G_ANYEXT $tmp, (GITypeOf<"$tmp"> 0)))>;
 
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: type 'GITypeOf<$src>' is ill-formed: 'src' is a variadic pack operand
+def TypeOfVariadic : GICombineRule<
+  (defs root:$dst),
+  (match (G_ZEXT $dst,  GIVariadic<>:$src)),
+  (apply (G_ANYEXT GITypeOf<"$src">:$tmp, $src),
+         (G_ANYEXT $dst, $tmp))>;
+
 // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse one or more rules
 def MyCombiner: GICombiner<"GenMyCombiner", [
   NoDollarSign,
@@ -71,5 +78,6 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
   UseInPF,
   InferredUseInMatch,
   InferenceConflict,
-  TypeOfApplyTmp
+  TypeOfApplyTmp,
+  TypeOfVariadic
 ]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td
new file mode 100644
index 0000000000000..68929c09da8d2
--- /dev/null
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/variadic-errors.td
@@ -0,0 +1,75 @@
+// RUN: not llvm-tblgen -I %p/../../../include -gen-global-isel-combiner \
+// RUN:     -combiners=MyCombiner %s 2>&1| \
+// RUN: FileCheck %s -implicit-check-not=error:
+
+include "llvm/Target/Target.td"
+include "llvm/Target/GlobalISel/Combine.td"
+
+def MyTargetISA : InstrInfo;
+def MyTarget : Target { let InstructionSet = MyTargetISA; }
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'G_BUILD_VECTOR': GIVariadic can only be used on the last operand
+def VariadicNotLastInList : GICombineRule<
+  (defs root:$dst),
+  (match (G_BUILD_VECTOR $dst, $a, GIVariadic<>:$b, $c)),
+  (apply (G_ANYEXT $dst, $a))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'G_IMPLICIT_DEF': GIVariadic cannot be used on defs
+def VariadicAsDef : GICombineRule<
+  (defs root:$dst),
+  (match (G_IMPLICIT_DEF GIVariadic<1>:$dst)),
+  (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: conflicting types for operand 'args': 'GIVariadic<2,4>' vs 'GIVariadic<3,6>'
+def ConflictingInference : GICombineRule<
+  (defs root:$dst),
+  (match (G_BUILD_VECTOR $dst, GIVariadic<2, 4>:$args)),
+  (apply (G_MERGE_VALUES $dst, GIVariadic<3, 6>:$args))>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: cannot parse operand type: minimum number of arguments must be greater than zero in GIVariadic
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(G_BUILD_VECTOR ?:$dst, anonymous_8021:$a)'
+def InvalidBounds0 : GICombineRule<
+  (defs root:$dst),
+  (match (G_BUILD_VECTOR $dst, GIVariadic<0>:$a)),
+  (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: cannot parse operand type: maximum number of arguments (1) must be zero, or greater than the minimum number of arguments (1) in GIVariadic
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(G_BUILD_VECTOR ?:$dst, anonymous_8022:$a)'
+def InvalidBounds1 : GICombineRule<
+  (defs root:$dst),
+  (match (G_BUILD_VECTOR $dst, GIVariadic<1,1>:$a)),
+  (apply [{ APPLY }])>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: each instance of a GIVariadic operand must have a unique name within the match patterns
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: note: 'c' is used multiple times
+def VariadicTypeTestEqOp : GICombineRule<
+  (defs root:$a),
+  (match (G_MERGE_VALUES $b, $c),
+         (G_BUILD_VECTOR $a, $b, GIVariadic<2, 4>:$c)),
+  (apply (G_MERGE_VALUES $a, $c))>;
+
+// TODO: We could support this if needed
+
+// CHECK: :[[@LINE+3]]:{{[0-9]+}}: error: GISpecialType is not supported in GICombinePatFrag
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: note: operand 1 of '__PFWithVariadic_alt0_pattern_0' has type 'GIVariadic<1,0
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Could not parse GICombinePatFrag 'PFWithVariadic'
+def PFWithVariadic: GICombinePatFrag<
+    (outs $dst), (ins),
+    [(pattern (G_ANYEXT $dst, GIVariadic<>:$b))]>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(PFWithVariadic ?:$dst)'
+def UseInPF: GICombineRule<
+  (defs root:$dst),
+  (match (PFWithVariadic $dst)),
+  (apply (G_ANYEXT $dst, (i32 0)))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse one or more rules
+def MyCombiner: GICombiner<"GenMyCombiner", [
+  VariadicNotLastInList,
+  VariadicAsDef,
+  ConflictingInference,
+  InvalidBounds0,
+  InvalidBounds1,
+  VariadicTypeTestEqOp,
+  UseInPF
+]>;
diff --git a/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h b/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
index 0b1e6ceab52c2..345da613f8435 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/CodeExpander.h
@@ -32,7 +32,7 @@ class raw_ostream;
 class CodeExpander {
   StringRef Code;
   const CodeExpansions &Expansions;
-  const ArrayRef<SMLoc> &Loc;
+  ArrayRef<SMLoc> Loc;
   bool ShowExpansions;
   StringRef Indent;
 
diff --git a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
index e011e78c67b15..d0dc515e83eb7 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
+++ b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.cpp
@@ -687,10 +687,6 @@ StringRef RuleMatcher::getOpcode() const {
   return Matchers.front()->getOpcode();
 }
 
-unsigned RuleMatcher::getNumOperands() const {
-  return Matchers.front()->getNumOperands();
-}
-
 LLTCodeGen RuleMatcher::getFirstConditionAsRootType() {
   InstructionMatcher &InsnMatcher = *Matchers.front();
   if (!InsnMatcher.predicates_empty())
@@ -1344,6 +1340,7 @@ std::string OperandMatcher::getOperandExpr(unsigned InsnVarID) const {
 unsigned OperandMatcher::getInsnVarID() const { return Insn.getInsnVarID(); }
 
 TempTypeIdx OperandMatcher::getTempTypeIdx(RuleMatcher &Rule) {
+  assert(!IsVariadic && "Cannot use this on variadic operands!");
   if (TTIdx >= 0) {
     // Temp type index not assigned yet, so assign one and add the necessary
     // predicate.
@@ -1506,8 +1503,20 @@ StringRef InstructionOpcodeMatcher::getOperandType(unsigned OpIdx) const {
 
 void InstructionNumOperandsMatcher::emitPredicateOpcodes(
     MatchTable &Table, RuleMatcher &Rule) const {
-  Table << MatchTable::Opcode("GIM_CheckNumOperands")
-        << MatchTable::Comment("MI") << MatchTable::ULEB128Value(InsnVarID)
+  StringRef Opc;
+  switch (CK) {
+  case CK_Eq:
+    Opc = "GIM_CheckNumOperands";
+    break;
+  case CK_GE:
+    Opc = "GIM_CheckNumOperandsGE";
+    break;
+  case CK_LE:
+    Opc = "GIM_CheckNumOperandsLE";
+    break;
+  }
+  Table << MatchTable::Opcode(Opc) << MatchTable::Comment("MI")
+        << MatchTable::ULEB128Value(InsnVarID)
         << MatchTable::Comment("Expected")
         << MatchTable::ULEB128Value(NumOperands) << MatchTable::LineBreak;
 }
@@ -1695,12 +1704,15 @@ void MIFlagsInstructionPredicateMatcher::emitPredicateOpcodes(
 
 OperandMatcher &
 InstructionMatcher::addOperand(unsigned OpIdx, const std::string &SymbolicName,
-                               unsigned AllocatedTemporariesBaseID) {
-  Operands.emplace_back(new OperandMatcher(*this, OpIdx, SymbolicName,
-                                           AllocatedTemporariesBaseID));
+                               unsigned AllocatedTemporariesBaseID,
+                               bool IsVariadic) {
+  assert(Operands.empty() ||
+         !Operands.back()->isVariadic() &&
+             "Cannot add more operands after a variadic operand");
+  Operands.emplace_back(new OperandMatcher(
+      *this, OpIdx, SymbolicName, AllocatedTemporariesBaseID, IsVariadic));
   if (!SymbolicName.empty())
     Rule.defineOperand(SymbolicName, *Operands.back());
-
   return *Operands.back();
 }
 
@@ -1726,9 +1738,10 @@ OperandMatcher &InstructionMatcher::addPhysRegInput(Record *Reg, unsigned OpIdx,
 
 void InstructionMatcher::emitPredicateOpcodes(MatchTable &Table,
                                               RuleMatcher &Rule) {
-  if (NumOperandsCheck)
-    InstructionNumOperandsMatcher(InsnVarID, getNumOperands())
+  if (canAddNumOperandsCheck()) {
+    InstructionNumOperandsMatcher(InsnVarID, getNumOperandMatchers())
         .emitPredicateOpcodes(Table, Rule);
+  }
 
   // First emit all instruction level predicates need to be verified before we
   // can verify operands.
@@ -1793,11 +1806,14 @@ void InstructionMatcher::optimize() {
 
   Stash.push_back(predicates_pop_front());
   if (Stash.back().get() == &OpcMatcher) {
-    if (NumOperandsCheck && OpcMatcher.isVariadicNumOperands() &&
-        getNumOperands() != 0)
-      Stash.emplace_back(
-          new InstructionNumOperandsMatcher(InsnVarID, getNumOperands()));
-    NumOperandsCheck = false;
+    // FIXME: Is this even needed still? Why the isVariadicNumOperands check?
+    if (canAddNumOperandsCheck() &&
+        OpcMatcher.isVariadicNumOperands() && getNumOperandMatchers() != 0) {
+      Stash.emplace_back(new InstructionNumOperandsMatcher(
+          InsnVarID,
+          getNumOperandMatchers()));
+    }
+    AllowNumOpsCheck = false;
 
     for (auto &OM : Operands)
       for (auto &OP : OM->predicates())
@@ -1862,11 +1878,13 @@ OperandRenderer::~OperandRenderer() {}
 
 void CopyRenderer::emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule,
                                      unsigned NewInsnID, unsigned OldInsnID,
-                                     unsigned OpIdx, StringRef Name) {
-  if (NewInsnID == 0 && OldInsnID == 0) {
+                                     unsigned OpIdx, StringRef Name,
+                                     bool ForVariadic) {
+  if (!ForVariadic && NewInsnID == 0 && OldInsnID == 0) {
     Table << MatchTable::Opcode("GIR_RootToRootCopy");
   } else {
-    Table << MatchTable::Opcode("GIR_Copy") << MatchTable::Comment("NewInsnID")
+    Table << MatchTable::Opcode(ForVariadic ? "GIR_CopyRemaining" : "GIR_Copy")
+          << MatchTable::Comment("NewInsnID")
           << MatchTable::ULEB128Value(NewInsnID)
           << MatchTable::Comment("OldInsnID")
           << MatchTable::ULEB128Value(OldInsnID);
@@ -1880,8 +1898,9 @@ void CopyRenderer::emitRenderOpcodes(MatchTable &Table,
                                      RuleMatcher &Rule) const {
   const OperandMatcher &Operand = Rule.getOperandMatcher(SymbolicName);
   unsigned OldInsnVarID = Rule.getInsnVarID(Operand.getInstructionMatcher());
+
   emitRenderOpcodes(Table, Rule, NewInsnID, OldInsnVarID, Operand.getOpIdx(),
-                    SymbolicName);
+                    SymbolicName, Operand.isVariadic());
 }
 
 //===- CopyPhysRegRenderer ------------------------------------------------===//
@@ -2127,10 +2146,10 @@ void CustomOperandRenderer::emitRenderOpcodes(MatchTable &Table,
 
 bool BuildMIAction::canMutate(RuleMatcher &Rule,
                               const InstructionMatcher *Insn) const {
-  if (!Insn)
+  if (!Insn || Insn->hasVariadicMatcher())
     return false;
 
-  if (OperandRenderers.size() != Insn->getNumOperands())
+  if (OperandRenderers.size() != Insn->getNumOperandMatchers())
     return false;
 
   for (const auto &Renderer : enumerate(OperandRenderers)) {
diff --git a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
index 9e345dceddf52..62292a4972c24 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/GlobalISelMatchTable.h
@@ -641,6 +641,8 @@ class RuleMatcher : public Matcher {
     return make_range(actions_begin(), actions_end());
   }
 
+  bool hasOperand(StringRef SymbolicName) const { return DefinedOperands.contains(SymbolicName); }
+
   void defineOperand(StringRef SymbolicName, OperandMatcher &OM);
 
   void definePhysRegOperand(Record *Reg, OperandMatcher &OM);
@@ -678,7 +680,6 @@ class RuleMatcher : public Matcher {
   const PredicateMatcher &getFirstCondition() const override;
   LLTCodeGen getFirstConditionAsRootType();
   bool hasFirstCondition() const override;
-  unsigned getNumOperands() const;
   StringRef getOpcode() const;
 
   // FIXME: Remove this as soon as possible
@@ -1255,12 +1256,15 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
 
   TempTypeIdx TTIdx = 0;
 
+  // TODO: has many implications, figure them all out
+  bool IsVariadic = false;
+
 public:
   OperandMatcher(InstructionMatcher &Insn, unsigned OpIdx,
                  const std::string &SymbolicName,
-                 unsigned AllocatedTemporariesBaseID)
+                 unsigned AllocatedTemporariesBaseID, bool IsVariadic = false)
       : Insn(Insn), OpIdx(OpIdx), SymbolicName(SymbolicName),
-        AllocatedTemporariesBaseID(AllocatedTemporariesBaseID) {}
+        AllocatedTemporariesBaseID(AllocatedTemporariesBaseID), IsVariadic(IsVariadic) {}
 
   bool hasSymbolicName() const { return !SymbolicName.empty(); }
   StringRef getSymbolicName() const { return SymbolicName; }
@@ -1272,7 +1276,8 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
   /// Construct a new operand predicate and add it to the matcher.
   template <class Kind, class... Args>
   std::optional<Kind *> addPredicate(Args &&...args) {
-    if (isSameAsAnotherOperand())
+    // TODO: Should variadic ops support predicates?
+    if (isSameAsAnotherOperand() || IsVariadic)
       return std::nullopt;
     Predicates.emplace_back(std::make_unique<Kind>(
         getInsnVarID(), getOpIdx(), std::forward<Args>(args)...));
@@ -1282,6 +1287,8 @@ class OperandMatcher : public PredicateListMatcher<OperandPredicateMatcher> {
   unsigned getOpIdx() const { return OpIdx; }
   unsigned getInsnVarID() const;
 
+  bool isVariadic() const { return IsVariadic; }
+
   /// If this OperandMatcher has not been assigned a TempTypeIdx yet, assigns it
   /// one and adds a `RecordRegisterType` predicate to this matcher. If one has
   /// already been assigned, simply returns it.
@@ -1405,20 +1412,32 @@ class InstructionOpcodeMatcher : public InstructionPredicateMatcher {
 };
 
 class InstructionNumOperandsMatcher final : public InstructionPredicateMatcher {
+public:
+  enum CheckKind {
+    CK_Eq,
+    CK_LE,
+    CK_GE
+  };
+
+private:
   unsigned NumOperands = 0;
+  CheckKind CK;
 
 public:
-  InstructionNumOperandsMatcher(unsigned InsnVarID, unsigned NumOperands)
+
+  InstructionNumOperandsMatcher(unsigned InsnVarID, unsigned NumOperands, CheckKind CK = CK_Eq)
       : InstructionPredicateMatcher(IPM_NumOperands, InsnVarID),
-        NumOperands(NumOperands) {}
+       NumOperands(NumOperands), CK(CK) {}
 
   static bool classof(const PredicateMatcher *P) {
     return P->getKind() == IPM_NumOperands;
   }
 
   bool isIdentical(const PredicateMatcher &B) const override {
-    return InstructionPredicateMatcher::isIdentical(B) &&
-           NumOperands == cast<InstructionNumOperandsMatcher>(&B)->NumOperands;
+    if(!InstructionPredicateMatcher::isIdentical(B))
+      return false;
+    const auto &Other = *cast<InstructionNumOperandsMatcher>(&B);
+    return NumOperands == Other.NumOperands && CK == Other.CK;
   }
 
   void emitPredicateOpcodes(MatchTable &Table,
@@ -1729,20 +1748,29 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
   /// The operands to match. All rendered operands must be present even if the
   /// condition is always true.
   OperandVec Operands;
-  bool NumOperandsCheck = true;
 
   std::string SymbolicName;
   unsigned InsnVarID;
+  bool AllowNumOpsCheck;
 
   /// PhysRegInputs - List list has an entry for each explicitly specified
   /// physreg input to the pattern.  The first elt is the Register node, the
   /// second is the recorded slot number the input pattern match saved it in.
   SmallVector<std::pair<Record *, unsigned>, 2> PhysRegInputs;
 
+  bool canAddNumOperandsCheck() const {
+    // Add if it's allowed, and:
+    //    - We don't have a variadic operand
+    //    - We don't already have such a check.
+    return AllowNumOpsCheck && !hasVariadicMatcher() && none_of(Predicates, [&](const auto& P){
+      return P->getKind() == InstructionPredicateMatcher::IPM_NumOperands;
+    });
+  }
+
 public:
-  InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName,
-                     bool NumOpsCheck = true)
-      : Rule(Rule), NumOperandsCheck(NumOpsCheck), SymbolicName(SymbolicName) {
+  InstructionMatcher(RuleMatcher &Rule, StringRef SymbolicName, bool AllowNumOpsCheck = true
+                     )
+      : Rule(Rule), SymbolicName(SymbolicName), AllowNumOpsCheck(AllowNumOpsCheck) {
     // We create a new instruction matcher.
     // Get a new ID for that instruction.
     InsnVarID = Rule.implicitlyDefineInsnVar(*this);
@@ -1762,7 +1790,7 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
 
   /// Add an operand to the matcher.
   OperandMatcher &addOperand(unsigned OpIdx, const std::string &SymbolicName,
-                             unsigned AllocatedTemporariesBaseID);
+                             unsigned AllocatedTemporariesBaseID, bool IsVariadic = false);
   OperandMatcher &getOperand(unsigned OpIdx);
   OperandMatcher &addPhysRegInput(Record *Reg, unsigned OpIdx,
                                   unsigned TempOpIdx);
@@ -1772,7 +1800,10 @@ class InstructionMatcher final : public PredicateListMatcher<PredicateMatcher> {
   }
 
   StringRef getSymbolicName() const { return SymbolicName; }
-  unsigned getNumOperands() const { return Operands.size(); }
+
+  unsigned getNumOperandMatchers() const { return Operands.size(); }
+  bool hasVariadicMatcher() const { return !Operands.empty() && Operands.back()->isVariadic(); }
+
   OperandVec::iterator operands_begin() { return Operands.begin(); }
   OperandVec::iterator operands_end() { return Operands.end(); }
   iterator_range<OperandVec::iterator> operands() {
@@ -1834,9 +1865,9 @@ class InstructionOperandMatcher : public OperandPredicateMatcher {
 public:
   InstructionOperandMatcher(unsigned InsnVarID, unsigned OpIdx,
                             RuleMatcher &Rule, StringRef SymbolicName,
-                            bool NumOpsCheck = true)
+                            bool AllowNumOpsCheck = true)
       : OperandPredicateMatcher(OPM_Instruction, InsnVarID, OpIdx),
-        InsnMatcher(new InstructionMatcher(Rule, SymbolicName, NumOpsCheck)),
+        InsnMatcher(new InstructionMatcher(Rule, SymbolicName, AllowNumOpsCheck)),
         Flags(Rule.getGISelFlags()) {}
 
   static bool classof(const PredicateMatcher *P) {
@@ -1917,7 +1948,7 @@ class CopyRenderer : public OperandRenderer {
 
   static void emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule,
                                 unsigned NewInsnID, unsigned OldInsnID,
-                                unsigned OpIdx, StringRef Name);
+                                unsigned OpIdx, StringRef Name, bool ForVariadic = false);
 
   void emitRenderOpcodes(MatchTable &Table, RuleMatcher &Rule) const override;
 };
diff --git a/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp b/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
index 28a9bcc2568c3..14eb927987100 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
+++ b/llvm/utils/TableGen/Common/GlobalISel/Patterns.cpp
@@ -46,6 +46,33 @@ std::optional<PatternType> PatternType::get(ArrayRef<SMLoc> DiagLoc,
     return PT;
   }
 
+  if (R->isSubClassOf(VariadicClassName)) {
+    const int64_t Min = R->getValueAsInt("MinArgs");
+    const int64_t Max = R->getValueAsInt("MaxArgs");
+
+    if (Min == 0) {
+      PrintError(
+          DiagLoc,
+          DiagCtx +
+              ": minimum number of arguments must be greater than zero in " +
+              VariadicClassName);
+      return std::nullopt;
+    }
+
+    if (Max <= Min && Max != 0) {
+      PrintError(DiagLoc,
+                 DiagCtx +
+                     ": maximum number of arguments (" + Twine(Max) + ") must be zero, or greater "
+                     "than the minimum number of arguments (" +
+                     Twine(Min) + ") in " + VariadicClassName);
+      return std::nullopt;
+    }
+
+    PatternType PT(PT_VariadicPack);
+    PT.Data.VPTI = {unsigned(Min), unsigned(Max)};
+    return PT;
+  }
+
   PrintError(DiagLoc, DiagCtx + ": unknown type '" + R->getName() + "'");
   return std::nullopt;
 }
@@ -66,6 +93,11 @@ const Record *PatternType::getLLTRecord() const {
   return Data.Def;
 }
 
+VariadicPackTypeInfo PatternType::getVariadicPackTypeInfo() const {
+  assert(isVariadicPack());
+  return Data.VPTI;
+}
+
 bool PatternType::operator==(const PatternType &Other) const {
   if (Kind != Other.Kind)
     return false;
@@ -77,6 +109,8 @@ bool PatternType::operator==(const PatternType &Other) const {
     return Data.Def == Other.Data.Def;
   case PT_TypeOf:
     return Data.Str == Other.Data.Str;
+  case PT_VariadicPack:
+    return Data.VPTI == Other.Data.VPTI;
   }
 
   llvm_unreachable("Unknown Type Kind");
@@ -90,6 +124,10 @@ std::string PatternType::str() const {
     return Data.Def->getName().str();
   case PT_TypeOf:
     return (TypeOfClassName + "<$" + getTypeOfOpName() + ">").str();
+  case PT_VariadicPack:
+    return (VariadicClassName + "<" + Twine(Data.VPTI.Min) + "," +
+            Twine(Data.VPTI.Max) + ">")
+        .str();
   }
 
   llvm_unreachable("Unknown type!");
@@ -525,6 +563,7 @@ bool PatFrag::checkSemantics() {
       case Pattern::K_CXX:
         continue;
       case Pattern::K_CodeGenInstruction:
+        // TODO: Allow VarArgs?
         if (cast<CodeGenInstructionPattern>(Pat.get())->diagnoseAllSpecialTypes(
                 Def.getLoc(), PatternType::SpecialTyClassName +
                                   " is not supported in " + ClassName))
diff --git a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
index 76d018bdbd71c..2d25ce37ed76c 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
@@ -45,21 +45,36 @@ class RuleMatcher;
 
 //===- PatternType --------------------------------------------------------===//
 
+struct VariadicPackTypeInfo {
+  VariadicPackTypeInfo(unsigned Min, unsigned Max) : Min(Min), Max(Max) {
+    assert(Min >= 1 && (Max >= Min || Max == 0));
+  }
+
+  bool operator==(const VariadicPackTypeInfo &Other) const {
+    return Min == Other.Min && Max == Other.Max;
+  }
+
+  unsigned Min;
+  unsigned Max;
+};
+
 /// Represent the type of a Pattern Operand.
 ///
 /// Types have two form:
 ///   - LLTs, which are straightforward.
-///   - Special types, e.g. GITypeOf
+///   - Special types, e.g. GITypeOf, Variadic arguments list.
 class PatternType {
 public:
   static constexpr StringLiteral SpecialTyClassName = "GISpecialType";
   static constexpr StringLiteral TypeOfClassName = "GITypeOf";
+  static constexpr StringLiteral VariadicClassName = "GIVariadic";
 
   enum PTKind : uint8_t {
     PT_None,
 
     PT_ValueType,
     PT_TypeOf,
+    PT_VariadicPack,
   };
 
   PatternType() : Kind(PT_None), Data() {}
@@ -70,11 +85,15 @@ class PatternType {
 
   bool isNone() const { return Kind == PT_None; }
   bool isLLT() const { return Kind == PT_ValueType; }
-  bool isSpecial() const { return isTypeOf(); }
+  bool isSpecial() const { return isTypeOf() || isVariadicPack(); }
   bool isTypeOf() const { return Kind == PT_TypeOf; }
+  bool isVariadicPack() const { return Kind == PT_VariadicPack; }
+
+  PTKind getKind() const { return Kind; }
 
   StringRef getTypeOfOpName() const;
   const Record *getLLTRecord() const;
+  VariadicPackTypeInfo getVariadicPackTypeInfo() const;
 
   explicit operator bool() const { return !isNone(); }
 
@@ -95,6 +114,9 @@ class PatternType {
 
     /// PT_TypeOf -> Operand name (without the '$')
     StringRef Str;
+
+    /// PT_VariadicPack -> min-max number of operands allowed.
+    VariadicPackTypeInfo VPTI;
   } Data;
 };
 
@@ -313,6 +335,8 @@ class InstructionPattern : public Pattern {
   InstructionOperand &getOperand(unsigned K) { return Operands[K]; }
   const InstructionOperand &getOperand(unsigned K) const { return Operands[K]; }
 
+  const InstructionOperand &operands_back() const { return Operands.back(); }
+
   /// When this InstructionPattern is used as the match root, returns the
   /// operands that must be redefined in the 'apply' pattern for the rule to be
   /// valid.
diff --git a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
index e8fbaed0f50e8..fd7bb68c86cd9 100644
--- a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
@@ -99,8 +99,13 @@ void declareInstExpansion(CodeExpansions &CE, const BuildMIAction &A,
 
 void declareOperandExpansion(CodeExpansions &CE, const OperandMatcher &OM,
                              StringRef Name) {
-  CE.declare(Name, "State.MIs[" + to_string(OM.getInsnVarID()) +
-                       "]->getOperand(" + to_string(OM.getOpIdx()) + ")");
+  if (OM.isVariadic()) {
+    CE.declare(Name, "drop_begin(State.MIs[" + to_string(OM.getInsnVarID()) +
+                         "]->operands(), " + to_string(OM.getOpIdx()) + ")");
+  } else {
+    CE.declare(Name, "State.MIs[" + to_string(OM.getInsnVarID()) +
+                         "]->getOperand(" + to_string(OM.getOpIdx()) + ")");
+  }
 }
 
 void declareTempRegExpansion(CodeExpansions &CE, unsigned TempRegID,
@@ -128,18 +133,6 @@ LLTCodeGen getLLTCodeGen(const PatternType &PT) {
   return *MVTToLLT(getValueType(PT.getLLTRecord()));
 }
 
-LLTCodeGenOrTempType getLLTCodeGenOrTempType(const PatternType &PT,
-                                             RuleMatcher &RM) {
-  assert(!PT.isNone());
-
-  if (PT.isLLT())
-    return getLLTCodeGen(PT);
-
-  assert(PT.isTypeOf());
-  auto &OM = RM.getOperandMatcher(PT.getTypeOfOpName());
-  return OM.getTempTypeIdx(RM);
-}
-
 //===- PrettyStackTrace Helpers  ------------------------------------------===//
 
 class PrettyStackTraceParse : public PrettyStackTraceEntry {
@@ -668,6 +661,9 @@ class CombineRuleBuilder {
     return CGT.getInstruction(RuleDef.getRecords().getDef("G_CONSTANT"));
   }
 
+  std::optional<LLTCodeGenOrTempType>
+  getLLTCodeGenOrTempType(const PatternType &PT, RuleMatcher &RM);
+
   void PrintError(Twine Msg) const { ::PrintError(&RuleDef, Msg); }
   void PrintWarning(Twine Msg) const { ::PrintWarning(RuleDef.getLoc(), Msg); }
   void PrintNote(Twine Msg) const { ::PrintNote(RuleDef.getLoc(), Msg); }
@@ -960,6 +956,24 @@ void CombineRuleBuilder::verify() const {
 }
 #endif
 
+std::optional<LLTCodeGenOrTempType>
+CombineRuleBuilder::getLLTCodeGenOrTempType(const PatternType &PT,
+                                            RuleMatcher &RM) {
+  assert(!PT.isNone());
+
+  if (PT.isLLT())
+    return getLLTCodeGen(PT);
+
+  assert(PT.isTypeOf());
+  auto &OM = RM.getOperandMatcher(PT.getTypeOfOpName());
+  if (OM.isVariadic()) {
+    PrintError("type '" + PT.str() + "' is ill-formed: '" +
+               OM.getSymbolicName() + "' is a variadic pack operand");
+    return std::nullopt;
+  }
+  return OM.getTempTypeIdx(RM);
+}
+
 void CombineRuleBuilder::print(raw_ostream &OS,
                                const PatternAlternatives &Alts) const {
   SmallVector<std::string, 1> Strings(
@@ -1079,11 +1093,18 @@ bool CombineRuleBuilder::typecheckPatterns() {
   // match patterns.
   for (auto &Pat : values(MatchPats)) {
     if (auto *IP = dyn_cast<InstructionPattern>(Pat.get())) {
-      if (IP->diagnoseAllSpecialTypes(
-              RuleDef.getLoc(), PatternType::SpecialTyClassName +
-                                    " is not supported in 'match' patterns")) {
-        return false;
+      bool HasDiag = false;
+      for (const auto &[Idx, Op] : enumerate(IP->operands())) {
+        if (Op.getType().isTypeOf()) {
+          PrintError(PatternType::TypeOfClassName +
+                     " is not supported in 'match' patterns");
+          PrintNote("operand " + Twine(Idx) + " of '" + IP->getName() +
+                    "' has type '" + Op.getType().str() + "'");
+          HasDiag = true;
+        }
       }
+      if (HasDiag)
+        return false;
     }
   }
   return true;
@@ -1145,6 +1166,29 @@ bool CombineRuleBuilder::buildPermutationsToEmit() {
 bool CombineRuleBuilder::checkSemantics() {
   assert(MatchRoot && "Cannot call this before findRoots()");
 
+  const auto CheckVariadicOperands = [&](const InstructionPattern &IP, bool IsMatch) {
+    for (auto &Op : IP.operands()) {
+      if (!Op.getType().isVariadicPack())
+        continue;
+
+      if (IsMatch && &Op != &IP.operands_back()) {
+        PrintError("'" + IP.getInstName() +
+                            "': " + PatternType::VariadicClassName +
+                            " can only be used on the last operand");
+        return false;
+      }
+
+      if (Op.isDef()) {
+        PrintError("'" + IP.getInstName() +
+                            "': " + PatternType::VariadicClassName +
+                            " cannot be used on defs");
+        return false;
+      }
+    }
+
+    return true;
+  };
+
   bool UsesWipMatchOpcode = false;
   for (const auto &Match : MatchPats) {
     const auto *Pat = Match.second.get();
@@ -1155,19 +1199,26 @@ bool CombineRuleBuilder::checkSemantics() {
       continue;
     }
 
-    // MIFlags in match cannot use the following syntax: (MIFlags $mi)
-    if (const auto *CGP = dyn_cast<CodeGenInstructionPattern>(Pat)) {
-      if (auto *FI = CGP->getMIFlagsInfo()) {
-        if (!FI->copy_flags().empty()) {
-          PrintError(
-              "'match' patterns cannot refer to flags from other instructions");
-          PrintNote("MIFlags in '" + CGP->getName() +
-                    "' refer to: " + join(FI->copy_flags(), ", "));
-          return false;
+    if (const auto IP = dyn_cast<InstructionPattern>(Pat)) {
+      if(!CheckVariadicOperands(*IP, /*IsMatch=*/true))
+        return false;
+
+      // MIFlags in match cannot use the following syntax: (MIFlags $mi)
+      if (const auto *CGP = dyn_cast<CodeGenInstructionPattern>(Pat)) {
+        if (auto *FI = CGP->getMIFlagsInfo()) {
+          if (!FI->copy_flags().empty()) {
+            PrintError(
+                "'match' patterns cannot refer to flags from other instructions");
+            PrintNote("MIFlags in '" + CGP->getName() +
+                      "' refer to: " + join(FI->copy_flags(), ", "));
+            return false;
+          }
         }
       }
+      continue;
     }
 
+
     const auto *AOP = dyn_cast<AnyOpcodePattern>(Pat);
     if (!AOP)
       continue;
@@ -1197,6 +1248,9 @@ bool CombineRuleBuilder::checkSemantics() {
     if (!IP)
       continue;
 
+    if (!CheckVariadicOperands(*IP, /*IsMatch=*/false))
+      return false;
+
     if (UsesWipMatchOpcode) {
       PrintError("cannot use wip_match_opcode in combination with apply "
                  "instruction patterns!");
@@ -1839,7 +1893,7 @@ bool CombineRuleBuilder::emitCXXMatchApply(CodeExpansions &CE, RuleMatcher &M,
   for (auto &Pat : ApplyPats) {
     auto *CXXPat = cast<CXXPattern>(Pat.second.get());
     CodeExpander Expander(CXXPat->getRawCode(), CE, RuleDef.getLoc(),
-                          /*ShowExpansions=*/ false);
+                          /*ShowExpansions=*/false);
     OS << LS;
     Expander.emit(OS);
   }
@@ -1939,8 +1993,8 @@ bool CombineRuleBuilder::emitInstructionApplyPattern(
       continue;
     }
 
-    // Determine what we're dealing with. Are we replace a matched instruction?
-    // Creating a new one?
+    // Determine what we're dealing with. Are we replacing a matched
+    // instruction? Creating a new one?
     auto OpLookupRes = MatchOpTable.lookup(OpName);
     if (OpLookupRes.Found) {
       if (OpLookupRes.isLiveIn()) {
@@ -1986,8 +2040,11 @@ bool CombineRuleBuilder::emitInstructionApplyPattern(
       declareTempRegExpansion(CE, TempRegID, OpName);
       // Always insert the action at the beginning, otherwise we may end up
       // using the temp reg before it's available.
-      M.insertAction<MakeTempRegisterAction>(
-          M.actions_begin(), getLLTCodeGenOrTempType(Ty, M), TempRegID);
+      auto Result = getLLTCodeGenOrTempType(Ty, M);
+      if (!Result)
+        return false;
+      M.insertAction<MakeTempRegisterAction>(M.actions_begin(), *Result,
+                                             TempRegID);
     }
 
     DstMI.addRenderer<TempRegRenderer>(TempRegID, /*IsDef=*/true);
@@ -2044,16 +2101,18 @@ bool CombineRuleBuilder::emitCodeGenInstructionApplyImmOperand(
   }
 
   auto ImmTy = getLLTCodeGenOrTempType(Ty, M);
+  if (!ImmTy)
+    return false;
 
   if (isGConstant) {
-    DstMI.addRenderer<ImmRenderer>(O.getImmValue(), ImmTy);
+    DstMI.addRenderer<ImmRenderer>(O.getImmValue(), *ImmTy);
     return true;
   }
 
   unsigned TempRegID = M.allocateTempRegID();
   // Ensure MakeTempReg & the BuildConstantAction occur at the beginning.
   auto InsertIt = M.insertAction<MakeTempRegisterAction>(M.actions_begin(),
-                                                         ImmTy, TempRegID);
+                                                         *ImmTy, TempRegID);
   M.insertAction<BuildConstantAction>(++InsertIt, TempRegID, O.getImmValue());
   DstMI.addRenderer<TempRegRenderer>(TempRegID);
   return true;
@@ -2159,6 +2218,8 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
     assert(RemappedO.isNamedOperand() == OriginalO.isNamedOperand() &&
            "Cannot remap an unnamed operand to a named one!");
 
+    const auto Ty = RemappedO.getType();
+
     const auto OpName =
         RemappedO.isNamedOperand() ? RemappedO.getOperandName().str() : "";
 
@@ -2170,11 +2231,41 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
     //    RealIdx = expected index in the MachineInstr.
     const unsigned RealIdx =
         (P.isIntrinsic() && !OriginalO.isDef()) ? (Idx + 1) : Idx;
+
+    if (Ty.isVariadicPack() && M.hasOperand(OpName)) {
+      // TODO: We could add some CheckIsSameOperand opcode variant that checks
+      // all operands. We could also just emit a C++ code snippet lazily to do
+      // the check since it's probably fairly rare that we need to do it.
+      //
+      // I'm just not sure it's worth the effort at this stage.
+      PrintError("each instance of a " + PatternType::VariadicClassName +
+                 " operand must have a unique name within the match patterns");
+      PrintNote("'" + OpName + "' is used multiple times");
+      return false;
+    }
+
     OperandMatcher &OM =
-        IM.addOperand(RealIdx, OpName, AllocatedTemporariesBaseID++);
+        IM.addOperand(RealIdx, OpName, AllocatedTemporariesBaseID++,
+                      /*IsVariadic=*/Ty.isVariadicPack());
     if (!OpName.empty())
       declareOperandExpansion(CE, OM, OriginalO.getOperandName());
 
+    if (Ty.isVariadicPack()) {
+      // In the presence of variadics, the InstructionMatcher won't insert a
+      // InstructionNumOperandsMatcher implicitly, so we have to emit our own.
+      assert((Idx + 1) == P.operands_size() &&
+             "VariadicPack isn't last operand!");
+      auto VPTI = Ty.getVariadicPackTypeInfo();
+      assert(VPTI.Min > 0 && (VPTI.Max == 0 || VPTI.Max > VPTI.Min));
+      IM.addPredicate<InstructionNumOperandsMatcher>(
+          RealIdx + VPTI.Min, InstructionNumOperandsMatcher::CK_GE);
+      if (VPTI.Max) {
+        IM.addPredicate<InstructionNumOperandsMatcher>(
+            RealIdx + VPTI.Max, InstructionNumOperandsMatcher::CK_LE);
+      }
+      break;
+    }
+
     // Handle immediates.
     if (RemappedO.hasImmValue()) {
       if (isLiteralImm(P, Idx))
@@ -2190,16 +2281,14 @@ bool CombineRuleBuilder::emitCodeGenInstructionMatchPattern(
     // for that Operand. "OM" here is always a new OperandMatcher.
     //
     // Always emit a check for unnamed operands.
-    if (OpName.empty() ||
-        !M.getOperandMatcher(OpName).contains<LLTOperandMatcher>()) {
-      if (const auto Ty = RemappedO.getType()) {
-        // TODO: We could support GITypeOf here on the condition that the
-        // OperandMatcher exists already. Though it's clunky to make this work
-        // and isn't all that useful so it's just rejected in typecheckPatterns
-        // at this time.
-        assert(Ty.isLLT() && "Only LLTs are supported in match patterns!");
-        OM.addPredicate<LLTOperandMatcher>(getLLTCodeGen(Ty));
-      }
+    if (Ty && (OpName.empty() ||
+               !M.getOperandMatcher(OpName).contains<LLTOperandMatcher>())) {
+      // TODO: We could support GITypeOf here on the condition that the
+      // OperandMatcher exists already. Though it's clunky to make this work
+      // and isn't all that useful so it's just rejected in typecheckPatterns
+      // at this time.
+      assert(Ty.isLLT());
+      OM.addPredicate<LLTOperandMatcher>(getLLTCodeGen(Ty));
     }
 
     // Stop here if the operand is a def, or if it had no name.
@@ -2558,8 +2647,10 @@ GICombinerEmitter::buildMatchTable(MutableArrayRef<RuleMatcher> Rules) {
                                                const Matcher *B) {
     auto *L = static_cast<const RuleMatcher *>(A);
     auto *R = static_cast<const RuleMatcher *>(B);
-    return std::make_tuple(OpcodeOrder[L->getOpcode()], L->getNumOperands()) <
-           std::make_tuple(OpcodeOrder[R->getOpcode()], R->getNumOperands());
+    return std::make_tuple(OpcodeOrder[L->getOpcode()],
+                           L->insnmatchers_front().getNumOperandMatchers()) <
+           std::make_tuple(OpcodeOrder[R->getOpcode()],
+                           R->insnmatchers_front().getNumOperandMatchers());
   });
 
   for (Matcher *Rule : InputRules)
diff --git a/llvm/utils/TableGen/GlobalISelEmitter.cpp b/llvm/utils/TableGen/GlobalISelEmitter.cpp
index c29cb4edec181..9b5275b4a1d85 100644
--- a/llvm/utils/TableGen/GlobalISelEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelEmitter.cpp
@@ -2248,8 +2248,8 @@ GlobalISelEmitter::buildMatchTable(MutableArrayRef<RuleMatcher> Rules,
                                                const Matcher *B) {
     auto *L = static_cast<const RuleMatcher *>(A);
     auto *R = static_cast<const RuleMatcher *>(B);
-    return std::tuple(OpcodeOrder[L->getOpcode()], L->getNumOperands()) <
-           std::tuple(OpcodeOrder[R->getOpcode()], R->getNumOperands());
+    return std::tuple(OpcodeOrder[L->getOpcode()], L->insnmatchers_front().getNumOperandMatchers()) <
+           std::tuple(OpcodeOrder[R->getOpcode()], R->insnmatchers_front().getNumOperandMatchers());
   });
 
   for (Matcher *Rule : InputRules)



More information about the llvm-commits mailing list