[flang-commits] [flang] [flang][debug] Keep the bounds of variables in stack slots at -O0. (PR #229876)

Abid Qadeer via flang-commits flang-commits at lists.llvm.org
Tue Oct 13 02:57:15 PDT 2026


https://github.com/abidh updated https://github.com/llvm/llvm-project/pull/229876

>From afe8b96552853c074b49d801c9b9dc8887fa6b9e Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 6 Oct 2026 20:11:32 +0100
Subject: [PATCH 1/4] [flang][debug] Keep the bounds of variables in stack
 slots at -O0.

flang uses hidden variables to carry the bounds and length parameters of an
array or character in the debug info. The fake_use thing was added to keep
them alive throughout the function. It works in many but not all cases, and
the ones it does not cover take a number of different forms. Most of them
show up on GPU, but several can be reproduced on the host as well, two of
which are https://github.com/llvm/llvm-project/issues/229434 and the GDB
fortran test gdb.fortran/mixed-lang-stack.exp.

For VLA, clang emits a stack slot to carry each dimension and points the
DISubrange at a dbg.declare on that slot. The optimizer promotes the slot
away, so it is at -O0 that it survives and gives the dimension a location
valid throughout the function. This PR mirrors that approach. At -O0,
lowering stores every bound and length parameter that is not the same
everywhere into a stack slot of its own, marked with fir.debug_bound_slot,
immediately before the declaration that uses it. AddDebugInfo then describes
the slot with a fircg.ext_declare that CodeGen lowers to a dbg.declare,
rather than describing the value with a dbg.value.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Lower/LoweringOptions.def |  5 +
 .../flang/Optimizer/Dialect/FIROpsSupport.h   |  6 ++
 .../Optimizer/Transforms/DebugTypeGenerator.h |  4 +
 flang/lib/Frontend/CompilerInvocation.cpp     |  2 +
 flang/lib/Lower/ConvertVariable.cpp           | 53 +++++++++++
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp |  5 +-
 .../Transforms/DebugTypeGenerator.cpp         | 42 ++++++++-
 .../Integration/debug-bounds-stack-slot.f90   | 39 ++++++++
 flang/test/Lower/HLFIR/trim.f90               |  1 +
 flang/test/Lower/Intrinsics/rename.f90        | 12 ++-
 .../target-private-multiple-variables.f90     |  2 +-
 .../OpenMP/compound-version-override.f90      |  5 +-
 .../OpenMP/dynamic-len-char-bounds-gen.f90    |  2 +-
 .../Lower/OpenMP/optional-argument-map-2.f90  |  2 +-
 flang/test/Lower/bounds-in-stack-slots.f90    | 88 ++++++++++++++++++
 .../Transforms/debug-bounds-stack-slot.fir    | 92 +++++++++++++++++++
 16 files changed, 344 insertions(+), 16 deletions(-)
 create mode 100644 flang/test/Integration/debug-bounds-stack-slot.f90
 create mode 100644 flang/test/Lower/bounds-in-stack-slots.f90
 create mode 100644 flang/test/Transforms/debug-bounds-stack-slot.fir

diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def
index 4590df1c8a6f8..f187ac654d663 100644
--- a/flang/include/flang/Lower/LoweringOptions.def
+++ b/flang/include/flang/Lower/LoweringOptions.def
@@ -89,6 +89,11 @@ ENUM_LOWERINGOPT(RegisterMLIRDiagnosticsHandler, unsigned, 1, 1)
 /// LineTablesOnly. Off by default.
 ENUM_LOWERINGOPT(PreserveUseDebugInfo, unsigned, 1, 0)
 
+/// If true, keep the non-constant bounds and length parameters of a variable
+/// in stack slots, so that the debug info can describe them wherever the
+/// variable is visible. Off by default.
+ENUM_LOWERINGOPT(BoundsInStackSlots, unsigned, 1, 0)
+
 /// FP max/min behavior for max/min intrinsics and [max|min][loc|val] (Legacy,
 /// Portable, Extremum, ExtremeNum). Default: Legacy.
 ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
index d5131500ecbbd..9fe37331f5ec1 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
@@ -163,6 +163,12 @@ static constexpr llvm::StringRef getAccessGroupsAttrName() {
   return "access_groups";
 }
 
+/// Attribute to mark an alloca that lowering creates to hold a bound or
+/// length parameter of a variable.
+static constexpr llvm::StringRef getDebugBoundSlotAttrName() {
+  return "fir.debug_bound_slot";
+}
+
 /// Attribute holding the unique name of the Fortran entity an allocation
 /// belongs to. It is an inherent attribute of the FIR allocation operations,
 /// and may also be carried by allocation operations of other dialects that
diff --git a/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h b/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
index 155b41f688579..8062c4e9c409a 100644
--- a/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
+++ b/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
@@ -22,6 +22,10 @@
 
 namespace fir {
 
+/// Return the stack slot that lowering created to hold \p val, if there is one
+/// and it still holds \p val.
+fir::AllocaOp getDebugBoundSlot(mlir::Value val);
+
 /// Special cache to deal with the fact that mlir::LLVM::DITypeAttr for
 /// derived types may only be valid in specific nesting contexts in presence
 /// of derived type recursion and cannot be cached for the whole compilation.
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index 13794f5720e74..b09bb0054cc61 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -2149,6 +2149,8 @@ void CompilerInvocation::setLoweringOptions() {
 
   // Lower TRANSPOSE as a runtime call under -O0.
   loweringOpts.setOptimizeTranspose(codegenOpts.OptimizationLevel > 0);
+  // Keep the bounds of variables in memory under -O0 for the debugger.
+  loweringOpts.setBoundsInStackSlots(codegenOpts.OptimizationLevel == 0);
   loweringOpts.setUnderscoring(codegenOpts.Underscoring);
   loweringOpts.setSkipExternalRttiDefinition(skipExternalRttiDefinition);
 
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 59f46a31256fe..633df9009bde8 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -37,6 +37,7 @@
 #include "flang/Optimizer/Dialect/CUF/CUFOps.h"
 #include "flang/Optimizer/Dialect/FIRAttr.h"
 #include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Dialect/FIRType.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
@@ -2571,6 +2572,55 @@ isCapturedInInternalProcedure(Fortran::lower::AbstractConverter &converter,
   return false;
 }
 
+/// Whether \p val is the same wherever it is read, so that the debug info can
+/// describe it directly and it does not need a slot.
+static bool isInvariantBound(mlir::Value val) {
+  mlir::Operation *op = val.getDefiningOp();
+  // A block argument, which for a bound means a dummy, varies between calls.
+  if (!op)
+    return false;
+  if (op->hasTrait<mlir::OpTrait::ConstantLike>())
+    return true;
+  // The assumed-size sentinel is fixed at compile time, and the debug info
+  // leaves it out rather than describing it as an extent.
+  if (mlir::isa<fir::AssumedSizeExtentOp>(op))
+    return true;
+  // A constant bound is usually folded before lowering, but a dimension of an
+  // array that has a non-constant dimension too still reaches here so look
+  // through the computation.
+  return !op->getOperands().empty() && mlir::isPure(op) &&
+         op->getNumRegions() == 0 &&
+         llvm::all_of(op->getOperands(), isInvariantBound);
+}
+
+/// Under the BoundsInStackSlots lowering option, store the non-constant bounds
+/// and length parameters of the variable declared by \p declare in stack slots
+/// that nothing else accesses.
+static void genBoundsInStackSlots(Fortran::lower::AbstractConverter &converter,
+                                  hlfir::DeclareOp declare) {
+  if (!converter.getLoweringOptions().getBoundsInStackSlots())
+    return;
+  llvm::SmallVector<mlir::Value> bounds(declare.getTypeparams());
+  if (mlir::Value shape = declare.getShape())
+    if (mlir::Operation *shapeOp = shape.getDefiningOp())
+      llvm::append_range(bounds, shapeOp->getOperands());
+  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+  mlir::NamedAttribute slotAttr(
+      builder.getStringAttr(fir::getDebugBoundSlotAttrName()),
+      builder.getUnitAttr());
+  // Neither the slot nor the store is something the user wrote, so keep them
+  // out of the line table.
+  mlir::Location loc = builder.getUnknownLoc();
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPoint(declare);
+  for (mlir::Value bound : bounds) {
+    if (isInvariantBound(bound))
+      continue;
+    mlir::Value slot = builder.createTemporary(loc, bound.getType(), slotAttr);
+    fir::StoreOp::create(builder, loc, bound, slot);
+  }
+}
+
 /// Map a symbol to its FIR address and evaluated specification expressions.
 /// Not for symbols lowered to fir.box.
 /// Will optionally create fir.declare.
@@ -2666,6 +2716,7 @@ static void genDeclareSymbol(Fortran::lower::AbstractConverter &converter,
     auto newBase = hlfir::DeclareOp::create(
         builder, loc, base, name, shapeOrShift, lenParams, dummyScope, storage,
         storageOffset, attributes, dataAttr, argNo);
+    genBoundsInStackSlots(converter, newBase);
     symMap.addVariableDefinition(sym, newBase, force);
     return;
   }
@@ -2728,6 +2779,8 @@ void Fortran::lower::genDeclareSymbol(
     hlfir::EntityWithAttributes declare =
         hlfir::genDeclare(loc, builder, base, name, attributes, dummyScope,
                           storage, storageOffset, dataAttr, argNo);
+    if (auto declareOp = declare.getDefiningOp<hlfir::DeclareOp>())
+      genBoundsInStackSlots(converter, declareOp);
     symMap.addVariableDefinition(sym, declare.getIfVariableInterface(), force);
     return;
   }
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 64cef884a5275..32c748b7ad298 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -386,7 +386,8 @@ bool AddDebugInfoPass::createCommonBlockGlobal(
 
 // Create fake uses for compiler-generated internal variables that represent
 // values needed by a debugger.  This prevents values from being optimized out
-// such as the count and lower bound of dynamic arrays.
+// such as the count and lower bound of dynamic arrays. A value kept in a stack
+// slot does not need one, since the debug info describes the slot.
 template <typename Op>
 static void InsertFakeUseForDebugVar(mlir::OpBuilder &builder, Op declOp,
                                      mlir::Value var) {
@@ -397,7 +398,7 @@ static void InsertFakeUseForDebugVar(mlir::OpBuilder &builder, Op declOp,
                 mlir::dyn_cast<mlir::func::ReturnOp>(block.getTerminator())) {
           mlir::OpBuilder::InsertionGuard guard(builder);
           builder.setInsertionPoint(returnOp);
-          if (!fir::getIntIfConstant(var))
+          if (!fir::getIntIfConstant(var) && !fir::getDebugBoundSlot(var))
             fir::FakeUseOp::create(builder, declOp.getLoc(), var);
         }
       }
diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index 43f3898043dda..c63a3dacdc3e0 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -14,6 +14,7 @@
 
 #include "flang/Optimizer/Transforms/DebugTypeGenerator.h"
 #include "flang/Optimizer/CodeGen/DescriptorModel.h"
+#include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Support/InternalNames.h"
 #include "flang/Optimizer/Support/Utils.h"
 #include "mlir/Pass/Pass.h"
@@ -91,8 +92,28 @@ static mlir::LLVM::DITypeAttr genPlaceholderType(mlir::MLIRContext *context) {
                       /*bitSize=*/32, llvm::dwarf::DW_ATE_signed);
 }
 
+fir::AllocaOp getDebugBoundSlot(mlir::Value val) {
+  for (mlir::Operation *user : val.getUsers()) {
+    auto store = mlir::dyn_cast<fir::StoreOp>(user);
+    if (!store || store.getValue() != val)
+      continue;
+    auto slot = store.getMemref().getDefiningOp<fir::AllocaOp>();
+    if (!slot || !slot->hasAttr(fir::getDebugBoundSlotAttrName()))
+      continue;
+    // Slot users should either be above mentioned store or a XDeclareOp which
+    // gets generated in generateArtificialVariable.
+    if (llvm::all_of(slot->getUsers(), [&](mlir::Operation *slotUser) {
+          return slotUser == store || mlir::isa<fir::cg::XDeclareOp>(slotUser);
+        }))
+      return slot;
+  }
+  return {};
+}
+
 // Helper function to create DILocalVariableAttr and DbgValueOp when information
-// about the size or dimension of a variable etc lives in an mlir::Value.
+// about the size or dimension of a variable etc lives in an mlir::Value. If the
+// value is kept in a stack slot, the variable describes the slot instead, which
+// holds the value wherever the variable is visible.
 mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
     mlir::MLIRContext *context, mlir::Value val,
     mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope,
@@ -107,8 +128,10 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
   auto name = mlir::StringAttr::get(context, "." + declOp.getUniqName().str() +
                                                  std::to_string(varID));
   builder.setInsertionPoint(declOp);
-  mlir::Type type = val.getType();
-  if (!mlir::isa<mlir::IntegerType>(type) || !type.isSignlessInteger()) {
+  fir::AllocaOp slot = getDebugBoundSlot(val);
+  mlir::Type type = slot ? slot.getInType() : val.getType();
+  if (!slot &&
+      (!mlir::isa<mlir::IntegerType>(type) || !type.isSignlessInteger())) {
     type = builder.getIntegerType(64);
     val = fir::ConvertOp::create(builder, declOp.getLoc(), type, val);
   }
@@ -116,8 +139,17 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
   auto lvAttr = mlir::LLVM::DILocalVariableAttr::get(
       context, scope, name, fileAttr, /*line=*/0, /*argNo=*/0,
       /*alignInBits=*/0, Ty, mlir::LLVM::DIFlags::Artificial);
-  mlir::LLVM::DbgValueOp::create(builder, declOp.getLoc(), val, lvAttr,
-                                 nullptr);
+  if (slot)
+    fir::cg::XDeclareOp::create(
+        builder, builder.getFusedLoc({declOp.getLoc()}, lvAttr), slot.getType(),
+        slot, /*shape=*/mlir::ValueRange{},
+        /*shift=*/mlir::ValueRange{}, /*typeparams=*/mlir::ValueRange{},
+        /*dummy_scope=*/mlir::Value{}, /*storage=*/mlir::Value{},
+        /*storage_offset=*/0, name, /*data_attr=*/cuf::DataAttributeAttr{},
+        /*dummy_arg_no=*/mlir::IntegerAttr{});
+  else
+    mlir::LLVM::DbgValueOp::create(builder, declOp.getLoc(), val, lvAttr,
+                                   nullptr);
   return lvAttr;
 }
 
diff --git a/flang/test/Integration/debug-bounds-stack-slot.f90 b/flang/test/Integration/debug-bounds-stack-slot.f90
new file mode 100644
index 0000000000000..75102955ad39f
--- /dev/null
+++ b/flang/test/Integration/debug-bounds-stack-slot.f90
@@ -0,0 +1,39 @@
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+
+subroutine array(a, lb, n)
+  integer :: lb, n
+  real :: a(lb:n)
+  a(lb) = 0.0
+end subroutine
+
+subroutine char_len(c)
+  character(*) :: c
+  c(1:1) = 'x'
+end subroutine
+
+! CHECK-LABEL: define void @array_(
+! CHECK:         %[[LB_SLOT:[0-9]+]] = alloca
+! CHECK:         %[[COUNT_SLOT:[0-9]+]] = alloca
+! CHECK:         #dbg_declare(ptr %[[LB_SLOT]], ![[LB:[0-9]+]], !DIExpression(),
+! CHECK:         #dbg_declare(ptr %[[COUNT_SLOT]], ![[COUNT:[0-9]+]], !DIExpression(),
+! CHECK-NOT:     #dbg_value
+! Test that no fake.use is generated with an integer type i{{[0-9]+}}.
+! CHECK-NOT:     @llvm.fake.use(i{{[0-9]+}} %
+! CHECK:         ret void
+
+! CHECK-LABEL: define void @char_len_(
+! CHECK:         %[[LEN_SLOT:[0-9]+]] = alloca
+! CHECK:         #dbg_declare(ptr %[[LEN_SLOT]], ![[LEN:[0-9]+]], !DIExpression(),
+! CHECK-NOT:     #dbg_value
+! CHECK-NOT:     @llvm.fake.use(i{{[0-9]+}} %
+! CHECK:         ret void
+
+! CHECK-DAG: ![[LB]] = !DILocalVariable(name: "._QFarrayEa2"{{.*}}flags: DIFlagArtificial)
+! CHECK-DAG: ![[COUNT]] = !DILocalVariable(name: "._QFarrayEa1"{{.*}}flags: DIFlagArtificial)
+! CHECK-DAG: !DILocalVariable(name: "a"{{.*}}type: ![[ATY:[0-9]+]])
+! CHECK-DAG: ![[ATY]] = !DICompositeType(tag: DW_TAG_array_type{{.*}}elements: ![[ELEMS:[0-9]+]])
+! CHECK-DAG: ![[ELEMS]] = !{![[SUBRANGE:[0-9]+]]}
+! CHECK-DAG: ![[SUBRANGE]] = !DISubrange(count: ![[COUNT]], lowerBound: ![[LB]])
+! CHECK-DAG: ![[LEN]] = !DILocalVariable(name: "._QFchar_lenEc1"{{.*}}flags: DIFlagArtificial)
+! CHECK-DAG: !DILocalVariable(name: "c"{{.*}}type: ![[CTY:[0-9]+]])
+! CHECK-DAG: ![[CTY]] = !DIStringType(stringLength: ![[LEN]], encoding: DW_ATE_ASCII)
diff --git a/flang/test/Lower/HLFIR/trim.f90 b/flang/test/Lower/HLFIR/trim.f90
index 7baac323290ee..c577f84024f44 100644
--- a/flang/test/Lower/HLFIR/trim.f90
+++ b/flang/test/Lower/HLFIR/trim.f90
@@ -4,6 +4,7 @@
 ! CHECK-SAME:     %[[VAL_0:.*]]: !fir.boxchar<1> {fir.bindc_name = "c"}) {
 ! CHECK:          %[[VAL_1:.*]] = fir.dummy_scope : !fir.dscope
 ! CHECK-NEXT:     %[[VAL_2:.*]]:2 = fir.unboxchar %[[VAL_0]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+! CHECK-NEXT:     fir.store %[[VAL_2]]#1 to %{{.*}} : !fir.ref<index>
 ! CHECK-NEXT:     %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_2]]#0 typeparams %[[VAL_2]]#1 dummy_scope %[[VAL_1]] arg {{[0-9]+}} uniq_name("_QFtrim_testEc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
 ! CHECK-NEXT:     %[[VAL_4:.*]] = arith.constant 8 : index
 ! CHECK-NEXT:     %[[VAL_5:.*]] = fir.alloca !fir.char<1,8> <{bindc_name = "tc", uniq_name = "_QFtrim_testEtc"}>
diff --git a/flang/test/Lower/Intrinsics/rename.f90 b/flang/test/Lower/Intrinsics/rename.f90
index e807ac7e93eed..29ee2acb7da13 100644
--- a/flang/test/Lower/Intrinsics/rename.f90
+++ b/flang/test/Lower/Intrinsics/rename.f90
@@ -9,9 +9,11 @@ subroutine test_rename(src, dst)
 
     call rename(src, dst)
     !CHECK:      %[[dstUnbox:.*]]:2 = fir.unboxchar %[[dummyDst]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_renameEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: fir.store %[[dstUnbox]]#1 to %{{.*}} : !fir.ref<index>
+    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_renameEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcUnbox:.*]]:2 = fir.unboxchar %[[dummySrc]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %3#0 typeparams %[[srcUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_renameEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: fir.store %[[srcUnbox]]#1 to %{{.*}} : !fir.ref<index>
+    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %[[srcUnbox]]#0 typeparams %[[srcUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_renameEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcBox:.*]] = fir.embox %[[srcDecl]]#1 typeparams %[[srcUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
     !CHECK-NEXT: %[[dstBox:.*]] = fir.embox %[[dstDecl]]#1 typeparams %[[dstUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
     !CHECK-NEXT: %[[statusBox:.*]] = fir.absent !fir.box<none>
@@ -33,9 +35,11 @@ subroutine test_rename_status(src, dst)
 
     call rename(src, dst, status)
     !CHECK:      %[[dstUnbox:.*]]:2 = fir.unboxchar %[[dummyDst]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_rename_statusEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: fir.store %[[dstUnbox]]#1 to %{{.*}} : !fir.ref<index>
+    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_rename_statusEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcUnbox:.*]]:2 = fir.unboxchar %[[dummySrc]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %3#0 typeparams %[[srcUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_rename_statusEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: fir.store %[[srcUnbox]]#1 to %{{.*}} : !fir.ref<index>
+    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %[[srcUnbox]]#0 typeparams %[[srcUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_rename_statusEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[statusAlloc:.*]] = fir.alloca i32 <{bindc_name = "status", uniq_name = "_QFtest_rename_statusEstatus"}>
     !CHECK-NEXT: %[[statusDecl:.*]]:2 = hlfir.declare %[[statusAlloc]] uniq_name("_QFtest_rename_statusEstatus") : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
     !CHECK-NEXT: %[[srcBox:.*]] = fir.embox %[[srcDecl]]#1 typeparams %[[srcUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
diff --git a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90 b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
index 8a673582951e8..2810f166470ee 100644
--- a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
+++ b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
@@ -167,7 +167,7 @@ end subroutine target_allocatable
 ! CHECK-SAME:       @[[CHAR_PRIVATIZER_SYM]] %{{[^[:space:]]+}}#0 -> %[[CHAR_ARG:[^,]+]] [map_idx=2]
 ! CHECK-SAME:       @[[FIRSTPRIVATE_SCALAR_SYM]] %{{[^[:space:]]+}}#0 -> %[[FP_SCALAR_ARG:[^,]+]] [map_idx=3] :
 ! CHECK-SAME:       !fir.ref<!fir.box<!fir.heap<i32>>>, !fir.ref<f32>, !fir.ref<i64>, !fir.ref<!fir.box<!fir.array<?xf32>>>, !fir.ref<complex<f32>>, !fir.boxchar<1>, !fir.ref<i32>)
-! CHECK-NOT:      fir.alloca
+! CHECK-NOT:      fir.alloca {{.*}}bindc_name
 ! CHECK:          hlfir.declare %[[ALLOC_ARG]]
 ! CHECK:          hlfir.declare %[[REAL_ARG]]
 ! CHECK:          hlfir.declare %[[LB_ARG]]
diff --git a/flang/test/Lower/OpenMP/compound-version-override.f90 b/flang/test/Lower/OpenMP/compound-version-override.f90
index 112ab98e4cd27..eaba19502deec 100644
--- a/flang/test/Lower/OpenMP/compound-version-override.f90
+++ b/flang/test/Lower/OpenMP/compound-version-override.f90
@@ -3,19 +3,20 @@
 
 !CHECK: warning: IF clause is not allowed on DO SIMD directive in OpenMP v4.5, try -fopenmp-version=50
 
+!CHECK: %[[A:[0-9]+]]:2 = hlfir.declare %{{.*}} uniq_name("_QFcompoundEa")
 !CHECK: omp.wsloop {
 !CHECK:   omp.simd if(%true) linear(%[[V0:[0-9]+]]#0 : !fir.ref<i32> = %c1_i32_0 : i32) linear_var_types([i32]) {
 !CHECK:     omp.loop_nest (%arg2) : i32 = (%c1_i32) to (%{{[0-9]+}}) inclusive step (%c1_i32_0) {
 !CHECK:       hlfir.assign %arg2 to %[[V0]]#0 : i32, !fir.ref<i32>
 !CHECK:       %[[V1:[0-9]+]] = fir.load %[[V0]]#0 : !fir.ref<i32>
 !CHECK:       %[[V2:[0-9]+]] = fir.convert %[[V1]] : (i32) -> i64
-!CHECK:       %[[V3:[0-9]+]] = hlfir.designate %10#0 (%[[V2]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
+!CHECK:       %[[V3:[0-9]+]] = hlfir.designate %[[A]]#0 (%[[V2]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
 !CHECK:       %[[V4:[0-9]+]] = fir.load %[[V3]] : !fir.ref<f32>
 !CHECK:       %cst = arith.constant 1.000000e+00 : f32
 !CHECK:       %[[V5:[0-9]+]] = arith.addf %[[V4]], %cst fastmath<contract> : f32
 !CHECK:       %[[V6:[0-9]+]] = fir.load %[[V0]]#0 : !fir.ref<i32>
 !CHECK:       %[[V7:[0-9]+]] = fir.convert %[[V6]] : (i32) -> i64
-!CHECK:       %[[V8:[0-9]+]] = hlfir.designate %10#0 (%[[V7]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
+!CHECK:       %[[V8:[0-9]+]] = hlfir.designate %[[A]]#0 (%[[V7]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
 !CHECK:       hlfir.assign %[[V5]] to %[[V8]] : f32, !fir.ref<f32>
 !CHECK:       omp.yield
 !CHECK:     }
diff --git a/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90 b/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
index bcd1f606bd823..af867c529993e 100644
--- a/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
+++ b/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
@@ -10,7 +10,7 @@ end subroutine TestCharLenBounds
 
 !CHECK: %[[DUMMY:.*]] = fir.dummy_scope : !fir.dscope
 !CHECK: %[[UNBOX:.*]]:2 = fir.unboxchar %{{.*}} : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-!CHECK: %[[DECLARE:.*]]:2 = hlfir.declare %[[UNBOX]]#0 typeparams %2#1 dummy_scope %[[DUMMY]] arg {{[0-9]+}} uniq_name("_QFtestcharlenboundsEclen") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+!CHECK: %[[DECLARE:.*]]:2 = hlfir.declare %[[UNBOX]]#0 typeparams %[[UNBOX]]#1 dummy_scope %[[DUMMY]] arg {{[0-9]+}} uniq_name("_QFtestcharlenboundsEclen") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
 !CHECK: %[[LB_START_IDX:.*]] = arith.constant 0 : index
 !CHECK: %[[STRIDE:.*]] = arith.constant 1 : index
 !CHECK: %[[EXTENT:.*]]:2 = fir.unboxchar %[[DECLARE]]#0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
diff --git a/flang/test/Lower/OpenMP/optional-argument-map-2.f90 b/flang/test/Lower/OpenMP/optional-argument-map-2.f90
index d904b0fbefed4..305e3436e9254 100644
--- a/flang/test/Lower/OpenMP/optional-argument-map-2.f90
+++ b/flang/test/Lower/OpenMP/optional-argument-map-2.f90
@@ -102,7 +102,7 @@ end module mod
 ! CHECK-NO-FPRIV:           %[[VAL_20:.*]] = arith.subi %[[VAL_19]]#1, %[[VAL_18]] : index
 ! CHECK-NO-FPRIV:           %[[VAL_21:.*]] = omp.map.bounds lower_bound(%[[VAL_17]] : index) upper_bound(%[[VAL_20]] : index) extent(%[[VAL_19]]#1 : index) stride(%[[VAL_18]] : index) start_idx(%[[VAL_17]] : index) stride_in_bytes(true)
 ! CHECK-NO-FPRIV:           %[[VAL_22:.*]] = fir.box_offset %[[VAL_0]] base_addr : (!fir.ref<!fir.boxchar<1>>) -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
-! CHECK-NO-FPRIV:           %[[VAL_23:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(implicit, to) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%14) name("") -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
+! CHECK-NO-FPRIV:           %[[VAL_23:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(implicit, to) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%{{.*}}) name("") -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
 ! CHECK-NO-FPRIV:           %[[VAL_24:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(always, implicit, to) capture(ByRef) members(%[[VAL_23]] : [0] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>) name("") -> !fir.ref<!fir.boxchar<1>>
 ! CHECK-NO-FPRIV:           %[[VAL_26:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(attach, ref_ptr, ref_ptee) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%[[VAL_21]]) name("") -> !fir.ref<!fir.boxchar<1>>
 ! CHECK-NO-FPRIV:  omp.target kernel_type(generic) map_entries(%[[VAL_7]] -> %[[VAL_27:.*]], %[[VAL_16]] -> %[[VAL_28:.*]], %[[VAL_24]] -> %[[VAL_29:.*]], %[[VAL_26]] -> %[[VAL_30:.*]], %[[VAL_23]] -> %[[VAL_30:.*]] : !fir.ref<!fir.char<1,4>>, !fir.ref<!fir.char<1,?>>, !fir.ref<!fir.boxchar<1>>, !fir.ref<!fir.boxchar<1>>, !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>) {
diff --git a/flang/test/Lower/bounds-in-stack-slots.f90 b/flang/test/Lower/bounds-in-stack-slots.f90
new file mode 100644
index 0000000000000..3930f84e7b148
--- /dev/null
+++ b/flang/test/Lower/bounds-in-stack-slots.f90
@@ -0,0 +1,88 @@
+! Test that at -O0 the non-constant bounds and length parameters of variables
+! are stored to stack slots, for the debug info.
+! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -O1 %s -o - | FileCheck %s --check-prefix=OPT
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -O2 %s -o - | FileCheck %s --check-prefix=OPT
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -O3 %s -o - | FileCheck %s --check-prefix=OPT
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -mmlir -mlir-print-debuginfo %s -o - \
+! RUN:   | FileCheck %s --check-prefix=LOC
+
+! OPT-NOT: fir.debug_bound_slot
+
+! The slot and the store are not anything the user wrote, so they stay out of
+! the line table.
+! LOC-LABEL: func.func @_QPchar_len(
+! LOC:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot} loc(#[[LOC:loc[0-9]+]])
+! LOC:         fir.store %{{.*}} to %[[SLOT]] : !fir.ref<index> loc(#[[LOC]])
+! LOC:       #[[LOC]] = loc(unknown)
+
+! The constant extent does not need a slot.
+! CHECK-LABEL: func.func @_QPexplicit_shape(
+! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
+! CHECK-NOT:     fir.debug_bound_slot
+! CHECK:         %[[SHAPE:.*]] = fir.shape %{{.*}}, %[[EXTENT:.*]] : (index, index) -> !fir.shape<2>
+! CHECK-NEXT:    fir.store %[[EXTENT]] to %[[SLOT]] : !fir.ref<index>
+! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHAPE]]) {{.*}}uniq_name("_QFexplicit_shapeEa")
+subroutine explicit_shape(a, n)
+  integer :: n
+  real :: a(4, n)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPchar_len(
+! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
+! CHECK:         %[[UNBOX:.*]]:2 = fir.unboxchar
+! CHECK-NEXT:    fir.store %[[UNBOX]]#1 to %[[SLOT]] : !fir.ref<index>
+! CHECK-NEXT:    hlfir.declare %[[UNBOX]]#0 typeparams %[[UNBOX]]#1 {{.*}}uniq_name("_QFchar_lenEstr")
+subroutine char_len(str)
+  character(*) :: str
+end subroutine
+
+! The assumed-size sentinel gets no slot.
+! CHECK-LABEL: func.func @_QPassumed_size(
+! CHECK-NOT:     fir.debug_bound_slot
+! CHECK:         hlfir.declare %{{.*}}uniq_name("_QFassumed_sizeEa")
+subroutine assumed_size(a)
+  real :: a(*)
+  a(1) = 0.0
+end subroutine
+
+! CHECK-LABEL: func.func @_QPassumed_shape(
+! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
+! CHECK:         %[[SHIFT:.*]] = fir.shift %[[LB:.*]] : (index) -> !fir.shift<1>
+! CHECK-NEXT:    fir.store %[[LB]] to %[[SLOT]] : !fir.ref<index>
+! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHIFT]]) {{.*}}uniq_name("_QFassumed_shapeEx")
+subroutine assumed_shape(x, n)
+  integer :: n
+  real :: x(n:)
+end subroutine
+
+! The slot is in the entry block of the function.
+! CHECK-LABEL: func.func @_QPblock_construct(
+! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
+! CHECK:         %[[SHAPE:.*]] = fir.shape %[[EXTENT:.*]] : (index) -> !fir.shape<1>
+! CHECK-NEXT:    fir.store %[[EXTENT]] to %[[SLOT]] : !fir.ref<index>
+! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHAPE]]) uniq_name("_QFblock_constructB1Eb")
+subroutine block_construct(n)
+  integer :: n
+  block
+    real :: b(n)
+    b = 0.0
+  end block
+end subroutine
+
+! The declaration in the target region has slots of its own.
+! CHECK-LABEL: func.func @_QPtarget_region(
+! CHECK:         omp.target
+! CHECK:           %[[COUNT_SLOT:.*]] = fir.alloca index <{pinned}> {fir.debug_bound_slot}
+! CHECK:           %[[LB_SLOT:.*]] = fir.alloca index <{pinned}> {fir.debug_bound_slot}
+! CHECK:           %[[SHAPE:.*]] = fir.shape_shift %[[LB:.*]], %[[EXTENT:.*]] : (index, index) -> !fir.shapeshift<1>
+! CHECK-NEXT:      fir.store %[[LB]] to %[[LB_SLOT]] : !fir.ref<index>
+! CHECK-NEXT:      fir.store %[[EXTENT]] to %[[COUNT_SLOT]] : !fir.ref<index>
+! CHECK-NEXT:      hlfir.declare %{{.*}}(%[[SHAPE]]) uniq_name("_QFtarget_regionEarr")
+subroutine target_region(lb, ub)
+  integer :: lb, ub
+  real :: arr(lb:ub)
+  !$omp target map(tofrom: arr)
+  arr = 1.0
+  !$omp end target
+end subroutine
diff --git a/flang/test/Transforms/debug-bounds-stack-slot.fir b/flang/test/Transforms/debug-bounds-stack-slot.fir
new file mode 100644
index 0000000000000..f8613ad6c2458
--- /dev/null
+++ b/flang/test/Transforms/debug-bounds-stack-slot.fir
@@ -0,0 +1,92 @@
+// RUN: fir-opt --add-debug-info="emit-fake-use-for-debug-vars=true" --mlir-print-debuginfo %s | FileCheck %s
+
+// A bound or length that lowering stored to a slot marked with
+// fir.debug_bound_slot is described by the slot, with a declaration that
+// becomes a dbg.declare, and does not need a fake use.
+
+#loc1 = loc("test.f90":1:1)
+#loc3 = loc("test.f90":3:14)
+#loc5 = loc("test.f90":5:1)
+#loc = loc("test.f90":0:0)
+
+module {
+  func.func private @foo()
+  func.func @test_dynamic_array(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1), %arg2: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_dynamic_array"} {
+    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
+    %1 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
+    %2 = fir.undefined !fir.dscope loc(#loc1)
+    fir.store %arg2 to %1 : !fir.ref<index> loc(#loc3)
+    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
+    %3 = fircg.ext_declare %arg0(%arg1) origin %arg2 dummy_scope %2 arg 1 uniq_name("_QFtest_dynamic_arrayEarr") : (!fir.ref<!fir.array<?xi32>>, index, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+    fir.call @foo() : () -> ()
+    return loc(#loc5)
+  } loc(#loc1)
+
+// CHECK-LABEL: func.func @test_dynamic_array(
+// CHECK:         %[[COUNT_SLOT:.*]] = fir.alloca index
+// CHECK:         %[[LB_SLOT:.*]] = fir.alloca index
+// CHECK-NOT:     llvm.intr.dbg.value
+// CHECK:         fircg.ext_declare %[[LB_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr2") : (!fir.ref<index>) -> !fir.ref<index>
+// CHECK:         fircg.ext_declare %[[COUNT_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr1") : (!fir.ref<index>) -> !fir.ref<index>
+// CHECK:         fircg.ext_declare %arg0(%arg1) origin %arg2
+// CHECK:         fir.call @foo()
+// CHECK-NEXT:    fir.fake_use %arg0
+// CHECK-NEXT:    return
+
+  func.func @test_char(%arg0: !fir.ref<!fir.char<1,?>> {fir.bindc_name = "str"} loc(#loc1), %arg1: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_char"} {
+    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
+    %1 = fir.undefined !fir.dscope loc(#loc1)
+    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
+    %2 = fircg.ext_declare %arg0 typeparams %arg1 dummy_scope %1 arg 1 uniq_name("_QFtest_charEstr") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> !fir.ref<!fir.char<1,?>> loc(#loc3)
+    fir.call @foo() : () -> ()
+    return loc(#loc5)
+  } loc(#loc1)
+
+// CHECK-LABEL: func.func @test_char(
+// CHECK:         %[[LEN_SLOT:.*]] = fir.alloca index
+// CHECK-NOT:     llvm.intr.dbg.value
+// CHECK:         fircg.ext_declare %[[LEN_SLOT]] uniq_name("._QFtest_charEstr1") : (!fir.ref<index>) -> !fir.ref<index>
+// CHECK:         fircg.ext_declare %arg0 typeparams %arg1
+// CHECK:         fir.call @foo()
+// CHECK-NEXT:    fir.fake_use %arg0
+// CHECK-NEXT:    return
+
+  // An alloca of the same shape that lowering did not mark is left alone.
+  func.func @test_unmarked_slot(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_unmarked_slot"} {
+    %0 = fir.alloca index loc(#loc3)
+    %1 = fir.undefined !fir.dscope loc(#loc1)
+    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
+    %2 = fircg.ext_declare %arg0(%arg1) dummy_scope %1 arg 1 uniq_name("_QFtest_unmarked_slotEarr") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+    fir.call @foo() : () -> ()
+    return loc(#loc5)
+  } loc(#loc1)
+
+// CHECK-LABEL: func.func @test_unmarked_slot(
+// CHECK:         %[[COUNT:.*]] = fir.convert %arg1 : (index) -> i64
+// CHECK:         llvm.intr.dbg.value #{{.*}} = %[[COUNT]] : i64
+// CHECK:         fircg.ext_declare %arg0(%arg1)
+// CHECK:         fir.call @foo()
+// CHECK-NEXT:    fir.fake_use %arg0
+// CHECK-NEXT:    fir.fake_use %arg1
+// CHECK-NEXT:    return
+
+  // A second store means the slot may not hold the count everywhere.
+  func.func @test_slot_overwritten(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1), %arg2: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_slot_overwritten"} {
+    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
+    %1 = fir.undefined !fir.dscope loc(#loc1)
+    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
+    %2 = fircg.ext_declare %arg0(%arg1) dummy_scope %1 arg 1 uniq_name("_QFtest_slot_overwrittenEarr") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+    fir.store %arg2 to %0 : !fir.ref<index> loc(#loc3)
+    fir.call @foo() : () -> ()
+    return loc(#loc5)
+  } loc(#loc1)
+
+// CHECK-LABEL: func.func @test_slot_overwritten(
+// CHECK:         %[[COUNT2:.*]] = fir.convert %arg1 : (index) -> i64
+// CHECK:         llvm.intr.dbg.value #{{.*}} = %[[COUNT2]] : i64
+// CHECK:         fircg.ext_declare %arg0(%arg1)
+// CHECK:         fir.call @foo()
+// CHECK-NEXT:    fir.fake_use %arg0
+// CHECK-NEXT:    fir.fake_use %arg1
+// CHECK-NEXT:    return
+} loc(#loc)

>From 7c1087dcb709d043870280ab089d9c140ccd6ed8 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Fri, 9 Oct 2026 15:09:18 +0100
Subject: [PATCH 2/4] [flang][debug] Create the bound slots in AddDebugInfo
 instead of lowering.

Reviewers asked for the slots to be limited to -g and be generated in
AddDebugInfo. AddDebugInfo only runs with -g, and the pipeline already
derives options for it from the optimisation level, so move the creation
there and drop it from lowering.

Only the bounds that are really described get a slot, and one slot serves a
value that a declaration names more than once, so a(n, n) no longer gets a
second slot that nothing refers to. The artificial variable is cached along
with it, so that such a value is also described once rather than twice.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Lower/LoweringOptions.def |   5 -
 .../flang/Optimizer/Dialect/FIROpsSupport.h   |   6 -
 .../Optimizer/Transforms/DebugTypeGenerator.h |  21 ++-
 .../flang/Optimizer/Transforms/Passes.td      |   7 +-
 flang/lib/Frontend/CompilerInvocation.cpp     |   2 -
 flang/lib/Lower/ConvertVariable.cpp           |  53 --------
 flang/lib/Optimizer/Passes/Pipelines.cpp      |   1 +
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp |  21 +--
 .../Transforms/DebugTypeGenerator.cpp         |  53 +++++---
 .../Integration/debug-bounds-stack-slot.f90   |   9 +-
 flang/test/Lower/HLFIR/trim.f90               |   1 -
 flang/test/Lower/Intrinsics/rename.f90        |  12 +-
 .../target-private-multiple-variables.f90     |   2 +-
 .../OpenMP/compound-version-override.f90      |   5 +-
 .../OpenMP/dynamic-len-char-bounds-gen.f90    |   2 +-
 .../Lower/OpenMP/optional-argument-map-2.f90  |   2 +-
 flang/test/Lower/bounds-in-stack-slots.f90    |  88 -------------
 .../Transforms/debug-bounds-stack-slot.fir    | 120 +++++++++++-------
 18 files changed, 158 insertions(+), 252 deletions(-)
 delete mode 100644 flang/test/Lower/bounds-in-stack-slots.f90

diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def
index f187ac654d663..4590df1c8a6f8 100644
--- a/flang/include/flang/Lower/LoweringOptions.def
+++ b/flang/include/flang/Lower/LoweringOptions.def
@@ -89,11 +89,6 @@ ENUM_LOWERINGOPT(RegisterMLIRDiagnosticsHandler, unsigned, 1, 1)
 /// LineTablesOnly. Off by default.
 ENUM_LOWERINGOPT(PreserveUseDebugInfo, unsigned, 1, 0)
 
-/// If true, keep the non-constant bounds and length parameters of a variable
-/// in stack slots, so that the debug info can describe them wherever the
-/// variable is visible. Off by default.
-ENUM_LOWERINGOPT(BoundsInStackSlots, unsigned, 1, 0)
-
 /// FP max/min behavior for max/min intrinsics and [max|min][loc|val] (Legacy,
 /// Portable, Extremum, ExtremeNum). Default: Legacy.
 ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
index 9fe37331f5ec1..d5131500ecbbd 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
@@ -163,12 +163,6 @@ static constexpr llvm::StringRef getAccessGroupsAttrName() {
   return "access_groups";
 }
 
-/// Attribute to mark an alloca that lowering creates to hold a bound or
-/// length parameter of a variable.
-static constexpr llvm::StringRef getDebugBoundSlotAttrName() {
-  return "fir.debug_bound_slot";
-}
-
 /// Attribute holding the unique name of the Fortran entity an allocation
 /// belongs to. It is an inherent attribute of the FIR allocation operations,
 /// and may also be carried by allocation operations of other dialects that
diff --git a/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h b/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
index 8062c4e9c409a..aff9e07e727fe 100644
--- a/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
+++ b/flang/include/flang/Optimizer/Transforms/DebugTypeGenerator.h
@@ -22,10 +22,6 @@
 
 namespace fir {
 
-/// Return the stack slot that lowering created to hold \p val, if there is one
-/// and it still holds \p val.
-fir::AllocaOp getDebugBoundSlot(mlir::Value val);
-
 /// Special cache to deal with the fact that mlir::LLVM::DITypeAttr for
 /// derived types may only be valid in specific nesting contexts in presence
 /// of derived type recursion and cannot be cached for the whole compilation.
@@ -101,13 +97,18 @@ class DerivedTypeCache {
 class DebugTypeGenerator {
 public:
   DebugTypeGenerator(mlir::ModuleOp module, mlir::SymbolTable *symbolTable,
-                     const mlir::DataLayout &dl);
+                     const mlir::DataLayout &dl,
+                     bool boundsInStackSlots = false);
 
   mlir::LLVM::DITypeAttr convertType(mlir::Type Ty,
                                      mlir::LLVM::DIFileAttr fileAttr,
                                      mlir::LLVM::DIScopeAttr scope,
                                      fir::cg::XDeclareOp declOp);
 
+  /// Whether \p val is held in a stack slot for the variable declared by
+  /// \p declOp, which makes a fake use of it unnecessary.
+  bool hasBoundSlot(mlir::Value val, fir::cg::XDeclareOp declOp) const;
+
 private:
   mlir::LLVM::DITypeAttr convertRecordType(fir::RecordType Ty,
                                            mlir::LLVM::DIFileAttr fileAttr,
@@ -158,6 +159,16 @@ class DebugTypeGenerator {
   mlir::ModuleOp module;
   mlir::SymbolTable *symbolTable;
   const mlir::DataLayout *dataLayout;
+  bool boundsInStackSlots;
+  struct ArtificialVar {
+    /// Null unless the value is held in a stack slot.
+    fir::AllocaOp slot;
+    mlir::LLVM::DILocalVariableAttr var;
+  };
+  /// For a bound and the declaration it belongs to, the artificial variable
+  /// made to describe that bound and the slot that holds its value.
+  llvm::DenseMap<std::pair<mlir::Value, mlir::Operation *>, ArtificialVar>
+      artificialVars;
   KindMapping kindMapping;
   fir::LLVMTypeConverter llvmTypeConverter;
   std::uint64_t dimsSize;
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 195bbe014f006..fc8a393935829 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -263,7 +263,12 @@ def AddDebugInfo : Pass<"add-debug-info", "mlir::ModuleOp"> {
            "Command-line flags to append to DWARF producer">,
     Option<"emitFakeUseForDebugVars", "emit-fake-use-for-debug-vars",
            "bool", /*default=*/"false",
-           "Emit fake use for debug variables to extend their lifetime">
+           "Emit fake use for debug variables to extend their lifetime">,
+    Option<"boundsInStackSlots", "bounds-in-stack-slots",
+           "bool", /*default=*/"false",
+           "Keep the bounds and length parameters of a variable in stack "
+           "slots, so that they can be described wherever the variable is "
+           "visible">
   ];
 }
 
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index b09bb0054cc61..13794f5720e74 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -2149,8 +2149,6 @@ void CompilerInvocation::setLoweringOptions() {
 
   // Lower TRANSPOSE as a runtime call under -O0.
   loweringOpts.setOptimizeTranspose(codegenOpts.OptimizationLevel > 0);
-  // Keep the bounds of variables in memory under -O0 for the debugger.
-  loweringOpts.setBoundsInStackSlots(codegenOpts.OptimizationLevel == 0);
   loweringOpts.setUnderscoring(codegenOpts.Underscoring);
   loweringOpts.setSkipExternalRttiDefinition(skipExternalRttiDefinition);
 
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 633df9009bde8..59f46a31256fe 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -37,7 +37,6 @@
 #include "flang/Optimizer/Dialect/CUF/CUFOps.h"
 #include "flang/Optimizer/Dialect/FIRAttr.h"
 #include "flang/Optimizer/Dialect/FIROps.h"
-#include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Dialect/FIRType.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
@@ -2572,55 +2571,6 @@ isCapturedInInternalProcedure(Fortran::lower::AbstractConverter &converter,
   return false;
 }
 
-/// Whether \p val is the same wherever it is read, so that the debug info can
-/// describe it directly and it does not need a slot.
-static bool isInvariantBound(mlir::Value val) {
-  mlir::Operation *op = val.getDefiningOp();
-  // A block argument, which for a bound means a dummy, varies between calls.
-  if (!op)
-    return false;
-  if (op->hasTrait<mlir::OpTrait::ConstantLike>())
-    return true;
-  // The assumed-size sentinel is fixed at compile time, and the debug info
-  // leaves it out rather than describing it as an extent.
-  if (mlir::isa<fir::AssumedSizeExtentOp>(op))
-    return true;
-  // A constant bound is usually folded before lowering, but a dimension of an
-  // array that has a non-constant dimension too still reaches here so look
-  // through the computation.
-  return !op->getOperands().empty() && mlir::isPure(op) &&
-         op->getNumRegions() == 0 &&
-         llvm::all_of(op->getOperands(), isInvariantBound);
-}
-
-/// Under the BoundsInStackSlots lowering option, store the non-constant bounds
-/// and length parameters of the variable declared by \p declare in stack slots
-/// that nothing else accesses.
-static void genBoundsInStackSlots(Fortran::lower::AbstractConverter &converter,
-                                  hlfir::DeclareOp declare) {
-  if (!converter.getLoweringOptions().getBoundsInStackSlots())
-    return;
-  llvm::SmallVector<mlir::Value> bounds(declare.getTypeparams());
-  if (mlir::Value shape = declare.getShape())
-    if (mlir::Operation *shapeOp = shape.getDefiningOp())
-      llvm::append_range(bounds, shapeOp->getOperands());
-  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
-  mlir::NamedAttribute slotAttr(
-      builder.getStringAttr(fir::getDebugBoundSlotAttrName()),
-      builder.getUnitAttr());
-  // Neither the slot nor the store is something the user wrote, so keep them
-  // out of the line table.
-  mlir::Location loc = builder.getUnknownLoc();
-  mlir::OpBuilder::InsertionGuard guard(builder);
-  builder.setInsertionPoint(declare);
-  for (mlir::Value bound : bounds) {
-    if (isInvariantBound(bound))
-      continue;
-    mlir::Value slot = builder.createTemporary(loc, bound.getType(), slotAttr);
-    fir::StoreOp::create(builder, loc, bound, slot);
-  }
-}
-
 /// Map a symbol to its FIR address and evaluated specification expressions.
 /// Not for symbols lowered to fir.box.
 /// Will optionally create fir.declare.
@@ -2716,7 +2666,6 @@ static void genDeclareSymbol(Fortran::lower::AbstractConverter &converter,
     auto newBase = hlfir::DeclareOp::create(
         builder, loc, base, name, shapeOrShift, lenParams, dummyScope, storage,
         storageOffset, attributes, dataAttr, argNo);
-    genBoundsInStackSlots(converter, newBase);
     symMap.addVariableDefinition(sym, newBase, force);
     return;
   }
@@ -2779,8 +2728,6 @@ void Fortran::lower::genDeclareSymbol(
     hlfir::EntityWithAttributes declare =
         hlfir::genDeclare(loc, builder, base, name, attributes, dummyScope,
                           storage, storageOffset, dataAttr, argNo);
-    if (auto declareOp = declare.getDefiningOp<hlfir::DeclareOp>())
-      genBoundsInStackSlots(converter, declareOp);
     symMap.addVariableDefinition(sym, declare.getIfVariableInterface(), force);
     return;
   }
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index d965400e028d2..afa7fc7c58a54 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -93,6 +93,7 @@ void addDebugInfoPass(mlir::PassManager &pm,
   options.emitFakeUseForDebugVars =
       (config.OptLevel == llvm::OptimizationLevel::O0) &&
       !disableArgumentFakeUse;
+  options.boundsInStackSlots = config.OptLevel == llvm::OptimizationLevel::O0;
   addPassConditionally(pm, disableDebugInfo,
                        [&]() { return fir::createAddDebugInfoPass(options); });
 }
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 32c748b7ad298..6d69a38f403fa 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -386,8 +386,7 @@ bool AddDebugInfoPass::createCommonBlockGlobal(
 
 // Create fake uses for compiler-generated internal variables that represent
 // values needed by a debugger.  This prevents values from being optimized out
-// such as the count and lower bound of dynamic arrays. A value kept in a stack
-// slot does not need one, since the debug info describes the slot.
+// such as the count and lower bound of dynamic arrays.
 template <typename Op>
 static void InsertFakeUseForDebugVar(mlir::OpBuilder &builder, Op declOp,
                                      mlir::Value var) {
@@ -398,7 +397,7 @@ static void InsertFakeUseForDebugVar(mlir::OpBuilder &builder, Op declOp,
                 mlir::dyn_cast<mlir::func::ReturnOp>(block.getTerminator())) {
           mlir::OpBuilder::InsertionGuard guard(builder);
           builder.setInsertionPoint(returnOp);
-          if (!fir::getIntIfConstant(var) && !fir::getDebugBoundSlot(var))
+          if (!fir::getIntIfConstant(var))
             fir::FakeUseOp::create(builder, declOp.getLoc(), var);
         }
       }
@@ -440,17 +439,21 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
 
   if (emitFakeUseForDebugVars) {
     // Create fake uses for internal variables that represent count and lower
-    // bound of dynamic arrays to ensure they are not optimized out.
+    // bound of dynamic arrays to ensure they are not optimized out. A value
+    // that convertType has put in a stack slot needs none, as the debug info
+    // describes the slot and nothing has to keep the value itself alive.
     if (auto arrayTy =
             mlir::dyn_cast<mlir::LLVM::DICompositeTypeAttr>(tyAttr)) {
       if (arrayTy.getTag() == llvm::dwarf::DW_TAG_array_type) {
         if constexpr (std::is_same_v<Op, fir::cg::XDeclareOp>) {
           // Count is represented as a value in the shape attribute
           for (auto val : declOp.getShape())
-            InsertFakeUseForDebugVar(builder, declOp, val);
+            if (!typeGen.hasBoundSlot(val, declOp))
+              InsertFakeUseForDebugVar(builder, declOp, val);
           // Lower bound is represented as a value in the shift attribute
           for (auto val : declOp.getShift())
-            InsertFakeUseForDebugVar(builder, declOp, val);
+            if (!typeGen.hasBoundSlot(val, declOp))
+              InsertFakeUseForDebugVar(builder, declOp, val);
         }
       }
     }
@@ -459,7 +462,8 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
     // are not optimized out.
     if constexpr (std::is_same_v<Op, fir::cg::XDeclareOp>) {
       for (auto val : declOp.getTypeparams())
-        InsertFakeUseForDebugVar(builder, declOp, val);
+        if (!typeGen.hasBoundSlot(val, declOp))
+          InsertFakeUseForDebugVar(builder, declOp, val);
     }
   }
 
@@ -1219,7 +1223,8 @@ void AddDebugInfoPass::runOnOperation() {
     mlir::LLVM::ModuleFlagsOp::create(builder, module.getLoc(),
                                       builder.getArrayAttr(moduleFlags));
   }
-  fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl);
+  fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl,
+                                  boundsInStackSlots);
   // We need 2 type of file paths here.
   // 1. Name of the file as was presented to compiler. This can be absolute
   // or relative to 2.
diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index c63a3dacdc3e0..2e43cf1ec9e16 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -13,8 +13,8 @@
 #define DEBUG_TYPE "flang-debug-type-generator"
 
 #include "flang/Optimizer/Transforms/DebugTypeGenerator.h"
+#include "flang/Optimizer/Builder/FIRBuilder.h"
 #include "flang/Optimizer/CodeGen/DescriptorModel.h"
-#include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Support/InternalNames.h"
 #include "flang/Optimizer/Support/Utils.h"
 #include "mlir/Pass/Pass.h"
@@ -46,9 +46,11 @@ std::uint64_t getComponentOffset<0>(const mlir::DataLayout &dl,
 
 DebugTypeGenerator::DebugTypeGenerator(mlir::ModuleOp m,
                                        mlir::SymbolTable *symbolTable_,
-                                       const mlir::DataLayout &dl)
+                                       const mlir::DataLayout &dl,
+                                       bool boundsInStackSlots_)
     : module(m), symbolTable(symbolTable_), dataLayout{&dl},
-      kindMapping(getKindMapping(m)), llvmTypeConverter(m, false, false, dl) {
+      boundsInStackSlots(boundsInStackSlots_), kindMapping(getKindMapping(m)),
+      llvmTypeConverter(m, false, false, dl) {
   LLVM_DEBUG(llvm::dbgs() << "DITypeAttr generator\n");
 
   mlir::MLIRContext *context = module.getContext();
@@ -92,22 +94,26 @@ static mlir::LLVM::DITypeAttr genPlaceholderType(mlir::MLIRContext *context) {
                       /*bitSize=*/32, llvm::dwarf::DW_ATE_signed);
 }
 
-fir::AllocaOp getDebugBoundSlot(mlir::Value val) {
-  for (mlir::Operation *user : val.getUsers()) {
-    auto store = mlir::dyn_cast<fir::StoreOp>(user);
-    if (!store || store.getValue() != val)
-      continue;
-    auto slot = store.getMemref().getDefiningOp<fir::AllocaOp>();
-    if (!slot || !slot->hasAttr(fir::getDebugBoundSlotAttrName()))
-      continue;
-    // Slot users should either be above mentioned store or a XDeclareOp which
-    // gets generated in generateArtificialVariable.
-    if (llvm::all_of(slot->getUsers(), [&](mlir::Operation *slotUser) {
-          return slotUser == store || mlir::isa<fir::cg::XDeclareOp>(slotUser);
-        }))
-      return slot;
-  }
-  return {};
+/// Return a stack slot holding \p val wherever the variable declared by
+/// \p declOp is visible.
+static fir::AllocaOp createBoundSlot(mlir::Value val,
+                                     fir::cg::XDeclareOp declOp) {
+  // Generate the slot and store with UnknownLoc so tha they dont impact the
+  // line table.
+  mlir::OpBuilder builder(declOp);
+  mlir::Location loc = builder.getUnknownLoc();
+  builder.setInsertionPointToStart(
+      fir::getAllocaBlock(*declOp->getParentRegion()));
+  fir::AllocaOp slot = fir::AllocaOp::create(builder, loc, val.getType());
+  builder.setInsertionPoint(declOp);
+  fir::StoreOp::create(builder, loc, val, slot);
+  return slot;
+}
+
+bool DebugTypeGenerator::hasBoundSlot(mlir::Value val,
+                                      fir::cg::XDeclareOp declOp) const {
+  auto it = artificialVars.find({val, declOp});
+  return it != artificialVars.end() && it->second.slot;
 }
 
 // Helper function to create DILocalVariableAttr and DbgValueOp when information
@@ -118,6 +124,10 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
     mlir::MLIRContext *context, mlir::Value val,
     mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope,
     fir::cg::XDeclareOp declOp) {
+  std::pair<mlir::Value, mlir::Operation *> key{val, declOp};
+  if (auto it = artificialVars.find(key); it != artificialVars.end())
+    return it->second.var;
+
   // There can be multiple artificial variable for a single declOp. To help
   // distinguish them, we pad the name with a counter. The counter is the
   // position of 'val' in the operands of declOp.
@@ -128,7 +138,9 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
   auto name = mlir::StringAttr::get(context, "." + declOp.getUniqName().str() +
                                                  std::to_string(varID));
   builder.setInsertionPoint(declOp);
-  fir::AllocaOp slot = getDebugBoundSlot(val);
+  fir::AllocaOp slot;
+  if (boundsInStackSlots)
+    slot = createBoundSlot(val, declOp);
   mlir::Type type = slot ? slot.getInType() : val.getType();
   if (!slot &&
       (!mlir::isa<mlir::IntegerType>(type) || !type.isSignlessInteger())) {
@@ -150,6 +162,7 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
   else
     mlir::LLVM::DbgValueOp::create(builder, declOp.getLoc(), val, lvAttr,
                                    nullptr);
+  artificialVars[key] = {slot, lvAttr};
   return lvAttr;
 }
 
diff --git a/flang/test/Integration/debug-bounds-stack-slot.f90 b/flang/test/Integration/debug-bounds-stack-slot.f90
index 75102955ad39f..422a2e2274caf 100644
--- a/flang/test/Integration/debug-bounds-stack-slot.f90
+++ b/flang/test/Integration/debug-bounds-stack-slot.f90
@@ -14,8 +14,10 @@ subroutine char_len(c)
 ! CHECK-LABEL: define void @array_(
 ! CHECK:         %[[LB_SLOT:[0-9]+]] = alloca
 ! CHECK:         %[[COUNT_SLOT:[0-9]+]] = alloca
-! CHECK:         #dbg_declare(ptr %[[LB_SLOT]], ![[LB:[0-9]+]], !DIExpression(),
-! CHECK:         #dbg_declare(ptr %[[COUNT_SLOT]], ![[COUNT:[0-9]+]], !DIExpression(),
+! CHECK:         store i64 %{{[0-9]+}}, ptr %[[LB_SLOT]]
+! CHECK-NEXT:    #dbg_declare(ptr %[[LB_SLOT]], ![[LB:[0-9]+]], !DIExpression(),
+! CHECK-NEXT:    store i64 %{{[0-9]+}}, ptr %[[COUNT_SLOT]]
+! CHECK-NEXT:    #dbg_declare(ptr %[[COUNT_SLOT]], ![[COUNT:[0-9]+]], !DIExpression(),
 ! CHECK-NOT:     #dbg_value
 ! Test that no fake.use is generated with an integer type i{{[0-9]+}}.
 ! CHECK-NOT:     @llvm.fake.use(i{{[0-9]+}} %
@@ -23,7 +25,8 @@ subroutine char_len(c)
 
 ! CHECK-LABEL: define void @char_len_(
 ! CHECK:         %[[LEN_SLOT:[0-9]+]] = alloca
-! CHECK:         #dbg_declare(ptr %[[LEN_SLOT]], ![[LEN:[0-9]+]], !DIExpression(),
+! CHECK:         store i64 %{{[0-9]+}}, ptr %[[LEN_SLOT]]
+! CHECK-NEXT:    #dbg_declare(ptr %[[LEN_SLOT]], ![[LEN:[0-9]+]], !DIExpression(),
 ! CHECK-NOT:     #dbg_value
 ! CHECK-NOT:     @llvm.fake.use(i{{[0-9]+}} %
 ! CHECK:         ret void
diff --git a/flang/test/Lower/HLFIR/trim.f90 b/flang/test/Lower/HLFIR/trim.f90
index c577f84024f44..7baac323290ee 100644
--- a/flang/test/Lower/HLFIR/trim.f90
+++ b/flang/test/Lower/HLFIR/trim.f90
@@ -4,7 +4,6 @@
 ! CHECK-SAME:     %[[VAL_0:.*]]: !fir.boxchar<1> {fir.bindc_name = "c"}) {
 ! CHECK:          %[[VAL_1:.*]] = fir.dummy_scope : !fir.dscope
 ! CHECK-NEXT:     %[[VAL_2:.*]]:2 = fir.unboxchar %[[VAL_0]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-! CHECK-NEXT:     fir.store %[[VAL_2]]#1 to %{{.*}} : !fir.ref<index>
 ! CHECK-NEXT:     %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_2]]#0 typeparams %[[VAL_2]]#1 dummy_scope %[[VAL_1]] arg {{[0-9]+}} uniq_name("_QFtrim_testEc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
 ! CHECK-NEXT:     %[[VAL_4:.*]] = arith.constant 8 : index
 ! CHECK-NEXT:     %[[VAL_5:.*]] = fir.alloca !fir.char<1,8> <{bindc_name = "tc", uniq_name = "_QFtrim_testEtc"}>
diff --git a/flang/test/Lower/Intrinsics/rename.f90 b/flang/test/Lower/Intrinsics/rename.f90
index 29ee2acb7da13..e807ac7e93eed 100644
--- a/flang/test/Lower/Intrinsics/rename.f90
+++ b/flang/test/Lower/Intrinsics/rename.f90
@@ -9,11 +9,9 @@ subroutine test_rename(src, dst)
 
     call rename(src, dst)
     !CHECK:      %[[dstUnbox:.*]]:2 = fir.unboxchar %[[dummyDst]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: fir.store %[[dstUnbox]]#1 to %{{.*}} : !fir.ref<index>
-    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_renameEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_renameEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcUnbox:.*]]:2 = fir.unboxchar %[[dummySrc]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: fir.store %[[srcUnbox]]#1 to %{{.*}} : !fir.ref<index>
-    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %[[srcUnbox]]#0 typeparams %[[srcUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_renameEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %3#0 typeparams %[[srcUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_renameEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcBox:.*]] = fir.embox %[[srcDecl]]#1 typeparams %[[srcUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
     !CHECK-NEXT: %[[dstBox:.*]] = fir.embox %[[dstDecl]]#1 typeparams %[[dstUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
     !CHECK-NEXT: %[[statusBox:.*]] = fir.absent !fir.box<none>
@@ -35,11 +33,9 @@ subroutine test_rename_status(src, dst)
 
     call rename(src, dst, status)
     !CHECK:      %[[dstUnbox:.*]]:2 = fir.unboxchar %[[dummyDst]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: fir.store %[[dstUnbox]]#1 to %{{.*}} : !fir.ref<index>
-    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_rename_statusEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: %[[dstDecl:.*]]:2 = hlfir.declare %[[dstUnbox]]#0 typeparams %[[dstUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_rename_statusEdst") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[srcUnbox:.*]]:2 = fir.unboxchar %[[dummySrc]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-    !CHECK-NEXT: fir.store %[[srcUnbox]]#1 to %{{.*}} : !fir.ref<index>
-    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %[[srcUnbox]]#0 typeparams %[[srcUnbox]]#1 dummy_scope %{{.*}} {{.*}} uniq_name("_QFtest_rename_statusEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+    !CHECK-NEXT: %[[srcDecl:.*]]:2 = hlfir.declare %3#0 typeparams %[[srcUnbox]]#1 dummy_scope %0 {{.*}} uniq_name("_QFtest_rename_statusEsrc") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
     !CHECK-NEXT: %[[statusAlloc:.*]] = fir.alloca i32 <{bindc_name = "status", uniq_name = "_QFtest_rename_statusEstatus"}>
     !CHECK-NEXT: %[[statusDecl:.*]]:2 = hlfir.declare %[[statusAlloc]] uniq_name("_QFtest_rename_statusEstatus") : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
     !CHECK-NEXT: %[[srcBox:.*]] = fir.embox %[[srcDecl]]#1 typeparams %[[srcUnbox]]#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
diff --git a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90 b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
index 2810f166470ee..8a673582951e8 100644
--- a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
+++ b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-multiple-variables.f90
@@ -167,7 +167,7 @@ end subroutine target_allocatable
 ! CHECK-SAME:       @[[CHAR_PRIVATIZER_SYM]] %{{[^[:space:]]+}}#0 -> %[[CHAR_ARG:[^,]+]] [map_idx=2]
 ! CHECK-SAME:       @[[FIRSTPRIVATE_SCALAR_SYM]] %{{[^[:space:]]+}}#0 -> %[[FP_SCALAR_ARG:[^,]+]] [map_idx=3] :
 ! CHECK-SAME:       !fir.ref<!fir.box<!fir.heap<i32>>>, !fir.ref<f32>, !fir.ref<i64>, !fir.ref<!fir.box<!fir.array<?xf32>>>, !fir.ref<complex<f32>>, !fir.boxchar<1>, !fir.ref<i32>)
-! CHECK-NOT:      fir.alloca {{.*}}bindc_name
+! CHECK-NOT:      fir.alloca
 ! CHECK:          hlfir.declare %[[ALLOC_ARG]]
 ! CHECK:          hlfir.declare %[[REAL_ARG]]
 ! CHECK:          hlfir.declare %[[LB_ARG]]
diff --git a/flang/test/Lower/OpenMP/compound-version-override.f90 b/flang/test/Lower/OpenMP/compound-version-override.f90
index eaba19502deec..112ab98e4cd27 100644
--- a/flang/test/Lower/OpenMP/compound-version-override.f90
+++ b/flang/test/Lower/OpenMP/compound-version-override.f90
@@ -3,20 +3,19 @@
 
 !CHECK: warning: IF clause is not allowed on DO SIMD directive in OpenMP v4.5, try -fopenmp-version=50
 
-!CHECK: %[[A:[0-9]+]]:2 = hlfir.declare %{{.*}} uniq_name("_QFcompoundEa")
 !CHECK: omp.wsloop {
 !CHECK:   omp.simd if(%true) linear(%[[V0:[0-9]+]]#0 : !fir.ref<i32> = %c1_i32_0 : i32) linear_var_types([i32]) {
 !CHECK:     omp.loop_nest (%arg2) : i32 = (%c1_i32) to (%{{[0-9]+}}) inclusive step (%c1_i32_0) {
 !CHECK:       hlfir.assign %arg2 to %[[V0]]#0 : i32, !fir.ref<i32>
 !CHECK:       %[[V1:[0-9]+]] = fir.load %[[V0]]#0 : !fir.ref<i32>
 !CHECK:       %[[V2:[0-9]+]] = fir.convert %[[V1]] : (i32) -> i64
-!CHECK:       %[[V3:[0-9]+]] = hlfir.designate %[[A]]#0 (%[[V2]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
+!CHECK:       %[[V3:[0-9]+]] = hlfir.designate %10#0 (%[[V2]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
 !CHECK:       %[[V4:[0-9]+]] = fir.load %[[V3]] : !fir.ref<f32>
 !CHECK:       %cst = arith.constant 1.000000e+00 : f32
 !CHECK:       %[[V5:[0-9]+]] = arith.addf %[[V4]], %cst fastmath<contract> : f32
 !CHECK:       %[[V6:[0-9]+]] = fir.load %[[V0]]#0 : !fir.ref<i32>
 !CHECK:       %[[V7:[0-9]+]] = fir.convert %[[V6]] : (i32) -> i64
-!CHECK:       %[[V8:[0-9]+]] = hlfir.designate %[[A]]#0 (%[[V7]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
+!CHECK:       %[[V8:[0-9]+]] = hlfir.designate %10#0 (%[[V7]])  : (!fir.box<!fir.array<?xf32>>, i64) -> !fir.ref<f32>
 !CHECK:       hlfir.assign %[[V5]] to %[[V8]] : f32, !fir.ref<f32>
 !CHECK:       omp.yield
 !CHECK:     }
diff --git a/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90 b/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
index af867c529993e..bcd1f606bd823 100644
--- a/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
+++ b/flang/test/Lower/OpenMP/dynamic-len-char-bounds-gen.f90
@@ -10,7 +10,7 @@ end subroutine TestCharLenBounds
 
 !CHECK: %[[DUMMY:.*]] = fir.dummy_scope : !fir.dscope
 !CHECK: %[[UNBOX:.*]]:2 = fir.unboxchar %{{.*}} : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-!CHECK: %[[DECLARE:.*]]:2 = hlfir.declare %[[UNBOX]]#0 typeparams %[[UNBOX]]#1 dummy_scope %[[DUMMY]] arg {{[0-9]+}} uniq_name("_QFtestcharlenboundsEclen") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+!CHECK: %[[DECLARE:.*]]:2 = hlfir.declare %[[UNBOX]]#0 typeparams %2#1 dummy_scope %[[DUMMY]] arg {{[0-9]+}} uniq_name("_QFtestcharlenboundsEclen") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
 !CHECK: %[[LB_START_IDX:.*]] = arith.constant 0 : index
 !CHECK: %[[STRIDE:.*]] = arith.constant 1 : index
 !CHECK: %[[EXTENT:.*]]:2 = fir.unboxchar %[[DECLARE]]#0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
diff --git a/flang/test/Lower/OpenMP/optional-argument-map-2.f90 b/flang/test/Lower/OpenMP/optional-argument-map-2.f90
index 305e3436e9254..d904b0fbefed4 100644
--- a/flang/test/Lower/OpenMP/optional-argument-map-2.f90
+++ b/flang/test/Lower/OpenMP/optional-argument-map-2.f90
@@ -102,7 +102,7 @@ end module mod
 ! CHECK-NO-FPRIV:           %[[VAL_20:.*]] = arith.subi %[[VAL_19]]#1, %[[VAL_18]] : index
 ! CHECK-NO-FPRIV:           %[[VAL_21:.*]] = omp.map.bounds lower_bound(%[[VAL_17]] : index) upper_bound(%[[VAL_20]] : index) extent(%[[VAL_19]]#1 : index) stride(%[[VAL_18]] : index) start_idx(%[[VAL_17]] : index) stride_in_bytes(true)
 ! CHECK-NO-FPRIV:           %[[VAL_22:.*]] = fir.box_offset %[[VAL_0]] base_addr : (!fir.ref<!fir.boxchar<1>>) -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
-! CHECK-NO-FPRIV:           %[[VAL_23:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(implicit, to) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%{{.*}}) name("") -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
+! CHECK-NO-FPRIV:           %[[VAL_23:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(implicit, to) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%14) name("") -> !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>
 ! CHECK-NO-FPRIV:           %[[VAL_24:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(always, implicit, to) capture(ByRef) members(%[[VAL_23]] : [0] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>) name("") -> !fir.ref<!fir.boxchar<1>>
 ! CHECK-NO-FPRIV:           %[[VAL_26:.*]] = omp.map.info var_ptr(%[[VAL_0]] : !fir.ref<!fir.boxchar<1>>, !fir.boxchar<1>) map_clauses(attach, ref_ptr, ref_ptee) capture(ByRef) var_ptr_ptr(%[[VAL_22]] : !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>, !fir.char<1,?>) bounds(%[[VAL_21]]) name("") -> !fir.ref<!fir.boxchar<1>>
 ! CHECK-NO-FPRIV:  omp.target kernel_type(generic) map_entries(%[[VAL_7]] -> %[[VAL_27:.*]], %[[VAL_16]] -> %[[VAL_28:.*]], %[[VAL_24]] -> %[[VAL_29:.*]], %[[VAL_26]] -> %[[VAL_30:.*]], %[[VAL_23]] -> %[[VAL_30:.*]] : !fir.ref<!fir.char<1,4>>, !fir.ref<!fir.char<1,?>>, !fir.ref<!fir.boxchar<1>>, !fir.ref<!fir.boxchar<1>>, !fir.llvm_ptr<!fir.ref<!fir.char<1,?>>>) {
diff --git a/flang/test/Lower/bounds-in-stack-slots.f90 b/flang/test/Lower/bounds-in-stack-slots.f90
deleted file mode 100644
index 3930f84e7b148..0000000000000
--- a/flang/test/Lower/bounds-in-stack-slots.f90
+++ /dev/null
@@ -1,88 +0,0 @@
-! Test that at -O0 the non-constant bounds and length parameters of variables
-! are stored to stack slots, for the debug info.
-! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -O1 %s -o - | FileCheck %s --check-prefix=OPT
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -O2 %s -o - | FileCheck %s --check-prefix=OPT
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -O3 %s -o - | FileCheck %s --check-prefix=OPT
-! RUN: %flang_fc1 -emit-hlfir -fopenmp -mmlir -mlir-print-debuginfo %s -o - \
-! RUN:   | FileCheck %s --check-prefix=LOC
-
-! OPT-NOT: fir.debug_bound_slot
-
-! The slot and the store are not anything the user wrote, so they stay out of
-! the line table.
-! LOC-LABEL: func.func @_QPchar_len(
-! LOC:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot} loc(#[[LOC:loc[0-9]+]])
-! LOC:         fir.store %{{.*}} to %[[SLOT]] : !fir.ref<index> loc(#[[LOC]])
-! LOC:       #[[LOC]] = loc(unknown)
-
-! The constant extent does not need a slot.
-! CHECK-LABEL: func.func @_QPexplicit_shape(
-! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
-! CHECK-NOT:     fir.debug_bound_slot
-! CHECK:         %[[SHAPE:.*]] = fir.shape %{{.*}}, %[[EXTENT:.*]] : (index, index) -> !fir.shape<2>
-! CHECK-NEXT:    fir.store %[[EXTENT]] to %[[SLOT]] : !fir.ref<index>
-! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHAPE]]) {{.*}}uniq_name("_QFexplicit_shapeEa")
-subroutine explicit_shape(a, n)
-  integer :: n
-  real :: a(4, n)
-end subroutine
-
-! CHECK-LABEL: func.func @_QPchar_len(
-! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
-! CHECK:         %[[UNBOX:.*]]:2 = fir.unboxchar
-! CHECK-NEXT:    fir.store %[[UNBOX]]#1 to %[[SLOT]] : !fir.ref<index>
-! CHECK-NEXT:    hlfir.declare %[[UNBOX]]#0 typeparams %[[UNBOX]]#1 {{.*}}uniq_name("_QFchar_lenEstr")
-subroutine char_len(str)
-  character(*) :: str
-end subroutine
-
-! The assumed-size sentinel gets no slot.
-! CHECK-LABEL: func.func @_QPassumed_size(
-! CHECK-NOT:     fir.debug_bound_slot
-! CHECK:         hlfir.declare %{{.*}}uniq_name("_QFassumed_sizeEa")
-subroutine assumed_size(a)
-  real :: a(*)
-  a(1) = 0.0
-end subroutine
-
-! CHECK-LABEL: func.func @_QPassumed_shape(
-! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
-! CHECK:         %[[SHIFT:.*]] = fir.shift %[[LB:.*]] : (index) -> !fir.shift<1>
-! CHECK-NEXT:    fir.store %[[LB]] to %[[SLOT]] : !fir.ref<index>
-! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHIFT]]) {{.*}}uniq_name("_QFassumed_shapeEx")
-subroutine assumed_shape(x, n)
-  integer :: n
-  real :: x(n:)
-end subroutine
-
-! The slot is in the entry block of the function.
-! CHECK-LABEL: func.func @_QPblock_construct(
-! CHECK:         %[[SLOT:.*]] = fir.alloca index {fir.debug_bound_slot}
-! CHECK:         %[[SHAPE:.*]] = fir.shape %[[EXTENT:.*]] : (index) -> !fir.shape<1>
-! CHECK-NEXT:    fir.store %[[EXTENT]] to %[[SLOT]] : !fir.ref<index>
-! CHECK-NEXT:    hlfir.declare %{{.*}}(%[[SHAPE]]) uniq_name("_QFblock_constructB1Eb")
-subroutine block_construct(n)
-  integer :: n
-  block
-    real :: b(n)
-    b = 0.0
-  end block
-end subroutine
-
-! The declaration in the target region has slots of its own.
-! CHECK-LABEL: func.func @_QPtarget_region(
-! CHECK:         omp.target
-! CHECK:           %[[COUNT_SLOT:.*]] = fir.alloca index <{pinned}> {fir.debug_bound_slot}
-! CHECK:           %[[LB_SLOT:.*]] = fir.alloca index <{pinned}> {fir.debug_bound_slot}
-! CHECK:           %[[SHAPE:.*]] = fir.shape_shift %[[LB:.*]], %[[EXTENT:.*]] : (index, index) -> !fir.shapeshift<1>
-! CHECK-NEXT:      fir.store %[[LB]] to %[[LB_SLOT]] : !fir.ref<index>
-! CHECK-NEXT:      fir.store %[[EXTENT]] to %[[COUNT_SLOT]] : !fir.ref<index>
-! CHECK-NEXT:      hlfir.declare %{{.*}}(%[[SHAPE]]) uniq_name("_QFtarget_regionEarr")
-subroutine target_region(lb, ub)
-  integer :: lb, ub
-  real :: arr(lb:ub)
-  !$omp target map(tofrom: arr)
-  arr = 1.0
-  !$omp end target
-end subroutine
diff --git a/flang/test/Transforms/debug-bounds-stack-slot.fir b/flang/test/Transforms/debug-bounds-stack-slot.fir
index f8613ad6c2458..ca76259daa57f 100644
--- a/flang/test/Transforms/debug-bounds-stack-slot.fir
+++ b/flang/test/Transforms/debug-bounds-stack-slot.fir
@@ -1,43 +1,44 @@
-// RUN: fir-opt --add-debug-info="emit-fake-use-for-debug-vars=true" --mlir-print-debuginfo %s | FileCheck %s
-
-// A bound or length that lowering stored to a slot marked with
-// fir.debug_bound_slot is described by the slot, with a declaration that
-// becomes a dbg.declare, and does not need a fake use.
+// RUN: fir-opt --add-debug-info="emit-fake-use-for-debug-vars=true bounds-in-stack-slots=true" --mlir-print-debuginfo %s | FileCheck %s
+// RUN: fir-opt --add-debug-info="emit-fake-use-for-debug-vars=true" %s | FileCheck %s --check-prefix=NOSLOT
 
 #loc1 = loc("test.f90":1:1)
 #loc3 = loc("test.f90":3:14)
 #loc5 = loc("test.f90":5:1)
 #loc = loc("test.f90":0:0)
 
-module {
+module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
   func.func private @foo()
+
   func.func @test_dynamic_array(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1), %arg2: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_dynamic_array"} {
-    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
-    %1 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
-    %2 = fir.undefined !fir.dscope loc(#loc1)
-    fir.store %arg2 to %1 : !fir.ref<index> loc(#loc3)
-    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
-    %3 = fircg.ext_declare %arg0(%arg1) origin %arg2 dummy_scope %2 arg 1 uniq_name("_QFtest_dynamic_arrayEarr") : (!fir.ref<!fir.array<?xi32>>, index, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+    %0 = fir.undefined !fir.dscope loc(#loc1)
+    %1 = fircg.ext_declare %arg0(%arg1) origin %arg2 dummy_scope %0 arg 1 uniq_name("_QFtest_dynamic_arrayEarr") : (!fir.ref<!fir.array<?xi32>>, index, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
     fir.call @foo() : () -> ()
     return loc(#loc5)
   } loc(#loc1)
 
 // CHECK-LABEL: func.func @test_dynamic_array(
-// CHECK:         %[[COUNT_SLOT:.*]] = fir.alloca index
-// CHECK:         %[[LB_SLOT:.*]] = fir.alloca index
+// CHECK:         %[[COUNT_SLOT:.*]] = fir.alloca index loc(#[[UNK:loc[0-9]+]])
+// CHECK:         %[[LB_SLOT:.*]] = fir.alloca index loc(#[[UNK]])
 // CHECK-NOT:     llvm.intr.dbg.value
-// CHECK:         fircg.ext_declare %[[LB_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr2") : (!fir.ref<index>) -> !fir.ref<index>
-// CHECK:         fircg.ext_declare %[[COUNT_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr1") : (!fir.ref<index>) -> !fir.ref<index>
+// CHECK:         fir.store %arg2 to %[[LB_SLOT]] : !fir.ref<index> loc(#[[UNK]])
+// CHECK:         fircg.ext_declare %[[LB_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr2")
+// CHECK:         fir.store %arg1 to %[[COUNT_SLOT]] : !fir.ref<index> loc(#[[UNK]])
+// CHECK:         fircg.ext_declare %[[COUNT_SLOT]] uniq_name("._QFtest_dynamic_arrayEarr1")
 // CHECK:         fircg.ext_declare %arg0(%arg1) origin %arg2
 // CHECK:         fir.call @foo()
 // CHECK-NEXT:    fir.fake_use %arg0
 // CHECK-NEXT:    return
 
+// NOSLOT-LABEL: func.func @test_dynamic_array(
+// NOSLOT-NOT:    fir.alloca
+// NOSLOT:        llvm.intr.dbg.value
+// NOSLOT:        fir.fake_use %arg0
+// NOSLOT-NEXT:   fir.fake_use %arg1
+// NOSLOT-NEXT:   fir.fake_use %arg2
+
   func.func @test_char(%arg0: !fir.ref<!fir.char<1,?>> {fir.bindc_name = "str"} loc(#loc1), %arg1: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_char"} {
-    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
-    %1 = fir.undefined !fir.dscope loc(#loc1)
-    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
-    %2 = fircg.ext_declare %arg0 typeparams %arg1 dummy_scope %1 arg 1 uniq_name("_QFtest_charEstr") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> !fir.ref<!fir.char<1,?>> loc(#loc3)
+    %0 = fir.undefined !fir.dscope loc(#loc1)
+    %1 = fircg.ext_declare %arg0 typeparams %arg1 dummy_scope %0 arg 1 uniq_name("_QFtest_charEstr") : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> !fir.ref<!fir.char<1,?>> loc(#loc3)
     fir.call @foo() : () -> ()
     return loc(#loc5)
   } loc(#loc1)
@@ -45,48 +46,75 @@ module {
 // CHECK-LABEL: func.func @test_char(
 // CHECK:         %[[LEN_SLOT:.*]] = fir.alloca index
 // CHECK-NOT:     llvm.intr.dbg.value
-// CHECK:         fircg.ext_declare %[[LEN_SLOT]] uniq_name("._QFtest_charEstr1") : (!fir.ref<index>) -> !fir.ref<index>
+// CHECK:         fir.store %arg1 to %[[LEN_SLOT]] : !fir.ref<index>
+// CHECK:         fircg.ext_declare %[[LEN_SLOT]] uniq_name("._QFtest_charEstr1")
 // CHECK:         fircg.ext_declare %arg0 typeparams %arg1
 // CHECK:         fir.call @foo()
 // CHECK-NEXT:    fir.fake_use %arg0
 // CHECK-NEXT:    return
 
-  // An alloca of the same shape that lowering did not mark is left alone.
-  func.func @test_unmarked_slot(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_unmarked_slot"} {
-    %0 = fir.alloca index loc(#loc3)
-    %1 = fir.undefined !fir.dscope loc(#loc1)
-    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
-    %2 = fircg.ext_declare %arg0(%arg1) dummy_scope %1 arg 1 uniq_name("_QFtest_unmarked_slotEarr") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+  // Both dimensions are the same value, and one slot describes them both.
+  func.func @test_repeated_extent(%arg0: !fir.ref<!fir.array<?x?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_repeated_extent"} {
+    %0 = fir.undefined !fir.dscope loc(#loc1)
+    %1 = fircg.ext_declare %arg0(%arg1, %arg1) dummy_scope %0 arg 1 uniq_name("_QFtest_repeated_extentEarr") : (!fir.ref<!fir.array<?x?xi32>>, index, index, !fir.dscope) -> !fir.ref<!fir.array<?x?xi32>> loc(#loc3)
     fir.call @foo() : () -> ()
     return loc(#loc5)
   } loc(#loc1)
 
-// CHECK-LABEL: func.func @test_unmarked_slot(
-// CHECK:         %[[COUNT:.*]] = fir.convert %arg1 : (index) -> i64
-// CHECK:         llvm.intr.dbg.value #{{.*}} = %[[COUNT]] : i64
-// CHECK:         fircg.ext_declare %arg0(%arg1)
-// CHECK:         fir.call @foo()
-// CHECK-NEXT:    fir.fake_use %arg0
-// CHECK-NEXT:    fir.fake_use %arg1
-// CHECK-NEXT:    return
+// CHECK-LABEL: func.func @test_repeated_extent(
+// CHECK:         %[[SLOT:.*]] = fir.alloca index
+// CHECK-NOT:     fir.alloca
+// CHECK:         fir.store %arg1 to %[[SLOT]] : !fir.ref<index>
+// CHECK:         fircg.ext_declare %[[SLOT]] uniq_name("._QFtest_repeated_extentEarr1")
+// CHECK-NOT:     fircg.ext_declare %[[SLOT]]
+// CHECK:         fircg.ext_declare %arg0(%arg1, %arg1)
 
-  // A second store means the slot may not hold the count everywhere.
-  func.func @test_slot_overwritten(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "arr"} loc(#loc1), %arg1: index loc(#loc1), %arg2: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_slot_overwritten"} {
-    %0 = fir.alloca index {fir.debug_bound_slot} loc(#loc3)
-    %1 = fir.undefined !fir.dscope loc(#loc1)
-    fir.store %arg1 to %0 : !fir.ref<index> loc(#loc3)
-    %2 = fircg.ext_declare %arg0(%arg1) dummy_scope %1 arg 1 uniq_name("_QFtest_slot_overwrittenEarr") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
-    fir.store %arg2 to %0 : !fir.ref<index> loc(#loc3)
+  // Two declarations name the same value, and each gets a slot of its own, so
+  // that a slot always belongs to the region of the declaration it describes.
+  func.func @test_shared_extent(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "a"} loc(#loc1), %arg1: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "b"} loc(#loc1), %arg2: index loc(#loc1)) attributes {fir.internal_name = "_QPtest_shared_extent"} {
+    %0 = fir.undefined !fir.dscope loc(#loc1)
+    %1 = fircg.ext_declare %arg0(%arg2) dummy_scope %0 arg 1 uniq_name("_QFtest_shared_extentEa") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+    %2 = fircg.ext_declare %arg1(%arg2) dummy_scope %0 arg 2 uniq_name("_QFtest_shared_extentEb") : (!fir.ref<!fir.array<?xi32>>, index, !fir.dscope) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
     fir.call @foo() : () -> ()
     return loc(#loc5)
   } loc(#loc1)
 
-// CHECK-LABEL: func.func @test_slot_overwritten(
-// CHECK:         %[[COUNT2:.*]] = fir.convert %arg1 : (index) -> i64
-// CHECK:         llvm.intr.dbg.value #{{.*}} = %[[COUNT2]] : i64
-// CHECK:         fircg.ext_declare %arg0(%arg1)
+// The slots go to the start of the block, so the one made last comes first.
+// CHECK-LABEL: func.func @test_shared_extent(
+// CHECK:         %[[B_SLOT:.*]] = fir.alloca index
+// CHECK:         %[[A_SLOT:.*]] = fir.alloca index
+// CHECK:         fir.store %arg2 to %[[A_SLOT]] : !fir.ref<index>
+// CHECK:         fircg.ext_declare %[[A_SLOT]] uniq_name("._QFtest_shared_extentEa1")
+// CHECK:         fircg.ext_declare %arg0(%arg2)
+// CHECK:         fir.store %arg2 to %[[B_SLOT]] : !fir.ref<index>
+// CHECK:         fircg.ext_declare %[[B_SLOT]] uniq_name("._QFtest_shared_extentEb1")
+// CHECK:         fircg.ext_declare %arg1(%arg2)
+// Neither declaration needs a fake use of the extent they share.
 // CHECK:         fir.call @foo()
 // CHECK-NEXT:    fir.fake_use %arg0
 // CHECK-NEXT:    fir.fake_use %arg1
 // CHECK-NEXT:    return
+
+  // A declaration in a target region gets its slot in the region
+  func.func @test_target_region(%arg0: !fir.ref<!fir.array<?xi32>> loc(#loc1), %arg1: !fir.ref<i32> loc(#loc1)) attributes {fir.internal_name = "_QPtest_target_region"} {
+    %0 = omp.map.info var_ptr(%arg0 : !fir.ref<!fir.array<?xi32>>, !fir.array<?xi32>) map_clauses(tofrom) capture(ByRef) -> !fir.ref<!fir.array<?xi32>>
+    %1 = omp.map.info var_ptr(%arg1 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) -> !fir.ref<i32>
+    omp.target kernel_type(generic) map_entries(%0 -> %arg2, %1 -> %arg3 : !fir.ref<!fir.array<?xi32>>, !fir.ref<i32>) {
+      %2 = fir.load %arg3 : !fir.ref<i32>
+      %3 = fir.convert %2 : (i32) -> index
+      %4 = fircg.ext_declare %arg2(%3) uniq_name("_QFtest_target_regionEarr") : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<!fir.array<?xi32>> loc(#loc3)
+      omp.terminator
+    } loc(#loc3)
+    return loc(#loc5)
+  } loc(#loc1)
+
+// CHECK-LABEL: func.func @test_target_region(
+// CHECK:         omp.target
+// CHECK-NEXT:      %[[TGT_SLOT:.*]] = fir.alloca index
+// CHECK:           %[[EXTENT:.*]] = fir.convert %{{.*}} : (i32) -> index
+// CHECK-NEXT:      fir.store %[[EXTENT]] to %[[TGT_SLOT]] : !fir.ref<index>
+// CHECK-NEXT:      fircg.ext_declare %[[TGT_SLOT]] uniq_name("._QFtest_target_regionEarr1")
+// CHECK-NEXT:      fircg.ext_declare %arg2(%[[EXTENT]])
+
+// CHECK:       #[[UNK]] = loc(unknown)
 } loc(#loc)

>From f4549dfdf8e16e4a518e36b404fe3881da9b992e Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Mon, 12 Oct 2026 15:20:15 +0100
Subject: [PATCH 3/4] Add tests for slots without -g and with -O2.

---
 .../Integration/debug-bounds-stack-slot.f90   | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/flang/test/Integration/debug-bounds-stack-slot.f90 b/flang/test/Integration/debug-bounds-stack-slot.f90
index 422a2e2274caf..3bb1743adf6fc 100644
--- a/flang/test/Integration/debug-bounds-stack-slot.f90
+++ b/flang/test/Integration/debug-bounds-stack-slot.f90
@@ -1,4 +1,7 @@
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-llvm -O2 -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefix=OPT
+! RUN: %flang_fc1 -emit-llvm %s -o - | FileCheck %s --check-prefix=NOSLOT
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-tables-only %s -o - | FileCheck %s --check-prefix=NOSLOT
 
 subroutine array(a, lb, n)
   integer :: lb, n
@@ -40,3 +43,20 @@ subroutine char_len(c)
 ! CHECK-DAG: ![[LEN]] = !DILocalVariable(name: "._QFchar_lenEc1"{{.*}}flags: DIFlagArtificial)
 ! CHECK-DAG: !DILocalVariable(name: "c"{{.*}}type: ![[CTY:[0-9]+]])
 ! CHECK-DAG: ![[CTY]] = !DIStringType(stringLength: ![[LEN]], encoding: DW_ATE_ASCII)
+
+! The slots are only made at -O0. Above it a bound is described by its value,
+! as it was before, so a debugger has to find that value in a register.
+! OPT-LABEL: define void @array_(
+! OPT:         #dbg_value({{.*}}![[OPT_LB:[0-9]+]], !DIExpression
+! OPT:         #dbg_value({{.*}}![[OPT_COUNT:[0-9]+]], !DIExpression
+! OPT-LABEL: define void @char_len_(
+! OPT:         #dbg_value({{.*}}![[OPT_LEN:[0-9]+]], !DIExpression
+
+! OPT-DAG: ![[OPT_LB]] = !DILocalVariable(name: "._QFarrayEa2"{{.*}}flags: DIFlagArtificial)
+! OPT-DAG: ![[OPT_COUNT]] = !DILocalVariable(name: "._QFarrayEa1"{{.*}}flags: DIFlagArtificial)
+! OPT-DAG: ![[OPT_LEN]] = !DILocalVariable(name: "._QFchar_lenEc1"{{.*}}flags: DIFlagArtificial)
+
+! A slot exists only to describe a variable, so neither is made when no
+! variables are asked for.
+! NOSLOT-NOT: #dbg_declare
+! NOSLOT-NOT: #dbg_value

>From 9b0b277b088e823664ed5dac57737a4d90ea31b1 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 13 Oct 2026 10:46:37 +0100
Subject: [PATCH 4/4] Fix a typo in a comment.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index 2e43cf1ec9e16..4a9e8ccb91d0c 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -98,7 +98,7 @@ static mlir::LLVM::DITypeAttr genPlaceholderType(mlir::MLIRContext *context) {
 /// \p declOp is visible.
 static fir::AllocaOp createBoundSlot(mlir::Value val,
                                      fir::cg::XDeclareOp declOp) {
-  // Generate the slot and store with UnknownLoc so tha they dont impact the
+  // Generate the slot and store with UnknownLoc so that they don't impact the
   // line table.
   mlir::OpBuilder builder(declOp);
   mlir::Location loc = builder.getUnknownLoc();



More information about the flang-commits mailing list