//===- AMDGPUCoExecSchedStrategy.h - CoExec Scheduling Strategy -*- 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 /// Coexecution-focused scheduling strategy for AMDGPU. // //===----------------------------------------------------------------------===// #ifndef LLVM_LIB_TARGET_AMDGPU_AMDGPUCOEXECSCHEDSTRATEGY_H #define LLVM_LIB_TARGET_AMDGPU_AMDGPUCOEXECSCHEDSTRATEGY_H #include "GCNSchedStrategy.h" #include "llvm/CodeGen/MachineScheduler.h" namespace llvm { namespace AMDGPU { //===----------------------------------------------------------------------===// // Instruction Flavor Classification //===----------------------------------------------------------------------===// enum class InstructionFlavor : uint8_t { WMMA, // WMMA/MFMA matrix operations SingleCycleVALU, // Single-cycle VALU (not TRANS32, not multi-cycle CVT) TRANS, // Transcendental ops (v_exp, v_log, etc.) MultiCycleVALU, // VALU instructions with repeat rate > 1 VMEM, // FLAT/GLOBAL memory operations DS, // LDS/GDS operations SALU, // Scalar ALU DMA, // Tensor DMA operations Fence, // Fences and waits Other, // Everything else NUM_FLAVORS }; constexpr StringRef getFlavorName(InstructionFlavor F) { switch (F) { case InstructionFlavor::WMMA: return "WMMA"; case InstructionFlavor::SingleCycleVALU: return "VALU(1c)"; case InstructionFlavor::TRANS: return "TRANS"; case InstructionFlavor::MultiCycleVALU: return "VALU(Nc)"; case InstructionFlavor::VMEM: return "VMEM"; case InstructionFlavor::DS: return "DS"; case InstructionFlavor::SALU: return "SALU"; case InstructionFlavor::DMA: return "DMA"; case InstructionFlavor::Fence: return "Fence"; case InstructionFlavor::Other: return "Other"; case InstructionFlavor::NUM_FLAVORS: llvm_unreachable("Unknown InstructionFlavor"); } llvm_unreachable("Unknown InstructionFlavor"); } constexpr StringRef getFlavorShortName(InstructionFlavor F) { switch (F) { case InstructionFlavor::WMMA: return "W"; case InstructionFlavor::SingleCycleVALU: return "V"; case InstructionFlavor::TRANS: return "T"; case InstructionFlavor::MultiCycleVALU: return "C"; case InstructionFlavor::VMEM: return "M"; case InstructionFlavor::DS: return "D"; case InstructionFlavor::SALU: return "S"; case InstructionFlavor::DMA: return "X"; case InstructionFlavor::Fence: return "F"; case InstructionFlavor::Other: return "O"; case InstructionFlavor::NUM_FLAVORS: llvm_unreachable("Unknown InstructionFlavor"); } llvm_unreachable("Unknown InstructionFlavor"); } InstructionFlavor classifyFlavor(const MachineInstr &MI, const SIInstrInfo &SII); using FlavorGroup = SmallVector; namespace FlavorGroups { inline FlavorGroup allVALU() { return {InstructionFlavor::SingleCycleVALU, InstructionFlavor::TRANS, InstructionFlavor::MultiCycleVALU}; } inline FlavorGroup allMem() { return {InstructionFlavor::VMEM, InstructionFlavor::DS, InstructionFlavor::DMA}; } inline FlavorGroup individual(InstructionFlavor F) { return {F}; } inline FlavorGroup all() { FlavorGroup G; for (unsigned I = 0; I < static_cast(InstructionFlavor::NUM_FLAVORS); ++I) G.push_back(static_cast(I)); return G; } } // namespace FlavorGroups /// AMDGPU-specific scheduling decision reasons. These provide more granularity /// than the generic CandReason enum for debugging purposes. enum class AMDGPUSchedReason : uint8_t { None, CritResourceBalance, // tryCriticalResource chose based on resource pressure CritResourceDep, // tryCriticalResourceDependency chose based on enabling NUM_REASONS }; constexpr StringRef getReasonName(AMDGPUSchedReason R) { switch (R) { case AMDGPUSchedReason::None: return "None"; case AMDGPUSchedReason::CritResourceBalance: return "CritResource"; case AMDGPUSchedReason::CritResourceDep: return "CritResourceDep"; case AMDGPUSchedReason::NUM_REASONS: llvm_unreachable("Unknown AMDGPUSchedReason"); } llvm_unreachable("Unknown AMDGPUSchedReason"); } } // End namespace AMDGPU //===----------------------------------------------------------------------===// // Hardware Unit Information //===----------------------------------------------------------------------===// /// HardwareUnitInfo is a wrapper class which maps to some real hardware /// resource. This is used to model hardware resource pressure per region, and /// guide scheduling heuristics. class HardwareUnitInfo { private: /// PrioritySUs maintains a list of the SUs we want to prioritize scheduling /// for this HardwareUnit. This is used for agreement between /// tryCriticalResourceDependency and tryCriticalResource: we schedule the /// dependencies for a SU on critical resource, then schedule that same SU on /// the critical resource. This agreement results in shorter live ranges and /// more regular HardwareUnit access patterns. SUs are prioritized based on /// depth for top-down scheduling. SmallSetVector PrioritySUs; /// All the SUs in the region that consume this resource. SmallSetVector AllSUs; /// The total number of busy cycles for this HardwareUnit for a given region. unsigned TotalCycles = 0; /// InstructionFlavor mapping. AMDGPU::InstructionFlavor Type; /// Whether or not instructions on this HardwareUnit may produce a window in /// which instructions in other HardwareUnits can coexecute. For example, WMMA /// / MFMA instructions may take multiple cycles, which may be overlapped with /// instructions on other HardwareUnits. bool ProducesCoexecWindow = false; public: HardwareUnitInfo() {} unsigned size() { return AllSUs.size(); } unsigned getTotalCycles() { return TotalCycles; } void setType(unsigned TheType) { assert(TheType < (unsigned)AMDGPU::InstructionFlavor::NUM_FLAVORS); Type = (AMDGPU::InstructionFlavor)(TheType); } AMDGPU::InstructionFlavor getType() const { return Type; } bool producesCoexecWindow() const { return ProducesCoexecWindow; } void setProducesCoexecWindow(bool Val) { ProducesCoexecWindow = Val; } bool contains(SUnit *SU) const { return AllSUs.contains(SU); } /// \returns the SUnit with higher priority or nullptr if they are the same. /// This method looks through the PrioritySUs to determine if one SU is more /// prioritized than the other. If neither are in the PrioritySUs list, then /// neither have priority over each other. SUnit *getHigherPriority(SUnit *SU, SUnit *Other) const { for (SUnit *SUOrder : PrioritySUs) { if (SUOrder == SU) return SU; if (SUOrder == Other) return Other; } return nullptr; } void reset() { AllSUs.clear(); PrioritySUs.clear(); TotalCycles = 0; Type = AMDGPU::InstructionFlavor::Other; ProducesCoexecWindow = false; } /// \returns the next SU in PrioritySUs that is not ready. If \p LookDeep is /// set, we will look beyond the PrioritySUs (if all the PrioritySUs are /// ready) to AllSUs to attempt to find a target SU. When looking through /// AllSUs we sort pick the target SU by minimal depth for top-down /// scheduling. getNextTargetSU is useful for determining which SU on this /// HardwareUnit we are trying to schedule - this info helps us determine /// which dependencies to schedule. LookDeep is useful if the dependencies are /// long latency (e.g. memory instructions). If we have many long latency /// dependencies, it is beneficial to enable SUs multiple levels ahead. SUnit *getNextTargetSU(bool LookDeep = false) const; /// Insert the \p SU into AllSUs and account its \p BlockingCycles into /// the TotalCycles. This maintains the list of PrioritySUs. void insert(SUnit *SU, unsigned BlockingCycles); /// Update the state for \p SU being scheduled by removing it from the AllSUs /// and reducing its \p BlockingCycles from the TotalCycles. This maintains /// the list of PrioritySUs. void markScheduled(SUnit *SU, unsigned BlockingCycles); }; //===----------------------------------------------------------------------===// // Candidate Heuristics //===----------------------------------------------------------------------===// /// CandidateHeuristics contains state and implementations to facilitate making /// per instruction scheduling decisions; it contains methods used in /// tryCandidate to decide which instruction to schedule next. class CandidateHeuristics { protected: ScheduleDAGMI *DAG; const SIInstrInfo *SII; const SIRegisterInfo *SRI; const TargetSchedModel *SchedModel; SmallVector HWUInfo; /// Walk over the region and collect total usage per HardwareUnit. void collectHWUIPressure(); /// Compute the blocking cycles for the appropriate HardwareUnit given an \p /// SU. unsigned getHWUICyclesForInst(SUnit *SU); /// Given a \p Flavor , find the corresponding HardwareUnit. \returns the /// mapped HardwareUnit. HardwareUnitInfo *getHWUIFromFlavor(AMDGPU::InstructionFlavor Flavor); public: CandidateHeuristics() = default; void initialize(ScheduleDAGMI *DAG, const TargetSchedModel *SchedModel, const TargetRegisterInfo *TRI); /// Update the state to reflect that \p SU is going to be scheduled. void updateForScheduling(SUnit *SU); /// Sort the HWUInfo vector. After sorting, the HardwareUnits that are highest /// priority are first. Priority is determined by maximizing coexecution and /// keeping the critical HardwareUnit busy. void sortHWUIResources(); /// Check for critical resource consumption. Prefer the candidate that uses /// the most prioritized HardwareUnit. If both candidates use the same /// HarwareUnit, prefer the candidate with higher priority on that /// HardwareUnit. bool tryCriticalResource(GenericSchedulerBase::SchedCandidate &TryCand, GenericSchedulerBase::SchedCandidate &Cand, SchedBoundary *Zone) const; /// Check for dependencies of instructions that use prioritized HardwareUnits. /// Prefer the candidate that is a dependency of an instruction that uses the /// most prioritized HardwareUnit. If both candidates enable the same /// HardwareUnit, prefer the candidate that enables the higher priority /// instruction on that HardwareUnit. bool tryCriticalResourceDependency(GenericSchedulerBase::SchedCandidate &TryCand, GenericSchedulerBase::SchedCandidate &Cand, SchedBoundary *Zone) const; void dumpRegionSummary(); }; class AMDGPUCoExecSchedStrategy final : public GCNSchedStrategy { protected: bool tryEffectiveStall(SchedCandidate &Cand, SchedCandidate &TryCand, SchedBoundary &Zone) const; AMDGPU::AMDGPUSchedReason LastAMDGPUReason = AMDGPU::AMDGPUSchedReason::None; CandidateHeuristics Heurs; #ifndef NDEBUG void dumpPickSummary(SUnit *SU, bool IsTopNode, SchedCandidate &Cand); #endif bool tryCandidateCoexec(SchedCandidate &Cand, SchedCandidate &TryCand, SchedBoundary *Zone); void pickNodeFromQueue(SchedBoundary &Zone, const CandPolicy &ZonePolicy, const RegPressureTracker &RPTracker, SchedCandidate &Cand, bool &PickedPending, bool IsBottomUp); public: AMDGPUCoExecSchedStrategy(const MachineSchedContext *C); void initPolicy(MachineBasicBlock::iterator Begin, MachineBasicBlock::iterator End, unsigned NumRegionInstrs) override; void initialize(ScheduleDAGMI *DAG) override; SUnit *pickNode(bool &IsTopNode) override; void schedNode(SUnit *SU, bool IsTopNode) override; }; ScheduleDAGInstrs *createGCNCoExecMachineScheduler(MachineSchedContext *C); ScheduleDAGInstrs *createGCNNoopPostMachineScheduler(MachineSchedContext *C); } // End namespace llvm #endif // LLVM_LIB_TARGET_AMDGPU_AMDGPUCOEXECSCHEDSTRATEGY_H