xref: /llvm-project/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Scheduler.cpp (revision b178c2d63e0701655046dfd2ead195b36e0df397)
11d09925bSvporpo //===- Scheduler.cpp ------------------------------------------------------===//
21d09925bSvporpo //
31d09925bSvporpo // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
41d09925bSvporpo // See https://llvm.org/LICENSE.txt for license information.
51d09925bSvporpo // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
61d09925bSvporpo //
71d09925bSvporpo //===----------------------------------------------------------------------===//
81d09925bSvporpo 
91d09925bSvporpo #include "llvm/Transforms/Vectorize/SandboxVectorizer/Scheduler.h"
10f7ef7b2fSvporpo #include "llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h"
111d09925bSvporpo 
121d09925bSvporpo namespace llvm::sandboxir {
131d09925bSvporpo 
141d09925bSvporpo // TODO: Check if we can cache top/bottom to reduce compile-time.
151d09925bSvporpo DGNode *SchedBundle::getTop() const {
161d09925bSvporpo   DGNode *TopN = Nodes.front();
171d09925bSvporpo   for (auto *N : drop_begin(Nodes)) {
181d09925bSvporpo     if (N->getInstruction()->comesBefore(TopN->getInstruction()))
191d09925bSvporpo       TopN = N;
201d09925bSvporpo   }
211d09925bSvporpo   return TopN;
221d09925bSvporpo }
231d09925bSvporpo 
241d09925bSvporpo DGNode *SchedBundle::getBot() const {
251d09925bSvporpo   DGNode *BotN = Nodes.front();
261d09925bSvporpo   for (auto *N : drop_begin(Nodes)) {
271d09925bSvporpo     if (BotN->getInstruction()->comesBefore(N->getInstruction()))
281d09925bSvporpo       BotN = N;
291d09925bSvporpo   }
301d09925bSvporpo   return BotN;
311d09925bSvporpo }
321d09925bSvporpo 
331d09925bSvporpo void SchedBundle::cluster(BasicBlock::iterator Where) {
341d09925bSvporpo   for (auto *N : Nodes) {
351d09925bSvporpo     auto *I = N->getInstruction();
361d09925bSvporpo     if (I->getIterator() == Where)
371d09925bSvporpo       ++Where; // Try to maintain bundle order.
381d09925bSvporpo     I->moveBefore(*Where.getNodeParent(), Where);
391d09925bSvporpo   }
401d09925bSvporpo }
411d09925bSvporpo 
421d09925bSvporpo #ifndef NDEBUG
431d09925bSvporpo void SchedBundle::dump(raw_ostream &OS) const {
441d09925bSvporpo   for (auto *N : Nodes)
451d09925bSvporpo     OS << *N;
461d09925bSvporpo }
471d09925bSvporpo 
481d09925bSvporpo void SchedBundle::dump() const {
491d09925bSvporpo   dump(dbgs());
501d09925bSvporpo   dbgs() << "\n";
511d09925bSvporpo }
521d09925bSvporpo #endif // NDEBUG
531d09925bSvporpo 
541d09925bSvporpo #ifndef NDEBUG
551d09925bSvporpo void ReadyListContainer::dump(raw_ostream &OS) const {
561d09925bSvporpo   auto ListCopy = List;
571d09925bSvporpo   while (!ListCopy.empty()) {
581d09925bSvporpo     OS << *ListCopy.top() << "\n";
591d09925bSvporpo     ListCopy.pop();
601d09925bSvporpo   }
611d09925bSvporpo }
621d09925bSvporpo 
631d09925bSvporpo void ReadyListContainer::dump() const {
641d09925bSvporpo   dump(dbgs());
651d09925bSvporpo   dbgs() << "\n";
661d09925bSvporpo }
671d09925bSvporpo #endif // NDEBUG
681d09925bSvporpo 
691d09925bSvporpo void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) {
701d09925bSvporpo   // Find where we should schedule the instructions.
711d09925bSvporpo   assert(ScheduleTopItOpt && "Should have been set by now!");
721d09925bSvporpo   auto Where = *ScheduleTopItOpt;
731d09925bSvporpo   // Move all instructions in `Bndl` to `Where`.
741d09925bSvporpo   Bndl.cluster(Where);
751d09925bSvporpo   // Update the last scheduled bundle.
761d09925bSvporpo   ScheduleTopItOpt = Bndl.getTop()->getInstruction()->getIterator();
771d09925bSvporpo   // Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all
781d09925bSvporpo   // dependency predecessors.
791d09925bSvporpo   for (DGNode *N : Bndl) {
801d09925bSvporpo     N->setScheduled(true);
811d09925bSvporpo     for (auto *DepN : N->preds(DAG)) {
821d09925bSvporpo       // TODO: preds() should not return nullptr.
831d09925bSvporpo       if (DepN == nullptr)
841d09925bSvporpo         continue;
851d09925bSvporpo       DepN->decrUnscheduledSuccs();
861d09925bSvporpo       if (DepN->ready())
871d09925bSvporpo         ReadyList.insert(DepN);
881d09925bSvporpo     }
891d09925bSvporpo   }
901d09925bSvporpo }
911d09925bSvporpo 
921d09925bSvporpo SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) {
931d09925bSvporpo   SchedBundle::ContainerTy Nodes;
941d09925bSvporpo   Nodes.reserve(Instrs.size());
951d09925bSvporpo   for (auto *I : Instrs)
961d09925bSvporpo     Nodes.push_back(DAG.getNode(I));
971d09925bSvporpo   auto BndlPtr = std::make_unique<SchedBundle>(std::move(Nodes));
981d09925bSvporpo   auto *Bndl = BndlPtr.get();
99f7ef7b2fSvporpo   Bndls[Bndl] = std::move(BndlPtr);
1001d09925bSvporpo   return Bndl;
1011d09925bSvporpo }
1021d09925bSvporpo 
103f7ef7b2fSvporpo void Scheduler::eraseBundle(SchedBundle *SB) { Bndls.erase(SB); }
104f7ef7b2fSvporpo 
1051d09925bSvporpo bool Scheduler::tryScheduleUntil(ArrayRef<Instruction *> Instrs) {
1061d09925bSvporpo   // Use a set of instructions, instead of `Instrs` for fast lookups.
1071d09925bSvporpo   DenseSet<Instruction *> InstrsToDefer(Instrs.begin(), Instrs.end());
1081d09925bSvporpo   // This collects the nodes that correspond to instructions found in `Instrs`
1091d09925bSvporpo   // that have just become ready. These nodes won't be scheduled right away.
1101d09925bSvporpo   SmallVector<DGNode *, 8> DeferredNodes;
1111d09925bSvporpo 
1121d09925bSvporpo   // Keep scheduling ready nodes until we either run out of ready nodes (i.e.,
1131d09925bSvporpo   // ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of
1141d09925bSvporpo   // which are collected in DeferredNodes) are all ready to schedule.
1151d09925bSvporpo   while (!ReadyList.empty()) {
1161d09925bSvporpo     auto *ReadyN = ReadyList.pop();
1171d09925bSvporpo     if (InstrsToDefer.contains(ReadyN->getInstruction())) {
1181d09925bSvporpo       // If the ready instruction is one of those in `Instrs`, then we don't
1191d09925bSvporpo       // schedule it right away. Instead we defer it until we can schedule it
1201d09925bSvporpo       // along with the rest of the instructions in `Instrs`, at the same
1211d09925bSvporpo       // time in a single scheduling bundle.
1221d09925bSvporpo       DeferredNodes.push_back(ReadyN);
1231d09925bSvporpo       bool ReadyToScheduleDeferred = DeferredNodes.size() == Instrs.size();
1241d09925bSvporpo       if (ReadyToScheduleDeferred) {
1251d09925bSvporpo         scheduleAndUpdateReadyList(*createBundle(Instrs));
1261d09925bSvporpo         return true;
1271d09925bSvporpo       }
1281d09925bSvporpo     } else {
1291d09925bSvporpo       // If the ready instruction is not found in `Instrs`, then we wrap it in a
1301d09925bSvporpo       // scheduling bundle and schedule it right away.
1311d09925bSvporpo       scheduleAndUpdateReadyList(*createBundle({ReadyN->getInstruction()}));
1321d09925bSvporpo     }
1331d09925bSvporpo   }
1341d09925bSvporpo   assert(DeferredNodes.size() != Instrs.size() &&
1351d09925bSvporpo          "We should have succesfully scheduled and early-returned!");
1361d09925bSvporpo   return false;
1371d09925bSvporpo }
1381d09925bSvporpo 
139f7ef7b2fSvporpo Scheduler::BndlSchedState
140f7ef7b2fSvporpo Scheduler::getBndlSchedState(ArrayRef<Instruction *> Instrs) const {
141f7ef7b2fSvporpo   assert(!Instrs.empty() && "Expected non-empty bundle");
142f7ef7b2fSvporpo   bool PartiallyScheduled = false;
143f7ef7b2fSvporpo   bool FullyScheduled = true;
144f7ef7b2fSvporpo   for (auto *I : Instrs) {
145f7ef7b2fSvporpo     auto *N = DAG.getNode(I);
146f7ef7b2fSvporpo     if (N != nullptr && N->scheduled())
147f7ef7b2fSvporpo       PartiallyScheduled = true;
148f7ef7b2fSvporpo     else
149f7ef7b2fSvporpo       FullyScheduled = false;
150f7ef7b2fSvporpo   }
151f7ef7b2fSvporpo   if (FullyScheduled) {
152f7ef7b2fSvporpo     // If not all instrs in the bundle are in the same SchedBundle then this
153f7ef7b2fSvporpo     // should be considered as partially-scheduled, because we will need to
154f7ef7b2fSvporpo     // re-schedule.
155f7ef7b2fSvporpo     SchedBundle *SB = DAG.getNode(Instrs[0])->getSchedBundle();
156f7ef7b2fSvporpo     assert(SB != nullptr && "FullyScheduled assumes that there is an SB!");
157f7ef7b2fSvporpo     if (any_of(drop_begin(Instrs), [this, SB](sandboxir::Value *SBV) {
158f7ef7b2fSvporpo           return DAG.getNode(cast<sandboxir::Instruction>(SBV))
159f7ef7b2fSvporpo                      ->getSchedBundle() != SB;
160f7ef7b2fSvporpo         }))
161f7ef7b2fSvporpo       FullyScheduled = false;
162f7ef7b2fSvporpo   }
163f7ef7b2fSvporpo   return FullyScheduled       ? BndlSchedState::FullyScheduled
164f7ef7b2fSvporpo          : PartiallyScheduled ? BndlSchedState::PartiallyOrDifferentlyScheduled
165f7ef7b2fSvporpo                               : BndlSchedState::NoneScheduled;
166f7ef7b2fSvporpo }
167f7ef7b2fSvporpo 
168f7ef7b2fSvporpo void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) {
169f7ef7b2fSvporpo   Instruction *TopI = &*ScheduleTopItOpt.value();
170f7ef7b2fSvporpo   Instruction *LowestI = VecUtils::getLowest(Instrs);
171f7ef7b2fSvporpo   // Destroy the schedule bundles from LowestI all the way to the top.
172f7ef7b2fSvporpo   for (auto *I = LowestI, *E = TopI->getPrevNode(); I != E;
173f7ef7b2fSvporpo        I = I->getPrevNode()) {
174f7ef7b2fSvporpo     auto *N = DAG.getNode(I);
175*b178c2d6SVasileios Porpodas     if (N == nullptr)
176*b178c2d6SVasileios Porpodas       continue;
177f7ef7b2fSvporpo     if (auto *SB = N->getSchedBundle())
178f7ef7b2fSvporpo       eraseBundle(SB);
179f7ef7b2fSvporpo   }
180f7ef7b2fSvporpo   // TODO: For now we clear the DAG. Trim view once it gets implemented.
181f7ef7b2fSvporpo   Bndls.clear();
182f7ef7b2fSvporpo   DAG.clear();
183f7ef7b2fSvporpo 
184f7ef7b2fSvporpo   // Since we are scheduling NewRegion from scratch, we clear the ready lists.
185f7ef7b2fSvporpo   // The nodes currently in the list may not be ready after clearing the View.
186f7ef7b2fSvporpo   ReadyList.clear();
187f7ef7b2fSvporpo }
188f7ef7b2fSvporpo 
1891d09925bSvporpo bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
1901d09925bSvporpo   assert(all_of(drop_begin(Instrs),
1911d09925bSvporpo                 [Instrs](Instruction *I) {
1921d09925bSvporpo                   return I->getParent() == (*Instrs.begin())->getParent();
1931d09925bSvporpo                 }) &&
1945cb2db3bSvporpo          "Instrs not in the same BB, should have been rejected by Legality!");
1955cb2db3bSvporpo   if (ScheduledBB == nullptr)
1965cb2db3bSvporpo     ScheduledBB = Instrs[0]->getParent();
1975cb2db3bSvporpo   // We don't support crossing BBs for now.
1985cb2db3bSvporpo   if (any_of(Instrs,
1995cb2db3bSvporpo              [this](Instruction *I) { return I->getParent() != ScheduledBB; }))
2005cb2db3bSvporpo     return false;
201f7ef7b2fSvporpo   auto SchedState = getBndlSchedState(Instrs);
202f7ef7b2fSvporpo   switch (SchedState) {
203f7ef7b2fSvporpo   case BndlSchedState::FullyScheduled:
204f7ef7b2fSvporpo     // Nothing to do.
205f7ef7b2fSvporpo     return true;
206f7ef7b2fSvporpo   case BndlSchedState::PartiallyOrDifferentlyScheduled:
207f7ef7b2fSvporpo     // If one or more instrs are already scheduled we need to destroy the
208f7ef7b2fSvporpo     // top-most part of the schedule that includes the instrs in the bundle and
209f7ef7b2fSvporpo     // re-schedule.
210f7ef7b2fSvporpo     trimSchedule(Instrs);
211f7ef7b2fSvporpo     [[fallthrough]];
212f7ef7b2fSvporpo   case BndlSchedState::NoneScheduled: {
2131d09925bSvporpo     // TODO: Set the window of the DAG that we are interested in.
2141d09925bSvporpo     // We start scheduling at the bottom instr of Instrs.
215f7ef7b2fSvporpo     ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
216f7ef7b2fSvporpo 
217c7053ac2Svporpo     // TODO: For now don't cross BBs.
218c7053ac2Svporpo     if (!DAG.getInterval().empty()) {
219c7053ac2Svporpo       auto *BB = DAG.getInterval().top()->getParent();
220c7053ac2Svporpo       if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; }))
221c7053ac2Svporpo         return false;
222c7053ac2Svporpo     }
223c7053ac2Svporpo 
224f7ef7b2fSvporpo     // Extend the DAG to include Instrs.
225f7ef7b2fSvporpo     Interval<Instruction> Extension = DAG.extend(Instrs);
2261d09925bSvporpo     // Add nodes to ready list.
2271d09925bSvporpo     for (auto &I : Extension) {
2281d09925bSvporpo       auto *N = DAG.getNode(&I);
2291d09925bSvporpo       if (N->ready())
2301d09925bSvporpo         ReadyList.insert(N);
2311d09925bSvporpo     }
2321d09925bSvporpo     // Try schedule all nodes until we can schedule Instrs back-to-back.
2331d09925bSvporpo     return tryScheduleUntil(Instrs);
2341d09925bSvporpo   }
235f7ef7b2fSvporpo   }
236490e58a9SSimon Pilgrim   llvm_unreachable("Unhandled BndlSchedState enum");
237f7ef7b2fSvporpo }
2381d09925bSvporpo 
2391d09925bSvporpo #ifndef NDEBUG
2401d09925bSvporpo void Scheduler::dump(raw_ostream &OS) const {
2411d09925bSvporpo   OS << "ReadyList:\n";
2421d09925bSvporpo   ReadyList.dump(OS);
2431d09925bSvporpo }
2441d09925bSvporpo void Scheduler::dump() const { dump(dbgs()); }
2451d09925bSvporpo #endif // NDEBUG
2461d09925bSvporpo 
2471d09925bSvporpo } // namespace llvm::sandboxir
248