blob: 6c0c44bd4d71aaa9f9eb7acbc11d2d890463a689 [file] [log] [blame]
Andrea Di Biagio3a6b0922018-03-08 13:05:02 +00001//===--------------------- Scheduler.cpp ------------------------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// A scheduler for processor resource units and processor resource groups.
11//
12//===----------------------------------------------------------------------===//
13
14#include "Scheduler.h"
15#include "Backend.h"
16#include "llvm/Support/Debug.h"
17#include "llvm/Support/raw_ostream.h"
18
19#define DEBUG_TYPE "llvm-mca"
20
21namespace mca {
22
23using namespace llvm;
24
25uint64_t ResourceState::selectNextInSequence() {
26 assert(isReady());
27 uint64_t Next = getNextInSequence();
28 while (!isSubResourceReady(Next)) {
29 updateNextInSequence();
30 Next = getNextInSequence();
31 }
32 return Next;
33}
34
35#ifndef NDEBUG
36void ResourceState::dump() const {
37 dbgs() << "MASK: " << ResourceMask << ", SIZE_MASK: " << ResourceSizeMask
38 << ", NEXT: " << NextInSequenceMask << ", RDYMASK: " << ReadyMask
39 << ", BufferSize=" << BufferSize
40 << ", AvailableSlots=" << AvailableSlots
41 << ", Reserved=" << Unavailable << '\n';
42}
43#endif
44
45// Adds a new resource state in Resources, as well as a new descriptor in
46// ResourceDescriptor. Map 'Resources' allows to quickly obtain ResourceState
47// objects from resource mask identifiers.
48void ResourceManager::addResource(const MCProcResourceDesc &Desc,
49 uint64_t Mask) {
50 assert(Resources.find(Mask) == Resources.end() && "Resource already added!");
51 Resources[Mask] = llvm::make_unique<ResourceState>(Desc, Mask);
52}
53
54// Populate vector ProcResID2Mask with resource masks. One per each processor
55// resource declared by the scheduling model.
56void ResourceManager::computeProcResourceMasks(const MCSchedModel &SM) {
57 unsigned ProcResourceID = 0;
58
59 // Create a unique bitmask for every processor resource unit.
60 // Skip resource at index 0, since it always references 'InvalidUnit'.
61 ProcResID2Mask.resize(SM.getNumProcResourceKinds());
62 for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) {
63 const MCProcResourceDesc &Desc = *SM.getProcResource(I);
64 if (Desc.SubUnitsIdxBegin)
65 continue;
66 ProcResID2Mask[I] = 1ULL << ProcResourceID;
67 ProcResourceID++;
68 }
69
70 // Create a unique bitmask for every processor resource group.
71 for (unsigned I = 1, E = SM.getNumProcResourceKinds(); I < E; ++I) {
72 const MCProcResourceDesc &Desc = *SM.getProcResource(I);
73 if (!Desc.SubUnitsIdxBegin)
74 continue;
75 ProcResID2Mask[I] |= 1ULL << ProcResourceID;
76 for (unsigned U = 0; U < Desc.NumUnits; ++U) {
77 uint64_t OtherMask = ProcResID2Mask[Desc.SubUnitsIdxBegin[U]];
78 ProcResID2Mask[I] |= OtherMask;
79 }
80 ProcResourceID++;
81 }
82}
83
84// Returns the actual resource consumed by this Use.
85// First, is the primary resource ID.
86// Second, is the specific sub-resource ID.
87std::pair<uint64_t, uint64_t> ResourceManager::selectPipe(uint64_t ResourceID) {
88 ResourceState &RS = *Resources[ResourceID];
89 uint64_t SubResourceID = RS.selectNextInSequence();
90 if (RS.isAResourceGroup())
91 return selectPipe(SubResourceID);
92 return std::pair<uint64_t, uint64_t>(ResourceID, SubResourceID);
93}
94
95void ResourceState::removeFromNextInSequence(uint64_t ID) {
96 assert(NextInSequenceMask);
97 assert(countPopulation(ID) == 1);
98 if (ID > getNextInSequence())
99 RemovedFromNextInSequence |= ID;
100 NextInSequenceMask = NextInSequenceMask & (~ID);
101 if (!NextInSequenceMask) {
102 NextInSequenceMask = ResourceSizeMask;
103 assert(NextInSequenceMask != RemovedFromNextInSequence);
104 NextInSequenceMask ^= RemovedFromNextInSequence;
105 RemovedFromNextInSequence = 0;
106 }
107}
108
109void ResourceManager::use(ResourceRef RR) {
110 // Mark the sub-resource referenced by RR as used.
111 ResourceState &RS = *Resources[RR.first];
112 RS.markSubResourceAsUsed(RR.second);
113 // If there are still available units in RR.first,
114 // then we are done.
115 if (RS.isReady())
116 return;
117
118 // Notify to other resources that RR.first is no longer available.
119 for (const std::pair<uint64_t, UniqueResourceState> &Res : Resources) {
120 ResourceState &Current = *Res.second.get();
121 if (!Current.isAResourceGroup() || Current.getResourceMask() == RR.first)
122 continue;
123
124 if (Current.containsResource(RR.first)) {
125 Current.markSubResourceAsUsed(RR.first);
126 Current.removeFromNextInSequence(RR.first);
127 }
128 }
129}
130
131void ResourceManager::release(ResourceRef RR) {
132 ResourceState &RS = *Resources[RR.first];
133 bool WasFullyUsed = !RS.isReady();
134 RS.releaseSubResource(RR.second);
135 if (!WasFullyUsed)
136 return;
137
138 for (const std::pair<uint64_t, UniqueResourceState> &Res : Resources) {
139 ResourceState &Current = *Res.second.get();
140 if (!Current.isAResourceGroup() || Current.getResourceMask() == RR.first)
141 continue;
142
143 if (Current.containsResource(RR.first))
144 Current.releaseSubResource(RR.first);
145 }
146}
147
148void ResourceManager::reserveDispatchHazardResources(const InstrDesc &Desc) {
149 for (const uint64_t R : Desc.Buffers) {
150 ResourceState &Resource = *Resources[R];
151 if (Resource.isADispatchHazard()) {
152 assert(!Resource.isReserved());
153 Resource.setReserved();
154 }
155 }
156}
157
158bool ResourceManager::canBeIssued(const InstrDesc &Desc) const {
159 return std::all_of(Desc.Resources.begin(), Desc.Resources.end(),
160 [&](const std::pair<uint64_t, const ResourceUsage> &E) {
161 unsigned NumUnits =
162 E.second.isReserved() ? 0U : E.second.NumUnits;
163 return isReady(E.first, NumUnits);
164 });
165}
166
167// Returns true if all resources are in-order, and there is at least one
168// resource which is a dispatch hazard (BufferSize = 0).
169bool ResourceManager::mustIssueImmediately(const InstrDesc &Desc) {
170 if (!canBeIssued(Desc))
171 return false;
172 bool AllInOrderResources = std::all_of(
173 Desc.Buffers.begin(), Desc.Buffers.end(), [&](const unsigned BufferMask) {
174 const ResourceState &Resource = *Resources[BufferMask];
175 return Resource.isInOrder() || Resource.isADispatchHazard();
176 });
177 if (!AllInOrderResources)
178 return false;
179
180 return std::any_of(Desc.Buffers.begin(), Desc.Buffers.end(),
181 [&](const unsigned BufferMask) {
182 return Resources[BufferMask]->isADispatchHazard();
183 });
184}
185
186double ResourceManager::getRThroughput(const InstrDesc &ID) const {
187 double RThroughput = 0;
188 for (const std::pair<uint64_t, ResourceUsage> &Usage : ID.Resources) {
189 const CycleSegment &CS = Usage.second.CS;
190 assert(CS.begin() == 0);
191
192 if (Usage.second.isReserved()) {
193 RThroughput = std::max(RThroughput, (double)CS.size());
194 continue;
195 }
196
197 unsigned Population = std::max(1U, countPopulation(Usage.first) - 1);
198 unsigned NumUnits = Population * getNumUnits(Usage.first);
199 NumUnits -= Usage.second.NumUnits - 1;
200 unsigned Cycles = CS.size();
201 RThroughput = std::max(RThroughput, (double)Cycles / NumUnits);
202 }
203 return RThroughput;
204}
205
206void ResourceManager::issueInstruction(
207 unsigned Index, const InstrDesc &Desc,
208 SmallVectorImpl<std::pair<ResourceRef, unsigned>> &Pipes) {
209 releaseBuffers(Desc);
210 for (const std::pair<uint64_t, ResourceUsage> &R : Desc.Resources) {
211 const CycleSegment &CS = R.second.CS;
212 if (!CS.size()) {
213 releaseResource(R.first);
214 continue;
215 }
216
217 assert(CS.begin() == 0 && "Invalid {Start, End} cycles!");
218 if (!R.second.isReserved()) {
219 ResourceRef Pipe = selectPipe(R.first);
220 use(Pipe);
221 BusyResources[Pipe] += CS.size();
222 Pipes.emplace_back(std::pair<ResourceRef, unsigned>(Pipe, CS.size()));
223 } else {
224 assert((countPopulation(R.first) > 1) && "Expected a group!");
225 // Mark this group as reserved.
226 assert(R.second.isReserved());
227 reserveResource(R.first);
228 BusyResources[ResourceRef(R.first, R.first)] += CS.size();
229 }
230 }
231}
232
233void ResourceManager::cycleEvent(SmallVectorImpl<ResourceRef> &ResourcesFreed) {
234 for (std::pair<ResourceRef, unsigned> &BR : BusyResources) {
235 if (BR.second)
236 BR.second--;
237 if (!BR.second) {
238 // Release this resource.
239 const ResourceRef &RR = BR.first;
240
241 if (countPopulation(RR.first) == 1)
242 release(RR);
243
244 releaseResource(RR.first);
245 ResourcesFreed.push_back(RR);
246 }
247 }
248
249 for (const ResourceRef &RF : ResourcesFreed)
250 BusyResources.erase(RF);
251}
252
253Instruction *Scheduler::scheduleInstruction(unsigned Idx, Instruction *MCIS) {
254 assert(WaitQueue.find(Idx) == WaitQueue.end());
255 assert(ReadyQueue.find(Idx) == ReadyQueue.end());
256 assert(IssuedQueue.find(Idx) == IssuedQueue.end());
257
258 // Special case where MCIS is a zero-latency instruction. A zero-latency
259 // instruction doesn't consume any scheduler resources. That is because it
260 // doesn't need to be executed. Most of the times, zero latency instructions
261 // are removed at register renaming stage. For example, register-register
262 // moves can be removed at register renaming stage by creating new aliases.
263 // Zero-idiom instruction (for example: a `xor reg, reg`) can also be
264 // eliminated at register renaming stage, since we know in advance that those
265 // clear their output register.
266 if (MCIS->isZeroLatency()) {
Andrea Di Biagio373c38a2018-03-08 20:21:55 +0000267 notifyInstructionReady(Idx);
Andrea Di Biagio3a6b0922018-03-08 13:05:02 +0000268 MCIS->forceExecuted();
269 notifyInstructionIssued(Idx, {});
270 notifyInstructionExecuted(Idx);
271 return MCIS;
272 }
273
274 // Consume entries in the reservation stations.
275 const InstrDesc &Desc = MCIS->getDesc();
276 Resources->reserveBuffers(Desc);
277
278 // Mark units with BufferSize=0 as reserved. These resources will only
279 // be released after MCIS is issued, and all the ResourceCycles for
280 // those units have been consumed.
281 Resources->reserveDispatchHazardResources(Desc);
282
283 bool MayLoad = Desc.MayLoad;
284 bool MayStore = Desc.MayStore;
285 if (MayLoad || MayStore)
286 LSU->reserve(Idx, MayLoad, MayStore, Desc.HasSideEffects);
287
288 MCIS->dispatch();
289 bool IsReady = MCIS->isReady();
290 if (IsReady && (MayLoad || MayStore))
291 IsReady &= LSU->isReady(Idx);
292
293 if (!IsReady) {
294 DEBUG(dbgs() << "[SCHEDULER] Adding " << Idx << " to the Wait Queue\n");
295 WaitQueue[Idx] = MCIS;
296 return MCIS;
297 }
298 notifyInstructionReady(Idx);
299
300 // Special case where the instruction is ready, and it uses an in-order
301 // dispatch/issue processor resource. The instruction is issued immediately to
302 // the pipelines. Any other in-order buffered resources (i.e. BufferSize=1)
303 // are consumed.
304 if (Resources->mustIssueImmediately(Desc)) {
305 DEBUG(dbgs() << "[SCHEDULER] Instruction " << Idx
306 << " issued immediately\n");
307 issueInstruction(MCIS, Idx);
308 return MCIS;
309 }
310
311 DEBUG(dbgs() << "[SCHEDULER] Adding " << Idx << " to the Ready Queue\n");
312 ReadyQueue[Idx] = MCIS;
313 return MCIS;
314}
315
316void Scheduler::cycleEvent(unsigned /* unused */) {
317 SmallVector<ResourceRef, 8> ResourcesFreed;
318 Resources->cycleEvent(ResourcesFreed);
319
320 for (const ResourceRef &RR : ResourcesFreed)
321 notifyResourceAvailable(RR);
322
323 updateIssuedQueue();
324 updatePendingQueue();
325 issue();
326}
327
328#ifndef NDEBUG
329void Scheduler::dump() const {
330 dbgs() << "[SCHEDULER]: WaitQueue size is: " << WaitQueue.size() << '\n';
331 dbgs() << "[SCHEDULER]: ReadyQueue size is: " << ReadyQueue.size() << '\n';
332 dbgs() << "[SCHEDULER]: IssuedQueue size is: " << IssuedQueue.size() << '\n';
333 Resources->dump();
334}
335#endif
336
337Scheduler::Event Scheduler::canBeDispatched(const InstrDesc &Desc) const {
338 if (Desc.MayLoad && LSU->isLQFull())
339 return HWS_LD_QUEUE_UNAVAILABLE;
340 if (Desc.MayStore && LSU->isSQFull())
341 return HWS_ST_QUEUE_UNAVAILABLE;
342
343 Scheduler::Event Event;
344 switch (Resources->canBeDispatched(Desc)) {
345 case ResourceStateEvent::RS_BUFFER_AVAILABLE:
346 Event = HWS_AVAILABLE;
347 break;
348 case ResourceStateEvent::RS_BUFFER_UNAVAILABLE:
349 Event = HWS_QUEUE_UNAVAILABLE;
350 break;
351 case ResourceStateEvent::RS_RESERVED:
352 Event = HWS_DISPATCH_GROUP_RESTRICTION;
353 }
354 return Event;
355}
356
357void Scheduler::issueInstruction(Instruction *IS, unsigned InstrIndex) {
358 // Issue the instruction and collect all the consumed resources
359 // into a vector. That vector is then used to notify the listener.
360 // Most instructions consume very few resurces (typically one or
361 // two resources). We use a small vector here, and conservatively
362 // initialize its capacity to 4. This should address the majority of
363 // the cases.
364 SmallVector<std::pair<ResourceRef, unsigned>, 4> UsedResources;
365
366 const InstrDesc &D = IS->getDesc();
367 Resources->issueInstruction(InstrIndex, D, UsedResources);
368 // Notify the instruction that it started executing.
369 // This updates the internal state of each write.
370 IS->execute();
371
372 if (D.MaxLatency) {
373 IssuedQueue[InstrIndex] = IS;
374 notifyInstructionIssued(InstrIndex, UsedResources);
375 } else {
376 // A zero latency instruction which reads and/or updates registers.
377 notifyInstructionIssued(InstrIndex, UsedResources);
378 notifyInstructionExecuted(InstrIndex);
379 }
380}
381
382void Scheduler::issue() {
383 std::vector<unsigned> ToRemove;
384 for (const QueueEntryTy QueueEntry : ReadyQueue) {
385 // Give priority to older instructions in ReadyQueue. The ready queue is
386 // ordered by key, and therefore older instructions are visited first.
387 Instruction *IS = QueueEntry.second;
388 const InstrDesc &D = IS->getDesc();
389 if (!Resources->canBeIssued(D))
390 continue;
391 unsigned InstrIndex = QueueEntry.first;
392 issueInstruction(IS, InstrIndex);
393 ToRemove.emplace_back(InstrIndex);
394 }
395
396 for (const unsigned InstrIndex : ToRemove)
397 ReadyQueue.erase(InstrIndex);
398}
399
400void Scheduler::updatePendingQueue() {
401 // Scan the set of waiting instructions and promote them to the
402 // ready queue if operands are all ready.
403 for (auto I = WaitQueue.begin(), E = WaitQueue.end(); I != E;) {
404 const QueueEntryTy Entry = *I;
405 Entry.second->cycleEvent();
406
407 const InstrDesc &Desc = Entry.second->getDesc();
408 bool IsMemOp = Desc.MayLoad || Desc.MayStore;
409 bool IsReady = Entry.second->isReady();
410 if (IsReady && IsMemOp)
411 IsReady &= LSU->isReady(Entry.first);
412
413 if (IsReady) {
414 notifyInstructionReady(Entry.first);
415 ReadyQueue[Entry.first] = Entry.second;
416 auto ToRemove = I;
417 ++I;
418 WaitQueue.erase(ToRemove);
419 } else {
420 ++I;
421 }
422 }
423}
424
425void Scheduler::updateIssuedQueue() {
426 for (auto I = IssuedQueue.begin(), E = IssuedQueue.end(); I != E;) {
427 const QueueEntryTy Entry = *I;
428 Entry.second->cycleEvent();
429 if (Entry.second->isExecuted()) {
430 notifyInstructionExecuted(Entry.first);
431 auto ToRemove = I;
432 ++I;
433 IssuedQueue.erase(ToRemove);
434 } else {
435 DEBUG(dbgs() << "[SCHEDULER]: Instruction " << Entry.first
436 << " is still executing.\n");
437 ++I;
438 }
439 }
440}
441
442void Scheduler::notifyInstructionIssued(
443 unsigned Index, const ArrayRef<std::pair<ResourceRef, unsigned>> &Used) {
444 Owner->notifyInstructionIssued(Index, Used);
445}
446
447void Scheduler::notifyInstructionExecuted(unsigned Index) {
448 LSU->onInstructionExecuted(Index);
449 Owner->notifyInstructionExecuted(Index);
450}
451
452void Scheduler::notifyInstructionReady(unsigned Index) {
453 Owner->notifyInstructionReady(Index);
454}
455
456void Scheduler::notifyResourceAvailable(const ResourceRef &RR) {
457 Owner->notifyResourceAvailable(RR);
458}
459} // namespace mca