This is an archive of the discontinued LLVM Phabricator instance.

[DebugInfo][InstrRef][NFC] Handle transfers of variadic debug values in InstrRefLDV
ClosedPublic

Authored by StephenTozer on Jun 20 2022, 8:33 AM.

Details

Summary

This patch adds the last of the changes required to enable DBG_VALUE_LIST handling in InstrRefLDV, handling variadic debug values during the transfer tracking step. Most of the changes are fairly straightforward, and based around tracking multiple locations per variable in TransferTracker::VLocTracker. There has also been a significant change in tracking use-before-defs, since we now have to 1) only emit a UseBeforeDef value when all of its values are defined, meaning that we can't just look up whether there is a dependent UseBeforeDef for each instruction as we go along, and 2) when it is time to emit a UseBeforeDef, we have to check that all the values it wants to use are still available - it is possible that one of the required values is killed before all of the values have been defined.

Diff Detail

Event Timeline

StephenTozer created this revision.Jun 20 2022, 8:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 20 2022, 8:33 AM
Herald added a subscriber: hiraditya. · View Herald Transcript
StephenTozer requested review of this revision.Jun 20 2022, 8:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 20 2022, 8:33 AM

Various comments inline, but this looks like the right direction and correct. A serious awkwardness is that we're adding an extra level of indirection to absolutely everything, which is painful, but it's fundamental to what variadic variable locations are. One thing that helps this is the loc_indices filter iterator you've added -- however that's not actually used in the patch? Please do use it, it'd eliminated numerous if conditionals!

loadInLocs is getting pretty big now -- if there's an obvious portion that can be split into a method, or a helper lambda that can be defined with less indentation, that'd help.

Adding iteration over all locations in checkInstForNewValues is awkward for performance: it doesn't happen for every MachineInstr, but there can be a _lot_ of locations when it does. And the reason it has to happen is hard to program for: tracking a value that's needed for a variable, but a half-formed variable that mustn't be given a full location yet. IIRC you were going to run this all over CTMark, were there any notable performance regressions there?

llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
200–1

Insert the range perhaps? Also, isn't this a meaningful change from the prior implementation -- variables that were active at the destination used to be erased (in the overwrite), now they'll remain active. I can't remember whether this presents an issue, but is it necessary to change the behaviour?

200–1

std::replace?

201–202

Nit: const refs to Var / Properties

201–203

Could we use a SmallDenseMap to avoid the initial DenseMap heap allocation? I don't think this makes much of a difference seeing how it's out of the fast-path, and use-before-defs aren't too common.

202

Splitting hairs, IMO IsValueValid reads closer to a full sentence, is easier to read.

203

Nit: is this somewhere we can use emplace_back / std::move? Makes a difference in the rare case that the operand vector in UBD allocates, I think.

203

Similar concerns as above, do we need this to be ordered during the search, do we not need to erase all operands of the DebugVariable from ActiveMLocs?

206

Good use of auto to avoid misery; definitely wants a docucomment to explain what's being produced here though -- a range over all the LocIdx's that are made use of?

While we're at it, perhaps this class needs renaming now that it refers to more than one location? ValueWithProperties?

212

A dense map here is expensive IMO -- can we append to a vector of pair<LocIdx, DebugVariable> and erase later, or is the ordering in the loop important? Also, I get the feeling we need to erase all operands of the untracked variable from ActiveMLocs, not just the ones that have been clobbered, so could we just keep a collection of DebugVariables?

213–220

This is difficult to unpick -- could we make the two 'if' conditions early-continues instead? IMO a longer but shallower for loop is beneficial to readability.

I tried to think about how we could get rid of this, but we need to:

  • Collect DILocalVariables, and
  • Scan their operands to see if they're now untracked,

Which necessitates two fors. I suppose this is the cost we pay for a lazy update to the tracking list.

Also: shouldn't Op.Loc != NewLoc be the opposite, we select this variable to be erased if one of its operands was at a location that's now clobbered?

236–237

LastUseBeforeDef = std::max(LastUseBeforeDef, Num.getInst());?

240–242

I'd suggest pushing this check into recoverAsEntryValue -- then we get free checking to ensure things like clobberMloc don't try to recover things as entry values.

Various comments inline, but this looks like the right direction and correct. A serious awkwardness is that we're adding an extra level of indirection to absolutely everything, which is painful, but it's fundamental to what variadic variable locations are. One thing that helps this is the loc_indices filter iterator you've added -- however that's not actually used in the patch? Please do use it, it'd eliminated numerous if conditionals!

Of course, I wrote a nice function for it and forgot to use it - consider it used!

loadInLocs is getting pretty big now -- if there's an obvious portion that can be split into a method, or a helper lambda that can be defined with less indentation, that'd help.

Probably a good idea, I'll look to find a way to split it up a bit more.

Adding iteration over all locations in checkInstForNewValues is awkward for performance: it doesn't happen for every MachineInstr, but there can be a _lot_ of locations when it does. And the reason it has to happen is hard to program for: tracking a value that's needed for a variable, but a half-formed variable that mustn't be given a full location yet. IIRC you were going to run this all over CTMark, were there any notable performance regressions there?

w.r.t. checkInstForNewValues, it is a somewhat naive implementation - a messier but typically-faster approach would be to keep track of every instruction in the block that defines values needed by the DbgValue, not just the last one to appear. Then in checkInstForNewValues, we can just look up the locations directly to see if they still contain the values we need; most of the time I'd imagine they will, and if they don't then we can either search every location or (if it's too harsh performance-wise) simply give up. Last I checked I think there was a surprisingly low performance impact, but I'll check again to make sure it's not an error in measurement.

llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
200–1

I'll double check this one. To summarize: we assume the destination to have been pre-clobbered. The reason we do this instead of clobbering it here is that when there is an "identity" copy, we may 1) clobber the location Dst, 2) select a backup location for those values in Src, and 3) subsequently declare those variables to be live in Dst. This change to the logic happened in D128101; I'm not sure if this is an unnecessary change on top of that one, but I'm moderately sure I came across some case where it was an issue; I'll double check and get a test case out this time.

203

Can also be made into a vector; see comment above.

212

This could quite reasonably be a vector - the reason for using a map structure was so that we can erase all the variables for each MLoc in one go, but since we still need to make one call per LocIdx:Variable pair it's probably not worth the overhead of having the map. It could be made into a SmallDenseMap declared outside of the loop (there probably won't be any entries here most of the time) and cleared after each iteration, but the Vector is probably simpler given the lack of an "erase many" function.

Also, I get the feeling we need to erase all operands of the untracked variable from ActiveMLocs, not just the ones that have been clobbered, so could we just keep a collection of DebugVariables?

That's what this loop is doing (see comment below).

213–220

Also: shouldn't Op.Loc != NewLoc be the opposite, we select this variable to be erased if one of its operands was at a location that's now clobbered?

This is meant to erase the ActiveMLocs mappings for all of the variable's other operands after the variable is killed by the NewLoc clobber. For all the operands Op.Loc == NewLoc, we don't need the map - it's trivially solved by the line ActiveMLocs[NewLoc.asU64()].clear(). This check is to stop us manually erasing every variable one at a time from ActiveMLocs[NewLoc] when we're just going to clear the whole set anyway.

Addressed all review comments except for the query around erasing existing locations in transferMlocs.

Further comment: this patch doesn't have tests currently as there are no existing unit tests for transfer tracking, and there are no changes that would be visible to an MIR test. I could create a suite of transfer tracking unit tests as part of this patch, though if possible I'd prefer that not to block this patch, since it would probably involve writing tests for more behaviour than is featured in this patch alone.

Orlando accepted this revision.Aug 17 2022, 3:15 AM

loadInLocs is getting pretty big now -- if there's an obvious portion that can be split into a method, or a helper lambda that can be defined with less indentation, that'd help.

Have you addressed this?

Further comment: this patch doesn't have tests currently as there are no existing unit tests for transfer tracking, and there are no changes that would be visible to an MIR test. I could create a suite of transfer tracking unit tests as part of this patch, though if possible I'd prefer that not to block this patch, since it would probably involve writing tests for more behaviour than is featured in this patch alone.

It would be reassuring to demonstrate that the feature as a whole is working with this patch in. A few llvm-lit tests for this purpose may be beneficial, or if such tests exist in other patches, mentioning them in the patch summary and in the commit message would be useful IMO.

LGTM once these comments have been addressed and on the understanding that lit tests for the not-yet-testable variadic use-before-defs are added when variadic DBG_INSTR_REF support is added.

llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
200–1

Did you check this?

201

Can DbgOps be a const &?

202

Incomplete comment here

This revision is now accepted and ready to land.Aug 17 2022, 3:15 AM
StephenTozer added inline comments.Aug 22 2022, 12:29 AM
llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
200–1

Checked, the reason we need this change is because as of the prior changes described above, if we copy from $r10 to $r11 when both already contain the same value, when we "clobber" $r11 it is possible the variables that were live there will continue to be live there. Then, if we choose to transfer the variables that were live in $r10 to be live in $r11, we do not want to terminate the perfectly valid variables that are live in $r11.

This revision was landed with ongoing or failed builds.Aug 23 2022, 7:03 AM
This revision was automatically updated to reflect the committed changes.