diff options
Diffstat (limited to 'contrib/llvm-project/llvm/lib/MCA/HardwareUnits/Scheduler.cpp')
| -rw-r--r-- | contrib/llvm-project/llvm/lib/MCA/HardwareUnits/Scheduler.cpp | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/contrib/llvm-project/llvm/lib/MCA/HardwareUnits/Scheduler.cpp b/contrib/llvm-project/llvm/lib/MCA/HardwareUnits/Scheduler.cpp new file mode 100644 index 000000000000..0f0f2ffb8325 --- /dev/null +++ b/contrib/llvm-project/llvm/lib/MCA/HardwareUnits/Scheduler.cpp @@ -0,0 +1,343 @@ +//===--------------------- 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) : llvm::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) { + const InstrDesc &Desc = IR.getInstruction()->getDesc(); + + ResourceStateEvent RSE = Resources->canBeDispatched(Desc.Buffers); + 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.getDesc().Buffers); + 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(); + const InstrDesc &Desc = IS.getDesc(); + Resources->reserveBuffers(Desc.Buffers); + + // 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 |
