Skip to content

Commit b26c514

Browse files
[SandboxVectorizer] Add container class to track and manage SeedBundles (#112048)
1 parent 9b8dbe2 commit b26c514

File tree

3 files changed

+266
-3
lines changed

3 files changed

+266
-3
lines changed

llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/SeedCollector.h

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class SeedBundle {
5454
NumUnusedBits += Utils::getNumBits(I);
5555
}
5656

57+
virtual void insert(Instruction *I, ScalarEvolution &SE) = 0;
58+
5759
unsigned getFirstUnusedElementIdx() const {
5860
for (unsigned ElmIdx : seq<unsigned>(0, Seeds.size()))
5961
if (!isUsed(ElmIdx))
@@ -96,6 +98,9 @@ class SeedBundle {
9698
MutableArrayRef<Instruction *>
9799
getSlice(unsigned StartIdx, unsigned MaxVecRegBits, bool ForcePowOf2);
98100

101+
/// \Returns the number of seed elements in the bundle.
102+
std::size_t size() const { return Seeds.size(); }
103+
99104
protected:
100105
SmallVector<Instruction *> Seeds;
101106
/// The lanes that we have already vectorized.
@@ -148,7 +153,7 @@ template <typename LoadOrStoreT> class MemSeedBundle : public SeedBundle {
148153
"Expected LoadInst or StoreInst!");
149154
assert(isa<LoadOrStoreT>(MemI) && "Expected Load or Store!");
150155
}
151-
void insert(sandboxir::Instruction *I, ScalarEvolution &SE) {
156+
void insert(sandboxir::Instruction *I, ScalarEvolution &SE) override {
152157
assert(isa<LoadOrStoreT>(I) && "Expected a Store or a Load!");
153158
auto Cmp = [&SE](Instruction *I0, Instruction *I1) {
154159
return Utils::atLowerAddress(cast<LoadOrStoreT>(I0),
@@ -162,5 +167,123 @@ template <typename LoadOrStoreT> class MemSeedBundle : public SeedBundle {
162167
using StoreSeedBundle = MemSeedBundle<sandboxir::StoreInst>;
163168
using LoadSeedBundle = MemSeedBundle<sandboxir::LoadInst>;
164169

170+
/// Class to conveniently track Seeds within SeedBundles. Saves newly collected
171+
/// seeds in the proper bundle. Supports constant-time removal, as seeds and
172+
/// entire bundles are vectorized and marked used to signify removal. Iterators
173+
/// skip bundles that are completely used.
174+
class SeedContainer {
175+
// Use the same key for different seeds if they are the same type and
176+
// reference the same pointer, even if at different offsets. This directs
177+
// potentially vectorizable seeds into the same bundle.
178+
using KeyT = std::tuple<Value *, Type *, Instruction::Opcode>;
179+
// Trying to vectorize too many seeds at once is expensive in
180+
// compilation-time. Use a vector of bundles (all with the same key) to
181+
// partition the candidate set into more manageable units. Each bundle is
182+
// size-limited by sbvec-seed-bundle-size-limit. TODO: There might be a
183+
// better way to divide these than by simple insertion order.
184+
using ValT = SmallVector<std::unique_ptr<SeedBundle>>;
185+
using BundleMapT = MapVector<KeyT, ValT>;
186+
// Map from {pointer, Type, Opcode} to a vector of bundles.
187+
BundleMapT Bundles;
188+
// Allows finding a particular Instruction's bundle.
189+
DenseMap<Instruction *, SeedBundle *> SeedLookupMap;
190+
191+
ScalarEvolution &SE;
192+
193+
template <typename LoadOrStoreT> KeyT getKey(LoadOrStoreT *LSI) const;
194+
195+
public:
196+
SeedContainer(ScalarEvolution &SE) : SE(SE) {}
197+
198+
class iterator {
199+
BundleMapT *Map = nullptr;
200+
BundleMapT::iterator MapIt;
201+
ValT *Vec = nullptr;
202+
size_t VecIdx;
203+
204+
public:
205+
using difference_type = std::ptrdiff_t;
206+
using value_type = SeedBundle;
207+
using pointer = value_type *;
208+
using reference = value_type &;
209+
using iterator_category = std::input_iterator_tag;
210+
211+
/// Iterates over the \p Map of SeedBundle Vectors, starting at \p MapIt,
212+
/// and \p Vec at \p VecIdx, skipping vectors that are completely
213+
/// used. Iteration order over the keys {Pointer, Type, Opcode} follows
214+
/// DenseMap iteration order. For a given key, the vectors of
215+
/// SeedBundles will be returned in insertion order. As in the
216+
/// pseudo code below:
217+
///
218+
/// for Key,Value in Bundles
219+
/// for SeedBundleVector in Value
220+
/// for SeedBundle in SeedBundleVector
221+
/// if !SeedBundle.allUsed() ...
222+
///
223+
/// Note that the bundles themselves may have additional ordering, created
224+
/// by the subclasses by insertAt. The bundles themselves may also have used
225+
/// instructions.
226+
iterator(BundleMapT &Map, BundleMapT::iterator MapIt, ValT *Vec, int VecIdx)
227+
: Map(&Map), MapIt(MapIt), Vec(Vec), VecIdx(VecIdx) {}
228+
value_type &operator*() {
229+
assert(Vec != nullptr && "Already at end!");
230+
return *(*Vec)[VecIdx];
231+
}
232+
// Skip completely used bundles by repeatedly calling operator++().
233+
void skipUsed() {
234+
while (Vec && VecIdx < Vec->size() && this->operator*().allUsed())
235+
++(*this);
236+
}
237+
// Iterators iterate over the bundles
238+
iterator &operator++() {
239+
assert(VecIdx >= 0 && "Already at end!");
240+
++VecIdx;
241+
if (VecIdx >= Vec->size()) {
242+
assert(MapIt != Map->end() && "Already at end!");
243+
VecIdx = 0;
244+
++MapIt;
245+
if (MapIt != Map->end())
246+
Vec = &MapIt->second;
247+
else {
248+
Vec = nullptr;
249+
}
250+
}
251+
skipUsed();
252+
return *this;
253+
}
254+
iterator operator++(int) {
255+
auto Copy = *this;
256+
++(*this);
257+
return Copy;
258+
}
259+
bool operator==(const iterator &Other) const {
260+
assert(Map == Other.Map && "Iterator of different objects!");
261+
return MapIt == Other.MapIt && VecIdx == Other.VecIdx;
262+
}
263+
bool operator!=(const iterator &Other) const { return !(*this == Other); }
264+
};
265+
using const_iterator = BundleMapT::const_iterator;
266+
template <typename LoadOrStoreT> void insert(LoadOrStoreT *LSI);
267+
// To support constant-time erase, these just mark the element used, rather
268+
// than actually removing them from the bundle.
269+
bool erase(Instruction *I);
270+
bool erase(const KeyT &Key) { return Bundles.erase(Key); }
271+
iterator begin() {
272+
if (Bundles.empty())
273+
return end();
274+
auto BeginIt =
275+
iterator(Bundles, Bundles.begin(), &Bundles.begin()->second, 0);
276+
BeginIt.skipUsed();
277+
return BeginIt;
278+
}
279+
iterator end() { return iterator(Bundles, Bundles.end(), nullptr, 0); }
280+
unsigned size() const { return Bundles.size(); }
281+
282+
#ifndef NDEBUG
283+
LLVM_DUMP_METHOD void dump() const;
284+
#endif // NDEBUG
285+
};
286+
165287
} // namespace llvm::sandboxir
288+
166289
#endif // LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_SEEDCOLLECTOR_H

llvm/lib/Transforms/Vectorize/SandboxVectorizer/SeedCollector.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
using namespace llvm;
2020
namespace llvm::sandboxir {
2121

22+
cl::opt<unsigned> SeedBundleSizeLimit(
23+
"sbvec-seed-bundle-size-limit", cl::init(32), cl::Hidden,
24+
cl::desc("Limit the size of the seed bundle to cap compilation time."));
25+
2226
MutableArrayRef<Instruction *> SeedBundle::getSlice(unsigned StartIdx,
2327
unsigned MaxVecRegBits,
2428
bool ForcePowerOf2) {
@@ -61,4 +65,68 @@ MutableArrayRef<Instruction *> SeedBundle::getSlice(unsigned StartIdx,
6165
return {};
6266
}
6367

68+
template <typename LoadOrStoreT>
69+
SeedContainer::KeyT SeedContainer::getKey(LoadOrStoreT *LSI) const {
70+
assert((isa<LoadInst>(LSI) || isa<StoreInst>(LSI)) &&
71+
"Expected Load or Store!");
72+
Value *Ptr = Utils::getMemInstructionBase(LSI);
73+
Instruction::Opcode Op = LSI->getOpcode();
74+
Type *Ty = Utils::getExpectedType(LSI);
75+
if (auto *VTy = dyn_cast<VectorType>(Ty))
76+
Ty = VTy->getElementType();
77+
return {Ptr, Ty, Op};
78+
}
79+
80+
// Explicit instantiations
81+
template SeedContainer::KeyT
82+
SeedContainer::getKey<LoadInst>(LoadInst *LSI) const;
83+
template SeedContainer::KeyT
84+
SeedContainer::getKey<StoreInst>(StoreInst *LSI) const;
85+
86+
bool SeedContainer::erase(Instruction *I) {
87+
assert((isa<LoadInst>(I) || isa<StoreInst>(I)) && "Expected Load or Store!");
88+
auto It = SeedLookupMap.find(I);
89+
if (It == SeedLookupMap.end())
90+
return false;
91+
SeedBundle *Bndl = It->second;
92+
Bndl->setUsed(I);
93+
return true;
94+
}
95+
96+
template <typename LoadOrStoreT> void SeedContainer::insert(LoadOrStoreT *LSI) {
97+
// Find the bundle containing seeds for this symbol and type-of-access.
98+
auto &BundleVec = Bundles[getKey(LSI)];
99+
// Fill this vector of bundles front to back so that only the last bundle in
100+
// the vector may have available space. This avoids iteration to find one with
101+
// space.
102+
if (BundleVec.empty() || BundleVec.back()->size() == SeedBundleSizeLimit)
103+
BundleVec.emplace_back(std::make_unique<MemSeedBundle<LoadOrStoreT>>(LSI));
104+
else
105+
BundleVec.back()->insert(LSI, SE);
106+
107+
SeedLookupMap[LSI] = BundleVec.back().get();
108+
}
109+
110+
// Explicit instantiations
111+
template void SeedContainer::insert<LoadInst>(LoadInst *);
112+
template void SeedContainer::insert<StoreInst>(StoreInst *);
113+
114+
#ifndef NDEBUG
115+
void SeedContainer::dump() const {
116+
for (const auto &Pair : Bundles) {
117+
auto [I, Ty, Opc] = Pair.first;
118+
const auto &SeedsVec = Pair.second;
119+
std::string RefType = dyn_cast<LoadInst>(I) ? "Load"
120+
: dyn_cast<StoreInst>(I) ? "Store"
121+
: "Other";
122+
dbgs() << "[Inst=" << *I << " Ty=" << Ty << " " << RefType << "]\n";
123+
for (const auto &SeedPtr : SeedsVec) {
124+
SeedPtr->dump(dbgs());
125+
dbgs() << "\n";
126+
}
127+
}
128+
dbgs() << "\n";
129+
}
130+
#endif // NDEBUG
131+
64132
} // namespace llvm::sandboxir

llvm/unittests/Transforms/Vectorize/SandboxVectorizer/SeedCollectorTest.cpp

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ struct SeedBundleTest : public testing::Test {
4040
}
4141
};
4242

43+
// Stub class to make the abstract base class testable.
44+
class SeedBundleForTest : public sandboxir::SeedBundle {
45+
public:
46+
using sandboxir::SeedBundle::SeedBundle;
47+
void insert(sandboxir::Instruction *I, ScalarEvolution &SE) override {
48+
insertAt(Seeds.end(), I);
49+
}
50+
};
51+
4352
TEST_F(SeedBundleTest, SeedBundle) {
4453
parseIR(C, R"IR(
4554
define void @foo(float %v0, i32 %i0, i16 %i1, i8 %i2) {
@@ -66,7 +75,7 @@ define void @foo(float %v0, i32 %i0, i16 %i1, i8 %i2) {
6675
// Assume first two instructions are identical in the number of bits.
6776
const unsigned IOBits = sandboxir::Utils::getNumBits(I0, DL);
6877
// Constructor
69-
sandboxir::SeedBundle SBO(I0);
78+
SeedBundleForTest SBO(I0);
7079
EXPECT_EQ(*SBO.begin(), I0);
7180
// getNumUnusedBits after constructor
7281
EXPECT_EQ(SBO.getNumUnusedBits(), IOBits);
@@ -103,7 +112,7 @@ define void @foo(float %v0, i32 %i0, i16 %i1, i8 %i2) {
103112
EXPECT_EQ(BundleBits, 88u);
104113
auto Seeds = Insts;
105114
// Constructor
106-
sandboxir::SeedBundle SB1(std::move(Seeds));
115+
SeedBundleForTest SB1(std::move(Seeds));
107116
// getNumUnusedBits after constructor
108117
EXPECT_EQ(SB1.getNumUnusedBits(), BundleBits);
109118
// setUsed with index
@@ -196,3 +205,66 @@ define void @foo(ptr %ptrA, float %val, ptr %ptr) {
196205
sandboxir::LoadSeedBundle LB(std::move(Loads), SE);
197206
EXPECT_THAT(LB, testing::ElementsAre(L0, L1, L2, L3));
198207
}
208+
209+
TEST_F(SeedBundleTest, Container) {
210+
parseIR(C, R"IR(
211+
define void @foo(ptr %ptrA, float %val, ptr %ptrB) {
212+
bb:
213+
%gepA0 = getelementptr float, ptr %ptrA, i32 0
214+
%gepA1 = getelementptr float, ptr %ptrA, i32 1
215+
%gepB0 = getelementptr float, ptr %ptrB, i32 0
216+
%gepB1 = getelementptr float, ptr %ptrB, i32 1
217+
store float %val, ptr %gepA0
218+
store float %val, ptr %gepA1
219+
store float %val, ptr %gepB0
220+
store float %val, ptr %gepB1
221+
ret void
222+
}
223+
)IR");
224+
Function &LLVMF = *M->getFunction("foo");
225+
226+
DominatorTree DT(LLVMF);
227+
TargetLibraryInfoImpl TLII;
228+
TargetLibraryInfo TLI(TLII);
229+
DataLayout DL(M->getDataLayout());
230+
LoopInfo LI(DT);
231+
AssumptionCache AC(LLVMF);
232+
ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);
233+
234+
sandboxir::Context Ctx(C);
235+
auto &F = *Ctx.createFunction(&LLVMF);
236+
auto &BB = *F.begin();
237+
auto It = std::next(BB.begin(), 4);
238+
auto *S0 = cast<sandboxir::StoreInst>(&*It++);
239+
auto *S1 = cast<sandboxir::StoreInst>(&*It++);
240+
auto *S2 = cast<sandboxir::StoreInst>(&*It++);
241+
auto *S3 = cast<sandboxir::StoreInst>(&*It++);
242+
sandboxir::SeedContainer SC(SE);
243+
// Check begin() end() when empty.
244+
EXPECT_EQ(SC.begin(), SC.end());
245+
246+
SC.insert(S0);
247+
SC.insert(S1);
248+
SC.insert(S2);
249+
SC.insert(S3);
250+
unsigned Cnt = 0;
251+
SmallVector<sandboxir::SeedBundle *> Bndls;
252+
for (auto &SeedBndl : SC) {
253+
EXPECT_EQ(SeedBndl.size(), 2u);
254+
++Cnt;
255+
Bndls.push_back(&SeedBndl);
256+
}
257+
EXPECT_EQ(Cnt, 2u);
258+
259+
// Mark them "Used" to check if operator++ skips them in the next loop.
260+
for (auto *SeedBndl : Bndls)
261+
for (auto Lane : seq<unsigned>(SeedBndl->size()))
262+
SeedBndl->setUsed(Lane);
263+
// Check if iterator::operator++ skips used lanes.
264+
Cnt = 0;
265+
for (auto &SeedBndl : SC) {
266+
(void)SeedBndl;
267+
++Cnt;
268+
}
269+
EXPECT_EQ(Cnt, 0u);
270+
}

0 commit comments

Comments
 (0)