[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