This is an archive of the discontinued LLVM Phabricator instance.

[LV] Generalize conditions for sinking instrs for first order recurrences.
ClosedPublic

Authored by fhahn on Oct 20 2019, 4:24 PM.

Details

Summary

If the recurrence PHI node has a single user, we can sink any
instruction without side effects, given that all users are dominated by
the instruction computing the incoming value of the next iteration
('Previous'). We can sink instructions that may cause traps, because
that only causes the trap to occur later, but not on any new paths.

With the relaxed check, we also have to make sure that we do not have a
direct cycle (meaning PHI user == 'Previous), which indicates a
reduction relation, which potentially gets missed by
ReductionDescriptor.

As follow-ups, we can also sink stores, iff they do not alias with
other instructions we move them across and we could also support sinking
chains of instructions and multiple users of the PHI.

Fixes PR43398.

Diff Detail

Event Timeline

fhahn created this revision.Oct 20 2019, 4:24 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 20 2019, 4:24 PM
Ayal added inline comments.Oct 22 2019, 3:10 AM
llvm/lib/Analysis/IVDescriptors.cpp
702–711

Above TODO comment should be updated as well (thanks :-). It's possible to further extend sinking loads/stores as noted, which will affect interleave-group analysis; and also to sinking multiple users of Phi, which will need to preserve relative order to maintain dependences between themselves.

705

by the instruction[s]

706

canSink >> canSinkAfter ?

auto >> bool ?

712–713

Maybe better to first check if Previous already dominates I, before checking all of I's uses.

715

That's true, but it isn't checked below if Phi has more than one use, and it isn't checked in canSink (if any of I's uses is equal to SinkPoint). Perhaps it should be an assert, in all these places, verifying the precondition that Phi does not belong to any reduction cycle.

729–730

The above for loop looks similar to the canSink[After]() lambda; maybe rename it allUsesDominatedBy() or something, and reapply here?

fhahn updated this revision to Diff 226915.Oct 29 2019, 9:29 AM
fhahn marked 7 inline comments as done.

Address review comments, thanks Ayal!

fhahn added inline comments.Oct 29 2019, 10:59 AM
llvm/lib/Analysis/IVDescriptors.cpp
706

I used auto for the lambda, but I've got rid of the lambda for now and moved it down.

715

Agreed. I guess to be sure we would have to recursively check all users, until we either reach a cycle or are outside the loop, right? I've added the (simple) check here because this is very easy to run into in practice. Would it be OK to have the assertion as follow-up?

Ayal accepted this revision.Oct 29 2019, 12:12 PM

This looks good to me, plus some final minor comments, thanks!

llvm/lib/Analysis/IVDescriptors.cpp
715

My bad, the added check is correct; cannot_sink_reduction() test below demonstrates how this can now happen. (It could not happen until now because a cast must change types :-). And dominates(DominatedBy, U) fails if DominatedBy==U, so the users of Phi below are covered.

"and there's nothing to sink" >> "which cannot be handled by sinking" (an instruction cannot sink after itself)

728

It may be better to check allUsesDominatedBy() last, i.e., after checking for same Parents, in terms of compile-time.

llvm/test/Transforms/LoopVectorize/first-order-recurrence-complex.ll
7

Worth commenting that the test is derived from PR43398, along with a simple version in C.

63

Note that a trapping instruction may sink past a trapping instruction; but if loop is vectorized, the order may swap anyhow...

122

Alternatively CHECK-NOT that no vector type got generated? Holds for additional not-to-be-vectorized tests below.

163

no[t] alias

This revision is now accepted and ready to land.Oct 29 2019, 12:12 PM
fhahn marked 4 inline comments as done.EditedNov 2 2019, 2:14 PM

Thanks Ayal, committed with the comments addressed.

llvm/lib/Analysis/IVDescriptors.cpp
728

I've moved the checks in the if directly, no need to trust the compiler to sink the checks.

llvm/test/Transforms/LoopVectorize/first-order-recurrence-complex.ll
122

I've kept the more verbose check lines, as it seems a bit more explicit (and they are still quite compact IMO)

This revision was automatically updated to reflect the committed changes.
Ayal added inline comments.Nov 2 2019, 3:01 PM
llvm/lib/Analysis/IVDescriptors.cpp
732

Post-commit note, to be considered e.g. when extending sinkings further: seems more logical to start with "if (allUsesDominatedBy(Phi, Previous)) return true; // We already are good w/o sinking", then try to sink with "if (Phi->hasOneUse()) {...}" and finally return false if sinking failed.

Ayal added a subscriber: gilr.Nov 7 2019, 2:59 PM

As revert eaff300 shows, more care must be taken to avoid having one FOR (First Order Recurring) phi sink an instruction that another FOR phi needs to keep in place. Namely, any Previous instruction that feeds a FOR phi across the backarc must not be allowed to sink, because that may break its dominance on other instructions who use the FOR phi. The original check against SinkAfter.count(Previous)) // Cannot rely on dominance due to motion addresses this concern but only partially; it works if the FOR phi that needs to sink an instruction is handled before the FOR phi that needs to keep it in place. But the two phi's could be encountered in the other order.

One way to fix this is to record every "Previous" instruction using the existing SinkAfter map, as illustrated below. Another way may be to post-process all FirstOrderRecurrences along with SinkAfter.

diff --git a/llvm/lib/Analysis/IVDescriptors.cpp b/llvm/lib/Analysis/IVDescriptors.cpp
index bdebd71d7f6..fd78a8335e0 100644
--- a/llvm/lib/Analysis/IVDescriptors.cpp
+++ b/llvm/lib/Analysis/IVDescriptors.cpp
@@ -696,6 +696,9 @@ bool RecurrenceDescriptor::isFirstOrderRecurrence(
       SinkAfter.count(Previous)) // Cannot rely on dominance due to motion.
     return false;
 
+  // Record that Previous must stay in place, as it must continue to dominate other instructions.
+  SinkAfter[Previous] = nullptr;
+
   // Ensure every user of the phi node is dominated by the previous value.
   // The dominance requirement ensures the loop vectorizer will not need to
   // vectorize the initial value prior to the first iteration of the loop.
@@ -717,6 +720,11 @@ bool RecurrenceDescriptor::isFirstOrderRecurrence(
     if (Previous == I)
       return false;
 
+    if (SinkAfter.count(I)) {
+      LLVM_DEBUG(dbgs() << "LV: cannot sink instruction " << *I << " because it is involved in multiple recurrence phi's.\n");
+      return false;
+    }
+
     if (DT->dominates(Previous, I)) // We already are good w/o sinking.
       return true;
 
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 44623dd0ebc..5ca40dc3974 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -7152,7 +7152,7 @@ VPlanPtr LoopVectorizationPlanner::buildVPlanWithVPRecipes(
       // handling this instruction until after we've handled the instruction it
       // should follow.
       auto SAIt = SinkAfter.find(Instr);
-      if (SAIt != SinkAfter.end()) {
+      if (SAIt != SinkAfter.end() && SAIt->second) {
         LLVM_DEBUG(dbgs() << "Sinking" << *SAIt->first << " after"
                           << *SAIt->second
                           << " to vectorize a 1st order recurrence.\n");
fhahn added a comment.Nov 8 2019, 5:26 AM

Thanks Ayal, I’ll add a fix and recommit the patch soon

fhahn added a comment.Nov 24 2019, 1:26 PM

As revert eaff300 shows, more care must be taken to avoid having one FOR (First Order Recurring) phi sink an instruction that another FOR phi needs to keep in place. Namely, any Previous instruction that feeds a FOR phi across the backarc must not be allowed to sink, because that may break its dominance on other instructions who use the FOR phi. The original check against SinkAfter.count(Previous)) // Cannot rely on dominance due to motion addresses this concern but only partially; it works if the FOR phi that needs to sink an instruction is handled before the FOR phi that needs to keep it in place. But the two phi's could be encountered in the other order.

I've recommitted the patch: https://github.com/llvm/llvm-project/commit/9d24933f79dd7db3e469b3c4402e076cc41418f7.

It checks for all found recurrence PHIs, if any of the previous values needs sinking and bails out in LoopVectorizationLegality. I think there's still a bit of cleanup potential, e.g. removing another check from isFirstOrderRecurrence and maybe moving it to a function (e.g. validateFirstOrderRecurrences in IVDescriptors). I think we could also handle additional cases, where the required dominance relationships still hold after sinking. Let's do that as follow ups, once the patch sticks.