[flang-commits] [flang] [flang] extend debugability of arguments by placing their argument on the stack (PR #185858)

via flang-commits flang-commits at lists.llvm.org
Wed Mar 11 04:12:05 PDT 2026


https://github.com/jeanPerier created https://github.com/llvm/llvm-project/pull/185858

Draft of one possible solution for https://github.com/llvm/llvm-project/issues/185432.
The idea is to put the arguments address on the stack like it is done by clang++ compilers for argument passed by reference at O0.

The drawback of this solution is that it will lead gdb versions < 13 to print bogus info for arguments when breaking on a a function symbols and the debug info for the arguments will only be accurate starting from the first line inside the function. This is because gdb < 13 does not uses the DWARF `prologue_end` flag and its heuristic does not recognize the code copying the argument registers to the stack as a prologue.

It also does not solve some other cases where variables are not available for the whole procedure lifetime in the debugger even at O0, like with automatic variables whose stack address is not a constant offset to the frame register and may currently be kept inside physical register with short lifetime at O0. Arguably, this solution could be extended to save these address inside a stack slot with a fix offset.

However, while working on the patch, I found out that clang has an option to extend liveness of variables for the very purpose of making it debug info accessible over longer code span. I will see if we could use a similar solution with flang at -g O0 instead.

>From eaf7731848e852d2b5f8825a9f612a56e54650dc Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Wed, 11 Mar 2026 02:22:24 -0700
Subject: [PATCH] [flang] extend debugability of arguments by placing their
 argument on the stack

test
---
 flang/lib/Optimizer/CodeGen/CodeGen.cpp      | 72 +++++++++++++++++++-
 flang/test/Integration/debug-argument.f90    | 23 +++++++
 flang/test/Integration/debug-local-var-2.f90 | 18 +++--
 3 files changed, 105 insertions(+), 8 deletions(-)
 create mode 100644 flang/test/Integration/debug-argument.f90

diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index e917eb7e97b6f..0cace57935ff0 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -65,6 +65,7 @@
 #include "mlir/Target/LLVMIR/ModuleTranslation.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/TypeSwitch.h"
+#include "llvm/BinaryFormat/Dwarf.h"
 
 namespace fir {
 #define GEN_PASS_DEF_FIRTOLLVMLOWERING
@@ -246,8 +247,75 @@ struct DeclareOpConversion : public fir::FIROpConversion<fir::cg::XDeclareOp> {
       if (auto varAttr =
               mlir::dyn_cast_or_null<mlir::LLVM::DILocalVariableAttr>(
                   fusedLoc.getMetadata())) {
-        mlir::LLVM::DbgDeclareOp::create(rewriter, memRef.getLoc(), memRef,
-                                         varAttr, nullptr);
+        mlir::Value dbgMemRef = memRef;
+        mlir::LLVM::DIExpressionAttr expr = nullptr;
+        if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(memRef)) {
+          if (blockArg.getOwner() &&
+              llvm::isa_and_nonnull<mlir::FunctionOpInterface>(
+                  blockArg.getOwner()->getParentOp())) {
+            // When emitting DbgDeclareOp on an llvm function argument
+            // directly, the debug info for the variable may not be accessible
+            // for the whole scope of the function. That is because such SSA
+            // value will usually be maintained physical registers as long as
+            // it is used by the program, and LLVM will not maintain these
+            // registers or spill the value to the stack just because it was
+            // used in a DbgDeclareOp. This means that the DWARF generated for
+            // this variable will be only valid and accessible as long as these
+            // registers are maintained, and will appear as "<optimized out>"
+            // afterwards. While this is the expected behavior above O0, this
+            // is not desired at O0 where a user should be able to print info
+            // about the function arguments at any point of the functions.
+            //
+            // To ensure the argument addresses are always accessible at O0,
+            // their address must be placed on the stack in the prologue of the
+            // function and the debug info should use this stack location (with
+            // an implicit dereference to get the variable address and not the
+            // stack address) like this is usually done for C/C++ function
+            // arguments. At O0, llvm will not get rid of this stack allocation
+            // even after its last use, and the debug info will be valid for the
+            // entire function scope.
+            //
+            // The drawback is that when breaking on the procedure symbol, it
+            // is now needed that the debugger stops after the prologue that is
+            // setting up the stack and copying the argument addresses to it.
+            // Otherwise, since the DWARF is pointing to these stack locations,
+            // the debugger will print invalid values for the arguments when
+            // breaking on the function.  This is unfortunate because debuggers
+            // will identify prologue in different ways depending on debugger
+            // versions, so it is not hard to ensure all debuggers will print
+            // accurate info for the arguments on entry.  LLVM will emit a
+            // prologue_end flag to help the debugger to identify prologues,
+            // but it is for instance only used by gdb starting from version
+            // 13.1, and earlier version will use a gdb heuristic to identify
+            // "prologue in Fortran procedure", and this heuristic does not
+            // work well with flang generated code and will usually fallback on
+            // the first function instruction.
+            mlir::OpBuilder::InsertionGuard guard(rewriter);
+            // The location of the alloca and store is set to unknown and they
+            // are placed directly at the function entry so that LLVM will
+            // consider these instructions as belonging to the prologue and
+            // will set the "prologue_end" flag on the first instruction with
+            // line location after that.
+            // Note that in flang, the evaluation of Fortran specification
+            // expressions are not part of the DWARF prologue. Since it involves
+            // potentially complex user code, we want to give the ability to the
+            // user to step in them. Operation generated for specification
+            // expressions are given the line number of the specification
+            // expression appears.
+            mlir::Location unknownLoc = rewriter.getUnknownLoc();
+            auto alloca = genAllocaAndAddrCastWithType(
+                unknownLoc, memRef.getType(), defaultAlign, rewriter);
+            rewriter.setInsertionPointAfter(alloca.getDefiningOp());
+            rewriter.create<mlir::LLVM::StoreOp>(unknownLoc, memRef, alloca);
+            dbgMemRef = alloca;
+            auto derefOp = mlir::LLVM::DIExpressionElemAttr::get(
+                rewriter.getContext(), llvm::dwarf::DW_OP_deref, {});
+            expr = mlir::LLVM::DIExpressionAttr::get(rewriter.getContext(),
+                                                     {derefOp});
+          }
+        }
+        mlir::LLVM::DbgDeclareOp::create(rewriter, memRef.getLoc(), dbgMemRef,
+                                         varAttr, expr);
       }
     }
     rewriter.replaceOp(declareOp, memRef);
diff --git a/flang/test/Integration/debug-argument.f90 b/flang/test/Integration/debug-argument.f90
new file mode 100644
index 0000000000000..4cd004a3204be
--- /dev/null
+++ b/flang/test/Integration/debug-argument.f90
@@ -0,0 +1,23 @@
+! Test that argument addresses are placed on the stack to extend their lifetime
+! when used for debug info.
+
+! RUN: %flang_fc1 -emit-llvm -O0 -debug-info-kind=standalone %s -o - | FileCheck  %s
+
+subroutine foo(x)
+  real :: x
+  call bar(x)
+  call bazz()
+end subroutine
+
+! CHECK-LABEL:   void @foo_(
+! CHECK-SAME:       %[[ARG:.*]])
+! CHECK:         %[[VAL_0:.*]] = alloca ptr
+! CHECK-NOT: !dbg
+! CHECK:         store ptr %[[ARG]], ptr %[[VAL_0]]
+! CHECK-NOT: !dbg
+! CHECK:           #dbg_declare(ptr %[[VAL_0]], ![[X_DBG:.*]], !DIExpression(DW_OP_deref), !{{.*}})
+! CHECK:         call void @bar_(ptr %[[ARG]]), !dbg !{{.*}}
+! CHECK:         call void @bazz_(), !dbg !{{.*}}
+! CHECK:         ret void, !dbg !{{.*}}
+
+! CHECK: ![[X_DBG]] = !DILocalVariable(name: "x", arg: 1,
diff --git a/flang/test/Integration/debug-local-var-2.f90 b/flang/test/Integration/debug-local-var-2.f90
index e95263e6841ad..b0eba55397dfe 100644
--- a/flang/test/Integration/debug-local-var-2.f90
+++ b/flang/test/Integration/debug-local-var-2.f90
@@ -20,18 +20,24 @@
 
 ! BOTH-LABEL: define {{.*}}i64 @_QFPfn1
 ! BOTH-SAME: (ptr {{[^%]*}}%[[ARG1:.*]], ptr {{[^%]*}}%[[ARG2:.*]], ptr {{[^%]*}}%[[ARG3:.*]])
-! RECORDS-DAG: #dbg_declare(ptr %[[ARG1]], ![[A1:.*]], !DIExpression(), !{{.*}})
-! RECORDS-DAG: #dbg_declare(ptr %[[ARG2]], ![[B1:.*]], !DIExpression(), !{{.*}})
-! RECORDS-DAG: #dbg_declare(ptr %[[ARG3]], ![[C1:.*]], !DIExpression(), !{{.*}})
+! RECORDS-DAG: store ptr %[[ARG3]], ptr %[[ARG3S:.*]],
+! RECORDS-DAG: store ptr %[[ARG2]], ptr %[[ARG2S:.*]],
+! RECORDS-DAG: store ptr %[[ARG1]], ptr %[[ARG1S:.*]],
+! RECORDS-DAG: #dbg_declare(ptr %[[ARG1S]], ![[A1:.*]], !DIExpression(DW_OP_deref), !{{.*}})
+! RECORDS-DAG: #dbg_declare(ptr %[[ARG2S]], ![[B1:.*]], !DIExpression(DW_OP_deref), !{{.*}})
+! RECORDS-DAG: #dbg_declare(ptr %[[ARG3S]], ![[C1:.*]], !DIExpression(DW_OP_deref), !{{.*}})
 ! BOTH-DAG: %[[AL2:.*]] = alloca i64
 ! RECORDS-DAG: #dbg_declare(ptr %[[AL2]], ![[RES1:.*]], !DIExpression(), !{{.*}})
 ! BOTH-LABEL: }
 
 ! BOTH-LABEL: define {{.*}}i32 @_QFPfn2
 ! BOTH-SAME: (ptr {{[^%]*}}%[[FN2ARG1:.*]], ptr {{[^%]*}}%[[FN2ARG2:.*]], ptr {{[^%]*}}%[[FN2ARG3:.*]])
-! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG1]], ![[A2:.*]], !DIExpression(), !{{.*}})
-! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG2]], ![[B2:.*]], !DIExpression(), !{{.*}})
-! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG3]], ![[C2:.*]], !DIExpression(), !{{.*}})
+! RECORDS-DAG: store ptr %[[FN2ARG3]], ptr %[[FN2ARG3S:.*]],
+! RECORDS-DAG: store ptr %[[FN2ARG2]], ptr %[[FN2ARG2S:.*]],
+! RECORDS-DAG: store ptr %[[FN2ARG1]], ptr %[[FN2ARG1S:.*]],
+! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG1S]], ![[A2:.*]], !DIExpression(DW_OP_deref), !{{.*}})
+! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG2S]], ![[B2:.*]], !DIExpression(DW_OP_deref), !{{.*}})
+! RECORDS-DAG: #dbg_declare(ptr %[[FN2ARG3S]], ![[C2:.*]], !DIExpression(DW_OP_deref), !{{.*}})
 ! BOTH-DAG: %[[AL3:.*]] = alloca i32
 ! RECORDS-DAG: #dbg_declare(ptr %[[AL3]], ![[RES2:.*]], !DIExpression(), !{{.*}})
 ! BOTH-LABEL: }



More information about the flang-commits mailing list