[Mlir-commits] [llvm] [mlir] [mlir][IR] Add SymbolUserTypeInterface (PR #198435)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon May 18 18:21:49 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-ods

Author: Jared Hoberock (jaredhoberock)

<details>
<summary>Changes</summary>

This change adds SymbolUserTypeInterface, analogous to SymbolUserAttrInterface, and extends SymbolTable verification to check participating types.

Unlike SymbolUserAttrInterface, verification first collects the types owned by an operation, (operand/result types, block argument types, attribute-contained types, nested type parameters). Each distinct type is then verified at most once per operation.

I'm interested in feedback on whether this collection step is the right structure, or whether verification should instead mirror the existing SymbolUserAttrInterface traversal.

Assisted-by: Codex (OpenAI)
Assisted-by: Claude (Anthropic)

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


9 Files Affected:

- (modified) mlir/include/mlir/IR/CMakeLists.txt (+2) 
- (modified) mlir/include/mlir/IR/SymbolInterfaces.td (+20-1) 
- (modified) mlir/include/mlir/IR/SymbolTable.h (+1) 
- (modified) mlir/lib/IR/SymbolTable.cpp (+55) 
- (added) mlir/test/IR/test-verifiers-symbol-user-type.mlir (+95) 
- (modified) mlir/test/lib/Dialect/Test/TestTypeDefs.td (+9) 
- (modified) mlir/test/lib/Dialect/Test/TestTypes.cpp (+14) 
- (modified) mlir/test/lib/Dialect/Test/TestTypes.h (+1) 
- (modified) utils/bazel/llvm-project-overlay/mlir/BUILD.bazel (+27-18) 


``````````diff
diff --git a/mlir/include/mlir/IR/CMakeLists.txt b/mlir/include/mlir/IR/CMakeLists.txt
index 15518901b901a..54ebbd0b51bb5 100644
--- a/mlir/include/mlir/IR/CMakeLists.txt
+++ b/mlir/include/mlir/IR/CMakeLists.txt
@@ -2,6 +2,8 @@ add_mlir_interface(SymbolInterfaces)
 set(LLVM_TARGET_DEFINITIONS SymbolInterfaces.td)
 mlir_tablegen(SymbolInterfacesAttrInterface.h.inc -gen-attr-interface-decls)
 mlir_tablegen(SymbolInterfacesAttrInterface.cpp.inc -gen-attr-interface-defs)
+mlir_tablegen(SymbolInterfacesTypeInterface.h.inc -gen-type-interface-decls)
+mlir_tablegen(SymbolInterfacesTypeInterface.cpp.inc -gen-type-interface-defs)
 add_mlir_interface(RegionKindInterface)
 
 add_mlir_type_interface(QuantStorageTypeInterface)
diff --git a/mlir/include/mlir/IR/SymbolInterfaces.td b/mlir/include/mlir/IR/SymbolInterfaces.td
index ebe0c26637ad3..292c355cbe157 100644
--- a/mlir/include/mlir/IR/SymbolInterfaces.td
+++ b/mlir/include/mlir/IR/SymbolInterfaces.td
@@ -228,7 +228,7 @@ def SymbolUserAttrInterface : AttrInterface<"SymbolUserAttrInterface"> {
     interface allows for users of symbols to hook into verification and other
     symbol related utilities that are either costly or otherwise disallowed
     within an operation (e.g., recreating symbol users per op verified rather
-    than per symbol table, or querying symbols usage of sibblings).
+    than per symbol table, or querying symbols usage of siblings).
   }];
   let cppNamespace = "::mlir";
 
@@ -241,6 +241,25 @@ def SymbolUserAttrInterface : AttrInterface<"SymbolUserAttrInterface"> {
   ];
 }
 
+def SymbolUserTypeInterface : TypeInterface<"SymbolUserTypeInterface"> {
+  let description = [{
+    This interface describes a type that may use a `Symbol`. This interface
+    allows types to hook into verification that needs a symbol table, which is
+    costly or otherwise disallowed within type construction and uniquing.
+    `op` is the operation whose verification triggered the check and should be
+    used as the anchor for symbol lookups.
+  }];
+  let cppNamespace = "::mlir";
+
+  let methods = [
+    InterfaceMethod<"Verify the symbol uses held by this type of this operation.",
+      "::llvm::LogicalResult", "verifySymbolUses",
+      (ins "::mlir::Operation *":$op,
+           "::mlir::SymbolTableCollection &":$symbolTable)
+    >,
+  ];
+}
+
 //===----------------------------------------------------------------------===//
 // Symbol Traits
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/IR/SymbolTable.h b/mlir/include/mlir/IR/SymbolTable.h
index a174062d8d019..e4790037d37b2 100644
--- a/mlir/include/mlir/IR/SymbolTable.h
+++ b/mlir/include/mlir/IR/SymbolTable.h
@@ -500,5 +500,6 @@ ParseResult parseOptionalVisibilityKeyword(OpAsmParser &parser,
 /// Include the generated symbol interfaces.
 #include "mlir/IR/SymbolInterfaces.h.inc"
 #include "mlir/IR/SymbolInterfacesAttrInterface.h.inc"
+#include "mlir/IR/SymbolInterfacesTypeInterface.h.inc"
 
 #endif // MLIR_IR_SYMBOLTABLE_H
diff --git a/mlir/lib/IR/SymbolTable.cpp b/mlir/lib/IR/SymbolTable.cpp
index 078401c8380f4..3a866824dfde2 100644
--- a/mlir/lib/IR/SymbolTable.cpp
+++ b/mlir/lib/IR/SymbolTable.cpp
@@ -476,6 +476,58 @@ raw_ostream &mlir::operator<<(raw_ostream &os,
 // SymbolTable Trait Types
 //===----------------------------------------------------------------------===//
 
+/// Collect `type` and any nested type parameters reachable from it.
+static void collectSymbolUseTypes(Type type, SetVector<Type> &types) {
+  type.walk<WalkOrder::PreOrder>([&](Type nestedType) {
+    types.insert(nestedType);
+    return WalkResult::advance();
+  });
+}
+
+/// Collect types directly owned by `op`, including nested type parameters.
+static void collectSymbolUseTypesInOpTypes(Operation *op,
+                                           SetVector<Type> &types) {
+  for (Type type : op->getOperandTypes())
+    collectSymbolUseTypes(type, types);
+  for (Type type : op->getResultTypes())
+    collectSymbolUseTypes(type, types);
+  for (Region &region : op->getRegions()) {
+    for (Block &block : region) {
+      for (BlockArgument argument : block.getArguments())
+        collectSymbolUseTypes(argument.getType(), types);
+    }
+  }
+}
+
+/// Collect types nested in operation attributes.
+static void collectSymbolUseTypesInAttrs(Operation *op,
+                                         SetVector<Type> &types) {
+  op->getAttrDictionary().walk<WalkOrder::PreOrder>([&](Type type) {
+    collectSymbolUseTypes(type, types);
+    return WalkResult::advance();
+  });
+}
+
+/// Verify all symbol uses held by types owned by `op`.
+static LogicalResult
+verifyOpTypeSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
+  // Type positions can overlap, and the same uniqued type may appear in
+  // multiple operands, results, block arguments, or attributes. Collect first
+  // so each (operation, type) pair is verified at most once.
+  SetVector<Type> types;
+  collectSymbolUseTypesInAttrs(op, types);
+  collectSymbolUseTypesInOpTypes(op, types);
+
+  for (Type type : types) {
+    auto user = dyn_cast<SymbolUserTypeInterface>(type);
+    if (!user)
+      continue;
+    if (failed(user.verifySymbolUses(op, symbolTable)))
+      return failure();
+  }
+  return success();
+}
+
 LogicalResult detail::verifySymbolTable(Operation *op) {
   if (op->getNumRegions() != 1)
     return op->emitOpError()
@@ -516,6 +568,8 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
           return WalkResult::interrupt();
       }
     }
+    if (failed(verifyOpTypeSymbolUses(op, symbolTable)))
+      return WalkResult::interrupt();
     return WalkResult::advance();
   };
 
@@ -1137,3 +1191,4 @@ ParseResult impl::parseOptionalVisibilityKeyword(OpAsmParser &parser,
 /// Include the generated symbol interfaces.
 #include "mlir/IR/SymbolInterfaces.cpp.inc"
 #include "mlir/IR/SymbolInterfacesAttrInterface.cpp.inc"
+#include "mlir/IR/SymbolInterfacesTypeInterface.cpp.inc"
diff --git a/mlir/test/IR/test-verifiers-symbol-user-type.mlir b/mlir/test/IR/test-verifiers-symbol-user-type.mlir
new file mode 100644
index 0000000000000..306b56065e71e
--- /dev/null
+++ b/mlir/test/IR/test-verifiers-symbol-user-type.mlir
@@ -0,0 +1,95 @@
+// RUN: mlir-opt %s -verify-diagnostics -split-input-file
+
+module {
+  func.func private @existing_symbol()
+
+  "test.type_producer"() : () -> !test.symbol_ref<@existing_symbol>
+}
+
+// -----
+
+module {
+  // expected-error at +1 {{TestSymbolUserType::verifySymbolUses: '@non_existent_symbol' does not reference a valid symbol}}
+  "test.type_producer"() : () -> !test.symbol_ref<@non_existent_symbol>
+}
+
+// -----
+
+module {
+  func.func private @existing_symbol()
+
+  %0 = "test.type_producer"() : () -> !test.symbol_ref<@existing_symbol>
+  "test.type_consumer"(%0) : (!test.symbol_ref<@existing_symbol>) -> ()
+}
+
+// -----
+
+module {
+  func.func private @existing_symbol()
+
+  "test.type_producer"() : () -> tuple<!test.symbol_ref<@existing_symbol>>
+}
+
+// -----
+
+module {
+  // expected-error at +1 {{TestSymbolUserType::verifySymbolUses: '@non_existent_symbol' does not reference a valid symbol}}
+  "test.type_producer"() : () -> tuple<!test.symbol_ref<@non_existent_symbol>>
+}
+
+// -----
+
+module {
+  func.func private @existing_symbol()
+
+  func.func private @uses_symbol_type(%arg0: !test.symbol_ref<@existing_symbol>)
+}
+
+// -----
+
+module {
+  // expected-error at +1 {{TestSymbolUserType::verifySymbolUses: '@non_existent_symbol' does not reference a valid symbol}}
+  func.func private @uses_symbol_type(%arg0: !test.symbol_ref<@non_existent_symbol>)
+}
+
+// -----
+
+module {
+  func.func private @existing_symbol()
+
+  "test.one_region_op"() ({
+  ^bb0(%arg0: !test.symbol_ref<@existing_symbol>):
+    "test.valid"() : () -> ()
+  }) : () -> ()
+}
+
+// -----
+
+module {
+  // expected-error at +1 {{TestSymbolUserType::verifySymbolUses: '@non_existent_symbol' does not reference a valid symbol}}
+  "test.one_region_op"() ({
+  ^bb0(%arg0: !test.symbol_ref<@non_existent_symbol>):
+    "test.valid"() : () -> ()
+  }) : () -> ()
+}
+
+// -----
+
+module {
+  func.func private @existing_symbol()
+
+  "test.typed_attr"() <{
+    type = !test.symbol_ref<@existing_symbol>,
+    attr = 0 : i32
+  }> : () -> ()
+}
+
+// -----
+
+module {
+  // expected-error at +1 {{TestSymbolUserType::verifySymbolUses: '@non_existent_symbol' does not reference a valid symbol}}
+  "test.typed_attr"() <{
+    type = !test.symbol_ref<@non_existent_symbol>,
+    attr = 0 : i32
+  }> : () -> ()
+}
diff --git a/mlir/test/lib/Dialect/Test/TestTypeDefs.td b/mlir/test/lib/Dialect/Test/TestTypeDefs.td
index 08600ce713a17..027bb3967c405 100644
--- a/mlir/test/lib/Dialect/Test/TestTypeDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestTypeDefs.td
@@ -19,6 +19,7 @@ include "TestAttrDefs.td"
 include "TestInterfaces.td"
 include "mlir/IR/BuiltinTypes.td"
 include "mlir/IR/BuiltinTypeInterfaces.td"
+include "mlir/IR/SymbolInterfaces.td"
 include "mlir/Interfaces/DataLayoutInterfaces.td"
 include "mlir/Dialect/Bufferization/IR/BufferizationTypeInterfaces.td"
 
@@ -84,6 +85,14 @@ def CompoundNestedOuterTypeQual : Test_Type<"CompoundNestedOuterQual"> {
   let assemblyFormat = "`<` `i`  qualified($inner) `>`";
 }
 
+def TestSymbolUserType : Test_Type<"TestSymbolUser",
+    [DeclareTypeInterfaceMethods<SymbolUserTypeInterface>]> {
+  let mnemonic = "symbol_ref";
+  let summary = "Test type that references a symbol";
+  let parameters = (ins "::mlir::FlatSymbolRefAttr":$symbol);
+  let assemblyFormat = "`<` $symbol `>`";
+}
+
 // An example of how one could implement a standard integer.
 def IntegerType : Test_Type<"TestInteger"> {
   let mnemonic = "int";
diff --git a/mlir/test/lib/Dialect/Test/TestTypes.cpp b/mlir/test/lib/Dialect/Test/TestTypes.cpp
index ef3396fc4f610..285d1753e38ed 100644
--- a/mlir/test/lib/Dialect/Test/TestTypes.cpp
+++ b/mlir/test/lib/Dialect/Test/TestTypes.cpp
@@ -332,6 +332,20 @@ uint64_t TestTypeWithLayoutType::extractKind(DataLayoutEntryListRef params,
   return 1;
 }
 
+//===----------------------------------------------------------------------===//
+// TestSymbolUserType
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+TestSymbolUserType::verifySymbolUses(Operation *op,
+                                     SymbolTableCollection &symbolTable) const {
+  if (!symbolTable.lookupNearestSymbolFrom<SymbolOpInterface>(op, getSymbol()))
+    return op->emitOpError()
+           << "TestSymbolUserType::verifySymbolUses: '" << getSymbol()
+           << "' does not reference a valid symbol";
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // Dynamic Types
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/lib/Dialect/Test/TestTypes.h b/mlir/test/lib/Dialect/Test/TestTypes.h
index 705fb86e9e9b3..cf3d1057de675 100644
--- a/mlir/test/lib/Dialect/Test/TestTypes.h
+++ b/mlir/test/lib/Dialect/Test/TestTypes.h
@@ -24,6 +24,7 @@
 #include "mlir/IR/Dialect.h"
 #include "mlir/IR/DialectImplementation.h"
 #include "mlir/IR/Operation.h"
+#include "mlir/IR/SymbolTable.h"
 #include "mlir/IR/Types.h"
 #include "mlir/Interfaces/DataLayoutInterfaces.h"
 
diff --git a/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel b/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel
index 15e01d7472191..ba827aba8aacb 100644
--- a/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel
@@ -79,24 +79,33 @@ filegroup(
 
 exports_files(glob(["include/**/*.td"]))
 
-[
-    gentbl_cc_library(
-        name = name + "IncGen",
-        tbl_outs = {
-            "include/mlir/IR/" + name + ".h.inc": ["-gen-op-interface-decls"],
-            "include/mlir/IR/" + name + ".cpp.inc": ["-gen-op-interface-defs"],
-            "include/mlir/IR/" + name + "AttrInterface.h.inc": ["-gen-attr-interface-decls"],
-            "include/mlir/IR/" + name + "AttrInterface.cpp.inc": ["-gen-attr-interface-defs"],
-        },
-        tblgen = ":mlir-tblgen",
-        td_file = "include/mlir/IR/" + name + ".td",
-        deps = [":OpBaseTdFiles"],
-    )
-    for name in [
-        "RegionKindInterface",
-        "SymbolInterfaces",
-    ]
-]
+gentbl_cc_library(
+    name = "RegionKindInterfaceIncGen",
+    tbl_outs = {
+        "include/mlir/IR/RegionKindInterface.h.inc": ["-gen-op-interface-decls"],
+        "include/mlir/IR/RegionKindInterface.cpp.inc": ["-gen-op-interface-defs"],
+        "include/mlir/IR/RegionKindInterfaceAttrInterface.h.inc": ["-gen-attr-interface-decls"],
+        "include/mlir/IR/RegionKindInterfaceAttrInterface.cpp.inc": ["-gen-attr-interface-defs"],
+    },
+    tblgen = ":mlir-tblgen",
+    td_file = "include/mlir/IR/RegionKindInterface.td",
+    deps = [":OpBaseTdFiles"],
+)
+
+gentbl_cc_library(
+    name = "SymbolInterfacesIncGen",
+    tbl_outs = {
+        "include/mlir/IR/SymbolInterfaces.h.inc": ["-gen-op-interface-decls"],
+        "include/mlir/IR/SymbolInterfaces.cpp.inc": ["-gen-op-interface-defs"],
+        "include/mlir/IR/SymbolInterfacesAttrInterface.h.inc": ["-gen-attr-interface-decls"],
+        "include/mlir/IR/SymbolInterfacesAttrInterface.cpp.inc": ["-gen-attr-interface-defs"],
+        "include/mlir/IR/SymbolInterfacesTypeInterface.h.inc": ["-gen-type-interface-decls"],
+        "include/mlir/IR/SymbolInterfacesTypeInterface.cpp.inc": ["-gen-type-interface-defs"],
+    },
+    tblgen = ":mlir-tblgen",
+    td_file = "include/mlir/IR/SymbolInterfaces.td",
+    deps = [":OpBaseTdFiles"],
+)
 
 gentbl_cc_library(
     name = "OpAsmInterfaceIncGen",

``````````

</details>


https://github.com/llvm/llvm-project/pull/198435


More information about the Mlir-commits mailing list