[flang-commits] [flang] [flang][FIRToMemRef] Look through zero-offset views to find fir.array_coor (PR #215402)
Susan Tan ス-ザン タン via flang-commits
flang-commits at lists.llvm.org
Mon Aug 10 15:06:11 PDT 2026
https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/215402
>From 3ba77140d74fa8ac63a63a0367004027373c2dcf Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 10 Aug 2026 13:33:12 -0700
Subject: [PATCH 1/5] add impl
---
.../lib/Optimizer/Transforms/FIRToMemRef.cpp | 83 +++++++------------
1 file changed, 32 insertions(+), 51 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 5851022b1bc47..7392516a9f377 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -101,6 +101,25 @@ static bool isMarshalLike(Operation *op) {
return resIsMemRef || argIsMemRef;
}
+/// Peel `FortranObjectViewOpInterface` ops with a statically-known zero
+/// offset (fir.declare, ref/box fir.convert, fir.box_addr,
+/// fir.volatile_cast, fir.create_box, unsliced fir.embox/fir.rebox) down to
+/// whatever produced the address underneath, e.g. fir.array_coor or a
+/// *sliced* embox/rebox (offset == nullopt, handled separately). Excludes a
+/// marshal-like fir.convert, which must go through its own dedicated path.
+static Value peelZeroOffsetViews(Value memref) {
+ while (Operation *defOp = memref.getDefiningOp()) {
+ auto view = dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
+ if (!view || isMarshalLike(defOp))
+ break;
+ auto result = cast<OpResult>(memref);
+ if (view.getViewOffset(result) != 0)
+ break;
+ memref = view.getViewSource(result);
+ }
+ return memref;
+}
+
using MemRefInfo = FailureOr<std::pair<Value, SmallVector<Value>>>;
static llvm::cl::opt<bool> enableFIRConvertOptimizations(
@@ -144,7 +163,8 @@ class FIRToMemRef : public fir::impl::FIRToMemRefBase<FIRToMemRef> {
PatternRewriter &,
FIRToMemRefTypeConverter &);
- void replaceFIRMemrefs(Value, Value, PatternRewriter &) const;
+ void replaceFIRMemrefs(Value, Value, ArrayRef<Value> indices,
+ PatternRewriter &) const;
FailureOr<Value> getFIRConvert(Operation *memOp, Operation *memref,
PatternRewriter &, FIRToMemRefTypeConverter &);
@@ -1453,6 +1473,8 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
PatternRewriter &rewriter,
FIRToMemRefTypeConverter &typeConverter,
Operation *memOp) {
+ firMemref = peelZeroOffsetViews(firMemref);
+
Operation *memrefOp = firMemref.getDefiningOp();
if (!memrefOp) {
if (auto blockArg = dyn_cast<BlockArgument>(firMemref)) {
@@ -1504,20 +1526,6 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
return std::pair{*converted, indices};
}
- if (auto declareOp = dyn_cast<fir::DeclareOp>(memrefOp)) {
- FailureOr<Value> converted =
- getFIRConvert(memOp, declareOp, rewriter, typeConverter);
- if (failed(converted)) {
- LLVM_DEBUG(llvm::dbgs()
- << "FIRToMemRef: unable to create convert for scalar "
- "memref:\n";
- firMemref.dump());
- return failure();
- }
- SmallVector<Value> indices;
- return std::pair{*converted, indices};
- }
-
if (auto coordinateOp = dyn_cast<fir::CoordinateOp>(memrefOp)) {
// Fast path: coordinate_of used as a plain array indexer on a static-extent
// scalar array (e.g. a struct component `A%v(i)`).
@@ -1558,39 +1566,6 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
return std::pair{*converted, indices};
}
- if (auto convertOp = dyn_cast<fir::ConvertOp>(memrefOp)) {
- Type fromTy = convertOp->getOperand(0).getType();
- Type toTy = firMemref.getType();
- if (isa<fir::ReferenceType>(fromTy) && isa<fir::ReferenceType>(toTy)) {
- FailureOr<Value> converted =
- getFIRConvert(memOp, convertOp, rewriter, typeConverter);
- if (failed(converted)) {
- LLVM_DEBUG(
- llvm::dbgs()
- << "FIRToMemRef: unable to create convert for conversion "
- "op:\n";
- firMemref.dump());
- return failure();
- }
- SmallVector<Value> indices;
- return std::pair{*converted, indices};
- }
- }
-
- if (auto boxAddrOp = dyn_cast<fir::BoxAddrOp>(memrefOp)) {
- FailureOr<Value> converted =
- getFIRConvert(memOp, boxAddrOp, rewriter, typeConverter);
- if (failed(converted)) {
- LLVM_DEBUG(llvm::dbgs()
- << "FIRToMemRef: unable to create convert for box_addr "
- "op:\n";
- firMemref.dump());
- return failure();
- }
- SmallVector<Value> indices;
- return std::pair{*converted, indices};
- }
-
if (memrefIsDeviceData(memrefOp)) {
FailureOr<Value> converted =
getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
@@ -1608,9 +1583,15 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
}
void FIRToMemRef::replaceFIRMemrefs(Value firMemref, Value converted,
+ ArrayRef<Value> indices,
PatternRewriter &rewriter) const {
+ // converted is only a base memref paired with indices, not a standalone
+ // address-equivalent for firMemref, so don't redirect other users to it.
+ if (!indices.empty())
+ return;
+
Operation *op = firMemref.getDefiningOp();
- if (op && (isa<fir::ArrayCoorOp>(op) || isMarshalLike(op)))
+ if (op && isMarshalLike(op))
return;
SmallPtrSet<Operation *, 4> worklist;
@@ -1721,7 +1702,7 @@ void FIRToMemRef::rewriteLoadOp(fir::LoadOp load, PatternRewriter &rewriter,
}
if (!isa<fir::LogicalType>(originalType))
- replaceFIRMemrefs(firMemref, converted, rewriter);
+ replaceFIRMemrefs(firMemref, converted, indices, rewriter);
}
void FIRToMemRef::rewriteStoreOp(fir::StoreOp store, PatternRewriter &rewriter,
@@ -1775,7 +1756,7 @@ void FIRToMemRef::rewriteStoreOp(fir::StoreOp store, PatternRewriter &rewriter,
llvm::dyn_cast<fir::ReferenceType>(firMemref.getType()))
isLogicalRef = llvm::isa<fir::LogicalType>(refTy.getEleTy());
if (!isLogicalRef)
- replaceFIRMemrefs(firMemref, converted, rewriter);
+ replaceFIRMemrefs(firMemref, converted, indices, rewriter);
}
// Lower operand and result type of FIR logical operation to get rid
>From 77c0151179dc1999872e3bc2c41a128f2f14c92c Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 10 Aug 2026 14:10:08 -0700
Subject: [PATCH 2/5] tweak
---
.../lib/Optimizer/Transforms/FIRToMemRef.cpp | 50 ++++++++++++++++++-
1 file changed, 48 insertions(+), 2 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 7392516a9f377..e31ee5decbda9 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -1473,8 +1473,6 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
PatternRewriter &rewriter,
FIRToMemRefTypeConverter &typeConverter,
Operation *memOp) {
- firMemref = peelZeroOffsetViews(firMemref);
-
Operation *memrefOp = firMemref.getDefiningOp();
if (!memrefOp) {
if (auto blockArg = dyn_cast<BlockArgument>(firMemref)) {
@@ -1575,6 +1573,54 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
return std::pair{*converted, indices};
}
+ // Some other zero-offset view (fir.declare, ref/box fir.convert,
+ // fir.box_addr, fir.volatile_cast, fir.create_box, unsliced
+ // fir.embox/fir.rebox): peek underneath for an fir.array_coor or indexing
+ // fir.coordinate_of that needs bounds-aware handling (e.g. fir.declare
+ // wrapping fir.array_coor); otherwise marshal the view itself so its
+ // naming is kept and getFIRConvert's dedup logic still applies.
+ if (isa<fir::FortranObjectViewOpInterface>(memrefOp)) {
+ if (Operation *peeledOp = peelZeroOffsetViews(firMemref).getDefiningOp()) {
+ if (auto arrayCoorOp = dyn_cast<fir::ArrayCoorOp>(peeledOp)) {
+ MemRefInfo memrefInfo =
+ convertArrayCoorOp(memOp, arrayCoorOp, rewriter, typeConverter);
+ if (succeeded(memrefInfo)) {
+ for (auto user : peeledOp->getUsers())
+ if (!isa<fir::LoadOp, fir::StoreOp>(user))
+ return memrefInfo;
+ eraseOps.insert(peeledOp);
+ }
+ return memrefInfo;
+ }
+
+ if (auto coordinateOp = dyn_cast<fir::CoordinateOp>(peeledOp);
+ coordinateOp &&
+ isArrayIndexingCoordinateOp(coordinateOp, typeConverter)) {
+ MemRefInfo memrefInfo = convertCoordinateArrayOp(
+ memOp, coordinateOp, rewriter, typeConverter);
+ if (succeeded(memrefInfo)) {
+ for (auto user : peeledOp->getUsers())
+ if (!isa<fir::LoadOp, fir::StoreOp>(user))
+ return memrefInfo;
+ eraseOps.insert(peeledOp);
+ return memrefInfo;
+ }
+ }
+ }
+
+ FailureOr<Value> converted =
+ getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
+ if (failed(converted)) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "FIRToMemRef: unable to create convert for view "
+ "memref:\n";
+ firMemref.dump());
+ return failure();
+ }
+ SmallVector<Value> indices;
+ return std::pair{*converted, indices};
+ }
+
LLVM_DEBUG(llvm::dbgs()
<< "FIRToMemRef: unable to create convert for memref value:\n";
firMemref.dump());
>From 0a33cf9aedf7ea159ccc67fb4961c09e5f6d52c9 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 10 Aug 2026 14:34:13 -0700
Subject: [PATCH 3/5] tweak
---
.../lib/Optimizer/Transforms/FIRToMemRef.cpp | 42 ++++++-------------
1 file changed, 13 insertions(+), 29 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index e31ee5decbda9..57c2359a7bce2 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -1573,39 +1573,23 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
return std::pair{*converted, indices};
}
- // Some other zero-offset view (fir.declare, ref/box fir.convert,
- // fir.box_addr, fir.volatile_cast, fir.create_box, unsliced
- // fir.embox/fir.rebox): peek underneath for an fir.array_coor or indexing
- // fir.coordinate_of that needs bounds-aware handling (e.g. fir.declare
+ // Some other zero-offset, ref-producing view (fir.declare, ref-to-ref
+ // fir.convert, fir.box_addr, fir.volatile_cast): peek underneath for an
+ // fir.array_coor that needs bounds-aware handling (e.g. fir.declare
// wrapping fir.array_coor); otherwise marshal the view itself so its
// naming is kept and getFIRConvert's dedup logic still applies.
if (isa<fir::FortranObjectViewOpInterface>(memrefOp)) {
- if (Operation *peeledOp = peelZeroOffsetViews(firMemref).getDefiningOp()) {
- if (auto arrayCoorOp = dyn_cast<fir::ArrayCoorOp>(peeledOp)) {
- MemRefInfo memrefInfo =
- convertArrayCoorOp(memOp, arrayCoorOp, rewriter, typeConverter);
- if (succeeded(memrefInfo)) {
- for (auto user : peeledOp->getUsers())
- if (!isa<fir::LoadOp, fir::StoreOp>(user))
- return memrefInfo;
- eraseOps.insert(peeledOp);
- }
- return memrefInfo;
- }
-
- if (auto coordinateOp = dyn_cast<fir::CoordinateOp>(peeledOp);
- coordinateOp &&
- isArrayIndexingCoordinateOp(coordinateOp, typeConverter)) {
- MemRefInfo memrefInfo = convertCoordinateArrayOp(
- memOp, coordinateOp, rewriter, typeConverter);
- if (succeeded(memrefInfo)) {
- for (auto user : peeledOp->getUsers())
- if (!isa<fir::LoadOp, fir::StoreOp>(user))
- return memrefInfo;
- eraseOps.insert(peeledOp);
- return memrefInfo;
- }
+ if (auto arrayCoorOp = dyn_cast_or_null<fir::ArrayCoorOp>(
+ peelZeroOffsetViews(firMemref).getDefiningOp())) {
+ MemRefInfo memrefInfo =
+ convertArrayCoorOp(memOp, arrayCoorOp, rewriter, typeConverter);
+ if (succeeded(memrefInfo)) {
+ for (auto user : arrayCoorOp->getUsers())
+ if (!isa<fir::LoadOp, fir::StoreOp>(user))
+ return memrefInfo;
+ eraseOps.insert(arrayCoorOp);
}
+ return memrefInfo;
}
FailureOr<Value> converted =
>From 26ab572422107f710220c3ed442914536b6e10b9 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 10 Aug 2026 15:03:09 -0700
Subject: [PATCH 4/5] add tests
---
.../lib/Optimizer/Transforms/FIRToMemRef.cpp | 15 +-
.../FIRToMemRef/view-op-wraps-array-coor.mlir | 174 ++++++++++++++++++
2 files changed, 183 insertions(+), 6 deletions(-)
create mode 100644 flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 57c2359a7bce2..12443742da2b9 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -102,15 +102,18 @@ static bool isMarshalLike(Operation *op) {
}
/// Peel `FortranObjectViewOpInterface` ops with a statically-known zero
-/// offset (fir.declare, ref/box fir.convert, fir.box_addr,
-/// fir.volatile_cast, fir.create_box, unsliced fir.embox/fir.rebox) down to
-/// whatever produced the address underneath, e.g. fir.array_coor or a
-/// *sliced* embox/rebox (offset == nullopt, handled separately). Excludes a
-/// marshal-like fir.convert, which must go through its own dedicated path.
+/// offset (fir.declare, ref/box fir.convert, fir.box_addr, fir.create_box,
+/// unsliced fir.embox/fir.rebox) down to whatever produced the address
+/// underneath, e.g. fir.array_coor or a *sliced* embox/rebox (offset ==
+/// nullopt, handled separately). Excludes a marshal-like fir.convert, which
+/// must go through its own dedicated path. Also stops at fir.volatile_cast:
+/// since it is the only op that may change volatility, whatever it wraps may
+/// have different volatility than its result, so peeling past it could
+/// marshal a still-volatile value without going through the explicit cast.
static Value peelZeroOffsetViews(Value memref) {
while (Operation *defOp = memref.getDefiningOp()) {
auto view = dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
- if (!view || isMarshalLike(defOp))
+ if (!view || isMarshalLike(defOp) || isa<fir::VolatileCastOp>(defOp))
break;
auto result = cast<OpResult>(memref);
if (view.getViewOffset(result) != 0)
diff --git a/flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir b/flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir
new file mode 100644
index 0000000000000..fb0afea5c72e4
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir
@@ -0,0 +1,174 @@
+// Test that each zero-offset FortranObjectViewOpInterface op wrapping (directly
+// or through a chain of such views) a fir.array_coor is peeled through, so the
+// array_coor is still lowered to a bounds-aware memref access instead of being
+// treated as an opaque scalar reference.
+// RUN: fir-opt %s --fir-to-memref --allow-unregistered-dialect | FileCheck %s
+// The pass must not introduce any fir.convert that drops volatility.
+// RUN: fir-opt %s --strict-fir-volatile-verifier --fir-to-memref -o /dev/null
+
+// CHECK-LABEL: func.func @declare_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @declare_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %decl = fir.declare %elem {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %load = fir.load %decl : !fir.ref<i32>
+ fir.store %v to %decl : !fir.ref<i32>
+ return
+}
+
+// CHECK-LABEL: func.func @convert_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @convert_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %cvt = fir.convert %elem : (!fir.ref<i32>) -> !fir.ref<i32>
+ %load = fir.load %cvt : !fir.ref<i32>
+ fir.store %v to %cvt : !fir.ref<i32>
+ return
+}
+
+// A fir.box_addr can only reach an array_coor through an intervening
+// fir.embox/rebox/create_box (its own operand is always a box, and
+// array_coor never produces one), so this also exercises peeling through
+// fir.embox one level down.
+// CHECK-LABEL: func.func @box_addr_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @box_addr_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %box = fir.embox %elem : (!fir.ref<i32>) -> !fir.box<i32>
+ %addr = fir.box_addr %box : (!fir.box<i32>) -> !fir.ref<i32>
+ %load = fir.load %addr : !fir.ref<i32>
+ fir.store %v to %addr : !fir.ref<i32>
+ return
+}
+
+// Longer chain (fir.declare -> fir.box_addr -> fir.embox -> fir.array_coor)
+// to check multi-hop peeling through fir.embox specifically.
+// CHECK-LABEL: func.func @embox_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @embox_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %box = fir.embox %elem : (!fir.ref<i32>) -> !fir.box<i32>
+ %addr = fir.box_addr %box : (!fir.box<i32>) -> !fir.ref<i32>
+ %decl = fir.declare %addr {uniq_name = "x"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %load = fir.load %decl : !fir.ref<i32>
+ fir.store %v to %decl : !fir.ref<i32>
+ return
+}
+
+// A fir.volatile_cast can go from volatile to non-volatile, e.g. after taking
+// the address of an element of a volatile array. Unlike the other view ops,
+// peeling must *not* look past it: the fir.array_coor underneath is still
+// volatile, so converting its base directly to memref would bypass the cast
+// and silently drop volatility. Instead only the (already non-volatile)
+// fir.volatile_cast result itself is marshaled, as a rank-0 memref, leaving
+// fir.array_coor untouched in FIR.
+// CHECK-LABEL: func.func @volatile_cast_wraps_array_coor
+// CHECK: %[[COOR:.+]] = fir.array_coor %arg0{{.*}} : {{.*}} -> !fir.ref<i32, volatile>
+// CHECK: %[[VC:.+]] = fir.volatile_cast %[[COOR]] : (!fir.ref<i32, volatile>) -> !fir.ref<i32>
+// CHECK: %[[M0:.+]] = fir.convert %[[VC]] : (!fir.ref<i32>) -> memref<i32>
+// CHECK: memref.load %[[M0]][]
+// CHECK: %[[M1:.+]] = fir.convert %[[VC]] : (!fir.ref<i32>) -> memref<i32>
+// CHECK: memref.store %arg1, %[[M1]][]
+func.func @volatile_cast_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>, volatile>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>, volatile>, !fir.shape<1>, index) -> !fir.ref<i32, volatile>
+ %vc = fir.volatile_cast %elem : (!fir.ref<i32, volatile>) -> !fir.ref<i32>
+ %load = fir.load %vc : !fir.ref<i32>
+ fir.store %v to %vc : !fir.ref<i32>
+ return
+}
+
+// fir.create_box wraps a non-contiguous byte-strided view (e.g. a
+// derived-type component array); box_addr unwraps it back to a ref.
+// CHECK-LABEL: func.func @create_box_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @create_box_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c4 = arith.constant 4 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %arr1 = fir.convert %elem : (!fir.ref<i32>) -> !fir.ref<!fir.array<1xi32>>
+ %box = fir.create_box %arr1 lbs(%c1) extents(%c1) strides(%c4) : (!fir.ref<!fir.array<1xi32>>, index, index, index) -> !fir.box<!fir.array<1xi32>>
+ %addrarr = fir.box_addr %box : (!fir.box<!fir.array<1xi32>>) -> !fir.ref<!fir.array<1xi32>>
+ %addr = fir.convert %addrarr : (!fir.ref<!fir.array<1xi32>>) -> !fir.ref<i32>
+ %load = fir.load %addr : !fir.ref<i32>
+ fir.store %v to %addr : !fir.ref<i32>
+ return
+}
+
+// fir.rebox re-views an existing box (e.g. to change its shape); the box it
+// re-views can itself be an fir.embox wrapping the array_coor result.
+// CHECK-LABEL: func.func @rebox_wraps_array_coor
+// CHECK: %[[M0:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: %[[M1:.+]] = fir.convert %arg0 : (!fir.ref<!fir.array<10xi32>>) -> memref<10xi32>
+// CHECK: memref.load %[[M0]]
+// CHECK: memref.store %arg1, %[[M1]]
+// CHECK-NOT: fir.array_coor
+func.func @rebox_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %box = fir.embox %elem : (!fir.ref<i32>) -> !fir.box<i32>
+ %rebox = fir.rebox %box : (!fir.box<i32>) -> !fir.box<i32>
+ %addr = fir.box_addr %rebox : (!fir.box<i32>) -> !fir.ref<i32>
+ %load = fir.load %addr : !fir.ref<i32>
+ fir.store %v to %addr : !fir.ref<i32>
+ return
+}
+
+// Negative case: fir.volatile_cast going the other way, non-volatile to
+// volatile (the common direction, e.g. declaring a variable volatile). Here
+// the load/store's own type ends up volatile, so the pass must bail out
+// before ever reaching getMemRefInfo/peeling — nothing should be converted.
+// CHECK-LABEL: func.func @volatile_cast_to_volatile_wraps_array_coor
+// CHECK: %[[COOR:.+]] = fir.array_coor %arg0{{.*}} : {{.*}} -> !fir.ref<i32>
+// CHECK: %[[VC:.+]] = fir.volatile_cast %[[COOR]] : (!fir.ref<i32>) -> !fir.ref<i32, volatile>
+// CHECK: fir.load %[[VC]] : !fir.ref<i32, volatile>
+// CHECK: fir.store %arg1 to %[[VC]] : !fir.ref<i32, volatile>
+// CHECK-NOT: memref
+func.func @volatile_cast_to_volatile_wraps_array_coor(%arg0: !fir.ref<!fir.array<10xi32>>, %v: i32) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %elem = fir.array_coor %arg0(%shape) %c1 : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>, index) -> !fir.ref<i32>
+ %vc = fir.volatile_cast %elem : (!fir.ref<i32>) -> !fir.ref<i32, volatile>
+ %load = fir.load %vc : !fir.ref<i32, volatile>
+ fir.store %v to %vc : !fir.ref<i32, volatile>
+ return
+}
>From 759b50f1b5ea569f59f83eb2291400ef8738ff0c Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 10 Aug 2026 15:05:56 -0700
Subject: [PATCH 5/5] tweak
---
.../lib/Optimizer/Transforms/FIRToMemRef.cpp | 21 +++++++------------
1 file changed, 8 insertions(+), 13 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 12443742da2b9..22e83d8bfd8ec 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -101,22 +101,17 @@ static bool isMarshalLike(Operation *op) {
return resIsMemRef || argIsMemRef;
}
-/// Peel `FortranObjectViewOpInterface` ops with a statically-known zero
-/// offset (fir.declare, ref/box fir.convert, fir.box_addr, fir.create_box,
-/// unsliced fir.embox/fir.rebox) down to whatever produced the address
-/// underneath, e.g. fir.array_coor or a *sliced* embox/rebox (offset ==
-/// nullopt, handled separately). Excludes a marshal-like fir.convert, which
-/// must go through its own dedicated path. Also stops at fir.volatile_cast:
-/// since it is the only op that may change volatility, whatever it wraps may
-/// have different volatility than its result, so peeling past it could
-/// marshal a still-volatile value without going through the explicit cast.
+/// Peel zero-offset views (e.g. fir.declare) down to the underlying address
+/// producer, e.g. fir.array_coor.
static Value peelZeroOffsetViews(Value memref) {
while (Operation *defOp = memref.getDefiningOp()) {
- auto view = dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
- if (!view || isMarshalLike(defOp) || isa<fir::VolatileCastOp>(defOp))
- break;
auto result = cast<OpResult>(memref);
- if (view.getViewOffset(result) != 0)
+ auto view = dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
+ // Marshal-like fir.convert has its own dedicated path.
+ // fir.volatile_cast may change volatility, so what it wraps could be
+ // more volatile than its result; don't peel past it.
+ if (!view || isMarshalLike(defOp) || isa<fir::VolatileCastOp>(defOp) ||
+ view.getViewOffset(result) != 0)
break;
memref = view.getViewSource(result);
}
More information about the flang-commits
mailing list