From fca3e8e024f0015604d21e6f76f3e199345679c5 Mon Sep 17 00:00:00 2001 From: OCHyams Date: Wed, 19 Apr 2023 08:43:02 +0100 Subject: [PATCH] [Assignment Tracking] Fix fragment error for some DSE-shortened stores `shortenAssignment` inserts dbg.assigns with fragments describing the dead part of a shortened store after each dbg.assign linked to the store. Without this patch it doesn't take into account that the dead part of a shortened store may be outside the bounds of a variable of a linked dbg.assign. It also doesn't correctly account for a non-zero offset in the address modifying `DIExpression` of the dbg.assign (which is possible for fragments now even though whole variables currently cannot have a non-zero offset in their alloca). Fix this by moving the dead slice into variable-space and performing an intersect of that adjusted slice with the existing fragment. This fixes a verifier error reported when building fuchsia with assignment tracking enabled: https://ci.chromium.org/ui/p/fuchsia/builders/ci/ clang_toolchain.ci.core.x64-release/b8784000953022145169/overview Reviewed By: jmorse Differential Revision: https://reviews.llvm.org/D148536 --- llvm/include/llvm/IR/DebugInfo.h | 14 +++ llvm/include/llvm/IR/DebugInfoMetadata.h | 10 ++ llvm/include/llvm/IR/IntrinsicInst.h | 13 ++ llvm/lib/IR/DebugInfo.cpp | 135 +++++++++++++++++++++ .../lib/Transforms/Scalar/DeadStoreElimination.cpp | 80 ++++++++---- llvm/lib/Transforms/Scalar/SROA.cpp | 1 + .../dse/dse-after-memcpyopt-merge.ll | 28 ++--- .../assignment-tracking/dse/shorten-offset.ll | 134 ++++++++++++++++++++ 8 files changed, 373 insertions(+), 42 deletions(-) create mode 100644 llvm/test/DebugInfo/Generic/assignment-tracking/dse/shorten-offset.ll diff --git a/llvm/include/llvm/IR/DebugInfo.h b/llvm/include/llvm/IR/DebugInfo.h index e717407..26a7cfb 100644 --- a/llvm/include/llvm/IR/DebugInfo.h +++ b/llvm/include/llvm/IR/DebugInfo.h @@ -224,6 +224,20 @@ void RAUW(DIAssignID *Old, DIAssignID *New); /// Remove all Assignment Tracking related intrinsics and metadata from \p F. void deleteAll(Function *F); +/// Calculate the fragment of the variable in \p DAI covered +/// from (Dest + SliceOffsetInBits) to +/// to (Dest + SliceOffsetInBits + SliceSizeInBits) +/// +/// Return false if it can't be calculated for any reason. +/// Result is set to nullopt if the intersect equals the variable fragment (or +/// variable size) in DAI. +/// +/// Result contains a zero-sized fragment if there's no intersect. +bool calculateFragmentIntersect( + const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits, + uint64_t SliceSizeInBits, const DbgAssignIntrinsic *DAI, + std::optional &Result); + /// Helper struct for trackAssignments, below. We don't use the similar /// DebugVariable class because trackAssignments doesn't (yet?) understand /// partial variables (fragment info) as input and want to make that clear and diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h index ebf011c..21d2904 100644 --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -2796,6 +2796,16 @@ public: /// 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; } + + /// Returns a zero-sized fragment if A and B don't intersect. + static DIExpression::FragmentInfo intersect(DIExpression::FragmentInfo A, + DIExpression::FragmentInfo B) { + uint64_t StartInBits = std::max(A.OffsetInBits, B.OffsetInBits); + uint64_t EndInBits = std::min(A.endInBits(), B.endInBits()); + if (EndInBits <= StartInBits) + return {0, 0}; + return DIExpression::FragmentInfo(EndInBits - StartInBits, StartInBits); + } }; /// Retrieve the details of this fragment expression. diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 548d88e..9b349c8 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -376,6 +376,19 @@ public: return getExpression()->getFragmentInfo(); } + /// Get the FragmentInfo for the variable if it exists, otherwise return a + /// FragmentInfo that covers the entire variable if the variable size is + /// known, otherwise return a zero-sized fragment. + DIExpression::FragmentInfo getFragmentOrEntireVariable() const { + DIExpression::FragmentInfo VariableSlice(0, 0); + // Get the fragment or variable size, or zero. + if (auto Sz = getFragmentSizeInBits()) + VariableSlice.SizeInBits = *Sz; + if (auto Frag = getExpression()->getFragmentInfo()) + VariableSlice.OffsetInBits = Frag->OffsetInBits; + return VariableSlice; + } + /// \name Casting methods /// @{ static bool classof(const IntrinsicInst *I) { diff --git a/llvm/lib/IR/DebugInfo.cpp b/llvm/lib/IR/DebugInfo.cpp index 3206b23..d3c965b 100644 --- a/llvm/lib/IR/DebugInfo.cpp +++ b/llvm/lib/IR/DebugInfo.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DIBuilder.h" @@ -1793,6 +1794,140 @@ void at::deleteAll(Function *F) { DAI->eraseFromParent(); } +bool at::calculateFragmentIntersect( + const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits, + uint64_t SliceSizeInBits, const DbgAssignIntrinsic *DAI, + std::optional &Result) { + // There are multiple offsets at play in this function, so let's break it + // down. Starting with how variables may be stored in allocas: + // + // 1 Simplest case: variable is alloca sized and starts at offset 0. + // 2 Variable is larger than the alloca: the alloca holds just a part of it. + // 3 Variable is smaller than the alloca: the alloca may hold multiple + // variables. + // + // Imagine we have a store to the entire alloca. In case (3) the store + // affects bits outside of the bounds of each variable. In case (2), where + // the alloca holds the Xth bit to the Yth bit of a variable, the + // zero-offset store doesn't represent an assignment at offset zero to the + // variable. It is an assignment to offset X. + // + // # Example 1 + // Obviously, not all stores are alloca-sized and have zero offset. Imagine + // the lower 32 bits of this store are dead and are going to be DSEd: + // + // store i64 %v, ptr %dest, !DIAssignID !1 + // dbg.assign(..., !DIExpression(fragment, 128, 32), !1, %dest, + // !DIExpression(DW_OP_plus_uconst, 4)) + // + // Goal: Given our dead bits at offset:0 size:32 for the store, determine the + // part of the variable, which fits in the fragment expressed by the + // dbg.assign, that has been killed, if any. + // + // calculateFragmentIntersect(..., SliceOffsetInBits=0, + // SliceSizeInBits=32, Dest=%dest, DAI=dbg.assign) + // + // Drawing the store (s) in memory followed by the shortened version ($), + // then the dbg.assign (d), with the fragment information on a seperate scale + // underneath: + // + // Memory + // offset + // from + // dest 0 63 + // | | + // s[######] - Original stores 64 bits to Dest. + // $----[##] - DSE says the lower 32 bits are dead, to be removed. + // d [##] - DAI's address-modifying expression adds 4 bytes to dest. + // Variable | | + // Fragment 128| + // Offsets 159 + // + // The answer is achieved in a few steps: + // 1. Add the fragment offset to the store offset: + // SliceOffsetInBits:0 + VarFrag.OffsetInBits:128 = 128 + // + // 2. Subtract the address-modifying expression offset plus difference + // between d.address and dest: + // 128 - (expression_offset:32 + (d.address - dest):0) = 96 + // + // 3. That offset along with the store size (32) represents the bits of the + // variable that'd be affected by the store. Call it SliceOfVariable. + // Intersect that with DAI's fragment info: + // SliceOfVariable ∩ DAI_fragment = none + // + // In this case: none of the dead bits of the store affect DAI. + // + // # Example 2 + // Similar example with the same goal. This time the upper 16 bits + // of the store are going to be DSE'd. + // + // store i64 %v, ptr %dest, !DIAssignID !1 + // dbg.assign(..., !DIExpression(fragment, 128, 32), !1, %dest, + // !DIExpression(DW_OP_plus_uconst, 4)) + // + // calculateFragmentIntersect(..., SliceOffsetInBits=48, + // SliceSizeInBits=16, Dest=%dest, DAI=dbg.assign) + // + // Memory + // offset + // from + // dest 0 63 + // | | + // s[######] - Original stores 64 bits to Dest. + // $[####]-- - DSE says the upper 16 bits are dead, to be removed. + // d [##] - DAI's address-modifying expression adds 4 bytes to dest. + // Variable | | + // Fragment 128| + // Offsets 159 + // + // Using the same steps in the first example: + // 1. SliceOffsetInBits:48 + VarFrag.OffsetInBits:128 = 176 + // 2. 176 - (expression_offset:32 + (d.address - dest):0) = 144 + // 3. SliceOfVariable offset = 144, size = 16: + // SliceOfVariable ∩ DAI_fragment = (offset: 144, size: 16) + // SliceOfVariable tells us the bits of the variable described by DAI that are + // affected by the DSE. + if (DAI->isKillAddress()) + return false; + + DIExpression::FragmentInfo VarFrag = DAI->getFragmentOrEntireVariable(); + if (VarFrag.SizeInBits == 0) + return false; // Variable size is unknown. + + // Calculate the difference between Dest and the dbg.assign address + + // address-modifying expression. + int64_t PointerOffsetInBits; + { + auto DestOffsetInBytes = llvm::isPointerOffset(Dest, DAI->getAddress(), DL); + if (!DestOffsetInBytes) + return false; // Can't calculate difference in addresses. + + int64_t ExprOffsetInBytes; + if (!DAI->getAddressExpression()->extractIfOffset(ExprOffsetInBytes)) + return false; + + int64_t PointerOffsetInBytes = *DestOffsetInBytes + ExprOffsetInBytes; + PointerOffsetInBits = PointerOffsetInBytes * 8; + } + + // Adjust the slice offset so that we go from describing the a slice + // of memory to a slice of the variable. + int64_t NewOffsetInBits = + SliceOffsetInBits + VarFrag.OffsetInBits - PointerOffsetInBits; + if (NewOffsetInBits < 0) + return false; // Fragment offsets can only be positive. + DIExpression::FragmentInfo SliceOfVariable(SliceSizeInBits, NewOffsetInBits); + // Intersect the variable slice with DAI's fragment to trim it down to size. + DIExpression::FragmentInfo TrimmedSliceOfVariable = + DIExpression::FragmentInfo::intersect(SliceOfVariable, VarFrag); + if (TrimmedSliceOfVariable == VarFrag) + Result = std::nullopt; + else + Result = TrimmedSliceOfVariable; + return true; +} + /// Collect constant properies (base, size, offset) of \p StoreDest. /// Return std::nullopt if any properties are not constants. static std::optional diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp index 56ea241..d0c249f 100644 --- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp +++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp @@ -482,41 +482,75 @@ memoryIsNotModifiedBetween(Instruction *FirstI, Instruction *SecondI, return true; } -static void shortenAssignment(Instruction *Inst, uint64_t OldOffsetInBits, - uint64_t OldSizeInBits, uint64_t NewSizeInBits, - bool IsOverwriteEnd) { - DIExpression::FragmentInfo DeadFragment; - DeadFragment.SizeInBits = OldSizeInBits - NewSizeInBits; - DeadFragment.OffsetInBits = +static void shortenAssignment(Instruction *Inst, Value *OriginalDest, + uint64_t OldOffsetInBits, uint64_t OldSizeInBits, + uint64_t NewSizeInBits, bool IsOverwriteEnd) { + const DataLayout &DL = Inst->getModule()->getDataLayout(); + uint64_t DeadSliceSizeInBits = OldSizeInBits - NewSizeInBits; + uint64_t DeadSliceOffsetInBits = OldOffsetInBits + (IsOverwriteEnd ? NewSizeInBits : 0); - - auto CreateDeadFragExpr = [Inst, DeadFragment]() { - // FIXME: This should be using the DIExpression in the Alloca's dbg.assign - // for the variable, since that could also contain a fragment? - return *DIExpression::createFragmentExpression( - DIExpression::get(Inst->getContext(), std::nullopt), + auto SetDeadFragExpr = [](DbgAssignIntrinsic *DAI, + DIExpression::FragmentInfo DeadFragment) { + // createFragmentExpression expects an offset relative to the existing + // fragment offset if there is one. + uint64_t RelativeOffset = DeadFragment.OffsetInBits - + DAI->getExpression() + ->getFragmentInfo() + .value_or(DIExpression::FragmentInfo(0, 0)) + .OffsetInBits; + if (auto NewExpr = DIExpression::createFragmentExpression( + DAI->getExpression(), RelativeOffset, DeadFragment.SizeInBits)) { + DAI->setExpression(*NewExpr); + return; + } + // Failed to create a fragment expression for this so discard the value, + // making this a kill location. + auto *Expr = *DIExpression::createFragmentExpression( + DIExpression::get(DAI->getContext(), std::nullopt), DeadFragment.OffsetInBits, DeadFragment.SizeInBits); + DAI->setExpression(Expr); + DAI->setKillLocation(); }; // A DIAssignID to use so that the inserted dbg.assign intrinsics do not // link to any instructions. Created in the loop below (once). DIAssignID *LinkToNothing = nullptr; + LLVMContext &Ctx = Inst->getContext(); + auto GetDeadLink = [&Ctx, &LinkToNothing]() { + if (!LinkToNothing) + LinkToNothing = DIAssignID::getDistinct(Ctx); + return LinkToNothing; + }; // Insert an unlinked dbg.assign intrinsic for the dead fragment after each - // overlapping dbg.assign intrinsic. - for (auto *DAI : at::getAssignmentMarkers(Inst)) { - if (auto FragInfo = DAI->getExpression()->getFragmentInfo()) { - if (!DIExpression::fragmentsOverlap(*FragInfo, DeadFragment)) - continue; + // overlapping dbg.assign intrinsic. The loop invalidates the iterators + // returned by getAssignmentMarkers so save a copy of the markers to iterate + // over. + auto LinkedRange = at::getAssignmentMarkers(Inst); + SmallVector Linked(LinkedRange.begin(), + LinkedRange.end()); + for (auto *DAI : Linked) { + std::optional NewFragment; + if (!at::calculateFragmentIntersect(DL, OriginalDest, DeadSliceOffsetInBits, + DeadSliceSizeInBits, DAI, + NewFragment) || + !NewFragment) { + // We couldn't calculate the intersecting fragment for some reason. Be + // cautious and unlink the whole assignment from the store. + DAI->setKillAddress(); + DAI->setAssignId(GetDeadLink()); + continue; } + // No intersect. + if (NewFragment->SizeInBits == 0) + continue; // Fragments overlap: insert a new dbg.assign for this dead part. auto *NewAssign = cast(DAI->clone()); NewAssign->insertAfter(DAI); - if (!LinkToNothing) - LinkToNothing = DIAssignID::getDistinct(Inst->getContext()); - NewAssign->setAssignId(LinkToNothing); - NewAssign->setExpression(CreateDeadFragExpr()); + NewAssign->setAssignId(GetDeadLink()); + if (NewFragment) + SetDeadFragExpr(NewAssign, *NewFragment); NewAssign->setKillAddress(); } } @@ -593,8 +627,8 @@ static bool tryToShorten(Instruction *DeadI, int64_t &DeadStart, DeadIntrinsic->setLength(TrimmedLength); DeadIntrinsic->setDestAlignment(PrefAlign); + Value *OrigDest = DeadIntrinsic->getRawDest(); if (!IsOverwriteEnd) { - Value *OrigDest = DeadIntrinsic->getRawDest(); Type *Int8PtrTy = Type::getInt8PtrTy(DeadIntrinsic->getContext(), OrigDest->getType()->getPointerAddressSpace()); @@ -613,7 +647,7 @@ static bool tryToShorten(Instruction *DeadI, int64_t &DeadStart, } // Update attached dbg.assign intrinsics. Assume 8-bit byte. - shortenAssignment(DeadI, DeadStart * 8, DeadSize * 8, NewSize * 8, + shortenAssignment(DeadI, OrigDest, DeadStart * 8, DeadSize * 8, NewSize * 8, IsOverwriteEnd); // Finally update start and size of dead access. diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp index d5615c9..db518a4 100644 --- a/llvm/lib/Transforms/Scalar/SROA.cpp +++ b/llvm/lib/Transforms/Scalar/SROA.cpp @@ -130,6 +130,7 @@ namespace { /// UseFrag - Use Target as the new fragment. /// UseNoFrag - The new slice already covers the whole variable. /// Skip - The new alloca slice doesn't include this variable. +/// FIXME: Can we use calculateFragmentIntersect instead? enum FragCalcResult { UseFrag, UseNoFrag, Skip }; static FragCalcResult calculateFragment(DILocalVariable *Variable, diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/dse/dse-after-memcpyopt-merge.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/dse/dse-after-memcpyopt-merge.ll index 0d6f6fd..f1704f4 100644 --- a/llvm/test/DebugInfo/Generic/assignment-tracking/dse/dse-after-memcpyopt-merge.ll +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/dse/dse-after-memcpyopt-merge.ll @@ -1,4 +1,4 @@ -; RUN: opt %s -S -passes=dse -o - | FileCheck %s +; RUN: opt %s -S -passes=dse -o - | FileCheck %s --implicit-check-not="call void @llvm.dbg" ;; Observed in the wild, but test is created by running memcpyopt on ;; assignment-tracking/memcpyopt/merge-stores.ll then manually inserting @@ -7,19 +7,17 @@ ;; A memory intrinsic (or vector instruction) might have multiple dbg.assigns ;; linked to it if it has been created as a result of merging scalar stores, ;; such as in this example. DSE is going to shorten the memset because there's -;; a later store that overwrites part of it. Insert a linked dbg.assign after -;; each overlapping fragment to undef the dead part's memory location without -;; needing to work out how to split the fragments exactly. +;; a later store that overwrites part of it. Unlink the dbg.assigns that +;; describe the overlapping fragments. ;; Check that there's an unlinked dbg.assign inserted after each overlapping ;; fragment of the shortened store. ;; -; CHECK: call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata ![[ID:[0-9]+]], metadata ptr %arrayidx5.i, metadata !DIExpression()) -; CHECK-NEXT: call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata ![[UniqueID1:[0-9]+]], metadata ptr undef, metadata !DIExpression()) - -; CHECK: call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata ![[ID]], metadata ptr %arrayidx7.i, metadata !DIExpression()) -; CHECK-NEXT: call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata ![[UniqueID2:[0-9]+]], metadata ptr undef, metadata !DIExpression()) - +; CHECK: llvm.dbg.assign({{.*}}, metadata ptr %g, metadata !DIExpression()) +; CHECK: llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[#]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata ![[ID:[0-9]+]], metadata ptr %arrayidx.i, metadata !DIExpression()) +; CHECK: llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[#]], metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata ![[ID]], metadata ptr %arrayidx3.i, metadata !DIExpression()) +; CHECK: llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[#]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata ![[UniqueID1:[0-9]+]], metadata ptr undef, metadata !DIExpression()) +; CHECK: llvm.dbg.assign(metadata float 0.000000e+00, metadata ![[#]], metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata ![[UniqueID2:[0-9]+]], metadata ptr undef, metadata !DIExpression()) ; CHECK: call void @llvm.memset{{.*}}, !DIAssignID ![[ID]] ; CHECK-DAG: ![[ID]] = distinct !DIAssignID() @@ -34,10 +32,7 @@ define dso_local void @_Z1fv() local_unnamed_addr !dbg !7 { entry: %g = alloca %struct.v, align 4, !DIAssignID !23 call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !23, metadata ptr %g, metadata !DIExpression()), !dbg !24 - call void @llvm.lifetime.start.p0i8(i64 16, ptr nonnull %g), !dbg !25 - call void @llvm.dbg.assign(metadata ptr %g, metadata !26, metadata !DIExpression(), metadata !35, metadata ptr undef, metadata !DIExpression()), !dbg !32 - call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata !29, metadata !DIExpression(), metadata !36, metadata ptr undef, metadata !DIExpression()), !dbg !32 - %arrayidx.i = getelementptr inbounds %struct.v, ptr %g, i64 0, i32 0, i64 2, !dbg !37 + %arrayidx.i = getelementptr inbounds %struct.v, ptr %g, i64 0, i32 0, i64 2, !dbg !37 call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !39, metadata ptr %arrayidx.i, metadata !DIExpression()), !dbg !24 %arrayidx3.i = getelementptr inbounds %struct.v, ptr %g, i64 0, i32 0, i64 1, !dbg !40 call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !39, metadata ptr %arrayidx3.i, metadata !DIExpression()), !dbg !24 @@ -50,19 +45,14 @@ entry: ;; -- Start modification %arrayidx7 = getelementptr inbounds %struct.v, ptr %g, i64 0, i32 0, i64 3, !dbg !24 store float 0.000000e+00, ptr %arrayidx7, align 4, !dbg !24, !DIAssignID !49 - call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 96, 32), metadata !49, metadata ptr %arrayidx7, metadata !DIExpression()), !dbg !24 %arrayidx = getelementptr inbounds %struct.v, ptr %g, i64 0, i32 0, i64 0, !dbg !24 store float 0.000000e+00, ptr %arrayidx, align 4, !dbg !24, !DIAssignID !50 - call void @llvm.dbg.assign(metadata float 0.000000e+00, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !50, metadata ptr %arrayidx, metadata !DIExpression()), !dbg !24 ;; -- End modification call void @_Z3escP1v(ptr nonnull %g), !dbg !43 - call void @llvm.lifetime.end.p0i8(i64 16, ptr nonnull %0), !dbg !45 ret void, !dbg !45 } -declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) declare !dbg !64 dso_local void @_Z3escP1v(ptr) local_unnamed_addr -declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) declare void @llvm.memset.p0i8.i64(ptr nocapture writeonly, i8, i64, i1 immarg) diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/dse/shorten-offset.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/dse/shorten-offset.ll new file mode 100644 index 0000000..b6dc56a --- /dev/null +++ b/llvm/test/DebugInfo/Generic/assignment-tracking/dse/shorten-offset.ll @@ -0,0 +1,134 @@ +; RUN: opt %s -S -passes=dse -o - | FileCheck %s --implicit-check-not="call void @llvm.dbg" + +;; Based on the test shorten.ll with some adjustments. +;; +;; $ cat test.cpp +;; void esc(char*); +;; void shortenEnd() { +;; char local[80]; // bits frag +;; __builtin_memset(local + 8, 0, 24); // local: 64-160 ( 64, 96) +;; __builtin_memset(local + 16, 8, 40); // local: 128-160 (128, 32) +;; esc(local); +;; } +;; void shortenStart() { +;; char local2[40]; // bits frag +;; __builtin_memset(local2, 0, 40); // local2: 0-160 (0, 160) +;; __builtin_memset(local2, 8, 16); // local2: 0-128 (0, 128) +;; esc(local2); +;; } + +;; The variables and intrinsics have been adjusted with by hand to test +;; what happens when the variable doesn't fill the whole alloca, and +;; when offsets are encoded with both the address component of the dbg.assign +;; and the address modifying DIExpression. + +;; DeadStoreElimination will shorten the first store in shortenEnd from [64, +;; 192) bits to [64, 128) bits. Variable 'local' has been adjusted to be 160 +;; bits large. Check that we get an unlinked dbg.assign covering the deleted +;; bits that overlap the dbg.assign's fagment: [128, 160) (offset=128 size=32). + +; CHECK: @_Z10shortenEndv +; CHECK: call void @llvm.dbg.assign({{.*}}, metadata ptr %local, metadata !DIExpression()) +; CHECK: call void @llvm.memset{{.*}}, !DIAssignID ![[ID:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 96), metadata ![[ID:[0-9]+]], metadata ptr %offset_4_bytes, metadata !DIExpression(DW_OP_plus_uconst, 4)) +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 32), metadata ![[UniqueID1:[0-9]+]], metadata ptr undef, metadata !DIExpression({{.*}})) + +;; DSE will shorten the first store in shortenStart from [0, 160) bits to [128, +;; 160) bits. Variable 'local2' has been adjusted to be 160 bits. Check we get +;; an unlinked dbg.assign covering the deleted bits that overlap the +;; dbg.assign's fragment (no fragment in this case, i.e. the whole variable): +;; [0, 128) (offset=0, size=128). + +; CHECK: @_Z12shortenStartv +; CHECK: call void @llvm.dbg.assign({{.*}}, metadata ptr %local2, metadata !DIExpression()) +; CHECK: call void @llvm.memset{{.*}}, !DIAssignID ![[ID2:[0-9]+]] +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR2:[0-9]+]], metadata !DIExpression(), metadata ![[ID2]], metadata ptr %local2, metadata !DIExpression()) +; CHECK-NEXT: call void @llvm.dbg.assign(metadata i8 0, metadata ![[VAR2]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 128), metadata ![[UniqueID2:[0-9]+]], metadata ptr undef, metadata !DIExpression()) + +; CHECK-DAG: ![[ID]] = distinct !DIAssignID() +; CHECK-DAG: ![[UniqueID1]] = distinct !DIAssignID() +; CHECK-DAG: ![[UniqueID2]] = distinct !DIAssignID() + +define dso_local void @_Z10shortenEndv() local_unnamed_addr #0 !dbg !7 { +entry: + %local = alloca [80 x i8], align 16, !DIAssignID !16 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !16, metadata ptr %local, metadata !DIExpression()), !dbg !17 + %arraydecay = getelementptr inbounds [80 x i8], ptr %local, i64 0, i64 0, !dbg !19 + %offset_4_bytes = getelementptr inbounds [80 x i8], ptr %local, i64 0, i64 4, !dbg !21 + %offset_8_bytes = getelementptr inbounds [80 x i8], ptr %local, i64 0, i64 8, !dbg !21 + call void @llvm.memset.p0i8.i64(ptr noundef nonnull align 16 dereferenceable(24) %offset_8_bytes, i8 0, i64 72, i1 false), !dbg !19, !DIAssignID !20 + call void @llvm.dbg.assign(metadata i8 0, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 96), metadata !20, metadata ptr %offset_4_bytes, metadata !DIExpression(DW_OP_plus_uconst, 4)), !dbg !17 + %offset_16_bytes = getelementptr inbounds [80 x i8], ptr %local, i64 0, i64 4, !dbg !21 + call void @llvm.memset.p0i8.i64(ptr noundef nonnull align 16 dereferenceable(40) %offset_16_bytes, i8 8, i64 64, i1 false), !dbg !22, !DIAssignID !23 + call void @_Z3escPi(ptr noundef nonnull %arraydecay), !dbg !24 + ret void, !dbg !25 +} + +declare void @llvm.memset.p0i8.i64(ptr nocapture writeonly, i8, i64, i1 immarg) +declare !dbg !26 dso_local void @_Z3escPi(ptr noundef) local_unnamed_addr + +define dso_local void @_Z12shortenStartv() local_unnamed_addr #0 !dbg !31 { +entry: + %local2 = alloca [40 x i8], align 16, !DIAssignID !37 + call void @llvm.dbg.assign(metadata i1 undef, metadata !33, metadata !DIExpression(), metadata !37, metadata ptr %local2, metadata !DIExpression()), !dbg !38 + %arraydecay = getelementptr inbounds [40 x i8], ptr %local2, i64 0, i64 0, !dbg !40 + call void @llvm.memset.p0i8.i64(ptr noundef nonnull align 16 dereferenceable(40) %local2, i8 0, i64 36, i1 false), !dbg !40, !DIAssignID !41 + call void @llvm.dbg.assign(metadata i8 0, metadata !33, metadata !DIExpression(), metadata !41, metadata ptr %local2, metadata !DIExpression()), !dbg !38 + call void @llvm.memset.p0i8.i64(ptr noundef nonnull align 16 dereferenceable(16) %local2, i8 8, i64 16, i1 false), !dbg !42, !DIAssignID !43 + call void @_Z3escPi(ptr noundef nonnull %arraydecay), !dbg !44 + ret void, !dbg !45 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !1000} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "shortenEnd", linkageName: "_Z10shortenEndv", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 3, type: !12) +!12 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 160, elements: !14) +!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!14 = !{!15} +!15 = !DISubrange(count: 5) +!16 = distinct !DIAssignID() +!17 = !DILocation(line: 0, scope: !7) +!18 = !DILocation(line: 3, column: 3, scope: !7) +!19 = !DILocation(line: 4, column: 3, scope: !7) +!20 = distinct !DIAssignID() +!21 = !DILocation(line: 5, column: 26, scope: !7) +!22 = !DILocation(line: 5, column: 3, scope: !7) +!23 = distinct !DIAssignID() +!24 = !DILocation(line: 6, column: 3, scope: !7) +!25 = !DILocation(line: 7, column: 1, scope: !7) +!26 = !DISubprogram(name: "esc", linkageName: "_Z3escPi", scope: !1, file: !1, line: 1, type: !27, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !30) +!27 = !DISubroutineType(types: !28) +!28 = !{null, !29} +!29 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64) +!30 = !{} +!31 = distinct !DISubprogram(name: "shortenStart", linkageName: "_Z12shortenStartv", scope: !1, file: !1, line: 8, type: !8, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !32) +!32 = !{!33} +!33 = !DILocalVariable(name: "local2", scope: !31, file: !1, line: 9, type: !34) +!34 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 160, elements: !35) +!35 = !{!36} +!36 = !DISubrange(count: 5) +!37 = distinct !DIAssignID() +!38 = !DILocation(line: 0, scope: !31) +!39 = !DILocation(line: 9, column: 3, scope: !31) +!40 = !DILocation(line: 10, column: 3, scope: !31) +!41 = distinct !DIAssignID() +!42 = !DILocation(line: 11, column: 3, scope: !31) +!43 = distinct !DIAssignID() +!44 = !DILocation(line: 12, column: 3, scope: !31) +!45 = !DILocation(line: 13, column: 1, scope: !31) +!1000 = !{i32 7, !"debug-info-assignment-tracking", i1 true} -- 2.7.4