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