This is an archive of the discontinued LLVM Phabricator instance.

[LV] Vectorize GEPs
ClosedPublic

Authored by mssimpso on Mar 7 2017, 11:21 AM.

Details

Summary

This patch adds support for vectorizing GEPs. Previously, we only generated vector GEPs on-demand when creating gather or scatter operations. All GEPs from the original loop were scalarized by default, and if a pointer was to be stored to memory, we would have to build up the pointer vector with insertelement instructions.

With this patch, we will vectorize all GEPs that haven't already been marked for scalarization.

The patch refines collectLoopScalars to more exactly identify the scalar GEPs. The function now more closely resembles collectLoopUniforms. And the patch moves vector GEP creation out of vectorizeMemoryInstruction and into the main vectorization loop. The vector GEPs needed for gather and scatter operations will have already been generated before vectoring the memory accesses.

I think this patch makes sense on its own, but it's primarily motivated by a follow-on, which merges pointer induction variable widening with the rest of the induction widening code. The follow-on creates vector-of-pointer induction variables, and we need to be able to properly consume them.

Event Timeline

mssimpso created this revision.Mar 7 2017, 11:21 AM
delena added inline comments.Mar 7 2017, 12:23 PM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

GEP is uniform when the memory instruction (User) is uniform, right?
Why do you need to broadcast it?

mssimpso added inline comments.Mar 7 2017, 12:45 PM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

This is "loop-invariant" in the LoopAccessInfo::isUniform sense not in the LoopVectorizationCostModel::isUniformAfterVectorization sense (we really should come up with some better names for these concepts). Sometimes we end up with GEPs contained in the loop body that have loop-invariant operands. I'm not sure why these GEPs aren't hoisted out of the loop before the vectorizer runs.

In theory, we should be able to hoist these GEPs out of the loop ourselves, but we have assumptions elsewhere that if an instruction existed in the original loop body, it will map to something inside the vectorized loop body. So I just clone and broadcast the original GEP inside the loop here. The change to the first-order-recurrence.ll test case is reflective of this.

mssimpso added inline comments.Mar 7 2017, 1:30 PM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I just thought about a different way to implement this. Instead of the uniform check, we could check if the value returned by the IRBuilder has a vector type, and if not, do the broadcast. If all the operands are loop-invariant in the code below, the IRBuilder will return a scalar GEP. This will probably be fewer lines of code anyway. Let me give it a shot.

mssimpso updated this revision to Diff 90940.Mar 7 2017, 2:26 PM

Re-implemented the widening, as previously mentioned.

  • Removed the Legal->isUniform() case. Now, I just let the IRBuilder return a scalar GEP if all the operands are loop-invariant. If the result is scalar, I do the broadcast.
  • The above change exposes a likely bug in fixFirstOrderRecurrences, so I've fixed that as well. The IRBuilder constant-folded the GEP in first-order-recurrences.ll away. The problematic code assumes that an instruction in the old loop will map to an instruction in the new loop. The is probably not a safe assumption in general.

I'll fix the fixFirstOrderRecurrences bug in a separate patch and rebase. I was able to reproduce the issue in a different test case.

mssimpso updated this revision to Diff 91050.Mar 8 2017, 10:37 AM

Addressed fixFirstOrderRecurrence bug in a separate patch and rebased.

Ping. Michael/Elena, do you have any more feedback for this patch (and D30587)?

mkuper edited edge metadata.Mar 16 2017, 1:50 PM

Sorry, lost track of this. I'll try to get to this today/tomorrow, unless Elena does first.

No problem! Thanks, Michael!

Sorry for the delay, Matt.

This makes sense to me.
It was reasonable to special-case GEPs when vectorizing them was mostly pointless, because the vast majority of uses would be scalar. But I think the infrastructure now is good enough to handle it in a generic way. Thanks!

Is there a test that actually stores a vector of pointers?

lib/Transforms/Vectorize/LoopVectorize.cpp
5449

nit: "If there's no pointer operand or the pointer operand is not an instruction"

delena added inline comments.Mar 20 2017, 1:03 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

Do you have any real case with loop invariant GEP? Do you mean the last test case from first-order-recurrence.ll:

define void @PR29559() {
entry:
  br label %scalar.body

scalar.body:
  %i = phi i64 [ 0, %entry ], [ %i.next, %scalar.body ]
  %tmp2 = phi float* [ undef, %entry ], [ %tmp3, %scalar.body ]
  %tmp3 = getelementptr inbounds [3 x float], [3 x float]* undef, i64 0, i64 0
  %i.next = add nuw nsw i64 %i, 1
  %cond = icmp eq i64 %i.next, undef
  br i1 %cond, label %for.end, label %scalar.body

for.end:
  ret void
}

The %tmp2 and %tmp3 should be scalar.
In my understanding, if all operands of the GEP are loop invariant, the Load/Store is uniform. I just do not understand in which cases we'll need to broadcast the GEP.

Is there a test that actually stores a vector of pointers?

I'll add one and update the patch. Thanks, Michael!

lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I should add a test for the broadcast case to make this clear, shouldn't I? If we have a GEP like %tmp3 in the example you pasted that was stored to memory with a vector store, we would need a vector version of it, and it wouldn't be "uniform-after-vectorization". It's still "uniform" in the LAA sense because it has loop-invariant operands. Because if this, IRBuilder will give us a scalar GEP that we will then broadcast. I'll add the test and update.

delena added inline comments.Mar 20 2017, 5:09 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

If %tmp3 should be stored as a vector, the user (store inst) will broadcast it. You can keep it scalar.

mssimpso added inline comments.Mar 20 2017, 8:42 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I see your point, in that getVectorValue() will do the broadcast upon the first use of a loop-invariant value, and initialize the mapping. But I think your comment applies to the vectorization of all instructions, right? Not just GEPs? For example, if for some reason we found an "add i32 0, 0" inside the body of the loop, we currently vectorize it like any instruction. But we could instead skip vectorizing it and allow getVectorValue() to do the broadcast on-demand.

Would it make sense to have something like:

if (Legal->isUniform(&I))
  continue;

Before the switch statement in the main loop of vectorizeBlockInLoop()?

mssimpso added inline comments.Mar 20 2017, 10:17 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

To answer my previous question, no, we can't rely on getVectorValue() to perform the broadcast for "uniform" values. The function that performs the broadcast (getBroadcastInstrs) doesn't clone the original instruction from the old loop body into the new loop body. So we would end up with a broken module where the broadcast in the vector loop preheader uses a "uniform" value defined within the body of the scalar loop.

So going back to Elena's comment about %tmp3 being broadcast by the store that uses it - this doesn't work. We have to either vectorize or scalarize the GEP. Otherwise, getBroadcastInsts would literally broadcasts %tmp3 from the body of the original loop into the vector loop preheader, violating dominance.

Again, I'll add a test case to make this more concrete. Sorry for the noise.

mssimpso updated this revision to Diff 92355.Mar 20 2017, 10:44 AM
mssimpso marked an inline comment as done.

Addressed feedback from Michael and Elena.

  • Updated comment.
  • Added two new test cases. In the first, we have a loop-varying GEP that is stored to memory, and in the second, we have a loop-invariant GEP that is stored to memory. In the first, we create a vector GEP, and in the second, we recreate the scalar GEP and then broadcast it.
delena added inline comments.Mar 21 2017, 12:18 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I agree that your code works. But I'd organize the code in another way:

if (OrigLoop->hasLoopInvariantOperands(GEP) || VF ==1 || isUniformAfterVectorization(GEP, VF)) {

NewGEP = GEP->clone();
initScalar(GEP, NewGEP);

} else {

// build vector GEP exactly as you do
 ...
VectorLoopValueMap.initVector(&I, Entry);

}

Michael, what do you think?

mkuper added inline comments.Mar 21 2017, 1:20 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I think the current version is slightly less brittle, but I understand why you'd want to be explicit. I'm fine with either option, but if we go with the one you're suggesting, we'd still need an assert that the resulting GEP isn't scalar.

Also, this is different from the current version, right? The current version looks equivalent to checking that "OrigLoop->hasLoopInvariantOperands(GEP) || VF ==1".

4731

Assuming we stay with this version - do we really need the VF == 1 check here? Can we ever end up with NewGEP->getType()->isVectorTy() when VF == 1?
I think this would require the original GEP to have a vector type, and I believe we filter this out in canVectorizeInstrs(), regardless of the VF (that is, before we even start thinking about VFs)

delena added inline comments.Mar 21 2017, 1:50 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

isUniformAfterVectorization is redundant here, I agree.
vectorizeMemoryInstruction() creates another GEP for consecutive loads and stores.

we'd still need an assert that the resulting GEP isn't scalar.

Yes.

Something like this?

if (OrigLoop->hasLoopInvariantOperands(GEP)) {

  NewGEP = GEP->clone();
  initScalar(GEP, NewGEP);
} else if (VF ==1) {
  NewGEP = GEP->clone();
  VectorLoopValueMap.initVector(GEP, NewGEP);
} else {

  // build vector GEP exactly as you do
 . ..
   assert(NewGEP->getType()->isVectorTy());
  Entry[Part] = NewGep;

  VectorLoopValueMap.initVector(&I, Entry);
}
mssimpso added inline comments.Mar 21 2017, 4:03 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

I agree that we should be explicit, but I'm not a huge fan of this approach. This is not how initScalar and initVector work (unless you've intentionally omitted some code). They are called with a (Value *, EntryTy), so before using them, we have to create all the VF x UF values.

For your call to initScalar, we would be better off just calling scalarizeInstruction (which does this). But if we really should scalarize the GEP, we should have already done so with the check at the top of the loop. Note, though, that scalarizing the GEP will change what it's vector version will look like. Instead of having the broadcast, we would build the vector with a bunch of inserts. But again, we already know we need a vector version of the GEP, so why scalarize?

For the VF == 1 case, once you create the Entry and initialize all the elements, the code will look pretty much the same as the "else" case here. So why separate it?

4731

We need the check. The VF == 1 check is to ensure that we don't do the broadcast when unrolling. When unrolling, NewGEP will be scalar (what we want) and we don't want to try and broadcast to a vector with VF == 1.

mssimpso added inline comments.Mar 21 2017, 4:20 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

If we want to be more explicit here, I would suggest something like this (which is very similar to the first version of the patch), although I haven't tested this yet:

if (VF > 1 && OrigLoop->hasLoopInvariantOperands(GEP)) {
  // clone GEP and broadcast clone
  for (unsigned Part = 0; Part < UF; ++Part)
    Entry[Part] = /* broadcast clone */
} else {
  for (unsigned Part = 0; Part < UF; ++Part) {
    // build GEP as we do now
    // add an assert that the GEP we build has a vector type
    Entry[Part] = /* vector gep */
}
VectorLoopValueMap.initVector(&I, Entry);

So the loop-invariant case is separate and more explicit, but we still vectorize the GEP instead of trying to scalarize it.

delena added inline comments.Mar 21 2017, 6:54 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

Note, though, that scalarizing the GEP will change what it's vector version will look like. Instead of having the broadcast, we would build the vector with a bunch of inserts.

The scalarizeInstruction() works fine and I agree, we can use it instead of clone(). The "bunch of inserts" is inserted by getVectorvalue().
It happens because inside getVectorValue() we does not check for loop-invariant operands:
We only check that the instruction is in the list of uniforms:

if (Cost->isUniformAfterVectorization(I, VF)) {
   VectorValue = getBroadcastInstrs(getScalarValue(V, Part, 0));
 }

But again, we already know we need a vector version of the GEP, so why scalarize?

How do you know this? You do not check the users. If you build a splat vector and the GEP
is used in a scalar form, the scalar value will be extracted from the broadcast. Hopefully, another pass will resolve this redundancy.

Can you try to change getvectorValue() to

if (Cost->isUniformAfterVectorization(I, VF) || OrigLoop->hasLoopInvariantOperands()) {
   VectorValue = getBroadcastInstrs(getScalarValue(V, Part, 0));
 }
mssimpso added inline comments.Mar 21 2017, 8:19 AM
lib/Transforms/Vectorize/LoopVectorize.cpp
4717

The "bunch of inserts" is inserted by getVectorvalue(). It happens because inside getVectorValue() we does not check for loop-invariant operands:

That's right.

How do you know this? You do not check the users. If you build a splat vector and the GEP is used in a scalar form, the scalar value will be extracted from the broadcast.

That's the whole point of collectLoopScalars(). It looks at the users of the GEPs and determines which ones should be scalar and which ones should be vector. Note that I updated collectLoopScalars to be more precise as part of this patch. So by the time we reach the code here, we "know" that we want a vector version of the GEP because of how it's used. We check the scalars and scalarize if necessary at the top of the main loop here. I see no reason to second-guess the scalarization decision. If something needs to change regarding scalarization, I think it should be done over in collectLoopScalars.

To be clear, we will prefer the vector version of the GEP if it has vector users. That is, if we have a GEP with both scalar and vector users, we will now vectorize the GEP and then extract the values for the scalar users. Previously, since we always scalarized the GEPS, if we have the same GEP with both scalar and vector users, we would build the vector on-demand with inserts for the vector users.

Can you try to change getvectorValue()

I would rather do that in a separate patch, either before or after this one.

Regarding what happens to a GEP with loop-invariant operands, we should note that this is no different than any other kind instruction. If we have an "add" instruction inside the loop with loop-invariant operands, we will still vectorize it (the operands would be broadcast). The reason this has to be special-cased for the GEP is simply because for vector GEPs, the vector-typed arguments are optional. We only produce a vector of pointers if at least one argument of the GEP is vector-typed. For the loop-invariant case, we can either pick an argument to broadcast or broadcast the result. But again, my view is that the scalarization decision should be left to collectLoopScalars.

There's no other instruction where we check to see if it has loop-invariant operands and then decide to scalarize it. I'm not saying we can't do this, just that we should do the same thing for all instructions.

mssimpso updated this revision to Diff 92494.Mar 21 2017, 9:19 AM

Incorporated feedback from Elena and Michael. Thanks!

  • I separated out the loop-invariant case to make the code more explicit. But I'm still vectorizing this case rather than scalarizing it as suggested by Elena. I don't think we need to revisit the scalarization decision here.
  • I added an assert for the loop-varying case to ensure we actually create a vector of pointers.
  • I rewrote the comments to hopefully add some more clarity.
mssimpso updated this revision to Diff 92496.Mar 21 2017, 9:26 AM

Fixed a typo in the new comments.

delena accepted this revision.Mar 22 2017, 2:35 AM
delena added inline comments.
lib/Transforms/Vectorize/LoopVectorize.cpp
4709

This "IF" should be dropped, if the GEP with all LoopInvariantOperands will be in the Scalars and the getVectorValue() will know to braodcast it.
Please add TODO to the comments.

This revision is now accepted and ready to land.Mar 22 2017, 2:35 AM

Thanks for the review and discussion, Elena! I'll add the TODO before committing.

This revision was automatically updated to reflect the committed changes.

It looks like this commit caused a regression in the test-suite on SystemZ, I now get an assertion failure:

clang-5.0: /home/uweigand/llvm/llvm-head/lib/Transforms/Vectorize/LoopVectorize.cpp:2740: llvm::Value* {anonymous}::InnerLoopVectorizer::getScalarValue(llvm::Value*, unsigned int, unsigned int): Assertion `Lane > 0 ? !Cost->isUniformAfterVectorization(cast<Instruction>(V), VF) : true && "Uniform values only have lane zero"' failed.

when building MultiSource/Benchmarks/Prolangs-C/TimberWolfMC/analyze.c

I suspect this introduced a regression (llvm bisect says it is between r298618 and r298625). This can be reproduced with the reduced example:

int a, c, e, *b, d;
void fn1() {
  int **f = 0;
  b = f + a;
  for (; e; e++, d += c)
    f[e] = b + d;
}

https://bugs.llvm.org//show_bug.cgi?id=32414