Fix for https://github.com/llvm/llvm-project/issues/185432. This patch extends the lifetime of procedure dummy arguments using llvm.fake.use so that they are accessible in debugger during the whole lifetime of the function. This is done by: - adding a new fir.fake_use operation and emitting it in AddDebugInfo.cpp for dummy arguments at the end of the procedure scope. - lower this new fir.fake_use to the llvm.fake.use intrinsic. This is done under -g at O0 only to avoid pessimizing the generated code when optimizations are requested.
1049 lines
46 KiB
C++
1049 lines
46 KiB
C++
//===-------------- 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<AddDebugInfoPass> {
|
|
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<mlir::LLVM::DIModuleAttr> moduleMap;
|
|
llvm::StringMap<mlir::LLVM::DICommonBlockAttr> commonBlockMap;
|
|
// List of GlobalVariableExpressionAttr that are attached to a given global
|
|
// that represents the storage for common block.
|
|
llvm::DenseMap<fir::GlobalOp, llvm::SmallVector<mlir::Attribute>>
|
|
globalToGlobalExprsMap;
|
|
|
|
/// Maps Fortran module name -> `fir.module_debug_imports`.
|
|
llvm::StringMap<fir::ModuleDebugImportsOp> 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<mlir::LLVM::DIImportedEntityAttr> &importedModules);
|
|
void handleRenamesWithoutOnly(
|
|
fir::UseStmtOp useOp, mlir::LLVM::DISubprogramAttr spAttr,
|
|
mlir::LLVM::DIModuleAttr modAttr, mlir::LLVM::DIFileAttr fileAttr,
|
|
mlir::SymbolTable *symbolTable,
|
|
llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &importedModules);
|
|
void handleUseStatements(
|
|
mlir::func::FuncOp funcOp, mlir::LLVM::DISubprogramAttr spAttr,
|
|
mlir::LLVM::DIFileAttr fileAttr, mlir::LLVM::DICompileUnitAttr cuAttr,
|
|
mlir::SymbolTable *symbolTable,
|
|
llvm::DenseSet<mlir::LLVM::DIImportedEntityAttr> &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<mlir::LLVM::DIImportedEntityAttr> &importedEntities,
|
|
llvm::StringSet<> &seenModuleNames);
|
|
std::optional<mlir::LLVM::DIImportedEntityAttr> 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<mlir::LLVM::DIModuleAttr>
|
|
getModuleAttrFromGlobalOp(fir::GlobalOp globalOp,
|
|
mlir::LLVM::DIFileAttr fileAttr,
|
|
mlir::LLVM::DIScopeAttr scope);
|
|
|
|
template <typename Op>
|
|
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<mlir::FusedLoc>(loc)) {
|
|
if (loc->findInstanceOf<mlir::FusedLocWith<fir::LocationKindAttr>>())
|
|
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<mlir::FileLineColLoc>();
|
|
|
|
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<mlir::LLVM::DIGlobalVariableAttr>
|
|
lookupDIGlobalVariable(llvm::StringRef symbolName,
|
|
mlir::SymbolTable *symbolTable) {
|
|
if (auto globalOp = symbolTable->lookup<fir::GlobalOp>(symbolName)) {
|
|
if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(globalOp.getLoc())) {
|
|
if (auto metadata = fusedLoc.getMetadata()) {
|
|
if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(metadata)) {
|
|
for (auto elem : arrayAttr) {
|
|
if (auto gvExpr =
|
|
mlir::dyn_cast<mlir::LLVM::DIGlobalVariableExpressionAttr>(
|
|
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<std::int64_t> 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<std::int64_t>(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<fir::ConvertOp>(storageOp))
|
|
storageOp = convertOp.getValue().getDefiningOp();
|
|
|
|
auto addrOfOp = mlir::dyn_cast_if_present<fir::AddrOfOp>(storageOp);
|
|
if (!addrOfOp)
|
|
return false;
|
|
|
|
mlir::SymbolRefAttr sym = addrOfOp.getSymbol();
|
|
fir::GlobalOp global =
|
|
symbolTable->lookup<fir::GlobalOp>(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<mlir::LLVM::DIExpressionElemAttr> 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 <typename Op>
|
|
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<Op, fir::cg::XDeclareOp>) {
|
|
if (auto funcOp =
|
|
declOp->template getParentOfType<mlir::func::FuncOp>()) {
|
|
if (declOp->getBlock() == &funcOp.getBody().front()) {
|
|
for (mlir::Block &block : funcOp.getBody()) {
|
|
if (auto returnOp = mlir::dyn_cast<mlir::func::ReturnOp>(
|
|
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<fir::AddrOfOp>(defOp)) {
|
|
if (auto global =
|
|
symbolTable->lookup<fir::GlobalOp>(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<mlir::LLVM::DIModuleAttr>
|
|
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<mlir::LLVM::DISubprogramAttr>(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<mlir::LLVM::DIModuleAttr> 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<mlir::omp::TargetOp>()) {
|
|
if (auto fusedLoc = llvm::dyn_cast<mlir::FusedLoc>(tOp.getLoc())) {
|
|
if (auto sp = llvm::dyn_cast<mlir::LLVM::DISubprogramAttr>(
|
|
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<mlir::FileLineColLoc>(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<mlir::StringAttr>(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<mlir::StringAttr>(fir::getSymbolAttrName());
|
|
if (bindcName)
|
|
funcName = bindcName;
|
|
}
|
|
|
|
llvm::SmallVector<mlir::LLVM::DITypeAttr> 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<fir::FortranProcedureFlagsEnum::pure>(funcOp))
|
|
subprogramFlags = subprogramFlags | mlir::LLVM::DISubprogramFlags::Pure;
|
|
|
|
if (fir::hasProcedureAttr<fir::FortranProcedureFlagsEnum::elemental>(funcOp))
|
|
subprogramFlags =
|
|
subprogramFlags | mlir::LLVM::DISubprogramFlags::Elemental;
|
|
|
|
if (fir::hasProcedureAttr<fir::FortranProcedureFlagsEnum::recursive>(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<mlir::SymbolRefAttr>(
|
|
funcOp->getAttr(fir::getHostSymbolAttrName()));
|
|
if (sym) {
|
|
if (auto func =
|
|
symbolTable->lookup<mlir::func::FuncOp>(sym.getLeafReference())) {
|
|
// Make sure that parent is processed.
|
|
handleFuncOp(func, fileAttr, cuAttr, typeGen, symbolTable);
|
|
if (auto fusedLoc =
|
|
mlir::dyn_cast_if_present<mlir::FusedLoc>(func.getLoc())) {
|
|
if (auto spAttr =
|
|
mlir::dyn_cast_if_present<mlir::LLVM::DISubprogramAttr>(
|
|
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<mlir::LLVM::DINodeAttr> 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<mlir::LLVM::DITypeAttr> 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<mlir::LLVM::DINodeAttr> opEntities;
|
|
for (mlir::LLVM::DINodeAttr N : entities) {
|
|
if (auto entity = mlir::dyn_cast<mlir::LLVM::DIImportedEntityAttr>(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<mlir::LLVM::DINodeAttr> 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<mlir::LLVM::DIImportedEntityAttr> 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<fir::DummyScopeType>(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<mlir::LLVM::DIImportedEntityAttr>
|
|
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<mlir::LLVM::DIImportedEntityAttr> &importedModules) {
|
|
// Process ONLY symbols (without renames)
|
|
if (auto onlySymbols = useOp.getOnlySymbols()) {
|
|
for (mlir::Attribute attr : *onlySymbols) {
|
|
auto symbolRef = mlir::cast<mlir::FlatSymbolRefAttr>(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<fir::UseRenameAttr>(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<mlir::LLVM::DIImportedEntityAttr> &importedModules) {
|
|
mlir::MLIRContext *context = &getContext();
|
|
llvm::SmallVector<mlir::LLVM::DINodeAttr> childDeclarations;
|
|
|
|
if (auto renames = useOp.getRenames()) {
|
|
for (auto attr : *renames) {
|
|
auto renameAttr = mlir::cast<fir::UseRenameAttr>(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<mlir::LLVM::DIImportedEntityAttr> &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<mlir::LLVM::DIImportedEntityAttr> &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<mlir::LLVM::DIImportedEntityAttr> 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<fir::UseStmtOp>())
|
|
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<mlir::DataLayout> 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<mlir::Attribute> moduleFlags;
|
|
mlir::IntegerType int32Ty = mlir::IntegerType::get(context, 32);
|
|
moduleFlags.push_back(builder.getAttr<mlir::LLVM::ModuleFlagAttr>(
|
|
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<mlir::FileLineColLoc>(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<fir::AddrOfOp>(defOp)) {
|
|
if (auto globalOp =
|
|
symbolTable.lookup<fir::GlobalOp>(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<fir::GlobalOp>())
|
|
handleGlobalOp(globalOp, fileAttr, cuAttr, typeGen, &symbolTable,
|
|
/*declOp=*/nullptr);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<mlir::Pass>
|
|
fir::createAddDebugInfoPass(fir::AddDebugInfoOptions options) {
|
|
return std::make_unique<AddDebugInfoPass>(options);
|
|
}
|