diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -2776,6 +2776,11 @@ struct FragmentInfo { uint64_t SizeInBits; uint64_t OffsetInBits; + /// Return the index of the first bit of the fragment. + uint64_t startInBits() const { return OffsetInBits; } + /// Return the index of the bit after the end of the fragment, e.g. for + /// fragment offset=16 and size=32 return their sum, 48. + uint64_t endInBits() const { return OffsetInBits + SizeInBits; } }; /// Retrieve the details of this fragment expression. diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp --- a/llvm/lib/Transforms/Scalar/SROA.cpp +++ b/llvm/lib/Transforms/Scalar/SROA.cpp @@ -118,13 +118,65 @@ /// GEPs. static cl::opt SROAStrictInbounds("sroa-strict-inbounds", cl::init(false), cl::Hidden); +/// Disable running mem2reg during SROA in order to test or debug SROA. +static cl::opt SROASkipMem2Reg("sroa-skip-mem2reg", cl::init(false), + cl::Hidden); namespace { + +/// Calculate the fragment of a variable to use when slicing a store +/// based on the slice dimensions, existing fragment, and base storage +/// fragment. +/// Note that a returned value of std::nullopt indicates that there is +/// no appropriate fragment available (rather than meaning use the whole +/// variable, which is a common usage). Because the store is being sliced +/// we always expect a fragment - there's never a case where the whole +/// variable should be used. +static std::optional +calculateFragment(uint64_t NewStorageSliceOffsetInBits, + uint64_t NewStorageSliceSizeInBits, + std::optional StorageFragment, + std::optional CurrentFragment) { + DIExpression::FragmentInfo Target; + // If the base storage describes part of the variable apply the offset and + // the size constraint. + if (StorageFragment) { + Target.SizeInBits = + std::min(NewStorageSliceSizeInBits, StorageFragment->SizeInBits); + Target.OffsetInBits = + NewStorageSliceOffsetInBits + StorageFragment->OffsetInBits; + } else { + Target.SizeInBits = NewStorageSliceSizeInBits; + Target.OffsetInBits = NewStorageSliceOffsetInBits; + } + + // No additional work to do if there isn't a fragment already, or there is + // but it already exactly describes the new assignment. + if (!CurrentFragment || *CurrentFragment == Target) + return Target; + + // Reject the target fragment if it doesn't fit wholly within the current + // fragment. TODO: We could instead chop up the target to fit in the case of + // a partial overlap. + if (Target.startInBits() < CurrentFragment->startInBits() || + Target.endInBits() > CurrentFragment->endInBits()) + return std::nullopt; + + // Target fits within the current fragment, return it. + return Target; +} + +static DebugVariable getAggregateVariable(DbgVariableIntrinsic *DVI) { + return DebugVariable(DVI->getVariable(), std::nullopt, + DVI->getDebugLoc().getInlinedAt()); +} + /// 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 IsSplit True if the store (not necessarily alloca) +/// is being split. +/// \param OldAllocaOffsetInBits Offset of the slice taken from OldAlloca. /// \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 @@ -132,8 +184,8 @@ /// \param Dest Store destination. /// \param Value Stored value. /// \param DL Datalayout. -static void migrateDebugInfo(AllocaInst *OldAlloca, - uint64_t RelativeOffsetInBits, +static void migrateDebugInfo(AllocaInst *OldAlloca, bool IsSplit, + uint64_t OldAllocaOffsetInBits, uint64_t SliceSizeInBits, Instruction *OldInst, Instruction *Inst, Value *Dest, Value *Value, const DataLayout &DL) { @@ -144,7 +196,9 @@ LLVM_DEBUG(dbgs() << " migrateDebugInfo\n"); LLVM_DEBUG(dbgs() << " OldAlloca: " << *OldAlloca << "\n"); - LLVM_DEBUG(dbgs() << " RelativeOffset: " << RelativeOffsetInBits << "\n"); + LLVM_DEBUG(dbgs() << " IsSplit: " << IsSplit << "\n"); + LLVM_DEBUG(dbgs() << " OldAllocaOffsetInBits: " << OldAllocaOffsetInBits + << "\n"); LLVM_DEBUG(dbgs() << " SliceSizeInBits: " << SliceSizeInBits << "\n"); LLVM_DEBUG(dbgs() << " OldInst: " << *OldInst << "\n"); LLVM_DEBUG(dbgs() << " Inst: " << *Inst << "\n"); @@ -152,13 +206,19 @@ if (Value) LLVM_DEBUG(dbgs() << " Value: " << *Value << "\n"); + /// Map of aggregate variables to their fragment associated with OldAlloca. + DenseMap> + BaseFragments; + for (auto *DAI : at::getAssignmentMarkers(OldAlloca)) + BaseFragments[getAggregateVariable(DAI)] = + DAI->getExpression()->getFragmentInfo(); + // 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) { @@ -166,30 +226,38 @@ << "\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 (IsSplit) { + std::optional BaseFragment = std::nullopt; + { + auto R = BaseFragments.find(getAggregateVariable(DbgAssign)); + if (R == BaseFragments.end()) + continue; + BaseFragment = R->second; + } + std::optional CurrentFragment = + Expr->getFragmentInfo(); + std::optional NewFragment = + calculateFragment(OldAllocaOffsetInBits, SliceSizeInBits, + BaseFragment, CurrentFragment); + // Note that std::nullopt here means "skip this fragment" rather than + // "there is no fragment / use the whole variable". + if (!NewFragment) + continue; + + if (!(NewFragment == CurrentFragment)) { + if (CurrentFragment) { + // Rewrite NewFragment to be relative to the existing one (this is + // what createFragmentExpression wants). CalculateFragment has + // already resolved the size for us. FIXME: Should it return the + // relative fragment too? + NewFragment->OffsetInBits -= CurrentFragment->OffsetInBits; + } + + auto E = DIExpression::createFragmentExpression( + Expr, NewFragment->OffsetInBits, NewFragment->SizeInBits); + 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. @@ -2334,7 +2402,6 @@ // original alloca. uint64_t NewBeginOffset = 0, NewEndOffset = 0; - uint64_t RelativeOffset = 0; uint64_t SliceSize = 0; bool IsSplittable = false; bool IsSplit = false; @@ -2408,14 +2475,13 @@ 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); + assert(IsSplit || NewBeginOffset == BeginOffset); OldUse = I->getUse(); OldPtr = cast(OldUse->get()); @@ -2678,8 +2744,8 @@ 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); + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &SI, + Store, Store->getPointerOperand(), OrigV, DL); LLVM_DEBUG(dbgs() << " to: " << *Store << "\n"); return true; } @@ -2703,8 +2769,9 @@ if (AATags) Store->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset)); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &SI, Store, - Store->getPointerOperand(), Store->getValueOperand(), DL); + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &SI, + Store, Store->getPointerOperand(), + Store->getValueOperand(), DL); Pass.DeadInsts.push_back(&SI); LLVM_DEBUG(dbgs() << " to: " << *Store << "\n"); @@ -2782,8 +2849,9 @@ if (NewSI->isAtomic()) NewSI->setAlignment(SI.getAlign()); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &SI, NewSI, - NewSI->getPointerOperand(), NewSI->getValueOperand(), DL); + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &SI, + NewSI, NewSI->getPointerOperand(), + NewSI->getValueOperand(), DL); Pass.DeadInsts.push_back(&SI); deleteIfTriviallyDead(OldOp); @@ -2883,8 +2951,8 @@ if (AATags) New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset)); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New, - New->getRawDest(), nullptr, DL); + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &II, + New, New->getRawDest(), nullptr, DL); LLVM_DEBUG(dbgs() << " to: " << *New << "\n"); return false; @@ -2959,8 +3027,8 @@ if (AATags) New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset)); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New, - New->getPointerOperand(), V, DL); + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &II, + New, New->getPointerOperand(), V, DL); LLVM_DEBUG(dbgs() << " to: " << *New << "\n"); return !II.isVolatile(); @@ -3088,8 +3156,16 @@ if (AATags) New->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset)); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, New, - DestPtr, nullptr, DL); + APInt Offset(DL.getIndexTypeSizeInBits(DestPtr->getType()), 0); + if (IsDest) { + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, + &II, New, DestPtr, nullptr, DL); + } else if (AllocaInst *Base = dyn_cast( + DestPtr->stripAndAccumulateConstantOffsets( + DL, Offset, /*AllowNonInbounds*/ true))) { + migrateDebugInfo(Base, IsSplit, Offset.getZExtValue() * 8, + SliceSize * 8, &II, New, DestPtr, nullptr, DL); + } LLVM_DEBUG(dbgs() << " to: " << *New << "\n"); return false; } @@ -3177,8 +3253,18 @@ if (AATags) Store->setAAMetadata(AATags.shift(NewBeginOffset - BeginOffset)); - migrateDebugInfo(&OldAI, RelativeOffset * 8, SliceSize * 8, &II, Store, - DstPtr, Src, DL); + APInt Offset(DL.getIndexTypeSizeInBits(DstPtr->getType()), 0); + if (IsDest) { + + migrateDebugInfo(&OldAI, IsSplit, NewBeginOffset * 8, SliceSize * 8, &II, + Store, DstPtr, Src, DL); + } else if (AllocaInst *Base = dyn_cast( + DstPtr->stripAndAccumulateConstantOffsets( + DL, Offset, /*AllowNonInbounds*/ true))) { + migrateDebugInfo(Base, IsSplit, Offset.getZExtValue() * 8, SliceSize * 8, + &II, Store, DstPtr, Src, DL); + } + LLVM_DEBUG(dbgs() << " to: " << *Store << "\n"); return !II.isVolatile(); } @@ -3540,23 +3626,22 @@ APInt Offset( DL.getIndexSizeInBits(Ptr->getType()->getPointerAddressSpace()), 0); - if (AATags && - GEPOperator::accumulateConstantOffset(BaseTy, GEPIndices, DL, Offset)) + GEPOperator::accumulateConstantOffset(BaseTy, GEPIndices, DL, Offset); + if (AATags) 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); + Value *Base = AggStore->getPointerOperand()->stripInBoundsOffsets(); if (auto *OldAI = dyn_cast(Base)) { uint64_t SizeInBits = DL.getTypeSizeInBits(Store->getValueOperand()->getType()); - migrateDebugInfo(OldAI, OffsetInBytes.getZExtValue() * 8, SizeInBits, - AggStore, Store, Store->getPointerOperand(), - Store->getValueOperand(), DL); + migrateDebugInfo(OldAI, /*IsSplit*/ true, Offset.getZExtValue() * 8, + SizeInBits, AggStore, Store, + Store->getPointerOperand(), Store->getValueOperand(), + DL); } else { assert(at::getAssignmentMarkers(Store).empty() && "AT: unexpected debug.assign linked to store through " @@ -4890,8 +4975,13 @@ NumPromoted += PromotableAllocas.size(); - LLVM_DEBUG(dbgs() << "Promoting allocas with mem2reg...\n"); - PromoteMemToReg(PromotableAllocas, DTU->getDomTree(), AC); + if (SROASkipMem2Reg) { + LLVM_DEBUG(dbgs() << "Not promoting allocas with mem2reg!\n"); + } else { + LLVM_DEBUG(dbgs() << "Promoting allocas with mem2reg...\n"); + PromoteMemToReg(PromotableAllocas, DTU->getDomTree(), AC); + } + PromotableAllocas.clear(); return true; } diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store-2.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store-2.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store-2.ll @@ -0,0 +1,104 @@ +; RUN: opt -S -passes=sroa -sroa-skip-mem2reg %s \ +; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; NOTE: This is the same as split-pre-fragmented-store.ll except the base +;; alloca's dbg.assign has been altered to contain a fragment of the full +;; variable - the variable's size has been modified from 64 to 96 bits. +;; This version of the test ensures that the behaviour being tested is still +;; correct when the base alloca doesn't hold an entire variable. + +;; IR hand-modified, originally generated from: +;; struct Pair { int a; int b; }; +;; Pair getVar(); +;; int fun() { +;; Pair var; +;; var = getVar(); +;; return var.b; +;; } +;; Modification: split the dbg.assign linked the the memcpy(64 bits) into two, +;; each describing a 32 bit fragment. +;; +;; Check that assignment tracking updates in SROA work when the store being +;; split is described with one dbg.assign (covering different fragments). The +;; store may have been already split and then merged again at some point. + +;; Alloca for var.a and associated dbg.assign: +; CHECK: %var.sroa.0 = alloca i32, align 4, !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, 32, 32), metadata ![[id_1]], metadata ptr %var.sroa.0, metadata !DIExpression()) + +;; Alloca for var.b and associated dbg.assign: +; CHECK-NEXT: %var.sroa.1 = alloca i32, align 4, !DIAssignID ![[id_2:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i1 undef, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata ![[id_2]], metadata ptr %var.sroa.1, metadata !DIExpression()) + +;; Store to var.b (split from store to var) and associated dbg.assigns. The +;; dbg.assign for the fragment covering the (pre-split) assignment to var.a +;; should not be linked to the store. +; CHECK: store i32 %[[v:.*]], ptr %var.sroa.1,{{.*}}!DIAssignID ![[id_3:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %{{.*var\.sroa\.0.*}}, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata ![[id_4:[0-9]+]], metadata ptr %var.sroa.0, metadata !DIExpression()) +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %[[v]], metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata ![[id_3]], metadata ptr %var.sroa.1, metadata !DIExpression()) + +; CHECK-DAG: ![[id_1]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_2]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_3]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_4]] = distinct !DIAssignID() + +%struct.Tuple = type { i32, i32, i32 } + +define dso_local noundef i32 @_Z3funv() !dbg !9 { +entry: + %var = alloca [2 x i32], align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 64), metadata !19, metadata ptr %var, metadata !DIExpression()), !dbg !20 + %ref.tmp = alloca %struct.Tuple, align 4 + %call = call i64 @_Z6getVarv(), !dbg !22 + store i64 %call, ptr %ref.tmp, align 4, !dbg !22 + call void @llvm.memcpy.p0.p0.i64(ptr align 4 %var, ptr align 4 %ref.tmp, i64 8, i1 false), !dbg !23, !DIAssignID !29 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !29, metadata ptr %var, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !29, metadata ptr %var, metadata !DIExpression(DW_OP_plus, DW_OP_constu, 4)), !dbg !20 + %b = getelementptr inbounds %struct.Tuple, ptr %var, i32 0, i32 1, !dbg !31 + %0 = load i32, ptr %b, align 4, !dbg !31 + ret i32 %0, !dbg !35 +} + +declare !dbg !36 i64 @_Z6getVarv() +declare void @llvm.dbg.declare(metadata, metadata, metadata) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!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: "fun", linkageName: "_Z3funv", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !13) +!10 = !DISubroutineType(types: !11) +!11 = !{!12} +!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!13 = !{!14} +!14 = !DILocalVariable(name: "var", scope: !9, file: !1, line: 4, type: !15) +!15 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Tuple", file: !1, line: 1, size: 96, flags: DIFlagTypePassByValue, elements: !16, identifier: "_ZTS4Pair") +!16 = !{!17, !18, !40} +!17 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !15, file: !1, line: 1, baseType: !12, size: 32) +!18 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !15, file: !1, line: 1, baseType: !12, size: 32, offset: 32) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !9) +!21 = !DILocation(line: 4, column: 3, scope: !9) +!22 = !DILocation(line: 5, column: 9, scope: !9) +!23 = !DILocation(line: 5, column: 7, scope: !9) +!29 = distinct !DIAssignID() +!30 = !DILocation(line: 5, column: 3, scope: !9) +!31 = !DILocation(line: 6, column: 14, scope: !9) +!34 = !DILocation(line: 7, column: 1, scope: !9) +!35 = !DILocation(line: 6, column: 3, scope: !9) +!36 = !DISubprogram(name: "getVar", linkageName: "_Z6getVarv", scope: !1, file: !1, line: 2, type: !37, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !39) +!37 = !DISubroutineType(types: !38) +!38 = !{!15} +!39 = !{} +!40 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !15, file: !1, line: 1, baseType: !12, size: 32, offset: 64) diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/sroa/split-pre-fragmented-store.ll @@ -0,0 +1,97 @@ +; RUN: opt -S -passes=sroa -sroa-skip-mem2reg %s \ +; RUN: | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; IR hand-modified, originally generated from: +;; struct Pair { int a; int b; }; +;; Pair getVar(); +;; int fun() { +;; Pair var; +;; var = getVar(); +;; return var.b; +;; } +;; Modification: split the dbg.assign linked the the memcpy(64 bits) into two, +;; each describing a 32 bit fragment. +;; +;; Check that assignment tracking updates in SROA work when the store being +;; split is described with one dbg.assign (covering different fragments). The +;; store may have been already split and then merged again at some point. + +;; Alloca for var.a and associated dbg.assign: +; CHECK: %var.sroa.0 = alloca i32, align 4, !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, 32), metadata ![[id_1]], metadata ptr %var.sroa.0, metadata !DIExpression()) + +;; Alloca for var.b and associated dbg.assign: +; CHECK-NEXT: %var.sroa.1 = alloca i32, align 4, !DIAssignID ![[id_2:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i1 undef, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata ![[id_2]], metadata ptr %var.sroa.1, metadata !DIExpression()) + +;; Store to var.b (split from store to var) and associated dbg.assigns. The +;; dbg.assign for the fragment covering the (pre-split) assignment to var.a +;; should not be linked to the store. +; CHECK: store i32 %[[v:.*]], ptr %var.sroa.1,{{.*}}!DIAssignID ![[id_3:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %{{.*var\.sroa\.0.*}}, metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata ![[id_4:[0-9]+]], metadata ptr %var.sroa.0, metadata !DIExpression()) +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i32 %[[v]], metadata ![[var]], metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata ![[id_3]], metadata ptr %var.sroa.1, metadata !DIExpression()) + +; CHECK-DAG: ![[id_1]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_2]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_3]] = distinct !DIAssignID() +; CHECK-DAG: ![[id_4]] = distinct !DIAssignID() + +%struct.Pair = type { i32, i32 } + +define dso_local noundef i32 @_Z3funv() !dbg !9 { +entry: + %var = alloca %struct.Pair, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(), metadata !19, metadata ptr %var, metadata !DIExpression()), !dbg !20 + %ref.tmp = alloca %struct.Pair, align 4 + %call = call i64 @_Z6getVarv(), !dbg !22 + store i64 %call, ptr %ref.tmp, align 4, !dbg !22 + call void @llvm.memcpy.p0.p0.i64(ptr align 4 %var, ptr align 4 %ref.tmp, i64 8, i1 false), !dbg !23, !DIAssignID !29 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !29, metadata ptr %var, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !29, metadata ptr %var, metadata !DIExpression(DW_OP_plus, DW_OP_constu, 4)), !dbg !20 + %b = getelementptr inbounds %struct.Pair, ptr %var, i32 0, i32 1, !dbg !31 + %0 = load i32, ptr %b, align 4, !dbg !31 + ret i32 %0, !dbg !35 +} + +declare !dbg !36 i64 @_Z6getVarv() +declare void @llvm.dbg.declare(metadata, metadata, metadata) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!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: "fun", linkageName: "_Z3funv", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !13) +!10 = !DISubroutineType(types: !11) +!11 = !{!12} +!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!13 = !{!14} +!14 = !DILocalVariable(name: "var", scope: !9, file: !1, line: 4, type: !15) +!15 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !1, line: 1, size: 64, flags: DIFlagTypePassByValue, elements: !16, identifier: "_ZTS4Pair") +!16 = !{!17, !18} +!17 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !15, file: !1, line: 1, baseType: !12, size: 32) +!18 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !15, file: !1, line: 1, baseType: !12, size: 32, offset: 32) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !9) +!21 = !DILocation(line: 4, column: 3, scope: !9) +!22 = !DILocation(line: 5, column: 9, scope: !9) +!23 = !DILocation(line: 5, column: 7, scope: !9) +!29 = distinct !DIAssignID() +!30 = !DILocation(line: 5, column: 3, scope: !9) +!31 = !DILocation(line: 6, column: 14, scope: !9) +!34 = !DILocation(line: 7, column: 1, scope: !9) +!35 = !DILocation(line: 6, column: 3, scope: !9) +!36 = !DISubprogram(name: "getVar", linkageName: "_Z6getVarv", scope: !1, file: !1, line: 2, type: !37, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !39) +!37 = !DISubroutineType(types: !38) +!38 = !{!15} +!39 = !{}