diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h index a6220035d25c2..c0b518b68b557 100644 --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -3084,6 +3084,43 @@ class DIExpression : public MDNode { return 0; } + /// Computes a fragment, bit-extract operation if needed, and new constant + /// offset to describe a part of a variable covered by some memory. + /// + /// The memory region starts at: + /// \p SliceStart + \p SliceOffsetInBits + /// And is size: + /// \p SliceSizeInBits + /// + /// The location of the existing variable fragment \p VarFrag is: + /// \p DbgPtr + \p DbgPtrOffsetInBits + \p DbgExtractOffsetInBits. + /// + /// It is intended that these arguments are derived from a debug record: + /// - \p DbgPtr is the (single) DIExpression operand. + /// - \p DbgPtrOffsetInBits is the constant offset applied to \p DbgPtr. + /// - \p DbgExtractOffsetInBits is the offset from a + /// DW_OP_LLVM_bit_extract_[sz]ext operation. + /// + /// Results and return value: + /// - Return false if the result can't be calculated for any reason. + /// - \p Result is set to nullopt if the intersect equals \p VarFarg. + /// - \p Result contains a zero-sized fragment if there's no intersect. + /// - \p OffsetFromLocationInBits is set to the difference between the first + /// bit of the variable location and the first bit of the slice. The + /// magnitude of a negative value therefore indicates the number of bits + /// into the variable fragment that the memory region begins. + /// + /// We don't pass in a debug record directly to get the constituent parts + /// and offsets because different debug records store the information in + /// different places (dbg_assign has two DIExpressions - one contains the + /// fragment info for the entire intrinsic). + static bool calculateFragmentIntersect( + const DataLayout &DL, const Value *SliceStart, uint64_t SliceOffsetInBits, + uint64_t SliceSizeInBits, const Value *DbgPtr, int64_t DbgPtrOffsetInBits, + int64_t DbgExtractOffsetInBits, DIExpression::FragmentInfo VarFrag, + std::optional &Result, + int64_t &OffsetFromLocationInBits); + using ExtOps = std::array; /// Returns the ops for a zero- or sign-extension in a DIExpression. diff --git a/llvm/lib/IR/DebugInfo.cpp b/llvm/lib/IR/DebugInfo.cpp index 7f1489ebbd740..7fa1f9696d43b 100644 --- a/llvm/lib/IR/DebugInfo.cpp +++ b/llvm/lib/IR/DebugInfo.cpp @@ -1864,170 +1864,42 @@ void at::deleteAll(Function *F) { DVR->eraseFromParent(); } -/// 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. -static DIExpression::FragmentInfo -getFragmentOrEntireVariable(const DbgVariableRecord *DVR) { - DIExpression::FragmentInfo VariableSlice(0, 0); - // Get the fragment or variable size, or zero. - if (auto Sz = DVR->getFragmentSizeInBits()) - VariableSlice.SizeInBits = *Sz; - if (auto Frag = DVR->getExpression()->getFragmentInfo()) - VariableSlice.OffsetInBits = Frag->OffsetInBits; - return VariableSlice; -} - -static DIExpression::FragmentInfo -getFragmentOrEntireVariable(const DbgVariableIntrinsic *DVI) { - DIExpression::FragmentInfo VariableSlice(0, 0); - // Get the fragment or variable size, or zero. - if (auto Sz = DVI->getFragmentSizeInBits()) - VariableSlice.SizeInBits = *Sz; - if (auto Frag = DVI->getExpression()->getFragmentInfo()) - VariableSlice.OffsetInBits = Frag->OffsetInBits; - return VariableSlice; -} +/// FIXME: Remove this wrapper function and call +/// DIExpression::calculateFragmentIntersect directly. template bool calculateFragmentIntersectImpl( const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits, uint64_t SliceSizeInBits, const T *AssignRecord, 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, Assign=dbg.assign) - // - // Drawing the store (s) in memory followed by the shortened version ($), - // then the dbg.assign (d), with the fragment information on a separate 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 [##] - Assign'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 Assign's fragment info: - // SliceOfVariable ∩ Assign_fragment = none - // - // In this case: none of the dead bits of the store affect Assign. - // - // # 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, Assign=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 [##] - Assign'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 ∩ Assign_fragment = (offset: 144, size: 16) - // SliceOfVariable tells us the bits of the variable described by Assign that - // are affected by the DSE. + // No overlap if this DbgRecord describes a killed location. if (AssignRecord->isKillAddress()) return false; - DIExpression::FragmentInfo VarFrag = - getFragmentOrEntireVariable(AssignRecord); - 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; + int64_t AddrOffsetInBits; { - auto DestOffsetInBytes = - AssignRecord->getAddress()->getPointerOffsetFrom(Dest, DL); - if (!DestOffsetInBytes) - return false; // Can't calculate difference in addresses. - - int64_t ExprOffsetInBytes; - if (!AssignRecord->getAddressExpression()->extractIfOffset( - ExprOffsetInBytes)) + int64_t AddrOffsetInBytes; + SmallVector PostOffsetOps; //< Unused. + // Bail if we can't find a constant offset (or none) in the expression. + if (!AssignRecord->getAddressExpression()->extractLeadingOffset( + AddrOffsetInBytes, PostOffsetOps)) return false; - - int64_t PointerOffsetInBytes = *DestOffsetInBytes + ExprOffsetInBytes; - PointerOffsetInBits = PointerOffsetInBytes * 8; + AddrOffsetInBits = AddrOffsetInBytes * 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 AssignRecord'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; + Value *Addr = AssignRecord->getAddress(); + // FIXME: It may not always be zero. + int64_t BitExtractOffsetInBits = 0; + DIExpression::FragmentInfo VarFrag = + AssignRecord->getFragmentOrEntireVariable(); + + int64_t OffsetFromLocationInBits; //< Unused. + return DIExpression::calculateFragmentIntersect( + DL, Dest, SliceOffsetInBits, SliceSizeInBits, Addr, AddrOffsetInBits, + BitExtractOffsetInBits, VarFrag, Result, OffsetFromLocationInBits); } + +/// FIXME: Remove this wrapper function and call +/// DIExpression::calculateFragmentIntersect directly. bool at::calculateFragmentIntersect( const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits, uint64_t SliceSizeInBits, const DbgAssignIntrinsic *DbgAssign, @@ -2035,6 +1907,9 @@ bool at::calculateFragmentIntersect( return calculateFragmentIntersectImpl(DL, Dest, SliceOffsetInBits, SliceSizeInBits, DbgAssign, Result); } + +/// FIXME: Remove this wrapper function and call +/// DIExpression::calculateFragmentIntersect directly. bool at::calculateFragmentIntersect( const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits, uint64_t SliceSizeInBits, const DbgVariableRecord *DVRAssign, diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp index b4f7a9df41281..3440fcf711c78 100644 --- a/llvm/lib/IR/DebugInfoMetadata.cpp +++ b/llvm/lib/IR/DebugInfoMetadata.cpp @@ -2091,6 +2091,75 @@ std::optional DIExpression::createFragmentExpression( return DIExpression::get(Expr->getContext(), Ops); } +/// See declaration for more info. +bool DIExpression::calculateFragmentIntersect( + const DataLayout &DL, const Value *SliceStart, uint64_t SliceOffsetInBits, + uint64_t SliceSizeInBits, const Value *DbgPtr, int64_t DbgPtrOffsetInBits, + int64_t DbgExtractOffsetInBits, DIExpression::FragmentInfo VarFrag, + std::optional &Result, + int64_t &OffsetFromLocationInBits) { + + if (VarFrag.SizeInBits == 0) + return false; // Variable size is unknown. + + // Difference between mem slice start and the dbg location start. + // 0 4 8 12 16 ... + // | | + // dbg location start + // | + // mem slice start + // Here MemStartRelToDbgStartInBits is 8. Note this can be negative. + int64_t MemStartRelToDbgStartInBits; + { + auto MemOffsetFromDbgInBytes = SliceStart->getPointerOffsetFrom(DbgPtr, DL); + if (!MemOffsetFromDbgInBytes) + return false; // Can't calculate difference in addresses. + // Difference between the pointers. + MemStartRelToDbgStartInBits = *MemOffsetFromDbgInBytes * 8; + // Add the difference of the offsets. + MemStartRelToDbgStartInBits += + SliceOffsetInBits - (DbgPtrOffsetInBits + DbgExtractOffsetInBits); + } + + // Out-param. Invert offset to get offset from debug location. + OffsetFromLocationInBits = -MemStartRelToDbgStartInBits; + + // Check if the variable fragment sits outside (before) this memory slice. + int64_t MemEndRelToDbgStart = MemStartRelToDbgStartInBits + SliceSizeInBits; + if (MemEndRelToDbgStart < 0) { + Result = {0, 0}; // Out-param. + return true; + } + + // Work towards creating SliceOfVariable which is the bits of the variable + // that the memory region covers. + // 0 4 8 12 16 ... + // | | + // dbg location start with VarFrag offset=32 + // | + // mem slice start: SliceOfVariable offset=40 + int64_t MemStartRelToVarInBits = + MemStartRelToDbgStartInBits + VarFrag.OffsetInBits; + int64_t MemEndRelToVarInBits = MemStartRelToVarInBits + SliceSizeInBits; + // If the memory region starts before the debug location the fragment + // offset would be negative, which we can't encode. Limit those to 0. This + // is fine because those bits necessarily don't overlap with the existing + // variable fragment. + int64_t MemFragStart = std::max(0, MemStartRelToVarInBits); + int64_t MemFragSize = + std::max(0, MemEndRelToVarInBits - MemFragStart); + DIExpression::FragmentInfo SliceOfVariable(MemFragSize, MemFragStart); + + // Intersect the memory region fragment with the variable location fragment. + DIExpression::FragmentInfo TrimmedSliceOfVariable = + DIExpression::FragmentInfo::intersect(SliceOfVariable, VarFrag); + if (TrimmedSliceOfVariable == VarFrag) + Result = std::nullopt; // Out-param. + else + Result = TrimmedSliceOfVariable; // Out-param. + return true; +} + std::pair DIExpression::constantFold(const ConstantInt *CI) { // Copy the APInt so we can modify it.