1 //===-------------- AddDebugInfo.cpp -- add debug info -------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 //===----------------------------------------------------------------------===// 10 /// \file 11 /// This pass populates some debug information for the module and functions. 12 //===----------------------------------------------------------------------===// 13 14 #include "DebugTypeGenerator.h" 15 #include "flang/Common/Version.h" 16 #include "flang/Optimizer/Builder/FIRBuilder.h" 17 #include "flang/Optimizer/Builder/Todo.h" 18 #include "flang/Optimizer/CodeGen/CGOps.h" 19 #include "flang/Optimizer/Dialect/FIRDialect.h" 20 #include "flang/Optimizer/Dialect/FIROps.h" 21 #include "flang/Optimizer/Dialect/FIRType.h" 22 #include "flang/Optimizer/Dialect/Support/FIRContext.h" 23 #include "flang/Optimizer/Support/InternalNames.h" 24 #include "flang/Optimizer/Transforms/Passes.h" 25 #include "mlir/Dialect/Func/IR/FuncOps.h" 26 #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 27 #include "mlir/IR/Matchers.h" 28 #include "mlir/IR/TypeUtilities.h" 29 #include "mlir/Pass/Pass.h" 30 #include "mlir/Transforms/DialectConversion.h" 31 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 32 #include "mlir/Transforms/RegionUtils.h" 33 #include "llvm/BinaryFormat/Dwarf.h" 34 #include "llvm/Support/Debug.h" 35 #include "llvm/Support/FileSystem.h" 36 #include "llvm/Support/Path.h" 37 #include "llvm/Support/raw_ostream.h" 38 39 namespace fir { 40 #define GEN_PASS_DEF_ADDDEBUGINFO 41 #include "flang/Optimizer/Transforms/Passes.h.inc" 42 } // namespace fir 43 44 #define DEBUG_TYPE "flang-add-debug-info" 45 46 namespace { 47 48 class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> { 49 void handleDeclareOp(fir::cg::XDeclareOp declOp, 50 mlir::LLVM::DIFileAttr fileAttr, 51 mlir::LLVM::DIScopeAttr scopeAttr, 52 fir::DebugTypeGenerator &typeGen, 53 mlir::SymbolTable *symbolTable); 54 55 public: 56 AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {} 57 void runOnOperation() override; 58 59 private: 60 llvm::StringMap<mlir::LLVM::DIModuleAttr> moduleMap; 61 62 mlir::LLVM::DIModuleAttr getOrCreateModuleAttr( 63 const std::string &name, mlir::LLVM::DIFileAttr fileAttr, 64 mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl); 65 66 void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr, 67 mlir::LLVM::DIScopeAttr scope, 68 fir::DebugTypeGenerator &typeGen, 69 mlir::SymbolTable *symbolTable, 70 fir::cg::XDeclareOp declOp); 71 void handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr fileAttr, 72 mlir::LLVM::DICompileUnitAttr cuAttr, 73 fir::DebugTypeGenerator &typeGen, 74 mlir::SymbolTable *symbolTable); 75 std::optional<mlir::LLVM::DIModuleAttr> 76 getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, 77 mlir::LLVM::DIFileAttr fileAttr, 78 mlir::LLVM::DIScopeAttr scope); 79 }; 80 81 bool debugInfoIsAlreadySet(mlir::Location loc) { 82 if (mlir::isa<mlir::FusedLoc>(loc)) { 83 if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>()) 84 return false; 85 return true; 86 } 87 return false; 88 } 89 90 } // namespace 91 92 void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp, 93 mlir::LLVM::DIFileAttr fileAttr, 94 mlir::LLVM::DIScopeAttr scopeAttr, 95 fir::DebugTypeGenerator &typeGen, 96 mlir::SymbolTable *symbolTable) { 97 mlir::MLIRContext *context = &getContext(); 98 mlir::OpBuilder builder(context); 99 auto result = fir::NameUniquer::deconstruct(declOp.getUniqName()); 100 101 if (result.first != fir::NameUniquer::NameKind::VARIABLE) 102 return; 103 // If this DeclareOp actually represents a global then treat it as such. 104 if (auto global = symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) { 105 handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp); 106 return; 107 } 108 109 // Only accept local variables. 110 if (result.second.procs.empty()) 111 return; 112 113 // FIXME: There may be cases where an argument is processed a bit before 114 // DeclareOp is generated. In that case, DeclareOp may point to an 115 // intermediate op and not to BlockArgument. 116 // Moreover, with MLIR inlining we cannot use the BlockArgument 117 // position to identify the original number of the dummy argument. 118 // If we want to keep running AddDebugInfoPass late, the dummy argument 119 // position in the argument list has to be expressed in FIR (e.g. as a 120 // constant attribute of [hl]fir.declare/fircg.ext_declare operation that has 121 // a dummy_scope operand). 122 unsigned argNo = 0; 123 if (fir::isDummyArgument(declOp.getMemref())) { 124 auto arg = llvm::cast<mlir::BlockArgument>(declOp.getMemref()); 125 argNo = arg.getArgNumber() + 1; 126 } 127 128 auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()), 129 fileAttr, scopeAttr, declOp); 130 131 auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get( 132 context, scopeAttr, mlir::StringAttr::get(context, result.second.name), 133 fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, 134 tyAttr, mlir::LLVM::DIFlags::Zero); 135 declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr)); 136 } 137 138 // The `module` does not have a first class representation in the `FIR`. We 139 // extract information about it from the name of the identifiers and keep a 140 // map to avoid duplication. 141 mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( 142 const std::string &name, mlir::LLVM::DIFileAttr fileAttr, 143 mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) { 144 mlir::MLIRContext *context = &getContext(); 145 mlir::LLVM::DIModuleAttr modAttr; 146 if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) { 147 modAttr = iter->getValue(); 148 } else { 149 modAttr = mlir::LLVM::DIModuleAttr::get( 150 context, fileAttr, scope, mlir::StringAttr::get(context, name), 151 /* configMacros */ mlir::StringAttr(), 152 /* includePath */ mlir::StringAttr(), 153 /* apinotes */ mlir::StringAttr(), line, decl); 154 moduleMap[name] = modAttr; 155 } 156 return modAttr; 157 } 158 159 /// If globalOp represents a module variable, return a ModuleAttr that 160 /// represents that module. 161 std::optional<mlir::LLVM::DIModuleAttr> 162 AddDebugInfoPass::getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, 163 mlir::LLVM::DIFileAttr fileAttr, 164 mlir::LLVM::DIScopeAttr scope) { 165 mlir::MLIRContext *context = &getContext(); 166 mlir::OpBuilder builder(context); 167 168 std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); 169 // Only look for module if this variable is not part of a function. 170 if (!result.second.procs.empty() || result.second.modules.empty()) 171 return std::nullopt; 172 173 // DWARF5 says following about the fortran modules: 174 // A Fortran 90 module may also be represented by a module entry 175 // (but no declaration attribute is warranted because Fortran has no concept 176 // of a corresponding module body). 177 // But in practice, compilers use declaration attribute with a module in cases 178 // where module was defined in another source file (only being used in this 179 // one). The isInitialized() seems to provide the right information 180 // but inverted. It is true where module is actually defined but false where 181 // it is used. 182 // FIXME: Currently we don't have the line number on which a module was 183 // declared. We are using a best guess of line - 1 where line is the source 184 // line of the first member of the module that we encounter. 185 unsigned line = getLineFromLoc(globalOp.getLoc()); 186 187 mlir::LLVM::DISubprogramAttr sp = 188 mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope); 189 // Modules are generated at compile unit scope 190 if (sp) 191 scope = sp.getCompileUnit(); 192 193 return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope, 194 std::max(line - 1, (unsigned)1), 195 !globalOp.isInitialized()); 196 } 197 198 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp, 199 mlir::LLVM::DIFileAttr fileAttr, 200 mlir::LLVM::DIScopeAttr scope, 201 fir::DebugTypeGenerator &typeGen, 202 mlir::SymbolTable *symbolTable, 203 fir::cg::XDeclareOp declOp) { 204 if (debugInfoIsAlreadySet(globalOp.getLoc())) 205 return; 206 mlir::MLIRContext *context = &getContext(); 207 mlir::OpBuilder builder(context); 208 209 std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); 210 if (result.first != fir::NameUniquer::NameKind::VARIABLE) 211 return; 212 213 // Discard entries that describe a derived type. Usually start with '.c.', 214 // '.dt.' or '.n.'. It would be better if result of the deconstruct had a flag 215 // for such values so that we dont have to look at string values. 216 if (!result.second.name.empty() && result.second.name[0] == '.') 217 return; 218 219 unsigned line = getLineFromLoc(globalOp.getLoc()); 220 std::optional<mlir::LLVM::DIModuleAttr> modOpt = 221 getModuleAttrFromGlobalOp(globalOp, fileAttr, scope); 222 if (modOpt) 223 scope = *modOpt; 224 225 mlir::LLVM::DITypeAttr diType = 226 typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp); 227 auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( 228 context, scope, mlir::StringAttr::get(context, result.second.name), 229 mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line, 230 diType, /*isLocalToUnit*/ false, 231 /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0); 232 globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr)); 233 } 234 235 void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, 236 mlir::LLVM::DIFileAttr fileAttr, 237 mlir::LLVM::DICompileUnitAttr cuAttr, 238 fir::DebugTypeGenerator &typeGen, 239 mlir::SymbolTable *symbolTable) { 240 mlir::Location l = funcOp->getLoc(); 241 // If fused location has already been created then nothing to do 242 // Otherwise, create a fused location. 243 if (debugInfoIsAlreadySet(l)) 244 return; 245 246 mlir::MLIRContext *context = &getContext(); 247 mlir::OpBuilder builder(context); 248 llvm::StringRef fileName(fileAttr.getName()); 249 llvm::StringRef filePath(fileAttr.getDirectory()); 250 unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry()) 251 ? llvm::dwarf::getCallingConvention("DW_CC_program") 252 : llvm::dwarf::getCallingConvention("DW_CC_normal"); 253 254 if (auto funcLoc = mlir::dyn_cast<mlir::FileLineColLoc>(l)) { 255 fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue()); 256 filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue()); 257 } 258 259 mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName()); 260 mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName()); 261 mlir::StringAttr funcName = 262 (attr) ? mlir::cast<mlir::StringAttr>(attr) 263 : mlir::StringAttr::get(context, funcOp.getName()); 264 265 auto result = fir::NameUniquer::deconstruct(funcName); 266 funcName = mlir::StringAttr::get(context, result.second.name); 267 268 llvm::SmallVector<mlir::LLVM::DITypeAttr> types; 269 for (auto resTy : funcOp.getResultTypes()) { 270 auto tyAttr = 271 typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr); 272 types.push_back(tyAttr); 273 } 274 for (auto inTy : funcOp.getArgumentTypes()) { 275 auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr, 276 cuAttr, /*declOp=*/nullptr); 277 types.push_back(tyAttr); 278 } 279 280 mlir::LLVM::DISubroutineTypeAttr subTypeAttr = 281 mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); 282 mlir::LLVM::DIFileAttr funcFileAttr = 283 mlir::LLVM::DIFileAttr::get(context, fileName, filePath); 284 285 // Only definitions need a distinct identifier and a compilation unit. 286 mlir::DistinctAttr id, id2; 287 mlir::LLVM::DIScopeAttr Scope = fileAttr; 288 mlir::LLVM::DICompileUnitAttr compilationUnit; 289 mlir::LLVM::DISubprogramFlags subprogramFlags = 290 mlir::LLVM::DISubprogramFlags{}; 291 if (isOptimized) 292 subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized; 293 if (!funcOp.isExternal()) { 294 // Place holder and final function have to have different IDs, otherwise 295 // translation code will reject one of them. 296 id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); 297 id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); 298 compilationUnit = cuAttr; 299 subprogramFlags = 300 subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; 301 } 302 unsigned line = getLineFromLoc(l); 303 if (fir::isInternalProcedure(funcOp)) { 304 // For contained functions, the scope is the parent subroutine. 305 mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>( 306 funcOp->getAttr(fir::getHostSymbolAttrName())); 307 if (sym) { 308 if (auto func = 309 symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) { 310 // Make sure that parent is processed. 311 handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable); 312 if (auto fusedLoc = 313 mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) { 314 if (auto spAttr = 315 mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>( 316 fusedLoc.getMetadata())) 317 Scope = spAttr; 318 } 319 } 320 } 321 } else if (!result.second.modules.empty()) { 322 Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr, 323 line - 1, false); 324 } 325 326 // Don't process variables if user asked for line tables only. 327 if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly) { 328 auto spAttr = mlir::LLVM::DISubprogramAttr::get( 329 context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, 330 line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}); 331 funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); 332 return; 333 } 334 335 mlir::DistinctAttr recId = 336 mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); 337 338 // The debug attribute in MLIR are readonly once created. But in case of 339 // imported entities, we have a circular dependency. The 340 // DIImportedEntityAttr requires scope information (DISubprogramAttr in this 341 // case) and DISubprogramAttr requires the list of imported entities. The 342 // MLIR provides a way where a DISubprogramAttr an be created with a certain 343 // recID and be used in places like DIImportedEntityAttr. After that another 344 // DISubprogramAttr can be created with same recID but with list of entities 345 // now available. The MLIR translation code takes care of updating the 346 // references. Note that references will be updated only in the things that 347 // are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to 348 // create the final DISubprogramAttr before we process local variables. 349 // Look at DIRecursiveTypeAttrInterface for more details. 350 351 auto spAttr = mlir::LLVM::DISubprogramAttr::get( 352 context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, funcName, 353 fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, 354 /*retainedNodes=*/{}); 355 356 // There is no direct information in the IR for any 'use' statement in the 357 // function. We have to extract that information from the DeclareOp. We do 358 // a pass on the DeclareOp and generate ModuleAttr and corresponding 359 // DIImportedEntityAttr for that module. 360 // FIXME: As we are depending on the variables to see which module is being 361 // 'used' in the function, there are certain limitations. 362 // For things like 'use mod1, only: v1', whole module will be brought into the 363 // namespace in the debug info. It is not a problem as such unless there is a 364 // clash of names. 365 // There is no information about module variable renaming 366 llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> importedModules; 367 funcOp.walk([&](fir::cg::XDeclareOp declOp) { 368 if (&funcOp.front() == declOp->getBlock()) 369 if (auto global = 370 symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) { 371 std::optional<mlir::LLVM::DIModuleAttr> modOpt = 372 getModuleAttrFromGlobalOp(global, fileAttr, cuAttr); 373 if (modOpt) { 374 auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get( 375 context, llvm::dwarf::DW_TAG_imported_module, spAttr, *modOpt, 376 fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {}); 377 importedModules.insert(importedEntity); 378 } 379 } 380 }); 381 llvm::SmallVector<mlir::LLVM::DINodeAttr> entities(importedModules.begin(), 382 importedModules.end()); 383 // We have the imported entities now. Generate the final DISubprogramAttr. 384 spAttr = mlir::LLVM::DISubprogramAttr::get( 385 context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope, 386 funcName, fullName, funcFileAttr, line, line, subprogramFlags, 387 subTypeAttr, entities); 388 funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); 389 390 funcOp.walk([&](fir::cg::XDeclareOp declOp) { 391 // FIXME: We currently dont handle variables that are not in the entry 392 // blocks of the fuctions. These may be variable or arguments used in the 393 // OpenMP target regions. 394 if (&funcOp.front() == declOp->getBlock()) 395 handleDeclareOp(declOp, fileAttr, spAttr, typeGen, symbolTable); 396 }); 397 } 398 399 void AddDebugInfoPass::runOnOperation() { 400 mlir::ModuleOp module = getOperation(); 401 mlir::MLIRContext *context = &getContext(); 402 mlir::SymbolTable symbolTable(module); 403 llvm::StringRef fileName; 404 std::string filePath; 405 std::optional<mlir::DataLayout> dl = 406 fir::support::getOrSetDataLayout(module, /*allowDefaultLayout=*/true); 407 if (!dl) { 408 mlir::emitError(module.getLoc(), "Missing data layout attribute in module"); 409 signalPassFailure(); 410 return; 411 } 412 fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl); 413 // We need 2 type of file paths here. 414 // 1. Name of the file as was presented to compiler. This can be absolute 415 // or relative to 2. 416 // 2. Current working directory 417 // 418 // We are also dealing with 2 different situations below. One is normal 419 // compilation where we will have a value in 'inputFilename' and we can 420 // obtain the current directory using 'current_path'. 421 // The 2nd case is when this pass is invoked directly from 'fir-opt' tool. 422 // In that case, 'inputFilename' may be empty. Location embedded in the 423 // module will be used to get file name and its directory. 424 if (inputFilename.empty()) { 425 if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) { 426 fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue()); 427 filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue()); 428 } else 429 fileName = "-"; 430 } else { 431 fileName = inputFilename; 432 llvm::SmallString<256> cwd; 433 if (!llvm::sys::fs::current_path(cwd)) 434 filePath = cwd.str(); 435 } 436 437 mlir::LLVM::DIFileAttr fileAttr = 438 mlir::LLVM::DIFileAttr::get(context, fileName, filePath); 439 mlir::StringAttr producer = 440 mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion()); 441 mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get( 442 mlir::DistinctAttr::create(mlir::UnitAttr::get(context)), 443 llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer, 444 isOptimized, debugLevel); 445 446 module.walk([&](mlir::func::FuncOp funcOp) { 447 handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable); 448 }); 449 // Process any global which was not processed through DeclareOp. 450 if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { 451 // Process 'GlobalOp' only if full debug info is requested. 452 for (auto globalOp : module.getOps<fir::GlobalOp>()) 453 handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable, 454 /*declOp=*/nullptr); 455 } 456 } 457 458 std::unique_ptr<mlir::Pass> 459 fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) { 460 return std::make_unique<AddDebugInfoPass>(options); 461 } 462