This is an archive of the discontinued LLVM Phabricator instance.

[CodeExtractor] Only lift lifetime markers present in the extraction region
ClosedPublic

Authored by vsk on Feb 6 2019, 11:39 AM.

Details

Summary

When CodeExtractor finds liftime markers referencing inputs to the
extraction region, it lifts these markers out of the region and inserts
them around the call to the extracted function (see r350420, PR39671).

However, it should *only* lift lifetime markers that are actually
present in the extraction region. I.e., if a start marker is present in
the extraction region but a corresponding end marker isn't (or vice
versa), only the start marker (or end marker, resp.) should be lifted.

This fixes a miscompile in which a lifetime start marker was lifted
inappropriately, causing an argument to the extracted function to
optimized out as undef.

rdar://47802482

Diff Detail

Event Timeline

vsk created this revision.Feb 6 2019, 11:39 AM
This revision is now accepted and ready to land.Feb 12 2019, 9:48 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 12 2019, 9:48 PM
Herald added a subscriber: jdoerfert. · View Herald Transcript
This revision was automatically updated to reflect the committed changes.
vsk added a comment.Feb 13 2019, 6:20 PM

@kachkov98 @davidxl apologies, but this patch is still not correct.

If a lifetime.end marker occurs along one path through the execution region, but not another, then it's still incorrect to lift the marker, because there is some path through the extracted function which would ordinarily not reach the marker. Example (extract blocks extract{1,2}):

entry:
  lifetime_start(%slot)
  br label %header

header:
  use(%slot)
  br i1 undef, label %extract1, label %extract2

extract1:
  ; Backedge.
  br label %header 

extract2:
  ; Lifting this marker would result in %slot being dead in the header block.
  lifetime_end(%slot)
  br label %exit

I think we have two options to fix this:

  1. Do not lift any lifetime.end markers. Continue to lift lifetime.start markers for inputs, as this should still be safe.
  2. Only lift a lifetime.end marker for an input if that marker would be reached in any every path through the extraction region. If an end marker can't be lifted, erase all lifetime markers for the input in the parent function to prevent bad stack slot merging.

Both options may cause some stack slot merging opportunities to go away. Wdyt?

In this example there are two possible entry points in region, so it can't be extracted (first block in region should dominate the others). Maybe there are better examples of issue?
As I understand, correct (but conservative) transformation is to move lifetime.start upwards to some dominator block. According to this, lifting lifetime.start before call must be right. Moving lifetime.end downwards to some post-dominator block is generally incorrect. The case when lifetime.end for the same value occured at every execution path doesn't seem very common, but placing lifetime.end after call gives more accurate information. The thing that is unclear fo me is how erasing all markers for input in case when lifetime.end can't be lifted helps stack coloring?
BTW, it seems there is no simple solution that doesn't reduce stack coloring opportunities in all cases (can't provide information to the caller whether lifetime.end for given value occured or not).

vsk added a comment.Feb 14 2019, 2:04 PM

In this example there are two possible entry points in region, so it can't be extracted (first block in region should dominate the others). Maybe there are better examples of issue?

I've posted an example in a follow-up patch: https://reviews.llvm.org/D58253

As I understand, correct (but conservative) transformation is to move lifetime.start upwards to some dominator block. According to this, lifting lifetime.start before call must be right.

Agreed.

Moving lifetime.end downwards to some post-dominator block is generally incorrect. The case when lifetime.end for the same value occured at every execution path doesn't seem very common, but placing lifetime.end after call gives more accurate information. The thing that is unclear fo me is how erasing all markers for input in case when lifetime.end can't be lifted helps stack coloring?

I don't think it does. For option (2), I meant, if we can't lift a lifetime.end marker then don't lift the lifetime.start marker either, and also erase all markers for the input to prevent bad stack coloring. It seems that option (1) is both simpler and sufficient.

BTW, it seems there is no simple solution that doesn't reduce stack coloring opportunities in all cases (can't provide information to the caller whether lifetime.end for given value occured or not).

Agreed, this seems unavoidable.

Just for note: it seems that skipping extracting lifetime.start when lifetime.end can't be lifted (in 2nd option) can again lead to miscompile as in https://reviews.llvm.org/D55967 (assume that markers for x and y in rhs are not lifted). It's safer to extract all lifetime.start markers referencing inputs to mark them all as simultaneously used, as in option (1).