[llvm] [GlobalISel] Add `combine` action for C++ combine rules (PR #135941)

Pierre van Houtryve via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 16 02:09:38 PDT 2025


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

Adds a `combine` action (DAG operator) which allows for easy definition of
combine rule that only match one or more instructions, and defer all remaining
match/apply logic to C++ code.

This avoids the need for split match/apply function in such cases. One function
can do the trick as long as it returns `true` if it changed any code.

This is implemented as syntactic sugar over match/apply. The combine rule is
just a match pattern BUT every C++ pattern inside is treated as an "apply" function.
This makes it fit seamlessly with the current backend.

Fixes #92410

>From 5ee539bd5d48dc9cc733552e7771cad26dcd5a20 Mon Sep 17 00:00:00 2001
From: pvanhout <pierre.vanhoutryve at amd.com>
Date: Tue, 15 Apr 2025 15:35:26 +0200
Subject: [PATCH] [GlobalISel] Add `combine` action for C++ combine rules

Adds a `combine` action (DAG operator) which allows for easy definition of
combine rule that only match one or more instructions, and defer all remaining
match/apply logic to C++ code.

This avoids the need for split match/apply function in such cases. One function
can do the trick as long as it returns `true` if it changed any code.

This is implemented as syntactic sugar over match/apply. The combine rule is
just a match pattern BUT every C++ pattern inside is treated as an "apply" function.
This makes it fit seamlessly with the current backend.

Fixes #92410
---
 llvm/docs/GlobalISel/MIRPatterns.rst          |  38 ++++-
 .../include/llvm/Target/GlobalISel/Combine.td |  15 +-
 .../match-table-cxx.td                        | 133 ++++++++++--------
 .../pattern-errors.td                         |  34 ++++-
 .../pattern-parsing.td                        |  35 ++++-
 .../TableGen/GlobalISelCombinerEmitter.cpp    |  49 +++++--
 6 files changed, 230 insertions(+), 74 deletions(-)

diff --git a/llvm/docs/GlobalISel/MIRPatterns.rst b/llvm/docs/GlobalISel/MIRPatterns.rst
index 42ceb1883f7de..3851698cc6686 100644
--- a/llvm/docs/GlobalISel/MIRPatterns.rst
+++ b/llvm/docs/GlobalISel/MIRPatterns.rst
@@ -74,7 +74,7 @@ Operands are ordered just like they would be in a MachineInstr: the defs (outs)
 come first, then the uses (ins).
 
 Patterns are generally grouped into another DAG datatype with a dummy operator
-such as ``match``, ``apply`` or ``pattern``.
+such as ``match``, ``apply``, ``combine`` or ``pattern``.
 
 Finally, any DAG datatype in TableGen can be named. This also holds for
 patterns. e.g. the following is valid: ``(G_FOO $root, (i32 0):$cst):$mypat``.
@@ -370,6 +370,42 @@ The following expansions are available for MIR patterns:
     (match (G_ZEXT $root, $src):$mi),
     (apply "foobar(${root}.getReg(), ${src}.getReg(), ${mi}->hasImplicitDef())")>;
 
+``combine`` Operator
+~~~~~~~~~~~~~~~~~~~~
+
+``GICombineRule`` also supports a single ``combine`` pattern, which is a shorter way to
+declare patterns that just match one or more instruction, then defer all remaining matching
+and rewriting logic to C++ code.
+
+.. code-block:: text
+  :caption: Example usage of the combine operator.
+
+  // match + apply
+  def FooLong : GICombineRule<
+    (defs root:$root),
+    (match (G_ZEXT $root, $src):$mi, "return matchFoo(${mi});"),
+    (apply "applyFoo(${mi});")>;
+
+  // combine
+  def FooShort : GICombineRule<
+    (defs root:$root),
+    (combine (G_ZEXT $root, $src):$mi, "return combineFoo(${mi});")>;
+
+This has a couple of advantages:
+
+* We only need one C++ function, not two.
+* We no longer need to use ``GIDefMatchData`` to pass information between the match/apply functions.
+
+As described above, this is syntactic sugar for the match+apply form. In a ``combine`` pattern:
+
+* Everything except C++ code is considered the ``match`` part.
+* The C++ code is the ``apply`` part. C++ code is emitted in order of appearance.
+
+.. note::
+
+  The C++ code **must** return true if it changed any instruction. Returning false when changing
+  instructions is undefined behavior.
+
 Common Pattern #1: Replace a Register with Another
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index e5e19a1d93486..efd88524a159e 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -31,6 +31,8 @@ def defs;
 def pattern;
 def match;
 def apply;
+def combine;
+def empty_action;
 
 def wip_match_opcode;
 
@@ -67,18 +69,17 @@ class GIDefMatchData<string type>  {
   string Type = type;
 }
 
-class GICombineRule<dag defs, dag match, dag apply> : GICombine {
+class GICombineRule<dag defs, dag a0, dag a1 = (empty_action)> : GICombine {
   /// Defines the external interface of the match rule. This includes:
   /// * The names of the root nodes (requires at least one)
   /// See GIDefKind for details.
   dag Defs = defs;
 
-  /// Defines the things which must be true for the pattern to match
-  dag Match = match;
-
-  /// Defines the things which happen after the decision is made to apply a
-  /// combine rule.
-  dag Apply = apply;
+  // The patterns that will be used. Two types of list can exist:
+  //  match (Action0) + apply (Action1).
+  //  combine (Action0) + empty_action (Action1).
+  dag Action0 = a0;
+  dag Action1 = a1;
 
   /// Defines the predicates that are checked before the match function
   /// is called. Targets can use this to, for instance, check Subtarget
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-cxx.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-cxx.td
index 9a1a15c8d30df..57f2b2ca53276 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-cxx.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/match-table-cxx.td
@@ -28,11 +28,16 @@ def NoMatchTwoApply : GICombineRule<
   (match (G_SEXT $a, $y)),
   (apply "APPLY0", "APPLY1")>;
 
+def CopmbineCXXOrder : GICombineRule<
+  (defs root:$a),
+  (combine (G_ZEXT $a, $y), "A0", "return A1")>;
+
 def MyCombiner: GICombiner<"GenMyCombiner", [
   OneMatchOneApply,
   TwoMatchTwoApply,
   TwoMatchNoApply,
-  NoMatchTwoApply
+  NoMatchTwoApply,
+  CopmbineCXXOrder
 ]>;
 
 // CHECK:      bool GenMyCombiner::testMIPredicate_MI(unsigned PredicateID, const MachineInstr & MI, const MatcherState &State) const {
@@ -79,65 +84,83 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
 // CHECK-NEXT:     APPLY1
 // CHECK-NEXT:     return true;
 // CHECK-NEXT:   }
+// CHECK-NEXT:   case GICXXCustomAction_GICombiner3:{
+// CHECK-NEXT:     // Apply Patterns
+// CHECK-NEXT:     A0
+// CHECK-NEXT:     return A1
+// CHECK-NEXT:     return true;
+// CHECK-NEXT:   }
 // CHECK-NEXT:   }
 // CHECK-NEXT:   llvm_unreachable("Unknown Apply Action");
 // CHECK-NEXT: }
 
 // 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 4*/ GIMT_Encode4([[#DEFAULT:]]),
-// CHECK-NEXT:     /*TargetOpcode::G_STORE*//*Label 0*/ GIMT_Encode4([[L418:[0-9]+]]), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
-// CHECK-NEXT:     /*TargetOpcode::G_SEXT*//*Label 1*/ GIMT_Encode4([[L436:[0-9]+]]), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
-// CHECK-NEXT:     /*TargetOpcode::G_FNEG*//*Label 2*/ GIMT_Encode4([[L448:[0-9]+]]), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
-// CHECK-NEXT:     /*TargetOpcode::G_FABS*//*Label 3*/ GIMT_Encode4([[L460:[0-9]+]]),
-// CHECK-NEXT:     // Label 0: @[[#%u, mul(UPPER-LOWER, 4) + 10]]
-// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 5*/ GIMT_Encode4([[L435:[0-9]+]]), // Rule ID 2 //
-// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule2Enabled),
-// CHECK-NEXT:       // MIs[0] x
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // MIs[0] y
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       GIM_CheckCxxInsnPredicate, /*MI*/0, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_GICombiner0),
-// CHECK-NEXT:       GIM_CheckCxxInsnPredicate, /*MI*/0, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_GICombiner1),
-// CHECK-NEXT:       // Combiner Rule #2: TwoMatchNoApply
-// CHECK-NEXT:       GIR_EraseRootFromParent_Done,
-// CHECK-NEXT:     // Label 5: @[[L435]]
-// CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     // Label 1: @[[L436]]
-// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4([[L447:[0-9]+]]), // Rule ID 3 //
-// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule3Enabled),
-// CHECK-NEXT:       // MIs[0] a
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // MIs[0] y
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // Combiner Rule #3: NoMatchTwoApply
-// CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner2),
-// CHECK-NEXT:     // Label 6: @[[L447]]
-// CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     // Label 2: @[[L448]]
-// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 7*/ GIMT_Encode4([[L459:[0-9]+]]), // Rule ID 1 //
-// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule1Enabled),
-// CHECK-NEXT:       // MIs[0] a
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // MIs[0] b
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // Combiner Rule #1: TwoMatchTwoApply
-// CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner1),
-// CHECK-NEXT:     // Label 7: @[[L459]]
-// CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     // Label 3: @[[L460]]
-// CHECK-NEXT:     GIM_Try, /*On fail goto*//*Label 8*/ GIMT_Encode4([[L471:[0-9]+]]), // Rule ID 0 //
-// CHECK-NEXT:       GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule0Enabled),
-// CHECK-NEXT:       // MIs[0] a
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // MIs[0] b
-// CHECK-NEXT:       // No operand predicates
-// CHECK-NEXT:       // Combiner Rule #0: OneMatchOneApply
-// CHECK-NEXT:       GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
-// CHECK-NEXT:     // Label 8: @[[L471]]
-// CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     // Label 4: @[[#%u, DEFAULT]]
-// CHECK-NEXT:     GIM_Reject,
-// CHECK-NEXT:     }; // Size: [[#%u, DEFAULT + 1]] bytes
+// CHECK-NEXT:      /*   0 */ GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2(99), GIMT_Encode2(205), /*)*//*default:*//*Label 5*/ GIMT_Encode4(500),
+// CHECK-NEXT:      /*  10 */ /*TargetOpcode::G_STORE*//*Label 0*/ GIMT_Encode4(434), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
+// CHECK-NEXT:      /* 162 */ /*TargetOpcode::G_SEXT*//*Label 1*/ GIMT_Encode4(452), GIMT_Encode4(0),
+// CHECK-NEXT:      /* 170 */ /*TargetOpcode::G_ZEXT*//*Label 2*/ GIMT_Encode4(464), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
+// CHECK-NEXT:      /* 394 */ /*TargetOpcode::G_FNEG*//*Label 3*/ GIMT_Encode4(476), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
+// CHECK-NEXT:      /* 430 */ /*TargetOpcode::G_FABS*//*Label 4*/ GIMT_Encode4(488),
+// CHECK-NEXT:      /* 434 */ // Label 0: @434
+// CHECK-NEXT:      /* 434 */ GIM_Try, /*On fail goto*//*Label 6*/ GIMT_Encode4(451), // Rule ID 2 //
+// CHECK-NEXT:      /* 439 */   GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule2Enabled),
+// CHECK-NEXT:      /* 442 */   // MIs[0] x
+// CHECK-NEXT:      /* 442 */   // No operand predicates
+// CHECK-NEXT:      /* 442 */   // MIs[0] y
+// CHECK-NEXT:      /* 442 */   // No operand predicates
+// CHECK-NEXT:      /* 442 */   GIM_CheckCxxInsnPredicate, /*MI*/0, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_GICombiner0),
+// CHECK-NEXT:      /* 446 */   GIM_CheckCxxInsnPredicate, /*MI*/0, /*FnId*/GIMT_Encode2(GICXXPred_MI_Predicate_GICombiner1),
+// CHECK-NEXT:      /* 450 */   // Combiner Rule #2: TwoMatchNoApply
+// CHECK-NEXT:      /* 450 */   GIR_EraseRootFromParent_Done,
+// CHECK-NEXT:      /* 451 */ // Label 6: @451
+// CHECK-NEXT:      /* 451 */ GIM_Reject,
+// CHECK-NEXT:      /* 452 */ // Label 1: @452
+// CHECK-NEXT:      /* 452 */ GIM_Try, /*On fail goto*//*Label 7*/ GIMT_Encode4(463), // Rule ID 3 //
+// CHECK-NEXT:      /* 457 */   GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule3Enabled),
+// CHECK-NEXT:      /* 460 */   // MIs[0] a
+// CHECK-NEXT:      /* 460 */   // No operand predicates
+// CHECK-NEXT:      /* 460 */   // MIs[0] y
+// CHECK-NEXT:      /* 460 */   // No operand predicates
+// CHECK-NEXT:      /* 460 */   // Combiner Rule #3: NoMatchTwoApply
+// CHECK-NEXT:      /* 460 */   GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner2),
+// CHECK-NEXT:      /* 463 */ // Label 7: @463
+// CHECK-NEXT:      /* 463 */ GIM_Reject,
+// CHECK-NEXT:      /* 464 */ // Label 2: @464
+// CHECK-NEXT:      /* 464 */ GIM_Try, /*On fail goto*//*Label 8*/ GIMT_Encode4(475), // Rule ID 4 //
+// CHECK-NEXT:      /* 469 */   GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule4Enabled),
+// CHECK-NEXT:      /* 472 */   // MIs[0] a
+// CHECK-NEXT:      /* 472 */   // No operand predicates
+// CHECK-NEXT:      /* 472 */   // MIs[0] y
+// CHECK-NEXT:      /* 472 */   // No operand predicates
+// CHECK-NEXT:      /* 472 */   // Combiner Rule #4: CopmbineCXXOrder
+// CHECK-NEXT:      /* 472 */   GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner3),
+// CHECK-NEXT:      /* 475 */ // Label 8: @475
+// CHECK-NEXT:      /* 475 */ GIM_Reject,
+// CHECK-NEXT:      /* 476 */ // Label 3: @476
+// CHECK-NEXT:      /* 476 */ GIM_Try, /*On fail goto*//*Label 9*/ GIMT_Encode4(487), // Rule ID 1 //
+// CHECK-NEXT:      /* 481 */   GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule1Enabled),
+// CHECK-NEXT:      /* 484 */   // MIs[0] a
+// CHECK-NEXT:      /* 484 */   // No operand predicates
+// CHECK-NEXT:      /* 484 */   // MIs[0] b
+// CHECK-NEXT:      /* 484 */   // No operand predicates
+// CHECK-NEXT:      /* 484 */   // Combiner Rule #1: TwoMatchTwoApply
+// CHECK-NEXT:      /* 484 */   GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner1),
+// CHECK-NEXT:      /* 487 */ // Label 9: @487
+// CHECK-NEXT:      /* 487 */ GIM_Reject,
+// CHECK-NEXT:      /* 488 */ // Label 4: @488
+// CHECK-NEXT:      /* 488 */ GIM_Try, /*On fail goto*//*Label 10*/ GIMT_Encode4(499), // Rule ID 0 //
+// CHECK-NEXT:      /* 493 */   GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule0Enabled),
+// CHECK-NEXT:      /* 496 */   // MIs[0] a
+// CHECK-NEXT:      /* 496 */   // No operand predicates
+// CHECK-NEXT:      /* 496 */   // MIs[0] b
+// CHECK-NEXT:      /* 496 */   // No operand predicates
+// CHECK-NEXT:      /* 496 */   // Combiner Rule #0: OneMatchOneApply
+// CHECK-NEXT:      /* 496 */   GIR_DoneWithCustomAction, /*Fn*/GIMT_Encode2(GICXXCustomAction_GICombiner0),
+// CHECK-NEXT:      /* 499 */ // Label 10: @499
+// CHECK-NEXT:      /* 499 */ GIM_Reject,
+// CHECK-NEXT:      /* 500 */ // Label 5: @500
+// CHECK-NEXT:      /* 500 */ GIM_Reject,
+// CHECK-NEXT:      /* 501 */ }; // Size: 501 bytes
 // CHECK-NEXT:   return MatchTable0;
 // CHECK-NEXT: }
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-errors.td
index 5017672f5cbd7..1642cca3aced0 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-errors.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-errors.td
@@ -283,6 +283,33 @@ def matchdata_without_cxx_apply : GICombineRule<
   (match (G_ZEXT $dst, $src):$mi),
   (apply (G_MUL $dst, $src, $src))>;
 
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Expected both a 'match' and 'apply' action in combine rule, or a single 'combine' action
+def missing_apply : GICombineRule<
+  (defs root:$dst),
+  (match (G_ZEXT $dst, $src))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'combine' action needs at least one pattern to match, and C++ code to apply
+def combineop_missing_cxx : GICombineRule<
+  (defs root:$d),
+  (combine (wip_match_opcode G_TRUNC):$d)>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'combine' action needs at least one pattern to match, and C++ code to apply
+def combineop_missing_mir : GICombineRule<
+  (defs root:$d),
+  (combine "return APPLY;")>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Expected both a 'match' and 'apply' action in combine rule, or a single 'combine' action
+def mixed_combine_match : GICombineRule<
+  (defs root:$d),
+  (combine (G_ZEXT $d, $y), "return APPLY;"),
+  (match (G_ZEXT $d, $y))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Expected both a 'match' and 'apply' action in combine rule, or a single 'combine' action
+def mixed_combine_apply : GICombineRule<
+  (defs root:$d),
+  (combine "return APPLY;"),
+  (apply (G_ZEXT $d, $y))>;
+
 // CHECK: error: Failed to parse one or more rules
 
 def MyCombiner: GICombiner<"GenMyCombiner", [
@@ -326,5 +353,10 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
   using_flagref_in_match,
   badflagref_in_apply,
   mixed_cxx_apply,
-  matchdata_without_cxx_apply
+  matchdata_without_cxx_apply,
+  missing_apply,
+  combineop_missing_cxx,
+  combineop_missing_mir,
+  mixed_combine_match,
+  mixed_combine_apply
 ]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-parsing.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-parsing.td
index 85075359df737..7541639480fc5 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-parsing.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/pattern-parsing.td
@@ -383,6 +383,37 @@ def IntrinTest1 : GICombineRule<
   (match (int_convergent_1in_1out $a, $b)),
   (apply (int_convergent_sideeffects_1in_1out $a, $b))>;
 
+// CHECK:      (CombineRule name:CombineOperator0 id:14 root:d
+// CHECK-NEXT:   (MatchPats
+// CHECK-NEXT:     <match_root>d:(AnyOpcodePattern [G_TRUNC])
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (ApplyPats
+// CHECK-NEXT:     __CombineOperator0_combine_1:(CXXPattern apply code:"return APPLY;")
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (OperandTable MatchPats <empty>)
+// CHECK-NEXT:   (OperandTable ApplyPats <empty>)
+// CHECK-NEXT: )
+def CombineOperator0 : GICombineRule<
+  (defs root:$d),
+  (combine (wip_match_opcode G_TRUNC):$d, "return APPLY;")>;
+
+// CHECK:      (CombineRule name:CombineOperator1 id:15 root:a
+// CHECK-NEXT:   (MatchPats
+// CHECK-NEXT:     <match_root>__CombineOperator1_combine_0:(CodeGenInstructionPattern G_TRUNC operands:[<def>$a, $b])
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (ApplyPats
+// CHECK-NEXT:     __CombineOperator1_combine_1:(CXXPattern apply code:"return APPLY ${a} ${b};")
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (OperandTable MatchPats
+// CHECK-NEXT:     a -> __CombineOperator1_combine_0
+// CHECK-NEXT:     b -> <live-in>
+// CHECK-NEXT:   )
+// CHECK-NEXT:   (OperandTable ApplyPats <empty>)
+// CHECK-NEXT: )
+def CombineOperator1 : GICombineRule<
+  (defs root:$a),
+  (combine (G_TRUNC $a, $b), "return APPLY ${a} ${b};")>;
+
 def MyCombiner: GICombiner<"GenMyCombiner", [
   WipOpcodeTest0,
   WipOpcodeTest1,
@@ -397,5 +428,7 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
   TypeOfTest,
   MIFlagsTest,
   IntrinTest0,
-  IntrinTest1
+  IntrinTest1,
+  CombineOperator0,
+  CombineOperator1
 ]>;
diff --git a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
index 3259f8caeb773..a2afd6c7770fe 100644
--- a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
@@ -798,17 +798,46 @@ bool CombineRuleBuilder::parseAll() {
   if (!parseDefs(*RuleDef.getValueAsDag("Defs")))
     return false;
 
-  if (!Parser.parsePatternList(
-          *RuleDef.getValueAsDag("Match"),
-          [this](auto Pat) { return addMatchPattern(std::move(Pat)); }, "match",
-          (RuleDef.getName() + "_match").str()))
-    return false;
+  const DagInit &Act0 = *RuleDef.getValueAsDag("Action0");
+  const DagInit &Act1 = *RuleDef.getValueAsDag("Action1");
+
+  StringRef Act0Op = Act0.getOperatorAsDef(RuleDef.getLoc())->getName();
+  StringRef Act1Op = Act1.getOperatorAsDef(RuleDef.getLoc())->getName();
+
+  if (Act0Op == "match" && Act1Op == "apply") {
+    if (!Parser.parsePatternList(
+      Act0,
+            [this](auto Pat) { return addMatchPattern(std::move(Pat)); },
+            "match", (RuleDef.getName() + "_match").str()))
+      return false;
+
+    if (!Parser.parsePatternList(
+      Act1,
+            [this](auto Pat) { return addApplyPattern(std::move(Pat)); },
+            "apply", (RuleDef.getName() + "_apply").str()))
+      return false;
+
+  } else if (Act0Op == "combine" && Act1Op == "empty_action") {
+    // combine: everything is a "match" except C++ code which is an apply.
+    const auto AddCombinePat = [this](std::unique_ptr<Pattern> Pat) {
+      if (isa<CXXPattern>(Pat.get()))
+        return addApplyPattern(std::move(Pat));
+      return addMatchPattern(std::move(Pat));
+    };
 
-  if (!Parser.parsePatternList(
-          *RuleDef.getValueAsDag("Apply"),
-          [this](auto Pat) { return addApplyPattern(std::move(Pat)); }, "apply",
-          (RuleDef.getName() + "_apply").str()))
+    if (!Parser.parsePatternList(Act0, AddCombinePat, "combine",
+                                 (RuleDef.getName() + "_combine").str()))
+      return false;
+
+    if (MatchPats.empty() || ApplyPats.empty()) {
+      PrintError("'combine' action needs at least one pattern to match, and C++ code to apply");
+      return false;
+    }
+  } else {
+    PrintError("Expected both a 'match' and 'apply' action in combine rule, "
+      "or a single 'combine' action");
     return false;
+  }
 
   if (!buildRuleOperandsTable() || !typecheckPatterns() || !findRoots() ||
       !checkSemantics() || !buildPermutationsToEmit())
@@ -1340,6 +1369,8 @@ bool CombineRuleBuilder::checkSemantics() {
     }
   }
 
+  // TODO: Diagnose uses of MatchDatas if the Rule doesn't have C++ on both the
+  //       match and apply. It's useless in such cases.
   if (!hasOnlyCXXApplyPatterns() && !MatchDatas.empty()) {
     PrintError(MatchDataClassName +
                " can only be used if 'apply' in entirely written in C++");



More information about the llvm-commits mailing list