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