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