diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp index 72172f63888e1c..fb7afcf07713c0 100644 --- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp +++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp @@ -2716,6 +2716,18 @@ struct GlobalOpConversion : public fir::FIROpConversion { mlir::LogicalResult matchAndRewrite(fir::GlobalOp global, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const override { + + mlir::LLVM::DIGlobalVariableExpressionAttr dbgExpr; + + if (auto fusedLoc = mlir::dyn_cast(global.getLoc())) { + if (auto gvAttr = + mlir::dyn_cast_or_null( + fusedLoc.getMetadata())) { + dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get( + global.getContext(), gvAttr, mlir::LLVM::DIExpressionAttr()); + } + } + auto tyAttr = convertType(global.getType()); if (auto boxType = mlir::dyn_cast(global.getType())) tyAttr = this->lowerTy().convertBoxTypeAsStruct(boxType); @@ -2724,8 +2736,11 @@ struct GlobalOpConversion : public fir::FIROpConversion { assert(attributeTypeIsCompatible(global.getContext(), initAttr, tyAttr)); auto linkage = convertLinkage(global.getLinkName()); auto isConst = global.getConstant().has_value(); + mlir::SymbolRefAttr comdat; + llvm::ArrayRef attrs; auto g = rewriter.create( - loc, tyAttr, isConst, linkage, global.getSymName(), initAttr); + loc, tyAttr, isConst, linkage, global.getSymName(), initAttr, 0, 0, + false, false, comdat, attrs, dbgExpr); auto module = global->getParentOfType(); // Add comdat if necessary diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp index 07e8aed4cd07b6..fb7c0bf0d1f977 100644 --- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp +++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp @@ -54,6 +54,16 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase { public: AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {} void runOnOperation() override; + +private: + llvm::StringMap moduleMap; + + mlir::LLVM::DIModuleAttr getOrCreateModuleAttr( + const std::string &name, mlir::LLVM::DIFileAttr fileAttr, + mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl); + + void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr, + mlir::LLVM::DIScopeAttr scope); }; static uint32_t getLineFromLoc(mlir::Location loc) { @@ -99,6 +109,70 @@ void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp, declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr)); } +// The `module` does not have a first class representation in the `FIR`. We +// extract information about it from the name of the identifiers and keep a +// map to avoid duplication. +mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( + const std::string &name, mlir::LLVM::DIFileAttr fileAttr, + mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) { + mlir::MLIRContext *context = &getContext(); + mlir::LLVM::DIModuleAttr modAttr; + if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) { + modAttr = iter->getValue(); + } else { + modAttr = mlir::LLVM::DIModuleAttr::get( + context, fileAttr, scope, mlir::StringAttr::get(context, name), + /* configMacros */ mlir::StringAttr(), + /* includePath */ mlir::StringAttr(), + /* apinotes */ mlir::StringAttr(), line, decl); + moduleMap[name] = modAttr; + } + return modAttr; +} + +void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp, + mlir::LLVM::DIFileAttr fileAttr, + mlir::LLVM::DIScopeAttr scope) { + mlir::ModuleOp module = getOperation(); + mlir::MLIRContext *context = &getContext(); + fir::DebugTypeGenerator typeGen(module); + mlir::OpBuilder builder(context); + + std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); + if (result.first != fir::NameUniquer::NameKind::VARIABLE) + return; + + unsigned line = getLineFromLoc(globalOp.getLoc()); + + // DWARF5 says following about the fortran modules: + // A Fortran 90 module may also be represented by a module entry + // (but no declaration attribute is warranted because Fortran has no concept + // of a corresponding module body). + // But in practice, compilers use declaration attribute with a module in cases + // where module was defined in another source file (only being used in this + // one). The isInitialized() seems to provide the right information + // but inverted. It is true where module is actually defined but false where + // it is used. + // FIXME: Currently we don't have the line number on which a module was + // declared. We are using a best guess of line - 1 where line is the source + // line of the first member of the module that we encounter. + + if (result.second.modules.empty()) + return; + + scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope, + line - 1, !globalOp.isInitialized()); + + mlir::LLVM::DITypeAttr diType = typeGen.convertType( + globalOp.getType(), fileAttr, scope, globalOp.getLoc()); + auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( + context, scope, mlir::StringAttr::get(context, result.second.name), + mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line, + diType, /*isLocalToUnit*/ false, + /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0); + globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr)); +} + void AddDebugInfoPass::runOnOperation() { mlir::ModuleOp module = getOperation(); mlir::MLIRContext *context = &getContext(); @@ -138,6 +212,12 @@ void AddDebugInfoPass::runOnOperation() { llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer, isOptimized, debugLevel); + if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { + // Process 'GlobalOp' only if full debug info is requested. + for (auto globalOp : module.getOps()) + handleGlobalOp(globalOp, fileAttr, cuAttr); + } + module.walk([&](mlir::func::FuncOp funcOp) { mlir::Location l = funcOp->getLoc(); // If fused location has already been created then nothing to do @@ -180,6 +260,7 @@ void AddDebugInfoPass::runOnOperation() { // Only definitions need a distinct identifier and a compilation unit. mlir::DistinctAttr id; + mlir::LLVM::DIScopeAttr Scope = fileAttr; mlir::LLVM::DICompileUnitAttr compilationUnit; mlir::LLVM::DISubprogramFlags subprogramFlags = mlir::LLVM::DISubprogramFlags{}; @@ -192,9 +273,13 @@ void AddDebugInfoPass::runOnOperation() { subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; } unsigned line = getLineFromLoc(l); + if (!result.second.modules.empty()) + Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr, + line - 1, false); + auto spAttr = mlir::LLVM::DISubprogramAttr::get( - context, id, compilationUnit, fileAttr, funcName, fullName, - funcFileAttr, line, line, subprogramFlags, subTypeAttr); + context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, + line, line, subprogramFlags, subTypeAttr); funcOp->setLoc(builder.getFusedLoc({funcOp->getLoc()}, spAttr)); // Don't process variables if user asked for line tables only. diff --git a/flang/test/Integration/debug-module-2.f90 b/flang/test/Integration/debug-module-2.f90 new file mode 100644 index 00000000000000..60fccaa2a6c1f2 --- /dev/null +++ b/flang/test/Integration/debug-module-2.f90 @@ -0,0 +1,39 @@ +! RUN: %flang_fc1 -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s +! RUN: %flang_fc1 -emit-llvm -debug-info-kind=line-tables-only %s -o - | FileCheck --check-prefix=LINEONLY %s + +! CHECK-DAG: ![[FILE:.*]] = !DIFile(filename: {{.*}}debug-module-2.f90{{.*}}) +! CHECK-DAG: ![[FILE2:.*]] = !DIFile(filename: {{.*}}debug-module-2.f90{{.*}}) +! CHECK-DAG: ![[CU:.*]] = distinct !DICompileUnit({{.*}}file: ![[FILE]]{{.*}} globals: ![[GLOBALS:.*]]) +! CHECK-DAG: ![[MOD:.*]] = !DIModule(scope: ![[CU]], name: "helper", file: ![[FILE]]{{.*}}) +! CHECK-DAG: ![[R4:.*]] = !DIBasicType(name: "real", size: 32, encoding: DW_ATE_float) +! CHECK-DAG: ![[I4:.*]] = !DIBasicType(name: "integer", size: 32, encoding: DW_ATE_signed) +module helper +! CHECK-DAG: ![[GLR:.*]] = distinct !DIGlobalVariable(name: "glr", linkageName: "_QMhelperEglr", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[R4]], isLocal: false, isDefinition: true) +! CHECK-DAG: ![[GLRX:.*]] = !DIGlobalVariableExpression(var: ![[GLR]], expr: !DIExpression()) + real glr + +! CHECK-DAG: ![[GLI:.*]] = distinct !DIGlobalVariable(name: "gli", linkageName: "_QMhelperEgli", scope: ![[MOD]], file: ![[FILE]], line: [[@LINE+2]], type: ![[I4]], isLocal: false, isDefinition: true) +! CHECK-DAG: ![[GLIX:.*]] = !DIGlobalVariableExpression(var: ![[GLI]], expr: !DIExpression()) + integer gli + + contains +!CHECK-DAG: !DISubprogram(name: "test", linkageName: "_QMhelperPtest", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+1]]{{.*}}unit: ![[CU]]) + subroutine test() + glr = 12.34 + gli = 67 + + end subroutine +end module helper + +program test +use helper +implicit none + + glr = 3.14 + gli = 2 + call test() + +end program test + +! CHECK-DAG: ![[GLOBALS]] = !{![[GLIX]], ![[GLRX]]} +! LINEONLY-NOT: DIGlobalVariable diff --git a/flang/test/Transforms/debug-module-1.fir b/flang/test/Transforms/debug-module-1.fir new file mode 100644 index 00000000000000..822ae01b99aa78 --- /dev/null +++ b/flang/test/Transforms/debug-module-1.fir @@ -0,0 +1,40 @@ +// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s + + +module attributes {} { + fir.global @_QMhelperEgli : i32 { + %0 = fir.zero_bits i32 + fir.has_value %0 : i32 + } loc(#loc1) + fir.global @_QMhelperEglr : f32 { + %0 = fir.zero_bits f32 + fir.has_value %0 : f32 + } loc(#loc2) + func.func @_QMhelperPtest() { + %c67_i32 = arith.constant 67 : i32 + %cst = arith.constant 1.234000e+01 : f32 + %0 = fir.address_of(@_QMhelperEgli) : !fir.ref + %1 = fir.address_of(@_QMhelperEglr) : !fir.ref + fir.store %cst to %1 : !fir.ref + fir.store %c67_i32 to %0 : !fir.ref + return + } loc(#loc3) +} +#loc1 = loc("test.f90":12:11) +#loc2 = loc("test.f90":15:8) +#loc3 = loc("test.f90":20:5) + +// CHECK-DAG: #[[I4:.*]] = #llvm.di_basic_type +// CHECK-DAG: #[[R4:.*]] = #llvm.di_basic_type +// CHECK-DAG: #[[CU:.*]] = #llvm.di_compile_unit<{{.*}}> +// CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]], name = "helper"{{.*}}> +// CHECK-DAG: #[[LOC1:.*]] = loc("{{.*}}test.f90":12{{.*}}) +// CHECK-DAG: #[[GLI:.*]] = #llvm.di_global_variable +// CHECK-DAG: #[[LOC2:.*]] = loc("{{.*}}test.f90":15{{.*}}) +// CHECK-DAG: #[[GLR:.*]] = #llvm.di_global_variable +// CHECK-DAG: #[[LOC3:.*]] = loc("{{.*}}test.f90":20{{.*}}) +// CHECK-DAG: #[[TEST:.*]] = #llvm.di_subprogram<{{.*}}compileUnit = #[[CU]], scope = #[[MOD]], name = "test", linkageName = "_QMhelperPtest"{{.*}}line = 20, scopeLine = 20{{.*}}> +// CHECK-DAG: loc(fused<#[[GLI]]>[#[[LOC1]]]) +// CHECK-DAG: loc(fused<#[[GLR]]>[#[[LOC2]]]) +// CHECK-DAG: loc(fused<#[[TEST]]>[#[[LOC3]]]) + diff --git a/flang/test/Transforms/debug-module-2.fir b/flang/test/Transforms/debug-module-2.fir new file mode 100644 index 00000000000000..6acdc1df23d27c --- /dev/null +++ b/flang/test/Transforms/debug-module-2.fir @@ -0,0 +1,35 @@ +// RUN: fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" --mlir-print-debuginfo %s | FileCheck %s + +module { + fir.global @_QMhelperEgli : i32 { + %0 = fir.zero_bits i32 + fir.has_value %0 : i32 + } loc(#loc3) + fir.global @_QMhelperEglr : f32 { + %0 = fir.zero_bits f32 + fir.has_value %0 : f32 + } loc(#loc4) +} +#di_basic_type = #llvm.di_basic_type +#di_basic_type1 = #llvm.di_basic_type + +#di_file = #llvm.di_file<"test.f90" in ""> +#di_subroutine_type = #llvm.di_subroutine_type + +#di_compile_unit = #llvm.di_compile_unit, sourceLanguage = DW_LANG_Fortran95, file = #di_file, producer = "flang version 19.0.0 (/home/haqadeer/work/llvm-project/flang 5d5c73cad421bdca6e43e1cc10704ff160f1a33e)", isOptimized = false, emissionKind = Full> +#di_module = #llvm.di_module +#di_global_variable = #llvm.di_global_variable +#di_global_variable1 = #llvm.di_global_variable + +#loc1 = loc("test.f90":12:11) +#loc2 = loc("test.f90":15:8) +#loc3 = loc(fused<#di_global_variable>[#loc1]) +#loc4 = loc(fused<#di_global_variable1>[#loc2]) + + +// CHECK-DAG: #[[GLI:.*]] = #llvm.di_global_variable<{{.*}}name = "gli", linkageName = "_QMhelperEgli"{{.*}}> +// CHECK-DAG: #[[GLR:.*]] = #llvm.di_global_variable<{{.*}}name = "glr", linkageName = "_QMhelperEglr"{{.*}}> +// CHECK-DAG: #[[GLIE:.*]] = #llvm.di_global_variable_expression +// CHECK-DAG: #[[GLRE:.*]] = #llvm.di_global_variable_expression +// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEgli() {{{.*}}dbg_expr = #[[GLIE]]} +// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEglr() {{{.*}}dbg_expr = #[[GLRE]]}