[StripDeadDebugInfo] Drop dead CUs

In situations when a submodule is extracted from big module (i.e. using
CloneModule) a lot of debug info is copied via metadata nodes. Despite of
the fact that part of that info is not linked to any instruction in extracted
IR file, StripDeadDebugInfo pass doesn't drop them.
Strengthen criteria for debug info that should be kept in a module:
- Only those compile units are left that referenced by a subprogram debug info
node that is attached to a function definition in the module or to an instruction
in the module that belongs to an inlined function.

Signed-off-by: Mikhail Lychkov <mikhail.lychkov@intel.com>

Differential Revision: https://reviews.llvm.org/D122163
This commit is contained in:
Alexey Bader 2022-08-03 06:06:50 -07:00
parent 355dbd3b2a
commit 2bb5535b58
2 changed files with 94 additions and 4 deletions

View File

@ -24,6 +24,7 @@
#include "llvm/IR/Constants.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
@ -294,6 +295,44 @@ bool StripDebugDeclare::runOnModule(Module &M) {
return stripDebugDeclareImpl(M);
}
/// Collects compilation units referenced by functions or lexical scopes.
/// Accepts any DIScope and uses recursive bottom-up approach to reach either
/// DISubprogram or DILexicalBlockBase.
static void
collectCUsWithScope(const DIScope *Scope, std::set<DICompileUnit *> &LiveCUs,
SmallPtrSet<const DIScope *, 8> &VisitedScopes) {
if (!Scope)
return;
auto InS = VisitedScopes.insert(Scope);
if (!InS.second)
return;
if (const auto *SP = dyn_cast<DISubprogram>(Scope)) {
if (SP->getUnit())
LiveCUs.insert(SP->getUnit());
return;
}
if (const auto *LB = dyn_cast<DILexicalBlockBase>(Scope)) {
const DISubprogram *SP = LB->getSubprogram();
if (SP && SP->getUnit())
LiveCUs.insert(SP->getUnit());
return;
}
collectCUsWithScope(Scope->getScope(), LiveCUs, VisitedScopes);
}
static void
collectCUsForInlinedFuncs(const DILocation *Loc,
std::set<DICompileUnit *> &LiveCUs,
SmallPtrSet<const DIScope *, 8> &VisitedScopes) {
if (!Loc || !Loc->getInlinedAt())
return;
collectCUsWithScope(Loc->getScope(), LiveCUs, VisitedScopes);
collectCUsForInlinedFuncs(Loc->getInlinedAt(), LiveCUs, VisitedScopes);
}
static bool stripDeadDebugInfoImpl(Module &M) {
bool Changed = false;
@ -321,10 +360,18 @@ static bool stripDeadDebugInfoImpl(Module &M) {
}
std::set<DICompileUnit *> LiveCUs;
// Any CU referenced from a subprogram is live.
for (DISubprogram *SP : F.subprograms()) {
if (SP->getUnit())
LiveCUs.insert(SP->getUnit());
SmallPtrSet<const DIScope *, 8> VisitedScopes;
// Any CU is live if is referenced from a subprogram metadata that is attached
// to a function defined or inlined in the module.
for (const Function &Fn : M.functions()) {
collectCUsWithScope(Fn.getSubprogram(), LiveCUs, VisitedScopes);
for (const_inst_iterator I = inst_begin(&Fn), E = inst_end(&Fn); I != E;
++I) {
if (!I->getDebugLoc())
continue;
const DILocation *DILoc = I->getDebugLoc().get();
collectCUsForInlinedFuncs(DILoc, LiveCUs, VisitedScopes);
}
}
bool HasDeadCUs = false;

View File

@ -0,0 +1,43 @@
; This test checks that strip-dead-debug-info pass deletes debug compile units
; if functions from those units are absent in that module.
; RUN: opt -passes='strip-dead-debug-info,verify' %s -S | FileCheck %s
; CHECK: !llvm.dbg.cu = !{!{{[0-9]+}}, !{{[0-9]+}}}
; CHECK-COUNT-2: !DICompileUnit
;target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
;target triple = "spir64-unknown-unknown"
; Function Attrs: nounwind
define void @dev_func1() #0 !dbg !13 {
ret void, !dbg !14
}
; Function Attrs: nounwind
define void @dev_func2() #0 !dbg !15 {
ret void, !dbg !16
}
attributes #0 = { nounwind }
!llvm.dbg.cu = !{!0, !3, !5}
!llvm.module.flags = !{!12}
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, imports: !2, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "dev_func1.cpp", directory: "/home/user/test")
!2 = !{}
!3 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !4, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, imports: !2, splitDebugInlining: false, nameTableKind: None)
!4 = !DIFile(filename: "dev_func2.cpp", directory: "/home/user/test")
!5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !6, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !2, imports: !7, splitDebugInlining: false, nameTableKind: None)
!6 = !DIFile(filename: "dev_func3.cpp", directory: "/home/user/test")
!7 = !{!8}
!8 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !6, entity: !9, file: !6, line: 129)
!9 = distinct !DISubprogram(name: "dev_func3", linkageName: "dev_func3", scope: !6, file: !6, line: 96, type: !10, scopeLine: 96, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !5, retainedNodes: !2)
!10 = !DISubroutineType(types: !11)
!11 = !{null}
!12 = !{i32 2, !"Debug Info Version", i32 3}
!13 = distinct !DISubprogram(name: "dev_func1", linkageName: "dev_func1", scope: !1, file: !1, line: 22, type: !10, scopeLine: 22, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
!14 = !DILocation(line: 29, column: 5, scope: !13)
!15 = distinct !DISubprogram(name: "dev_func2", linkageName: "dev_func2", scope: !4, file: !4, line: 22, type: !10, scopeLine: 22, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !3, retainedNodes: !2)
!16 = !DILocation(line: 29, column: 5, scope: !15)