/// that has an associated llvm.dbg.declare or llvm.dbg.addr intrinsic.
void llvm::ConvertDebugDeclareToDebugValue(DbgVariableIntrinsic *DII,
StoreInst *SI, DIBuilder &Builder) {
- assert(DII->isAddressOfVariable());
+ assert(DII->isAddressOfVariable() || isa<DbgAssignIntrinsic>(DII));
auto *DIVar = DII->getVariable();
assert(DIVar && "Missing variable");
auto *DIExpr = DII->getExpression();
namespace {
+/// Helper for updating assignment tracking debug info when promoting allocas.
+class AssignmentTrackingInfo {
+ /// DbgAssignIntrinsics linked to the alloca with at most one per variable
+ /// fragment. (i.e. not be a comprehensive set if there are multiple
+ /// dbg.assigns for one variable fragment).
+ SmallVector<DbgVariableIntrinsic *> DbgAssigns;
+
+public:
+ void init(AllocaInst *AI) {
+ SmallSet<DebugVariable, 2> Vars;
+ for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(AI)) {
+ if (Vars.insert(DebugVariable(DAI)).second)
+ DbgAssigns.push_back(DAI);
+ }
+ }
+
+ /// Update assignment tracking debug info given for the to-be-deleted store
+ /// \p ToDelete that stores to this alloca.
+ void updateForDeletedStore(StoreInst *ToDelete, DIBuilder &DIB) const {
+ // There's nothing to do if the alloca doesn't have any variables using
+ // assignment tracking.
+ if (DbgAssigns.empty()) {
+ assert(at::getAssignmentMarkers(ToDelete).empty());
+ return;
+ }
+
+ // Just leave dbg.assign intrinsics in place and remember that we've seen
+ // one for each variable fragment.
+ SmallSet<DebugVariable, 2> VarHasDbgAssignForStore;
+ for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(ToDelete))
+ VarHasDbgAssignForStore.insert(DebugVariable(DAI));
+
+ // It's possible for variables using assignment tracking to have no
+ // dbg.assign linked to this store. These are variables in DbgAssigns that
+ // are missing from VarHasDbgAssignForStore. Since there isn't a dbg.assign
+ // to mark the assignment - and the store is going to be deleted - insert a
+ // dbg.value to do that now. An untracked store may be either one that
+ // cannot be represented using assignment tracking (non-const offset or
+ // size) or one that is trackable but has had its DIAssignID attachment
+ // dropped accidentally.
+ for (auto *DAI : DbgAssigns) {
+ if (VarHasDbgAssignForStore.contains(DebugVariable(DAI)))
+ continue;
+ ConvertDebugDeclareToDebugValue(DAI, ToDelete, DIB);
+ }
+ }
+
+ /// Update assignment tracking debug info given for the newly inserted PHI \p
+ /// NewPhi.
+ void updateForNewPhi(PHINode *NewPhi, DIBuilder &DIB) const {
+ // Regardless of the position of dbg.assigns relative to stores, the
+ // incoming values into a new PHI should be the same for the (imaginary)
+ // debug-phi.
+ for (auto *DAI : DbgAssigns)
+ ConvertDebugDeclareToDebugValue(DAI, NewPhi, DIB);
+ }
+
+ void clear() { DbgAssigns.clear(); }
+ bool empty() { return DbgAssigns.empty(); }
+};
+
struct AllocaInfo {
using DbgUserVec = SmallVector<DbgVariableIntrinsic *, 1>;
BasicBlock *OnlyBlock;
bool OnlyUsedInOneBlock;
+ /// Debug users of the alloca - does not include dbg.assign intrinsics.
DbgUserVec DbgUsers;
+ /// Helper to update assignment tracking debug info.
+ AssignmentTrackingInfo AssignmentTracking;
void clear() {
DefiningBlocks.clear();
OnlyBlock = nullptr;
OnlyUsedInOneBlock = true;
DbgUsers.clear();
+ AssignmentTracking.clear();
}
/// Scan the uses of the specified alloca, filling in the AllocaInfo used
OnlyUsedInOneBlock = false;
}
}
-
- findDbgUsers(DbgUsers, AI);
+ DbgUserVec AllDbgUsers;
+ findDbgUsers(AllDbgUsers, AI);
+ std::copy_if(AllDbgUsers.begin(), AllDbgUsers.end(),
+ std::back_inserter(DbgUsers), [](DbgVariableIntrinsic *DII) {
+ return !isa<DbgAssignIntrinsic>(DII);
+ });
+ AssignmentTracking.init(AI);
}
};
/// intrinsic if the alloca gets promoted.
SmallVector<AllocaInfo::DbgUserVec, 8> AllocaDbgUsers;
+ /// For each alloca, keep an instance of a helper class that gives us an easy
+ /// way to update assignment tracking debug info if the alloca is promoted.
+ SmallVector<AssignmentTrackingInfo, 8> AllocaATInfo;
+
/// The set of basic blocks the renamer has already visited.
SmallPtrSet<BasicBlock *, 16> Visited;
if (!Info.UsingBlocks.empty())
return false; // If not, we'll have to fall back for the remainder.
+ DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false);
+ // Update assignment tracking info for the store we're going to delete.
+ Info.AssignmentTracking.updateForDeletedStore(Info.OnlyStore, DIB);
+
// Record debuginfo for the store and remove the declaration's
// debuginfo.
for (DbgVariableIntrinsic *DII : Info.DbgUsers) {
if (DII->isAddressOfVariable()) {
- DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false);
ConvertDebugDeclareToDebugValue(DII, Info.OnlyStore, DIB);
DII->eraseFromParent();
} else if (DII->getExpression()->startsWithDeref()) {
DII->eraseFromParent();
}
}
+
+ // Remove dbg.assigns linked to the alloca as these are now redundant.
+ at::deleteAssignmentMarkers(AI);
+
// Remove the (now dead) store and alloca.
Info.OnlyStore->eraseFromParent();
LBI.deleteValue(Info.OnlyStore);
}
// Remove the (now dead) stores and alloca.
+ DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false);
while (!AI->use_empty()) {
StoreInst *SI = cast<StoreInst>(AI->user_back());
+ // Update assignment tracking info for the store we're going to delete.
+ Info.AssignmentTracking.updateForDeletedStore(SI, DIB);
// Record debuginfo for the store before removing it.
for (DbgVariableIntrinsic *DII : Info.DbgUsers) {
if (DII->isAddressOfVariable()) {
- DIBuilder DIB(*AI->getModule(), /*AllowUnresolved*/ false);
ConvertDebugDeclareToDebugValue(DII, SI, DIB);
}
}
LBI.deleteValue(SI);
}
+ // Remove dbg.assigns linked to the alloca as these are now redundant.
+ at::deleteAssignmentMarkers(AI);
AI->eraseFromParent();
// The alloca's debuginfo can be removed as well.
Function &F = *DT.getRoot()->getParent();
AllocaDbgUsers.resize(Allocas.size());
+ AllocaATInfo.resize(Allocas.size());
AllocaInfo Info;
LargeBlockInfo LBI;
// Remember the dbg.declare intrinsic describing this alloca, if any.
if (!Info.DbgUsers.empty())
AllocaDbgUsers[AllocaNum] = Info.DbgUsers;
+ if (!Info.AssignmentTracking.empty())
+ AllocaATInfo[AllocaNum] = Info.AssignmentTracking;
// Keep the reverse mapping of the 'Allocas' array for the rename pass.
AllocaLookup[Allocas[AllocaNum]] = AllocaNum;
// Remove the allocas themselves from the function.
for (Instruction *A : Allocas) {
+ // Remove dbg.assigns linked to the alloca as these are now redundant.
+ at::deleteAssignmentMarkers(A);
// If there are any uses of the alloca instructions left, they must be in
// unreachable basic blocks that were not processed by walking the dominator
// tree. Just delete the users now.
// The currently active variable for this block is now the PHI.
IncomingVals[AllocaNo] = APN;
+ AllocaATInfo[AllocaNo].updateForNewPhi(APN, DIB);
for (DbgVariableIntrinsic *DII : AllocaDbgUsers[AllocaNo])
if (DII->isAddressOfVariable())
ConvertDebugDeclareToDebugValue(DII, APN, DIB);
// Record debuginfo for the store before removing it.
IncomingLocs[AllocaNo] = SI->getDebugLoc();
+ AllocaATInfo[AllocaNo].updateForDeletedStore(SI, DIB);
for (DbgVariableIntrinsic *DII : AllocaDbgUsers[ai->second])
if (DII->isAddressOfVariable())
ConvertDebugDeclareToDebugValue(DII, SI, DIB);
--- /dev/null
+; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+;; Test assignment tracking debug info when mem2reg promotes an alloca with
+;; stores requiring insertion of a phi. Check the output when the stores are
+;; tagged and also untagged (test manually updated for the latter by linking a
+;; dbg.assgin for another variable "b" to the alloca).
+
+; CHECK: entry:
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef
+; CHECK: if.then:
+; CHECK-NEXT: %add =
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %add, metadata ![[B]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %add, metadata ![[A]], {{.*}}, metadata i32* undef
+; CHECK: if.else:
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 -1, metadata ![[B]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 -1, metadata ![[A]], {{.*}}, metadata i32* undef
+; CHECK: if.end:
+; CHECK-NEXT: %a.addr.0 = phi i32
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a.addr.0, metadata ![[A]]
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a.addr.0, metadata ![[B]]
+
+; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a",
+; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b",
+
+;; $ cat test.cpp
+;; int f(int a) {
+;; if (a)
+;; a += 1;
+;; else
+;; a = -1;
+;; return a;
+;; }
+
+define dso_local noundef i32 @_Z1fi(i32 noundef %a) #0 !dbg !7 {
+entry:
+ %a.addr = alloca i32, align 4, !DIAssignID !13
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !30, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ store i32 %a, i32* %a.addr, align 4, !DIAssignID !19
+ call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ %0 = load i32, i32* %a.addr, align 4, !dbg !20
+ %tobool = icmp ne i32 %0, 0, !dbg !20
+ br i1 %tobool, label %if.then, label %if.else, !dbg !22
+
+if.then: ; preds = %entry
+ %1 = load i32, i32* %a.addr, align 4, !dbg !23
+ %add = add nsw i32 %1, 1, !dbg !23
+ store i32 %add, i32* %a.addr, align 4, !dbg !23, !DIAssignID !24
+ call void @llvm.dbg.assign(metadata i32 %add, metadata !12, metadata !DIExpression(), metadata !24, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ br label %if.end, !dbg !25
+
+if.else: ; preds = %entry
+ store i32 -1, i32* %a.addr, align 4, !dbg !26, !DIAssignID !27
+ call void @llvm.dbg.assign(metadata i32 -1, metadata !12, metadata !DIExpression(), metadata !27, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ %2 = load i32, i32* %a.addr, align 4, !dbg !28
+ ret i32 %2, !dbg !29
+}
+
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"uwtable", i32 1}
+!6 = !{!"clang version 14.0.0"}
+!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!10, !10}
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10)
+!13 = distinct !DIAssignID()
+!14 = !DILocation(line: 0, scope: !7)
+!19 = distinct !DIAssignID()
+!20 = !DILocation(line: 2, column: 7, scope: !21)
+!21 = distinct !DILexicalBlock(scope: !7, file: !1, line: 2, column: 7)
+!22 = !DILocation(line: 2, column: 7, scope: !7)
+!23 = !DILocation(line: 3, column: 7, scope: !21)
+!24 = distinct !DIAssignID()
+!25 = !DILocation(line: 3, column: 5, scope: !21)
+!26 = !DILocation(line: 5, column: 7, scope: !21)
+!27 = distinct !DIAssignID()
+!28 = !DILocation(line: 6, column: 10, scope: !7)
+!29 = !DILocation(line: 6, column: 3, scope: !7)
+!30 = !DILocalVariable(name: "b", arg: 2, scope: !7, file: !1, line: 1, type: !10)
--- /dev/null
+; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+;; Test assignment tracking debug info when mem2reg promotes a single-block
+;; alloca. Check the output when the stores are tagged and also untagged (test
+;; manually updated for the latter by linking a dbg.assgin for another variable
+;; "b" to the alloca).
+
+; CHECK: entry:
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef
+; CHECK-NEXT: %add =
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %add, metadata ![[B]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %add, metadata ![[A]], {{.*}}, metadata i32* undef
+
+; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a",
+; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b",
+
+;; $ cat test.cpp
+;; int f(int a) {
+;; a += 1;
+;; return a;
+;; }
+
+define dso_local noundef i32 @_Z1fi(i32 noundef %a) !dbg !7 {
+entry:
+ %a.addr = alloca i32, align 4, !DIAssignID !13
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !24, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ store i32 %a, i32* %a.addr, align 4, !DIAssignID !19
+ call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ %0 = load i32, i32* %a.addr, align 4, !dbg !20
+ %add = add nsw i32 %0, 1, !dbg !20
+ store i32 %add, i32* %a.addr, align 4, !dbg !20, !DIAssignID !21
+ call void @llvm.dbg.assign(metadata i32 %add, metadata !12, metadata !DIExpression(), metadata !21, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ %1 = load i32, i32* %a.addr, align 4, !dbg !22
+ ret i32 %1, !dbg !23
+}
+
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"uwtable", i32 1}
+!6 = !{!"clang version 14.0.0"}
+!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!10, !10}
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10)
+!13 = distinct !DIAssignID()
+!14 = !DILocation(line: 0, scope: !7)
+!19 = distinct !DIAssignID()
+!20 = !DILocation(line: 2, column: 5, scope: !7)
+!21 = distinct !DIAssignID()
+!22 = !DILocation(line: 3, column: 10, scope: !7)
+!23 = !DILocation(line: 3, column: 3, scope: !7)
+!24 = !DILocalVariable(name: "b", scope: !7, file: !1, line: 1, type: !10)
--- /dev/null
+; RUN: opt -passes=mem2reg -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+;; Test assignment tracking debug info when mem2reg promotes a single-store
+;; alloca. Additionally, check that all the dbg.assigns linked to the alloca
+;; are cleaned up, including duplciates.
+
+; CHECK: entry:
+; CHECK-NEXT: call void @llvm.dbg.value(metadata i32 %a, metadata ![[B:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %a, metadata ![[A:[0-9]+]], {{.*}}, metadata i32* undef
+; CHECK-NEXT: ret
+
+; CHECK-DAG: ![[A]] = !DILocalVariable(name: "a",
+; CHECK-DAG: ![[B]] = !DILocalVariable(name: "b",
+
+;; 1. using source:
+;; $ cat test.cpp
+;; int f(int a) { return a; }
+;; 2. manually duplicating the dbg.assign lnked to a's alloca to ensure
+;; duplicates are still cleaned up,
+;; 3. and with a dbg.assign for another variable ("b") attached to a's alloca
+;; to check that a dbg.value is generated for the variable to represent the
+;; store despite the store not having a dbg.assign linked for it.
+
+; Function Attrs: mustprogress nounwind uwtable
+define dso_local noundef i32 @_Z1fi(i32 noundef %a) #0 !dbg !7 {
+entry:
+ %a.addr = alloca i32, align 4, !DIAssignID !13
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ call void @llvm.dbg.assign(metadata i1 undef, metadata !22, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ store i32 %a, i32* %a.addr, align 4, !DIAssignID !19
+ call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !19, metadata i32* %a.addr, metadata !DIExpression()), !dbg !14
+ %0 = load i32, i32* %a.addr, align 4, !dbg !20
+ ret i32 %0, !dbg !21
+}
+
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"uwtable", i32 1}
+!6 = !{!"clang version 14.0.0)"}
+!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!10, !10}
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 1, type: !10)
+!13 = distinct !DIAssignID()
+!14 = !DILocation(line: 0, scope: !7)
+!19 = distinct !DIAssignID()
+!20 = !DILocation(line: 1, column: 23, scope: !7)
+!21 = !DILocation(line: 1, column: 16, scope: !7)
+!22 = !DILocalVariable(name: "b", scope: !7, file: !1, line: 1, type: !10)