[flang-commits] [flang] 47fee85 - [flang][FIRToMemRef] Look through zero-offset views to find fir.array_coor (#215402)
via flang-commits
flang-commits at lists.llvm.org
Fri Sep 4 08:34:09 PDT 2026
Author: Susan Tan (ス-ザン タン)
Date: 2026-09-04T11:34:05-04:00
New Revision: 47fee85e3b5e4c749829a2ebac498dc89e6a5f2c
URL: https://github.com/llvm/llvm-project/commit/47fee85e3b5e4c749829a2ebac498dc89e6a5f2c
DIFF: https://github.com/llvm/llvm-project/commit/47fee85e3b5e4c749829a2ebac498dc89e6a5f2c.diff
LOG: [flang][FIRToMemRef] Look through zero-offset views to find fir.array_coor (#215402)
`getMemRefInfo` dispatched only on a FIR memref's immediate defining op,
so a fir.declare (or other zero-offset view) wrapping
`fir.array_coor`/indexing `fir.coordinate_of` was treated as an opaque
scalar reference, dropping bounds information and causing incorrect
runtime behavior (e.g. missing array dimensions) after inlining under an
OpenACC kernels loop.
This peeks through zero-offset FortranObjectViewOpInterface views
(fir.declare, ref/box fir.convert, fir.box_addr, fir.volatile_cast,
fir.create_box, unsliced fir.embox/fir.rebox) for a genuine
fir.array_coor/indexing fir.coordinate_of underneath and dispatches
bounds-aware lowering on it.
Added:
flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir
Modified:
flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
Removed:
################################################################################
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 3975cddeed75b..619614280c8a4 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -101,6 +101,22 @@ static bool isMarshalLike(Operation *op) {
return resIsMemRef || argIsMemRef;
}
+/// 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 result = cast<OpResult>(memref);
+ auto view = dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
+ // Marshal-like fir.convert has its own dedicated path.
+ // fir.volatile_cast may change volatility, don't peel past it.
+ if (!view || isMarshalLike(defOp) || isa<fir::VolatileCastOp>(defOp) ||
+ 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,11 +160,18 @@ 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 &);
+ /// Marshal \p memrefOp itself (no bounds-aware indexing) via getFIRConvert,
+ /// logging \p debugContext on failure.
+ MemRefInfo marshalView(Operation *memOp, Operation *memrefOp, Value firMemref,
+ PatternRewriter &, FIRToMemRefTypeConverter &,
+ llvm::StringRef debugContext);
+
FailureOr<SmallVector<Value>> getMemrefIndices(fir::ArrayCoorOp, Operation *,
PatternRewriter &,
Value) const;
@@ -1303,6 +1326,22 @@ FIRToMemRef::getFIRConvert(Operation *memOp, Operation *op,
return convert->getResult(0);
}
+MemRefInfo FIRToMemRef::marshalView(Operation *memOp, Operation *memrefOp,
+ Value firMemref, PatternRewriter &rewriter,
+ FIRToMemRefTypeConverter &typeConverter,
+ llvm::StringRef debugContext) {
+ FailureOr<Value> converted =
+ getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
+ if (failed(converted)) {
+ LLVM_DEBUG(llvm::dbgs() << "FIRToMemRef: unable to create convert for "
+ << debugContext << ":\n";
+ firMemref.dump());
+ return failure();
+ }
+ SmallVector<Value> indices;
+ return std::pair{*converted, indices};
+}
+
/// Peephole-simplify an index-shaped SSA value before it gets fed into
/// memref index arithmetic. Returns a (possibly newly-created) `Value`;
/// the input is left untouched. Callers must not assume the result is
@@ -1481,6 +1520,16 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
"FIRToMemRef: expected defining op or block argument for FIR memref");
}
+ // If a zero-offset view (fir.declare, ref-to-ref fir.convert, ...) wraps an
+ // fir.array_coor, dispatch on the array_coor so bounds-aware lowering runs.
+ // Otherwise leave firMemref as the view so marshal keeps its naming.
+ Value peeled = peelZeroOffsetViews(firMemref);
+ if (auto arrayCoorOp =
+ dyn_cast_or_null<fir::ArrayCoorOp>(peeled.getDefiningOp())) {
+ firMemref = peeled;
+ memrefOp = arrayCoorOp;
+ }
+
if (auto arrayCoorOp = dyn_cast<fir::ArrayCoorOp>(memrefOp)) {
MemRefInfo memrefInfo =
convertArrayCoorOp(memOp, arrayCoorOp, rewriter, typeConverter);
@@ -1501,33 +1550,9 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
rewriter.setInsertionPoint(memOp);
- if (isMarshalLike(memrefOp)) {
- FailureOr<Value> converted =
- getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
- if (failed(converted)) {
- LLVM_DEBUG(llvm::dbgs()
- << "FIRToMemRef: expected FIR memref in convert, bailing "
- "out:\n";
- firMemref.dump());
- return failure();
- }
- SmallVector<Value> indices;
- 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 (isMarshalLike(memrefOp))
+ return marshalView(memOp, memrefOp, firMemref, rewriter, typeConverter,
+ "marshal-like convert");
if (auto coordinateOp = dyn_cast<fir::CoordinateOp>(memrefOp)) {
// Fast path: coordinate_of used as a plain array indexer on a static-extent
@@ -1555,61 +1580,20 @@ MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
// Fallback: struct field access or dynamic array — produce a rank-0 scalar
// memref from the leaf reference.
- FailureOr<Value> converted =
- getFIRConvert(memOp, coordinateOp, rewriter, typeConverter);
- if (failed(converted)) {
- LLVM_DEBUG(
- llvm::dbgs()
- << "FIRToMemRef: unable to create convert for derived-type "
- "memref:\n";
- firMemref.dump());
- return failure();
- }
- SmallVector<Value> indices;
- return std::pair{*converted, indices};
+ return marshalView(memOp, coordinateOp, firMemref, rewriter, typeConverter,
+ "derived-type memref");
}
- 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 (memrefIsDeviceData(memrefOp))
+ return marshalView(memOp, memrefOp, firMemref, rewriter, typeConverter,
+ "device-data memref");
- 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);
- if (failed(converted))
- return failure();
- SmallVector<Value> indices;
- return std::pair{*converted, indices};
- }
+ // Remaining zero-offset view (fir.declare, ref-to-ref fir.convert,
+ // fir.box_addr, fir.volatile_cast, ...): no array_coor underneath, so
+ // marshal the view itself to keep naming and getFIRConvert dedup.
+ if (isa<fir::FortranObjectViewOpInterface>(memrefOp))
+ return marshalView(memOp, memrefOp, firMemref, rewriter, typeConverter,
+ "view memref");
LLVM_DEBUG(llvm::dbgs()
<< "FIRToMemRef: unable to create convert for memref value:\n";
@@ -1619,9 +1603,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;
@@ -1732,7 +1722,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,
@@ -1786,7 +1776,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
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..fbc654cf361ed
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/view-op-wraps-array-coor.mlir
@@ -0,0 +1,108 @@
+// Test that zero-offset FortranObjectViewOpInterface ops wrapping a
+// fir.array_coor are peeled through, so the array_coor is still lowered to a
+// bounds-aware memref access instead of being treated as an opaque scalar
+// reference. fir.volatile_cast is a peel barrier.
+// 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 (its own operand is always a box, and array_coor never produces
+// one), so this also exercises peeling through fir.embox.
+// 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
+}
+
+// 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
+}
+
+// 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
+}
More information about the flang-commits
mailing list