xref: /llvm-project/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Scheduler.cpp (revision b178c2d63e0701655046dfd2ead195b36e0df397)
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.
15 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 
24 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 
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
43 void SchedBundle::dump(raw_ostream &OS) const {
44   for (auto *N : Nodes)
45     OS << *N;
46 }
47 
48 void SchedBundle::dump() const {
49   dump(dbgs());
50   dbgs() << "\n";
51 }
52 #endif // NDEBUG
53 
54 #ifndef NDEBUG
55 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 
63 void ReadyListContainer::dump() const {
64   dump(dbgs());
65   dbgs() << "\n";
66 }
67 #endif // NDEBUG
68 
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     N->setScheduled(true);
81     for (auto *DepN : N->preds(DAG)) {
82       // TODO: preds() should not return nullptr.
83       if (DepN == nullptr)
84         continue;
85       DepN->decrUnscheduledSuccs();
86       if (DepN->ready())
87         ReadyList.insert(DepN);
88     }
89   }
90 }
91 
92 SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) {
93   SchedBundle::ContainerTy Nodes;
94   Nodes.reserve(Instrs.size());
95   for (auto *I : Instrs)
96     Nodes.push_back(DAG.getNode(I));
97   auto BndlPtr = std::make_unique<SchedBundle>(std::move(Nodes));
98   auto *Bndl = BndlPtr.get();
99   Bndls[Bndl] = std::move(BndlPtr);
100   return Bndl;
101 }
102 
103 void Scheduler::eraseBundle(SchedBundle *SB) { Bndls.erase(SB); }
104 
105 bool Scheduler::tryScheduleUntil(ArrayRef<Instruction *> Instrs) {
106   // Use a set of instructions, instead of `Instrs` for fast lookups.
107   DenseSet<Instruction *> InstrsToDefer(Instrs.begin(), Instrs.end());
108   // This collects the nodes that correspond to instructions found in `Instrs`
109   // that have just become ready. These nodes won't be scheduled right away.
110   SmallVector<DGNode *, 8> DeferredNodes;
111 
112   // Keep scheduling ready nodes until we either run out of ready nodes (i.e.,
113   // ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of
114   // which are collected in DeferredNodes) are all ready to schedule.
115   while (!ReadyList.empty()) {
116     auto *ReadyN = ReadyList.pop();
117     if (InstrsToDefer.contains(ReadyN->getInstruction())) {
118       // If the ready instruction is one of those in `Instrs`, then we don't
119       // schedule it right away. Instead we defer it until we can schedule it
120       // along with the rest of the instructions in `Instrs`, at the same
121       // time in a single scheduling bundle.
122       DeferredNodes.push_back(ReadyN);
123       bool ReadyToScheduleDeferred = DeferredNodes.size() == Instrs.size();
124       if (ReadyToScheduleDeferred) {
125         scheduleAndUpdateReadyList(*createBundle(Instrs));
126         return true;
127       }
128     } else {
129       // If the ready instruction is not found in `Instrs`, then we wrap it in a
130       // scheduling bundle and schedule it right away.
131       scheduleAndUpdateReadyList(*createBundle({ReadyN->getInstruction()}));
132     }
133   }
134   assert(DeferredNodes.size() != Instrs.size() &&
135          "We should have succesfully scheduled and early-returned!");
136   return false;
137 }
138 
139 Scheduler::BndlSchedState
140 Scheduler::getBndlSchedState(ArrayRef<Instruction *> Instrs) const {
141   assert(!Instrs.empty() && "Expected non-empty bundle");
142   bool PartiallyScheduled = false;
143   bool FullyScheduled = true;
144   for (auto *I : Instrs) {
145     auto *N = DAG.getNode(I);
146     if (N != nullptr && N->scheduled())
147       PartiallyScheduled = true;
148     else
149       FullyScheduled = false;
150   }
151   if (FullyScheduled) {
152     // If not all instrs in the bundle are in the same SchedBundle then this
153     // should be considered as partially-scheduled, because we will need to
154     // re-schedule.
155     SchedBundle *SB = DAG.getNode(Instrs[0])->getSchedBundle();
156     assert(SB != nullptr && "FullyScheduled assumes that there is an SB!");
157     if (any_of(drop_begin(Instrs), [this, SB](sandboxir::Value *SBV) {
158           return DAG.getNode(cast<sandboxir::Instruction>(SBV))
159                      ->getSchedBundle() != SB;
160         }))
161       FullyScheduled = false;
162   }
163   return FullyScheduled       ? BndlSchedState::FullyScheduled
164          : PartiallyScheduled ? BndlSchedState::PartiallyOrDifferentlyScheduled
165                               : BndlSchedState::NoneScheduled;
166 }
167 
168 void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) {
169   Instruction *TopI = &*ScheduleTopItOpt.value();
170   Instruction *LowestI = VecUtils::getLowest(Instrs);
171   // Destroy the schedule bundles from LowestI all the way to the top.
172   for (auto *I = LowestI, *E = TopI->getPrevNode(); I != E;
173        I = I->getPrevNode()) {
174     auto *N = DAG.getNode(I);
175     if (N == nullptr)
176       continue;
177     if (auto *SB = N->getSchedBundle())
178       eraseBundle(SB);
179   }
180   // TODO: For now we clear the DAG. Trim view once it gets implemented.
181   Bndls.clear();
182   DAG.clear();
183 
184   // Since we are scheduling NewRegion from scratch, we clear the ready lists.
185   // The nodes currently in the list may not be ready after clearing the View.
186   ReadyList.clear();
187 }
188 
189 bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
190   assert(all_of(drop_begin(Instrs),
191                 [Instrs](Instruction *I) {
192                   return I->getParent() == (*Instrs.begin())->getParent();
193                 }) &&
194          "Instrs not in the same BB, should have been rejected by Legality!");
195   if (ScheduledBB == nullptr)
196     ScheduledBB = Instrs[0]->getParent();
197   // We don't support crossing BBs for now.
198   if (any_of(Instrs,
199              [this](Instruction *I) { return I->getParent() != ScheduledBB; }))
200     return false;
201   auto SchedState = getBndlSchedState(Instrs);
202   switch (SchedState) {
203   case BndlSchedState::FullyScheduled:
204     // Nothing to do.
205     return true;
206   case BndlSchedState::PartiallyOrDifferentlyScheduled:
207     // If one or more instrs are already scheduled we need to destroy the
208     // top-most part of the schedule that includes the instrs in the bundle and
209     // re-schedule.
210     trimSchedule(Instrs);
211     [[fallthrough]];
212   case BndlSchedState::NoneScheduled: {
213     // TODO: Set the window of the DAG that we are interested in.
214     // We start scheduling at the bottom instr of Instrs.
215     ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
216 
217     // TODO: For now don't cross BBs.
218     if (!DAG.getInterval().empty()) {
219       auto *BB = DAG.getInterval().top()->getParent();
220       if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; }))
221         return false;
222     }
223 
224     // Extend the DAG to include Instrs.
225     Interval<Instruction> Extension = DAG.extend(Instrs);
226     // Add nodes to ready list.
227     for (auto &I : Extension) {
228       auto *N = DAG.getNode(&I);
229       if (N->ready())
230         ReadyList.insert(N);
231     }
232     // Try schedule all nodes until we can schedule Instrs back-to-back.
233     return tryScheduleUntil(Instrs);
234   }
235   }
236   llvm_unreachable("Unhandled BndlSchedState enum");
237 }
238 
239 #ifndef NDEBUG
240 void Scheduler::dump(raw_ostream &OS) const {
241   OS << "ReadyList:\n";
242   ReadyList.dump(OS);
243 }
244 void Scheduler::dump() const { dump(dbgs()); }
245 #endif // NDEBUG
246 
247 } // namespace llvm::sandboxir
248