[flang-commits] [flang] [flang][mem2reg] promote memory slots through declares (PR #196975)
via flang-commits
flang-commits at lists.llvm.org
Tue Jun 16 03:08:50 PDT 2026
https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/196975
>From b0a76c038b3f3f2fc49288a4f6b8010f68da34ef Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Mon, 11 May 2026 07:58:42 -0700
Subject: [PATCH 1/3] [flang][mem2reg] promote memory slots through declares
---
.../include/flang/Optimizer/Dialect/FIROps.h | 1 +
.../include/flang/Optimizer/Dialect/FIROps.td | 13 +-
flang/lib/Optimizer/Dialect/FIROps.cpp | 111 +++++++--
flang/test/Fir/mem2reg.mlir | 211 ++++++++++++++++--
4 files changed, 292 insertions(+), 44 deletions(-)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h
index 9d771b1e60604..54aa3d58abe8c 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.h
@@ -19,6 +19,7 @@
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
+#include "mlir/Interfaces/MemorySlotInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Interfaces/ViewLikeInterface.h"
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 35a87c29d0cb6..f36a5e5cc82fe 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3070,9 +3070,13 @@ def fir_VolatileCastOp
}
def fir_ConvertOp
- : fir_SimpleOneResultOp<"convert", [NoMemoryEffect, ViewLikeOpInterface,
- ConditionallySpeculatable,
- fir_FortranObjectViewOpInterface]> {
+ : fir_SimpleOneResultOp<"convert",
+ [NoMemoryEffect, ViewLikeOpInterface,
+ ConditionallySpeculatable,
+ DeclareOpInterfaceMethods<PromotableOpInterface,
+ ["getPromotableSlotView",
+ "convertSlotValue"]>,
+ fir_FortranObjectViewOpInterface]> {
let summary = "encapsulates all Fortran entity type conversions";
let description = [{
@@ -3599,7 +3603,8 @@ def fir_DeclareOp
DeclareOpInterfaceMethods<
fir_FortranVariableStorageOpInterface>,
DeclareOpInterfaceMethods<PromotableOpInterface,
- ["requiresReplacedValues",
+ ["getPromotableSlotView",
+ "requiresReplacedValues",
"visitReplacedValues"]>,
fir_FortranObjectViewOpInterface]> {
let summary = "declare a variable";
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 1d61989eba6cf..5013ebd6700dd 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -2012,6 +2012,68 @@ void fir::ConvertOp::getCanonicalizationPatterns(
context);
}
+// Returns the pointee element type of a FIR reference-like type or a memref;
+// returns null if `ty` is not pointer-like or has no extractable element
+// type (e.g. unranked memref, box).
+static mlir::Type slotPointeeElemType(mlir::Type ty) {
+ if (mlir::Type fEle = fir::dyn_cast_ptrEleTy(ty))
+ return fEle;
+ if (auto memrefTy = mlir::dyn_cast<mlir::MemRefType>(ty))
+ return memrefTy.getElementType();
+ return {};
+}
+
+// Returns true if a `fir.convert` between pointers with element types
+// `srcElem` and `dstElem` is a no-op at the value level or can be replaced by
+// a bicast.
+static bool isAcceptableSlotElemTypePair(mlir::Type srcElem,
+ mlir::Type dstElem) {
+ if (srcElem == dstElem)
+ return true;
+ if (!isBitcastCompatibleType(srcElem) || !isBitcastCompatibleType(dstElem))
+ return false;
+ auto srcBits = getBitcastBitSize(srcElem);
+ auto dstBits = getBitcastBitSize(dstElem);
+ return srcBits && dstBits && *srcBits == *dstBits;
+}
+
+std::optional<mlir::PromotableSlotView>
+fir::ConvertOp::getPromotableSlotView() {
+ mlir::Type srcElem = slotPointeeElemType(getValue().getType());
+ mlir::Type dstElem = slotPointeeElemType(getResult().getType());
+ if (!srcElem || !dstElem || !isAcceptableSlotElemTypePair(srcElem, dstElem))
+ return std::nullopt;
+ return mlir::PromotableSlotView{getValue(),
+ mlir::MemorySlot{getResult(), dstElem}};
+}
+
+mlir::Value fir::ConvertOp::convertSlotValue(mlir::Value value,
+ mlir::Type targetType,
+ mlir::OpBuilder &builder) {
+ if (value.getType() == targetType)
+ return value;
+ return fir::BitcastOp::create(builder, getLoc(), targetType, value);
+}
+
+bool fir::ConvertOp::canUsesBeRemoved(
+ const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses,
+ mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses,
+ const mlir::DataLayout &dataLayout) {
+ // Only participate in promotion when this convert is a slot view (if the
+ // address is cast is not used, DCE should have removed it)
+ if (!getPromotableSlotView())
+ return false;
+ for (mlir::OpOperand &use : getResult().getUses())
+ newBlockingUses.push_back(&use);
+ return true;
+}
+
+mlir::DeletionKind fir::ConvertOp::removeBlockingUses(
+ const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses,
+ mlir::OpBuilder &builder) {
+ return mlir::DeletionKind::Delete;
+}
+
static bool isI1(mlir::Type ty) {
if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty))
return intTy.getWidth() == 1;
@@ -3588,17 +3650,8 @@ llvm::SmallVector<mlir::Attribute> fir::LenParamIndexOp::getAttributes() {
// LoadOp
//===----------------------------------------------------------------------===//
-static bool isSlotOrDeclaredSlot(mlir::Value val,
- const mlir::MemorySlot &slot) {
- if (val == slot.ptr)
- return true;
- if (auto declareOp = val.getDefiningOp<fir::DeclareOp>())
- return declareOp.getMemref() == slot.ptr;
- return false;
-}
-
bool fir::LoadOp::loadsFrom(const mlir::MemorySlot &slot) {
- return isSlotOrDeclaredSlot(getMemref(), slot);
+ return getMemref() == slot.ptr;
}
bool fir::LoadOp::storesTo(const mlir::MemorySlot &slot) { return false; }
@@ -3618,7 +3671,8 @@ bool fir::LoadOp::canUsesBeRemoved(
if (blockingUses.size() != 1)
return false;
mlir::Value blockingUse = (*blockingUses.begin())->get();
- return isSlotOrDeclaredSlot(blockingUse, slot) && getMemref() == blockingUse;
+ return blockingUse == slot.ptr && getMemref() == slot.ptr &&
+ getResult().getType() == slot.elemType;
}
mlir::DeletionKind fir::LoadOp::removeBlockingUses(
@@ -5168,7 +5222,7 @@ llvm::LogicalResult fir::SliceOp::verify() {
bool fir::StoreOp::loadsFrom(const mlir::MemorySlot &slot) { return false; }
bool fir::StoreOp::storesTo(const mlir::MemorySlot &slot) {
- return isSlotOrDeclaredSlot(getMemref(), slot);
+ return getMemref() == slot.ptr;
}
mlir::Value fir::StoreOp::getStored(const mlir::MemorySlot &slot,
@@ -5186,8 +5240,8 @@ bool fir::StoreOp::canUsesBeRemoved(
if (blockingUses.size() != 1)
return false;
mlir::Value blockingUse = (*blockingUses.begin())->get();
- return isSlotOrDeclaredSlot(blockingUse, slot) &&
- getMemref() == blockingUse && getValue() != blockingUse;
+ return blockingUse == slot.ptr && getMemref() == slot.ptr &&
+ getValue() != slot.ptr && getValue().getType() == slot.elemType;
}
mlir::DeletionKind fir::StoreOp::removeBlockingUses(
@@ -6050,17 +6104,8 @@ bool fir::DeclareOp::canUsesBeRemoved(
const mlir::DataLayout &dataLayout) {
if (!isLegalTypeForValueDeclare(fir::unwrapRefType(getType())))
return false;
- // MLIR's mem2reg computes defining blocks only from direct users of
- // the slot pointer. Stores through fir.declare are not direct users,
- // so they are not registered as defining blocks. This causes missing
- // phi nodes at join points (e.g., loop headers). Restrict promotion
- // to the single-block case where no phi nodes are needed.
- mlir::Block *declBlock = getOperation()->getBlock();
- for (mlir::OpOperand &use : getResult().getUses()) {
- if (use.getOwner()->getBlock() != declBlock)
- return false;
+ for (mlir::OpOperand &use : getResult().getUses())
newBlockingUses.push_back(&use);
- }
return true;
}
@@ -6070,17 +6115,35 @@ mlir::DeletionKind fir::DeclareOp::removeBlockingUses(
return mlir::DeletionKind::Delete;
}
+std::optional<mlir::PromotableSlotView>
+fir::DeclareOp::getPromotableSlotView() {
+ // fir.declare is a transparent alias of its memref operand at the same
+ // element type.
+ mlir::Type aliasElemType = fir::dyn_cast_ptrEleTy(getResult().getType());
+ if (!aliasElemType)
+ return std::nullopt;
+ return mlir::PromotableSlotView{getMemref(),
+ mlir::MemorySlot{getResult(), aliasElemType}};
+}
+
bool fir::DeclareOp::requiresReplacedValues() { return true; }
void fir::DeclareOp::visitReplacedValues(
llvm::ArrayRef<std::pair<mlir::Operation *, mlir::Value>> definitions,
mlir::OpBuilder &builder) {
+ // TODO: extend `fir.declare_value` to carry the variable's declared type
+ // independently of the value's type. Once that is in place the type
+ // mismatch check below can be dropped (a bit-cast equivalent will be
+ // recorded on the op instead of skipping emission).
+ mlir::Type declaredElemType = fir::dyn_cast_ptrEleTy(getResult().getType());
for (auto [op, value] : definitions) {
// Do not emit DeclareValue when we have a dummy scope as this can
// potentially result in us generating it where the DummyScope does not
// dominate it. This can happen after inlining.
if (getDummyScope())
continue;
+ if (value.getType() != declaredElemType)
+ continue;
builder.setInsertionPointAfter(op);
fir::DeclareValueOp::create(builder, getLoc(), value, nullptr,
getUniqNameAttr(), getFortranAttrsAttr(),
diff --git a/flang/test/Fir/mem2reg.mlir b/flang/test/Fir/mem2reg.mlir
index 154580c626e7c..697324a603285 100644
--- a/flang/test/Fir/mem2reg.mlir
+++ b/flang/test/Fir/mem2reg.mlir
@@ -158,17 +158,24 @@ func.func @box_not_mem2reg(%arg0: !fir.ref<!fir.box<f32>> {fir.bindc_name = "i"}
// -----
-// Conditional store in a different block through fir.declare is not promoted
-// because MLIR mem2reg would not place the needed phi nodes correctly.
+// Conditional store in a different block through fir.declare is promoted:
+// the through-declare store is discovered as a defining block via the
+// `getPromotableSlotView` interface and a block argument is added at the
+// merge point.
// CHECK-LABEL: func.func @block_argument_value(
// CHECK-SAME: %[[ARG0:.*]]: i32,
// CHECK-SAME: %[[ARG1:.*]]: i1) -> i32 {
-// CHECK: fir.alloca i32
-// CHECK: fir.declare
-// CHECK: fir.store
-// CHECK: fir.store
-// CHECK: fir.load
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>)
+// CHECK: %[[C42:.*]] = arith.constant 42 : i32
+// CHECK: fir.declare_value %[[C42]] {{.*}}
+// CHECK: llvm.cond_br %[[ARG1]], ^bb1, ^bb2(%[[C42]] : i32)
+// CHECK: ^bb1:
+// CHECK: fir.declare_value %[[ARG0]] {{.*}}
+// CHECK: llvm.br ^bb2(%[[ARG0]] : i32)
+// CHECK: ^bb2(%[[MERGE:.*]]: i32):
+// CHECK: return %[[MERGE]] : i32
func.func @block_argument_value(%arg0: i32, %cdt: i1) -> i32 {
%c42_i32 = arith.constant 42 : i32
%3 = fir.alloca i32 {bindc_name = "jlocal", uniq_name = "_QFfooEjlocal"}
@@ -185,19 +192,26 @@ func.func @block_argument_value(%arg0: i32, %cdt: i1) -> i32 {
// -----
-// Conditional store inside a loop through fir.declare must not be promoted.
-// MLIR's mem2reg does not register stores through declares as defining blocks,
-// so phi nodes at the loop header would be missing, losing the update.
+// Conditional store inside a loop through fir.declare is promoted: the
+// loop header gets a block argument carrying the conditional update from
+// the loop body. This is the case that motivated the previous same-block
+// restriction in fir::DeclareOp::canUsesBeRemoved.
// CHECK-LABEL: func.func @loop_conditional_update(
// CHECK-SAME: %[[ARG0:.*]]: i32,
// CHECK-SAME: %[[ARG1:.*]]: i1) -> i32 {
-// CHECK: fir.alloca i32
-// CHECK: fir.declare
-// CHECK: fir.store
-// CHECK: fir.load
-// CHECK: fir.store
-// CHECK: fir.load
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>)
+// CHECK: fir.declare_value %[[ARG0]] {{.*}}
+// CHECK: llvm.br ^bb1(%[[ARG0]] : i32)
+// CHECK: ^bb1(%[[LOOP_ARG:.*]]: i32):
+// CHECK: llvm.cond_br %[[ARG1]], ^bb2, ^bb3
+// CHECK: ^bb2:
+// CHECK: %[[NEW:.*]] = arith.subi %[[LOOP_ARG]], {{.*}} : i32
+// CHECK: fir.declare_value %[[NEW]] {{.*}}
+// CHECK: llvm.br ^bb1(%[[NEW]] : i32)
+// CHECK: ^bb3:
+// CHECK: return %[[LOOP_ARG]] : i32
func.func @loop_conditional_update(%arg0: i32, %cdt: i1) -> i32 {
%c1 = arith.constant 1 : i32
%alloca = fir.alloca i32 {bindc_name = "mywatch", uniq_name = "_QFkernelEmywatch"}
@@ -218,6 +232,171 @@ func.func @loop_conditional_update(%arg0: i32, %cdt: i1) -> i32 {
// -----
+// fir.convert at the same element type is a transparent view; the slot is
+// fully promoted with no value conversion needed.
+
+// CHECK-LABEL: func.func @convert_same_type(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32 {
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: fir.convert
+// CHECK: return %[[ARG0]] : i32
+func.func @convert_same_type(%arg0: i32) -> i32 {
+ %alloca = fir.alloca i32
+ %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.store %arg0 to %ptr : !fir.ref<i32>
+ %v = fir.load %ptr : !fir.ref<i32>
+ return %v : i32
+}
+
+// -----
+
+// A type-changing fir.convert exposes the slot at a different element type;
+// mem2reg materialises fir.bitcast value conversions at the store and load.
+
+// CHECK-LABEL: func.func @convert_type_changing(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32
+// CHECK-NOT: fir.alloca
+// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32
+// CHECK: fir.bitcast %[[I32]] : (i32) -> f32
+// CHECK: return %{{.*}} : f32
+func.func @convert_type_changing(%arg0: f32) -> f32 {
+ %alloca = fir.alloca i32
+ %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<f32>
+ fir.store %arg0 to %ptr : !fir.ref<f32>
+ %v = fir.load %ptr : !fir.ref<f32>
+ return %v : f32
+}
+
+// -----
+
+// Chained view: alloca -> fir.declare -> fir.convert -> load/store. The
+// declare's element type (i32) differs from the value type the store sees
+// at the leaf view (f32), so `fir.declare_value` is skipped until
+// `fir.declare_value` can carry the declared type independently of the
+// value type (see the TODO in fir::DeclareOp::visitReplacedValues).
+
+// CHECK-LABEL: func.func @declare_then_convert(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>)
+// CHECK-NOT: fir.declare_value
+// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32
+// CHECK: %[[F32:.*]] = fir.bitcast %[[I32]] : (i32) -> f32
+// CHECK: return %[[F32]] : f32
+func.func @declare_then_convert(%arg0: f32) -> f32 {
+ %alloca = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFEx"}
+ %declare = fir.declare %alloca {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %ptr = fir.convert %declare : (!fir.ref<i32>) -> !fir.ref<f32>
+ fir.store %arg0 to %ptr : !fir.ref<f32>
+ %v = fir.load %ptr : !fir.ref<f32>
+ return %v : f32
+}
+
+// -----
+
+// Inverse chain: alloca -> fir.convert -> fir.declare -> load/store. The
+// declare's memref is the result of a type-changing convert, so the
+// declare sees the slot at f32 — the same type as the store value, so
+// `fir.declare_value` is emitted.
+
+// CHECK-LABEL: func.func @convert_then_declare(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<f32>)
+// CHECK: fir.declare_value %[[ARG0]] {{.*}} : f32
+// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32
+// CHECK: %[[F32:.*]] = fir.bitcast %[[I32]] : (i32) -> f32
+// CHECK: return %[[F32]] : f32
+func.func @convert_then_declare(%arg0: f32) -> f32 {
+ %alloca = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFEx"}
+ %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<f32>
+ %declare = fir.declare %ptr {uniq_name = "_QFEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.store %arg0 to %declare : !fir.ref<f32>
+ %v = fir.load %declare : !fir.ref<f32>
+ return %v : f32
+}
+
+// -----
+
+// A memref.alloca slot with a mixed memref/FIR view chain
+// (memref -> fir.convert -> fir.declare -> fir.convert -> memref). All
+// element types are the same, so the slot promotes with no value
+// conversions; only `fir.declare_value` is emitted from the declare.
+
+// CHECK-LABEL: func.func @memref_with_declare_chain(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>)
+// CHECK-NOT: fir.convert
+// CHECK-NOT: memref.store
+// CHECK-NOT: memref.load
+// CHECK: fir.declare_value %[[ARG0]] {{.*}} : i32
+// CHECK: return %[[ARG0]] : i32
+func.func @memref_with_declare_chain(%arg0: i32) -> i32 {
+ %alloca = memref.alloca() : memref<i32>
+ %fref = fir.convert %alloca : (memref<i32>) -> !fir.ref<i32>
+ %decl = fir.declare %fref {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %mref = fir.convert %decl : (!fir.ref<i32>) -> memref<i32>
+ memref.store %arg0, %mref[] : memref<i32>
+ %v = memref.load %mref[] : memref<i32>
+ return %v : i32
+}
+
+// -----
+
+// Same mixed memref/FIR chain with a conditional store across blocks. The
+// merge-point block argument is at the root slot's element type (i32), and
+// `fir.declare_value` is emitted at each store site.
+
+// CHECK-LABEL: func.func @memref_with_declare_chain_blocks(
+// CHECK-SAME: %[[ARG0:.*]]: i32,
+// CHECK-SAME: %[[COND:.*]]: i1) -> i32
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>)
+// CHECK: %[[C42:.*]] = arith.constant 42 : i32
+// CHECK: fir.declare_value %[[C42]] {{.*}} : i32
+// CHECK: cf.cond_br %[[COND]], ^[[BB1:.*]], ^[[BB2:.*]](%[[C42]] : i32)
+// CHECK: ^[[BB1]]:
+// CHECK: fir.declare_value %[[ARG0]] {{.*}} : i32
+// CHECK: cf.br ^[[BB2]](%[[ARG0]] : i32)
+// CHECK: ^[[BB2]](%[[MERGE:.*]]: i32):
+// CHECK: return %[[MERGE]] : i32
+func.func @memref_with_declare_chain_blocks(%arg0: i32, %cond: i1) -> i32 {
+ %c42 = arith.constant 42 : i32
+ %alloca = memref.alloca() : memref<i32>
+ %fref = fir.convert %alloca : (memref<i32>) -> !fir.ref<i32>
+ %decl = fir.declare %fref {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %mref = fir.convert %decl : (!fir.ref<i32>) -> memref<i32>
+ memref.store %c42, %mref[] : memref<i32>
+ cf.cond_br %cond, ^bb1, ^bb2
+^bb1:
+ memref.store %arg0, %mref[] : memref<i32>
+ cf.br ^bb2
+^bb2:
+ %v = memref.load %mref[] : memref<i32>
+ return %v : i32
+}
+
+// -----
+
+// A ref->integer fir.convert is not a memory view; it must block promotion.
+
+// CHECK-LABEL: func.func @convert_to_integer_blocks_promotion(
+// CHECK: %[[ALLOCA:.*]] = fir.alloca i32
+// CHECK: fir.store
+// CHECK: %[[INT:.*]] = fir.convert %[[ALLOCA]] : (!fir.ref<i32>) -> i64
+// CHECK: fir.call @use_addr(%[[INT]])
+func.func private @use_addr(%a: i64)
+func.func @convert_to_integer_blocks_promotion(%arg0: i32) {
+ %alloca = fir.alloca i32
+ fir.store %arg0 to %alloca : !fir.ref<i32>
+ %addr = fir.convert %alloca : (!fir.ref<i32>) -> i64
+ fir.call @use_addr(%addr) : (i64) -> ()
+ return
+}
+
+// -----
+
// Make sure we do not generate fir.declare_value for a replaced value
// fir.declare with dummy_scope. This can result in the declare_value being
// inserted before the dummy_scope it uses as would be the case here.
>From f36f02ed804805d7b8396d9d079d4d4ef468e655 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Thu, 28 May 2026 10:30:24 -0700
Subject: [PATCH 2/3] rebase
---
.../include/flang/Optimizer/Dialect/FIROps.td | 12 ++--
flang/lib/Optimizer/Dialect/FIROps.cpp | 66 +++++++++++++------
2 files changed, 52 insertions(+), 26 deletions(-)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index f36a5e5cc82fe..5a17db1c0ef1e 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3073,9 +3073,11 @@ def fir_ConvertOp
: fir_SimpleOneResultOp<"convert",
[NoMemoryEffect, ViewLikeOpInterface,
ConditionallySpeculatable,
- DeclareOpInterfaceMethods<PromotableOpInterface,
- ["getPromotableSlotView",
- "convertSlotValue"]>,
+ DeclareOpInterfaceMethods<PromotableOpInterface>,
+ DeclareOpInterfaceMethods<
+ PromotableAliaserInterface,
+ ["projectSlotValueToAliasValue",
+ "projectAliasValueToSlotValue"]>,
fir_FortranObjectViewOpInterface]> {
let summary = "encapsulates all Fortran entity type conversions";
@@ -3603,9 +3605,9 @@ def fir_DeclareOp
DeclareOpInterfaceMethods<
fir_FortranVariableStorageOpInterface>,
DeclareOpInterfaceMethods<PromotableOpInterface,
- ["getPromotableSlotView",
- "requiresReplacedValues",
+ ["requiresReplacedValues",
"visitReplacedValues"]>,
+ DeclareOpInterfaceMethods<PromotableAliaserInterface>,
fir_FortranObjectViewOpInterface]> {
let summary = "declare a variable";
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 5013ebd6700dd..6a64b76736cf5 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -2037,31 +2037,52 @@ static bool isAcceptableSlotElemTypePair(mlir::Type srcElem,
return srcBits && dstBits && *srcBits == *dstBits;
}
-std::optional<mlir::PromotableSlotView>
-fir::ConvertOp::getPromotableSlotView() {
- mlir::Type srcElem = slotPointeeElemType(getValue().getType());
+static bool isPromotableSlotAliasConvert(fir::ConvertOp op) {
+ mlir::Type srcElem = slotPointeeElemType(op.getValue().getType());
+ mlir::Type dstElem = slotPointeeElemType(op.getResult().getType());
+ return srcElem && dstElem && isAcceptableSlotElemTypePair(srcElem, dstElem);
+}
+
+void fir::ConvertOp::getPromotableSlotAliases(
+ mlir::OpOperand &aliasedSlotPointerOperand,
+ const mlir::MemorySlot & /*parentSlot*/,
+ llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) {
+ if (aliasedSlotPointerOperand.get() != getValue())
+ return;
+ if (!isPromotableSlotAliasConvert(*this))
+ return;
mlir::Type dstElem = slotPointeeElemType(getResult().getType());
- if (!srcElem || !dstElem || !isAcceptableSlotElemTypePair(srcElem, dstElem))
- return std::nullopt;
- return mlir::PromotableSlotView{getValue(),
- mlir::MemorySlot{getResult(), dstElem}};
+ newMemorySlots.push_back(mlir::MemorySlot{getResult(), dstElem});
}
-mlir::Value fir::ConvertOp::convertSlotValue(mlir::Value value,
- mlir::Type targetType,
- mlir::OpBuilder &builder) {
- if (value.getType() == targetType)
- return value;
- return fir::BitcastOp::create(builder, getLoc(), targetType, value);
+mlir::Value fir::ConvertOp::projectSlotValueToAliasValue(
+ mlir::OpOperand & /*aliasedSlotPointerOperand*/,
+ const mlir::MemorySlot & /*parentSlot*/, const mlir::MemorySlot &aliasSlot,
+ mlir::Value slotValue, mlir::OpBuilder &builder) {
+ if (slotValue.getType() == aliasSlot.elemType)
+ return slotValue;
+ return fir::BitcastOp::create(builder, getLoc(), aliasSlot.elemType,
+ slotValue);
+}
+
+mlir::Value fir::ConvertOp::projectAliasValueToSlotValue(
+ mlir::OpOperand & /*aliasedSlotPointerOperand*/,
+ const mlir::MemorySlot &parentSlot, const mlir::MemorySlot & /*aliasSlot*/,
+ mlir::Value aliasValue, mlir::Value /*reachingDef*/,
+ mlir::OpBuilder &builder) {
+ if (aliasValue.getType() == parentSlot.elemType)
+ return aliasValue;
+ return fir::BitcastOp::create(builder, getLoc(), parentSlot.elemType,
+ aliasValue);
}
bool fir::ConvertOp::canUsesBeRemoved(
const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses,
mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses,
const mlir::DataLayout &dataLayout) {
- // Only participate in promotion when this convert is a slot view (if the
- // address is cast is not used, DCE should have removed it)
- if (!getPromotableSlotView())
+ // Only participate in promotion when this convert is a slot alias (if the
+ // address cast is not used, DCE should have removed it).
+ if (!isPromotableSlotAliasConvert(*this))
return false;
for (mlir::OpOperand &use : getResult().getUses())
newBlockingUses.push_back(&use);
@@ -6115,15 +6136,18 @@ mlir::DeletionKind fir::DeclareOp::removeBlockingUses(
return mlir::DeletionKind::Delete;
}
-std::optional<mlir::PromotableSlotView>
-fir::DeclareOp::getPromotableSlotView() {
+void fir::DeclareOp::getPromotableSlotAliases(
+ mlir::OpOperand &aliasedSlotPointerOperand,
+ const mlir::MemorySlot & /*parentSlot*/,
+ llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) {
+ if (aliasedSlotPointerOperand.get() != getMemref())
+ return;
// fir.declare is a transparent alias of its memref operand at the same
// element type.
mlir::Type aliasElemType = fir::dyn_cast_ptrEleTy(getResult().getType());
if (!aliasElemType)
- return std::nullopt;
- return mlir::PromotableSlotView{getMemref(),
- mlir::MemorySlot{getResult(), aliasElemType}};
+ return;
+ newMemorySlots.push_back(mlir::MemorySlot{getResult(), aliasElemType});
}
bool fir::DeclareOp::requiresReplacedValues() { return true; }
>From 292362dd71f9829e8e86d835d0d4cf82f8505062 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Tue, 16 Jun 2026 03:07:25 -0700
Subject: [PATCH 3/3] update test after #198552
---
flang/test/Fir/mem2reg.mlir | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/test/Fir/mem2reg.mlir b/flang/test/Fir/mem2reg.mlir
index 697324a603285..2aaf856c620d8 100644
--- a/flang/test/Fir/mem2reg.mlir
+++ b/flang/test/Fir/mem2reg.mlir
@@ -303,8 +303,8 @@ func.func @declare_then_convert(%arg0: f32) -> f32 {
// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32
// CHECK-NOT: fir.alloca
// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<f32>)
-// CHECK: fir.declare_value %[[ARG0]] {{.*}} : f32
// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32
+// CHECK: fir.declare_value %[[ARG0]] {{.*}} : f32
// CHECK: %[[F32:.*]] = fir.bitcast %[[I32]] : (i32) -> f32
// CHECK: return %[[F32]] : f32
func.func @convert_then_declare(%arg0: f32) -> f32 {
More information about the flang-commits
mailing list