[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