1 //===--------------------- Scheduler.cpp ------------------------*- C++ -*-===//
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 // A scheduler for processor resource units and processor resource groups.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/MCA/HardwareUnits/Scheduler.h"
14 #include "llvm/Support/Debug.h"
15 #include "llvm/Support/raw_ostream.h"
16 
17 namespace llvm {
18 namespace mca {
19 
20 #define DEBUG_TYPE "llvm-mca"
21 
22 void Scheduler::initializeStrategy(std::unique_ptr<SchedulerStrategy> S) {
23   // Ensure we have a valid (non-null) strategy object.
24   Strategy = S ? std::move(S) : llvm::make_unique<DefaultSchedulerStrategy>();
25 }
26 
27 // Anchor the vtable of SchedulerStrategy and DefaultSchedulerStrategy.
28 SchedulerStrategy::~SchedulerStrategy() = default;
29 DefaultSchedulerStrategy::~DefaultSchedulerStrategy() = default;
30 
31 #ifndef NDEBUG
32 void Scheduler::dump() const {
33   dbgs() << "[SCHEDULER]: WaitSet size is: " << WaitSet.size() << '\n';
34   dbgs() << "[SCHEDULER]: ReadySet size is: " << ReadySet.size() << '\n';
35   dbgs() << "[SCHEDULER]: IssuedSet size is: " << IssuedSet.size() << '\n';
36   Resources->dump();
37 }
38 #endif
39 
40 Scheduler::Status Scheduler::isAvailable(const InstRef &IR) {
41   const InstrDesc &Desc = IR.getInstruction()->getDesc();
42 
43   ResourceStateEvent RSE = Resources->canBeDispatched(Desc.Buffers);
44   HadTokenStall = RSE != RS_BUFFER_AVAILABLE;
45 
46   switch (RSE) {
47   case ResourceStateEvent::RS_BUFFER_UNAVAILABLE:
48     return Scheduler::SC_BUFFERS_FULL;
49   case ResourceStateEvent::RS_RESERVED:
50     return Scheduler::SC_DISPATCH_GROUP_STALL;
51   case ResourceStateEvent::RS_BUFFER_AVAILABLE:
52     break;
53   }
54 
55   // Give lower priority to LSUnit stall events.
56   LSUnit::Status LSS = LSU.isAvailable(IR);
57   HadTokenStall = LSS != LSUnit::LSU_AVAILABLE;
58 
59   switch (LSS) {
60   case LSUnit::LSU_LQUEUE_FULL:
61     return Scheduler::SC_LOAD_QUEUE_FULL;
62   case LSUnit::LSU_SQUEUE_FULL:
63     return Scheduler::SC_STORE_QUEUE_FULL;
64   case LSUnit::LSU_AVAILABLE:
65     return Scheduler::SC_AVAILABLE;
66   }
67 
68   llvm_unreachable("Don't know how to process this LSU state result!");
69 }
70 
71 void Scheduler::issueInstructionImpl(
72     InstRef &IR,
73     SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &UsedResources) {
74   Instruction *IS = IR.getInstruction();
75   const InstrDesc &D = IS->getDesc();
76 
77   // Issue the instruction and collect all the consumed resources
78   // into a vector. That vector is then used to notify the listener.
79   Resources->issueInstruction(D, UsedResources);
80 
81   // Notify the instruction that it started executing.
82   // This updates the internal state of each write.
83   IS->execute(IR.getSourceIndex());
84 
85   if (IS->isExecuting())
86     IssuedSet.emplace_back(IR);
87   else if (IS->isExecuted())
88     LSU.onInstructionExecuted(IR);
89 }
90 
91 // Release the buffered resources and issue the instruction.
92 void Scheduler::issueInstruction(
93     InstRef &IR,
94     SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &UsedResources,
95     SmallVectorImpl<InstRef> &PendingInstructions,
96     SmallVectorImpl<InstRef> &ReadyInstructions) {
97   const Instruction &Inst = *IR.getInstruction();
98   bool HasDependentUsers = Inst.hasDependentUsers();
99 
100   Resources->releaseBuffers(Inst.getDesc().Buffers);
101   issueInstructionImpl(IR, UsedResources);
102   // Instructions that have been issued during this cycle might have unblocked
103   // other dependent instructions. Dependent instructions may be issued during
104   // this same cycle if operands have ReadAdvance entries.  Promote those
105   // instructions to the ReadySet and notify the caller that those are ready.
106   if (HasDependentUsers && promoteToPendingSet(PendingInstructions))
107     promoteToReadySet(ReadyInstructions);
108 }
109 
110 bool Scheduler::promoteToReadySet(SmallVectorImpl<InstRef> &Ready) {
111   // Scan the set of waiting instructions and promote them to the
112   // ready set if operands are all ready.
113   unsigned PromotedElements = 0;
114   for (auto I = PendingSet.begin(), E = PendingSet.end(); I != E;) {
115     InstRef &IR = *I;
116     if (!IR)
117       break;
118 
119     // Check if there are still unsolved memory dependencies.
120     Instruction &IS = *IR.getInstruction();
121     if (IS.isMemOp()) {
122       const InstRef &CriticalMemDep = LSU.isReady(IR);
123       if (CriticalMemDep != IR) {
124         IS.setCriticalMemDep(CriticalMemDep.getSourceIndex());
125         ++I;
126         continue;
127       }
128     }
129 
130     // Check if this instruction is now ready. In case, force
131     // a transition in state using method 'update()'.
132     if (!IS.isReady() && !IS.updatePending()) {
133       ++I;
134       continue;
135     }
136     LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
137                       << " promoted to the READY set.\n");
138 
139     Ready.emplace_back(IR);
140     ReadySet.emplace_back(IR);
141 
142     IR.invalidate();
143     ++PromotedElements;
144     std::iter_swap(I, E - PromotedElements);
145   }
146 
147   PendingSet.resize(PendingSet.size() - PromotedElements);
148   return PromotedElements;
149 }
150 
151 bool Scheduler::promoteToPendingSet(SmallVectorImpl<InstRef> &Pending) {
152   // Scan the set of waiting instructions and promote them to the
153   // pending set if operands are all ready.
154   unsigned RemovedElements = 0;
155   for (auto I = WaitSet.begin(), E = WaitSet.end(); I != E;) {
156     InstRef &IR = *I;
157     if (!IR)
158       break;
159 
160     // Check if this instruction is now ready. In case, force
161     // a transition in state using method 'updateDispatched()'.
162     Instruction &IS = *IR.getInstruction();
163     if (IS.isDispatched() && !IS.updateDispatched()) {
164       ++I;
165       continue;
166     }
167     LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
168                       << " promoted to the PENDING set.\n");
169 
170     Pending.emplace_back(IR);
171     PendingSet.emplace_back(IR);
172 
173     IR.invalidate();
174     ++RemovedElements;
175     std::iter_swap(I, E - RemovedElements);
176   }
177 
178   WaitSet.resize(WaitSet.size() - RemovedElements);
179   return RemovedElements;
180 }
181 
182 InstRef Scheduler::select() {
183   unsigned QueueIndex = ReadySet.size();
184   for (unsigned I = 0, E = ReadySet.size(); I != E; ++I) {
185     InstRef &IR = ReadySet[I];
186     if (QueueIndex == ReadySet.size() ||
187         Strategy->compare(IR, ReadySet[QueueIndex])) {
188       Instruction &IS = *IR.getInstruction();
189       uint64_t BusyResourceMask = Resources->checkAvailability(IS.getDesc());
190       IS.setCriticalResourceMask(BusyResourceMask);
191       BusyResourceUnits |= BusyResourceMask;
192       if (!BusyResourceMask)
193         QueueIndex = I;
194     }
195   }
196 
197   if (QueueIndex == ReadySet.size())
198     return InstRef();
199 
200   // We found an instruction to issue.
201   InstRef IR = ReadySet[QueueIndex];
202   std::swap(ReadySet[QueueIndex], ReadySet[ReadySet.size() - 1]);
203   ReadySet.pop_back();
204   return IR;
205 }
206 
207 void Scheduler::updateIssuedSet(SmallVectorImpl<InstRef> &Executed) {
208   unsigned RemovedElements = 0;
209   for (auto I = IssuedSet.begin(), E = IssuedSet.end(); I != E;) {
210     InstRef &IR = *I;
211     if (!IR)
212       break;
213     Instruction &IS = *IR.getInstruction();
214     if (!IS.isExecuted()) {
215       LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
216                         << " is still executing.\n");
217       ++I;
218       continue;
219     }
220 
221     // Instruction IR has completed execution.
222     LSU.onInstructionExecuted(IR);
223     Executed.emplace_back(IR);
224     ++RemovedElements;
225     IR.invalidate();
226     std::iter_swap(I, E - RemovedElements);
227   }
228 
229   IssuedSet.resize(IssuedSet.size() - RemovedElements);
230 }
231 
232 uint64_t Scheduler::analyzeResourcePressure(SmallVectorImpl<InstRef> &Insts) {
233   Insts.insert(Insts.end(), ReadySet.begin(), ReadySet.end());
234   return BusyResourceUnits;
235 }
236 
237 void Scheduler::analyzeDataDependencies(SmallVectorImpl<InstRef> &RegDeps,
238                                         SmallVectorImpl<InstRef> &MemDeps) {
239   const auto EndIt = PendingSet.end() - NumDispatchedToThePendingSet;
240   for (InstRef &IR : make_range(PendingSet.begin(), EndIt)) {
241     Instruction &IS = *IR.getInstruction();
242     if (Resources->checkAvailability(IS.getDesc()))
243       continue;
244 
245     if (IS.isReady() || (IS.isMemOp() && LSU.isReady(IR) != IR))
246       MemDeps.emplace_back(IR);
247     else
248       RegDeps.emplace_back(IR);
249   }
250 }
251 
252 void Scheduler::cycleEvent(SmallVectorImpl<ResourceRef> &Freed,
253                            SmallVectorImpl<InstRef> &Executed,
254                            SmallVectorImpl<InstRef> &Pending,
255                            SmallVectorImpl<InstRef> &Ready) {
256   // Release consumed resources.
257   Resources->cycleEvent(Freed);
258 
259   for (InstRef &IR : IssuedSet)
260     IR.getInstruction()->cycleEvent();
261   updateIssuedSet(Executed);
262 
263   for (InstRef &IR : PendingSet)
264     IR.getInstruction()->cycleEvent();
265 
266   for (InstRef &IR : WaitSet)
267     IR.getInstruction()->cycleEvent();
268 
269   promoteToPendingSet(Pending);
270   promoteToReadySet(Ready);
271 
272   NumDispatchedToThePendingSet = 0;
273   BusyResourceUnits = 0;
274 }
275 
276 bool Scheduler::mustIssueImmediately(const InstRef &IR) const {
277   const InstrDesc &Desc = IR.getInstruction()->getDesc();
278   if (Desc.isZeroLatency())
279     return true;
280   // Instructions that use an in-order dispatch/issue processor resource must be
281   // issued immediately to the pipeline(s). Any other in-order buffered
282   // resources (i.e. BufferSize=1) is consumed.
283   return Desc.MustIssueImmediately;
284 }
285 
286 bool Scheduler::dispatch(const InstRef &IR) {
287   const Instruction &IS = *IR.getInstruction();
288   const InstrDesc &Desc = IS.getDesc();
289   Resources->reserveBuffers(Desc.Buffers);
290 
291   // If necessary, reserve queue entries in the load-store unit (LSU).
292   if (IS.isMemOp())
293     LSU.dispatch(IR);
294 
295   if (IS.isPending()) {
296     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR
297                       << " to the PendingSet\n");
298     PendingSet.push_back(IR);
299     ++NumDispatchedToThePendingSet;
300     return false;
301   }
302 
303   // Memory operations that are not in a ready state are initially assigned to
304   // the WaitSet.
305   if (!IS.isReady() || (IS.isMemOp() && LSU.isReady(IR) != IR)) {
306     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the WaitSet\n");
307     WaitSet.push_back(IR);
308     return false;
309   }
310 
311   // Don't add a zero-latency instruction to the Ready queue.
312   // A zero-latency instruction doesn't consume any scheduler resources. That is
313   // because it doesn't need to be executed, and it is often removed at register
314   // renaming stage. For example, register-register moves are often optimized at
315   // register renaming stage by simply updating register aliases. On some
316   // targets, zero-idiom instructions (for example: a xor that clears the value
317   // of a register) are treated specially, and are often eliminated at register
318   // renaming stage.
319   if (!mustIssueImmediately(IR)) {
320     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the ReadySet\n");
321     ReadySet.push_back(IR);
322   }
323 
324   return true;
325 }
326 
327 } // namespace mca
328 } // namespace llvm
329