This is an archive of the discontinued LLVM Phabricator instance.

[LiveDebugValues][InstrRef][2/2] Emit entry value variable locations
ClosedPublic

Authored by jmorse on Sep 28 2020, 5:10 AM.

Details

Summary

Hi,

This patch adds support to the instruction-referencing LiveDebugValues implementation for emitting entry values. It depends on the parent patch to detect when variable locations are clobbered within blocks. Here are the headline figures from the 'PC Ranges covered' field of llvm-locstats:

VarLocBasedLDVInstrRefBasedLDV
No entry values54%56%
Entry values enabled56%61%

Which, assuming that I've followed the preconditions for emitting entry value locations, is an improvement. Full output of llvm-locstats can be found here [0]. ("Total availability" goes down a little because instr-ref-ldv is a little more aggressive about not propagating variable locations out of their scopes).

The instruction referencing implementations tracking by value rather than location means that we can get around two of the issues with the VarLoc implementation. DBG_VALUE instructions that re-assign the same value to a variable are no longer a problem, because we can "see through" to the value being assigned rather than just the location. We also don't need to do anything special during the dataflow stages: the "variable value problem" doesn't need to know whether a value is available most of the time, and the times it does needs to know are always when entry values need to be terminated (i.e., when differing variable values merge).

The patch modifies the "TransferTracker" class, which is responsible for turning maps of variable => values, and values => locations, into DBG_VALUE instructions within blocks. Two helper functions identify when a variable is an entry value candidate; and when a machine value is an entry value. recoverAsEntryValue tests these two things and emits a DBG_VALUE with an entry value expression if they're true. recoverAsEntryValue is called on two occasions: when a register (or spill slot) is clobbered and we can't find a replacement location for the value it contained; and on entry to a block, if the value isn't live at that point.

This can be tested by using the flag added in the parent patch: -force-instr-ref-livedebugvalues=1. I've added additional RUN lines to a bunch of entry value tests to ensure they work with InstRefBasedLDV: I found these by "git grep"'ing for 'entry_value' and ignoring anything that appeared to be testing call sites. Note that there's one serious modification to a test, kill-entry-value-after-diamond-bbs.mir. My reading of the test is that VarLocBasedLDV has to drop the entry value location because it doesn't know if the DBG_VALUEs in the diamond structure are new value assignments; wheras InstrRefBasedLDV can "see" that the DBG_VALUEs aren't assigning a new value. Advice most welcome: it might be best to update the test to ensure the DBG_VALUEs refer to a different value.

[0] https://gist.github.com/jmorse/a14763d935ef5d20e6e5dc41672c734a

Diff Detail

Event Timeline

jmorse created this revision.Sep 28 2020, 5:10 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 28 2020, 5:10 AM
jmorse requested review of this revision.Sep 28 2020, 5:10 AM

...additionally there are a few lines modified to better support instruction bundles (DBG_VALUEs were being inserted inside them on Sparc). I can split that out into a different patch if it gets int the way.

Hi @jmorse, cool, nice improvement! It turns out that this value-based value tracking pass makes it simple as well as effective!

My overall impression regarding the instr-based-ldv is very positive, but I haven't had much time to take a look into all the details regarding the implementation (but I have started looking into it). So I have a problem reading of some of these statements, e.g.:

DBG_VALUE instructions that re-assign the same value to a variable are no longer a problem, because we can "see through" to the value being assigned rather than just the location.

Can you please explain what part of the implementation of the instr-based-ldv gives us ability to "see through" to the value assignments?

llvm/test/DebugInfo/MIR/X86/kill-entry-value-after-diamond-bbs.mir
26

if we can "see through", it does seem reasonable to me.

Djordje wrote:

Can you please explain what part of the implementation of the instr-based-ldv gives us ability to "see through" to the value assignments?

Good question, and it's probably time for a worked example of the whole "system", using kill-entry-value-after-diamond-bbs.mir. It's ideal because it doesn't contains loops, and loops are an entirely different kettle of fish. The overall summary is: we effectively compute a map for every instruction in the function recording which value is in each register, so we can "see" that the DBG_VALUEs assigns the same value that was already assigned to the variable. I'll be overly verbose below and use some tables to illustrate what's going on

Let's also pretend that I've applied D85760 to the tree -- it doesn't change the overall algorithm, but makes it easier to understand. Without it, steps one and three (below) are merged. Haven't gotten around to commiting D85760 yet :|

The kill-entry-value-after-diamond-bbs.mir test has the diamond shape:

   a
  / \
 /   \
b     c
 \   /
  \ /
   d

Each value in the function is represented by a unique ValueIDNum, which I'm representing below as "Value{a, b, c}", where 'a' is the block number, 'b' is the defining instructions number in the block or 'live-in', and 'c' identifies the register where the value is defined.

There are roughly five steps in the "ExtendRanges" method in InstrRefBasedLDV that deal with everything:

  1. Produce machine value transfer function
  2. Work out the live-in machine values for each block
  3. Produce variable value transfer function
  4. Work out the live-in variable values for each block
  5. Produce variable locations for each variable in each block.

Step one: we produce a transfer function for each block. This is the same ``transfer function'' as defined by dataflow literature, it defines what values each block places in each location. Here's an example for the entry block in the example test:

$rsp = Value{0, 8, $rsp}
$edi = Value{0, 18, $edi}
$ebp = Value{0, live-in, $edx}
$edx = Value{0, 16, $edx}

It's a summary of how values move around in the block. All of $rsp, $edx and $edi are assigned new values in the block -- but $ebp is assigned a copy of $edx in the entry block. Whatever value is live-in to the block in $edx is copied into $ebp during the block. All registers that aren't defined in a block are live-through, whatever value comes in goes out in the same location. The transfer functions for the two diamond branches, block 1:

$ebx = Value{1, 4, $ebx}
$esi = Value{1, 3, $esi}

Block 2:

$ebp = Value{2, 4, $ebp}
$esi = Value{2, 3, $esi}

Which represents the registers defined by the MOV32ri instructions.

Step two: we use the "normal" dataflow process to work out what values are live-in to each block. The lattice used is a bit crazy, but ignoring loops it works like this: the initial live-ins to the entry block are these values (lets ignore rsp):

$edi = Value{0, live-in, $edi}
$esi = Value{0, live-in, $esi}
$ebx = Value{0, live-in, $ebx}
$ebp = Value{0, live-in, $ebp}
$edx = Value{0, live-in, $edx}

We can apply the transfer function above, and find the live-outs from the entry block to be:

$edi = Value{0, 18,  $edi}
$esi = Value{0, live-in, $esi}
$ebx = Value{0, live-in, $ebx}
$ebp = Value{0, live-in, $edx}
$edx = Value{0, 16, $edx}

Note that $edi and $edx have been assigned values; $ebp has been assigned whatever was in $edx on entry, and both $esi and $ebx are "live-through", so unchanged. Because blocks 1 and 2 only have the entry block as their predecessor, we can just copy the live-outs from the entry block to be the live-ins for blocks 1 and 2. We apply the transfer function from each block again to work out what their live-outs are: in block 1 $ebx and $esi are assigned values, while in block 2 $ebp and $esi are assigned values. Given the live-out values for blocks 1 and 2, we can compute the live-ins to block 3: but we have to resolve the different values being merged from the two predecessors: {$ebx, $esi, $ebp} have different values in each block. These are recorded as being PHI values, like this:

$edi = Value{0, 18,  $edi}
$esi = Value{3, live-in, $esi}
$ebx = Value{3, live-in, $ebx}
$ebp = Value{3, live-in, $ebx}
$edx = Value{0, 16, $edx}

$edi and $edx get the same Value because the predecessors agree on the value; wheras the other registers get new Values invented indicating that a PHI occurred on entry to block 3.

Key observation about this: once we have a set of live-in values for each block, we can work out a mapping that identifies what value each register has for every instruction. That brings us on to,

Step three (assume D85760 is applied, see the code immediately after mlocDataflow is called). For each block, we:

  • Take its live-in values as calculated above (they get loaded into the MLocTracker class),
  • Step through each instruction in the block again,
  • For every DBG_VALUE instruction read the value in the specified register, and record it as a variable assignment.

How we "see through" those DBG_VALUE assignments should now be clear(er): We know the live-in value in $esi for blocks 1 and 2 is Value{0, live-in, $esi}, and we can track the copy to $ebx or $ebp respectively. When the DBG_VALUE instructions read either $ebx or $ebp, they will read Value{0, live-in, $esi}.

We then get the following variable value transfer functions for each block (looking only at !17):

entry: !17 = Value{0, live-in, $esi}
block1: !17 = Value{0, live-in, $esi}
block2: !17 = Value{0, live-in, $esi}

And can then perform:

Step four: another dataflow process with the variable transfer functions above, which calculates the live-in values for each variable. It's very simple in this example: the variable is assigned the same value in every block. It becomes much more painful with loops, but let's skip over that, and move to:

Step five: We now have a set of live-in values for each register for each block; and a set of live-in values for each variable in each block. For each block, we:

  • Create a map of each Value to a location (register)
  • For each live-in variable value, look up whether that value is in a register somewhere, and if so emit a DBG_VALUE,
  • Also for each live-in variable, record what location we placed it in,
  • Step through each instruction, and
    • If it's a "normal" instruction, record any definitions / copies of values that happen,
    • If it's a DBG_VALUE, update the value and location that the variable has,
    • If a "normal" instruction clobbers any variable locations, try to find a backup location and emit a DBG_VALUE.

Entry values can be emitted when the variable value is an entry value, i.e. it was the value live-in to the entry block Value{0, live-in, $somereg}, and we can't find a location when entering a block or when a register is clobbered.

~

So to repeat the summary from above: we have a map for every instruction recording what value is in each register. The difference that those DBG_VALUEs in blocks 1 and 2 make is that the variable value transfer function is:

entry: !17 = Value{0, live-in, $esi}
block1: !17 = Value{0, live-in, $esi}
block2: !17 = Value{0, live-in, $esi}

Wheras without them it would be:

entry: !17 = Value{0, live-in, $esi}

Where the value in entry would propagate into the other blocks anyway.

~

I'm still struggling to find a good way to describe how these things (are supposed to) work; hopefully spamming the information above will help a bit.

Thanks a lot for such a nice explanation! I've got it (at least for this example)!

The entry value work should be "simple" as well as more effective with this approach. Some initial comments inline.

llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
3438

Should this be a separate patch? Does this affect/apply to the "copies" as backups?

llvm/test/DebugInfo/MIR/X86/livedebugvalues_load_in_loop.mir
83

Why including this here?

jmorse marked 3 inline comments as done.Sep 30 2020, 8:14 AM
jmorse added inline comments.
llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
3438

Shouldn't affect variable locations; I'll split it into a separate patch though and have a dedicated test case. The quick summary is that on X86 tailcalls are written:

TAILCALL @foo, implicit $rdi, $implicit-def $rax

Or similar, and are the last instruction in the block. InstrRefBasedLDV sees the implicit-def of $rax, and tries to recover variable locations afterwards, that leads to DBG_VALUEs being inserted after the terminator; which the MachineVerifier complains about.

There may be some exotic architectures out there that can have multiple terminators in their block, and def registers on the first terminator, which would be tricker to handle. But I think that bridge can be crossed when we come to it.

llvm/test/DebugInfo/MIR/X86/livedebugvalues_load_in_loop.mir
83

/me squints; I think I moved this because otherwise the the output looks like this:

DBG_VALUE $rdi
$rbp = MOV64rr $rdi
$rcx = MOV64ri 0
CALL64pcrel32 @bees
DBG_VALUE $rbp

i.e., the location is recovered to $rbp after the call clobbers $rdi. Wheras with this change, LiveDebugValues does not have to recover any clobbers.

My thinking was that, because this test is targeted at one thing VarLocBasedLDV does poorly, it's best to reduce the number of features being tested in the test, so that clobber being recovered from should be avoided.

djtodoro added inline comments.Oct 1 2020, 2:15 AM
llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
3438

OK. That is fine with me. We avoid such entry-values within old-(varloc-based)-ldv as well.

There may be some exotic architectures out there that can have multiple terminators in their block, and def registers on the first terminator, which would be tricker to handle. But I think that bridge can be crossed when we come to it.

I think that I have faced such case on the PPC64, but as long as we have the MachineVerifier checking that, we are safe.

llvm/test/DebugInfo/MIR/X86/livedebugvalues_load_in_loop.mir
83

OK then :)

Does moving it into the previous change(D88405) make sense to you?

jmorse updated this revision to Diff 300028.Oct 22 2020, 10:09 AM
jmorse marked 2 inline comments as done.

Rebase to account for me gluing patch series together; one or two new code paths crop up where one has to decide whether to try and use an entry value OR register a debug use-before-def.

llvm/test/DebugInfo/MIR/X86/livedebugvalues_load_in_loop.mir
83

Yeah, I've got an intermediate patch to stick between the two that sorts these things out, I'm doing a little patch gardening first though., should appear shortly.

djtodoro added inline comments.Oct 27 2020, 6:05 AM
llvm/test/DebugInfo/ARM/entry-value-multi-byte-expr.ll
2

is the -force-instr-ref-livedebugvalues=1 equivalent to the -experimental-debug-variable-locations?

jmorse added inline comments.Oct 27 2020, 6:24 AM
llvm/test/DebugInfo/ARM/entry-value-multi-byte-expr.ll
2

Not quite, the truth table is:

Produce DBG_INSTR_REF in isel?Use InstrRefBasedLDV?
no optionsFalseFalse
-force-isntr-ref-livedebugvalues=1FalseTrue
-experimental-debug-variable-locationsTrueTrue

Thus, this option lets you can explore how InstrRefBasedLDV behaves compared to VarLocBasedLDV without using all the other instruction referencing work. The two implementations should produce the same output at all times, aside from a few limitations, see the "Testing and Validation" section in:

http://lists.llvm.org/pipermail/llvm-dev/2020-June/142368.html

(There are still a couple of minor bugs hanging around InstrRefBasedLDV, and this is helping me hunt them).

djtodoro added inline comments.Oct 27 2020, 6:29 AM
llvm/test/DebugInfo/ARM/entry-value-multi-byte-expr.ll
2

Oh, I see.. It will be in after D88405 is landed.

Thanks!

djtodoro accepted this revision.Oct 28 2020, 5:00 AM

LGTM; Please remove "Entry values" from the top-level comment TODO list.

This revision is now accepted and ready to land.Oct 28 2020, 5:00 AM