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

Abid Qadeer via flang-commits flang-commits at lists.llvm.org
Thu Mar 12 10:09:23 PDT 2026


https://github.com/abidh created https://github.com/llvm/llvm-project/pull/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 commit solves this problem by recursively traversing module dependencies and generating `fir.use_stmt` for all transitive USE statements.

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

>From fe1ce33c3f1be95cf415f14edb878e8ff0fb629c Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Thu, 26 Feb 2026 11:23:27 +0000
Subject: [PATCH] [flang][debug] Handle USE statements inside modules

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 commit solves this problem by recursively traversing module
dependencies and generating fir.use_stmt for all transitive USE
statements.

Fixes https://github.com/llvm/llvm-project/issues/177040
---
 flang/include/flang/Lower/PFTBuilder.h        |   2 +
 flang/lib/Lower/Bridge.cpp                    | 121 ++++++++++++++----
 flang/lib/Lower/PFTBuilder.cpp                |  27 +++-
 .../test/Lower/debug-use-stmt-transitive.f90  |  88 +++++++++++++
 4 files changed, 206 insertions(+), 32 deletions(-)
 create mode 100644 flang/test/Lower/debug-use-stmt-transitive.f90

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/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 7459f814a0d4d..7de61ee8826a6 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -223,57 +223,85 @@ static mlir::FlatSymbolRefAttr gatherComponentInit(
   return mlir::FlatSymbolRefAttr::get(mlirContext, name);
 }
 
+/// Find a ModuleLikeUnit by module name in the PFT
+static const Fortran::lower::pft::ModuleLikeUnit *
+findModuleLikeUnit(const Fortran::lower::pft::Program &pft,
+                   const std::string &moduleName) {
+  for (const Fortran::lower::pft::Program::Units &u : pft.getUnits()) {
+    if (const auto *modUnit =
+            std::get_if<Fortran::lower::pft::ModuleLikeUnit>(&u)) {
+      const Fortran::semantics::Scope &modScope = modUnit->getScope();
+      if (const Fortran::semantics::Symbol *symbol = modScope.symbol()) {
+        if (symbol->name().ToString() == moduleName)
+          return modUnit;
+      }
+    }
+  }
+  return nullptr;
+}
+
 /// Emit fir.use_stmt operations for USE statements in the given function unit
+/// and transitive USE statements from modules used by the function
 static void
 emitUseStatementsFromFunit(Fortran::lower::AbstractConverter &converter,
                            mlir::OpBuilder &builder, mlir::Location loc,
-                           const Fortran::lower::pft::FunctionLikeUnit &funit) {
+                           const Fortran::lower::pft::FunctionLikeUnit &funit,
+                           const Fortran::lower::pft::Program *pft) {
   mlir::MLIRContext *context = builder.getContext();
   const Fortran::semantics::Scope &scope = funit.getScope();
 
-  for (const auto &preservedStmt : funit.preservedUseStmts) {
+  // Track which modules we've already emitted USE statements for to avoid
+  // duplicates in transitive cases
+  llvm::SmallSet<std::string, 4> emittedModules;
 
-    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 "";
+  // Helper function to get mangled name for a symbol in a given scope
+  auto getMangledName =
+      [&](const std::string &localName,
+          const Fortran::semantics::Scope &lookupScope) -> std::string {
+    Fortran::parser::CharBlock charBlock{localName.data(), localName.size()};
+    const auto *sym = lookupScope.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>())
+      return "";
 
-      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);
+  };
 
+  // Helper function to emit a single USE statement with ONLY and renames.
+  // Uses the provided scope for symbol lookup
+  auto emitUseStmt = [&](const Fortran::semantics::PreservedUseStmt &stmt,
+                         const Fortran::semantics::Scope &lookupScope) {
     mlir::StringAttr moduleNameAttr =
-        mlir::StringAttr::get(context, preservedStmt.moduleName);
+        mlir::StringAttr::get(context, stmt.moduleName);
 
     llvm::SmallVector<mlir::Attribute> onlySymbolAttrs;
     llvm::SmallVector<mlir::Attribute> renameAttrs;
 
     // Handle only
-    for (const auto &name : preservedStmt.onlyNames) {
-      std::string mangledName = getMangledName(name);
+    for (const auto &name : stmt.onlyNames) {
+      std::string mangledName = getMangledName(name, lookupScope);
       if (!mangledName.empty())
         onlySymbolAttrs.push_back(
             mlir::FlatSymbolRefAttr::get(context, mangledName));
     }
 
     // Handle renames
-    for (const auto &local : preservedStmt.renames) {
-      std::string mangledName = getMangledName(local);
+    for (const auto &local : stmt.renames) {
+      std::string mangledName = getMangledName(local, lookupScope);
       if (!mangledName.empty()) {
         auto localAttr = mlir::StringAttr::get(context, local);
         auto symbolRef = mlir::FlatSymbolRefAttr::get(context, mangledName);
@@ -293,6 +321,42 @@ emitUseStatementsFromFunit(Fortran::lower::AbstractConverter &converter,
 
     fir::UseStmtOp::create(builder, loc, moduleNameAttr, onlySymbolsAttr,
                            renamesAttr);
+  };
+
+  // Recursive lambda to emit transitive USE statements
+  std::function<void(const std::string &)> emitTransitiveUses =
+      [&](const std::string &moduleName) {
+        if (!pft)
+          return;
+
+        if (const auto *modUnit = findModuleLikeUnit(*pft, moduleName)) {
+          // Get the module's scope for symbol lookup
+          const Fortran::semantics::Scope &modScope = modUnit->getScope();
+          // For each USE statement in this module, first recursively emit
+          // transitive USE statements for the used module (depth-first),
+          // then emit this module's USE statement.
+          for (const auto &modUseStmt : modUnit->preservedUseStmts) {
+            emitTransitiveUses(modUseStmt.moduleName);
+
+            if (!emittedModules.contains(modUseStmt.moduleName)) {
+              emitUseStmt(modUseStmt, modScope);
+              emittedModules.insert(modUseStmt.moduleName);
+            }
+          }
+        }
+      };
+
+  // Emit direct USE statements from the function
+  for (const auto &preservedStmt : funit.preservedUseStmts) {
+    if (emittedModules.contains(preservedStmt.moduleName))
+      continue;
+
+    // First, recursively emit transitive USE statements (depth-first)
+    emitTransitiveUses(preservedStmt.moduleName);
+
+    // Then emit this function's direct USE statement
+    emitUseStmt(preservedStmt, scope);
+    emittedModules.insert(preservedStmt.moduleName);
   }
 }
 
@@ -482,6 +546,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   /// Convert the PFT to FIR.
   void run(Fortran::lower::pft::Program &pft) {
+    currentPft = &pft;
     // Preliminary translation pass.
 
     // Lower common blocks, taking into account initialization and the largest
@@ -1317,6 +1382,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   FirConverter(const FirConverter &) = delete;
   FirConverter &operator=(const FirConverter &) = delete;
 
+  /// Current PFT being processed (for finding modules with USE statements)
+  Fortran::lower::pft::Program *currentPft{nullptr};
+
   //===--------------------------------------------------------------------===//
   // Helper member functions
   //===--------------------------------------------------------------------===//
@@ -6488,7 +6556,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     mapDummiesAndResults(funit, callee);
 
     // Emit USE statement operations for debug info generation
-    emitUseStatementsFromFunit(*this, *builder, toLocation(), funit);
+    emitUseStatementsFromFunit(*this, *builder, toLocation(), funit,
+                               currentPft);
 
     // Map host associated symbols from parent procedure if any.
     if (funit.parentHasHostAssoc())
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 7e9f8b5de0827..36ee2adda3fb5 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 For module-like units, process USE statements anywhere in the module
+    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/test/Lower/debug-use-stmt-transitive.f90 b/flang/test/Lower/debug-use-stmt-transitive.f90
new file mode 100644
index 0000000000000..4851824909645
--- /dev/null
+++ b/flang/test/Lower/debug-use-stmt-transitive.f90
@@ -0,0 +1,88 @@
+! 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_a
+  integer :: x = 10
+  integer :: w = 40
+  integer :: z = 50
+  integer :: base = 100
+end module mod_a
+
+module mod_b
+  use mod_a, only: x, renamed_w => w
+  integer :: y = 20
+  integer :: b_var = 200
+end module mod_b
+
+module mod_c
+  use mod_b
+  integer :: c_var = 300
+end module mod_c
+
+module mod_d
+  use mod_c, only: y, c_var, renamed_x => x
+  integer :: d_var = 400
+end module mod_d
+
+! Test 1: Function uses mod_b directly (1 level transitive)
+program test_main
+  use mod_b
+  implicit none
+  print *, x, y, renamed_w
+end program
+
+! Test 2: Function uses mod_c (2 levels transitive: mod_c -> mod_b -> mod_a)
+subroutine test_sub1
+  use mod_c
+  implicit none
+  print *, x, y, renamed_w, c_var
+end subroutine
+
+! Test 3: Function uses mod_d (3 levels transitive: mod_d -> mod_c -> mod_b -> mod_a)
+function test_func() result(res)
+  use mod_d
+  implicit none
+  integer :: res
+  res = renamed_x + y + c_var + d_var
+end function
+
+! Test 4: Function uses both mod_a directly and mod_c (mixed direct and transitive)
+subroutine test_sub2
+  use mod_a
+  use mod_c, only: c_var
+  implicit none
+  print *, x, w, z, base, c_var
+end subroutine
+
+! WITH_DEBUG-LABEL: func.func @_QQmain()
+! WITH_DEBUG: fir.use_stmt "mod_a" only_symbols{{\[}}[@_QMmod_aEx]] renames{{\[}}[#fir.use_rename<"renamed_w", @_QMmod_aEw>]]
+! WITH_DEBUG: fir.use_stmt "mod_b"
+
+! NO_DEBUG-LABEL: func.func @_QQmain()
+! NO_DEBUG-NOT: fir.use_stmt
+
+! WITH_DEBUG-LABEL: func.func @_QPtest_sub1()
+! WITH_DEBUG: fir.use_stmt "mod_a" only_symbols{{\[}}[@_QMmod_aEx]] renames{{\[}}[#fir.use_rename<"renamed_w", @_QMmod_aEw>]]
+! WITH_DEBUG: fir.use_stmt "mod_b"
+! WITH_DEBUG: fir.use_stmt "mod_c"
+
+! NO_DEBUG-LABEL: func.func @_QPtest_sub1()
+! NO_DEBUG-NOT: fir.use_stmt
+
+! WITH_DEBUG-LABEL: func.func @_QPtest_func()
+! WITH_DEBUG: fir.use_stmt "mod_a" only_symbols{{\[}}[@_QMmod_aEx]] renames{{\[}}[#fir.use_rename<"renamed_w", @_QMmod_aEw>]]
+! WITH_DEBUG: fir.use_stmt "mod_b"
+! WITH_DEBUG: fir.use_stmt "mod_c" only_symbols{{\[}}[@_QMmod_bEy, @_QMmod_cEc_var]] renames{{\[}}[#fir.use_rename<"renamed_x", @_QMmod_aEx>]]
+! WITH_DEBUG: fir.use_stmt "mod_d"
+
+! NO_DEBUG-LABEL: func.func @_QPtest_func()
+! NO_DEBUG-NOT: fir.use_stmt
+
+! WITH_DEBUG-LABEL: func.func @_QPtest_sub2()
+! WITH_DEBUG: fir.use_stmt "mod_a"
+! WITH_DEBUG: fir.use_stmt "mod_b"
+! WITH_DEBUG: fir.use_stmt "mod_c" only_symbols{{\[}}[@_QMmod_cEc_var]]
+
+! NO_DEBUG-LABEL: func.func @_QPtest_sub2()
+! NO_DEBUG-NOT: fir.use_stmt



More information about the flang-commits mailing list