Page MenuHomePhabricator

[LoopVectorize] Optimise away the icmp when tail-folding for some low trip counts

Authored by david-arm on Mar 17 2022, 3:38 AM.



For low trip counts the vectoriser will attempt to create a single
predicated loop that folds the scalar tail into the vector body. For
some combinations of the trip count and the VF it is possible to
determine at compile time if there will only be a single vector
iteration. If so, we can avoid creating the comparison at the end of
the loop and just always branch to the loop exit. This improves the
code quality for smaller loops with low trip counts because the
compare + branch add a relatively high cost to the loop.

This optimisation may also apply for unpredicated vector loops with
low trip counts too, hence the change in test X86/pr42674.ll.

Diff Detail

Unit TestsFailed

60,180 msx64 debian > AddressSanitizer-x86_64-linux-dynamic.TestCases::scariness_score_test.cpp
Script: -- : 'RUN: at line 4'; /var/lib/buildkite-agent/builds/llvm-project/build/./bin/clang --driver-mode=g++ -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -m64 -shared-libasan -O0 /var/lib/buildkite-agent/builds/llvm-project/compiler-rt/test/asan/TestCases/scariness_score_test.cpp -o /var/lib/buildkite-agent/builds/llvm-project/build/projects/compiler-rt/test/asan/X86_64LinuxDynamicConfig/TestCases/Output/scariness_score_test.cpp.tmp
60,090 msx64 debian > AddressSanitizer-x86_64-linux.TestCases::scariness_score_test.cpp
Script: -- : 'RUN: at line 4'; /var/lib/buildkite-agent/builds/llvm-project/build/./bin/clang --driver-mode=g++ -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -m64 -O0 /var/lib/buildkite-agent/builds/llvm-project/compiler-rt/test/asan/TestCases/scariness_score_test.cpp -o /var/lib/buildkite-agent/builds/llvm-project/build/projects/compiler-rt/test/asan/X86_64LinuxConfig/TestCases/Output/scariness_score_test.cpp.tmp

Event Timeline

david-arm created this revision.Mar 17 2022, 3:38 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 17 2022, 3:38 AM
david-arm requested review of this revision.Mar 17 2022, 3:38 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 17 2022, 3:38 AM

Thanks for this patch Dave, I've left a few comments.

Perhaps it's worth highlighting in the commit message that this is more of a problem for scalable vectors, where the branch/compare isn't constant folded or instcombined away that easily as it is for fixed-width vectors.


Initially I wanted to suggest creating a new (unconditional) branch instruction here, but at this point you don't know which VF will be chosen for the given VPlan, so we have to defer this decision until codegen.


There's nothing wrong with it, but perhaps it's a bit unfortunate this has to be passed into the VPInstruction as an operand. It seems more like a bit of useful knowledge of the loop that the VPInstruction could use to optimise the branch, rather than an actual operand for the conceptual BranchOnCount intrinsic. If the trip-count of the loop is constant, maybe we can store that information as a piece of state somewhere.

@fhahn what would be the desired place to add such information about the loop? My understanding was that the uses of the InnerLoopVectorizer in VPlan were being phased out, is that right?


nit: How about

Value *ConstCmp = nullptr;
if (auto *C = dyn_cast<ConstantInt>(TC))
  if (C->getZExtValue() <= State.UF * State.VF.getKnownMinValue())
    ConstCmp = Builder.getInt1(true);
Value *Cond = ConstCmp ? ConstCmp : Builder.CreateICmpEQ(IV, VTC);

I noticed some of this code here has been removed and I'm not sure if this change is then still required. In any case this patch needs a rebase.

david-arm added inline comments.Apr 27 2022, 8:49 AM

Yeah I'm not terribly happy about adding this as an operand, but I wasn't sure how else to pass this. I could possibly add a constant trip count to the VPTransformState, which is available when generating the instruction?


It would have to be something like this I think:

Value *ConstCmp = nullptr;
if (auto *C = dyn_cast<ConstantInt>(TC)) {
  uint64_t TCVal = C->getZExtValue();
  if (TCVal && TCVal  <= State.UF * State.VF.getKnownMinValue())
    ConstCmp = Builder.getInt1(true);
Value *Cond = ConstCmp ? ConstCmp : Builder.CreateICmpEQ(IV, VTC);

because I'm pretty sure I found a test where the trip count was actually defined as a zero constant. I'm happy to change it to the code above, but not sure it looks much better to be honest. :)


Yeah I noticed that too. Will fix in a new patch!

sdesmalen added inline comments.Apr 27 2022, 8:59 AM

If the trip-count is zero, wouldn't true be the correct value for the condition?

fhahn added inline comments.Apr 27 2022, 2:05 PM

Hmm, I am not sure if doing this late in codegen is the right place. If we simplify the condition and effectively remove the loop, it would be good to remove the branch-on-count recipe directly in VPlan. I *think* we should have almost everything needed to do that already in place. Let me check.

david-arm added inline comments.May 10 2022, 5:30 AM

Hi @fhahn, have you had a chance to look into this at all?

fhahn added inline comments.May 16 2022, 12:43 AM

Unfortunately not yet, I've still got a backlog of other things to work through :(. Hopefully I'll be able to check this week, otherwise I think we should proceed with this patch next week.

david-arm added inline comments.May 20 2022, 7:10 AM

So the problem with treating zero-trip counts the same as non-zero trip counts is that the loop vectoriser considers loops like this as having a zero trip count:

  br label %loop.body

  %iv = phi i32 [ 0, %entry ], [, %loop.body ]
  %a = extractvalue { i64, i64 } %sv, 0
  %b = extractvalue { i64, i64 } %sv, 1
  %addr = getelementptr i64, i64* %dst, i32 %iv
  %add = add i64 %a, %b
  store i64 %add, i64* %addr = add nsw i32 %iv, 1
  %cond = icmp ne i32, 0
  br i1 %cond, label %loop.body, label %exit

  ret void

even though this loop is actually going to execute UINT32_MAX times before finally the IV overflows back to 0! This came from a real test:


If I fold away the comparison and always branch out of the loop then I've changed the behaviour of the original scalar loop. So I think we either have to:

  1. Fix the vectoriser to report the trip count as UINT32_MAX for loops like this, or
  2. Only apply my optimisation for low trip counts > 0
david-arm updated this revision to Diff 431302.May 23 2022, 1:35 AM
  • I've removed the need for a separate TripCount VPValue in the VPlan class because we're always going to need the original scalar trip count, and the value is the same for each Part anyway. Now we just store a copy in the VPTransformState so that the execute() functions can access it.
david-arm marked 7 inline comments as done.May 23 2022, 1:36 AM
fhahn added inline comments.Mon, May 30, 2:43 PM

Took a bit longer, but D126680 is what simplification directly on VPlan would look like. It depends on a few upcoming improvements though.

david-arm abandoned this revision.Tue, Jun 7, 5:48 AM

Already fixed by an alternative implementation - D126680