Skip to content

Commit 1d09925

Browse files
authored
[SandboxVec][Scheduler] Boilerplate and initial implementation. (llvm#112449)
This patch implements a ready-list-based scheduler that operates on DependencyGraph. It is used by the sandbox vectorizer to test the legality of vectorizing a group of instrs. SchedBundle is a helper container, containing all DGNodes that correspond to the instructions that we are attempting to schedule with trySchedule(Instrs).
1 parent 6d347fd commit 1d09925

File tree

8 files changed

+540
-2
lines changed

8 files changed

+540
-2
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,15 @@ class DGNode {
113113
virtual ~DGNode() = default;
114114
/// \Returns the number of unscheduled successors.
115115
unsigned getNumUnscheduledSuccs() const { return UnscheduledSuccs; }
116+
void decrUnscheduledSuccs() {
117+
assert(UnscheduledSuccs > 0 && "Counting error!");
118+
--UnscheduledSuccs;
119+
}
120+
/// \Returns true if all dependent successors have been scheduled.
121+
bool ready() const { return UnscheduledSuccs == 0; }
116122
/// \Returns true if this node has been scheduled.
117123
bool scheduled() const { return Scheduled; }
124+
void setScheduled(bool NewVal) { Scheduled = NewVal; }
118125
/// \Returns true if this is before \p Other in program order.
119126
bool comesBefore(const DGNode *Other) { return I->comesBefore(Other->I); }
120127
using iterator = PredIterator;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===- Scheduler.h ----------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This is the bottom-up list scheduler used by the vectorizer. It is used for
10+
// checking the legality of vectorization and for scheduling instructions in
11+
// such a way that makes vectorization possible, if legal.
12+
//
13+
// The legality check is performed by `trySchedule(Instrs)`, which will try to
14+
// schedule the IR until all instructions in `Instrs` can be scheduled together
15+
// back-to-back. If this fails then it is illegal to vectorize `Instrs`.
16+
//
17+
// Internally the scheduler uses the vectorizer-specific DependencyGraph class.
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
#ifndef LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_SCHEDULER_H
22+
#define LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_SCHEDULER_H
23+
24+
#include "llvm/SandboxIR/Instruction.h"
25+
#include "llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h"
26+
#include <queue>
27+
28+
namespace llvm::sandboxir {
29+
30+
class PriorityCmp {
31+
public:
32+
bool operator()(const DGNode *N1, const DGNode *N2) {
33+
// TODO: This should be a hierarchical comparator.
34+
return N1->getInstruction()->comesBefore(N2->getInstruction());
35+
}
36+
};
37+
38+
/// The list holding nodes that are ready to schedule. Used by the scheduler.
39+
class ReadyListContainer {
40+
PriorityCmp Cmp;
41+
/// Control/Other dependencies are not modeled by the DAG to save memory.
42+
/// These have to be modeled in the ready list for correctness.
43+
/// This means that the list will hold back nodes that need to meet such
44+
/// unmodeled dependencies.
45+
std::priority_queue<DGNode *, std::vector<DGNode *>, PriorityCmp> List;
46+
47+
public:
48+
ReadyListContainer() : List(Cmp) {}
49+
void insert(DGNode *N) { List.push(N); }
50+
DGNode *pop() {
51+
auto *Back = List.top();
52+
List.pop();
53+
return Back;
54+
}
55+
bool empty() const { return List.empty(); }
56+
#ifndef NDEBUG
57+
void dump(raw_ostream &OS) const;
58+
LLVM_DUMP_METHOD void dump() const;
59+
#endif // NDEBUG
60+
};
61+
62+
/// The nodes that need to be scheduled back-to-back in a single scheduling
63+
/// cycle form a SchedBundle.
64+
class SchedBundle {
65+
public:
66+
using ContainerTy = SmallVector<DGNode *, 4>;
67+
68+
private:
69+
ContainerTy Nodes;
70+
71+
public:
72+
SchedBundle() = default;
73+
SchedBundle(ContainerTy &&Nodes) : Nodes(std::move(Nodes)) {}
74+
using iterator = ContainerTy::iterator;
75+
using const_iterator = ContainerTy::const_iterator;
76+
iterator begin() { return Nodes.begin(); }
77+
iterator end() { return Nodes.end(); }
78+
const_iterator begin() const { return Nodes.begin(); }
79+
const_iterator end() const { return Nodes.end(); }
80+
/// \Returns the bundle node that comes before the others in program order.
81+
DGNode *getTop() const;
82+
/// \Returns the bundle node that comes after the others in program order.
83+
DGNode *getBot() const;
84+
/// Move all bundle instructions to \p Where back-to-back.
85+
void cluster(BasicBlock::iterator Where);
86+
#ifndef NDEBUG
87+
void dump(raw_ostream &OS) const;
88+
LLVM_DUMP_METHOD void dump() const;
89+
#endif
90+
};
91+
92+
/// The list scheduler.
93+
class Scheduler {
94+
ReadyListContainer ReadyList;
95+
DependencyGraph DAG;
96+
std::optional<BasicBlock::iterator> ScheduleTopItOpt;
97+
SmallVector<std::unique_ptr<SchedBundle>> Bndls;
98+
99+
/// \Returns a scheduling bundle containing \p Instrs.
100+
SchedBundle *createBundle(ArrayRef<Instruction *> Instrs);
101+
/// Schedule nodes until we can schedule \p Instrs back-to-back.
102+
bool tryScheduleUntil(ArrayRef<Instruction *> Instrs);
103+
/// Schedules all nodes in \p Bndl, marks them as scheduled, updates the
104+
/// UnscheduledSuccs counter of all dependency predecessors, and adds any of
105+
/// them that become ready to the ready list.
106+
void scheduleAndUpdateReadyList(SchedBundle &Bndl);
107+
108+
/// Disable copies.
109+
Scheduler(const Scheduler &) = delete;
110+
Scheduler &operator=(const Scheduler &) = delete;
111+
112+
public:
113+
Scheduler(AAResults &AA) : DAG(AA) {}
114+
~Scheduler() {}
115+
116+
bool trySchedule(ArrayRef<Instruction *> Instrs);
117+
118+
#ifndef NDEBUG
119+
void dump(raw_ostream &OS) const;
120+
LLVM_DUMP_METHOD void dump() const;
121+
#endif
122+
};
123+
124+
} // namespace llvm::sandboxir
125+
126+
#endif // LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_SCHEDULER_H

llvm/lib/Transforms/Vectorize/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_llvm_component_library(LLVMVectorize
99
SandboxVectorizer/Passes/RegionsFromMetadata.cpp
1010
SandboxVectorizer/SandboxVectorizer.cpp
1111
SandboxVectorizer/SandboxVectorizerPassBuilder.cpp
12+
SandboxVectorizer/Scheduler.cpp
1213
SandboxVectorizer/SeedCollector.cpp
1314
SLPVectorizer.cpp
1415
Vectorize.cpp

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ bool PredIterator::operator==(const PredIterator &Other) const {
6060

6161
#ifndef NDEBUG
6262
void DGNode::print(raw_ostream &OS, bool PrintDeps) const {
63-
OS << *I << " USuccs:" << UnscheduledSuccs << "\n";
63+
OS << *I << " USuccs:" << UnscheduledSuccs << " Sched:" << Scheduled << "\n";
6464
}
6565
void DGNode::dump() const { print(dbgs()); }
6666
void MemDGNode::print(raw_ostream &OS, bool PrintDeps) const {
@@ -249,6 +249,10 @@ void DependencyGraph::setDefUseUnscheduledSuccs(
249249
// Walk over all instructions in "BotInterval" and update the counter
250250
// of operands that are in "TopInterval".
251251
for (Instruction &BotI : BotInterval) {
252+
auto *BotN = getNode(&BotI);
253+
// Skip scheduled nodes.
254+
if (BotN->scheduled())
255+
continue;
252256
for (Value *Op : BotI.operands()) {
253257
auto *OpI = dyn_cast<Instruction>(Op);
254258
if (OpI == nullptr)
@@ -286,7 +290,9 @@ void DependencyGraph::createNewNodes(const Interval<Instruction> &NewInterval) {
286290
MemDGNodeIntervalBuilder::getBotMemDGNode(TopInterval, *this);
287291
MemDGNode *LinkBotN =
288292
MemDGNodeIntervalBuilder::getTopMemDGNode(BotInterval, *this);
289-
assert(LinkTopN->comesBefore(LinkBotN) && "Wrong order!");
293+
assert((LinkTopN == nullptr || LinkBotN == nullptr ||
294+
LinkTopN->comesBefore(LinkBotN)) &&
295+
"Wrong order!");
290296
if (LinkTopN != nullptr && LinkBotN != nullptr) {
291297
LinkTopN->setNextNode(LinkBotN);
292298
LinkBotN->setPrevNode(LinkTopN);
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//===- Scheduler.cpp ------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Transforms/Vectorize/SandboxVectorizer/Scheduler.h"
10+
11+
namespace llvm::sandboxir {
12+
13+
// TODO: Check if we can cache top/bottom to reduce compile-time.
14+
DGNode *SchedBundle::getTop() const {
15+
DGNode *TopN = Nodes.front();
16+
for (auto *N : drop_begin(Nodes)) {
17+
if (N->getInstruction()->comesBefore(TopN->getInstruction()))
18+
TopN = N;
19+
}
20+
return TopN;
21+
}
22+
23+
DGNode *SchedBundle::getBot() const {
24+
DGNode *BotN = Nodes.front();
25+
for (auto *N : drop_begin(Nodes)) {
26+
if (BotN->getInstruction()->comesBefore(N->getInstruction()))
27+
BotN = N;
28+
}
29+
return BotN;
30+
}
31+
32+
void SchedBundle::cluster(BasicBlock::iterator Where) {
33+
for (auto *N : Nodes) {
34+
auto *I = N->getInstruction();
35+
if (I->getIterator() == Where)
36+
++Where; // Try to maintain bundle order.
37+
I->moveBefore(*Where.getNodeParent(), Where);
38+
}
39+
}
40+
41+
#ifndef NDEBUG
42+
void SchedBundle::dump(raw_ostream &OS) const {
43+
for (auto *N : Nodes)
44+
OS << *N;
45+
}
46+
47+
void SchedBundle::dump() const {
48+
dump(dbgs());
49+
dbgs() << "\n";
50+
}
51+
#endif // NDEBUG
52+
53+
#ifndef NDEBUG
54+
void ReadyListContainer::dump(raw_ostream &OS) const {
55+
auto ListCopy = List;
56+
while (!ListCopy.empty()) {
57+
OS << *ListCopy.top() << "\n";
58+
ListCopy.pop();
59+
}
60+
}
61+
62+
void ReadyListContainer::dump() const {
63+
dump(dbgs());
64+
dbgs() << "\n";
65+
}
66+
#endif // NDEBUG
67+
68+
void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) {
69+
// Find where we should schedule the instructions.
70+
assert(ScheduleTopItOpt && "Should have been set by now!");
71+
auto Where = *ScheduleTopItOpt;
72+
// Move all instructions in `Bndl` to `Where`.
73+
Bndl.cluster(Where);
74+
// Update the last scheduled bundle.
75+
ScheduleTopItOpt = Bndl.getTop()->getInstruction()->getIterator();
76+
// Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all
77+
// dependency predecessors.
78+
for (DGNode *N : Bndl) {
79+
N->setScheduled(true);
80+
for (auto *DepN : N->preds(DAG)) {
81+
// TODO: preds() should not return nullptr.
82+
if (DepN == nullptr)
83+
continue;
84+
DepN->decrUnscheduledSuccs();
85+
if (DepN->ready())
86+
ReadyList.insert(DepN);
87+
}
88+
}
89+
}
90+
91+
SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) {
92+
SchedBundle::ContainerTy Nodes;
93+
Nodes.reserve(Instrs.size());
94+
for (auto *I : Instrs)
95+
Nodes.push_back(DAG.getNode(I));
96+
auto BndlPtr = std::make_unique<SchedBundle>(std::move(Nodes));
97+
auto *Bndl = BndlPtr.get();
98+
Bndls.push_back(std::move(BndlPtr));
99+
return Bndl;
100+
}
101+
102+
bool Scheduler::tryScheduleUntil(ArrayRef<Instruction *> Instrs) {
103+
// Use a set of instructions, instead of `Instrs` for fast lookups.
104+
DenseSet<Instruction *> InstrsToDefer(Instrs.begin(), Instrs.end());
105+
// This collects the nodes that correspond to instructions found in `Instrs`
106+
// that have just become ready. These nodes won't be scheduled right away.
107+
SmallVector<DGNode *, 8> DeferredNodes;
108+
109+
// Keep scheduling ready nodes until we either run out of ready nodes (i.e.,
110+
// ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of
111+
// which are collected in DeferredNodes) are all ready to schedule.
112+
while (!ReadyList.empty()) {
113+
auto *ReadyN = ReadyList.pop();
114+
if (InstrsToDefer.contains(ReadyN->getInstruction())) {
115+
// If the ready instruction is one of those in `Instrs`, then we don't
116+
// schedule it right away. Instead we defer it until we can schedule it
117+
// along with the rest of the instructions in `Instrs`, at the same
118+
// time in a single scheduling bundle.
119+
DeferredNodes.push_back(ReadyN);
120+
bool ReadyToScheduleDeferred = DeferredNodes.size() == Instrs.size();
121+
if (ReadyToScheduleDeferred) {
122+
scheduleAndUpdateReadyList(*createBundle(Instrs));
123+
return true;
124+
}
125+
} else {
126+
// If the ready instruction is not found in `Instrs`, then we wrap it in a
127+
// scheduling bundle and schedule it right away.
128+
scheduleAndUpdateReadyList(*createBundle({ReadyN->getInstruction()}));
129+
}
130+
}
131+
assert(DeferredNodes.size() != Instrs.size() &&
132+
"We should have succesfully scheduled and early-returned!");
133+
return false;
134+
}
135+
136+
bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
137+
assert(all_of(drop_begin(Instrs),
138+
[Instrs](Instruction *I) {
139+
return I->getParent() == (*Instrs.begin())->getParent();
140+
}) &&
141+
"Instrs not in the same BB!");
142+
// Extend the DAG to include Instrs.
143+
Interval<Instruction> Extension = DAG.extend(Instrs);
144+
// TODO: Set the window of the DAG that we are interested in.
145+
// We start scheduling at the bottom instr of Instrs.
146+
auto getBottomI = [](ArrayRef<Instruction *> Instrs) -> Instruction * {
147+
return *min_element(Instrs,
148+
[](auto *I1, auto *I2) { return I1->comesBefore(I2); });
149+
};
150+
ScheduleTopItOpt = std::next(getBottomI(Instrs)->getIterator());
151+
// Add nodes to ready list.
152+
for (auto &I : Extension) {
153+
auto *N = DAG.getNode(&I);
154+
if (N->ready())
155+
ReadyList.insert(N);
156+
}
157+
// Try schedule all nodes until we can schedule Instrs back-to-back.
158+
return tryScheduleUntil(Instrs);
159+
}
160+
161+
#ifndef NDEBUG
162+
void Scheduler::dump(raw_ostream &OS) const {
163+
OS << "ReadyList:\n";
164+
ReadyList.dump(OS);
165+
}
166+
void Scheduler::dump() const { dump(dbgs()); }
167+
#endif // NDEBUG
168+
169+
} // namespace llvm::sandboxir

llvm/unittests/Transforms/Vectorize/SandboxVectorizer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ add_llvm_unittest(SandboxVectorizerTests
1111
DependencyGraphTest.cpp
1212
IntervalTest.cpp
1313
LegalityTest.cpp
14+
SchedulerTest.cpp
1415
SeedCollectorTest.cpp
1516
)

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,18 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
254254
EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // N1
255255
EXPECT_EQ(N1->getNumUnscheduledSuccs(), 0u);
256256
EXPECT_EQ(N2->getNumUnscheduledSuccs(), 0u);
257+
258+
// Check decrUnscheduledSuccs.
259+
N0->decrUnscheduledSuccs();
260+
EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u);
261+
#ifndef NDEBUG
262+
EXPECT_DEATH(N0->decrUnscheduledSuccs(), ".*Counting.*");
263+
#endif // NDEBUG
264+
265+
// Check scheduled(), setScheduled().
266+
EXPECT_FALSE(N0->scheduled());
267+
N0->setScheduled(true);
268+
EXPECT_TRUE(N0->scheduled());
257269
}
258270

259271
TEST_F(DependencyGraphTest, Preds) {
@@ -773,4 +785,16 @@ define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %v4, i8 %v5) {
773785
EXPECT_EQ(S4N->getNumUnscheduledSuccs(), 1u); // S5N
774786
EXPECT_EQ(S5N->getNumUnscheduledSuccs(), 0u);
775787
}
788+
789+
{
790+
// Check UnscheduledSuccs when a node is scheduled
791+
sandboxir::DependencyGraph DAG(getAA(*LLVMF));
792+
DAG.extend({S2, S2});
793+
auto *S2N = cast<sandboxir::MemDGNode>(DAG.getNode(S2));
794+
S2N->setScheduled(true);
795+
796+
DAG.extend({S1, S1});
797+
auto *S1N = cast<sandboxir::MemDGNode>(DAG.getNode(S1));
798+
EXPECT_EQ(S1N->getNumUnscheduledSuccs(), 0u); // S1 is scheduled
799+
}
776800
}

0 commit comments

Comments
 (0)