[Mlir-commits] [mlir] faf4728 - [mlir][EmitC] Convert MemRef::DeallocOp (#194591)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Jun 4 03:12:55 PDT 2026
Author: ioana ghiban
Date: 2026-06-04T12:12:49+02:00
New Revision: faf4728454f57e93530a1ce4fa6f6ca2192e8afb
URL: https://github.com/llvm/llvm-project/commit/faf4728454f57e93530a1ce4fa6f6ca2192e8afb
DIFF: https://github.com/llvm/llvm-project/commit/faf4728454f57e93530a1ce4fa6f6ca2192e8afb.diff
LOG: [mlir][EmitC] Convert MemRef::DeallocOp (#194591)
Add `memref.dealloc` lowering to EmitC by mapping pointer-backed
deallocations to `void*` cast + `free()` call. This complements the
existing `memref.alloc` lowering to `malloc()` / `aligned_alloc()` and
ensures the pass emits the required standard library include when
`free()` is used.
Assisted-by: Codex (refine implementation + tests). I reviewed all code
and tests before submission.
Added:
Modified:
mlir/include/mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h
mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitCPass.cpp
mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-failed.mlir
Removed:
################################################################################
diff --git a/mlir/include/mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h b/mlir/include/mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h
index 5abfb3d7e72dd..64c9ce091dcd0 100644
--- a/mlir/include/mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h
+++ b/mlir/include/mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h
@@ -10,6 +10,7 @@
constexpr const char *alignedAllocFunctionName = "aligned_alloc";
constexpr const char *mallocFunctionName = "malloc";
+constexpr const char *freeFunctionName = "free";
constexpr const char *memcpyFunctionName = "memcpy";
constexpr const char *cppStandardLibraryHeader = "cstdlib";
constexpr const char *cStandardLibraryHeader = "stdlib.h";
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
index b8924c63adf09..87144aac9f6f9 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
@@ -237,6 +237,37 @@ struct ConvertAlloc final : public OpConversionPattern<memref::AllocOp> {
}
};
+struct ConvertDealloc final : public OpConversionPattern<memref::DeallocOp> {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(memref::DeallocOp deallocOp, OpAdaptor operands,
+ ConversionPatternRewriter &rewriter) const override {
+ Location loc = deallocOp.getLoc();
+ // `free` can only be emitted when the dealloc operand is recoverable as an
+ // `emitc.ptr<T>`. In the current conversion, that happens via an
+ // unrealized_conversion_cast from the pointer-backed EmitC form.
+ Value strippedPtr = stripPointerUnrealizedCast(operands.getMemref());
+ if (!strippedPtr) {
+ return rewriter.notifyMatchFailure(
+ loc, "expected pointer-backed memref for EmitC deallocation");
+ }
+
+ // The allocation APIs used by MemRefToEmitC return `void *`, and `free`
+ // expects that same pointer type. Deallocation therefore only needs the
+ // recovered base pointer cast back to `void *` before calling `free`.
+ Type opaqueVoidPtrType = emitc::PointerType::get(
+ emitc::OpaqueType::get(rewriter.getContext(), "void"));
+ Value freeArg =
+ emitc::CastOp::create(rewriter, loc, opaqueVoidPtrType, strippedPtr);
+ emitc::CallOpaqueOp freeCall = emitc::CallOpaqueOp::create(
+ rewriter, loc, TypeRange{}, rewriter.getStringAttr(freeFunctionName),
+ ValueRange{freeArg});
+ rewriter.replaceOp(deallocOp, freeCall.getResults());
+ return success();
+ }
+};
+
struct ConvertCopy final : public OpConversionPattern<memref::CopyOp> {
using OpConversionPattern::OpConversionPattern;
@@ -472,7 +503,7 @@ void mlir::populateMemRefToEmitCTypeConversion(TypeConverter &typeConverter) {
void mlir::populateMemRefToEmitCConversionPatterns(
RewritePatternSet &patterns, const TypeConverter &converter) {
- patterns.add<ConvertAlloca, ConvertAlloc, ConvertCopy, ConvertGlobal,
- ConvertGetGlobal, ConvertLoad, ConvertStore>(
+ patterns.add<ConvertAlloca, ConvertAlloc, ConvertCopy, ConvertDealloc,
+ ConvertGlobal, ConvertGetGlobal, ConvertLoad, ConvertStore>(
converter, patterns.getContext());
}
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitCPass.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitCPass.cpp
index a073a9acf752f..29974c213fcfb 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitCPass.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitCPass.cpp
@@ -76,6 +76,7 @@ struct ConvertMemRefToEmitCPass
module.walk([&](mlir::emitc::CallOpaqueOp callOp) {
StringRef expectedHeader;
if (callOp.getCallee() == alignedAllocFunctionName ||
+ callOp.getCallee() == freeFunctionName ||
callOp.getCallee() == mallocFunctionName)
expectedHeader = options.lowerToCpp ? cppStandardLibraryHeader
: cStandardLibraryHeader;
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
index ca7f3fbe20eff..9d20667a6490c 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
@@ -1,72 +1,91 @@
-// RUN: mlir-opt -convert-memref-to-emitc="lower-to-cpp=true" %s -split-input-file | FileCheck %s --check-prefix=CPP
-// RUN: mlir-opt -convert-memref-to-emitc="lower-to-cpp=false" %s -split-input-file | FileCheck %s --check-prefix=NOCPP
+// RUN: mlir-opt -convert-memref-to-emitc="lower-to-cpp=true" %s | FileCheck %s --check-prefix=CPP
+// RUN: mlir-opt -convert-memref-to-emitc="lower-to-cpp=false" %s | FileCheck %s --check-prefix=NOCPP
-func.func @alloc() {
+/// Tests for converting `memref.alloc` and `memref.dealloc`.
+/// At the moment, `memref.dealloc` lowering only accepts the pointer-backed form
+/// produced by the current `memref.alloc` lowering, so alloc and dealloc tests
+/// are kept together.
+
+func.func @alloc_and_dealloc() {
%alloc = memref.alloc() : memref<999xi32>
+ memref.dealloc %alloc : memref<999xi32>
return
}
// CPP: module {
// CPP-NEXT: emitc.include <"cstdlib">
-// CPP-LABEL: alloc()
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CPP-LABEL: alloc_and_dealloc()
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
// CPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
+// CPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<i32> to !emitc.ptr<!emitc.opaque<"void">>
+// CPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
// CPP-NEXT: return
// NOCPP: module {
// NOCPP-NEXT: emitc.include <"stdlib.h">
-// NOCPP-LABEL: alloc()
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// NOCPP-LABEL: alloc_and_dealloc()
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
// NOCPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
+// NOCPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<i32> to !emitc.ptr<!emitc.opaque<"void">>
+// NOCPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
// NOCPP-NEXT: return
-func.func @alloc_aligned() {
+func.func @alloc_and_dealloc_aligned() {
%alloc = memref.alloc() {alignment = 64 : i64} : memref<999xf32>
+ memref.dealloc %alloc : memref<999xf32>
return
}
-// CPP-LABEL: alloc_aligned
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+// CPP-LABEL: alloc_and_dealloc_aligned
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALIGNMENT:.*]] = "emitc.constant"() <{value = 64 : index}> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "aligned_alloc"(%[[ALIGNMENT]], %[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t, !emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
// CPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<f32>
+// CPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<f32> to !emitc.ptr<!emitc.opaque<"void">>
+// CPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
// CPP-NEXT: return
-// NOCPP-LABEL: alloc_aligned
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+// NOCPP-LABEL: alloc_and_dealloc_aligned
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALIGNMENT:.*]] = "emitc.constant"() <{value = 64 : index}> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "aligned_alloc"(%[[ALIGNMENT]], %[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t, !emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
// NOCPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<f32>
+// NOCPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<f32> to !emitc.ptr<!emitc.opaque<"void">>
+// NOCPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
// NOCPP-NEXT: return
-func.func @allocating_multi() {
- %alloc_5 = memref.alloc() : memref<7x999xi32>
+func.func @allocating_and_deallocating_multi() {
+ %alloc = memref.alloc() : memref<7x999xi32>
+ memref.dealloc %alloc : memref<7x999xi32>
return
}
-// CPP-LABEL: allocating_multi
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CPP-LABEL: allocating_and_deallocating_multi
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 6993 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">
// CPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
-// CPP-NEXT: return
+// CPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<i32> to !emitc.ptr<!emitc.opaque<"void">>
+// CPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
+// CPP-NEXT: return
-// NOCPP-LABEL: allocating_multi
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// NOCPP-LABEL: allocating_and_deallocating_multi
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 6993 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
// NOCPP-NEXT: %[[ALLOC_CAST:.*]] = emitc.cast %[[ALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
-// NOCPP-NEXT: return
-
+// NOCPP-NEXT: %[[FREE_PTR:.*]] = emitc.cast %[[ALLOC_CAST]] : !emitc.ptr<i32> to !emitc.ptr<!emitc.opaque<"void">>
+// NOCPP-NEXT: emitc.call_opaque "free"(%[[FREE_PTR]]) : (!emitc.ptr<!emitc.opaque<"void">>) -> ()
+// NOCPP-NEXT: return
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-failed.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-failed.mlir
index b6eccfc8f0050..753f749fabbd6 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-failed.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-failed.mlir
@@ -17,6 +17,53 @@ func.func @alloca_with_alignment() {
// -----
+// Unsupported: memref.dealloc of a function argument. Function arguments do not
+// lower to the pointer-backed EmitC form expected by the current dealloc
+// lowering.
+func.func @alloc_and_dealloc_arg(%arg0: memref<999xi32>) {
+ // expected-error at +1 {{failed to legalize operation 'memref.dealloc'}}
+ memref.dealloc %arg0 : memref<999xi32>
+ return
+}
+
+// -----
+
+// Unsupported: memref.dealloc of stack storage. memref.alloca does not lower to
+// a heap pointer that can be passed to free().
+func.func @alloca_and_dealloc() {
+ %0 = memref.alloca() : memref<4xf32>
+ // expected-error at +1 {{failed to legalize operation 'memref.dealloc'}}
+ memref.dealloc %0 : memref<4xf32>
+ return
+}
+
+// -----
+
+memref.global "private" constant @g_dense : memref<4xf32> = dense<[0.0, 1.0, 2.0, 3.0]>
+
+// Unsupported: memref.dealloc of a dense-initialized global. memref.get_global
+// does not produce the pointer-backed EmitC form accepted by dealloc lowering.
+func.func @get_global_dense_and_dealloc() {
+ %0 = memref.get_global @g_dense : memref<4xf32>
+ // expected-error at +1 {{failed to legalize operation 'memref.dealloc'}}
+ memref.dealloc %0 : memref<4xf32>
+ return
+}
+
+// -----
+
+memref.global "private" @g_uninit : memref<4xf32> = uninitialized
+
+// Unsupported: memref.dealloc of an uninitialized global.
+func.func @get_global_uninit_and_dealloc() {
+ %0 = memref.get_global @g_uninit : memref<4xf32>
+ // expected-error at +1 {{failed to legalize operation 'memref.dealloc'}}
+ memref.dealloc %0 : memref<4xf32>
+ return
+}
+
+// -----
+
func.func @non_identity_layout() {
// expected-error at +1 {{failed to legalize operation 'memref.alloca'}}
%0 = memref.alloca() : memref<4x3xf32, affine_map<(d0, d1) -> (d1, d0)>>
More information about the Mlir-commits
mailing list