1 //===--------------------- 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 "Support.h"
16 #include "llvm/Support/Debug.h"
17 #include "llvm/Support/raw_ostream.h"
18
19 namespace mca {
20
21 using namespace llvm;
22
23 #define DEBUG_TYPE "llvm-mca"
24
selectNextInSequence()25 uint64_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
dump() const36 void 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
initialize(const llvm::MCSchedModel & SM)45 void ResourceManager::initialize(const llvm::MCSchedModel &SM) {
46 computeProcResourceMasks(SM, ProcResID2Mask);
47 for (unsigned I = 0, E = SM.getNumProcResourceKinds(); I < E; ++I)
48 addResource(*SM.getProcResource(I), I, ProcResID2Mask[I]);
49 }
50
51 // Adds a new resource state in Resources, as well as a new descriptor in
52 // ResourceDescriptor. Map 'Resources' allows to quickly obtain ResourceState
53 // objects from resource mask identifiers.
addResource(const MCProcResourceDesc & Desc,unsigned Index,uint64_t Mask)54 void ResourceManager::addResource(const MCProcResourceDesc &Desc,
55 unsigned Index, uint64_t Mask) {
56 assert(Resources.find(Mask) == Resources.end() && "Resource already added!");
57 Resources[Mask] = llvm::make_unique<ResourceState>(Desc, Index, Mask);
58 }
59
60 // Returns the actual resource consumed by this Use.
61 // First, is the primary resource ID.
62 // Second, is the specific sub-resource ID.
selectPipe(uint64_t ResourceID)63 std::pair<uint64_t, uint64_t> ResourceManager::selectPipe(uint64_t ResourceID) {
64 ResourceState &RS = *Resources[ResourceID];
65 uint64_t SubResourceID = RS.selectNextInSequence();
66 if (RS.isAResourceGroup())
67 return selectPipe(SubResourceID);
68 return std::pair<uint64_t, uint64_t>(ResourceID, SubResourceID);
69 }
70
removeFromNextInSequence(uint64_t ID)71 void ResourceState::removeFromNextInSequence(uint64_t ID) {
72 assert(NextInSequenceMask);
73 assert(countPopulation(ID) == 1);
74 if (ID > getNextInSequence())
75 RemovedFromNextInSequence |= ID;
76 NextInSequenceMask = NextInSequenceMask & (~ID);
77 if (!NextInSequenceMask) {
78 NextInSequenceMask = ResourceSizeMask;
79 assert(NextInSequenceMask != RemovedFromNextInSequence);
80 NextInSequenceMask ^= RemovedFromNextInSequence;
81 RemovedFromNextInSequence = 0;
82 }
83 }
84
use(ResourceRef RR)85 void ResourceManager::use(ResourceRef RR) {
86 // Mark the sub-resource referenced by RR as used.
87 ResourceState &RS = *Resources[RR.first];
88 RS.markSubResourceAsUsed(RR.second);
89 // If there are still available units in RR.first,
90 // then we are done.
91 if (RS.isReady())
92 return;
93
94 // Notify to other resources that RR.first is no longer available.
95 for (const std::pair<uint64_t, UniqueResourceState> &Res : Resources) {
96 ResourceState &Current = *Res.second.get();
97 if (!Current.isAResourceGroup() || Current.getResourceMask() == RR.first)
98 continue;
99
100 if (Current.containsResource(RR.first)) {
101 Current.markSubResourceAsUsed(RR.first);
102 Current.removeFromNextInSequence(RR.first);
103 }
104 }
105 }
106
release(ResourceRef RR)107 void ResourceManager::release(ResourceRef RR) {
108 ResourceState &RS = *Resources[RR.first];
109 bool WasFullyUsed = !RS.isReady();
110 RS.releaseSubResource(RR.second);
111 if (!WasFullyUsed)
112 return;
113
114 for (const std::pair<uint64_t, UniqueResourceState> &Res : Resources) {
115 ResourceState &Current = *Res.second.get();
116 if (!Current.isAResourceGroup() || Current.getResourceMask() == RR.first)
117 continue;
118
119 if (Current.containsResource(RR.first))
120 Current.releaseSubResource(RR.first);
121 }
122 }
123
124 ResourceStateEvent
canBeDispatched(ArrayRef<uint64_t> Buffers) const125 ResourceManager::canBeDispatched(ArrayRef<uint64_t> Buffers) const {
126 ResourceStateEvent Result = ResourceStateEvent::RS_BUFFER_AVAILABLE;
127 for (uint64_t Buffer : Buffers) {
128 Result = isBufferAvailable(Buffer);
129 if (Result != ResourceStateEvent::RS_BUFFER_AVAILABLE)
130 break;
131 }
132 return Result;
133 }
134
reserveBuffers(ArrayRef<uint64_t> Buffers)135 void ResourceManager::reserveBuffers(ArrayRef<uint64_t> Buffers) {
136 for (const uint64_t R : Buffers) {
137 reserveBuffer(R);
138 ResourceState &Resource = *Resources[R];
139 if (Resource.isADispatchHazard()) {
140 assert(!Resource.isReserved());
141 Resource.setReserved();
142 }
143 }
144 }
145
releaseBuffers(ArrayRef<uint64_t> Buffers)146 void ResourceManager::releaseBuffers(ArrayRef<uint64_t> Buffers) {
147 for (const uint64_t R : Buffers)
148 releaseBuffer(R);
149 }
150
canBeIssued(const InstrDesc & Desc) const151 bool ResourceManager::canBeIssued(const InstrDesc &Desc) const {
152 return std::all_of(Desc.Resources.begin(), Desc.Resources.end(),
153 [&](const std::pair<uint64_t, const ResourceUsage> &E) {
154 unsigned NumUnits =
155 E.second.isReserved() ? 0U : E.second.NumUnits;
156 return isReady(E.first, NumUnits);
157 });
158 }
159
160 // Returns true if all resources are in-order, and there is at least one
161 // resource which is a dispatch hazard (BufferSize = 0).
mustIssueImmediately(const InstrDesc & Desc)162 bool ResourceManager::mustIssueImmediately(const InstrDesc &Desc) {
163 if (!canBeIssued(Desc))
164 return false;
165 bool AllInOrderResources = all_of(Desc.Buffers, [&](uint64_t BufferMask) {
166 const ResourceState &Resource = *Resources[BufferMask];
167 return Resource.isInOrder() || Resource.isADispatchHazard();
168 });
169 if (!AllInOrderResources)
170 return false;
171
172 return any_of(Desc.Buffers, [&](uint64_t BufferMask) {
173 return Resources[BufferMask]->isADispatchHazard();
174 });
175 }
176
issueInstruction(const InstrDesc & Desc,SmallVectorImpl<std::pair<ResourceRef,double>> & Pipes)177 void ResourceManager::issueInstruction(
178 const InstrDesc &Desc,
179 SmallVectorImpl<std::pair<ResourceRef, double>> &Pipes) {
180 for (const std::pair<uint64_t, ResourceUsage> &R : Desc.Resources) {
181 const CycleSegment &CS = R.second.CS;
182 if (!CS.size()) {
183 releaseResource(R.first);
184 continue;
185 }
186
187 assert(CS.begin() == 0 && "Invalid {Start, End} cycles!");
188 if (!R.second.isReserved()) {
189 ResourceRef Pipe = selectPipe(R.first);
190 use(Pipe);
191 BusyResources[Pipe] += CS.size();
192 // Replace the resource mask with a valid processor resource index.
193 const ResourceState &RS = *Resources[Pipe.first];
194 Pipe.first = RS.getProcResourceID();
195 Pipes.emplace_back(
196 std::pair<ResourceRef, double>(Pipe, static_cast<double>(CS.size())));
197 } else {
198 assert((countPopulation(R.first) > 1) && "Expected a group!");
199 // Mark this group as reserved.
200 assert(R.second.isReserved());
201 reserveResource(R.first);
202 BusyResources[ResourceRef(R.first, R.first)] += CS.size();
203 }
204 }
205 }
206
cycleEvent(SmallVectorImpl<ResourceRef> & ResourcesFreed)207 void ResourceManager::cycleEvent(SmallVectorImpl<ResourceRef> &ResourcesFreed) {
208 for (std::pair<ResourceRef, unsigned> &BR : BusyResources) {
209 if (BR.second)
210 BR.second--;
211 if (!BR.second) {
212 // Release this resource.
213 const ResourceRef &RR = BR.first;
214
215 if (countPopulation(RR.first) == 1)
216 release(RR);
217
218 releaseResource(RR.first);
219 ResourcesFreed.push_back(RR);
220 }
221 }
222
223 for (const ResourceRef &RF : ResourcesFreed)
224 BusyResources.erase(RF);
225 }
226
227 #ifndef NDEBUG
dump() const228 void Scheduler::dump() const {
229 dbgs() << "[SCHEDULER]: WaitQueue size is: " << WaitQueue.size() << '\n';
230 dbgs() << "[SCHEDULER]: ReadyQueue size is: " << ReadyQueue.size() << '\n';
231 dbgs() << "[SCHEDULER]: IssuedQueue size is: " << IssuedQueue.size() << '\n';
232 Resources->dump();
233 }
234 #endif
235
canBeDispatched(const InstRef & IR,HWStallEvent::GenericEventType & Event) const236 bool Scheduler::canBeDispatched(const InstRef &IR,
237 HWStallEvent::GenericEventType &Event) const {
238 Event = HWStallEvent::Invalid;
239 const InstrDesc &Desc = IR.getInstruction()->getDesc();
240
241 if (Desc.MayLoad && LSU->isLQFull())
242 Event = HWStallEvent::LoadQueueFull;
243 else if (Desc.MayStore && LSU->isSQFull())
244 Event = HWStallEvent::StoreQueueFull;
245 else {
246 switch (Resources->canBeDispatched(Desc.Buffers)) {
247 default:
248 return true;
249 case ResourceStateEvent::RS_BUFFER_UNAVAILABLE:
250 Event = HWStallEvent::SchedulerQueueFull;
251 break;
252 case ResourceStateEvent::RS_RESERVED:
253 Event = HWStallEvent::DispatchGroupStall;
254 }
255 }
256
257 return false;
258 }
259
issueInstructionImpl(InstRef & IR,SmallVectorImpl<std::pair<ResourceRef,double>> & UsedResources)260 void Scheduler::issueInstructionImpl(
261 InstRef &IR,
262 SmallVectorImpl<std::pair<ResourceRef, double>> &UsedResources) {
263 Instruction *IS = IR.getInstruction();
264 const InstrDesc &D = IS->getDesc();
265
266 // Issue the instruction and collect all the consumed resources
267 // into a vector. That vector is then used to notify the listener.
268 Resources->issueInstruction(D, UsedResources);
269
270 // Notify the instruction that it started executing.
271 // This updates the internal state of each write.
272 IS->execute();
273
274 if (IS->isExecuting())
275 IssuedQueue[IR.getSourceIndex()] = IS;
276 }
277
278 // Release the buffered resources and issue the instruction.
issueInstruction(InstRef & IR,SmallVectorImpl<std::pair<ResourceRef,double>> & UsedResources)279 void Scheduler::issueInstruction(
280 InstRef &IR,
281 SmallVectorImpl<std::pair<ResourceRef, double>> &UsedResources) {
282 const InstrDesc &Desc = IR.getInstruction()->getDesc();
283 releaseBuffers(Desc.Buffers);
284 issueInstructionImpl(IR, UsedResources);
285 }
286
promoteToReadyQueue(SmallVectorImpl<InstRef> & Ready)287 void Scheduler::promoteToReadyQueue(SmallVectorImpl<InstRef> &Ready) {
288 // Scan the set of waiting instructions and promote them to the
289 // ready queue if operands are all ready.
290 for (auto I = WaitQueue.begin(), E = WaitQueue.end(); I != E;) {
291 const unsigned IID = I->first;
292 Instruction *IS = I->second;
293
294 // Check if this instruction is now ready. In case, force
295 // a transition in state using method 'update()'.
296 if (!IS->isReady())
297 IS->update();
298
299 const InstrDesc &Desc = IS->getDesc();
300 bool IsMemOp = Desc.MayLoad || Desc.MayStore;
301 if (!IS->isReady() || (IsMemOp && !LSU->isReady({IID, IS}))) {
302 ++I;
303 continue;
304 }
305
306 Ready.emplace_back(IID, IS);
307 ReadyQueue[IID] = IS;
308 auto ToRemove = I;
309 ++I;
310 WaitQueue.erase(ToRemove);
311 }
312 }
313
select()314 InstRef Scheduler::select() {
315 // Find the oldest ready-to-issue instruction in the ReadyQueue.
316 auto It = std::find_if(ReadyQueue.begin(), ReadyQueue.end(),
317 [&](const QueueEntryTy &Entry) {
318 const InstrDesc &D = Entry.second->getDesc();
319 return Resources->canBeIssued(D);
320 });
321
322 if (It == ReadyQueue.end())
323 return {0, nullptr};
324
325 // We want to prioritize older instructions over younger instructions to
326 // minimize the pressure on the reorder buffer. We also want to
327 // rank higher the instructions with more users to better expose ILP.
328
329 // Compute a rank value based on the age of an instruction (i.e. its source
330 // index) and its number of users. The lower the rank value, the better.
331 int Rank = It->first - It->second->getNumUsers();
332 for (auto I = It, E = ReadyQueue.end(); I != E; ++I) {
333 int CurrentRank = I->first - I->second->getNumUsers();
334 if (CurrentRank < Rank) {
335 const InstrDesc &D = I->second->getDesc();
336 if (Resources->canBeIssued(D))
337 It = I;
338 }
339 }
340
341 // We found an instruction to issue.
342 InstRef IR(It->first, It->second);
343 ReadyQueue.erase(It);
344 return IR;
345 }
346
updatePendingQueue(SmallVectorImpl<InstRef> & Ready)347 void Scheduler::updatePendingQueue(SmallVectorImpl<InstRef> &Ready) {
348 // Notify to instructions in the pending queue that a new cycle just
349 // started.
350 for (QueueEntryTy Entry : WaitQueue)
351 Entry.second->cycleEvent();
352 promoteToReadyQueue(Ready);
353 }
354
updateIssuedQueue(SmallVectorImpl<InstRef> & Executed)355 void Scheduler::updateIssuedQueue(SmallVectorImpl<InstRef> &Executed) {
356 for (auto I = IssuedQueue.begin(), E = IssuedQueue.end(); I != E;) {
357 const QueueEntryTy Entry = *I;
358 Instruction *IS = Entry.second;
359 IS->cycleEvent();
360 if (IS->isExecuted()) {
361 Executed.push_back({Entry.first, Entry.second});
362 auto ToRemove = I;
363 ++I;
364 IssuedQueue.erase(ToRemove);
365 } else {
366 LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << Entry.first
367 << " is still executing.\n");
368 ++I;
369 }
370 }
371 }
372
onInstructionExecuted(const InstRef & IR)373 void Scheduler::onInstructionExecuted(const InstRef &IR) {
374 LSU->onInstructionExecuted(IR);
375 }
376
reclaimSimulatedResources(SmallVectorImpl<ResourceRef> & Freed)377 void Scheduler::reclaimSimulatedResources(SmallVectorImpl<ResourceRef> &Freed) {
378 Resources->cycleEvent(Freed);
379 }
380
reserveResources(InstRef & IR)381 bool Scheduler::reserveResources(InstRef &IR) {
382 // If necessary, reserve queue entries in the load-store unit (LSU).
383 const bool Reserved = LSU->reserve(IR);
384 if (!IR.getInstruction()->isReady() || (Reserved && !LSU->isReady(IR))) {
385 LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the Wait Queue\n");
386 WaitQueue[IR.getSourceIndex()] = IR.getInstruction();
387 return false;
388 }
389 return true;
390 }
391
issueImmediately(InstRef & IR)392 bool Scheduler::issueImmediately(InstRef &IR) {
393 const InstrDesc &Desc = IR.getInstruction()->getDesc();
394 if (!Desc.isZeroLatency() && !Resources->mustIssueImmediately(Desc)) {
395 LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR
396 << " to the Ready Queue\n");
397 ReadyQueue[IR.getSourceIndex()] = IR.getInstruction();
398 return false;
399 }
400 return true;
401 }
402
403 } // namespace mca
404