| //===--------------------- Scheduler.cpp ------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // A scheduler for processor resource units and processor resource groups. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/MCA/HardwareUnits/Scheduler.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| namespace llvm { |
| namespace mca { |
| |
| #define DEBUG_TYPE "llvm-mca" |
| |
| void Scheduler::initializeStrategy(std::unique_ptr<SchedulerStrategy> S) { |
| // Ensure we have a valid (non-null) strategy object. |
| Strategy = S ? std::move(S) : std::make_unique<DefaultSchedulerStrategy>(); |
| } |
| |
| // Anchor the vtable of SchedulerStrategy and DefaultSchedulerStrategy. |
| SchedulerStrategy::~SchedulerStrategy() = default; |
| DefaultSchedulerStrategy::~DefaultSchedulerStrategy() = default; |
| |
| #ifndef NDEBUG |
| void Scheduler::dump() const { |
| dbgs() << "[SCHEDULER]: WaitSet size is: " << WaitSet.size() << '\n'; |
| dbgs() << "[SCHEDULER]: ReadySet size is: " << ReadySet.size() << '\n'; |
| dbgs() << "[SCHEDULER]: IssuedSet size is: " << IssuedSet.size() << '\n'; |
| Resources->dump(); |
| } |
| #endif |
| |
| Scheduler::Status Scheduler::isAvailable(const InstRef &IR) { |
| ResourceStateEvent RSE = |
| Resources->canBeDispatched(IR.getInstruction()->getUsedBuffers()); |
| HadTokenStall = RSE != RS_BUFFER_AVAILABLE; |
| |
| switch (RSE) { |
| case ResourceStateEvent::RS_BUFFER_UNAVAILABLE: |
| return Scheduler::SC_BUFFERS_FULL; |
| case ResourceStateEvent::RS_RESERVED: |
| return Scheduler::SC_DISPATCH_GROUP_STALL; |
| case ResourceStateEvent::RS_BUFFER_AVAILABLE: |
| break; |
| } |
| |
| // Give lower priority to LSUnit stall events. |
| LSUnit::Status LSS = LSU.isAvailable(IR); |
| HadTokenStall = LSS != LSUnit::LSU_AVAILABLE; |
| |
| switch (LSS) { |
| case LSUnit::LSU_LQUEUE_FULL: |
| return Scheduler::SC_LOAD_QUEUE_FULL; |
| case LSUnit::LSU_SQUEUE_FULL: |
| return Scheduler::SC_STORE_QUEUE_FULL; |
| case LSUnit::LSU_AVAILABLE: |
| return Scheduler::SC_AVAILABLE; |
| } |
| |
| llvm_unreachable("Don't know how to process this LSU state result!"); |
| } |
| |
| void Scheduler::issueInstructionImpl( |
| InstRef &IR, |
| SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &UsedResources) { |
| Instruction *IS = IR.getInstruction(); |
| const InstrDesc &D = IS->getDesc(); |
| |
| // Issue the instruction and collect all the consumed resources |
| // into a vector. That vector is then used to notify the listener. |
| Resources->issueInstruction(D, UsedResources); |
| |
| // Notify the instruction that it started executing. |
| // This updates the internal state of each write. |
| IS->execute(IR.getSourceIndex()); |
| |
| IS->computeCriticalRegDep(); |
| |
| if (IS->isMemOp()) { |
| LSU.onInstructionIssued(IR); |
| const MemoryGroup &Group = LSU.getGroup(IS->getLSUTokenID()); |
| IS->setCriticalMemDep(Group.getCriticalPredecessor()); |
| } |
| |
| if (IS->isExecuting()) |
| IssuedSet.emplace_back(IR); |
| else if (IS->isExecuted()) |
| LSU.onInstructionExecuted(IR); |
| } |
| |
| // Release the buffered resources and issue the instruction. |
| void Scheduler::issueInstruction( |
| InstRef &IR, |
| SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &UsedResources, |
| SmallVectorImpl<InstRef> &PendingInstructions, |
| SmallVectorImpl<InstRef> &ReadyInstructions) { |
| const Instruction &Inst = *IR.getInstruction(); |
| bool HasDependentUsers = Inst.hasDependentUsers(); |
| HasDependentUsers |= Inst.isMemOp() && LSU.hasDependentUsers(IR); |
| |
| Resources->releaseBuffers(Inst.getUsedBuffers()); |
| issueInstructionImpl(IR, UsedResources); |
| // Instructions that have been issued during this cycle might have unblocked |
| // other dependent instructions. Dependent instructions may be issued during |
| // this same cycle if operands have ReadAdvance entries. Promote those |
| // instructions to the ReadySet and notify the caller that those are ready. |
| if (HasDependentUsers) |
| if (promoteToPendingSet(PendingInstructions)) |
| promoteToReadySet(ReadyInstructions); |
| } |
| |
| bool Scheduler::promoteToReadySet(SmallVectorImpl<InstRef> &Ready) { |
| // Scan the set of waiting instructions and promote them to the |
| // ready set if operands are all ready. |
| unsigned PromotedElements = 0; |
| for (auto I = PendingSet.begin(), E = PendingSet.end(); I != E;) { |
| InstRef &IR = *I; |
| if (!IR) |
| break; |
| |
| // Check if there are unsolved register dependencies. |
| Instruction &IS = *IR.getInstruction(); |
| if (!IS.isReady() && !IS.updatePending()) { |
| ++I; |
| continue; |
| } |
| // Check if there are unsolved memory dependencies. |
| if (IS.isMemOp() && !LSU.isReady(IR)) { |
| ++I; |
| continue; |
| } |
| |
| LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR |
| << " promoted to the READY set.\n"); |
| |
| Ready.emplace_back(IR); |
| ReadySet.emplace_back(IR); |
| |
| IR.invalidate(); |
| ++PromotedElements; |
| std::iter_swap(I, E - PromotedElements); |
| } |
| |
| PendingSet.resize(PendingSet.size() - PromotedElements); |
| return PromotedElements; |
| } |
| |
| bool Scheduler::promoteToPendingSet(SmallVectorImpl<InstRef> &Pending) { |
| // Scan the set of waiting instructions and promote them to the |
| // pending set if operands are all ready. |
| unsigned RemovedElements = 0; |
| for (auto I = WaitSet.begin(), E = WaitSet.end(); I != E;) { |
| InstRef &IR = *I; |
| if (!IR) |
| break; |
| |
| // Check if this instruction is now ready. In case, force |
| // a transition in state using method 'updateDispatched()'. |
| Instruction &IS = *IR.getInstruction(); |
| if (IS.isDispatched() && !IS.updateDispatched()) { |
| ++I; |
| continue; |
| } |
| |
| if (IS.isMemOp() && LSU.isWaiting(IR)) { |
| ++I; |
| continue; |
| } |
| |
| LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR |
| << " promoted to the PENDING set.\n"); |
| |
| Pending.emplace_back(IR); |
| PendingSet.emplace_back(IR); |
| |
| IR.invalidate(); |
| ++RemovedElements; |
| std::iter_swap(I, E - RemovedElements); |
| } |
| |
| WaitSet.resize(WaitSet.size() - RemovedElements); |
| return RemovedElements; |
| } |
| |
| InstRef Scheduler::select() { |
| unsigned QueueIndex = ReadySet.size(); |
| for (unsigned I = 0, E = ReadySet.size(); I != E; ++I) { |
| InstRef &IR = ReadySet[I]; |
| if (QueueIndex == ReadySet.size() || |
| Strategy->compare(IR, ReadySet[QueueIndex])) { |
| Instruction &IS = *IR.getInstruction(); |
| uint64_t BusyResourceMask = Resources->checkAvailability(IS.getDesc()); |
| if (BusyResourceMask) |
| IS.setCriticalResourceMask(BusyResourceMask); |
| BusyResourceUnits |= BusyResourceMask; |
| if (!BusyResourceMask) |
| QueueIndex = I; |
| } |
| } |
| |
| if (QueueIndex == ReadySet.size()) |
| return InstRef(); |
| |
| // We found an instruction to issue. |
| InstRef IR = ReadySet[QueueIndex]; |
| std::swap(ReadySet[QueueIndex], ReadySet[ReadySet.size() - 1]); |
| ReadySet.pop_back(); |
| return IR; |
| } |
| |
| void Scheduler::updateIssuedSet(SmallVectorImpl<InstRef> &Executed) { |
| unsigned RemovedElements = 0; |
| for (auto I = IssuedSet.begin(), E = IssuedSet.end(); I != E;) { |
| InstRef &IR = *I; |
| if (!IR) |
| break; |
| Instruction &IS = *IR.getInstruction(); |
| if (!IS.isExecuted()) { |
| LLVM_DEBUG(dbgs() << "[SCHEDULER]: Instruction #" << IR |
| << " is still executing.\n"); |
| ++I; |
| continue; |
| } |
| |
| // Instruction IR has completed execution. |
| LSU.onInstructionExecuted(IR); |
| Executed.emplace_back(IR); |
| ++RemovedElements; |
| IR.invalidate(); |
| std::iter_swap(I, E - RemovedElements); |
| } |
| |
| IssuedSet.resize(IssuedSet.size() - RemovedElements); |
| } |
| |
| uint64_t Scheduler::analyzeResourcePressure(SmallVectorImpl<InstRef> &Insts) { |
| Insts.insert(Insts.end(), ReadySet.begin(), ReadySet.end()); |
| return BusyResourceUnits; |
| } |
| |
| void Scheduler::analyzeDataDependencies(SmallVectorImpl<InstRef> &RegDeps, |
| SmallVectorImpl<InstRef> &MemDeps) { |
| const auto EndIt = PendingSet.end() - NumDispatchedToThePendingSet; |
| for (const InstRef &IR : make_range(PendingSet.begin(), EndIt)) { |
| const Instruction &IS = *IR.getInstruction(); |
| if (Resources->checkAvailability(IS.getDesc())) |
| continue; |
| |
| if (IS.isMemOp() && LSU.isPending(IR)) |
| MemDeps.emplace_back(IR); |
| |
| if (IS.isPending()) |
| RegDeps.emplace_back(IR); |
| } |
| } |
| |
| void Scheduler::cycleEvent(SmallVectorImpl<ResourceRef> &Freed, |
| SmallVectorImpl<InstRef> &Executed, |
| SmallVectorImpl<InstRef> &Pending, |
| SmallVectorImpl<InstRef> &Ready) { |
| LSU.cycleEvent(); |
| |
| // Release consumed resources. |
| Resources->cycleEvent(Freed); |
| |
| for (InstRef &IR : IssuedSet) |
| IR.getInstruction()->cycleEvent(); |
| updateIssuedSet(Executed); |
| |
| for (InstRef &IR : PendingSet) |
| IR.getInstruction()->cycleEvent(); |
| |
| for (InstRef &IR : WaitSet) |
| IR.getInstruction()->cycleEvent(); |
| |
| promoteToPendingSet(Pending); |
| promoteToReadySet(Ready); |
| |
| NumDispatchedToThePendingSet = 0; |
| BusyResourceUnits = 0; |
| } |
| |
| bool Scheduler::mustIssueImmediately(const InstRef &IR) const { |
| const InstrDesc &Desc = IR.getInstruction()->getDesc(); |
| if (Desc.isZeroLatency()) |
| return true; |
| // Instructions that use an in-order dispatch/issue processor resource must be |
| // issued immediately to the pipeline(s). Any other in-order buffered |
| // resources (i.e. BufferSize=1) is consumed. |
| return Desc.MustIssueImmediately; |
| } |
| |
| bool Scheduler::dispatch(InstRef &IR) { |
| Instruction &IS = *IR.getInstruction(); |
| Resources->reserveBuffers(IS.getUsedBuffers()); |
| |
| // If necessary, reserve queue entries in the load-store unit (LSU). |
| if (IS.isMemOp()) |
| IS.setLSUTokenID(LSU.dispatch(IR)); |
| |
| if (IS.isDispatched() || (IS.isMemOp() && LSU.isWaiting(IR))) { |
| LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the WaitSet\n"); |
| WaitSet.push_back(IR); |
| return false; |
| } |
| |
| if (IS.isPending() || (IS.isMemOp() && LSU.isPending(IR))) { |
| LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR |
| << " to the PendingSet\n"); |
| PendingSet.push_back(IR); |
| ++NumDispatchedToThePendingSet; |
| return false; |
| } |
| |
| assert(IS.isReady() && (!IS.isMemOp() || LSU.isReady(IR)) && |
| "Unexpected internal state found!"); |
| // Don't add a zero-latency instruction to the Ready queue. |
| // A zero-latency instruction doesn't consume any scheduler resources. That is |
| // because it doesn't need to be executed, and it is often removed at register |
| // renaming stage. For example, register-register moves are often optimized at |
| // register renaming stage by simply updating register aliases. On some |
| // targets, zero-idiom instructions (for example: a xor that clears the value |
| // of a register) are treated specially, and are often eliminated at register |
| // renaming stage. |
| if (!mustIssueImmediately(IR)) { |
| LLVM_DEBUG(dbgs() << "[SCHEDULER] Adding #" << IR << " to the ReadySet\n"); |
| ReadySet.push_back(IR); |
| } |
| |
| return true; |
| } |
| |
| } // namespace mca |
| } // namespace llvm |