//===--------------------- Scheduler.h ------------------------*- 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
 
//
 
//===----------------------------------------------------------------------===//
 
/// \file
 
///
 
/// A scheduler for Processor Resource Units and Processor Resource Groups.
 
///
 
//===----------------------------------------------------------------------===//
 
 
 
#ifndef LLVM_MCA_HARDWAREUNITS_SCHEDULER_H
 
#define LLVM_MCA_HARDWAREUNITS_SCHEDULER_H
 
 
 
#include "llvm/ADT/SmallVector.h"
 
#include "llvm/MC/MCSchedule.h"
 
#include "llvm/MCA/HardwareUnits/HardwareUnit.h"
 
#include "llvm/MCA/HardwareUnits/LSUnit.h"
 
#include "llvm/MCA/HardwareUnits/ResourceManager.h"
 
#include "llvm/MCA/Support.h"
 
 
 
namespace llvm {
 
namespace mca {
 
 
 
class SchedulerStrategy {
 
public:
 
  SchedulerStrategy() = default;
 
  virtual ~SchedulerStrategy();
 
 
 
  /// Returns true if Lhs should take priority over Rhs.
 
  ///
 
  /// This method is used by class Scheduler to select the "best" ready
 
  /// instruction to issue to the underlying pipelines.
 
  virtual bool compare(const InstRef &Lhs, const InstRef &Rhs) const = 0;
 
};
 
 
 
/// Default instruction selection strategy used by class Scheduler.
 
class DefaultSchedulerStrategy : public SchedulerStrategy {
 
  /// This method ranks instructions based on their age, and the number of known
 
  /// users. The lower the rank value, the better.
 
  int computeRank(const InstRef &Lhs) const {
 
    return Lhs.getSourceIndex() - Lhs.getInstruction()->getNumUsers();
 
  }
 
 
 
public:
 
  DefaultSchedulerStrategy() = default;
 
  virtual ~DefaultSchedulerStrategy();
 
 
 
  bool compare(const InstRef &Lhs, const InstRef &Rhs) const override {
 
    int LhsRank = computeRank(Lhs);
 
    int RhsRank = computeRank(Rhs);
 
 
 
    /// Prioritize older instructions over younger instructions to minimize the
 
    /// pressure on the reorder buffer.
 
    if (LhsRank == RhsRank)
 
      return Lhs.getSourceIndex() < Rhs.getSourceIndex();
 
    return LhsRank < RhsRank;
 
  }
 
};
 
 
 
/// Class Scheduler is responsible for issuing instructions to pipeline
 
/// resources.
 
///
 
/// Internally, it delegates to a ResourceManager the management of processor
 
/// resources. This class is also responsible for tracking the progress of
 
/// instructions from the dispatch stage, until the write-back stage.
 
///
 
class Scheduler : public HardwareUnit {
 
  LSUnitBase &LSU;
 
 
 
  // Instruction selection strategy for this Scheduler.
 
  std::unique_ptr<SchedulerStrategy> Strategy;
 
 
 
  // Hardware resources that are managed by this scheduler.
 
  std::unique_ptr<ResourceManager> Resources;
 
 
 
  // Instructions dispatched to the Scheduler are internally classified based on
 
  // the instruction stage (see Instruction::InstrStage).
 
  //
 
  // An Instruction dispatched to the Scheduler is added to the WaitSet if not
 
  // all its register operands are available, and at least one latency is
 
  // unknown.  By construction, the WaitSet only contains instructions that are
 
  // in the IS_DISPATCHED stage.
 
  //
 
  // An Instruction transitions from the WaitSet to the PendingSet if the
 
  // instruction is not ready yet, but the latency of every register read is
 
  // known.  Instructions in the PendingSet can only be in the IS_PENDING or
 
  // IS_READY stage.  Only IS_READY instructions that are waiting on memory
 
  // dependencies can be added to the PendingSet.
 
  //
 
  // Instructions in the PendingSet are immediately dominated only by
 
  // instructions that have already been issued to the underlying pipelines.  In
 
  // the presence of bottlenecks caused by data dependencies, the PendingSet can
 
  // be inspected to identify problematic data dependencies between
 
  // instructions.
 
  //
 
  // An instruction is moved to the ReadySet when all register operands become
 
  // available, and all memory dependencies are met.  Instructions that are
 
  // moved from the PendingSet to the ReadySet must transition to the 'IS_READY'
 
  // stage.
 
  //
 
  // On every cycle, the Scheduler checks if it can promote instructions from the
 
  // PendingSet to the ReadySet.
 
  //
 
  // An Instruction is moved from the ReadySet to the `IssuedSet` when it starts
 
  // exection. This event also causes an instruction state transition (i.e. from
 
  // state IS_READY, to state IS_EXECUTING). An Instruction leaves the IssuedSet
 
  // only when it reaches the write-back stage.
 
  std::vector<InstRef> WaitSet;
 
  std::vector<InstRef> PendingSet;
 
  std::vector<InstRef> ReadySet;
 
  std::vector<InstRef> IssuedSet;
 
 
 
  // A mask of busy resource units. It defaults to the empty set (i.e. a zero
 
  // mask), and it is cleared at the beginning of every cycle.
 
  // It is updated every time the scheduler fails to issue an instruction from
 
  // the ready set due to unavailable pipeline resources.
 
  // Each bit of the mask represents an unavailable resource.
 
  uint64_t BusyResourceUnits;
 
 
 
  // Counts the number of instructions in the pending set that were dispatched
 
  // during this cycle.
 
  unsigned NumDispatchedToThePendingSet;
 
 
 
  // True if the previous pipeline Stage was unable to dispatch a full group of
 
  // opcodes because scheduler buffers (or LS queues) were unavailable.
 
  bool HadTokenStall;
 
 
 
  /// Verify the given selection strategy and set the Strategy member
 
  /// accordingly.  If no strategy is provided, the DefaultSchedulerStrategy is
 
  /// used.
 
  void initializeStrategy(std::unique_ptr<SchedulerStrategy> S);
 
 
 
  /// Issue an instruction without updating the ready queue.
 
  void issueInstructionImpl(
 
      InstRef &IR,
 
      SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &Pipes);
 
 
 
  // Identify instructions that have finished executing, and remove them from
 
  // the IssuedSet. References to executed instructions are added to input
 
  // vector 'Executed'.
 
  void updateIssuedSet(SmallVectorImpl<InstRef> &Executed);
 
 
 
  // Try to promote instructions from the PendingSet to the ReadySet.
 
  // Add promoted instructions to the 'Ready' vector in input.
 
  // Returns true if at least one instruction was promoted.
 
  bool promoteToReadySet(SmallVectorImpl<InstRef> &Ready);
 
 
 
  // Try to promote instructions from the WaitSet to the PendingSet.
 
  // Add promoted instructions to the 'Pending' vector in input.
 
  // Returns true if at least one instruction was promoted.
 
  bool promoteToPendingSet(SmallVectorImpl<InstRef> &Pending);
 
 
 
public:
 
  Scheduler(const MCSchedModel &Model, LSUnitBase &Lsu)
 
      : Scheduler(Model, Lsu, nullptr) {}
 
 
 
  Scheduler(const MCSchedModel &Model, LSUnitBase &Lsu,
 
            std::unique_ptr<SchedulerStrategy> SelectStrategy)
 
      : Scheduler(std::make_unique<ResourceManager>(Model), Lsu,
 
                  std::move(SelectStrategy)) {}
 
 
 
  Scheduler(std::unique_ptr<ResourceManager> RM, LSUnitBase &Lsu,
 
            std::unique_ptr<SchedulerStrategy> SelectStrategy)
 
      : LSU(Lsu), Resources(std::move(RM)), BusyResourceUnits(0),
 
        NumDispatchedToThePendingSet(0), HadTokenStall(false) {
 
    initializeStrategy(std::move(SelectStrategy));
 
  }
 
 
 
  // Stalls generated by the scheduler.
 
  enum Status {
 
    SC_AVAILABLE,
 
    SC_LOAD_QUEUE_FULL,
 
    SC_STORE_QUEUE_FULL,
 
    SC_BUFFERS_FULL,
 
    SC_DISPATCH_GROUP_STALL,
 
  };
 
 
 
  /// Check if the instruction in 'IR' can be dispatched during this cycle.
 
  /// Return SC_AVAILABLE if both scheduler and LS resources are available.
 
  ///
 
  /// This method is also responsible for setting field HadTokenStall if
 
  /// IR cannot be dispatched to the Scheduler due to unavailable resources.
 
  Status isAvailable(const InstRef &IR);
 
 
 
  /// Reserves buffer and LSUnit queue resources that are necessary to issue
 
  /// this instruction.
 
  ///
 
  /// Returns true if instruction IR is ready to be issued to the underlying
 
  /// pipelines. Note that this operation cannot fail; it assumes that a
 
  /// previous call to method `isAvailable(IR)` returned `SC_AVAILABLE`.
 
  ///
 
  /// If IR is a memory operation, then the Scheduler queries the LS unit to
 
  /// obtain a LS token. An LS token is used internally to track memory
 
  /// dependencies.
 
  bool dispatch(InstRef &IR);
 
 
 
  /// Issue an instruction and populates a vector of used pipeline resources,
 
  /// and a vector of instructions that transitioned to the ready state as a
 
  /// result of this event.
 
  void issueInstruction(
 
      InstRef &IR,
 
      SmallVectorImpl<std::pair<ResourceRef, ResourceCycles>> &Used,
 
      SmallVectorImpl<InstRef> &Pending,
 
      SmallVectorImpl<InstRef> &Ready);
 
 
 
  /// Returns true if IR has to be issued immediately, or if IR is a zero
 
  /// latency instruction.
 
  bool mustIssueImmediately(const InstRef &IR) const;
 
 
 
  /// This routine notifies the Scheduler that a new cycle just started.
 
  ///
 
  /// It notifies the underlying ResourceManager that a new cycle just started.
 
  /// Vector `Freed` is populated with resourceRef related to resources that
 
  /// have changed in state, and that are now available to new instructions.
 
  /// Instructions executed are added to vector Executed, while vector Ready is
 
  /// populated with instructions that have become ready in this new cycle.
 
  /// Vector Pending is popluated by instructions that have transitioned through
 
  /// the pending stat during this cycle. The Pending and Ready sets may not be
 
  /// disjoint. An instruction is allowed to transition from the WAIT state to
 
  /// the READY state (going through the PENDING state) within a single cycle.
 
  /// That means, instructions may appear in both the Pending and Ready set.
 
  void cycleEvent(SmallVectorImpl<ResourceRef> &Freed,
 
                  SmallVectorImpl<InstRef> &Executed,
 
                  SmallVectorImpl<InstRef> &Pending,
 
                  SmallVectorImpl<InstRef> &Ready);
 
 
 
  /// Convert a resource mask into a valid llvm processor resource identifier.
 
  ///
 
  /// Only the most significant bit of the Mask is used by this method to
 
  /// identify the processor resource.
 
  unsigned getResourceID(uint64_t Mask) const {
 
    return Resources->resolveResourceMask(Mask);
 
  }
 
 
 
  /// Select the next instruction to issue from the ReadySet. Returns an invalid
 
  /// instruction reference if there are no ready instructions, or if processor
 
  /// resources are not available.
 
  InstRef select();
 
 
 
  bool isReadySetEmpty() const { return ReadySet.empty(); }
 
  bool isWaitSetEmpty() const { return WaitSet.empty(); }
 
 
 
  /// This method is called by the ExecuteStage at the end of each cycle to
 
  /// identify bottlenecks caused by data dependencies. Vector RegDeps is
 
  /// populated by instructions that were not issued because of unsolved
 
  /// register dependencies.  Vector MemDeps is populated by instructions that
 
  /// were not issued because of unsolved memory dependencies.
 
  void analyzeDataDependencies(SmallVectorImpl<InstRef> &RegDeps,
 
                               SmallVectorImpl<InstRef> &MemDeps);
 
 
 
  /// Returns a mask of busy resources, and populates vector Insts with
 
  /// instructions that could not be issued to the underlying pipelines because
 
  /// not all pipeline resources were available.
 
  uint64_t analyzeResourcePressure(SmallVectorImpl<InstRef> &Insts);
 
 
 
  // Returns true if the dispatch logic couldn't dispatch a full group due to
 
  // unavailable scheduler and/or LS resources.
 
  bool hadTokenStall() const { return HadTokenStall; }
 
 
 
#ifndef NDEBUG
 
  // Update the ready queues.
 
  void dump() const;
 
 
 
  // This routine performs a basic correctness check.  This routine should only
 
  // be called when we know that 'IR' is not in the scheduler's instruction
 
  // queues.
 
  void instructionCheck(const InstRef &IR) const {
 
    assert(!is_contained(WaitSet, IR) && "Already in the wait set!");
 
    assert(!is_contained(ReadySet, IR) && "Already in the ready set!");
 
    assert(!is_contained(IssuedSet, IR) && "Already executing!");
 
  }
 
#endif // !NDEBUG
 
};
 
} // namespace mca
 
} // namespace llvm
 
 
 
#endif // LLVM_MCA_HARDWAREUNITS_SCHEDULER_H