Skip to content

Conversation

linuxlonelyeagle
Copy link
Member

Add hoist-dynamic-allocs-option to buffer-results-to-out-params. This PR supported that obtain the size of the dynamic shape memref through the caller-callee relationship.

@llvmbot llvmbot added mlir mlir:bufferization Bufferization infrastructure labels Sep 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 27, 2025

@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-bufferization

Author: lonely eagle (linuxlonelyeagle)

Changes

Add hoist-dynamic-allocs-option to buffer-results-to-out-params. This PR supported that obtain the size of the dynamic shape memref through the caller-callee relationship.


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

5 Files Affected:

  • (modified) mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h (+16-6)
  • (modified) mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td (+2)
  • (modified) mlir/lib/Dialect/Bufferization/Transforms/BufferResultsToOutParams.cpp (+84-8)
  • (added) mlir/test/Transforms/buffer-results-to-out-params-hosit-dynamic-allocs.mlir (+79)
  • (renamed) mlir/test/Transforms/buffer-results-to-out-params-hosit-static-allocs.mlir ()
diff --git a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h
index a2409f2796b94..e413a5ede5d64 100644
--- a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h
@@ -5,6 +5,7 @@
 #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
 #include "mlir/Pass/Pass.h"
+#include "llvm/ADT/MapVector.h"
 
 namespace mlir {
 class FunctionOpInterface;
@@ -131,8 +132,8 @@ struct BufferResultsToOutParamsOpts {
   /// Allocator function: Generate a memref allocation with the given type.
   /// Since `promoteBufferResultsToOutParams` doesn't allow dynamically shaped
   /// results, we don't allow passing a range of values for dynamic dims.
-  using AllocationFn =
-      std::function<FailureOr<Value>(OpBuilder &, Location, MemRefType)>;
+  using AllocationFn = std::function<FailureOr<Value>(OpBuilder &, Location,
+                                                      MemRefType, ValueRange)>;
 
   /// Memcpy function: Generate a memcpy between two memrefs.
   using MemCpyFn =
@@ -147,8 +148,9 @@ struct BufferResultsToOutParamsOpts {
   /// Allocation function; used to allocate a memref.
   /// Default memref.alloc is used
   AllocationFn allocationFn = [](OpBuilder &builder, Location loc,
-                                 MemRefType type) {
-    return memref::AllocOp::create(builder, loc, type).getResult();
+                                 MemRefType type, ValueRange dynamicSizes) {
+    return memref::AllocOp::create(builder, loc, type, dynamicSizes)
+        .getResult();
   };
 
   /// Memcpy function; used to create a copy between two memrefs.
@@ -164,15 +166,23 @@ struct BufferResultsToOutParamsOpts {
   bool addResultAttribute = false;
 
   /// If true, the pass eliminates the memref.alloc and memcpy if the returned
-  /// memref is allocated in the current function.
+  /// memref is static allocated in the current function.
   bool hoistStaticAllocs = false;
+
+  /// If true, the pass eliminates the memref.alloc and memcpy if the returned
+  /// memref is dynamic allocated in the current function.
+  bool hoistDynamicAllocs = false;
+
+  /// It maps the shape source of the dynamic shape memref returned by each
+  /// function.
+  llvm::DenseMap<func::FuncOp, SmallVector<SmallVector<Value>>> dynamicSizesMap;
 };
 
 /// Replace buffers that are returned from a function with an out parameter.
 /// Also update all call sites.
 LogicalResult
 promoteBufferResultsToOutParams(ModuleOp module,
-                                const BufferResultsToOutParamsOpts &options);
+                                BufferResultsToOutParamsOpts &options);
 
 /// Drop all memref function results that are equivalent to a function argument.
 LogicalResult dropEquivalentBufferResults(ModuleOp module);
diff --git a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
index a0d113c150c5e..cad44cb15f479 100644
--- a/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td
@@ -256,6 +256,8 @@ def BufferResultsToOutParamsPass
               "Add the attribute 'bufferize.result' to all output parameters.">,
        Option<"hoistStaticAllocs", "hoist-static-allocs", "bool",
               /*default=*/"false", "Hoist static allocations to call sites.">,
+       Option<"hoistDynamicAllocs", "hoist-dynamic-allocs", "bool",
+              /*default=*/"false", "Hoist dynamic allocations to call sites.">,
   ];
   let dependentDialects = ["memref::MemRefDialect"];
 }
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/BufferResultsToOutParams.cpp b/mlir/lib/Dialect/Bufferization/Transforms/BufferResultsToOutParams.cpp
index e30e094c28467..ae68477f57a0d 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/BufferResultsToOutParams.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/BufferResultsToOutParams.cpp
@@ -43,6 +43,52 @@ static bool hasStaticIdentityLayout(MemRefType type) {
   return type.getLayout().isIdentity();
 }
 
+/// Return the dynamic shapes of the `memref` based on the define op. If the
+/// complete dynamic shape fails to be captured, return an empty value.
+/// Currently, only function parameters are supported for capturing.
+static ValueRange getDynamicSize(Value memref, func::FuncOp funcOp) {
+  auto *defOp = memref.getDefiningOp();
+  if (!defOp)
+    return {};
+  auto operands = defOp->getOperands();
+  SmallVector<Value> dynamicSizes;
+  for (Value size : operands) {
+    BlockArgument sizeSrc = mlir::dyn_cast<BlockArgument>(size);
+    if (!sizeSrc)
+      return {};
+
+    bool finded = false;
+    for (BlockArgument argument : funcOp.getArguments()) {
+      if (argument == sizeSrc) {
+        dynamicSizes.push_back(argument);
+        finded = true;
+        break;
+      }
+    }
+    if (!finded)
+      return {};
+  }
+  return dynamicSizes;
+}
+
+/// Returns the dynamic sizes at the callee, through the call relationship
+/// between the caller and callee.
+static ValueRange mapDynamicSizeAtCaller(func::CallOp call, func::FuncOp callee,
+                                         ValueRange dynamicSizes) {
+  SmallVector<Value> mapedDynamicSizes;
+  for (Value size : dynamicSizes) {
+    auto callOperands = call.getOperands();
+    for (size_t i = 0, e = callOperands.size(); i < e; ++i) {
+      Value src = callOperands[i];
+      BlockArgument dst = callee.getArgument(i);
+      if (size != dst)
+        continue;
+      mapedDynamicSizes.push_back(src);
+    }
+  }
+  return mapedDynamicSizes;
+}
+
 // Updates the func op and entry block.
 //
 // Any args appended to the entry block are added to `appendedEntryArgs`.
@@ -109,7 +155,7 @@ updateFuncOp(func::FuncOp func,
 // the given out-params.
 static LogicalResult
 updateReturnOps(func::FuncOp func, ArrayRef<BlockArgument> appendedEntryArgs,
-                const bufferization::BufferResultsToOutParamsOpts &options) {
+                bufferization::BufferResultsToOutParamsOpts &options) {
   auto res = func.walk([&](func::ReturnOp op) {
     SmallVector<Value, 6> copyIntoOutParams;
     SmallVector<Value, 6> keepAsReturnOperands;
@@ -120,12 +166,22 @@ updateReturnOps(func::FuncOp func, ArrayRef<BlockArgument> appendedEntryArgs,
         keepAsReturnOperands.push_back(operand);
     }
     OpBuilder builder(op);
+    SmallVector<SmallVector<Value>> dynamicSizes;
     for (auto [orig, arg] : llvm::zip(copyIntoOutParams, appendedEntryArgs)) {
-      if (options.hoistStaticAllocs &&
+      bool hoistStaticAllocs =
+          options.hoistStaticAllocs &&
+          mlir::cast<MemRefType>(orig.getType()).hasStaticShape();
+      bool hoistDynamicAllocs =
+          options.hoistDynamicAllocs &&
+          !mlir::cast<MemRefType>(orig.getType()).hasStaticShape();
+      if ((hoistStaticAllocs || hoistDynamicAllocs) &&
           isa_and_nonnull<bufferization::AllocationOpInterface>(
-              orig.getDefiningOp()) &&
-          mlir::cast<MemRefType>(orig.getType()).hasStaticShape()) {
+              orig.getDefiningOp())) {
         orig.replaceAllUsesWith(arg);
+        if (hoistDynamicAllocs) {
+          SmallVector<Value> dynamicSize = getDynamicSize(orig, func);
+          dynamicSizes.push_back(dynamicSize);
+        }
         orig.getDefiningOp()->erase();
       } else {
         if (failed(options.memCpyFn(builder, op.getLoc(), orig, arg)))
@@ -134,6 +190,10 @@ updateReturnOps(func::FuncOp func, ArrayRef<BlockArgument> appendedEntryArgs,
     }
     func::ReturnOp::create(builder, op.getLoc(), keepAsReturnOperands);
     op.erase();
+    auto dynamicSizePair =
+        std::pair<func::FuncOp, SmallVector<SmallVector<Value>>>(func,
+                                                                 dynamicSizes);
+    options.dynamicSizesMap.insert(dynamicSizePair);
     return WalkResult::advance();
   });
   return failure(res.wasInterrupted());
@@ -166,8 +226,16 @@ updateCalls(ModuleOp module,
     }
     SmallVector<Value, 6> outParams;
     OpBuilder builder(op);
+    SmallVector<SmallVector<Value>> dynamicSizes =
+        options.dynamicSizesMap.lookup(callee);
+    size_t dynamicSizesIndex = 0;
     for (Value memref : replaceWithOutParams) {
-      if (!cast<MemRefType>(memref.getType()).hasStaticShape()) {
+      ValueRange dynamicSize = dynamicSizes.size() > dynamicSizesIndex
+                                   ? dynamicSizes[dynamicSizesIndex]
+                                   : SmallVector<Value>();
+      bool memrefStaticShape =
+          cast<MemRefType>(memref.getType()).hasStaticShape();
+      if (!memrefStaticShape && dynamicSize.empty()) {
         op.emitError()
             << "cannot create out param for dynamically shaped result";
         didFail = true;
@@ -177,8 +245,15 @@ updateCalls(ModuleOp module,
       auto allocType =
           MemRefType::get(memrefType.getShape(), memrefType.getElementType(),
                           AffineMap(), memrefType.getMemorySpace());
+
+      if (memrefStaticShape) {
+        dynamicSize = {};
+      } else {
+        ++dynamicSizesIndex;
+        dynamicSize = mapDynamicSizeAtCaller(op, callee, dynamicSize);
+      }
       auto maybeOutParam =
-          options.allocationFn(builder, op.getLoc(), allocType);
+          options.allocationFn(builder, op.getLoc(), allocType, dynamicSize);
       if (failed(maybeOutParam)) {
         op.emitError() << "failed to create allocation op";
         didFail = true;
@@ -211,8 +286,7 @@ updateCalls(ModuleOp module,
 }
 
 LogicalResult mlir::bufferization::promoteBufferResultsToOutParams(
-    ModuleOp module,
-    const bufferization::BufferResultsToOutParamsOpts &options) {
+    ModuleOp module, bufferization::BufferResultsToOutParamsOpts &options) {
   for (auto func : module.getOps<func::FuncOp>()) {
     if (!options.filterFn(&func))
       continue;
@@ -243,6 +317,8 @@ struct BufferResultsToOutParamsPass
       options.addResultAttribute = true;
     if (hoistStaticAllocs)
       options.hoistStaticAllocs = true;
+    if (hoistDynamicAllocs)
+      options.hoistDynamicAllocs = true;
 
     if (failed(bufferization::promoteBufferResultsToOutParams(getOperation(),
                                                               options)))
diff --git a/mlir/test/Transforms/buffer-results-to-out-params-hosit-dynamic-allocs.mlir b/mlir/test/Transforms/buffer-results-to-out-params-hosit-dynamic-allocs.mlir
new file mode 100644
index 0000000000000..f33eb8e26fbce
--- /dev/null
+++ b/mlir/test/Transforms/buffer-results-to-out-params-hosit-dynamic-allocs.mlir
@@ -0,0 +1,79 @@
+// RUN: mlir-opt -allow-unregistered-dialect -p 'builtin.module(buffer-results-to-out-params{hoist-dynamic-allocs})' %s -split-input-file | FileCheck %s
+
+func.func private @single_alloc(%size : index) -> (memref<?xf32>) {
+  %alloc = memref.alloc(%size) : memref<?xf32>
+  return %alloc : memref<?xf32>
+}
+
+func.func @single_alloc_test(%size : index) {
+  %alloc = call @single_alloc(%size) : (index) -> (memref<?xf32>)
+  "test.sink"(%alloc) : (memref<?xf32>) -> ()
+}
+
+// CHECK-LABEL: func.func private @single_alloc(
+//  CHECK-SAME:   %{{.*}}: index,
+//  CHECK-SAME:   %{{.*}}: memref<?xf32>) {
+
+// CHECK-LABEL: func.func @single_alloc_test(
+//  CHECK-SAME:   %[[size:.*]]: index) {
+//       CHECK:   %[[alloc:.*]] = memref.alloc(%[[size]]) : memref<?xf32>
+//       CHECK:   call @single_alloc(%[[size]], %[[alloc]]) : (index, memref<?xf32>) -> ()
+//       CHECK:   "test.sink"(%[[alloc]]) : (memref<?xf32>) -> ()
+//       CHECK: }
+
+// -----
+
+func.func private @mult_alloc(%size0 : index, %size1 : index) -> (memref<?x?xf32>, memref<?xf32>) {
+  %alloc0 = memref.alloc(%size0, %size1) : memref<?x?xf32>
+  %alloc1 = memref.alloc(%size1) : memref<?xf32>
+  return %alloc0, %alloc1 : memref<?x?xf32>, memref<?xf32>
+}
+
+func.func @mult_alloc_test(%size0 : index, %size1: index) {
+  %alloc0, %alloc1 = call @mult_alloc(%size0, %size1) : (index, index) -> (memref<?x?xf32>, memref<?xf32>)
+  "test.sink"(%alloc0, %alloc1) : (memref<?x?xf32>, memref<?xf32>) -> ()
+}
+
+// CHECK-LABEL: func private @mult_alloc(
+//  CHECK-SAME:    %{{.*}}: index,  %{{.*}}: index,
+//  CHECK-SAME:    %{{.*}}: memref<?x?xf32>, %{{.*}}: memref<?xf32>) {
+
+// CHECK-LABEL: func @mult_alloc_test(
+//  CHECK-SAME:   %[[size0:.*]]: index,
+//  CHECK-SAME:   %[[size1:.*]]: index) {
+//       CHECK:   %[[alloc0:.*]] = memref.alloc(%[[size0]], %[[size1]]) : memref<?x?xf32>
+//       CHECK:   %[[alloc1:.*]] = memref.alloc(%[[size1]]) : memref<?xf32>
+//       CHECK:   call @mult_alloc(%[[size0]], %[[size1]], %[[alloc0]], %[[alloc1]]) : (index, index, memref<?x?xf32>, memref<?xf32>) -> ()
+//       CHECK:   "test.sink"(%[[alloc0]], %[[alloc1]]) : (memref<?x?xf32>, memref<?xf32>) -> ()
+//       CHECK: }
+
+
+// -----
+
+func.func private @complex_alloc(%size0 : index, %size1 : index) -> (memref<?x?xf32>, memref<4xf32>, memref<?xf32>) {
+  %alloc0 = memref.alloc(%size0, %size1) : memref<?x?xf32>
+  %alloc1 = memref.alloc() : memref<4xf32>
+  %alloc2 = memref.alloc(%size1) : memref<?xf32>
+  return %alloc0, %alloc1, %alloc2 : memref<?x?xf32>, memref<4xf32>, memref<?xf32>
+}
+
+func.func @complex_alloc_test(%size0 : index, %size1: index) {
+  %alloc0, %alloc1, %alloc2 = call @complex_alloc(%size0, %size1) : (index, index) -> (memref<?x?xf32>, memref<4xf32>, memref<?xf32>)
+  "test.sink"(%alloc0, %alloc1, %alloc2) : (memref<?x?xf32>, memref<4xf32>, memref<?xf32>) -> ()
+}
+
+// CHECK-LABEL: func private @complex_alloc(
+//  CHECK-SAME:   %{{.*}}: index, %{{.*}}: index,
+//  CHECK-SAME:   %{{.*}}: memref<?x?xf32>,
+//  CHECK-SAME:   %{{.*}}: memref<4xf32>,
+//  CHECK-SAME:   %{{.*}}: memref<?xf32>) {
+
+// CHECK-LABEL: func @complex_alloc_test(
+//  CHECK-SAME:   %[[size0:.*]]: index,
+//  CHECK-SAME:   %[[size1:.*]]: index) {
+//       CHECK:   %[[alloc0:.*]] = memref.alloc(%[[size0]], %[[size1]]) : memref<?x?xf32>
+//       CHECK:   %[[alloc1:.*]] = memref.alloc() : memref<4xf32>
+//       CHECK:   %[[alloc2:.*]] = memref.alloc(%[[size1]]) : memref<?xf32>
+//       CHECK:   call @complex_alloc(%[[size0]], %[[size1]], %[[alloc0]], %[[alloc1]], %[[alloc2]]) : (index, index, memref<?x?xf32>, memref<4xf32>, memref<?xf32>) -> ()
+//       CHECK:   "test.sink"(%[[alloc0]], %[[alloc1]], %[[alloc2]]) : (memref<?x?xf32>, memref<4xf32>, memref<?xf32>) -> ()
+//       CHECK: }
diff --git a/mlir/test/Transforms/buffer-results-to-out-params-elim.mlir b/mlir/test/Transforms/buffer-results-to-out-params-hosit-static-allocs.mlir
similarity index 100%
rename from mlir/test/Transforms/buffer-results-to-out-params-elim.mlir
rename to mlir/test/Transforms/buffer-results-to-out-params-hosit-static-allocs.mlir

@srcarroll
Copy link
Contributor

maybe a naive question, but is there a reason to have separate options for static and dynamic allocs instead of just one hoist-allocs? When would one want to hoist one but not the other?

}

// CHECK-LABEL: func.func private @single_alloc(
// CHECK-SAME: %{{.*}}: index,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should size args be removed from callee when they aren't used after hoisting?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have already considered this issue. This issue has been resolved. #160755

// CHECK: %[[alloc2:.*]] = memref.alloc(%[[size1]]) : memref<?xf32>
// CHECK: call @complex_alloc(%[[size0]], %[[size1]], %[[alloc0]], %[[alloc1]], %[[alloc2]]) : (index, index, memref<?x?xf32>, memref<4xf32>, memref<?xf32>) -> ()
// CHECK: "test.sink"(%[[alloc0]], %[[alloc1]], %[[alloc2]]) : (memref<?x?xf32>, memref<4xf32>, memref<?xf32>) -> ()
// CHECK: }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a "no-op" case where it's impossible to hoist? like when a dynamic size is defined inside the callee func.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could introduce such examples in subsequent PRs, such as support for conatsant Op.I believe it is also acceptable to introduce such an example at this point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although i guess in some cases it would be possible, but non-trivial. but it doesn't look like this option handles that case so would be good to track that behavior in a test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could introduce such examples in subsequent PRs, such as support for constant Op.I believe it is also acceptable to introduce such an example at this point.

sure. i'm not suggesting handling this case now. but more to show this pass won't break in that case in the meantime. maybe i'm just being too cautious though

@linuxlonelyeagle
Copy link
Member Author

maybe a naive question, but is there a reason to have separate options for static and dynamic allocs instead of just one hoist-allocs? When would one want to hoist one but not the other?

Good question. Separating them can improve the coupling of the Pass, but we can also use static allocation and dynamic allocation together. Please tell us your thoughts.

@srcarroll
Copy link
Contributor

maybe a naive question, but is there a reason to have separate options for static and dynamic allocs instead of just one hoist-allocs? When would one want to hoist one but not the other?

Good question. Separating them can improve the coupling of the Pass, but we can also use static allocation and dynamic allocation together. Please tell us your thoughts.

I dont have a strong opinion on it, and I admit that I'm not confident about the repercussions of combining them. I think having options is always good, so fine with me to keep separate.

options.hoistDynamicAllocs &&
!cast<MemRefType>(orig.getType()).hasStaticShape();
if ((hoistStaticAllocs || hoistDynamicAllocs) &&
isa_and_nonnull<bufferization::AllocationOpInterface>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if your implementation also works with memref.realloc, which implements this interface. The first operand is not a size, but I think it does not matter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func.func private @realloc(%memref : memref<?xf32>, %d:index) -> memref<?xf32> {
  %alloc = memref.realloc %memref(%d) : memref<?xf32> to memref<?xf32>
  return %alloc : memref<?xf32>
}

func.func private @main(%d:index) {
  %c1 = arith.constant 1 : index
  %alloc = memref.alloc(%c1) : memref<?xf32>
  func.call @realloc(%alloc, %d) : (memref<?xf32>, index) -> (memref<?xf32>)
  return
}

run pass

a.mlir:9:3: error: 'memref.alloc' op operand #0 must be variadic of index, but got 'memref<?xf32>'
  func.call @realloc(%alloc, %d) : (memref<?xf32>, index) -> (memref<?xf32>)
  ^
a.mlir:9:3: note: see current operation: %2 = "memref.alloc"(%1, %arg0) <{operandSegmentSizes = array<i32: 2, 0>}> : (memref<?xf32>, index) -> memref<?xf32>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func.func private @realloc(%memref : memref<?xf32>, %d:index) -> memref<?xf32> {
  %alloc = memref.realloc %memref(%d) : memref<?xf32> to memref<?xf32>
  return %alloc : memref<?xf32>
}

func.func private @main(%d:index) {
  %c1 = arith.constant 1 : index
  %alloc = memref.alloc(%c1) : memref<?xf32>
  func.call @realloc(%alloc, %d) : (memref<?xf32>, index) -> (memref<?xf32>)
  return
}

run pass

module {
  func.func private @realloc(%arg0: memref<?xf32>, %arg1: index, %arg2: memref<?xf32>) {
    return
  }
  func.func private @main(%arg0: index) {
    %c1 = arith.constant 1 : index
    %alloc = memref.alloc(%c1) : memref<?xf32>
    %alloc_0 = memref.alloc(%arg0) : memref<?xf32>
    call @realloc(%alloc, %arg0, %alloc_0) : (memref<?xf32>, index, memref<?xf32>) -> ()
    return
  }
}

Do I need to add realloc test for realloc?

orig.getDefiningOp())) {
orig.replaceAllUsesWith(arg);
if (hoistDynamicAllocs) {
SmallVector<Value> dynamicSize = getDynamicSize(orig, func);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the sizes could not be captured? You already performed the replaceAllUsesWith. Is that correct? Should the hoisting of the value be skipped instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func.func  @foo() -> memref<?xf32> {
  %c1 = arith.constant 1 : index
  %c0 = arith.constant 0 : index
  %f1 = arith.constant 1.0 : f32
  %alloc = memref.alloc(%c1) : memref<?xf32>
  memref.store %f1, %alloc[%c0] : memref<?xf32>
  return %alloc : memref<?xf32>
}

run the pass

module {
  func.func private @foo(%arg0: memref<?xf32>) {
    %c1 = arith.constant 1 : index
    %c0 = arith.constant 0 : index
    %cst = arith.constant 1.000000e+00 : f32
    memref.store %cst, %arg0[%c0] : memref<?xf32>
    return
  }
}

can't find dynamic size case.

func.func private @foo() -> memref<?xf32> {
  %c1 = arith.constant 1 : index
  %c0 = arith.constant 0 : index
  %f1 = arith.constant 1.0 : f32
  %alloc = memref.alloc(%c1) : memref<?xf32>
  memref.store %f1, %alloc[%c0] : memref<?xf32>
  return %alloc : memref<?xf32>
}

func.func @main() {
  func.call @foo() : () -> memref<?xf32>
  return
}

run the pass.
a.mlir:11:3: error: cannot create out param for dynamically shaped result
  func.call @foo() : () -> memref<?xf32>
  ^
a.mlir:11:3: note: see current operation: %0 = "func.call"() <{callee = @foo}> : () -> memref<?xf32>

@linuxlonelyeagle linuxlonelyeagle force-pushed the add-hoist-static-allocs-option branch from d87d4af to 6e4abeb Compare September 28, 2025 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mlir:bufferization Bufferization infrastructure mlir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants