blob: 283641d1bf516cec5784ab23897a186366bd804c [file] [edit]
//===-- NVPTXDwarfDebug.cpp - NVPTX DwarfDebug Implementation ------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements helper functions for NVPTX-specific debug information
// processing.
//
//===----------------------------------------------------------------------===//
#include "NVPTXDwarfDebug.h"
#include "NVPTXSubtarget.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/NVPTXAddrSpace.h"
#include "llvm/Target/TargetMachine.h"
using namespace llvm;
// Command line option to control inlined_at enhancement to lineinfo support.
// Valid only when debuginfo emissionkind is DebugDirectivesOnly or
// LineTablesOnly.
static cl::opt<bool> LineInfoWithInlinedAt(
"line-info-inlined-at",
cl::desc("Emit line with inlined_at enhancement for NVPTX"), cl::init(true),
cl::Hidden);
NVPTXDwarfDebug::NVPTXDwarfDebug(AsmPrinter *A) : DwarfDebug(A) {
// PTX emits debug strings inline (no .debug_str section), does not support
// .debug_ranges, and uses sections as references (no temp symbols inside
// DWARF sections). DWARF v2 is the default for NVPTX and does not support
// accelerator tables.
setUseInlineStrings(true);
setUseRangesSection(false);
setUseSectionsAsReferences(true);
Asm->OutStreamer->getContext().setDwarfVersion(2);
setTheAccelTableKind(AccelTableKind::None);
}
MCSymbol *NVPTXDwarfDebug::getOrCreateFuncNameSymbol(StringRef LinkageName) {
return InfoHolder.getStringPool().getEntry(*Asm, LinkageName).getSymbol();
}
bool NVPTXDwarfDebug::isEnhancedLineinfo(const MachineFunction &MF) const {
const DISubprogram *SP = MF.getFunction().getSubprogram();
const NVPTXSubtarget &STI = MF.getSubtarget<NVPTXSubtarget>();
return LineInfoWithInlinedAt && (STI.getPTXVersion() >= 72) && SP &&
(SP->getUnit()->isDebugDirectivesOnly() ||
SP->getUnit()->getEmissionKind() == DICompileUnit::LineTablesOnly);
}
/// NVPTX-specific source line recording with inlined_at support.
///
/// Why this exists:
/// NVPTX supports an "enhanced lineinfo" mode where inlining context is carried
/// via line-table directives, rather than full DWARF DIEs. This is conceptually
/// similar to proposals[1] for richer DWARF line tables that carry inline call
/// context and callee identity in the line table. NVPTX implements this via
/// target-specific `.loc` extensions in the PTX ISA[3].
///
/// How it impacts PTX assembly generation:
/// - When enabled (PTX ISA >= 7.2 + line-tables-only / debug-directives-only),
/// we emit multiple consecutive `.loc` directives for a single inlined
/// instruction: the instruction's own location and its `inlined_at` parent
/// chain.
/// - During emission we use `MCStreamer::emitDwarfLocDirectiveWithInlinedAt` to
/// emit an enhanced `.loc` directive[3] that carries the extra
/// `function_name` and `inlined_at` operands in the PTX assembly stream.
///
/// Example (conceptual PTX `.loc` sequence for an inlined callsite):
/// .loc 1 16 3 // caller location
/// .loc 1 5 3, function_name $L__info_stringN, inlined_at 1 16 3
/// // inlined callee location
/// Here, $L__info_stringN is a label (or label+immediate) referring into
/// `.debug_str`.
///
/// How this impacts DWARF :
/// DWARF generation tools that consume this PTX(e.g. ptxas assembler) can use
/// the `inlined_at` and `function_name` operands to extend the DWARF v2
/// line table information.
/// This adds:
/// - a `context` column[2]: the `inlined_at <file> <line> <col>` information
/// populates an inlining "context" (a reference to the parent/callsite row)
/// enabling reconstruction of inline call chains from the line table.
/// - a `function_name` column[2]: the `.loc ... function_name <sym>` identifies
/// the inlined callee associated with a non-zero context.
///
/// References:
/// - [1] DWARF line tables / Two-Level Line Tables:
/// https://wiki.dwarfstd.org/TwoLevelLineTables.md
/// - [2] DWARF issue tracking for Two-Level Line Tables:
/// https://dwarfstd.org/issues/140906.1.html
/// - [3] NVIDIA PTX ISA `.loc` (debugging directives; PTX ISA 7.2+):
/// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#debugging-directives-loc
void NVPTXDwarfDebug::recordTargetSourceLine(const DebugLoc &DL,
unsigned Flags) {
// Maintain a work list of .loc to be emitted. If we are emitting the
// inlined_at directive, we might need to emit additional .loc prior
// to it for the location contained in the inlined_at.
SmallVector<const DILocation *, 8> WorkList;
SmallDenseSet<const DILocation *, 8> WorkListSet;
const DILocation *EmitLoc = DL.get();
if (!EmitLoc)
return;
const MachineFunction *MF = Asm->MF;
if (!MF)
return;
const bool EnhancedLineinfo = isEnhancedLineinfo(*MF);
while (EmitLoc) {
// Get the scope for the current location.
const DIScope *Scope = EmitLoc->getScope();
if (!Scope)
break; // scope is null, we are done.
// Check if this loc is already in work list, if so, we are done.
if (WorkListSet.contains(EmitLoc))
break;
// Add this location to the work list.
WorkList.push_back(EmitLoc);
WorkListSet.insert(EmitLoc);
if (!EnhancedLineinfo) // No enhanced lineinfo, we are done.
break;
const DILocation *IA = EmitLoc->getInlinedAt();
// Check if this has inlined_at information, and if the parent location
// has not yet been emitted. If already emitted, we don't need to
// re-emit the parent chain.
if (IA && !EmittedInlinedAtLocs.contains(IA))
EmitLoc = IA;
else // We are done.
break;
}
const unsigned CUID = Asm->OutStreamer->getContext().getDwarfCompileUnitID();
// Traverse the work list, and emit .loc.
while (!WorkList.empty()) {
const DILocation *Current = WorkList.pop_back_val();
const DIScope *Scope = Current->getScope();
if (!Scope)
llvm_unreachable("we shouldn't be here for null scope");
const DILocation *InlinedAt = Current->getInlinedAt();
StringRef Fn = Scope->getFilename();
const unsigned Line = Current->getLine();
const unsigned Col = Current->getColumn();
unsigned Discriminator = 0;
if (Line != 0 && getDwarfVersion() >= 4)
if (const DILexicalBlockFile *LBF = dyn_cast<DILexicalBlockFile>(Scope))
Discriminator = LBF->getDiscriminator();
const unsigned FileNo = static_cast<DwarfCompileUnit &>(*getUnits()[CUID])
.getOrCreateSourceID(Scope->getFile());
if (EnhancedLineinfo && InlinedAt) {
const unsigned FileIA = static_cast<DwarfCompileUnit &>(*getUnits()[CUID])
.getOrCreateSourceID(InlinedAt->getFile());
const DISubprogram *SubProgram = getDISubprogram(Current->getScope());
DwarfStringPoolEntryRef Entry = InfoHolder.getStringPool().getEntry(
*Asm, SubProgram->getLinkageName());
Asm->OutStreamer->emitDwarfLocDirectiveWithInlinedAt(
FileNo, Line, Col, FileIA, InlinedAt->getLine(),
InlinedAt->getColumn(), Entry.getSymbol(), Flags, 0, Discriminator,
Fn);
} else {
Asm->OutStreamer->emitDwarfLocDirective(FileNo, Line, Col, Flags, 0,
Discriminator, Fn);
}
// Mark this location as emitted so we don't re-emit the parent chain
// for subsequent instructions that share the same inlined_at parent.
if (EnhancedLineinfo)
EmittedInlinedAtLocs.insert(Current);
}
}
/// NVPTX-specific debug info initialization.
void NVPTXDwarfDebug::initializeTargetDebugInfo(const MachineFunction &MF) {
EmittedInlinedAtLocs.clear();
}
// PTX does not support subtracting labels from the code section in the
// debug_loc section. To work around this, the NVPTX backend needs the
// compile unit to have no low_pc in order to have a zero base_address
// when handling debug_loc in cuda-gdb.
bool NVPTXDwarfDebug::shouldAttachCompileUnitRanges() const {
return !tuneForGDB();
}
// Same label-subtraction limitation as above: cuda-gdb doesn't handle
// setting a per-variable base to zero, so we emit labels with no base
// while having no compile unit low_pc.
bool NVPTXDwarfDebug::shouldResetBaseAddress(const MCSection &Section) const {
return tuneForGDB();
}
static unsigned translateToNVVMDWARFAddrSpace(unsigned AddrSpace) {
switch (AddrSpace) {
case NVPTXAS::ADDRESS_SPACE_GENERIC:
return NVPTXAS::DWARF_ADDR_generic_space;
case NVPTXAS::ADDRESS_SPACE_GLOBAL:
return NVPTXAS::DWARF_ADDR_global_space;
case NVPTXAS::ADDRESS_SPACE_SHARED:
return NVPTXAS::DWARF_ADDR_shared_space;
case NVPTXAS::ADDRESS_SPACE_CONST:
return NVPTXAS::DWARF_ADDR_const_space;
case NVPTXAS::ADDRESS_SPACE_LOCAL:
return NVPTXAS::DWARF_ADDR_local_space;
default:
llvm_unreachable(
"Cannot translate unknown address space to DWARF address space");
return AddrSpace;
}
}
// cuda-gdb requires DW_AT_address_class on variable DIEs. The address space
// is encoded in the DIExpression as a DW_OP_constu <DWARF Address Space>
// DW_OP_swap DW_OP_xderef sequence. We strip that sequence from the
// expression and return the address space so the caller can emit
// DW_AT_address_class separately.
const DIExpression *NVPTXDwarfDebug::adjustExpressionForTarget(
const DIExpression *Expr, std::optional<unsigned> &TargetAddrSpace) const {
if (!tuneForGDB())
return Expr;
unsigned LocalAddrSpace;
const DIExpression *NewExpr =
DIExpression::extractAddressClass(Expr, LocalAddrSpace);
if (NewExpr != Expr) {
TargetAddrSpace = LocalAddrSpace;
return NewExpr;
}
return Expr;
}
// Emit DW_AT_address_class for cuda-gdb. See NVPTXAS::DWARF_AddressSpace.
//
// The address class depends on the variable's storage kind:
// Global: from the expression (if encoded) or the IR address space
// Register: DWARF_ADDR_reg_space (no expression means register location)
// FrameIndex: from the expression (if encoded) or DWARF_ADDR_local_space
void NVPTXDwarfDebug::addTargetVariableAttributes(
DwarfCompileUnit &CU, DIE &Die, std::optional<unsigned> TargetAddrSpace,
VariableLocationKind VarLocKind, const GlobalVariable *GV) const {
if (!tuneForGDB())
return;
unsigned DefaultAddrSpace = NVPTXAS::DWARF_ADDR_global_space;
switch (VarLocKind) {
case VariableLocationKind::Global:
if (!TargetAddrSpace && GV)
TargetAddrSpace =
translateToNVVMDWARFAddrSpace(GV->getType()->getAddressSpace());
DefaultAddrSpace = NVPTXAS::DWARF_ADDR_global_space;
break;
case VariableLocationKind::Register:
DefaultAddrSpace = NVPTXAS::DWARF_ADDR_reg_space;
break;
case VariableLocationKind::FrameIndex:
DefaultAddrSpace = NVPTXAS::DWARF_ADDR_local_space;
break;
}
CU.addUInt(Die, dwarf::DW_AT_address_class, dwarf::DW_FORM_data1,
TargetAddrSpace.value_or(DefaultAddrSpace));
}
void NVPTXDwarfDebug::finishTargetUnitAttributes(const DICompileUnit &DIUnit,
DwarfCompileUnit &NewCU) {
uint16_t Dialect = DIUnit.getSourceLanguage().getDialect();
// A zero dialect means "no dialect specified"; nothing to emit. This field
// should have already been range-checked by the assembly parser, IR verifier,
// and bitcode reader.
assert(Dialect <= dwarf::DW_LLVM_LANG_DIALECT_max &&
"Invalid or unsupported NVPTX language dialect.");
if (Dialect == 0)
return;
NewCU.addUInt(NewCU.getUnitDie(), dwarf::DW_AT_LLVM_language_dialect,
dwarf::DW_FORM_data1, Dialect);
}