Skip to content

[NFC] Add DIExpression::calculateFragmentIntersect #97738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
177 changes: 26 additions & 151 deletions llvm/lib/IR/DebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1864,177 +1864,52 @@ 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;
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<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 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,
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,
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 MemStartRelToVarInBits =
MemStartRelToDbgStartInBits + VarFrag.OffsetInBits;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the term RelToFrag is misleading, I feel that implies relative to the current debug fragment rather than to the variable as a whole?

Copy link
Contributor Author

@OCHyams OCHyams Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. Change Frag to Var. Still not an amazing name, but I found coming up with meaningful names in this function was pretty difficult (hence all the comments!). I'm happy to take further suggestions?

Copy link
Contributor

@SLTozer SLTozer Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No better suggestions unfortunately, "Var" was the best I had too. I think it's reasonable, it's just slightly nonsensical in that the variable isn't a contiguous region of memory that the memory slice can exist relative to, but it at least makes sense in terms of how we ultimately calculate the variable fragment covered by the slice.

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<int64_t>(0, MemStartRelToVarInBits);
int64_t MemFragSize =
std::max<int64_t>(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 *, const ConstantInt *>
DIExpression::constantFold(const ConstantInt *CI) {
// Copy the APInt so we can modify it.
Expand Down
Loading