123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- //===--------------------- 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) {
- llvm::append_range(Insts, ReadySet);
- 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
|