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 }; 76 77 bool debugInfoIsAlreadySet(mlir::Location loc) { 78 if (mlir::isa<mlir::FusedLoc>(loc)) { 79 if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>()) 80 return false; 81 return true; 82 } 83 return false; 84 } 85 86 } // namespace 87 88 void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp, 89 mlir::LLVM::DIFileAttr fileAttr, 90 mlir::LLVM::DIScopeAttr scopeAttr, 91 fir::DebugTypeGenerator &typeGen, 92 mlir::SymbolTable *symbolTable) { 93 mlir::MLIRContext *context = &getContext(); 94 mlir::OpBuilder builder(context); 95 auto result = fir::NameUniquer::deconstruct(declOp.getUniqName()); 96 97 if (result.first != fir::NameUniquer::NameKind::VARIABLE) 98 return; 99 // If this DeclareOp actually represents a global then treat it as such. 100 if (auto global = symbolTable->lookup<fir::GlobalOp>(declOp.getUniqName())) { 101 handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp); 102 return; 103 } 104 105 // Only accept local variables. 106 if (result.second.procs.empty()) 107 return; 108 109 // FIXME: There may be cases where an argument is processed a bit before 110 // DeclareOp is generated. In that case, DeclareOp may point to an 111 // intermediate op and not to BlockArgument. 112 // Moreover, with MLIR inlining we cannot use the BlockArgument 113 // position to identify the original number of the dummy argument. 114 // If we want to keep running AddDebugInfoPass late, the dummy argument 115 // position in the argument list has to be expressed in FIR (e.g. as a 116 // constant attribute of [hl]fir.declare/fircg.ext_declare operation that has 117 // a dummy_scope operand). 118 unsigned argNo = 0; 119 if (fir::isDummyArgument(declOp.getMemref())) { 120 auto arg = llvm::cast<mlir::BlockArgument>(declOp.getMemref()); 121 argNo = arg.getArgNumber() + 1; 122 } 123 124 auto tyAttr = typeGen.convertType(fir::unwrapRefType(declOp.getType()), 125 fileAttr, scopeAttr, declOp); 126 127 auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get( 128 context, scopeAttr, mlir::StringAttr::get(context, result.second.name), 129 fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, 130 tyAttr, mlir::LLVM::DIFlags::Zero); 131 declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr)); 132 } 133 134 // The `module` does not have a first class representation in the `FIR`. We 135 // extract information about it from the name of the identifiers and keep a 136 // map to avoid duplication. 137 mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( 138 const std::string &name, mlir::LLVM::DIFileAttr fileAttr, 139 mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) { 140 mlir::MLIRContext *context = &getContext(); 141 mlir::LLVM::DIModuleAttr modAttr; 142 if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) { 143 modAttr = iter->getValue(); 144 } else { 145 modAttr = mlir::LLVM::DIModuleAttr::get( 146 context, fileAttr, scope, mlir::StringAttr::get(context, name), 147 /* configMacros */ mlir::StringAttr(), 148 /* includePath */ mlir::StringAttr(), 149 /* apinotes */ mlir::StringAttr(), line, decl); 150 moduleMap[name] = modAttr; 151 } 152 return modAttr; 153 } 154 155 void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp, 156 mlir::LLVM::DIFileAttr fileAttr, 157 mlir::LLVM::DIScopeAttr scope, 158 fir::DebugTypeGenerator &typeGen, 159 mlir::SymbolTable *symbolTable, 160 fir::cg::XDeclareOp declOp) { 161 if (debugInfoIsAlreadySet(globalOp.getLoc())) 162 return; 163 mlir::MLIRContext *context = &getContext(); 164 mlir::OpBuilder builder(context); 165 166 std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); 167 if (result.first != fir::NameUniquer::NameKind::VARIABLE) 168 return; 169 170 // Discard entries that describe a derived type. Usually start with '.c.', 171 // '.dt.' or '.n.'. It would be better if result of the deconstruct had a flag 172 // for such values so that we dont have to look at string values. 173 if (!result.second.name.empty() && result.second.name[0] == '.') 174 return; 175 176 unsigned line = getLineFromLoc(globalOp.getLoc()); 177 178 // DWARF5 says following about the fortran modules: 179 // A Fortran 90 module may also be represented by a module entry 180 // (but no declaration attribute is warranted because Fortran has no concept 181 // of a corresponding module body). 182 // But in practice, compilers use declaration attribute with a module in cases 183 // where module was defined in another source file (only being used in this 184 // one). The isInitialized() seems to provide the right information 185 // but inverted. It is true where module is actually defined but false where 186 // it is used. 187 // FIXME: Currently we don't have the line number on which a module was 188 // declared. We are using a best guess of line - 1 where line is the source 189 // line of the first member of the module that we encounter. 190 191 if (result.second.procs.empty()) { 192 // Only look for module if this variable is not part of a function. 193 if (result.second.modules.empty()) 194 return; 195 196 // Modules are generated at compile unit scope 197 if (mlir::LLVM::DISubprogramAttr sp = 198 mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(scope)) 199 scope = sp.getCompileUnit(); 200 201 scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope, 202 line - 1, !globalOp.isInitialized()); 203 } 204 mlir::LLVM::DITypeAttr diType = 205 typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp); 206 auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( 207 context, scope, mlir::StringAttr::get(context, result.second.name), 208 mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line, 209 diType, /*isLocalToUnit*/ false, 210 /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0); 211 globalOp->setLoc(builder.getFusedLoc({globalOp->getLoc()}, gvAttr)); 212 } 213 214 void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, 215 mlir::LLVM::DIFileAttr fileAttr, 216 mlir::LLVM::DICompileUnitAttr cuAttr, 217 fir::DebugTypeGenerator &typeGen, 218 mlir::SymbolTable *symbolTable) { 219 mlir::Location l = funcOp->getLoc(); 220 // If fused location has already been created then nothing to do 221 // Otherwise, create a fused location. 222 if (debugInfoIsAlreadySet(l)) 223 return; 224 225 mlir::MLIRContext *context = &getContext(); 226 mlir::OpBuilder builder(context); 227 llvm::StringRef fileName(fileAttr.getName()); 228 llvm::StringRef filePath(fileAttr.getDirectory()); 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 = mlir::StringAttr::get(context, funcOp.getName()); 239 mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName()); 240 mlir::StringAttr funcName = 241 (attr) ? mlir::cast<mlir::StringAttr>(attr) 242 : mlir::StringAttr::get(context, funcOp.getName()); 243 244 auto result = fir::NameUniquer::deconstruct(funcName); 245 funcName = mlir::StringAttr::get(context, result.second.name); 246 247 llvm::SmallVector<mlir::LLVM::DITypeAttr> types; 248 for (auto resTy : funcOp.getResultTypes()) { 249 auto tyAttr = 250 typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr); 251 types.push_back(tyAttr); 252 } 253 for (auto inTy : funcOp.getArgumentTypes()) { 254 auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr, 255 cuAttr, /*declOp=*/nullptr); 256 types.push_back(tyAttr); 257 } 258 259 mlir::LLVM::DISubroutineTypeAttr subTypeAttr = 260 mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); 261 mlir::LLVM::DIFileAttr funcFileAttr = 262 mlir::LLVM::DIFileAttr::get(context, fileName, filePath); 263 264 // Only definitions need a distinct identifier and a compilation unit. 265 mlir::DistinctAttr id; 266 mlir::LLVM::DIScopeAttr Scope = fileAttr; 267 mlir::LLVM::DICompileUnitAttr compilationUnit; 268 mlir::LLVM::DISubprogramFlags subprogramFlags = 269 mlir::LLVM::DISubprogramFlags{}; 270 if (isOptimized) 271 subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized; 272 if (!funcOp.isExternal()) { 273 id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); 274 compilationUnit = cuAttr; 275 subprogramFlags = 276 subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; 277 } 278 unsigned line = getLineFromLoc(l); 279 if (fir::isInternalProcedure(funcOp)) { 280 // For contained functions, the scope is the parent subroutine. 281 mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>( 282 funcOp->getAttr(fir::getHostSymbolAttrName())); 283 if (sym) { 284 if (auto func = 285 symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) { 286 // Make sure that parent is processed. 287 handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable); 288 if (auto fusedLoc = 289 mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) { 290 if (auto spAttr = 291 mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>( 292 fusedLoc.getMetadata())) 293 Scope = spAttr; 294 } 295 } 296 } 297 } else if (!result.second.modules.empty()) { 298 Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr, 299 line - 1, false); 300 } 301 302 auto spAttr = mlir::LLVM::DISubprogramAttr::get( 303 context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, 304 line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}); 305 funcOp->setLoc(builder.getFusedLoc({funcOp->getLoc()}, spAttr)); 306 307 // Don't process variables if user asked for line tables only. 308 if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly) 309 return; 310 311 funcOp.walk([&](fir::cg::XDeclareOp declOp) { 312 handleDeclareOp(declOp, fileAttr, spAttr, typeGen, symbolTable); 313 }); 314 } 315 316 void AddDebugInfoPass::runOnOperation() { 317 mlir::ModuleOp module = getOperation(); 318 mlir::MLIRContext *context = &getContext(); 319 mlir::SymbolTable symbolTable(module); 320 llvm::StringRef fileName; 321 std::string filePath; 322 std::optional<mlir::DataLayout> dl = 323 fir::support::getOrSetDataLayout(module, /*allowDefaultLayout=*/true); 324 if (!dl) { 325 mlir::emitError(module.getLoc(), "Missing data layout attribute in module"); 326 signalPassFailure(); 327 return; 328 } 329 fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl); 330 // We need 2 type of file paths here. 331 // 1. Name of the file as was presented to compiler. This can be absolute 332 // or relative to 2. 333 // 2. Current working directory 334 // 335 // We are also dealing with 2 different situations below. One is normal 336 // compilation where we will have a value in 'inputFilename' and we can 337 // obtain the current directory using 'current_path'. 338 // The 2nd case is when this pass is invoked directly from 'fir-opt' tool. 339 // In that case, 'inputFilename' may be empty. Location embedded in the 340 // module will be used to get file name and its directory. 341 if (inputFilename.empty()) { 342 if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(module.getLoc())) { 343 fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue()); 344 filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue()); 345 } else 346 fileName = "-"; 347 } else { 348 fileName = inputFilename; 349 llvm::SmallString<256> cwd; 350 if (!llvm::sys::fs::current_path(cwd)) 351 filePath = cwd.str(); 352 } 353 354 mlir::LLVM::DIFileAttr fileAttr = 355 mlir::LLVM::DIFileAttr::get(context, fileName, filePath); 356 mlir::StringAttr producer = 357 mlir::StringAttr::get(context, Fortran::common::getFlangFullVersion()); 358 mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get( 359 mlir::DistinctAttr::create(mlir::UnitAttr::get(context)), 360 llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer, 361 isOptimized, debugLevel); 362 363 module.walk([&](mlir::func::FuncOp funcOp) { 364 handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable); 365 }); 366 // Process any global which was not processed through DeclareOp. 367 if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { 368 // Process 'GlobalOp' only if full debug info is requested. 369 for (auto globalOp : module.getOps<fir::GlobalOp>()) 370 handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable, 371 /*declOp=*/nullptr); 372 } 373 } 374 375 std::unique_ptr<mlir::Pass> 376 fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) { 377 return std::make_unique<AddDebugInfoPass>(options); 378 } 379