//===--------------------- 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 S) { // Ensure we have a valid (non-null) strategy object. Strategy = S ? std::move(S) : std::make_unique(); } // 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> &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> &UsedResources, SmallVectorImpl &PendingInstructions, SmallVectorImpl &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 &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 &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 &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 &Insts) { Insts.insert(Insts.end(), ReadySet.begin(), ReadySet.end()); return BusyResourceUnits; } void Scheduler::analyzeDataDependencies(SmallVectorImpl &RegDeps, SmallVectorImpl &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 &Freed, SmallVectorImpl &Executed, SmallVectorImpl &Pending, SmallVectorImpl &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