diff --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.td b/flang/include/flang/Optimizer/Dialect/FIRAttr.td index 8a8c60ff0722b..f2723d730ed5b 100644 --- a/flang/include/flang/Optimizer/Dialect/FIRAttr.td +++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.td @@ -239,4 +239,21 @@ def fir_FortranInlineAttr : EnumAttr { let assemblyFormat = "`<` $value `>`"; } + +// USE statement rename mapping: local_name => use_name +def fir_UseRenameAttr : fir_Attr<"UseRename"> { + let mnemonic = "use_rename"; + let summary = "Represents a rename in a Fortran USE statement"; + let description = [{ + This attribute stores the mapping for a renamed symbol in a USE statement. + For example, in "USE mod, local_var => module_var", this stores the + local name and a symbol reference to the module variable. + }]; + + let parameters = (ins "mlir::StringAttr":$local_name, + "mlir::FlatSymbolRefAttr":$symbol); + + let assemblyFormat = "`<` $local_name `,` $symbol `>`"; +} + #endif // FIR_DIALECT_FIR_ATTRS diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td index 289c79bd9b831..9bfe10b27ea8b 100644 --- a/flang/include/flang/Optimizer/Dialect/FIROps.td +++ b/flang/include/flang/Optimizer/Dialect/FIROps.td @@ -3054,6 +3054,58 @@ def fir_GlobalLenOp : fir_Op<"global_len", []> { }]; } +def fir_UseStmtOp + : fir_Op<"use_stmt", [MemoryEffects<[MemWrite]>]> { + let summary = "Represents a Fortran USE statement"; + let description = [{ + This operation records a Fortran USE statement with its associated only/rename + information. It has no runtime effect but preserves semantic information for + debug information generation. + + The operation captures: + - The module being used (via module_name string) + - Symbol references to symbols imported via the ONLY clause (if present) + - Symbol renames (local_name and symbol reference) + + Examples: + ``` + // USE mod1 + fir.use_stmt "mod1" + + // USE mod1, ONLY: var2 + fir.use_stmt "mod1" only_symbols [@_QMmod1Evar2] + + // USE mod2, var4 => var3 + fir.use_stmt "mod2" renames [#fir.use_rename<"var4", @_QMmod2Evar3>] + + // USE mod2, ONLY: var1, renamed => original + fir.use_stmt "mod2" only_symbols [@_QMmod2Evar1] + renames [#fir.use_rename<"renamed", @_QMmod2Eoriginal>] + ``` + }]; + + let arguments = (ins StrAttr:$module_name, + OptionalAttr:$only_symbols, OptionalAttr:$renames); + + let assemblyFormat = [{ + $module_name + (`only_symbols` `[` $only_symbols^ `]`)? + (`renames` `[` $renames^ `]`)? + attr-dict + }]; + + let extraClassDeclaration = [{ + /// Returns true if this is a USE with ONLY clause + bool hasOnlyClause() { return getOnlySymbols().has_value(); } + + /// Returns true if this has any renames + bool hasRenames() { return getRenames().has_value(); } + + /// Returns true if this imports the entire module (no ONLY clause) + bool importsAll() { return !hasOnlyClause(); } + }]; +} + def ImplicitFirTerminator : SingleBlockImplicitTerminator<"FirEndOp">; def fir_TypeInfoOp : fir_Op<"type_info", diff --git a/flang/include/flang/Semantics/scope.h b/flang/include/flang/Semantics/scope.h index ecffdb468bf6c..586659781491b 100644 --- a/flang/include/flang/Semantics/scope.h +++ b/flang/include/flang/Semantics/scope.h @@ -55,6 +55,19 @@ struct EquivalenceObject { }; using EquivalenceSet = std::vector; +// Preserved USE statement information for debug info generation. +struct PreservedUseStmt { + enum class Kind { UseOnly, UseRenames, UseAll }; + + std::string moduleName; + Kind kind; + std::vector onlyNames; // For Kind::UseOnly + std::vector renames; // local_name (resolved via GetUltimate) + + PreservedUseStmt(std::string modName, Kind k) + : moduleName(std::move(modName)), kind(k) {} +}; + class Scope { using mapType = std::map; @@ -190,6 +203,17 @@ class Scope { return equivalenceSets_; } void add_equivalenceSet(EquivalenceSet &&); + + // Access preserved USE statements for debug info generation + std::list &preservedUseStmts() { + return preservedUseStmts_; + } + const std::list &preservedUseStmts() const { + return preservedUseStmts_; + } + void add_preservedUseStmt(PreservedUseStmt &&stmt) { + preservedUseStmts_.push_back(std::move(stmt)); + } // Cray pointers are saved as map of pointee name -> pointer symbol const mapType &crayPointers() const { return crayPointers_; } void add_crayPointer(const SourceName &, Symbol &); @@ -301,6 +325,7 @@ class Scope { mapType commonBlocks_; mapType commonBlockUses_; // USE-assocated COMMON blocks std::list equivalenceSets_; + std::list preservedUseStmts_; mapType crayPointers_; std::map> submodules_; std::list declTypeSpecs_; diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp index 5779bcd5d293c..fd0a866c2abac 100644 --- a/flang/lib/Lower/Bridge.cpp +++ b/flang/lib/Lower/Bridge.cpp @@ -226,6 +226,95 @@ static mlir::FlatSymbolRefAttr gatherComponentInit( return mlir::FlatSymbolRefAttr::get(mlirContext, name); } +/// Emit fir.use_stmt operations for USE statements in the given scope +static void +emitUseStatementsFromScope(Fortran::lower::AbstractConverter &converter, + mlir::OpBuilder &builder, mlir::Location loc, + const Fortran::semantics::Scope &scope) { + mlir::MLIRContext *context = builder.getContext(); + + for (const auto &preservedStmt : scope.preservedUseStmts()) { + + 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(); + + // Skip cases which can cause mangleName to fail. + if (ultimateSym.has()) + return ""; + + if (const auto *generic = + ultimateSym.detailsIf()) { + if (!generic->specific()) + return ""; + } + + return converter.mangleName(ultimateSym); + }; + + mlir::StringAttr moduleNameAttr = + mlir::StringAttr::get(context, preservedStmt.moduleName); + + llvm::SmallVector onlySymbolAttrs; + llvm::SmallVector renameAttrs; + + switch (preservedStmt.kind) { + case Fortran::semantics::PreservedUseStmt::Kind::UseOnly: + // USE mod, ONLY: list + for (const auto &name : preservedStmt.onlyNames) { + std::string mangledName = getMangledName(name); + if (!mangledName.empty()) + onlySymbolAttrs.push_back( + mlir::FlatSymbolRefAttr::get(context, mangledName)); + } + // Handle renames within ONLY clause + 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)); + } + } + break; + + case Fortran::semantics::PreservedUseStmt::Kind::UseRenames: + // USE mod, renames (import all with some 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)); + } + } + break; + + case Fortran::semantics::PreservedUseStmt::Kind::UseAll: + // USE mod (import all, no renames) + break; + } + + // 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); + } +} + /// Helper class to generate the runtime type info global data and the /// fir.type_info operations that contain the dipatch tables (if any). /// The type info global data is required to describe the derived type to the @@ -6126,6 +6215,9 @@ class FirConverter : public Fortran::lower::AbstractConverter { mapDummiesAndResults(funit, callee); + // Emit USE statement operations for debug info generation + emitUseStatementsFromScope(*this, *builder, toLocation(), funit.getScope()); + // Map host associated symbols from parent procedure if any. if (funit.parentHasHostAssoc()) funit.parentHostAssoc().internalProcedureBindings(*this, localSymbols); diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp index ca4aefb653d2a..b92726a50d125 100644 --- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp +++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp @@ -3446,6 +3446,20 @@ struct NoReassocOpConversion : public fir::FIROpConversion { } }; +/// Erase `fir.use_stmt` operations during LLVM lowering. +/// These operations are only used for debug info generation by the +/// AddDebugInfo pass and have no runtime representation. +struct UseStmtOpConversion : public fir::FIROpConversion { + using FIROpConversion::FIROpConversion; + + llvm::LogicalResult + matchAndRewrite(fir::UseStmtOp useStmt, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const override { + rewriter.eraseOp(useStmt); + return mlir::success(); + } +}; + static void genCondBrOp(mlir::Location loc, mlir::Value cmp, mlir::Block *dest, std::optional destOps, mlir::ConversionPatternRewriter &rewriter, @@ -4429,8 +4443,9 @@ void fir::populateFIRToLLVMConversionPatterns( SliceOpConversion, StoreOpConversion, StringLitOpConversion, SubcOpConversion, TypeDescOpConversion, TypeInfoOpConversion, UnboxCharOpConversion, UnboxProcOpConversion, UndefOpConversion, - UnreachableOpConversion, XArrayCoorOpConversion, XEmboxOpConversion, - XReboxOpConversion, ZeroOpConversion>(converter, options); + UnreachableOpConversion, UseStmtOpConversion, 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 e006d2e878fd8..a3d4ed3ca0247 100644 --- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp +++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp @@ -74,6 +74,7 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase { getOrCreateCommonBlockAttr(llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, unsigned line); + bool isModuleVariable(fir::GlobalOp globalOp); void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, @@ -84,6 +85,24 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase { mlir::LLVM::DICompileUnitAttr cuAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable); + std::optional + lookupDIGlobalVariable(llvm::StringRef symbolName, + mlir::SymbolTable *symbolTable); + void processOnlyClause( + fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr, + mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedModules); + void processRenamesWithoutOnly( + fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr, + mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedModules); + void processUseStatementsInFunction( + mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr, + mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, + mlir::SymbolTable *symbolTable, + llvm::DenseSet &importedEntities); bool createCommonBlockGlobal(fir::cg::XDeclareOp declOp, const std::string &name, mlir::LLVM::DIFileAttr fileAttr, @@ -300,6 +319,14 @@ mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( return modAttr; } +/// Check if a global represents a module variable (not a SAVE variable). +/// Module variables have empty procs list and non-empty modules list. +/// SAVE variables have non-empty procs list (they belong to a function). +bool AddDebugInfoPass::isModuleVariable(fir::GlobalOp globalOp) { + std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); + return result.second.procs.empty() && !result.second.modules.empty(); +} + /// If globalOp represents a module variable, return a ModuleAttr that /// represents that module. std::optional @@ -440,7 +467,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr::get(context, fileName, filePath); // Only definitions need a distinct identifier and a compilation unit. - mlir::DistinctAttr id, id2; + mlir::DistinctAttr id; mlir::LLVM::DIScopeAttr Scope = fileAttr; mlir::LLVM::DICompileUnitAttr compilationUnit; mlir::LLVM::DISubprogramFlags subprogramFlags = @@ -451,10 +478,7 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::MainSubprogram; if (!funcOp.isExternal()) { - // Place holder and final function have to have different IDs, otherwise - // translation code will reject one of them. id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); - id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); compilationUnit = cuAttr; subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; @@ -483,7 +507,8 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, line - 1, false); } - auto addTargetOpDISP = [&](bool lineTableOnly, + // Lambda to create DISubprogramAttr for OpenMP target operations. + auto addTargetOpDISP = [&](mlir::omp::TargetOp targetOp, llvm::ArrayRef entities) { // When we process the DeclareOp inside the OpenMP target region, all the // variables get the DISubprogram of the parent function of the target op as @@ -498,62 +523,61 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, // We can avoid this problem by generating a DISubprogramAttr here for the // target op and make sure that all the variables inside the target region // get the correct scope in the first place. - funcOp.walk([&](mlir::omp::TargetOp targetOp) { - unsigned line = getLineFromLoc(targetOp.getLoc()); - mlir::StringAttr name = - getTargetFunctionName(context, targetOp.getLoc(), funcOp.getName()); - mlir::LLVM::DISubprogramFlags flags = - mlir::LLVM::DISubprogramFlags::Definition | - mlir::LLVM::DISubprogramFlags::LocalToUnit; - if (isOptimized) - flags = flags | mlir::LLVM::DISubprogramFlags::Optimized; - - mlir::DistinctAttr id = - mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); - llvm::SmallVector types; - types.push_back(mlir::LLVM::DINullTypeAttr::get(context)); - for (auto arg : targetOp.getRegion().getArguments()) { - auto tyAttr = typeGen.convertType(fir::unwrapRefType(arg.getType()), - fileAttr, cuAttr, /*declOp=*/nullptr); - types.push_back(tyAttr); - } - CC = llvm::dwarf::getCallingConvention("DW_CC_normal"); - mlir::LLVM::DISubroutineTypeAttr spTy = - mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); - if (lineTableOnly) { - auto spAttr = mlir::LLVM::DISubprogramAttr::get( - context, id, compilationUnit, Scope, name, name, funcFileAttr, line, - line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{}); - targetOp->setLoc(builder.getFusedLoc({targetOp.getLoc()}, spAttr)); - return; - } - mlir::DistinctAttr recId = - mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); - auto spAttr = mlir::LLVM::DISubprogramAttr::get( + unsigned line = getLineFromLoc(targetOp.getLoc()); + mlir::StringAttr name = + getTargetFunctionName(context, targetOp.getLoc(), funcOp.getName()); + mlir::LLVM::DISubprogramFlags flags = + mlir::LLVM::DISubprogramFlags::Definition | + mlir::LLVM::DISubprogramFlags::LocalToUnit; + if (isOptimized) + flags = flags | mlir::LLVM::DISubprogramFlags::Optimized; + + auto id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); + llvm::SmallVector types; + types.push_back(mlir::LLVM::DINullTypeAttr::get(context)); + for (auto arg : targetOp.getRegion().getArguments()) { + auto tyAttr = typeGen.convertType(fir::unwrapRefType(arg.getType()), + fileAttr, cuAttr, /*declOp=*/nullptr); + types.push_back(tyAttr); + } + unsigned targetCC = llvm::dwarf::getCallingConvention("DW_CC_normal"); + mlir::LLVM::DISubroutineTypeAttr spTy = + mlir::LLVM::DISubroutineTypeAttr::get(context, targetCC, types); + + mlir::LLVM::DISubprogramAttr spAttr; + + if (!entities.empty()) { + auto recId = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); + spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, name, name, funcFileAttr, line, line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{}); // Make sure that information about the imported modules is copied in the - // new function. + // new function. Preserve the original tag (could be + // DW_TAG_imported_module or DW_TAG_imported_declaration). llvm::SmallVector opEntities; 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); } } - id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); + // Use same 'id' for both placeholder and final - recId handles the + // recursion spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/false, id, compilationUnit, Scope, name, name, funcFileAttr, line, line, flags, spTy, opEntities, /*annotations=*/{}); - targetOp->setLoc(builder.getFusedLoc({targetOp.getLoc()}, spAttr)); - }); + } else + spAttr = mlir::LLVM::DISubprogramAttr::get( + context, id, compilationUnit, Scope, name, name, funcFileAttr, line, + line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{}); + targetOp->setLoc(builder.getFusedLoc({targetOp.getLoc()}, spAttr)); }; // Don't process variables if user asked for line tables only. @@ -563,65 +587,69 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}, /*annotations=*/{}); funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); - addTargetOpDISP(/*lineTableOnly=*/true, /*entities=*/{}); + + // Create DISubprogram for OpenMP target operations + funcOp.walk( + [&](mlir::omp::TargetOp targetOp) { addTargetOpDISP(targetOp, {}); }); 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 immediately (module globals are already processed) + llvm::DenseSet importedEntities; + processUseStatementsInFunction(funcOp, spAttr, fileAttr, cuAttr, + symbolTable, importedEntities); + + // Add imported entities to retained nodes + retainedNodes.append(importedEntities.begin(), importedEntities.end()); + + // Create final DISubprogramAttr with imported entities and same recId + spAttr = mlir::LLVM::DISubprogramAttr::get( + context, recId, /*isRecSelf=*/false, id, 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); + + // Create DISubprogram for OpenMP target operations. + funcOp.walk([&](mlir::omp::TargetOp targetOp) { + addTargetOpDISP(targetOp, retainedNodes); + }); funcOp.walk([&](fir::cg::XDeclareOp declOp) { mlir::LLVM::DISubprogramAttr spTy = spAttr; @@ -641,6 +669,130 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, commonBlockMap.clear(); } +// Helper: Look up DIGlobalVariable from a global symbol +std::optional +AddDebugInfoPass::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; +} + +// Helper: Process USE with ONLY clause +void AddDebugInfoPass::processOnlyClause( + 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(); + + 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(attr); + createImportedDecl(symbolRef.getValue(), mlir::StringAttr()); + } + } + + // Process renames within ONLY clause + if (auto renames = useOp.getRenames()) { + for (auto attr : *renames) { + auto renameAttr = mlir::cast(attr); + createImportedDecl(renameAttr.getSymbol().getValue(), + renameAttr.getLocalName()); + } + } +} + +// Helper: Process USE with renames but no ONLY clause +void AddDebugInfoPass::processRenamesWithoutOnly( + 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); + 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::processUseStatementsInFunction( + 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(); + + // Process each USE statement directly + // Note: We don't erase these operations here. Like other metadata operations + // (fir.type_info, fir.dt_entry), they will be erased during CodeGen. + 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()) { + processOnlyClause(useOp, spAttr, modAttr, fileAttr, symbolTable, + importedModules); + } else if (useOp.hasRenames()) { + processRenamesWithoutOnly(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(); @@ -704,10 +856,37 @@ void AddDebugInfoPass::runOnOperation() { splitDwarfFile.empty() ? mlir::StringAttr() : mlir::StringAttr::get(context, splitDwarfFile)); + // FIRST PASS: Process only module globals (not SAVE variables). + // 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. + // Only do this for full debug info, not for line-tables-only. + 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); + } + } + } + }); + } + + // SECOND PASS: Process functions. During this pass: + // - Module globals are already processed (from first pass) + // - USE statements will be processed immediately (not deferred) + // - SAVE variables will be processed normally + // - Final DISubprogramAttr is created directly with all imported entities module.walk([&](mlir::func::FuncOp funcOp) { handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable); }); - // We have processed all function. Attach common block variables to the + + // We have processed all functions. Attach common block variables to the // global that represent the storage. for (auto [global, exprs] : globalToGlobalExprsMap) { auto arrayAttr = mlir::ArrayAttr::get(context, exprs); diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp index 09ec951a422ca..85b3b36329471 100644 --- a/flang/lib/Semantics/resolve-names.cpp +++ b/flang/lib/Semantics/resolve-names.cpp @@ -3638,6 +3638,86 @@ void ModuleVisitor::Post(const parser::UseStmt &x) { for (const auto &[name, symbol] : useModuleScope_->commonBlockUses()) { currScope().AddCommonBlockUse(name, symbol->attrs(), symbol->GetUltimate()); } + + // Preserve USE statement information for debug info generation + std::string moduleName{x.moduleName.source.ToString()}; + + if (const auto *onlyList{std::get_if>(&x.u)}) { + // USE mod, ONLY: list + PreservedUseStmt stmt{moduleName, PreservedUseStmt::Kind::UseOnly}; + + for (const auto &only : *onlyList) { + common::visit( + common::visitors{ + [&](const parser::Rename &rename) { + // ONLY with rename: ONLY: local => use + common::visit(common::visitors{ + [&](const parser::Rename::Names &names) { + std::string localName{ + std::get<0>(names.t).source.ToString()}; + stmt.renames.push_back(localName); + }, + [&](const parser::Rename::Operators &) { + // Operator renames - not commonly needed + // for debug info + }, + }, + rename.u); + }, + [&](const parser::Name &name) { + // ONLY without rename: ONLY: name + stmt.onlyNames.push_back(name.source.ToString()); + }, + [&](const common::Indirection &genericSpec) { + // Generic spec can contain a Name (for regular symbols) or + // operators + common::visit(common::visitors{ + [&](const parser::Name &name) { + stmt.onlyNames.push_back( + name.source.ToString()); + }, + [&](const auto &) { + // Operators and special forms - not + // commonly needed for variable debug info + }, + }, + genericSpec.value().u); + }, + }, + only.u); + } + + currScope().add_preservedUseStmt(std::move(stmt)); + } else if (const auto *renameList{ + std::get_if>(&x.u)}) { + // USE mod with optional renames (not ONLY) + if (renameList->empty()) { + // USE mod (import all, no renames) + PreservedUseStmt stmt{moduleName, PreservedUseStmt::Kind::UseAll}; + currScope().add_preservedUseStmt(std::move(stmt)); + } else { + // USE mod, renames (import all with some renames) + PreservedUseStmt stmt{moduleName, PreservedUseStmt::Kind::UseRenames}; + + for (const auto &rename : *renameList) { + common::visit(common::visitors{ + [&](const parser::Rename::Names &names) { + std::string localName{ + std::get<0>(names.t).source.ToString()}; + stmt.renames.push_back(localName); + }, + [&](const parser::Rename::Operators &) { + // Operator renames - not commonly needed for debug + // info + }, + }, + rename.u); + } + + currScope().add_preservedUseStmt(std::move(stmt)); + } + } + useModuleScope_ = nullptr; } 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/Lower/debug-use-stmt-symbol-refs.f90 b/flang/test/Lower/debug-use-stmt-symbol-refs.f90 new file mode 100644 index 0000000000000..0f2c1db58740b --- /dev/null +++ b/flang/test/Lower/debug-use-stmt-symbol-refs.f90 @@ -0,0 +1,59 @@ +! RUN: bbc -emit-fir %s -o - | FileCheck %s + +! Test USE statement lowering to fir.use_stmt operations +! Covers: USE ONLY, USE with renames, and USE (all) + +module mod1 + integer :: a = 10, b = 20, c = 30 +end module mod1 + +module mod2 + real :: x = 1.0, y = 2.0, z = 3.0 +end module mod2 + +module mod3 + logical :: flag = .true. +end module mod3 + +! Test 1: Program with USE ONLY and USE with renames +program test_main + use mod1, only: b, c + use mod2, renamed_y => y + implicit none + print *, b, c, renamed_y +end program + +! Test 2: Subroutine with USE (all) and different renames +subroutine test_sub() + use mod1 + use mod2, only: x + use mod3, my_flag => flag + implicit none + print *, a, b, c, x, my_flag +end subroutine + +! Test 3: Function with multiple USE patterns +function test_func() result(res) + use mod1, only: a + use mod2, renamed_x => x, renamed_z => z + use mod3 + implicit none + integer :: res + res = a +end function + +! CHECK-LABEL: func.func @_QQmain() +! CHECK-DAG: fir.use_stmt "mod1" only_symbols{{\[}}[@_QMmod1Eb, @_QMmod1Ec]] +! CHECK-DAG: fir.use_stmt "mod2" renames{{\[}}[#fir.use_rename<"renamed_y", @_QMmod2Ey>]] + +! CHECK-LABEL: func.func @_QPtest_sub() +! CHECK-DAG: fir.use_stmt "mod1"{{$}} +! CHECK-DAG: fir.use_stmt "mod2" only_symbols{{\[}}[@_QMmod2Ex]] +! CHECK-DAG: fir.use_stmt "mod3" renames{{\[}}[#fir.use_rename<"my_flag", @_QMmod3Eflag>]] + +! CHECK-LABEL: func.func @_QPtest_func() +! CHECK-DAG: fir.use_stmt "mod1" only_symbols{{\[}}[@_QMmod1Ea]] +! CHECK-DAG: fir.use_stmt "mod2" renames{{\[}}[#fir.use_rename<"renamed_x", @_QMmod2Ex>, #fir.use_rename<"renamed_z", @_QMmod2Ez>]] +! CHECK-DAG: fir.use_stmt "mod3"{{$}} + + diff --git a/flang/test/Transforms/debug-imported-entity.fir b/flang/test/Transforms/debug-imported-entity.fir index 194bc82724583..eb080e3390802 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 @@ -23,8 +24,7 @@ module { #loc3 = loc("test.f90":10:1) #loc4 = loc("test.f90":13:1) -// CHECK: #[[MOD:.+]] = #llvm.di_module<{{.*}}name = "foo"{{.*}}> -// CHECK: #[[SP_REC:.+]] = #llvm.di_subprogram, isRecSelf = true{{.*}}> -// CHECK: #[[IMP_ENTITY:.+]] = #llvm.di_imported_entity -// CHECK: #[[SP:.+]] = #llvm.di_subprogram{{.*}}retainedNodes = #[[IMP_ENTITY]]> -// CHECK: #llvm.di_global_variable +// CHECK-DAG: #[[MOD:.+]] = #llvm.di_module<{{.*}}name = "foo"{{.*}}> +// CHECK-DAG: #[[SP_REC:.+]] = #llvm.di_subprogram, isRecSelf = true{{.*}}> +// CHECK-DAG: #[[IMP_ENTITY:.+]] = #llvm.di_imported_entity +// CHECK-DAG: #[[SP:.+]] = #llvm.di_subprogram{{.*}}retainedNodes = #[[IMP_ENTITY]]> diff --git a/flang/test/Transforms/debug-local-global-storage-1.fir b/flang/test/Transforms/debug-local-global-storage-1.fir index 2638464dbab0b..bb213771247b1 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) @@ -45,8 +46,8 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry : // CHECK-DAG: #[[CU:.*]] = #llvm.di_compile_unit<{{.*}}> // CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]]{{.*}}name = "example"{{.*}}> // CHECK-DAG: #[[SP:.*]] = #llvm.di_subprogram<{{.*}}name = "test"{{.*}}> -// CHECK-DAG: #[[MOD_SP:.*]] = #llvm.di_subprogram<{{.*}}name = "mod_sub"{{.*}}retainedNodes = {{.*}}> +// CHECK-DAG: #llvm.di_imported_entity +// CHECK-DAG: #llvm.di_subprogram<{{.*}}name = "mod_sub"{{.*}}retainedNodes = {{.*}}> // CHECK-DAG: #llvm.di_global_variable // CHECK-DAG: #llvm.di_global_variable -// CHECK-DAG: #llvm.di_global_variable -// CHECK-DAG: #llvm.di_global_variable +// CHECK-DAG: #llvm.di_global_variable<{{.*}}name = "mod_arr"{{.*}}line = 5{{.*}}> diff --git a/flang/test/Transforms/debug-use-stmt.fir b/flang/test/Transforms/debug-use-stmt.fir new file mode 100644 index 0000000000000..73623fd0b125e --- /dev/null +++ b/flang/test/Transforms/debug-use-stmt.fir @@ -0,0 +1,71 @@ +// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s + +// Test AddDebugInfo pass processes USE statements with ONLY and renames correctly + +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 = {{.+}}>