Skip to content

Commit 0cc7381

Browse files
authored
[SandboxVec][Scheduler] Don't insert scheduled instrs into the ready list (#127688)
In a particular scenario (see test) we used to insert scheduled instructions into the ready list. This patch fixes this by fixing the trimSchedule() function.
1 parent 3430bc3 commit 0cc7381

File tree

4 files changed

+131
-19
lines changed

4 files changed

+131
-19
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ class DGNode {
123123
--UnscheduledSuccs;
124124
}
125125
void incrUnscheduledSuccs() { ++UnscheduledSuccs; }
126+
void resetScheduleState() {
127+
UnscheduledSuccs = 0;
128+
Scheduled = false;
129+
}
126130
/// \Returns true if all dependent successors have been scheduled.
127131
bool ready() const { return UnscheduledSuccs == 0; }
128132
/// \Returns true if this node has been scheduled.

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ class ReadyListContainer {
6161

6262
public:
6363
ReadyListContainer() : List(Cmp) {}
64-
void insert(DGNode *N) { List.push(N); }
64+
void insert(DGNode *N) {
65+
#ifndef NDEBUG
66+
assert(!N->scheduled() && "Don't insert a scheduled node!");
67+
auto ListCopy = List;
68+
while (!ListCopy.empty()) {
69+
DGNode *Top = ListCopy.top();
70+
ListCopy.pop();
71+
assert(Top != N && "Node already exists in ready list!");
72+
}
73+
#endif
74+
List.push(N);
75+
}
6576
DGNode *pop() {
6677
auto *Back = List.top();
6778
List.pop();

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

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) {
7777
// Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all
7878
// dependency predecessors.
7979
for (DGNode *N : Bndl) {
80-
N->setScheduled(true);
8180
for (auto *DepN : N->preds(DAG)) {
8281
DepN->decrUnscheduledSuccs();
83-
if (DepN->ready())
82+
if (DepN->ready() && !DepN->scheduled())
8483
ReadyList.insert(DepN);
8584
}
85+
N->setScheduled(true);
8686
}
8787
}
8888

@@ -188,6 +188,19 @@ Scheduler::getBndlSchedState(ArrayRef<Instruction *> Instrs) const {
188188
}
189189

190190
void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) {
191+
// | Legend: N: DGNode
192+
// N <- DAGInterval.top() | B: SchedBundle
193+
// N | *: Contains instruction in Instrs
194+
// B <- TopI (Top of schedule) +-------------------------------------------
195+
// B
196+
// B *
197+
// B
198+
// B * <- LowestI (Lowest in Instrs)
199+
// B
200+
// N
201+
// N
202+
// N <- DAGInterval.bottom()
203+
//
191204
Instruction *TopI = &*ScheduleTopItOpt.value();
192205
Instruction *LowestI = VecUtils::getLowest(Instrs);
193206
// Destroy the schedule bundles from LowestI all the way to the top.
@@ -199,13 +212,28 @@ void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) {
199212
if (auto *SB = N->getSchedBundle())
200213
eraseBundle(SB);
201214
}
202-
// TODO: For now we clear the DAG. Trim view once it gets implemented.
203-
Bndls.clear();
204-
DAG.clear();
205-
206-
// Since we are scheduling NewRegion from scratch, we clear the ready lists.
207-
// The nodes currently in the list may not be ready after clearing the View.
215+
// The DAG Nodes contain state like the number of UnscheduledSuccs and the
216+
// Scheduled flag. We need to reset their state. We need to do this for all
217+
// nodes from LowestI to the top of the schedule. DAG Nodes that are above the
218+
// top of schedule that depend on nodes that got reset need to have their
219+
// UnscheduledSuccs adjusted.
220+
Interval<Instruction> ResetIntvl(TopI, LowestI);
221+
for (Instruction &I : ResetIntvl) {
222+
auto *N = DAG.getNode(&I);
223+
N->resetScheduleState();
224+
// Recompute UnscheduledSuccs for nodes not only in ResetIntvl but even for
225+
// nodes above the top of schedule.
226+
for (auto *PredN : N->preds(DAG))
227+
PredN->incrUnscheduledSuccs();
228+
}
229+
// Refill the ready list by visiting all nodes from the top of DAG to LowestI.
208230
ReadyList.clear();
231+
Interval<Instruction> RefillIntvl(DAG.getInterval().top(), LowestI);
232+
for (Instruction &I : RefillIntvl) {
233+
auto *N = DAG.getNode(&I);
234+
if (N->ready())
235+
ReadyList.insert(N);
236+
}
209237
}
210238

211239
bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
@@ -214,6 +242,12 @@ bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
214242
return I->getParent() == (*Instrs.begin())->getParent();
215243
}) &&
216244
"Instrs not in the same BB, should have been rejected by Legality!");
245+
// TODO: For now don't cross BBs.
246+
if (!DAG.getInterval().empty()) {
247+
auto *BB = DAG.getInterval().top()->getParent();
248+
if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; }))
249+
return false;
250+
}
217251
if (ScheduledBB == nullptr)
218252
ScheduledBB = Instrs[0]->getParent();
219253
// We don't support crossing BBs for now.
@@ -230,21 +264,13 @@ bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
230264
// top-most part of the schedule that includes the instrs in the bundle and
231265
// re-schedule.
232266
trimSchedule(Instrs);
233-
ScheduleTopItOpt = std::nullopt;
234-
[[fallthrough]];
267+
ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
268+
return tryScheduleUntil(Instrs);
235269
case BndlSchedState::NoneScheduled: {
236270
// TODO: Set the window of the DAG that we are interested in.
237271
if (!ScheduleTopItOpt)
238272
// We start scheduling at the bottom instr of Instrs.
239273
ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
240-
241-
// TODO: For now don't cross BBs.
242-
if (!DAG.getInterval().empty()) {
243-
auto *BB = DAG.getInterval().top()->getParent();
244-
if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; }))
245-
return false;
246-
}
247-
248274
// Extend the DAG to include Instrs.
249275
Interval<Instruction> Extension = DAG.extend(Instrs);
250276
// Add nodes to ready list.

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,77 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1) {
246246
EXPECT_TRUE(Sched.trySchedule({L0, L1}));
247247
}
248248

249+
// Check scheduling in the following order: {A0,A1},{B0,B1},{C0,C1},{D0,D1}
250+
// assuming program order: B0,B1,C0,C1,D0,D1,E0,D1.
251+
// This will effectively schedule nodes below already scheduled nodes, which
252+
// can expose issues in the code that adds nodes to the ready list.
253+
// For example, we schedule {D0,D1} while {C0,C1} are scheduled and there is
254+
// a dependency D0->C0 and D1->C1.
255+
//
256+
// {A0,A1} {B0,B1} {C0,C1} {D0,D1}
257+
// B0,B1 | S
258+
// |\ |
259+
// | C0,C1 | | S | S
260+
// | | \ | |
261+
// | | D0,D1 | | S
262+
// | / |
263+
// A0,A1 | S | S
264+
// +------------------------+
265+
// | Legend |: DAG |
266+
// | S: Scheduled |
267+
TEST_F(SchedulerTest, ScheduledPredecessors) {
268+
parseIR(C, R"IR(
269+
define void @foo(ptr noalias %ptrA0, ptr noalias %ptrA1,
270+
ptr noalias %ptrB0, ptr noalias %ptrB1,
271+
ptr noalias %ptrD0, ptr noalias %ptrD1) {
272+
%B0 = load i8, ptr %ptrB0
273+
%B1 = load i8, ptr %ptrB1
274+
%C0 = add i8 %B0, 0
275+
%C1 = add i8 %B1, 1
276+
store i8 %C0, ptr %ptrD0
277+
store i8 %C1, ptr %ptrD1
278+
store i8 %B0, ptr %ptrA0
279+
store i8 %B1, ptr %ptrA1
280+
ret void
281+
}
282+
)IR");
283+
llvm::Function *LLVMF = &*M->getFunction("foo");
284+
sandboxir::Context Ctx(C);
285+
auto *F = Ctx.createFunction(LLVMF);
286+
auto *BB = &*F->begin();
287+
auto It = BB->begin();
288+
auto *B1 = cast<sandboxir::LoadInst>(&*It++);
289+
auto *B0 = cast<sandboxir::LoadInst>(&*It++);
290+
auto *C1 = cast<sandboxir::BinaryOperator>(&*It++);
291+
auto *C0 = cast<sandboxir::BinaryOperator>(&*It++);
292+
auto *D1 = cast<sandboxir::StoreInst>(&*It++);
293+
auto *D0 = cast<sandboxir::StoreInst>(&*It++);
294+
auto *A1 = cast<sandboxir::StoreInst>(&*It++);
295+
auto *A0 = cast<sandboxir::StoreInst>(&*It++);
296+
auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
297+
298+
sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
299+
EXPECT_TRUE(Sched.trySchedule({A0, A1}));
300+
// NOTE: We schedule the intermediate nodes between {A0,A1} and {B0,B1} by
301+
// hand one by one to make sure they are scheduled in that order because
302+
// the scheduler may reorder them a bit if we let it do it.
303+
EXPECT_TRUE(Sched.trySchedule(D0));
304+
EXPECT_TRUE(Sched.trySchedule(D1));
305+
EXPECT_TRUE(Sched.trySchedule(C0));
306+
EXPECT_TRUE(Sched.trySchedule(C1));
307+
EXPECT_TRUE(Sched.trySchedule({B0, B1}));
308+
// At this point all nodes must have been scheduled from B0,B1 to A0,A1.
309+
// The ones in between are scheduled as single-instruction nodes.
310+
// So when we attempt to schedule {C0,C1} we will need to reschedule.
311+
// At this point we will trim the schedule from {C0,C1} upwards.
312+
EXPECT_TRUE(Sched.trySchedule({C0, C1}));
313+
// Now the schedule should only contain {C0,C1} which should be marked as
314+
// "scheduled".
315+
// {D0,D1} are below {C0,C1}, so we grow the DAG downwards, while
316+
// {C0,C1} are marked as "scheduled" above them.
317+
EXPECT_TRUE(Sched.trySchedule({D0, D1}));
318+
}
319+
249320
TEST_F(SchedulerTest, DontCrossBBs) {
250321
parseIR(C, R"IR(
251322
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) {

0 commit comments

Comments
 (0)