Skip to content

Commit 6b99a7b

Browse files
authored
[LV] Provide utility routine to find uncounted exit recipes (#152530)
Splitting out just the recipe finding code from #148626 into a utility function (along with the extra pattern matchers). Hopefully this makes reviewing a bit easier. Added a gtest, since this isn't actually used anywhere yet.
1 parent 44a1f7e commit 6b99a7b

File tree

7 files changed

+245
-2
lines changed

7 files changed

+245
-2
lines changed

llvm/lib/Transforms/Vectorize/VPlanPatternMatch.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ m_BranchOnCount(const Op0_t &Op0, const Op1_t &Op1) {
356356
return m_VPInstruction<VPInstruction::BranchOnCount>(Op0, Op1);
357357
}
358358

359+
template <typename Op0_t>
360+
inline VPInstruction_match<VPInstruction::AnyOf, Op0_t>
361+
m_AnyOf(const Op0_t &Op0) {
362+
return m_VPInstruction<VPInstruction::AnyOf>(Op0);
363+
}
364+
359365
template <unsigned Opcode, typename Op0_t>
360366
inline AllRecipe_match<Opcode, Op0_t> m_Unary(const Op0_t &Op0) {
361367
return AllRecipe_match<Opcode, Op0_t>(Op0);
@@ -714,6 +720,29 @@ m_Intrinsic(const T0 &Op0, const T1 &Op1, const T2 &Op2, const T3 &Op3) {
714720
return m_CombineAnd(m_Intrinsic<IntrID>(Op0, Op1, Op2), m_Argument<3>(Op3));
715721
}
716722

723+
struct live_in_vpvalue {
724+
template <typename ITy> bool match(ITy *V) const {
725+
VPValue *Val = dyn_cast<VPValue>(V);
726+
return Val && Val->isLiveIn();
727+
}
728+
};
729+
730+
inline live_in_vpvalue m_LiveIn() { return live_in_vpvalue(); }
731+
732+
template <typename SubPattern_t> struct OneUse_match {
733+
SubPattern_t SubPattern;
734+
735+
OneUse_match(const SubPattern_t &SP) : SubPattern(SP) {}
736+
737+
template <typename OpTy> bool match(OpTy *V) {
738+
return V->hasOneUse() && SubPattern.match(V);
739+
}
740+
};
741+
742+
template <typename T> inline OneUse_match<T> m_OneUse(const T &SubPattern) {
743+
return SubPattern;
744+
}
745+
717746
} // namespace VPlanPatternMatch
718747
} // namespace llvm
719748

llvm/lib/Transforms/Vectorize/VPlanUtils.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,101 @@ VPBasicBlock *vputils::getFirstLoopHeader(VPlan &Plan, VPDominatorTree &VPDT) {
140140
});
141141
return I == DepthFirst.end() ? nullptr : cast<VPBasicBlock>(*I);
142142
}
143+
144+
std::optional<VPValue *>
145+
vputils::getRecipesForUncountableExit(VPlan &Plan,
146+
SmallVectorImpl<VPRecipeBase *> &Recipes,
147+
SmallVectorImpl<VPRecipeBase *> &GEPs) {
148+
using namespace llvm::VPlanPatternMatch;
149+
// Given a VPlan like the following (just including the recipes contributing
150+
// to loop control exiting here, not the actual work), we're looking to match
151+
// the recipes contributing to the uncountable exit condition comparison
152+
// (here, vp<%4>) back to either live-ins or the address nodes for the load
153+
// used as part of the uncountable exit comparison so that we can copy them
154+
// to a preheader and rotate the address in the loop to the next vector
155+
// iteration.
156+
//
157+
// Currently, the address of the load is restricted to a GEP with 2 operands
158+
// and a live-in base address. This constraint may be relaxed later.
159+
//
160+
// VPlan ' for UF>=1' {
161+
// Live-in vp<%0> = VF
162+
// Live-in ir<64> = original trip-count
163+
//
164+
// entry:
165+
// Successor(s): preheader, vector.ph
166+
//
167+
// vector.ph:
168+
// Successor(s): vector loop
169+
//
170+
// <x1> vector loop: {
171+
// vector.body:
172+
// EMIT vp<%2> = CANONICAL-INDUCTION ir<0>
173+
// vp<%3> = SCALAR-STEPS vp<%2>, ir<1>, vp<%0>
174+
// CLONE ir<%ee.addr> = getelementptr ir<0>, vp<%3>
175+
// WIDEN ir<%ee.load> = load ir<%ee.addr>
176+
// WIDEN vp<%4> = icmp eq ir<%ee.load>, ir<0>
177+
// EMIT vp<%5> = any-of vp<%4>
178+
// EMIT vp<%6> = add vp<%2>, vp<%0>
179+
// EMIT vp<%7> = icmp eq vp<%6>, ir<64>
180+
// EMIT vp<%8> = or vp<%5>, vp<%7>
181+
// EMIT branch-on-cond vp<%8>
182+
// No successors
183+
// }
184+
// Successor(s): middle.block
185+
//
186+
// middle.block:
187+
// Successor(s): preheader
188+
//
189+
// preheader:
190+
// No successors
191+
// }
192+
193+
// Find the uncountable loop exit condition.
194+
auto *Region = Plan.getVectorLoopRegion();
195+
VPValue *UncountableCondition = nullptr;
196+
if (!match(Region->getExitingBasicBlock()->getTerminator(),
197+
m_BranchOnCond(m_OneUse(m_c_BinaryOr(
198+
m_AnyOf(m_VPValue(UncountableCondition)), m_VPValue())))))
199+
return std::nullopt;
200+
201+
SmallVector<VPValue *, 4> Worklist;
202+
Worklist.push_back(UncountableCondition);
203+
while (!Worklist.empty()) {
204+
VPValue *V = Worklist.pop_back_val();
205+
206+
// Any value defined outside the loop does not need to be copied.
207+
if (V->isDefinedOutsideLoopRegions())
208+
continue;
209+
210+
// FIXME: Remove the single user restriction; it's here because we're
211+
// starting with the simplest set of loops we can, and multiple
212+
// users means needing to add PHI nodes in the transform.
213+
if (V->getNumUsers() > 1)
214+
return std::nullopt;
215+
216+
VPValue *Op1, *Op2;
217+
// Walk back through recipes until we find at least one load from memory.
218+
if (match(V, m_ICmp(m_VPValue(Op1), m_VPValue(Op2)))) {
219+
Worklist.push_back(Op1);
220+
Worklist.push_back(Op2);
221+
Recipes.push_back(V->getDefiningRecipe());
222+
} else if (auto *Load = dyn_cast<VPWidenLoadRecipe>(V)) {
223+
// Reject masked loads for the time being; they make the exit condition
224+
// more complex.
225+
if (Load->isMasked())
226+
return std::nullopt;
227+
228+
VPValue *GEP = Load->getAddr();
229+
if (!match(GEP, m_GetElementPtr(m_LiveIn(), m_VPValue())))
230+
return std::nullopt;
231+
232+
Recipes.push_back(Load);
233+
Recipes.push_back(GEP->getDefiningRecipe());
234+
GEPs.push_back(GEP->getDefiningRecipe());
235+
} else
236+
return std::nullopt;
237+
}
238+
239+
return UncountableCondition;
240+
}

llvm/lib/Transforms/Vectorize/VPlanUtils.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ bool isUniformAcrossVFsAndUFs(VPValue *V);
101101
/// Returns the header block of the first, top-level loop, or null if none
102102
/// exist.
103103
VPBasicBlock *getFirstLoopHeader(VPlan &Plan, VPDominatorTree &VPDT);
104+
105+
/// Returns the VPValue representing the uncountable exit comparison used by
106+
/// AnyOf if the recipes it depends on can be traced back to live-ins and
107+
/// the addresses (in GEP/PtrAdd form) of any (non-masked) load used in
108+
/// generating the values for the comparison. The recipes are stored in
109+
/// \p Recipes, and recipes forming an address for a load are also added to
110+
/// \p GEPs.
111+
std::optional<VPValue *>
112+
getRecipesForUncountableExit(VPlan &Plan,
113+
SmallVectorImpl<VPRecipeBase *> &Recipes,
114+
SmallVectorImpl<VPRecipeBase *> &GEPs);
104115
} // namespace vputils
105116

106117
//===----------------------------------------------------------------------===//

llvm/lib/Transforms/Vectorize/VPlanValue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ class LLVM_ABI_FOR_TEST VPValue {
148148
return Current != user_end();
149149
}
150150

151+
bool hasOneUse() const { return getNumUsers() == 1; }
152+
151153
void replaceAllUsesWith(VPValue *New);
152154

153155
/// Go through the uses list for this VPValue and make each use point to \p

llvm/unittests/Transforms/Vectorize/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ add_llvm_unittest(VectorizeTests
1414
VPlanHCFGTest.cpp
1515
VPlanPatternMatchTest.cpp
1616
VPlanSlpTest.cpp
17+
VPlanUncountableExitTest.cpp
1718
VPlanVerifierTest.cpp
1819
)

llvm/unittests/Transforms/Vectorize/VPlanTestBase.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class VPlanTestIRBase : public testing::Test {
6565
}
6666

6767
/// Build the VPlan for the loop starting from \p LoopHeader.
68-
VPlanPtr buildVPlan(BasicBlock *LoopHeader) {
68+
VPlanPtr buildVPlan(BasicBlock *LoopHeader, bool HasUncountableExit = false) {
6969
Function &F = *LoopHeader->getParent();
7070
assert(!verifyFunction(F) && "input function must be valid");
7171
doAnalysis(F);
@@ -75,7 +75,7 @@ class VPlanTestIRBase : public testing::Test {
7575
auto Plan = VPlanTransforms::buildVPlan0(L, *LI, IntegerType::get(*Ctx, 64),
7676
{}, PSE);
7777

78-
VPlanTransforms::handleEarlyExits(*Plan, false);
78+
VPlanTransforms::handleEarlyExits(*Plan, HasUncountableExit);
7979
VPlanTransforms::addMiddleCheck(*Plan, true, false);
8080

8181
VPlanTransforms::createLoopRegions(*Plan);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//===- llvm/unittests/Transforms/Vectorize/VPlanUncountableExitTest.cpp ---===//
2+
//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "../lib/Transforms/Vectorize/VPlan.h"
11+
#include "../lib/Transforms/Vectorize/VPlanUtils.h"
12+
#include "VPlanTestBase.h"
13+
#include "llvm/ADT/SmallVector.h"
14+
#include "gtest/gtest.h"
15+
16+
namespace llvm {
17+
18+
namespace {
19+
class VPUncountableExitTest : public VPlanTestIRBase {};
20+
21+
TEST_F(VPUncountableExitTest, FindUncountableExitRecipes) {
22+
const char *ModuleString =
23+
"define void @f(ptr %array, ptr %pred) {\n"
24+
"entry:\n"
25+
" br label %for.body\n"
26+
"for.body:\n"
27+
" %iv = phi i64 [ 0, %entry ], [ %iv.next, %for.inc ]\n"
28+
" %st.addr = getelementptr inbounds i16, ptr %array, i64 %iv\n"
29+
" %data = load i16, ptr %st.addr, align 2\n"
30+
" %inc = add nsw i16 %data, 1\n"
31+
" store i16 %inc, ptr %st.addr, align 2\n"
32+
" %uncountable.addr = getelementptr inbounds nuw i16, ptr %pred, i64 "
33+
"%iv\n"
34+
" %uncountable.val = load i16, ptr %uncountable.addr, align 2\n"
35+
" %uncountable.cond = icmp sgt i16 %uncountable.val, 500\n"
36+
" br i1 %uncountable.cond, label %exit, label %for.inc\n"
37+
"for.inc:\n"
38+
" %iv.next = add nuw nsw i64 %iv, 1\n"
39+
" %countable.cond = icmp eq i64 %iv.next, 20\n"
40+
" br i1 %countable.cond, label %exit, label %for.body\n"
41+
"exit:\n"
42+
" ret void\n"
43+
"}\n";
44+
45+
Module &M = parseModule(ModuleString);
46+
47+
Function *F = M.getFunction("f");
48+
BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor();
49+
auto Plan = buildVPlan(LoopHeader, /*HasUncountableExit=*/true);
50+
VPlanTransforms::tryToConvertVPInstructionsToVPRecipes(
51+
Plan, [](PHINode *P) { return nullptr; }, *TLI);
52+
VPlanTransforms::runPass(VPlanTransforms::optimize, *Plan);
53+
54+
SmallVector<VPRecipeBase *> Recipes;
55+
SmallVector<VPRecipeBase *> GEPs;
56+
57+
std::optional<VPValue *> UncountableCondition =
58+
vputils::getRecipesForUncountableExit(*Plan, Recipes, GEPs);
59+
ASSERT_TRUE(UncountableCondition.has_value());
60+
ASSERT_EQ(GEPs.size(), 1ull);
61+
ASSERT_EQ(Recipes.size(), 3ull);
62+
}
63+
64+
TEST_F(VPUncountableExitTest, NoUncountableExit) {
65+
const char *ModuleString =
66+
"define void @f(ptr %array, ptr %pred) {\n"
67+
"entry:\n"
68+
" br label %for.body\n"
69+
"for.body:\n"
70+
" %iv = phi i64 [ 0, %entry ], [ %iv.next, %for.body ]\n"
71+
" %st.addr = getelementptr inbounds i16, ptr %array, i64 %iv\n"
72+
" %data = load i16, ptr %st.addr, align 2\n"
73+
" %inc = add nsw i16 %data, 1\n"
74+
" store i16 %inc, ptr %st.addr, align 2\n"
75+
" %iv.next = add nuw nsw i64 %iv, 1\n"
76+
" %countable.cond = icmp eq i64 %iv.next, 20\n"
77+
" br i1 %countable.cond, label %exit, label %for.body\n"
78+
"exit:\n"
79+
" ret void\n"
80+
"}\n";
81+
82+
Module &M = parseModule(ModuleString);
83+
84+
Function *F = M.getFunction("f");
85+
BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor();
86+
auto Plan = buildVPlan(LoopHeader);
87+
VPlanTransforms::tryToConvertVPInstructionsToVPRecipes(
88+
Plan, [](PHINode *P) { return nullptr; }, *TLI);
89+
VPlanTransforms::runPass(VPlanTransforms::optimize, *Plan);
90+
91+
SmallVector<VPRecipeBase *> Recipes;
92+
SmallVector<VPRecipeBase *> GEPs;
93+
94+
std::optional<VPValue *> UncountableCondition =
95+
vputils::getRecipesForUncountableExit(*Plan, Recipes, GEPs);
96+
ASSERT_FALSE(UncountableCondition.has_value());
97+
ASSERT_EQ(GEPs.size(), 0ull);
98+
ASSERT_EQ(Recipes.size(), 0ull);
99+
}
100+
101+
} // namespace
102+
} // namespace llvm

0 commit comments

Comments
 (0)