Repository: llvm/llvm-project
Pull request #139816: [MLIR][DRR] Fix inconsistent operand and arg index usage
Author: login: xl4624
Labels: name: mlir:core
description: MLIR Core Infrastructure
name: mlir
Created: 2025-05-14T00:20:51Z | Merged: 2025-05-23T10:03:23Z

Background issue: #139813

In [emitEitherOperandMatch()](https://github.com/llvm/llvm-project/blob/e62fc14a5d214f801758b35bdcad0c8efc65e8b8/mlir/tools/mlir-tblgen/RewriterGen.cpp#L774) we check if `op.getArg(argIndex)` is a `NamedTypeConstraint`:

```cpp
} else if (isa<NamedTypeConstraint *>(op.getArg(argIndex))) {
      emitOperandMatch(tree, opName, /*operandName=*/formatv("v{0}", i).str(),
                       operandIndex,
                       /*operandMatcher=*/eitherArgTree.getArgAsLeaf(i),
                       /*argName=*/eitherArgTree.getArgName(i), argIndex,
                       /*variadicSubIndex=*/std::nullopt);
      ++operandIndex;
}
```

but in `emitOperandMatch()` we cast on `op.getArg(operandIndex)`, which is incorrect if the operation has attributes or other non-operand arguments before its operands.


Diff:
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 43a0bdaf86cf3..1d0849e479d37 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -1872,6 +1872,11 @@ def TestEitherOpB : TEST_Op<"either_op_b"> {
   let results = (outs I32:$output);
 }
 
+def TestEitherOpC : TEST_Op<"either_op_c"> {
+  let arguments = (ins AnyI32Attr:$attr, AnyInteger:$arg0, AnyInteger:$arg1);
+  let results = (outs I32:$output);
+}
+
 def : Pat<(TestEitherOpA (either I32:$arg1, I16:$arg2), $x),
           (TestEitherOpB $arg2, $x)>;
 
@@ -1883,6 +1888,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
 def TestEitherHelperOpA : TEST_Op<"either_helper_op_a"> {
   let arguments = (ins I32:$arg0);
   let results = (outs I32:$output);
diff --git a/mlir/test/mlir-tblgen/pattern.mlir b/mlir/test/mlir-tblgen/pattern.mlir
index 90905280c0796..27598fb63a6c8 100644
--- a/mlir/test/mlir-tblgen/pattern.mlir
+++ b/mlir/test/mlir-tblgen/pattern.mlir
@@ -609,8 +609,8 @@ func.func @redundantTest(%arg0: i32) -> i32 {
 // Test either directive
 //===----------------------------------------------------------------------===//
 
-// CHECK: @either_dag_leaf_only
-func.func @either_dag_leaf_only_1(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
+// CHECK-LABEL: @eitherDagLeafOnly
+func.func @eitherDagLeafOnly(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
   // CHECK: "test.either_op_b"(%arg1, %arg2) : (i16, i8) -> i32
   %0 = "test.either_op_a"(%arg0, %arg1, %arg2) : (i32, i16, i8) -> i32
   // CHECK: "test.either_op_b"(%arg1, %arg2) : (i16, i8) -> i32
@@ -618,8 +618,8 @@ func.func @either_dag_leaf_only_1(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
   return
 }
 
-// CHECK: @either_dag_leaf_dag_node
-func.func @either_dag_leaf_dag_node(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
+// CHECK-LABEL: @eitherDagLeafDagNode
+func.func @eitherDagLeafDagNode(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
   %0 = "test.either_op_b"(%arg0, %arg0) : (i32, i32) -> i32
   // CHECK: "test.either_op_b"(%arg1, %arg2) : (i16, i8) -> i32
   %1 = "test.either_op_a"(%0, %arg1, %arg2) : (i32, i16, i8) -> i32
@@ -628,8 +628,8 @@ func.func @either_dag_leaf_dag_node(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> ()
   return
 }
 
-// CHECK: @either_dag_node_dag_node
-func.func @either_dag_node_dag_node(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
+// CHECK-LABEL: @eitherDagNodeDagNode
+func.func @eitherDagNodeDagNode(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> () {
   %0 = "test.either_op_b"(%arg0, %arg0) : (i32, i32) -> i32
   %1 = "test.either_op_b"(%arg1, %arg1) : (i16, i16) -> i32
   // CHECK: "test.either_op_b"(%arg1, %arg2) : (i16, i8) -> i32
@@ -639,10 +639,22 @@ func.func @either_dag_node_dag_node(%arg0 : i32, %arg1 : i16, %arg2 : i8) -> ()
   return
 }
 
+// CHECK-LABEL: @testEitherOpWithAttr
+func.func @testEitherOpWithAttr(%arg0 : i32, %arg1 : i16) -> () {
+  // CHECK: "test.either_op_b"(%arg1, %arg0) : (i16, i32) -> i32
+  %0 = "test.either_op_c"(%arg0, %arg1) {attr = 0 : i32} : (i32, i16) -> i32
+  // CHECK: "test.either_op_b"(%arg1, %arg0) : (i16, i32) -> i32
+  %1 = "test.either_op_c"(%arg1, %arg0) {attr = 0 : i32} : (i16, i32) -> i32
+  // CHECK: "test.either_op_c"(%arg0, %arg1) <{attr = 1 : i32}> : (i32, i16) -> i32
+  %2 = "test.either_op_c"(%arg0, %arg1) {attr = 1 : i32} : (i32, i16) -> i32
+  return
+}
+
 //===----------------------------------------------------------------------===//
 // Test that ops without type deduction can be created with type builders.
 //===----------------------------------------------------------------------===//
 
+// CHECK-LABEL: @explicitReturnTypeTest
 func.func @explicitReturnTypeTest(%arg0 : i64) -> i8 {
   %0 = "test.source_op"(%arg0) {tag = 11 : i32} : (i64) -> i8
   // CHECK: "test.op_x"(%arg0) : (i64) -> i32
@@ -650,6 +662,7 @@ func.func @explicitReturnTypeTest(%arg0 : i64) -> i8 {
   return %0 : i8
 }
 
+// CHECK-LABEL: @returnTypeBuilderTest
 func.func @returnTypeBuilderTest(%arg0 : i1) -> i8 {
   %0 = "test.source_op"(%arg0) {tag = 22 : i32} : (i1) -> i8
   // CHECK: "test.op_x"(%arg0) : (i1) -> i1
@@ -657,6 +670,7 @@ func.func @returnTypeBuilderTest(%arg0 : i1) -> i8 {
   return %0 : i8
 }
 
+// CHECK-LABEL: @multipleReturnTypeBuildTest
 func.func @multipleReturnTypeBuildTest(%arg0 : i1) -> i1 {
   %0 = "test.source_op"(%arg0) {tag = 33 : i32} : (i1) -> i1
   // CHECK: "test.one_to_two"(%arg0) : (i1) -> (i64, i32)
@@ -666,6 +680,7 @@ func.func @multipleReturnTypeBuildTest(%arg0 : i1) -> i1 {
   return %0 : i1
 }
 
+// CHECK-LABEL: @copyValueType
 func.func @copyValueType(%arg0 : i8) -> i32 {
   %0 = "test.source_op"(%arg0) {tag = 44 : i32} : (i8) -> i32
   // CHECK: "test.op_x"(%arg0) : (i8) -> i8
@@ -673,6 +688,7 @@ func.func @copyValueType(%arg0 : i8) -> i32 {
   return %0 : i32
 }
 
+// CHECK-LABEL: @multipleReturnTypeDifferent
 func.func @multipleReturnTypeDifferent(%arg0 : i1) -> i64 {
   %0 = "test.source_op"(%arg0) {tag = 55 : i32} : (i1) -> i64
   // CHECK: "test.one_to_two"(%arg0) : (i1) -> (i1, i64)
@@ -684,6 +700,7 @@ func.func @multipleReturnTypeDifferent(%arg0 : i1) -> i64 {
 // Test that multiple trailing directives can be mixed in patterns.
 //===----------------------------------------------------------------------===//
 
+// CHECK-LABEL: @returnTypeAndLocation
 func.func @returnTypeAndLocation(%arg0 : i32) -> i1 {
   %0 = "test.source_op"(%arg0) {tag = 66 : i32} : (i32) -> i1
   // CHECK: "test.op_x"(%arg0) : (i32) -> i32 loc("loc1")
@@ -696,6 +713,7 @@ func.func @returnTypeAndLocation(%arg0 : i32) -> i1 {
 // Test that patterns can create ConstantStrAttr
 //===----------------------------------------------------------------------===//
 
+// CHECK-LABEL: @testConstantStrAttr
 func.func @testConstantStrAttr() -> () {
   // CHECK: test.has_str_value {value = "foo"}
   test.no_str_value {value = "bar"}
@@ -706,6 +724,7 @@ func.func @testConstantStrAttr() -> () {
 // Test that patterns with variadics propagate sizes
 //===----------------------------------------------------------------------===//
 
+// CHECK-LABEL: @testVariadic
 func.func @testVariadic(%arg_0: i32, %arg_1: i32, %brg: i64,
     %crg_0: f32, %crg_1: f32, %crg_2: f32, %crg_3: f32) -> () {
   // CHECK: "test.variadic_rewrite_dst_op"(%arg2, %arg3, %arg4, %arg5, %arg6, %arg0, %arg1) <{operandSegmentSizes = array<i32: 1, 4, 2>}> : (i64, f32, f32, f32, f32, i32, i32) -> ()
diff --git a/mlir/tools/mlir-tblgen/RewriterGen.cpp b/mlir/tools/mlir-tblgen/RewriterGen.cpp
index 58abcc2bee895..75721c89793b5 100644
--- a/mlir/tools/mlir-tblgen/RewriterGen.cpp
+++ b/mlir/tools/mlir-tblgen/RewriterGen.cpp
@@ -658,7 +658,7 @@ void PatternEmitter::emitOpMatch(DagNode tree, StringRef opName, int depth) {
     if (isa<NamedTypeConstraint *>(opArg)) {
       auto operandName =
           formatv("{0}.getODSOperands({1})", castedName, nextOperand);
-      emitOperandMatch(tree, castedName, operandName.str(), opArgIdx,
+      emitOperandMatch(tree, castedName, operandName.str(), nextOperand,
                        /*operandMatcher=*/tree.getArgAsLeaf(i),
                        /*argName=*/tree.getArgName(i), opArgIdx,
                        /*variadicSubIndex=*/std::nullopt);
@@ -680,7 +680,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
                                       int argIndex,
                                       std::optional<int> variadicSubIndex) {
   Operator &op = tree.getDialectOp(opMap);
-  auto *operand = cast<NamedTypeConstraint *>(op.getArg(operandIndex));
+  NamedTypeConstraint operand = op.getOperand(operandIndex);
 
   // If a constraint is specified, we need to generate C++ statements to
   // check the constraint.
@@ -693,8 +693,8 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
     // Only need to verify if the matcher's type is different from the one
     // of op definition.
     Constraint constraint = operandMatcher.getAsConstraint();
-    if (operand->constraint != constraint) {
-      if (operand->isVariableLength()) {
+    if (operand.constraint != constraint) {
+      if (operand.isVariableLength()) {
         auto error = formatv(
             "further constrain op {0}'s variadic operand #{1} unsupported now",
             op.getOperationName(), argIndex);
@@ -706,7 +706,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
           verifier, opName, self.str(),
           formatv(
               "\"operand {0} of op '{1}' failed to satisfy constraint: '{2}'\"",
-              operand - op.operand_begin(), op.getOperationName(),
+              operandIndex, op.getOperationName(),
               escapeString(constraint.getSummary()))
               .str());
     }
@@ -715,7 +715,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
   // Capture the value
   // `$_` is a special symbol to ignore op argument matching.
   if (!argName.empty() && argName != "_") {
-    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, operandIndex,
+    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, argIndex,
                                              variadicSubIndex);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", argName));
@@ -821,7 +821,7 @@ void PatternEmitter::emitVariadicOperandMatch(DagNode tree,
   StringRef variadicTreeName = variadicArgTree.getSymbol();
   if (!variadicTreeName.empty()) {
     auto res =
-        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, operandIndex,
+        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, argIndex,
                                       /*variadicSubIndex=*/std::nullopt);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", variadicTreeName));


Reviews:
user: login: loganchien
body: Thanks.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T05:28:22Z
user: login: joker-eph
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-17T11:05:12Z
user: login: xl4624
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T14:14:55Z
user: login: joker-eph
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-18T07:49:24Z
user: login: xl4624
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-18T19:05:04Z
user: login: loganchien
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T04:04:26Z
user: login: joker-eph
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T10:03:17Z

Review comments:
diff_hunk: @@ -1883,6 +1888,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
path: mlir/test/lib/Dialect/Test/TestOps.td
user: login: joker-eph
body: What happens with this without the changes to the RewriterGen.cpp file?
created_at: 2025-05-17T11:05:12Z
author_association: COLLABORATOR
diff_hunk: @@ -1883,6 +1888,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
path: mlir/test/lib/Dialect/Test/TestOps.td
user: login: xl4624
body: Without the changes this test fails on a cast assertion:

```
mlir-tblgen: /home/xiaomin/dev/llvm-project/llvm/include/llvm/Support/Casting.h:566: decltype(auto) llvm::cast(const From &) [To = mlir::tblgen::NamedTypeConstraint *, From = llvm::PointerUnion<mlir::tblgen::NamedAttribute *, mlir::tblgen::NamedProperty *, mlir::tblgen::NamedTypeConstraint *>]: Assertion `isa<To>(Val) && "cast<Ty>() argument of incompatible type!"' failed.
```

Coming from:

https://github.com/llvm/llvm-project/blob/751e6c08b2f6cd40908b8810a2a2da476e88417e/mlir/tools/mlir-tblgen/RewriterGen.cpp#L773-L778

https://github.com/llvm/llvm-project/blob/bf1d4a0710a6e1d2c99e579bcf435b92ab5d0506/mlir/tools/mlir-tblgen/RewriterGen.cpp#L677-L683

In `emitEitherOperand()`, we check if `op.getArg(argIndex)` is a `NamedTypeConstraint`, but in `emitOperandMatch` we cast `op.getArg(operandIndex)`. In the test case above, `operandIndex` and `argIndex` get out of sync due to the Attribute being in the front which leads to a cast with the wrong index (specifically `argIndex=1` referring to `$arg1` and `operandIndex=0` referring to the `ConstantAttr`)
created_at: 2025-05-17T14:14:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1883,6 +1888,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
path: mlir/test/lib/Dialect/Test/TestOps.td
user: login: joker-eph
body: Can we add a test for this rewrite though to ensure it works as expected?
created_at: 2025-05-18T07:49:24Z
author_association: COLLABORATOR
diff_hunk: @@ -1883,6 +1888,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
path: mlir/test/lib/Dialect/Test/TestOps.td
user: login: xl4624
body: Added a test for this in `pattern.mlir`, also cleaned up surrounding tests to match the style of the rest of the file.
created_at: 2025-05-18T19:04:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-14T00:21:08Z
body: <!--LLVM NEW CONTRIBUTOR COMMENT-->

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using `@` followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the [LLVM GitHub User Guide](https://llvm.org/docs/GitHub.html).

You can also ask questions in a comment on this PR, on the [LLVM Discord](https://discord.com/invite/xS7Z362) or on the [forums](https://discourse.llvm.org/).
author_association: NONE
user: login: llvmbot
created_at: 2025-05-14T00:21:37Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: Xiaomin Liu (xl4624)

<details>
<summary>Changes</summary>

Background issue: #<!-- -->139813

In [emitEitherOperandMatch()](https://github.com/llvm/llvm-project/blob/e62fc14a5d214f801758b35bdcad0c8efc65e8b8/mlir/tools/mlir-tblgen/RewriterGen.cpp#L774) we check if `op.getArg(argIndex)` is a `NamedTypeConstraint`:

```cpp
} else if (isa&lt;NamedTypeConstraint *&gt;(op.getArg(argIndex))) {
      emitOperandMatch(tree, opName, /*operandName=*/formatv("v{0}", i).str(),
                       operandIndex,
                       /*operandMatcher=*/eitherArgTree.getArgAsLeaf(i),
                       /*argName=*/eitherArgTree.getArgName(i), argIndex,
                       /*variadicSubIndex=*/std::nullopt);
      ++operandIndex;
}
```

but in `emitOperandMatch()` we cast on `op.getArg(operandIndex)`, which is incorrect if the operation has attributes or other non-operand arguments before its operands.

---
Full diff: https://github.com/llvm/llvm-project/pull/139816.diff


2 Files Affected:

- (modified) mlir/test/lib/Dialect/Test/TestOps.td (+8) 
- (modified) mlir/tools/mlir-tblgen/RewriterGen.cpp (+12-12) 


``````````diff
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 3e461999e2730..5bcd66b85c364 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -1878,6 +1878,11 @@ def TestEitherOpB : TEST_Op<"either_op_b"> {
   let results = (outs I32:$output);
 }
 
+def TestEitherOpC : TEST_Op<"either_op_b"> {
+  let arguments = (ins AnyI32Attr:$attr, AnyInteger:$arg0, AnyInteger:$arg1);
+  let results = (outs I32:$output);
+}
+
 def : Pat<(TestEitherOpA (either I32:$arg1, I16:$arg2), $x),
           (TestEitherOpB $arg2, $x)>;
 
@@ -1889,6 +1894,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
 def TestEitherHelperOpA : TEST_Op<"either_helper_op_a"> {
   let arguments = (ins I32:$arg0);
   let results = (outs I32:$output);
diff --git a/mlir/tools/mlir-tblgen/RewriterGen.cpp b/mlir/tools/mlir-tblgen/RewriterGen.cpp
index 58abcc2bee895..471baea5f5268 100644
--- a/mlir/tools/mlir-tblgen/RewriterGen.cpp
+++ b/mlir/tools/mlir-tblgen/RewriterGen.cpp
@@ -568,11 +568,11 @@ void PatternEmitter::emitOpMatch(DagNode tree, StringRef opName, int depth) {
     return formatv("castedOp{0}", depth);
   };
 
-  // The order of generating static matcher follows the topological order so
-  // that for every dependent DagNode already have their static matcher
-  // generated if needed. The reason we check if `getMatcherName(tree).empty()`
-  // is when we are generating the static matcher for a DagNode itself. In this
-  // case, we need to emit the function body rather than a function call.
+  // Static matchersare generated in topological order so that all dependent
+  // DagNodes have their static matcher generated beforehand if needed. We check
+  // if `getMatcherName(tree).empty()` for when we are generating the static
+  // matcher for a DagNode itself. In this case, we need to emit the function
+  // body rather than a function call.
   if (staticMatcherHelper.useStaticMatcher(tree) &&
       !staticMatcherHelper.getMatcherName(tree).empty()) {
     emitStaticMatchCall(tree, opName);
@@ -658,7 +658,7 @@ void PatternEmitter::emitOpMatch(DagNode tree, StringRef opName, int depth) {
     if (isa<NamedTypeConstraint *>(opArg)) {
       auto operandName =
           formatv("{0}.getODSOperands({1})", castedName, nextOperand);
-      emitOperandMatch(tree, castedName, operandName.str(), opArgIdx,
+      emitOperandMatch(tree, castedName, operandName.str(), nextOperand,
                        /*operandMatcher=*/tree.getArgAsLeaf(i),
                        /*argName=*/tree.getArgName(i), opArgIdx,
                        /*variadicSubIndex=*/std::nullopt);
@@ -680,7 +680,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
                                       int argIndex,
                                       std::optional<int> variadicSubIndex) {
   Operator &op = tree.getDialectOp(opMap);
-  auto *operand = cast<NamedTypeConstraint *>(op.getArg(operandIndex));
+  NamedTypeConstraint operand = op.getOperand(operandIndex);
 
   // If a constraint is specified, we need to generate C++ statements to
   // check the constraint.
@@ -693,8 +693,8 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
     // Only need to verify if the matcher's type is different from the one
     // of op definition.
     Constraint constraint = operandMatcher.getAsConstraint();
-    if (operand->constraint != constraint) {
-      if (operand->isVariableLength()) {
+    if (operand.constraint != constraint) {
+      if (operand.isVariableLength()) {
         auto error = formatv(
             "further constrain op {0}'s variadic operand #{1} unsupported now",
             op.getOperationName(), argIndex);
@@ -706,7 +706,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
           verifier, opName, self.str(),
           formatv(
               "\"operand {0} of op '{1}' failed to satisfy constraint: '{2}'\"",
-              operand - op.operand_begin(), op.getOperationName(),
+              operandIndex, op.getOperationName(),
               escapeString(constraint.getSummary()))
               .str());
     }
@@ -715,7 +715,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
   // Capture the value
   // `$_` is a special symbol to ignore op argument matching.
   if (!argName.empty() && argName != "_") {
-    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, operandIndex,
+    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, argIndex,
                                              variadicSubIndex);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", argName));
@@ -821,7 +821,7 @@ void PatternEmitter::emitVariadicOperandMatch(DagNode tree,
   StringRef variadicTreeName = variadicArgTree.getSymbol();
   if (!variadicTreeName.empty()) {
     auto res =
-        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, operandIndex,
+        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, argIndex,
                                       /*variadicSubIndex=*/std::nullopt);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", variadicTreeName));

``````````

</details>

author_association: MEMBER
user: login: llvmbot
created_at: 2025-05-14T00:21:38Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-core

Author: Xiaomin Liu (xl4624)

<details>
<summary>Changes</summary>

Background issue: #<!-- -->139813

In [emitEitherOperandMatch()](https://github.com/llvm/llvm-project/blob/e62fc14a5d214f801758b35bdcad0c8efc65e8b8/mlir/tools/mlir-tblgen/RewriterGen.cpp#L774) we check if `op.getArg(argIndex)` is a `NamedTypeConstraint`:

```cpp
} else if (isa&lt;NamedTypeConstraint *&gt;(op.getArg(argIndex))) {
      emitOperandMatch(tree, opName, /*operandName=*/formatv("v{0}", i).str(),
                       operandIndex,
                       /*operandMatcher=*/eitherArgTree.getArgAsLeaf(i),
                       /*argName=*/eitherArgTree.getArgName(i), argIndex,
                       /*variadicSubIndex=*/std::nullopt);
      ++operandIndex;
}
```

but in `emitOperandMatch()` we cast on `op.getArg(operandIndex)`, which is incorrect if the operation has attributes or other non-operand arguments before its operands.

---
Full diff: https://github.com/llvm/llvm-project/pull/139816.diff


2 Files Affected:

- (modified) mlir/test/lib/Dialect/Test/TestOps.td (+8) 
- (modified) mlir/tools/mlir-tblgen/RewriterGen.cpp (+12-12) 


``````````diff
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 3e461999e2730..5bcd66b85c364 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -1878,6 +1878,11 @@ def TestEitherOpB : TEST_Op<"either_op_b"> {
   let results = (outs I32:$output);
 }
 
+def TestEitherOpC : TEST_Op<"either_op_b"> {
+  let arguments = (ins AnyI32Attr:$attr, AnyInteger:$arg0, AnyInteger:$arg1);
+  let results = (outs I32:$output);
+}
+
 def : Pat<(TestEitherOpA (either I32:$arg1, I16:$arg2), $x),
           (TestEitherOpB $arg2, $x)>;
 
@@ -1889,6 +1894,9 @@ def : Pat<(TestEitherOpA (either (TestEitherOpB I32:$arg1, $_),
                           $x),
           (TestEitherOpB $arg2, $x)>;
 
+def : Pat<(TestEitherOpC ConstantAttr<I32Attr, "0">, (either $arg1, I32:$arg2)),
+          (TestEitherOpB $arg1, $arg2)>;
+
 def TestEitherHelperOpA : TEST_Op<"either_helper_op_a"> {
   let arguments = (ins I32:$arg0);
   let results = (outs I32:$output);
diff --git a/mlir/tools/mlir-tblgen/RewriterGen.cpp b/mlir/tools/mlir-tblgen/RewriterGen.cpp
index 58abcc2bee895..471baea5f5268 100644
--- a/mlir/tools/mlir-tblgen/RewriterGen.cpp
+++ b/mlir/tools/mlir-tblgen/RewriterGen.cpp
@@ -568,11 +568,11 @@ void PatternEmitter::emitOpMatch(DagNode tree, StringRef opName, int depth) {
     return formatv("castedOp{0}", depth);
   };
 
-  // The order of generating static matcher follows the topological order so
-  // that for every dependent DagNode already have their static matcher
-  // generated if needed. The reason we check if `getMatcherName(tree).empty()`
-  // is when we are generating the static matcher for a DagNode itself. In this
-  // case, we need to emit the function body rather than a function call.
+  // Static matchersare generated in topological order so that all dependent
+  // DagNodes have their static matcher generated beforehand if needed. We check
+  // if `getMatcherName(tree).empty()` for when we are generating the static
+  // matcher for a DagNode itself. In this case, we need to emit the function
+  // body rather than a function call.
   if (staticMatcherHelper.useStaticMatcher(tree) &&
       !staticMatcherHelper.getMatcherName(tree).empty()) {
     emitStaticMatchCall(tree, opName);
@@ -658,7 +658,7 @@ void PatternEmitter::emitOpMatch(DagNode tree, StringRef opName, int depth) {
     if (isa<NamedTypeConstraint *>(opArg)) {
       auto operandName =
           formatv("{0}.getODSOperands({1})", castedName, nextOperand);
-      emitOperandMatch(tree, castedName, operandName.str(), opArgIdx,
+      emitOperandMatch(tree, castedName, operandName.str(), nextOperand,
                        /*operandMatcher=*/tree.getArgAsLeaf(i),
                        /*argName=*/tree.getArgName(i), opArgIdx,
                        /*variadicSubIndex=*/std::nullopt);
@@ -680,7 +680,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
                                       int argIndex,
                                       std::optional<int> variadicSubIndex) {
   Operator &op = tree.getDialectOp(opMap);
-  auto *operand = cast<NamedTypeConstraint *>(op.getArg(operandIndex));
+  NamedTypeConstraint operand = op.getOperand(operandIndex);
 
   // If a constraint is specified, we need to generate C++ statements to
   // check the constraint.
@@ -693,8 +693,8 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
     // Only need to verify if the matcher's type is different from the one
     // of op definition.
     Constraint constraint = operandMatcher.getAsConstraint();
-    if (operand->constraint != constraint) {
-      if (operand->isVariableLength()) {
+    if (operand.constraint != constraint) {
+      if (operand.isVariableLength()) {
         auto error = formatv(
             "further constrain op {0}'s variadic operand #{1} unsupported now",
             op.getOperationName(), argIndex);
@@ -706,7 +706,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
           verifier, opName, self.str(),
           formatv(
               "\"operand {0} of op '{1}' failed to satisfy constraint: '{2}'\"",
-              operand - op.operand_begin(), op.getOperationName(),
+              operandIndex, op.getOperationName(),
               escapeString(constraint.getSummary()))
               .str());
     }
@@ -715,7 +715,7 @@ void PatternEmitter::emitOperandMatch(DagNode tree, StringRef opName,
   // Capture the value
   // `$_` is a special symbol to ignore op argument matching.
   if (!argName.empty() && argName != "_") {
-    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, operandIndex,
+    auto res = symbolInfoMap.findBoundSymbol(argName, tree, op, argIndex,
                                              variadicSubIndex);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", argName));
@@ -821,7 +821,7 @@ void PatternEmitter::emitVariadicOperandMatch(DagNode tree,
   StringRef variadicTreeName = variadicArgTree.getSymbol();
   if (!variadicTreeName.empty()) {
     auto res =
-        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, operandIndex,
+        symbolInfoMap.findBoundSymbol(variadicTreeName, tree, op, argIndex,
                                       /*variadicSubIndex=*/std::nullopt);
     if (res == symbolInfoMap.end())
       PrintFatalError(loc, formatv("symbol not found: {0}", variadicTreeName));

``````````

</details>

author_association: MEMBER
user: login: xl4624
created_at: 2025-05-23T02:32:33Z
body: Ping
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-23T10:03:43Z
body: <!--LLVM BUILDBOT INFORMATION COMMENT-->

@xl4624 Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our [build bots](https://lab.llvm.org/buildbot/). If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail [here](https://llvm.org/docs/MyFirstTypoFix.html#myfirsttypofix-issues-after-landing-your-pr).

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of [LLVM development](https://llvm.org/docs/DeveloperPolicy.html#patch-reversion-policy). You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

author_association: NONE