xref: /llvm-project/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp (revision db64e69fa250ea3a8d7a761220a7922fbdad0f2c)
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/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 "mlir/Dialect/Func/IR/FuncOps.h"
26 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
27 #include "mlir/IR/Matchers.h"
28 #include "mlir/IR/TypeUtilities.h"
29 #include "mlir/Pass/Pass.h"
30 #include "mlir/Transforms/DialectConversion.h"
31 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
32 #include "mlir/Transforms/RegionUtils.h"
33 #include "llvm/BinaryFormat/Dwarf.h"
34 #include "llvm/Support/Debug.h"
35 #include "llvm/Support/FileSystem.h"
36 #include "llvm/Support/Path.h"
37 #include "llvm/Support/raw_ostream.h"
38 
39 namespace fir {
40 #define GEN_PASS_DEF_ADDDEBUGINFO
41 #include "flang/Optimizer/Transforms/Passes.h.inc"
42 } // namespace fir
43 
44 #define DEBUG_TYPE "flang-add-debug-info"
45 
46 namespace {
47 
48 class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
49   void handleDeclareOp(fir::cg::XDeclareOp declOp,
50                        mlir::LLVM::DIFileAttr fileAttr,
51                        mlir::LLVM::DIScopeAttr scopeAttr,
52                        fir::DebugTypeGenerator &typeGen,
53                        mlir::SymbolTable *symbolTable);
54 
55 public:
56   AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {}
57   void runOnOperation() override;
58 
59 private:
60   llvm::StringMap<mlir::LLVM::DIModuleAttr> moduleMap;
61 
62   mlir::LLVM::DIModuleAttr getOrCreateModuleAttr(
63       const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
64       mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl);
65 
66   void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr,
67                       mlir::LLVM::DIScopeAttr scope,
68                       fir::DebugTypeGenerator &typeGen,
69                       mlir::SymbolTable *symbolTable,
70                       fir::cg::XDeclareOp declOp);
71   void handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr fileAttr,
72                     mlir::LLVM::DICompileUnitAttr cuAttr,
73                     fir::DebugTypeGenerator &typeGen,
74                     mlir::SymbolTable *symbolTable);
75   std::optional<mlir::LLVM::DIModuleAttr>
76   getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
77                             mlir::LLVM::DIFileAttr fileAttr,
78                             mlir::LLVM::DIScopeAttr scope);
79 };
80 
81 bool debugInfoIsAlreadySet(mlir::Location loc) {
82   if (mlir::isa<mlir::FusedLoc>(loc)) {
83     if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>())
84       return false;
85     return true;
86   }
87   return false;
88 }
89 
90 } // namespace
91 
92 void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
93                                        mlir::LLVM::DIFileAttr fileAttr,
94                                        mlir::LLVM::DIScopeAttr scopeAttr,
95                                        fir::DebugTypeGenerator &typeGen,
96                                        mlir::SymbolTable *symbolTable) {
97   mlir::MLIRContext *context = &getContext();
98   mlir::OpBuilder builder(context);
99   auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
100 
101   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
102     return;
103   // If this DeclareOp actually represents a global then treat it as such.
104   if (auto global = symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) {
105     handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp);
106     return;
107   }
108 
109   // Only accept local variables.
110   if (result.second.procs.empty())
111     return;
112 
113   // FIXME: There may be cases where an argument is processed a bit before
114   // DeclareOp is generated. In that case, DeclareOp may point to an
115   // intermediate op and not to BlockArgument.
116   // Moreover, with MLIR inlining we cannot use the BlockArgument
117   // position to identify the original number of the dummy argument.
118   // If we want to keep running AddDebugInfoPass late, the dummy argument
119   // position in the argument list has to be expressed in FIR (e.g. as a
120   // constant attribute of [hl]fir.declare/fircg.ext_declare operation that has
121   // a dummy_scope operand).
122   unsigned argNo = 0;
123   if (fir::isDummyArgument(declOp.getMemref())) {
124     auto arg = llvm::cast<mlir::BlockArgument>(declOp.getMemref());
125     argNo = arg.getArgNumber() + 1;
126   }
127 
128   auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()),
129                                     fileAttr, scopeAttr, declOp);
130 
131   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
132       context, scopeAttr, mlir::StringAttr::get(context, result.second.name),
133       fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0,
134       tyAttr, mlir::LLVM::DIFlags::Zero);
135   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
136 }
137 
138 // The `module` does not have a first class representation in the `FIR`. We
139 // extract information about it from the name of the identifiers and keep a
140 // map to avoid duplication.
141 mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr(
142     const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
143     mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) {
144   mlir::MLIRContext *context = &getContext();
145   mlir::LLVM::DIModuleAttr modAttr;
146   if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) {
147     modAttr = iter->getValue();
148   } else {
149     modAttr = mlir::LLVM::DIModuleAttr::get(
150         context, fileAttr, scope, mlir::StringAttr::get(context, name),
151         /* configMacros */ mlir::StringAttr(),
152         /* includePath */ mlir::StringAttr(),
153         /* apinotes */ mlir::StringAttr(), line, decl);
154     moduleMap[name] = modAttr;
155   }
156   return modAttr;
157 }
158 
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 
198 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
199                                       mlir::LLVM::DIFileAttr fileAttr,
200                                       mlir::LLVM::DIScopeAttr scope,
201                                       fir::DebugTypeGenerator &typeGen,
202                                       mlir::SymbolTable *symbolTable,
203                                       fir::cg::XDeclareOp declOp) {
204   if (debugInfoIsAlreadySet(globalOp.getLoc()))
205     return;
206   mlir::MLIRContext *context = &getContext();
207   mlir::OpBuilder builder(context);
208 
209   std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
210   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
211     return;
212 
213   // Discard entries that describe a derived type. Usually start with '.c.',
214   // '.dt.' or '.n.'. It would be better if result of the deconstruct had a flag
215   // for such values so that we dont have to look at string values.
216   if (!result.second.name.empty() && result.second.name[0] == '.')
217     return;
218 
219   unsigned line = getLineFromLoc(globalOp.getLoc());
220   std::optional<mlir::LLVM::DIModuleAttr> modOpt =
221       getModuleAttrFromGlobalOp(globalOp, fileAttr, scope);
222   if (modOpt)
223     scope = *modOpt;
224 
225   mlir::LLVM::DITypeAttr diType =
226       typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
227   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
228       context, scope, mlir::StringAttr::get(context, result.second.name),
229       mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line,
230       diType, /*isLocalToUnit*/ false,
231       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
232   globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr));
233 }
234 
235 void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
236                                     mlir::LLVM::DIFileAttr fileAttr,
237                                     mlir::LLVM::DICompileUnitAttr cuAttr,
238                                     fir::DebugTypeGenerator &typeGen,
239                                     mlir::SymbolTable *symbolTable) {
240   mlir::Location l = funcOp->getLoc();
241   // If fused location has already been created then nothing to do
242   // Otherwise, create a fused location.
243   if (debugInfoIsAlreadySet(l))
244     return;
245 
246   mlir::MLIRContext *context = &getContext();
247   mlir::OpBuilder builder(context);
248   llvm::StringRef fileName(fileAttr.getName());
249   llvm::StringRef filePath(fileAttr.getDirectory());
250   unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry())
251                         ? llvm::dwarf::getCallingConvention("DW_CC_program")
252                         : llvm::dwarf::getCallingConvention("DW_CC_normal");
253 
254   if (auto funcLoc = mlir::dyn_cast<mlir::FileLineColLoc>(l)) {
255     fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue());
256     filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue());
257   }
258 
259   mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName());
260   mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName());
261   mlir::StringAttr funcName =
262       (attr) ? mlir::cast<mlir::StringAttr>(attr)
263              : mlir::StringAttr::get(context, funcOp.getName());
264 
265   auto result = fir::NameUniquer::deconstruct(funcName);
266   funcName = mlir::StringAttr::get(context, result.second.name);
267 
268   llvm::SmallVector<mlir::LLVM::DITypeAttr> types;
269   for (auto resTy : funcOp.getResultTypes()) {
270     auto tyAttr =
271         typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr);
272     types.push_back(tyAttr);
273   }
274   for (auto inTy : funcOp.getArgumentTypes()) {
275     auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr,
276                                       cuAttr, /*declOp=*/nullptr);
277     types.push_back(tyAttr);
278   }
279 
280   mlir::LLVM::DISubroutineTypeAttr subTypeAttr =
281       mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
282   mlir::LLVM::DIFileAttr funcFileAttr =
283       mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
284 
285   // Only definitions need a distinct identifier and a compilation unit.
286   mlir::DistinctAttr id, id2;
287   mlir::LLVM::DIScopeAttr Scope = fileAttr;
288   mlir::LLVM::DICompileUnitAttr compilationUnit;
289   mlir::LLVM::DISubprogramFlags subprogramFlags =
290       mlir::LLVM::DISubprogramFlags{};
291   if (isOptimized)
292     subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized;
293   if (!funcOp.isExternal()) {
294     // Place holder and final function have to have different IDs, otherwise
295     // translation code will reject one of them.
296     id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
297     id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
298     compilationUnit = cuAttr;
299     subprogramFlags =
300         subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition;
301   }
302   unsigned line = getLineFromLoc(l);
303   if (fir::isInternalProcedure(funcOp)) {
304     // For contained functions, the scope is the parent subroutine.
305     mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>(
306         funcOp->getAttr(fir::getHostSymbolAttrName()));
307     if (sym) {
308       if (auto func =
309               symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) {
310         // Make sure that parent is processed.
311         handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable);
312         if (auto fusedLoc =
313                 mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) {
314           if (auto spAttr =
315                   mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(
316                       fusedLoc.getMetadata()))
317             Scope = spAttr;
318         }
319       }
320     }
321   } else if (!result.second.modules.empty()) {
322     Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr,
323                                   line - 1, false);
324   }
325 
326   // Don't process variables if user asked for line tables only.
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));
332     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));
389 
390   funcOp.walk([&](fir::cg::XDeclareOp declOp) {
391     // FIXME: We currently dont handle variables that are not in the entry
392     // blocks of the fuctions. These may be variable or arguments used in the
393     // OpenMP target regions.
394     if (&funcOp.front() == declOp->getBlock())
395       handleDeclareOp(declOp, fileAttr, spAttr, typeGen, symbolTable);
396   });
397 }
398 
399 void AddDebugInfoPass::runOnOperation() {
400   mlir::ModuleOp module = getOperation();
401   mlir::MLIRContext *context = &getContext();
402   mlir::SymbolTable symbolTable(module);
403   llvm::StringRef fileName;
404   std::string filePath;
405   std::optional<mlir::DataLayout> dl =
406       fir::support::getOrSetDataLayout(module, /*allowDefaultLayout=*/true);
407   if (!dl) {
408     mlir::emitError(module.getLoc(), "Missing data layout attribute in module");
409     signalPassFailure();
410     return;
411   }
412   fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl);
413   // We need 2 type of file paths here.
414   // 1. Name of the file as was presented to compiler. This can be absolute
415   // or relative to 2.
416   // 2. Current working directory
417   //
418   // We are also dealing with 2 different situations below. One is normal
419   // compilation where we will have a value in 'inputFilename' and we can
420   // obtain the current directory using 'current_path'.
421   // The 2nd case is when this pass is invoked directly from 'fir-opt' tool.
422   // In that case, 'inputFilename' may be empty. Location embedded in the
423   // module will be used to get file name and its directory.
424   if (inputFilename.empty()) {
425     if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) {
426       fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue());
427       filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue());
428     } else
429       fileName = "-";
430   } else {
431     fileName = inputFilename;
432     llvm::SmallString<256> cwd;
433     if (!llvm::sys::fs::current_path(cwd))
434       filePath = cwd.str();
435   }
436 
437   mlir::LLVM::DIFileAttr fileAttr =
438       mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
439   mlir::StringAttr producer =
440       mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion());
441   mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get(
442       mlir::DistinctAttr::create(mlir::UnitAttr::get(context)),
443       llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer,
444       isOptimized, debugLevel);
445 
446   module.walk([&](mlir::func::FuncOp funcOp) {
447     handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable);
448   });
449   // Process any global which was not processed through DeclareOp.
450   if (debugLevel == mlir::LLVM::DIEmissionKind::Full) {
451     // Process 'GlobalOp' only if full debug info is requested.
452     for (auto globalOp : module.getOps<fir::GlobalOp>())
453       handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable,
454                      /*declOp=*/nullptr);
455   }
456 }
457 
458 std::unique_ptr<mlir::Pass>
459 fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) {
460   return std::make_unique<AddDebugInfoPass>(options);
461 }
462