Index: include/llvm/Transforms/Utils/Local.h =================================================================== --- include/llvm/Transforms/Utils/Local.h +++ include/llvm/Transforms/Utils/Local.h @@ -285,6 +285,9 @@ /// Finds the llvm.dbg.value intrinsics describing a value, if any. void findDbgValues(SmallVectorImpl &DbgValues, Value *V); +/// Constants for \p replaceDbgDeclare and friends. +enum { NoDeref = false, WithDeref = true }; + /// Replaces llvm.dbg.declare instruction when the address it describes /// is replaced with a new value. If Deref is true, an additional DW_OP_deref is /// prepended to the expression. If Offset is non-zero, a constant displacement @@ -310,6 +313,11 @@ void replaceDbgValueForAlloca(AllocaInst *AI, Value *NewAllocaAddress, DIBuilder &Builder, int Offset = 0); +/// Assuming the instruction \p I is going to be deleted, attempt to salvage any +/// dbg.value intrinsics referring to \p I by rewriting its effect into a +/// DIExpression. +void salvageDebugInfo(Instruction &I); + /// Remove all instructions from a basic block other than it's terminator /// and any present EH pad instructions. unsigned removeAllNonTerminatorAndEHPadInstructions(BasicBlock *BB); Index: lib/Transforms/InstCombine/InstCombineInternal.h =================================================================== --- lib/Transforms/InstCombine/InstCombineInternal.h +++ lib/Transforms/InstCombine/InstCombineInternal.h @@ -28,6 +28,9 @@ #include "llvm/IR/PatternMatch.h" #include "llvm/Pass.h" #include "llvm/Transforms/InstCombine/InstCombineWorklist.h" +#include "llvm/Transforms/Utils/Local.h" +#include "llvm/Support/Dwarf.h" +#include "llvm/IR/DIBuilder.h" #define DEBUG_TYPE "instcombine" @@ -470,8 +473,9 @@ /// methods should return the value returned by this function. Instruction *eraseInstFromFunction(Instruction &I) { DEBUG(dbgs() << "IC: ERASE " << I << '\n'); - assert(I.use_empty() && "Cannot erase instruction that is used!"); + salvageDebugInfo(I); + // Make sure that we reprocess all operands now that we reduced their // use counts. if (I.getNumOperands() < 8) { Index: lib/Transforms/Utils/Local.cpp =================================================================== --- lib/Transforms/Utils/Local.cpp +++ lib/Transforms/Utils/Local.cpp @@ -1273,9 +1273,9 @@ } } -static DIExpression *BuildReplacementDIExpr(DIBuilder &Builder, - DIExpression *DIExpr, bool Deref, - int Offset) { +/// Prepend \p DIExpr with a deref and offset operation. +static DIExpression *prependDIExpr(DIBuilder &Builder, DIExpression *DIExpr, + bool Deref, uint64_t Offset) { if (!Deref && !Offset) return DIExpr; // Create a copy of the original DIDescriptor for user variable, prepending @@ -1302,7 +1302,7 @@ auto *DIExpr = DDI->getExpression(); assert(DIVar && "Missing variable"); - DIExpr = BuildReplacementDIExpr(Builder, DIExpr, Deref, Offset); + DIExpr = prependDIExpr(Builder, DIExpr, Deref, Offset); // Insert llvm.dbg.declare immediately after the original alloca, and remove // old llvm.dbg.declare. @@ -1357,6 +1357,50 @@ } } +void llvm::salvageDebugInfo(Instruction &I) { + auto MDWrap = [&](Value *V) { + return MetadataAsValue::get(I.getContext(), ValueAsMetadata::get(V)); + }; + auto &M = *I.getModule(); + SmallVector DbgValues; + if (auto *BitCast = dyn_cast(&I)) { + // Bitcasts are entirely irrelevant for debug info. Rewrite the dbg.value to + // use the cast's source. + findDbgValues(DbgValues, &I); + for (auto *DVI : DbgValues) { + DVI->setOperand(0, MDWrap(I.getOperand(0))); + DEBUG(dbgs() << "SALVAGE: " << *DVI << '\n'); + } + } else if (auto *GEP = dyn_cast(&I)) { + findDbgValues(DbgValues, &I); + for (auto *DVI : DbgValues) { + unsigned BitWidth = + M.getDataLayout().getPointerSizeInBits(GEP->getPointerAddressSpace()); + APInt Offset(BitWidth, 0); + + if (GEP->accumulateConstantOffset(M.getDataLayout(), Offset)) { + auto *DIExpr = DVI->getExpression(); + DIBuilder DIB(M, /*AllowUnresolved*/ false); + // GEP offsets are always i32, so getLimitedValue() is always fine here. + DIExpr = prependDIExpr(DIB, DIExpr, NoDeref, Offset.getLimitedValue()); + DVI->setOperand(0, MDWrap(I.getOperand(0))); + DVI->setOperand(3, MetadataAsValue::get(I.getContext(), DIExpr)); + DEBUG(dbgs() << "SALVAGE: " << *DVI << '\n'); + } + } + } else if (auto *Load = dyn_cast(&I)) { + findDbgValues(DbgValues, &I); + for (auto *DVI : DbgValues) { + auto *DIExpr = DVI->getExpression(); + DIBuilder DIB(M, /*AllowUnresolved*/ false); + DIExpr = prependDIExpr(DIB, DIExpr, WithDeref, 0); + DVI->setOperand(0, MDWrap(I.getOperand(0))); + DVI->setOperand(3, MetadataAsValue::get(I.getContext(), DIExpr)); + DEBUG(dbgs() << "SALVAGE: " << *DVI << '\n'); + } + } +} + unsigned llvm::removeAllNonTerminatorAndEHPadInstructions(BasicBlock *BB) { unsigned NumDeadInst = 0; // Delete the instructions backwards, as it has a reduced likelihood of Index: test/Transforms/InstCombine/debuginfo-dce.ll =================================================================== --- /dev/null +++ test/Transforms/InstCombine/debuginfo-dce.ll @@ -0,0 +1,106 @@ +; RUN: opt -instcombine %s -S -o - | FileCheck %s +; Verify that the eliminated instructions (bitcast, gep, load) are salvaged into +; a DIExpression. +; +; Originally created from the following C source and then heavily isolated/reduced. +; +; struct entry { +; struct entry *next; +; }; +; void scan(struct entry *queue, struct entry *end) +; { +; struct entry *entry; +; for (entry = (struct entry *)((char *)(queue->next) - 8); +; &entry->next == end; +; entry = (struct entry *)((char *)(entry->next) - 8)) { +; } +; } + +; ModuleID = '' +source_filename = "test.c" +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.12.0" + +%struct.entry = type { %struct.entry* } + +; Function Attrs: nounwind ssp uwtable +define void @salvage_load(%struct.entry** %queue) local_unnamed_addr #0 !dbg !14 { +entry: + %im_not_dead = alloca %struct.entry* + %0 = load %struct.entry*, %struct.entry** %queue, align 8, !dbg !19 + %1 = load %struct.entry*, %struct.entry** %queue, align 8, !dbg !19 + call void @llvm.dbg.value(metadata %struct.entry* %1, i64 0, metadata !18, metadata !20), !dbg !19 +; CHECK: define void @salvage_load +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata %struct.entry** %queue, i64 0, +; CHECK-SAME: metadata ![[LOAD_EXPR:[0-9]+]]) + store %struct.entry* %1, %struct.entry** %im_not_dead, align 8 + ret void, !dbg !21 +} + +; Function Attrs: nounwind ssp uwtable +define void @salvage_bitcast(%struct.entry* %queue) local_unnamed_addr #0 !dbg !14 { +entry: + %im_not_dead = alloca i8* + %0 = bitcast %struct.entry* %queue to i8*, !dbg !19 + %1 = bitcast %struct.entry* %queue to i8*, !dbg !19 + call void @llvm.dbg.value(metadata i8* %1, i64 0, metadata !18, metadata !20), !dbg !19 +; CHECK: define void @salvage_bitcast +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata %struct.entry* %queue, i64 0, +; CHECK-SAME: metadata ![[BITCAST_EXPR:[0-9]+]]) + store i8* %1, i8** %im_not_dead, align 8 + ret void, !dbg !21 +} + +; Function Attrs: nounwind ssp uwtable +define void @salvage_gep(%struct.entry* %queue, %struct.entry* %end) local_unnamed_addr #0 !dbg !14 { +entry: + %im_not_dead = alloca %struct.entry** + %0 = getelementptr inbounds %struct.entry, %struct.entry* %queue, i32 -1, i32 0, !dbg !19 + %1 = getelementptr inbounds %struct.entry, %struct.entry* %queue, i32 -1, i32 0, !dbg !19 + call void @llvm.dbg.value(metadata %struct.entry** %1, i64 0, metadata !18, metadata !20), !dbg !19 +; CHECK: define void @salvage_gep +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.dbg.value(metadata %struct.entry* %queue, i64 0, +; CHECK-SAME: metadata ![[GEP_EXPR:[0-9]+]]) + store %struct.entry** %1, %struct.entry*** %im_not_dead, align 8 + ret void, !dbg !21 +} + +; CHECK: ![[LOAD_EXPR]] = !DIExpression(DW_OP_deref, DW_OP_plus, 0) +; CHECK: ![[BITCAST_EXPR]] = !DIExpression(DW_OP_plus, 0) +; CHECK: ![[GEP_EXPR]] = !DIExpression(DW_OP_minus, 8, DW_OP_plus, 0) + +; Function Attrs: nounwind readnone +declare void @llvm.dbg.value(metadata, i64, metadata, metadata) #1 + +attributes #0 = { nounwind ssp uwtable } +attributes #1 = { nounwind readnone } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!10, !11, !12} +!llvm.ident = !{!13} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 5.0.0 (trunk 297628) (llvm/trunk 297643)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !3) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{} +!3 = !{!4, !8} +!4 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64) +!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "entry", file: !1, line: 1, size: 64, elements: !6) +!6 = !{!7} +!7 = !DIDerivedType(tag: DW_TAG_member, name: "next", scope: !5, file: !1, line: 2, baseType: !4, size: 64) +!8 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !9, size: 64) +!9 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!10 = !{i32 2, !"Dwarf Version", i32 4} +!11 = !{i32 2, !"Debug Info Version", i32 3} +!12 = !{i32 1, !"PIC Level", i32 2} +!13 = !{!"clang version 5.0.0 (trunk 297628) (llvm/trunk 297643)"} +!14 = distinct !DISubprogram(name: "scan", scope: !1, file: !1, line: 4, type: !15, isLocal: false, isDefinition: true, scopeLine: 5, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !17) +!15 = !DISubroutineType(types: !16) +!16 = !{null, !4, !4} +!17 = !{!18} +!18 = !DILocalVariable(name: "entry", scope: !14, file: !1, line: 6, type: !4) +!19 = !DILocation(line: 6, column: 17, scope: !14) +!20 = !DIExpression(DW_OP_plus, 0) +!21 = !DILocation(line: 11, column: 1, scope: !14)