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   IS->computeCriticalRegDep();
86 
87   if (IS->isExecuting())
88     IssuedSet.emplace_back(IR);
89   else if (IS->isExecuted())
90     LSU.onInstructionExecuted(IR);
91 }
92 
93 // Release the buffered resources and issue the instruction.
94 void Scheduler::issueInstruction(
95     InstRef &IR,
96     SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &UsedResources,
97     SmallVectorImpl<InstRef> &PendingInstructions,
98     SmallVectorImpl<InstRef> &ReadyInstructions) {
99   const Instruction &Inst = *IR.getInstruction();
100   bool HasDependentUsers = Inst.hasDependentUsers();
101 
102   Resources->releaseBuffers(Inst.getDesc().Buffers);
103   issueInstructionImpl(IR, UsedResources);
104   // Instructions that have been issued during this cycle might have unblocked
105   // other dependent instructions. Dependent instructions may be issued during
106   // this same cycle if operands have ReadAdvance entries.  Promote those
107   // instructions to the ReadySet and notify the caller that those are ready.
108   if (HasDependentUsers && promoteToPendingSet(PendingInstructions))
109     promoteToReadySet(ReadyInstructions);
110 }
111 
112 static bool initializeCriticalMemDepInfo(InstRef &IR, const LSUnit &LSU) {
113   Instruction &IS = *IR.getInstruction();
114   assert(IS.isMemOp() && "Not a memory operation!");
115 
116   // Check if this instruction depends on another memory operation.
117   InstRef DependentMemOp = LSU.isReady(IR);
118   const Instruction *MemOp = DependentMemOp.getInstruction();
119   IS.setCurrentMemDep(MemOp);
120 
121   // Initialize the CriticalMemDep structure.
122   unsigned Cycles = 0;
123   if (MemOp->isExecuting())
124     Cycles = static_cast<unsigned>(MemOp->getCyclesLeft());
125   IS.setCriticalMemDep(DependentMemOp.getSourceIndex(), Cycles);
126   return IR.getSourceIndex() == DependentMemOp.getSourceIndex();
127 }
128 
129 static bool updateMemoryDependencyInfo(InstRef &IR, const LSUnit &LSU) {
130   Instruction &IS = *IR.getInstruction();
131   assert(IS.isMemOp() && "Not a memory operation!");
132 
133   const Instruction *MemOp = IS.getCurrentMemDep();
134   if (!MemOp && initializeCriticalMemDepInfo(IR, LSU))
135     return true;
136 
137   MemOp = IS.getCurrentMemDep();
138   if (MemOp == IR.getInstruction())
139     return true;
140 
141   const CriticalDependency &CMD = IS.getCriticalMemDep();
142   if (MemOp->isExecuting() && !CMD.Cycles) {
143     // Update the critical memory dependency info.
144     IS.setCriticalMemDep(CMD.IID, MemOp->getCyclesLeft());
145     return false;
146   }
147 
148   if (!MemOp->isExecuted() && !MemOp->isRetired())
149     return false;
150 
151   // Check if there are still unsolved memory dependencies.
152   InstRef DependentMemOp = LSU.isReady(IR);
153   MemOp = DependentMemOp.getInstruction();
154   IS.setCurrentMemDep(MemOp);
155   if (DependentMemOp == IR)
156     return true;
157 
158   unsigned Cycles = 0;
159   if (MemOp->isExecuting())
160     Cycles = static_cast<unsigned>(MemOp->getCyclesLeft());
161   IS.setCriticalMemDep(DependentMemOp.getSourceIndex(), Cycles);
162   return false;
163 }
164 
165 bool Scheduler::promoteToReadySet(SmallVectorImpl<InstRef> &Ready) {
166   // Scan the set of waiting instructions and promote them to the
167   // ready set if operands are all ready.
168   unsigned PromotedElements = 0;
169   for (auto I = PendingSet.begin(), E = PendingSet.end(); I != E;) {
170     InstRef &IR = *I;
171     if (!IR)
172       break;
173 
174     // Check if there are unsolved memory dependencies.
175     Instruction &IS = *IR.getInstruction();
176     if (IS.isMemOp() && !updateMemoryDependencyInfo(IR, LSU)) {
177       ++I;
178       continue;
179     }
180 
181     // Check if there are unsolved register dependencies.
182     if (!IS.isReady() && !IS.updatePending()) {
183       ++I;
184       continue;
185     }
186     LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
187                       << " promoted to the READY set.\n");
188 
189     Ready.emplace_back(IR);
190     ReadySet.emplace_back(IR);
191 
192     IR.invalidate();
193     ++PromotedElements;
194     std::iter_swap(I, E - PromotedElements);
195   }
196 
197   PendingSet.resize(PendingSet.size() - PromotedElements);
198   return PromotedElements;
199 }
200 
201 bool Scheduler::promoteToPendingSet(SmallVectorImpl<InstRef> &Pending) {
202   // Scan the set of waiting instructions and promote them to the
203   // pending set if operands are all ready.
204   unsigned RemovedElements = 0;
205   for (auto I = WaitSet.begin(), E = WaitSet.end(); I != E;) {
206     InstRef &IR = *I;
207     if (!IR)
208       break;
209 
210     // Check if this instruction is now ready. In case, force
211     // a transition in state using method 'updateDispatched()'.
212     Instruction &IS = *IR.getInstruction();
213     if (IS.isDispatched() && !IS.updateDispatched()) {
214       ++I;
215       continue;
216     }
217     LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
218                       << " promoted to the PENDING set.\n");
219 
220     Pending.emplace_back(IR);
221     PendingSet.emplace_back(IR);
222 
223     IR.invalidate();
224     ++RemovedElements;
225     std::iter_swap(I, E - RemovedElements);
226   }
227 
228   WaitSet.resize(WaitSet.size() - RemovedElements);
229   return RemovedElements;
230 }
231 
232 InstRef Scheduler::select() {
233   unsigned QueueIndex = ReadySet.size();
234   for (unsigned I = 0, E = ReadySet.size(); I != E; ++I) {
235     InstRef &IR = ReadySet[I];
236     if (QueueIndex == ReadySet.size() ||
237         Strategy->compare(IR, ReadySet[QueueIndex])) {
238       Instruction &IS = *IR.getInstruction();
239       uint64_t BusyResourceMask = Resources->checkAvailability(IS.getDesc());
240       IS.setCriticalResourceMask(BusyResourceMask);
241       BusyResourceUnits |= BusyResourceMask;
242       if (!BusyResourceMask)
243         QueueIndex = I;
244     }
245   }
246 
247   if (QueueIndex == ReadySet.size())
248     return InstRef();
249 
250   // We found an instruction to issue.
251   InstRef IR = ReadySet[QueueIndex];
252   std::swap(ReadySet[QueueIndex], ReadySet[ReadySet.size() - 1]);
253   ReadySet.pop_back();
254   return IR;
255 }
256 
257 void Scheduler::updateIssuedSet(SmallVectorImpl<InstRef> &Executed) {
258   unsigned RemovedElements = 0;
259   for (auto I = IssuedSet.begin(), E = IssuedSet.end(); I != E;) {
260     InstRef &IR = *I;
261     if (!IR)
262       break;
263     Instruction &IS = *IR.getInstruction();
264     if (!IS.isExecuted()) {
265       LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR
266                         << " is still executing.\n");
267       ++I;
268       continue;
269     }
270 
271     // Instruction IR has completed execution.
272     LSU.onInstructionExecuted(IR);
273     Executed.emplace_back(IR);
274     ++RemovedElements;
275     IR.invalidate();
276     std::iter_swap(I, E - RemovedElements);
277   }
278 
279   IssuedSet.resize(IssuedSet.size() - RemovedElements);
280 }
281 
282 uint64_t Scheduler::analyzeResourcePressure(SmallVectorImpl<InstRef> &Insts) {
283   Insts.insert(Insts.end(), ReadySet.begin(), ReadySet.end());
284   return BusyResourceUnits;
285 }
286 
287 void Scheduler::analyzeDataDependencies(SmallVectorImpl<InstRef> &RegDeps,
288                                         SmallVectorImpl<InstRef> &MemDeps) {
289   const auto EndIt = PendingSet.end() - NumDispatchedToThePendingSet;
290   for (InstRef &IR : make_range(PendingSet.begin(), EndIt)) {
291     Instruction &IS = *IR.getInstruction();
292     if (Resources->checkAvailability(IS.getDesc()))
293       continue;
294 
295     if (IS.isReady() || (IS.isMemOp() && LSU.isReady(IR) != IR))
296       MemDeps.emplace_back(IR);
297     else
298       RegDeps.emplace_back(IR);
299   }
300 }
301 
302 void Scheduler::cycleEvent(SmallVectorImpl<ResourceRef> &Freed,
303                            SmallVectorImpl<InstRef> &Executed,
304                            SmallVectorImpl<InstRef> &Pending,
305                            SmallVectorImpl<InstRef> &Ready) {
306   // Release consumed resources.
307   Resources->cycleEvent(Freed);
308 
309   for (InstRef &IR : IssuedSet)
310     IR.getInstruction()->cycleEvent();
311   updateIssuedSet(Executed);
312 
313   for (InstRef &IR : PendingSet)
314     IR.getInstruction()->cycleEvent();
315 
316   for (InstRef &IR : WaitSet)
317     IR.getInstruction()->cycleEvent();
318 
319   promoteToPendingSet(Pending);
320   promoteToReadySet(Ready);
321 
322   NumDispatchedToThePendingSet = 0;
323   BusyResourceUnits = 0;
324 }
325 
326 bool Scheduler::mustIssueImmediately(const InstRef &IR) const {
327   const InstrDesc &Desc = IR.getInstruction()->getDesc();
328   if (Desc.isZeroLatency())
329     return true;
330   // Instructions that use an in-order dispatch/issue processor resource must be
331   // issued immediately to the pipeline(s). Any other in-order buffered
332   // resources (i.e. BufferSize=1) is consumed.
333   return Desc.MustIssueImmediately;
334 }
335 
336 bool Scheduler::dispatch(const InstRef &IR) {
337   const Instruction &IS = *IR.getInstruction();
338   const InstrDesc &Desc = IS.getDesc();
339   Resources->reserveBuffers(Desc.Buffers);
340 
341   // If necessary, reserve queue entries in the load-store unit (LSU).
342   if (IS.isMemOp())
343     LSU.dispatch(IR);
344 
345   if (IS.isPending()) {
346     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR
347                       << " to the PendingSet\n");
348     PendingSet.push_back(IR);
349     ++NumDispatchedToThePendingSet;
350     return false;
351   }
352 
353   // Memory operations that are not in a ready state are initially assigned to
354   // the WaitSet.
355   if (!IS.isReady() || (IS.isMemOp() && LSU.isReady(IR) != IR)) {
356     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the WaitSet\n");
357     WaitSet.push_back(IR);
358     return false;
359   }
360 
361   // Don't add a zero-latency instruction to the Ready queue.
362   // A zero-latency instruction doesn't consume any scheduler resources. That is
363   // because it doesn't need to be executed, and it is often removed at register
364   // renaming stage. For example, register-register moves are often optimized at
365   // register renaming stage by simply updating register aliases. On some
366   // targets, zero-idiom instructions (for example: a xor that clears the value
367   // of a register) are treated specially, and are often eliminated at register
368   // renaming stage.
369   if (!mustIssueImmediately(IR)) {
370     LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the ReadySet\n");
371     ReadySet.push_back(IR);
372   }
373 
374   return true;
375 }
376 
377 } // namespace mca
378 } // namespace llvm
379