| 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/BinaryFormat/Dwarf.h" |
| 17 | #include "llvm/CodeGen/MachineFunction.h" |
| 18 | #include "llvm/CodeGen/MachineInstr.h" |
| 19 | #include "llvm/IR/DebugInfoMetadata.h" |
| 20 | #include "llvm/IR/Function.h" |
| 21 | #include "llvm/IR/GlobalVariable.h" |
| 22 | #include "llvm/MC/MCAsmInfo.h" |
| 23 | #include "llvm/MC/MCContext.h" |
| 24 | #include "llvm/MC/MCStreamer.h" |
| 25 | #include "llvm/Support/CommandLine.h" |
| 26 | #include "llvm/Support/NVPTXAddrSpace.h" |
| 27 | #include "llvm/Target/TargetMachine.h" |
| 28 | |
| 29 | using namespace llvm; |
| 30 | |
| 31 | // Command line option to control inlined_at enhancement to lineinfo support. |
| 32 | // Valid only when debuginfo emissionkind is DebugDirectivesOnly or |
| 33 | // LineTablesOnly. |
| 34 | static cl::opt<bool> LineInfoWithInlinedAt( |
| 35 | "line-info-inlined-at" , |
| 36 | cl::desc("Emit line with inlined_at enhancement for NVPTX" ), cl::init(Val: true), |
| 37 | cl::Hidden); |
| 38 | |
| 39 | NVPTXDwarfDebug::NVPTXDwarfDebug(AsmPrinter *A) : DwarfDebug(A) { |
| 40 | // PTX emits debug strings inline (no .debug_str section), does not support |
| 41 | // .debug_ranges, and uses sections as references (no temp symbols inside |
| 42 | // DWARF sections). DWARF v2 is the default for NVPTX. |
| 43 | setUseInlineStrings(true); |
| 44 | setUseRangesSection(false); |
| 45 | setUseSectionsAsReferences(true); |
| 46 | Asm->OutStreamer->getContext().setDwarfVersion(2); |
| 47 | } |
| 48 | |
| 49 | /// NVPTX-specific source line recording with inlined_at support. |
| 50 | /// |
| 51 | /// Why this exists: |
| 52 | /// NVPTX supports an "enhanced lineinfo" mode where inlining context is carried |
| 53 | /// via line-table directives, rather than full DWARF DIEs. This is conceptually |
| 54 | /// similar to proposals[1] for richer DWARF line tables that carry inline call |
| 55 | /// context and callee identity in the line table. NVPTX implements this via |
| 56 | /// target-specific `.loc` extensions in the PTX ISA[3]. |
| 57 | /// |
| 58 | /// How it impacts PTX assembly generation: |
| 59 | /// - When enabled (PTX ISA >= 7.2 + line-tables-only / debug-directives-only), |
| 60 | /// we emit multiple consecutive `.loc` directives for a single inlined |
| 61 | /// instruction: the instruction's own location and its `inlined_at` parent |
| 62 | /// chain. |
| 63 | /// - During emission we use `MCStreamer::emitDwarfLocDirectiveWithInlinedAt` to |
| 64 | /// emit an enhanced `.loc` directive[3] that carries the extra |
| 65 | /// `function_name` and `inlined_at` operands in the PTX assembly stream. |
| 66 | /// |
| 67 | /// Example (conceptual PTX `.loc` sequence for an inlined callsite): |
| 68 | /// .loc 1 16 3 // caller location |
| 69 | /// .loc 1 5 3, function_name $L__info_stringN, inlined_at 1 16 3 |
| 70 | /// // inlined callee location |
| 71 | /// Here, $L__info_stringN is a label (or label+immediate) referring into |
| 72 | /// `.debug_str`. |
| 73 | /// |
| 74 | /// How this impacts DWARF : |
| 75 | /// DWARF generation tools that consume this PTX(e.g. ptxas assembler) can use |
| 76 | /// the `inlined_at` and `function_name` operands to extend the DWARF v2 |
| 77 | /// line table information. |
| 78 | /// This adds: |
| 79 | /// - a `context` column[2]: the `inlined_at <file> <line> <col>` information |
| 80 | /// populates an inlining "context" (a reference to the parent/callsite row) |
| 81 | /// enabling reconstruction of inline call chains from the line table. |
| 82 | /// - a `function_name` column[2]: the `.loc ... function_name <sym>` identifies |
| 83 | /// the inlined callee associated with a non-zero context. |
| 84 | /// |
| 85 | /// References: |
| 86 | /// - [1] DWARF line tables / Two-Level Line Tables: |
| 87 | /// https://wiki.dwarfstd.org/TwoLevelLineTables.md |
| 88 | /// - [2] DWARF issue tracking for Two-Level Line Tables: |
| 89 | /// https://dwarfstd.org/issues/140906.1.html |
| 90 | /// - [3] NVIDIA PTX ISA `.loc` (debugging directives; PTX ISA 7.2+): |
| 91 | /// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#debugging-directives-loc |
| 92 | void NVPTXDwarfDebug::recordTargetSourceLine(const DebugLoc &DL, |
| 93 | unsigned Flags) { |
| 94 | // Maintain a work list of .loc to be emitted. If we are emitting the |
| 95 | // inlined_at directive, we might need to emit additional .loc prior |
| 96 | // to it for the location contained in the inlined_at. |
| 97 | SmallVector<const DILocation *, 8> WorkList; |
| 98 | SmallDenseSet<const DILocation *, 8> WorkListSet; |
| 99 | const DILocation *EmitLoc = DL.get(); |
| 100 | |
| 101 | if (!EmitLoc) |
| 102 | return; |
| 103 | |
| 104 | const MachineFunction *MF = Asm->MF; |
| 105 | if (!MF) |
| 106 | return; |
| 107 | |
| 108 | const DISubprogram *SP = MF->getFunction().getSubprogram(); |
| 109 | const NVPTXSubtarget &STI = MF->getSubtarget<NVPTXSubtarget>(); |
| 110 | const bool EnhancedLineinfo = |
| 111 | LineInfoWithInlinedAt && (STI.getPTXVersion() >= 72) && SP && |
| 112 | (SP->getUnit()->isDebugDirectivesOnly() || |
| 113 | SP->getUnit()->getEmissionKind() == DICompileUnit::LineTablesOnly); |
| 114 | |
| 115 | while (EmitLoc) { |
| 116 | // Get the scope for the current location. |
| 117 | const DIScope *Scope = EmitLoc->getScope(); |
| 118 | if (!Scope) |
| 119 | break; // scope is null, we are done. |
| 120 | |
| 121 | // Check if this loc is already in work list, if so, we are done. |
| 122 | if (WorkListSet.contains(V: EmitLoc)) |
| 123 | break; |
| 124 | |
| 125 | // Add this location to the work list. |
| 126 | WorkList.push_back(Elt: EmitLoc); |
| 127 | WorkListSet.insert(V: EmitLoc); |
| 128 | |
| 129 | if (!EnhancedLineinfo) // No enhanced lineinfo, we are done. |
| 130 | break; |
| 131 | |
| 132 | const DILocation *IA = EmitLoc->getInlinedAt(); |
| 133 | // Check if this has inlined_at information, and if the parent location |
| 134 | // has not yet been emitted. If already emitted, we don't need to |
| 135 | // re-emit the parent chain. |
| 136 | if (IA && !EmittedInlinedAtLocs.contains(V: IA)) |
| 137 | EmitLoc = IA; |
| 138 | else // We are done. |
| 139 | break; |
| 140 | } |
| 141 | |
| 142 | const unsigned CUID = Asm->OutStreamer->getContext().getDwarfCompileUnitID(); |
| 143 | // Traverse the work list, and emit .loc. |
| 144 | while (!WorkList.empty()) { |
| 145 | const DILocation *Current = WorkList.pop_back_val(); |
| 146 | const DIScope *Scope = Current->getScope(); |
| 147 | |
| 148 | if (!Scope) |
| 149 | llvm_unreachable("we shouldn't be here for null scope" ); |
| 150 | |
| 151 | const DILocation *InlinedAt = Current->getInlinedAt(); |
| 152 | StringRef Fn = Scope->getFilename(); |
| 153 | const unsigned Line = Current->getLine(); |
| 154 | const unsigned Col = Current->getColumn(); |
| 155 | unsigned Discriminator = 0; |
| 156 | if (Line != 0 && getDwarfVersion() >= 4) |
| 157 | if (const DILexicalBlockFile *LBF = dyn_cast<DILexicalBlockFile>(Val: Scope)) |
| 158 | Discriminator = LBF->getDiscriminator(); |
| 159 | |
| 160 | const unsigned FileNo = static_cast<DwarfCompileUnit &>(*getUnits()[CUID]) |
| 161 | .getOrCreateSourceID(File: Scope->getFile()); |
| 162 | |
| 163 | if (EnhancedLineinfo && InlinedAt) { |
| 164 | const unsigned FileIA = static_cast<DwarfCompileUnit &>(*getUnits()[CUID]) |
| 165 | .getOrCreateSourceID(File: InlinedAt->getFile()); |
| 166 | const DISubprogram *SubProgram = getDISubprogram(Scope: Current->getScope()); |
| 167 | DwarfStringPoolEntryRef Entry = InfoHolder.getStringPool().getEntry( |
| 168 | Asm&: *Asm, Str: SubProgram->getLinkageName()); |
| 169 | Asm->OutStreamer->emitDwarfLocDirectiveWithInlinedAt( |
| 170 | FileNo, Line, Column: Col, FileIA, LineIA: InlinedAt->getLine(), |
| 171 | ColumnIA: InlinedAt->getColumn(), Sym: Entry.getSymbol(), Flags, Isa: 0, Discriminator, |
| 172 | FileName: Fn); |
| 173 | } else { |
| 174 | Asm->OutStreamer->emitDwarfLocDirective(FileNo, Line, Column: Col, Flags, Isa: 0, |
| 175 | Discriminator, FileName: Fn); |
| 176 | } |
| 177 | // Mark this location as emitted so we don't re-emit the parent chain |
| 178 | // for subsequent instructions that share the same inlined_at parent. |
| 179 | if (EnhancedLineinfo) |
| 180 | EmittedInlinedAtLocs.insert(V: Current); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | /// NVPTX-specific debug info initialization. |
| 185 | void NVPTXDwarfDebug::initializeTargetDebugInfo(const MachineFunction &MF) { |
| 186 | EmittedInlinedAtLocs.clear(); |
| 187 | } |
| 188 | |
| 189 | // PTX does not support subtracting labels from the code section in the |
| 190 | // debug_loc section. To work around this, the NVPTX backend needs the |
| 191 | // compile unit to have no low_pc in order to have a zero base_address |
| 192 | // when handling debug_loc in cuda-gdb. |
| 193 | bool NVPTXDwarfDebug::shouldAttachCompileUnitRanges() const { |
| 194 | return !tuneForGDB(); |
| 195 | } |
| 196 | |
| 197 | // Same label-subtraction limitation as above: cuda-gdb doesn't handle |
| 198 | // setting a per-variable base to zero, so we emit labels with no base |
| 199 | // while having no compile unit low_pc. |
| 200 | bool NVPTXDwarfDebug::shouldResetBaseAddress(const MCSection &Section) const { |
| 201 | return tuneForGDB(); |
| 202 | } |
| 203 | |
| 204 | static unsigned translateToNVVMDWARFAddrSpace(unsigned AddrSpace) { |
| 205 | switch (AddrSpace) { |
| 206 | case NVPTXAS::ADDRESS_SPACE_GENERIC: |
| 207 | return NVPTXAS::DWARF_ADDR_generic_space; |
| 208 | case NVPTXAS::ADDRESS_SPACE_GLOBAL: |
| 209 | return NVPTXAS::DWARF_ADDR_global_space; |
| 210 | case NVPTXAS::ADDRESS_SPACE_SHARED: |
| 211 | return NVPTXAS::DWARF_ADDR_shared_space; |
| 212 | case NVPTXAS::ADDRESS_SPACE_CONST: |
| 213 | return NVPTXAS::DWARF_ADDR_const_space; |
| 214 | case NVPTXAS::ADDRESS_SPACE_LOCAL: |
| 215 | return NVPTXAS::DWARF_ADDR_local_space; |
| 216 | default: |
| 217 | llvm_unreachable( |
| 218 | "Cannot translate unknown address space to DWARF address space" ); |
| 219 | return AddrSpace; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | // cuda-gdb requires DW_AT_address_class on variable DIEs. The address space |
| 224 | // is encoded in the DIExpression as a DW_OP_constu <DWARF Address Space> |
| 225 | // DW_OP_swap DW_OP_xderef sequence. We strip that sequence from the |
| 226 | // expression and return the address space so the caller can emit |
| 227 | // DW_AT_address_class separately. |
| 228 | const DIExpression *NVPTXDwarfDebug::adjustExpressionForTarget( |
| 229 | const DIExpression *Expr, std::optional<unsigned> &TargetAddrSpace) const { |
| 230 | if (!tuneForGDB()) |
| 231 | return Expr; |
| 232 | unsigned LocalAddrSpace; |
| 233 | const DIExpression *NewExpr = |
| 234 | DIExpression::extractAddressClass(Expr, AddrClass&: LocalAddrSpace); |
| 235 | if (NewExpr != Expr) { |
| 236 | TargetAddrSpace = LocalAddrSpace; |
| 237 | return NewExpr; |
| 238 | } |
| 239 | return Expr; |
| 240 | } |
| 241 | |
| 242 | // Emit DW_AT_address_class for cuda-gdb. See NVPTXAS::DWARF_AddressSpace. |
| 243 | // |
| 244 | // The address class depends on the variable's storage kind: |
| 245 | // Global: from the expression (if encoded) or the IR address space |
| 246 | // Register: DWARF_ADDR_reg_space (no expression means register location) |
| 247 | // FrameIndex: from the expression (if encoded) or DWARF_ADDR_local_space |
| 248 | void NVPTXDwarfDebug::addTargetVariableAttributes( |
| 249 | DwarfCompileUnit &CU, DIE &Die, std::optional<unsigned> TargetAddrSpace, |
| 250 | VariableLocationKind VarLocKind, const GlobalVariable *GV) const { |
| 251 | if (!tuneForGDB()) |
| 252 | return; |
| 253 | |
| 254 | unsigned DefaultAddrSpace = NVPTXAS::DWARF_ADDR_global_space; |
| 255 | switch (VarLocKind) { |
| 256 | case VariableLocationKind::Global: |
| 257 | if (!TargetAddrSpace && GV) |
| 258 | TargetAddrSpace = |
| 259 | translateToNVVMDWARFAddrSpace(AddrSpace: GV->getType()->getAddressSpace()); |
| 260 | DefaultAddrSpace = NVPTXAS::DWARF_ADDR_global_space; |
| 261 | break; |
| 262 | case VariableLocationKind::Register: |
| 263 | DefaultAddrSpace = NVPTXAS::DWARF_ADDR_reg_space; |
| 264 | break; |
| 265 | case VariableLocationKind::FrameIndex: |
| 266 | DefaultAddrSpace = NVPTXAS::DWARF_ADDR_local_space; |
| 267 | break; |
| 268 | } |
| 269 | |
| 270 | CU.addUInt(Die, Attribute: dwarf::DW_AT_address_class, Form: dwarf::DW_FORM_data1, |
| 271 | Integer: TargetAddrSpace.value_or(u&: DefaultAddrSpace)); |
| 272 | } |
| 273 | |