xref: /llvm-project/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp (revision bc4bedd5ed2c8d9f1a648c72d38e17361aff8a30)
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 };
76 
77 bool debugInfoIsAlreadySet(mlir::Location loc) {
78   if (mlir::isa<mlir::FusedLoc>(loc)) {
79     if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>())
80       return false;
81     return true;
82   }
83   return false;
84 }
85 
86 } // namespace
87 
88 void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
89                                        mlir::LLVM::DIFileAttr fileAttr,
90                                        mlir::LLVM::DIScopeAttr scopeAttr,
91                                        fir::DebugTypeGenerator &typeGen,
92                                        mlir::SymbolTable *symbolTable) {
93   mlir::MLIRContext *context = &getContext();
94   mlir::OpBuilder builder(context);
95   auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
96 
97   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
98     return;
99   // If this DeclareOp actually represents a global then treat it as such.
100   if (auto global = symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) {
101     handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp);
102     return;
103   }
104 
105   // Only accept local variables.
106   if (result.second.procs.empty())
107     return;
108 
109   // FIXME: There may be cases where an argument is processed a bit before
110   // DeclareOp is generated. In that case, DeclareOp may point to an
111   // intermediate op and not to BlockArgument.
112   // Moreover, with MLIR inlining we cannot use the BlockArgument
113   // position to identify the original number of the dummy argument.
114   // If we want to keep running AddDebugInfoPass late, the dummy argument
115   // position in the argument list has to be expressed in FIR (e.g. as a
116   // constant attribute of [hl]fir.declare/fircg.ext_declare operation that has
117   // a dummy_scope operand).
118   unsigned argNo = 0;
119   if (fir::isDummyArgument(declOp.getMemref())) {
120     auto arg = llvm::cast<mlir::BlockArgument>(declOp.getMemref());
121     argNo = arg.getArgNumber() + 1;
122   }
123 
124   auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()),
125                                     fileAttr, scopeAttr, declOp);
126 
127   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
128       context, scopeAttr, mlir::StringAttr::get(context, result.second.name),
129       fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0,
130       tyAttr, mlir::LLVM::DIFlags::Zero);
131   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
132 }
133 
134 // The `module` does not have a first class representation in the `FIR`. We
135 // extract information about it from the name of the identifiers and keep a
136 // map to avoid duplication.
137 mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr(
138     const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
139     mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) {
140   mlir::MLIRContext *context = &getContext();
141   mlir::LLVM::DIModuleAttr modAttr;
142   if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) {
143     modAttr = iter->getValue();
144   } else {
145     modAttr = mlir::LLVM::DIModuleAttr::get(
146         context, fileAttr, scope, mlir::StringAttr::get(context, name),
147         /* configMacros */ mlir::StringAttr(),
148         /* includePath */ mlir::StringAttr(),
149         /* apinotes */ mlir::StringAttr(), line, decl);
150     moduleMap[name] = modAttr;
151   }
152   return modAttr;
153 }
154 
155 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
156                                       mlir::LLVM::DIFileAttr fileAttr,
157                                       mlir::LLVM::DIScopeAttr scope,
158                                       fir::DebugTypeGenerator &typeGen,
159                                       mlir::SymbolTable *symbolTable,
160                                       fir::cg::XDeclareOp declOp) {
161   if (debugInfoIsAlreadySet(globalOp.getLoc()))
162     return;
163   mlir::MLIRContext *context = &getContext();
164   mlir::OpBuilder builder(context);
165 
166   std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
167   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
168     return;
169 
170   // Discard entries that describe a derived type. Usually start with '.c.',
171   // '.dt.' or '.n.'. It would be better if result of the deconstruct had a flag
172   // for such values so that we dont have to look at string values.
173   if (!result.second.name.empty() && result.second.name[0] == '.')
174     return;
175 
176   unsigned line = getLineFromLoc(globalOp.getLoc());
177 
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   }
204   mlir::LLVM::DITypeAttr diType =
205       typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp);
206   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
207       context, scope, mlir::StringAttr::get(context, result.second.name),
208       mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line,
209       diType, /*isLocalToUnit*/ false,
210       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
211   globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr));
212 }
213 
214 void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
215                                     mlir::LLVM::DIFileAttr fileAttr,
216                                     mlir::LLVM::DICompileUnitAttr cuAttr,
217                                     fir::DebugTypeGenerator &typeGen,
218                                     mlir::SymbolTable *symbolTable) {
219   mlir::Location l = funcOp->getLoc();
220   // If fused location has already been created then nothing to do
221   // Otherwise, create a fused location.
222   if (debugInfoIsAlreadySet(l))
223     return;
224 
225   mlir::MLIRContext *context = &getContext();
226   mlir::OpBuilder builder(context);
227   llvm::StringRef fileName(fileAttr.getName());
228   llvm::StringRef filePath(fileAttr.getDirectory());
229   unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry())
230                         ? llvm::dwarf::getCallingConvention("DW_CC_program")
231                         : llvm::dwarf::getCallingConvention("DW_CC_normal");
232 
233   if (auto funcLoc = mlir::dyn_cast<mlir::FileLineColLoc>(l)) {
234     fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue());
235     filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue());
236   }
237 
238   mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName());
239   mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName());
240   mlir::StringAttr funcName =
241       (attr) ? mlir::cast<mlir::StringAttr>(attr)
242              : mlir::StringAttr::get(context, funcOp.getName());
243 
244   auto result = fir::NameUniquer::deconstruct(funcName);
245   funcName = mlir::StringAttr::get(context, result.second.name);
246 
247   llvm::SmallVector<mlir::LLVM::DITypeAttr> types;
248   for (auto resTy : funcOp.getResultTypes()) {
249     auto tyAttr =
250         typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr);
251     types.push_back(tyAttr);
252   }
253   for (auto inTy : funcOp.getArgumentTypes()) {
254     auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr,
255                                       cuAttr, /*declOp=*/nullptr);
256     types.push_back(tyAttr);
257   }
258 
259   mlir::LLVM::DISubroutineTypeAttr subTypeAttr =
260       mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
261   mlir::LLVM::DIFileAttr funcFileAttr =
262       mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
263 
264   // Only definitions need a distinct identifier and a compilation unit.
265   mlir::DistinctAttr id;
266   mlir::LLVM::DIScopeAttr Scope = fileAttr;
267   mlir::LLVM::DICompileUnitAttr compilationUnit;
268   mlir::LLVM::DISubprogramFlags subprogramFlags =
269       mlir::LLVM::DISubprogramFlags{};
270   if (isOptimized)
271     subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized;
272   if (!funcOp.isExternal()) {
273     id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
274     compilationUnit = cuAttr;
275     subprogramFlags =
276         subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition;
277   }
278   unsigned line = getLineFromLoc(l);
279   if (fir::isInternalProcedure(funcOp)) {
280     // For contained functions, the scope is the parent subroutine.
281     mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>(
282         funcOp->getAttr(fir::getHostSymbolAttrName()));
283     if (sym) {
284       if (auto func =
285               symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) {
286         // Make sure that parent is processed.
287         handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable);
288         if (auto fusedLoc =
289                 mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) {
290           if (auto spAttr =
291                   mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(
292                       fusedLoc.getMetadata()))
293             Scope = spAttr;
294         }
295       }
296     }
297   } else if (!result.second.modules.empty()) {
298     Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr,
299                                   line - 1, false);
300   }
301 
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 
307   // Don't process variables if user asked for line tables only.
308   if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly)
309     return;
310 
311   funcOp.walk([&](fir::cg::XDeclareOp declOp) {
312     handleDeclareOp(declOp, fileAttr, spAttr, typeGen, symbolTable);
313   });
314 }
315 
316 void AddDebugInfoPass::runOnOperation() {
317   mlir::ModuleOp module = getOperation();
318   mlir::MLIRContext *context = &getContext();
319   mlir::SymbolTable symbolTable(module);
320   llvm::StringRef fileName;
321   std::string filePath;
322   std::optional<mlir::DataLayout> dl =
323       fir::support::getOrSetDataLayout(module, /*allowDefaultLayout=*/true);
324   if (!dl) {
325     mlir::emitError(module.getLoc(), "Missing data layout attribute in module");
326     signalPassFailure();
327     return;
328   }
329   fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl);
330   // We need 2 type of file paths here.
331   // 1. Name of the file as was presented to compiler. This can be absolute
332   // or relative to 2.
333   // 2. Current working directory
334   //
335   // We are also dealing with 2 different situations below. One is normal
336   // compilation where we will have a value in 'inputFilename' and we can
337   // obtain the current directory using 'current_path'.
338   // The 2nd case is when this pass is invoked directly from 'fir-opt' tool.
339   // In that case, 'inputFilename' may be empty. Location embedded in the
340   // module will be used to get file name and its directory.
341   if (inputFilename.empty()) {
342     if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) {
343       fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue());
344       filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue());
345     } else
346       fileName = "-";
347   } else {
348     fileName = inputFilename;
349     llvm::SmallString<256> cwd;
350     if (!llvm::sys::fs::current_path(cwd))
351       filePath = cwd.str();
352   }
353 
354   mlir::LLVM::DIFileAttr fileAttr =
355       mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
356   mlir::StringAttr producer =
357       mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion());
358   mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get(
359       mlir::DistinctAttr::create(mlir::UnitAttr::get(context)),
360       llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer,
361       isOptimized, debugLevel);
362 
363   module.walk([&](mlir::func::FuncOp funcOp) {
364     handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable);
365   });
366   // Process any global which was not processed through DeclareOp.
367   if (debugLevel == mlir::LLVM::DIEmissionKind::Full) {
368     // Process 'GlobalOp' only if full debug info is requested.
369     for (auto globalOp : module.getOps<fir::GlobalOp>())
370       handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable,
371                      /*declOp=*/nullptr);
372   }
373 }
374 
375 std::unique_ptr<mlir::Pass>
376 fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) {
377   return std::make_unique<AddDebugInfoPass>(options);
378 }
379