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