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