[Mlir-commits] [mlir] [MLIR][AsmPrinter] Add alwaysUseAliases printing flag (PR #232518)
Bruno Cardoso Lopes
llvmlistbot at llvm.org
Thu Oct 15 17:47:04 PDT 2026
https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/232518
>From 04bedac725f9e34446367c8a80baffbfce9cfa06 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Wed, 14 Oct 2026 21:08:44 -0700
Subject: [PATCH 1/3] [MLIR][AsmPrinter] Add alwaysUseAliases printing flag
Per-function IR dumps print a nested op, which takes the non top-level
path where types and attributes expand in full. For passes over functions
with heavy types this buries the dump in noise that aliases would fold away.
Add OpPrintingFlags::alwaysUseAliases plus the mlir-print-always-use-aliases
tool option. When set, printing initializes aliases and takes the top-level
path for any op, so nested dumps use aliases. The flag takes precedence over
local scope on purpose: pass instrumentation prints nested ops with local
scope, so without precedence the per-function dumps this flag targets would
never use aliases.
Reusing one AsmState across prints must not carry the previous op aliases or
dialect resources into the next print, so initialize now starts both fresh.
Assisted-by: Muse Code (Muse)
---
mlir/include/mlir/IR/OperationSupport.h | 13 +++++
mlir/lib/IR/AsmPrinter.cpp | 33 +++++++++--
.../Pass/ir-printing-always-use-aliases.mlir | 20 +++++++
mlir/unittests/IR/CMakeLists.txt | 1 +
mlir/unittests/IR/OpImplementationTest.cpp | 56 +++++++++++++++++++
5 files changed, 119 insertions(+), 4 deletions(-)
create mode 100644 mlir/test/Pass/ir-printing-always-use-aliases.mlir
diff --git a/mlir/include/mlir/IR/OperationSupport.h b/mlir/include/mlir/IR/OperationSupport.h
index 991d9847672e2e..3b43a1cf07bf99 100644
--- a/mlir/include/mlir/IR/OperationSupport.h
+++ b/mlir/include/mlir/IR/OperationSupport.h
@@ -1295,6 +1295,13 @@ class OpPrintingFlags {
/// Print SSA IDs using their NameLoc, if provided, as prefix.
OpPrintingFlags &printNameLocAsPrefix(bool enable = true);
+ /// Always use aliases even for non top-level ops. When set, printing any
+ /// op initializes aliases and prints via the top-level path, so dumps of
+ /// nested ops (e.g. per-function pass instrumentation) use type and
+ /// attribute aliases instead of expanding them. Takes precedence over
+ /// local scope.
+ OpPrintingFlags &alwaysUseAliases(bool enable = true);
+
/// Return if the given ElementsAttr should be elided.
bool shouldElideElementsAttr(ElementsAttr attr) const;
@@ -1325,6 +1332,9 @@ class OpPrintingFlags {
/// Return if operation verification should be skipped.
bool shouldAssumeVerified() const;
+ /// Return if aliases should always be used.
+ bool shouldAlwaysUseAliases() const;
+
/// Return if the printer should use local scope when dumping the IR.
bool shouldUseLocalScope() const;
@@ -1363,6 +1373,9 @@ class OpPrintingFlags {
/// Skip operation verification.
bool assumeVerifiedFlag : 1;
+ /// Always use aliases even for non top-level ops.
+ bool alwaysUseAliasesFlag : 1;
+
/// Print operations with numberings local to the current operation.
bool printLocalScope : 1;
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index f78d33338904b0..93fdf69cf286d5 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -210,6 +210,10 @@ struct AsmPrinterOptions {
llvm::cl::opt<bool> useNameLocAsPrefix{
"mlir-use-nameloc-as-prefix", llvm::cl::init(false),
llvm::cl::desc("Print SSA IDs using NameLocs as prefixes")};
+
+ llvm::cl::opt<bool> alwaysUseAliasesOpt{
+ "mlir-print-always-use-aliases", llvm::cl::init(false),
+ llvm::cl::desc("Always use aliases even for non top-level ops")};
};
} // namespace
@@ -226,9 +230,9 @@ void mlir::registerAsmPrinterCLOptions() {
OpPrintingFlags::OpPrintingFlags()
: printDebugInfoFlag(false), printDebugInfoPrettyFormFlag(false),
printGenericOpFormFlag(false), skipRegionsFlag(false),
- assumeVerifiedFlag(false), printLocalScope(false),
- printValueUsersFlag(false), printUniqueSSAIDsFlag(false),
- useNameLocAsPrefix(false) {
+ assumeVerifiedFlag(false), alwaysUseAliasesFlag(false),
+ printLocalScope(false), printValueUsersFlag(false),
+ printUniqueSSAIDsFlag(false), useNameLocAsPrefix(false) {
// Initialize based upon command line options, if they are available.
if (!clOptions.isConstructed())
return;
@@ -248,6 +252,7 @@ OpPrintingFlags::OpPrintingFlags()
printValueUsersFlag = clOptions->printValueUsers;
printUniqueSSAIDsFlag = clOptions->printUniqueSSAIDs;
useNameLocAsPrefix = clOptions->useNameLocAsPrefix;
+ alwaysUseAliasesFlag = clOptions->alwaysUseAliasesOpt;
}
/// Enable the elision of large elements attributes, by printing a '...'
@@ -299,6 +304,11 @@ OpPrintingFlags &OpPrintingFlags::assumeVerified(bool enable) {
return *this;
}
+OpPrintingFlags &OpPrintingFlags::alwaysUseAliases(bool enable) {
+ alwaysUseAliasesFlag = enable;
+ return *this;
+}
+
/// Use local scope when printing the operation. This allows for using the
/// printer in a more localized and thread-safe setting, but may not necessarily
/// be identical of what the IR will look like when dumping the full module.
@@ -378,6 +388,10 @@ bool OpPrintingFlags::shouldAssumeVerified() const {
return assumeVerifiedFlag;
}
+bool OpPrintingFlags::shouldAlwaysUseAliases() const {
+ return alwaysUseAliasesFlag;
+}
+
/// Return if the printer should use local scope when dumping the IR.
bool OpPrintingFlags::shouldUseLocalScope() const { return printLocalScope; }
@@ -1341,6 +1355,9 @@ class AliasState {
void AliasState::initialize(
Operation *op, const OpPrintingFlags &printerFlags,
DialectInterfaceCollection<OpAsmDialectInterface> &interfaces) {
+ // Each initialization starts fresh: a state reused across prints must not
+ // carry the previous op's aliases.
+ attrTypeToAlias.clear();
AliasInitializer initializer(interfaces, aliasAllocator);
initializer.initialize(op, printerFlags, attrTypeToAlias);
}
@@ -2005,6 +2022,9 @@ class AsmStateImpl {
/// Initialize the alias state to enable the printing of aliases.
void initializeAliases(Operation *op) {
+ // A state reused across prints must not re-print the previous op's
+ // dialect resources.
+ dialectResources.clear();
aliasState.initialize(op, printerFlags, interfaces);
}
@@ -4245,7 +4265,12 @@ void Operation::print(raw_ostream &os, const OpPrintingFlags &printerFlags) {
}
void Operation::print(raw_ostream &os, AsmState &state) {
OperationPrinter printer(os, state.getImpl());
- if (!getParent() && !state.getPrinterFlags().shouldUseLocalScope()) {
+ // alwaysUseAliases takes precedence over local scope, not just as a
+ // tiebreak: pass instrumentation prints nested ops with local scope, so
+ // without precedence the per-function dumps this flag targets would never
+ // use aliases. The two compose: aliases stay consistent within each dump.
+ if (state.getPrinterFlags().shouldAlwaysUseAliases() ||
+ (!getParent() && !state.getPrinterFlags().shouldUseLocalScope())) {
state.getImpl().initializeAliases(this);
printer.printTopLevelOperation(this);
} else {
diff --git a/mlir/test/Pass/ir-printing-always-use-aliases.mlir b/mlir/test/Pass/ir-printing-always-use-aliases.mlir
new file mode 100644
index 00000000000000..0a3969fcea76ae
--- /dev/null
+++ b/mlir/test/Pass/ir-printing-always-use-aliases.mlir
@@ -0,0 +1,20 @@
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' -mlir-print-ir-after=cse -o /dev/null 2>&1 | FileCheck --check-prefix=DEFAULT %s
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' -mlir-print-ir-after=cse -mlir-print-always-use-aliases -o /dev/null 2>&1 | FileCheck --check-prefix=ALIASES %s
+
+// Per-function IR dumps (CSE here only triggers one) print the func op,
+// which is not a top-level op, so types expand in full by default. With
+// the flag, the dump uses aliases.
+
+// DEFAULT: IR Dump After
+// DEFAULT-NOT: !tuple =
+// DEFAULT: func.func @f(%arg0: tuple<i32,
+// DEFAULT: return %arg0 : tuple<i32,
+
+// ALIASES: IR Dump After
+// ALIASES: !tuple = tuple<i32,
+// ALIASES: func.func @f(%arg0: !tuple, %arg1: !tuple) -> !tuple
+// ALIASES: return %arg0 : !tuple
+
+func.func @f(%arg0: tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>, %arg1: tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>) -> tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32> {
+ return %arg0 : tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>
+}
diff --git a/mlir/unittests/IR/CMakeLists.txt b/mlir/unittests/IR/CMakeLists.txt
index ce9b230222f621..def9b366e010f7 100644
--- a/mlir/unittests/IR/CMakeLists.txt
+++ b/mlir/unittests/IR/CMakeLists.txt
@@ -33,5 +33,6 @@ add_mlir_unittest(MLIRIRTests
target_include_directories(MLIRIRTests PRIVATE "${MLIR_BINARY_DIR}/test/lib/Dialect/Test")
mlir_target_link_libraries(MLIRIRTests PRIVATE MLIRIR)
target_link_libraries(MLIRIRTests PRIVATE MLIRBytecodeWriter)
+target_link_libraries(MLIRIRTests PRIVATE MLIRFuncDialect)
target_link_libraries(MLIRIRTests PRIVATE MLIRTestDialect)
target_link_libraries(MLIRIRTests PRIVATE MLIRRemarkStreamer)
diff --git a/mlir/unittests/IR/OpImplementationTest.cpp b/mlir/unittests/IR/OpImplementationTest.cpp
index 9049e57cf6fc18..c0140ab03b8910 100644
--- a/mlir/unittests/IR/OpImplementationTest.cpp
+++ b/mlir/unittests/IR/OpImplementationTest.cpp
@@ -7,11 +7,67 @@
//===----------------------------------------------------------------------===//
#include "mlir/IR/OpImplementation.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/IR/AsmState.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Parser/Parser.h"
#include "gtest/gtest.h"
using namespace mlir;
namespace {
+static unsigned countSubstr(StringRef str, StringRef sub) {
+ unsigned count = 0;
+ size_t pos = 0;
+ while ((pos = str.find(sub, pos)) != StringRef::npos) {
+ ++count;
+ pos += sub.size();
+ }
+ return count;
+}
+
+TEST(AlwaysUseAliasesTest, SharedStateDoesNotLeakAliases) {
+ MLIRContext context;
+ context.getOrLoadDialect<func::FuncDialect>();
+ const char *src = R"mlir(
+ builtin.module {
+ func.func @f(%arg0: tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>) -> tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32> {
+ return %arg0 : tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>
+ }
+ func.func @g(%arg0: tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32>) -> tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32> {
+ return %arg0 : tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32>
+ }
+ }
+ )mlir";
+ ParserConfig config(&context);
+ OwningOpRef<Operation *> module = parseSourceString(src, config);
+ ASSERT_TRUE(module);
+
+ SmallVector<func::FuncOp> funcs;
+ module->walk([&](func::FuncOp func) { funcs.push_back(func); });
+ ASSERT_EQ(funcs.size(), 2u);
+
+ // Print both with one shared state: each print must define exactly its
+ // own alias, with nothing leaking across.
+ OpPrintingFlags flags;
+ flags.alwaysUseAliases();
+ AsmState state(module.get(), flags);
+ std::string first, second;
+ {
+ llvm::raw_string_ostream os(first);
+ funcs[0]->print(os, state);
+ }
+ {
+ llvm::raw_string_ostream os(second);
+ funcs[1]->print(os, state);
+ }
+ EXPECT_EQ(countSubstr(first, "!tuple ="), 1u);
+ EXPECT_NE(first.find("tuple<i32,"), std::string::npos);
+ EXPECT_EQ(first.find("f32"), std::string::npos);
+ EXPECT_EQ(countSubstr(second, "!tuple ="), 1u);
+ EXPECT_NE(second.find("tuple<f32,"), std::string::npos);
+ EXPECT_EQ(second.find("i32"), std::string::npos);
+}
TEST(AsmDialectResourceHandleTest, DefaultHandle) {
AsmDialectResourceHandle handle;
EXPECT_EQ(handle.getResource(), nullptr);
>From 2d7ca4f3e0fa93c7ad6b3c1f1b6fef1513fb5788 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Thu, 15 Oct 2026 17:33:08 -0700
Subject: [PATCH 2/3] Drop shared-state unittest per review
---
mlir/unittests/IR/CMakeLists.txt | 1 -
mlir/unittests/IR/OpImplementationTest.cpp | 56 ----------------------
2 files changed, 57 deletions(-)
diff --git a/mlir/unittests/IR/CMakeLists.txt b/mlir/unittests/IR/CMakeLists.txt
index def9b366e010f7..ce9b230222f621 100644
--- a/mlir/unittests/IR/CMakeLists.txt
+++ b/mlir/unittests/IR/CMakeLists.txt
@@ -33,6 +33,5 @@ add_mlir_unittest(MLIRIRTests
target_include_directories(MLIRIRTests PRIVATE "${MLIR_BINARY_DIR}/test/lib/Dialect/Test")
mlir_target_link_libraries(MLIRIRTests PRIVATE MLIRIR)
target_link_libraries(MLIRIRTests PRIVATE MLIRBytecodeWriter)
-target_link_libraries(MLIRIRTests PRIVATE MLIRFuncDialect)
target_link_libraries(MLIRIRTests PRIVATE MLIRTestDialect)
target_link_libraries(MLIRIRTests PRIVATE MLIRRemarkStreamer)
diff --git a/mlir/unittests/IR/OpImplementationTest.cpp b/mlir/unittests/IR/OpImplementationTest.cpp
index c0140ab03b8910..9049e57cf6fc18 100644
--- a/mlir/unittests/IR/OpImplementationTest.cpp
+++ b/mlir/unittests/IR/OpImplementationTest.cpp
@@ -7,67 +7,11 @@
//===----------------------------------------------------------------------===//
#include "mlir/IR/OpImplementation.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
-#include "mlir/IR/AsmState.h"
-#include "mlir/IR/BuiltinOps.h"
-#include "mlir/Parser/Parser.h"
#include "gtest/gtest.h"
using namespace mlir;
namespace {
-static unsigned countSubstr(StringRef str, StringRef sub) {
- unsigned count = 0;
- size_t pos = 0;
- while ((pos = str.find(sub, pos)) != StringRef::npos) {
- ++count;
- pos += sub.size();
- }
- return count;
-}
-
-TEST(AlwaysUseAliasesTest, SharedStateDoesNotLeakAliases) {
- MLIRContext context;
- context.getOrLoadDialect<func::FuncDialect>();
- const char *src = R"mlir(
- builtin.module {
- func.func @f(%arg0: tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>) -> tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32> {
- return %arg0 : tuple<i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32>
- }
- func.func @g(%arg0: tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32>) -> tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32> {
- return %arg0 : tuple<f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32>
- }
- }
- )mlir";
- ParserConfig config(&context);
- OwningOpRef<Operation *> module = parseSourceString(src, config);
- ASSERT_TRUE(module);
-
- SmallVector<func::FuncOp> funcs;
- module->walk([&](func::FuncOp func) { funcs.push_back(func); });
- ASSERT_EQ(funcs.size(), 2u);
-
- // Print both with one shared state: each print must define exactly its
- // own alias, with nothing leaking across.
- OpPrintingFlags flags;
- flags.alwaysUseAliases();
- AsmState state(module.get(), flags);
- std::string first, second;
- {
- llvm::raw_string_ostream os(first);
- funcs[0]->print(os, state);
- }
- {
- llvm::raw_string_ostream os(second);
- funcs[1]->print(os, state);
- }
- EXPECT_EQ(countSubstr(first, "!tuple ="), 1u);
- EXPECT_NE(first.find("tuple<i32,"), std::string::npos);
- EXPECT_EQ(first.find("f32"), std::string::npos);
- EXPECT_EQ(countSubstr(second, "!tuple ="), 1u);
- EXPECT_NE(second.find("tuple<f32,"), std::string::npos);
- EXPECT_EQ(second.find("i32"), std::string::npos);
-}
TEST(AsmDialectResourceHandleTest, DefaultHandle) {
AsmDialectResourceHandle handle;
EXPECT_EQ(handle.getResource(), nullptr);
>From 6fa5353d7b3e0561d8d49fc65271b11879af3017 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Thu, 15 Oct 2026 17:34:08 -0700
Subject: [PATCH 3/3] Use canonicalize instead of CSE in test
---
mlir/test/Pass/ir-printing-always-use-aliases.mlir | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/mlir/test/Pass/ir-printing-always-use-aliases.mlir b/mlir/test/Pass/ir-printing-always-use-aliases.mlir
index 0a3969fcea76ae..328df66670a480 100644
--- a/mlir/test/Pass/ir-printing-always-use-aliases.mlir
+++ b/mlir/test/Pass/ir-printing-always-use-aliases.mlir
@@ -1,9 +1,9 @@
-// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' -mlir-print-ir-after=cse -o /dev/null 2>&1 | FileCheck --check-prefix=DEFAULT %s
-// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' -mlir-print-ir-after=cse -mlir-print-always-use-aliases -o /dev/null 2>&1 | FileCheck --check-prefix=ALIASES %s
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(canonicalize))' -mlir-print-ir-after=canonicalize -o /dev/null 2>&1 | FileCheck --check-prefix=DEFAULT %s
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(canonicalize))' -mlir-print-ir-after=canonicalize -mlir-print-always-use-aliases -o /dev/null 2>&1 | FileCheck --check-prefix=ALIASES %s
-// Per-function IR dumps (CSE here only triggers one) print the func op,
-// which is not a top-level op, so types expand in full by default. With
-// the flag, the dump uses aliases.
+// Per-function IR dumps (canonicalize here only triggers one; the pass
+// itself is irrelevant) print the func op, which is not a top-level op,
+// so types expand in full by default. With the flag, the dump uses aliases.
// DEFAULT: IR Dump After
// DEFAULT-NOT: !tuple =
More information about the Mlir-commits
mailing list