1//===-- NVPTXDwarfDebug.cpp - NVPTX DwarfDebug Implementation ------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements helper functions for NVPTX-specific debug information
10// processing.
11//
12//===----------------------------------------------------------------------===//
13
14#include "NVPTXDwarfDebug.h"
15#include "NVPTXSubtarget.h"
16#include "llvm/CodeGen/MachineFunction.h"
17#include "llvm/CodeGen/MachineInstr.h"
18#include "llvm/IR/DebugInfoMetadata.h"
19#include "llvm/IR/Function.h"
20#include "llvm/MC/MCAsmInfo.h"
21#include "llvm/MC/MCContext.h"
22#include "llvm/MC/MCStreamer.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Target/TargetMachine.h"
25
26using namespace llvm;
27
28// Command line option to control inlined_at enhancement to lineinfo support.
29// Valid only when debuginfo emissionkind is DebugDirectivesOnly or
30// LineTablesOnly.
31static cl::opt<bool> LineInfoWithInlinedAt(
32 "line-info-inlined-at",
33 cl::desc("Emit line with inlined_at enhancement for NVPTX"), cl::init(Val: true),
34 cl::Hidden);
35
36NVPTXDwarfDebug::NVPTXDwarfDebug(AsmPrinter *A) : DwarfDebug(A) {}
37
38/// NVPTX-specific source line recording with inlined_at support.
39///
40/// Why this exists:
41/// NVPTX supports an "enhanced lineinfo" mode where inlining context is carried
42/// via line-table directives, rather than full DWARF DIEs. This is conceptually
43/// similar to proposals[1] for richer DWARF line tables that carry inline call
44/// context and callee identity in the line table. NVPTX implements this via
45/// target-specific `.loc` extensions in the PTX ISA[3].
46///
47/// How it impacts PTX assembly generation:
48/// - When enabled (PTX ISA >= 7.2 + line-tables-only / debug-directives-only),
49/// we emit multiple consecutive `.loc` directives for a single inlined
50/// instruction: the instruction's own location and its `inlined_at` parent
51/// chain.
52/// - During emission we use `MCStreamer::emitDwarfLocDirectiveWithInlinedAt` to
53/// emit an enhanced `.loc` directive[3] that carries the extra
54/// `function_name` and `inlined_at` operands in the PTX assembly stream.
55///
56/// Example (conceptual PTX `.loc` sequence for an inlined callsite):
57/// .loc 1 16 3 // caller location
58/// .loc 1 5 3, function_name $L__info_stringN, inlined_at 1 16 3
59/// // inlined callee location
60/// Here, $L__info_stringN is a label (or label+immediate) referring into
61/// `.debug_str`.
62///
63/// How this impacts DWARF :
64/// DWARF generation tools that consume this PTX(e.g. ptxas assembler) can use
65/// the `inlined_at` and `function_name` operands to extend the DWARF v2
66/// line table information.
67/// This adds:
68/// - a `context` column[2]: the `inlined_at <file> <line> <col>` information
69/// populates an inlining "context" (a reference to the parent/callsite row)
70/// enabling reconstruction of inline call chains from the line table.
71/// - a `function_name` column[2]: the `.loc ... function_name <sym>` identifies
72/// the inlined callee associated with a non-zero context.
73///
74/// References:
75/// - [1] DWARF line tables / Two-Level Line Tables:
76/// https://wiki.dwarfstd.org/TwoLevelLineTables.md
77/// - [2] DWARF issue tracking for Two-Level Line Tables:
78/// https://dwarfstd.org/issues/140906.1.html
79/// - [3] NVIDIA PTX ISA `.loc` (debugging directives; PTX ISA 7.2+):
80/// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#debugging-directives-loc
81void NVPTXDwarfDebug::recordTargetSourceLine(const DebugLoc &DL,
82 unsigned Flags) {
83 // Maintain a work list of .loc to be emitted. If we are emitting the
84 // inlined_at directive, we might need to emit additional .loc prior
85 // to it for the location contained in the inlined_at.
86 SmallVector<const DILocation *, 8> WorkList;
87 SmallDenseSet<const DILocation *, 8> WorkListSet;
88 const DILocation *EmitLoc = DL.get();
89
90 if (!EmitLoc)
91 return;
92
93 const MachineFunction *MF = Asm->MF;
94 if (!MF)
95 return;
96
97 const DISubprogram *SP = MF->getFunction().getSubprogram();
98 const NVPTXSubtarget &STI = MF->getSubtarget<NVPTXSubtarget>();
99 const bool EnhancedLineinfo =
100 LineInfoWithInlinedAt && (STI.getPTXVersion() >= 72) && SP &&
101 (SP->getUnit()->isDebugDirectivesOnly() ||
102 SP->getUnit()->getEmissionKind() == DICompileUnit::LineTablesOnly);
103
104 while (EmitLoc) {
105 // Get the scope for the current location.
106 const DIScope *Scope = EmitLoc->getScope();
107 if (!Scope)
108 break; // scope is null, we are done.
109
110 // Check if this loc is already in work list, if so, we are done.
111 if (WorkListSet.contains(V: EmitLoc))
112 break;
113
114 // Add this location to the work list.
115 WorkList.push_back(Elt: EmitLoc);
116 WorkListSet.insert(V: EmitLoc);
117
118 if (!EnhancedLineinfo) // No enhanced lineinfo, we are done.
119 break;
120
121 const DILocation *IA = EmitLoc->getInlinedAt();
122 // Check if this has inlined_at information, and if the parent location
123 // has not yet been emitted. If already emitted, we don't need to
124 // re-emit the parent chain.
125 if (IA && !EmittedInlinedAtLocs.contains(V: IA))
126 EmitLoc = IA;
127 else // We are done.
128 break;
129 }
130
131 const unsigned CUID = Asm->OutStreamer->getContext().getDwarfCompileUnitID();
132 // Traverse the work list, and emit .loc.
133 while (!WorkList.empty()) {
134 const DILocation *Current = WorkList.pop_back_val();
135 const DIScope *Scope = Current->getScope();
136
137 if (!Scope)
138 llvm_unreachable("we shouldn't be here for null scope");
139
140 const DILocation *InlinedAt = Current->getInlinedAt();
141 StringRef Fn = Scope->getFilename();
142 const unsigned Line = Current->getLine();
143 const unsigned Col = Current->getColumn();
144 unsigned Discriminator = 0;
145 if (Line != 0 && getDwarfVersion() >= 4)
146 if (const DILexicalBlockFile *LBF = dyn_cast<DILexicalBlockFile>(Val: Scope))
147 Discriminator = LBF->getDiscriminator();
148
149 const unsigned FileNo = static_cast<DwarfCompileUnit &>(*getUnits()[CUID])
150 .getOrCreateSourceID(File: Scope->getFile());
151
152 if (EnhancedLineinfo && InlinedAt) {
153 const unsigned FileIA = static_cast<DwarfCompileUnit &>(*getUnits()[CUID])
154 .getOrCreateSourceID(File: InlinedAt->getFile());
155 const DISubprogram *SubProgram = getDISubprogram(Scope: Current->getScope());
156 DwarfStringPoolEntryRef Entry = InfoHolder.getStringPool().getEntry(
157 Asm&: *Asm, Str: SubProgram->getLinkageName());
158 Asm->OutStreamer->emitDwarfLocDirectiveWithInlinedAt(
159 FileNo, Line, Column: Col, FileIA, LineIA: InlinedAt->getLine(),
160 ColumnIA: InlinedAt->getColumn(), Sym: Entry.getSymbol(), Flags, Isa: 0, Discriminator,
161 FileName: Fn);
162 } else {
163 Asm->OutStreamer->emitDwarfLocDirective(FileNo, Line, Column: Col, Flags, Isa: 0,
164 Discriminator, FileName: Fn);
165 }
166 // Mark this location as emitted so we don't re-emit the parent chain
167 // for subsequent instructions that share the same inlined_at parent.
168 if (EnhancedLineinfo)
169 EmittedInlinedAtLocs.insert(V: Current);
170 }
171}
172
173/// NVPTX-specific debug info initialization.
174void NVPTXDwarfDebug::initializeTargetDebugInfo(const MachineFunction &MF) {
175 // Clear the set of emitted inlined_at locations for each new function.
176 EmittedInlinedAtLocs.clear();
177}
178