xref: /freebsd/contrib/llvm-project/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Scheduler.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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 #include "llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h"
11 
12 namespace llvm::sandboxir {
13 
14 // TODO: Check if we can cache top/bottom to reduce compile-time.
getTop() const15 DGNode *SchedBundle::getTop() const {
16   DGNode *TopN = Nodes.front();
17   for (auto *N : drop_begin(Nodes)) {
18     if (N->getInstruction()->comesBefore(TopN->getInstruction()))
19       TopN = N;
20   }
21   return TopN;
22 }
23 
getBot() const24 DGNode *SchedBundle::getBot() const {
25   DGNode *BotN = Nodes.front();
26   for (auto *N : drop_begin(Nodes)) {
27     if (BotN->getInstruction()->comesBefore(N->getInstruction()))
28       BotN = N;
29   }
30   return BotN;
31 }
32 
cluster(BasicBlock::iterator Where)33 void SchedBundle::cluster(BasicBlock::iterator Where) {
34   for (auto *N : Nodes) {
35     auto *I = N->getInstruction();
36     if (I->getIterator() == Where)
37       ++Where; // Try to maintain bundle order.
38     I->moveBefore(*Where.getNodeParent(), Where);
39   }
40 }
41 
42 #ifndef NDEBUG
dump(raw_ostream & OS) const43 void SchedBundle::dump(raw_ostream &OS) const {
44   for (auto *N : Nodes)
45     OS << *N;
46 }
47 
dump() const48 void SchedBundle::dump() const {
49   dump(dbgs());
50   dbgs() << "\n";
51 }
52 #endif // NDEBUG
53 
54 #ifndef NDEBUG
dump(raw_ostream & OS) const55 void ReadyListContainer::dump(raw_ostream &OS) const {
56   auto ListCopy = List;
57   while (!ListCopy.empty()) {
58     OS << *ListCopy.top() << "\n";
59     ListCopy.pop();
60   }
61 }
62 
dump() const63 void ReadyListContainer::dump() const {
64   dump(dbgs());
65   dbgs() << "\n";
66 }
67 #endif // NDEBUG
68 
scheduleAndUpdateReadyList(SchedBundle & Bndl)69 void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) {
70   // Find where we should schedule the instructions.
71   assert(ScheduleTopItOpt && "Should have been set by now!");
72   auto Where = *ScheduleTopItOpt;
73   // Move all instructions in `Bndl` to `Where`.
74   Bndl.cluster(Where);
75   // Update the last scheduled bundle.
76   ScheduleTopItOpt = Bndl.getTop()->getInstruction()->getIterator();
77   // Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all
78   // dependency predecessors.
79   for (DGNode *N : Bndl) {
80     for (auto *DepN : N->preds(DAG)) {
81       DepN->decrUnscheduledSuccs();
82       if (DepN->ready() && !DepN->scheduled())
83         ReadyList.insert(DepN);
84     }
85     N->setScheduled(true);
86   }
87 }
88 
notifyCreateInstr(Instruction * I)89 void Scheduler::notifyCreateInstr(Instruction *I) {
90   // The DAG notifier should have run by now.
91   auto *N = DAG.getNode(I);
92   // If there is no DAG node for `I` it means that this is out of scope for the
93   // DAG and as such out of scope for the scheduler too, so nothing to do.
94   if (N == nullptr)
95     return;
96   // If the instruction is inserted below the top-of-schedule then we mark it as
97   // "scheduled".
98   bool IsScheduled = ScheduleTopItOpt &&
99                      *ScheduleTopItOpt != I->getParent()->end() &&
100                      (*ScheduleTopItOpt.value()).comesBefore(I);
101   if (IsScheduled)
102     N->setScheduled(true);
103   // If the new instruction is above the top of schedule we need to remove its
104   // dependency predecessors from the ready list and increment their
105   // `UnscheduledSuccs` counters.
106   if (!IsScheduled) {
107     for (auto *PredN : N->preds(DAG)) {
108       ReadyList.remove(PredN);
109       PredN->incrUnscheduledSuccs();
110     }
111   }
112 }
113 
createBundle(ArrayRef<Instruction * > Instrs)114 SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) {
115   SchedBundle::ContainerTy Nodes;
116   Nodes.reserve(Instrs.size());
117   for (auto *I : Instrs)
118     Nodes.push_back(DAG.getNode(I));
119   auto BndlPtr = std::make_unique<SchedBundle>(std::move(Nodes));
120   auto *Bndl = BndlPtr.get();
121   Bndls[Bndl] = std::move(BndlPtr);
122   return Bndl;
123 }
124 
eraseBundle(SchedBundle * SB)125 void Scheduler::eraseBundle(SchedBundle *SB) { Bndls.erase(SB); }
126 
tryScheduleUntil(ArrayRef<Instruction * > Instrs)127 bool Scheduler::tryScheduleUntil(ArrayRef<Instruction *> Instrs) {
128   // Create a bundle for Instrs. If it turns out the schedule is infeasible we
129   // will dismantle it.
130   auto *InstrsSB = createBundle(Instrs);
131   // Keep scheduling ready nodes until we either run out of ready nodes (i.e.,
132   // ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of
133   // which are collected in DeferredNodes) are all ready to schedule.
134   SmallVector<DGNode *> Retry;
135   bool KeepScheduling = true;
136   while (KeepScheduling) {
137     enum class TryScheduleRes {
138       Success,  ///> We successfully scheduled the bundle.
139       Failure,  ///> We failed to schedule the bundle.
140       Finished, ///> We successfully scheduled the bundle and it is the last
141                 /// bundle to be scheduled.
142     };
143     /// TryScheduleNode() attempts to schedule all DAG nodes in the bundle that
144     /// ReadyN is in. If it's not in a bundle it will create a singleton bundle
145     /// and will try to schedule it.
146     auto TryScheduleBndl = [this, InstrsSB](DGNode *ReadyN) -> TryScheduleRes {
147       auto *SB = ReadyN->getSchedBundle();
148       if (SB == nullptr) {
149         // If ReadyN does not belong to a bundle, create a singleton bundle
150         // and schedule it.
151         auto *SingletonSB = createBundle({ReadyN->getInstruction()});
152         scheduleAndUpdateReadyList(*SingletonSB);
153         return TryScheduleRes::Success;
154       }
155       if (SB->ready()) {
156         // Remove the rest of the bundle from the ready list.
157         // TODO: Perhaps change the Scheduler + ReadyList to operate on
158         // SchedBundles instead of DGNodes.
159         for (auto *N : *SB) {
160           if (N != ReadyN)
161             ReadyList.remove(N);
162         }
163         // If all nodes in the bundle are ready.
164         scheduleAndUpdateReadyList(*SB);
165         if (SB == InstrsSB)
166           // We just scheduled InstrsSB bundle, so we are done scheduling.
167           return TryScheduleRes::Finished;
168         return TryScheduleRes::Success;
169       }
170       return TryScheduleRes::Failure;
171     };
172     while (!ReadyList.empty()) {
173       auto *ReadyN = ReadyList.pop();
174       auto Res = TryScheduleBndl(ReadyN);
175       switch (Res) {
176       case TryScheduleRes::Success:
177         // We successfully scheduled ReadyN, keep scheduling.
178         continue;
179       case TryScheduleRes::Failure:
180         // We failed to schedule ReadyN, defer it to later and keep scheduling
181         // other ready instructions.
182         Retry.push_back(ReadyN);
183         continue;
184       case TryScheduleRes::Finished:
185         // We successfully scheduled the instruction bundle, so we are done.
186         return true;
187       }
188       llvm_unreachable("Unhandled TrySchedule() result");
189     }
190     // Try to schedule nodes from the Retry list.
191     KeepScheduling = false;
192     for (auto *N : make_early_inc_range(Retry)) {
193       auto Res = TryScheduleBndl(N);
194       if (Res == TryScheduleRes::Success) {
195         Retry.erase(find(Retry, N));
196         KeepScheduling = true;
197       }
198     }
199   }
200 
201   eraseBundle(InstrsSB);
202   return false;
203 }
204 
205 Scheduler::BndlSchedState
getBndlSchedState(ArrayRef<Instruction * > Instrs) const206 Scheduler::getBndlSchedState(ArrayRef<Instruction *> Instrs) const {
207   assert(!Instrs.empty() && "Expected non-empty bundle");
208   auto *N0 = DAG.getNode(Instrs[0]);
209   auto *SB0 = N0 != nullptr ? N0->getSchedBundle() : nullptr;
210   bool AllUnscheduled = SB0 == nullptr;
211   bool FullyScheduled = SB0 != nullptr && !SB0->isSingleton();
212   for (auto *I : drop_begin(Instrs)) {
213     auto *N = DAG.getNode(I);
214     auto *SB = N != nullptr ? N->getSchedBundle() : nullptr;
215     if (SB != nullptr) {
216       // We found a scheduled instr, so there is now way all are unscheduled.
217       AllUnscheduled = false;
218       if (SB->isSingleton()) {
219         // We found an instruction in a temporarily scheduled singleton. There
220         // is no way that all instructions are scheduled in the same bundle.
221         FullyScheduled = false;
222       }
223     }
224 
225     if (SB != SB0) {
226       // Either one of SB, SB0 is null, or they are in different bundles, so
227       // Instrs are definitely not in the same vector bundle.
228       FullyScheduled = false;
229       // One of SB, SB0 are in a vector bundle and they differ.
230       if ((SB != nullptr && !SB->isSingleton()) ||
231           (SB0 != nullptr && !SB0->isSingleton()))
232         return BndlSchedState::AlreadyScheduled;
233     }
234   }
235   return AllUnscheduled   ? BndlSchedState::NoneScheduled
236          : FullyScheduled ? BndlSchedState::FullyScheduled
237                           : BndlSchedState::TemporarilyScheduled;
238 }
239 
trimSchedule(ArrayRef<Instruction * > Instrs)240 void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) {
241   //                                |  Legend: N: DGNode
242   //  N <- DAGInterval.top()        |          B: SchedBundle
243   //  N                             |          *: Contains instruction in Instrs
244   //  B <- TopI (Top of schedule)   +-------------------------------------------
245   //  B
246   //  B *
247   //  B
248   //  B * <- LowestI (Lowest in Instrs)
249   //  B
250   //  N
251   //  N
252   //  N <- DAGInterval.bottom()
253   //
254   Instruction *TopI = &*ScheduleTopItOpt.value();
255   Instruction *LowestI = VecUtils::getLowest(Instrs);
256   // Destroy the singleton schedule bundles from LowestI all the way to the top.
257   for (auto *I = LowestI, *E = TopI->getPrevNode(); I != E;
258        I = I->getPrevNode()) {
259     auto *N = DAG.getNode(I);
260     if (N == nullptr)
261       continue;
262     auto *SB = N->getSchedBundle();
263     if (SB->isSingleton())
264       eraseBundle(SB);
265   }
266   // The DAG Nodes contain state like the number of UnscheduledSuccs and the
267   // Scheduled flag. We need to reset their state. We need to do this for all
268   // nodes from LowestI to the top of the schedule. DAG Nodes that are above the
269   // top of schedule that depend on nodes that got reset need to have their
270   // UnscheduledSuccs adjusted.
271   Interval<Instruction> ResetIntvl(TopI, LowestI);
272   for (Instruction &I : ResetIntvl) {
273     auto *N = DAG.getNode(&I);
274     N->resetScheduleState();
275     // Recompute UnscheduledSuccs for nodes not only in ResetIntvl but even for
276     // nodes above the top of schedule.
277     for (auto *PredN : N->preds(DAG))
278       PredN->incrUnscheduledSuccs();
279   }
280   // Refill the ready list by visiting all nodes from the top of DAG to LowestI.
281   ReadyList.clear();
282   Interval<Instruction> RefillIntvl(DAG.getInterval().top(), LowestI);
283   for (Instruction &I : RefillIntvl) {
284     auto *N = DAG.getNode(&I);
285     if (N->ready())
286       ReadyList.insert(N);
287   }
288 }
289 
trySchedule(ArrayRef<Instruction * > Instrs)290 bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
291   assert(all_of(drop_begin(Instrs),
292                 [Instrs](Instruction *I) {
293                   return I->getParent() == (*Instrs.begin())->getParent();
294                 }) &&
295          "Instrs not in the same BB, should have been rejected by Legality!");
296   // TODO: For now don't cross BBs.
297   if (!DAG.getInterval().empty()) {
298     auto *BB = DAG.getInterval().top()->getParent();
299     if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; }))
300       return false;
301   }
302   if (ScheduledBB == nullptr)
303     ScheduledBB = Instrs[0]->getParent();
304   // We don't support crossing BBs for now.
305   if (any_of(Instrs,
306              [this](Instruction *I) { return I->getParent() != ScheduledBB; }))
307     return false;
308   auto SchedState = getBndlSchedState(Instrs);
309   switch (SchedState) {
310   case BndlSchedState::FullyScheduled:
311     // Nothing to do.
312     return true;
313   case BndlSchedState::AlreadyScheduled:
314     // Instructions are part of a different vector schedule, so we can't
315     // schedule \p Instrs in the same bundle (without destroying the existing
316     // schedule).
317     return false;
318   case BndlSchedState::TemporarilyScheduled:
319     // If one or more instrs are already scheduled we need to destroy the
320     // top-most part of the schedule that includes the instrs in the bundle and
321     // re-schedule.
322     DAG.extend(Instrs);
323     trimSchedule(Instrs);
324     ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
325     return tryScheduleUntil(Instrs);
326   case BndlSchedState::NoneScheduled: {
327     // TODO: Set the window of the DAG that we are interested in.
328     if (!ScheduleTopItOpt)
329       // We start scheduling at the bottom instr of Instrs.
330       ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
331     // Extend the DAG to include Instrs.
332     Interval<Instruction> Extension = DAG.extend(Instrs);
333     // Add nodes to ready list.
334     for (auto &I : Extension) {
335       auto *N = DAG.getNode(&I);
336       if (N->ready())
337         ReadyList.insert(N);
338     }
339     // Try schedule all nodes until we can schedule Instrs back-to-back.
340     return tryScheduleUntil(Instrs);
341   }
342   }
343   llvm_unreachable("Unhandled BndlSchedState enum");
344 }
345 
346 #ifndef NDEBUG
dump(raw_ostream & OS) const347 void Scheduler::dump(raw_ostream &OS) const {
348   OS << "ReadyList:\n";
349   ReadyList.dump(OS);
350   OS << "Top of schedule: ";
351   if (ScheduleTopItOpt)
352     OS << **ScheduleTopItOpt;
353   else
354     OS << "Empty";
355   OS << "\n";
356 }
dump() const357 void Scheduler::dump() const { dump(dbgs()); }
358 #endif // NDEBUG
359 
360 } // namespace llvm::sandboxir
361