[flang-commits] [flang] e0424be - Reland [flang][debug] Emit debug info for named constants- #213974 (#215369)

via flang-commits flang-commits at lists.llvm.org
Tue Sep 1 08:21:28 PDT 2026


Author: Abid Qadeer
Date: 2026-09-01T16:21:22+01:00
New Revision: e0424be5bb143bac25257f42fc2d0e217c5e1895

URL: https://github.com/llvm/llvm-project/commit/e0424be5bb143bac25257f42fc2d0e217c5e1895
DIFF: https://github.com/llvm/llvm-project/commit/e0424be5bb143bac25257f42fc2d0e217c5e1895.diff

LOG: Reland [flang][debug] Emit debug info for named constants- #213974 (#215369)

Reland of #213974, which was reverted in #214813 because of #214777.

This is the same as #213974 with a check added to skip a constant for which we only have a declaration. A constant of a user module is still described in the unit defining the module, so nothing is lost there. An intrinsic module has no such unit, so its constants are not described until we emit DW_AT_const_value for them, which can be done separately.

Whether a module is described as a definition or a declaration is now decided up front rather than inferred from whichever member reached getOrCreateModuleAttr first.

Two tests are added: one on the pass, and one checking that the object file has no undefined symbol from iso_fortran_env, which is the case the original change did not cover.

Added: 
    flang/test/Integration/debug-local-storage.f90
    flang/test/Integration/debug-module-constant-relocation.f90
    flang/test/Integration/debug-module-constant.f90
    flang/test/Integration/debug-module-not-defined.f90
    flang/test/Integration/debug-submodule-procedure.F90
    flang/test/Transforms/debug-local-constant.fir
    flang/test/Transforms/debug-module-constant-declaration.fir
    flang/test/Transforms/debug-module-constant.fir
    flang/test/Transforms/debug-module-submodule-procedure.fir

Modified: 
    flang/docs/ReleaseNotes.md
    flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
    flang/test/Integration/debug-use-stmt.f90
    flang/test/Transforms/debug-local-global-storage-1.fir
    flang/test/Transforms/debug-module-1.fir
    flang/test/Transforms/debug-module-line.fir

Removed: 
    


################################################################################
diff  --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 0815355a86844..c8d6466ac60d6 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -57,6 +57,13 @@ page](https://llvm.org/releases/).
   `-mmlir -disable-fir-licm`. The `-mmlir -enable-fir-licm` option that
   previously opted into the pass has been removed.
 
+- Named constants (`PARAMETER`) now appear in the debug information, so a
+  debugger can print them by name. A constant is described only in the
+  compilation unit that defines it: one declared in a module is described
+  where that module is compiled, and one declared in a procedure is local to
+  that unit. Constants of an intrinsic module such as `iso_fortran_env` are
+  not described yet, because no compilation unit defines them.
+
 ## New Compiler Flags
 - Added the gfortran-compatible `-ffpe-trap=` flag, which sets the initial
   floating-point exception halting mode of the main program. It takes a

diff  --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 8ab96172349ce..0847b4d5d3b89 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -77,9 +77,13 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
   /// Maps Fortran module name -> `fir.module_debug_imports`.
   llvm::StringMap<fir::ModuleDebugImportsOp> moduleDebugImportsByName;
 
-  mlir::LLVM::DIModuleAttr getOrCreateModuleAttr(
-      const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
-      mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl);
+  /// Names of the modules whose DIModule this compilation unit defines.
+  llvm::StringSet<> definedModuleNames;
+
+  mlir::LLVM::DIModuleAttr
+  getOrCreateModuleAttr(const std::string &name,
+                        mlir::LLVM::DIFileAttr fileAttr,
+                        mlir::LLVM::DIScopeAttr scope);
   mlir::LLVM::DICommonBlockAttr
   getOrCreateCommonBlockAttr(llvm::StringRef name,
                              mlir::LLVM::DIFileAttr fileAttr,
@@ -109,6 +113,8 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
       mlir::SymbolTable *symbolTable,
       llvm::SetVector<mlir::LLVM::DIImportedEntityAttr> &importedEntities);
   void buildModuleDebugImportsMap(mlir::ModuleOp module);
+  void buildDefinedModuleNames(mlir::ModuleOp module);
+  void markSubmoduleAncestorsDefined(mlir::ModuleOp module);
   void expandUseStmtForDebug(
       fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
       mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
@@ -181,11 +187,22 @@ mlir::StringAttr getTargetFunctionName(mlir::MLIRContext *context,
 
 } // namespace
 
-// Check if a global represents a module variable
-static bool isModuleVariable(fir::GlobalOp globalOp) {
+// Check if a name is that of a data object, which in Fortran is a variable or
+// a named constant.
+static bool isDataObjectName(fir::NameUniquer::NameKind kind) {
+  return kind == fir::NameUniquer::NameKind::VARIABLE ||
+         kind == fir::NameUniquer::NameKind::CONSTANT;
+}
+
+// Check if a name belongs to a module rather than to a procedure.
+static bool isModuleLevelName(const fir::NameUniquer::DeconstructedName &name) {
+  return name.procs.empty() && !name.modules.empty();
+}
+
+// Check if a global represents a data object declared in a module.
+static bool isModuleDataObject(fir::GlobalOp globalOp) {
   std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
-  return result.first == fir::NameUniquer::NameKind::VARIABLE &&
-         result.second.procs.empty() && !result.second.modules.empty();
+  return isDataObjectName(result.first) && isModuleLevelName(result.second);
 }
 
 // Look up DIGlobalVariable from a global symbol
@@ -379,7 +396,7 @@ void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
                                        mlir::Value dummyScope) {
   auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
 
-  if (result.first != fir::NameUniquer::NameKind::VARIABLE)
+  if (!isDataObjectName(result.first))
     return;
 
   if (createCommonBlockGlobal(declOp, result.second.name, fileAttr, scopeAttr,
@@ -434,17 +451,20 @@ mlir::LLVM::DICommonBlockAttr AddDebugInfoPass::getOrCreateCommonBlockAttr(
 // The `module` does not have a first class representation in the `FIR`. We
 // extract information about it from the name of the identifiers and keep a
 // map to avoid duplication.
-mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr(
-    const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
-    mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) {
+mlir::LLVM::DIModuleAttr
+AddDebugInfoPass::getOrCreateModuleAttr(const std::string &name,
+                                        mlir::LLVM::DIFileAttr fileAttr,
+                                        mlir::LLVM::DIScopeAttr scope) {
   mlir::MLIRContext *context = &getContext();
   mlir::LLVM::DIModuleAttr modAttr;
   if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) {
     modAttr = iter->getValue();
   } else {
-    // A module defined in this compilation unit has a fir.module_debug_imports
-    // whose location is that of the MODULE statement. Prefer it over the
-    // caller's guess, which is derived from a member's declaration.
+    unsigned line = 0;
+    bool decl = !definedModuleNames.contains(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())
       line = fir::getLineFromLoc(iter->second.getLoc());
@@ -484,20 +504,14 @@ AddDebugInfoPass::getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
   // of a corresponding module body).
   // But in practice, compilers use declaration attribute with a module in cases
   // where module was defined in another source file (only being used in this
-  // one). The isInitialized() seems to provide the right information
-  // but inverted. It is true where module is actually defined but false where
-  // it is used.
-  unsigned line = fir::getLineFromLoc(globalOp.getLoc());
-
+  // one). Whether that is the case here is settled by getOrCreateModuleAttr.
   mlir::LLVM::DISubprogramAttr sp =
       mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope);
   // Modules are generated at compile unit scope
   if (sp)
     scope = sp.getCompileUnit();
 
-  return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope,
-                               std::max(line - 1, (unsigned)1),
-                               !globalOp.isInitialized());
+  return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope);
 }
 
 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
@@ -512,8 +526,27 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
   mlir::OpBuilder builder(context);
 
   std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
-  if (result.first != fir::NameUniquer::NameKind::VARIABLE)
+  switch (result.first) {
+  case fir::NameUniquer::NameKind::VARIABLE:
+    break;
+  case fir::NameUniquer::NameKind::CONSTANT:
+    // A constant local to a procedure is described while walking that
+    // procedure, where `scope` is its DISubprogramAttr. Reaching here with any
+    // other scope means the procedure is not in the IR, typically because it
+    // was never called and got removed while its constant survived. There is
+    // no procedure to attach the constant to, and describing it at compile
+    // unit scope would wrongly make it visible everywhere.
+    if (!isModuleLevelName(result.second) &&
+        !mlir::isa<mlir::LLVM::DISubprogramAttr>(scope))
+      return;
+    // Don't describe a constant for which we only have a declaration. It could
+    // leave an unresolved symbol in the debug information.
+    if (!globalOp.isInitialized())
+      return;
+    break;
+  default:
     return;
+  }
 
   if (fir::NameUniquer::isSpecialSymbol(result.second.name))
     return;
@@ -524,12 +557,20 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
   if (modOpt)
     scope = *modOpt;
 
+  // An entity with internal linkage, such as a constant or a SAVE variable
+  // declared inside a procedure, is not visible outside this compilation unit.
+  // It also needs no linkage name because there is no external symbol for a
+  // debugger to match it against.
+  const bool isLocalToUnit = globalOp.getLinkName() == "internal";
+  mlir::StringAttr linkageName =
+      isLocalToUnit ? mlir::StringAttr()
+                    : mlir::StringAttr::get(context, globalOp.getName());
+
   mlir::LLVM::DITypeAttr diType =
       typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
       context, scope, mlir::StringAttr::get(context, result.second.name),
-      mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line,
-      diType, /*isLocalToUnit*/ false,
+      linkageName, fileAttr, line, diType, isLocalToUnit,
       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
   auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get(
       globalOp.getContext(), gvAttr, nullptr);
@@ -668,8 +709,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
       }
     }
   } else if (!result.second.modules.empty()) {
-    Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr,
-                                  line - 1, false);
+    Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr);
   }
 
   auto addTargetOpDISP = [&](bool lineTableOnly,
@@ -933,6 +973,62 @@ void AddDebugInfoPass::buildModuleDebugImportsMap(mlir::ModuleOp module) {
   });
 }
 
+// Work out which modules this compilation unit defines. It has to be settled
+// before any DIModuleAttr is built, because the attribute is immutable and the
+// first member to mention a module fixes it for all the others.
+//
+// Lowering emits one fir.module_debug_imports for every module and submodule it
+// compiles, which is what says the module is defined here. No individual member
+// can say it: an array named constant from an intrinsic module, for one, is
+// materialized locally as a linkonce_odr definition in every unit that uses it,
+// although the module is defined in no object file at all.
+void AddDebugInfoPass::buildDefinedModuleNames(mlir::ModuleOp module) {
+  definedModuleNames.clear();
+  for (auto &entry : moduleDebugImportsByName)
+    definedModuleNames.insert(entry.getKey());
+  markSubmoduleAncestorsDefined(module);
+}
+
+// We do not describe submodules yet: a submodule gets no DIModuleAttr of its
+// own and the entities it defines hang off the DIModuleAttr of its ancestor
+// module. So the ancestor has to be a definition in a unit that compiles the
+// submodule. Were it a declaration, it would carry no scope, and those entities
+// would not be able to reach a compile unit and would be dropped from the debug
+// information entirely. Members of a submodule name it in their mangled name,
+// so take the ancestor from them. All of this goes away once submodules are
+// described in their own right.
+void AddDebugInfoPass::markSubmoduleAncestorsDefined(mlir::ModuleOp module) {
+  // The mangled name of a module level global carries its whole module chain,
+  // so mark the ancestor as defined whenever a submodule below it is compiled
+  // here.
+  for (auto globalOp : module.getOps<fir::GlobalOp>()) {
+    std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
+    if (!isModuleLevelName(result.second))
+      continue;
+    llvm::ArrayRef<std::string> modules = result.second.modules;
+    for (const std::string &submodule : modules.drop_front()) {
+      if (moduleDebugImportsByName.contains(submodule)) {
+        definedModuleNames.insert(modules.front());
+        break;
+      }
+    }
+  }
+
+  // Handle a submodule whose members are all procedures, which has no global to
+  // go by. A procedure without a body is defined elsewhere and says nothing
+  // about what this unit defines.
+  for (auto funcOp : module.getOps<mlir::func::FuncOp>()) {
+    if (funcOp.isExternal())
+      continue;
+    mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName());
+    llvm::StringRef name =
+        attr ? mlir::cast<mlir::StringAttr>(attr).getValue() : funcOp.getName();
+    std::pair result = fir::NameUniquer::deconstruct(name);
+    if (!result.second.modules.empty())
+      definedModuleNames.insert(result.second.modules.front());
+  }
+}
+
 void AddDebugInfoPass::expandUseStmtForDebug(
     fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
     mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
@@ -945,8 +1041,7 @@ void AddDebugInfoPass::expandUseStmtForDebug(
 
   mlir::MLIRContext *context = &getContext();
   mlir::LLVM::DIModuleAttr modAttr =
-      getOrCreateModuleAttr(modName, fileAttr, cuAttr, /*line=*/1,
-                            /*decl=*/true);
+      getOrCreateModuleAttr(modName, fileAttr, cuAttr);
 
   llvm::SetVector<mlir::LLVM::DIImportedEntityAttr> importedModules;
   if (useOp.hasOnlyClause() || useOp.getHasOnlyWithRenames())
@@ -982,6 +1077,7 @@ void AddDebugInfoPass::runOnOperation() {
   mlir::MLIRContext *context = &getContext();
   mlir::SymbolTable symbolTable(module);
   buildModuleDebugImportsMap(module);
+  buildDefinedModuleNames(module);
   llvm::StringRef fileName;
   std::string filePath;
   std::optional<mlir::DataLayout> dl =
@@ -1055,7 +1151,7 @@ void AddDebugInfoPass::runOnOperation() {
 
   // Process module globals early.
   // Walk through all DeclareOps in functions and process globals that are
-  // module variables. This ensures that when we process USE statements,
+  // module data objects. This ensures that when we process USE statements,
   // the DIGlobalVariable lookups will succeed.
   if (debugLevel == mlir::LLVM::DIEmissionKind::Full) {
     module.walk([&](fir::cg::XDeclareOp declOp) {
@@ -1063,8 +1159,8 @@ void AddDebugInfoPass::runOnOperation() {
       if (defOp && llvm::isa<fir::AddrOfOp>(defOp)) {
         if (auto globalOp =
                 symbolTable.lookup<fir::GlobalOp>(declOp.getUniqName())) {
-          // Only process module variables here, not SAVE variables
-          if (isModuleVariable(globalOp)) {
+          // Only process module data objects here, not SAVE variables
+          if (isModuleDataObject(globalOp)) {
             handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable,
                            declOp);
           }

diff  --git a/flang/test/Integration/debug-local-storage.f90 b/flang/test/Integration/debug-local-storage.f90
new file mode 100644
index 0000000000000..71fe2d212f80d
--- /dev/null
+++ b/flang/test/Integration/debug-local-storage.f90
@@ -0,0 +1,20 @@
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+
+! A named constant and a SAVE variable declared inside a procedure both have
+! internal linkage. They are described in the scope of that procedure, are
+! local to the compile unit and carry no linkage name. The `name` field being
+! followed directly by `scope` is what checks that no linkage name is emitted.
+
+! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer(kind=4)", size: 32, encoding: DW_ATE_signed)
+! CHECK-DAG: ![[SUB:.*]] = distinct !DISubprogram(name: "counter_fn"{{.*}})
+
+integer function counter_fn()
+! CHECK-DAG: ![[Q:.*]] = distinct !DIGlobalVariable(name: "q", scope: ![[SUB]], file: !{{[0-9]+}}, line: [[@LINE+2]], type: ![[I4]], isLocal: true, isDefinition: true)
+! CHECK-DAG: !DIGlobalVariableExpression(var: ![[Q]], expr: !DIExpression())
+  integer, parameter :: q = 7
+! CHECK-DAG: ![[COUNT:.*]] = distinct !DIGlobalVariable(name: "counter", scope: ![[SUB]], file: !{{[0-9]+}}, line: [[@LINE+2]], type: ![[I4]], isLocal: true, isDefinition: true)
+! CHECK-DAG: !DIGlobalVariableExpression(var: ![[COUNT]], expr: !DIExpression())
+  integer, save :: counter = 0
+  counter = counter + 1
+  counter_fn = q + counter
+end function counter_fn

diff  --git a/flang/test/Integration/debug-module-constant-relocation.f90 b/flang/test/Integration/debug-module-constant-relocation.f90
new file mode 100644
index 0000000000000..51ad55cd3fc89
--- /dev/null
+++ b/flang/test/Integration/debug-module-constant-relocation.f90
@@ -0,0 +1,18 @@
+! REQUIRES: x86-registered-target
+
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-obj -debug-info-kind=standalone %s -o %t.o
+! RUN: llvm-readelf -r %t.o | FileCheck %s
+! RUN: llvm-readelf --symbols %t.o | FileCheck %s --check-prefix=NO_UND
+
+! Test that the object file has no undefined symbol from iso_fortran_env, which
+! means the debug information leaves no unresolved relocation behind.
+
+program p
+  use iso_fortran_env
+  implicit none
+  print *, 'hello'
+end program p
+
+! CHECK: .rela.debug_info
+
+! NO_UND-NOT: UND{{.*}}_QMiso_fortran_env

diff  --git a/flang/test/Integration/debug-module-constant.f90 b/flang/test/Integration/debug-module-constant.f90
new file mode 100644
index 0000000000000..5e48029adc6c8
--- /dev/null
+++ b/flang/test/Integration/debug-module-constant.f90
@@ -0,0 +1,42 @@
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-tables-only %s -o - | FileCheck --check-prefix=LINEONLY %s
+
+! A named constant declared in a module is described like a module variable: in
+! the scope of the module, with a linkage name, and visible outside this compile
+! unit.
+
+! CHECK-DAG: ![[FILE:.*]] = !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: !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: !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: ![[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: ![[STR]] = !DIStringType(size: 40, encoding: DW_ATE_ASCII)
+! CHECK-DAG: !DIGlobalVariableExpression(var: ![[TAG]], expr: !DIExpression())
+  character(len=5), parameter :: tag = "hello"
+end module helper
+
+program test
+  use helper
+  implicit none
+  integer :: n
+  n = max_size + primes(2)
+  print *, pi, tag, n
+end program test
+
+! LINEONLY-NOT: DIGlobalVariable

diff  --git a/flang/test/Integration/debug-module-not-defined.f90 b/flang/test/Integration/debug-module-not-defined.f90
new file mode 100644
index 0000000000000..e8637bcc88b2b
--- /dev/null
+++ b/flang/test/Integration/debug-module-not-defined.f90
@@ -0,0 +1,14 @@
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+
+! A module that this compilation unit does not define is described as a
+! declaration. Nothing here compiles iso_fortran_env, so its DIModule must carry
+! no scope, file or line, even though using it materializes named constants of
+! its own in this unit.
+
+program p
+  use iso_fortran_env
+  implicit none
+  print *, 'hello'
+end program p
+
+! CHECK: !DIModule(scope: null, name: "iso_fortran_env", isDecl: true)

diff  --git a/flang/test/Integration/debug-submodule-procedure.F90 b/flang/test/Integration/debug-submodule-procedure.F90
new file mode 100644
index 0000000000000..d8c8fea8a6cf4
--- /dev/null
+++ b/flang/test/Integration/debug-submodule-procedure.F90
@@ -0,0 +1,24 @@
+! RUN: rm -rf %t && mkdir -p %t
+! RUN: %flang_fc1 -fsyntax-only -DSTEP=1 -J%t %s
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone -J%t %s -o - \
+! RUN:   | FileCheck %s
+
+#if STEP == 1
+module subpar
+  implicit none
+  interface
+    module subroutine hello()
+    end subroutine
+  end interface
+end module subpar
+#else
+submodule (subpar) subkid
+contains
+  module subroutine hello()
+    print *, 'hello from submodule'
+  end subroutine hello
+end submodule subkid
+#endif
+
+! CHECK: !DISubprogram(name: "hello", linkageName: "_QMsubparPhello", scope: ![[MOD:[0-9]+]]
+! CHECK: ![[MOD]] = !DIModule(scope: ![[#]], name: "subpar"

diff  --git a/flang/test/Integration/debug-use-stmt.f90 b/flang/test/Integration/debug-use-stmt.f90
index d55c381deadf0..3a8c16a775aae 100644
--- a/flang/test/Integration/debug-use-stmt.f90
+++ b/flang/test/Integration/debug-use-stmt.f90
@@ -2,27 +2,40 @@
 
 module testmod
   integer :: var_a = 10, var_b = 20, var_c = 30
+  integer, parameter :: par_a = 11, par_b = 22
+  integer, parameter :: tbl(4) = [1, 2, 3, 4]
 end module testmod
 
 module testmod2
   real :: var_x = 1.0, var_y = 2.0
 end module testmod2
 
+module testmod3
+  real, parameter :: par_x = 3.5, par_y = 4.5
+end module testmod3
+
 program test_use
-  use testmod, only: var_b, var_d => var_c
+  use testmod, only: var_b, var_d => var_c, par_a, par_d => par_b, tbl
   use testmod2, var_z => var_y
+  use testmod3, par_z => par_y
   implicit none
   print *, var_b
   print *, var_d
   print *, var_z
+  print *, par_a, par_d, tbl(2), par_z
 end program
 
 ! CHECK-DAG: [[TESTMOD:![0-9]+]] = !DIModule(scope: !{{.*}}, name: "testmod"
 ! CHECK-DAG: [[TESTMOD2:![0-9]+]] = !DIModule(scope: !{{.*}}, name: "testmod2"
+! CHECK-DAG: [[TESTMOD3:![0-9]+]] = !DIModule(scope: !{{.*}}, name: "testmod3"
 
 ! CHECK-DAG: [[VAR_B:![0-9]+]] = distinct !DIGlobalVariable(name: "var_b", linkageName: "_QMtestmodEvar_b"
 ! CHECK-DAG: [[VAR_C:![0-9]+]] = distinct !DIGlobalVariable(name: "var_c", linkageName: "_QMtestmodEvar_c"
 ! CHECK-DAG: [[VAR_Y:![0-9]+]] = distinct !DIGlobalVariable(name: "var_y", linkageName: "_QMtestmod2Evar_y"
+! CHECK-DAG: [[PAR_A:![0-9]+]] = distinct !DIGlobalVariable(name: "par_a", linkageName: "_QMtestmodECpar_a"
+! CHECK-DAG: [[PAR_B:![0-9]+]] = distinct !DIGlobalVariable(name: "par_b", linkageName: "_QMtestmodECpar_b"
+! CHECK-DAG: [[TBL:![0-9]+]] = distinct !DIGlobalVariable(name: "tbl", linkageName: "_QMtestmodECtbl"
+! CHECK-DAG: [[PAR_Y:![0-9]+]] = distinct !DIGlobalVariable(name: "par_y", linkageName: "_QMtestmod3ECpar_y"
 
 ! CHECK-DAG: [[SP:![0-9]+]] = distinct !DISubprogram(name: "test_use", linkageName: "_QQmain"{{.*}}retainedNodes:
 
@@ -31,6 +44,11 @@ program test_use
 ! Check testmod imports: var_b directly (no rename), var_d as rename of var_c
 ! CHECK-DAG: !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: [[SP]], entity: [[VAR_B]],{{.*}}file:{{.*}}line:
 ! CHECK-DAG: !DIImportedEntity(tag: DW_TAG_imported_declaration, name: "var_d", scope: [[SP]], entity: [[VAR_C]],{{.*}}file:{{.*}}line:
+! A named constant reached through ONLY is imported the same way, whether it is
+! scalar or an array, and whether or not it is renamed.
+! CHECK-DAG: !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: [[SP]], entity: [[PAR_A]],{{.*}}file:{{.*}}line:
+! CHECK-DAG: !DIImportedEntity(tag: DW_TAG_imported_declaration, name: "par_d", scope: [[SP]], entity: [[PAR_B]],{{.*}}file:{{.*}}line:
+! CHECK-DAG: !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: [[SP]], entity: [[TBL]],{{.*}}file:{{.*}}line:
 
 ! Check testmod2 import: module imported with rename in elements array
 ! The module import should have elements containing the var_z rename
@@ -38,3 +56,7 @@ program test_use
 ! CHECK-DAG: [[ELEMENTS]] = !{[[VAR_Z:![0-9]+]]}
 ! CHECK-DAG: [[VAR_Z]] = !DIImportedEntity(tag: DW_TAG_imported_declaration, name: "var_z",{{.*}}entity: [[VAR_Y]],
 
+! A named constant renamed without ONLY lands in the same elements array.
+! CHECK-DAG: [[MOD3_IMPORT:![0-9]+]] = !DIImportedEntity(tag: DW_TAG_imported_module, scope: [[SP]], entity: [[TESTMOD3]],{{.*}}elements: [[ELEMENTS3:![0-9]+]]
+! CHECK-DAG: [[ELEMENTS3]] = !{[[PAR_Z:![0-9]+]]}
+! CHECK-DAG: [[PAR_Z]] = !DIImportedEntity(tag: DW_TAG_imported_declaration, name: "par_z",{{.*}}entity: [[PAR_Y]],

diff  --git a/flang/test/Transforms/debug-local-constant.fir b/flang/test/Transforms/debug-local-constant.fir
new file mode 100644
index 0000000000000..7c70637b1f2a4
--- /dev/null
+++ b/flang/test/Transforms/debug-local-constant.fir
@@ -0,0 +1,41 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+// Test that a named constant (Fortran PARAMETER) declared inside a procedure is
+// described in the scope of that procedure. Two procedures declaring the same
+// name must produce entries in distinct scopes, otherwise a debugger cannot
+// tell them apart. Such a constant has internal linkage, so it is local to the
+// compile unit and carries no linkage name.
+
+module {
+  func.func @_QPone() {
+    %0 = fir.address_of(@_QFoneECq) : !fir.ref<i32>
+    %1 = fircg.ext_declare %0 {uniq_name = "_QFoneECq"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc2)
+    return
+  } loc(#loc1)
+  func.func @_QPtwo() {
+    %0 = fir.address_of(@_QFtwoECq) : !fir.ref<i32>
+    %1 = fircg.ext_declare %0 {uniq_name = "_QFtwoECq"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc4)
+    return
+  } loc(#loc3)
+  fir.global internal @_QFoneECq constant : i32 {
+    %c111_i32 = arith.constant 111 : i32
+    fir.has_value %c111_i32 : i32
+  } loc(#loc2)
+  fir.global internal @_QFtwoECq constant : i32 {
+    %c777_i32 = arith.constant 777 : i32
+    fir.has_value %c777_i32 : i32
+  } loc(#loc4)
+}
+#loc1 = loc("test.f90":1:1)
+#loc2 = loc("test.f90":3:3)
+#loc3 = loc("test.f90":7:1)
+#loc4 = loc("test.f90":9:3)
+
+// CHECK-DAG: #[[I4:.*]] = #llvm.di_basic_type<tag = DW_TAG_base_type, name = "integer(kind=4)", sizeInBits = 32, encoding = DW_ATE_signed>
+// CHECK-DAG: #[[ONE:.*]] = #llvm.di_subprogram<{{.*}}name = "one"{{.*}}>
+// CHECK-DAG: #[[TWO:.*]] = #llvm.di_subprogram<{{.*}}name = "two"{{.*}}>
+
+// Each constant is scoped to its own procedure and, having internal linkage,
+// is local to the unit and has no linkage name.
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[ONE]], name = "q", file = {{.*}}, line = 3, type = #[[I4]], isLocalToUnit = true, isDefined = true>
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[TWO]], name = "q", file = {{.*}}, line = 9, type = #[[I4]], isLocalToUnit = true, isDefined = true>

diff  --git a/flang/test/Transforms/debug-local-global-storage-1.fir b/flang/test/Transforms/debug-local-global-storage-1.fir
index 6b9ea5b9dbb2d..572727594c323 100644
--- a/flang/test/Transforms/debug-local-global-storage-1.fir
+++ b/flang/test/Transforms/debug-local-global-storage-1.fir
@@ -1,6 +1,10 @@
 // RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
 
 module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i64, dense<64> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr<272>, dense<64> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<271>, dense<32> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<270>, dense<32> : vector<4xi64>>, #dlti.dl_entry<f128, dense<128> : vector<2xi64>>, #dlti.dl_entry<f80, dense<128> : vector<2xi64>>, #dlti.dl_entry<i128, dense<128> : vector<2xi64>>, #dlti.dl_entry<i8, dense<8> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr, dense<64> : vector<4xi64>>, #dlti.dl_entry<i1, dense<8> : vector<2xi64>>, #dlti.dl_entry<f16, dense<16> : vector<2xi64>>, #dlti.dl_entry<f64, dense<64> : vector<2xi64>>, #dlti.dl_entry<i32, dense<32> : vector<2xi64>>, #dlti.dl_entry<i16, dense<16> : vector<2xi64>>, #dlti.dl_entry<"dlti.stack_alignment", 128 : i64>, #dlti.dl_entry<"dlti.endianness", "little">>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"} {
+  // Marks `example` as defined in this compilation unit, as lowering does for
+  // every module it compiles.
+  fir.module_debug_imports "example" {
+  } loc(#loc0)
   func.func @_QMexamplePmod_sub() {
     fir.use_stmt "example"
     %c2 = arith.constant 2 : index
@@ -36,6 +40,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i64, dense<64> :
     fir.has_value %c2_i32 : i32
   } loc(#loc3)
 }
+#loc0 = loc("test.f90":1:1)
 #loc1 = loc("test.f90":21:1)
 #loc2 = loc("test.f90":22:1)
 #loc3 = loc("test.f90":23:1)
@@ -47,7 +52,10 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i64, dense<64> :
 // CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]]{{.*}}name = "example"{{.*}}>
 // CHECK-DAG: #[[SP:.*]] = #llvm.di_subprogram<{{.*}}name = "test"{{.*}}>
 // CHECK-DAG: #[[MOD_SP:.*]] = #llvm.di_subprogram<{{.*}}name = "mod_sub"{{.*}}retainedNodes = {{.*}}>
-// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "arr"{{.*}}line = 22{{.*}}>
-// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "s"{{.*}}line = 23{{.*}}>
-// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD_SP]], name = "ss"{{.*}}line = 12{{.*}}>
-// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD]], name = "mod_arr"{{.*}}line = 5{{.*}}>
+// A variable with the SAVE attribute inside a procedure has internal linkage,
+// so it is local to the compile unit and has no linkage name. A module
+// variable is visible to other compilation units and keeps its linkage name.
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "arr", file = {{.*}}, line = 22, type = {{.*}}, isLocalToUnit = true, isDefined = true>
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "s", file = {{.*}}, line = 23, type = {{.*}}, isLocalToUnit = true, isDefined = true>
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD_SP]], name = "ss", file = {{.*}}, line = 12, type = {{.*}}, isLocalToUnit = true, isDefined = true>
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD]], name = "mod_arr", linkageName = "_QMexampleEmod_arr"{{.*}}line = 5{{.*}}>

diff  --git a/flang/test/Transforms/debug-module-1.fir b/flang/test/Transforms/debug-module-1.fir
index 18f4892d60632..f68a164b548ed 100644
--- a/flang/test/Transforms/debug-module-1.fir
+++ b/flang/test/Transforms/debug-module-1.fir
@@ -2,6 +2,10 @@
 
 
 module {
+  // Marks `helper` as defined in this compilation unit, as lowering does for
+  // every module it compiles.
+  fir.module_debug_imports "helper" {
+  } loc(#loc0)
   fir.global @_QMhelperEgli : i32 {
     %0 = fir.zero_bits i32
     fir.has_value %0 : i32
@@ -20,6 +24,7 @@ module {
     return
   } loc(#loc3)
 }
+#loc0 = loc("test.f90":10:1)
 #loc1 = loc("test.f90":12:11)
 #loc2 = loc("test.f90":15:8)
 #loc3 = loc("test.f90":20:5)

diff  --git a/flang/test/Transforms/debug-module-constant-declaration.fir b/flang/test/Transforms/debug-module-constant-declaration.fir
new file mode 100644
index 0000000000000..385f9a3c0ddf3
--- /dev/null
+++ b/flang/test/Transforms/debug-module-constant-declaration.fir
@@ -0,0 +1,19 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s --check-prefix=NO_DECL
+
+module {
+  // This constant is defined here and should be described.
+  fir.global @_QMhelperECdefined constant : f32 {
+    %cst = arith.constant 3.14159274 : f32
+    fir.has_value %cst : f32
+  } loc(#loc1)
+  // This constant is only a declaration and should not be described.
+  fir.global @_QMhelperECdeclared constant : f32 loc(#loc2)
+}
+#loc1 = loc("test.f90":8:26)
+#loc2 = loc("test.f90":9:26)
+
+// CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}name = "helper"{{.*}}>
+// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD]], name = "defined"{{.*}}>
+
+// NO_DECL-NOT: name = "declared"

diff  --git a/flang/test/Transforms/debug-module-constant.fir b/flang/test/Transforms/debug-module-constant.fir
new file mode 100644
index 0000000000000..8f0d052d01a61
--- /dev/null
+++ b/flang/test/Transforms/debug-module-constant.fir
@@ -0,0 +1,27 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+// Test that a named constant (Fortran PARAMETER) declared in a module is
+// described in the scope of that module.
+
+module {
+  // Marks `helper` as defined in this compilation unit, as lowering does for
+  // every module it compiles.
+  fir.module_debug_imports "helper" {
+  } loc(#loc0)
+  fir.global @_QMhelperECpi constant : f32 {
+    %cst = arith.constant 3.14159274 : f32
+    fir.has_value %cst : f32
+  } loc(#loc1)
+  func.func @_QMhelperPtest() {
+    return
+  } loc(#loc2)
+}
+#loc0 = loc("test.f90":5:1)
+#loc1 = loc("test.f90":8:26)
+#loc2 = loc("test.f90":12:5)
+
+// CHECK-DAG: #[[R4:.*]] = #llvm.di_basic_type<tag = DW_TAG_base_type, name = "real(kind=4)", sizeInBits = 32, encoding = DW_ATE_float>
+// CHECK-DAG: #[[CU:.*]] = #llvm.di_compile_unit<{{.*}}>
+// CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]], name = "helper"{{.*}}>
+// CHECK-DAG: #[[PI:.*]] = #llvm.di_global_variable<scope = #[[MOD]], name = "pi", linkageName = "_QMhelperECpi"{{.*}}line = 8, type = #[[R4]], isDefined = true>
+// CHECK-DAG: #[[PIE:.*]] = #llvm.di_global_variable_expression<var = #[[PI]]>

diff  --git a/flang/test/Transforms/debug-module-line.fir b/flang/test/Transforms/debug-module-line.fir
index 80e8541b8469e..e4e1ca407c23e 100644
--- a/flang/test/Transforms/debug-module-line.fir
+++ b/flang/test/Transforms/debug-module-line.fir
@@ -1,8 +1,9 @@
 // RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
 
 // The line of a DIModuleAttr comes from the location of the module's
-// `fir.module_debug_imports`, which is that of the MODULE statement. Without
-// one, it falls back to a guess based on the first member of the module.
+// `fir.module_debug_imports`, which is that of the MODULE statement. A module
+// without one is not defined in this compilation unit, so it is described as a
+// declaration and carries no line at all.
 
 module {
   // A module whose MODULE statement is on line 4, well before its first
@@ -27,7 +28,7 @@ module {
     fir.has_value %0 : i32
   } loc(#loc_y)
 
-  // No `fir.module_debug_imports`, so the line is guessed from the member.
+  // No `fir.module_debug_imports`, so this module is only used here.
   fir.global @_QMlegacyEz : i32 {
     %0 = fir.zero_bits i32
     fir.has_value %0 : i32
@@ -45,5 +46,4 @@ module {
 
 // CHECK-DAG: #llvm.di_module<{{.*}}name = "with_use", line = 8>
 
-// Fallback when the module has no `fir.module_debug_imports`.
-// CHECK-DAG: #llvm.di_module<{{.*}}name = "legacy", line = 29>
+// CHECK-DAG: #llvm.di_module<name = "legacy", isDecl = true>

diff  --git a/flang/test/Transforms/debug-module-submodule-procedure.fir b/flang/test/Transforms/debug-module-submodule-procedure.fir
new file mode 100644
index 0000000000000..db054a913e3a4
--- /dev/null
+++ b/flang/test/Transforms/debug-module-submodule-procedure.fir
@@ -0,0 +1,20 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+module {
+  // Marks `subkid` as compiled here, as lowering does for every submodule.
+  fir.module_debug_imports "subkid" {
+  } loc(#loc0)
+  func.func @_QMsubparPhello() {
+    return
+  } loc(#loc1)
+  func.func private @_QMelsewherePthere() loc(#loc2)
+}
+#loc0 = loc("kid.f90":1:1)
+#loc1 = loc("kid.f90":3:3)
+#loc2 = loc("kid.f90":6:3)
+
+// CHECK-DAG: #[[CU:.*]] = #llvm.di_compile_unit<{{.*}}>
+// CHECK-DAG: #[[SUBPAR:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]], name = "subpar"{{.*}}>
+// CHECK-DAG: #[[ELSEWHERE:.*]] = #llvm.di_module<name = "elsewhere", isDecl = true>
+// CHECK-DAG: #llvm.di_subprogram<{{.*}}scope = #[[SUBPAR]], name = "hello", linkageName = "_QMsubparPhello"{{.*}}>
+// CHECK-DAG: #llvm.di_subprogram<{{.*}}scope = #[[ELSEWHERE]], name = "there", linkageName = "_QMelsewherePthere"{{.*}}>


        


More information about the flang-commits mailing list