forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			342 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--------------------- 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
 |