[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