[flang-commits] [flang] [flang][debug] Generate DWARF debug info using fir.use_stmt. (PR #168541)

Abid Qadeer via flang-commits flang-commits at lists.llvm.org
Fri Jan 2 13:12:18 PST 2026


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

>From bda75e1691f22d7180bae099370b8dd709dd7bee Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Fri, 2 Jan 2026 19:14:53 +0000
Subject: [PATCH] [flang] Generate DWARF debug info using fir.use_stmt.

This patch uses the fir.use_stmt operations to generate correct
debug metadata for use statement when `only` and `=>` are used.

The debug flow is changed a bit where we process the module globals
first so that we have the global variables when we start to process
fir.use_stmt.

Fixes #160923.
---
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp | 270 ++++++++++++++----
 .../debug-use-stmt-symbol-refs.f90            |  38 +++
 .../test/Transforms/debug-imported-entity.fir |   1 +
 .../debug-local-global-storage-1.fir          |   1 +
 flang/test/Transforms/debug-use-stmt.fir      |  69 +++++
 5 files changed, 322 insertions(+), 57 deletions(-)
 create mode 100644 flang/test/Integration/debug-use-stmt-symbol-refs.f90
 create mode 100644 flang/test/Transforms/debug-use-stmt.fir

diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 7491b7b6d52d2..dd0af88c656d1 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -84,6 +84,20 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
                     mlir::LLVM::DICompileUnitAttr cuAttr,
                     fir::DebugTypeGenerator &typeGen,
                     mlir::SymbolTable *symbolTable);
+  void handleOnlyClause(
+      fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+      mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable,
+      llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedModules);
+  void handleRenamesWithoutOnly(
+      fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+      mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr,
+      mlir::SymbolTable *symbolTable,
+      llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedModules);
+  void 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);
   bool createCommonBlockGlobal(fir::cg::XDeclareOp declOp,
                                const std::string &name,
                                mlir::LLVM::DIFileAttr fileAttr,
@@ -136,6 +150,34 @@ mlir::StringAttr getTargetFunctionName(mlir::MLIRContext *context,
                                 deviceId, fileId, parentName, line)));
 }
 
+/// Check if a global represents a module variable
+bool isModuleVariable(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();
+}
+
+// Look up DIGlobalVariable from a global symbol
+std::optional<mlir::LLVM::DIGlobalVariableAttr>
+lookupDIGlobalVariable(llvm::StringRef symbolName,
+                       mlir::SymbolTable *symbolTable) {
+  if (auto globalOp = symbolTable->lookup<fir::GlobalOp>(symbolName)) {
+    if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(globalOp.getLoc())) {
+      if (auto metadata = fusedLoc.getMetadata()) {
+        if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(metadata)) {
+          for (auto elem : arrayAttr) {
+            if (auto gvExpr =
+                    mlir::dyn_cast<mlir::LLVM::DIGlobalVariableExpressionAttr>(
+                        elem))
+              return gvExpr.getVar();
+          }
+        }
+      }
+    }
+  }
+  return std::nullopt;
+}
+
 } // namespace
 
 bool AddDebugInfoPass::createCommonBlockGlobal(
@@ -526,7 +568,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
       CC = llvm::dwarf::getCallingConvention("DW_CC_normal");
       mlir::LLVM::DISubroutineTypeAttr spTy =
           mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
-      if (lineTableOnly) {
+      if (lineTableOnly || entities.empty()) {
         auto spAttr = mlir::LLVM::DISubprogramAttr::get(
             context, id, compilationUnit, Scope, name, name, funcFileAttr, line,
             line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{});
@@ -546,9 +588,9 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
       for (mlir::LLVM::DINodeAttr N : entities) {
         if (auto entity = mlir::dyn_cast<mlir::LLVM::DIImportedEntityAttr>(N)) {
           auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get(
-              context, llvm::dwarf::DW_TAG_imported_module, spAttr,
-              entity.getEntity(), fileAttr, /*line=*/1, /*name=*/nullptr,
-              /*elements*/ {});
+              context, entity.getTag(), spAttr, entity.getEntity(),
+              entity.getFile(), entity.getLine(), entity.getName(),
+              entity.getElements());
           opEntities.push_back(importedEntity);
         }
       }
@@ -573,61 +615,57 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
     return;
   }
 
-  mlir::DistinctAttr recId =
-      mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
-
-  // The debug attribute in MLIR are readonly once created. But in case of
-  // imported entities, we have a circular dependency. The
-  // DIImportedEntityAttr requires scope information (DISubprogramAttr in this
-  // case) and DISubprogramAttr requires the list of imported entities. The
-  // MLIR provides a way where a DISubprogramAttr an be created with a certain
-  // recID and be used in places like DIImportedEntityAttr. After that another
-  // DISubprogramAttr can be created with same recID but with list of entities
-  // now available. The MLIR translation code takes care of updating the
-  // references. Note that references will be updated only in the things that
-  // are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to
-  // create the final DISubprogramAttr before we process local variables.
-  // Look at DIRecursiveTypeAttrInterface for more details.
-
-  auto spAttr = mlir::LLVM::DISubprogramAttr::get(
-      context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, funcName,
-      fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr,
-      /*retainedNodes=*/{}, /*annotations=*/{});
-
-  // There is no direct information in the IR for any 'use' statement in the
-  // function. We have to extract that information from the DeclareOp. We do
-  // a pass on the DeclareOp and generate ModuleAttr and corresponding
-  // DIImportedEntityAttr for that module.
-  // FIXME: As we are depending on the variables to see which module is being
-  // 'used' in the function, there are certain limitations.
-  // For things like 'use mod1, only: v1', whole module will be brought into the
-  // namespace in the debug info. It is not a problem as such unless there is a
-  // clash of names.
-  // There is no information about module variable renaming
-  llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules;
-  funcOp.walk([&](fir::cg::XDeclareOp declOp) {
-    if (&funcOp.front() == declOp->getBlock())
-      if (auto global =
-              symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) {
-        std::optional<mlir::LLVM::DIModuleAttr> modOpt =
-            getModuleAttrFromGlobalOp(global, fileAttr, cuAttr);
-        if (modOpt) {
-          auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get(
-              context, llvm::dwarf::DW_TAG_imported_module, spAttr, *modOpt,
-              fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {});
-          importedModules.insert(importedEntity);
-        }
-      }
+  // Check if there are any USE statements
+  bool hasUseStmts = false;
+  funcOp.walk([&](fir::UseStmtOp useOp) {
+    hasUseStmts = true;
+    return mlir::WalkResult::interrupt();
   });
-  llvm::SmallVector<mlir::LLVM::DINodeAttr> entities(importedModules.begin(),
-                                                     importedModules.end());
-  // We have the imported entities now. Generate the final DISubprogramAttr.
-  spAttr = mlir::LLVM::DISubprogramAttr::get(
-      context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope,
-      funcName, fullName, funcFileAttr, line, line, subprogramFlags,
-      subTypeAttr, entities, /*annotations=*/{});
+
+  mlir::LLVM::DISubprogramAttr spAttr;
+  llvm::SmallVector<mlir::LLVM::DINodeAttr> retainedNodes;
+
+  if (hasUseStmts) {
+    mlir::DistinctAttr recId =
+        mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
+    // The debug attribute in MLIR are readonly once created. But in case of
+    // imported entities, we have a circular dependency. The
+    // DIImportedEntityAttr requires scope information (DISubprogramAttr in this
+    // case) and DISubprogramAttr requires the list of imported entities. The
+    // MLIR provides a way where a DISubprogramAttr an be created with a certain
+    // recID and be used in places like DIImportedEntityAttr. After that another
+    // DISubprogramAttr can be created with same recID but with list of entities
+    // now available. The MLIR translation code takes care of updating the
+    // references. Note that references will be updated only in the things that
+    // are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to
+    // create the final DISubprogramAttr before we process local variables.
+    // Look at DIRecursiveTypeAttrInterface for more details.
+    spAttr = mlir::LLVM::DISubprogramAttr::get(
+        context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope,
+        funcName, fullName, funcFileAttr, line, line, subprogramFlags,
+        subTypeAttr, /*retainedNodes=*/{}, /*annotations=*/{});
+
+    // Process USE statements (module globals are already processed)
+    llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedEntities;
+    handleUseStatements(funcOp, spAttr, fileAttr, cuAttr, symbolTable,
+                        importedEntities);
+
+    retainedNodes.append(importedEntities.begin(), importedEntities.end());
+
+    // Create final DISubprogramAttr with imported entities and same recId
+    spAttr = mlir::LLVM::DISubprogramAttr::get(
+        context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope,
+        funcName, fullName, funcFileAttr, line, line, subprogramFlags,
+        subTypeAttr, retainedNodes, /*annotations=*/{});
+  } else
+    // No USE statements - create final DISubprogramAttr directly
+    spAttr = mlir::LLVM::DISubprogramAttr::get(
+        context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr,
+        line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{},
+        /*annotations=*/{});
+
   funcOp->setLoc(builder.getFusedLoc({l}, spAttr));
-  addTargetOpDISP(/*lineTableOnly=*/false, entities);
+  addTargetOpDISP(/*lineTableOnly=*/false, retainedNodes);
 
   // Find the first dummy_scope definition. This is the one of the current
   // function. The other ones may come from inlined calls. The variables inside
@@ -662,6 +700,104 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
   commonBlockMap.clear();
 }
 
+// Process USE with ONLY clause
+void AddDebugInfoPass::handleOnlyClause(
+    fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+    mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable,
+    llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedModules) {
+  mlir::MLIRContext *context = &getContext();
+
+  auto createImportedDecl = [&](llvm::StringRef symbolName,
+                                mlir::StringAttr localNameAttr) {
+    if (auto gvAttr = lookupDIGlobalVariable(symbolName, symbolTable)) {
+      auto importedDecl = mlir::LLVM::DIImportedEntityAttr::get(
+          context, llvm::dwarf::DW_TAG_imported_declaration, spAttr, *gvAttr,
+          fileAttr, /*line=*/1, /*name=*/localNameAttr, /*elements*/ {});
+      importedModules.insert(importedDecl);
+    }
+  };
+
+  // Process ONLY symbols (without renames)
+  if (auto onlySymbols = useOp.getOnlySymbols()) {
+    for (mlir::Attribute attr : *onlySymbols) {
+      auto symbolRef = mlir::cast<mlir::FlatSymbolRefAttr>(attr);
+      createImportedDecl(symbolRef.getValue(), mlir::StringAttr());
+    }
+  }
+
+  // Process renames within ONLY clause
+  if (auto renames = useOp.getRenames()) {
+    for (auto attr : *renames) {
+      auto renameAttr = mlir::cast<fir::UseRenameAttr>(attr);
+      createImportedDecl(renameAttr.getSymbol().getValue(),
+                         renameAttr.getLocalName());
+    }
+  }
+}
+
+// Process USE with renames but no ONLY clause
+void AddDebugInfoPass::handleRenamesWithoutOnly(
+    fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
+    mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr,
+    mlir::SymbolTable *symbolTable,
+    llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedModules) {
+  mlir::MLIRContext *context = &getContext();
+  llvm::SmallVector<mlir::LLVM::DINodeAttr> childDeclarations;
+
+  if (auto renames = useOp.getRenames()) {
+    for (auto attr : *renames) {
+      auto renameAttr = mlir::cast<fir::UseRenameAttr>(attr);
+      llvm::StringRef symbolName = renameAttr.getSymbol().getValue();
+      mlir::StringAttr localNameAttr = renameAttr.getLocalName();
+
+      if (auto gvAttr = lookupDIGlobalVariable(symbolName, symbolTable)) {
+        auto importedDecl = mlir::LLVM::DIImportedEntityAttr::get(
+            context, llvm::dwarf::DW_TAG_imported_declaration, spAttr, *gvAttr,
+            fileAttr, /*line=*/1, /*name=*/localNameAttr, /*elements*/ {});
+        childDeclarations.push_back(importedDecl);
+      }
+    }
+  }
+
+  // Create module import with renamed declarations as children
+  auto moduleImport = mlir::LLVM::DIImportedEntityAttr::get(
+      context, llvm::dwarf::DW_TAG_imported_module, spAttr, modAttr, fileAttr,
+      /*line=*/1, /*name=*/nullptr, childDeclarations);
+  importedModules.insert(moduleImport);
+}
+
+// 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();
+
+  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);
+    }
+
+    importedEntities.insert(importedModules.begin(), importedModules.end());
+  });
+}
+
 void AddDebugInfoPass::runOnOperation() {
   mlir::ModuleOp module = getOperation();
   mlir::MLIRContext *context = &getContext();
@@ -725,6 +861,26 @@ void AddDebugInfoPass::runOnOperation() {
       splitDwarfFile.empty() ? mlir::StringAttr()
                              : mlir::StringAttr::get(context, splitDwarfFile));
 
+  // 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,
+  // the DIGlobalVariable lookups will succeed.
+  if (debugLevel == mlir::LLVM::DIEmissionKind::Full) {
+    module.walk([&](fir::cg::XDeclareOp declOp) {
+      mlir::Operation *defOp = declOp.getMemref().getDefiningOp();
+      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)) {
+            handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable,
+                           declOp);
+          }
+        }
+      }
+    });
+  }
+
   module.walk([&](mlir::func::FuncOp funcOp) {
     handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable);
   });
diff --git a/flang/test/Integration/debug-use-stmt-symbol-refs.f90 b/flang/test/Integration/debug-use-stmt-symbol-refs.f90
new file mode 100644
index 0000000000000..db20dd94e85d1
--- /dev/null
+++ b/flang/test/Integration/debug-use-stmt-symbol-refs.f90
@@ -0,0 +1,38 @@
+! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s
+
+module testmod
+  integer :: var_a = 10, var_b = 20, var_c = 30
+end module testmod
+
+module testmod2
+  real :: var_x = 1.0, var_y = 2.0
+end module testmod2
+
+program test_use
+  use testmod, only: var_b, var_d => var_c
+  use testmod2, var_z => var_y
+  implicit none
+  print *, var_b
+  print *, var_d
+  print *, var_z
+end program
+
+! CHECK-DAG: [[TESTMOD:![0-9]+]] = !DIModule(scope: !{{.*}}, name: "testmod"
+! CHECK-DAG: [[TESTMOD2:![0-9]+]] = !DIModule(scope: !{{.*}}, name: "testmod2"
+
+! 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: [[SP:![0-9]+]] = distinct !DISubprogram(name: "TEST_USE", linkageName: "_QQmain"{{.*}}retainedNodes:
+
+! 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:
+
+! Check testmod2 import: module imported with rename in elements array
+! The module import should have elements containing the var_z rename
+! CHECK-DAG: [[MOD2_IMPORT:![0-9]+]] = !DIImportedEntity(tag: DW_TAG_imported_module, scope: [[SP]], entity: [[TESTMOD2]],{{.*}}elements: [[ELEMENTS:![0-9]+]]
+! CHECK-DAG: [[ELEMENTS]] = !{[[VAR_Z:![0-9]+]]}
+! CHECK-DAG: [[VAR_Z]] = !DIImportedEntity(tag: DW_TAG_imported_declaration, name: "var_z",{{.*}}entity: [[VAR_Y]],
+
diff --git a/flang/test/Transforms/debug-imported-entity.fir b/flang/test/Transforms/debug-imported-entity.fir
index 194bc82724583..246bdd8bf4e95 100644
--- a/flang/test/Transforms/debug-imported-entity.fir
+++ b/flang/test/Transforms/debug-imported-entity.fir
@@ -11,6 +11,7 @@ module {
     fir.has_value %c12_i32 : i32
   } loc(#loc4)
   func.func @test() attributes {fir.bindc_name = "test"} {
+    fir.use_stmt "foo"
     %0 = fir.address_of(@_QMfooEv1) : !fir.ref<i32>
     %1 = fircg.ext_declare %0 {uniq_name = "_QMfooEv1"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc1)
     %4 = fir.address_of(@_QFtestExyz) : !fir.ref<i32>
diff --git a/flang/test/Transforms/debug-local-global-storage-1.fir b/flang/test/Transforms/debug-local-global-storage-1.fir
index 2638464dbab0b..6b9ea5b9dbb2d 100644
--- a/flang/test/Transforms/debug-local-global-storage-1.fir
+++ b/flang/test/Transforms/debug-local-global-storage-1.fir
@@ -2,6 +2,7 @@
 
 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"} {
   func.func @_QMexamplePmod_sub() {
+    fir.use_stmt "example"
     %c2 = arith.constant 2 : index
     %1 = fir.address_of(@_QMexampleEmod_arr) : !fir.ref<!fir.array<2x2xi32>>
     %2 = fircg.ext_declare %1(%c2, %c2) {uniq_name = "_QMexampleEmod_arr"} : (!fir.ref<!fir.array<2x2xi32>>, index, index) -> !fir.ref<!fir.array<2x2xi32>> loc(#loc4)
diff --git a/flang/test/Transforms/debug-use-stmt.fir b/flang/test/Transforms/debug-use-stmt.fir
new file mode 100644
index 0000000000000..c1db92af072f4
--- /dev/null
+++ b/flang/test/Transforms/debug-use-stmt.fir
@@ -0,0 +1,69 @@
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
+
+module {
+  // Module globals
+  fir.global @_QMtestmodEvar_b : i32 {
+    %c20_i32 = arith.constant 20 : i32
+    fir.has_value %c20_i32 : i32
+  }
+
+  fir.global @_QMtestmodEvar_c : i32 {
+    %c30_i32 = arith.constant 30 : i32
+    fir.has_value %c30_i32 : i32
+  }
+
+  fir.global @_QMtestmod2Evar_y : f32 {
+    %cst = arith.constant 2.000000e+00 : f32
+    fir.has_value %cst : f32
+  }
+
+  func.func @_QQmain() attributes {fir.bindc_name = "TEST_USE"} {
+    // USE testmod, ONLY: var_b, var_d => var_c
+    fir.use_stmt "testmod" only_symbols[[@_QMtestmodEvar_b]] renames[[#fir.use_rename<"var_d", @_QMtestmodEvar_c>]]
+
+    // USE testmod2, var_z => var_y  (no ONLY)
+    fir.use_stmt "testmod2" renames[[#fir.use_rename<"var_z", @_QMtestmod2Evar_y>]]
+
+    %0 = fir.address_of(@_QMtestmodEvar_b) : !fir.ref<i32>
+    %1 = fircg.ext_declare %0 {uniq_name = "_QMtestmodEvar_b"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc_b)
+
+    %2 = fir.address_of(@_QMtestmodEvar_c) : !fir.ref<i32>
+    %3 = fircg.ext_declare %2 {uniq_name = "_QMtestmodEvar_c"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc_c)
+
+    %4 = fir.address_of(@_QMtestmod2Evar_y) : !fir.ref<f32>
+    %5 = fircg.ext_declare %4 {uniq_name = "_QMtestmod2Evar_y"} : (!fir.ref<f32>) -> !fir.ref<f32> loc(#loc_y)
+
+    return
+  } loc(#loc_main)
+}
+
+#loc_b = loc("test.f90":4:26)
+#loc_c = loc("test.f90":4:38)
+#loc_y = loc("test.f90":8:24)
+#loc_main = loc("test.f90":11:1)
+
+// CHECK-DAG: #[[MOD_TESTMOD:.+]] = #llvm.di_module<{{.*}}name = "testmod"{{.*}}>
+// CHECK-DAG: #[[MOD_TESTMOD2:.+]] = #llvm.di_module<{{.*}}name = "testmod2"{{.*}}>
+
+// CHECK-DAG: #[[GVAR_B:.+]] = #llvm.di_global_variable<scope = #[[MOD_TESTMOD]], name = "var_b", linkageName = "_QMtestmodEvar_b"
+// CHECK-DAG: #[[GVAR_C:.+]] = #llvm.di_global_variable<scope = #[[MOD_TESTMOD]], name = "var_c", linkageName = "_QMtestmodEvar_c"
+// CHECK-DAG: #[[GVAR_Y:.+]] = #llvm.di_global_variable<scope = #[[MOD_TESTMOD2]], name = "var_y", linkageName = "_QMtestmod2Evar_y"
+
+// DISubprogram placeholder (for recursive reference)
+// CHECK-DAG: #[[SP_REC:.+]] = #llvm.di_subprogram<recId = distinct[[[RECID:[0-9]+]]]<>, isRecSelf = true{{.*}}name = "TEST_USE"
+
+// 1. Imported declaration without rename (var_b) - has entity but NO name attribute
+// CHECK-DAG: #llvm.di_imported_entity<tag = DW_TAG_imported_declaration, scope = #[[SP_REC]], entity = #[[GVAR_B]],{{.*}}>
+
+// 2. Imported declaration with rename (var_d => var_c) - has both entity and name
+// CHECK-DAG: #llvm.di_imported_entity<tag = DW_TAG_imported_declaration, scope = #[[SP_REC]], entity = #[[GVAR_C]],{{.*}}name = "var_d">
+
+// 3. Imported declaration with rename (var_z => var_y) - for module import element
+// CHECK-DAG: #[[IMPORT_Z:.+]] = #llvm.di_imported_entity<tag = DW_TAG_imported_declaration, scope = #[[SP_REC]], entity = #[[GVAR_Y]],{{.*}}name = "var_z">
+
+// 4. Imported module (testmod2) with renamed element in its elements field
+// CHECK-DAG: #llvm.di_imported_entity<tag = DW_TAG_imported_module, scope = #[[SP_REC]], entity = #[[MOD_TESTMOD2]]{{.*}}elements = #[[IMPORT_Z]]
+
+// Verify final DISubprogram has retainedNodes (non-empty)
+// We don't check the exact order since retainedNodes comes from an unordered collection
+// CHECK-DAG: #llvm.di_subprogram<recId = distinct[[[RECID]]]<>{{.*}}name = "TEST_USE"{{.*}}retainedNodes = {{.+}}>



More information about the flang-commits mailing list