[Mlir-commits] [mlir] [mlir][EmitC] Support pointer-based memrefs in load/store lowering (PR #186828)
ioana ghiban
llvmlistbot at llvm.org
Tue Mar 17 01:08:07 PDT 2026
https://github.com/ioghiban updated https://github.com/llvm/llvm-project/pull/186828
>From 8ccccb521f7a2b01c1a48ba2e795580a1795be6c Mon Sep 17 00:00:00 2001
From: Ioana Ghiban <ioana.ghiban at arm.com>
Date: Mon, 16 Mar 2026 14:43:12 +0100
Subject: [PATCH] [mlir][EmitC] Support pointer-based memrefs in load/store
lowering
Assisted-by: ChatGPT (refine implementation + tests). I reviewed all code and tests before submission.
---
.../MemRefToEmitC/MemRefToEmitC.cpp | 97 ++++++++++++++++---
.../memref-to-emitc-alloc-load-store.mlir | 75 ++++++++++++++
2 files changed, 159 insertions(+), 13 deletions(-)
create mode 100644 mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
index c0c0d87289704..c15a63e3a09e9 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
@@ -140,6 +140,41 @@ createPointerFromEmitcArray(Location loc, OpBuilder &builder,
return ptr;
}
+static Value stripPointerUnrealizedCast(Value v) {
+ if (auto cast = v.getDefiningOp<UnrealizedConversionCastOp>())
+ if (cast.getNumOperands() == 1 &&
+ isa<emitc::PointerType>(cast.getOperand(0).getType()))
+ return cast.getOperand(0); // Pointer path
+ return Value(); // Array path
+}
+
+static Value computeRowMajorLinearIndex(Location loc, MemRefType memrefType,
+ ValueRange indices,
+ OpBuilder &builder) {
+ ArrayRef<int64_t> shape = memrefType.getShape();
+
+ Type idxType =
+ indices.empty() ? builder.getIndexType() : indices[0].getType();
+
+ Value linearIndex = indices.empty()
+ ? emitc::ConstantOp::create(builder, loc, idxType,
+ builder.getIndexAttr(0))
+ : indices[0];
+
+ for (size_t i = 1; i < shape.size(); ++i) {
+ Value dimSize = emitc::ConstantOp::create(builder, loc, idxType,
+ builder.getIndexAttr(shape[i]));
+
+ linearIndex =
+ emitc::MulOp::create(builder, loc, idxType, linearIndex, dimSize);
+
+ linearIndex =
+ emitc::AddOp::create(builder, loc, idxType, linearIndex, indices[i]);
+ }
+
+ return linearIndex;
+}
+
struct ConvertAlloc final : public OpConversionPattern<memref::AllocOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
@@ -347,15 +382,32 @@ struct ConvertLoad final : public OpConversionPattern<memref::LoadOp> {
auto arrayValue =
dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
- if (!arrayValue) {
- return rewriter.notifyMatchFailure(op.getLoc(), "expected array type");
+ Value strippedPtr = stripPointerUnrealizedCast(operands.getMemref());
+ if (!strippedPtr && arrayValue) {
+ auto subscript = emitc::SubscriptOp::create(
+ rewriter, op.getLoc(), arrayValue, operands.getIndices());
+
+ rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
+ return success();
}
+ if (strippedPtr) {
+ Location loc = op.getLoc();
+ MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
+ ValueRange indices = operands.getIndices();
- auto subscript = emitc::SubscriptOp::create(
- rewriter, op.getLoc(), arrayValue, operands.getIndices());
+ // Compute row-major linear index
+ Value linearIndex =
+ computeRowMajorLinearIndex(loc, opMemrefType, indices, rewriter);
- rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
- return success();
+ auto typedPtr = cast<TypedValue<emitc::PointerType>>(strippedPtr);
+ auto subscript = emitc::SubscriptOp::create(rewriter, op.getLoc(),
+ typedPtr, linearIndex);
+
+ rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
+ return success();
+ }
+ return rewriter.notifyMatchFailure(op.getLoc(),
+ "expected array or pointer type");
}
};
@@ -367,17 +419,36 @@ struct ConvertStore final : public OpConversionPattern<memref::StoreOp> {
ConversionPatternRewriter &rewriter) const override {
auto arrayValue =
dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
- if (!arrayValue) {
- return rewriter.notifyMatchFailure(op.getLoc(), "expected array type");
+ Value strippedPtr = stripPointerUnrealizedCast(operands.getMemref());
+ if (!strippedPtr && arrayValue) {
+ auto subscript = emitc::SubscriptOp::create(
+ rewriter, op.getLoc(), arrayValue, operands.getIndices());
+ rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
+ operands.getValue());
+ return success();
}
- auto subscript = emitc::SubscriptOp::create(
- rewriter, op.getLoc(), arrayValue, operands.getIndices());
- rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
- operands.getValue());
- return success();
+ if (strippedPtr) {
+ Location loc = op.getLoc();
+ MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
+ ValueRange indices = operands.getIndices();
+
+ // Compute row-major linear index
+ Value linearIndex =
+ computeRowMajorLinearIndex(loc, opMemrefType, indices, rewriter);
+ auto typedPtr = cast<TypedValue<emitc::PointerType>>(strippedPtr);
+ auto subscript =
+ emitc::SubscriptOp::create(rewriter, loc, typedPtr, linearIndex);
+
+ rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
+ operands.getValue());
+ return success();
+ }
+ return rewriter.notifyMatchFailure(op.getLoc(),
+ "expected array or pointer type");
}
};
+
} // namespace
void mlir::populateMemRefToEmitCTypeConversion(TypeConverter &typeConverter) {
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
new file mode 100644
index 0000000000000..9606a3ca7ee78
--- /dev/null
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
@@ -0,0 +1,75 @@
+// RUN: mlir-opt -convert-to-emitc %s -split-input-file \
+// RUN: | FileCheck %s
+
+/// NOTE: This test intentionally uses `-convert-to-emitc` only.
+///
+/// The `-convert-memref-to-emitc` pass introduces
+/// `builtin.unrealized_conversion_cast` operations when lowering
+/// `memref.alloc` results (which are lowered to `emitc.ptr`) to the canonical
+/// memref representation used by the type converter (`emitc.array`).
+/// These casts are expected at that stage of the pipeline.
+///
+/// The purpose of this test is to verify the final lowering produced by
+/// `-convert-to-emitc`, where `memref.load` and `memref.store` conversions now
+/// handle pointer-backed buffers directly and eliminate the intermediate
+/// `unrealized_conversion_cast`.
+/// Therefore, the test must run the full EmitC conversion pipeline.
+
+/// AllocOp conversion always returns a ptr
+// CHECK-LABEL: emitc.func private @memref_alloc_store(
+// CHECK-SAME: %[[VAL:.*]]: f32,
+// CHECK-SAME: %[[ARG_I:.*]]: !emitc.size_t,
+// CHECK-SAME: %[[ARG_J:.*]]: !emitc.size_t)
+func.func private @memref_alloc_store(%v : f32, %i: index, %j: index) {
+ /// Size to alloc computation
+ // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() {args = [f32]} : () -> !emitc.size_t
+ // CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 32 : index}> : () -> index
+ // CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_F32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
+ // CHECK: %[[MALLOC_PTR:.*]] = call_opaque "malloc"(%[[TOTAL_BYTES]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
+ // CHECK: %[[ELEM_PTR:.*]] = cast %[[MALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<f32>
+ %alloc = memref.alloc() : memref<4x8xf32>
+ /// Subscript computation
+ // CHECK: %[[ROW_STRIDE:.*]] = "emitc.constant"() <{value = 8 : index}> : () -> !emitc.size_t
+ // CHECK: %[[ROW_OFFSET:.*]] = mul %[[ARG_I]], %[[ROW_STRIDE]] : (!emitc.size_t, !emitc.size_t) -> !emitc.size_t
+ // CHECK: %[[LINEAR_INDEX:.*]] = add %[[ROW_OFFSET]], %[[ARG_J]] : (!emitc.size_t, !emitc.size_t) -> !emitc.size_t
+ // CHECK: %[[ELEM_LVALUE:.*]] = subscript %[[ELEM_PTR]]{{\[}}%[[LINEAR_INDEX]]] : (!emitc.ptr<f32>, !emitc.size_t) -> !emitc.lvalue<f32>
+ // CHECK: assign %[[VAL]] : f32 to %[[ELEM_LVALUE]] : <f32>
+ memref.store %v, %alloc[%i, %j] : memref<4x8xf32>
+ return
+}
+// CHECK-LABEL: emitc.func private @memref_alloc_load(
+// CHECK-SAME: %[[ARG_I:.*]]: !emitc.size_t,
+// CHECK-SAME: %[[ARG_J:.*]]: !emitc.size_t) -> f32
+func.func private @memref_alloc_load(%i: index, %j: index) -> f32 {
+ // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() {args = [f32]} : () -> !emitc.size_t
+ // CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 32 : index}> : () -> index
+ // CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_F32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
+ // CHECK: %[[MALLOC_PTR:.*]] = call_opaque "malloc"(%[[TOTAL_BYTES]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
+ // CHECK: %[[ELEM_PTR:.*]] = cast %[[MALLOC_PTR]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<f32>
+ %alloc = memref.alloc() : memref<4x8xf32>
+ // CHECK: %[[ROW_STRIDE:.*]] = "emitc.constant"() <{value = 8 : index}> : () -> !emitc.size_t
+ // CHECK: %[[ROW_OFFSET:.*]] = mul %[[ARG_I]], %[[ROW_STRIDE]] : (!emitc.size_t, !emitc.size_t) -> !emitc.size_t
+ // CHECK: %[[LINEAR_INDEX:.*]] = add %[[ROW_OFFSET]], %[[ARG_J]] : (!emitc.size_t, !emitc.size_t) -> !emitc.size_t
+ // CHECK: %[[ELEM_LVALUE:.*]] = subscript %[[ELEM_PTR]]{{\[}}%[[LINEAR_INDEX]]] : (!emitc.ptr<f32>, !emitc.size_t) -> !emitc.lvalue<f32>
+ // CHECK: %[[LOADED_VAL:.*]] = load %[[ELEM_LVALUE]] : <f32>
+ %v = memref.load %alloc[%i, %j] : memref<4x8xf32>
+ return %v : f32
+}
+
+/// LoadOp and StoreOp still compatible
+/// Previous array paths still available
+// CHECK-LABEL: emitc.func @memref_load_store(
+// CHECK-SAME: %[[BUFF0:.*]]: !emitc.array<2xf32>,
+// CHECK-SAME: %[[BUFF1:.*]]: !emitc.array<4x8xf32>,
+// CHECK-SAME: %[[ARG_I:.*]]: !emitc.size_t,
+// CHECK-SAME: %[[ARG_J:.*]]: !emitc.size_t) {
+func.func @memref_load_store(%buff0: memref<2xf32>,
+ %buff1: memref<4x8xf32>, %i : index, %j : index) {
+ // CHECK: %[[ELEM_LVALUE0:.*]] = subscript %[[BUFF0]]{{\[}}%[[ARG_I]]] : (!emitc.array<2xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+ // CHECK: %[[VAL:.*]] = load %[[ELEM_LVALUE0]] : <f32>
+ %v = memref.load %buff0[%i] : memref<2xf32>
+ // CHECK: %[[ELEM_LVALUE1:.*]] = subscript %[[BUFF1]]{{\[}}%[[ARG_I]], %[[ARG_J]]] : (!emitc.array<4x8xf32>, !emitc.size_t, !emitc.size_t) -> !emitc.lvalue<f32>
+ // CHECK: assign %[[VAL]] : f32 to %[[ELEM_LVALUE1]] : <f32>
+ memref.store %v, %buff1[%i, %j] : memref<4x8xf32>
+ return
+}
More information about the Mlir-commits
mailing list