//===-------------- AddDebugInfo.cpp -- add debug info -------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// /// \file /// This pass populates some debug information for the module and functions. //===----------------------------------------------------------------------===// #include "DebugTypeGenerator.h" #include "flang/Optimizer/Builder/FIRBuilder.h" #include "flang/Optimizer/Builder/Todo.h" #include "flang/Optimizer/Dialect/FIRCG/CGOps.h" #include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Dialect/FIROps.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Optimizer/Dialect/FIRType.h" #include "flang/Optimizer/Dialect/Support/FIRContext.h" #include "flang/Optimizer/Support/InternalNames.h" #include "flang/Optimizer/Transforms/Passes.h" #include "flang/Support/Version.h" #include "mlir/Dialect/DLTI/DLTI.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/TypeUtilities.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/RegionUtils.h" #include "llvm/ADT/StringSet.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" namespace fir { #define GEN_PASS_DEF_ADDDEBUGINFO #include "flang/Optimizer/Transforms/Passes.h.inc" } // namespace fir #define DEBUG_TYPE "flang-add-debug-info" namespace { class AddDebugInfoPass : public fir::impl::AddDebugInfoBase { void handleDeclareOp(fir::cg::XDeclareOp declOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, mlir::Value dummyScope); void handleDeclareValueOp(fir::DeclareValueOp declOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, mlir::Value dummyScope); public: AddDebugInfoPass(fir::AddDebugInfoOptions options) : Base(options) {} void runOnOperation() override; private: llvm::StringMap moduleMap; llvm::StringMap commonBlockMap; // List of GlobalVariableExpressionAttr that are attached to a given global // that represents the storage for common block. llvm::DenseMap> globalToGlobalExprsMap; /// Maps Fortran module name -> `fir.module_debug_imports`. llvm::StringMap moduleDebugImportsByName; mlir::LLVM::DIModuleAttr getOrCreateModuleAttr( const std::string &name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl); mlir::LLVM::DICommonBlockAttr getOrCreateCommonBlockAttr(llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, unsigned line); void handleGlobalOp(fir::GlobalOp glocalOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, fir::cg::XDeclareOp declOp); void handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable); void handleOnlyClause( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedModules); void handleRenamesWithoutOnly( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedModules); void handleUseStatements( mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedEntities); void buildModuleDebugImportsMap(mlir::ModuleOp module); void expandUseStmtForDebug( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedEntities, llvm::StringSet<> &seenModuleNames); std::optional createImportedDeclForGlobal( llvm::StringRef symbolName, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::StringAttr localNameAttr, mlir::SymbolTable *symbolTable); bool createCommonBlockGlobal(fir::cg::XDeclareOp declOp, const std::string &name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable); std::optional getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope); template void handleLocalVariable(Op declOp, llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::Value dummyScope, mlir::Type typeToConvert, fir::cg::XDeclareOp typeGenDeclOp); }; bool debugInfoIsAlreadySet(mlir::Location loc) { if (mlir::isa(loc)) { if (loc->findInstanceOf>()) return false; return true; } return false; } // Generates the name for the artificial DISubprogram that we are going to // generate for omp::TargetOp. Its logic is borrowed from // getTargetEntryUniqueInfo and // TargetRegionEntryInfo::getTargetRegionEntryFnName to generate the same name. // But even if there was a slight mismatch, it is not a problem because this // name is artificial and not important to debug experience. mlir::StringAttr getTargetFunctionName(mlir::MLIRContext *context, mlir::Location Loc, llvm::StringRef parentName) { auto fileLoc = Loc->findInstanceOf(); assert(fileLoc && "No file found from location"); llvm::StringRef fileName = fileLoc.getFilename().getValue(); llvm::sys::fs::UniqueID id; uint64_t line = fileLoc.getLine(); size_t fileId; size_t deviceId; if (auto ec = llvm::sys::fs::getUniqueID(fileName, id)) { fileId = llvm::hash_value(fileName.str()); deviceId = 0xdeadf17e; } else { fileId = id.getFile(); deviceId = id.getDevice(); } return mlir::StringAttr::get( context, std::string(llvm::formatv("__omp_offloading_{0:x-}_{1:x-}_{2}_l{3}", deviceId, fileId, parentName, line))); } } // namespace // Check if a global represents a module variable static bool isModuleVariable(fir::GlobalOp globalOp) { std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); return result.first == fir::NameUniquer::NameKind::VARIABLE && result.second.procs.empty() && !result.second.modules.empty(); } // Look up DIGlobalVariable from a global symbol static std::optional lookupDIGlobalVariable(llvm::StringRef symbolName, mlir::SymbolTable *symbolTable) { if (auto globalOp = symbolTable->lookup(symbolName)) { if (auto fusedLoc = mlir::dyn_cast(globalOp.getLoc())) { if (auto metadata = fusedLoc.getMetadata()) { if (auto arrayAttr = mlir::dyn_cast(metadata)) { for (auto elem : arrayAttr) { if (auto gvExpr = mlir::dyn_cast( elem)) return gvExpr.getVar(); } } } } } return std::nullopt; } bool AddDebugInfoPass::createCommonBlockGlobal( fir::cg::XDeclareOp declOp, const std::string &name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable) { mlir::MLIRContext *context = &getContext(); mlir::OpBuilder builder(context); std::optional offset; mlir::Value storage = declOp.getStorage(); if (!storage) return false; // Extract offset from storage_offset attribute uint64_t storageOffset = declOp.getStorageOffset(); if (storageOffset != 0) offset = static_cast(storageOffset); // Get the GlobalOp from the storage value. // The storage may be wrapped in ConvertOp, so unwrap it first. mlir::Operation *storageOp = storage.getDefiningOp(); if (auto convertOp = mlir::dyn_cast_if_present(storageOp)) storageOp = convertOp.getValue().getDefiningOp(); auto addrOfOp = mlir::dyn_cast_if_present(storageOp); if (!addrOfOp) return false; mlir::SymbolRefAttr sym = addrOfOp.getSymbol(); fir::GlobalOp global = symbolTable->lookup(sym.getRootReference()); if (!global) return false; // Check if the global is actually a common block by demangling its name. // Module EQUIVALENCE variables also use storage operands but are mangled // as VARIABLE type, so we reject them to avoid treating them as common // blocks. llvm::StringRef globalSymbol = sym.getRootReference(); auto globalResult = fir::NameUniquer::deconstruct(globalSymbol); if (globalResult.first == fir::NameUniquer::NameKind::VARIABLE) return false; // FIXME: We are trying to extract the name of the common block from the // name of the global. As part of mangling, GetCommonBlockObjectName can // add a trailing _ in the name of that global. The demangle function // does not seem to handle such cases. So the following hack is used to // remove the trailing '_'. llvm::StringRef commonName = globalSymbol; if (commonName != Fortran::common::blankCommonObjectName && !commonName.empty() && commonName.back() == '_') commonName = commonName.drop_back(); // Create the debug attributes. unsigned line = getLineFromLoc(global.getLoc()); mlir::LLVM::DICommonBlockAttr commonBlock = getOrCreateCommonBlockAttr(commonName, fileAttr, scopeAttr, line); mlir::LLVM::DITypeAttr diType = typeGen.convertType( fir::unwrapRefType(declOp.getType()), fileAttr, scopeAttr, declOp); line = getLineFromLoc(declOp.getLoc()); auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( context, commonBlock, mlir::StringAttr::get(context, name), declOp.getUniqName(), fileAttr, line, diType, /*isLocalToUnit*/ false, /*isDefinition*/ true, /* alignInBits*/ 0); // Create DIExpression for offset if needed mlir::LLVM::DIExpressionAttr expr; if (offset && *offset != 0) { llvm::SmallVector ops; ops.push_back(mlir::LLVM::DIExpressionElemAttr::get( context, llvm::dwarf::DW_OP_plus_uconst, *offset)); expr = mlir::LLVM::DIExpressionAttr::get(context, ops); } auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get( global.getContext(), gvAttr, expr); globalToGlobalExprsMap[global].push_back(dbgExpr); return true; } template void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::Value dummyScope, mlir::Type typeToConvert, fir::cg::XDeclareOp typeGenDeclOp) { mlir::MLIRContext *context = &getContext(); mlir::OpBuilder builder(context); // Get the dummy argument position from the explicit attribute. unsigned argNo = 0; if (dummyScope && declOp.getDummyScope() == dummyScope) { if (auto argNoOpt = declOp.getDummyArgNo()) { argNo = *argNoOpt; if (emitFakeUseForArguments) { if constexpr (std::is_same_v) { if (auto funcOp = declOp->template getParentOfType()) { if (declOp->getBlock() == &funcOp.getBody().front()) { for (mlir::Block &block : funcOp.getBody()) { if (auto returnOp = mlir::dyn_cast( block.getTerminator())) { mlir::OpBuilder::InsertionGuard guard(builder); builder.setInsertionPoint(returnOp); fir::FakeUseOp::create(builder, declOp.getLoc(), declOp.getMemref()); } } } } } } } } auto tyAttr = typeGen.convertType(typeToConvert, fileAttr, scopeAttr, typeGenDeclOp); auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get( context, scopeAttr, mlir::StringAttr::get(context, name), fileAttr, getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, tyAttr, mlir::LLVM::DIFlags::Zero); declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr)); } void AddDebugInfoPass::handleDeclareOp(fir::cg::XDeclareOp declOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, mlir::Value dummyScope) { auto result = fir::NameUniquer::deconstruct(declOp.getUniqName()); if (result.first != fir::NameUniquer::NameKind::VARIABLE) return; if (createCommonBlockGlobal(declOp, result.second.name, fileAttr, scopeAttr, typeGen, symbolTable)) return; // If this DeclareOp actually represents a global then treat it as such. mlir::Operation *defOp = declOp.getMemref().getDefiningOp(); if (defOp && llvm::isa(defOp)) { if (auto global = symbolTable->lookup(declOp.getUniqName())) { handleGlobalOp(global, fileAttr, scopeAttr, typeGen, symbolTable, declOp); return; } } handleLocalVariable(declOp, result.second.name, fileAttr, scopeAttr, typeGen, dummyScope, fir::unwrapRefType(declOp.getType()), declOp); } void AddDebugInfoPass::handleDeclareValueOp(fir::DeclareValueOp declOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scopeAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, mlir::Value dummyScope) { auto result = fir::NameUniquer::deconstruct(declOp.getUniqName()); if (result.first != fir::NameUniquer::NameKind::VARIABLE) return; handleLocalVariable(declOp, result.second.name, fileAttr, scopeAttr, typeGen, dummyScope, declOp.getValue().getType(), nullptr); } mlir::LLVM::DICommonBlockAttr AddDebugInfoPass::getOrCreateCommonBlockAttr( llvm::StringRef name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, unsigned line) { mlir::MLIRContext *context = &getContext(); mlir::LLVM::DICommonBlockAttr cbAttr; if (auto iter{commonBlockMap.find(name)}; iter != commonBlockMap.end()) { cbAttr = iter->getValue(); } else { cbAttr = mlir::LLVM::DICommonBlockAttr::get( context, scope, nullptr, mlir::StringAttr::get(context, name), fileAttr, line); commonBlockMap[name] = cbAttr; } return cbAttr; } // The `module` does not have a first class representation in the `FIR`. We // extract information about it from the name of the identifiers and keep a // map to avoid duplication. mlir::LLVM::DIModuleAttr AddDebugInfoPass::getOrCreateModuleAttr( const std::string &name, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, unsigned line, bool decl) { mlir::MLIRContext *context = &getContext(); mlir::LLVM::DIModuleAttr modAttr; if (auto iter{moduleMap.find(name)}; iter != moduleMap.end()) { modAttr = iter->getValue(); } else { // When decl is true, it means that module is only being used in this // compilation unit and it is defined elsewhere. But if the file/line/scope // fields are valid, the module is not merged with its definition and is // considered different. So we only set those fields when decl is false. modAttr = mlir::LLVM::DIModuleAttr::get( context, decl ? nullptr : fileAttr, decl ? nullptr : scope, mlir::StringAttr::get(context, name), /* configMacros */ mlir::StringAttr(), /* includePath */ mlir::StringAttr(), /* apinotes */ mlir::StringAttr(), decl ? 0 : line, decl); moduleMap[name] = modAttr; } return modAttr; } /// If globalOp represents a module variable, return a ModuleAttr that /// represents that module. std::optional AddDebugInfoPass::getModuleAttrFromGlobalOp(fir::GlobalOp globalOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope) { mlir::MLIRContext *context = &getContext(); mlir::OpBuilder builder(context); std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); // Only look for module if this variable is not part of a function. if (!result.second.procs.empty() || result.second.modules.empty()) return std::nullopt; // DWARF5 says following about the fortran modules: // A Fortran 90 module may also be represented by a module entry // (but no declaration attribute is warranted because Fortran has no concept // of a corresponding module body). // But in practice, compilers use declaration attribute with a module in cases // where module was defined in another source file (only being used in this // one). The isInitialized() seems to provide the right information // but inverted. It is true where module is actually defined but false where // it is used. // FIXME: Currently we don't have the line number on which a module was // declared. We are using a best guess of line - 1 where line is the source // line of the first member of the module that we encounter. unsigned line = getLineFromLoc(globalOp.getLoc()); mlir::LLVM::DISubprogramAttr sp = mlir::dyn_cast_if_present(scope); // Modules are generated at compile unit scope if (sp) scope = sp.getCompileUnit(); return getOrCreateModuleAttr(result.second.modules[0], fileAttr, scope, std::max(line - 1, (unsigned)1), !globalOp.isInitialized()); } void AddDebugInfoPass::handleGlobalOp(fir::GlobalOp globalOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DIScopeAttr scope, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable, fir::cg::XDeclareOp declOp) { if (debugInfoIsAlreadySet(globalOp.getLoc())) return; mlir::MLIRContext *context = &getContext(); mlir::OpBuilder builder(context); std::pair result = fir::NameUniquer::deconstruct(globalOp.getSymName()); if (result.first != fir::NameUniquer::NameKind::VARIABLE) return; if (fir::NameUniquer::isSpecialSymbol(result.second.name)) return; unsigned line = getLineFromLoc(globalOp.getLoc()); std::optional modOpt = getModuleAttrFromGlobalOp(globalOp, fileAttr, scope); if (modOpt) scope = *modOpt; mlir::LLVM::DITypeAttr diType = typeGen.convertType(globalOp.getType(), fileAttr, scope, declOp); auto gvAttr = mlir::LLVM::DIGlobalVariableAttr::get( context, scope, mlir::StringAttr::get(context, result.second.name), mlir::StringAttr::get(context, globalOp.getName()), fileAttr, line, diType, /*isLocalToUnit*/ false, /*isDefinition*/ globalOp.isInitialized(), /* alignInBits*/ 0); auto dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get( globalOp.getContext(), gvAttr, nullptr); auto arrayAttr = mlir::ArrayAttr::get(context, {dbgExpr}); globalOp->setLoc(builder.getFusedLoc({globalOp.getLoc()}, arrayAttr)); } static mlir::LLVM::DISubprogramAttr getScope(mlir::Operation *op, mlir::LLVM::DISubprogramAttr defaultScope) { if (auto tOp = op->getParentOfType()) { if (auto fusedLoc = llvm::dyn_cast(tOp.getLoc())) { if (auto sp = llvm::dyn_cast( fusedLoc.getMetadata())) return sp; } } return defaultScope; } void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, fir::DebugTypeGenerator &typeGen, mlir::SymbolTable *symbolTable) { mlir::Location l = funcOp->getLoc(); // If fused location has already been created then nothing to do // Otherwise, create a fused location. if (debugInfoIsAlreadySet(l)) return; mlir::MLIRContext *context = &getContext(); mlir::OpBuilder builder(context); llvm::StringRef fileName(fileAttr.getName()); llvm::StringRef filePath(fileAttr.getDirectory()); unsigned int CC = (funcOp.getName() == fir::NameUniquer::doProgramEntry()) ? llvm::dwarf::getCallingConvention("DW_CC_program") : llvm::dwarf::getCallingConvention("DW_CC_normal"); if (auto funcLoc = mlir::dyn_cast(l)) { fileName = llvm::sys::path::filename(funcLoc.getFilename().getValue()); filePath = llvm::sys::path::parent_path(funcLoc.getFilename().getValue()); } mlir::StringAttr fullName = mlir::StringAttr::get(context, funcOp.getName()); mlir::Attribute attr = funcOp->getAttr(fir::getInternalFuncNameAttrName()); mlir::StringAttr funcName = (attr) ? mlir::cast(attr) : mlir::StringAttr::get(context, funcOp.getName()); auto result = fir::NameUniquer::deconstruct(funcName); funcName = mlir::StringAttr::get(context, result.second.name); // try to use a better function name than _QQmain for the program statement bool isMain = false; if (funcName == fir::NameUniquer::doProgramEntry()) { isMain = true; mlir::StringAttr bindcName = funcOp->getAttrOfType(fir::getSymbolAttrName()); if (bindcName) funcName = bindcName; } llvm::SmallVector types; for (auto resTy : funcOp.getResultTypes()) { auto tyAttr = typeGen.convertType(resTy, fileAttr, cuAttr, /*declOp=*/nullptr); types.push_back(tyAttr); } // If no return type then add a null type as a place holder for that. if (types.empty()) types.push_back(mlir::LLVM::DINullTypeAttr::get(context)); for (auto inTy : funcOp.getArgumentTypes()) { auto tyAttr = typeGen.convertType(fir::unwrapRefType(inTy), fileAttr, cuAttr, /*declOp=*/nullptr); types.push_back(tyAttr); } mlir::LLVM::DISubroutineTypeAttr subTypeAttr = mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); mlir::LLVM::DIFileAttr funcFileAttr = mlir::LLVM::DIFileAttr::get(context, fileName, filePath); // Only definitions need a distinct identifier and a compilation unit. mlir::DistinctAttr id, id2; mlir::LLVM::DIScopeAttr Scope = fileAttr; mlir::LLVM::DICompileUnitAttr compilationUnit; mlir::LLVM::DISubprogramFlags subprogramFlags = mlir::LLVM::DISubprogramFlags{}; if (isOptimized) subprogramFlags = mlir::LLVM::DISubprogramFlags::Optimized; if (isMain) subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::MainSubprogram; if (!funcOp.isExternal()) { // Place holder and final function have to have different IDs, otherwise // translation code will reject one of them. id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); id2 = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); compilationUnit = cuAttr; subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Definition; } // Check if the function has the pure, elemental, or recursive procedure // attribute if (fir::hasProcedureAttr(funcOp)) subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Pure; if (fir::hasProcedureAttr(funcOp)) subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Elemental; if (fir::hasProcedureAttr(funcOp)) subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Recursive; unsigned line = getLineFromLoc(l); if (fir::isInternalProcedure(funcOp)) { // For contained functions, the scope is the parent subroutine. mlir::SymbolRefAttr sym = mlir::cast( funcOp->getAttr(fir::getHostSymbolAttrName())); if (sym) { if (auto func = symbolTable->lookup(sym.getLeafReference())) { // Make sure that parent is processed. handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable); if (auto fusedLoc = mlir::dyn_cast_if_present(func.getLoc())) { if (auto spAttr = mlir::dyn_cast_if_present( fusedLoc.getMetadata())) Scope = spAttr; } } } } else if (!result.second.modules.empty()) { Scope = getOrCreateModuleAttr(result.second.modules[0], fileAttr, cuAttr, line - 1, false); } auto addTargetOpDISP = [&](bool lineTableOnly, llvm::ArrayRef entities) { // When we process the DeclareOp inside the OpenMP target region, all the // variables get the DISubprogram of the parent function of the target op as // the scope. In the codegen (to llvm ir), OpenMP target op results in the // creation of a separate function. As the variables in the debug info have // the DISubprogram of the parent function as the scope, the variables // need to be updated at codegen time to avoid verification failures. // This updating after the fact becomes more and more difficult when types // are dependent on local variables like in the case of variable size arrays // or string. We not only have to generate new variables but also new types. // We can avoid this problem by generating a DISubprogramAttr here for the // target op and make sure that all the variables inside the target region // get the correct scope in the first place. funcOp.walk([&](mlir::omp::TargetOp targetOp) { unsigned line = getLineFromLoc(targetOp.getLoc()); mlir::StringAttr name = getTargetFunctionName(context, targetOp.getLoc(), funcOp.getName()); mlir::LLVM::DISubprogramFlags flags = mlir::LLVM::DISubprogramFlags::Definition | mlir::LLVM::DISubprogramFlags::LocalToUnit; if (isOptimized) flags = flags | mlir::LLVM::DISubprogramFlags::Optimized; mlir::DistinctAttr id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); llvm::SmallVector types; types.push_back(mlir::LLVM::DINullTypeAttr::get(context)); for (auto arg : targetOp.getRegion().getArguments()) { auto tyAttr = typeGen.convertType(fir::unwrapRefType(arg.getType()), fileAttr, cuAttr, /*declOp=*/nullptr); types.push_back(tyAttr); } CC = llvm::dwarf::getCallingConvention("DW_CC_normal"); mlir::LLVM::DISubroutineTypeAttr spTy = mlir::LLVM::DISubroutineTypeAttr::get(context, CC, types); if (lineTableOnly || entities.empty()) { auto spAttr = mlir::LLVM::DISubprogramAttr::get( context, id, compilationUnit, Scope, name, name, funcFileAttr, line, line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{}); targetOp->setLoc(builder.getFusedLoc({targetOp.getLoc()}, spAttr)); return; } mlir::DistinctAttr recId = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); auto spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, name, name, funcFileAttr, line, line, flags, spTy, /*retainedNodes=*/{}, /*annotations=*/{}); // Make sure that information about the imported modules is copied in the // new function. llvm::SmallVector opEntities; for (mlir::LLVM::DINodeAttr N : entities) { if (auto entity = mlir::dyn_cast(N)) { auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get( context, entity.getTag(), spAttr, entity.getEntity(), entity.getFile(), entity.getLine(), entity.getName(), entity.getElements()); opEntities.push_back(importedEntity); } } id = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/false, id, compilationUnit, Scope, name, name, funcFileAttr, line, line, flags, spTy, opEntities, /*annotations=*/{}); targetOp->setLoc(builder.getFusedLoc({targetOp.getLoc()}, spAttr)); }); }; // Don't process variables if user asked for line tables only. if (debugLevel == mlir::LLVM::DIEmissionKind::LineTablesOnly) { auto spAttr = mlir::LLVM::DISubprogramAttr::get( context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}, /*annotations=*/{}); funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); addTargetOpDISP(/*lineTableOnly=*/true, /*entities=*/{}); return; } // Check if there are any USE statements bool hasUseStmts = false; funcOp.walk([&](fir::UseStmtOp useOp) { hasUseStmts = true; return mlir::WalkResult::interrupt(); }); mlir::LLVM::DISubprogramAttr spAttr; llvm::SmallVector retainedNodes; if (hasUseStmts) { mlir::DistinctAttr recId = mlir::DistinctAttr::create(mlir::UnitAttr::get(context)); // The debug attribute in MLIR are readonly once created. But in case of // imported entities, we have a circular dependency. The // DIImportedEntityAttr requires scope information (DISubprogramAttr in this // case) and DISubprogramAttr requires the list of imported entities. The // MLIR provides a way where a DISubprogramAttr an be created with a certain // recID and be used in places like DIImportedEntityAttr. After that another // DISubprogramAttr can be created with same recID but with list of entities // now available. The MLIR translation code takes care of updating the // references. Note that references will be updated only in the things that // are part of DISubprogramAttr (like DIImportedEntityAttr) so we have to // create the final DISubprogramAttr before we process local variables. // Look at DIRecursiveTypeAttrInterface for more details. spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/true, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}, /*annotations=*/{}); // Process USE statements (module globals are already processed) llvm::DenseSet importedEntities; handleUseStatements(funcOp, spAttr, fileAttr, cuAttr, symbolTable, importedEntities); retainedNodes.append(importedEntities.begin(), importedEntities.end()); // Create final DISubprogramAttr with imported entities and same recId spAttr = mlir::LLVM::DISubprogramAttr::get( context, recId, /*isRecSelf=*/false, id2, compilationUnit, Scope, funcName, fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, retainedNodes, /*annotations=*/{}); } else // No USE statements - create final DISubprogramAttr directly spAttr = mlir::LLVM::DISubprogramAttr::get( context, id, compilationUnit, Scope, funcName, fullName, funcFileAttr, line, line, subprogramFlags, subTypeAttr, /*retainedNodes=*/{}, /*annotations=*/{}); funcOp->setLoc(builder.getFusedLoc({l}, spAttr)); addTargetOpDISP(/*lineTableOnly=*/false, retainedNodes); // Find the first dummy_scope definition. This is the one of the current // function. The other ones may come from inlined calls. The variables inside // those inlined calls should not be identified as arguments of the current // function. mlir::Value dummyScope; funcOp.walk([&](fir::UndefOp undef) -> mlir::WalkResult { // TODO: delay fir.dummy_scope translation to undefined until // codegeneration. This is nicer and safer to match. if (llvm::isa(undef.getType())) { dummyScope = undef; return mlir::WalkResult::interrupt(); } return mlir::WalkResult::advance(); }); funcOp.walk([&](fir::cg::XDeclareOp declOp) { mlir::LLVM::DISubprogramAttr spTy = getScope(declOp, spAttr); handleDeclareOp(declOp, fileAttr, spTy, typeGen, symbolTable, dummyScope); }); funcOp.walk([&](fir::DeclareValueOp declOp) { mlir::LLVM::DISubprogramAttr spTy = getScope(declOp, spAttr); handleDeclareValueOp(declOp, fileAttr, spTy, typeGen, symbolTable, dummyScope); }); // commonBlockMap ensures that we don't create multiple DICommonBlockAttr of // the same name in one function. But it is ok (rather required) to create // them in different functions if common block of the same name has been used // there. commonBlockMap.clear(); } // Helper function to create a DIImportedEntityAttr for an imported declaration. // Looks up the DIGlobalVariable for the given symbol and creates an imported // declaration with the optional local name (for renames). // Returns std::nullopt if the symbol's DIGlobalVariable is not found. std::optional AddDebugInfoPass::createImportedDeclForGlobal( llvm::StringRef symbolName, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::StringAttr localNameAttr, mlir::SymbolTable *symbolTable) { mlir::MLIRContext *context = &getContext(); if (auto gvAttr = lookupDIGlobalVariable(symbolName, symbolTable)) { return mlir::LLVM::DIImportedEntityAttr::get( context, llvm::dwarf::DW_TAG_imported_declaration, spAttr, *gvAttr, fileAttr, /*line=*/1, /*name=*/localNameAttr, /*elements*/ {}); } return std::nullopt; } // Process USE with ONLY clause void AddDebugInfoPass::handleOnlyClause( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedModules) { // Process ONLY symbols (without renames) if (auto onlySymbols = useOp.getOnlySymbols()) { for (mlir::Attribute attr : *onlySymbols) { auto symbolRef = mlir::cast(attr); if (auto importedDecl = createImportedDeclForGlobal( symbolRef.getValue(), spAttr, fileAttr, mlir::StringAttr(), symbolTable)) importedModules.insert(*importedDecl); } } // Process renames within ONLY clause if (auto renames = useOp.getRenames()) { for (auto attr : *renames) { auto renameAttr = mlir::cast(attr); if (auto importedDecl = createImportedDeclForGlobal( renameAttr.getSymbol().getValue(), spAttr, fileAttr, renameAttr.getLocalName(), symbolTable)) importedModules.insert(*importedDecl); } } } // Process USE with renames but no ONLY clause void AddDebugInfoPass::handleRenamesWithoutOnly( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedModules) { mlir::MLIRContext *context = &getContext(); llvm::SmallVector childDeclarations; if (auto renames = useOp.getRenames()) { for (auto attr : *renames) { auto renameAttr = mlir::cast(attr); if (auto importedDecl = createImportedDeclForGlobal( renameAttr.getSymbol().getValue(), spAttr, fileAttr, renameAttr.getLocalName(), symbolTable)) childDeclarations.push_back(*importedDecl); } } // Create module import with renamed declarations as children auto moduleImport = mlir::LLVM::DIImportedEntityAttr::get( context, llvm::dwarf::DW_TAG_imported_module, spAttr, modAttr, fileAttr, /*line=*/1, /*name=*/nullptr, childDeclarations); importedModules.insert(moduleImport); } // Process all USE statements in a function and collect imported entities. void AddDebugInfoPass::handleUseStatements( mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedEntities) { llvm::StringSet<> seenModuleNames; funcOp.walk([&](fir::UseStmtOp useOp) { expandUseStmtForDebug(useOp, spAttr, fileAttr, cuAttr, symbolTable, importedEntities, seenModuleNames); }); } void AddDebugInfoPass::buildModuleDebugImportsMap(mlir::ModuleOp module) { moduleDebugImportsByName.clear(); module.walk([&](fir::ModuleDebugImportsOp op) { moduleDebugImportsByName[op.getModuleName().str()] = op; }); } void AddDebugInfoPass::expandUseStmtForDebug( fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr, mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr, mlir::SymbolTable *symbolTable, llvm::DenseSet &importedEntities, llvm::StringSet<> &seenModuleNames) { std::string modName = useOp.getModuleName().str(); if (seenModuleNames.contains(modName)) return; mlir::MLIRContext *context = &getContext(); mlir::LLVM::DIModuleAttr modAttr = getOrCreateModuleAttr(modName, fileAttr, cuAttr, /*line=*/1, /*decl=*/true); llvm::DenseSet importedModules; if (useOp.hasOnlyClause()) handleOnlyClause(useOp, spAttr, fileAttr, symbolTable, importedModules); else if (useOp.hasRenames()) handleRenamesWithoutOnly(useOp, spAttr, modAttr, fileAttr, symbolTable, importedModules); else { auto importedEntity = mlir::LLVM::DIImportedEntityAttr::get( context, llvm::dwarf::DW_TAG_imported_module, spAttr, modAttr, fileAttr, /*line=*/1, /*name=*/nullptr, /*elements*/ {}); importedModules.insert(importedEntity); } importedEntities.insert(importedModules.begin(), importedModules.end()); seenModuleNames.insert(modName); if (useOp.hasOnlyClause()) return; auto it = moduleDebugImportsByName.find(modName); if (it == moduleDebugImportsByName.end()) return; fir::ModuleDebugImportsOp mdi = it->second; if (mdi.getUses().empty()) return; for (auto childUse : mdi.getUses().front().getOps()) expandUseStmtForDebug(childUse, spAttr, fileAttr, cuAttr, symbolTable, importedEntities, seenModuleNames); } void AddDebugInfoPass::runOnOperation() { mlir::ModuleOp module = getOperation(); mlir::MLIRContext *context = &getContext(); mlir::SymbolTable symbolTable(module); buildModuleDebugImportsMap(module); llvm::StringRef fileName; std::string filePath; std::optional dl = fir::support::getOrSetMLIRDataLayout(module, /*allowDefaultLayout=*/true); if (!dl) { mlir::emitError(module.getLoc(), "Missing data layout attribute in module"); signalPassFailure(); return; } mlir::OpBuilder builder(context); if (dwarfVersion > 0) { mlir::OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointToEnd(module.getBody()); llvm::SmallVector moduleFlags; mlir::IntegerType int32Ty = mlir::IntegerType::get(context, 32); moduleFlags.push_back(builder.getAttr( mlir::LLVM::ModFlagBehavior::Max, mlir::StringAttr::get(context, "Dwarf Version"), mlir::IntegerAttr::get(int32Ty, dwarfVersion))); mlir::LLVM::ModuleFlagsOp::create(builder, module.getLoc(), builder.getArrayAttr(moduleFlags)); } fir::DebugTypeGenerator typeGen(module, &symbolTable, *dl); // We need 2 type of file paths here. // 1. Name of the file as was presented to compiler. This can be absolute // or relative to 2. // 2. Current working directory // // We are also dealing with 2 different situations below. One is normal // compilation where we will have a value in 'inputFilename' and we can // obtain the current directory using 'current_path'. // The 2nd case is when this pass is invoked directly from 'fir-opt' tool. // In that case, 'inputFilename' may be empty. Location embedded in the // module will be used to get file name and its directory. if (inputFilename.empty()) { if (auto fileLoc = mlir::dyn_cast(module.getLoc())) { fileName = llvm::sys::path::filename(fileLoc.getFilename().getValue()); filePath = llvm::sys::path::parent_path(fileLoc.getFilename().getValue()); } else fileName = "-"; } else { fileName = inputFilename; llvm::SmallString<256> cwd; if (!llvm::sys::fs::current_path(cwd)) filePath = cwd.str(); } mlir::LLVM::DIFileAttr fileAttr = mlir::LLVM::DIFileAttr::get(context, fileName, filePath); // Match Clang style by starting with the full compiler version and // appending -dwarf-debug-flags content when provided. std::string producerString = Fortran::common::getFlangFullVersion(); if (!dwarfDebugFlags.empty()) producerString += " " + dwarfDebugFlags; mlir::StringAttr producer = mlir::StringAttr::get(context, producerString); mlir::LLVM::DICompileUnitAttr cuAttr = mlir::LLVM::DICompileUnitAttr::get( mlir::DistinctAttr::create(mlir::UnitAttr::get(context)), llvm::dwarf::getLanguage("DW_LANG_Fortran95"), fileAttr, producer, isOptimized, debugLevel, debugInfoForProfiling, /*nameTableKind=*/mlir::LLVM::DINameTableKind::Default, splitDwarfFile.empty() ? mlir::StringAttr() : mlir::StringAttr::get(context, splitDwarfFile)); // Process module globals early. // Walk through all DeclareOps in functions and process globals that are // module variables. This ensures that when we process USE statements, // the DIGlobalVariable lookups will succeed. if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { module.walk([&](fir::cg::XDeclareOp declOp) { mlir::Operation *defOp = declOp.getMemref().getDefiningOp(); if (defOp && llvm::isa(defOp)) { if (auto globalOp = symbolTable.lookup(declOp.getUniqName())) { // Only process module variables here, not SAVE variables if (isModuleVariable(globalOp)) { handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable, declOp); } } } }); } module.walk([&](mlir::func::FuncOp funcOp) { handleFuncOp(funcOp, fileAttr, cuAttr, typeGen, &symbolTable); }); // We have processed all function. Attach common block variables to the // global that represent the storage. for (auto [global, exprs] : globalToGlobalExprsMap) { auto arrayAttr = mlir::ArrayAttr::get(context, exprs); global->setLoc(builder.getFusedLoc({global.getLoc()}, arrayAttr)); } // Process any global which was not processed through DeclareOp. if (debugLevel == mlir::LLVM::DIEmissionKind::Full) { // Process 'GlobalOp' only if full debug info is requested. for (auto globalOp : module.getOps()) handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable, /*declOp=*/nullptr); } } std::unique_ptr fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) { return std::make_unique(options); }