[llvm] f354716 - Reapply [Assignment Tracking][13/*] Account for assignment tracking in SROA

via llvm-commits llvm-commits at lists.llvm.org
Tue Dec 13 04:53:35 PST 2022


Author: OCHyams
Date: 2022-12-13T12:52:45Z
New Revision: f354716b052d19b4543617dfe3954503a6827ff8

URL: https://github.com/llvm/llvm-project/commit/f354716b052d19b4543617dfe3954503a6827ff8
DIFF: https://github.com/llvm/llvm-project/commit/f354716b052d19b4543617dfe3954503a6827ff8.diff

LOG: Reapply [Assignment Tracking][13/*] Account for assignment tracking in SROA

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

Split dbg.assign intrinsics into fragments similarly to what SROA already does
for dbg.declares, except that there's many more intrinsics to split. The
function migrateDebugInfo generates new dbg.assigns intrinsic for each part of
a split store.

Reviewed By: jmorse

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

Added: 
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/after-inlining.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/alloca-single-slice.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/complex.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag-2.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/id.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memcpy.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memmove-to-from-same-alloca.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/rewrite.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/store.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/unspecified-var-size.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/user-memcpy.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-1.ll
    llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-2.ll

Modified: 
    llvm/lib/Transforms/Scalar/SROA.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index f592b34dd5592..748b8627be7ed 100644
--- a/llvm/lib/Transforms/Scalar/SROA.cpp
+++ b/llvm/lib/Transforms/Scalar/SROA.cpp
@@ -119,6 +119,110 @@ STATISTIC(NumVectorized, "Number of vectorized aggregates");
 static cl::opt<bool> SROAStrictInbounds("sroa-strict-inbounds", cl::init(false),
                                         cl::Hidden);
 namespace {
+/// Find linked dbg.assign and generate a new one with the correct
+/// FragmentInfo. Link Inst to the new dbg.assign.  If Value is nullptr the
+/// value component is copied from the old dbg.assign to the new.
+/// \param OldAlloca             Alloca for the variable before splitting.
+/// \param RelativeOffsetInBits  Offset into \p OldAlloca relative to the
+///                              offset prior to splitting (change in offset).
+/// \param SliceSizeInBits       New number of bits being written to.
+/// \param OldInst               Instruction that is being split.
+/// \param Inst                  New instruction performing this part of the
+///                              split store.
+/// \param Dest                  Store destination.
+/// \param Value                 Stored value.
+/// \param DL                    Datalayout.
+static void migrateDebugInfo(AllocaInst *OldAlloca,
+                             uint64_t RelativeOffsetInBits,
+                             uint64_t SliceSizeInBits, Instruction *OldInst,
+                             Instruction *Inst, Value *Dest, Value *Value,
+                             const DataLayout &DL) {
+  auto MarkerRange = at::getAssignmentMarkers(OldInst);
+  // Nothing to do if OldInst has no linked dbg.assign intrinsics.
+  if (MarkerRange.empty())
+    return;
+
+  LLVM_DEBUG(dbgs() << "  migrateDebugInfo\n");
+  LLVM_DEBUG(dbgs() << "    OldAlloca: " << *OldAlloca << "\n");
+  LLVM_DEBUG(dbgs() << "    RelativeOffset: " << RelativeOffsetInBits << "\n");
+  LLVM_DEBUG(dbgs() << "    SliceSizeInBits: " << SliceSizeInBits << "\n");
+  LLVM_DEBUG(dbgs() << "    OldInst: " << *OldInst << "\n");
+  LLVM_DEBUG(dbgs() << "    Inst: " << *Inst << "\n");
+  LLVM_DEBUG(dbgs() << "    Dest: " << *Dest << "\n");
+  if (Value)
+    LLVM_DEBUG(dbgs() << "    Value: " << *Value << "\n");
+
+  // The new inst needs a DIAssignID unique metadata tag (if OldInst has
+  // one). It shouldn't already have one: assert this assumption.
+  assert(!Inst->getMetadata(LLVMContext::MD_DIAssignID));
+  DIAssignID *NewID = nullptr;
+  auto &Ctx = Inst->getContext();
+  DIBuilder DIB(*OldInst->getModule(), /*AllowUnresolved*/ false);
+  uint64_t AllocaSizeInBits = *OldAlloca->getAllocationSizeInBits(DL);
+  assert(OldAlloca->isStaticAlloca());
+
+  for (DbgAssignIntrinsic *DbgAssign : MarkerRange) {
+    LLVM_DEBUG(dbgs() << "      existing dbg.assign is: " << *DbgAssign
+                      << "\n");
+    auto *Expr = DbgAssign->getExpression();
+
+    // Check if the dbg.assign already describes a fragment.
+    auto GetCurrentFragSize = [AllocaSizeInBits, DbgAssign,
+                               Expr]() -> uint64_t {
+      if (auto FI = Expr->getFragmentInfo())
+        return FI->SizeInBits;
+      if (auto VarSize = DbgAssign->getVariable()->getSizeInBits())
+        return *VarSize;
+      // The variable type has an unspecified size. This can happen in the
+      // case of DW_TAG_unspecified_type types, e.g.  std::nullptr_t. Because
+      // there is no fragment and we do not know the size of the variable type,
+      // we'll guess by looking at the alloca.
+      return AllocaSizeInBits;
+    };
+    uint64_t CurrentFragSize = GetCurrentFragSize();
+    bool MakeNewFragment = CurrentFragSize != SliceSizeInBits;
+    assert(MakeNewFragment || RelativeOffsetInBits == 0);
+
+    assert(SliceSizeInBits <= AllocaSizeInBits);
+    if (MakeNewFragment) {
+      assert(RelativeOffsetInBits + SliceSizeInBits <= CurrentFragSize);
+      auto E = DIExpression::createFragmentExpression(
+          Expr, RelativeOffsetInBits, SliceSizeInBits);
+      assert(E && "Failed to create fragment expr!");
+      Expr = *E;
+    }
+
+    // If we haven't created a DIAssignID ID do that now and attach it to Inst.
+    if (!NewID) {
+      NewID = DIAssignID::getDistinct(Ctx);
+      Inst->setMetadata(LLVMContext::MD_DIAssignID, NewID);
+    }
+
+    Value = Value ? Value : DbgAssign->getValue();
+    auto *NewAssign = DIB.insertDbgAssign(
+        Inst, Value, DbgAssign->getVariable(), Expr, Dest,
+        DIExpression::get(Ctx, std::nullopt), DbgAssign->getDebugLoc());
+
+    // We could use more precision here at the cost of some additional (code)
+    // complexity - if the original dbg.assign was adjacent to its store, we
+    // could position this new dbg.assign adjacent to its store rather than the
+    // old dbg.assgn. That would result in interleaved dbg.assigns rather than
+    // what we get now:
+    //    split store !1
+    //    split store !2
+    //    dbg.assign !1
+    //    dbg.assign !2
+    // This (current behaviour) results results in debug assignments being
+    // noted as slightly offset (in code) from the store. In practice this
+    // should have little effect on the debugging experience due to the fact
+    // that all the split stores should get the same line number.
+    NewAssign->moveBefore(DbgAssign);
+
+    NewAssign->setDebugLoc(DbgAssign->getDebugLoc());
+    LLVM_DEBUG(dbgs() << "Created new assign intrinsic: " << *NewAssign
+                      << "\n");
+  }
+}
 
 /// A custom IRBuilder inserter which prefixes all names, but only in
 /// Assert builds.
@@ -2440,6 +2544,7 @@ class llvm::sroa::AllocaSliceRewriter
   // original alloca.
   uint64_t NewBeginOffset = 0, NewEndOffset = 0;
 
+  uint64_t RelativeOffset = 0;
   uint64_t SliceSize = 0;
   bool IsSplittable = false;
   bool IsSplit = false;
@@ -2513,8 +2618,14 @@ class llvm::sroa::AllocaSliceRewriter
     NewBeginOffset = std::max(BeginOffset, NewAllocaBeginOffset);
     NewEndOffset = std::min(EndOffset, NewAllocaEndOffset);
 
+    RelativeOffset = NewBeginOffset - BeginOffset;
     SliceSize = NewEndOffset - NewBeginOffset;
-
+    LLVM_DEBUG(dbgs() << "   Begin:(" << BeginOffset << ", " << EndOffset
+                      << ") NewBegin:(" << NewBeginOffset << ", "
+                      << NewEndOffset << ") NewAllocaBegin:("
+                      << NewAllocaBeginOffset << ", " << NewAllocaEndOffset
+                      << ")\n");
+    assert(IsSplit || RelativeOffset == 0);
     OldUse = I->getUse();
     OldPtr = cast<Instruction>(OldUse->get());
 
@@ -2754,6 +2865,9 @@ class llvm::sroa::AllocaSliceRewriter
 
   bool rewriteVectorizedStoreInst(Value *V, StoreInst &SI, Value *OldOp,
                                   AAMDNodes AATags) {
+    // Capture V for the purpose of debug-info accounting once it's converted
+    // to a vector store.
+    Value *OrigV = V;
     if (V->getType() != VecTy) {
       unsigned BeginIndex = getIndex(NewBeginOffset);
       unsigned EndIndex = getIndex(NewEndOffset);
@@ -2779,6 +2893,9 @@ class llvm::sroa::AllocaSliceRewriter
       Store->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
     Pass.DeadInsts.push_back(&SI);
 
+    // NOTE: Careful to use OrigV rather than V.
+    migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &SI, Store,
+                     Store->getPointerOperand(), OrigV, DL);
     LLVM_DEBUG(dbgs() << "          to: " << *Store << "\n");
     return true;
   }
@@ -2801,6 +2918,10 @@ class llvm::sroa::AllocaSliceRewriter
                              LLVMContext::MD_access_group});
     if (AATags)
       Store->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
+
+    migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &SI, Store,
+                     Store->getPointerOperand(), Store->getValueOperand(), DL);
+
     Pass.DeadInsts.push_back(&SI);
     LLVM_DEBUG(dbgs() << "          to: " << *Store << "\n");
     return true;
@@ -2876,6 +2997,10 @@ class llvm::sroa::AllocaSliceRewriter
       NewSI->setAtomic(SI.getOrdering(), SI.getSyncScopeID());
     if (NewSI->isAtomic())
       NewSI->setAlignment(SI.getAlign());
+
+    migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &SI, NewSI,
+                     NewSI->getPointerOperand(), NewSI->getValueOperand(), DL);
+
     Pass.DeadInsts.push_back(&SI);
     deleteIfTriviallyDead(OldOp);
 
@@ -2931,7 +3056,11 @@ class llvm::sroa::AllocaSliceRewriter
       assert(NewBeginOffset == BeginOffset);
       II.setDest(getNewAllocaSlicePtr(IRB, OldPtr->getType()));
       II.setDestAlignment(getSliceAlign());
-
+      // In theory we should call migrateDebugInfo here. However, we do not
+      // emit dbg.assign intrinsics for mem intrinsics storing through non-
+      // constant geps, or storing a variable number of bytes.
+      assert(at::getAssignmentMarkers(&II).empty() &&
+             "AT: Unexpected link to non-const GEP");
       deleteIfTriviallyDead(OldPtr);
       return false;
     }
@@ -2964,11 +3093,15 @@ class llvm::sroa::AllocaSliceRewriter
     if (!CanContinue) {
       Type *SizeTy = II.getLength()->getType();
       Constant *Size = ConstantInt::get(SizeTy, NewEndOffset - NewBeginOffset);
-      CallInst *New = IRB.CreateMemSet(
+      MemIntrinsic *New = cast<MemIntrinsic>(IRB.CreateMemSet(
           getNewAllocaSlicePtr(IRB, OldPtr->getType()), II.getValue(), Size,
-          MaybeAlign(getSliceAlign()), II.isVolatile());
+          MaybeAlign(getSliceAlign()), II.isVolatile()));
       if (AATags)
         New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
+
+      migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New,
+                       New->getRawDest(), nullptr, DL);
+
       LLVM_DEBUG(dbgs() << "          to: " << *New << "\n");
       return false;
     }
@@ -3041,6 +3174,10 @@ class llvm::sroa::AllocaSliceRewriter
                            LLVMContext::MD_access_group});
     if (AATags)
       New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
+
+    migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New,
+                     New->getPointerOperand(), V, DL);
+
     LLVM_DEBUG(dbgs() << "          to: " << *New << "\n");
     return !II.isVolatile();
   }
@@ -3058,7 +3195,6 @@ class llvm::sroa::AllocaSliceRewriter
            (!IsDest && II.getRawSource() == OldPtr));
 
     Align SliceAlign = getSliceAlign();
-
     // For unsplit intrinsics, we simply modify the source and destination
     // pointers in place. This isn't just an optimization, it is a matter of
     // correctness. With unsplit intrinsics we may be dealing with transfers
@@ -3069,10 +3205,16 @@ class llvm::sroa::AllocaSliceRewriter
     if (!IsSplittable) {
       Value *AdjustedPtr = getNewAllocaSlicePtr(IRB, OldPtr->getType());
       if (IsDest) {
+        // Update the address component of linked dbg.assigns.
+        for (auto *DAI : at::getAssignmentMarkers(&II)) {
+          if (any_of(DAI->location_ops(),
+                     [&](Value *V) { return V == II.getDest(); }) ||
+              DAI->getAddress() == II.getDest())
+            DAI->replaceVariableLocationOp(II.getDest(), AdjustedPtr);
+        }
         II.setDest(AdjustedPtr);
         II.setDestAlignment(SliceAlign);
-      }
-      else {
+      } else {
         II.setSource(AdjustedPtr);
         II.setSourceAlignment(SliceAlign);
       }
@@ -3161,6 +3303,9 @@ class llvm::sroa::AllocaSliceRewriter
                                        Size, II.isVolatile());
       if (AATags)
         New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
+
+      migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New,
+                       DestPtr, nullptr, DL);
       LLVM_DEBUG(dbgs() << "          to: " << *New << "\n");
       return false;
     }
@@ -3247,6 +3392,9 @@ class llvm::sroa::AllocaSliceRewriter
                              LLVMContext::MD_access_group});
     if (AATags)
       Store->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset));
+
+    migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, Store,
+                     DstPtr, Src, DL);
     LLVM_DEBUG(dbgs() << "          to: " << *Store << "\n");
     return !II.isVolatile();
   }
@@ -3584,12 +3732,13 @@ class AggLoadStoreRewriter : public InstVisitor<AggLoadStoreRewriter, bool> {
 
   struct StoreOpSplitter : public OpSplitter<StoreOpSplitter> {
     StoreOpSplitter(Instruction *InsertionPoint, Value *Ptr, Type *BaseTy,
-                    AAMDNodes AATags, Align BaseAlign, const DataLayout &DL,
-                    IRBuilderTy &IRB)
+                    AAMDNodes AATags, StoreInst *AggStore, Align BaseAlign,
+                    const DataLayout &DL, IRBuilderTy &IRB)
         : OpSplitter<StoreOpSplitter>(InsertionPoint, Ptr, BaseTy, BaseAlign,
                                       DL, IRB),
-          AATags(AATags) {}
+          AATags(AATags), AggStore(AggStore) {}
     AAMDNodes AATags;
+    StoreInst *AggStore;
     /// Emit a leaf store of a single value. This is called at the leaves of the
     /// recursive emission to actually produce stores.
     void emitFunc(Type *Ty, Value *&Agg, Align Alignment, const Twine &Name) {
@@ -3611,6 +3760,24 @@ class AggLoadStoreRewriter : public InstVisitor<AggLoadStoreRewriter, bool> {
           GEPOperator::accumulateConstantOffset(BaseTy, GEPIndices, DL, Offset))
         Store->setAAMetadata(AATags.shift(Offset.getZExtValue()));
 
+      // migrateDebugInfo requires the base Alloca. Walk to it from this gep.
+      // If we cannot (because there's an intervening non-const or unbounded
+      // gep) then we wouldn't expect to see dbg.assign intrinsics linked to
+      // this instruction.
+      APInt OffsetInBytes(DL.getTypeSizeInBits(Ptr->getType()), false);
+      Value *Base = InBoundsGEP->stripAndAccumulateInBoundsConstantOffsets(
+          DL, OffsetInBytes);
+      if (auto *OldAI = dyn_cast<AllocaInst>(Base)) {
+        uint64_t SizeInBits =
+            DL.getTypeSizeInBits(Store->getValueOperand()->getType());
+        migrateDebugInfo(OldAI, OffsetInBytes.getZExtValue() * 8, SizeInBits,
+                         AggStore, Store, Store->getPointerOperand(),
+                         Store->getValueOperand(), DL);
+      } else {
+        assert(at::getAssignmentMarkers(Store).empty() &&
+               "AT: unexpected debug.assign linked to store through "
+               "unbounded GEP");
+      }
       LLVM_DEBUG(dbgs() << "          to: " << *Store << "\n");
     }
   };
@@ -3624,7 +3791,7 @@ class AggLoadStoreRewriter : public InstVisitor<AggLoadStoreRewriter, bool> {
 
     // We have an aggregate being stored, split it apart.
     LLVM_DEBUG(dbgs() << "    original: " << SI << "\n");
-    StoreOpSplitter Splitter(&SI, *U, V->getType(), SI.getAAMetadata(),
+    StoreOpSplitter Splitter(&SI, *U, V->getType(), SI.getAAMetadata(), &SI,
                              getAdjustedAlignment(&SI, 0), DL, IRB);
     Splitter.emitSplitOps(V->getType(), V, V->getName() + ".fca");
     Visited.erase(&SI);
@@ -4225,7 +4392,8 @@ bool SROAPass::presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS) {
             getAdjustedAlignment(SI, PartOffset),
             /*IsVolatile*/ false);
         PStore->copyMetadata(*SI, {LLVMContext::MD_mem_parallel_loop_access,
-                                   LLVMContext::MD_access_group});
+                                   LLVMContext::MD_access_group,
+                                   LLVMContext::MD_DIAssignID});
         LLVM_DEBUG(dbgs() << "      +" << PartOffset << ":" << *PStore << "\n");
       }
 
@@ -4698,6 +4866,8 @@ bool SROAPass::splitAlloca(AllocaInst &AI, AllocaSlices &AS) {
   // Migrate debug information from the old alloca to the new alloca(s)
   // and the individual partitions.
   TinyPtrVector<DbgVariableIntrinsic *> DbgDeclares = FindDbgAddrUses(&AI);
+  for (auto *DbgAssign : at::getAssignmentMarkers(&AI))
+    DbgDeclares.push_back(DbgAssign);
   for (DbgVariableIntrinsic *DbgDeclare : DbgDeclares) {
     auto *Expr = DbgDeclare->getExpression();
     DIBuilder DIB(*AI.getModule(), /*AllowUnresolved*/ false);
@@ -4717,9 +4887,10 @@ bool SROAPass::splitAlloca(AllocaInst &AI, AllocaSlices &AS) {
         if (ExprFragment) {
           uint64_t AbsEnd =
               ExprFragment->OffsetInBits + ExprFragment->SizeInBits;
-          if (Start >= AbsEnd)
+          if (Start >= AbsEnd) {
             // No need to describe a SROAed padding.
             continue;
+          }
           Size = std::min(Size, AbsEnd - Start);
         }
         // The new, smaller fragment is stenciled out from the old fragment.
@@ -4761,8 +4932,23 @@ bool SROAPass::splitAlloca(AllocaInst &AI, AllocaSlices &AS) {
           OldDII->eraseFromParent();
       }
 
-      DIB.insertDeclare(Fragment.Alloca, DbgDeclare->getVariable(), FragmentExpr,
-                        DbgDeclare->getDebugLoc(), &AI);
+      if (auto *DbgAssign = dyn_cast<DbgAssignIntrinsic>(DbgDeclare)) {
+        if (!Fragment.Alloca->hasMetadata(LLVMContext::MD_DIAssignID)) {
+          Fragment.Alloca->setMetadata(
+              LLVMContext::MD_DIAssignID,
+              DIAssignID::getDistinct(AI.getContext()));
+        }
+        auto *NewAssign = DIB.insertDbgAssign(
+            Fragment.Alloca, DbgAssign->getValue(), DbgAssign->getVariable(),
+            FragmentExpr, Fragment.Alloca, DbgAssign->getAddressExpression(),
+            DbgAssign->getDebugLoc());
+        NewAssign->setDebugLoc(DbgAssign->getDebugLoc());
+        LLVM_DEBUG(dbgs() << "Created new assign intrinsic: " << *NewAssign
+                          << "\n");
+      } else {
+        DIB.insertDeclare(Fragment.Alloca, DbgDeclare->getVariable(),
+                          FragmentExpr, DbgDeclare->getDebugLoc(), &AI);
+      }
     }
   }
   return Changed;
@@ -4888,6 +5074,7 @@ bool SROAPass::deleteDeadInstructions(
         OldDII->eraseFromParent();
     }
 
+    at::deleteAssignmentMarkers(I);
     I->replaceAllUsesWith(UndefValue::get(I->getType()));
 
     for (Use &Operand : I->operands())

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/after-inlining.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/after-inlining.ll
new file mode 100644
index 0000000000000..96fed34d8a68c
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/after-inlining.ll
@@ -0,0 +1,144 @@
+; RUN: opt %s -S -passes=sroa -o - -experimental-assignment-tracking | FileCheck %s
+
+;; Check that SROA preserves the InlinedAt status of new dbg.assign intriniscs
+;; it inserts.
+
+;; $cat test.c
+;; typedef struct {
+;;   int a;
+;;   int b[];
+;; } c;
+;; int d, e, f;
+;; void g(c *h) {
+;;   if (d)
+;;     h->a = 1;
+;; }
+;; void i(c *h) {
+;;   long j = f = 0;
+;;   for (; f < h->a; f++)
+;;     j += h->b[f];
+;;   e = j;
+;; }
+;; void k() {
+;;   c j;
+;;   g(&j);
+;;   i(&j);
+;; }
+;; void l() { k(); }
+;;
+;; $ clang test.c -Xclang -fexperimental-assignment-tracking  -O2 -g
+
+; CHECK: call void @llvm.dbg.assign(metadata i1 undef, metadata !{{.+}}, metadata !DIExpression(), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg ![[DBG:[0-9]+]]
+
+; CHECK-DAG: ![[DBG]] = !DILocation(line: 0, scope: ![[INL_SC:[0-9]+]], inlinedAt: ![[IA:[0-9]+]])
+; CHECK-DAG: ![[IA]] = distinct !DILocation(line: 21, column: 12, scope: ![[SC:[0-9]+]])
+; CHECK-DAG: ![[SC]] = distinct !DISubprogram(name: "l",
+; CHECK-DAG: ![[INL_SC]] = distinct !DISubprogram(name: "k"
+
+%struct.c = type { i32, [0 x i32] }
+
+ at f = dso_local local_unnamed_addr global i32 0, align 4, !dbg !9
+ at e = dso_local local_unnamed_addr global i32 0, align 4, !dbg !6
+
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #2
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #2
+
+define dso_local void @l() local_unnamed_addr #4 !dbg !73 {
+entry:
+  %j.i = alloca %struct.c, align 4, !DIAssignID !74
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !64, metadata !DIExpression(), metadata !74, metadata ptr %j.i, metadata !DIExpression()) #5, !dbg !75
+  %0 = bitcast ptr %j.i to ptr, !dbg !77
+  call void @llvm.lifetime.start.p0i8(i64 4, ptr nonnull %0) #5, !dbg !77
+  %arrayidx.i.i = getelementptr inbounds %struct.c, ptr %j.i, i64 0, i32 1, i64 0, !dbg !78
+  %1 = load i32, ptr %arrayidx.i.i, align 4, !dbg !78
+  store i32 1, ptr @f, align 4, !dbg !80
+  store i32 %1, ptr @e, align 4, !dbg !81
+  call void @llvm.lifetime.end.p0i8(i64 4, ptr nonnull %0) #5, !dbg !82
+  ret void, !dbg !83
+}
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!11, !12, !13}
+!llvm.ident = !{!14}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "d", scope: !2, file: !3, line: 5, type: !8, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "test.c", directory: "/")
+!4 = !{}
+!5 = !{!0, !6, !9}
+!6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
+!7 = distinct !DIGlobalVariable(name: "e", scope: !2, file: !3, line: 5, type: !8, isLocal: false, isDefinition: true)
+!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!9 = !DIGlobalVariableExpression(var: !10, expr: !DIExpression())
+!10 = distinct !DIGlobalVariable(name: "f", scope: !2, file: !3, line: 5, type: !8, isLocal: false, isDefinition: true)
+!11 = !{i32 7, !"Dwarf Version", i32 4}
+!12 = !{i32 2, !"Debug Info Version", i32 3}
+!13 = !{i32 1, !"wchar_size", i32 4}
+!14 = !{!"clang version 12.0.0)"}
+!15 = distinct !DISubprogram(name: "g", scope: !3, file: !3, line: 6, type: !16, scopeLine: 6, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !27)
+!16 = !DISubroutineType(types: !17)
+!17 = !{null, !18}
+!18 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !19, size: 64)
+!19 = !DIDerivedType(tag: DW_TAG_typedef, name: "c", file: !3, line: 4, baseType: !20)
+!20 = distinct !DICompositeType(tag: DW_TAG_structure_type, file: !3, line: 1, size: 32, elements: !21)
+!21 = !{!22, !23}
+!22 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !20, file: !3, line: 2, baseType: !8, size: 32)
+!23 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !20, file: !3, line: 3, baseType: !24, offset: 32)
+!24 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, elements: !25)
+!25 = !{!26}
+!26 = !DISubrange(count: -1)
+!27 = !{!28}
+!28 = !DILocalVariable(name: "h", arg: 1, scope: !15, file: !3, line: 6, type: !18)
+!29 = !DILocation(line: 7, column: 7, scope: !30)
+!30 = distinct !DILexicalBlock(scope: !15, file: !3, line: 7, column: 7)
+!35 = !DILocation(line: 7, column: 7, scope: !15)
+!36 = !DILocation(line: 8, column: 8, scope: !30)
+!37 = !DILocation(line: 8, column: 10, scope: !30)
+!38 = !DILocation(line: 8, column: 5, scope: !30)
+!39 = !DILocation(line: 9, column: 1, scope: !15)
+!40 = distinct !DISubprogram(name: "i", scope: !3, file: !3, line: 10, type: !16, scopeLine: 10, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !41)
+!41 = !{!42, !43}
+!42 = !DILocalVariable(name: "h", arg: 1, scope: !40, file: !3, line: 10, type: !18)
+!43 = !DILocalVariable(name: "j", scope: !40, file: !3, line: 11, type: !44)
+!44 = !DIBasicType(name: "long int", size: 64, encoding: DW_ATE_signed)
+!45 = !DILocation(line: 0, scope: !40)
+!46 = !DILocation(line: 12, column: 17, scope: !47)
+!47 = distinct !DILexicalBlock(scope: !48, file: !3, line: 12, column: 3)
+!48 = distinct !DILexicalBlock(scope: !40, file: !3, line: 12, column: 3)
+!49 = !DILocation(line: 12, column: 12, scope: !47)
+!50 = !DILocation(line: 12, column: 3, scope: !48)
+!51 = !DILocation(line: 13, column: 10, scope: !47)
+!52 = !DILocation(line: 13, column: 7, scope: !47)
+!53 = !DILocation(line: 12, column: 21, scope: !47)
+!54 = distinct !{!54, !50, !55, !56}
+!55 = !DILocation(line: 13, column: 16, scope: !48)
+!56 = !{!"llvm.loop.mustprogress"}
+!57 = !DILocation(line: 14, column: 7, scope: !40)
+!58 = !DILocation(line: 14, column: 5, scope: !40)
+!59 = !DILocation(line: 15, column: 1, scope: !40)
+!60 = distinct !DISubprogram(name: "k", scope: !3, file: !3, line: 16, type: !61, scopeLine: 16, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !63)
+!61 = !DISubroutineType(types: !62)
+!62 = !{null}
+!63 = !{!64}
+!64 = !DILocalVariable(name: "j", scope: !60, file: !3, line: 17, type: !19)
+!65 = distinct !DIAssignID()
+!66 = !DILocation(line: 0, scope: !60)
+!67 = !DILocation(line: 17, column: 3, scope: !60)
+!68 = !DILocation(line: 13, column: 10, scope: !47, inlinedAt: !69)
+!69 = distinct !DILocation(line: 19, column: 3, scope: !60)
+!70 = !DILocation(line: 0, scope: !40, inlinedAt: !69)
+!71 = !DILocation(line: 14, column: 5, scope: !40, inlinedAt: !69)
+!72 = !DILocation(line: 20, column: 1, scope: !60)
+!73 = distinct !DISubprogram(name: "l", scope: !3, file: !3, line: 21, type: !61, scopeLine: 21, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !4)
+!74 = distinct !DIAssignID()
+!75 = !DILocation(line: 0, scope: !60, inlinedAt: !76)
+!76 = distinct !DILocation(line: 21, column: 12, scope: !73)
+!77 = !DILocation(line: 17, column: 3, scope: !60, inlinedAt: !76)
+!78 = !DILocation(line: 13, column: 10, scope: !47, inlinedAt: !79)
+!79 = distinct !DILocation(line: 19, column: 3, scope: !60, inlinedAt: !76)
+!80 = !DILocation(line: 0, scope: !40, inlinedAt: !79)
+!81 = !DILocation(line: 14, column: 5, scope: !40, inlinedAt: !79)
+!82 = !DILocation(line: 20, column: 1, scope: !60, inlinedAt: !76)
+!83 = !DILocation(line: 21, column: 17, scope: !73)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/alloca-single-slice.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/alloca-single-slice.ll
new file mode 100644
index 0000000000000..c550489ab4f3e
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/alloca-single-slice.ll
@@ -0,0 +1,82 @@
+; RUN: opt -passes=sroa,verify -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+; Check that single sliced allocas retain their assignment tracking debug info.
+
+;; $ cat test.c
+;; struct a {
+;;   char b[8];
+;; };
+;; int c;
+;; void d() {
+;;   struct a a;
+;;   memcpy(a.b, 0, c);
+;; }
+;; $ clang test.c -Xclang -disable-llvm-passes -O2 -g -c -S -emit-llvm -o - \
+;;   | opt -passes=declare-to-assign -S -o -
+
+; CHECK: entry:
+; CHECK-NEXT: %a.sroa.0 = alloca i64, align 8, !DIAssignID ![[ID_1:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i1 undef, metadata ![[VAR:[0-9]+]], metadata !DIExpression(), metadata ![[ID_1]], metadata ptr %a.sroa.0, metadata !DIExpression()), !dbg
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+%struct.a = type { [8 x i8] }
+
+ at c = dso_local global i32 0, align 4, !dbg !0
+
+define dso_local void @d() !dbg !11 {
+entry:
+  %a = alloca %struct.a, align 1, !DIAssignID !23
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !15, metadata !DIExpression(), metadata !23, metadata ptr %a, metadata !DIExpression()), !dbg !24
+  %0 = bitcast ptr %a to ptr, !dbg !25
+  call void @llvm.lifetime.start.p0i8(i64 8, ptr %0), !dbg !25
+  %b = getelementptr inbounds %struct.a, ptr %a, i32 0, i32 0, !dbg !26
+  %arraydecay = getelementptr inbounds [8 x i8], ptr %b, i64 0, i64 0, !dbg !27
+  %1 = load i32, ptr @c, align 4, !dbg !28
+  %conv = sext i32 %1 to i64, !dbg !28
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 1 %arraydecay, ptr align 1 null, i64 %conv, i1 false), !dbg !27
+  %2 = bitcast ptr %a to ptr, !dbg !33
+  call void @llvm.lifetime.end.p0i8(i64 8, ptr %2), !dbg !33
+  ret void, !dbg !33
+}
+
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture)
+declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg)
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture)
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!7, !8, !9}
+!llvm.ident = !{!10}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "c", scope: !2, file: !3, line: 4, type: !6, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "test.c", directory: "/")
+!4 = !{}
+!5 = !{!0}
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!7 = !{i32 7, !"Dwarf Version", i32 4}
+!8 = !{i32 2, !"Debug Info Version", i32 3}
+!9 = !{i32 1, !"wchar_size", i32 4}
+!10 = !{!"clang version 12.0.0"}
+!11 = distinct !DISubprogram(name: "d", scope: !3, file: !3, line: 5, type: !12, scopeLine: 5, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !14)
+!12 = !DISubroutineType(types: !13)
+!13 = !{null}
+!14 = !{!15}
+!15 = !DILocalVariable(name: "a", scope: !11, file: !3, line: 6, type: !16)
+!16 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "a", file: !3, line: 1, size: 64, elements: !17)
+!17 = !{!18}
+!18 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !16, file: !3, line: 2, baseType: !19, size: 64)
+!19 = !DICompositeType(tag: DW_TAG_array_type, baseType: !20, size: 64, elements: !21)
+!20 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+!21 = !{!22}
+!22 = !DISubrange(count: 8)
+!23 = distinct !DIAssignID()
+!24 = !DILocation(line: 0, scope: !11)
+!25 = !DILocation(line: 6, column: 3, scope: !11)
+!26 = !DILocation(line: 7, column: 12, scope: !11)
+!27 = !DILocation(line: 7, column: 3, scope: !11)
+!28 = !DILocation(line: 7, column: 18, scope: !11)
+!33 = !DILocation(line: 8, column: 1, scope: !11)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/complex.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/complex.ll
new file mode 100644
index 0000000000000..106aa4f5e6a77
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/complex.ll
@@ -0,0 +1,69 @@
+; RUN: opt -passes=sroa -S -o - %s -experimental-assignment-tracking | FileCheck %s
+;
+;; Based on llvm/test/DebugInfo/ARM/sroa-complex.ll
+;; generated from:
+;; $ cat test.c
+;; void f(_Complex double c) { c = 0; }
+;; $ clang test.c -g -O2 -c -Xclang -disable-llvm-passes -S \
+;;     -emit-llvm -o - --target="thumbv7-apple-unknown"
+;;
+;; Commented out some parts of the function that are not relevant to the test.
+;;
+;; Check that a split store gets dbg.assigns fragments. Ensure that only the
+;; value-expression gets fragment info; that the address-expression remains
+;; untouched.
+
+;; dbg.assigns for the split (then promoted) stores.
+; CHECK: %c.coerce.fca.0.extract = extractvalue [2 x i64] %c.coerce, 0
+; CHECK: %c.coerce.fca.1.extract = extractvalue [2 x i64] %c.coerce, 1
+; CHECK: call void @llvm.dbg.assign(metadata i64 %c.coerce.fca.0.extract,{{.+}}, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64),{{.+}}, metadata ptr undef, metadata !DIExpression())
+; CHECK: call void @llvm.dbg.assign(metadata i64 %c.coerce.fca.1.extract,{{.+}}, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64),{{.+}}, metadata ptr undef, {{.+}})
+
+target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
+target triple = "armv7-apple-unknown"
+
+define dso_local arm_aapcscc void @f([2 x i64] %c.coerce) #0 !dbg !8 {
+entry:
+  %c = alloca { double, double }, align 8, !DIAssignID !14
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !13, metadata !DIExpression(), metadata !14, metadata ptr %c, metadata !DIExpression()), !dbg !15
+  %0 = bitcast ptr %c to [2 x i64]*
+  store [2 x i64] %c.coerce, [2 x i64]* %0, align 8, !DIAssignID !16
+  call void @llvm.dbg.assign(metadata [2 x i64] %c.coerce, metadata !13, metadata !DIExpression(), metadata !16, metadata [2 x i64]* %0, metadata !DIExpression()), !dbg !15
+  ; --- The rest of this function isn't useful for the test ---
+  ;%c.realp = getelementptr inbounds { double, double }, ptr %c, i32 0, i32 0, !dbg !17
+  ;%c.imagp = getelementptr inbounds { double, double }, ptr %c, i32 0, i32 1, !dbg !17
+  ;store double 0.000000e+00, ptr %c.realp, align 8, !dbg !17, !DIAssignID !18
+  ;call void @llvm.dbg.assign(metadata double 0.000000e+00, metadata !13, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !18, metadata ptr %c.realp, metadata !DIExpression()), !dbg !15
+  ;store double 0.000000e+00, ptr %c.imagp, align 8, !dbg !17, !DIAssignID !19
+  ;call void @llvm.dbg.assign(metadata double 0.000000e+00, metadata !13, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !19, metadata ptr %c.imagp, metadata !DIExpression()), !dbg !15
+  ret void, !dbg !20
+}
+
+declare void @llvm.dbg.declare(metadata, metadata, metadata)
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5, !6}
+!llvm.ident = !{!7}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{i32 1, !"min_enum_size", i32 4}
+!7 = !{!"clang version 12.0.0"}
+!8 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 2, type: !9, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !12)
+!9 = !DISubroutineType(types: !10)
+!10 = !{null, !11}
+!11 = !DIBasicType(name: "complex", size: 128, encoding: DW_ATE_complex_float)
+!12 = !{!13}
+!13 = !DILocalVariable(name: "c", arg: 1, scope: !8, file: !1, line: 2, type: !11)
+!14 = distinct !DIAssignID()
+!15 = !DILocation(line: 0, scope: !8)
+!16 = distinct !DIAssignID()
+!17 = !DILocation(line: 2, column: 31, scope: !8)
+!18 = distinct !DIAssignID()
+!19 = distinct !DIAssignID()
+!20 = !DILocation(line: 2, column: 36, scope: !8)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag-2.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag-2.ll
new file mode 100644
index 0000000000000..aa9321e1de7cb
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag-2.ll
@@ -0,0 +1,260 @@
+; RUN: opt -passes=sroa -S %s -o - -experimental-assignment-tracking | FileCheck %s
+
+;; $ cat test.cpp
+;; class a {
+;; public:
+;;   float b[4];
+;;   void c();
+;; };
+;; class B {
+;; public:
+;;   B(a d) : e(d) {}
+;;   a &f() { return e; }
+;;   B operator*(const B &)const;
+;;   int g;
+;;   a e;
+;; };
+;; B B::operator*(const B &)const { return e; }
+;; class h {
+;; public:
+;;   B i();
+;; };
+;; void j() {
+;;   h convexbody, k;
+;;   B l = k.i(), m = convexbody.i(), n = l * m;
+;;   a o = n.f(); // Looking at this store, o[0, 128] <- n[32, 160].
+;;   o.c();
+;; }
+;; Generated by grabbing IR before sroa in:
+;; $ clang++ -O2 -g -c test.cpp -Xclang -fexperimental-assignment-tracking
+
+;; Check that the store 4xfloat split into 2x store 2xfloat has correct debug
+;; info when the source (n, 160 bits of int+5*float) is split beforehand (see
+;; comment in test.cpp above). Ensure that only the value-expression gets
+;; fragment info; that the address-expression remains untouched.
+
+;; Check nearby instructions to make sure we're looking in the right place.
+; CHECK: define dso_local void @_Z1jv()
+; CHECK: call void @_ZN1h1iEv(ptr nonnull sret(%class.B) align 4 %m,
+
+; CHECK: store <2 x float> %agg.tmp.sroa.0.0.copyload.i, ptr %4, align 4,{{.+}}!DIAssignID ![[id1:[0-9]+]]
+; CHECK: store <2 x float> %agg.tmp.sroa.2.0.copyload.i, ptr %n.sroa.4.4..sroa_idx, align 4,{{.+}}!DIAssignID ![[id2:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %agg.tmp.sroa.0.0.copyload.i, metadata ![[var:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata ![[id1]], metadata ptr %4, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %agg.tmp.sroa.2.0.copyload.i, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata ![[id2]], metadata ptr %n.sroa.4.4..sroa_idx, metadata !DIExpression()), !dbg
+
+; CHECK: ret
+
+%class.B = type { i32, %class.a }
+%class.a = type { [4 x float] }
+%class.h = type { i8 }
+
+$_ZN1BC2E1a = comdat any
+
+$_ZN1B1fEv = comdat any
+
+; Function Attrs: nofree norecurse nounwind uwtable
+define dso_local void @_ZNK1BmlERKS_(ptr noalias nocapture sret(%class.B) align 4 %agg.result, ptr nocapture readonly %this, ptr nocapture nonnull readnone align 4 dereferenceable(20) %0) local_unnamed_addr #0 align 2 !dbg !7 {
+entry:
+  %agg.tmp.sroa.0.0..sroa_idx = getelementptr inbounds %class.B, ptr %this, i64 0, i32 1, !dbg !42
+  %agg.tmp.sroa.0.0..sroa_cast = bitcast ptr %agg.tmp.sroa.0.0..sroa_idx to ptr, !dbg !42
+  %agg.tmp.sroa.0.0.copyload = load <2 x float>, ptr %agg.tmp.sroa.0.0..sroa_cast, align 4, !dbg !42
+  %agg.tmp.sroa.2.0..sroa_idx2 = getelementptr inbounds %class.B, ptr %this, i64 0, i32 1, i32 0, i64 2, !dbg !42
+  %agg.tmp.sroa.2.0..sroa_cast = bitcast ptr %agg.tmp.sroa.2.0..sroa_idx2 to ptr, !dbg !42
+  %agg.tmp.sroa.2.0.copyload = load <2 x float>, ptr %agg.tmp.sroa.2.0..sroa_cast, align 4, !dbg !42
+  %d.sroa.0.0..sroa_idx.i = getelementptr inbounds %class.B, ptr %agg.result, i64 0, i32 1, !dbg !47
+  %d.sroa.0.0..sroa_cast.i = bitcast ptr %d.sroa.0.0..sroa_idx.i to ptr, !dbg !47
+  store <2 x float> %agg.tmp.sroa.0.0.copyload, ptr %d.sroa.0.0..sroa_cast.i, align 4, !dbg !47
+  %d.sroa.2.0..sroa_idx2.i = getelementptr inbounds %class.B, ptr %agg.result, i64 0, i32 1, i32 0, i64 2, !dbg !47
+  %d.sroa.2.0..sroa_cast.i = bitcast ptr %d.sroa.2.0..sroa_idx2.i to ptr, !dbg !47
+  store <2 x float> %agg.tmp.sroa.2.0.copyload, ptr %d.sroa.2.0..sroa_cast.i, align 4, !dbg !47
+  ret void, !dbg !54
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
+
+; Function Attrs: nounwind uwtable
+define linkonce_odr dso_local void @_ZN1BC2E1a(ptr %this, <2 x float> %d.coerce0, <2 x float> %d.coerce1) unnamed_addr #2 comdat align 2 !dbg !48 {
+entry:
+  %d.sroa.0.0..sroa_idx = getelementptr inbounds %class.B, ptr %this, i64 0, i32 1, !dbg !55
+  %d.sroa.0.0..sroa_cast = bitcast ptr %d.sroa.0.0..sroa_idx to ptr, !dbg !55
+  store <2 x float> %d.coerce0, ptr %d.sroa.0.0..sroa_cast, align 4, !dbg !55
+  %d.sroa.2.0..sroa_idx2 = getelementptr inbounds %class.B, ptr %this, i64 0, i32 1, i32 0, i64 2, !dbg !55
+  %d.sroa.2.0..sroa_cast = bitcast ptr %d.sroa.2.0..sroa_idx2 to ptr, !dbg !55
+  store <2 x float> %d.coerce1, ptr %d.sroa.2.0..sroa_cast, align 4, !dbg !55
+  ret void, !dbg !56
+}
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3
+
+; Function Attrs: uwtable
+define dso_local void @_Z1jv() local_unnamed_addr #4 !dbg !57 {
+entry:
+  %convexbody = alloca %class.h, align 1, !DIAssignID !73
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !61, metadata !DIExpression(), metadata !73, metadata ptr %convexbody, metadata !DIExpression()), !dbg !74
+  %k = alloca %class.h, align 1, !DIAssignID !75
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !68, metadata !DIExpression(), metadata !75, metadata ptr %k, metadata !DIExpression()), !dbg !74
+  %l = alloca %class.B, align 4, !DIAssignID !76
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !69, metadata !DIExpression(), metadata !76, metadata ptr %l, metadata !DIExpression()), !dbg !74
+  %m = alloca %class.B, align 4, !DIAssignID !77
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !70, metadata !DIExpression(), metadata !77, metadata ptr %m, metadata !DIExpression()), !dbg !74
+  %n = alloca %class.B, align 4, !DIAssignID !78
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !71, metadata !DIExpression(), metadata !78, metadata ptr %n, metadata !DIExpression()), !dbg !74
+  %o = alloca %class.a, align 4, !DIAssignID !79
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !72, metadata !DIExpression(), metadata !79, metadata ptr %o, metadata !DIExpression()), !dbg !74
+  %0 = getelementptr inbounds %class.h, ptr %convexbody, i64 0, i32 0, !dbg !80
+  call void @llvm.lifetime.start.p0i8(i64 1, ptr nonnull %0) #7, !dbg !80
+  %1 = getelementptr inbounds %class.h, ptr %k, i64 0, i32 0, !dbg !80
+  call void @llvm.lifetime.start.p0i8(i64 1, ptr nonnull %1) #7, !dbg !80
+  %2 = bitcast ptr %l to ptr, !dbg !81
+  call void @llvm.lifetime.start.p0i8(i64 20, ptr nonnull %2) #7, !dbg !81
+  call void @_ZN1h1iEv(ptr nonnull sret(%class.B) align 4 %l, ptr nonnull %k), !dbg !82
+  %3 = bitcast ptr %m to ptr, !dbg !81
+  call void @llvm.lifetime.start.p0i8(i64 20, ptr nonnull %3) #7, !dbg !81
+  call void @_ZN1h1iEv(ptr nonnull sret(%class.B) align 4 %m, ptr nonnull %convexbody), !dbg !83
+  %4 = bitcast ptr %n to ptr, !dbg !81
+  call void @llvm.lifetime.start.p0i8(i64 20, ptr nonnull %4) #7, !dbg !81
+  %agg.tmp.sroa.0.0..sroa_idx.i = getelementptr inbounds %class.B, ptr %l, i64 0, i32 1, !dbg !84
+  %agg.tmp.sroa.0.0..sroa_cast.i = bitcast ptr %agg.tmp.sroa.0.0..sroa_idx.i to ptr, !dbg !84
+  %agg.tmp.sroa.0.0.copyload.i = load <2 x float>, ptr %agg.tmp.sroa.0.0..sroa_cast.i, align 4, !dbg !84
+  %agg.tmp.sroa.2.0..sroa_idx2.i = getelementptr inbounds %class.B, ptr %l, i64 0, i32 1, i32 0, i64 2, !dbg !84
+  %agg.tmp.sroa.2.0..sroa_cast.i = bitcast ptr %agg.tmp.sroa.2.0..sroa_idx2.i to ptr, !dbg !84
+  %agg.tmp.sroa.2.0.copyload.i = load <2 x float>, ptr %agg.tmp.sroa.2.0..sroa_cast.i, align 4, !dbg !84
+  %d.sroa.0.0..sroa_idx.i.i = getelementptr inbounds %class.B, ptr %n, i64 0, i32 1, !dbg !89
+  %d.sroa.0.0..sroa_cast.i.i = bitcast ptr %d.sroa.0.0..sroa_idx.i.i to ptr, !dbg !89
+  store <2 x float> %agg.tmp.sroa.0.0.copyload.i, ptr %d.sroa.0.0..sroa_cast.i.i, align 4, !dbg !89
+  %d.sroa.2.0..sroa_idx2.i.i = getelementptr inbounds %class.B, ptr %n, i64 0, i32 1, i32 0, i64 2, !dbg !89
+  %d.sroa.2.0..sroa_cast.i.i = bitcast ptr %d.sroa.2.0..sroa_idx2.i.i to ptr, !dbg !89
+  store <2 x float> %agg.tmp.sroa.2.0.copyload.i, ptr %d.sroa.2.0..sroa_cast.i.i, align 4, !dbg !89
+  %5 = bitcast ptr %o to ptr, !dbg !91
+  call void @llvm.lifetime.start.p0i8(i64 16, ptr nonnull %5) #7, !dbg !91
+  %e.i = getelementptr inbounds %class.B, ptr %n, i64 0, i32 1, !dbg !92
+  %6 = bitcast ptr %e.i to ptr, !dbg !97
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr nonnull align 4 dereferenceable(16) %5, ptr nonnull align 4 dereferenceable(16) %6, i64 16, i1 false), !dbg !97, !DIAssignID !98
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !72, metadata !DIExpression(), metadata !98, metadata ptr %5, metadata !DIExpression()), !dbg !74
+  call void @_ZN1a1cEv(ptr nonnull %o), !dbg !99
+  call void @llvm.lifetime.end.p0i8(i64 16, ptr nonnull %5) #7, !dbg !100
+  call void @llvm.lifetime.end.p0i8(i64 20, ptr nonnull %4) #7, !dbg !100
+  call void @llvm.lifetime.end.p0i8(i64 20, ptr nonnull %3) #7, !dbg !100
+  call void @llvm.lifetime.end.p0i8(i64 20, ptr nonnull %2) #7, !dbg !100
+  call void @llvm.lifetime.end.p0i8(i64 1, ptr nonnull %1) #7, !dbg !100
+  call void @llvm.lifetime.end.p0i8(i64 1, ptr nonnull %0) #7, !dbg !100
+  ret void, !dbg !100
+}
+
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+declare dso_local void @_ZN1h1iEv(ptr sret(%class.B) align 4, ptr) local_unnamed_addr #5
+
+; Function Attrs: nounwind uwtable
+define linkonce_odr dso_local nonnull align 4 dereferenceable(16) ptr @_ZN1B1fEv(ptr %this) local_unnamed_addr #6 comdat align 2 !dbg !93 {
+entry:
+  %e = getelementptr inbounds %class.B, ptr %this, i64 0, i32 1, !dbg !101
+  ret ptr %e, !dbg !102
+}
+
+declare dso_local void @_ZN1a1cEv(ptr) local_unnamed_addr #5
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "reduce.cpp", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{!"clang version 12.0.0"}
+!7 = distinct !DISubprogram(name: "operator*", linkageName: "_ZNK1BmlERKS_", scope: !8, file: !1, line: 14, type: !33, scopeLine: 14, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !32, retainedNodes: !38)
+!8 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "B", file: !1, line: 6, size: 160, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !9, identifier: "_ZTS1B")
+!9 = !{!10, !12, !24, !28, !32}
+!10 = !DIDerivedType(tag: DW_TAG_member, name: "g", scope: !8, file: !1, line: 11, baseType: !11, size: 32, flags: DIFlagPublic)
+!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "e", scope: !8, file: !1, line: 12, baseType: !13, size: 128, offset: 32, flags: DIFlagPublic)
+!13 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "a", file: !1, line: 1, size: 128, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS1a")
+!14 = !{!15, !20}
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !13, file: !1, line: 3, baseType: !16, size: 128, flags: DIFlagPublic)
+!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !17, size: 128, elements: !18)
+!17 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!18 = !{!19}
+!19 = !DISubrange(count: 4)
+!20 = !DISubprogram(name: "c", linkageName: "_ZN1a1cEv", scope: !13, file: !1, line: 4, type: !21, scopeLine: 4, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!21 = !DISubroutineType(types: !22)
+!22 = !{null, !23}
+!23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!24 = !DISubprogram(name: "B", scope: !8, file: !1, line: 8, type: !25, scopeLine: 8, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!25 = !DISubroutineType(types: !26)
+!26 = !{null, !27, !13}
+!27 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !8, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!28 = !DISubprogram(name: "f", linkageName: "_ZN1B1fEv", scope: !8, file: !1, line: 9, type: !29, scopeLine: 9, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!29 = !DISubroutineType(types: !30)
+!30 = !{!31, !27}
+!31 = !DIDerivedType(tag: DW_TAG_reference_type, baseType: !13, size: 64)
+!32 = !DISubprogram(name: "operator*", linkageName: "_ZNK1BmlERKS_", scope: !8, file: !1, line: 10, type: !33, scopeLine: 10, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!33 = !DISubroutineType(types: !34)
+!34 = !{!8, !35, !37}
+!35 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !36, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!36 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !8)
+!37 = !DIDerivedType(tag: DW_TAG_reference_type, baseType: !36, size: 64)
+!38 = !{!39, !41}
+!39 = !DILocalVariable(name: "this", arg: 1, scope: !7, type: !40, flags: DIFlagArtificial | DIFlagObjectPointer)
+!40 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !36, size: 64)
+!41 = !DILocalVariable(arg: 2, scope: !7, file: !1, line: 14, type: !37)
+!42 = !DILocation(line: 14, column: 41, scope: !7)
+!47 = !DILocation(line: 8, column: 12, scope: !48, inlinedAt: !53)
+!48 = distinct !DISubprogram(name: "B", linkageName: "_ZN1BC2E1a", scope: !8, file: !1, line: 8, type: !25, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !24, retainedNodes: !49)
+!49 = !{!50, !52}
+!50 = !DILocalVariable(name: "this", arg: 1, scope: !48, type: !51, flags: DIFlagArtificial | DIFlagObjectPointer)
+!51 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !8, size: 64)
+!52 = !DILocalVariable(name: "d", arg: 2, scope: !48, file: !1, line: 8, type: !13)
+!53 = distinct !DILocation(line: 14, column: 41, scope: !7)
+!54 = !DILocation(line: 14, column: 34, scope: !7)
+!55 = !DILocation(line: 8, column: 12, scope: !48)
+!56 = !DILocation(line: 8, column: 18, scope: !48)
+!57 = distinct !DISubprogram(name: "j", linkageName: "_Z1jv", scope: !1, file: !1, line: 19, type: !58, scopeLine: 19, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !60)
+!58 = !DISubroutineType(types: !59)
+!59 = !{null}
+!60 = !{!61, !68, !69, !70, !71, !72}
+!61 = !DILocalVariable(name: "convexbody", scope: !57, file: !1, line: 20, type: !62)
+!62 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "h", file: !1, line: 15, size: 8, flags: DIFlagTypePassByValue, elements: !63, identifier: "_ZTS1h")
+!63 = !{!64}
+!64 = !DISubprogram(name: "i", linkageName: "_ZN1h1iEv", scope: !62, file: !1, line: 17, type: !65, scopeLine: 17, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!65 = !DISubroutineType(types: !66)
+!66 = !{!8, !67}
+!67 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !62, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!68 = !DILocalVariable(name: "k", scope: !57, file: !1, line: 20, type: !62)
+!69 = !DILocalVariable(name: "l", scope: !57, file: !1, line: 21, type: !8)
+!70 = !DILocalVariable(name: "m", scope: !57, file: !1, line: 21, type: !8)
+!71 = !DILocalVariable(name: "n", scope: !57, file: !1, line: 21, type: !8)
+!72 = !DILocalVariable(name: "o", scope: !57, file: !1, line: 22, type: !13)
+!73 = distinct !DIAssignID()
+!74 = !DILocation(line: 0, scope: !57)
+!75 = distinct !DIAssignID()
+!76 = distinct !DIAssignID()
+!77 = distinct !DIAssignID()
+!78 = distinct !DIAssignID()
+!79 = distinct !DIAssignID()
+!80 = !DILocation(line: 20, column: 3, scope: !57)
+!81 = !DILocation(line: 21, column: 3, scope: !57)
+!82 = !DILocation(line: 21, column: 11, scope: !57)
+!83 = !DILocation(line: 21, column: 31, scope: !57)
+!84 = !DILocation(line: 14, column: 41, scope: !7, inlinedAt: !85)
+!85 = distinct !DILocation(line: 21, column: 42, scope: !57)
+!89 = !DILocation(line: 8, column: 12, scope: !48, inlinedAt: !90)
+!90 = distinct !DILocation(line: 14, column: 41, scope: !7, inlinedAt: !85)
+!91 = !DILocation(line: 22, column: 3, scope: !57)
+!92 = !DILocation(line: 9, column: 19, scope: !93, inlinedAt: !96)
+!93 = distinct !DISubprogram(name: "f", linkageName: "_ZN1B1fEv", scope: !8, file: !1, line: 9, type: !29, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !28, retainedNodes: !94)
+!94 = !{!95}
+!95 = !DILocalVariable(name: "this", arg: 1, scope: !93, type: !51, flags: DIFlagArtificial | DIFlagObjectPointer)
+!96 = distinct !DILocation(line: 22, column: 11, scope: !57)
+!97 = !DILocation(line: 22, column: 9, scope: !57)
+!98 = distinct !DIAssignID()
+!99 = !DILocation(line: 23, column: 5, scope: !57)
+!100 = !DILocation(line: 24, column: 1, scope: !57)
+!101 = !DILocation(line: 9, column: 19, scope: !93)
+!102 = !DILocation(line: 9, column: 12, scope: !93)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag.ll
new file mode 100644
index 0000000000000..18010c6a283e6
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/frag.ll
@@ -0,0 +1,113 @@
+; RUN: opt %s -S -passes=sroa -o - -experimental-assignment-tracking | FileCheck %s
+
+;; $ cat test.cpp
+;; class c {
+;;   float b[4];
+;; };
+;; c fn1();
+;; void d() {
+;;   c a[3];
+;;   a[2] = fn1();
+;; }
+;;
+;; Generated by grabbing IR before sroa in:
+;; $ clang++ -O2 -g -c test.cpp -Xclang -fexperimental-assignment-tracking
+;;
+;; Check that when the memcpy to fragment(256, 128) is split into two 2xfloat
+;; stores, the dbg.assign is split into two with fragment(256, 64) &
+;; fragment(320, 64). Ensure that only the value-expression gets fragment info;
+;; that the address-expression remains untouched.
+
+; CHECK: %call = call
+; CHECK-NEXT: %0 = extractvalue { <2 x float>, <2 x float> } %call, 0
+; CHECK-NEXT: %1 = extractvalue { <2 x float>, <2 x float> } %call, 1
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %0, metadata ![[var:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 256, 64),{{.+}},{{.+}}undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %1, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 320, 64),{{.+}},{{.+}}undef, metadata !DIExpression()), !dbg
+
+%class.c = type { [4 x float] }
+
+; Function Attrs: uwtable
+define dso_local void @_Z1dv() #0 !dbg !7 {
+entry:
+  %a = alloca [3 x %class.c], align 16, !DIAssignID !22
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !22, metadata ptr %a, metadata !DIExpression()), !dbg !23
+  %ref.tmp = alloca %class.c, align 4
+  %0 = bitcast ptr %a to ptr, !dbg !24
+  call void @llvm.lifetime.start.p0i8(i64 48, ptr %0) #4, !dbg !24
+  %1 = bitcast ptr %ref.tmp to ptr, !dbg !25
+  call void @llvm.lifetime.start.p0i8(i64 16, ptr %1) #4, !dbg !25
+  %call = call { <2 x float>, <2 x float> } @_Z3fn1v(), !dbg !25
+  %coerce.dive = getelementptr inbounds %class.c, ptr %ref.tmp, i32 0, i32 0, !dbg !25
+  %2 = bitcast ptr %coerce.dive to ptr, !dbg !25
+  %3 = getelementptr inbounds { <2 x float>, <2 x float> }, ptr %2, i32 0, i32 0, !dbg !25
+  %4 = extractvalue { <2 x float>, <2 x float> } %call, 0, !dbg !25
+  store <2 x float> %4, ptr %3, align 4, !dbg !25
+  %5 = getelementptr inbounds { <2 x float>, <2 x float> }, ptr %2, i32 0, i32 1, !dbg !25
+  %6 = extractvalue { <2 x float>, <2 x float> } %call, 1, !dbg !25
+  store <2 x float> %6, ptr %5, align 4, !dbg !25
+  %arrayidx = getelementptr inbounds [3 x %class.c], ptr %a, i64 0, i64 2, !dbg !26
+  %7 = bitcast ptr %arrayidx to ptr, !dbg !27
+  %8 = bitcast ptr %ref.tmp to ptr, !dbg !27
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 16 %7, ptr align 4 %8, i64 16, i1 false), !dbg !27, !DIAssignID !32
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 256, 128), metadata !32, metadata ptr %7, metadata !DIExpression()), !dbg !23
+  %9 = bitcast ptr %ref.tmp to ptr, !dbg !26
+  call void @llvm.lifetime.end.p0i8(i64 16, ptr %9) #4, !dbg !26
+  %10 = bitcast ptr %a to ptr, !dbg !33
+  call void @llvm.lifetime.end.p0i8(i64 48, ptr %10) #4, !dbg !33
+  ret void, !dbg !33
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #2
+
+declare !dbg !34 dso_local { <2 x float>, <2 x float> } @_Z3fn1v() #3
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "reduce.cpp", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{!"clang version 12.0.0"}
+!7 = distinct !DISubprogram(name: "d", linkageName: "_Z1dv", scope: !1, file: !1, line: 5, type: !8, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10)
+!8 = !DISubroutineType(types: !9)
+!9 = !{null}
+!10 = !{!11}
+!11 = !DILocalVariable(name: "a", scope: !7, file: !1, line: 6, type: !12)
+!12 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 384, elements: !20)
+!13 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "c", file: !1, line: 1, size: 128, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS1c")
+!14 = !{!15}
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !13, file: !1, line: 2, baseType: !16, size: 128)
+!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !17, size: 128, elements: !18)
+!17 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!18 = !{!19}
+!19 = !DISubrange(count: 4)
+!20 = !{!21}
+!21 = !DISubrange(count: 3)
+!22 = distinct !DIAssignID()
+!23 = !DILocation(line: 0, scope: !7)
+!24 = !DILocation(line: 6, column: 3, scope: !7)
+!25 = !DILocation(line: 7, column: 10, scope: !7)
+!26 = !DILocation(line: 7, column: 3, scope: !7)
+!27 = !DILocation(line: 7, column: 8, scope: !7)
+!32 = distinct !DIAssignID()
+!33 = !DILocation(line: 8, column: 1, scope: !7)
+!34 = !DISubprogram(name: "fn1", linkageName: "_Z3fn1v", scope: !1, file: !1, line: 4, type: !35, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2)
+!35 = !DISubroutineType(types: !36)
+!36 = !{!13}

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/id.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/id.ll
new file mode 100644
index 0000000000000..0fab6f0727583
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/id.ll
@@ -0,0 +1,174 @@
+; RUN: opt -passes=sroa -S %s -o - -experimental-assignment-tracking | FileCheck %s
+
+;; Check that multiple dbg.assign intrinsics linked to a store that is getting
+;; split (or at least that is touched by SROA, causing a replacement store to
+;; be generated) are still both linked to the new store(s).
+;;
+;; Additionally, check that SROA inserts new dbg.assign intrinsics by the
+;; originals.
+
+;; $ cat test.cpp
+;; class a {
+;; public:
+;;   a(int, float &) {}
+;; };
+;; float b, d;
+;; int c;
+;; void f() {
+;;   float e;
+;;   if (c)
+;;     e = b;
+;;   else
+;;     e = b / d;
+;;   a(c, e);
+;; }
+;;
+;; Generated by grabbing IR before sroa in:
+;; $ clang++ -O2 -g -c test.cpp -Xclang -fexperimental-assignment-tracking 
+
+; CHECK: if.then:
+; CHECK-NEXT: %1 = load float
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata float %storemerge, metadata ![[var:[0-9]+]], metadata !DIExpression(), metadata ![[id:[0-9]+]], metadata ptr undef, metadata !DIExpression()), !dbg ![[dbg:[0-9]+]]
+
+; CHECK: if.else:
+; CHECK-NEXT: %2 = load float
+; CHECK-NEXT: %3 = load float
+; CHECK-NEXT: %div = fdiv float
+; CHECK: call void @llvm.dbg.assign(metadata float %storemerge, metadata ![[var]], metadata !DIExpression(), metadata ![[id]], metadata ptr undef, metadata !DIExpression()), !dbg ![[dbg]]
+
+%class.a = type { i8 }
+
+$_ZN1aC2EiRf = comdat any
+
+ at b = dso_local local_unnamed_addr global float 0.000000e+00, align 4, !dbg !0
+ at d = dso_local local_unnamed_addr global float 0.000000e+00, align 4, !dbg !6
+ at c = dso_local local_unnamed_addr global i32 0, align 4, !dbg !9
+
+; Function Attrs: nounwind readonly uwtable
+define dso_local void @_Z1fv() local_unnamed_addr #0 !dbg !16 {
+entry:
+  %e = alloca float, align 4, !DIAssignID !21
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !20, metadata !DIExpression(), metadata !21, metadata ptr %e, metadata !DIExpression()), !dbg !22
+  %agg.tmp.ensured = alloca %class.a, align 1
+  %0 = bitcast ptr %e to ptr, !dbg !23
+  call void @llvm.lifetime.start.p0i8(i64 4, ptr nonnull %0) #4, !dbg !23
+  %1 = load i32, ptr @c, align 4, !dbg !24
+  %tobool.not = icmp eq i32 %1, 0, !dbg !24
+  br i1 %tobool.not, label %if.else, label %if.then, !dbg !30
+
+if.then:                                          ; preds = %entry
+  %2 = load float, ptr @b, align 4, !dbg !31
+  call void @llvm.dbg.assign(metadata float %2, metadata !20, metadata !DIExpression(), metadata !34, metadata ptr %e, metadata !DIExpression()), !dbg !22
+  br label %if.end, !dbg !35
+
+if.else:                                          ; preds = %entry
+  %3 = load float, ptr @b, align 4, !dbg !36
+  %4 = load float, ptr @d, align 4, !dbg !37
+  %div = fdiv float %3, %4, !dbg !38
+  call void @llvm.dbg.assign(metadata float %div, metadata !20, metadata !DIExpression(), metadata !34, metadata ptr %e, metadata !DIExpression()), !dbg !22
+  br label %if.end
+
+if.end:                                           ; preds = %if.else, %if.then
+  %storemerge = phi float [ %div, %if.else ], [ %2, %if.then ], !dbg !39
+  store float %storemerge, ptr %e, align 4, !dbg !39, !DIAssignID !34
+  %5 = load i32, ptr @c, align 4, !dbg !40
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !41, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !51, metadata !DIExpression(), metadata !57, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !52, metadata !DIExpression(), metadata !58, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.dbg.assign(metadata ptr %agg.tmp.ensured, metadata !41, metadata !DIExpression(), metadata !59, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.dbg.assign(metadata i32 %5, metadata !51, metadata !DIExpression(), metadata !60, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.dbg.assign(metadata ptr %e, metadata !52, metadata !DIExpression(), metadata !61, metadata ptr undef, metadata !DIExpression()), !dbg !55
+  call void @llvm.lifetime.end.p0i8(i64 4, ptr nonnull %0) #4, !dbg !62
+  ret void, !dbg !62
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nounwind uwtable
+define linkonce_odr dso_local void @_ZN1aC2EiRf(ptr %this, i32 %0, ptr nonnull align 4 dereferenceable(4) %1) unnamed_addr #2 comdat align 2 !dbg !42 {
+entry:
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !41, metadata !DIExpression(), metadata !63, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !51, metadata !DIExpression(), metadata !65, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !52, metadata !DIExpression(), metadata !66, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  call void @llvm.dbg.assign(metadata ptr %this, metadata !41, metadata !DIExpression(), metadata !67, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  call void @llvm.dbg.assign(metadata i32 %0, metadata !51, metadata !DIExpression(), metadata !68, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  call void @llvm.dbg.assign(metadata ptr %1, metadata !52, metadata !DIExpression(), metadata !69, metadata ptr undef, metadata !DIExpression()), !dbg !64
+  ret void, !dbg !70
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!12, !13, !14}
+!llvm.ident = !{!15}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "b", scope: !2, file: !3, line: 5, type: !8, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "reduce.cpp", directory: "")
+!4 = !{}
+!5 = !{!0, !6, !9}
+!6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
+!7 = distinct !DIGlobalVariable(name: "d", scope: !2, file: !3, line: 5, type: !8, isLocal: false, isDefinition: true)
+!8 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!9 = !DIGlobalVariableExpression(var: !10, expr: !DIExpression())
+!10 = distinct !DIGlobalVariable(name: "c", scope: !2, file: !3, line: 6, type: !11, isLocal: false, isDefinition: true)
+!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!12 = !{i32 7, !"Dwarf Version", i32 4}
+!13 = !{i32 2, !"Debug Info Version", i32 3}
+!14 = !{i32 1, !"wchar_size", i32 4}
+!15 = !{!"clang version 12.0.0"}
+!16 = distinct !DISubprogram(name: "f", linkageName: "_Z1fv", scope: !3, file: !3, line: 7, type: !17, scopeLine: 7, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19)
+!17 = !DISubroutineType(types: !18)
+!18 = !{null}
+!19 = !{!20}
+!20 = !DILocalVariable(name: "e", scope: !16, file: !3, line: 8, type: !8)
+!21 = distinct !DIAssignID()
+!22 = !DILocation(line: 0, scope: !16)
+!23 = !DILocation(line: 8, column: 3, scope: !16)
+!24 = !DILocation(line: 9, column: 7, scope: !25)
+!25 = distinct !DILexicalBlock(scope: !16, file: !3, line: 9, column: 7)
+!30 = !DILocation(line: 9, column: 7, scope: !16)
+!31 = !DILocation(line: 10, column: 9, scope: !25)
+!34 = distinct !DIAssignID()
+!35 = !DILocation(line: 10, column: 5, scope: !25)
+!36 = !DILocation(line: 12, column: 9, scope: !25)
+!37 = !DILocation(line: 12, column: 13, scope: !25)
+!38 = !DILocation(line: 12, column: 11, scope: !25)
+!39 = !DILocation(line: 0, scope: !25)
+!40 = !DILocation(line: 13, column: 5, scope: !16)
+!41 = !DILocalVariable(name: "this", arg: 1, scope: !42, type: !53, flags: DIFlagArtificial | DIFlagObjectPointer)
+!42 = distinct !DISubprogram(name: "a", linkageName: "_ZN1aC2EiRf", scope: !43, file: !3, line: 3, type: !46, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, declaration: !45, retainedNodes: !50)
+!43 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "a", file: !3, line: 1, size: 8, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !44, identifier: "_ZTS1a")
+!44 = !{!45}
+!45 = !DISubprogram(name: "a", scope: !43, file: !3, line: 3, type: !46, scopeLine: 3, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!46 = !DISubroutineType(types: !47)
+!47 = !{null, !48, !11, !49}
+!48 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !43, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!49 = !DIDerivedType(tag: DW_TAG_reference_type, baseType: !8, size: 64)
+!50 = !{!41, !51, !52}
+!51 = !DILocalVariable(arg: 2, scope: !42, file: !3, line: 3, type: !11)
+!52 = !DILocalVariable(arg: 3, scope: !42, file: !3, line: 3, type: !49)
+!53 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !43, size: 64)
+!54 = distinct !DIAssignID()
+!55 = !DILocation(line: 0, scope: !42, inlinedAt: !56)
+!56 = distinct !DILocation(line: 13, column: 3, scope: !16)
+!57 = distinct !DIAssignID()
+!58 = distinct !DIAssignID()
+!59 = distinct !DIAssignID()
+!60 = distinct !DIAssignID()
+!61 = distinct !DIAssignID()
+!62 = !DILocation(line: 14, column: 1, scope: !16)
+!63 = distinct !DIAssignID()
+!64 = !DILocation(line: 0, scope: !42)
+!65 = distinct !DIAssignID()
+!66 = distinct !DIAssignID()
+!67 = distinct !DIAssignID()
+!68 = distinct !DIAssignID()
+!69 = distinct !DIAssignID()
+!70 = !DILocation(line: 3, column: 20, scope: !42)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memcpy.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memcpy.ll
new file mode 100644
index 0000000000000..f310456cc60a8
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memcpy.ll
@@ -0,0 +1,117 @@
+; RUN: opt -passes=sroa,verify -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+;; Check that the new slices of an alloca and memcpy intructions get dbg.assign
+;; intrinsics with the correct fragment info.
+;;
+;; Also check that the new dbg.assign intrinsics are inserted after each split
+;; store. See llvm/test/DebugInfo/Generic/dbg-assign-sroa-id.ll for the
+;; counterpart check. Ensure that only the value-expression gets fragment info;
+;; that the address-expression remains untouched.
+
+;; $ cat test.cpp
+;; struct LargeStruct {
+;;   int A, B, C;
+;;   int Var;
+;;   int D, E, F;
+;; };
+;; LargeStruct From;
+;; int example() {
+;;   LargeStruct To = From;
+;;   return To.Var;
+;; }
+;; $ clang test.cpp -Xclang -fexperimental-assignment-tracking \
+;;     -Xclang -disable-llvm-passes -O2 -g -c -S -emit-llvm -o -
+
+;; Split alloca.
+; CHECK: entry:
+; CHECK-NEXT: %To.sroa.0 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_1:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata {{.+}} undef, metadata ![[TO:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_1]], metadata ptr %To.sroa.0, metadata !DIExpression()), !dbg
+
+; CHECK-NEXT: %To.sroa.4 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_3:[0-9]+]]
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata {{.+}} undef, metadata ![[TO]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_3]], metadata ptr %To.sroa.4, metadata !DIExpression()), !dbg
+
+;; Split memcpy.
+; CHECK: call void @llvm.memcpy{{.*}}(ptr align 8 %To.sroa.0, ptr align 4 @From, i64 12, i1 false),{{.*}}!DIAssignID ![[ID_4:[0-9]+]]
+;; This slice has been split and is promoted.
+; CHECK: %To.sroa.3.0.copyload = load i32, ptr getelementptr inbounds (i8, ptr @From, i64 12)
+; CHECK: call void @llvm.memcpy{{.*}}(ptr align 8 %To.sroa.4, ptr align 4 getelementptr inbounds (i8, ptr @From, i64 16), i64 12, i1 false){{.*}}!DIAssignID ![[ID_6:[0-9]+]]
+
+;; Intrinsics for the splits above.
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata {{.+}} undef, metadata ![[TO]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_4]], metadata ptr %To.sroa.0, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %To.sroa.3.0.copyload, metadata ![[TO]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata {{.+}} undef, metadata ![[TO]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_6]], metadata ptr %To.sroa.4, metadata !DIExpression()), !dbg
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+%struct.LargeStruct = type { i32, i32, i32, i32, i32, i32, i32 }
+
+ at From = dso_local global %struct.LargeStruct zeroinitializer, align 4, !dbg !0
+
+; Function Attrs: nounwind uwtable mustprogress
+define dso_local i32 @_Z7examplev() #0 !dbg !20 {
+entry:
+  %To = alloca %struct.LargeStruct, align 4, !DIAssignID !25
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !24, metadata !DIExpression(), metadata !25, metadata ptr %To, metadata !DIExpression()), !dbg !26
+  %0 = bitcast ptr %To to ptr, !dbg !27
+  call void @llvm.lifetime.start.p0i8(i64 28, ptr %0) #3, !dbg !27
+  %1 = bitcast ptr %To to ptr, !dbg !28
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 4 %1, ptr align 4 bitcast (ptr @From to ptr), i64 28, i1 false), !dbg !28, !DIAssignID !34
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !24, metadata !DIExpression(), metadata !34, metadata ptr %1, metadata !DIExpression()), !dbg !28
+  %Var = getelementptr inbounds %struct.LargeStruct, ptr %To, i32 0, i32 3, !dbg !35
+  %2 = load i32, ptr %Var, align 4, !dbg !35
+  %3 = bitcast ptr %To to ptr, !dbg !38
+  call void @llvm.lifetime.end.p0i8(i64 28, ptr %3) #3, !dbg !38
+  ret i32 %2, !dbg !39
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2
+
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!16, !17, !18}
+!llvm.ident = !{!19}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "From", scope: !2, file: !3, line: 6, type: !6, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "sroa-test.cpp", directory: "/")
+!4 = !{}
+!5 = !{!0}
+!6 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "LargeStruct", file: !3, line: 1, size: 224, flags: DIFlagTypePassByValue, elements: !7, identifier: "_ZTS11LargeStruct")
+!7 = !{!8, !10, !11, !12, !13, !14, !15}
+!8 = !DIDerivedType(tag: DW_TAG_member, name: "A", scope: !6, file: !3, line: 2, baseType: !9, size: 32)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !DIDerivedType(tag: DW_TAG_member, name: "B", scope: !6, file: !3, line: 2, baseType: !9, size: 32, offset: 32)
+!11 = !DIDerivedType(tag: DW_TAG_member, name: "C", scope: !6, file: !3, line: 2, baseType: !9, size: 32, offset: 64)
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "Var", scope: !6, file: !3, line: 3, baseType: !9, size: 32, offset: 96)
+!13 = !DIDerivedType(tag: DW_TAG_member, name: "D", scope: !6, file: !3, line: 4, baseType: !9, size: 32, offset: 128)
+!14 = !DIDerivedType(tag: DW_TAG_member, name: "E", scope: !6, file: !3, line: 4, baseType: !9, size: 32, offset: 160)
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "F", scope: !6, file: !3, line: 4, baseType: !9, size: 32, offset: 192)
+!16 = !{i32 7, !"Dwarf Version", i32 4}
+!17 = !{i32 2, !"Debug Info Version", i32 3}
+!18 = !{i32 1, !"wchar_size", i32 4}
+!19 = !{!"clang version 12.0.0"}
+!20 = distinct !DISubprogram(name: "example", linkageName: "_Z7examplev", scope: !3, file: !3, line: 7, type: !21, scopeLine: 7, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !23)
+!21 = !DISubroutineType(types: !22)
+!22 = !{!9}
+!23 = !{!24}
+!24 = !DILocalVariable(name: "To", scope: !20, file: !3, line: 8, type: !6)
+!25 = distinct !DIAssignID()
+!26 = !DILocation(line: 0, scope: !20)
+!27 = !DILocation(line: 8, column: 3, scope: !20)
+!28 = !DILocation(line: 8, column: 20, scope: !20)
+!34 = distinct !DIAssignID()
+!35 = !DILocation(line: 9, column: 13, scope: !20)
+!38 = !DILocation(line: 10, column: 1, scope: !20)
+!39 = !DILocation(line: 9, column: 3, scope: !20)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memmove-to-from-same-alloca.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memmove-to-from-same-alloca.ll
new file mode 100644
index 0000000000000..766e338aab5d7
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/memmove-to-from-same-alloca.ll
@@ -0,0 +1,163 @@
+; RUN: opt %s -passes=sroa -o - -S -experimental-assignment-tracking \
+; RUN: | FileCheck %s
+
+;; Generated from this C++ source:
+;; __attribute__((nodebug)) struct Blob {int P[6];} Glob;
+;; __attribute__((nodebug)) int Cond;
+;; __attribute__((nodebug)) Blob *C;
+;; __attribute__((nodebug)) void call(int);
+;; 
+;; void f() {
+;;   int A[16];
+;;   __attribute__ ((nodebug)) int B[16];
+;;   // A[0:6) <- Glob
+;;   __builtin_memmove(&A[0], &Glob, sizeof(Blob));
+;;   call(0);
+;;   // B[8:14) <- Glob
+;;   __builtin_memmove(&B[8], &Glob, sizeof(Blob));  
+;;   call(A[0]);
+;;   // A[8:14) <- A[0:6)
+;;   __builtin_memmove(&A[8], &A[0], sizeof(Blob));
+;;   call(A[8]);
+;;   if (Cond)
+;;     // C <- A[8:14)
+;;     __builtin_memmove(C, &A[8], sizeof(Blob));
+;;   else
+;;     // C <- B[8:14)
+;;     __builtin_memmove(C, &B[8], sizeof(Blob));    
+;; }
+;; 
+;; using:
+;;   clang test.cpp -emit-llvm -S -g -O2 -Xclang -disable-llvm-passes -o - \
+;;   | opt -passes=declare-to-assign -o test.ll - -S
+
+;; We're interested in variable A and the second memmove with A as a dest (the
+;; third memmove in the source). SROA is going to chop up A so that the only
+;; Alloca'd slice remaining is what were originally elements 1 through 5
+;; inclusive (element 0 is promoted). Incidentally, the memmove later becomes a
+;; memcpy. Check that the dbg.assign address and fragment are correct and
+;; ensure the DIAssignID still links it to the memmove(/memcpy).
+
+; CHECK: %A.sroa.0.sroa.5 = alloca [5 x i32]
+; CHECK: llvm.memcpy{{.*}}(ptr align 4 %A.sroa.0.sroa.5, ptr align 4 getelementptr inbounds (i8, ptr @Glob, i64 4), i64 20, i1 false){{.*}}!DIAssignID ![[ID:[0-9]+]]
+;; Here's the dbg.assign for element 0 - it's not important for the test.
+; CHECK-NEXT: llvm.dbg.assign({{.*}}!DIExpression(DW_OP_LLVM_fragment, 0, 32){{.*}})
+;; This is the dbg.assign we care about:
+; CHECK-NEXT: llvm.dbg.assign(metadata i1 undef, metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 32, 160), metadata ![[ID]], metadata ptr %A.sroa.0.sroa.5, metadata !DIExpression())
+
+; CHECK: ![[VAR]] = !DILocalVariable(name: "A"
+
+source_filename = "test.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.Blob = type { [6 x i32] }
+
+ at Glob = dso_local global %struct.Blob zeroinitializer, align 4
+ at Cond = dso_local global i32 0, align 4
+ at C = dso_local global ptr null, align 8
+
+; Function Attrs: mustprogress uwtable
+define dso_local void @_Z1fv() #0 !dbg !9 {
+entry:
+  %A = alloca [16 x i32], align 16, !DIAssignID !18
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !13, metadata !DIExpression(), metadata !18, metadata ptr %A, metadata !DIExpression()), !dbg !19
+  %B = alloca [16 x i32], align 16
+  call void @llvm.lifetime.start.p0(i64 64, ptr %A) #5, !dbg !20
+  call void @llvm.lifetime.start.p0(i64 64, ptr %B) #5, !dbg !21
+  %arrayidx = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 0, !dbg !22
+  call void @llvm.memmove.p0.p0.i64(ptr align 16 %arrayidx, ptr align 4 @Glob, i64 24, i1 false), !dbg !23, !DIAssignID !24
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !13, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 192), metadata !24, metadata ptr %arrayidx, metadata !DIExpression()), !dbg !19
+  call void @_Z4calli(i32 noundef 0), !dbg !25
+  %arrayidx1 = getelementptr inbounds [16 x i32], ptr %B, i64 0, i64 8, !dbg !26
+  call void @llvm.memmove.p0.p0.i64(ptr align 16 %arrayidx1, ptr align 4 @Glob, i64 24, i1 false), !dbg !27
+  %arrayidx2 = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 0, !dbg !28
+  %0 = load i32, ptr %arrayidx2, align 16, !dbg !28
+  call void @_Z4calli(i32 noundef %0), !dbg !33
+  %arrayidx3 = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 8, !dbg !34
+  %arrayidx4 = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 0, !dbg !35
+  call void @llvm.memmove.p0.p0.i64(ptr align 16 %arrayidx3, ptr align 16 %arrayidx4, i64 24, i1 false), !dbg !36, !DIAssignID !37
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !13, metadata !DIExpression(DW_OP_LLVM_fragment, 256, 192), metadata !37, metadata ptr %arrayidx3, metadata !DIExpression()), !dbg !19
+  %arrayidx5 = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 8, !dbg !38
+  %1 = load i32, ptr %arrayidx5, align 16, !dbg !38
+  call void @_Z4calli(i32 noundef %1), !dbg !39
+  %2 = load i32, ptr @Cond, align 4, !dbg !40
+  %tobool = icmp ne i32 %2, 0, !dbg !40
+  br i1 %tobool, label %if.then, label %if.else, !dbg !42
+
+if.then:                                          ; preds = %entry
+  %3 = load ptr, ptr @C, align 8, !dbg !43
+  %arrayidx6 = getelementptr inbounds [16 x i32], ptr %A, i64 0, i64 8, !dbg !46
+  call void @llvm.memmove.p0.p0.i64(ptr align 4 %3, ptr align 16 %arrayidx6, i64 24, i1 false), !dbg !47
+  br label %if.end, !dbg !47
+
+if.else:                                          ; preds = %entry
+  %4 = load ptr, ptr @C, align 8, !dbg !48
+  %arrayidx7 = getelementptr inbounds [16 x i32], ptr %B, i64 0, i64 8, !dbg !49
+  call void @llvm.memmove.p0.p0.i64(ptr align 4 %4, ptr align 16 %arrayidx7, i64 24, i1 false), !dbg !50
+  br label %if.end
+
+if.end:                                           ; preds = %if.else, %if.then
+  call void @llvm.lifetime.end.p0(i64 64, ptr %B) #5, !dbg !51
+  call void @llvm.lifetime.end.p0(i64 64, ptr %A) #5, !dbg !51
+  ret void, !dbg !51
+}
+
+declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #2
+declare void @llvm.memmove.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) #3
+declare void @_Z4calli(i32 noundef) #4
+declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2
+
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 16.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 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{!"clang version 16.0.0"}
+!9 = distinct !DISubprogram(name: "f", linkageName: "_Z1fv", scope: !1, file: !1, line: 6, type: !10, scopeLine: 6, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !12)
+!10 = !DISubroutineType(types: !11)
+!11 = !{null}
+!12 = !{!13}
+!13 = !DILocalVariable(name: "A", scope: !9, file: !1, line: 7, type: !14)
+!14 = !DICompositeType(tag: DW_TAG_array_type, baseType: !15, size: 512, elements: !16)
+!15 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!16 = !{!17}
+!17 = !DISubrange(count: 16)
+!18 = distinct !DIAssignID()
+!19 = !DILocation(line: 0, scope: !9)
+!20 = !DILocation(line: 7, column: 3, scope: !9)
+!21 = !DILocation(line: 8, column: 3, scope: !9)
+!22 = !DILocation(line: 10, column: 22, scope: !9)
+!23 = !DILocation(line: 10, column: 3, scope: !9)
+!24 = distinct !DIAssignID()
+!25 = !DILocation(line: 11, column: 3, scope: !9)
+!26 = !DILocation(line: 13, column: 22, scope: !9)
+!27 = !DILocation(line: 13, column: 3, scope: !9)
+!28 = !DILocation(line: 14, column: 8, scope: !9)
+!33 = !DILocation(line: 14, column: 3, scope: !9)
+!34 = !DILocation(line: 16, column: 22, scope: !9)
+!35 = !DILocation(line: 16, column: 29, scope: !9)
+!36 = !DILocation(line: 16, column: 3, scope: !9)
+!37 = distinct !DIAssignID()
+!38 = !DILocation(line: 17, column: 8, scope: !9)
+!39 = !DILocation(line: 17, column: 3, scope: !9)
+!40 = !DILocation(line: 18, column: 7, scope: !41)
+!41 = distinct !DILexicalBlock(scope: !9, file: !1, line: 18, column: 7)
+!42 = !DILocation(line: 18, column: 7, scope: !9)
+!43 = !DILocation(line: 20, column: 23, scope: !41)
+!46 = !DILocation(line: 20, column: 27, scope: !41)
+!47 = !DILocation(line: 20, column: 5, scope: !41)
+!48 = !DILocation(line: 23, column: 23, scope: !41)
+!49 = !DILocation(line: 23, column: 27, scope: !41)
+!50 = !DILocation(line: 23, column: 5, scope: !41)
+!51 = !DILocation(line: 24, column: 1, scope: !9)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/rewrite.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/rewrite.ll
new file mode 100644
index 0000000000000..001693a4aecdf
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/rewrite.ll
@@ -0,0 +1,130 @@
+; RUN: opt -passes=sroa,verify -S %s -experimental-assignment-tracking -o - \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+; Check that the new slices of an alloca and memset intructions get dbg.assign
+; intrinsics with the correct fragment info. Ensure that only the
+; value-expression gets fragment info; that the address-expression remains
+; untouched.
+
+;; $ cat test.cpp
+;; void do_something();
+;; struct LargeStruct {
+;;   int A, B, C;
+;;   int Var;
+;;   int D, E, F;
+;; };
+;; int Glob;
+;; bool Cond;
+;; int example() {
+;;   LargeStruct S = {0};
+;;   S.Var = Glob;
+;;   return S.Var;
+;; }
+;; $ clang test.cpp -Xclang -disable-llvm-passes -O2 -g -c -S -emit-llvm -o -
+
+; CHECK: entry:
+; CHECK-NEXT:   %S.sroa.0 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_1:[0-9]+]]
+; CHECK-NEXT:   call void @llvm.dbg.assign(metadata i1 undef, metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_1]], metadata ptr %S.sroa.0, metadata !DIExpression()), !dbg
+
+;; The middle slice has been promoted, so the alloca has gone away.
+
+; CHECK-NEXT:   %S.sroa.5 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_3:[0-9]+]]
+; CHECK-NEXT:   call void @llvm.dbg.assign(metadata i1 undef, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_3]], metadata ptr %S.sroa.5, metadata !DIExpression()), !dbg
+
+;; The memset has been sliced up (middle slice removed).
+; CHECK: call void @llvm.memset{{.*}}(ptr align 8 %S.sroa.0, i8 0, i64 12, i1 false), !dbg !{{.+}}, !DIAssignID ![[ID_5:[0-9]+]]
+; CHECK: call void @llvm.memset{{.*}}(ptr align 8 %S.sroa.5, i8 0, i64 12, i1 false), !dbg !{{.+}}, !DIAssignID ![[ID_6:[0-9]+]]
+
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_5]], metadata ptr %S.sroa.0, metadata !DIExpression()), !dbg
+;; Check the middle slice (no memset) gets a correct dbg.assign.
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT:   call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_6]], metadata ptr %S.sroa.5, metadata !DIExpression()), !dbg
+
+;; mem2reg promotes the load/store to the middle slice created by SROA:
+; CHECK-NEXT: %0 = load i32, ptr @Glob, align 4, !dbg !{{.+}}
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata ![[ID_4:[0-9]+]], metadata ptr undef, metadata !DIExpression()), !dbg
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+%struct.LargeStruct = type { i32, i32, i32, i32, i32, i32, i32 }
+
+ at Glob = dso_local global i32 0, align 4, !dbg !0
+ at Cond = dso_local global i8 0, align 1, !dbg !6
+
+; Function Attrs: nounwind uwtable mustprogress
+define dso_local i32 @_Z7examplev() #0 !dbg !14 {
+entry:
+  %S = alloca %struct.LargeStruct, align 4, !DIAssignID !28
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !18, metadata !DIExpression(), metadata !28, metadata ptr %S, metadata !DIExpression()), !dbg !29
+  %0 = bitcast ptr %S to ptr, !dbg !30
+  call void @llvm.lifetime.start.p0i8(i64 28, ptr %0) #4, !dbg !30
+  %1 = bitcast ptr %S to ptr, !dbg !31
+  call void @llvm.memset.p0i8.i64(ptr align 4 %1, i8 0, i64 28, i1 false), !dbg !31, !DIAssignID !32
+  call void @llvm.dbg.assign(metadata i8 0, metadata !18, metadata !DIExpression(), metadata !32, metadata ptr %1, metadata !DIExpression()), !dbg !31
+  %2 = load i32, ptr @Glob, align 4, !dbg !33
+  %Var = getelementptr inbounds %struct.LargeStruct, ptr %S, i32 0, i32 3, !dbg !38
+  store i32 %2, ptr %Var, align 4, !dbg !39, !DIAssignID !42
+  call void @llvm.dbg.assign(metadata i32 %2, metadata !18, metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !42, metadata ptr %Var, metadata !DIExpression()), !dbg !39
+  %Var1 = getelementptr inbounds %struct.LargeStruct, ptr %S, i32 0, i32 3, !dbg !43
+  %3 = load i32, ptr %Var1, align 4, !dbg !43
+  %4 = bitcast ptr %S to ptr, !dbg !44
+  call void @llvm.lifetime.end.p0i8(i64 28, ptr %4) #4, !dbg !44
+  ret i32 %3, !dbg !45
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn writeonly
+declare void @llvm.memset.p0i8.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!10, !11, !12}
+!llvm.ident = !{!13}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "Glob", scope: !2, file: !3, line: 7, type: !9, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "test.cpp", directory: "/")
+!4 = !{}
+!5 = !{!0, !6}
+!6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
+!7 = distinct !DIGlobalVariable(name: "Cond", scope: !2, file: !3, line: 8, type: !8, isLocal: false, isDefinition: true)
+!8 = !DIBasicType(name: "bool", size: 8, encoding: DW_ATE_boolean)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 7, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
+!12 = !{i32 1, !"wchar_size", i32 4}
+!13 = !{!"clang version 12.0.0"}
+!14 = distinct !DISubprogram(name: "example", linkageName: "_Z7examplev", scope: !3, file: !3, line: 9, type: !15, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17)
+!15 = !DISubroutineType(types: !16)
+!16 = !{!9}
+!17 = !{!18}
+!18 = !DILocalVariable(name: "S", scope: !14, file: !3, line: 10, type: !19)
+!19 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "LargeStruct", file: !3, line: 2, size: 224, flags: DIFlagTypePassByValue, elements: !20, identifier: "_ZTS11LargeStruct")
+!20 = !{!21, !22, !23, !24, !25, !26, !27}
+!21 = !DIDerivedType(tag: DW_TAG_member, name: "A", scope: !19, file: !3, line: 3, baseType: !9, size: 32)
+!22 = !DIDerivedType(tag: DW_TAG_member, name: "B", scope: !19, file: !3, line: 3, baseType: !9, size: 32, offset: 32)
+!23 = !DIDerivedType(tag: DW_TAG_member, name: "C", scope: !19, file: !3, line: 3, baseType: !9, size: 32, offset: 64)
+!24 = !DIDerivedType(tag: DW_TAG_member, name: "Var", scope: !19, file: !3, line: 4, baseType: !9, size: 32, offset: 96)
+!25 = !DIDerivedType(tag: DW_TAG_member, name: "D", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 128)
+!26 = !DIDerivedType(tag: DW_TAG_member, name: "E", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 160)
+!27 = !DIDerivedType(tag: DW_TAG_member, name: "F", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 192)
+!28 = distinct !DIAssignID()
+!29 = !DILocation(line: 0, scope: !14)
+!30 = !DILocation(line: 10, column: 3, scope: !14)
+!31 = !DILocation(line: 10, column: 15, scope: !14)
+!32 = distinct !DIAssignID()
+!33 = !DILocation(line: 11, column: 11, scope: !14)
+!38 = !DILocation(line: 11, column: 5, scope: !14)
+!39 = !DILocation(line: 11, column: 9, scope: !14)
+!42 = distinct !DIAssignID()
+!43 = !DILocation(line: 12, column: 12, scope: !14)
+!44 = !DILocation(line: 13, column: 1, scope: !14)
+!45 = !DILocation(line: 12, column: 3, scope: !14)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/store.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/store.ll
new file mode 100644
index 0000000000000..c29cf488ce8b9
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/store.ll
@@ -0,0 +1,150 @@
+; RUN: opt -passes=sroa,verify -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+; Check that the new slices of an alloca and memset intructions get dbg.assign
+; intrinsics with the correct fragment info. Ensure that only the
+; value-expression gets fragment info; that the address-expression remains
+; untouched.
+
+;; $ cat test.cpp
+;; void do_something();
+;; struct LargeStruct {
+;;   int A, B, C;
+;;   int Var;
+;;   int D, E, F;
+;; };
+;; int Glob;
+;; bool Cond;
+;; int use(LargeStruct);
+;; int example() {
+;;   LargeStruct S = {0};
+;;   S.Var = Glob;
+;;   use(S);
+;;   return S.Var;
+;; }
+;; $ clang test.cpp -Xclang -disable-llvm-passes -O2 -g -c -S -emit-llvm -o - \
+;;   | opt -passes=declare-to-assign -S -o -
+
+; CHECK: entry:
+; CHECK-NEXT:   %S.sroa.0 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_1:[0-9]+]]
+; CHECK-NEXT:   call void @llvm.dbg.assign(metadata i1 undef, metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_1]], metadata ptr %S.sroa.0, metadata !DIExpression()), !dbg
+
+; CHECK-NEXT:   %S.sroa.6 = alloca { i32, i32, i32 }, align 8, !DIAssignID ![[ID_3:[0-9]+]]
+; CHECK-NEXT:   call void @llvm.dbg.assign(metadata i1 undef, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_3]], metadata ptr %S.sroa.6, metadata !DIExpression()), !dbg
+
+;; The memset has been split into [0, 96)[96, 128)[128, 224) bit slices. The
+;; memset for the middle slice has been removed.
+; CHECK: call void @llvm.memset{{.*}}(ptr align 8 %S.sroa.0, i8 0, i64 12, i1 false), !dbg !{{.+}}, !DIAssignID ![[ID_4:[0-9]+]]
+; CHECK-NEXT: call void @llvm.memset{{.*}}(ptr align 8 %S.sroa.6, i8 0, i64 12, i1 false), !dbg !{{.+}}, !DIAssignID ![[ID_5:[0-9]+]]
+
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 96), metadata ![[ID_4]], metadata ptr %S.sroa.0, metadata !DIExpression()), !dbg
+;; This is the one we care about most in this test: check that a memset->store
+;; gets a correct dbg.assign.
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 96), metadata ![[ID_5]], metadata ptr %S.sroa.6, metadata !DIExpression()), !dbg
+
+;; The load from global+store becomes a load.
+;; FIXME: In reality it is actually stored again later on.
+; CHECK-NEXT: %0 = load i32, ptr @Glob, align 4, !dbg !{{.+}}
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg !
+
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+%struct.LargeStruct = type { i32, i32, i32, i32, i32, i32, i32 }
+
+ at Glob = dso_local global i32 0, align 4, !dbg !0
+ at Cond = dso_local global i8 0, align 1, !dbg !6
+
+; Function Attrs: uwtable mustprogress
+define dso_local i32 @_Z7examplev() #0 !dbg !14 {
+entry:
+  %S = alloca %struct.LargeStruct, align 4, !DIAssignID !28
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !18, metadata !DIExpression(), metadata !28, metadata ptr %S, metadata !DIExpression()), !dbg !29
+  %agg.tmp = alloca %struct.LargeStruct, align 8
+  %0 = bitcast ptr %S to ptr, !dbg !30
+  call void @llvm.lifetime.start.p0i8(i64 28, ptr %0) #5, !dbg !30
+  %1 = bitcast ptr %S to ptr, !dbg !31
+  call void @llvm.memset.p0i8.i64(ptr align 4 %1, i8 0, i64 28, i1 false), !dbg !31, !DIAssignID !32
+  call void @llvm.dbg.assign(metadata i8 0, metadata !18, metadata !DIExpression(), metadata !32, metadata ptr %1, metadata !DIExpression()), !dbg !31
+  %2 = load i32, ptr @Glob, align 4, !dbg !33
+  %Var = getelementptr inbounds %struct.LargeStruct, ptr %S, i32 0, i32 3, !dbg !38
+  store i32 %2, ptr %Var, align 4, !dbg !39, !DIAssignID !42
+  call void @llvm.dbg.assign(metadata i32 %2, metadata !18, metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !42, metadata ptr %Var, metadata !DIExpression()), !dbg !39
+  %3 = bitcast ptr %agg.tmp to ptr, !dbg !43
+  %4 = bitcast ptr %S to ptr, !dbg !43
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 4 %3, ptr align 4 %4, i64 28, i1 false), !dbg !43
+  %call = call i32 @_Z3use11LargeStruct(ptr byval(%struct.LargeStruct) align 8 %agg.tmp), !dbg !45
+  %Var1 = getelementptr inbounds %struct.LargeStruct, ptr %S, i32 0, i32 3, !dbg !46
+  %5 = load i32, ptr %Var1, align 4, !dbg !46
+  %6 = bitcast ptr %S to ptr, !dbg !47
+  call void @llvm.lifetime.end.p0i8(i64 28, ptr %6) #5, !dbg !47
+  ret i32 %5, !dbg !48
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn writeonly
+declare void @llvm.memset.p0i8.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2
+
+declare !dbg !49 dso_local i32 @_Z3use11LargeStruct(ptr byval(%struct.LargeStruct) align 8) #3
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #4
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!10, !11, !12}
+!llvm.ident = !{!13}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "Glob", scope: !2, file: !3, line: 7, type: !9, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "test.cpp", directory: "/")
+!4 = !{}
+!5 = !{!0, !6}
+!6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
+!7 = distinct !DIGlobalVariable(name: "Cond", scope: !2, file: !3, line: 8, type: !8, isLocal: false, isDefinition: true)
+!8 = !DIBasicType(name: "bool", size: 8, encoding: DW_ATE_boolean)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 7, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
+!12 = !{i32 1, !"wchar_size", i32 4}
+!13 = !{!"clang version 12.0.0"}
+!14 = distinct !DISubprogram(name: "example", linkageName: "_Z7examplev", scope: !3, file: !3, line: 10, type: !15, scopeLine: 10, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17)
+!15 = !DISubroutineType(types: !16)
+!16 = !{!9}
+!17 = !{!18}
+!18 = !DILocalVariable(name: "S", scope: !14, file: !3, line: 11, type: !19)
+!19 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "LargeStruct", file: !3, line: 2, size: 224, flags: DIFlagTypePassByValue, elements: !20, identifier: "_ZTS11LargeStruct")
+!20 = !{!21, !22, !23, !24, !25, !26, !27}
+!21 = !DIDerivedType(tag: DW_TAG_member, name: "A", scope: !19, file: !3, line: 3, baseType: !9, size: 32)
+!22 = !DIDerivedType(tag: DW_TAG_member, name: "B", scope: !19, file: !3, line: 3, baseType: !9, size: 32, offset: 32)
+!23 = !DIDerivedType(tag: DW_TAG_member, name: "C", scope: !19, file: !3, line: 3, baseType: !9, size: 32, offset: 64)
+!24 = !DIDerivedType(tag: DW_TAG_member, name: "Var", scope: !19, file: !3, line: 4, baseType: !9, size: 32, offset: 96)
+!25 = !DIDerivedType(tag: DW_TAG_member, name: "D", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 128)
+!26 = !DIDerivedType(tag: DW_TAG_member, name: "E", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 160)
+!27 = !DIDerivedType(tag: DW_TAG_member, name: "F", scope: !19, file: !3, line: 5, baseType: !9, size: 32, offset: 192)
+!28 = distinct !DIAssignID()
+!29 = !DILocation(line: 0, scope: !14)
+!30 = !DILocation(line: 11, column: 3, scope: !14)
+!31 = !DILocation(line: 11, column: 15, scope: !14)
+!32 = distinct !DIAssignID()
+!33 = !DILocation(line: 12, column: 11, scope: !14)
+!38 = !DILocation(line: 12, column: 5, scope: !14)
+!39 = !DILocation(line: 12, column: 9, scope: !14)
+!42 = distinct !DIAssignID()
+!43 = !DILocation(line: 13, column: 7, scope: !14)
+!45 = !DILocation(line: 13, column: 3, scope: !14)
+!46 = !DILocation(line: 14, column: 12, scope: !14)
+!47 = !DILocation(line: 15, column: 1, scope: !14)
+!48 = !DILocation(line: 14, column: 3, scope: !14)
+!49 = !DISubprogram(name: "use", linkageName: "_Z3use11LargeStruct", scope: !3, file: !3, line: 9, type: !50, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !4)
+!50 = !DISubroutineType(types: !51)
+!51 = !{!9, !19}

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/unspecified-var-size.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/unspecified-var-size.ll
new file mode 100644
index 0000000000000..6234e6c6b1d05
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/unspecified-var-size.ll
@@ -0,0 +1,54 @@
+; RUN: opt -S %s -passes=sroa -o - -experimental-assignment-tracking | FileCheck %s
+
+;; $ cat test.cpp
+;; #include <cstddef>
+;; void fun(std::nullptr_t) {}
+;;
+;; Check that migrateDebugInfo doesn't crash when encountering an alloca for a
+;; variable with a type of unspecified size (e.g. DW_TAG_unspecified_type).
+
+; CHECK: @llvm.dbg.assign(metadata ptr %0,{{.+}}, metadata !DIExpression(),{{.+}}, metadata ptr undef, {{.+}})
+;; There should be no new fragment and the value component should remain as %0.
+
+define dso_local void @_Z3funDn(ptr %0) #0 !dbg !14 {
+entry:
+  %.addr = alloca i8*, align 8, !DIAssignID !22
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !21, metadata !DIExpression(), metadata !22, metadata ptr %.addr, metadata !DIExpression()), !dbg !23
+  store ptr %0, ptr %.addr, align 8, !DIAssignID !28
+  call void @llvm.dbg.assign(metadata ptr %0, metadata !21, metadata !DIExpression(), metadata !28, metadata ptr %.addr, metadata !DIExpression()), !dbg !23
+  ret void, !dbg !29
+}
+
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!10, !11, !12}
+!llvm.ident = !{!13}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, imports: !3, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{}
+!3 = !{!4}
+!4 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !6, file: !9, line: 56)
+!5 = !DINamespace(name: "std", scope: null)
+!6 = !DIDerivedType(tag: DW_TAG_typedef, name: "max_align_t", file: !7, line: 24, baseType: !8)
+!7 = !DIFile(filename: "clang/12.0.0/include/__stddef_max_align_t.h", directory: "/")
+!8 = !DICompositeType(tag: DW_TAG_structure_type, file: !7, line: 19, size: 256, flags: DIFlagFwdDecl, identifier: "_ZTS11max_align_t")
+!9 = !DIFile(filename: "include/c++/7.5.0/cstddef", directory: "")
+!10 = !{i32 7, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
+!12 = !{i32 1, !"wchar_size", i32 4}
+!13 = !{!"clang version 12.0.0"}
+!14 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funDn", scope: !1, file: !1, line: 20, type: !15, scopeLine: 20, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !20)
+!15 = !DISubroutineType(types: !16)
+!16 = !{null, !17}
+!17 = !DIDerivedType(tag: DW_TAG_typedef, name: "nullptr_t", scope: !5, file: !18, line: 235, baseType: !19)
+!18 = !DIFile(filename: "include/x86_64-linux-gnu/c++/7.5.0/bits/c++config.h", directory: "")
+!19 = !DIBasicType(tag: DW_TAG_unspecified_type, name: "decltype(nullptr)")
+!20 = !{!21}
+!21 = !DILocalVariable(arg: 1, scope: !14, file: !1, line: 20, type: !17)
+!22 = distinct !DIAssignID()
+!23 = !DILocation(line: 0, scope: !14)
+!28 = distinct !DIAssignID()
+!29 = !DILocation(line: 20, column: 27, scope: !14)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/user-memcpy.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/user-memcpy.ll
new file mode 100644
index 0000000000000..d8b6d76456d80
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/user-memcpy.ll
@@ -0,0 +1,235 @@
+; RUN: opt -passes=sroa -S %s -o - -experimental-assignment-tracking \
+; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg"
+
+;; Check that the fragments generated in SROA for a split alloca that has a
+;; dbg.assign with non-zero-offset fragment already are correct.  Ensure that
+;; only the value-expression gets fragment info; that the address-expression
+;; remains untouched.
+
+;; $ cat test.cpp
+;; #include <cstring>
+;;
+;; struct V3i { long x, y, z; };
+;; void fun() {
+;;   V3i point = {0, 0, 0};
+;;   point.z = 5000;
+;;   V3i other = {10, 9, 8};
+;;   std::memcpy(&point.y, &other.x, sizeof(long) * 2);
+;; }
+;; $ clang++ -c -O2 -g test.cpp -o - -Xclang -disable-llvm-passes -S -emit-llvm \
+;;   | opt -passes=declare-to-assign -S -o -
+
+; CHECK: entry:
+;; Allocas have been promoted - the linked dbg.assigns have been removed.
+
+;; | V3i point = {0, 0, 0};
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 0, metadata ![[point:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 0, metadata ![[point]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 0, metadata ![[point]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+
+;; point.z = 5000;
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 5000, metadata ![[point]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+
+;; | V3i other = {10, 9, 8};
+;;   other is global const:
+;;     local.other.x = global.other.x
+;;     local.other.y = global.other.y
+;;     local.other.z = global.other.z
+; CHECK-NEXT: %other.sroa.0.0.copyload = load i64, ptr @__const._Z3funv.other
+; CHECK-NEXT: %other.sroa.4.0.copyload = load i64, ptr getelementptr inbounds (i8, ptr @__const._Z3funv.other, i64 8)
+; CHECK-NEXT: %other.sroa.5.0.copyload = load i64, ptr getelementptr inbounds (i8, ptr @__const._Z3funv.other, i64 16)
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 %other.sroa.0.0.copyload, metadata ![[other:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 %other.sroa.4.0.copyload, metadata ![[other]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 %other.sroa.5.0.copyload, metadata ![[other]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+
+;; | std::memcpy(&point.y, &other.x, sizeof(long) * 2);
+;;   other is now 3 scalars:
+;;     point.y = other.x
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 %other.sroa.0.0.copyload, metadata ![[point]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+;;
+;;     point.z = other.y
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata i64 %other.sroa.4.0.copyload, metadata ![[point]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64), metadata !{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+
+; CHECK: ![[point]] = !DILocalVariable(name: "point",
+; CHECK: ![[other]] = !DILocalVariable(name: "other",
+
+source_filename = "test.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.V3i = type { i64, i64, i64 }
+
+ at __const._Z3funv.other = private unnamed_addr constant %struct.V3i { i64 10, i64 9, i64 8 }, align 8
+
+; Function Attrs: nounwind uwtable mustprogress
+define dso_local void @_Z3funv() !dbg !100 {
+entry:
+  %point = alloca %struct.V3i, align 8, !DIAssignID !112
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !104, metadata !DIExpression(), metadata !112, metadata ptr %point, metadata !DIExpression()), !dbg !113
+  %other = alloca %struct.V3i, align 8, !DIAssignID !114
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !111, metadata !DIExpression(), metadata !114, metadata ptr %other, metadata !DIExpression()), !dbg !113
+  %0 = bitcast ptr %point to ptr, !dbg !115
+  call void @llvm.lifetime.start.p0i8(i64 24, ptr %0), !dbg !115
+  %1 = bitcast ptr %point to ptr, !dbg !116
+  call void @llvm.memset.p0i8.i64(ptr align 8 %1, i8 0, i64 24, i1 false), !dbg !116, !DIAssignID !117
+  call void @llvm.dbg.assign(metadata i8 0, metadata !104, metadata !DIExpression(), metadata !117, metadata ptr %1, metadata !DIExpression()), !dbg !116
+  %z = getelementptr inbounds %struct.V3i, ptr %point, i32 0, i32 2, !dbg !118
+  store i64 5000, ptr %z, align 8, !dbg !119, !DIAssignID !125
+  call void @llvm.dbg.assign(metadata i64 5000, metadata !104, metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64), metadata !125, metadata ptr %z, metadata !DIExpression()), !dbg !119
+  %2 = bitcast ptr %other to ptr, !dbg !126
+  call void @llvm.lifetime.start.p0i8(i64 24, ptr %2), !dbg !126
+  %3 = bitcast ptr %other to ptr, !dbg !127
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 8 %3, ptr align 8 bitcast (ptr @__const._Z3funv.other to ptr), i64 24, i1 false), !dbg !127, !DIAssignID !128
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !111, metadata !DIExpression(), metadata !128, metadata ptr %3, metadata !DIExpression()), !dbg !127
+  %y = getelementptr inbounds %struct.V3i, ptr %point, i32 0, i32 1, !dbg !129
+  %4 = bitcast ptr %y to ptr, !dbg !130
+  %x = getelementptr inbounds %struct.V3i, ptr %other, i32 0, i32 0, !dbg !131
+  %5 = bitcast ptr %x to ptr, !dbg !130
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr align 8 %4, ptr align 8 %5, i64 16, i1 false), !dbg !130, !DIAssignID !132
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !104, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 128), metadata !132, metadata ptr %4, metadata !DIExpression()), !dbg !130
+  %6 = bitcast ptr %other to ptr, !dbg !133
+  call void @llvm.lifetime.end.p0i8(i64 24, ptr %6), !dbg !133
+  %7 = bitcast ptr %point to ptr, !dbg !133
+  call void @llvm.lifetime.end.p0i8(i64 24, ptr %7), !dbg !133
+  ret void, !dbg !133
+}
+
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture)
+declare void @llvm.memset.p0i8.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg)
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture)
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!96, !97, !98}
+!llvm.ident = !{!99}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, imports: !3, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{}
+!3 = !{!4, !18, !22, !28, !32, !36, !46, !50, !52, !54, !58, !62, !66, !70, !74, !76, !78, !80, !84, !88, !92, !94}
+!4 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !6, file: !17, line: 75)
+!5 = !DINamespace(name: "std", scope: null)
+!6 = !DISubprogram(name: "memchr", scope: !7, file: !7, line: 90, type: !8, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!7 = !DIFile(filename: "/usr/include/string.h", directory: "")
+!8 = !DISubroutineType(types: !9)
+!9 = !{!10, !11, !13, !14}
+!10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+!11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64)
+!12 = !DIDerivedType(tag: DW_TAG_const_type, baseType: null)
+!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "size_t", file: !15, line: 46, baseType: !16)
+!15 = !DIFile(filename: "lib/clang/12.0.0/include/stddef.h", directory: "/")
+!16 = !DIBasicType(name: "long unsigned int", size: 64, encoding: DW_ATE_unsigned)
+!17 = !DIFile(filename: "/usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/cstring", directory: "")
+!18 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !19, file: !17, line: 76)
+!19 = !DISubprogram(name: "memcmp", scope: !7, file: !7, line: 63, type: !20, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!20 = !DISubroutineType(types: !21)
+!21 = !{!13, !11, !11, !14}
+!22 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !23, file: !17, line: 77)
+!23 = !DISubprogram(name: "memcpy", scope: !7, file: !7, line: 42, type: !24, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!24 = !DISubroutineType(types: !25)
+!25 = !{!10, !26, !27, !14}
+!26 = !DIDerivedType(tag: DW_TAG_restrict_type, baseType: !10)
+!27 = !DIDerivedType(tag: DW_TAG_restrict_type, baseType: !11)
+!28 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !29, file: !17, line: 78)
+!29 = !DISubprogram(name: "memmove", scope: !7, file: !7, line: 46, type: !30, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!30 = !DISubroutineType(types: !31)
+!31 = !{!10, !10, !11, !14}
+!32 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !33, file: !17, line: 79)
+!33 = !DISubprogram(name: "memset", scope: !7, file: !7, line: 60, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!34 = !DISubroutineType(types: !35)
+!35 = !{!10, !10, !13, !14}
+!36 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !37, file: !17, line: 80)
+!37 = !DISubprogram(name: "strcat", scope: !7, file: !7, line: 129, type: !38, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!38 = !DISubroutineType(types: !39)
+!39 = !{!40, !42, !43}
+!40 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !41, size: 64)
+!41 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+!42 = !DIDerivedType(tag: DW_TAG_restrict_type, baseType: !40)
+!43 = !DIDerivedType(tag: DW_TAG_restrict_type, baseType: !44)
+!44 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !45, size: 64)
+!45 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !41)
+!46 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !47, file: !17, line: 81)
+!47 = !DISubprogram(name: "strcmp", scope: !7, file: !7, line: 136, type: !48, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!48 = !DISubroutineType(types: !49)
+!49 = !{!13, !44, !44}
+!50 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !51, file: !17, line: 82)
+!51 = !DISubprogram(name: "strcoll", scope: !7, file: !7, line: 143, type: !48, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!52 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !53, file: !17, line: 83)
+!53 = !DISubprogram(name: "strcpy", scope: !7, file: !7, line: 121, type: !38, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!54 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !55, file: !17, line: 84)
+!55 = !DISubprogram(name: "strcspn", scope: !7, file: !7, line: 272, type: !56, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!56 = !DISubroutineType(types: !57)
+!57 = !{!14, !44, !44}
+!58 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !59, file: !17, line: 85)
+!59 = !DISubprogram(name: "strerror", scope: !7, file: !7, line: 396, type: !60, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!60 = !DISubroutineType(types: !61)
+!61 = !{!40, !13}
+!62 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !63, file: !17, line: 86)
+!63 = !DISubprogram(name: "strlen", scope: !7, file: !7, line: 384, type: !64, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!64 = !DISubroutineType(types: !65)
+!65 = !{!14, !44}
+!66 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !67, file: !17, line: 87)
+!67 = !DISubprogram(name: "strncat", scope: !7, file: !7, line: 132, type: !68, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!68 = !DISubroutineType(types: !69)
+!69 = !{!40, !42, !43, !14}
+!70 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !71, file: !17, line: 88)
+!71 = !DISubprogram(name: "strncmp", scope: !7, file: !7, line: 139, type: !72, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!72 = !DISubroutineType(types: !73)
+!73 = !{!13, !44, !44, !14}
+!74 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !75, file: !17, line: 89)
+!75 = !DISubprogram(name: "strncpy", scope: !7, file: !7, line: 124, type: !68, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!76 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !77, file: !17, line: 90)
+!77 = !DISubprogram(name: "strspn", scope: !7, file: !7, line: 276, type: !56, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!78 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !79, file: !17, line: 91)
+!79 = !DISubprogram(name: "strtok", scope: !7, file: !7, line: 335, type: !38, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!80 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !81, file: !17, line: 92)
+!81 = !DISubprogram(name: "strxfrm", scope: !7, file: !7, line: 146, type: !82, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!82 = !DISubroutineType(types: !83)
+!83 = !{!14, !42, !43, !14}
+!84 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !85, file: !17, line: 93)
+!85 = !DISubprogram(name: "strchr", scope: !7, file: !7, line: 225, type: !86, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!86 = !DISubroutineType(types: !87)
+!87 = !{!40, !44, !13}
+!88 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !89, file: !17, line: 94)
+!89 = !DISubprogram(name: "strpbrk", scope: !7, file: !7, line: 302, type: !90, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!90 = !DISubroutineType(types: !91)
+!91 = !{!40, !44, !44}
+!92 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !93, file: !17, line: 95)
+!93 = !DISubprogram(name: "strrchr", scope: !7, file: !7, line: 252, type: !86, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!94 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !5, entity: !95, file: !17, line: 96)
+!95 = !DISubprogram(name: "strstr", scope: !7, file: !7, line: 329, type: !90, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!96 = !{i32 7, !"Dwarf Version", i32 4}
+!97 = !{i32 2, !"Debug Info Version", i32 3}
+!98 = !{i32 1, !"wchar_size", i32 4}
+!99 = !{!"clang version 12.0.0"}
+!100 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !1, file: !1, line: 4, type: !101, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !103)
+!101 = !DISubroutineType(types: !102)
+!102 = !{null}
+!103 = !{!104, !111}
+!104 = !DILocalVariable(name: "point", scope: !100, file: !1, line: 5, type: !105)
+!105 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "V3i", file: !1, line: 3, size: 192, flags: DIFlagTypePassByValue, elements: !106, identifier: "_ZTS3V3i")
+!106 = !{!107, !109, !110}
+!107 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !105, file: !1, line: 3, baseType: !108, size: 64)
+!108 = !DIBasicType(name: "long int", size: 64, encoding: DW_ATE_signed)
+!109 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !105, file: !1, line: 3, baseType: !108, size: 64, offset: 64)
+!110 = !DIDerivedType(tag: DW_TAG_member, name: "z", scope: !105, file: !1, line: 3, baseType: !108, size: 64, offset: 128)
+!111 = !DILocalVariable(name: "other", scope: !100, file: !1, line: 7, type: !105)
+!112 = distinct !DIAssignID()
+!113 = !DILocation(line: 0, scope: !100)
+!114 = distinct !DIAssignID()
+!115 = !DILocation(line: 5, column: 3, scope: !100)
+!116 = !DILocation(line: 5, column: 7, scope: !100)
+!117 = distinct !DIAssignID()
+!118 = !DILocation(line: 6, column: 9, scope: !100)
+!119 = !DILocation(line: 6, column: 11, scope: !100)
+!125 = distinct !DIAssignID()
+!126 = !DILocation(line: 7, column: 3, scope: !100)
+!127 = !DILocation(line: 7, column: 7, scope: !100)
+!128 = distinct !DIAssignID()
+!129 = !DILocation(line: 8, column: 22, scope: !100)
+!130 = !DILocation(line: 8, column: 3, scope: !100)
+!131 = !DILocation(line: 8, column: 32, scope: !100)
+!132 = distinct !DIAssignID()
+!133 = !DILocation(line: 9, column: 1, scope: !100)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-1.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-1.ll
new file mode 100644
index 0000000000000..3d83e18afdf6e
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-1.ll
@@ -0,0 +1,104 @@
+; RUN: opt %s -S -passes=sroa -o - -experimental-assignment-tracking | FileCheck %s
+
+;; Ensure that only the value-expression gets fragment info; that the
+;; address-expression remains untouched.
+
+;; $ cat test.cpp
+;; class a {
+;;   float b[4];
+;; };
+;; class c {
+;;   a m_fn1() const;
+;;   void d() const;
+;; };
+;; void c::d() const { a e = m_fn1(); }
+;;
+;; Generated by grabbing IR before sroa in:
+;; $ clang++ -O2 -g -c test.cpp -Xclang  -fexperimental-assignment-tracking
+
+; CHECK: %call = call
+; CHECK-NEXT: %0 = extractvalue { <2 x float>, <2 x float> } %call, 0
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %0, metadata ![[var:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata ![[id1:[0-9]+]],{{.+}} undef, metadata !DIExpression()), !dbg
+; CHECK-NEXT: %1 = extractvalue { <2 x float>, <2 x float> } %call, 1
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata <2 x float> %1, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata ![[id2:[0-9]+]], {{.+}} undef, metadata !DIExpression()), !dbg
+
+%class.c = type { i8 }
+%class.a = type { [4 x float] }
+
+; Function Attrs: uwtable
+define dso_local void @_ZNK1c1dEv(ptr %this) #0 align 2 !dbg !7 {
+entry:
+  %this.addr = alloca ptr, align 8, !DIAssignID !29
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !26, metadata !DIExpression(), metadata !29, metadata ptr %this.addr, metadata !DIExpression()), !dbg !30
+  %e = alloca %class.a, align 4, !DIAssignID !31
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !28, metadata !DIExpression(), metadata !31, metadata ptr %e, metadata !DIExpression()), !dbg !30
+  store ptr %this, ptr %this.addr, align 8, !DIAssignID !36
+  call void @llvm.dbg.assign(metadata ptr %this, metadata !26, metadata !DIExpression(), metadata !36, metadata ptr %this.addr, metadata !DIExpression()), !dbg !30
+  %this1 = load ptr, ptr %this.addr, align 8
+  %0 = bitcast ptr %e to ptr, !dbg !37
+  call void @llvm.lifetime.start.p0i8(i64 16, ptr %0) #4, !dbg !37
+  %call = call { <2 x float>, <2 x float> } @_ZNK1c5m_fn1Ev(ptr %this1), !dbg !38
+  %coerce.dive = getelementptr inbounds %class.a, ptr %e, i32 0, i32 0, !dbg !38
+  %1 = bitcast ptr %coerce.dive to ptr, !dbg !38
+  %2 = getelementptr inbounds { <2 x float>, <2 x float> }, ptr %1, i32 0, i32 0, !dbg !38
+  %3 = extractvalue { <2 x float>, <2 x float> } %call, 0, !dbg !38
+  store <2 x float> %3, ptr %2, align 4, !dbg !38, !DIAssignID !39
+  call void @llvm.dbg.assign(metadata <2 x float> %3, metadata !28, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !39, metadata ptr %2, metadata !DIExpression()), !dbg !30
+  %4 = getelementptr inbounds { <2 x float>, <2 x float> }, ptr %1, i32 0, i32 1, !dbg !38
+  %5 = extractvalue { <2 x float>, <2 x float> } %call, 1, !dbg !38
+  store <2 x float> %5, ptr %4, align 4, !dbg !38, !DIAssignID !40
+  call void @llvm.dbg.assign(metadata <2 x float> %5, metadata !28, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !40, metadata ptr %4, metadata !DIExpression()), !dbg !30
+  %6 = bitcast ptr %e to ptr, !dbg !41
+  call void @llvm.lifetime.end.p0i8(i64 16, ptr %6) #4, !dbg !41
+  ret void, !dbg !41
+}
+
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #2
+declare dso_local { <2 x float>, <2 x float> } @_ZNK1c5m_fn1Ev(ptr) #3
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #2
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1
+
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{!"clang version 12.0.0"}
+!7 = distinct !DISubprogram(name: "d", linkageName: "_ZNK1c1dEv", scope: !8, file: !1, line: 8, type: !23, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !22, retainedNodes: !25)
+!8 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "c", file: !1, line: 4, size: 8, flags: DIFlagTypePassByValue, elements: !9, identifier: "_ZTS1c")
+!9 = !{!10, !22}
+!10 = !DISubprogram(name: "m_fn1", linkageName: "_ZNK1c5m_fn1Ev", scope: !8, file: !1, line: 5, type: !11, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13, !20}
+!13 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "a", file: !1, line: 1, size: 128, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS1a")
+!14 = !{!15}
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !13, file: !1, line: 2, baseType: !16, size: 128)
+!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !17, size: 128, elements: !18)
+!17 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!18 = !{!19}
+!19 = !DISubrange(count: 4)
+!20 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !21, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!21 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !8)
+!22 = !DISubprogram(name: "d", linkageName: "_ZNK1c1dEv", scope: !8, file: !1, line: 6, type: !23, scopeLine: 6, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!23 = !DISubroutineType(types: !24)
+!24 = !{null, !20}
+!25 = !{!26, !28}
+!26 = !DILocalVariable(name: "this", arg: 1, scope: !7, type: !27, flags: DIFlagArtificial | DIFlagObjectPointer)
+!27 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !21, size: 64)
+!28 = !DILocalVariable(name: "e", scope: !7, file: !1, line: 8, type: !13)
+!29 = distinct !DIAssignID()
+!30 = !DILocation(line: 0, scope: !7)
+!31 = distinct !DIAssignID()
+!36 = distinct !DIAssignID()
+!37 = !DILocation(line: 8, column: 21, scope: !7)
+!38 = !DILocation(line: 8, column: 27, scope: !7)
+!39 = distinct !DIAssignID()
+!40 = distinct !DIAssignID()
+!41 = !DILocation(line: 8, column: 36, scope: !7)

diff  --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-2.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-2.ll
new file mode 100644
index 0000000000000..e04a5a9b58e0c
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/vec-2.ll
@@ -0,0 +1,206 @@
+; RUN: opt %s -S -passes=sroa -o - -experimental-assignment-tracking | FileCheck %s
+
+;; $ cat test.cpp
+;; class a {
+;; protected:
+;;   float b[4];
+;; };
+;; float c;
+;; class d : a {
+;; public:
+;;   d() { b[3] = c; }
+;;   void e() {}
+;; };
+;; d f();
+;; void g() {
+;;   d j = f(), h = j, i = h;
+;;   i.e();
+;;   i = d();
+;; }
+;;
+;; Generated by grabbing IR before sroa in:
+;; $ clang++ -O2 -g -c test.cpp -Xclang -fexperimental-assignment-tracking
+
+;; Check that the dbg.assign value is scalar rather than a vector value because
+;; we don't have a way of indexing a vector from within a
+;; dbg.assign/DIExpression. Ensure that only the value-expression gets fragment
+;; info; that the address-expression remains untouched.
+
+; CHECK: %i.sroa.4.12.vec.insert = insertelement <2 x float> %i.sroa.4.0.vec.insert, float %2, i32 1, !dbg
+;; There's a few dbg intrinsics we're not interested in testing wedged in here.
+; CHECK-NEXT: dbg.value
+; CHECK-NEXT: dbg.assign
+; CHECK-NEXT: dbg.assign
+; CHECK-NEXT: call void @llvm.dbg.assign(metadata float %2,{{.+}}, metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32),{{.+}}, metadata ptr undef, metadata !DIExpression()), !dbg
+
+%class.d = type { %class.a }
+%class.a = type { [4 x float] }
+
+$_ZN1d1eEv = comdat any
+
+$_ZN1dC2Ev = comdat any
+
+ at c = dso_local local_unnamed_addr global float 0.000000e+00, align 4, !dbg !0
+
+; Function Attrs: uwtable
+define dso_local void @_Z1gv() local_unnamed_addr #0 !dbg !11 {
+entry:
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !15, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !32, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !15, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !34, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !30, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !35, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !30, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !36, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  %i = alloca %class.d, align 8, !DIAssignID !37
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !31, metadata !DIExpression(), metadata !37, metadata ptr %i, metadata !DIExpression()), !dbg !33
+  %ref.tmp = alloca %class.d, align 4
+  %call = call { <2 x float>, <2 x float> } @_Z1fv(), !dbg !38
+  %0 = extractvalue { <2 x float>, <2 x float> } %call, 0, !dbg !38
+  call void @llvm.dbg.assign(metadata <2 x float> %0, metadata !15, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !39, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  %1 = extractvalue { <2 x float>, <2 x float> } %call, 1, !dbg !38
+  call void @llvm.dbg.assign(metadata <2 x float> %1, metadata !15, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !40, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata <2 x float> %0, metadata !30, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !41, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata <2 x float> %1, metadata !30, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !42, metadata ptr undef, metadata !DIExpression()), !dbg !33
+  %2 = bitcast ptr %i to ptr, !dbg !43
+  call void @llvm.lifetime.start.p0i8(i64 16, ptr nonnull %2) #5, !dbg !43
+  %h.sroa.0.sroa.0.0.h.sroa.0.0..sroa_cast4.sroa_cast = bitcast ptr %i to ptr, !dbg !44
+  store <2 x float> %0, ptr %h.sroa.0.sroa.0.0.h.sroa.0.0..sroa_cast4.sroa_cast, align 8, !dbg !44, !DIAssignID !45
+  call void @llvm.dbg.assign(metadata <2 x float> %0, metadata !31, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64), metadata !45, metadata ptr %h.sroa.0.sroa.0.0.h.sroa.0.0..sroa_cast4.sroa_cast, metadata !DIExpression()), !dbg !33
+  %h.sroa.0.sroa.4.0.h.sroa.0.0..sroa_cast4.sroa_idx13 = getelementptr inbounds %class.d, ptr %i, i64 0, i32 0, i32 0, i64 2, !dbg !44
+  %h.sroa.0.sroa.4.0.h.sroa.0.0..sroa_cast4.sroa_cast = bitcast ptr %h.sroa.0.sroa.4.0.h.sroa.0.0..sroa_cast4.sroa_idx13 to ptr, !dbg !44
+  store <2 x float> %1, ptr %h.sroa.0.sroa.4.0.h.sroa.0.0..sroa_cast4.sroa_cast, align 8, !dbg !44, !DIAssignID !46
+  call void @llvm.dbg.assign(metadata <2 x float> %1, metadata !31, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !46, metadata ptr %h.sroa.0.sroa.4.0.h.sroa.0.0..sroa_cast4.sroa_cast, metadata !DIExpression()), !dbg !33
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !47, metadata !DIExpression(), metadata !51, metadata ptr undef, metadata !DIExpression()), !dbg !52
+  call void @llvm.dbg.assign(metadata ptr %i, metadata !47, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !52
+  %3 = bitcast ptr %ref.tmp to ptr, !dbg !55
+  call void @llvm.lifetime.start.p0i8(i64 16, ptr nonnull %3) #5, !dbg !55
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !56, metadata !DIExpression(), metadata !59, metadata ptr undef, metadata !DIExpression()), !dbg !60
+  call void @llvm.dbg.assign(metadata ptr %ref.tmp, metadata !56, metadata !DIExpression(), metadata !62, metadata ptr undef, metadata !DIExpression()), !dbg !60
+  %4 = load float, ptr @c, align 4, !dbg !63
+  %arrayidx.i = getelementptr inbounds %class.d, ptr %ref.tmp, i64 0, i32 0, i32 0, i64 3, !dbg !69
+  store float %4, ptr %arrayidx.i, align 4, !dbg !70
+  call void @llvm.memcpy.p0i8.p0i8.i64(ptr nonnull align 8 dereferenceable(16) %2, ptr nonnull align 4 dereferenceable(16) %3, i64 16, i1 false), !dbg !71, !DIAssignID !72
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !31, metadata !DIExpression(), metadata !72, metadata ptr %2, metadata !DIExpression()), !dbg !33
+  call void @llvm.lifetime.end.p0i8(i64 16, ptr nonnull %3) #5, !dbg !73
+  call void @llvm.lifetime.end.p0i8(i64 16, ptr nonnull %2) #5, !dbg !74
+  ret void, !dbg !74
+}
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1
+
+declare !dbg !75 dso_local { <2 x float>, <2 x float> } @_Z1fv() local_unnamed_addr #2
+
+; Function Attrs: argmemonly nofree nosync nounwind willreturn
+declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
+
+; Function Attrs: nounwind uwtable
+define linkonce_odr dso_local void @_ZN1d1eEv(ptr %this) local_unnamed_addr #3 comdat align 2 !dbg !48 {
+entry:
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !47, metadata !DIExpression(), metadata !78, metadata ptr undef, metadata !DIExpression()), !dbg !79
+  call void @llvm.dbg.assign(metadata ptr %this, metadata !47, metadata !DIExpression(), metadata !80, metadata ptr undef, metadata !DIExpression()), !dbg !79
+  ret void, !dbg !81
+}
+
+; Function Attrs: nounwind uwtable
+define linkonce_odr dso_local void @_ZN1dC2Ev(ptr %this) unnamed_addr #3 comdat align 2 !dbg !57 {
+entry:
+  call void @llvm.dbg.assign(metadata i1 undef, metadata !56, metadata !DIExpression(), metadata !82, metadata ptr undef, metadata !DIExpression()), !dbg !83
+  call void @llvm.dbg.assign(metadata ptr %this, metadata !56, metadata !DIExpression(), metadata !84, metadata ptr undef, metadata !DIExpression()), !dbg !83
+  %0 = load float, ptr @c, align 4, !dbg !85
+  %arrayidx = getelementptr inbounds %class.d, ptr %this, i64 0, i32 0, i32 0, i64 3, !dbg !86
+  store float %0, ptr %arrayidx, align 4, !dbg !87
+  ret void, !dbg !88
+}
+
+declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1
+declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #4
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!7, !8, !9}
+!llvm.ident = !{!10}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "c", scope: !2, file: !3, line: 5, type: !6, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "test.cpp", directory: "/")
+!4 = !{}
+!5 = !{!0}
+!6 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!7 = !{i32 7, !"Dwarf Version", i32 4}
+!8 = !{i32 2, !"Debug Info Version", i32 3}
+!9 = !{i32 1, !"wchar_size", i32 4}
+!10 = !{!"clang version 12.0.0"}
+!11 = distinct !DISubprogram(name: "g", linkageName: "_Z1gv", scope: !3, file: !3, line: 12, type: !12, scopeLine: 12, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !14)
+!12 = !DISubroutineType(types: !13)
+!13 = !{null}
+!14 = !{!15, !30, !31}
+!15 = !DILocalVariable(name: "j", scope: !11, file: !3, line: 13, type: !16)
+!16 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "d", file: !3, line: 6, size: 128, flags: DIFlagTypePassByValue | DIFlagNonTrivial, elements: !17, identifier: "_ZTS1d")
+!17 = !{!18, !25, !29}
+!18 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !16, baseType: !19, extraData: i32 0)
+!19 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "a", file: !3, line: 1, size: 128, flags: DIFlagTypePassByValue, elements: !20, identifier: "_ZTS1a")
+!20 = !{!21}
+!21 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !19, file: !3, line: 3, baseType: !22, size: 128, flags: DIFlagProtected)
+!22 = !DICompositeType(tag: DW_TAG_array_type, baseType: !6, size: 128, elements: !23)
+!23 = !{!24}
+!24 = !DISubrange(count: 4)
+!25 = !DISubprogram(name: "d", scope: !16, file: !3, line: 8, type: !26, scopeLine: 8, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!26 = !DISubroutineType(types: !27)
+!27 = !{null, !28}
+!28 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !16, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!29 = !DISubprogram(name: "e", linkageName: "_ZN1d1eEv", scope: !16, file: !3, line: 9, type: !26, scopeLine: 9, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!30 = !DILocalVariable(name: "h", scope: !11, file: !3, line: 13, type: !16)
+!31 = !DILocalVariable(name: "i", scope: !11, file: !3, line: 13, type: !16)
+!32 = distinct !DIAssignID()
+!33 = !DILocation(line: 0, scope: !11)
+!34 = distinct !DIAssignID()
+!35 = distinct !DIAssignID()
+!36 = distinct !DIAssignID()
+!37 = distinct !DIAssignID()
+!38 = !DILocation(line: 13, column: 9, scope: !11)
+!39 = distinct !DIAssignID()
+!40 = distinct !DIAssignID()
+!41 = distinct !DIAssignID()
+!42 = distinct !DIAssignID()
+!43 = !DILocation(line: 13, column: 3, scope: !11)
+!44 = !DILocation(line: 13, column: 25, scope: !11)
+!45 = distinct !DIAssignID()
+!46 = distinct !DIAssignID()
+!47 = !DILocalVariable(name: "this", arg: 1, scope: !48, type: !50, flags: DIFlagArtificial | DIFlagObjectPointer)
+!48 = distinct !DISubprogram(name: "e", linkageName: "_ZN1d1eEv", scope: !16, file: !3, line: 9, type: !26, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, declaration: !29, retainedNodes: !49)
+!49 = !{!47}
+!50 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !16, size: 64)
+!51 = distinct !DIAssignID()
+!52 = !DILocation(line: 0, scope: !48, inlinedAt: !53)
+!53 = distinct !DILocation(line: 14, column: 5, scope: !11)
+!54 = distinct !DIAssignID()
+!55 = !DILocation(line: 15, column: 7, scope: !11)
+!56 = !DILocalVariable(name: "this", arg: 1, scope: !57, type: !50, flags: DIFlagArtificial | DIFlagObjectPointer)
+!57 = distinct !DISubprogram(name: "d", linkageName: "_ZN1dC2Ev", scope: !16, file: !3, line: 8, type: !26, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, declaration: !25, retainedNodes: !58)
+!58 = !{!56}
+!59 = distinct !DIAssignID()
+!60 = !DILocation(line: 0, scope: !57, inlinedAt: !61)
+!61 = distinct !DILocation(line: 15, column: 7, scope: !11)
+!62 = distinct !DIAssignID()
+!63 = !DILocation(line: 8, column: 16, scope: !64, inlinedAt: !61)
+!64 = distinct !DILexicalBlock(scope: !57, file: !3, line: 8, column: 7)
+!69 = !DILocation(line: 8, column: 9, scope: !64, inlinedAt: !61)
+!70 = !DILocation(line: 8, column: 14, scope: !64, inlinedAt: !61)
+!71 = !DILocation(line: 15, column: 5, scope: !11)
+!72 = distinct !DIAssignID()
+!73 = !DILocation(line: 15, column: 3, scope: !11)
+!74 = !DILocation(line: 16, column: 1, scope: !11)
+!75 = !DISubprogram(name: "f", linkageName: "_Z1fv", scope: !3, file: !3, line: 11, type: !76, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !4)
+!76 = !DISubroutineType(types: !77)
+!77 = !{!16}
+!78 = distinct !DIAssignID()
+!79 = !DILocation(line: 0, scope: !48)
+!80 = distinct !DIAssignID()
+!81 = !DILocation(line: 9, column: 13, scope: !48)
+!82 = distinct !DIAssignID()
+!83 = !DILocation(line: 0, scope: !57)
+!84 = distinct !DIAssignID()
+!85 = !DILocation(line: 8, column: 16, scope: !64)
+!86 = !DILocation(line: 8, column: 9, scope: !64)
+!87 = !DILocation(line: 8, column: 14, scope: !64)
+!88 = !DILocation(line: 8, column: 19, scope: !57)
+


        


More information about the llvm-commits mailing list