Skip to content

Commit

Permalink
[DebugInfo] Add support for DWARF5 call site-related attributes
Browse files Browse the repository at this point in the history
DWARF v5 introduces DW_AT_call_all_calls, a subprogram attribute which
indicates that all calls (both regular and tail) within the subprogram
have call site entries. The information within these call site entries
can be used by a debugger to populate backtraces with synthetic tail
call frames.

Tail calling frames go missing in backtraces because the frame of the
caller is reused by the callee. Call site entries allow a debugger to
reconstruct a sequence of (tail) calls which led from one function to
another. This improves backtrace quality. There are limitations: tail
recursion isn't handled, variables within synthetic frames may not
survive to be inspected, etc. This approach is not novel, see:

  https://gcc.gnu.org/wiki/summit2010?action=AttachFile&do=get&target=jelinek.pdf

This patch adds an IR-level flag (DIFlagAllCallsDescribed) which lowers
to DW_AT_call_all_calls. It adds the minimal amount of DWARF generation
support needed to emit standards-compliant call site entries. For easier
deployment, when the debugger tuning is LLDB, the DWARF requirement is
adjusted to v4.

Testing: Apart from check-{llvm, clang}, I built a stage2 RelWithDebInfo
clang binary. Its dSYM passed verification and grew by 1.4% compared to
the baseline. 151,879 call site entries were added.

rdar://42001377

Differential Revision: https://reviews.llvm.org/D49887

llvm-svn: 343883
  • Loading branch information
vedantk committed Oct 5, 2018
1 parent f84ece6 commit 5931b4e
Show file tree
Hide file tree
Showing 16 changed files with 814 additions and 5 deletions.
24 changes: 23 additions & 1 deletion clang/lib/CodeGen/CGDebugInfo.cpp
Expand Up @@ -3168,6 +3168,7 @@ llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
QualType FnType = CGM.getContext().getFunctionType(
FD->getReturnType(), ArgTypes, FunctionProtoType::ExtProtoInfo(CC));
if (Stub) {
Flags |= getCallSiteRelatedAttrs();
return DBuilder.createFunction(
DContext, Name, LinkageName, Unit, Line,
getOrCreateFunctionType(GD.getDecl(), FnType, Unit),
Expand Down Expand Up @@ -3407,6 +3408,8 @@ void CGDebugInfo::EmitFunctionStart(GlobalDecl GD, SourceLocation Loc,
if (CurFuncIsThunk)
Flags |= llvm::DINode::FlagThunk;

llvm::DINode::DIFlags FlagsForDef = Flags | getCallSiteRelatedAttrs();

unsigned LineNo = getLineNumber(Loc);
unsigned ScopeLine = getLineNumber(ScopeLoc);

Expand All @@ -3418,7 +3421,7 @@ void CGDebugInfo::EmitFunctionStart(GlobalDecl GD, SourceLocation Loc,
llvm::DISubprogram *SP = DBuilder.createFunction(
FDContext, Name, LinkageName, Unit, LineNo,
getOrCreateFunctionType(D, FnType, Unit), Fn->hasLocalLinkage(),
true /*definition*/, ScopeLine, Flags, CGM.getLangOpts().Optimize,
true /*definition*/, ScopeLine, FlagsForDef, CGM.getLangOpts().Optimize,
TParamsArray.get(), getFunctionDeclaration(D));
Fn->setSubprogram(SP);
// We might get here with a VarDecl in the case we're generating
Expand Down Expand Up @@ -4422,3 +4425,22 @@ llvm::DebugLoc CGDebugInfo::SourceLocToDebugLoc(SourceLocation Loc) {
llvm::MDNode *Scope = LexicalBlockStack.back();
return llvm::DebugLoc::get(getLineNumber(Loc), getColumnNumber(Loc), Scope);
}

llvm::DINode::DIFlags CGDebugInfo::getCallSiteRelatedAttrs() const {
// Call site-related attributes are only useful in optimized programs, and
// when there's a possibility of debugging backtraces.
if (!CGM.getLangOpts().Optimize || DebugKind == codegenoptions::NoDebugInfo ||
DebugKind == codegenoptions::LocTrackingOnly)
return llvm::DINode::FlagZero;

// Call site-related attributes are available in DWARF v5. Some debuggers,
// while not fully DWARF v5-compliant, may accept these attributes as if they
// were part of DWARF v4.
bool SupportsDWARFv4Ext =
CGM.getCodeGenOpts().DwarfVersion == 4 &&
CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB;
if (!SupportsDWARFv4Ext && CGM.getCodeGenOpts().DwarfVersion < 5)
return llvm::DINode::FlagZero;

return llvm::DINode::FlagAllCallsDescribed;
}
5 changes: 5 additions & 0 deletions clang/lib/CodeGen/CGDebugInfo.h
Expand Up @@ -608,6 +608,11 @@ class CGDebugInfo {
unsigned LineNo, StringRef LinkageName,
llvm::GlobalVariable *Var, llvm::DIScope *DContext);


/// Return flags which enable debug info emission for call sites, provided
/// that it is supported and enabled.
llvm::DINode::DIFlags getCallSiteRelatedAttrs() const;

/// Get the printing policy for producing names for debug info.
PrintingPolicy getPrintingPolicy() const;

Expand Down
61 changes: 61 additions & 0 deletions clang/test/CodeGenCXX/dbg-info-all-calls-described.cpp
@@ -0,0 +1,61 @@
// Test that call site debug info is (un)supported in various configurations.

// Supported: DWARF5, -O1, standalone DI
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - \
// RUN: -O1 -disable-llvm-passes \
// RUN: -debug-info-kind=standalone -dwarf-version=5 \
// RUN: | FileCheck %s -check-prefix=HAS-ATTR \
// RUN: -implicit-check-not=DISubprogram -implicit-check-not=DIFlagAllCallsDescribed

// Supported: DWARF4 + LLDB tuning, -O1, limited DI
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - \
// RUN: -O1 -disable-llvm-passes \
// RUN: -debugger-tuning=lldb \
// RUN: -debug-info-kind=standalone -dwarf-version=4 \
// RUN: | FileCheck %s -check-prefix=HAS-ATTR \
// RUN: -implicit-check-not=DISubprogram -implicit-check-not=DIFlagAllCallsDescribed

// Supported: DWARF4 + LLDB tuning, -O1, line-tables only DI
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - \
// RUN: -O1 -disable-llvm-passes \
// RUN: -debugger-tuning=lldb \
// RUN: -debug-info-kind=line-tables-only -dwarf-version=4 \
// RUN: | FileCheck %s -check-prefix=LINE-TABLES-ONLY

// Unsupported: -O0
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - \
// RUN: -O0 \
// RUN: -debug-info-kind=standalone -dwarf-version=5 \
// RUN: | FileCheck %s -check-prefix=NO-ATTR

// Unsupported: DWARF4
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - \
// RUN: -O1 -disable-llvm-passes \
// RUN: -debug-info-kind=standalone -dwarf-version=4 \
// RUN: | FileCheck %s -check-prefix=NO-ATTR

// NO-ATTR-NOT: FlagAllCallsDescribed

// HAS-ATTR-DAG: DISubprogram(name: "declaration2", {{.*}}, isDefinition: true, {{.*}}, flags: DIFlagPrototyped | DIFlagAllCallsDescribed
// HAS-ATTR-DAG: DISubprogram(name: "struct1", {{.*}}, isDefinition: false, {{.*}}, flags: DIFlagPrototyped
// HAS-ATTR-DAG: DISubprogram(name: "struct1", {{.*}}, isDefinition: true, {{.*}}, flags: DIFlagPrototyped | DIFlagAllCallsDescribed
// HAS-ATTR-DAG: DISubprogram(name: "method1", {{.*}}, isDefinition: true, {{.*}}, flags: DIFlagPrototyped | DIFlagAllCallsDescribed
// HAS-ATTR-DAG: DISubprogram(name: "force_irgen", {{.*}}, isDefinition: true, {{.*}}, flags: DIFlagPrototyped | DIFlagAllCallsDescribed

// LINE-TABLES-ONLY: DISubprogram(name: "force_irgen", {{.*}}, isDefinition: true, {{.*}}, flags: DIFlagPrototyped | DIFlagAllCallsDescribed

void declaration1();

void declaration2();

void declaration2() {}

struct struct1 {
struct1() {}
void method1() {}
};

void __attribute__((optnone)) force_irgen() {
declaration1();
struct1().method1();
}
8 changes: 8 additions & 0 deletions llvm/include/llvm/DebugInfo/DWARF/DWARFVerifier.h
Expand Up @@ -148,6 +148,8 @@ class DWARFVerifier {
/// - That the root DIE is a unit DIE.
/// - If a unit type is provided, that the unit DIE matches the unit type.
/// - The DIE ranges.
/// - That call site entries are only nested within subprograms with a
/// DW_AT_call attribute.
///
/// \param Unit The DWARF Unit to verify.
///
Expand All @@ -164,6 +166,12 @@ class DWARFVerifier {
unsigned verifyUnitSection(const DWARFSection &S,
DWARFSectionKind SectionKind);

/// Verifies that a call site entry is nested within a subprogram with a
/// DW_AT_call attribute.
///
/// \returns Number of errors that occurred during verification.
unsigned verifyDebugInfoCallSite(const DWARFDie &Die);

/// Verify that all Die ranges are valid.
///
/// This function currently checks for:
Expand Down
3 changes: 2 additions & 1 deletion llvm/include/llvm/IR/DebugInfoFlags.def
Expand Up @@ -50,6 +50,7 @@ HANDLE_DI_FLAG((1 << 25), Thunk)
HANDLE_DI_FLAG((1 << 26), Trivial)
HANDLE_DI_FLAG((1 << 27), BigEndian)
HANDLE_DI_FLAG((1 << 28), LittleEndian)
HANDLE_DI_FLAG((1 << 29), AllCallsDescribed)

// To avoid needing a dedicated value for IndirectVirtualBase, we use
// the bitwise or of Virtual and FwdDecl, which does not otherwise
Expand All @@ -59,7 +60,7 @@ HANDLE_DI_FLAG((1 << 2) | (1 << 5), IndirectVirtualBase)
#ifdef DI_FLAG_LARGEST_NEEDED
// intended to be used with ADT/BitmaskEnum.h
// NOTE: always must be equal to largest flag, check this when adding new flag
HANDLE_DI_FLAG((1 << 28), Largest)
HANDLE_DI_FLAG((1 << 29), Largest)
#undef DI_FLAG_LARGEST_NEEDED
#endif

Expand Down
3 changes: 3 additions & 0 deletions llvm/include/llvm/IR/DebugInfoMetadata.h
Expand Up @@ -1736,6 +1736,9 @@ class DISubprogram : public DILocalScope {
}
bool isExplicit() const { return getFlags() & FlagExplicit; }
bool isPrototyped() const { return getFlags() & FlagPrototyped; }
bool areAllCallsDescribed() const {
return getFlags() & FlagAllCallsDescribed;
}
bool isMainSubprogram() const { return getFlags() & FlagMainSubprogram; }

/// Check if this is reference-qualified.
Expand Down
31 changes: 30 additions & 1 deletion llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
Expand Up @@ -730,7 +730,8 @@ DIE *DwarfCompileUnit::createScopeChildrenDIE(LexicalScope *Scope,
return ObjectPointer;
}

void DwarfCompileUnit::constructSubprogramScopeDIE(const DISubprogram *Sub, LexicalScope *Scope) {
DIE &DwarfCompileUnit::constructSubprogramScopeDIE(const DISubprogram *Sub,
LexicalScope *Scope) {
DIE &ScopeDIE = updateSubprogramScopeDIE(Sub);

if (Scope) {
Expand All @@ -753,6 +754,8 @@ void DwarfCompileUnit::constructSubprogramScopeDIE(const DISubprogram *Sub, Lexi
!includeMinimalInlineScopes())
ScopeDIE.addChild(
DIE::get(DIEValueAllocator, dwarf::DW_TAG_unspecified_parameters));

return ScopeDIE;
}

DIE *DwarfCompileUnit::createAndAddScopeChildren(LexicalScope *Scope,
Expand Down Expand Up @@ -807,6 +810,32 @@ void DwarfCompileUnit::constructAbstractSubprogramScopeDIE(
ContextCU->addDIEEntry(*AbsDef, dwarf::DW_AT_object_pointer, *ObjectPointer);
}

DIE &DwarfCompileUnit::constructCallSiteEntryDIE(DIE &ScopeDIE,
const DISubprogram &CalleeSP,
bool IsTail,
const MCSymbol *ReturnPC) {
// Insert a call site entry DIE within ScopeDIE.
DIE &CallSiteDIE =
createAndAddDIE(dwarf::DW_TAG_call_site, ScopeDIE, nullptr);

// For the purposes of showing tail call frames in backtraces, a key piece of
// information is DW_AT_call_origin, a pointer to the callee DIE.
DIE *CalleeDIE = getOrCreateSubprogramDIE(&CalleeSP);
assert(CalleeDIE && "Could not create DIE for call site entry origin");
addDIEEntry(CallSiteDIE, dwarf::DW_AT_call_origin, *CalleeDIE);

if (IsTail) {
// Attach DW_AT_call_tail_call to tail calls for standards compliance.
addFlag(CallSiteDIE, dwarf::DW_AT_call_tail_call);
} else {
// Attach the return PC to allow the debugger to disambiguate call paths
// from one function to another.
assert(ReturnPC && "Missing return PC information for a call");
addLabelAddress(CallSiteDIE, dwarf::DW_AT_call_return_pc, ReturnPC);
}
return CallSiteDIE;
}

DIE *DwarfCompileUnit::constructImportedEntityDIE(
const DIImportedEntity *Module) {
DIE *IMDie = DIE::get(DIEValueAllocator, (dwarf::Tag)Module->getTag());
Expand Down
10 changes: 9 additions & 1 deletion llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h
Expand Up @@ -203,12 +203,20 @@ class DwarfCompileUnit final : public DwarfUnit {
bool *HasNonScopeChildren = nullptr);

/// Construct a DIE for this subprogram scope.
void constructSubprogramScopeDIE(const DISubprogram *Sub, LexicalScope *Scope);
DIE &constructSubprogramScopeDIE(const DISubprogram *Sub,
LexicalScope *Scope);

DIE *createAndAddScopeChildren(LexicalScope *Scope, DIE &ScopeDIE);

void constructAbstractSubprogramScopeDIE(LexicalScope *Scope);

/// Construct a call site entry DIE describing a call within \p Scope to a
/// callee described by \p CalleeSP. \p IsTail specifies whether the call is
/// a tail call. \p ReturnPC must be non-null for non-tail calls and point
/// to the PC value after the call returns.
DIE &constructCallSiteEntryDIE(DIE &ScopeDIE, const DISubprogram &CalleeSP,
bool IsTail, const MCSymbol *ReturnPC);

/// Construct import_module DIE.
DIE *constructImportedEntityDIE(const DIImportedEntity *Module);

Expand Down
68 changes: 67 additions & 1 deletion llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
Expand Up @@ -39,6 +39,7 @@
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/Constants.h"
Expand Down Expand Up @@ -502,6 +503,63 @@ void DwarfDebug::constructAbstractSubprogramScopeDIE(DwarfCompileUnit &SrcCU,
}
}

void DwarfDebug::constructCallSiteEntryDIEs(const DISubprogram &SP,
DwarfCompileUnit &CU, DIE &ScopeDIE,
const MachineFunction &MF) {
// Add a call site-related attribute (DWARF5, Sec. 3.3.1.3). Do this only if
// the subprogram is required to have one.
if (!SP.areAllCallsDescribed() || !SP.isDefinition())
return;

// Use DW_AT_call_all_calls to express that call site entries are present
// for both tail and non-tail calls. Don't use DW_AT_call_all_source_calls
// because one of its requirements is not met: call site entries for
// optimized-out calls are elided.
CU.addFlag(ScopeDIE, dwarf::DW_AT_call_all_calls);

const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
assert(TII && "TargetInstrInfo not found: cannot label tail calls");

// Emit call site entries for each call or tail call in the function.
for (const MachineBasicBlock &MBB : MF) {
for (const MachineInstr &MI : MBB.instrs()) {
// Skip instructions which aren't calls. Both calls and tail-calling jump
// instructions (e.g TAILJMPd64) are classified correctly here.
if (!MI.isCall())
continue;

// TODO: Add support for targets with delay slots (see: beginInstruction).
if (MI.hasDelaySlot())
return;

// If this is a direct call, find the callee's subprogram.
const MachineOperand &CalleeOp = MI.getOperand(0);
if (!CalleeOp.isGlobal())
continue;
const Function *CalleeDecl = dyn_cast<Function>(CalleeOp.getGlobal());
if (!CalleeDecl || !CalleeDecl->getSubprogram())
continue;

// TODO: Omit call site entries for runtime calls (objc_msgSend, etc).
// TODO: Add support for indirect calls.

bool IsTail = TII->isTailCall(MI);

// For tail calls, no return PC information is needed. For regular calls,
// the return PC is needed to disambiguate paths in the call graph which
// could lead to some target function.
const MCSymbol *ReturnPC = IsTail ? nullptr : getLabelAfterInsn(&MI);

assert((IsTail || ReturnPC) && "Call without return PC information");
LLVM_DEBUG(dbgs() << "CallSiteEntry: " << MF.getName() << " -> "
<< CalleeDecl->getName() << (IsTail ? " [tail]" : "")
<< "\n");
CU.constructCallSiteEntryDIE(ScopeDIE, *CalleeDecl->getSubprogram(),
IsTail, ReturnPC);
}
}
}

void DwarfDebug::addGnuPubAttributes(DwarfCompileUnit &U, DIE &D) const {
if (!U.hasDwarfPubSections())
return;
Expand Down Expand Up @@ -1376,6 +1434,11 @@ 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 Expand Up @@ -1546,12 +1609,15 @@ void DwarfDebug::endFunctionImpl(const MachineFunction *MF) {
}

ProcessedSPNodes.insert(SP);
TheCU.constructSubprogramScopeDIE(SP, FnScope);
DIE &ScopeDIE = TheCU.constructSubprogramScopeDIE(SP, FnScope);
if (auto *SkelCU = TheCU.getSkeleton())
if (!LScopes.getAbstractScopesList().empty() &&
TheCU.getCUNode()->getSplitDebugInlining())
SkelCU->constructSubprogramScopeDIE(SP, FnScope);

// Construct call site entries.
constructCallSiteEntryDIEs(*SP, TheCU, ScopeDIE, *MF);

// Clear debug info
// Ownership of DbgVariables is a bit subtle - ScopeVariables owns all the
// DbgVariables except those that are also in AbstractVariables (since they
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
Expand Up @@ -424,6 +424,10 @@ class DwarfDebug : public DebugHandlerBase {
/// Construct a DIE for this abstract scope.
void constructAbstractSubprogramScopeDIE(DwarfCompileUnit &SrcCU, LexicalScope *Scope);

/// Construct DIEs for call site entries describing the calls in \p MF.
void constructCallSiteEntryDIEs(const DISubprogram &SP, DwarfCompileUnit &CU,
DIE &ScopeDIE, const MachineFunction &MF);

template <typename DataT>
void addAccelNameImpl(const DICompileUnit &CU, AccelTable<DataT> &AppleAccel,
StringRef Name, const DIE &Die);
Expand Down

0 comments on commit 5931b4e

Please sign in to comment.