Skip to content

Commit

Permalink
[DWARF] Emit DW_AT_call_pc for tail calls
Browse files Browse the repository at this point in the history
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

(cherry picked from commit f7052da)
  • Loading branch information
vedantk committed Mar 24, 2020
1 parent a270c87 commit 4d178b1
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 35 deletions.
33 changes: 25 additions & 8 deletions llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
Expand Up @@ -904,13 +904,12 @@ void DwarfCompileUnit::constructAbstractSubprogramScopeDIE(
ContextCU->addDIEEntry(*AbsDef, dwarf::DW_AT_object_pointer, *ObjectPointer);
}

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

dwarf::Tag DwarfCompileUnit::getDwarf5OrGNUTag(dwarf::Tag Tag) const {
if (!useGNUAnalogForDwarf5Feature(DD))
if (!useGNUAnalogForDwarf5Feature())
return Tag;
switch (Tag) {
case dwarf::DW_TAG_call_site:
Expand All @@ -924,7 +923,7 @@ dwarf::Tag DwarfCompileUnit::getDwarf5OrGNUTag(dwarf::Tag Tag) const {

dwarf::Attribute
DwarfCompileUnit::getDwarf5OrGNUAttr(dwarf::Attribute Attr) const {
if (!useGNUAnalogForDwarf5Feature(DD))
if (!useGNUAnalogForDwarf5Feature())
return Attr;
switch (Attr) {
case dwarf::DW_AT_call_all_calls:
Expand All @@ -946,7 +945,7 @@ DwarfCompileUnit::getDwarf5OrGNUAttr(dwarf::Attribute Attr) const {

dwarf::LocationAtom
DwarfCompileUnit::getDwarf5OrGNULocationAtom(dwarf::LocationAtom Loc) const {
if (!useGNUAnalogForDwarf5Feature(DD))
if (!useGNUAnalogForDwarf5Feature())
return Loc;
switch (Loc) {
case dwarf::DW_OP_entry_value:
Expand All @@ -960,6 +959,7 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(DIE &ScopeDIE,
DIE *CalleeDIE,
bool IsTail,
const MCSymbol *PCAddr,
const MCSymbol *CallAddr,
unsigned CallReg) {
// Insert a call site entry DIE within ScopeDIE.
DIE &CallSiteDIE = createAndAddDIE(getDwarf5OrGNUTag(dwarf::DW_TAG_call_site),
Expand All @@ -975,16 +975,33 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(DIE &ScopeDIE,
*CalleeDIE);
}

if (IsTail)
if (IsTail) {
// Attach DW_AT_call_tail_call to tail calls for standards compliance.
addFlag(CallSiteDIE, getDwarf5OrGNUAttr(dwarf::DW_AT_call_tail_call));

// Attach the address of the branch instruction to allow the debugger to
// show where the tail call occurred. This attribute has no GNU analog.
//
// GDB works backwards from non-standard usage of DW_AT_low_pc (in DWARF4
// mode -- equivalently, in DWARF5 mode, DW_AT_call_return_pc) at tail-call
// site entries to figure out the PC of tail-calling branch instructions.
// This means it doesn't need the compiler to emit DW_AT_call_pc, so we
// don't emit it here.
//
// There's no need to tie non-GDB debuggers to this non-standardness, as it
// adds unnecessary complexity to the debugger. For non-GDB debuggers, emit
// the standard DW_AT_call_pc info.
if (!useGNUAnalogForDwarf5Feature())
addLabelAddress(CallSiteDIE, dwarf::DW_AT_call_pc, CallAddr);
}

// Attach the return PC to allow the debugger to disambiguate call paths
// from one function to another.
//
// The return PC is only really needed when the call /isn't/ a tail call, but
// for some reason GDB always expects it.
if (!IsTail || DD->tuneForGDB()) {
// GDB expects it in DWARF4 mode, even for tail calls (see the comment above
// the DW_AT_call_pc emission logic for an explanation).
if (!IsTail || useGNUAnalogForDwarf5Feature()) {
assert(PCAddr && "Missing return PC information for a call");
addLabelAddress(CallSiteDIE,
getDwarf5OrGNUAttr(dwarf::DW_AT_call_return_pc), PCAddr);
Expand Down
8 changes: 7 additions & 1 deletion llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h
Expand Up @@ -230,6 +230,10 @@ class DwarfCompileUnit final : public DwarfUnit {

void constructAbstractSubprogramScopeDIE(LexicalScope *Scope);

/// Whether to use the GNU analog for a DWARF5 tag, attribute, or location
/// atom. Only applicable when emitting otherwise DWARF4-compliant debug info.
bool useGNUAnalogForDwarf5Feature() const;

/// This takes a DWARF 5 tag and returns it or a GNU analog.
dwarf::Tag getDwarf5OrGNUTag(dwarf::Tag Tag) const;

Expand All @@ -245,10 +249,12 @@ class DwarfCompileUnit final : public DwarfUnit {
/// For indirect calls \p CalleeDIE is set to nullptr.
/// \p IsTail specifies whether the call is a tail call.
/// \p PCAddr points to the PC value after the call instruction.
/// \p CallAddr points to the PC value at the call instruction (or is null).
/// \p CallReg is a register location for an indirect call. For direct calls
/// the \p CallReg is set to 0.
DIE &constructCallSiteEntryDIE(DIE &ScopeDIE, DIE *CalleeDIE, bool IsTail,
const MCSymbol *PCAddr, unsigned CallReg);
const MCSymbol *PCAddr,
const MCSymbol *CallAddr, unsigned CallReg);
/// Construct call site parameter DIEs for the \p CallSiteDIE. The \p Params
/// were collected by the \ref collectCallSiteParameters.
/// Note: The order of parameters does not matter, since debuggers recognize
Expand Down
55 changes: 38 additions & 17 deletions llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
Expand Up @@ -878,16 +878,21 @@ void DwarfDebug::constructCallSiteEntryDIEs(const DISubprogram &SP,
const MachineInstr *TopLevelCallMI =
MI.isInsideBundle() ? &*getBundleStart(MI.getIterator()) : &MI;

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

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

assert((IsTail || PCAddr) && "Non-tail call without return PC");

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

DIE &CallSiteDIE = CU.constructCallSiteEntryDIE(ScopeDIE, CalleeDIE,
IsTail, PCAddr, CallReg);
DIE &CallSiteDIE = CU.constructCallSiteEntryDIE(
ScopeDIE, CalleeDIE, IsTail, PCAddr, CallAddr, CallReg);

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

// Process beginning of an instruction.
void DwarfDebug::beginInstruction(const MachineInstr *MI) {
const MachineFunction &MF = *MI->getMF();
const auto *SP = MF.getFunction().getSubprogram();
bool NoDebug =
!SP || SP->getUnit()->getEmissionKind() == DICompileUnit::NoDebug;

// When describing calls, we need a label for the call instruction.
// TODO: Add support for targets with delay slots.
if (!NoDebug && SP->areAllCallsDescribed() &&
MI->isCandidateForCallSiteEntry(MachineInstr::AnyInBundle) &&
!MI->hasDelaySlot()) {
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
bool IsTail = TII->isTailCall(*MI);
// For tail calls, we need the address of the branch instruction for
// DW_AT_call_pc.
if (IsTail)
requestLabelBeforeInsn(MI);
// For non-tail calls, we need the return address for the call for
// DW_AT_call_return_pc. Under GDB tuning, this information is needed for
// tail calls as well.
requestLabelAfterInsn(MI);
}

DebugHandlerBase::beginInstruction(MI);
assert(CurMI);

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

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

// Request a label after the call in order to emit AT_return_pc information
// in call site entries. TODO: Add support for targets with delay slots.
if (SP->areAllCallsDescribed() && MI->isCall() && !MI->hasDelaySlot())
requestLabelAfterInsn(MI);

if (DL == PrevInstLoc) {
// If we have an ongoing unspecified location, nothing to do here.
if (!DL)
Expand Down
25 changes: 17 additions & 8 deletions llvm/test/DebugInfo/MIR/X86/call-site-gnu-vs-dwarf5-attrs.mir
@@ -1,20 +1,24 @@
# Test the call site encoding in DWARF5 vs GNU extensions.
#
# === DWARF4, tune for gdb ===
# RUN: llc -dwarf-version 4 -debugger-tune=gdb -filetype=obj \
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-GNU
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-GNU -implicit-check-not=DW_AT_call
#
# RUN: llc -dwarf-version 5 -debugger-tune=lldb -filetype=obj \
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5
# === DWARF5, tune for gdb ===
# RUN: llc -dwarf-version 5 -debugger-tune=gdb -filetype=obj \
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
#
# RUN: llc -dwarf-version 5 -filetype=obj \
# === DWARF4, tune for lldb ===
# RUN: llc -dwarf-version 4 -debugger-tune=lldb -filetype=obj \
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
#
# RUN: llc -dwarf-version 5 -filetype=obj -debugger-tune=sce -emit-debug-entry-values \
# === DWARF5, tune for lldb ===
# RUN: llc -dwarf-version 5 -debugger-tune=lldb -filetype=obj \
# RUN: -mtriple=x86_64-unknown-unknown -start-after=machineverifier -o - %s \
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5
# RUN: | llvm-dwarfdump - | FileCheck %s -check-prefixes=CHECK-DWARF5 -implicit-check-not=DW_AT_call
#
# This is based on the following reproducer:
#
Expand Down Expand Up @@ -49,6 +53,7 @@
# CHECK-GNU: DW_TAG_GNU_call_site
# CHECK-GNU-NEXT: DW_AT_abstract_origin
# CHECK-GNU-NEXT: DW_AT_GNU_tail_call
# CHECK-GNU-NEXT: DW_AT_low_pc
#
#
# Check DWARF 5:
Expand All @@ -58,6 +63,9 @@
# CHECK-DWARF5: DW_TAG_call_site
# CHECK-DWARF5-NEXT: DW_AT_call_origin
# CHECK-DWARF5-NEXT: DW_AT_call_return_pc
# CHECK-DWARF5: DW_TAG_call_site
# CHECK-DWARF5-NEXT: DW_AT_call_origin
# CHECK-DWARF5-NEXT: DW_AT_call_return_pc
# CHECK-DWARF5: DW_TAG_call_site_parameter
# CHECK-DWARF5-NEXT: DW_AT_location
# CHECK-DWARF5-NEXT: DW_AT_call_value
Expand All @@ -67,6 +75,7 @@
# CHECK-DWARF5: DW_TAG_call_site
# CHECK-DWARF5-NEXT: DW_AT_call_origin
# CHECK-DWARF5-NEXT: DW_AT_call_tail_call
# CHECK-DWARF5-NEXT: DW_AT_call_pc
#
--- |
; ModuleID = 'call-site-attrs.c'
Expand Down
3 changes: 2 additions & 1 deletion llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs.ll
Expand Up @@ -15,7 +15,7 @@
; REQUIRES: object-emission
; RUN: %llc_dwarf -mtriple=x86_64-- < %s -o - | FileCheck %s -check-prefix=ASM
; RUN: %llc_dwarf -debugger-tune=lldb -mtriple=x86_64-- < %s -filetype=obj -o %t.o
; RUN: llvm-dwarfdump %t.o -o - | FileCheck %s -check-prefix=OBJ -implicit-check-not=DW_TAG_call_site
; RUN: llvm-dwarfdump %t.o -o - | FileCheck %s -check-prefix=OBJ -implicit-check-not=DW_TAG_call -implicit-check-not=DW_AT_call
; RUN: llvm-dwarfdump -verify %t.o 2>&1 | FileCheck %s -check-prefix=VERIFY
; RUN: llvm-dwarfdump -statistics %t.o | FileCheck %s -check-prefix=STATS
; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis -o /dev/null
Expand Down Expand Up @@ -76,6 +76,7 @@ entry:
; OBJ: DW_TAG_call_site
; OBJ: DW_AT_call_origin ([[bat_sp]])
; OBJ: DW_AT_call_tail_call
; OBJ: DW_AT_call_pc
define void @_Z3foov() !dbg !25 {
entry:
tail call void @__has_no_subprogram()
Expand Down
28 changes: 28 additions & 0 deletions llvm/test/tools/dsymutil/X86/Inputs/tail-call.cpp
@@ -0,0 +1,28 @@
/*
* This file is used to test dsymutil support for call site entries with tail
* calls (DW_AT_call_pc).
*
* Instructions for regenerating binaries (on Darwin/x86_64):
*
* 1. Copy the source to a top-level directory to work around having absolute
* paths in the symtab's OSO entries.
*
* mkdir -p /Inputs/ && cp tail-call.c /Inputs && cd /Inputs
*
* 2. Compile with call site info enabled. -O2 is used to get tail call
* promotion.
*
* clang -g -O2 tail-call.c -c -o tail-call.macho.x86_64.o
* clang tail-call.macho.x86_64.o -o tail-call.macho.x86_64
*
* 3. Copy the binaries back into the repo's Inputs directory. You'll need
* -oso-prepend-path=%p to link.
*/

volatile int x;

__attribute__((disable_tail_calls, noinline)) void func2() { x++; }

__attribute__((noinline)) void func1() { func2(); /* tail */ }

__attribute__((disable_tail_calls)) int main() { func1(); /* regular */ }
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions llvm/test/tools/dsymutil/X86/tail-call-linking.test
@@ -0,0 +1,4 @@
RUN: dsymutil -oso-prepend-path=%p %p/Inputs/tail-call.macho.x86_64 -o %t.dSYM
RUN: llvm-dwarfdump %t.dSYM | FileCheck %s -implicit-check-not=DW_AT_call_pc

CHECK: DW_AT_call_pc (0x0000000100000f95)
4 changes: 4 additions & 0 deletions llvm/tools/dsymutil/DwarfLinker.cpp
Expand Up @@ -1428,6 +1428,10 @@ unsigned DwarfLinker::DIECloner::cloneAddressAttribute(
if (Die.getTag() == dwarf::DW_TAG_call_site)
Addr = (Info.OrigCallReturnPc ? Info.OrigCallReturnPc : Addr) +
Info.PCOffset;
} else if (AttrSpec.Attr == dwarf::DW_AT_call_pc) {
// Relocate the address of a branch instruction within a call site entry.
if (Die.getTag() == dwarf::DW_TAG_call_site)
Addr = (Info.OrigCallPc ? Info.OrigCallPc : Addr) + Info.PCOffset;
}

Die.addValue(DIEAlloc, static_cast<dwarf::Attribute>(AttrSpec.Attr),
Expand Down
3 changes: 3 additions & 0 deletions llvm/tools/dsymutil/DwarfLinker.h
Expand Up @@ -321,6 +321,9 @@ class DwarfLinker {
/// Value of DW_AT_call_return_pc in the input DIE
uint64_t OrigCallReturnPc = 0;

/// Value of DW_AT_call_pc in the input DIE
uint64_t OrigCallPc = 0;

/// Offset to apply to PC addresses inside a function.
int64_t PCOffset = 0;

Expand Down

0 comments on commit 4d178b1

Please sign in to comment.