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