[Assignment Tracking][12/*] Account for assignment tracking in mem2reg
authorOCHyams <orlando.hyams@sony.com>
Tue, 15 Nov 2022 10:52:45 +0000 (10:52 +0000)
committerOCHyams <orlando.hyams@sony.com>
Tue, 15 Nov 2022 11:11:57 +0000 (11:11 +0000)
The Assignment Tracking debug-info feature is outlined in this RFC:

https://discourse.llvm.org/t/
rfc-assignment-tracking-a-better-way-of-specifying-variable-locations-in-ir

The changes for assignment tracking in mem2reg don't require much of a
deviation from existing behaviour. dbg.assign intrinsics linked to an alloca
are treated much in the same way as dbg.declare users of an alloca, except that
we don't insert dbg.value intrinsics to describe assignments when there is
already a dbg.assign intrinsic present, e.g. one linked to a store that is
going to be removed.

Reviewed By: jmorse

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

llvm/lib/Transforms/Utils/Local.cpp
llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll [new file with mode: 0644]
llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll [new file with mode: 0644]
llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll [new file with mode: 0644]

index bef22ff..a68d834 100644 (file)
@@ -1499,7 +1499,7 @@ static bool valueCoversEntireFragment(Type *ValTy, DbgVariableIntrinsic *DII) {
 /// 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();
index bec1db8..195b1bc 100644 (file)
@@ -100,6 +100,67 @@ bool llvm::isAllocaPromotable(const AllocaInst *AI) {
 
 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>;
 
@@ -110,7 +171,10 @@ struct AllocaInfo {
   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();
@@ -119,6 +183,7 @@ struct AllocaInfo {
     OnlyBlock = nullptr;
     OnlyUsedInOneBlock = true;
     DbgUsers.clear();
+    AssignmentTracking.clear();
   }
 
   /// Scan the uses of the specified alloca, filling in the AllocaInfo used
@@ -150,8 +215,13 @@ struct AllocaInfo {
           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);
   }
 };
 
@@ -251,6 +321,10 @@ struct PromoteMem2Reg {
   /// 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;
 
@@ -417,17 +491,24 @@ static bool rewriteSingleStoreAlloca(AllocaInst *AI, AllocaInfo &Info,
   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);
@@ -520,12 +601,14 @@ static bool promoteSingleBlockAlloca(AllocaInst *AI, const AllocaInfo &Info,
   }
 
   // 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);
       }
     }
@@ -533,6 +616,8 @@ static bool promoteSingleBlockAlloca(AllocaInst *AI, const AllocaInfo &Info,
     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.
@@ -548,6 +633,7 @@ void PromoteMem2Reg::run() {
   Function &F = *DT.getRoot()->getParent();
 
   AllocaDbgUsers.resize(Allocas.size());
+  AllocaATInfo.resize(Allocas.size());
 
   AllocaInfo Info;
   LargeBlockInfo LBI;
@@ -607,6 +693,8 @@ void PromoteMem2Reg::run() {
     // 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;
@@ -670,6 +758,8 @@ void PromoteMem2Reg::run() {
 
   // 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.
@@ -923,6 +1013,7 @@ NextIteration:
 
         // 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);
@@ -984,6 +1075,7 @@ NextIteration:
 
       // 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);
diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/phi.ll
new file mode 100644 (file)
index 0000000..ad81a42
--- /dev/null
@@ -0,0 +1,96 @@
+; 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)
diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-block-alloca.ll
new file mode 100644 (file)
index 0000000..c03e71d
--- /dev/null
@@ -0,0 +1,66 @@
+; 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)
diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/mem2reg/single-store-alloca.ll
new file mode 100644 (file)
index 0000000..c64a265
--- /dev/null
@@ -0,0 +1,62 @@
+; 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)