[flang-commits] [flang] [flang][FIR] allow mem2reg over fir.declare (PR #181848)
via flang-commits
flang-commits at lists.llvm.org
Thu Feb 19 01:16:37 PST 2026
https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/181848
>From 8b64795b156418f429ef221eed1a0aff2334ffd2 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Tue, 17 Feb 2026 07:55:49 -0800
Subject: [PATCH 1/2] [flang][FIR] allow mem2reg over fir.declare
---
.../include/flang/Optimizer/Dialect/FIROps.td | 32 +++++
flang/lib/Optimizer/CodeGen/CodeGen.cpp | 23 +++-
flang/lib/Optimizer/Dialect/FIROps.cpp | 86 ++++++++++++-
.../lib/Optimizer/Transforms/AddDebugInfo.cpp | 98 +++++++++++----
flang/test/Fir/declare_value-codegen.fir | 17 +++
flang/test/Fir/fir-ops.fir | 12 ++
flang/test/Fir/invalid.fir | 8 ++
flang/test/Fir/mem2reg.mlir | 117 ++++++++++++++++++
8 files changed, 363 insertions(+), 30 deletions(-)
create mode 100644 flang/test/Fir/declare_value-codegen.fir
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 02a204c9a6e26..ea7e0c2936672 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3399,6 +3399,9 @@ def fir_DeclareOp
MemoryEffects<[MemAlloc<DebuggingResource>]>,
DeclareOpInterfaceMethods<
fir_FortranVariableStorageOpInterface>,
+ DeclareOpInterfaceMethods<PromotableOpInterface,
+ ["requiresReplacedValues",
+ "visitReplacedValues"]>,
fir_FortranObjectViewOpInterface]> {
let summary = "declare a variable";
@@ -3473,6 +3476,35 @@ def fir_DeclareOp
let hasVerifier = 1;
}
+def fir_DeclareValueOp
+ : fir_Op<"declare_value", [MemoryEffects<[MemAlloc<DebuggingResource>]>]> {
+ let summary = "declare a value";
+
+ let description = [{
+ Tie the properties of a simple scalar Fortran variable to a value.
+ The value must be a scalar integer, real, complex, or logical.
+ This is the value based version of fir.declare. It is used to keep track
+ of variable properties and debug info after mem2reg promotion.
+ }];
+
+ let arguments = (ins AnyType:$value,
+ Optional<fir_DummyScopeType>:$dummy_scope,
+ Builtin_StringAttr:$uniq_name,
+ OptionalAttr<fir_FortranVariableFlagsAttr>:$fortran_attrs,
+ OptionalAttr<cuf_DataAttributeAttr>:$data_attr,
+ OptionalAttr<UI32Attr>:$dummy_arg_no);
+
+ let results = (outs);
+
+ let assemblyFormat = [{
+ $value
+ (`dummy_scope` $dummy_scope^ (`arg` $dummy_arg_no^)?)?
+ attr-dict `:` type($value)
+ }];
+
+ let hasVerifier = 1;
+}
+
def fir_BoxOffsetOp : fir_Op<"box_offset", [NoMemoryEffect]> {
let summary = "Get the address of a field in a fir.ref<fir.box>";
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index f9d469f869619..93f3806b18648 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -237,6 +237,27 @@ struct DeclareOpConversion : public fir::FIROpConversion<fir::cg::XDeclareOp> {
return mlir::success();
}
};
+
+struct DeclareValueOpConversion
+ : public fir::FIROpConversion<fir::DeclareValueOp> {
+public:
+ using FIROpConversion::FIROpConversion;
+ llvm::LogicalResult
+ matchAndRewrite(fir::DeclareValueOp declareOp, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ auto value = adaptor.getOperands()[0];
+ if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(declareOp.getLoc())) {
+ if (auto varAttr =
+ mlir::dyn_cast_or_null<mlir::LLVM::DILocalVariableAttr>(
+ fusedLoc.getMetadata())) {
+ mlir::LLVM::DbgValueOp::create(rewriter, value.getLoc(), value, varAttr,
+ nullptr);
+ }
+ }
+ rewriter.eraseOp(declareOp);
+ return mlir::success();
+ }
+};
} // namespace
namespace {
@@ -4528,7 +4549,7 @@ void fir::populateFIRToLLVMConversionPatterns(
BoxTypeCodeOpConversion, BoxTypeDescOpConversion, CallOpConversion,
CmpcOpConversion, VolatileCastOpConversion, ConvertOpConversion,
CoordinateOpConversion, CopyOpConversion, DTEntryOpConversion,
- DeclareOpConversion,
+ DeclareOpConversion, DeclareValueOpConversion,
DoConcurrentSpecifierOpConversion<fir::LocalitySpecifierOp>,
DoConcurrentSpecifierOpConversion<fir::DeclareReductionOp>,
DivcOpConversion, EmboxOpConversion, EmboxCharOpConversion,
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 9c22b614d0cf9..03deba420ec4c 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -227,7 +227,25 @@ mlir::Value fir::AllocaOp::getDefaultValue(const mlir::MemorySlot &slot,
void fir::AllocaOp::handleBlockArgument(const mlir::MemorySlot &slot,
mlir::BlockArgument argument,
- mlir::OpBuilder &builder) {}
+ mlir::OpBuilder &builder) {
+ // When there is a fir.declare, fir.debug_value must be emitted at each value
+ // change and at each beginning of a block where the reaching value is
+ // propagated as a block argument.
+ // TODO: in order to get proper inter-dialect mem2reg, the
+ // PromotableOpInterface should be provided with a
+ // requiresInsertedBlockArguments similar to requiresReplacedValues so that
+ // fir::DeclareOp can be the one dictating that this needs to happen instead
+ // of the allocation. There are other challenges to inter dialect mem2reg to
+ // solve first, like having a common concept for going through converts and
+ // no-ops like fir.declare (i.e., to replace the FIR specific
+ // isSlotOrDeclaredSlot).
+ for (mlir::Operation *user : getOperation()->getUsers())
+ if (auto declareOp = mlir::dyn_cast<fir::DeclareOp>(user))
+ fir::DeclareValueOp::create(
+ builder, declareOp.getLoc(), argument, declareOp.getDummyScope(),
+ declareOp.getUniqNameAttr(), declareOp.getFortranAttrsAttr(),
+ declareOp.getDataAttrAttr(), declareOp.getDummyArgNoAttr());
+}
std::optional<mlir::PromotableAllocationOpInterface>
fir::AllocaOp::handlePromotionComplete(const mlir::MemorySlot &slot,
@@ -2961,8 +2979,17 @@ 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 getMemref() == slot.ptr;
+ return isSlotOrDeclaredSlot(getMemref(), slot);
}
bool fir::LoadOp::storesTo(const mlir::MemorySlot &slot) { return false; }
@@ -2982,7 +3009,7 @@ bool fir::LoadOp::canUsesBeRemoved(
if (blockingUses.size() != 1)
return false;
mlir::Value blockingUse = (*blockingUses.begin())->get();
- return blockingUse == slot.ptr && getMemref() == slot.ptr;
+ return isSlotOrDeclaredSlot(blockingUse, slot) && getMemref() == blockingUse;
}
mlir::DeletionKind fir::LoadOp::removeBlockingUses(
@@ -4397,7 +4424,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 getMemref() == slot.ptr;
+ return isSlotOrDeclaredSlot(getMemref(), slot);
}
mlir::Value fir::StoreOp::getStored(const mlir::MemorySlot &slot,
@@ -4415,8 +4442,8 @@ bool fir::StoreOp::canUsesBeRemoved(
if (blockingUses.size() != 1)
return false;
mlir::Value blockingUse = (*blockingUses.begin())->get();
- return blockingUse == slot.ptr && getMemref() == slot.ptr &&
- getValue() != slot.ptr;
+ return isSlotOrDeclaredSlot(blockingUse, slot) &&
+ getMemref() == blockingUse && getValue() != blockingUse;
}
mlir::DeletionKind fir::StoreOp::removeBlockingUses(
@@ -5247,6 +5274,22 @@ std::optional<int64_t> fir::getAllocaByteSize(fir::AllocaOp alloca,
return std::nullopt;
}
+//===----------------------------------------------------------------------===//
+// DeclareValueOp
+//===----------------------------------------------------------------------===//
+
+static bool isLegalTypeForValueDeclare(mlir::Type type) {
+ return mlir::isa<mlir::IntegerType, mlir::FloatType, mlir::ComplexType,
+ fir::LogicalType>(type);
+}
+
+llvm::LogicalResult fir::DeclareValueOp::verify() {
+ if (!isLegalTypeForValueDeclare(getValue().getType()))
+ return emitOpError(
+ "value must be a simple scalar (integer, real, complex, or logical)");
+ return mlir::success();
+}
+
//===----------------------------------------------------------------------===//
// DeclareOp
//===----------------------------------------------------------------------===//
@@ -5257,6 +5300,37 @@ llvm::LogicalResult fir::DeclareOp::verify() {
return fortranVar.verifyDeclareLikeOpImpl(getMemref());
}
+bool fir::DeclareOp::canUsesBeRemoved(
+ const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses,
+ mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses,
+ const mlir::DataLayout &dataLayout) {
+ if (!isLegalTypeForValueDeclare(fir::unwrapRefType(getType())))
+ return false;
+ // Forward uses to the users of the fir.declare.
+ for (mlir::OpOperand &use : getResult().getUses())
+ newBlockingUses.push_back(&use);
+ return true;
+}
+
+mlir::DeletionKind fir::DeclareOp::removeBlockingUses(
+ const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses,
+ mlir::OpBuilder &builder) {
+ return mlir::DeletionKind::Delete;
+}
+
+bool fir::DeclareOp::requiresReplacedValues() { return true; }
+
+void fir::DeclareOp::visitReplacedValues(
+ llvm::ArrayRef<std::pair<mlir::Operation *, mlir::Value>> definitions,
+ mlir::OpBuilder &builder) {
+ for (auto [op, value] : definitions) {
+ builder.setInsertionPointAfter(op);
+ fir::DeclareValueOp::create(builder, getLoc(), value, getDummyScope(),
+ getUniqNameAttr(), getFortranAttrsAttr(),
+ getDataAttrAttr(), getDummyArgNoAttr());
+ }
+}
+
//===----------------------------------------------------------------------===//
// PackArrayOp
//===----------------------------------------------------------------------===//
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 35d8a2f6c3aa9..42080dacc731a 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -54,6 +54,12 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
mlir::LLVM::DIScopeAttr scopeAttr,
fir::DebugTypeGenerator &typeGen,
mlir::SymbolTable *symbolTable, mlir::Value dummyScope);
+ void handleDeclareValueOp(fir::DeclareValueOp declOp,
+ mlir::LLVM::DIFileAttr fileAttr,
+ mlir::LLVM::DIScopeAttr scopeAttr,
+ fir::DebugTypeGenerator &typeGen,
+ mlir::SymbolTable *symbolTable,
+ mlir::Value dummyScope);
public:
AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {}
@@ -112,6 +118,14 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
mlir::LLVM::DIFileAttr fileAttr,
mlir::LLVM::DIScopeAttr scope);
+
+ template <typename Op>
+ void handleLocalVariable(Op declOp, llvm::StringRef name,
+ mlir::LLVM::DIFileAttr fileAttr,
+ mlir::LLVM::DIScopeAttr scopeAttr,
+ fir::DebugTypeGenerator &typeGen,
+ mlir::Value dummyScope, mlir::Type typeToConvert,
+ fir::cg::XDeclareOp typeGenDeclOp);
};
bool debugInfoIsAlreadySet(mlir::Location loc) {
@@ -266,14 +280,40 @@ bool AddDebugInfoPass::createCommonBlockGlobal(
return true;
}
+template <typename Op>
+void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
+ mlir::LLVM::DIFileAttr fileAttr,
+ mlir::LLVM::DIScopeAttr scopeAttr,
+ fir::DebugTypeGenerator &typeGen,
+ mlir::Value dummyScope,
+ mlir::Type typeToConvert,
+ fir::cg::XDeclareOp typeGenDeclOp) {
+ mlir::MLIRContext *context = &getContext();
+ mlir::OpBuilder builder(context);
+
+ // Get the dummy argument position from the explicit attribute.
+ unsigned argNo = 0;
+ if (dummyScope && declOp.getDummyScope() == dummyScope) {
+ if (auto argNoOpt = declOp.getDummyArgNo())
+ argNo = *argNoOpt;
+ }
+
+ auto tyAttr =
+ typeGen.convertType(typeToConvert, fileAttr, scopeAttr, typeGenDeclOp);
+
+ auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
+ context, scopeAttr, mlir::StringAttr::get(context, name), fileAttr,
+ getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, tyAttr,
+ mlir::LLVM::DIFlags::Zero);
+ declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
+}
+
void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
mlir::LLVM::DIFileAttr fileAttr,
mlir::LLVM::DIScopeAttr scopeAttr,
fir::DebugTypeGenerator &typeGen,
mlir::SymbolTable *symbolTable,
mlir::Value dummyScope) {
- mlir::MLIRContext *context = &getContext();
- mlir::OpBuilder builder(context);
auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
if (result.first != fir::NameUniquer::NameKind::VARIABLE)
@@ -293,21 +333,23 @@ void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
}
}
- // Get the dummy argument position from the explicit attribute.
- unsigned argNo = 0;
- if (dummyScope && declOp.getDummyScope() == dummyScope) {
- if (auto argNoOpt = declOp.getDummyArgNo())
- argNo = *argNoOpt;
- }
+ handleLocalVariable(declOp, result.second.name, fileAttr, scopeAttr, typeGen,
+ dummyScope, fir::unwrapRefType(declOp.getType()), declOp);
+}
- auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()),
- fileAttr, scopeAttr, declOp);
+void AddDebugInfoPass::handleDeclareValueOp(fir::DeclareValueOp declOp,
+ mlir::LLVM::DIFileAttr fileAttr,
+ mlir::LLVM::DIScopeAttr scopeAttr,
+ fir::DebugTypeGenerator &typeGen,
+ mlir::SymbolTable *symbolTable,
+ mlir::Value dummyScope) {
+ auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
- auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
- context, scopeAttr, mlir::StringAttr::get(context, result.second.name),
- fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0,
- tyAttr, mlir::LLVM::DIFlags::Zero);
- declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
+ if (result.first != fir::NameUniquer::NameKind::VARIABLE)
+ return;
+
+ handleLocalVariable(declOp, result.second.name, fileAttr, scopeAttr, typeGen,
+ dummyScope, declOp.getValue().getType(), nullptr);
}
mlir::LLVM::DICommonBlockAttr AddDebugInfoPass::getOrCreateCommonBlockAttr(
@@ -428,6 +470,18 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
globalOp->setLoc(builder.getFusedLoc({globalOp.getLoc()}, arrayAttr));
}
+static mlir::LLVM::DISubprogramAttr
+getScope(mlir::Operation *op, mlir::LLVM::DISubprogramAttr defaultScope) {
+ if (auto tOp = op->getParentOfType<mlir::omp::TargetOp>()) {
+ if (auto fusedLoc = llvm::dyn_cast<mlir::FusedLoc>(tOp.getLoc())) {
+ if (auto sp = llvm::dyn_cast<mlir::LLVM::DISubprogramAttr>(
+ fusedLoc.getMetadata()))
+ return sp;
+ }
+ }
+ return defaultScope;
+}
+
void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
mlir::LLVM::DIFileAttr fileAttr,
mlir::LLVM::DICompileUnitAttr cuAttr,
@@ -687,16 +741,14 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
});
funcOp.walk([&](fir::cg::XDeclareOp declOp) {
- mlir::LLVM::DISubprogramAttr spTy = spAttr;
- if (auto tOp = declOp->getParentOfType<mlir::omp::TargetOp>()) {
- if (auto fusedLoc = llvm::dyn_cast<mlir::FusedLoc>(tOp.getLoc())) {
- if (auto sp = llvm::dyn_cast<mlir::LLVM::DISubprogramAttr>(
- fusedLoc.getMetadata()))
- spTy = sp;
- }
- }
+ mlir::LLVM::DISubprogramAttr spTy = getScope(declOp, spAttr);
handleDeclareOp(declOp, fileAttr, spTy, typeGen, symbolTable, dummyScope);
});
+ funcOp.walk([&](fir::DeclareValueOp declOp) {
+ mlir::LLVM::DISubprogramAttr spTy = getScope(declOp, spAttr);
+ handleDeclareValueOp(declOp, fileAttr, spTy, typeGen, symbolTable,
+ dummyScope);
+ });
// commonBlockMap ensures that we don't create multiple DICommonBlockAttr of
// the same name in one function. But it is ok (rather required) to create
// them in different functions if common block of the same name has been used
diff --git a/flang/test/Fir/declare_value-codegen.fir b/flang/test/Fir/declare_value-codegen.fir
new file mode 100644
index 0000000000000..4b50c6f535c1f
--- /dev/null
+++ b/flang/test/Fir/declare_value-codegen.fir
@@ -0,0 +1,17 @@
+// RUN: fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s | FileCheck %s
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
+ func.func @test_codegen(%arg0: i32) {
+ fir.declare_value %arg0 {uniq_name = "test"} : i32 loc(#loc1)
+ return
+ }
+}
+
+#di_file = #llvm.di_file<"test.f90" in "">
+#di_compile_unit = #llvm.di_compile_unit<id = distinct[0]<>, sourceLanguage = DW_LANG_Fortran95, file = #di_file, producer = "Flang", isOptimized = false, emissionKind = Full>
+#di_subprogram = #llvm.di_subprogram<compileUnit = #di_compile_unit, scope = #di_file, name = "test_codegen", file = #di_file, subprogramFlags = Definition>
+#di_local_variable = #llvm.di_local_variable<scope = #di_subprogram, name = "test", file = #di_file, line = 1, arg = 1, alignInBits = 0, type = #llvm.di_basic_type<tag = DW_TAG_base_type, name = "integer", sizeInBits = 32, encoding = DW_ATE_signed>>
+#loc1 = loc(fused<#di_local_variable>["test.f90":1:1])
+
+// CHECK-LABEL: llvm.func @test_codegen
+// CHECK: llvm.intr.dbg.value
diff --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
index 8336b6d89e721..51c064290de35 100644
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -1048,3 +1048,15 @@ func.func @test_if_weights(%cond: i1) {
}
return
}
+
+// CHECK-LABEL: func @test_declare_value(
+// CHECK-SAME: %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: !fir.dscope) {
+func.func @test_declare_value(%arg0: i32, %scope: !fir.dscope) {
+// CHECK: fir.declare_value %[[VAL_0]] {uniq_name = "test"} : i32
+ fir.declare_value %arg0 {uniq_name = "test"} : i32
+// CHECK: fir.declare_value %[[VAL_0]] dummy_scope %[[VAL_1]] {uniq_name = "test"} : i32
+ fir.declare_value %arg0 dummy_scope %scope {uniq_name = "test"} : i32
+// CHECK: fir.declare_value %[[VAL_0]] dummy_scope %[[VAL_1]] arg 1 {uniq_name = "test"} : i32
+ fir.declare_value %arg0 dummy_scope %scope arg 1 {uniq_name = "test"} : i32
+ return
+}
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index 553f69ccf83fd..e37e8ec77fcb7 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -1483,3 +1483,11 @@ func.func @fir_declare_bad_storage_offset(%arg0: !fir.ref<i8>, %arg1: !fir.ref<!
%decl = fir.declare %arg0 storage (%arg1[2]) {uniq_name = "a"} : (!fir.ref<i8>, !fir.ref<!fir.array<1xi8>>) -> !fir.ref<i8>
return
}
+
+// -----
+
+func.func @bad_declare_value(%arg0: !fir.ref<i32>) {
+ // expected-error at +1 {{'fir.declare_value' op value must be a simple scalar (integer, real, complex, or logical)}}
+ fir.declare_value %arg0 {uniq_name = "test"} : !fir.ref<i32>
+ return
+}
diff --git a/flang/test/Fir/mem2reg.mlir b/flang/test/Fir/mem2reg.mlir
index 25d114a55e1a4..0329dfc6f26d2 100644
--- a/flang/test/Fir/mem2reg.mlir
+++ b/flang/test/Fir/mem2reg.mlir
@@ -66,3 +66,120 @@ func.func @cycle(%arg0: i64, %arg1: i1, %arg2: i64) {
^bb2:
cf.br ^bb1
}
+
+// -----
+
+// CHECK-LABEL: func.func @test_simple_declare(%arg0: !fir.ref<i32> {fir.bindc_name = "i"}) {
+// CHECK: %[[C42:.*]] = arith.constant 42 : i32
+// CHECK: %[[SCOPE:.*]] = fir.dummy_scope : !fir.dscope
+// CHECK: %[[ARG_DECL:.*]] = fir.declare %arg0 dummy_scope %[[SCOPE]] arg 1 {uniq_name = "_QFfooEi"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+// CHECK: fir.declare_value %[[C42]] {uniq_name = "_QFfooEj"} : i32
+// CHECK: fir.store %[[C42]] to %[[ARG_DECL]] : !fir.ref<i32>
+func.func @test_simple_declare(%arg0: !fir.ref<i32> {fir.bindc_name = "i"}) {
+ %c42_i32 = arith.constant 42 : i32
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.declare %arg0 dummy_scope %0 arg 1 {uniq_name = "_QFfooEi"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+ %2 = fir.alloca i32 {bindc_name = "j", uniq_name = "_QFfooEj"}
+ %3 = fir.declare %2 {uniq_name = "_QFfooEj"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.store %c42_i32 to %3 : !fir.ref<i32>
+ %4 = fir.load %3 : !fir.ref<i32>
+ fir.store %4 to %1 : !fir.ref<i32>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_two_values(
+// CHECK: %[[CONSTANT_0:.*]] = arith.constant 43 : i32
+// CHECK: %[[CONSTANT_1:.*]] = arith.constant 42 : i32
+// CHECK: fir.declare_value %[[CONSTANT_1]] {uniq_name = "_QFfooEjlocal"} : i32
+// CHECK: fir.store %[[CONSTANT_1]] to %{{.*}} : !fir.ref<i32>
+// CHECK: fir.declare_value %[[CONSTANT_0]] {uniq_name = "_QFfooEjlocal"} : i32
+// CHECK: fir.store %[[CONSTANT_0]] to %{{.*}} : !fir.ref<i32>
+
+func.func @test_two_values(%arg0: !fir.ref<i32> {fir.bindc_name = "i"}, %arg1: !fir.ref<i32> {fir.bindc_name = "j"}) {
+ %c43_i32 = arith.constant 43 : i32
+ %c42_i32 = arith.constant 42 : i32
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.declare %arg0 dummy_scope %0 arg 1 {uniq_name = "_QFfooEi"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+ %2 = fir.declare %arg1 dummy_scope %0 arg 2 {uniq_name = "_QFfooEj"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+ %3 = fir.alloca i32 {bindc_name = "jlocal", uniq_name = "_QFfooEjlocal"}
+ %4 = fir.declare %3 {uniq_name = "_QFfooEjlocal"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.store %c42_i32 to %4 : !fir.ref<i32>
+ %5 = fir.load %4 : !fir.ref<i32>
+ fir.store %5 to %1 : !fir.ref<i32>
+ fir.store %c43_i32 to %4 : !fir.ref<i32>
+ %6 = fir.load %4 : !fir.ref<i32>
+ fir.store %6 to %2 : !fir.ref<i32>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @array_val_not_mem2reg(
+// CHECK: fir.alloca !fir.array<2xi32>
+// CHECK: fir.store
+// CHECK: fir.load
+// CHECK: fir.store
+
+func.func @array_val_not_mem2reg(%arg0: !fir.ref<!fir.array<2xi32>> {fir.bindc_name = "i"}, %arg1: !fir.ref<i32> {fir.bindc_name = "j"}, %arrayval : !fir.array<2xi32>) {
+ %c2 = arith.constant 2 : index
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.shape %c2 : (index) -> !fir.shape<1>
+ %2 = fir.declare %arg0(%1) dummy_scope %0 arg 1 {uniq_name = "_QFarrayEi"} : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<2xi32>>
+ %3 = fir.declare %arg1 dummy_scope %0 arg 2 {uniq_name = "_QFarrayEj"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+ %4 = fir.alloca !fir.array<2xi32> {bindc_name = "jlocal", uniq_name = "_QFarrayEjlocal"}
+ %5 = fir.declare %4(%1) {uniq_name = "_QFarrayEjlocal"} : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.ref<!fir.array<2xi32>>
+ fir.store %arrayval to %5 : !fir.ref<!fir.array<2xi32>>
+ %val_load = fir.load %5 : !fir.ref<!fir.array<2xi32>>
+ fir.store %val_load to %2 : !fir.ref<!fir.array<2xi32>>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @box_not_mem2reg(
+// CHECK: fir.alloca !fir.box<f32>
+// CHECK: fir.store
+// CHECK: fir.load
+// CHECK: fir.store
+
+func.func @box_not_mem2reg(%arg0: !fir.ref<!fir.box<f32>> {fir.bindc_name = "i"}, %arg1: !fir.ref<i32> {fir.bindc_name = "j"}, %arrayval : !fir.box<f32>) {
+ %0 = fir.dummy_scope : !fir.dscope
+ %2 = fir.declare %arg0 dummy_scope %0 arg 1 {uniq_name = "_QFarrayEi"} : (!fir.ref<!fir.box<f32>>, !fir.dscope) -> !fir.ref<!fir.box<f32>>
+ %3 = fir.declare %arg1 dummy_scope %0 arg 2 {uniq_name = "_QFarrayEj"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+ %4 = fir.alloca !fir.box<f32> {bindc_name = "jlocal", uniq_name = "_QFarrayEjlocal"}
+ %5 = fir.declare %4 {uniq_name = "_QFarrayEjlocal"} : (!fir.ref<!fir.box<f32>>) -> !fir.ref<!fir.box<f32>>
+ fir.store %arrayval to %5 : !fir.ref<!fir.box<f32>>
+ %val_load = fir.load %5 : !fir.ref<!fir.box<f32>>
+ fir.store %val_load to %2 : !fir.ref<!fir.box<f32>>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @block_argument_value(
+// CHECK-SAME: %[[ARG0:.*]]: i32,
+// CHECK-SAME: %[[ARG1:.*]]: i1) -> i32 {
+// CHECK: %[[CONSTANT_0:.*]] = arith.constant 42 : i32
+// CHECK: fir.declare_value %[[CONSTANT_0]] {uniq_name = "_QFfooEjlocal"} : i32
+// CHECK: llvm.cond_br %[[ARG1]], ^bb1, ^bb2
+// CHECK: ^bb1:
+// CHECK: fir.declare_value %[[ARG0]] {uniq_name = "_QFfooEjlocal"} : i32
+// CHECK: llvm.br ^bb2
+// CHECK: ^bb2:
+// CHECK: return %[[CONSTANT_0]] : i32
+// CHECK: }
+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"}
+ %4 = fir.declare %3 {uniq_name = "_QFfooEjlocal"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.store %c42_i32 to %4 : !fir.ref<i32>
+ llvm.cond_br %cdt, ^bb1, ^bb2
+^bb1:
+ fir.store %arg0 to %4 : !fir.ref<i32>
+ llvm.br ^bb2
+^bb2:
+ %6 = fir.load %4 : !fir.ref<i32>
+ return %6 : i32
+}
>From 34acd4879072eddd40c12eb9474d82b73cb4e1a5 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Thu, 19 Feb 2026 01:15:25 -0800
Subject: [PATCH 2/2] add comment about fir.declare_value placements
---
flang/include/flang/Optimizer/Dialect/FIROps.td | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index ea7e0c2936672..142bc98c134f5 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3485,6 +3485,13 @@ def fir_DeclareValueOp
The value must be a scalar integer, real, complex, or logical.
This is the value based version of fir.declare. It is used to keep track
of variable properties and debug info after mem2reg promotion.
+
+ Note that there is currently nothing that allows pinning this operation
+ relatively to the operation using the value of the variable,
+ this means that the when a mem2reg variable was assigned several
+ values, the variable value printed in the debugger at a given code
+ point main not be the actual value the variable had at that code point
+ if instructions using the value were moved above the fir.declare_value.
}];
let arguments = (ins AnyType:$value,
More information about the flang-commits
mailing list