[flang-commits] [flang] [FIRToMemRef] Fix wrong-code for array_coor over sliced fir.embox (PR #207749)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Mon Jul 6 08:04:15 PDT 2026


https://github.com/ergawy created https://github.com/llvm/llvm-project/pull/207749

FIRToMemRef::convertArrayCoorOp routes through getMemrefIndices, which only folds the first `rank` triples of sliceInfo.sliceVec into the memref indices (i.e. the array_coor's own slice); the embox's slice triples -- which sit at [rank*3 .. 2*rank*3-1] when both are present -- were dropped. Three consequences, three fixes here:

  1. Non-collapsed embox slice lbs contribute (lb - 1) per Fortran dim to each memref index. Fold them in in memref order (reversed Fortran order) so the reinterpret_cast view lands at the right column.

  2. The shapeVec-else stride path used shapeVec[0..rank-1] to build the outer strides. With both slices present, that's the box's (slice's) extents. Use shapeVec[rank..2*rank-1] instead -- the parent's extents -- so the outer stride is the parent's leading dim rather than the slice's own size.

  3. Rank-reducing embox slices (undef ub/step triples) have no memref index position for their (lb - 1) shift. Fold the collapsed dim's (lb - 1) * parent_stride into the reinterpret_cast's flat offset and override the corresponding memref index entry to 0.

>From eab33518be4b670499808c15e4773f143c240f41 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Sun, 5 Jul 2026 06:19:56 -0700
Subject: [PATCH] [FIRToMemRef] Fix wrong-code for array_coor over sliced
 fir.embox

FIRToMemRef::convertArrayCoorOp routes through getMemrefIndices, which
only folds the first `rank` triples of sliceInfo.sliceVec into the memref
indices (i.e. the array_coor's own slice); the embox's slice triples --
which sit at [rank*3 .. 2*rank*3-1] when both are present -- were dropped.
Three consequences, three fixes here:

  1. Non-collapsed embox slice lbs contribute (lb - 1) per Fortran dim to
     each memref index. Fold them in in memref order (reversed Fortran
     order) so the reinterpret_cast view lands at the right column.

  2. The shapeVec-else stride path used shapeVec[0..rank-1] to build the
     outer strides. With both slices present, that's the box's (slice's)
     extents. Use shapeVec[rank..2*rank-1] instead -- the parent's extents
     -- so the outer stride is the parent's leading dim rather than the
     slice's own size.

  3. Rank-reducing embox slices (undef ub/step triples) have no memref
     index position for their (lb - 1) shift. Fold the collapsed dim's
     (lb - 1) * parent_stride into the reinterpret_cast's flat offset and
     override the corresponding memref index entry to 0.

Co-Authored-By: Claude Opus 4.7 <noreply at anthropic.com>
---
 .../lib/Optimizer/Transforms/FIRToMemRef.cpp  | 151 +++++++++++++++++-
 .../FIRToMemRef/emboxed-slice-array-coor.mlir |  65 ++++++++
 .../emboxed-slice-rank-reduction.mlir         |  82 ++++++++++
 .../FIRToMemRef/emboxed-slice-stride.mlir     |  67 ++++++++
 4 files changed, 363 insertions(+), 2 deletions(-)
 create mode 100644 flang/test/Transforms/FIRToMemRef/emboxed-slice-array-coor.mlir
 create mode 100644 flang/test/Transforms/FIRToMemRef/emboxed-slice-rank-reduction.mlir
 create mode 100644 flang/test/Transforms/FIRToMemRef/emboxed-slice-stride.mlir

diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 1dcc056387c48..ff844d7b29978 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -169,6 +169,10 @@ class FIRToMemRef : public fir::impl::FIRToMemRefBase<FIRToMemRef> {
   template <typename OpTy>
   void collectSliceInfoFrom(OpTy op, SliceInfo &info) const;
 
+  void foldSliceLbIntoIndices(SmallVectorImpl<Value> &indices,
+                              fir::EmboxOp embox, PatternRewriter &rewriter,
+                              Location loc) const;
+
   void populateShapeAndShift(SmallVectorImpl<Value> &shapeVec,
                              SmallVectorImpl<Value> &shiftVec,
                              fir::ShapeShiftOp shift) const;
@@ -456,6 +460,101 @@ static Value castTypeToIndexType(Value originalValue,
                                     originalValue);
 }
 
+/// Fold the embox's slice per-dim (lb - 1) shift into `indices` after
+/// `getMemrefIndices` has produced them. Handles both range and rank-reducing
+/// (scalar-subscript) slice triples uniformly.
+///
+/// Necessary because `getMemrefIndices` only consumes the first `rank*3`
+/// entries of `sliceInfo.sliceVec` (the array_coor's own slice); the embox's
+/// triples sit at `sliceVec[rank*3 .. 2*rank*3-1]` and are otherwise dropped,
+/// leaving the memref access short by `(lb - 1)` per dim -- and, for
+/// collapsed dims, additionally polluted with the array_coor's own scalar
+/// subscript (typically a loop variable) that would drag the load off the
+/// target dim across iterations.
+///
+/// Behaviour, per Fortran dim `d` of the embox slice (memref position
+/// `m = parentRank - 1 - d`):
+///   - Range triple `(lb, ub, step)`:
+///       indices[m] += (lb - 1)
+///     Adds the (lb - 1) shift on top of whatever `getMemrefIndices` produced
+///     for that dim.
+///   - Scalar-subscript triple `(lb, undef, undef)`:
+///       indices[m]  = (lb - 1)
+///     Overwrites the memref position outright. The dim is collapsed in the
+///     box's rank, but the reinterpret_cast still keeps a memref index slot
+///     for it (with the correct parent stride). Placing `(lb - 1)` there is
+///     mathematically equivalent to shifting the flat offset by
+///     `(lb - 1) * parentStride[d]`, without needing any parent-shape
+///     lookups from the caller.
+///
+/// Example A -- non-rank-reducing (2-D):
+///
+///   `fir.array_coor %box(...)[%innerSlice] %i, %j` with
+///     - array_coor slice: identity in both dims
+///     - embox slice     : `fir.slice %c2,%c2,%c1, %c1,%c1,%c1`
+///                         (Fortran dim 0 lb = 2, dim 1 lb = 1)
+///
+///   Incoming `indices` (memref order): [ %j', %i' ]
+///   Fold:
+///     - dim 0 (range): delta = 1 -> indices[1] += 1
+///     - dim 1 (range): delta = 0 -> indices[0] += 0
+///   Result:  [ %j', %i' + 1 ]
+///
+/// Example B -- rank-reducing (3-D parent, dim 1 collapsed):
+///
+///   %parent : !fir.ref<!fir.array<3x2x2xi32>>
+///   %shape  = fir.shape %c3, %c2, %c2
+///   %eslc   = fir.slice %c1, %c3, %c1,  %c2, %undef, %undef,  %c1, %c2, %c1
+///   %box    = fir.embox %parent(%shape) [%eslc]
+///                                          : !fir.box<!fir.array<3x2xi32>>
+///   %bshape = fir.shape %c3, %c2                     ; box's rank-2 shape
+///   %addr   = fir.array_coor %box(%bshape) %ii, %jj
+///        : (!fir.box<!fir.array<3x2xi32>>, !fir.shape<2>, index, index)
+///        -> !fir.ref<i32>
+///
+///   parentRank = 3. `getRankFromEmbox` returns the parent's rank, so
+///   `getMemrefIndices` builds a rank-3 index vector (memref order):
+///     indices = [ %jj', <ac scalar sub for collapsed dim>, %ii' ]
+///                ^ dim 2   ^ dim 1 (collapsed)              ^ dim 0
+///
+///   Fold:
+///     - dim 0 (range, lb=1): indices[2] += 0
+///     - dim 1 (scalar, lb=2): indices[1]  = 1        (overwrite)
+///     - dim 2 (range, lb=1): indices[0] += 0
+///
+///   Final: `memref.load %reinterpret_cast[%jj', 1, %ii'] : memref<?x?x?xi32,
+///   strided<[?, ?, ?], offset: ?>>` -- stride 3 on the middle position
+///   lands on the correct plane, `offset` stays at 0.
+void FIRToMemRef::foldSliceLbIntoIndices(SmallVectorImpl<Value> &indices,
+                                         fir::EmboxOp embox,
+                                         PatternRewriter &rewriter,
+                                         Location loc) const {
+  auto sliceOp = getSliceOp(embox.getSlice());
+  if (!sliceOp)
+    return;
+  auto triples = sliceOp.getTriples();
+  unsigned parentRank = triples.size() / 3;
+  if (parentRank == 0)
+    return;
+
+  auto isUndef = [](Value v) {
+    return v && v.getDefiningOp<fir::UndefOp>() != nullptr;
+  };
+  Value cOne = arith::ConstantIndexOp::create(rewriter, loc, 1);
+  for (unsigned d = 0; d < parentRank; ++d) {
+    unsigned m = parentRank - 1 - d;
+    if (m >= indices.size())
+      continue;
+    Value lb = castTypeToIndexType(triples[d * 3], rewriter);
+    Value delta = arith::SubIOp::create(rewriter, loc, lb, cOne);
+    bool isScalar =
+        isUndef(triples[d * 3 + 1]) || isUndef(triples[d * 3 + 2]);
+    indices[m] = isScalar
+                     ? delta
+                     : arith::AddIOp::create(rewriter, loc, indices[m], delta);
+  }
+}
+
 static bool shouldUseBoundaryBitcast(mlir::Type fromTy, mlir::Type toTy) {
   auto isBitcastCompatibleScalarType = [](mlir::Type ty) {
     return mlir::isa<mlir::IntegerType, mlir::FloatType, fir::LogicalType>(
@@ -814,13 +913,52 @@ FIRToMemRef::convertArrayCoorOp(Operation *memOp, fir::ArrayCoorOp arrayCoorOp,
   } else {
     Value oneIdx =
         arith::ConstantIndexOp::create(rewriter, arrayCoorOp->getLoc(), 1);
+    // shapeVec is populated by collectSliceInfoFrom in the order:
+    //   [<arrayCoor's shape>, <embox/rebox's shape>]
+    // When both contribute (firMemrefIsEmbox && arrayCoorOp has a slice), the
+    // first `rank` entries are the *box's* shape (= slice extents) while the
+    // next `rank` are the underlying *parent's* extents. Strides for the
+    // reinterpret_cast must come from the parent's contiguous element strides,
+    // not the slice extents (using the latter yields an outer stride that
+    // walks by the slice's size instead of the parent's leading dim).
+    //
+    // Example -- 4x2 parent, slice a(1:2, :) keeping 2 elements per dim:
+    //   %parent : !fir.ref<!fir.array<4x2xi32>>
+    //   %shape  = fir.shape %c4, %c2
+    //   %eslc   = fir.slice %c1, %c2, %c1, %c1, %c2, %c1    ; a(1:2, :)
+    //   %box    = fir.embox %parent(%shape) [%eslc]
+    //                                             : !fir.box<!fir.array<2x2xi32>>
+    //   ...  %addr = fir.array_coor %box(...) [%innerSlc] %i, %j
+    //
+    // After collectSliceInfoFrom is called on both the array_coor and the
+    // embox, shapeVec becomes (four entries, Fortran order):
+    //   [ box_dim0_extent(=2), box_dim1_extent(=2),         ; array_coor shape
+    //     parent_dim0_extent(=4), parent_dim1_extent(=2) ]  ; embox shape
+    //
+    // For rank = 2 the loop below emits (memref order, outer -> inner):
+    //   size[outer]   = shapeVec[1]                       = 2  (slice's dim1)
+    //   stride[outer] = shapeVec[parentShapeBase + 0]     = 4  (parent's dim0)
+    //   size[inner]   = shapeVec[0]                       = 2  (slice's dim0)
+    //   stride[inner] = 1
+    //
+    // Without the `parentShapeBase` shift, `stride[outer]` would be
+    // `shapeVec[0] = 2` -- the slice's own extent -- and successive outer
+    // steps would walk by 2 elements instead of the parent's 4-element row,
+    // clobbering the wrong columns.
+    const bool hasParentShape =
+        firMemrefIsEmbox && arrayCoorOp.getSlice() &&
+        shapeVec.size() >= 2 * rank;
+    const unsigned parentShapeBase = hasParentShape ? rank : 0;
     for (unsigned i = rank - 1; i > 0; --i) {
+      // Sizes are always the box/slice's visible extents (shapeVec[0..rank-1]).
       Value size = shapeVec[i];
       sizes.push_back(castTypeToIndexType(size, rewriter));
 
-      Value stride = shapeVec[0];
+      // Strides use the parent's extents (via `parentShapeBase`).
+      Value stride = shapeVec[parentShapeBase + 0];
       for (unsigned j = 1; j <= i - 1; ++j)
-        stride = arith::MulIOp::create(rewriter, loc, shapeVec[j], stride);
+        stride = arith::MulIOp::create(rewriter, loc,
+                                       shapeVec[parentShapeBase + j], stride);
       if (complexPartIdx)
         stride = arith::MulIOp::create(
             rewriter, loc, stride,
@@ -860,6 +998,15 @@ FIRToMemRef::convertArrayCoorOp(Operation *memOp, fir::ArrayCoorOp arrayCoorOp,
 
   Value offset = arith::ConstantIndexOp::create(rewriter, loc, 0);
 
+  // Fold the embox's slice contribution into the memref indices.
+  // getMemrefIndices only consumed the array_coor's own slice; the embox's
+  // triples are still unconsumed at this point. Handles both range and
+  // rank-reducing (scalar-subscript) dims uniformly.
+  if (firMemrefIsEmbox && !complexPartIdx && arrayCoorOp.getSlice()) {
+    auto embox = firMemref.getDefiningOp<fir::EmboxOp>();
+    foldSliceLbIntoIndices(indices, embox, rewriter, loc);
+  }
+
   auto reinterpret = memref::ReinterpretCastOp::create(
       rewriter, loc, memRefTy, convertedVal, offset, sizes, strides);
 
diff --git a/flang/test/Transforms/FIRToMemRef/emboxed-slice-array-coor.mlir b/flang/test/Transforms/FIRToMemRef/emboxed-slice-array-coor.mlir
new file mode 100644
index 0000000000000..236b8f6a97dea
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/emboxed-slice-array-coor.mlir
@@ -0,0 +1,65 @@
+// RUN: fir-opt %s --fir-to-memref | FileCheck %s
+
+// Verify that when both the embox and the array_coor carry a slice, the
+// embox's per-dim (lb - 1) shift is folded into the memref indices.
+
+func.func @emboxed_slice_array_coor(%arg0: !fir.ref<!fir.array<4x2xi32>>) -> i32 {
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %c4 = arith.constant 4 : index
+  %ci = arith.constant 1 : i64
+  %shape = fir.shape %c4, %c2 : (index, index) -> !fir.shape<2>
+  // Embox slice: a(2:2, 1:1) -- Fortran dim-0 lb = 2, dim-1 lb = 1.
+  %eslice = fir.slice %c2, %c2, %c1, %c1, %c1, %c1
+      : (index, index, index, index, index, index) -> !fir.slice<2>
+  %box = fir.embox %arg0(%shape) [%eslice]
+      : (!fir.ref<!fir.array<4x2xi32>>, !fir.shape<2>, !fir.slice<2>)
+      -> !fir.box<!fir.array<1x1xi32>>
+  // Inner array_coor slice (identity 1:1:1 in both dims).
+  %ashape = fir.shape %c1, %c1 : (index, index) -> !fir.shape<2>
+  %islice = fir.slice %c1, %c1, %c1, %c1, %c1, %c1
+      : (index, index, index, index, index, index) -> !fir.slice<2>
+  %addr = fir.array_coor %box(%ashape) [%islice] %ci, %c1
+      : (!fir.box<!fir.array<1x1xi32>>, !fir.shape<2>, !fir.slice<2>, i64, index)
+      -> !fir.ref<i32>
+  %v = fir.load %addr : !fir.ref<i32>
+  return %v : i32
+}
+
+// CHECK-LABEL: func.func @emboxed_slice_array_coor(
+// CHECK-SAME:      %[[ARG0:.+]]: !fir.ref<!fir.array<4x2xi32>>) -> i32
+// CHECK-DAG:   %[[C1:.+]] = arith.constant 1 : index
+// CHECK-DAG:   %[[C2:.+]] = arith.constant 2 : index
+// CHECK-DAG:   %[[C4:.+]] = arith.constant 4 : index
+
+// Parent fir.shape and embox fir.slice survive to the emitted module.
+// CHECK:       %[[SHAPE:.+]] = fir.shape %[[C4]], %[[C2]] : (index, index) -> !fir.shape<2>
+// CHECK:       %[[ESLICE:.+]] = fir.slice %[[C2]], %[[C2]], %[[C1]], %[[C1]], %[[C1]], %[[C1]] : ({{.+}}) -> !fir.slice<2>
+// CHECK:       fir.embox %[[ARG0]](%[[SHAPE]]) [%[[ESLICE]]] : ({{.+}}) -> !fir.box<!fir.array<1x1xi32>>
+
+// Row-major memref view of the parent (col-major !fir.array<4x2>).
+// CHECK:       %[[MEMREF:.+]] = fir.convert %[[ARG0]] : (!fir.ref<!fir.array<4x2xi32>>) -> memref<2x4xi32>
+
+// Anchor: `arith.constant 0 : index` appears exactly twice between the
+// fir.convert and the fold -- once for getMemrefIndices' zero placeholder,
+// then again as the reinterpret_cast offset. The fold's `cOne` is emitted
+// immediately after the second.
+// CHECK:       arith.constant 0 : index
+// CHECK:       arith.constant 0 : index
+// CHECK-NEXT:  %[[CONE:.+]] = arith.constant 1 : index
+
+// Fortran dim 0 (embox lb = 2) -> delta 1, added onto memref position 1.
+// CHECK-NEXT:  %[[LB0_DELTA:.+]] = arith.subi %[[C2]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM0:.+]] = arith.addi %{{.+}}, %[[LB0_DELTA]] : index
+
+// Fortran dim 1 (embox lb = 1) -> delta 0, added onto memref position 0.
+// CHECK-NEXT:  %[[LB1_DELTA:.+]] = arith.subi %[[C1]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM1:.+]] = arith.addi %{{.+}}, %[[LB1_DELTA]] : index
+
+// reinterpret_cast: offset stays a plain 0 constant, sizes = slice extents
+// (1, 1), strides = [parent's dim-0 element stride, 1] in memref order.
+// CHECK-NEXT:  memref.reinterpret_cast %[[MEMREF]] to offset: [%{{.+}}], sizes: [%[[C1]], %[[C1]]], strides: [%[[C4]], %{{.+}}] : memref<2x4xi32> to memref<?x?xi32, strided<[?, ?], offset: ?>>
+
+// memref.load consumes the shifted indices in memref order
+// (dim 1 outer -> IDX_DIM1, dim 0 inner -> IDX_DIM0).
+// CHECK-NEXT:  memref.load %{{.+}}[%[[IDX_DIM1]], %[[IDX_DIM0]]] : memref<?x?xi32, strided<[?, ?], offset: ?>>
diff --git a/flang/test/Transforms/FIRToMemRef/emboxed-slice-rank-reduction.mlir b/flang/test/Transforms/FIRToMemRef/emboxed-slice-rank-reduction.mlir
new file mode 100644
index 0000000000000..7c98a6c6bac1a
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/emboxed-slice-rank-reduction.mlir
@@ -0,0 +1,82 @@
+// RUN: fir-opt %s --fir-to-memref | FileCheck %s
+
+// When the fir.embox slice has a rank-reducing scalar-subscript triple
+// (undef ub/step), the corresponding parent Fortran dim is collapsed in the
+// box's rank but still has a memref index position under the reinterpret_cast
+// (getRankFromEmbox returns the parent rank). The fold OVERWRITES that index
+// with (lb - 1) instead of adding to it; the memref stride carried by that
+// slot times (lb - 1) reaches the correct parent element, without needing to
+// touch the reinterpret_cast's flat offset.
+
+func.func @rank_reduced_embox_slice(
+    %arg0: !fir.ref<!fir.array<3x2x2xi32>>, %i: index, %j: index) -> i32 {
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %c3 = arith.constant 3 : index
+  %undef = fir.undefined index
+  %shape = fir.shape %c3, %c2, %c2 : (index, index, index) -> !fir.shape<3>
+  // Embox slice: a(:, 2, :) -- range/scalar/range. Collapses parent dim 1
+  // (Fortran dim 1) at lb = 2. Result is a rank-2 fir.box.
+  %eslice = fir.slice %c1, %c3, %c1, %c2, %undef, %undef, %c1, %c2, %c1
+      : (index, index, index, index, index, index, index, index, index)
+      -> !fir.slice<3>
+  %box = fir.embox %arg0(%shape) [%eslice]
+      : (!fir.ref<!fir.array<3x2x2xi32>>, !fir.shape<3>, !fir.slice<3>)
+      -> !fir.box<!fir.array<3x2xi32>>
+  // Array_coor with its own rank-2 slice on the box + 2 indices.
+  %ashape = fir.shape %c3, %c2 : (index, index) -> !fir.shape<2>
+  %ai = arith.index_cast %i : index to i64
+  %islice = fir.slice %c1, %c3, %c1, %j, %undef, %undef
+      : (index, index, index, index, index, index) -> !fir.slice<2>
+  %addr = fir.array_coor %box(%ashape) [%islice] %ai, %j
+      : (!fir.box<!fir.array<3x2xi32>>, !fir.shape<2>, !fir.slice<2>, i64, index)
+      -> !fir.ref<i32>
+  %v = fir.load %addr : !fir.ref<i32>
+  return %v : i32
+}
+
+// CHECK-LABEL: func.func @rank_reduced_embox_slice(
+// CHECK-SAME:      %[[ARG0:.+]]: !fir.ref<!fir.array<3x2x2xi32>>,
+// CHECK-SAME:      %[[I:.+]]: index, %[[J:.+]]: index) -> i32
+// CHECK-DAG:   %[[C1:.+]] = arith.constant 1 : index
+// CHECK-DAG:   %[[C2:.+]] = arith.constant 2 : index
+// CHECK-DAG:   %[[C3:.+]] = arith.constant 3 : index
+// CHECK:       %[[UNDEF:.+]] = fir.undefined index
+
+// Parent (rank-3) fir.shape and rank-reducing fir.slice survive.
+// CHECK:       %[[SHAPE:.+]] = fir.shape %[[C3]], %[[C2]], %[[C2]] : (index, index, index) -> !fir.shape<3>
+// CHECK:       %[[ESLICE:.+]] = fir.slice %[[C1]], %[[C3]], %[[C1]], %[[C2]], %[[UNDEF]], %[[UNDEF]], %[[C1]], %[[C2]], %[[C1]] : ({{.+}}) -> !fir.slice<3>
+// CHECK:       fir.embox %[[ARG0]](%[[SHAPE]]) [%[[ESLICE]]] : ({{.+}}) -> !fir.box<!fir.array<3x2xi32>>
+
+// Row-major memref view of the parent (col-major !fir.array<3x2x2>).
+// CHECK:       %[[MEMREF:.+]] = fir.convert %[[ARG0]] : (!fir.ref<!fir.array<3x2x2xi32>>) -> memref<2x2x3xi32>
+
+// Anchor via the two `arith.constant 0 : index` uses (getMemrefIndices' zero
+// placeholder, then reinterpret_cast's offset) -- the fold's `cOne` is
+// emitted immediately after the second.
+// CHECK:       arith.constant 0 : index
+// CHECK:       arith.constant 0 : index
+// CHECK-NEXT:  %[[CONE:.+]] = arith.constant 1 : index
+
+// Fortran dim 0 (embox slice range, lb = 1) -> delta 0 added onto memref
+// position 2.
+// CHECK-NEXT:  %[[LB0_DELTA:.+]] = arith.subi %[[C1]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM0:.+]] = arith.addi %{{.+}}, %[[LB0_DELTA]] : index
+
+// Fortran dim 1 (embox slice SCALAR, lb = 2) -> delta 1 OVERWRITES memref
+// position 1 (no arith.addi follows -- the delta itself becomes the index).
+// CHECK-NEXT:  %[[LB1_DELTA:.+]] = arith.subi %[[C2]], %[[CONE]] : index
+
+// Fortran dim 2 (embox slice range, lb = 1) -> delta 0 added onto memref
+// position 0.
+// CHECK-NEXT:  %[[LB2_DELTA:.+]] = arith.subi %[[C1]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM2:.+]] = arith.addi %{{.+}}, %[[LB2_DELTA]] : index
+
+// reinterpret_cast: offset stays 0 (never touched), sizes = parent extents in
+// memref order (3, 2, 3) via getMemrefIndices' rank override, strides =
+// parent's col-major element strides in memref order (2*3, 3, 1).
+// CHECK-NEXT:  memref.reinterpret_cast %[[MEMREF]] to offset: [%{{.+}}], sizes: [%[[C3]], %[[C2]], %[[C3]]], strides: [%{{.+}}, %[[C3]], %{{.+}}] : memref<2x2x3xi32> to memref<?x?x?xi32, strided<[?, ?, ?], offset: ?>>
+
+// memref.load: memref-order indices. Position 1 (collapsed dim) is
+// LB1_DELTA directly (not through arith.addi).
+// CHECK-NEXT:  memref.load %{{.+}}[%[[IDX_DIM2]], %[[LB1_DELTA]], %[[IDX_DIM0]]] : memref<?x?x?xi32, strided<[?, ?, ?], offset: ?>>
diff --git a/flang/test/Transforms/FIRToMemRef/emboxed-slice-stride.mlir b/flang/test/Transforms/FIRToMemRef/emboxed-slice-stride.mlir
new file mode 100644
index 0000000000000..8a283fd52d570
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/emboxed-slice-stride.mlir
@@ -0,0 +1,67 @@
+// RUN: fir-opt %s --fir-to-memref | FileCheck %s
+
+// When both the embox and its array_coor consumer carry a slice, FIRToMemRef
+// must synthesize reinterpret_cast strides from the *parent's* extents, not
+// the box's (= slice's). For a slice that keeps > 1 element per dim, using
+// the slice extents makes the outer stride walk by the slice's size instead
+// of the parent's leading dim, so memref.load reads across the wrong columns.
+
+func.func @emboxed_slice_stride(%arg0: !fir.ref<!fir.array<4x2xi32>>) -> i32 {
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %c4 = arith.constant 4 : index
+  %ci = arith.constant 1 : i64
+  %shape = fir.shape %c4, %c2 : (index, index) -> !fir.shape<2>
+  // Embox slice: a(1:2, :) -- keeps 2 elements per dim (all steps 1, all lb 1).
+  %eslice = fir.slice %c1, %c2, %c1, %c1, %c2, %c1
+      : (index, index, index, index, index, index) -> !fir.slice<2>
+  %box = fir.embox %arg0(%shape) [%eslice]
+      : (!fir.ref<!fir.array<4x2xi32>>, !fir.shape<2>, !fir.slice<2>)
+      -> !fir.box<!fir.array<2x2xi32>>
+  // Inner array_coor slice (identity 1:1:1 in both dims).
+  %ashape = fir.shape %c2, %c2 : (index, index) -> !fir.shape<2>
+  %islice = fir.slice %c1, %c1, %c1, %c1, %c1, %c1
+      : (index, index, index, index, index, index) -> !fir.slice<2>
+  %addr = fir.array_coor %box(%ashape) [%islice] %ci, %c1
+      : (!fir.box<!fir.array<2x2xi32>>, !fir.shape<2>, !fir.slice<2>, i64, index)
+      -> !fir.ref<i32>
+  %v = fir.load %addr : !fir.ref<i32>
+  return %v : i32
+}
+
+// CHECK-LABEL: func.func @emboxed_slice_stride(
+// CHECK-SAME:      %[[ARG0:.+]]: !fir.ref<!fir.array<4x2xi32>>) -> i32
+// CHECK-DAG:   %[[C1:.+]] = arith.constant 1 : index
+// CHECK-DAG:   %[[C2:.+]] = arith.constant 2 : index
+// CHECK-DAG:   %[[C4:.+]] = arith.constant 4 : index
+
+// Parent fir.shape and embox fir.slice survive.
+// CHECK:       %[[SHAPE:.+]] = fir.shape %[[C4]], %[[C2]] : (index, index) -> !fir.shape<2>
+// CHECK:       %[[ESLICE:.+]] = fir.slice %[[C1]], %[[C2]], %[[C1]], %[[C1]], %[[C2]], %[[C1]] : ({{.+}}) -> !fir.slice<2>
+// CHECK:       fir.embox %[[ARG0]](%[[SHAPE]]) [%[[ESLICE]]] : ({{.+}}) -> !fir.box<!fir.array<2x2xi32>>
+
+// Row-major memref view of the parent (col-major !fir.array<4x2>).
+// CHECK:       %[[MEMREF:.+]] = fir.convert %[[ARG0]] : (!fir.ref<!fir.array<4x2xi32>>) -> memref<2x4xi32>
+
+// Anchor via the two `arith.constant 0 : index` uses (getMemrefIndices' zero
+// placeholder, then reinterpret_cast's offset) -- the fold's `cOne` is
+// emitted immediately after the second.
+// CHECK:       arith.constant 0 : index
+// CHECK:       arith.constant 0 : index
+// CHECK-NEXT:  %[[CONE:.+]] = arith.constant 1 : index
+
+// Fortran dim 0 (embox lb = 1) -> delta 0 added onto memref position 1.
+// CHECK-NEXT:  %[[LB0_DELTA:.+]] = arith.subi %[[C1]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM0:.+]] = arith.addi %{{.+}}, %[[LB0_DELTA]] : index
+
+// Fortran dim 1 (embox lb = 1) -> delta 0 added onto memref position 0.
+// CHECK-NEXT:  %[[LB1_DELTA:.+]] = arith.subi %[[C1]], %[[CONE]] : index
+// CHECK-NEXT:  %[[IDX_DIM1:.+]] = arith.addi %{{.+}}, %[[LB1_DELTA]] : index
+
+// The stride check is the point of the test: sizes = slice extents (2, 2),
+// strides = [parent's dim-0 element stride = 4, 1] in memref order. The
+// outer stride MUST be %c4 (parent dim 0), not %c2 (slice's own extent).
+// CHECK-NEXT:  memref.reinterpret_cast %[[MEMREF]] to offset: [%{{.+}}], sizes: [%[[C2]], %[[C2]]], strides: [%[[C4]], %{{.+}}] : memref<2x4xi32> to memref<?x?xi32, strided<[?, ?], offset: ?>>
+
+// memref.load consumes the shifted indices in memref order.
+// CHECK-NEXT:  memref.load %{{.+}}[%[[IDX_DIM1]], %[[IDX_DIM0]]] : memref<?x?xi32, strided<[?, ?], offset: ?>>



More information about the flang-commits mailing list