[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