From f7a8e99d86c296cb4a40d6eee6d3f96766af2c4b Mon Sep 17 00:00:00 2001 From: Abid Qadeer Date: Fri, 14 Nov 2025 17:46:50 +0000 Subject: [PATCH 1/4] [flang] Represent use statement in fir. This patch adds infrastructure to emit Fortran USE statement information in FIR, which will be used by subsequent patches to generate DWARF debug information. The information about use statement is collected during semantic analysis and stored in PreservedUseStmt objects. During lowering, fir.use_stmt operations are emitted for each PreservedUseStmt object. The fir.use_stmt operation captures the module name, ONLY list symbols, and any renames specified in the USE statement. --- .../flang/Optimizer/Dialect/FIRAttr.td | 17 ++++ .../include/flang/Optimizer/Dialect/FIROps.td | 52 +++++++++++ flang/include/flang/Semantics/scope.h | 25 +++++ flang/lib/Lower/Bridge.cpp | 92 +++++++++++++++++++ flang/lib/Optimizer/CodeGen/CodeGen.cpp | 19 +++- flang/lib/Semantics/resolve-names.cpp | 80 ++++++++++++++++ .../test/Lower/debug-use-stmt-symbol-refs.f90 | 59 ++++++++++++ 7 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 flang/test/Lower/debug-use-stmt-symbol-refs.f90 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/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/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"{{$}} + + From 7218b208b4ab48b1269b78251d11fadb9eda357f Mon Sep 17 00:00:00 2001 From: Abid Qadeer Date: Mon, 17 Nov 2025 18:58:04 +0000 Subject: [PATCH 2/4] [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. There was another small cleanup that same ID is used in both the placeholder and the actual DISubprogramAttr. --- .../lib/Optimizer/Transforms/AddDebugInfo.cpp | 374 +++++++++++++----- .../debug-use-stmt-symbol-refs.f90 | 38 ++ .../test/Transforms/debug-imported-entity.fir | 10 +- .../debug-local-global-storage-1.fir | 7 +- 4 files changed, 323 insertions(+), 106 deletions(-) create mode 100644 flang/test/Integration/debug-use-stmt-symbol-refs.f90 diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp index e006d2e878fd8..103bbf104e2c5 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 @@ -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,60 @@ 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 +586,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 +668,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 +855,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/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..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{{.*}}> From 403d802e6dd8e68a73b9cddebec8de16b07815c2 Mon Sep 17 00:00:00 2001 From: Abid Qadeer Date: Tue, 18 Nov 2025 12:52:02 +0000 Subject: [PATCH 3/4] Add test for changes in AddDebugInfo pass. --- flang/test/Transforms/debug-use-stmt.fir | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 flang/test/Transforms/debug-use-stmt.fir 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 = {{.+}}> From 7e4039b31874c290ee1fa12288e442bf2b186ae9 Mon Sep 17 00:00:00 2001 From: Abid Qadeer Date: Wed, 19 Nov 2025 12:10:09 +0000 Subject: [PATCH 4/4] Fix a formatting issue and build issue. --- flang/lib/Optimizer/Transforms/AddDebugInfo.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp index 103bbf104e2c5..a3d4ed3ca0247 100644 --- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp +++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp @@ -467,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 = @@ -567,7 +567,8 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, } } - // Use same 'id' for both placeholder and final - recId handles the recursion + // 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,