Skip to content

Commit f7052da

Browse files
committed
[DWARF] Emit DW_AT_call_pc for tail calls
Record the address of a tail-calling branch instruction within its call site entry using DW_AT_call_pc. This allows a debugger to determine the address to use when creating aritificial frames. This creates an extra attribute + relocation at tail call sites, which constitute 3-5% of all call sites in xnu/clang respectively. rdar://60307600 Differential Revision: https://reviews.llvm.org/D76336
1 parent c5f4b72 commit f7052da

File tree

11 files changed

+129
-32
lines changed

11 files changed

+129
-32
lines changed

llvm/include/llvm/DWARFLinker/DWARFLinker.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,9 @@ class DWARFLinker {
576576
/// Value of DW_AT_call_return_pc in the input DIE
577577
uint64_t OrigCallReturnPc = 0;
578578

579+
/// Value of DW_AT_call_pc in the input DIE
580+
uint64_t OrigCallPc = 0;
581+
579582
/// Offset to apply to PC addresses inside a function.
580583
int64_t PCOffset = 0;
581584

llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -925,13 +925,12 @@ void DwarfCompileUnit::constructAbstractSubprogramScopeDIE(
925925
ContextCU->addDIEEntry(*AbsDef, dwarf::DW_AT_object_pointer, *ObjectPointer);
926926
}
927927

928-
/// Whether to use the GNU analog for a DWARF5 tag, attribute, or location atom.
929-
static bool useGNUAnalogForDwarf5Feature(DwarfDebug *DD) {
928+
bool DwarfCompileUnit::useGNUAnalogForDwarf5Feature() const {
930929
return DD->getDwarfVersion() == 4 && DD->tuneForGDB();
931930
}
932931

933932
dwarf::Tag DwarfCompileUnit::getDwarf5OrGNUTag(dwarf::Tag Tag) const {
934-
if (!useGNUAnalogForDwarf5Feature(DD))
933+
if (!useGNUAnalogForDwarf5Feature())
935934
return Tag;
936935
switch (Tag) {
937936
case dwarf::DW_TAG_call_site:
@@ -945,7 +944,7 @@ dwarf::Tag DwarfCompileUnit::getDwarf5OrGNUTag(dwarf::Tag Tag) const {
945944

946945
dwarf::Attribute
947946
DwarfCompileUnit::getDwarf5OrGNUAttr(dwarf::Attribute Attr) const {
948-
if (!useGNUAnalogForDwarf5Feature(DD))
947+
if (!useGNUAnalogForDwarf5Feature())
949948
return Attr;
950949
switch (Attr) {
951950
case dwarf::DW_AT_call_all_calls:
@@ -967,7 +966,7 @@ DwarfCompileUnit::getDwarf5OrGNUAttr(dwarf::Attribute Attr) const {
967966

968967
dwarf::LocationAtom
969968
DwarfCompileUnit::getDwarf5OrGNULocationAtom(dwarf::LocationAtom Loc) const {
970-
if (!useGNUAnalogForDwarf5Feature(DD))
969+
if (!useGNUAnalogForDwarf5Feature())
971970
return Loc;
972971
switch (Loc) {
973972
case dwarf::DW_OP_entry_value:
@@ -981,6 +980,7 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(DIE &ScopeDIE,
981980
DIE *CalleeDIE,
982981
bool IsTail,
983982
const MCSymbol *PCAddr,
983+
const MCSymbol *CallAddr,
984984
unsigned CallReg) {
985985
// Insert a call site entry DIE within ScopeDIE.
986986
DIE &CallSiteDIE = createAndAddDIE(getDwarf5OrGNUTag(dwarf::DW_TAG_call_site),
@@ -996,16 +996,33 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(DIE &ScopeDIE,
996996
*CalleeDIE);
997997
}
998998

999-
if (IsTail)
999+
if (IsTail) {
10001000
// Attach DW_AT_call_tail_call to tail calls for standards compliance.
10011001
addFlag(CallSiteDIE, getDwarf5OrGNUAttr(dwarf::DW_AT_call_tail_call));
10021002

1003+
// Attach the address of the branch instruction to allow the debugger to
1004+
// show where the tail call occurred. This attribute has no GNU analog.
1005+
//
1006+
// GDB works backwards from non-standard usage of DW_AT_low_pc (in DWARF4
1007+
// mode -- equivalently, in DWARF5 mode, DW_AT_call_return_pc) at tail-call
1008+
// site entries to figure out the PC of tail-calling branch instructions.
1009+
// This means it doesn't need the compiler to emit DW_AT_call_pc, so we
1010+
// don't emit it here.
1011+
//
1012+
// There's no need to tie non-GDB debuggers to this non-standardness, as it
1013+
// adds unnecessary complexity to the debugger. For non-GDB debuggers, emit
1014+
// the standard DW_AT_call_pc info.
1015+
if (!useGNUAnalogForDwarf5Feature())
1016+
addLabelAddress(CallSiteDIE, dwarf::DW_AT_call_pc, CallAddr);
1017+
}
1018+
10031019
// Attach the return PC to allow the debugger to disambiguate call paths
10041020
// from one function to another.
10051021
//
10061022
// The return PC is only really needed when the call /isn't/ a tail call, but
1007-
// for some reason GDB always expects it.
1008-
if (!IsTail || DD->tuneForGDB()) {
1023+
// GDB expects it in DWARF4 mode, even for tail calls (see the comment above
1024+
// the DW_AT_call_pc emission logic for an explanation).
1025+
if (!IsTail || useGNUAnalogForDwarf5Feature()) {
10091026
assert(PCAddr && "Missing return PC information for a call");
10101027
addLabelAddress(CallSiteDIE,
10111028
getDwarf5OrGNUAttr(dwarf::DW_AT_call_return_pc), PCAddr);

llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ class DwarfCompileUnit final : public DwarfUnit {
230230

231231
void constructAbstractSubprogramScopeDIE(LexicalScope *Scope);
232232

233+
/// Whether to use the GNU analog for a DWARF5 tag, attribute, or location
234+
/// atom. Only applicable when emitting otherwise DWARF4-compliant debug info.
235+
bool useGNUAnalogForDwarf5Feature() const;
236+
233237
/// This takes a DWARF 5 tag and returns it or a GNU analog.
234238
dwarf::Tag getDwarf5OrGNUTag(dwarf::Tag Tag) const;
235239

@@ -245,10 +249,12 @@ class DwarfCompileUnit final : public DwarfUnit {
245249
/// For indirect calls \p CalleeDIE is set to nullptr.
246250
/// \p IsTail specifies whether the call is a tail call.
247251
/// \p PCAddr points to the PC value after the call instruction.
252+
/// \p CallAddr points to the PC value at the call instruction (or is null).
248253
/// \p CallReg is a register location for an indirect call. For direct calls
249254
/// the \p CallReg is set to 0.
250255
DIE &constructCallSiteEntryDIE(DIE &ScopeDIE, DIE *CalleeDIE, bool IsTail,
251-
const MCSymbol *PCAddr, unsigned CallReg);
256+
const MCSymbol *PCAddr,
257+
const MCSymbol *CallAddr, unsigned CallReg);
252258
/// Construct call site parameter DIEs for the \p CallSiteDIE. The \p Params
253259
/// were collected by the \ref collectCallSiteParameters.
254260
/// Note: The order of parameters does not matter, since debuggers recognize

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -878,16 +878,21 @@ void DwarfDebug::constructCallSiteEntryDIEs(const DISubprogram &SP,
878878
const MachineInstr *TopLevelCallMI =
879879
MI.isInsideBundle() ? &*getBundleStart(MI.getIterator()) : &MI;
880880

881-
// For tail calls, no return PC information is needed.
882-
// For regular calls (and tail calls in GDB tuning), the return PC
883-
// is needed to disambiguate paths in the call graph which could lead to
884-
// some target function.
881+
// For non-tail calls, the return PC is needed to disambiguate paths in
882+
// the call graph which could lead to some target function. For tail
883+
// calls, no return PC information is needed, unless tuning for GDB in
884+
// DWARF4 mode in which case we fake a return PC for compatibility.
885885
const MCSymbol *PCAddr =
886-
(IsTail && !tuneForGDB())
887-
? nullptr
888-
: const_cast<MCSymbol *>(getLabelAfterInsn(TopLevelCallMI));
886+
(!IsTail || CU.useGNUAnalogForDwarf5Feature())
887+
? const_cast<MCSymbol *>(getLabelAfterInsn(TopLevelCallMI))
888+
: nullptr;
889889

890-
assert((IsTail || PCAddr) && "Call without return PC information");
890+
// For tail calls, it's necessary to record the address of the branch
891+
// instruction so that the debugger can show where the tail call occurred.
892+
const MCSymbol *CallAddr =
893+
IsTail ? getLabelBeforeInsn(TopLevelCallMI) : nullptr;
894+
895+
assert((IsTail || PCAddr) && "Non-tail call without return PC");
891896

892897
LLVM_DEBUG(dbgs() << "CallSiteEntry: " << MF.getName() << " -> "
893898
<< (CalleeDecl ? CalleeDecl->getName()
@@ -896,8 +901,8 @@ void DwarfDebug::constructCallSiteEntryDIEs(const DISubprogram &SP,
896901
->getName(CallReg)))
897902
<< (IsTail ? " [IsTail]" : "") << "\n");
898903

899-
DIE &CallSiteDIE = CU.constructCallSiteEntryDIE(ScopeDIE, CalleeDIE,
900-
IsTail, PCAddr, CallReg);
904+
DIE &CallSiteDIE = CU.constructCallSiteEntryDIE(
905+
ScopeDIE, CalleeDIE, IsTail, PCAddr, CallAddr, CallReg);
901906

902907
// Optionally emit call-site-param debug info.
903908
if (emitDebugEntryValues()) {
@@ -1786,11 +1791,32 @@ void DwarfDebug::collectEntityInfo(DwarfCompileUnit &TheCU,
17861791

17871792
// Process beginning of an instruction.
17881793
void DwarfDebug::beginInstruction(const MachineInstr *MI) {
1794+
const MachineFunction &MF = *MI->getMF();
1795+
const auto *SP = MF.getFunction().getSubprogram();
1796+
bool NoDebug =
1797+
!SP || SP->getUnit()->getEmissionKind() == DICompileUnit::NoDebug;
1798+
1799+
// When describing calls, we need a label for the call instruction.
1800+
// TODO: Add support for targets with delay slots.
1801+
if (!NoDebug && SP->areAllCallsDescribed() &&
1802+
MI->isCandidateForCallSiteEntry(MachineInstr::AnyInBundle) &&
1803+
!MI->hasDelaySlot()) {
1804+
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
1805+
bool IsTail = TII->isTailCall(*MI);
1806+
// For tail calls, we need the address of the branch instruction for
1807+
// DW_AT_call_pc.
1808+
if (IsTail)
1809+
requestLabelBeforeInsn(MI);
1810+
// For non-tail calls, we need the return address for the call for
1811+
// DW_AT_call_return_pc. Under GDB tuning, this information is needed for
1812+
// tail calls as well.
1813+
requestLabelAfterInsn(MI);
1814+
}
1815+
17891816
DebugHandlerBase::beginInstruction(MI);
17901817
assert(CurMI);
17911818

1792-
const auto *SP = MI->getMF()->getFunction().getSubprogram();
1793-
if (!SP || SP->getUnit()->getEmissionKind() == DICompileUnit::NoDebug)
1819+
if (NoDebug)
17941820
return;
17951821

17961822
// Check if source location changes, but ignore DBG_VALUE and CFI locations.
@@ -1804,11 +1830,6 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
18041830
unsigned LastAsmLine =
18051831
Asm->OutStreamer->getContext().getCurrentDwarfLoc().getLine();
18061832

1807-
// Request a label after the call in order to emit AT_return_pc information
1808-
// in call site entries. TODO: Add support for targets with delay slots.
1809-
if (SP->areAllCallsDescribed() && MI->isCall() && !MI->hasDelaySlot())
1810-
requestLabelAfterInsn(MI);
1811-
18121833
if (DL == PrevInstLoc) {
18131834
// If we have an ongoing unspecified location, nothing to do here.
18141835
if (!DL)

llvm/lib/DWARFLinker/DWARFLinker.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,10 @@ unsigned DWARFLinker::DIECloner::cloneAddressAttribute(
10561056
if (Die.getTag() == dwarf::DW_TAG_call_site)
10571057
Addr = (Info.OrigCallReturnPc ? Info.OrigCallReturnPc : Addr) +
10581058
Info.PCOffset;
1059+
} else if (AttrSpec.Attr == dwarf::DW_AT_call_pc) {
1060+
// Relocate the address of a branch instruction within a call site entry.
1061+
if (Die.getTag() == dwarf::DW_TAG_call_site)
1062+
Addr = (Info.OrigCallPc ? Info.OrigCallPc : Addr) + Info.PCOffset;
10591063
}
10601064

10611065
Die.addValue(DIEAlloc, static_cast<dwarf::Attribute>(AttrSpec.Attr),

llvm/test/DebugInfo/MIR/X86/call-site-gnu-vs-dwarf5-attrs.mir

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
# Test the call site encoding in DWARF5 vs GNU extensions.
22
#
3+
# === DWARF4, tune for gdb ===
34
# RUN: llc -emit-call-site-info -dwarf-version 4 -debugger-tune=gdb -filetype=obj \
45
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
5-
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-GNU
6+
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-GNU -implicit-check-not=DW_AT_call
67
#
7-
# RUN: llc -emit-call-site-info -dwarf-version 5 -debugger-tune=lldb -filetype=obj \
8+
# === DWARF5, tune for gdb ===
9+
# RUN: llc -dwarf-version 5 -debugger-tune=gdb -emit-call-site-info -filetype=obj \
10+
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
11+
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
12+
#
13+
# === DWARF4, tune for lldb ===
14+
# RUN: llc -dwarf-version 4 -debugger-tune=lldb -emit-call-site-info -filetype=obj \
815
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
9-
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5
16+
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
1017
#
11-
# RUN: llc -emit-call-site-info -dwarf-version 5 -filetype=obj \
18+
# === DWARF5, tune for lldb ===
19+
# RUN: llc -dwarf-version 5 -debugger-tune=lldb -emit-call-site-info -filetype=obj \
1220
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
13-
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5
21+
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
1422
#
1523
# RUN: llc -emit-call-site-info -dwarf-version 5 -filetype=obj -debugger-tune=sce \
1624
# RUN: -emit-debug-entry-values -debug-entry-values -mtriple=x86_64-unknown-unknown \
@@ -49,6 +57,7 @@
4957
# CHECK-GNU: DW_TAG_GNU_call_site
5058
# CHECK-GNU-NEXT: DW_AT_abstract_origin
5159
# CHECK-GNU-NEXT: DW_AT_GNU_tail_call
60+
# CHECK-GNU-NEXT: DW_AT_low_pc
5261
#
5362
#
5463
# Check DWARF 5:
@@ -58,6 +67,9 @@
5867
# CHECK-DWARF5: DW_TAG_call_site
5968
# CHECK-DWARF5-NEXT: DW_AT_call_origin
6069
# CHECK-DWARF5-NEXT: DW_AT_call_return_pc
70+
# CHECK-DWARF5: DW_TAG_call_site
71+
# CHECK-DWARF5-NEXT: DW_AT_call_origin
72+
# CHECK-DWARF5-NEXT: DW_AT_call_return_pc
6173
# CHECK-DWARF5: DW_TAG_call_site_parameter
6274
# CHECK-DWARF5-NEXT: DW_AT_location
6375
# CHECK-DWARF5-NEXT: DW_AT_call_value
@@ -67,6 +79,7 @@
6779
# CHECK-DWARF5: DW_TAG_call_site
6880
# CHECK-DWARF5-NEXT: DW_AT_call_origin
6981
# CHECK-DWARF5-NEXT: DW_AT_call_tail_call
82+
# CHECK-DWARF5-NEXT: DW_AT_call_pc
7083
#
7184
--- |
7285
; ModuleID = 'call-site-attrs.c'

llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs.ll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
; RUN: %llc_dwarf -mtriple=x86_64-- < %s -o - | FileCheck %s -check-prefix=ASM
1616
; RUN: %llc_dwarf -debugger-tune=lldb -mtriple=x86_64-- < %s -filetype=obj -o %t.o
17-
; RUN: llvm-dwarfdump %t.o -o - | FileCheck %s -check-prefix=OBJ -implicit-check-not=DW_TAG_call_site
17+
; RUN: llvm-dwarfdump %t.o -o - | FileCheck %s -check-prefix=OBJ -implicit-check-not=DW_TAG_call -implicit-check-not=DW_AT_call
1818
; RUN: llvm-dwarfdump -verify %t.o 2>&1 | FileCheck %s -check-prefix=VERIFY
1919
; RUN: llvm-dwarfdump -statistics %t.o | FileCheck %s -check-prefix=STATS
2020
; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis -o /dev/null
@@ -75,6 +75,7 @@ entry:
7575
; OBJ: DW_TAG_call_site
7676
; OBJ: DW_AT_call_origin ([[bat_sp]])
7777
; OBJ: DW_AT_call_tail_call
78+
; OBJ: DW_AT_call_pc
7879
define void @_Z3foov() !dbg !25 {
7980
entry:
8081
tail call void @__has_no_subprogram()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* This file is used to test dsymutil support for call site entries with tail
3+
* calls (DW_AT_call_pc).
4+
*
5+
* Instructions for regenerating binaries (on Darwin/x86_64):
6+
*
7+
* 1. Copy the source to a top-level directory to work around having absolute
8+
* paths in the symtab's OSO entries.
9+
*
10+
* mkdir -p /Inputs/ && cp tail-call.c /Inputs && cd /Inputs
11+
*
12+
* 2. Compile with call site info enabled. -O2 is used to get tail call
13+
* promotion.
14+
*
15+
* clang -g -O2 tail-call.c -c -o tail-call.macho.x86_64.o
16+
* clang tail-call.macho.x86_64.o -o tail-call.macho.x86_64
17+
*
18+
* 3. Copy the binaries back into the repo's Inputs directory. You'll need
19+
* -oso-prepend-path=%p to link.
20+
*/
21+
22+
volatile int x;
23+
24+
__attribute__((disable_tail_calls, noinline)) void func2() { x++; }
25+
26+
__attribute__((noinline)) void func1() { func2(); /* tail */ }
27+
28+
__attribute__((disable_tail_calls)) int main() { func1(); /* regular */ }
Binary file not shown.
Binary file not shown.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
RUN: dsymutil -oso-prepend-path=%p %p/Inputs/tail-call.macho.x86_64 -o %t.dSYM
2+
RUN: llvm-dwarfdump %t.dSYM | FileCheck %s -implicit-check-not=DW_AT_call_pc
3+
4+
CHECK: DW_AT_call_pc (0x0000000100000f95)

0 commit comments

Comments
 (0)