ADCE: Remove debug info intrinsics in dead scopes
authorDuncan P. N. Exon Smith <dexonsmith@apple.com>
Tue, 29 Mar 2016 22:57:12 +0000 (22:57 +0000)
committerDuncan P. N. Exon Smith <dexonsmith@apple.com>
Tue, 29 Mar 2016 22:57:12 +0000 (22:57 +0000)
During ADCE, track which debug info scopes still have live references
from the code, and delete debug info intrinsics for the dead ones.

These intrinsics describe the locations of variables (in registers or
stack slots).  If there's no code left corresponding to a variable's
scope, then there's no way to reference the variable in the debugger and
it doesn't matter what its value is.

I add a DEBUG printout when the described location in an SSA register,
in case it helps some trying to track down why locations get lost.
However, we still delete these; the scope itself isn't attached to any
real code, so the ship has already sailed.

llvm-svn: 264800

llvm/lib/Transforms/Scalar/ADCE.cpp
llvm/test/Assembler/2010-02-05-FunctionLocalMetadataBecomesNull.ll
llvm/test/Transforms/ADCE/debug-info-intrinsic.ll [new file with mode: 0644]

index f3ad955..d94f83d 100644 (file)
@@ -22,6 +22,7 @@
 #include "llvm/Analysis/GlobalsModRef.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/CFG.h"
+#include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
@@ -33,22 +34,55 @@ using namespace llvm;
 
 STATISTIC(NumRemoved, "Number of instructions removed");
 
+static void collectLiveScopes(const DILocalScope &LS,
+                              SmallPtrSetImpl<const Metadata *> &AliveScopes) {
+  if (!AliveScopes.insert(&LS).second)
+    return;
+
+  if (isa<DISubprogram>(LS))
+    return;
+
+  // Tail-recurse through the scope chain.
+  collectLiveScopes(cast<DILocalScope>(*LS.getScope()), AliveScopes);
+}
+
+static void collectLiveScopes(const DILocation &DL,
+                              SmallPtrSetImpl<const Metadata *> &AliveScopes) {
+  // Even though DILocations are not scopes, shove them into AliveScopes so we
+  // don't revisit them.
+  if (!AliveScopes.insert(&DL).second)
+    return;
+
+  // Collect live scopes from the scope chain.
+  collectLiveScopes(*DL.getScope(), AliveScopes);
+
+  // Tail-recurse through the inlined-at chain.
+  if (const DILocation *IA = DL.getInlinedAt())
+    collectLiveScopes(*IA, AliveScopes);
+}
+
 static bool aggressiveDCE(Function& F) {
   SmallPtrSet<Instruction*, 32> Alive;
   SmallVector<Instruction*, 128> Worklist;
 
   // Collect the set of "root" instructions that are known live.
   for (Instruction &I : instructions(F)) {
-    if (isa<TerminatorInst>(I) || isa<DbgInfoIntrinsic>(I) || I.isEHPad() ||
-        I.mayHaveSideEffects()) {
+    if (isa<TerminatorInst>(I) || I.isEHPad() || I.mayHaveSideEffects()) {
       Alive.insert(&I);
       Worklist.push_back(&I);
     }
   }
 
-  // Propagate liveness backwards to operands.
+  // Propagate liveness backwards to operands.  Keep track of live debug info
+  // scopes.
+  SmallPtrSet<const Metadata *, 32> AliveScopes;
   while (!Worklist.empty()) {
     Instruction *Curr = Worklist.pop_back_val();
+
+    // Collect the live debug info scopes attached to this instruction.
+    if (const DILocation *DL = Curr->getDebugLoc())
+      collectLiveScopes(*DL, AliveScopes);
+
     for (Use &OI : Curr->operands()) {
       if (Instruction *Inst = dyn_cast<Instruction>(OI))
         if (Alive.insert(Inst).second)
@@ -61,10 +95,30 @@ static bool aggressiveDCE(Function& F) {
   // value of the function, and may therefore be deleted safely.
   // NOTE: We reuse the Worklist vector here for memory efficiency.
   for (Instruction &I : instructions(F)) {
-    if (!Alive.count(&I)) {
-      Worklist.push_back(&I);
-      I.dropAllReferences();
+    // Check if the instruction is alive.
+    if (Alive.count(&I))
+      continue;
+
+    if (auto *DII = dyn_cast<DbgInfoIntrinsic>(&I)) {
+      // Check if the scope of this variable location is alive.
+      if (AliveScopes.count(DII->getDebugLoc()->getScope()))
+        continue;
+
+      // Fallthrough and drop the intrinsic.
+      DEBUG({
+        // If intrinsic is pointing at a live SSA value, there may be an
+        // earlier optimization bug: if we know the location of the variable,
+        // why isn't the scope of the location alive?
+        if (Value *V = DII->getVariableLocation())
+          if (Instruction *II = dyn_cast<Instruction>(V))
+            if (Alive.count(II))
+              dbgs() << "Dropping debug info for " << *DII << "\n";
+      });
     }
+
+    // Prepare to delete.
+    Worklist.push_back(&I);
+    I.dropAllReferences();
   }
 
   for (Instruction *&I : Worklist) {
index 0a24626..a049f18 100644 (file)
@@ -19,7 +19,7 @@ define i32 @main() nounwind readonly !dbg !1 {
   %v2 = ptrtoint %struct.test* %v1 to i64 ; <i64> [#uses=1]
   %v3 = sub i64 %v2, ptrtoint ([10 x %struct.test]* @TestArray to i64) ; <i64> [#uses=1]
   store i64 %v3, i64* %diff1, align 8
-  ret i32 4
+  ret i32 4, !dbg !DILocation(scope: !1)
 }
 
 declare void @llvm.dbg.declare(metadata, metadata, metadata) nounwind readnone
diff --git a/llvm/test/Transforms/ADCE/debug-info-intrinsic.ll b/llvm/test/Transforms/ADCE/debug-info-intrinsic.ll
new file mode 100644 (file)
index 0000000..fc8011b
--- /dev/null
@@ -0,0 +1,101 @@
+; RUN: opt -adce -S < %s | FileCheck %s
+; Test that debug info intrinsics in dead scopes get eliminated by -adce.
+
+; Generated with 'clang -g -S -emit-llvm | opt -mem2reg -inline' at r262899
+; (before -adce was augmented) and then hand-reduced.  This was the input:
+;
+;;void sink(void);
+;;
+;;void variable_in_unused_subscope(void) {
+;;  { int i = 0; }
+;;  sink();
+;;}
+;;
+;;void variable_in_parent_scope(void) {
+;;  int i = 0;
+;;  { sink(); }
+;;}
+;;
+;;static int empty_function_with_unused_variable(void) {
+;;  { int i = 0; }
+;;  return 0;
+;;}
+;;
+;;void calls_empty_function_with_unused_variable_in_unused_subscope(void) {
+;;  { empty_function_with_unused_variable(); }
+;;  sink();
+;;}
+
+declare void @llvm.dbg.value(metadata, i64, metadata, metadata)
+
+declare void @sink()
+
+; CHECK-LABEL: define void @variable_in_unused_subscope(
+define void @variable_in_unused_subscope() !dbg !4 {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   call void @sink
+; CHECK-NEXT:   ret void
+entry:
+  call void @llvm.dbg.value(metadata i32 0, i64 0, metadata !15, metadata !17), !dbg !18
+  call void @sink(), !dbg !19
+  ret void, !dbg !20
+}
+
+; CHECK-LABEL: define void @variable_in_parent_scope(
+define void @variable_in_parent_scope() !dbg !7 {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   call void @llvm.dbg.value
+; CHECK-NEXT:   call void @sink
+; CHECK-NEXT:   ret void
+entry:
+  call void @llvm.dbg.value(metadata i32 0, i64 0, metadata !21, metadata !17), !dbg !22
+  call void @sink(), !dbg !23
+  ret void, !dbg !25
+}
+
+; CHECK-LABEL: define void @calls_empty_function_with_unused_variable_in_unused_subscope(
+define void @calls_empty_function_with_unused_variable_in_unused_subscope() !dbg !8 {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   call void @sink
+; CHECK-NEXT:   ret void
+entry:
+  call void @llvm.dbg.value(metadata i32 0, i64 0, metadata !26, metadata !17), !dbg !28
+  call void @sink(), !dbg !31
+  ret void, !dbg !32
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!14}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !2, subprograms: !3)
+!1 = !DIFile(filename: "t.c", directory: "/path/to/test/Transforms/ADCE")
+!2 = !{}
+!3 = !{!4, !7, !8, !10}
+!4 = distinct !DISubprogram(name: "variable_in_unused_subscope", scope: !1, file: !1, line: 3, type: !5, isLocal: false, isDefinition: true, scopeLine: 3, flags: DIFlagPrototyped, isOptimized: false, variables: !2)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
+!7 = distinct !DISubprogram(name: "variable_in_parent_scope", scope: !1, file: !1, line: 8, type: !5, isLocal: false, isDefinition: true, scopeLine: 8, flags: DIFlagPrototyped, isOptimized: false, variables: !2)
+!8 = distinct !DISubprogram(name: "calls_empty_function_with_unused_variable_in_unused_subscope", scope: !1, file: !1, line: 18, type: !5, isLocal: false, isDefinition: true, scopeLine: 18, flags: DIFlagPrototyped, isOptimized: false, variables: !2)
+!10 = distinct !DISubprogram(name: "empty_function_with_unused_variable", scope: !1, file: !1, line: 13, type: !11, isLocal: true, isDefinition: true, scopeLine: 13, flags: DIFlagPrototyped, isOptimized: false, variables: !2)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13}
+!13 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed)
+!14 = !{i32 2, !"Debug Info Version", i32 3}
+!15 = !DILocalVariable(name: "i", scope: !16, file: !1, line: 4, type: !13)
+!16 = distinct !DILexicalBlock(scope: !4, file: !1, line: 4, column: 3)
+!17 = !DIExpression()
+!18 = !DILocation(line: 4, column: 9, scope: !16)
+!19 = !DILocation(line: 5, column: 3, scope: !4)
+!20 = !DILocation(line: 6, column: 1, scope: !4)
+!21 = !DILocalVariable(name: "i", scope: !7, file: !1, line: 9, type: !13)
+!22 = !DILocation(line: 9, column: 7, scope: !7)
+!23 = !DILocation(line: 10, column: 5, scope: !24)
+!24 = distinct !DILexicalBlock(scope: !7, file: !1, line: 10, column: 3)
+!25 = !DILocation(line: 11, column: 1, scope: !7)
+!26 = !DILocalVariable(name: "i", scope: !27, file: !1, line: 14, type: !13)
+!27 = distinct !DILexicalBlock(scope: !10, file: !1, line: 14, column: 3)
+!28 = !DILocation(line: 14, column: 9, scope: !27, inlinedAt: !29)
+!29 = distinct !DILocation(line: 19, column: 5, scope: !30)
+!30 = distinct !DILexicalBlock(scope: !8, file: !1, line: 19, column: 3)
+!31 = !DILocation(line: 20, column: 3, scope: !8)
+!32 = !DILocation(line: 21, column: 1, scope: !8)