Skip to content

Commit db64e69

Browse files
authored
[flang][debug] Handle 'used' module. (llvm#107626)
As described in llvm#98883, we have to qualify a module variable name in debugger to get its value. This PR tries to remove this limitation. LLVM provides `DIImportedEntity` to handle such cases but the PR is made more complicated due to the following 2 issues. 1. The MLIR attributes are readonly and we have a circular dependency here. This has to be handled using the recursive interface provided by the MLIR. This requires us to first create a place holder `DISubprogramAttr` which is used in creating `DIImportedEntityAttr`. Later another `DISubprogramAttr` is created which replaces the place holder. 2. The flang IR does not provide any information about the 'used' module so this has to be extracted by doing a pass over the `DeclareOp` in the function. This presents certain limitation as 'only' and module variable renaming may not be handled properly. Due to the change in `DISubprogramAttr`, some tests also needed to be adjusted. Fixes llvm#98883.
1 parent c9aa55d commit db64e69

File tree

7 files changed

+150
-41
lines changed

7 files changed

+150
-41
lines changed

flang/lib/Optimizer/Transforms/AddDebugInfo.cpp

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
7272
mlir::LLVM::DICompileUnitAttr cuAttr,
7373
fir::DebugTypeGenerator &typeGen,
7474
mlir::SymbolTable *symbolTable);
75+
std::optional<mlir::LLVM::DIModuleAttr>
76+
getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
77+
mlir::LLVM::DIFileAttr fileAttr,
78+
mlir::LLVM::DIScopeAttr scope);
7579
};
7680

7781
bool debugInfoIsAlreadySet(mlir::Location loc) {
@@ -152,6 +156,45 @@ mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr(
152156
return modAttr;
153157
}
154158

159+
/// If globalOp represents a module variable, return a ModuleAttr that
160+
/// represents that module.
161+
std::optional<mlir::LLVM::DIModuleAttr>
162+
AddDebugInfoPass::getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
163+
mlir::LLVM::DIFileAttr fileAttr,
164+
mlir::LLVM::DIScopeAttr scope) {
165+
mlir::MLIRContext *context = &getContext();
166+
mlir::OpBuilder builder(context);
167+
168+
std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
169+
// Only look for module if this variable is not part of a function.
170+
if (!result.second.procs.empty() || result.second.modules.empty())
171+
return std::nullopt;
172+
173+
// DWARF5 says following about the fortran modules:
174+
// A Fortran 90 module may also be represented by a module entry
175+
// (but no declaration attribute is warranted because Fortran has no concept
176+
// of a corresponding module body).
177+
// But in practice, compilers use declaration attribute with a module in cases
178+
// where module was defined in another source file (only being used in this
179+
// one). The isInitialized() seems to provide the right information
180+
// but inverted. It is true where module is actually defined but false where
181+
// it is used.
182+
// FIXME: Currently we don't have the line number on which a module was
183+
// declared. We are using a best guess of line - 1 where line is the source
184+
// line of the first member of the module that we encounter.
185+
unsigned line = getLineFromLoc(globalOp.getLoc());
186+
187+
mlir::LLVM::DISubprogramAttr sp =
188+
mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope);
189+
// Modules are generated at compile unit scope
190+
if (sp)
191+
scope = sp.getCompileUnit();
192+
193+
return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope,
194+
std::max(line - 1, (unsigned)1),
195+
!globalOp.isInitialized());
196+
}
197+
155198
void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
156199
mlir::LLVM::DIFileAttr fileAttr,
157200
mlir::LLVM::DIScopeAttr scope,
@@ -174,33 +217,11 @@ void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
174217
return;
175218

176219
unsigned line = getLineFromLoc(globalOp.getLoc());
220+
std::optional<mlir::LLVM::DIModuleAttr> modOpt =
221+
getModuleAttrFromGlobalOp(globalOp, fileAttr, scope);
222+
if (modOpt)
223+
scope = *modOpt;
177224

178-
// DWARF5 says following about the fortran modules:
179-
// A Fortran 90 module may also be represented by a module entry
180-
// (but no declaration attribute is warranted because Fortran has no concept
181-
// of a corresponding module body).
182-
// But in practice, compilers use declaration attribute with a module in cases
183-
// where module was defined in another source file (only being used in this
184-
// one). The isInitialized() seems to provide the right information
185-
// but inverted. It is true where module is actually defined but false where
186-
// it is used.
187-
// FIXME: Currently we don't have the line number on which a module was
188-
// declared. We are using a best guess of line - 1 where line is the source
189-
// line of the first member of the module that we encounter.
190-
191-
if (result.second.procs.empty()) {
192-
// Only look for module if this variable is not part of a function.
193-
if (result.second.modules.empty())
194-
return;
195-
196-
// Modules are generated at compile unit scope
197-
if (mlir::LLVM::DISubprogramAttr sp =
198-
mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope))
199-
scope = sp.getCompileUnit();
200-
201-
scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope,
202-
line - 1, !globalOp.isInitialized());
203-
}
204225
mlir::LLVM::DITypeAttr diType =
205226
typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
206227
auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
@@ -262,15 +283,18 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
262283
mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
263284

264285
// Only definitions need a distinct identifier and a compilation unit.
265-
mlir::DistinctAttr id;
286+
mlir::DistinctAttr id, id2;
266287
mlir::LLVM::DIScopeAttr Scope = fileAttr;
267288
mlir::LLVM::DICompileUnitAttr compilationUnit;
268289
mlir::LLVM::DISubprogramFlags subprogramFlags =
269290
mlir::LLVM::DISubprogramFlags{};
270291
if (isOptimized)
271292
subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized;
272293
if (!funcOp.isExternal()) {
294+
// Place holder and final function have to have different IDs, otherwise
295+
// translation code will reject one of them.
273296
id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
297+
id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
274298
compilationUnit = cuAttr;
275299
subprogramFlags =
276300
subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition;
@@ -299,14 +323,69 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
299323
line - 1, false);
300324
}
301325

302-
auto spAttr = mlir::LLVM::DISubprogramAttr::get(
303-
context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr,
304-
line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{});
305-
funcOp->setLoc(builder.getFusedLoc({funcOp->getLoc()}, spAttr));
306-
307326
// Don't process variables if user asked for line tables only.
308-
if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly)
327+
if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly) {
328+
auto spAttr = mlir::LLVM::DISubprogramAttr::get(
329+
context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr,
330+
line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{});
331+
funcOp->setLoc(builder.getFusedLoc({l}, spAttr));
309332
return;
333+
}
334+
335+
mlir::DistinctAttr recId =
336+
mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
337+
338+
// The debug attribute in MLIR are readonly once created. But in case of
339+
// imported entities, we have a circular dependency. The
340+
// DIImportedEntityAttr requires scope information (DISubprogramAttr in this
341+
// case) and DISubprogramAttr requires the list of imported entities. The
342+
// MLIR provides a way where a DISubprogramAttr an be created with a certain
343+
// recID and be used in places like DIImportedEntityAttr. After that another
344+
// DISubprogramAttr can be created with same recID but with list of entities
345+
// now available. The MLIR translation code takes care of updating the
346+
// references. Note that references will be updated only in the things that
347+
// are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to
348+
// create the final DISubprogramAttr before we process local variables.
349+
// Look at DIRecursiveTypeAttrInterface for more details.
350+
351+
auto spAttr = mlir::LLVM::DISubprogramAttr::get(
352+
context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, funcName,
353+
fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr,
354+
/*retainedNodes=*/{});
355+
356+
// There is no direct information in the IR for any 'use' statement in the
357+
// function. We have to extract that information from the DeclareOp. We do
358+
// a pass on the DeclareOp and generate ModuleAttr and corresponding
359+
// DIImportedEntityAttr for that module.
360+
// FIXME: As we are depending on the variables to see which module is being
361+
// 'used' in the function, there are certain limitations.
362+
// For things like 'use mod1, only: v1', whole module will be brought into the
363+
// namespace in the debug info. It is not a problem as such unless there is a
364+
// clash of names.
365+
// There is no information about module variable renaming
366+
llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules;
367+
funcOp.walk([&](fir::cg::XDeclareOp declOp) {
368+
if (&funcOp.front() == declOp->getBlock())
369+
if (auto global =
370+
symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) {
371+
std::optional<mlir::LLVM::DIModuleAttr> modOpt =
372+
getModuleAttrFromGlobalOp(global, fileAttr, cuAttr);
373+
if (modOpt) {
374+
auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get(
375+
context, llvm::dwarf::DW_TAG_imported_module, spAttr, *modOpt,
376+
fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {});
377+
importedModules.insert(importedEntity);
378+
}
379+
}
380+
});
381+
llvm::SmallVector<mlir::LLVM::DINodeAttr> entities(importedModules.begin(),
382+
importedModules.end());
383+
// We have the imported entities now. Generate the final DISubprogramAttr.
384+
spAttr = mlir::LLVM::DISubprogramAttr::get(
385+
context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope,
386+
funcName, fullName, funcFileAttr, line, line, subprogramFlags,
387+
subTypeAttr, entities);
388+
funcOp->setLoc(builder.getFusedLoc({l}, spAttr));
310389

311390
funcOp.walk([&](fir::cg::XDeclareOp declOp) {
312391
// FIXME: We currently dont handle variables that are not in the entry

flang/test/Integration/debug-module-2.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module helper
1717
integer gli
1818

1919
contains
20-
!CHECK-DAG: !DISubprogram(name: "test", linkageName: "_QMhelperPtest", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+1]]{{.*}}unit: ![[CU]])
20+
!CHECK-DAG: !DISubprogram(name: "test", linkageName: "_QMhelperPtest", scope: ![[MOD]], file: ![[FILE2]], line: [[@LINE+1]]{{.*}}unit: ![[CU]]{{.*}})
2121
subroutine test()
2222
glr = 12.34
2323
gli = 67

flang/test/Transforms/debug-90683.fir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
2222

2323
// CHECK-DAG: #[[TY:.*]] = #llvm.di_basic_type<tag = DW_TAG_base_type, name = "real", sizeInBits = 64, encoding = DW_ATE_float>
2424
// CHECK-DAG: #[[TY1:.*]] = #llvm.di_subroutine_type<callingConvention = DW_CC_normal, types = #[[TY]], #[[TY]], #[[TY]]>
25-
// CHECK-DAG: #{{.*}} = #llvm.di_subprogram<scope = #{{.*}}, name = "cabs", linkageName = "cabs", file = #{{.*}}, line = {{.*}}, scopeLine = {{.*}}, type = #[[TY1]]>
25+
// CHECK-DAG: #{{.*}} = #llvm.di_subprogram<{{.*}}name = "cabs", linkageName = "cabs"{{.*}}, type = #[[TY1]]>

flang/test/Transforms/debug-fn-info.fir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
6969
// CHECK: #[[TY2:.*]] = #llvm.di_subroutine_type<callingConvention = DW_CC_normal, types = #[[INT4]], #[[INT8]], #[[REAL4]], #[[LOG4]]>
7070

7171
// Line numbers should match the number in corresponding loc entry.
72-
// CHECK: #llvm.di_subprogram<id = {{.*}}, compileUnit = {{.*}}, scope = {{.*}}, name = "_QQmain", linkageName = "_QQmain", file = {{.*}}, line = 15, scopeLine = 15, subprogramFlags = Definition, type = #[[TY0]]>
73-
// CHECK: #llvm.di_subprogram<id = {{.*}}, compileUnit = {{.*}}, scope = {{.*}}, name = "fn1", linkageName = "_QFPfn1", file = {{.*}}, line = 26, scopeLine = 26, subprogramFlags = Definition, type = #[[TY1]]>
74-
// CHECK: #llvm.di_subprogram<id = {{.*}}, compileUnit = {{.*}}, scope = {{.*}}, name = "fn2", linkageName = "_QFPfn2", file = {{.*}}, line = 43, scopeLine = 43, subprogramFlags = Definition, type = #[[TY2]]>
72+
// CHECK: #llvm.di_subprogram<{{.*}}name = "_QQmain", linkageName = "_QQmain", file = {{.*}}, line = 15, scopeLine = 15, subprogramFlags = Definition, type = #[[TY0]]>
73+
// CHECK: #llvm.di_subprogram<{{.*}}name = "fn1", linkageName = "_QFPfn1", file = {{.*}}, line = 26, scopeLine = 26, subprogramFlags = Definition, type = #[[TY1]]>
74+
// CHECK: #llvm.di_subprogram<{{.*}}name = "fn2", linkageName = "_QFPfn2", file = {{.*}}, line = 43, scopeLine = 43, subprogramFlags = Definition, type = #[[TY2]]>
7575

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %s | FileCheck %s
2+
3+
4+
module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
5+
fir.global @_QMfooEv1 : i32 {
6+
%0 = fir.zero_bits i32
7+
fir.has_value %0 : i32
8+
}
9+
fir.global internal @_QFtestExyz : i32 {
10+
%c12_i32 = arith.constant 12 : i32
11+
fir.has_value %c12_i32 : i32
12+
} loc(#loc4)
13+
func.func @test() attributes {fir.bindc_name = "test"} {
14+
%0 = fir.address_of(@_QMfooEv1) : !fir.ref<i32>
15+
%1 = fircg.ext_declare %0 {uniq_name = "_QMfooEv1"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc1)
16+
%4 = fir.address_of(@_QFtestExyz) : !fir.ref<i32>
17+
%5 = fircg.ext_declare %4 {uniq_name = "_QFtestExyz"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc4)
18+
return
19+
} loc(#loc3)
20+
}
21+
#loc1 = loc("test.f90":2:14)
22+
#loc2 = loc("test.f90":6:1)
23+
#loc3 = loc("test.f90":10:1)
24+
#loc4 = loc("test.f90":13:1)
25+
26+
// CHECK: #[[MOD:.+]] = #llvm.di_module<{{.*}}name = "foo"{{.*}}>
27+
// CHECK: #[[SP_REC:.+]] = #llvm.di_subprogram<recId = distinct[[[REC_ID:[0-9]+]]]<>, isRecSelf = true{{.*}}>
28+
// CHECK: #[[IMP_ENTITY:.+]] = #llvm.di_imported_entity<tag = DW_TAG_imported_module, scope = #[[SP_REC]], entity = #[[MOD]]{{.*}}>
29+
// CHECK: #[[SP:.+]] = #llvm.di_subprogram<recId = distinct[[[REC_ID]]]<>{{.*}}retainedNodes = #[[IMP_ENTITY]]>
30+
// CHECK: #llvm.di_global_variable<scope = #[[SP]], name = "xyz"{{.*}}>

flang/test/Transforms/debug-line-table-inc-file.fir

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
3131
// CHECK: #[[LOC_INC_FILE:.*]] = loc("{{.*}}inc.f90":1:1)
3232
// CHECK: #[[LOC_FILE:.*]] = loc("{{.*}}simple.f90":3:1)
3333
// CHECK: #[[DI_CU:.*]] = #llvm.di_compile_unit<id = distinct[{{.*}}]<>, sourceLanguage = DW_LANG_Fortran95, file = #[[DI_FILE]], producer = "{{.*}}flang{{.*}}", isOptimized = false, emissionKind = LineTablesOnly>
34-
// CHECK: #[[DI_SP_INC:.*]] = #llvm.di_subprogram<id = distinct[{{.*}}]<>, compileUnit = #[[DI_CU]], scope = #[[DI_FILE]], name = "sinc", linkageName = "_QPsinc", file = #[[DI_INC_FILE]], {{.*}}>
35-
// CHECK: #[[DI_SP:.*]] = #llvm.di_subprogram<id = distinct[{{.*}}]<>, compileUnit = #[[DI_CU]], scope = #[[DI_FILE]], name = "_QQmain", linkageName = "_QQmain", file = #[[DI_FILE]], {{.*}}>
34+
// CHECK: #[[DI_SP_INC:.*]] = #llvm.di_subprogram<{{.*}}id = distinct[{{.*}}]<>, compileUnit = #[[DI_CU]], scope = #[[DI_FILE]], name = "sinc", linkageName = "_QPsinc", file = #[[DI_INC_FILE]], {{.*}}>
35+
// CHECK: #[[DI_SP:.*]] = #llvm.di_subprogram<{{.*}}id = distinct[{{.*}}]<>, compileUnit = #[[DI_CU]], scope = #[[DI_FILE]], name = "_QQmain", linkageName = "_QQmain", file = #[[DI_FILE]], {{.*}}>
3636
// CHECK: #[[FUSED_LOC_INC_FILE]] = loc(fused<#[[DI_SP_INC]]>[#[[LOC_INC_FILE]]])
3737
// CHECK: #[[FUSED_LOC_FILE]] = loc(fused<#[[DI_SP]]>[#[[LOC_FILE]]])

flang/test/Transforms/debug-local-global-storage-1.fir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i64, dense<64> :
4545
// CHECK-DAG: #[[CU:.*]] = #llvm.di_compile_unit<{{.*}}>
4646
// CHECK-DAG: #[[MOD:.*]] = #llvm.di_module<{{.*}}scope = #[[CU]]{{.*}}name = "example"{{.*}}>
4747
// CHECK-DAG: #[[SP:.*]] = #llvm.di_subprogram<{{.*}}name = "_QQmain"{{.*}}>
48-
// CHECK-DAG: #[[MOD_SP:.*]] = #llvm.di_subprogram<{{.*}}name = "mod_sub"{{.*}}>
48+
// CHECK-DAG: #[[MOD_SP:.*]] = #llvm.di_subprogram<{{.*}}name = "mod_sub"{{.*}}retainedNodes = {{.*}}>
4949
// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "arr"{{.*}}line = 22{{.*}}>
5050
// CHECK-DAG: #llvm.di_global_variable<scope = #[[SP]], name = "s"{{.*}}line = 23{{.*}}>
5151
// CHECK-DAG: #llvm.di_global_variable<scope = #[[MOD_SP]], name = "ss"{{.*}}line = 12{{.*}}>

0 commit comments

Comments
 (0)