[flang][FIRToMemRef] Fix lowering of complex array component slices (z%re, z%im) (#191846)
fir.slice with a path component (z%re, z%im) was silently dropped by
FIRToMemRef. Since memref.reinterpret_cast cannot change element type,
layout must come from the projected box descriptor via
fir.box_dims/fir.box_elesize rather than the triplets. Only
complex-array projections are handled here —
sizeof(complex<T>)/sizeof(T) = 2 is always exact for divsi. Derived-type
component projections bail out to downstream FIR-to-LLVM lowering where
strides can be non-integer.
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 07a87a4..ec58d6f 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -142,10 +142,18 @@
Value canonicalizeIndex(Value, PatternRewriter &) const;
+ // Logical section information used by FIRToMemRef. For projected slices, the
+ // descriptor still owns the physical layout, so `sliceVec` intentionally
+ // stays empty while `shapeVec`/`shiftVec` remain available for index math.
+ struct SliceInfo {
+ SmallVector<Value> shapeVec;
+ SmallVector<Value> shiftVec;
+ SmallVector<Value> sliceVec;
+ bool hasProjectedSlice = false;
+ };
+
template <typename OpTy>
- void getShapeFrom(OpTy op, SmallVector<Value> &shapeVec,
- SmallVector<Value> &shiftVec,
- SmallVector<Value> &sliceVec) const;
+ void collectSliceInfoFrom(OpTy op, SliceInfo &info) const;
void populateShapeAndShift(SmallVectorImpl<Value> &shapeVec,
SmallVectorImpl<Value> &shiftVec,
@@ -155,6 +163,14 @@
void populateShape(SmallVectorImpl<Value> &vec, fir::ShapeOp shape) const;
+ static fir::SliceOp getSliceOp(Value sliceVal) {
+ return sliceVal ? sliceVal.getDefiningOp<fir::SliceOp>() : fir::SliceOp{};
+ }
+
+ static bool hasProjectedSlice(fir::SliceOp sliceOp) {
+ return sliceOp && !sliceOp.getFields().empty();
+ }
+
unsigned getRankFromEmbox(fir::EmboxOp embox) const {
auto memrefType = embox.getMemref().getType();
Type unwrappedType = fir::unwrapRefType(memrefType);
@@ -278,9 +294,7 @@
}
template <typename OpTy>
-void FIRToMemRef::getShapeFrom(OpTy op, SmallVector<Value> &shapeVec,
- SmallVector<Value> &shiftVec,
- SmallVector<Value> &sliceVec) const {
+void FIRToMemRef::collectSliceInfoFrom(OpTy op, SliceInfo &info) const {
if constexpr (std::is_same_v<OpTy, fir::ArrayCoorOp> ||
std::is_same_v<OpTy, fir::ReboxOp> ||
std::is_same_v<OpTy, fir::EmboxOp>) {
@@ -290,19 +304,23 @@
Operation *shapeValOp = shapeVal.getDefiningOp();
if (auto shapeOp = dyn_cast<fir::ShapeOp>(shapeValOp)) {
- populateShape(shapeVec, shapeOp);
+ populateShape(info.shapeVec, shapeOp);
} else if (auto shapeShiftOp = dyn_cast<fir::ShapeShiftOp>(shapeValOp)) {
- populateShapeAndShift(shapeVec, shiftVec, shapeShiftOp);
+ populateShapeAndShift(info.shapeVec, info.shiftVec, shapeShiftOp);
} else if (auto shiftOp = dyn_cast<fir::ShiftOp>(shapeValOp)) {
- populateShift(shiftVec, shiftOp);
+ populateShift(info.shiftVec, shiftOp);
}
}
- Value sliceVal = op.getSlice();
- if (sliceVal) {
- if (auto sliceOp = sliceVal.getDefiningOp<fir::SliceOp>()) {
+ if (auto sliceOp = getSliceOp(op.getSlice())) {
+ // A slice path changes the physical projection of the boxed entity (for
+ // example, `complex -> real` for `%re`). Preserve shape/shift for logical
+ // indexing, but do not treat the triplets alone as layout information.
+ if (hasProjectedSlice(sliceOp)) {
+ info.hasProjectedSlice = true;
+ } else {
auto triples = sliceOp.getTriples();
- sliceVec.append(triples.begin(), triples.end());
+ info.sliceVec.append(triples.begin(), triples.end());
}
}
}
@@ -431,15 +449,35 @@
IndexType indexTy = rewriter.getIndexType();
SmallVector<Value> indices;
Location loc = arrayCoorOp->getLoc();
- SmallVector<Value> shiftVec, shapeVec, sliceVec;
+ SliceInfo sliceInfo;
int rank = arrayCoorOp.getIndices().size();
- getShapeFrom<fir::ArrayCoorOp>(arrayCoorOp, shapeVec, shiftVec, sliceVec);
+ collectSliceInfoFrom(arrayCoorOp, sliceInfo);
+ // Only collect shape/shift from fir.embox, never from fir.rebox.
+ //
+ // The distinction is a Fortran descriptor invariant:
+ //
+ // | fir.embox | fir.rebox
+ // --------------|------------------------|---------------------------
+ // base_addr | raw pointer | pre-adjusted by (lb-1)*stride
+ // Index formula | index - lb | index - 1
+ // Collect shift | yes | no
+ //
+ // fir.embox creates a box from a raw pointer so base_addr is not adjusted;
+ // the shift must be collected so index arithmetic subtracts lb correctly.
+ // fir.rebox re-boxes a live descriptor whose base_addr the runtime has
+ // already adjusted downward by (lb-1)*stride; collecting the shift here
+ // would subtract the lower bound a second time, giving the wrong element.
if (auto embox = dyn_cast_or_null<fir::EmboxOp>(memref)) {
- getShapeFrom<fir::EmboxOp>(embox, shapeVec, shiftVec, sliceVec);
+ collectSliceInfoFrom(embox, sliceInfo);
rank = getRankFromEmbox(embox);
}
+ // Projected boxed slices leave `sliceVec` empty on purpose: indices are
+ // computed in the logical section coordinate space, while stride/base come
+ // later from the box descriptor.
+ SmallVector<Value> &shiftVec = sliceInfo.shiftVec;
+ SmallVector<Value> &sliceVec = sliceInfo.sliceVec;
SmallVector<Value> sliceLbs, sliceStrides;
for (size_t i = 0; i < sliceVec.size(); i += 3) {
sliceLbs.push_back(castTypeToIndexType(sliceVec[i], rewriter));
@@ -604,18 +642,22 @@
SmallVector<Value> strides;
strides.reserve(rank);
- SmallVector<Value> shapeVec, shiftVec, sliceVec;
- getShapeFrom<fir::ArrayCoorOp>(arrayCoorOp, shapeVec, shiftVec, sliceVec);
+ SliceInfo sliceInfo;
+ collectSliceInfoFrom(arrayCoorOp, sliceInfo);
Value box = firMemref;
if (!isa<BlockArgument>(firMemref)) {
- if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>())
- getShapeFrom<fir::EmboxOp>(embox, shapeVec, shiftVec, sliceVec);
- else if (auto rebox = firMemref.getDefiningOp<fir::ReboxOp>())
- getShapeFrom<fir::ReboxOp>(rebox, shapeVec, shiftVec, sliceVec);
+ if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>()) {
+ collectSliceInfoFrom(embox, sliceInfo);
+ } else if (auto rebox = firMemref.getDefiningOp<fir::ReboxOp>()) {
+ collectSliceInfoFrom(rebox, sliceInfo);
+ }
}
- if (shapeVec.empty()) {
+ SmallVector<Value> &shapeVec = sliceInfo.shapeVec;
+ if (sliceInfo.hasProjectedSlice || shapeVec.empty()) {
+ // Projected slices carry their physical layout in the descriptor. Rebuild
+ // the MemRef view from box metadata instead of from slice triplets.
auto boxElementSize =
fir::BoxEleSizeOp::create(rewriter, loc, indexTy, box);
@@ -722,13 +764,36 @@
};
if (auto embox = dyn_cast_or_null<fir::EmboxOp>(baseOp)) {
- if (!sameBaseBoxTypes(embox.getType(), embox.getMemref().getType())) {
+ // A projected slice changes the element type of the boxed view. We
+ // can only lower it here when the storage element is complex<T> and
+ // the projection is the real or imaginary part (i.e. %re / %im). For
+ // such cases sizeof(complex<T>) == 2*sizeof(T), so
+ // divsi(byte_stride, elesize) is always an exact integer.
+ //
+ // Derived-type component projections (e.g. a%x, a%y) may produce a
+ // non-integer element-unit stride (e.g. sizeof(T)=24,
+ // sizeof(complex<f64>)=16 -> 24/16 = 1 after truncation, which is
+ // wrong). For those, the type-restriction check below fires and we
+ // bail out, leaving the ops for downstream FIR-to-LLVM lowering.
+ auto isComplexComponentProjection = [&](fir::EmboxOp embox) -> bool {
+ if (!hasProjectedSlice(getSliceOp(embox.getSlice())))
+ return false;
+ Type memTy = fir::unwrapRefType(embox.getMemref().getType());
+ if (auto seqTy = dyn_cast<fir::SequenceType>(memTy))
+ memTy = seqTy.getEleTy();
+ return mlir::isa<mlir::ComplexType>(memTy);
+ };
+ bool projectedSlice = isComplexComponentProjection(embox);
+ if (!projectedSlice &&
+ !sameBaseBoxTypes(embox.getType(), embox.getMemref().getType())) {
LLVM_DEBUG(llvm::dbgs()
<< "FIRToMemRef: embox base type and memref type are not "
"the same, bailing out of conversion\n");
return failure();
}
- if (embox.getSlice() &&
+ // Keep `box_addr` on the projected box so the descriptor remains the
+ // source of truth for projected element type and stride.
+ if (!projectedSlice && embox.getSlice() &&
embox.getSlice().getDefiningOp<fir::SliceOp>()) {
Type originalType = embox.getMemref().getType();
basePtr = embox.getMemref();
diff --git a/flang/test/Transforms/FIRToMemRef/slice-projected.mlir b/flang/test/Transforms/FIRToMemRef/slice-projected.mlir
new file mode 100644
index 0000000..7b0fbdf
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/slice-projected.mlir
@@ -0,0 +1,99 @@
+// RUN: fir-opt %s --fir-to-memref --allow-unregistered-dialect | FileCheck %s
+
+// Tests for fir.slice with a path component (projected component slice).
+// A projected slice changes the element type of the boxed view, e.g.
+// z%re projects complex<f32> -> f32. The layout (strides / base address)
+// must come from the projected box descriptor, NOT from reconstructing the
+// triplets, because memref.reinterpret_cast requires the same element type
+// on both sides and the triplet strides are in storage-element units
+// (complex<f32>) while the MemRef strides must be in projected-element units
+// (f32).
+//
+// Derived from:
+// complex, target :: z(4) = 0.
+// real, pointer :: r(:)
+// r => z%re
+// r = r + z(4:1:-1)%re
+
+// ----------------------------------------------------------------------------
+// Forward projected slice: z(1:4:1)%re
+// The slice path %c0 projects complex<f32> -> f32 (real part).
+// Expected lowering:
+// - fir.box_addr on the projected box (!fir.box<!fir.array<4xf32>>)
+// - fir.convert to memref<4xf32> (NOT to memref<4xcomplex<f32>>)
+// - index = i - 1 (1-based, no triplet arithmetic)
+// - strides from fir.box_dims / fir.box_elesize on the projected box
+// ----------------------------------------------------------------------------
+// CHECK-LABEL: func.func @projected_slice_fwd
+// CHECK: [[C1:%.*]] = arith.constant 1 : index
+// CHECK: [[C4:%.*]] = arith.constant 4 : index
+// CHECK: [[C0:%.*]] = arith.constant 0 : index
+// CHECK: [[SHAPE:%.*]] = fir.shape [[C4]] : (index) -> !fir.shape<1>
+// CHECK: [[SLICE:%.*]] = fir.slice [[C1]], [[C4]], [[C1]] path [[C0]] : (index, index, index, index) -> !fir.slice<1>
+// CHECK: [[EMBOX:%.*]] = fir.embox %arg0([[SHAPE]]) {{\[}}[[SLICE]]{{\]}} : (!fir.ref<!fir.array<4xcomplex<f32>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf32>>
+// CHECK: fir.do_loop [[I:%.*]] = [[C1]] to [[C4]] step [[C1]] unordered {
+// Projected box_addr gives f32 pointer, not complex<f32>.
+// CHECK: [[BOXADDR:%.*]] = fir.box_addr [[EMBOX]] : (!fir.box<!fir.array<4xf32>>) -> !fir.ref<!fir.array<4xf32>>
+// CHECK: [[CONVERT:%.*]] = fir.convert [[BOXADDR]] : (!fir.ref<!fir.array<4xf32>>) -> memref<4xf32>
+// Index: i-1 (1-based). The lowering emits: delta=i-1, scaled=delta*1,
+// offset=1-1=0, finalIdx=scaled+offset. The addi result is what feeds the load.
+// CHECK: [[C1_0:%.*]] = arith.constant 1 : index
+// CHECK: [[DELTA:%.*]] = arith.subi [[I]], [[C1_0]] : index
+// CHECK: [[SCALED:%.*]] = arith.muli [[DELTA]], [[C1_0]] : index
+// CHECK: [[OFFSET:%.*]] = arith.subi [[C1_0]], [[C1_0]] : index
+// CHECK: [[IDX:%.*]] = arith.addi [[SCALED]], [[OFFSET]] : index
+// Layout: extent and stride come from the projected box descriptor.
+// CHECK: [[ELE:%.*]] = fir.box_elesize [[EMBOX]] : (!fir.box<!fir.array<4xf32>>) -> index
+// CHECK: [[C0_0:%.*]] = arith.constant 0 : index
+// CHECK: [[DIMS:%.*]]:3 = fir.box_dims [[EMBOX]], [[C0_0]] : (!fir.box<!fir.array<4xf32>>, index) -> (index, index, index)
+// CHECK: [[STRIDE:%.*]] = arith.divsi [[DIMS]]#2, [[ELE]] : index
+// CHECK: [[C0_1:%.*]] = arith.constant 0 : index
+// CHECK: [[VIEW:%.*]] = memref.reinterpret_cast [[CONVERT]] to offset: {{\[}}[[C0_1]]{{\]}}, sizes: {{\[}}[[DIMS]]#1{{\]}}, strides: {{\[}}[[STRIDE]]{{\]}} : memref<4xf32> to memref<?xf32, strided<[?], offset: ?>>
+// CHECK: memref.load [[VIEW]]{{\[}}[[IDX]]{{\]}} : memref<?xf32, strided<[?], offset: ?>>
+func.func @projected_slice_fwd(%arg0: !fir.ref<!fir.array<4xcomplex<f32>>>) {
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %c0 = arith.constant 0 : index
+ %shape = fir.shape %c4 : (index) -> !fir.shape<1>
+ %slice = fir.slice %c1, %c4, %c1 path %c0 : (index, index, index, index) -> !fir.slice<1>
+ %embox = fir.embox %arg0(%shape) [%slice] : (!fir.ref<!fir.array<4xcomplex<f32>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf32>>
+ fir.do_loop %i = %c1 to %c4 step %c1 unordered {
+ %coor = fir.array_coor %embox %i : (!fir.box<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+ %val = fir.load %coor : !fir.ref<f32>
+ }
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Derived-type component projection: a%x where a : TYPE{x:f64, y:complex<f64>}
+//
+// This is NOT a complex projection — the storage element is the derived type T,
+// not complex<T>. FIRToMemRef cannot safely compute element-unit strides via
+// divsi(byte_stride, elesize) because sizeof(T)/sizeof(component) may not be an
+// integer (e.g. sizeof(T)=24, sizeof(complex<f64>)=16 -> 1.5, truncated to 1).
+//
+// The pass must leave fir.array_coor and fir.store/fir.load unconverted;
+// downstream FIR-to-LLVM lowering handles them correctly via the descriptor.
+//
+// CHECK-LABEL: func.func @derived_component_not_projected
+// The fir.array_coor must survive (not be erased).
+// CHECK: fir.array_coor
+// The store must remain as fir.store, not memref.store.
+// CHECK: fir.store
+// CHECK-NOT: memref.store
+// ----------------------------------------------------------------------------
+func.func @derived_component_not_projected(
+ %arg0: !fir.ref<!fir.array<4x!fir.type<T{x:f64,y:complex<f64>}>>>) {
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %cst = arith.constant 9.9e+01 : f64
+ %field = fir.field_index x, !fir.type<T{x:f64,y:complex<f64>}>
+ %shape = fir.shape %c4 : (index) -> !fir.shape<1>
+ %slice = fir.slice %c1, %c4, %c1 path %field : (index, index, index, !fir.field) -> !fir.slice<1>
+ %embox = fir.embox %arg0(%shape) [%slice] : (!fir.ref<!fir.array<4x!fir.type<T{x:f64,y:complex<f64>}>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf64>>
+ fir.do_loop %i = %c1 to %c4 step %c1 unordered {
+ %coor = fir.array_coor %embox %i : (!fir.box<!fir.array<4xf64>>, index) -> !fir.ref<f64>
+ fir.store %cst to %coor : !fir.ref<f64>
+ }
+ return
+}