xref: /llvm-project/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Scheduler.cpp (revision 490e58a98e0518542c87aa16e326fcb446d7b1cc)
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 (auto *SB = N->getSchedBundle())
176       eraseBundle(SB);
177   }
178   // TODO: For now we clear the DAG. Trim view once it gets implemented.
179   Bndls.clear();
180   DAG.clear();
181 
182   // Since we are scheduling NewRegion from scratch, we clear the ready lists.
183   // The nodes currently in the list may not be ready after clearing the View.
184   ReadyList.clear();
185 }
186 
187 bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) {
188   assert(all_of(drop_begin(Instrs),
189                 [Instrs](Instruction *I) {
190                   return I->getParent() == (*Instrs.begin())->getParent();
191                 }) &&
192          "Instrs not in the same BB!");
193   auto SchedState = getBndlSchedState(Instrs);
194   switch (SchedState) {
195   case BndlSchedState::FullyScheduled:
196     // Nothing to do.
197     return true;
198   case BndlSchedState::PartiallyOrDifferentlyScheduled:
199     // If one or more instrs are already scheduled we need to destroy the
200     // top-most part of the schedule that includes the instrs in the bundle and
201     // re-schedule.
202     trimSchedule(Instrs);
203     [[fallthrough]];
204   case BndlSchedState::NoneScheduled: {
205     // TODO: Set the window of the DAG that we are interested in.
206     // We start scheduling at the bottom instr of Instrs.
207     ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator());
208 
209     // Extend the DAG to include Instrs.
210     Interval<Instruction> Extension = DAG.extend(Instrs);
211     // Add nodes to ready list.
212     for (auto &I : Extension) {
213       auto *N = DAG.getNode(&I);
214       if (N->ready())
215         ReadyList.insert(N);
216     }
217     // Try schedule all nodes until we can schedule Instrs back-to-back.
218     return tryScheduleUntil(Instrs);
219   }
220   }
221   llvm_unreachable("Unhandled BndlSchedState enum");
222 }
223 
224 #ifndef NDEBUG
225 void Scheduler::dump(raw_ostream &OS) const {
226   OS << "ReadyList:\n";
227   ReadyList.dump(OS);
228 }
229 void Scheduler::dump() const { dump(dbgs()); }
230 #endif // NDEBUG
231 
232 } // namespace llvm::sandboxir
233