From dc9c08e6e0081ae0799597ba5f95ca5a6eddeac0 Mon Sep 17 00:00:00 2001 From: Abid Qadeer Date: Mon, 19 Jan 2026 17:16:11 +0000 Subject: [PATCH] [flang][debug] Generate DWARF debug info using fir.use_stmt. (#168541) 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 | 280 ++++++++++++++---- flang/test/Integration/debug-use-stmt.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, 332 insertions(+), 57 deletions(-) create mode 100644 flang/test/Integration/debug-use-stmt.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 7491b7b6d52d..35d8a2f6c3aa 100644 --- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp +++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp @@ -84,6 +84,24 @@ private: 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 &importedModules); + void handleRenamesWithoutOnly( + fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr, + mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedModules); + void handleUseStatements( + mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, + mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedEntities); + std::optional createImportedDeclForGlobal( + llvm::StringRef symbolName, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIFileAttr fileAttr, mlir::StringAttr localNameAttr, + mlir::SymbolTable *symbolTable); bool createCommonBlockGlobal(fir::cg::XDeclareOp declOp, const std::string &name, mlir::LLVM::DIFileAttr fileAttr, @@ -138,6 +156,34 @@ mlir::StringAttr getTargetFunctionName(mlir::MLIRContext *context, } // namespace +// Check if a global represents a module variable +static 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 +static std::optional +lookupDIGlobalVariable(llvm::StringRef symbolName, + mlir::SymbolTable *symbolTable) { + if (auto globalOp = symbolTable->lookup(symbolName)) { + if (auto fusedLoc = mlir::dyn_cast(globalOp.getLoc())) { + if (auto metadata = fusedLoc.getMetadata()) { + if (auto arrayAttr = mlir::dyn_cast(metadata)) { + for (auto elem : arrayAttr) { + if (auto gvExpr = + mlir::dyn_cast( + elem)) + return gvExpr.getVar(); + } + } + } + } + } + return std::nullopt; +} + bool AddDebugInfoPass::createCommonBlockGlobal( fir::cg::XDeclareOp declOp, const std::string &name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, @@ -526,7 +572,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 +592,9 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, for (mlir::LLVM::DINodeAttr N : entities) { if (auto entity = mlir::dyn_cast(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 +619,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 importedModules; - funcOp.walk([&](fir::cg::XDeclareOp declOp) { - if (&funcOp.front() == declOp->getBlock()) - if (auto global = - symbolTable->lookup(declOp.getUniqName())) { - std::optional 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 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 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 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 +704,110 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, commonBlockMap.clear(); } +// Helper function to create a DIImportedEntityAttr for an imported declaration. +// Looks up the DIGlobalVariable for the given symbol and creates an imported +// declaration with the optional local name (for renames). +// Returns std::nullopt if the symbol's DIGlobalVariable is not found. +std::optional +AddDebugInfoPass::createImportedDeclForGlobal( + llvm::StringRef symbolName, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIFileAttr fileAttr, mlir::StringAttr localNameAttr, + mlir::SymbolTable *symbolTable) { + mlir::MLIRContext *context = &getContext(); + if (auto gvAttr = lookupDIGlobalVariable(symbolName, symbolTable)) { + return mlir::LLVM::DIImportedEntityAttr::get( + context, llvm::dwarf::DW_TAG_imported_declaration, spAttr, *gvAttr, + fileAttr, /*line=*/1, /*name=*/localNameAttr, /*elements*/ {}); + } + return std::nullopt; +} + +// Process USE with ONLY clause +void AddDebugInfoPass::handleOnlyClause( + fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedModules) { + // Process ONLY symbols (without renames) + if (auto onlySymbols = useOp.getOnlySymbols()) { + for (mlir::Attribute attr : *onlySymbols) { + auto symbolRef = mlir::cast(attr); + if (auto importedDecl = createImportedDeclForGlobal( + symbolRef.getValue(), spAttr, fileAttr, mlir::StringAttr(), + symbolTable)) + importedModules.insert(*importedDecl); + } + } + + // Process renames within ONLY clause + if (auto renames = useOp.getRenames()) { + for (auto attr : *renames) { + auto renameAttr = mlir::cast(attr); + if (auto importedDecl = createImportedDeclForGlobal( + renameAttr.getSymbol().getValue(), spAttr, fileAttr, + renameAttr.getLocalName(), symbolTable)) + importedModules.insert(*importedDecl); + } + } +} + +// 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 &importedModules) { + mlir::MLIRContext *context = &getContext(); + llvm::SmallVector childDeclarations; + + if (auto renames = useOp.getRenames()) { + for (auto attr : *renames) { + auto renameAttr = mlir::cast(attr); + if (auto importedDecl = createImportedDeclForGlobal( + renameAttr.getSymbol().getValue(), spAttr, fileAttr, + renameAttr.getLocalName(), symbolTable)) + 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 &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 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 +871,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(defOp)) { + if (auto globalOp = + symbolTable.lookup(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.f90 b/flang/test/Integration/debug-use-stmt.f90 new file mode 100644 index 000000000000..db20dd94e85d --- /dev/null +++ b/flang/test/Integration/debug-use-stmt.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 194bc8272458..246bdd8bf4e9 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 %1 = fircg.ext_declare %0 {uniq_name = "_QMfooEv1"} : (!fir.ref) -> !fir.ref loc(#loc1) %4 = fir.address_of(@_QFtestExyz) : !fir.ref diff --git a/flang/test/Transforms/debug-local-global-storage-1.fir b/flang/test/Transforms/debug-local-global-storage-1.fir index 2638464dbab0..6b9ea5b9dbb2 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 : vector<2xi64>>, #dlti.dl_entry, dense<64> : vector<4xi64>>, #dlti.dl_entry, dense<32> : vector<4xi64>>, #dlti.dl_entry, dense<32> : vector<4xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<4xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : vector<2xi64>>, #dlti.dl_entry : 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> %2 = fircg.ext_declare %1(%c2, %c2) {uniq_name = "_QMexampleEmod_arr"} : (!fir.ref>, index, index) -> !fir.ref> 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 000000000000..c1db92af072f --- /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 + %1 = fircg.ext_declare %0 {uniq_name = "_QMtestmodEvar_b"} : (!fir.ref) -> !fir.ref loc(#loc_b) + + %2 = fir.address_of(@_QMtestmodEvar_c) : !fir.ref + %3 = fircg.ext_declare %2 {uniq_name = "_QMtestmodEvar_c"} : (!fir.ref) -> !fir.ref loc(#loc_c) + + %4 = fir.address_of(@_QMtestmod2Evar_y) : !fir.ref + %5 = fircg.ext_declare %4 {uniq_name = "_QMtestmod2Evar_y"} : (!fir.ref) -> !fir.ref 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, isRecSelf = true{{.*}}name = "TEST_USE" + +// 1. Imported declaration without rename (var_b) - has entity but NO name attribute +// CHECK-DAG: #llvm.di_imported_entity + +// 2. Imported declaration with rename (var_d => var_c) - has both entity and name +// CHECK-DAG: #llvm.di_imported_entity + +// 3. Imported declaration with rename (var_z => var_y) - for module import element +// CHECK-DAG: #[[IMPORT_Z:.+]] = #llvm.di_imported_entity + +// 4. Imported module (testmod2) with renamed element in its elements field +// CHECK-DAG: #llvm.di_imported_entity{{.*}}name = "TEST_USE"{{.*}}retainedNodes = {{.+}}>