1 | //===-------------- AddDebugInfo.cpp -- add debug info -------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | //===----------------------------------------------------------------------===// |
10 | /// \file |
11 | /// This pass populates some debug information for the module and functions. |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "DebugTypeGenerator.h" |
15 | #include "flang/Optimizer/Builder/FIRBuilder.h" |
16 | #include "flang/Optimizer/Builder/Todo.h" |
17 | #include "flang/Optimizer/Dialect/FIRCG/CGOps.h" |
18 | #include "flang/Optimizer/Dialect/FIRDialect.h" |
19 | #include "flang/Optimizer/Dialect/FIROps.h" |
20 | #include "flang/Optimizer/Dialect/FIROpsSupport.h" |
21 | #include "flang/Optimizer/Dialect/FIRType.h" |
22 | #include "flang/Optimizer/Dialect/Support/FIRContext.h" |
23 | #include "flang/Optimizer/Support/InternalNames.h" |
24 | #include "flang/Optimizer/Transforms/Passes.h" |
25 | #include "flang/Support/Version.h" |
26 | #include "mlir/Dialect/DLTI/DLTI.h" |
27 | #include "mlir/Dialect/Func/IR/FuncOps.h" |
28 | #include "mlir/Dialect/LLVMIR/LLVMDialect.h" |
29 | #include "mlir/IR/Matchers.h" |
30 | #include "mlir/IR/TypeUtilities.h" |
31 | #include "mlir/Pass/Pass.h" |
32 | #include "mlir/Transforms/DialectConversion.h" |
33 | #include "mlir/Transforms/GreedyPatternRewriteDriver.h" |
34 | #include "mlir/Transforms/RegionUtils.h" |
35 | #include "llvm/BinaryFormat/Dwarf.h" |
36 | #include "llvm/Support/Debug.h" |
37 | #include "llvm/Support/FileSystem.h" |
38 | #include "llvm/Support/Path.h" |
39 | #include "llvm/Support/raw_ostream.h" |
40 | |
41 | namespace fir { |
42 | #define GEN_PASS_DEF_ADDDEBUGINFO |
43 | #include "flang/Optimizer/Transforms/Passes.h.inc" |
44 | } // namespace fir |
45 | |
46 | #define DEBUG_TYPE "flang-add-debug-info" |
47 | |
48 | namespace { |
49 | |
50 | class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> { |
51 | void handleDeclareOp(fir::cg::XDeclareOp declOp, |
52 | mlir::LLVM::DIFileAttr fileAttr, |
53 | mlir::LLVM::DIScopeAttr scopeAttr, |
54 | fir::DebugTypeGenerator &typeGen, |
55 | mlir::SymbolTable *symbolTable); |
56 | |
57 | public: |
58 | AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {} |
59 | void runOnOperation() override; |
60 | |
61 | private: |
62 | llvm::StringMap<mlir::LLVM::DIModuleAttr> moduleMap; |
63 | llvm::StringMap<mlir::LLVM::DICommonBlockAttr> commonBlockMap; |
64 | // List of GlobalVariableExpressionAttr that are attached to a given global |
65 | // that represents the storage for common block. |
66 | llvm::DenseMap<fir::GlobalOp, llvm::SmallVector<mlir::Attribute>> |
67 | globalToGlobalExprsMap; |
68 | |
69 | mlir::LLVM::DIModuleAttr getOrCreateModuleAttr( |
70 | const std::string &name, mlir::LLVM::DIFileAttr fileAttr, |
71 | mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl); |
72 | mlir::LLVM::DICommonBlockAttr |
73 | getOrCreateCommonBlockAttr(llvm::StringRef name, |
74 | mlir::LLVM::DIFileAttr fileAttr, |
75 | mlir::LLVM::DIScopeAttr scope, unsigned line); |
76 | |
77 | void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr, |
78 | mlir::LLVM::DIScopeAttr scope, |
79 | fir::DebugTypeGenerator &typeGen, |
80 | mlir::SymbolTable *symbolTable, |
81 | fir::cg::XDeclareOp declOp); |
82 | void handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr fileAttr, |
83 | mlir::LLVM::DICompileUnitAttr cuAttr, |
84 | fir::DebugTypeGenerator &typeGen, |
85 | mlir::SymbolTable *symbolTable); |
86 | bool createCommonBlockGlobal(fir::cg::XDeclareOp declOp, |
87 | const std::string &name, |
88 | mlir::LLVM::DIFileAttr fileAttr, |
89 | mlir::LLVM::DIScopeAttr scopeAttr, |
90 | fir::DebugTypeGenerator &typeGen, |
91 | mlir::SymbolTable *symbolTable); |
92 | std::optional<mlir::LLVM::DIModuleAttr> |
93 | getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, |
94 | mlir::LLVM::DIFileAttr fileAttr, |
95 | mlir::LLVM::DIScopeAttr scope); |
96 | }; |
97 | |
98 | bool debugInfoIsAlreadySet(mlir::Location loc) { |
99 | if (mlir::isa<mlir::FusedLoc>(loc)) { |
100 | if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>()) |
101 | return false; |
102 | return true; |
103 | } |
104 | return false; |
105 | } |
106 | |
107 | } // namespace |
108 | |
109 | bool AddDebugInfoPass::createCommonBlockGlobal( |
110 | fir::cg::XDeclareOp declOp, const std::string &name, |
111 | mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, |
112 | fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable) { |
113 | mlir::MLIRContext *context = &getContext(); |
114 | mlir::OpBuilder builder(context); |
115 | std::optional<std::int64_t> optint; |
116 | mlir::Operation *op = declOp.getMemref().getDefiningOp(); |
117 | |
118 | if (auto conOp = mlir::dyn_cast_if_present<fir::ConvertOp>(op)) |
119 | op = conOp.getValue().getDefiningOp(); |
120 | |
121 | if (auto cordOp = mlir::dyn_cast_if_present<fir::CoordinateOp>(op)) { |
122 | auto coors = cordOp.getCoor(); |
123 | if (coors.size() != 1) |
124 | return false; |
125 | optint = fir::getIntIfConstant(coors[0]); |
126 | if (!optint) |
127 | return false; |
128 | op = cordOp.getRef().getDefiningOp(); |
129 | if (auto conOp2 = mlir::dyn_cast_if_present<fir::ConvertOp>(op)) |
130 | op = conOp2.getValue().getDefiningOp(); |
131 | |
132 | if (auto addrOfOp = mlir::dyn_cast_if_present<fir::AddrOfOp>(op)) { |
133 | mlir::SymbolRefAttr sym = addrOfOp.getSymbol(); |
134 | if (auto global = |
135 | symbolTable->lookup<fir::GlobalOp>(sym.getRootReference())) { |
136 | |
137 | unsigned line = getLineFromLoc(global.getLoc()); |
138 | llvm::StringRef commonName(sym.getRootReference()); |
139 | // FIXME: We are trying to extract the name of the common block from the |
140 | // name of the global. As part of mangling, GetCommonBlockObjectName can |
141 | // add a trailing _ in the name of that global. The demangle function |
142 | // does not seem to handle such cases. So the following hack is used to |
143 | // remove the trailing '_'. |
144 | if (commonName != Fortran::common::blankCommonObjectName && |
145 | commonName.back() == '_') |
146 | commonName = commonName.drop_back(); |
147 | mlir::LLVM::DICommonBlockAttr commonBlock = |
148 | getOrCreateCommonBlockAttr(commonName, fileAttr, scopeAttr, line); |
149 | mlir::LLVM::DITypeAttr diType = typeGen.convertType( |
150 | fir::unwrapRefType(declOp.getType()), fileAttr, scopeAttr, declOp); |
151 | line = getLineFromLoc(declOp.getLoc()); |
152 | auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( |
153 | context, commonBlock, mlir::StringAttr::get(context, name), |
154 | declOp.getUniqName(), fileAttr, line, diType, |
155 | /*isLocalToUnit*/ false, /*isDefinition*/ true, /* alignInBits*/ 0); |
156 | mlir::LLVM::DIExpressionAttr expr; |
157 | if (*optint != 0) { |
158 | llvm::SmallVector<mlir::LLVM::DIExpressionElemAttr> ops; |
159 | ops.push_back(mlir::LLVM::DIExpressionElemAttr::get( |
160 | context, llvm::dwarf::DW_OP_plus_uconst, *optint)); |
161 | expr = mlir::LLVM::DIExpressionAttr::get(context, ops); |
162 | } |
163 | auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get( |
164 | global.getContext(), gvAttr, expr); |
165 | globalToGlobalExprsMap[global].push_back(dbgExpr); |
166 | return true; |
167 | } |
168 | } |
169 | } |
170 | return false; |
171 | } |
172 | |
173 | void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp, |
174 | mlir::LLVM::DIFileAttr fileAttr, |
175 | mlir::LLVM::DIScopeAttr scopeAttr, |
176 | fir::DebugTypeGenerator &typeGen, |
177 | mlir::SymbolTable *symbolTable) { |
178 | mlir::MLIRContext *context = &getContext(); |
179 | mlir::OpBuilder builder(context); |
180 | auto result = fir::NameUniquer::deconstruct(declOp.getUniqName()); |
181 | |
182 | if (result.first != fir::NameUniquer::NameKind::VARIABLE) |
183 | return; |
184 | |
185 | if (createCommonBlockGlobal(declOp, result.second.name, fileAttr, scopeAttr, |
186 | typeGen, symbolTable)) |
187 | return; |
188 | |
189 | // If this DeclareOp actually represents a global then treat it as such. |
190 | mlir::Operation *defOp = declOp.getMemref().getDefiningOp(); |
191 | if (defOp && llvm::isa<fir::AddrOfOp>(defOp)) { |
192 | if (auto global = |
193 | symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) { |
194 | handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp); |
195 | return; |
196 | } |
197 | } |
198 | |
199 | // FIXME: There may be cases where an argument is processed a bit before |
200 | // DeclareOp is generated. In that case, DeclareOp may point to an |
201 | // intermediate op and not to BlockArgument. |
202 | // Moreover, with MLIR inlining we cannot use the BlockArgument |
203 | // position to identify the original number of the dummy argument. |
204 | // If we want to keep running AddDebugInfoPass late, the dummy argument |
205 | // position in the argument list has to be expressed in FIR (e.g. as a |
206 | // constant attribute of [hl]fir.declare/fircg.ext_declare operation that has |
207 | // a dummy_scope operand). |
208 | unsigned argNo = 0; |
209 | if (declOp.getDummyScope()) { |
210 | if (auto arg = llvm::dyn_cast<mlir::BlockArgument>(declOp.getMemref())) { |
211 | // Check if it is the BlockArgument of the function's entry block. |
212 | if (auto funcLikeOp = |
213 | declOp->getParentOfType<mlir::FunctionOpInterface>()) |
214 | if (arg.getOwner() == &funcLikeOp.front()) |
215 | argNo = arg.getArgNumber() + 1; |
216 | } |
217 | } |
218 | |
219 | auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()), |
220 | fileAttr, scopeAttr, declOp); |
221 | |
222 | auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get( |
223 | context, scopeAttr, mlir::StringAttr::get(context, result.second.name), |
224 | fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, |
225 | tyAttr, mlir::LLVM::DIFlags::Zero); |
226 | declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr)); |
227 | } |
228 | |
229 | mlir::LLVM::DICommonBlockAttr AddDebugInfoPass::getOrCreateCommonBlockAttr( |
230 | llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, |
231 | mlir::LLVM::DIScopeAttr scope, unsigned line) { |
232 | mlir::MLIRContext *context = &getContext(); |
233 | mlir::LLVM::DICommonBlockAttr cbAttr; |
234 | if (auto iter{commonBlockMap.find(name)}; iter != commonBlockMap.end()) { |
235 | cbAttr = iter->getValue(); |
236 | } else { |
237 | cbAttr = mlir::LLVM::DICommonBlockAttr::get( |
238 | context, scope, nullptr, mlir::StringAttr::get(context, name), fileAttr, |
239 | line); |
240 | commonBlockMap[name] = cbAttr; |
241 | } |
242 | return cbAttr; |
243 | } |
244 | |
245 | // The `module` does not have a first class representation in the `FIR`. We |
246 | // extract information about it from the name of the identifiers and keep a |
247 | // map to avoid duplication. |
248 | mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( |
249 | const std::string &name, mlir::LLVM::DIFileAttr fileAttr, |
250 | mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) { |
251 | mlir::MLIRContext *context = &getContext(); |
252 | mlir::LLVM::DIModuleAttr modAttr; |
253 | if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) { |
254 | modAttr = iter->getValue(); |
255 | } else { |
256 | modAttr = mlir::LLVM::DIModuleAttr::get( |
257 | context, fileAttr, scope, mlir::StringAttr::get(context, name), |
258 | /* configMacros */ mlir::StringAttr(), |
259 | /* includePath */ mlir::StringAttr(), |
260 | /* apinotes */ mlir::StringAttr(), line, decl); |
261 | moduleMap[name] = modAttr; |
262 | } |
263 | return modAttr; |
264 | } |
265 | |
266 | /// If globalOp represents a module variable, return a ModuleAttr that |
267 | /// represents that module. |
268 | std::optional<mlir::LLVM::DIModuleAttr> |
269 | AddDebugInfoPass::getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, |
270 | mlir::LLVM::DIFileAttr fileAttr, |
271 | mlir::LLVM::DIScopeAttr scope) { |
272 | mlir::MLIRContext *context = &getContext(); |
273 | mlir::OpBuilder builder(context); |
274 | |
275 | std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); |
276 | // Only look for module if this variable is not part of a function. |
277 | if (!result.second.procs.empty() || result.second.modules.empty()) |
278 | return std::nullopt; |
279 | |
280 | // DWARF5 says following about the fortran modules: |
281 | // A Fortran 90 module may also be represented by a module entry |
282 | // (but no declaration attribute is warranted because Fortran has no concept |
283 | // of a corresponding module body). |
284 | // But in practice, compilers use declaration attribute with a module in cases |
285 | // where module was defined in another source file (only being used in this |
286 | // one). The isInitialized() seems to provide the right information |
287 | // but inverted. It is true where module is actually defined but false where |
288 | // it is used. |
289 | // FIXME: Currently we don't have the line number on which a module was |
290 | // declared. We are using a best guess of line - 1 where line is the source |
291 | // line of the first member of the module that we encounter. |
292 | unsigned line = getLineFromLoc(globalOp.getLoc()); |
293 | |
294 | mlir::LLVM::DISubprogramAttr sp = |
295 | mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope); |
296 | // Modules are generated at compile unit scope |
297 | if (sp) |
298 | scope = sp.getCompileUnit(); |
299 | |
300 | return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope, |
301 | std::max(line - 1, (unsigned)1), |
302 | !globalOp.isInitialized()); |
303 | } |
304 | |
305 | void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp, |
306 | mlir::LLVM::DIFileAttr fileAttr, |
307 | mlir::LLVM::DIScopeAttr scope, |
308 | fir::DebugTypeGenerator &typeGen, |
309 | mlir::SymbolTable *symbolTable, |
310 | fir::cg::XDeclareOp declOp) { |
311 | if (debugInfoIsAlreadySet(globalOp.getLoc())) |
312 | return; |
313 | mlir::MLIRContext *context = &getContext(); |
314 | mlir::OpBuilder builder(context); |
315 | |
316 | std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); |
317 | if (result.first != fir::NameUniquer::NameKind::VARIABLE) |
318 | return; |
319 | |
320 | if (fir::NameUniquer::isSpecialSymbol(result.second.name)) |
321 | return; |
322 | |
323 | unsigned line = getLineFromLoc(globalOp.getLoc()); |
324 | std::optional<mlir::LLVM::DIModuleAttr> modOpt = |
325 | getModuleAttrFromGlobalOp(globalOp, fileAttr, scope); |
326 | if (modOpt) |
327 | scope = *modOpt; |
328 | |
329 | mlir::LLVM::DITypeAttr diType = |
330 | typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp); |
331 | auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( |
332 | context, scope, mlir::StringAttr::get(context, result.second.name), |
333 | mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line, |
334 | diType, /*isLocalToUnit*/ false, |
335 | /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0); |
336 | auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get( |
337 | globalOp.getContext(), gvAttr, nullptr); |
338 | auto arrayAttr = mlir::ArrayAttr::get(context, {dbgExpr}); |
339 | globalOp->setLoc(builder.getFusedLoc({globalOp.getLoc()}, arrayAttr)); |
340 | } |
341 | |
342 | void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, |
343 | mlir::LLVM::DIFileAttr fileAttr, |
344 | mlir::LLVM::DICompileUnitAttr cuAttr, |
345 | fir::DebugTypeGenerator &typeGen, |
346 | mlir::SymbolTable *symbolTable) { |
347 | mlir::Location l = funcOp->getLoc(); |
348 | // If fused location has already been created then nothing to do |
349 | // Otherwise, create a fused location. |
350 | if (debugInfoIsAlreadySet(l)) |
351 | return; |
352 | |
353 | mlir::MLIRContext *context = &getContext(); |
354 | mlir::OpBuilder builder(context); |
355 | llvm::StringRef fileName(fileAttr.getName()); |
356 | llvm::StringRef filePath(fileAttr.getDirectory()); |
357 | unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry()) |
358 | ? llvm::dwarf::getCallingConvention("DW_CC_program" ) |
359 | : llvm::dwarf::getCallingConvention("DW_CC_normal" ); |
360 | |
361 | if (auto funcLoc = mlir::dyn_cast<mlir::FileLineColLoc>(l)) { |
362 | fileName = llvm::sys::path::filename(path: funcLoc.getFilename().getValue()); |
363 | filePath = llvm::sys::path::parent_path(path: funcLoc.getFilename().getValue()); |
364 | } |
365 | |
366 | mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName()); |
367 | mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName()); |
368 | mlir::StringAttr funcName = |
369 | (attr) ? mlir::cast<mlir::StringAttr>(attr) |
370 | : mlir::StringAttr::get(context, funcOp.getName()); |
371 | |
372 | auto result = fir::NameUniquer::deconstruct(funcName); |
373 | funcName = mlir::StringAttr::get(context, result.second.name); |
374 | |
375 | // try to use a better function name than _QQmain for the program statement |
376 | bool isMain = false; |
377 | if (funcName == fir::NameUniquer::doProgramEntry()) { |
378 | isMain = true; |
379 | mlir::StringAttr bindcName = |
380 | funcOp->getAttrOfType<mlir::StringAttr>(fir::getSymbolAttrName()); |
381 | if (bindcName) |
382 | funcName = bindcName; |
383 | } |
384 | |
385 | llvm::SmallVector<mlir::LLVM::DITypeAttr> types; |
386 | for (auto resTy : funcOp.getResultTypes()) { |
387 | auto tyAttr = |
388 | typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr); |
389 | types.push_back(tyAttr); |
390 | } |
391 | // If no return type then add a null type as a place holder for that. |
392 | if (types.empty()) |
393 | types.push_back(mlir::LLVM::DINullTypeAttr::get(context)); |
394 | for (auto inTy : funcOp.getArgumentTypes()) { |
395 | auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr, |
396 | cuAttr, /*declOp=*/nullptr); |
397 | types.push_back(tyAttr); |
398 | } |
399 | |
400 | mlir::LLVM::DISubroutineTypeAttr subTypeAttr = |
401 | mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); |
402 | mlir::LLVM::DIFileAttr funcFileAttr = |
403 | mlir::LLVM::DIFileAttr::get(context, fileName, filePath); |
404 | |
405 | // Only definitions need a distinct identifier and a compilation unit. |
406 | mlir::DistinctAttr id, id2; |
407 | mlir::LLVM::DIScopeAttr Scope = fileAttr; |
408 | mlir::LLVM::DICompileUnitAttr compilationUnit; |
409 | mlir::LLVM::DISubprogramFlags subprogramFlags = |
410 | mlir::LLVM::DISubprogramFlags{}; |
411 | if (isOptimized) |
412 | subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized; |
413 | if (isMain) |
414 | subprogramFlags = |
415 | subprogramFlags | mlir::LLVM::DISubprogramFlags::MainSubprogram; |
416 | if (!funcOp.isExternal()) { |
417 | // Place holder and final function have to have different IDs, otherwise |
418 | // translation code will reject one of them. |
419 | id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); |
420 | id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); |
421 | compilationUnit = cuAttr; |
422 | subprogramFlags = |
423 | subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; |
424 | } |
425 | unsigned line = getLineFromLoc(l); |
426 | if (fir::isInternalProcedure(funcOp)) { |
427 | // For contained functions, the scope is the parent subroutine. |
428 | mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>( |
429 | funcOp->getAttr(fir::getHostSymbolAttrName())); |
430 | if (sym) { |
431 | if (auto func = |
432 | symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) { |
433 | // Make sure that parent is processed. |
434 | handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable); |
435 | if (auto fusedLoc = |
436 | mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) { |
437 | if (auto spAttr = |
438 | mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>( |
439 | fusedLoc.getMetadata())) |
440 | Scope = spAttr; |
441 | } |
442 | } |
443 | } |
444 | } else if (!result.second.modules.empty()) { |
445 | Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr, |
446 | line - 1, false); |
447 | } |
448 | |
449 | // Don't process variables if user asked for line tables only. |
450 | if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly) { |
451 | auto spAttr = mlir::LLVM::DISubprogramAttr::get( |
452 | context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, |
453 | line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}, |
454 | /*annotations=*/{}); |
455 | funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); |
456 | return; |
457 | } |
458 | |
459 | mlir::DistinctAttr recId = |
460 | mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); |
461 | |
462 | // The debug attribute in MLIR are readonly once created. But in case of |
463 | // imported entities, we have a circular dependency. The |
464 | // DIImportedEntityAttr requires scope information (DISubprogramAttr in this |
465 | // case) and DISubprogramAttr requires the list of imported entities. The |
466 | // MLIR provides a way where a DISubprogramAttr an be created with a certain |
467 | // recID and be used in places like DIImportedEntityAttr. After that another |
468 | // DISubprogramAttr can be created with same recID but with list of entities |
469 | // now available. The MLIR translation code takes care of updating the |
470 | // references. Note that references will be updated only in the things that |
471 | // are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to |
472 | // create the final DISubprogramAttr before we process local variables. |
473 | // Look at DIRecursiveTypeAttrInterface for more details. |
474 | |
475 | auto spAttr = mlir::LLVM::DISubprogramAttr::get( |
476 | context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, funcName, |
477 | fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, |
478 | /*retainedNodes=*/{}, /*annotations=*/{}); |
479 | |
480 | // There is no direct information in the IR for any 'use' statement in the |
481 | // function. We have to extract that information from the DeclareOp. We do |
482 | // a pass on the DeclareOp and generate ModuleAttr and corresponding |
483 | // DIImportedEntityAttr for that module. |
484 | // FIXME: As we are depending on the variables to see which module is being |
485 | // 'used' in the function, there are certain limitations. |
486 | // For things like 'use mod1, only: v1', whole module will be brought into the |
487 | // namespace in the debug info. It is not a problem as such unless there is a |
488 | // clash of names. |
489 | // There is no information about module variable renaming |
490 | llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules; |
491 | funcOp.walk([&](fir::cg::XDeclareOp declOp) { |
492 | if (&funcOp.front() == declOp->getBlock()) |
493 | if (auto global = |
494 | symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) { |
495 | std::optional<mlir::LLVM::DIModuleAttr> modOpt = |
496 | getModuleAttrFromGlobalOp(global, fileAttr, cuAttr); |
497 | if (modOpt) { |
498 | auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get( |
499 | context, llvm::dwarf::DW_TAG_imported_module, spAttr, *modOpt, |
500 | fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {}); |
501 | importedModules.insert(importedEntity); |
502 | } |
503 | } |
504 | }); |
505 | llvm::SmallVector<mlir::LLVM::DINodeAttr> entities(importedModules.begin(), |
506 | importedModules.end()); |
507 | // We have the imported entities now. Generate the final DISubprogramAttr. |
508 | spAttr = mlir::LLVM::DISubprogramAttr::get( |
509 | context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope, |
510 | funcName, fullName, funcFileAttr, line, line, subprogramFlags, |
511 | subTypeAttr, entities, /*annotations=*/{}); |
512 | funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); |
513 | |
514 | funcOp.walk([&](fir::cg::XDeclareOp declOp) { |
515 | handleDeclareOp(declOp, fileAttr, spAttr, typeGen, symbolTable); |
516 | }); |
517 | // commonBlockMap ensures that we don't create multiple DICommonBlockAttr of |
518 | // the same name in one function. But it is ok (rather required) to create |
519 | // them in different functions if common block of the same name has been used |
520 | // there. |
521 | commonBlockMap.clear(); |
522 | } |
523 | |
524 | void AddDebugInfoPass::runOnOperation() { |
525 | mlir::ModuleOp module = getOperation(); |
526 | mlir::MLIRContext *context = &getContext(); |
527 | mlir::SymbolTable symbolTable(module); |
528 | llvm::StringRef fileName; |
529 | std::string filePath; |
530 | std::optional<mlir::DataLayout> dl = |
531 | fir::support::getOrSetMLIRDataLayout(module, /*allowDefaultLayout=*/true); |
532 | if (!dl) { |
533 | mlir::emitError(module.getLoc(), "Missing data layout attribute in module" ); |
534 | signalPassFailure(); |
535 | return; |
536 | } |
537 | fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl); |
538 | // We need 2 type of file paths here. |
539 | // 1. Name of the file as was presented to compiler. This can be absolute |
540 | // or relative to 2. |
541 | // 2. Current working directory |
542 | // |
543 | // We are also dealing with 2 different situations below. One is normal |
544 | // compilation where we will have a value in 'inputFilename' and we can |
545 | // obtain the current directory using 'current_path'. |
546 | // The 2nd case is when this pass is invoked directly from 'fir-opt' tool. |
547 | // In that case, 'inputFilename' may be empty. Location embedded in the |
548 | // module will be used to get file name and its directory. |
549 | if (inputFilename.empty()) { |
550 | if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) { |
551 | fileName = llvm::sys::path::filename(path: fileLoc.getFilename().getValue()); |
552 | filePath = llvm::sys::path::parent_path(path: fileLoc.getFilename().getValue()); |
553 | } else |
554 | fileName = "-" ; |
555 | } else { |
556 | fileName = inputFilename; |
557 | llvm::SmallString<256> cwd; |
558 | if (!llvm::sys::fs::current_path(result&: cwd)) |
559 | filePath = cwd.str(); |
560 | } |
561 | |
562 | mlir::LLVM::DIFileAttr fileAttr = |
563 | mlir::LLVM::DIFileAttr::get(context, fileName, filePath); |
564 | mlir::StringAttr producer = |
565 | mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion()); |
566 | mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get( |
567 | mlir::DistinctAttr::create(mlir::UnitAttr::get(context)), |
568 | llvm::dwarf::getLanguage("DW_LANG_Fortran95" ), fileAttr, producer, |
569 | isOptimized, debugLevel); |
570 | |
571 | module.walk([&](mlir::func::FuncOp funcOp) { |
572 | handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable); |
573 | }); |
574 | mlir::OpBuilder builder(context); |
575 | // We have processed all function. Attach common block variables to the |
576 | // global that represent the storage. |
577 | for (auto [global, exprs] : globalToGlobalExprsMap) { |
578 | auto arrayAttr = mlir::ArrayAttr::get(context, exprs); |
579 | global->setLoc(builder.getFusedLoc({global.getLoc()}, arrayAttr)); |
580 | } |
581 | // Process any global which was not processed through DeclareOp. |
582 | if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { |
583 | // Process 'GlobalOp' only if full debug info is requested. |
584 | for (auto globalOp : module.getOps<fir::GlobalOp>()) |
585 | handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable, |
586 | /*declOp=*/nullptr); |
587 | } |
588 | } |
589 | |
590 | std::unique_ptr<mlir::Pass> |
591 | fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) { |
592 | return std::make_unique<AddDebugInfoPass>(options); |
593 | } |
594 | |