[flang-commits] [flang] [flang][debug] Emit debug info for procedures from an INCLUDE'd file. (PR #225068)

Abid Qadeer via flang-commits flang-commits at lists.llvm.org
Tue Sep 22 06:40:34 PDT 2026


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

>From 665d47f3e1c4161b3bc2223da1af779b485cc5c4 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Mon, 21 Sep 2026 11:26:38 +0100
Subject: [PATCH 1/5] [flang][debug] Emit debug info for procedures from an
 INCLUDE'd file.

A declaration read through INCLUDE gets a location that is fused with the
inclusion information, carrying a fir::LocationKindArrayAttr.
debugInfoIsAlreadySet treated any fused location as one this pass had
already handled, apart from a fir::LocationKindAttr that is never the
attribute actually used, so handleFuncOp and handleGlobalOp returned
immediately and such a procedure or global got no debug information at all.

Examine what the fusion holds instead of only whether there is one. The
attribute to look for is now a template argument, so each caller names the
one it would attach itself, a DISubprogramAttr for a function and an array
of DIGlobalVariableExpressionAttr for a global, and no central list has to
be kept in step with them.

Doing only that describes the procedure at the line of the INCLUDE
statement rather than at its own, because the file and line are read with
a cast that a fused location does not satisfy, leaving them at the compile
unit's file and at line 1. Search the location for the FileLineColLoc in
both places instead. Searching it also gives a variable inlined from
another procedure the line it is declared on rather than that same
fallback of 1, which is what changes in debug-dummy-argument-inline.fir.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Optimizer/Support/Utils.h |  6 ++-
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp | 26 +++++++----
 flang/test/Integration/debug-include-file.f90 | 35 ++++++++++++++
 .../debug-dummy-argument-inline.fir           |  4 +-
 flang/test/Transforms/debug-include-file.fir  | 46 +++++++++++++++++++
 5 files changed, 104 insertions(+), 13 deletions(-)
 create mode 100644 flang/test/Integration/debug-include-file.f90
 create mode 100644 flang/test/Transforms/debug-include-file.fir

diff --git a/flang/include/flang/Optimizer/Support/Utils.h b/flang/include/flang/Optimizer/Support/Utils.h
index d2f0be15d7dd9..34f41b8cf9524 100644
--- a/flang/include/flang/Optimizer/Support/Utils.h
+++ b/flang/include/flang/Optimizer/Support/Utils.h
@@ -32,10 +32,12 @@
 #include "flang/Optimizer/CodeGen/TypeConverter.h"
 
 namespace fir {
-/// Return the line of a location, or 1 if it does not carry one.
+/// Return the line of a location, or 1 if it does not carry one. The location
+/// can be a fused one, e.g. for something read from an INCLUDE'd file, so
+/// search it rather than expecting a bare FileLineColLoc.
 inline uint32_t getLineFromLoc(mlir::Location loc) {
   uint32_t line = 1;
-  if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(loc))
+  if (auto fileLoc = loc->findInstanceOf<mlir::FileLineColLoc>())
     line = fileLoc.getLine();
   return line;
 }
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 045caafa3e5f9..e710cf003bd4c 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -146,13 +146,17 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
                            fir::cg::XDeclareOp typeGenDeclOp);
 };
 
+/// Whether \p loc already carries debug information of type \c AttrT, fused
+/// onto it by this pass. A location is fused for unrelated reasons too, most
+/// notably one that came from an INCLUDE'd file, so what the fusion holds has
+/// to be examined rather than the fusion merely detected. Each caller names
+/// the attribute it would attach itself, so nothing here needs updating when
+/// another kind of debug information is generated elsewhere. Only the
+/// outermost fusion is examined, because that is the one this pass adds.
+template <typename AttrT>
 bool debugInfoIsAlreadySet(mlir::Location loc) {
-  if (mlir::isa<mlir::FusedLoc>(loc)) {
-    if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>())
-      return false;
-    return true;
-  }
-  return false;
+  auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(loc);
+  return fusedLoc && mlir::isa_and_present<AttrT>(fusedLoc.getMetadata());
 }
 
 // Generates the name for the artificial DISubprogram that we are going to
@@ -566,7 +570,8 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
                                       fir::DebugTypeGenerator &typeGen,
                                       mlir::SymbolTable *symbolTable,
                                       fir::cg::XDeclareOp declOp) {
-  if (debugInfoIsAlreadySet(globalOp.getLoc()))
+  // A global is described by an array of DIGlobalVariableExpressionAttr.
+  if (debugInfoIsAlreadySet<mlir::ArrayAttr>(globalOp.getLoc()))
     return;
   mlir::MLIRContext *context = &getContext();
   mlir::OpBuilder builder(context);
@@ -644,8 +649,9 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
                                     mlir::SymbolTable *symbolTable) {
   mlir::Location l = funcOp->getLoc();
   // If fused location has already been created then nothing to do
-  // Otherwise, create a fused location.
-  if (debugInfoIsAlreadySet(l))
+  // Otherwise, create a fused location. A function is described by a
+  // DISubprogramAttr.
+  if (debugInfoIsAlreadySet<mlir::LLVM::DISubprogramAttr>(l))
     return;
 
   mlir::MLIRContext *context = &getContext();
@@ -656,7 +662,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
                         ? llvm::dwarf::getCallingConvention("DW_CC_program")
                         : llvm::dwarf::getCallingConvention("DW_CC_normal");
 
-  if (auto funcLoc = mlir::dyn_cast<mlir::FileLineColLoc>(l)) {
+  if (auto funcLoc = l->findInstanceOf<mlir::FileLineColLoc>()) {
     fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue());
     filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue());
   }
diff --git a/flang/test/Integration/debug-include-file.f90 b/flang/test/Integration/debug-include-file.f90
new file mode 100644
index 0000000000000..ab69921c98149
--- /dev/null
+++ b/flang/test/Integration/debug-include-file.f90
@@ -0,0 +1,35 @@
+! RUN: split-file %s %t
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone -I %t %t/main.f90 -o - | FileCheck %s
+
+! A procedure written in an INCLUDE'd file gets a location that is fused with
+! the inclusion information. Check that it still gets debug information, and
+! that the information points at the file and line where the procedure is
+! written rather than at the INCLUDE statement.
+
+!--- body.f90
+! Nothing here starts on line 1 on purpose. 1 is also the line that is reported
+! when a position cannot be read out of the location, so checking for it would
+! pass whether or not the location was understood.
+subroutine included_sub(i)
+  integer :: i
+  i = 1
+  call inner()
+contains
+subroutine inner()
+  i = i + 1
+end subroutine
+end subroutine
+
+!--- main.f90
+include 'body.f90'
+program p
+  integer :: i
+  call included_sub(i)
+  print *, i
+end program
+
+! CHECK-DAG: ![[BODY:[0-9]+]] = !DIFile(filename: "body.f90"
+! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 4, {{.*}}scopeLine: 4
+! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 9, {{.*}}scopeLine: 9
+! CHECK-DAG: !DISubprogram(name: "p", linkageName: "_QQmain", {{.*}}file: ![[MAIN:[0-9]+]], line: 2
+! CHECK-DAG: ![[MAIN]] = !DIFile(filename: "main.f90"
diff --git a/flang/test/Transforms/debug-dummy-argument-inline.fir b/flang/test/Transforms/debug-dummy-argument-inline.fir
index 02f0d5c6539b4..e4ec5e182c078 100644
--- a/flang/test/Transforms/debug-dummy-argument-inline.fir
+++ b/flang/test/Transforms/debug-dummy-argument-inline.fir
@@ -2,8 +2,10 @@
 // procedure where the calls were inlined.
 // RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s -o - | FileCheck %s
 
+// The inlined declaration is a callsite location, so its line is the one the
+// variable is declared on in the inlined procedure, not the line of the call.
 // CHECK: #di_local_variable = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file, line = 6, arg = 1, type = #di_basic_type>
-// CHECK: #di_local_variable1 = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file, line = 1, type = #di_basic_type>
+// CHECK: #di_local_variable1 = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file, line = 2, type = #di_basic_type>
 
 func.func @foo_(%arg0: !fir.ref<i32> {fir.bindc_name = "i"} loc("debug-dummy-argument-inline.f90":5:1)) attributes {fir.internal_name = "_QPfoo"} {
   %0 = fir.undefined !fir.dscope loc(#loc5)
diff --git a/flang/test/Transforms/debug-include-file.fir b/flang/test/Transforms/debug-include-file.fir
new file mode 100644
index 0000000000000..007c5932637a1
--- /dev/null
+++ b/flang/test/Transforms/debug-include-file.fir
@@ -0,0 +1,46 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+// Lowering fuses the inclusion information onto the location of anything read
+// through an INCLUDE statement, which is the shape reproduced below. Check that
+// a procedure and a global carrying such a location still get debug
+// information, and that it names the file and line where they are written
+// rather than the INCLUDE statement.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
+  func.func @_QPincluded_sub(%arg0: !fir.ref<i32>) {
+    return loc(#ret_in_body)
+  } loc(#sub_in_body)
+
+  fir.global @_QMmEmodvar : i32 {
+    %c7_i32 = arith.constant 7 : i32 loc(#global_in_body)
+    fir.has_value %c7_i32 : i32 loc(#global_in_body)
+  } loc(#global_in_body)
+
+  func.func @_QQmain() {
+    return loc(#ret_in_main)
+  } loc(#prog_in_main)
+} loc(#module_loc)
+
+#module_loc = loc("main.f90":0:0)
+// The INCLUDE statement itself, on line 1 of the including file.
+#include_stmt = loc("main.f90":1:1)
+
+// Written in the included file, reached through the INCLUDE statement.
+#sub_pos = loc("body.f90":4:1)
+#sub_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#sub_pos, #include_stmt])
+#ret_pos = loc("body.f90":6:1)
+#ret_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#ret_pos, #include_stmt])
+#global_pos = loc("body.f90":2:14)
+#global_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#global_pos, #include_stmt])
+
+// Written in the including file, for contrast.
+#prog_in_main = loc("main.f90":2:1)
+#ret_in_main = loc("main.f90":4:1)
+
+// CHECK-DAG: #[[MAIN:.*]] = #llvm.di_file<"main.f90" in "">
+// CHECK-DAG: #[[BODY:.*]] = #llvm.di_file<"body.f90" in "">
+// CHECK-DAG: #llvm.di_subprogram<{{.*}}name = "included_sub", linkageName = "_QPincluded_sub", file = #[[BODY]], line = 4, scopeLine = 4{{.*}}>
+// CHECK-DAG: #llvm.di_subprogram<{{.*}}name = "_QQmain", linkageName = "_QQmain", file = #[[MAIN]], line = 2, scopeLine = 2{{.*}}>
+// A global takes its file from the compile unit rather than from its own
+// location, so only the line is checked here.
+// CHECK-DAG: #llvm.di_global_variable<{{.*}}name = "modvar", linkageName = "_QMmEmodvar", {{.*}}line = 2{{.*}}>

>From ede37da1adc6c7d230c60764ccaa8af0545ae24c Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 22 Sep 2026 10:11:57 +0100
Subject: [PATCH 2/5] [flang][debug] Describe variables in the file they are
 written in.

Only handleFuncOp derived a file from the operation's own location. A
local, a dummy argument and a global all took the compile unit's file,
while taking their line from their own location, so a variable written in
an INCLUDE'd file was reported at that line of the file doing the
including.

Move that lookup into a helper and use it for the local variable and the
global as well. A location that names no file still falls back to the
compile unit's.

Nothing outside an INCLUDE changes meaning. Where a variable was already in
the file being compiled the emitted DWARF is identical, because the compile
unit's file and the one its location names resolve to the same path, and a
subprogram was described in the latter already. The two are still separate
DIFileAttr in a normal compilation, where the compile unit is built from the
name the input was given together with the working directory and a location
is split into its base name and its parent, so the tests that pinned a
particular one have been updated.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp | 28 +++++++++++--------
 flang/test/Integration/debug-include-file.f90 |  1 +
 flang/test/Integration/debug-module-2.f90     |  4 +--
 .../Integration/debug-module-constant.f90     | 11 +++++---
 .../test/Transforms/debug-derived-type-1.fir  |  2 +-
 .../debug-dummy-argument-inline.fir           |  4 +--
 flang/test/Transforms/debug-include-file.fir  | 15 ++++++----
 7 files changed, 39 insertions(+), 26 deletions(-)

diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index e710cf003bd4c..8734e45de244b 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -159,6 +159,18 @@ bool debugInfoIsAlreadySet(mlir::Location loc) {
   return fusedLoc && mlir::isa_and_present<AttrT>(fusedLoc.getMetadata());
 }
 
+/// The file that \p loc names, or \p fallback if it names none.
+mlir::LLVM::DIFileAttr getFileAttrFromLoc(mlir::Location loc,
+                                          mlir::LLVM::DIFileAttr fallback) {
+  auto fileLoc = loc->findInstanceOf<mlir::FileLineColLoc>();
+  if (!fileLoc)
+    return fallback;
+  llvm::StringRef path = fileLoc.getFilename().getValue();
+  return mlir::LLVM::DIFileAttr::get(loc.getContext(),
+                                     llvm::sys::path::filename(path),
+                                     llvm::sys::path::parent_path(path));
+}
+
 // Generates the name for the artificial DISubprogram that we are going to
 // generate for omp::TargetOp. Its logic is borrowed from
 // getTargetEntryUniqueInfo and
@@ -432,7 +444,8 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
   }
 
   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
-      context, scopeAttr, mlir::StringAttr::get(context, name), fileAttr,
+      context, scopeAttr, mlir::StringAttr::get(context, name),
+      getFileAttrFromLoc(declOp.getLoc(), fileAttr),
       fir::getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, tyAttr,
       mlir::LLVM::DIFlags::Zero);
   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
@@ -622,7 +635,8 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
       typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
       context, scope, mlir::StringAttr::get(context, result.second.name),
-      linkageName, fileAttr, line, diType, isLocalToUnit,
+      linkageName, getFileAttrFromLoc(globalOp.getLoc(), fileAttr), line,
+      diType, isLocalToUnit,
       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
   auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get(
       globalOp.getContext(), gvAttr, nullptr);
@@ -656,17 +670,10 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
 
   mlir::MLIRContext *context = &getContext();
   mlir::OpBuilder builder(context);
-  llvm::StringRef fileName(fileAttr.getName());
-  llvm::StringRef filePath(fileAttr.getDirectory());
   unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry())
                         ? llvm::dwarf::getCallingConvention("DW_CC_program")
                         : llvm::dwarf::getCallingConvention("DW_CC_normal");
 
-  if (auto funcLoc = l->findInstanceOf<mlir::FileLineColLoc>()) {
-    fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue());
-    filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue());
-  }
-
   mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName());
   mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName());
   mlir::StringAttr funcName =
@@ -712,8 +719,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
 
   mlir::LLVM::DISubroutineTypeAttr subTypeAttr =
       mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
-  mlir::LLVM::DIFileAttr funcFileAttr =
-      mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
+  mlir::LLVM::DIFileAttr funcFileAttr = getFileAttrFromLoc(l, fileAttr);
 
   // Only definitions need a distinct identifier and a compilation unit.
   mlir::DistinctAttr id, id2;
diff --git a/flang/test/Integration/debug-include-file.f90 b/flang/test/Integration/debug-include-file.f90
index ab69921c98149..73393dc8bfacc 100644
--- a/flang/test/Integration/debug-include-file.f90
+++ b/flang/test/Integration/debug-include-file.f90
@@ -31,5 +31,6 @@ program p
 ! CHECK-DAG: ![[BODY:[0-9]+]] = !DIFile(filename: "body.f90"
 ! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 4, {{.*}}scopeLine: 4
 ! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 9, {{.*}}scopeLine: 9
+! CHECK-DAG: !DILocalVariable(name: "i", arg: 1, scope: ![[SUB]], file: ![[BODY]], line: 5
 ! CHECK-DAG: !DISubprogram(name: "p", linkageName: "_QQmain", {{.*}}file: ![[MAIN:[0-9]+]], line: 2
 ! CHECK-DAG: ![[MAIN]] = !DIFile(filename: "main.f90"
diff --git a/flang/test/Integration/debug-module-2.f90 b/flang/test/Integration/debug-module-2.f90
index 5e95702e47b75..98396023f45ab 100644
--- a/flang/test/Integration/debug-module-2.f90
+++ b/flang/test/Integration/debug-module-2.f90
@@ -9,11 +9,11 @@
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 module helper
-! CHECK-DAG: ![[GLR:.*]] = distinct !DIGlobalVariable(name: "glr", linkageName: "_QMhelperEglr", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[GLR:.*]] = distinct !DIGlobalVariable(name: "glr", linkageName: "_QMhelperEglr", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[GLRX:.*]] = !DIGlobalVariableExpression(var: ![[GLR]], expr: !DIExpression())
   real glr
 
-! CHECK-DAG: ![[GLI:.*]] = distinct !DIGlobalVariable(name: "gli", linkageName: "_QMhelperEgli", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[GLI:.*]] = distinct !DIGlobalVariable(name: "gli", linkageName: "_QMhelperEgli", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[GLIX:.*]] = !DIGlobalVariableExpression(var: ![[GLI]], expr: !DIExpression())
   integer gli
 
diff --git a/flang/test/Integration/debug-module-constant.f90 b/flang/test/Integration/debug-module-constant.f90
index 5e48029adc6c8..b5d7c9dad2c8a 100644
--- a/flang/test/Integration/debug-module-constant.f90
+++ b/flang/test/Integration/debug-module-constant.f90
@@ -5,27 +5,30 @@
 ! the scope of the module, with a linkage name, and visible outside this compile
 ! unit.
 
+! The compile unit names the file as it was given on the command line, while
+! anything that has a source location names it as the location does.
 ! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: {{.*}}debug-module-constant.f90{{.*}})
+! CHECK-DAG: ![[FILE2:.*]] = !DIFile(filename: {{.*}}debug-module-constant.f90{{.*}})
 ! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[FILE]]{{.*}})
 ! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper"{{.*}})
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 
 module helper
-! CHECK-DAG: ![[MAX:.*]] = distinct !DIGlobalVariable(name: "max_size", linkageName: "_QMhelperECmax_size", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[MAX:.*]] = distinct !DIGlobalVariable(name: "max_size", linkageName: "_QMhelperECmax_size", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[MAX]], expr: !DIExpression())
   integer, parameter :: max_size = 100
 
-! CHECK-DAG: ![[PI:.*]] = distinct !DIGlobalVariable(name: "pi", linkageName: "_QMhelperECpi", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[PI:.*]] = distinct !DIGlobalVariable(name: "pi", linkageName: "_QMhelperECpi", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[PI]], expr: !DIExpression())
   real, parameter :: pi = 3.14159274
 
-! CHECK-DAG: ![[PRIMES:.*]] = distinct !DIGlobalVariable(name: "primes", linkageName: "_QMhelperECprimes", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+3]], type: ![[ARR:.*]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[PRIMES:.*]] = distinct !DIGlobalVariable(name: "primes", linkageName: "_QMhelperECprimes", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+3]], type: ![[ARR:.*]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[ARR]] = !DICompositeType(tag: DW_TAG_array_type, baseType: ![[I4]]{{.*}})
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[PRIMES]], expr: !DIExpression())
   integer, parameter :: primes(3) = [2, 3, 5]
 
-! CHECK-DAG: ![[TAG:.*]] = distinct !DIGlobalVariable(name: "tag", linkageName: "_QMhelperECtag", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+3]], type: ![[STR:.*]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[TAG:.*]] = distinct !DIGlobalVariable(name: "tag", linkageName: "_QMhelperECtag", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+3]], type: ![[STR:.*]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[STR]] = !DIStringType(size: 40, encoding: DW_ATE_ASCII)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[TAG]], expr: !DIExpression())
   character(len=5), parameter :: tag = "hello"
diff --git a/flang/test/Transforms/debug-derived-type-1.fir b/flang/test/Transforms/debug-derived-type-1.fir
index 0440e7dd21091..d8bd0c7e4dec1 100644
--- a/flang/test/Transforms/debug-derived-type-1.fir
+++ b/flang/test/Transforms/debug-derived-type-1.fir
@@ -59,7 +59,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr<272>, d
 // CHECK-DAG: #[[ELMD1:.*]] = #llvm.di_derived_type<tag = DW_TAG_member, name = "year", baseType = #[[INT_TY]], sizeInBits = 32, alignInBits = 32>
 // CHECK-DAG: #[[ELMD2:.*]] = #llvm.di_derived_type<tag = DW_TAG_member, name = "month", baseType = #[[INT_TY]], sizeInBits = 32, alignInBits = 32, offsetInBits = 32>
 // CHECK-DAG: #[[ELMD3:.*]] = #llvm.di_derived_type<tag = DW_TAG_member, name = "day", baseType = #[[INT_TY]], sizeInBits = 32, alignInBits = 32, offsetInBits = 64>
-// CHECK-DAG: #[[DATE:.*]] = #llvm.di_composite_type<{{.*}}tag = DW_TAG_structure_type, name = "t_date", file = #di_file, line = 17, scope = #[[MOD]], sizeInBits = 96, elements = #[[ELMD1]], #[[ELMD2]], #[[ELMD3]]>
+// CHECK-DAG: #[[DATE:.*]] = #llvm.di_composite_type<{{.*}}tag = DW_TAG_structure_type, name = "t_date"{{.*}}line = 17, scope = #[[MOD]], sizeInBits = 96, elements = #[[ELMD1]], #[[ELMD2]], #[[ELMD3]]>
 // CHECK-DAG: #[[ELMP1:.*]] = #llvm.di_derived_type<tag = DW_TAG_member, name = "t_address", baseType = #[[ADDR]], sizeInBits = 32, alignInBits = 32>
 // CHECK-DAG: #[[ELMP2:.*]] = #llvm.di_derived_type<tag = DW_TAG_member, name = "name", baseType = #[[STR_TY]], sizeInBits = 160, alignInBits = 8, offsetInBits = 32>
 // CHECK-DAG: #[[PERS:.*]] = #llvm.di_composite_type<{{.*}}tag = DW_TAG_structure_type, name = "t_person"{{.*}}line = 35, scope = #[[MOD]], sizeInBits = 192, elements = #[[ELMP1]], #[[ELMP2]]>
diff --git a/flang/test/Transforms/debug-dummy-argument-inline.fir b/flang/test/Transforms/debug-dummy-argument-inline.fir
index e4ec5e182c078..db6214d9d9beb 100644
--- a/flang/test/Transforms/debug-dummy-argument-inline.fir
+++ b/flang/test/Transforms/debug-dummy-argument-inline.fir
@@ -4,8 +4,8 @@
 
 // The inlined declaration is a callsite location, so its line is the one the
 // variable is declared on in the inlined procedure, not the line of the call.
-// CHECK: #di_local_variable = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file, line = 6, arg = 1, type = #di_basic_type>
-// CHECK: #di_local_variable1 = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file, line = 2, type = #di_basic_type>
+// CHECK: #di_local_variable = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file1, line = 6, arg = 1, type = #di_basic_type>
+// CHECK: #di_local_variable1 = #llvm.di_local_variable<scope = #di_subprogram, name = "i", file = #di_file1, line = 2, type = #di_basic_type>
 
 func.func @foo_(%arg0: !fir.ref<i32> {fir.bindc_name = "i"} loc("debug-dummy-argument-inline.f90":5:1)) attributes {fir.internal_name = "_QPfoo"} {
   %0 = fir.undefined !fir.dscope loc(#loc5)
diff --git a/flang/test/Transforms/debug-include-file.fir b/flang/test/Transforms/debug-include-file.fir
index 007c5932637a1..d1277d7b35378 100644
--- a/flang/test/Transforms/debug-include-file.fir
+++ b/flang/test/Transforms/debug-include-file.fir
@@ -2,12 +2,14 @@
 
 // Lowering fuses the inclusion information onto the location of anything read
 // through an INCLUDE statement, which is the shape reproduced below. Check that
-// a procedure and a global carrying such a location still get debug
-// information, and that it names the file and line where they are written
-// rather than the INCLUDE statement.
+// a procedure, its dummy argument and a global carrying such a location still
+// get debug information, and that it names the file and line where they are
+// written rather than the INCLUDE statement.
 
 module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
   func.func @_QPincluded_sub(%arg0: !fir.ref<i32>) {
+    %0 = fir.undefined !fir.dscope loc(#sub_in_body)
+    %1 = fircg.ext_declare %arg0 dummy_scope %0 arg 1 {uniq_name = "_QFincluded_subEi"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32> loc(#arg_in_body)
     return loc(#ret_in_body)
   } loc(#sub_in_body)
 
@@ -28,6 +30,8 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
 // Written in the included file, reached through the INCLUDE statement.
 #sub_pos = loc("body.f90":4:1)
 #sub_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#sub_pos, #include_stmt])
+#arg_pos = loc("body.f90":5:14)
+#arg_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#arg_pos, #include_stmt])
 #ret_pos = loc("body.f90":6:1)
 #ret_in_body = loc(fused<#fir<loc_kind_array[<base>, <inclusion>]>>[#ret_pos, #include_stmt])
 #global_pos = loc("body.f90":2:14)
@@ -41,6 +45,5 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
 // CHECK-DAG: #[[BODY:.*]] = #llvm.di_file<"body.f90" in "">
 // CHECK-DAG: #llvm.di_subprogram<{{.*}}name = "included_sub", linkageName = "_QPincluded_sub", file = #[[BODY]], line = 4, scopeLine = 4{{.*}}>
 // CHECK-DAG: #llvm.di_subprogram<{{.*}}name = "_QQmain", linkageName = "_QQmain", file = #[[MAIN]], line = 2, scopeLine = 2{{.*}}>
-// A global takes its file from the compile unit rather than from its own
-// location, so only the line is checked here.
-// CHECK-DAG: #llvm.di_global_variable<{{.*}}name = "modvar", linkageName = "_QMmEmodvar", {{.*}}line = 2{{.*}}>
+// CHECK-DAG: #llvm.di_local_variable<{{.*}}name = "i", file = #[[BODY]], line = 5{{.*}}>
+// CHECK-DAG: #llvm.di_global_variable<{{.*}}name = "modvar", linkageName = "_QMmEmodvar", file = #[[BODY]], line = 2{{.*}}>

>From e3f64a3334c241636304a695b638e943c330e92a Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 22 Sep 2026 11:33:41 +0100
Subject: [PATCH 3/5] [flang][debug] Describe a module in the file it is
 written in.

getOrCreateModuleAttr took the line of a DIModule from the location of the
fir.module_debug_imports op, which is the MODULE statement, but its file
from the compile unit. A module written in an INCLUDE'd file was therefore
reported at that line of the file doing the including. Take the file from
the same op the line already comes from.

The two module tests captured both DIFileAttr with the same pattern and
relied on the order they were emitted in to tell them apart, which this
changes. Match them on how the path is split instead, so that a variable,
the module containing it and the compile unit each say which one they mean.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp |  4 +++-
 flang/test/Integration/debug-module-2.f90     | 16 +++++++++-----
 .../Integration/debug-module-constant.f90     | 22 ++++++++++---------
 3 files changed, 25 insertions(+), 17 deletions(-)

diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 8734e45de244b..1a43778504f0d 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -529,8 +529,10 @@ AddDebugInfoPass::getOrCreateModuleAttr(const std::string &name,
     // The location of the fir.module_debug_imports is that of the MODULE
     // statement. A module that has none is not defined here, and gets no line.
     if (auto iter{moduleDebugImportsByName.find(name)};
-        iter != moduleDebugImportsByName.end())
+        iter != moduleDebugImportsByName.end()) {
       line = fir::getLineFromLoc(iter->second.getLoc());
+      fileAttr = getFileAttrFromLoc(iter->second.getLoc(), fileAttr);
+    }
 
     // When decl is true, it means that module is only being used in this
     // compilation unit and it is defined elsewhere. But if the file/line/scope
diff --git a/flang/test/Integration/debug-module-2.f90 b/flang/test/Integration/debug-module-2.f90
index 98396023f45ab..4e7eb9c3619cf 100644
--- a/flang/test/Integration/debug-module-2.f90
+++ b/flang/test/Integration/debug-module-2.f90
@@ -2,23 +2,27 @@
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-tables-only %s -o - | FileCheck --check-prefix=LINEONLY %s
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-directives-only %s -o - | FileCheck --check-prefix=LINEONLY %s
 
-! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: {{.*}}debug-module-2.f90{{.*}})
-! CHECK-DAG: ![[FILE2:.*]] = !DIFile(filename: {{.*}}debug-module-2.f90{{.*}})
-! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[FILE]]{{.*}} globals: ![[GLOBALS:.*]])
+! Two DIFile name this file: the compile unit's, which keeps the path the
+! driver was given, and the one anything with a source location is described
+! in, which is that path split into a base name and a parent. Match them on
+! that difference rather than on the order they happen to be emitted in.
+! CHECK-DAG: ![[CUFILE:.*]] = !DIFile(filename: "{{.*}}/debug-module-2.f90", directory: "{{.*}}")
+! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: "debug-module-2.f90", directory: "{{.*}}")
+! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[CUFILE]]{{.*}} globals: ![[GLOBALS:.*]])
 ! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE]]{{.*}})
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 module helper
-! CHECK-DAG: ![[GLR:.*]] = distinct !DIGlobalVariable(name: "glr", linkageName: "_QMhelperEglr", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[GLR:.*]] = distinct !DIGlobalVariable(name: "glr", linkageName: "_QMhelperEglr", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[GLRX:.*]] = !DIGlobalVariableExpression(var: ![[GLR]], expr: !DIExpression())
   real glr
 
-! CHECK-DAG: ![[GLI:.*]] = distinct !DIGlobalVariable(name: "gli", linkageName: "_QMhelperEgli", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[GLI:.*]] = distinct !DIGlobalVariable(name: "gli", linkageName: "_QMhelperEgli", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[GLIX:.*]] = !DIGlobalVariableExpression(var: ![[GLI]], expr: !DIExpression())
   integer gli
 
   contains
-!CHECK-DAG: !DISubprogram(name: "test", linkageName: "_QMhelperPtest", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+1]]{{.*}}unit: ![[CU]]{{.*}})
+!CHECK-DAG: !DISubprogram(name: "test", linkageName: "_QMhelperPtest", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+1]]{{.*}}unit: ![[CU]]{{.*}})
     subroutine test()
     glr = 12.34
     gli = 67
diff --git a/flang/test/Integration/debug-module-constant.f90 b/flang/test/Integration/debug-module-constant.f90
index b5d7c9dad2c8a..aa974ac60489e 100644
--- a/flang/test/Integration/debug-module-constant.f90
+++ b/flang/test/Integration/debug-module-constant.f90
@@ -5,30 +5,32 @@
 ! the scope of the module, with a linkage name, and visible outside this compile
 ! unit.
 
-! The compile unit names the file as it was given on the command line, while
-! anything that has a source location names it as the location does.
-! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: {{.*}}debug-module-constant.f90{{.*}})
-! CHECK-DAG: ![[FILE2:.*]] = !DIFile(filename: {{.*}}debug-module-constant.f90{{.*}})
-! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[FILE]]{{.*}})
-! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper"{{.*}})
+! Two DIFile name this file: the compile unit's, which keeps the path the
+! driver was given, and the one anything with a source location is described
+! in, which is that path split into a base name and a parent. Match them on
+! that difference rather than on the order they happen to be emitted in.
+! CHECK-DAG: ![[CUFILE:.*]] = !DIFile(filename: "{{.*}}/debug-module-constant.f90", directory: "{{.*}}")
+! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: "debug-module-constant.f90", directory: "{{.*}}")
+! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[CUFILE]]{{.*}})
+! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE]]{{.*}})
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 
 module helper
-! CHECK-DAG: ![[MAX:.*]] = distinct !DIGlobalVariable(name: "max_size", linkageName: "_QMhelperECmax_size", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[MAX:.*]] = distinct !DIGlobalVariable(name: "max_size", linkageName: "_QMhelperECmax_size", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[MAX]], expr: !DIExpression())
   integer, parameter :: max_size = 100
 
-! CHECK-DAG: ![[PI:.*]] = distinct !DIGlobalVariable(name: "pi", linkageName: "_QMhelperECpi", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[PI:.*]] = distinct !DIGlobalVariable(name: "pi", linkageName: "_QMhelperECpi", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[PI]], expr: !DIExpression())
   real, parameter :: pi = 3.14159274
 
-! CHECK-DAG: ![[PRIMES:.*]] = distinct !DIGlobalVariable(name: "primes", linkageName: "_QMhelperECprimes", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+3]], type: ![[ARR:.*]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[PRIMES:.*]] = distinct !DIGlobalVariable(name: "primes", linkageName: "_QMhelperECprimes", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+3]], type: ![[ARR:.*]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[ARR]] = !DICompositeType(tag: DW_TAG_array_type, baseType: ![[I4]]{{.*}})
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[PRIMES]], expr: !DIExpression())
   integer, parameter :: primes(3) = [2, 3, 5]
 
-! CHECK-DAG: ![[TAG:.*]] = distinct !DIGlobalVariable(name: "tag", linkageName: "_QMhelperECtag", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+3]], type: ![[STR:.*]], isLocal: false, isDefinition: true)
+! CHECK-DAG: ![[TAG:.*]] = distinct !DIGlobalVariable(name: "tag", linkageName: "_QMhelperECtag", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+3]], type: ![[STR:.*]], isLocal: false, isDefinition: true)
 ! CHECK-DAG: ![[STR]] = !DIStringType(size: 40, encoding: DW_ATE_ASCII)
 ! CHECK-DAG: !DIGlobalVariableExpression(var: ![[TAG]], expr: !DIExpression())
   character(len=5), parameter :: tag = "hello"

>From c4cc493bdb73b31b61edc82130aa301a72e855e5 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 22 Sep 2026 11:54:26 +0100
Subject: [PATCH 4/5] [flang][debug] Cover a module read through INCLUDE, and
 match files by use.

The previous commit took a DIModule's file from the MODULE statement rather
than from the compile unit, but nothing tested it. Give the INCLUDE'd file a
module, so its DIModule and its variable are checked against the file and
line they are written in like the procedures already were.

The two module tests told the compile unit's DIFile from the one an
operation is described in by how each spells the path, which only holds
because lit passes an absolute POSIX path. Take the file from the module
that uses it instead, and check what that node is. Nothing is then assumed
about separators, about absolute against relative paths, or about the order
the two are emitted in, and the only literal left is the base name, which
llvm::sys::path::filename guarantees.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/test/Integration/debug-include-file.f90 | 24 ++++++++++++-------
 flang/test/Integration/debug-module-2.f90     | 14 +++++------
 .../Integration/debug-module-constant.f90     | 14 +++++------
 3 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/flang/test/Integration/debug-include-file.f90 b/flang/test/Integration/debug-include-file.f90
index 73393dc8bfacc..3ac6fe8d747c7 100644
--- a/flang/test/Integration/debug-include-file.f90
+++ b/flang/test/Integration/debug-include-file.f90
@@ -1,15 +1,20 @@
 ! RUN: split-file %s %t
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone -I %t %t/main.f90 -o - | FileCheck %s
 
-! A procedure written in an INCLUDE'd file gets a location that is fused with
-! the inclusion information. Check that it still gets debug information, and
-! that the information points at the file and line where the procedure is
-! written rather than at the INCLUDE statement.
+! Anything read through an INCLUDE gets a location that is fused with the
+! inclusion information. Check that a module, a module variable, a procedure,
+! its dummy argument and an internal procedure all still get debug
+! information, and that it points at the file and line where each is written
+! rather than at the INCLUDE statement.
 
 !--- body.f90
 ! Nothing here starts on line 1 on purpose. 1 is also the line that is reported
 ! when a position cannot be read out of the location, so checking for it would
 ! pass whether or not the location was understood.
+module included_mod
+  integer :: modvar = 7
+end module
+
 subroutine included_sub(i)
   integer :: i
   i = 1
@@ -23,14 +28,17 @@ subroutine inner()
 !--- main.f90
 include 'body.f90'
 program p
+  use included_mod
   integer :: i
   call included_sub(i)
-  print *, i
+  print *, i, modvar
 end program
 
 ! CHECK-DAG: ![[BODY:[0-9]+]] = !DIFile(filename: "body.f90"
-! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 4, {{.*}}scopeLine: 4
-! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 9, {{.*}}scopeLine: 9
-! CHECK-DAG: !DILocalVariable(name: "i", arg: 1, scope: ![[SUB]], file: ![[BODY]], line: 5
+! CHECK-DAG: ![[MOD:[0-9]+]] = !DIModule({{.*}}name: "included_mod", file: ![[BODY]], line: 4)
+! CHECK-DAG: !DIGlobalVariable(name: "modvar", linkageName: "_QMincluded_modEmodvar", scope: ![[MOD]], file: ![[BODY]], line: 5
+! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 8, {{.*}}scopeLine: 8
+! CHECK-DAG: !DILocalVariable(name: "i", arg: 1, scope: ![[SUB]], file: ![[BODY]], line: 9
+! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 13, {{.*}}scopeLine: 13
 ! CHECK-DAG: !DISubprogram(name: "p", linkageName: "_QQmain", {{.*}}file: ![[MAIN:[0-9]+]], line: 2
 ! CHECK-DAG: ![[MAIN]] = !DIFile(filename: "main.f90"
diff --git a/flang/test/Integration/debug-module-2.f90 b/flang/test/Integration/debug-module-2.f90
index 4e7eb9c3619cf..bd5dd63508148 100644
--- a/flang/test/Integration/debug-module-2.f90
+++ b/flang/test/Integration/debug-module-2.f90
@@ -2,14 +2,12 @@
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-tables-only %s -o - | FileCheck --check-prefix=LINEONLY %s
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-directives-only %s -o - | FileCheck --check-prefix=LINEONLY %s
 
-! Two DIFile name this file: the compile unit's, which keeps the path the
-! driver was given, and the one anything with a source location is described
-! in, which is that path split into a base name and a parent. Match them on
-! that difference rather than on the order they happen to be emitted in.
-! CHECK-DAG: ![[CUFILE:.*]] = !DIFile(filename: "{{.*}}/debug-module-2.f90", directory: "{{.*}}")
-! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: "debug-module-2.f90", directory: "{{.*}}")
-! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[CUFILE]]{{.*}} globals: ![[GLOBALS:.*]])
-! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE]]{{.*}})
+! More than one DIFile can name this source, so take the one the module is
+! described in from the module itself rather than trying to tell them apart by
+! how each spells the path.
+! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}globals: ![[GLOBALS:.*]])
+! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE:[0-9]+]]{{.*}})
+! CHECK-DAG: ![[FILE]] = !DIFile(filename: "debug-module-2.f90"{{.*}})
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 module helper
diff --git a/flang/test/Integration/debug-module-constant.f90 b/flang/test/Integration/debug-module-constant.f90
index aa974ac60489e..37ae40e94f14e 100644
--- a/flang/test/Integration/debug-module-constant.f90
+++ b/flang/test/Integration/debug-module-constant.f90
@@ -5,14 +5,12 @@
 ! the scope of the module, with a linkage name, and visible outside this compile
 ! unit.
 
-! Two DIFile name this file: the compile unit's, which keeps the path the
-! driver was given, and the one anything with a source location is described
-! in, which is that path split into a base name and a parent. Match them on
-! that difference rather than on the order they happen to be emitted in.
-! CHECK-DAG: ![[CUFILE:.*]] = !DIFile(filename: "{{.*}}/debug-module-constant.f90", directory: "{{.*}}")
-! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: "debug-module-constant.f90", directory: "{{.*}}")
-! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[CUFILE]]{{.*}})
-! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE]]{{.*}})
+! More than one DIFile can name this source, so take the one the module is
+! described in from the module itself rather than trying to tell them apart by
+! how each spells the path.
+! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}})
+! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE:[0-9]+]]{{.*}})
+! CHECK-DAG: ![[FILE]] = !DIFile(filename: "debug-module-constant.f90"{{.*}})
 ! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
 ! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real(kind=4)", size: 32, encoding: DW_ATE_float)
 

>From 3df8e23dc5dd828a9061ee198d098b3f4936bf8f Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 22 Sep 2026 14:10:49 +0100
Subject: [PATCH 5/5] [flang][debug] Describe a derived type in the file it is
 written in.

convertRecordType took a DICompositeType's line from the fir.type_info op,
which sits at the derived type definition, but its file from the compile
unit. A type declared in an INCLUDE'd file was therefore reported in the
file doing the including. Take the file from the same op the line already
comes from.

getFileAttrFromLoc moves to fir:: in Optimizer/Support/Utils.h, next to
getLineFromLoc, so that DebugTypeGenerator can use it too.

A type that gets no fir.type_info carries no position to read and keeps the
compile unit's file and a line of 1, which is why the fallback stays.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Optimizer/Support/Utils.h | 14 +++++++++++
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp | 20 ++++------------
 .../Transforms/DebugTypeGenerator.cpp         | 13 +++++++---
 flang/test/Integration/debug-include-file.f90 | 24 ++++++++++++-------
 4 files changed, 43 insertions(+), 28 deletions(-)

diff --git a/flang/include/flang/Optimizer/Support/Utils.h b/flang/include/flang/Optimizer/Support/Utils.h
index 34f41b8cf9524..a932d9934949e 100644
--- a/flang/include/flang/Optimizer/Support/Utils.h
+++ b/flang/include/flang/Optimizer/Support/Utils.h
@@ -22,11 +22,13 @@
 #include "flang/Support/default-kinds.h"
 #include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/Location.h"
 #include "mlir/Interfaces/FunctionInterfaces.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
 #include <string>
 
 #include "flang/Optimizer/CodeGen/TypeConverter.h"
@@ -42,6 +44,18 @@ inline uint32_t getLineFromLoc(mlir::Location loc) {
   return line;
 }
 
+/// Return the file that \p loc names, or \p fallback if it names none.
+inline mlir::LLVM::DIFileAttr
+getFileAttrFromLoc(mlir::Location loc, mlir::LLVM::DIFileAttr fallback) {
+  auto fileLoc = loc->findInstanceOf<mlir::FileLineColLoc>();
+  if (!fileLoc)
+    return fallback;
+  llvm::StringRef path = fileLoc.getFilename().getValue();
+  return mlir::LLVM::DIFileAttr::get(loc.getContext(),
+                                     llvm::sys::path::filename(path),
+                                     llvm::sys::path::parent_path(path));
+}
+
 /// Return the integer value of a arith::ConstantOp.
 inline std::int64_t toInt(mlir::arith::ConstantOp cop) {
   return mlir::cast<mlir::IntegerAttr>(cop.getValue())
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 1a43778504f0d..41fe99c348f4c 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -159,18 +159,6 @@ bool debugInfoIsAlreadySet(mlir::Location loc) {
   return fusedLoc && mlir::isa_and_present<AttrT>(fusedLoc.getMetadata());
 }
 
-/// The file that \p loc names, or \p fallback if it names none.
-mlir::LLVM::DIFileAttr getFileAttrFromLoc(mlir::Location loc,
-                                          mlir::LLVM::DIFileAttr fallback) {
-  auto fileLoc = loc->findInstanceOf<mlir::FileLineColLoc>();
-  if (!fileLoc)
-    return fallback;
-  llvm::StringRef path = fileLoc.getFilename().getValue();
-  return mlir::LLVM::DIFileAttr::get(loc.getContext(),
-                                     llvm::sys::path::filename(path),
-                                     llvm::sys::path::parent_path(path));
-}
-
 // Generates the name for the artificial DISubprogram that we are going to
 // generate for omp::TargetOp. Its logic is borrowed from
 // getTargetEntryUniqueInfo and
@@ -445,7 +433,7 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
 
   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
       context, scopeAttr, mlir::StringAttr::get(context, name),
-      getFileAttrFromLoc(declOp.getLoc(), fileAttr),
+      fir::getFileAttrFromLoc(declOp.getLoc(), fileAttr),
       fir::getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, tyAttr,
       mlir::LLVM::DIFlags::Zero);
   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
@@ -531,7 +519,7 @@ AddDebugInfoPass::getOrCreateModuleAttr(const std::string &name,
     if (auto iter{moduleDebugImportsByName.find(name)};
         iter != moduleDebugImportsByName.end()) {
       line = fir::getLineFromLoc(iter->second.getLoc());
-      fileAttr = getFileAttrFromLoc(iter->second.getLoc(), fileAttr);
+      fileAttr = fir::getFileAttrFromLoc(iter->second.getLoc(), fileAttr);
     }
 
     // When decl is true, it means that module is only being used in this
@@ -637,7 +625,7 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
       typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
       context, scope, mlir::StringAttr::get(context, result.second.name),
-      linkageName, getFileAttrFromLoc(globalOp.getLoc(), fileAttr), line,
+      linkageName, fir::getFileAttrFromLoc(globalOp.getLoc(), fileAttr), line,
       diType, isLocalToUnit,
       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
   auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get(
@@ -721,7 +709,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
 
   mlir::LLVM::DISubroutineTypeAttr subTypeAttr =
       mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
-  mlir::LLVM::DIFileAttr funcFileAttr = getFileAttrFromLoc(l, fileAttr);
+  mlir::LLVM::DIFileAttr funcFileAttr = fir::getFileAttrFromLoc(l, fileAttr);
 
   // Only definitions need a distinct identifier and a compilation unit.
   mlir::DistinctAttr id, id2;
diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index 96f7078044e78..43f3898043dda 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -405,6 +405,16 @@ mlir::LLVM::DITypeAttr DebugTypeGenerator::convertRecordType(
   if (nameKind != fir::NameUniquer::NameKind::DERIVED_TYPE)
     return genPlaceholderType(context);
 
+  // The fir.type_info is at the derived type definition, so it names the file
+  // the type is written in. That is not the file being compiled when the
+  // definition was read through an INCLUDE statement. A type with no
+  // fir.type_info carries no position at all, and keeps the compile unit's
+  // file and a line of 1.
+  fir::TypeInfoOp tiOp = symbolTable->lookup<fir::TypeInfoOp>(Ty.getName());
+  unsigned line = (tiOp) ? getLineFromLoc(tiOp.getLoc()) : 1;
+  if (tiOp)
+    fileAttr = fir::getFileAttrFromLoc(tiOp.getLoc(), fileAttr);
+
   llvm::SmallVector<mlir::LLVM::DINodeAttr> elements;
   // Generate a place holder TypeAttr which will be used if a member
   // references the parent type.
@@ -419,9 +429,6 @@ mlir::LLVM::DITypeAttr DebugTypeGenerator::convertRecordType(
   DerivedTypeCache::ActiveLevels nestedRecursions =
       derivedTypeCache.startTranslating(Ty, placeHolder);
 
-  fir::TypeInfoOp tiOp = symbolTable->lookup<fir::TypeInfoOp>(Ty.getName());
-  unsigned line = (tiOp) ? getLineFromLoc(tiOp.getLoc()) : 1;
-
   mlir::OpBuilder builder(context);
   mlir::IntegerType intTy = mlir::IntegerType::get(context, 64);
   std::uint64_t offset = 0;
diff --git a/flang/test/Integration/debug-include-file.f90 b/flang/test/Integration/debug-include-file.f90
index 3ac6fe8d747c7..0795a07436e9a 100644
--- a/flang/test/Integration/debug-include-file.f90
+++ b/flang/test/Integration/debug-include-file.f90
@@ -2,16 +2,19 @@
 ! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone -I %t %t/main.f90 -o - | FileCheck %s
 
 ! Anything read through an INCLUDE gets a location that is fused with the
-! inclusion information. Check that a module, a module variable, a procedure,
-! its dummy argument and an internal procedure all still get debug
-! information, and that it points at the file and line where each is written
-! rather than at the INCLUDE statement.
+! inclusion information. Check that a module, a derived type, a module
+! variable, a procedure, its dummy argument and an internal procedure all
+! still get debug information, and that it points at the file and line where
+! each is written rather than at the INCLUDE statement.
 
 !--- body.f90
 ! Nothing here starts on line 1 on purpose. 1 is also the line that is reported
 ! when a position cannot be read out of the location, so checking for it would
 ! pass whether or not the location was understood.
 module included_mod
+  type :: point
+    integer :: x
+  end type
   integer :: modvar = 7
 end module
 
@@ -30,15 +33,18 @@ subroutine inner()
 program p
   use included_mod
   integer :: i
+  type(point) :: pt
   call included_sub(i)
-  print *, i, modvar
+  pt%x = modvar
+  print *, i, pt%x
 end program
 
 ! CHECK-DAG: ![[BODY:[0-9]+]] = !DIFile(filename: "body.f90"
 ! CHECK-DAG: ![[MOD:[0-9]+]] = !DIModule({{.*}}name: "included_mod", file: ![[BODY]], line: 4)
-! CHECK-DAG: !DIGlobalVariable(name: "modvar", linkageName: "_QMincluded_modEmodvar", scope: ![[MOD]], file: ![[BODY]], line: 5
-! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 8, {{.*}}scopeLine: 8
-! CHECK-DAG: !DILocalVariable(name: "i", arg: 1, scope: ![[SUB]], file: ![[BODY]], line: 9
-! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 13, {{.*}}scopeLine: 13
+! CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "point", {{.*}}file: ![[BODY]], line: 5
+! CHECK-DAG: !DIGlobalVariable(name: "modvar", linkageName: "_QMincluded_modEmodvar", scope: ![[MOD]], file: ![[BODY]], line: 8
+! CHECK-DAG: ![[SUB:[0-9]+]] = distinct !DISubprogram(name: "included_sub", linkageName: "included_sub_", {{.*}}file: ![[BODY]], line: 11, {{.*}}scopeLine: 11
+! CHECK-DAG: !DILocalVariable(name: "i", arg: 1, scope: ![[SUB]], file: ![[BODY]], line: 12
+! CHECK-DAG: !DISubprogram(name: "inner", linkageName: "_QFincluded_subPinner", scope: ![[SUB]], file: ![[BODY]], line: 16, {{.*}}scopeLine: 16
 ! CHECK-DAG: !DISubprogram(name: "p", linkageName: "_QQmain", {{.*}}file: ![[MAIN:[0-9]+]], line: 2
 ! CHECK-DAG: ![[MAIN]] = !DIFile(filename: "main.f90"



More information about the flang-commits mailing list