[Mlir-commits] [mlir] [mlir] Skip symbol-user type verify when no type has the interface (PR #212160)
Eric Hein
llvmlistbot at llvm.org
Sun Jul 26 17:50:54 PDT 2026
https://github.com/ehein6 created https://github.com/llvm/llvm-project/pull/212160
#198435 extended symbol table verification to walk, for every operation in a symbol table's scope, all operand, result and block-argument types plus the operation's entire attribute dictionary, looking for types that implement SymbolUserTypeInterface. The verifier runs after every pass, so that walk is repeated over the whole scope on every verification.
When no registered type implements the interface the walk cannot find anything, so the cost is pure overhead. It is not small: it doubled the compile time of a downstream MLIR pass pipeline, 14.1s -> 26.1s (1.85x) on a small graph, with the cost spread as a roughly fixed per-pass increment. The deduplication sets added by #198435 do not help here, as they are rebuilt on each verification.
Add MLIRContext::hasTypeImplementingInterface and query it once per symbol table, skipping the walk when no registered type participates. A type instance cannot exist unless its dialect is loaded and its type registered, so a negative answer guarantees that no type reachable in the scope implements the interface and verification results are unchanged.
>From f61b593c5a2f8d1f32a8b249c5f895c58fe6d7ee Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 24 Jul 2026 18:00:14 -0400
Subject: [PATCH] [mlir][IR] Skip symbol-user type verification when no type
implements the interface
#198435 extended symbol table verification to walk, for every operation in a
symbol table's scope, all operand, result and block-argument types plus the
operation's entire attribute dictionary, looking for types that implement
SymbolUserTypeInterface. The verifier runs after every pass, so that walk is
repeated over the whole scope on every verification.
When no registered type implements the interface the walk cannot find anything,
so the cost is pure overhead. It is not small: it doubled the compile time of a
downstream MLIR pass pipeline, 14.1s -> 26.1s (1.85x) on a small graph, with the
cost spread as a roughly fixed per-pass increment. The deduplication sets added
by #198435 do not help here, as they are rebuilt on each verification.
Add MLIRContext::hasTypeImplementingInterface and query it once per symbol
table, skipping the walk when no registered type participates. A type instance
cannot exist unless its dialect is loaded and its type registered, so a negative
answer guarantees that no type reachable in the scope implements the interface
and verification results are unchanged. The registry is read without locking, as
getRegisteredOperations does: Dialect::addType asserts it is not called from a
multi-threaded execution context.
---
mlir/include/mlir/IR/MLIRContext.h | 10 ++++++++++
mlir/lib/IR/MLIRContext.cpp | 6 ++++++
mlir/lib/IR/SymbolTable.cpp | 5 ++++-
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/mlir/include/mlir/IR/MLIRContext.h b/mlir/include/mlir/IR/MLIRContext.h
index 12cd00b60215e..1f4050362ef39 100644
--- a/mlir/include/mlir/IR/MLIRContext.h
+++ b/mlir/include/mlir/IR/MLIRContext.h
@@ -208,6 +208,16 @@ class MLIRContext {
/// Return true if this operation name is registered in this context.
bool isOperationRegistered(StringRef name);
+ /// Return true if any type registered with this context implements
+ /// `interfaceID`; false guarantees no type in this context's IR does.
+ bool hasTypeImplementingInterface(TypeID interfaceID);
+
+ /// Return true if any type registered with this context implements
+ /// `InterfaceT`.
+ template <typename InterfaceT> bool hasTypeImplementingInterface() {
+ return hasTypeImplementingInterface(InterfaceT::getInterfaceID());
+ }
+
// This is effectively private given that only MLIRContext.cpp can see the
// MLIRContextImpl type.
MLIRContextImpl &getImpl() { return *impl; }
diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp
index da891a7e6e014..58df5210e12e4 100644
--- a/mlir/lib/IR/MLIRContext.cpp
+++ b/mlir/lib/IR/MLIRContext.cpp
@@ -714,6 +714,12 @@ ArrayRef<RegisteredOperationName> MLIRContext::getRegisteredOperations() {
return impl->sortedRegisteredOperations;
}
+bool MLIRContext::hasTypeImplementingInterface(TypeID interfaceID) {
+ return llvm::any_of(impl->registeredTypes, [&](const auto ®istered) {
+ return registered.second->hasInterface(interfaceID);
+ });
+}
+
/// Return information for registered operations by dialect.
ArrayRef<RegisteredOperationName>
MLIRContext::getRegisteredOperationsByDialect(StringRef dialectName) {
diff --git a/mlir/lib/IR/SymbolTable.cpp b/mlir/lib/IR/SymbolTable.cpp
index 100eba7146d81..c07ca879538af 100644
--- a/mlir/lib/IR/SymbolTable.cpp
+++ b/mlir/lib/IR/SymbolTable.cpp
@@ -558,6 +558,8 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
// most once across the whole scope.
SetVector<Attribute> verifiedAttrs;
SetVector<Type> verifiedTypes;
+ const bool anyTypeImplementsSymbolUserTypeInterface =
+ op->getContext()->hasTypeImplementingInterface<SymbolUserTypeInterface>();
auto verifySymbolUserFn = [&](Operation *op) -> std::optional<WalkResult> {
if (SymbolUserOpInterface user = dyn_cast<SymbolUserOpInterface>(op))
if (failed(user.verifySymbolUses(symbolTable)))
@@ -570,7 +572,8 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
return WalkResult::interrupt();
}
}
- if (failed(verifyOpTypeSymbolUses(op, symbolTable, verifiedTypes)))
+ if (anyTypeImplementsSymbolUserTypeInterface &&
+ failed(verifyOpTypeSymbolUses(op, symbolTable, verifiedTypes)))
return WalkResult::interrupt();
return WalkResult::advance();
};
More information about the Mlir-commits
mailing list