[Mlir-commits] [mlir] [mlir] Add normalize pass to MLIR (PR #186647)
lonely eagle
llvmlistbot at llvm.org
Tue Jul 7 08:19:22 PDT 2026
https://github.com/linuxlonelyeagle updated https://github.com/llvm/llvm-project/pull/186647
>From d6f66c9c22d70eb3722b8e520331e872234467af Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Fri, 13 Mar 2026 02:26:50 +0000
Subject: [PATCH 1/4] complete basic normalize.
---
mlir/include/mlir/Transforms/Passes.h | 1 +
mlir/include/mlir/Transforms/Passes.td | 8 ++
mlir/lib/Transforms/CMakeLists.txt | 1 +
mlir/lib/Transforms/Normalize.cpp | 112 +++++++++++++++++++++++++
mlir/test/Transforms/normalize.mlir | 84 +++++++++++++++++++
5 files changed, 206 insertions(+)
create mode 100644 mlir/lib/Transforms/Normalize.cpp
create mode 100644 mlir/test/Transforms/normalize.mlir
diff --git a/mlir/include/mlir/Transforms/Passes.h b/mlir/include/mlir/Transforms/Passes.h
index 313755fde3479..be56a548f63b1 100644
--- a/mlir/include/mlir/Transforms/Passes.h
+++ b/mlir/include/mlir/Transforms/Passes.h
@@ -42,6 +42,7 @@ class GreedyRewriteConfig;
#define GEN_PASS_DECL_LOOPINVARIANTSUBSETHOISTINGPASS
#define GEN_PASS_DECL_INLINERPASS
#define GEN_PASS_DECL_MEM2REG
+#define GEN_PASS_DECL_NORMALIZEPASS
#define GEN_PASS_DECL_PRINTIRPASS
#define GEN_PASS_DECL_PRINTOPSTATSPASS
#define GEN_PASS_DECL_REMOVEDEADVALUESPASS
diff --git a/mlir/include/mlir/Transforms/Passes.td b/mlir/include/mlir/Transforms/Passes.td
index 74ac370ea950b..10193bbcef2bd 100644
--- a/mlir/include/mlir/Transforms/Passes.td
+++ b/mlir/include/mlir/Transforms/Passes.td
@@ -656,4 +656,12 @@ def BubbleDownMemorySpaceCasts :
}];
}
+def NormalizePass : InterfacePass<"normalize", "FunctionOpInterface"> {
+ let summary = "Transforms IR into a normal form that's easier to diff.";
+ let description = [{
+ This pass attempts to relocate the defining ops of operands for any
+ side-effecting or terminator operation to their nearest dominating positions.
+ }];
+}
+
#endif // MLIR_TRANSFORMS_PASSES
diff --git a/mlir/lib/Transforms/CMakeLists.txt b/mlir/lib/Transforms/CMakeLists.txt
index 66b39f53c91df..41347d23b0f49 100644
--- a/mlir/lib/Transforms/CMakeLists.txt
+++ b/mlir/lib/Transforms/CMakeLists.txt
@@ -12,6 +12,7 @@ add_mlir_library(MLIRTransforms
LocationSnapshot.cpp
LoopInvariantCodeMotion.cpp
Mem2Reg.cpp
+ Normalize.cpp
OpStats.cpp
PrintIR.cpp
RemoveDeadValues.cpp
diff --git a/mlir/lib/Transforms/Normalize.cpp b/mlir/lib/Transforms/Normalize.cpp
new file mode 100644
index 0000000000000..1904d25d36674
--- /dev/null
+++ b/mlir/lib/Transforms/Normalize.cpp
@@ -0,0 +1,112 @@
+//===- Normalize.cpp - Transforms IR into a normal form ---------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Dominance.h"
+#include "mlir/Interfaces/FunctionInterfaces.h"
+#include "mlir/Interfaces/SideEffectInterfaces.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Transforms/Passes.h"
+#include "llvm/Support/DebugLog.h"
+
+using namespace mlir;
+
+namespace mlir {
+#define GEN_PASS_DEF_NORMALIZEPASS
+#include "mlir/Transforms/Passes.h.inc"
+} // namespace mlir
+
+#define DEBUG_TYPE "normalize"
+
+namespace {
+
+bool isOutput(Operation *op) {
+ if (!op)
+ return false;
+ return !isMemoryEffectFree(op) || op->hasTrait<OpTrait::IsTerminator>();
+}
+
+/// Returns a vector of output ops. An output is a op which
+/// has side-effects or is terminator.
+SmallVector<Operation *> collectOutputs(Operation *root) {
+ SmallVector<Operation *> outputs;
+ root->walk([&](Operation *op) {
+ if (isOutput(op))
+ outputs.push_back(op);
+ });
+ return outputs;
+}
+
+/// The function returns the operation that dominates all other operations in
+/// the given list.
+Operation *getDominateOp(SmallVectorImpl<Operation *> &ops) {
+ if (ops.empty())
+ return {};
+ Operation *curDomOp = ops.front();
+ DominanceInfo domInfo(curDomOp);
+ for (size_t i = 1, e = ops.size(); i < e; ++i) {
+ bool dominateA = domInfo.dominates(ops[i], curDomOp);
+ bool dominateB = domInfo.dominates(curDomOp, ops[i]);
+ if (dominateA) {
+ LDBG() << OpWithFlags(ops[i], OpPrintingFlags().skipRegions())
+ << "\ndominate\n"
+ << OpWithFlags(curDomOp, OpPrintingFlags().skipRegions());
+ curDomOp = ops[i];
+ }
+ if (!dominateA && !dominateB) {
+ LDBG() << OpWithFlags(ops[i], OpPrintingFlags().skipRegions())
+ << "\nand\n"
+ << OpWithFlags(curDomOp, OpPrintingFlags().skipRegions())
+ << "\ndo not dominate each other";
+ return {};
+ }
+ }
+ return curDomOp;
+}
+
+/// Move used to its nearest user and recursively perform the same process on
+/// the defining operations of its operands.
+void reorderOutput(IRRewriter &rewriter, Operation *used) {
+ if (!isPure(used))
+ return;
+ SmallVector<Operation *> users(used->getUsers());
+ if (Operation *domOp = getDominateOp(users)) {
+ rewriter.moveOpBefore(used, domOp);
+ for (Value operand : used->getOperands())
+ if (Operation *defineOp = operand.getDefiningOp())
+ reorderOutput(rewriter, defineOp);
+ }
+}
+
+/// Reorders ops by walking up the tree from each operand of an output op and
+/// reducing the def-use distance. This method assumes that output ops were
+/// collected top-down, otherwise the def-use chain may be broken. This method
+/// is a wrapper for recursive reorderOutput().
+void reorderOutputs(IRRewriter &rewriter,
+ SmallVectorImpl<Operation *> &outputs) {
+ SmallPtrSet<Operation *, 16> visited;
+ for (Operation *output : outputs) {
+ for (Value operand : output->getOperands()) {
+ if (Operation *defineOp = operand.getDefiningOp();
+ defineOp && !visited.contains(defineOp)) {
+ reorderOutput(rewriter, defineOp);
+ }
+ }
+ }
+}
+
+struct NormalizePass : public impl::NormalizePassBase<NormalizePass> {
+ using impl::NormalizePassBase<NormalizePass>::NormalizePassBase;
+ void runOnOperation() override;
+};
+} // namespace
+
+void NormalizePass::runOnOperation() {
+ IRRewriter rewriter(&getContext());
+ SmallVector<Operation *> outputs = collectOutputs(getOperation());
+ reorderOutputs(rewriter, outputs);
+}
diff --git a/mlir/test/Transforms/normalize.mlir b/mlir/test/Transforms/normalize.mlir
new file mode 100644
index 0000000000000..241e28c3c385d
--- /dev/null
+++ b/mlir/test/Transforms/normalize.mlir
@@ -0,0 +1,84 @@
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(normalize))" -split-input-file | FileCheck %s
+
+// CHECK-LABEL: func @multiple_memref_store
+// CHECK-SAME: %[[ARG0:.*]]: index,
+// CHECK-SAME: %[[ARG1:.*]]: memref<?xf32>
+func.func @multiple_memref_store(%arg0: index, %arg1 : memref<?xf32>) {
+ %f0 = arith.constant 0.0 : f32
+ %f1 = arith.constant 1.0 : f32
+ %add = arith.addi %arg0, %arg0 : index
+ %sub = arith.subi %arg0, %arg0 : index
+ memref.store %f0, %arg1[%add] : memref<?xf32>
+ memref.store %f1, %arg1[%sub] : memref<?xf32>
+ return
+}
+
+// CHECK-NEXT: %[[C0:.*]] = arith.constant 0.000000e+00 : f32
+// CHECK-NEXT: %[[ADD:.*]] = arith.addi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: memref.store %[[C0]], %[[ARG1]]{{\[}}%[[ADD]]] : memref<?xf32>
+// CHECK-NEXT: %[[C1:.*]] = arith.constant 1.000000e+00 : f32
+// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: memref.store %[[C1]], %[[ARG1]]{{\[}}%[[SUB]]] : memref<?xf32>
+
+// -----
+
+// CHECK-LABEL: func @normalize_return
+// CHECK-SAME: %[[ARG0:.*]]: index,
+// CHECK-SAME: %[[ARG1:.*]]: memref<?xf32>
+func.func @normalize_return(%arg0: index, %arg1 : memref<?xf32>) -> index {
+ %f0 = arith.constant 0.0 : f32
+ %add = arith.addi %arg0, %arg0 : index
+ %sub = arith.subi %arg0, %arg0 : index
+ memref.store %f0, %arg1[%add] : memref<?xf32>
+ return %sub : index
+}
+
+// CHECK: memref.store
+// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: return %[[SUB]] : index
+
+// -----
+
+// CHECK-LABEL: func @cross_region
+// CHECK-SAME: %[[ARG0:.*]]: f32,
+// CHECK-SAME: %[[ARG1:.*]]: memref<10xf32>
+func.func @cross_region(%arg0: f32, %arg1 : memref<10xf32>) {
+ %add = arith.addf %arg0, %arg0 : f32
+ affine.for %i = 0 to 5 {
+ memref.store %add, %arg1[%i] : memref<10xf32>
+ }
+ %exp = math.log2 %add : f32
+ affine.for %i = 6 to 10 {
+ memref.store %exp, %arg1[%i] : memref<10xf32>
+ }
+ return
+}
+
+// CHECK: affine.for %[[IV:.*]] = 6 to 10 {
+// CHECK-NEXT: %[[LOG:.*]] = math.log2
+// CHECK-NEXT: memref.store %[[LOG]], %[[ARG1]]{{\[}}%[[IV]]] : memref<10xf32>
+// CHECK-NEXT: }
+
+// -----
+
+
+// CHECK-LABEL: func @side_effect_for_op
+// CHECK-SAME: %[[ARG0:.*]]: memref<?xf32>
+func.func @side_effect_for_op(%arg1 : memref<?xf32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %upper = memref.dim %arg1, %c0 : memref<?xf32>
+ %f1 = arith.constant 1.0 : f32
+ scf.for %i = %c0 to %upper step %c1 {
+ memref.store %f1, %arg1[%i] : memref<?xf32>
+ }
+ return
+}
+
+// CHECK-NEXT: %[[C0:.*]] = arith.constant 0 : index
+// CHECK-NEXT: %[[DIM:.*]] = memref.dim %[[ARG0]], %[[C0]] : memref<?xf32>
+// CHECK-NEXT: %[[C1:.*]] = arith.constant 1 : index
+// CHECK-NEXT: scf.for %[[IV:.*]] = %[[C0]] to %[[DIM]] step %[[C1]] {
+// CHECK-NEXT: %[[F1:.*]] = arith.constant 1.000000e+00 : f32
+// CHECK-NEXT: memref.store %[[F1]], %[[ARG0]]{{\[}}%[[IV]]] : memref<?xf32>
+// CHECK-NEXT: }
>From b025af24e07486c0142bec3f76c0e9cafb559d3f Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Mon, 16 Mar 2026 06:58:47 +0000
Subject: [PATCH 2/4] fix nit and add more comment.
---
mlir/include/mlir/Transforms/Passes.td | 10 ++++++++--
mlir/lib/Transforms/Normalize.cpp | 20 ++++++++++---------
mlir/test/Transforms/normalize.mlir | 27 +++++++++++++-------------
3 files changed, 33 insertions(+), 24 deletions(-)
diff --git a/mlir/include/mlir/Transforms/Passes.td b/mlir/include/mlir/Transforms/Passes.td
index 10193bbcef2bd..a03c69c12461e 100644
--- a/mlir/include/mlir/Transforms/Passes.td
+++ b/mlir/include/mlir/Transforms/Passes.td
@@ -659,8 +659,14 @@ def BubbleDownMemorySpaceCasts :
def NormalizePass : InterfacePass<"normalize", "FunctionOpInterface"> {
let summary = "Transforms IR into a normal form that's easier to diff.";
let description = [{
- This pass attempts to relocate the defining ops of operands for any
- side-effecting or terminator operation to their nearest dominating positions.
+ This pass aims to transform MLIR Modules into a normal form by reordering
+ operations while preserving the same semantics. It attempts to relocate
+ the defining ops of operands for any side-effecting or terminator operation
+ to their nearest dominating positions.
+
+ Note: The pass trying to increase syntactic equivalence of code to reduce
+ diff size while retaining semantic equivalence. It cannot replace the
+ canonicalization pass (a pass that aims for increasing semantic equivalence instead).
}];
}
diff --git a/mlir/lib/Transforms/Normalize.cpp b/mlir/lib/Transforms/Normalize.cpp
index 1904d25d36674..7e63c42b6d163 100644
--- a/mlir/lib/Transforms/Normalize.cpp
+++ b/mlir/lib/Transforms/Normalize.cpp
@@ -43,21 +43,22 @@ SmallVector<Operation *> collectOutputs(Operation *root) {
/// The function returns the operation that dominates all other operations in
/// the given list.
-Operation *getDominateOp(SmallVectorImpl<Operation *> &ops) {
+Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops) {
if (ops.empty())
return {};
Operation *curDomOp = ops.front();
DominanceInfo domInfo(curDomOp);
for (size_t i = 1, e = ops.size(); i < e; ++i) {
bool dominateA = domInfo.dominates(ops[i], curDomOp);
- bool dominateB = domInfo.dominates(curDomOp, ops[i]);
if (dominateA) {
LDBG() << OpWithFlags(ops[i], OpPrintingFlags().skipRegions())
<< "\ndominate\n"
<< OpWithFlags(curDomOp, OpPrintingFlags().skipRegions());
curDomOp = ops[i];
+ continue;
}
- if (!dominateA && !dominateB) {
+ bool dominateB = domInfo.dominates(curDomOp, ops[i]);
+ if (!dominateB) {
LDBG() << OpWithFlags(ops[i], OpPrintingFlags().skipRegions())
<< "\nand\n"
<< OpWithFlags(curDomOp, OpPrintingFlags().skipRegions())
@@ -70,13 +71,13 @@ Operation *getDominateOp(SmallVectorImpl<Operation *> &ops) {
/// Move used to its nearest user and recursively perform the same process on
/// the defining operations of its operands.
-void reorderOutput(IRRewriter &rewriter, Operation *used) {
- if (!isPure(used))
+void reorderOutput(IRRewriter &rewriter, Operation *producer) {
+ if (!isPure(producer))
return;
- SmallVector<Operation *> users(used->getUsers());
+ SmallVector<Operation *> users(producer->getUsers());
if (Operation *domOp = getDominateOp(users)) {
- rewriter.moveOpBefore(used, domOp);
- for (Value operand : used->getOperands())
+ rewriter.moveOpBefore(producer, domOp);
+ for (Value operand : producer->getOperands())
if (Operation *defineOp = operand.getDefiningOp())
reorderOutput(rewriter, defineOp);
}
@@ -87,12 +88,13 @@ void reorderOutput(IRRewriter &rewriter, Operation *used) {
/// collected top-down, otherwise the def-use chain may be broken. This method
/// is a wrapper for recursive reorderOutput().
void reorderOutputs(IRRewriter &rewriter,
- SmallVectorImpl<Operation *> &outputs) {
+ const SmallVectorImpl<Operation *> &outputs) {
SmallPtrSet<Operation *, 16> visited;
for (Operation *output : outputs) {
for (Value operand : output->getOperands()) {
if (Operation *defineOp = operand.getDefiningOp();
defineOp && !visited.contains(defineOp)) {
+ visited.insert(defineOp);
reorderOutput(rewriter, defineOp);
}
}
diff --git a/mlir/test/Transforms/normalize.mlir b/mlir/test/Transforms/normalize.mlir
index 241e28c3c385d..28f0c05263278 100644
--- a/mlir/test/Transforms/normalize.mlir
+++ b/mlir/test/Transforms/normalize.mlir
@@ -22,20 +22,17 @@ func.func @multiple_memref_store(%arg0: index, %arg1 : memref<?xf32>) {
// -----
-// CHECK-LABEL: func @normalize_return
-// CHECK-SAME: %[[ARG0:.*]]: index,
-// CHECK-SAME: %[[ARG1:.*]]: memref<?xf32>
-func.func @normalize_return(%arg0: index, %arg1 : memref<?xf32>) -> index {
- %f0 = arith.constant 0.0 : f32
- %add = arith.addi %arg0, %arg0 : index
- %sub = arith.subi %arg0, %arg0 : index
- memref.store %f0, %arg1[%add] : memref<?xf32>
- return %sub : index
+// CHECK-LABEL: func @return_multiple_operands
+// CHECK-SAME: %[[ARG0:.*]]: index
+func.func @return_multiple_operands (%arg0: index) -> (index, index) {
+ %0 = arith.addi %arg0, %arg0 : index
+ %1 = arith.subi %arg0, %arg0 : index
+ return %1, %0 : index, index
}
-// CHECK: memref.store
// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG0]] : index
-// CHECK-NEXT: return %[[SUB]] : index
+// CHECK-NEXT: %[[ADD:.*]] = arith.addi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: return %[[SUB]], %[[ADD]] : index, index
// -----
@@ -61,10 +58,14 @@ func.func @cross_region(%arg0: f32, %arg1 : memref<10xf32>) {
// -----
+// This test verifies the reordering of scf.for ops.
+// The memref.store within the scf.for causes the loop to have side effects.
+// The lower bound of the scf.for remains in its original position
+// because the upper bound depends on it, but the step has been reordered.
-// CHECK-LABEL: func @side_effect_for_op
+// CHECK-LABEL: func @side_effect_loop_op
// CHECK-SAME: %[[ARG0:.*]]: memref<?xf32>
-func.func @side_effect_for_op(%arg1 : memref<?xf32>) {
+func.func @side_effect_loop_op(%arg1 : memref<?xf32>) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%upper = memref.dim %arg1, %c0 : memref<?xf32>
>From c91b6a5f4b2bada8263b6063854c8cb39fc408c7 Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Sun, 19 Apr 2026 05:00:23 +0000
Subject: [PATCH 3/4] use getAnalysis to get DominanceInfo and use
markAnalysesPreserved.
---
mlir/lib/Transforms/Normalize.cpp | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/mlir/lib/Transforms/Normalize.cpp b/mlir/lib/Transforms/Normalize.cpp
index 7e63c42b6d163..6f42c61ee7d18 100644
--- a/mlir/lib/Transforms/Normalize.cpp
+++ b/mlir/lib/Transforms/Normalize.cpp
@@ -43,11 +43,11 @@ SmallVector<Operation *> collectOutputs(Operation *root) {
/// The function returns the operation that dominates all other operations in
/// the given list.
-Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops) {
+Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops,
+ const DominanceInfo &domInfo) {
if (ops.empty())
return {};
Operation *curDomOp = ops.front();
- DominanceInfo domInfo(curDomOp);
for (size_t i = 1, e = ops.size(); i < e; ++i) {
bool dominateA = domInfo.dominates(ops[i], curDomOp);
if (dominateA) {
@@ -71,15 +71,16 @@ Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops) {
/// Move used to its nearest user and recursively perform the same process on
/// the defining operations of its operands.
-void reorderOutput(IRRewriter &rewriter, Operation *producer) {
+void reorderOutput(IRRewriter &rewriter, Operation *producer,
+ const DominanceInfo &domInfo) {
if (!isPure(producer))
return;
SmallVector<Operation *> users(producer->getUsers());
- if (Operation *domOp = getDominateOp(users)) {
+ if (Operation *domOp = getDominateOp(users, domInfo)) {
rewriter.moveOpBefore(producer, domOp);
for (Value operand : producer->getOperands())
if (Operation *defineOp = operand.getDefiningOp())
- reorderOutput(rewriter, defineOp);
+ reorderOutput(rewriter, defineOp, domInfo);
}
}
@@ -88,14 +89,15 @@ void reorderOutput(IRRewriter &rewriter, Operation *producer) {
/// collected top-down, otherwise the def-use chain may be broken. This method
/// is a wrapper for recursive reorderOutput().
void reorderOutputs(IRRewriter &rewriter,
- const SmallVectorImpl<Operation *> &outputs) {
+ const SmallVectorImpl<Operation *> &outputs,
+ const DominanceInfo &domInfo) {
SmallPtrSet<Operation *, 16> visited;
for (Operation *output : outputs) {
for (Value operand : output->getOperands()) {
if (Operation *defineOp = operand.getDefiningOp();
defineOp && !visited.contains(defineOp)) {
visited.insert(defineOp);
- reorderOutput(rewriter, defineOp);
+ reorderOutput(rewriter, defineOp, domInfo);
}
}
}
@@ -108,7 +110,11 @@ struct NormalizePass : public impl::NormalizePassBase<NormalizePass> {
} // namespace
void NormalizePass::runOnOperation() {
+ DominanceInfo &domInfo = getAnalysis<DominanceInfo>();
IRRewriter rewriter(&getContext());
SmallVector<Operation *> outputs = collectOutputs(getOperation());
- reorderOutputs(rewriter, outputs);
+ reorderOutputs(rewriter, outputs, domInfo);
+ // Since we only changed the positions of the operations, DominanceInfo and
+ // PostDominanceInfo are marked as preserved.
+ markAnalysesPreserved<DominanceInfo, PostDominanceInfo>();
}
>From 4046f799eb3296b58af7e8dedf0987946f509112 Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Mon, 6 Jul 2026 08:44:03 +0000
Subject: [PATCH 4/4] impl rename logic and add test.
---
mlir/include/mlir/Transforms/Passes.td | 10 +-
mlir/lib/Transforms/Normalize.cpp | 473 +++++++++++++++++++++++--
mlir/test/Transforms/normalize.mlir | 117 +++++-
3 files changed, 557 insertions(+), 43 deletions(-)
diff --git a/mlir/include/mlir/Transforms/Passes.td b/mlir/include/mlir/Transforms/Passes.td
index a03c69c12461e..f195588e4ca67 100644
--- a/mlir/include/mlir/Transforms/Passes.td
+++ b/mlir/include/mlir/Transforms/Passes.td
@@ -662,12 +662,20 @@ def NormalizePass : InterfacePass<"normalize", "FunctionOpInterface"> {
This pass aims to transform MLIR Modules into a normal form by reordering
operations while preserving the same semantics. It attempts to relocate
the defining ops of operands for any side-effecting or terminator operation
- to their nearest dominating positions.
+ to their nearest dominating positions. In addition, it makes it easier to see
+ which SSA values an op uses just by reading its renamed name.
Note: The pass trying to increase syntactic equivalence of code to reduce
diff size while retaining semantic equivalence. It cannot replace the
canonicalization pass (a pass that aims for increasing semantic equivalence instead).
}];
+ let options = [
+ Option<"foldDepth", "fold-depth", "int64_t",
+ /*default=*/"0",
+ "Control the maximum depth of operands to include in the name."
+ "If less than 0, name folding is entirely disabled."
+ "If set to 0, only folds the op name itself without operands.">,
+ ];
}
#endif // MLIR_TRANSFORMS_PASSES
diff --git a/mlir/lib/Transforms/Normalize.cpp b/mlir/lib/Transforms/Normalize.cpp
index 6f42c61ee7d18..3309c4cbb2c3b 100644
--- a/mlir/lib/Transforms/Normalize.cpp
+++ b/mlir/lib/Transforms/Normalize.cpp
@@ -6,12 +6,33 @@
//
//===----------------------------------------------------------------------===//
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/Block.h"
#include "mlir/IR/Dominance.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OpDefinition.h"
+#include "mlir/IR/Operation.h"
+#include "mlir/IR/OperationSupport.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Region.h"
+#include "mlir/IR/Value.h"
+#include "mlir/IR/Visitors.h"
+#include "mlir/Interfaces/CallInterfaces.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
+#include "mlir/Support/LLVM.h"
+#include "mlir/Support/WalkResult.h"
+#include "mlir/Transforms/CommutativityUtils.h"
+#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "mlir/Transforms/Passes.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
#include "llvm/Support/DebugLog.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cstdint>
+#include <string>
using namespace mlir;
@@ -24,27 +45,133 @@ namespace mlir {
namespace {
-bool isOutput(Operation *op) {
+class Normalize {
+public:
+ Normalize(IRRewriter &rewriter, DominanceInfo &domInfo,
+ NormalizePassOptions &options)
+ : rewriter(rewriter), domInfo(domInfo), options(options) {}
+ /// Collect a vector of output ops within in \p root.
+ void collectOutputs(Operation *root);
+
+ /// Reorders ops by walking up the tree from each operand of an output op and
+ /// reducing the def-use distance. This method assumes that output ops were
+ /// collected top-down, otherwise the def-use chain may be broken. This method
+ /// is a wrapper for recursive reorderOutput().
+ void reorderOutputs();
+
+ /// Assigns unique, sequential names (e.g., "a0", "a1") to all block arguments
+ /// within \p root.
+ void nameBlockArguments(Operation *root);
+
+ /// Assigns unique, sequential names to all collected output operations.
+ void nameOperations();
+
+ /// Fold the operation name within \p root.
+ void foldOperationsName(Operation *root);
+
+ /// Greedily applies commutativity patterns using \p root to define the
+ /// transformation scope.
+ LogicalResult sortCommutativeOperands(Operation *root);
+
+private:
+ /// Reorders operations along the def-use chain from left to right, bottom to
+ /// top, starting from \p producer.
+ void reorderOutput(Operation *producer);
+
+ /// Returns true if the \p is a terminator or contains memory/side effects.
+ bool isOutput(Operation *op);
+
+ /// Returns true if the \p op is an initial operation (has no operands or only
+ /// constant-like operands).
+ bool isInitialOperation(Operation *op);
+
+ /// Assigns a unique name to \p op, using \p visited to track and skip already
+ /// processed operations.
+ void nameOperation(Operation *op, SmallPtrSet<Operation *, 32> &visited);
+
+ /// Generates and assigns a stable, deterministic name to the initial \p op,
+ /// while recursively resolving names for its upstream operands.
+ void nameAsInitialOperation(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited);
+ void nameAsRegularOperation(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited);
+
+ /// Computes the output footprint for the given \p op.
+ SetVector<int> getOutputFootprint(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited);
+ /// Simplifies the name of the given \p op.
+ void foldOperationName(Operation *op);
+
+ Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops);
+
+ void appendCallAndOperandNames(Operation *op, SmallString<512> &name,
+ SmallVectorImpl<StringRef> &operandNames);
+
+ /// Collapses "$-"..."-$" nesting in \p name beyond \p depth, keeping only
+ /// the outer \p depth levels of markers.
+ std::string trimNameByDepth(StringRef name, int64_t depth);
+
+ IRRewriter &rewriter;
+ DominanceInfo &domInfo;
+ NormalizePassOptions &options;
+
+ /// Outputs collected by collectOutputs.
+ SmallVector<Operation *> outputs;
+
+ /// Caches, for output ops, their accumulated nested distance: the sum of
+ /// each enclosing region's op-position, walked upward until the parent
+ /// operation is a FunctionOpInterface.
+ DenseMap<Operation *, int64_t> footprintCache;
+
+ // Random constant for hashing, so the state isn't zero.
+ const uint64_t magicHashConstant = 0x6acaa36bef8325c5ULL;
+};
+
+// Frozen mixer; basic-block names derived from these hashes appear in
+// the normalized IR text and must be deterministic across processes
+// for the normalizer's "compare normalized IR" workflow to work.
+static constexpr uint64_t hash_16_bytes(uint64_t Low, uint64_t High) {
+ const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+ uint64_t A = (Low ^ High) * kMul;
+ A ^= (A >> 47);
+ uint64_t B = (High ^ A) * kMul;
+ B ^= (B >> 47);
+ B *= kMul;
+ return B;
+}
+
+/// Computes the 64-bit FNV-1a hash value of the given string \p data.
+static constexpr uint64_t strHash(std::string_view data) noexcept {
+ const uint64_t fnvOffset = 0xcbf29ce484222325ULL;
+ const uint64_t fnvPrime = 0x100000001b3ULL;
+ uint64_t hash = fnvOffset;
+ for (const auto &c : data) {
+ hash ^= static_cast<uint64_t>(c);
+ hash *= fnvPrime;
+ }
+ return hash;
+}
+
+bool Normalize::isOutput(Operation *op) {
if (!op)
return false;
return !isMemoryEffectFree(op) || op->hasTrait<OpTrait::IsTerminator>();
}
-/// Returns a vector of output ops. An output is a op which
-/// has side-effects or is terminator.
-SmallVector<Operation *> collectOutputs(Operation *root) {
- SmallVector<Operation *> outputs;
+void Normalize::collectOutputs(Operation *root) {
root->walk([&](Operation *op) {
- if (isOutput(op))
+ if (op == root)
+ return WalkResult::advance();
+ if (isOutput(op)) {
+ LDBG() << "insert " << OpWithFlags(op, OpPrintingFlags().skipRegions())
+ << " to outputs";
outputs.push_back(op);
+ }
+ return WalkResult::advance();
});
- return outputs;
}
-/// The function returns the operation that dominates all other operations in
-/// the given list.
-Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops,
- const DominanceInfo &domInfo) {
+Operation *Normalize::getDominateOp(const SmallVectorImpl<Operation *> &ops) {
if (ops.empty())
return {};
Operation *curDomOp = ops.front();
@@ -69,38 +196,315 @@ Operation *getDominateOp(const SmallVectorImpl<Operation *> &ops,
return curDomOp;
}
-/// Move used to its nearest user and recursively perform the same process on
-/// the defining operations of its operands.
-void reorderOutput(IRRewriter &rewriter, Operation *producer,
- const DominanceInfo &domInfo) {
+void Normalize::reorderOutput(Operation *producer) {
if (!isPure(producer))
return;
SmallVector<Operation *> users(producer->getUsers());
- if (Operation *domOp = getDominateOp(users, domInfo)) {
+ if (Operation *domOp = getDominateOp(users)) {
rewriter.moveOpBefore(producer, domOp);
for (Value operand : producer->getOperands())
if (Operation *defineOp = operand.getDefiningOp())
- reorderOutput(rewriter, defineOp, domInfo);
+ reorderOutput(defineOp);
}
}
-/// Reorders ops by walking up the tree from each operand of an output op and
-/// reducing the def-use distance. This method assumes that output ops were
-/// collected top-down, otherwise the def-use chain may be broken. This method
-/// is a wrapper for recursive reorderOutput().
-void reorderOutputs(IRRewriter &rewriter,
- const SmallVectorImpl<Operation *> &outputs,
- const DominanceInfo &domInfo) {
+void Normalize::reorderOutputs() {
SmallPtrSet<Operation *, 16> visited;
for (Operation *output : outputs) {
for (Value operand : output->getOperands()) {
if (Operation *defineOp = operand.getDefiningOp();
defineOp && !visited.contains(defineOp)) {
visited.insert(defineOp);
- reorderOutput(rewriter, defineOp, domInfo);
+ reorderOutput(defineOp);
+ }
+ }
+ }
+}
+
+bool Normalize::isInitialOperation(Operation *op) {
+ for (Value operand : op->getOperands()) {
+ if (Operation *define = operand.getDefiningOp();
+ !define || !define->hasTrait<OpTrait::ConstantLike>())
+ return false;
+ }
+ return true;
+}
+
+/// Computes the output footprint for the given \p op.
+///
+/// Traverses downstream users recursively to find all reachable output
+/// operations. For each output operation, it calculates its precise distance
+/// (in terms of operation count) relative to the entry block of its enclosing
+/// `FunctionOpInterface`.
+SetVector<int>
+Normalize::getOutputFootprint(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited) {
+ SetVector<int> outputs;
+ if (visited.count(op))
+ return outputs;
+ visited.insert(op);
+
+ // If the operation is an output, compute its nested absolute distance to the
+ // Function entry.
+ if (isOutput(op)) {
+ if (footprintCache.contains(op)) {
+ outputs.insert(footprintCache[op]);
+ return outputs;
+ }
+
+ // Calculates the total distance of 'op' to its enclosing parent region's
+ // start, accumulating nested offsets upward until the parent operation
+ // matches `FunctionOpInterface`.
+ int count = 0;
+ Operation *curOp = op;
+ do {
+ Region *parentRegion = curOp->getParentRegion();
+ int distance = 0;
+ for (Operation &it : parentRegion->getOps()) {
+ if (&it == curOp)
+ break;
+ ++distance;
}
+ count += distance;
+ curOp = parentRegion->getParentOp();
+ } while (!isa<FunctionOpInterface>(curOp));
+ outputs.insert(count);
+ footprintCache[op] = count;
+ return outputs;
+ }
+
+ // Otherwise, recursively aggregate footprints from all downstream users.
+ for (Operation *user : op->getUsers()) {
+ SetVector<int> outputsUser = getOutputFootprint(user, visited);
+ outputs.insert(outputsUser.begin(), outputsUser.end());
+ }
+ return outputs;
+}
+
+void Normalize::appendCallAndOperandNames(
+ Operation *op, SmallString<512> &name,
+ SmallVectorImpl<StringRef> &operandNames) {
+ // In case of CallInst, consider callee in the operation name.
+ if (auto callOp = dyn_cast<CallOpInterface>(op))
+ if (auto funcOp = dyn_cast<FunctionOpInterface>(callOp.resolveCallable()))
+ name.append(funcOp.getNameAttr());
+
+ if (operandNames.size() > 0) {
+ name.append("$-");
+ for (size_t i = 0, e = operandNames.size(); i < e; ++i) {
+ name.append(operandNames[i]);
+ if (i < e - 1)
+ name.append(".");
}
+ name.append("-$");
}
+ NameLoc loc = NameLoc::get(StringAttr::get(op->getContext(), name));
+ LDBG() << "set NameLoc: " << loc
+ << "\nfor: " << OpWithFlags(op, OpPrintingFlags().skipRegions());
+ op->setLoc(loc);
+}
+
+/// Names operation following the scheme:
+/// vl00000Callee(Operands)
+///
+/// Where 00000 is a hash calculated considering operation's opcode, output
+/// footprint and block position. Callee's name is only included when
+/// operation's type is `CallOpInterface`. The Operands are derived from the
+/// names of the operation's operands.
+void Normalize::nameAsInitialOperation(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited) {
+ // Recursively name defining ops of operands and collect their names.
+ SmallVector<StringRef, 4> operandNames;
+ for (Value operand : op->getOperands()) {
+ if (Operation *define = operand.getDefiningOp())
+ nameOperation(define, visited);
+ if (NameLoc loc = dyn_cast<NameLoc>(operand.getLoc()))
+ operandNames.push_back(loc.getName());
+ }
+
+ // Early exit if the op don't have results.
+ if (!op->getNumResults())
+ return;
+
+ // Initialize to a magic constant, so the state isn't zero.
+ uint64_t hash = magicHashConstant;
+
+ // Consider operation's opcode in the hash.
+ hash = hash_16_bytes(hash, strHash(op->getName().getStringRef().str()));
+
+ // Get output footprint for \p op.
+ SmallPtrSet<Operation *, 32> visitedOutputFoot;
+ SetVector<int> outputFootprint = getOutputFootprint(op, visitedOutputFoot);
+
+ // Consider output footprint in the hash.
+ for (const int &output : outputFootprint)
+ hash = hash_16_bytes(hash, output);
+
+ // Include the operation's relative position within its basic block.
+ hash = hash_16_bytes(
+ hash, std::distance(op->getBlock()->begin(), op->getIterator()));
+
+ // Base operation name.
+ SmallString<512> name;
+ name.append("vl" + std::to_string(hash).substr(0, 5));
+
+ // Append call and operand name.
+ appendCallAndOperandNames(op, name, operandNames);
+}
+
+/// Names operation following the scheme:
+/// op00000Callee(Operands)
+///
+/// Where 00000 is a hash calculated considering operation's opcode, its
+/// operands' opcodes, and block position. Callee's name is only included
+/// when operation's type is `CallOpInterface`, The Operands are derived from
+/// the names of the operation's operands.
+void Normalize::nameAsRegularOperation(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited) {
+ // Recursively name defining ops of operands and collect their names.
+ SmallVector<StringRef, 2> operandNames;
+ for (Value operand : op->getOperands()) {
+ if (Operation *define = operand.getDefiningOp())
+ nameOperation(define, visited);
+ if (NameLoc loc = dyn_cast<NameLoc>(operand.getLoc())) {
+ operandNames.push_back(loc.getName());
+ }
+ }
+
+ // Early exit if the op don't have results.
+ if (!op->getNumResults())
+ return;
+
+ // Initialize to a magic constant, so the state isn't zero.
+ uint64_t hash = magicHashConstant;
+
+ // Consider operation opcode in the hash.
+ uint64_t ophash = strHash(op->getName().getStringRef().str());
+ hash = hash_16_bytes(hash, ophash);
+
+ // Fuses the opcodes of upstream defining ops into the hash.
+ for (Value operand : op->getOperands())
+ if (Operation *define = operand.getDefiningOp())
+ hash =
+ hash_16_bytes(hash, strHash(define->getName().getStringRef().str()));
+
+ // Include the operation's relative position within its basic block.
+ hash = hash_16_bytes(
+ hash, std::distance(op->getBlock()->begin(), op->getIterator()));
+
+ // Base operation name.
+ SmallString<512> name;
+ name.append("op" + std::to_string(hash).substr(0, 5));
+
+ // Append call and operand name.
+ appendCallAndOperandNames(op, name, operandNames);
+}
+
+void Normalize::nameOperation(Operation *op,
+ SmallPtrSet<Operation *, 32> &visited) {
+ if (visited.count(op))
+ return;
+ visited.insert(op);
+
+ // Determine the type of operation to name.
+ if (isInitialOperation(op)) {
+ // This is an initial operation.
+ nameAsInitialOperation(op, visited);
+ } else {
+ // This must be a regular operation.
+ nameAsRegularOperation(op, visited);
+ }
+}
+
+void Normalize::nameOperations() {
+ SmallPtrSet<Operation *, 32> visited;
+ for (Operation *op : outputs)
+ nameOperation(op, visited);
+}
+
+void Normalize::nameBlockArguments(Operation *root) {
+ size_t argumentCount = 0;
+ MLIRContext *context = root->getContext();
+ root->walk<WalkOrder::PreOrder>([&](Block *b) {
+ for (auto argument : b->getArguments()) {
+ NameLoc loc =
+ NameLoc::get(StringAttr::get(context, "a" + Twine(argumentCount++)));
+ argument.setLoc(loc);
+ }
+ return;
+ });
+}
+
+/// Collapses nested "$-"..."-$" segments in `name` beyond the given
+/// `depth`, keeping only the outer `depth` levels of markers.
+///
+/// "$-" / "-$" mark the start/end of a nested scope, e.g.
+/// op80011$-a0.op11483-$ has one level of nesting.
+/// depth == 0 strips it down to "op80011" (no markers left);
+/// depth < 0, or fewer than depth+1 marker pairs, returns `name` unchanged.
+std::string Normalize::trimNameByDepth(StringRef name, int64_t depth) {
+ std::string str = name.str();
+ if (depth < 0)
+ return str;
+
+ int targetCount = depth + 1;
+
+ size_t startPos = 0;
+ for (int i = 0, e = targetCount; i < e; ++i) {
+ startPos = str.find("$-", startPos);
+ if (startPos == std::string::npos)
+ return str;
+ startPos += 2;
+ }
+
+ size_t endPos = str.size() - 1;
+ for (int i = 0, e = targetCount; i < e; ++i) {
+ endPos = str.rfind("-$", endPos);
+ if (endPos == std::string::npos)
+ return str;
+ endPos -= 2;
+ }
+
+ startPos -= 2;
+ endPos += 2;
+
+ if (startPos >= endPos)
+ return str;
+
+ return str.substr(0, startPos) + str.substr(endPos + 2);
+}
+
+/// Folds the name of \p op into a simplified form containing a truncated
+/// prefix of its own name and its operands' names,
+void Normalize::foldOperationName(Operation *op) {
+ NameLoc loc = dyn_cast<NameLoc>(op->getLoc());
+
+ // Only process operations prefixed with "op" since their names are complex
+ // and need simplification.
+ if (!loc || loc.getName().empty() || loc.getName().str().substr(0, 2) != "op")
+ return;
+
+ std::string name = trimNameByDepth(loc.getName().str(), options.foldDepth);
+ loc = NameLoc::get(StringAttr::get(op->getContext(), name));
+ LDBG() << "fold NameLoc: " << loc
+ << "\nfor: " << OpWithFlags(op, OpPrintingFlags().skipRegions());
+ op->setLoc(loc);
+}
+
+void Normalize::foldOperationsName(Operation *root) {
+ if (options.foldDepth < 0)
+ return;
+ root->walk<WalkOrder::PreOrder>(
+ [&](Operation *op) { foldOperationName(op); });
+}
+
+LogicalResult Normalize::sortCommutativeOperands(Operation *root) {
+ MLIRContext *context = root->getContext();
+ RewritePatternSet patterns(context);
+ populateCommutativityUtilsPatterns(patterns);
+ if (failed(applyPatternsGreedily(root, std::move(patterns))))
+ return failure();
+ return success();
}
struct NormalizePass : public impl::NormalizePassBase<NormalizePass> {
@@ -112,9 +516,20 @@ struct NormalizePass : public impl::NormalizePassBase<NormalizePass> {
void NormalizePass::runOnOperation() {
DominanceInfo &domInfo = getAnalysis<DominanceInfo>();
IRRewriter rewriter(&getContext());
- SmallVector<Operation *> outputs = collectOutputs(getOperation());
- reorderOutputs(rewriter, outputs, domInfo);
- // Since we only changed the positions of the operations, DominanceInfo and
- // PostDominanceInfo are marked as preserved.
+ NormalizePassOptions options = {foldDepth};
+ Normalize normalize(rewriter, domInfo, options);
+
+ // Sort commutative operands up front, so operand order doesn't need to be
+ // re-sorted (by name) later when renaming ops.
+ if (failed(normalize.sortCommutativeOperands(getOperation())))
+ signalPassFailure();
+ normalize.collectOutputs(getOperation());
+ normalize.reorderOutputs();
+ normalize.nameBlockArguments(getOperation());
+ normalize.nameOperations();
+ normalize.foldOperationsName(getOperation());
+
+ // Since we only changed the positions of the operations, `DominanceInfo` and
+ // `PostDominanceInfo` are marked as preserved.
markAnalysesPreserved<DominanceInfo, PostDominanceInfo>();
}
diff --git a/mlir/test/Transforms/normalize.mlir b/mlir/test/Transforms/normalize.mlir
index 28f0c05263278..4b11c1f7427c5 100644
--- a/mlir/test/Transforms/normalize.mlir
+++ b/mlir/test/Transforms/normalize.mlir
@@ -1,4 +1,9 @@
// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(normalize))" -split-input-file | FileCheck %s
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(normalize{fold-depth=1}))" -mlir-use-nameloc-as-prefix -split-input-file | FileCheck %s -check-prefix=CHECK-NAMELOC
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(normalize{fold-depth=0}))" -mlir-use-nameloc-as-prefix -split-input-file | FileCheck %s -check-prefix=CHECK-NAMELOC-DEPTH-0
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(normalize{fold-depth=2}))" -mlir-use-nameloc-as-prefix -split-input-file | FileCheck %s -check-prefix=CHECK-NAMELOC-DEPTH-2
+
+// This test verifies op ordering and the sorting of commutative operands.
// CHECK-LABEL: func @multiple_memref_store
// CHECK-SAME: %[[ARG0:.*]]: index,
@@ -7,35 +12,53 @@ func.func @multiple_memref_store(%arg0: index, %arg1 : memref<?xf32>) {
%f0 = arith.constant 0.0 : f32
%f1 = arith.constant 1.0 : f32
%add = arith.addi %arg0, %arg0 : index
- %sub = arith.subi %arg0, %arg0 : index
+ %sub = arith.addi %add, %arg0 : index
memref.store %f0, %arg1[%add] : memref<?xf32>
memref.store %f1, %arg1[%sub] : memref<?xf32>
return
}
-// CHECK-NEXT: %[[C0:.*]] = arith.constant 0.000000e+00 : f32
-// CHECK-NEXT: %[[ADD:.*]] = arith.addi %[[ARG0]], %[[ARG0]] : index
-// CHECK-NEXT: memref.store %[[C0]], %[[ARG1]]{{\[}}%[[ADD]]] : memref<?xf32>
-// CHECK-NEXT: %[[C1:.*]] = arith.constant 1.000000e+00 : f32
-// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG0]] : index
-// CHECK-NEXT: memref.store %[[C1]], %[[ARG1]]{{\[}}%[[SUB]]] : memref<?xf32>
+// CHECK-NEXT: %[[C_0:.*]] = arith.constant 0.000000e+00 : f32
+// CHECK-NEXT: %[[ADD_0:.*]] = arith.addi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: memref.store %[[C_0]], %[[ARG1]]{{\[}}%[[ADD_0]]] : memref<?xf32>
+// CHECK-NEXT: %[[CONSTANT_1:.*]] = arith.constant 1.000000e+00 : f32
+// CHECK-NEXT: %[[ADD_1:.*]] = arith.addi %[[ARG0]], %[[ADD_0]] : index
+// CHECK-NEXT: memref.store %[[CONSTANT_1]], %[[ARG1]]{{\[}}%[[ADD_1]]] : memref<?xf32>
+
+// CHECK-NAMELOC-LABEL: func @multiple_memref_store
+// CHECK-NAMELOC-NEXT: %vl48293 = arith.constant 0.000000e+00 : f32
+// CHECK-NAMELOC-NEXT: %op13600$-a0.a0-$ = arith.addi %a0, %a0 : index
+// CHECK-NAMELOC-NEXT: memref.store %vl48293, %a1[%op13600$-a0.a0-$] : memref<?xf32>
+// CHECK-NAMELOC-NEXT: %vl15553 = arith.constant 1.000000e+00 : f32
+// CHECK-NAMELOC-NEXT: %op69768$-a0.op13600-$ = arith.addi %a0, %op13600$-a0.a0-$ : index
+// CHECK-NAMELOC-NEXT: memref.store %vl15553, %a1[%op69768$-a0.op13600-$] : memref<?xf32>
// -----
+// This test verifies an 'output' op with multiple operands that are all results of another op.
+
// CHECK-LABEL: func @return_multiple_operands
-// CHECK-SAME: %[[ARG0:.*]]: index
-func.func @return_multiple_operands (%arg0: index) -> (index, index) {
- %0 = arith.addi %arg0, %arg0 : index
- %1 = arith.subi %arg0, %arg0 : index
+// CHECK-SAME: %[[ARG0:.*]]: index,
+// CHECK-SAME: %[[ARG1:.*]]: index
+func.func @return_multiple_operands (%arg0: index, %arg1: index) -> (index, index) {
+ %0 = arith.addi %arg0, %arg1 : index
+ %1 = arith.subi %arg0, %arg1 : index
return %1, %0 : index, index
}
-// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG0]] : index
-// CHECK-NEXT: %[[ADD:.*]] = arith.addi %[[ARG0]], %[[ARG0]] : index
+// CHECK-NEXT: %[[SUB:.*]] = arith.subi %[[ARG0]], %[[ARG1]] : index
+// CHECK-NEXT: %[[ADD:.*]] = arith.addi %[[ARG0]], %[[ARG1]] : index
// CHECK-NEXT: return %[[SUB]], %[[ADD]] : index, index
+// CHECK-NAMELOC-LABEL: func @return_multiple_operands
+// CHECK-NAMELOC-NEXT: %op89776$-a0.a1-$ = arith.subi %a0, %a1 : index
+// CHECK-NAMELOC-NEXT: %op13600$-a0.a1-$ = arith.addi %a0, %a1 : index
+// CHECK-NAMELOC-NEXT: return %op89776$-a0.a1-$, %op13600$-a0.a1-$ : index, index
+
// -----
+// This test checks if '%add' is scheduled down to the second 'memref.store' site.
+
// CHECK-LABEL: func @cross_region
// CHECK-SAME: %[[ARG0:.*]]: f32,
// CHECK-SAME: %[[ARG1:.*]]: memref<10xf32>
@@ -56,6 +79,12 @@ func.func @cross_region(%arg0: f32, %arg1 : memref<10xf32>) {
// CHECK-NEXT: memref.store %[[LOG]], %[[ARG1]]{{\[}}%[[IV]]] : memref<10xf32>
// CHECK-NEXT: }
+// CHECK-NAMELOC-LABEL: func @cross_region
+// CHECK-NAMELOC: affine.for %a3 = 6 to 10 {
+// CHECK-NAMELOC-NEXT: %op16592$-op10970-$ = math.log2 %op10970$-a0.a0-$ : f32
+// CHECK-NAMELOC-NEXT: memref.store %op16592$-op10970-$, %a1[%a3] : memref<10xf32>
+// CHECK-NAMELOC-NEXT: }
+
// -----
// This test verifies the reordering of scf.for ops.
@@ -83,3 +112,65 @@ func.func @side_effect_loop_op(%arg1 : memref<?xf32>) {
// CHECK-NEXT: %[[F1:.*]] = arith.constant 1.000000e+00 : f32
// CHECK-NEXT: memref.store %[[F1]], %[[ARG0]]{{\[}}%[[IV]]] : memref<?xf32>
// CHECK-NEXT: }
+
+// CHECK-NAMELOC-LABEL: func @side_effect_loop_op
+// CHECK-NAMELOC-NEXT: %vl15499 = arith.constant 0 : index
+// CHECK-NAMELOC-NEXT: %op18509$-a0.vl15499-$ = memref.dim %a0, %vl15499 : memref<?xf32>
+// CHECK-NAMELOC-NEXT: %vl14483 = arith.constant 1 : index
+// CHECK-NAMELOC-NEXT: scf.for %a1 = %vl15499 to %op18509$-a0.vl15499-$ step %vl14483 {
+// CHECK-NAMELOC-NEXT: %vl71256 = arith.constant 1.000000e+00 : f32
+// CHECK-NAMELOC-NEXT: memref.store %vl71256, %a0[%a1] : memref<?xf32>
+// CHECK-NAMELOC-NEXT: }
+
+// -----
+
+// This test verifies the naming of func.call operations.
+
+func.func private @callee_0(%arg0: f32) -> f32
+func.func private @callee_1(%arg0: f32) -> f32
+
+func.func @test_call_operation(%arg0: f32) -> f32 {
+ %0 = func.call @callee_0(%arg0) : (f32) -> f32
+ %1 = func.call @callee_1(%0) : (f32) -> f32
+ return %1 : f32
+}
+
+// CHECK-NAMELOC-LABEL: func @test_call_operation
+// CHECK-NAMELOC-NEXT: %op71372callee_0$-a0-$ = call @callee_0(%a0) : (f32) -> f32
+// CHECK-NAMELOC-NEXT: %op15508callee_1$-op71372callee_0-$ = call @callee_1(%op71372callee_0$-a0-$) : (f32) -> f32
+// CHECK-NAMELOC-NEXT: return %op15508callee_1$-op71372callee_0-$ : f32
+
+// -----
+
+func.func @deep_use_chain(%arg0: i32, %arg1: i32) -> i32 {
+ %0 = arith.addi %arg0, %arg1 : i32
+ %1 = arith.muli %arg0, %0 : i32
+ %2 = arith.addi %arg0, %1 : i32
+ %3 = arith.muli %2, %2 : i32
+ %4 = arith.addi %arg1, %3 : i32
+ return %4 : i32
+}
+
+// CHECK-NAMELOC-DEPTH-0-LABEL: func @deep_use_chain
+// CHECK-NAMELOC-DEPTH-0-NEXT: %op11483 = arith.addi %a0, %a1 : i32
+// CHECK-NAMELOC-DEPTH-0-NEXT: %op80011 = arith.muli %a0, %op11483 : i32
+// CHECK-NAMELOC-DEPTH-0-NEXT: %op14133 = arith.addi %a0, %op80011 : i32
+// CHECK-NAMELOC-DEPTH-0-NEXT: %op27579 = arith.muli %op14133, %op14133 : i32
+// CHECK-NAMELOC-DEPTH-0-NEXT: %op17770 = arith.addi %a1, %op27579 : i32
+// CHECK-NAMELOC-DEPTH-0-NEXT: return %op17770 : i32
+
+// CHECK-NAMELOC-LABEL: func @deep_use_chain
+// CHECK-NAMELOC-NEXT: %op11483$-a0.a1-$ = arith.addi %a0, %a1 : i32
+// CHECK-NAMELOC-NEXT: %op80011$-a0.op11483-$ = arith.muli %a0, %op11483$-a0.a1-$ : i32
+// CHECK-NAMELOC-NEXT: %op14133$-a0.op80011-$ = arith.addi %a0, %op80011$-a0.op11483-$ : i32
+// CHECK-NAMELOC-NEXT: %op27579$-op14133-$ = arith.muli %op14133$-a0.op80011-$, %op14133$-a0.op80011-$ : i32
+// CHECK-NAMELOC-NEXT: %op17770$-a1.op27579-$ = arith.addi %a1, %op27579$-op14133-$ : i32
+// CHECK-NAMELOC-NEXT: return %op17770$-a1.op27579-$ : i32
+
+// CHECK-NAMELOC-DEPTH-2-LABEL: func @deep_use_chain
+// CHECK-NAMELOC-DEPTH-2-NEXT: %op11483$-a0.a1-$ = arith.addi %a0, %a1 : i32
+// CHECK-NAMELOC-DEPTH-2-NEXT: %op80011$-a0.op11483$-a0.a1-$-$ = arith.muli %a0, %op11483$-a0.a1-$ : i32
+// CHECK-NAMELOC-DEPTH-2-NEXT: %op14133$-a0.op80011$-a0.op11483-$-$ = arith.addi %a0, %op80011$-a0.op11483$-a0.a1-$-$ : i32
+// CHECK-NAMELOC-DEPTH-2-NEXT: %op27579$-op14133$-a0.op80011-$-$ = arith.muli %op14133$-a0.op80011$-a0.op11483-$-$, %op14133$-a0.op80011$-a0.op11483-$-$ : i32
+// CHECK-NAMELOC-DEPTH-2-NEXT: %op17770$-a1.op27579$-op14133-$-$ = arith.addi %a1, %op27579$-op14133$-a0.op80011-$-$ : i32
+// CHECK-NAMELOC-DEPTH-2-NEXT: return %op17770$-a1.op27579$-op14133-$-$ : i32
More information about the Mlir-commits
mailing list