| 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 | |
| 26 | using 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. |
| 31 | static 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 | |
| 36 | NVPTXDwarfDebug::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 |
| 81 | void 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. |
| 174 | void NVPTXDwarfDebug::initializeTargetDebugInfo(const MachineFunction &MF) { |
| 175 | // Clear the set of emitted inlined_at locations for each new function. |
| 176 | EmittedInlinedAtLocs.clear(); |
| 177 | } |
| 178 | |