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