Skip to content

Conversation

fabianmcg
Copy link
Contributor

This patch fixes a bug occurring when properties are mixed with any of the InferType traits, causing tblgen to crash. A simple reproducer is:

def _TypeInferredPropOp : NS_Op<"type_inferred_prop_op_with_properties", [
    AllTypesMatch<["value", "result"]>
  ]> {
  let arguments = (ins Property<"unsigned">:$prop, AnyType:$value);
  let results = (outs AnyType:$result);
  let hasCustomAssemblyFormat = 1;
}

The issue occurs because of the call:

op.getArgToOperandOrAttribute(infer.getIndex());

To understand better the issue, consider:

attrOrOperandMapping = [Operand0]
arguments = [Prop0, Operand0]

In this case, infer.getIndex() will return 1 for Operand0, but getArgToOperandOrAttribute expects 0, causing the discrepancy that causes the crash.

The fix is to change attrOrOperandMapping to also include props.

Copy link

github-actions bot commented Sep 7, 2025

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Developer Policy and LLVM Discourse for more information.

@fabianmcg fabianmcg marked this pull request as ready for review September 7, 2025 22:12
@llvmbot llvmbot added mlir:core MLIR Core Infrastructure mlir labels Sep 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 7, 2025

@llvm/pr-subscribers-mlir

Author: Fabian Mora (fabianmcg)

Changes

This patch fixes a bug occurring when properties are mixed with any of the InferType traits, causing tblgen to crash. A simple reproducer is:

def _TypeInferredPropOp : NS_Op&lt;"type_inferred_prop_op_with_properties", [
    AllTypesMatch&lt;["value", "result"]&gt;
  ]&gt; {
  let arguments = (ins Property&lt;"unsigned"&gt;:$prop, AnyType:$value);
  let results = (outs AnyType:$result);
  let hasCustomAssemblyFormat = 1;
}

The issue occurs because of the call:

op.getArgToOperandOrAttribute(infer.getIndex());

To understand better the issue, consider:

attrOrOperandMapping = [Operand0]
arguments = [Prop0, Operand0]

In this case, infer.getIndex() will return 1 for Operand0, but getArgToOperandOrAttribute expects 0, causing the discrepancy that causes the crash.

The fix is to change attrOrOperandMapping to also include props.


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

4 Files Affected:

  • (modified) mlir/include/mlir/TableGen/Operator.h (+12-11)
  • (modified) mlir/lib/TableGen/Operator.cpp (+10-8)
  • (modified) mlir/test/mlir-tblgen/op-decl-and-defs.td (+9)
  • (modified) mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp (+10-9)
diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h
index 9e570373d9cd3..f0514d8e61748 100644
--- a/mlir/include/mlir/TableGen/Operator.h
+++ b/mlir/include/mlir/TableGen/Operator.h
@@ -323,21 +323,22 @@ class Operator {
   /// Requires: all result types are known.
   const InferredResultType &getInferredResultType(int index) const;
 
-  /// Pair consisting kind of argument and index into operands or attributes.
-  struct OperandOrAttribute {
-    enum class Kind { Operand, Attribute };
-    OperandOrAttribute(Kind kind, int index) {
-      packed = (index << 1) | (kind == Kind::Attribute);
+  /// Pair consisting kind of argument and index into operands, attributes, or
+  /// properties.
+  struct OperandAttrOrProp {
+    enum class Kind { Operand = 0x0, Attribute = 0x1, Property = 0x2 };
+    OperandAttrOrProp(Kind kind, int index) {
+      packed = (index << 2) | static_cast<int>(kind);
     }
-    int operandOrAttributeIndex() const { return (packed >> 1); }
-    Kind kind() { return (packed & 0x1) ? Kind::Attribute : Kind::Operand; }
+    int operandOrAttributeIndex() const { return (packed >> 2); }
+    Kind kind() const { return static_cast<Kind>(packed & 0x3); }
 
   private:
     int packed;
   };
 
-  /// Returns the OperandOrAttribute corresponding to the index.
-  OperandOrAttribute getArgToOperandOrAttribute(int index) const;
+  /// Returns the OperandAttrOrProp corresponding to the index.
+  OperandAttrOrProp getArgToOperandAttrOrProp(int index) const;
 
   /// Returns the builders of this operation.
   ArrayRef<Builder> getBuilders() const { return builders; }
@@ -405,8 +406,8 @@ class Operator {
   /// The argument with the same type as the result.
   SmallVector<InferredResultType> resultTypeMapping;
 
-  /// Map from argument to attribute or operand number.
-  SmallVector<OperandOrAttribute, 4> attrOrOperandMapping;
+  /// Map from argument to attribute, property, or operand number.
+  SmallVector<OperandAttrOrProp, 4> attrPropOrOperandMapping;
 
   /// The builders of this operator.
   SmallVector<Builder> builders;
diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp
index da86b00ebc300..926ffd0e363a0 100644
--- a/mlir/lib/TableGen/Operator.cpp
+++ b/mlir/lib/TableGen/Operator.cpp
@@ -385,7 +385,8 @@ void Operator::populateTypeInferenceInfo(
   if (getTrait("::mlir::OpTrait::SameOperandsAndResultType")) {
     // Check for a non-variable length operand to use as the type anchor.
     auto *operandI = llvm::find_if(arguments, [](const Argument &arg) {
-      NamedTypeConstraint *operand = llvm::dyn_cast_if_present<NamedTypeConstraint *>(arg);
+      NamedTypeConstraint *operand =
+          llvm::dyn_cast_if_present<NamedTypeConstraint *>(arg);
       return operand && !operand->isVariableLength();
     });
     if (operandI == arguments.end())
@@ -663,15 +664,17 @@ void Operator::populateOpStructure() {
       argDef = argDef->getValueAsDef("constraint");
 
     if (argDef->isSubClassOf(typeConstraintClass)) {
-      attrOrOperandMapping.push_back(
-          {OperandOrAttribute::Kind::Operand, operandIndex});
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Operand, operandIndex});
       arguments.emplace_back(&operands[operandIndex++]);
     } else if (argDef->isSubClassOf(attrClass)) {
-      attrOrOperandMapping.push_back(
-          {OperandOrAttribute::Kind::Attribute, attrIndex});
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Attribute, attrIndex});
       arguments.emplace_back(&attributes[attrIndex++]);
     } else {
       assert(argDef->isSubClassOf(propertyClass));
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Property, propIndex});
       arguments.emplace_back(&properties[propIndex++]);
     }
   }
@@ -867,9 +870,8 @@ auto Operator::VariableDecoratorIterator::unwrap(const Init *init)
   return VariableDecorator(cast<DefInit>(init)->getDef());
 }
 
-auto Operator::getArgToOperandOrAttribute(int index) const
-    -> OperandOrAttribute {
-  return attrOrOperandMapping[index];
+auto Operator::getArgToOperandAttrOrProp(int index) const -> OperandAttrOrProp {
+  return attrPropOrOperandMapping[index];
 }
 
 std::string Operator::getGetterName(StringRef name) const {
diff --git a/mlir/test/mlir-tblgen/op-decl-and-defs.td b/mlir/test/mlir-tblgen/op-decl-and-defs.td
index f213f50ae2f39..87b41f9dea995 100644
--- a/mlir/test/mlir-tblgen/op-decl-and-defs.td
+++ b/mlir/test/mlir-tblgen/op-decl-and-defs.td
@@ -543,3 +543,12 @@ def _BOp : NS_Op<"_op_with_leading_underscore_and_no_namespace", []>;
 
 // REDUCE_EXC-NOT: NS::AOp declarations
 // REDUCE_EXC-LABEL: NS::BOp declarations
+
+// CHECK-LABEL: _TypeInferredPropOp declarations
+def _TypeInferredPropOp : NS_Op<"type_inferred_prop_op_with_properties", [
+    AllTypesMatch<["value", "result"]>
+  ]> {
+  let arguments = (ins Property<"unsigned">:$prop, AnyType:$value);
+  let results = (outs AnyType:$result);
+  let hasCustomAssemblyFormat = 1;
+}
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 8ea4eb7b3eeca..270522380eb59 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -3849,9 +3849,9 @@ void OpEmitter::genTypeInterfaceMethods() {
     const InferredResultType &infer = op.getInferredResultType(i);
     if (!infer.isArg())
       continue;
-    Operator::OperandOrAttribute arg =
-        op.getArgToOperandOrAttribute(infer.getIndex());
-    if (arg.kind() == Operator::OperandOrAttribute::Kind::Operand) {
+    Operator::OperandAttrOrProp arg =
+        op.getArgToOperandAttrOrProp(infer.getIndex());
+    if (arg.kind() == Operator::OperandAttrOrProp::Kind::Operand) {
       maxAccessedIndex =
           std::max(maxAccessedIndex, arg.operandOrAttributeIndex());
     }
@@ -3877,17 +3877,16 @@ void OpEmitter::genTypeInterfaceMethods() {
       if (infer.isArg()) {
         // If this is an operand, just index into operand list to access the
         // type.
-        Operator::OperandOrAttribute arg =
-            op.getArgToOperandOrAttribute(infer.getIndex());
-        if (arg.kind() == Operator::OperandOrAttribute::Kind::Operand) {
+        Operator::OperandAttrOrProp arg =
+            op.getArgToOperandAttrOrProp(infer.getIndex());
+        if (arg.kind() == Operator::OperandAttrOrProp::Kind::Operand) {
           typeStr = ("operands[" + Twine(arg.operandOrAttributeIndex()) +
                      "].getType()")
                         .str();
 
           // If this is an attribute, index into the attribute dictionary.
-        } else {
-          auto *attr =
-              cast<NamedAttribute *>(op.getArg(arg.operandOrAttributeIndex()));
+        } else if (auto *attr = dyn_cast<NamedAttribute *>(
+                       op.getArg(arg.operandOrAttributeIndex()))) {
           body << "  ::mlir::TypedAttr odsInferredTypeAttr" << inferredTypeIdx
                << " = ";
           if (op.getDialect().usePropertiesForAttributes()) {
@@ -3907,6 +3906,8 @@ void OpEmitter::genTypeInterfaceMethods() {
           typeStr =
               ("odsInferredTypeAttr" + Twine(inferredTypeIdx) + ".getType()")
                   .str();
+        } else {
+          llvm_unreachable("Properties cannot be used for type inference");
         }
       } else if (std::optional<StringRef> builder =
                      op.getResult(infer.getResultIndex())

@llvmbot
Copy link
Member

llvmbot commented Sep 7, 2025

@llvm/pr-subscribers-mlir-core

Author: Fabian Mora (fabianmcg)

Changes

This patch fixes a bug occurring when properties are mixed with any of the InferType traits, causing tblgen to crash. A simple reproducer is:

def _TypeInferredPropOp : NS_Op&lt;"type_inferred_prop_op_with_properties", [
    AllTypesMatch&lt;["value", "result"]&gt;
  ]&gt; {
  let arguments = (ins Property&lt;"unsigned"&gt;:$prop, AnyType:$value);
  let results = (outs AnyType:$result);
  let hasCustomAssemblyFormat = 1;
}

The issue occurs because of the call:

op.getArgToOperandOrAttribute(infer.getIndex());

To understand better the issue, consider:

attrOrOperandMapping = [Operand0]
arguments = [Prop0, Operand0]

In this case, infer.getIndex() will return 1 for Operand0, but getArgToOperandOrAttribute expects 0, causing the discrepancy that causes the crash.

The fix is to change attrOrOperandMapping to also include props.


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

4 Files Affected:

  • (modified) mlir/include/mlir/TableGen/Operator.h (+12-11)
  • (modified) mlir/lib/TableGen/Operator.cpp (+10-8)
  • (modified) mlir/test/mlir-tblgen/op-decl-and-defs.td (+9)
  • (modified) mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp (+10-9)
diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h
index 9e570373d9cd3..f0514d8e61748 100644
--- a/mlir/include/mlir/TableGen/Operator.h
+++ b/mlir/include/mlir/TableGen/Operator.h
@@ -323,21 +323,22 @@ class Operator {
   /// Requires: all result types are known.
   const InferredResultType &getInferredResultType(int index) const;
 
-  /// Pair consisting kind of argument and index into operands or attributes.
-  struct OperandOrAttribute {
-    enum class Kind { Operand, Attribute };
-    OperandOrAttribute(Kind kind, int index) {
-      packed = (index << 1) | (kind == Kind::Attribute);
+  /// Pair consisting kind of argument and index into operands, attributes, or
+  /// properties.
+  struct OperandAttrOrProp {
+    enum class Kind { Operand = 0x0, Attribute = 0x1, Property = 0x2 };
+    OperandAttrOrProp(Kind kind, int index) {
+      packed = (index << 2) | static_cast<int>(kind);
     }
-    int operandOrAttributeIndex() const { return (packed >> 1); }
-    Kind kind() { return (packed & 0x1) ? Kind::Attribute : Kind::Operand; }
+    int operandOrAttributeIndex() const { return (packed >> 2); }
+    Kind kind() const { return static_cast<Kind>(packed & 0x3); }
 
   private:
     int packed;
   };
 
-  /// Returns the OperandOrAttribute corresponding to the index.
-  OperandOrAttribute getArgToOperandOrAttribute(int index) const;
+  /// Returns the OperandAttrOrProp corresponding to the index.
+  OperandAttrOrProp getArgToOperandAttrOrProp(int index) const;
 
   /// Returns the builders of this operation.
   ArrayRef<Builder> getBuilders() const { return builders; }
@@ -405,8 +406,8 @@ class Operator {
   /// The argument with the same type as the result.
   SmallVector<InferredResultType> resultTypeMapping;
 
-  /// Map from argument to attribute or operand number.
-  SmallVector<OperandOrAttribute, 4> attrOrOperandMapping;
+  /// Map from argument to attribute, property, or operand number.
+  SmallVector<OperandAttrOrProp, 4> attrPropOrOperandMapping;
 
   /// The builders of this operator.
   SmallVector<Builder> builders;
diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp
index da86b00ebc300..926ffd0e363a0 100644
--- a/mlir/lib/TableGen/Operator.cpp
+++ b/mlir/lib/TableGen/Operator.cpp
@@ -385,7 +385,8 @@ void Operator::populateTypeInferenceInfo(
   if (getTrait("::mlir::OpTrait::SameOperandsAndResultType")) {
     // Check for a non-variable length operand to use as the type anchor.
     auto *operandI = llvm::find_if(arguments, [](const Argument &arg) {
-      NamedTypeConstraint *operand = llvm::dyn_cast_if_present<NamedTypeConstraint *>(arg);
+      NamedTypeConstraint *operand =
+          llvm::dyn_cast_if_present<NamedTypeConstraint *>(arg);
       return operand && !operand->isVariableLength();
     });
     if (operandI == arguments.end())
@@ -663,15 +664,17 @@ void Operator::populateOpStructure() {
       argDef = argDef->getValueAsDef("constraint");
 
     if (argDef->isSubClassOf(typeConstraintClass)) {
-      attrOrOperandMapping.push_back(
-          {OperandOrAttribute::Kind::Operand, operandIndex});
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Operand, operandIndex});
       arguments.emplace_back(&operands[operandIndex++]);
     } else if (argDef->isSubClassOf(attrClass)) {
-      attrOrOperandMapping.push_back(
-          {OperandOrAttribute::Kind::Attribute, attrIndex});
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Attribute, attrIndex});
       arguments.emplace_back(&attributes[attrIndex++]);
     } else {
       assert(argDef->isSubClassOf(propertyClass));
+      attrPropOrOperandMapping.push_back(
+          {OperandAttrOrProp::Kind::Property, propIndex});
       arguments.emplace_back(&properties[propIndex++]);
     }
   }
@@ -867,9 +870,8 @@ auto Operator::VariableDecoratorIterator::unwrap(const Init *init)
   return VariableDecorator(cast<DefInit>(init)->getDef());
 }
 
-auto Operator::getArgToOperandOrAttribute(int index) const
-    -> OperandOrAttribute {
-  return attrOrOperandMapping[index];
+auto Operator::getArgToOperandAttrOrProp(int index) const -> OperandAttrOrProp {
+  return attrPropOrOperandMapping[index];
 }
 
 std::string Operator::getGetterName(StringRef name) const {
diff --git a/mlir/test/mlir-tblgen/op-decl-and-defs.td b/mlir/test/mlir-tblgen/op-decl-and-defs.td
index f213f50ae2f39..87b41f9dea995 100644
--- a/mlir/test/mlir-tblgen/op-decl-and-defs.td
+++ b/mlir/test/mlir-tblgen/op-decl-and-defs.td
@@ -543,3 +543,12 @@ def _BOp : NS_Op<"_op_with_leading_underscore_and_no_namespace", []>;
 
 // REDUCE_EXC-NOT: NS::AOp declarations
 // REDUCE_EXC-LABEL: NS::BOp declarations
+
+// CHECK-LABEL: _TypeInferredPropOp declarations
+def _TypeInferredPropOp : NS_Op<"type_inferred_prop_op_with_properties", [
+    AllTypesMatch<["value", "result"]>
+  ]> {
+  let arguments = (ins Property<"unsigned">:$prop, AnyType:$value);
+  let results = (outs AnyType:$result);
+  let hasCustomAssemblyFormat = 1;
+}
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 8ea4eb7b3eeca..270522380eb59 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -3849,9 +3849,9 @@ void OpEmitter::genTypeInterfaceMethods() {
     const InferredResultType &infer = op.getInferredResultType(i);
     if (!infer.isArg())
       continue;
-    Operator::OperandOrAttribute arg =
-        op.getArgToOperandOrAttribute(infer.getIndex());
-    if (arg.kind() == Operator::OperandOrAttribute::Kind::Operand) {
+    Operator::OperandAttrOrProp arg =
+        op.getArgToOperandAttrOrProp(infer.getIndex());
+    if (arg.kind() == Operator::OperandAttrOrProp::Kind::Operand) {
       maxAccessedIndex =
           std::max(maxAccessedIndex, arg.operandOrAttributeIndex());
     }
@@ -3877,17 +3877,16 @@ void OpEmitter::genTypeInterfaceMethods() {
       if (infer.isArg()) {
         // If this is an operand, just index into operand list to access the
         // type.
-        Operator::OperandOrAttribute arg =
-            op.getArgToOperandOrAttribute(infer.getIndex());
-        if (arg.kind() == Operator::OperandOrAttribute::Kind::Operand) {
+        Operator::OperandAttrOrProp arg =
+            op.getArgToOperandAttrOrProp(infer.getIndex());
+        if (arg.kind() == Operator::OperandAttrOrProp::Kind::Operand) {
           typeStr = ("operands[" + Twine(arg.operandOrAttributeIndex()) +
                      "].getType()")
                         .str();
 
           // If this is an attribute, index into the attribute dictionary.
-        } else {
-          auto *attr =
-              cast<NamedAttribute *>(op.getArg(arg.operandOrAttributeIndex()));
+        } else if (auto *attr = dyn_cast<NamedAttribute *>(
+                       op.getArg(arg.operandOrAttributeIndex()))) {
           body << "  ::mlir::TypedAttr odsInferredTypeAttr" << inferredTypeIdx
                << " = ";
           if (op.getDialect().usePropertiesForAttributes()) {
@@ -3907,6 +3906,8 @@ void OpEmitter::genTypeInterfaceMethods() {
           typeStr =
               ("odsInferredTypeAttr" + Twine(inferredTypeIdx) + ".getType()")
                   .str();
+        } else {
+          llvm_unreachable("Properties cannot be used for type inference");
         }
       } else if (std::optional<StringRef> builder =
                      op.getResult(infer.getResultIndex())

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a crash in mlir-tblgen when mixing properties with InferType traits. The issue occurred because the argument-to-operand/attribute mapping didn't account for properties, causing index mismatches during type inference.

Key changes:

  • Expand the argument mapping to include properties alongside operands and attributes
  • Update the enum and data structures to handle three kinds of arguments instead of two
  • Add proper error handling for unsupported property-based type inference

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
mlir/include/mlir/TableGen/Operator.h Updates data structures and enums to support properties in argument mapping
mlir/lib/TableGen/Operator.cpp Implements the expanded mapping logic and updates method signatures
mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp Updates type inference code to use new mapping and adds error handling
mlir/test/mlir-tblgen/op-decl-and-defs.td Adds test case for the previously crashing scenario

@fabianmcg fabianmcg requested a review from zero9178 September 8, 2025 12:17
Copy link
Member

@zero9178 zero9178 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look good to me overall! Just one comment regarding error handling

@fabianmcg fabianmcg requested a review from zero9178 September 8, 2025 13:02
@fabianmcg fabianmcg merged commit 9465ef5 into llvm:main Sep 10, 2025
9 checks passed
@fabianmcg fabianmcg deleted the pr-fix-tblgen branch September 10, 2025 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mlir:core MLIR Core Infrastructure mlir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants