This is an archive of the discontinued LLVM Phabricator instance.

[LiveDebugValues] recognize spilled register that is killed in instruction after the spill

Authored by NikolaPrica on Dec 14 2017, 12:40 AM.



Current condition for spill instruction recognition in LiveDebugValues does not recognize case when register is spilled and killed in next instruction.

Diff Detail


Event Timeline

NikolaPrica created this revision.Dec 14 2017, 12:40 AM

Thanks! Couple of comments inline.

435 ↗(On Diff #126904)

please run your patch through clang-format to fix up the indentation

441 ↗(On Diff #126904)

perhaps write this as auto NextI = std::next(MI); ?

448 ↗(On Diff #126904)

This looks like it is *almost* the same code as on line 434. Would it make sense to factor this into a separate function or lambda?

1 ↗(On Diff #126904)

This test is really long, could it be further reduced?

4 ↗(On Diff #126904)

For Machine passes, it is usually preferable to write MIR tests.

To convert this into MIR, run

llc -stop-before=livedebugvalues -o -

and then in the test do

RUN: llc -run-pass=livedebugvalues

I wasn't able to produce simpler test example. I've deleted some of IR instructions from previous test. Deletion of any other instruction from IR would bypass the problem.

Thank you for your comments.

rnk added a subscriber: rnk.Dec 15 2017, 11:42 AM
rnk added inline comments.
432–434 ↗(On Diff #127167)

Probably simpler as return MO.isKill();.

NikolaPrica marked 4 inline comments as done.
aprantl added inline comments.Dec 15 2017, 12:41 PM
451 ↗(On Diff #127172)

Isn't that a no-op?

Hmm.. I just try to apply your patch to trunk, but the testcase doesn't parse for me. Do I need a specific revision of LLVM?

443 ↗(On Diff #127180)


1 ↗(On Diff #127180)

this now needs to go into test/DebugInfo/MIR/X86/kill-after-spill.mir

Please try it now.

Thanks, I can run the test now. Unfortunately it looks like the test is not yet sufficient (see inline comment).

451 ↗(On Diff #127172)

The test still passes when I comment out line 449-450. Could you add coverage for these two lines, too?

NikolaPrica added inline comments.Dec 19 2017, 11:15 AM
451 ↗(On Diff #127172)

I'm not sure what are you asking. Do you think I should add test coverage for this particular condition on 449. If you do, only what I could think of is changing MIR test example in order to hit condition on line 449. It would go something like this then:

%r12d = MOV32rr %r15d
MOV32mr %rbp, 1, %noreg, -48, %noreg, renamable %r15d :: (store 4 into %stack.0)
%edi = MOV32rr killed %r12d

Then this wouldn't be recognized as spill instruction but it could be mentioned in test that this is changed in testing purpose.

Or you are thinking of dispatching condition on 449? I've tried to describe particular situation from MIR with conditions from lines 449 and 448. But this situation could be described as spill instruction after which we expect that copy operation is done and that spill register is killed in the next instruction. Said like that then we don't need condition from line 449.

aprantl added inline comments.Dec 19 2017, 11:25 AM
445 ↗(On Diff #127317)

Perhaps add a comment here why we are skipping here?

449 ↗(On Diff #127317)

Perhaps add a comment here why we are breaking out of the loop here?

451 ↗(On Diff #127172)

I'm asking for 100% test coverage for the patch. If a patch is adding new functionality, that functionality should be tested, so (1) we know that it works as intended and (2) future generations of LLVM developers won't be tempted to remove it "because it has no effect (on the testsuite)".
It's fine to add a second testcase to the patch if that makes it easier. Thanks!

This comment was removed by NikolaPrica.
wolfgangp added inline comments.Dec 19 2017, 4:05 PM
452 ↗(On Diff #127317)

If I read the code correctly, it would identify *any* instruction that stores a register to the stack as a spill instruction. The lambda sets "Reg" in line 439 unconditionally and the following code just checks whether "Reg" is used and killed, but doesn't set it back to 0 if it is not, so line 455 would always return true.

Was this the intent? If so, we wouldn't need to look at the next instruction at all.

I wrote the code originally to be very conservative in the identification of a spill, perhaps too conservative.

5 ↗(On Diff #127317)

Typo: instruction

Also, please end the sentence with a full stop!

MBB.instr_end() changed to MBB.end()

NikolaPrica marked 8 inline comments as done.Jan 6 2018, 6:15 AM

I'll defer to Wolfgang here.

bjope added a subscriber: bjope.Jan 6 2018, 6:22 PM

I do not quite get it why the case when the register is killed in the subsequent instructions is so interesting.
Is this some kind of heuristic just to cover one specific scenario (then I think the summary, and future commit message should mention that, and also add some code comments describing what is going on here).

I mean, what makes this situation with a kill in the subsequent instruction different from a situation when there is one or more instructions in between, such as

A: spills register x
B: an MI not using or defining x
C: last use of x (kill x)

With you solution the result would differ depending on how instruction B and C are scheduled. That seems a little bit hacky to me, and it would at least be worth mentioning that the solution is some kind of heuristic (or that there is a technical debt left).

451 ↗(On Diff #128325)

Isn't it still weird to use Reg here. The getRegIfKilled call on line 439 can return false without setting Reg (due to the operand not being a use, or not even being a register).

13 ↗(On Diff #128325)

typo: instruction

bjope added inline comments.Jan 7 2018, 12:43 PM
443 ↗(On Diff #128325)

In case of bundles this will move to the next instruction inside the bundle (unless it is the last instruction in the bundle)?
That seems weird. One reason is that the result would depend on the order of the bundled instructions.

Is perhaps the intent to look at the next BUNDLE instruction in case of MI being inside a bundle?
If so then I guess something like this would do the trick:

auto NextI = std::next(MachineBasicBlock::const_iterator::getAtBundleBegin(MI.getIterator()));

There could ofcourse be parallel uses of "Reg" in the same bundle, and the KILL marking could perhaps be on one of those instructions. So if MI is bundled, perhaps you need to analyse all the parallel instructions (and the instructions in the next bundle)?

The situation when there is another instruction in the same bundle that defines "Reg" is perhaps also interesting when it comes to additional test cases. But in that case I guess there should be a KILL marking for "Reg" already in the spilling store instruction (or in the BUNDLE head).

Some test cases involving bundles would not hurt.

aprantl removed a reviewer: aprantl.Jan 8 2018, 8:54 AM
aprantl added a subscriber: aprantl.

I agree that this is a solution to a specific case. I would be OK with it going in for now (after addressing the remaining review comments) with a FIXME comment added to provide a more comprehensive solution later, i.e. one that addresses bundles and kills in instructions further down the chain, as Bjorn suggested.

451 ↗(On Diff #128325)

The getRegIfKilled call on line 439 can return false without setting Reg (due to the operand not being a use, or not even being a register).

Yes, Reg can be undefined in that case. Perhaps it should be set to 0 in the lambda when it's not a use or a register.

NikolaPrica marked 2 inline comments as done.
NikolaPrica retitled this revision from LiveDebugValues spill recognition expasnsion to [LiveDebugValues] recognize spilled register that is killed in instruction after the spill.
NikolaPrica edited the summary of this revision. (Show Details)
wolfgangp added inline comments.Jan 8 2018, 5:33 PM
429 ↗(On Diff #128951)

I would prefer if the name of the lambda would reflect that we are returning a boolean quantity, i.e. whether the operand is a killed register or not. The register itself is returned as a side-effect, so something like 'isKilledReg()' instead of 'getKilledReg()' would sound a bit more consistent to me.

443 ↗(On Diff #128951)

I don't believe we need the 'else' now.

451 ↗(On Diff #128951)

We don't need to initialize RegNext here, I think.

bjope added a comment.Jan 9 2018, 1:32 PM

I agree that this is a solution to a specific case. I would be OK with it going in for now (after addressing the remaining review comments) with a FIXME comment added to provide a more comprehensive solution later, i.e. one that addresses bundles and kills in instructions further down the chain, as Bjorn suggested.

Ok, that is fine with me. The FIXME tells me that the current solution is a deliberate choice (without the FIXME the code looks incomplete, and it is hard to tell what kind of scenarios the author was aiming for). Thanks!

NikolaPrica marked 3 inline comments as done.Jan 12 2018, 9:06 AM
wolfgangp accepted this revision.Jan 12 2018, 12:00 PM
wolfgangp added inline comments.
429 ↗(On Diff #128951)

I'd still prefer to call the lambda 'isKilledReg()' but I leave it up to you.

This revision is now accepted and ready to land.Jan 12 2018, 12:00 PM
This revision was automatically updated to reflect the committed changes.
dongAxis1944 added inline comments.

@wolfgangp hello, I have a question about that why the register of MI must be marked, "killed"?

Considering the following case:

MOV32mr $rsp, 1, $noreg, 8, $noreg, $r9d :: (store 4 into %stack.3)
DBG_VALUE $rsp, 0, !"fuck", !DIExpression(DW_OP_plus_uconst, 8), debug-location !960; llvm-project/build_release/bin/test.cpp:0 line no:14 indirect
MOVSDmr $rsp, 1, $noreg, 8, $noreg, killed renamable $xmm0 :: (store 8 into %stack.3)

Because the r9d is not marked kill, the LLVM will not know the value of r9d is on %stack.3.

Any ideas?

Herald added a project: Restricted Project. · View Herald TranscriptMar 15 2021, 2:07 AM
jmorse added a subscriber: jmorse.Mar 15 2021, 3:27 AM
jmorse added inline comments.

Note Bjorns comment at asking something similar -- this code is mostly a heuristic to catch a common case, where a variable is spilt then restored over a function call for example. It's not guaranteed to catch everything, and we would end up having to follow multiple locations per variable if we did.

(Shameless plug: my LiveDebugValues rewrite should get this right, see . I've paused landing it for a bit, but will continue shortly).