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"{{$}} + +