[flang-commits] [flang] 06e70f6 - [flang][debug] Handle USE statements inside modules (#186184)

via flang-commits flang-commits at lists.llvm.org
Tue Apr 21 02:56:07 PDT 2026


Author: Abid Qadeer
Date: 2026-04-21T10:56:03+01:00
New Revision: 06e70f60c9cc25a4dc321c7c9cc3b813e0191e06

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

LOG: [flang][debug] Handle USE statements inside modules (#186184)

Previously, we only generated `fir.use_stmt` when a USE statement was
inside a function. USE statements inside modules were not handled,
resulting in missing debug information for transitive module
dependencies.

The problem can be seen with the following testcase:

```
  module mod_a
    integer :: x = 10
  end module mod_a

  module mod_b
    use mod_a
    integer :: y = 20
  end module mod_b

  program main
    use mod_b
    print *, x, y
  end program main

```
We only generate `fir.use_stmt` for mod_b and nothing for `mod_a`,
making symbols from `mod_a` (like `x`) inaccessible in the debugger.

This PR solves this problem by introducing ModuleDebugImportsOp
which represents a use statement in a module. It is then used to
generate di_imported_entity in AddDebugInfo. Note that we generate
flattened debug information (one di_imported_entity for each use statement
that we can transitively reach in each function). This is to side step a
GDB issue (https://sourceware.org/bugzilla/show_bug.cgi?id=34034).

Fixes https://github.com/llvm/llvm-project/issues/177040

Added: 
    flang/test/Lower/debug-use-stmt-module-multiple.f90
    flang/test/Transforms/debug-module-use-imports.fir

Modified: 
    flang/include/flang/Lower/PFTBuilder.h
    flang/include/flang/Optimizer/Dialect/FIROps.td
    flang/lib/Lower/Bridge.cpp
    flang/lib/Lower/PFTBuilder.cpp
    flang/lib/Optimizer/CodeGen/CodeGen.cpp
    flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
    flang/test/Fir/fir-ops.fir

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index 9cff324692341..55a755acaaeb7 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -768,6 +768,8 @@ struct ModuleLikeUnit : public ProgramUnit {
   ModuleStatement endStmt;
   ContainedUnitList containedUnitList;
   EvaluationList evaluationList;
+  /// Preserved USE statements for debug info generation
+  std::list<Fortran::semantics::PreservedUseStmt> preservedUseStmts;
 };
 
 /// Block data units contain the variables and data initializers for common

diff  --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index d6fe214e0e9a0..eba4ddda4b6ad 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3311,6 +3311,29 @@ def fir_UseStmtOp
 
 def ImplicitFirTerminator : SingleBlockImplicitTerminator<"FirEndOp">;
 
+def fir_ModuleDebugImportsOp : fir_Op<"module_debug_imports", [
+    MemoryEffects<[MemWrite<DebuggingResource>]>,
+    IsolatedFromAbove,
+    ImplicitFirTerminator
+  ]> {
+  let summary = "USE statements inside a Fortran module";
+  let description = [{
+    Records the Fortran `USE` statements that appear in a module's specification
+    part for debug metadata.
+
+    The region holds one `fir.use_stmt` per `USE`, in program order. This
+    operation has no runtime effect. It is emitted only when full debug
+    information is requested.
+  }];
+
+  let arguments = (ins StrAttr:$module_name);
+  let regions = (region SizedRegion<1>:$uses);
+
+  let assemblyFormat = [{
+    $module_name attr-dict-with-keyword $uses
+  }];
+}
+
 def fir_TypeInfoOp : fir_Op<"type_info",
     [IsolatedFromAbove, Symbol, ImplicitFirTerminator]> {
   let summary = "Derived type information";

diff  --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 8d8786a4c25bc..655edc8ffa7b3 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -223,77 +223,111 @@ static mlir::FlatSymbolRefAttr gatherComponentInit(
   return mlir::FlatSymbolRefAttr::get(mlirContext, name);
 }
 
-/// Emit fir.use_stmt operations for USE statements in the given function unit
-static void
-emitUseStatementsFromFunit(Fortran::lower::AbstractConverter &converter,
-                           mlir::OpBuilder &builder, mlir::Location loc,
-                           const Fortran::lower::pft::FunctionLikeUnit &funit) {
+/// Emit a single fir.use_stmt from preserved frontend USE metadata.
+static void emitUseStmtOp(Fortran::lower::AbstractConverter &converter,
+                          mlir::OpBuilder &builder, mlir::Location loc,
+                          const Fortran::semantics::PreservedUseStmt &stmt,
+                          const Fortran::semantics::Scope &lookupScope) {
   mlir::MLIRContext *context = builder.getContext();
-  const Fortran::semantics::Scope &scope = funit.getScope();
+  mlir::StringAttr moduleNameAttr =
+      mlir::StringAttr::get(context, stmt.moduleName);
 
-  for (const auto &preservedStmt : funit.preservedUseStmts) {
+  // Helper function to get mangled name for a symbol
+  auto getMangledName = [&](const std::string &localName) -> std::string {
+    Fortran::parser::CharBlock charBlock{localName.data(), localName.size()};
+    const auto *sym = lookupScope.FindSymbol(charBlock);
+    if (!sym)
+      return "";
 
-    auto getMangledName = [&](const std::string &localName) -> std::string {
-      Fortran::parser::CharBlock charBlock{localName.data(), localName.size()};
-      const auto *sym = scope.FindSymbol(charBlock);
-      if (!sym)
-        return "";
+    const auto &ultimateSym = sym->GetUltimate();
 
-      const auto &ultimateSym = sym->GetUltimate();
+    // Skip cases which can cause mangleName to fail.
+    if (ultimateSym.has<Fortran::semantics::DerivedTypeDetails>())
+      return "";
 
-      // Skip cases which can cause mangleName to fail.
-      if (ultimateSym.has<Fortran::semantics::DerivedTypeDetails>())
-        return "";
+    if (ultimateSym.has<Fortran::semantics::UseErrorDetails>())
+      return "";
 
-      if (ultimateSym.has<Fortran::semantics::UseErrorDetails>())
+    if (const auto *generic =
+            ultimateSym.detailsIf<Fortran::semantics::GenericDetails>()) {
+      if (!generic->specific())
         return "";
+    }
 
-      if (const auto *generic =
-              ultimateSym.detailsIf<Fortran::semantics::GenericDetails>()) {
-        if (!generic->specific())
-          return "";
-      }
-
-      return converter.mangleName(ultimateSym);
-    };
+    return converter.mangleName(ultimateSym);
+  };
 
-    mlir::StringAttr moduleNameAttr =
-        mlir::StringAttr::get(context, preservedStmt.moduleName);
+  llvm::SmallVector<mlir::Attribute> onlySymbolAttrs;
+  llvm::SmallVector<mlir::Attribute> renameAttrs;
 
-    llvm::SmallVector<mlir::Attribute> onlySymbolAttrs;
-    llvm::SmallVector<mlir::Attribute> renameAttrs;
+  for (const auto &name : stmt.onlyNames) {
+    std::string mangledName = getMangledName(name);
+    if (!mangledName.empty())
+      onlySymbolAttrs.push_back(
+          mlir::FlatSymbolRefAttr::get(context, mangledName));
+  }
 
-    // Handle only
-    for (const auto &name : preservedStmt.onlyNames) {
-      std::string mangledName = getMangledName(name);
-      if (!mangledName.empty())
-        onlySymbolAttrs.push_back(
-            mlir::FlatSymbolRefAttr::get(context, mangledName));
+  for (const auto &local : stmt.renames) {
+    std::string mangledName = getMangledName(local);
+    if (!mangledName.empty()) {
+      auto localAttr = mlir::StringAttr::get(context, local);
+      auto symbolRef = mlir::FlatSymbolRefAttr::get(context, mangledName);
+      renameAttrs.push_back(
+          fir::UseRenameAttr::get(context, localAttr, symbolRef));
     }
+  }
 
-    // Handle renames
-    for (const auto &local : preservedStmt.renames) {
-      std::string mangledName = getMangledName(local);
-      if (!mangledName.empty()) {
-        auto localAttr = mlir::StringAttr::get(context, local);
-        auto symbolRef = mlir::FlatSymbolRefAttr::get(context, mangledName);
-        renameAttrs.push_back(
-            fir::UseRenameAttr::get(context, localAttr, symbolRef));
-      }
-    }
+  mlir::ArrayAttr onlySymbolsAttr =
+      onlySymbolAttrs.empty() ? mlir::ArrayAttr()
+                              : mlir::ArrayAttr::get(context, onlySymbolAttrs);
+  mlir::ArrayAttr renamesAttr =
+      renameAttrs.empty() ? mlir::ArrayAttr()
+                          : mlir::ArrayAttr::get(context, renameAttrs);
 
-    // Create optional array attributes
-    mlir::ArrayAttr onlySymbolsAttr =
-        onlySymbolAttrs.empty()
-            ? mlir::ArrayAttr()
-            : mlir::ArrayAttr::get(context, onlySymbolAttrs);
-    mlir::ArrayAttr renamesAttr =
-        renameAttrs.empty() ? mlir::ArrayAttr()
-                            : mlir::ArrayAttr::get(context, renameAttrs);
+  fir::UseStmtOp::create(builder, loc, moduleNameAttr, onlySymbolsAttr,
+                         renamesAttr);
+}
 
-    fir::UseStmtOp::create(builder, loc, moduleNameAttr, onlySymbolsAttr,
-                           renamesAttr);
-  }
+/// Emit fir.module_debug_imports for USE statements in a module.
+static void
+emitModuleDebugImports(Fortran::lower::AbstractConverter &converter,
+                       mlir::OpBuilder &builder, mlir::Location loc,
+                       const Fortran::lower::pft::ModuleLikeUnit &mod) {
+  if (!converter.getLoweringOptions().getPreserveUseDebugInfo())
+    return;
+  if (mod.preservedUseStmts.empty())
+    return;
+
+  const Fortran::semantics::Scope &modScope = mod.getScope();
+  const Fortran::semantics::Symbol *modSym = modScope.symbol();
+  if (!modSym)
+    return;
+
+  mlir::ModuleOp mlirModule = converter.getModuleOp();
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPoint(mlirModule.getBody(), mlirModule.getBody()->end());
+
+  auto op = fir::ModuleDebugImportsOp::create(
+      builder, loc,
+      mlir::StringAttr::get(builder.getContext(), modSym->name().ToString()));
+  mlir::Region &region = op.getUses();
+  mlir::Block *block = new mlir::Block();
+  region.push_back(block);
+  builder.setInsertionPointToStart(block);
+  for (const auto &stmt : mod.preservedUseStmts)
+    emitUseStmtOp(converter, builder, loc, stmt, modScope);
+  fir::FirEndOp::create(builder, loc);
+}
+
+/// Emit `fir.use_stmt` operations for USE statements that appear in the given
+/// function unit.
+static void
+emitUseStatementsFromFunit(Fortran::lower::AbstractConverter &converter,
+                           mlir::OpBuilder &builder, mlir::Location loc,
+                           const Fortran::lower::pft::FunctionLikeUnit &funit) {
+  const Fortran::semantics::Scope &scope = funit.getScope();
+  for (const auto &preservedStmt : funit.preservedUseStmts)
+    emitUseStmtOp(converter, builder, loc, preservedStmt, scope);
 }
 
 /// Helper class to generate the runtime type info global data and the
@@ -6897,6 +6931,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   /// declarative construct.
   void lowerModuleDeclScope(Fortran::lower::pft::ModuleLikeUnit &mod) {
     setCurrentPosition(mod.getStartingSourceLoc());
+    emitModuleDebugImports(*this, *builder, toLocation(), mod);
     auto &scopeVariableListMap =
         Fortran::lower::pft::getScopeVariableListMap(mod);
     for (const auto &var : Fortran::lower::pft::getScopeVariableList(

diff  --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 7e9f8b5de0827..437e70265de94 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -215,17 +215,28 @@ class PFTBuilder {
 
   /// Process USE statements for debug info generation.
   /// Captures USE statement information and stores it in the current
-  /// FunctionLikeUnit for later use.
+  /// FunctionLikeUnit or ModuleLikeUnit for later use.
   void processUseStmt(const parser::UseStmt &useStmt) {
     if (!loweringOptions.getPreserveUseDebugInfo())
       return;
 
-    // Only process USE statements in specification part of function-like units
-    if (specificationPartLevel == 0 || !currentFunctionUnit)
+    if (!currentFunctionUnit && !currentModuleUnit)
+      return;
+
+    // For function-like units, only process USE statements in specification
+    // part.
+    if (currentFunctionUnit && specificationPartLevel == 0)
       return;
 
     std::string moduleName{useStmt.moduleName.source.ToString()};
 
+    auto addUseStmt = [&](Fortran::semantics::PreservedUseStmt &&stmt) {
+      if (currentFunctionUnit)
+        currentFunctionUnit->preservedUseStmts.push_back(std::move(stmt));
+      else if (currentModuleUnit)
+        currentModuleUnit->preservedUseStmts.push_back(std::move(stmt));
+    };
+
     if (const auto *onlyList{
             std::get_if<std::list<parser::Only>>(&useStmt.u)}) {
       // USE mod, ONLY: list
@@ -275,14 +286,14 @@ class PFTBuilder {
             only.u);
       }
 
-      currentFunctionUnit->preservedUseStmts.push_back(std::move(stmt));
+      addUseStmt(std::move(stmt));
     } else if (const auto *renameList{
                    std::get_if<std::list<parser::Rename>>(&useStmt.u)}) {
       // USE mod with optional renames (not ONLY)
       if (renameList->empty()) {
         // USE mod (import all, no renames)
         Fortran::semantics::PreservedUseStmt stmt{moduleName};
-        currentFunctionUnit->preservedUseStmts.push_back(std::move(stmt));
+        addUseStmt(std::move(stmt));
       } else {
         // USE mod, renames (import all with some renames)
         Fortran::semantics::PreservedUseStmt stmt{moduleName};
@@ -302,7 +313,7 @@ class PFTBuilder {
               rename.u);
         }
 
-        currentFunctionUnit->preservedUseStmts.push_back(std::move(stmt));
+        addUseStmt(std::move(stmt));
       }
     }
   }
@@ -411,11 +422,13 @@ class PFTBuilder {
     containedUnitList = &unit.containedUnitList;
     pushEvaluationList(&unit.evaluationList);
     pftParentStack.emplace_back(unit);
+    currentModuleUnit = &unit;
     LLVM_DEBUG(dumpScope(&unit.getScope()));
     return true;
   }
 
   void exitModule() {
+    currentModuleUnit = nullptr; // Clear when exiting module
     containsStmtStack.pop_back();
     if (!evaluationListStack.empty())
       popEvaluationList();
@@ -1250,6 +1263,8 @@ class PFTBuilder {
   lower::pft::Evaluation *lastLexicalEvaluation{};
   /// Current function-like unit being processed (for USE statement tracking)
   lower::pft::FunctionLikeUnit *currentFunctionUnit{nullptr};
+  /// Current module-like unit being processed (for USE statement tracking)
+  lower::pft::ModuleLikeUnit *currentModuleUnit{nullptr};
 };
 
 #ifndef NDEBUG

diff  --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index b03b169e0af4f..4986a20ab3123 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -3695,6 +3695,19 @@ struct UseStmtOpConversion : public fir::FIROpConversion<fir::UseStmtOp> {
   }
 };
 
+/// Erase `fir.module_debug_imports` during LLVM lowering (debug metadata only).
+struct ModuleDebugImportsOpConversion
+    : public fir::FIROpConversion<fir::ModuleDebugImportsOp> {
+  using FIROpConversion::FIROpConversion;
+
+  llvm::LogicalResult
+  matchAndRewrite(fir::ModuleDebugImportsOp op, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override {
+    rewriter.eraseOp(op);
+    return mlir::success();
+  }
+};
+
 static void genCondBrOp(mlir::Location loc, mlir::Value cmp, mlir::Block *dest,
                         std::optional<mlir::ValueRange> destOps,
                         mlir::ConversionPatternRewriter &rewriter,
@@ -4813,8 +4826,9 @@ void fir::populateFIRToLLVMConversionPatterns(
       StoreOpConversion, StringLitOpConversion, SubcOpConversion,
       TypeDescOpConversion, TypeInfoOpConversion, UnboxCharOpConversion,
       UnboxProcOpConversion, UndefOpConversion, UnreachableOpConversion,
-      UseStmtOpConversion, XArrayCoorOpConversion, XEmboxOpConversion,
-      XReboxOpConversion, ZeroOpConversion>(converter, options);
+      UseStmtOpConversion, ModuleDebugImportsOpConversion,
+      XArrayCoorOpConversion, XEmboxOpConversion, XReboxOpConversion,
+      ZeroOpConversion>(converter, options);
 
   // Patterns that are populated without a type converter do not trigger
   // target materializations for the operands of the root op.

diff  --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 389581c376a6d..9a53e7ff11771 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -32,6 +32,7 @@
 #include "mlir/Transforms/DialectConversion.h"
 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
 #include "mlir/Transforms/RegionUtils.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/FileSystem.h"
@@ -73,6 +74,9 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
   llvm::DenseMap<fir::GlobalOp, llvm::SmallVector<mlir::Attribute>>
       globalToGlobalExprsMap;
 
+  /// 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);
@@ -104,6 +108,13 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
       mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
       mlir::SymbolTable *symbolTable,
       llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedEntities);
+  void buildModuleDebugImportsMap(mlir::ModuleOp module);
+  void expandUseStmtForDebug(
+      fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+      mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
+      mlir::SymbolTable *symbolTable,
+      llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedEntities,
+      llvm::StringSet<> &seenModuleNames);
   std::optional<mlir::LLVM::DIImportedEntityAttr> createImportedDeclForGlobal(
       llvm::StringRef symbolName, mlir::LLVM::DISubprogramAttr spAttr,
       mlir::LLVM::DIFileAttr fileAttr, mlir::StringAttr localNameAttr,
@@ -842,42 +853,75 @@ void AddDebugInfoPass::handleRenamesWithoutOnly(
   importedModules.insert(moduleImport);
 }
 
-// Process all USE statements in a function and collect imported entities
+// Process all USE statements in a function and collect imported entities.
 void AddDebugInfoPass::handleUseStatements(
     mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr,
     mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
     mlir::SymbolTable *symbolTable,
     llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedEntities) {
-  mlir::MLIRContext *context = &getContext();
-
+  llvm::StringSet<> seenModuleNames;
   funcOp.walk([&](fir::UseStmtOp useOp) {
-    mlir::LLVM::DIModuleAttr modAttr = getOrCreateModuleAttr(
-        useOp.getModuleName().str(), fileAttr, cuAttr, /*line=*/1,
-        /*decl=*/true);
-
-    llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules;
-
-    if (useOp.hasOnlyClause())
-      handleOnlyClause(useOp, spAttr, fileAttr, symbolTable, importedModules);
-    else if (useOp.hasRenames())
-      handleRenamesWithoutOnly(useOp, spAttr, modAttr, fileAttr, symbolTable,
-                               importedModules);
-    else {
-      // Simple module import
-      auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get(
-          context, llvm::dwarf::DW_TAG_imported_module, spAttr, modAttr,
-          fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {});
-      importedModules.insert(importedEntity);
-    }
+    expandUseStmtForDebug(useOp, spAttr, fileAttr, cuAttr, symbolTable,
+                          importedEntities, seenModuleNames);
+  });
+}
 
-    importedEntities.insert(importedModules.begin(), importedModules.end());
+void AddDebugInfoPass::buildModuleDebugImportsMap(mlir::ModuleOp module) {
+  moduleDebugImportsByName.clear();
+  module.walk([&](fir::ModuleDebugImportsOp op) {
+    moduleDebugImportsByName[op.getModuleName().str()] = op;
   });
 }
 
+void AddDebugInfoPass::expandUseStmtForDebug(
+    fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+    mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
+    mlir::SymbolTable *symbolTable,
+    llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedEntities,
+    llvm::StringSet<> &seenModuleNames) {
+  std::string modName = useOp.getModuleName().str();
+  if (seenModuleNames.contains(modName))
+    return;
+
+  mlir::MLIRContext *context = &getContext();
+  mlir::LLVM::DIModuleAttr modAttr =
+      getOrCreateModuleAttr(modName, fileAttr, cuAttr, /*line=*/1,
+                            /*decl=*/true);
+
+  llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules;
+  if (useOp.hasOnlyClause())
+    handleOnlyClause(useOp, spAttr, fileAttr, symbolTable, importedModules);
+  else if (useOp.hasRenames())
+    handleRenamesWithoutOnly(useOp, spAttr, modAttr, fileAttr, symbolTable,
+                             importedModules);
+  else {
+    auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get(
+        context, llvm::dwarf::DW_TAG_imported_module, spAttr, modAttr, fileAttr,
+        /*line=*/1, /*name=*/nullptr, /*elements*/ {});
+    importedModules.insert(importedEntity);
+  }
+  importedEntities.insert(importedModules.begin(), importedModules.end());
+  seenModuleNames.insert(modName);
+
+  if (useOp.hasOnlyClause())
+    return;
+
+  auto it = moduleDebugImportsByName.find(modName);
+  if (it == moduleDebugImportsByName.end())
+    return;
+  fir::ModuleDebugImportsOp mdi = it->second;
+  if (mdi.getUses().empty())
+    return;
+  for (auto childUse : mdi.getUses().front().getOps<fir::UseStmtOp>())
+    expandUseStmtForDebug(childUse, spAttr, fileAttr, cuAttr, symbolTable,
+                          importedEntities, seenModuleNames);
+}
+
 void AddDebugInfoPass::runOnOperation() {
   mlir::ModuleOp module = getOperation();
   mlir::MLIRContext *context = &getContext();
   mlir::SymbolTable symbolTable(module);
+  buildModuleDebugImportsMap(module);
   llvm::StringRef fileName;
   std::string filePath;
   std::optional<mlir::DataLayout> dl =

diff  --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
index 2dd220134a73d..79e25d286fb3f 100644
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -1106,3 +1106,10 @@ func.func @test_logical_ops(%l1: !fir.logical<4>, %l2: !fir.logical<4>, %i1: i32
   %7 = fir.neqv %i1, %i2 : i32
   return
 }
+
+// CHECK-LABEL: fir.module_debug_imports "debug_mod"
+// CHECK-NEXT: fir.use_stmt "used_mod"
+// CHECK-NEXT: }
+fir.module_debug_imports "debug_mod" {
+  fir.use_stmt "used_mod"
+}

diff  --git a/flang/test/Lower/debug-use-stmt-module-multiple.f90 b/flang/test/Lower/debug-use-stmt-module-multiple.f90
new file mode 100644
index 0000000000000..fdcc079f8987c
--- /dev/null
+++ b/flang/test/Lower/debug-use-stmt-module-multiple.f90
@@ -0,0 +1,42 @@
+! RUN: %flang_fc1 -emit-hlfir -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefix=WITH_DEBUG
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s --check-prefix=NO_DEBUG
+! RUN: %flang_fc1 -emit-hlfir -debug-info-kind=line-tables-only %s -o - | FileCheck %s --check-prefix=NO_DEBUG
+
+module mod_ma
+  integer :: va = 1
+end module mod_ma
+
+module mod_mb
+  integer :: vb = 2
+end module mod_mb
+
+module mod_mc
+  integer :: vc = 3
+end module mod_mc
+
+! A module that uses three 
diff erent modules in its specification part.
+module mod_multi
+  use mod_ma, only: va
+  use mod_mb
+  use mod_mc, renamed_vc => vc
+  integer :: vx = 0
+end module mod_multi
+
+subroutine test_sub_uses_multi
+  use mod_multi
+  implicit none
+  print *, vx
+end subroutine
+
+! WITH_DEBUG: fir.module_debug_imports "mod_multi" {
+! WITH_DEBUG-NEXT: fir.use_stmt "mod_ma" only_symbols{{\[}}[@_QMmod_maEva]]
+! WITH_DEBUG-NEXT: fir.use_stmt "mod_mb"{{$}}
+! WITH_DEBUG-NEXT: fir.use_stmt "mod_mc" renames{{\[}}[#fir.use_rename<"renamed_vc", @_QMmod_mcEvc>]]
+! WITH_DEBUG-NEXT: }
+
+! There should be no more fir.use_stmt for mod_ma, mod_mb and mod_mc.
+! WITH_DEBUG-NOT: fir.use_stmt "mod_ma"
+! WITH_DEBUG-NOT: fir.use_stmt "mod_mb"
+! WITH_DEBUG-NOT: fir.use_stmt "mod_mc"
+
+! NO_DEBUG-NOT: fir.module_debug_imports

diff  --git a/flang/test/Transforms/debug-module-use-imports.fir b/flang/test/Transforms/debug-module-use-imports.fir
new file mode 100644
index 0000000000000..e3127c888e806
--- /dev/null
+++ b/flang/test/Transforms/debug-module-use-imports.fir
@@ -0,0 +1,31 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+// `fir.module_debug_imports` supplies transitive USE metadata; AddDebugInfo
+// flattens it onto the subprogram.
+
+module {
+  fir.global @_QMchildEx : i32 {
+    %0 = fir.zero_bits i32
+    fir.has_value %0 : i32
+  }
+  fir.global @_QMparentEy : i32 {
+    %0 = fir.zero_bits i32
+    fir.has_value %0 : i32
+  }
+  fir.module_debug_imports "parent" {
+    fir.use_stmt "child"
+  }
+  func.func @_QQmain() attributes {fir.bindc_name = "MAIN_USE"} {
+    fir.use_stmt "parent"
+    return
+  } loc(#loc1)
+}
+#loc1 = loc("m.f90":1:1)
+
+// CHECK-DAG: #[[MOD_CHILD:.+]] = #llvm.di_module<{{.*}}name = "child"{{.*}}>
+// CHECK-DAG: #[[MOD_PARENT:.+]] = #llvm.di_module<{{.*}}name = "parent"{{.*}}>
+
+// CHECK-DAG: #[[SP_REC:.+]] = #llvm.di_subprogram<recId = distinct[[[RECID:[0-9]+]]]<>, isRecSelf = true{{.*}}name = "MAIN_USE"
+// CHECK-DAG: #llvm.di_imported_entity<tag = DW_TAG_imported_module, scope = #[[SP_REC]], entity = #[[MOD_CHILD]],{{.*}}>
+// CHECK-DAG: #llvm.di_imported_entity<tag = DW_TAG_imported_module, scope = #[[SP_REC]], entity = #[[MOD_PARENT]],{{.*}}>
+// CHECK-DAG: #llvm.di_subprogram<recId = distinct[[[RECID]]]<>{{.*}}name = "MAIN_USE"{{.*}}retainedNodes = {{.+}}>


        


More information about the flang-commits mailing list