[Mlir-commits] [mlir] [MLIR] Missed Optimization: Incomplete elimination of a tensor.empty (PR #228032)

Ashutosh Sharma llvmlistbot at llvm.org
Sun Nov 1 10:36:17 PST 2026


https://github.com/ashu3103 updated https://github.com/llvm/llvm-project/pull/228032

>From c70356234cf929cac313e05368759d86d37ae067 Mon Sep 17 00:00:00 2001
From: ashu3103 <ash2003sharma at gmail.com>
Date: Thu, 1 Oct 2026 15:50:39 +0530
Subject: [PATCH] [MLIR] Missed Optimization: Incomplete elimination of a
 tensor.empty shared by conditional branches (#226767)

---
 .../Transforms/EmptyTensorElimination.cpp     | 67 ++++++++++++-------
 ...ensor-elimination-control-flow-shared.mlir | 33 +++++++++
 2 files changed, 74 insertions(+), 26 deletions(-)
 create mode 100644 mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-empty-tensor-elimination-control-flow-shared.mlir

diff --git a/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp b/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
index a9ccc8fe1a1af5..0405b1b58431f0 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
@@ -125,6 +125,7 @@ LogicalResult mlir::bufferization::eliminateEmptyTensors(
     ControlBuildSubsetExtractionFn subsetsExtractionFn) {
   OpBuilder::InsertionGuard g(rewriter);
   llvm::DenseSet<OpOperand *> visitedOpOperands;
+  DominanceInfo domInfo(op);
   op->walk([&](SubsetInsertionOpInterface op) {
     visitedOpOperands.clear();
     OpOperand &source = op.getSourceOperand();
@@ -155,33 +156,47 @@ LogicalResult mlir::bufferization::eliminateEmptyTensors(
     for (Value v : emptyTensors) {
       auto emptyTensorOp = v.getDefiningOp<tensor::EmptyOp>();
       assert(emptyTensorOp && "expected tensor.empty op");
-      // Find the use to be replaced from the use-def chain.
-      auto iter = llvm::find_if(
-          visitedOpOperands, [&emptyTensorOp](OpOperand *opOperand) {
-            return llvm::count(emptyTensorOp->getUses(), *opOperand);
-          });
-
-      assert(iter != visitedOpOperands.end() && "could not find use");
-      OpOperand *useToBeReplaced = *iter;
-      Operation *user = useToBeReplaced->getOwner();
-      auto replacement = subsetsExtractionFn(rewriter, op, emptyTensorOp, user);
-      if (!replacement)
-        continue;
-      if (emptyTensorOp == replacement.getDefiningOp())
-        continue;
-      if (replacement.getType() != v.getType()) {
-        if (cast<ShapedType>(replacement.getType()).getElementType() !=
-            cast<ShapedType>(v.getType()).getElementType())
-          continue;
-        rewriter.setInsertionPointAfterValue(replacement);
-        replacement = tensor::CastOp::create(rewriter, v.getLoc(), v.getType(),
-                                             replacement);
+      // Find all the uses that are in the reverse use-def chain and whose
+      // immediate defining op is the tensor.empty op.
+      SmallVector<OpOperand *> usesForReplacement;
+      for (OpOperand *opOperand : visitedOpOperands) {
+        if (llvm::count(emptyTensorOp->getUses(), *opOperand))
+          usesForReplacement.push_back(opOperand);
+      }
+
+      Value sharedReplacement;
+      assert(!usesForReplacement.empty() && "could not find any use");
+      for (OpOperand *useToBeReplaced : usesForReplacement) {
+        Operation *user = useToBeReplaced->getOwner();
+
+        Value replacement;
+        if (sharedReplacement && domInfo.properlyDominates(sharedReplacement, user))
+        {
+          replacement = sharedReplacement;
+        } else {
+          replacement =
+              subsetsExtractionFn(rewriter, op, emptyTensorOp, user);
+          if (!replacement)
+            continue;
+          if (emptyTensorOp == replacement.getDefiningOp())
+            continue;
+          if (replacement.getType() != v.getType()) {
+            if (cast<ShapedType>(replacement.getType()).getElementType() !=
+                cast<ShapedType>(v.getType()).getElementType())
+              continue;
+            rewriter.setInsertionPointAfterValue(replacement);
+            replacement = tensor::CastOp::create(rewriter, v.getLoc(),
+                                                 v.getType(), replacement);
+          }
+          sharedReplacement = replacement;
+        }
+
+        // Replace the specific use of the tensor::EmptyOp.
+        rewriter.modifyOpInPlace(user, [&]() {
+          user->setOperand(useToBeReplaced->getOperandNumber(), replacement);
+        });
+        state.resetCache();
       }
-      // Replace the specific use of the tensor::EmptyOp.
-      rewriter.modifyOpInPlace(user, [&]() {
-        user->setOperand(useToBeReplaced->getOperandNumber(), replacement);
-      });
-      state.resetCache();
     }
 
     return WalkResult::advance();
diff --git a/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-empty-tensor-elimination-control-flow-shared.mlir b/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-empty-tensor-elimination-control-flow-shared.mlir
new file mode 100644
index 00000000000000..d78f4226ec2a24
--- /dev/null
+++ b/mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize-empty-tensor-elimination-control-flow-shared.mlir
@@ -0,0 +1,33 @@
+// RUN: mlir-opt %s -eliminate-empty-tensors -empty-tensor-to-alloc-tensor -one-shot-bufferize="bufferize-function-boundaries" -cse -canonicalize -split-input-file | FileCheck %s
+
+//      CHECK: func @shared_empty(
+// CHECK-SAME: %[[FUNC_ARG_1:[0-9a-zA-Z]*]]: i1
+// CHECK-SAME: %[[FUNC_ARG_2:[0-9a-zA-Z]*]]: f32
+// CHECK-SAME: %[[FUNC_ARG_3:[0-9a-zA-Z]*]]: f32
+// CHECK-SAME: %[[FUNC_ARG_4:[0-9a-zA-Z]*]]: memref<16xf32, strided{{.*}}>
+func.func @shared_empty(
+    %arg0: i1,
+    %arg1: f32,
+    %arg2: f32,
+    %arg3: tensor<16xf32>)
+        -> tensor<16xf32>
+{
+    // CHECK-NOT: memref.alloc
+    // CHECK: %[[subview:.*]] = memref.subview %[[FUNC_ARG_4]][4] [4] [1]
+    %0 = tensor.empty() : tensor<4xf32>
+    // CHECK: scf.if %[[FUNC_ARG_1]] {
+    %1 = scf.if %arg0 -> (tensor<4xf32>) {
+        // CHECK: linalg.fill ins(%[[FUNC_ARG_2]]{{.*}}) outs(%[[subview]] : memref<4xf32, strided{{.*}}>)
+        %2 = linalg.fill ins(%arg1 : f32) outs(%0 : tensor<4xf32>) -> tensor<4xf32>
+        scf.yield %2 : tensor<4xf32>
+        // CHECK: } else {
+    } else {
+        // CHECK: linalg.fill ins(%[[FUNC_ARG_3]]{{.*}}) outs(%[[subview]] : memref<4xf32, strided{{.*}}>)
+        %2 = linalg.fill ins(%arg2 : f32) outs(%0 : tensor<4xf32>) -> tensor<4xf32>
+        scf.yield %2 : tensor<4xf32>
+    }
+    // CHECK-NOT: memref.copy
+    // CHECK:   return %[[FUNC_ARG_4]]
+    %inserted_slice = tensor.insert_slice %1 into %arg3[4] [4] [1] : tensor<4xf32> into tensor<16xf32>
+    return %inserted_slice : tensor<16xf32>
+}
\ No newline at end of file



More information about the Mlir-commits mailing list