xref: /llvm-project/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp (revision 5bfc444524d74b714b9efb2dc00c7bc36a3838e2)
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 
54 public:
55   AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {}
56   void runOnOperation() override;
57 
58 private:
59   llvm::StringMap<mlir::LLVM::DIModuleAttr> moduleMap;
60 
61   mlir::LLVM::DIModuleAttr getOrCreateModuleAttr(
62       const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
63       mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl);
64 
65   void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr,
66                       mlir::LLVM::DIScopeAttr scope);
67 };
68 
69 static uint32_t getLineFromLoc(mlir::Location loc) {
70   uint32_t line = 1;
71   if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(loc))
72     line = fileLoc.getLine();
73   return line;
74 }
75 
76 } // namespace
77 
78 void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp,
79                                        mlir::LLVM::DIFileAttr fileAttr,
80                                        mlir::LLVM::DIScopeAttr scopeAttr,
81                                        fir::DebugTypeGenerator &typeGen) {
82   mlir::MLIRContext *context = &getContext();
83   mlir::OpBuilder builder(context);
84   auto result = fir::NameUniquer::deconstruct(declOp.getUniqName());
85 
86   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
87     return;
88 
89   // Only accept local variables.
90   if (result.second.procs.empty())
91     return;
92 
93   // FIXME: There may be cases where an argument is processed a bit before
94   // DeclareOp is generated. In that case, DeclareOp may point to an
95   // intermediate op and not to BlockArgument. We need to find those cases and
96   // walk the chain to get to the actual argument.
97   unsigned argNo = 0;
98   if (fir::isDummyArgument(declOp.getMemref())) {
99     auto arg = llvm::cast<mlir::BlockArgument>(declOp.getMemref());
100     argNo = arg.getArgNumber() + 1;
101   }
102 
103   auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()),
104                                     fileAttr, scopeAttr, declOp.getLoc());
105 
106   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
107       context, scopeAttr, mlir::StringAttr::get(context, result.second.name),
108       fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0,
109       tyAttr);
110   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
111 }
112 
113 // The `module` does not have a first class representation in the `FIR`. We
114 // extract information about it from the name of the identifiers and keep a
115 // map to avoid duplication.
116 mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr(
117     const std::string &name, mlir::LLVM::DIFileAttr fileAttr,
118     mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) {
119   mlir::MLIRContext *context = &getContext();
120   mlir::LLVM::DIModuleAttr modAttr;
121   if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) {
122     modAttr = iter->getValue();
123   } else {
124     modAttr = mlir::LLVM::DIModuleAttr::get(
125         context, fileAttr, scope, mlir::StringAttr::get(context, name),
126         /* configMacros */ mlir::StringAttr(),
127         /* includePath */ mlir::StringAttr(),
128         /* apinotes */ mlir::StringAttr(), line, decl);
129     moduleMap[name] = modAttr;
130   }
131   return modAttr;
132 }
133 
134 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp,
135                                       mlir::LLVM::DIFileAttr fileAttr,
136                                       mlir::LLVM::DIScopeAttr scope) {
137   mlir::ModuleOp module = getOperation();
138   mlir::MLIRContext *context = &getContext();
139   fir::DebugTypeGenerator typeGen(module);
140   mlir::OpBuilder builder(context);
141 
142   std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName());
143   if (result.first != fir::NameUniquer::NameKind::VARIABLE)
144     return;
145 
146   unsigned line = getLineFromLoc(globalOp.getLoc());
147 
148   // DWARF5 says following about the fortran modules:
149   // A Fortran 90 module may also be represented by a module entry
150   // (but no declaration attribute is warranted because Fortran has no concept
151   // of a corresponding module body).
152   // But in practice, compilers use declaration attribute with a module in cases
153   // where module was defined in another source file (only being used in this
154   // one). The isInitialized() seems to provide the right information
155   // but inverted. It is true where module is actually defined but false where
156   // it is used.
157   // FIXME: Currently we don't have the line number on which a module was
158   // declared. We are using a best guess of line - 1 where line is the source
159   // line of the first member of the module that we encounter.
160 
161   if (result.second.modules.empty())
162     return;
163 
164   scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope,
165                                 line - 1, !globalOp.isInitialized());
166 
167   mlir::LLVM::DITypeAttr diType = typeGen.convertType(
168       globalOp.getType(), fileAttr, scope, globalOp.getLoc());
169   auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get(
170       context, scope, mlir::StringAttr::get(context, result.second.name),
171       mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line,
172       diType, /*isLocalToUnit*/ false,
173       /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0);
174   globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr));
175 }
176 
177 void AddDebugInfoPass::runOnOperation() {
178   mlir::ModuleOp module = getOperation();
179   mlir::MLIRContext *context = &getContext();
180   mlir::OpBuilder builder(context);
181   llvm::StringRef fileName;
182   std::string filePath;
183   // We need 2 type of file paths here.
184   // 1. Name of the file as was presented to compiler. This can be absolute
185   // or relative to 2.
186   // 2. Current working directory
187   //
188   // We are also dealing with 2 different situations below. One is normal
189   // compilation where we will have a value in 'inputFilename' and we can
190   // obtain the current directory using 'current_path'.
191   // The 2nd case is when this pass is invoked directly from 'fir-opt' tool.
192   // In that case, 'inputFilename' may be empty. Location embedded in the
193   // module will be used to get file name and its directory.
194   if (inputFilename.empty()) {
195     if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) {
196       fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue());
197       filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue());
198     } else
199       fileName = "-";
200   } else {
201     fileName = inputFilename;
202     llvm::SmallString<256> cwd;
203     if (!llvm::sys::fs::current_path(cwd))
204       filePath = cwd.str();
205   }
206 
207   mlir::LLVM::DIFileAttr fileAttr =
208       mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
209   mlir::StringAttr producer =
210       mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion());
211   mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get(
212       mlir::DistinctAttr::create(mlir::UnitAttr::get(context)),
213       llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer,
214       isOptimized, debugLevel);
215 
216   if (debugLevel == mlir::LLVM::DIEmissionKind::Full) {
217     // Process 'GlobalOp' only if full debug info is requested.
218     for (auto globalOp : module.getOps<fir::GlobalOp>())
219       handleGlobalOp(globalOp, fileAttr, cuAttr);
220   }
221 
222   module.walk([&](mlir::func::FuncOp funcOp) {
223     mlir::Location l = funcOp->getLoc();
224     // If fused location has already been created then nothing to do
225     // Otherwise, create a fused location.
226     if (mlir::dyn_cast<mlir::FusedLoc>(l))
227       return;
228 
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 =
239         mlir::StringAttr::get(context, funcOp.getName());
240     auto result = fir::NameUniquer::deconstruct(funcOp.getName());
241     mlir::StringAttr funcName =
242         mlir::StringAttr::get(context, result.second.name);
243 
244     llvm::SmallVector<mlir::LLVM::DITypeAttr> types;
245     fir::DebugTypeGenerator typeGen(module);
246     for (auto resTy : funcOp.getResultTypes()) {
247       auto tyAttr =
248           typeGen.convertType(resTy, fileAttr, cuAttr, funcOp.getLoc());
249       types.push_back(tyAttr);
250     }
251     for (auto inTy : funcOp.getArgumentTypes()) {
252       auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr,
253                                         cuAttr, funcOp.getLoc());
254       types.push_back(tyAttr);
255     }
256 
257     mlir::LLVM::DISubroutineTypeAttr subTypeAttr =
258         mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types);
259     mlir::LLVM::DIFileAttr funcFileAttr =
260         mlir::LLVM::DIFileAttr::get(context, fileName, filePath);
261 
262     // Only definitions need a distinct identifier and a compilation unit.
263     mlir::DistinctAttr id;
264     mlir::LLVM::DIScopeAttr Scope = fileAttr;
265     mlir::LLVM::DICompileUnitAttr compilationUnit;
266     mlir::LLVM::DISubprogramFlags subprogramFlags =
267         mlir::LLVM::DISubprogramFlags{};
268     if (isOptimized)
269       subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized;
270     if (!funcOp.isExternal()) {
271       id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context));
272       compilationUnit = cuAttr;
273       subprogramFlags =
274           subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition;
275     }
276     unsigned line = getLineFromLoc(l);
277     if (!result.second.modules.empty())
278       Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr,
279                                     line - 1, false);
280 
281     auto spAttr = mlir::LLVM::DISubprogramAttr::get(
282         context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr,
283         line, line, subprogramFlags, subTypeAttr);
284     funcOp->setLoc(builder.getFusedLoc({funcOp->getLoc()}, spAttr));
285 
286     // Don't process variables if user asked for line tables only.
287     if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly)
288       return;
289 
290     funcOp.walk([&](fir::cg::XDeclareOp declOp) {
291       handleDeclareOp(declOp, fileAttr, spAttr, typeGen);
292     });
293   });
294 }
295 
296 std::unique_ptr<mlir::Pass>
297 fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) {
298   return std::make_unique<AddDebugInfoPass>(options);
299 }
300