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

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Oct 12 01:56:24 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-bufferization

Author: Ashutosh Sharma (ashu3103)

<details>
<summary>Changes</summary>

Earlier the replacement logic could only select one of the immediate uses of tensor.empty. This patch fixes it by considering all the immediate uses of tensor.empty (which is a potential candidate for replacement) in different control flow paths.

So now for input.mlir:

```
module {
  func.func @<!-- -->shared_empty(%arg0: i1, %arg1: f32, %arg2: f32, %arg3: tensor<16xf32>) -> tensor<16xf32> {
    %0 = tensor.empty() : tensor<4xf32>
    %1 = scf.if %arg0 -> (tensor<4xf32>) {
      %2 = linalg.fill ins(%arg1 : f32) outs(%0 : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %2 : tensor<4xf32>
    } else {
      %2 = linalg.fill ins(%arg2 : f32) outs(%0 : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %2 : tensor<4xf32>
    }
    %inserted_slice = tensor.insert_slice %1 into %arg3[4] [4] [1] : tensor<4xf32> into tensor<16xf32>
    return %inserted_slice : tensor<16xf32>
  }
}
```

the corresponding output after executing `mlir-opt input.mlir --eliminate-empty-tensors`:

```
module {
  func.func @<!-- -->shared_empty(%arg0: i1, %arg1: f32, %arg2: f32, %arg3: tensor<16xf32>) -> tensor<16xf32> {
    %extracted_slice = tensor.extract_slice %arg3[4] [4] [1] : tensor<16xf32> to tensor<4xf32>
    %extracted_slice_0 = tensor.extract_slice %arg3[4] [4] [1] : tensor<16xf32> to tensor<4xf32>
    %0 = tensor.empty() : tensor<4xf32>
    %1 = scf.if %arg0 -> (tensor<4xf32>) {
      %2 = linalg.fill ins(%arg1 : f32) outs(%extracted_slice_0 : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %2 : tensor<4xf32>
    } else {
      %2 = linalg.fill ins(%arg2 : f32) outs(%extracted_slice : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %2 : tensor<4xf32>
    }
    %inserted_slice = tensor.insert_slice %1 into %arg3[4] [4] [1] : tensor<4xf32> into tensor<16xf32>
    return %inserted_slice : tensor<16xf32>
  }
}
```

Notice now both fills write into a subview of `%arg3` which is semantically correct and upon applying common sub-expression elimination pass to it, it merges & hence eliminates that additional `%extracted_slice_0` too

Output of `mlir-opt input.mlir --eliminate-empty-tensors -cse`

```
module {
  func.func @<!-- -->shared_empty(%arg0: i1, %arg1: f32, %arg2: f32, %arg3: tensor<16xf32>) -> tensor<16xf32> {
    %extracted_slice = tensor.extract_slice %arg3[4] [4] [1] : tensor<16xf32> to tensor<4xf32>
    %0 = scf.if %arg0 -> (tensor<4xf32>) {
      %1 = linalg.fill ins(%arg1 : f32) outs(%extracted_slice : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %1 : tensor<4xf32>
    } else {
      %1 = linalg.fill ins(%arg2 : f32) outs(%extracted_slice : tensor<4xf32>) -> tensor<4xf32>
      scf.yield %1 : tensor<4xf32>
    }
    %inserted_slice = tensor.insert_slice %0 into %arg3[4] [4] [1] : tensor<4xf32> into tensor<16xf32>
    return %inserted_slice : tensor<16xf32>
  }
}
```

TODO: add tests and modify docs (if applicable)

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


1 Files Affected:

- (modified) mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp (+29-25) 


``````````diff
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp b/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
index a9ccc8fe1a1af..1182f4bcde2de 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/EmptyTensorElimination.cpp
@@ -155,33 +155,37 @@ 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())
+      // 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);
+      }
+
+      assert(!usesForReplacement.empty() && "could not find any use");
+      for (OpOperand *useToBeReplaced : usesForReplacement) {
+        Operation *user = useToBeReplaced->getOwner();
+        auto replacement =
+            subsetsExtractionFn(rewriter, op, emptyTensorOp, user);
+        if (!replacement)
+          continue;
+        if (emptyTensorOp == replacement.getDefiningOp())
           continue;
-        rewriter.setInsertionPointAfterValue(replacement);
-        replacement = tensor::CastOp::create(rewriter, v.getLoc(), v.getType(),
-                                             replacement);
+        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);
+        }
+        // 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();

``````````

</details>


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


More information about the Mlir-commits mailing list