Skip to content

Commit

Permalink
[NFC] Add DIExpression::calculateFragmentIntersect
Browse files Browse the repository at this point in the history
Patch [3/x] to fix structured bindings debug info in SROA.

This function computes a fragment, bit-extract operation if needed, and new
constant offset to describe a part of a variable covered by some memory.

This generalises, simplifies, and replaces at::calculateFragmentIntersect. That
version is still used as a wrapper for now though to keep this change NFC.

The new version takes doesn't have a DbgRecord parameter, instead using an
explicit address and address offset. The old version only operates on
dbg_assigns and this change means it can also operate on dbg_declare records
easily, which it will do in a subsequent patch.

The new version has a new out-param OffsetFromLocationInBits which is set to the
difference between the first bit of the variable location and the first bit of
the memory slice. This will be used in a subsequent patch in SROA to determine
the new offset to use in the address expression after splitting an alloca.
  • Loading branch information
OCHyams committed Jul 4, 2024
1 parent 9a5bd0a commit e1c71d8
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 153 deletions.
37 changes: 37 additions & 0 deletions llvm/include/llvm/IR/DebugInfoMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<DIExpression::FragmentInfo> &Result,
int64_t &OffsetFromLocationInBits);

using ExtOps = std::array<uint64_t, 6>;

/// Returns the ops for a zero- or sign-extension in a DIExpression.
Expand Down
181 changes: 28 additions & 153 deletions llvm/lib/IR/DebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1864,181 +1864,56 @@ 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 <typename T>
bool calculateFragmentIntersectImpl(
const DataLayout &DL, const Value *Dest, uint64_t SliceOffsetInBits,
uint64_t SliceSizeInBits, const T *AssignRecord,
std::optional<DIExpression::FragmentInfo> &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;
{
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))
return false;
int64_t AddrOffsetInBytes;
SmallVector<uint64_t> 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 AddrOffsetInBits = AddrOffsetInBytes * 8;

int64_t PointerOffsetInBytes = *DestOffsetInBytes + ExprOffsetInBytes;
PointerOffsetInBits = PointerOffsetInBytes * 8;
}
Value *Addr = AssignRecord->getAddress();
// FIXME: It may not always be zero.
int64_t BitExtractOffsetInBits = 0;
DIExpression::FragmentInfo VarFrag =
AssignRecord->getFragmentOrEntireVariable();

// 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;
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,
std::optional<DIExpression::FragmentInfo> &Result) {

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,
std::optional<DIExpression::FragmentInfo> &Result) {
// FIXME: Remove this wrapper function and call
// DIExpression::calculateFragmentIntersect directly.
return calculateFragmentIntersectImpl(DL, Dest, SliceOffsetInBits,
SliceSizeInBits, DVRAssign, Result);
}
Expand Down
69 changes: 69 additions & 0 deletions llvm/lib/IR/DebugInfoMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,75 @@ std::optional<DIExpression *> 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<DIExpression::FragmentInfo> &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 MemStartRelToFragInBits =
MemStartRelToDbgStartInBits + VarFrag.OffsetInBits;
int64_t MemEndRelToFragInBits = MemStartRelToFragInBits + 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 becausethose bits necessarily don't overlap with the variable
// fragment.
int64_t MemFragStart = std::max<int64_t>(0, MemStartRelToFragInBits);
int64_t MemFragSize =
std::max<int64_t>(0, MemEndRelToFragInBits - 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 *, const ConstantInt *>
DIExpression::constantFold(const ConstantInt *CI) {
// Copy the APInt so we can modify it.
Expand Down

0 comments on commit e1c71d8

Please sign in to comment.