| //===- SIInsertWaitcnts.cpp - Insert Wait Instructions --------------------===// | |
| // | |
| // 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 | |
| /// Insert wait instructions for memory reads and writes. | |
| /// | |
| /// Memory reads and writes are issued asynchronously, so we need to insert | |
| /// S_WAITCNT instructions when we want to access any of their results or | |
| /// overwrite any register that's used asynchronously. | |
| /// | |
| /// TODO: This pass currently keeps one timeline per hardware counter. A more | |
| /// finely-grained approach that keeps one timeline per event type could | |
| /// sometimes get away with generating weaker s_waitcnt instructions. For | |
| /// example, when both SMEM and LDS are in flight and we need to wait for | |
| /// the i-th-last LDS instruction, then an lgkmcnt(i) is actually sufficient, | |
| /// but the pass will currently generate a conservative lgkmcnt(0) because | |
| /// multiple event types are in flight. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "AMDGPU.h" | |
| #include "AMDGPUWaitcntUtils.h" | |
| #include "GCNSubtarget.h" | |
| #include "MCTargetDesc/AMDGPUMCTargetDesc.h" | |
| #include "SIMachineFunctionInfo.h" | |
| #include "Utils/AMDGPUBaseInfo.h" | |
| #include "llvm/ADT/MapVector.h" | |
| #include "llvm/ADT/PostOrderIterator.h" | |
| #include "llvm/ADT/Sequence.h" | |
| #include "llvm/Analysis/AliasAnalysis.h" | |
| #include "llvm/CodeGen/MachineFrameInfo.h" | |
| #include "llvm/CodeGen/MachineLoopInfo.h" | |
| #include "llvm/CodeGen/MachinePassManager.h" | |
| #include "llvm/CodeGen/MachinePostDominators.h" | |
| #include "llvm/IR/Dominators.h" | |
| #include "llvm/InitializePasses.h" | |
| #include "llvm/Support/DebugCounter.h" | |
| #include "llvm/TargetParser/TargetParser.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "si-insert-waitcnts" | |
| DEBUG_COUNTER(ForceExpCounter, DEBUG_TYPE "-forceexp", | |
| "Force emit s_waitcnt expcnt(0) instrs"); | |
| DEBUG_COUNTER(ForceLgkmCounter, DEBUG_TYPE "-forcelgkm", | |
| "Force emit s_waitcnt lgkmcnt(0) instrs"); | |
| DEBUG_COUNTER(ForceVMCounter, DEBUG_TYPE "-forcevm", | |
| "Force emit s_waitcnt vmcnt(0) instrs"); | |
| static cl::opt<bool> | |
| ForceEmitZeroFlag("amdgpu-waitcnt-forcezero", | |
| cl::desc("Force all waitcnt instrs to be emitted as " | |
| "s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)"), | |
| cl::init(false), cl::Hidden); | |
| static cl::opt<bool> ForceEmitZeroLoadFlag( | |
| "amdgpu-waitcnt-load-forcezero", | |
| cl::desc("Force all waitcnt load counters to wait until 0"), | |
| cl::init(false), cl::Hidden); | |
| static cl::opt<bool> ExpertSchedulingModeFlag( | |
| "amdgpu-expert-scheduling-mode", | |
| cl::desc("Enable expert scheduling mode 2 for all functions (GFX12+ only)"), | |
| cl::init(false), cl::Hidden); | |
| namespace { | |
| // Get the maximum wait count value for a given counter type. | |
| static unsigned getWaitCountMax(const AMDGPU::HardwareLimits &Limits, | |
| AMDGPU::InstCounterType T) { | |
| switch (T) { | |
| case AMDGPU::LOAD_CNT: | |
| return Limits.LoadcntMax; | |
| case AMDGPU::DS_CNT: | |
| return Limits.DscntMax; | |
| case AMDGPU::EXP_CNT: | |
| return Limits.ExpcntMax; | |
| case AMDGPU::STORE_CNT: | |
| return Limits.StorecntMax; | |
| case AMDGPU::SAMPLE_CNT: | |
| return Limits.SamplecntMax; | |
| case AMDGPU::BVH_CNT: | |
| return Limits.BvhcntMax; | |
| case AMDGPU::KM_CNT: | |
| return Limits.KmcntMax; | |
| case AMDGPU::X_CNT: | |
| return Limits.XcntMax; | |
| case AMDGPU::VA_VDST: | |
| return Limits.VaVdstMax; | |
| case AMDGPU::VM_VSRC: | |
| return Limits.VmVsrcMax; | |
| default: | |
| return 0; | |
| } | |
| } | |
| /// Integer IDs used to track vector memory locations we may have to wait on. | |
| /// Encoded as u16 chunks: | |
| /// | |
| /// [0, REGUNITS_END ): MCRegUnit | |
| /// [LDSDMA_BEGIN, LDSDMA_END ) : LDS DMA IDs | |
| /// | |
| /// NOTE: The choice of encoding these as "u16 chunks" is arbitrary. | |
| /// It gives (2 << 16) - 1 entries per category which is more than enough | |
| /// for all register units. MCPhysReg is u16 so we don't even support >u16 | |
| /// physical register numbers at this time, let alone >u16 register units. | |
| /// In any case, an assertion in "WaitcntBrackets" ensures REGUNITS_END | |
| /// is enough for all register units. | |
| using VMEMID = uint32_t; | |
| enum : VMEMID { | |
| TRACKINGID_RANGE_LEN = (1 << 16), | |
| // Important: MCRegUnits must always be tracked starting from 0, as we | |
| // need to be able to convert between a MCRegUnit and a VMEMID freely. | |
| REGUNITS_BEGIN = 0, | |
| REGUNITS_END = REGUNITS_BEGIN + TRACKINGID_RANGE_LEN, | |
| // Note for LDSDMA: LDSDMA_BEGIN corresponds to the "common" | |
| // entry, which is updated for all LDS DMA operations encountered. | |
| // Specific LDS DMA IDs start at LDSDMA_BEGIN + 1. | |
| NUM_LDSDMA = TRACKINGID_RANGE_LEN, | |
| LDSDMA_BEGIN = REGUNITS_END, | |
| LDSDMA_END = LDSDMA_BEGIN + NUM_LDSDMA, | |
| }; | |
| /// Convert a MCRegUnit to a VMEMID. | |
| static constexpr VMEMID toVMEMID(MCRegUnit RU) { | |
| return static_cast<unsigned>(RU); | |
| } | |
| #define AMDGPU_DECLARE_WAIT_EVENTS(DECL) \ | |
| DECL(VMEM_ACCESS) /* vmem read & write (pre-gfx10), vmem read (gfx10+) */ \ | |
| DECL(VMEM_SAMPLER_READ_ACCESS) /* vmem SAMPLER read (gfx12+ only) */ \ | |
| DECL(VMEM_BVH_READ_ACCESS) /* vmem BVH read (gfx12+ only) */ \ | |
| DECL(GLOBAL_INV_ACCESS) /* GLOBAL_INV (gfx12+ only) */ \ | |
| DECL(VMEM_WRITE_ACCESS) /* vmem write that is not scratch */ \ | |
| DECL(SCRATCH_WRITE_ACCESS) /* vmem write that may be scratch */ \ | |
| DECL(VMEM_GROUP) /* vmem group */ \ | |
| DECL(LDS_ACCESS) /* lds read & write */ \ | |
| DECL(GDS_ACCESS) /* gds read & write */ \ | |
| DECL(SQ_MESSAGE) /* send message */ \ | |
| DECL(SCC_WRITE) /* write to SCC from barrier */ \ | |
| DECL(SMEM_ACCESS) /* scalar-memory read & write */ \ | |
| DECL(SMEM_GROUP) /* scalar-memory group */ \ | |
| DECL(EXP_GPR_LOCK) /* export holding on its data src */ \ | |
| DECL(GDS_GPR_LOCK) /* GDS holding on its data and addr src */ \ | |
| DECL(EXP_POS_ACCESS) /* write to export position */ \ | |
| DECL(EXP_PARAM_ACCESS) /* write to export parameter */ \ | |
| DECL(VMW_GPR_LOCK) /* vmem write holding on its data src */ \ | |
| DECL(EXP_LDS_ACCESS) /* read by ldsdir counting as export */ \ | |
| DECL(VGPR_CSMACC_WRITE) /* write VGPR dest in Core/Side-MACC VALU */ \ | |
| DECL(VGPR_DPMACC_WRITE) /* write VGPR dest in DPMACC VALU */ \ | |
| DECL(VGPR_TRANS_WRITE) /* write VGPR dest in TRANS VALU */ \ | |
| DECL(VGPR_XDL_WRITE) /* write VGPR dest in XDL VALU */ \ | |
| DECL(VGPR_LDS_READ) /* read VGPR source in LDS */ \ | |
| DECL(VGPR_FLAT_READ) /* read VGPR source in FLAT */ \ | |
| DECL(VGPR_VMEM_READ) /* read VGPR source in other VMEM */ \ | |
| DECL(ASYNC_ACCESS) /* access that uses ASYNC_CNT */ | |
| // clang-format off | |
| #define AMDGPU_EVENT_ENUM(Name) Name, | |
| enum WaitEventType { | |
| AMDGPU_DECLARE_WAIT_EVENTS(AMDGPU_EVENT_ENUM) | |
| NUM_WAIT_EVENTS | |
| }; | |
| #undef AMDGPU_EVENT_ENUM | |
| } // namespace | |
| namespace llvm { | |
| template <> struct enum_iteration_traits<WaitEventType> { | |
| static constexpr bool is_iterable = true; | |
| }; | |
| } // namespace llvm | |
| namespace { | |
| /// Return an iterator over all events between VMEM_ACCESS (the first event) | |
| /// and \c MaxEvent (exclusive, default value yields an enumeration over | |
| /// all counters). | |
| auto wait_events(WaitEventType MaxEvent = NUM_WAIT_EVENTS) { | |
| return enum_seq(VMEM_ACCESS, MaxEvent); | |
| } | |
| #define AMDGPU_EVENT_NAME(Name) #Name, | |
| static constexpr StringLiteral WaitEventTypeName[] = { | |
| AMDGPU_DECLARE_WAIT_EVENTS(AMDGPU_EVENT_NAME) | |
| }; | |
| #undef AMDGPU_EVENT_NAME | |
| static constexpr StringLiteral getWaitEventTypeName(WaitEventType Event) { | |
| return WaitEventTypeName[Event]; | |
| } | |
| // clang-format on | |
| // Enumerate different types of result-returning VMEM operations. Although | |
| // s_waitcnt orders them all with a single vmcnt counter, in the absence of | |
| // s_waitcnt only instructions of the same VmemType are guaranteed to write | |
| // their results in order -- so there is no need to insert an s_waitcnt between | |
| // two instructions of the same type that write the same vgpr. | |
| enum VmemType { | |
| // BUF instructions and MIMG instructions without a sampler. | |
| VMEM_NOSAMPLER, | |
| // MIMG instructions with a sampler. | |
| VMEM_SAMPLER, | |
| // BVH instructions | |
| VMEM_BVH, | |
| NUM_VMEM_TYPES | |
| }; | |
| // Maps values of InstCounterType to the instruction that waits on that | |
| // counter. Only used if GCNSubtarget::hasExtendedWaitCounts() | |
| // returns true, and does not cover VA_VDST or VM_VSRC. | |
| static const unsigned | |
| instrsForExtendedCounterTypes[AMDGPU::NUM_EXTENDED_INST_CNTS] = { | |
| AMDGPU::S_WAIT_LOADCNT, AMDGPU::S_WAIT_DSCNT, | |
| AMDGPU::S_WAIT_EXPCNT, AMDGPU::S_WAIT_STORECNT, | |
| AMDGPU::S_WAIT_SAMPLECNT, AMDGPU::S_WAIT_BVHCNT, | |
| AMDGPU::S_WAIT_KMCNT, AMDGPU::S_WAIT_XCNT, | |
| AMDGPU::S_WAIT_ASYNCCNT}; | |
| // ASYNCMARK and WAIT_ASYNCMARK are meta instructions that emit no hardware | |
| // code but still need to be processed by this pass for async vmcnt tracking. | |
| static bool isNonWaitcntMetaInst(const MachineInstr &MI) { | |
| switch (MI.getOpcode()) { | |
| case AMDGPU::ASYNCMARK: | |
| case AMDGPU::WAIT_ASYNCMARK: | |
| return false; | |
| default: | |
| return MI.isMetaInstruction(); | |
| } | |
| } | |
| static bool updateVMCntOnly(const MachineInstr &Inst) { | |
| return (SIInstrInfo::isVMEM(Inst) && !SIInstrInfo::isFLAT(Inst)) || | |
| SIInstrInfo::isFLATGlobal(Inst) || SIInstrInfo::isFLATScratch(Inst); | |
| } | |
| #ifndef NDEBUG | |
| static bool isNormalMode(AMDGPU::InstCounterType MaxCounter) { | |
| return MaxCounter == AMDGPU::NUM_NORMAL_INST_CNTS; | |
| } | |
| #endif // NDEBUG | |
| VmemType getVmemType(const MachineInstr &Inst) { | |
| assert(updateVMCntOnly(Inst)); | |
| if (!SIInstrInfo::isImage(Inst)) | |
| return VMEM_NOSAMPLER; | |
| const AMDGPU::MIMGInfo *Info = AMDGPU::getMIMGInfo(Inst.getOpcode()); | |
| const AMDGPU::MIMGBaseOpcodeInfo *BaseInfo = | |
| AMDGPU::getMIMGBaseOpcodeInfo(Info->BaseOpcode); | |
| if (BaseInfo->BVH) | |
| return VMEM_BVH; | |
| // We have to make an additional check for isVSAMPLE here since some | |
| // instructions don't have a sampler, but are still classified as sampler | |
| // instructions for the purposes of e.g. waitcnt. | |
| if (BaseInfo->Sampler || BaseInfo->MSAA || SIInstrInfo::isVSAMPLE(Inst)) | |
| return VMEM_SAMPLER; | |
| return VMEM_NOSAMPLER; | |
| } | |
| void addWait(AMDGPU::Waitcnt &Wait, AMDGPU::InstCounterType T, unsigned Count) { | |
| Wait.set(T, std::min(Wait.get(T), Count)); | |
| } | |
| void setNoWait(AMDGPU::Waitcnt &Wait, AMDGPU::InstCounterType T) { | |
| Wait.set(T, ~0u); | |
| } | |
| /// A small set of events. | |
| class WaitEventSet { | |
| unsigned Mask = 0; | |
| public: | |
| WaitEventSet() = default; | |
| explicit constexpr WaitEventSet(WaitEventType Event) { | |
| static_assert(NUM_WAIT_EVENTS <= sizeof(Mask) * 8, | |
| "Not enough bits in Mask for all the events"); | |
| Mask |= 1 << Event; | |
| } | |
| constexpr WaitEventSet(std::initializer_list<WaitEventType> Events) { | |
| for (auto &E : Events) { | |
| Mask |= 1 << E; | |
| } | |
| } | |
| void insert(const WaitEventType &Event) { Mask |= 1 << Event; } | |
| void remove(const WaitEventType &Event) { Mask &= ~(1 << Event); } | |
| void remove(const WaitEventSet &Other) { Mask &= ~Other.Mask; } | |
| bool contains(const WaitEventType &Event) const { | |
| return Mask & (1 << Event); | |
| } | |
| /// \Returns true if this set contains all elements of \p Other. | |
| bool contains(const WaitEventSet &Other) const { | |
| return (~Mask & Other.Mask) == 0; | |
| } | |
| /// \Returns the intersection of this and \p Other. | |
| WaitEventSet operator&(const WaitEventSet &Other) const { | |
| auto Copy = *this; | |
| Copy.Mask &= Other.Mask; | |
| return Copy; | |
| } | |
| /// \Returns the union of this and \p Other. | |
| WaitEventSet operator|(const WaitEventSet &Other) const { | |
| auto Copy = *this; | |
| Copy.Mask |= Other.Mask; | |
| return Copy; | |
| } | |
| /// This set becomes the union of this and \p Other. | |
| WaitEventSet &operator|=(const WaitEventSet &Other) { | |
| Mask |= Other.Mask; | |
| return *this; | |
| } | |
| /// This set becomes the intersection of this and \p Other. | |
| WaitEventSet &operator&=(const WaitEventSet &Other) { | |
| Mask &= Other.Mask; | |
| return *this; | |
| } | |
| bool operator==(const WaitEventSet &Other) const { | |
| return Mask == Other.Mask; | |
| } | |
| bool operator!=(const WaitEventSet &Other) const { return !(*this == Other); } | |
| bool empty() const { return Mask == 0; } | |
| /// \Returns true if the set contains more than one element. | |
| bool twoOrMore() const { return Mask & (Mask - 1); } | |
| operator bool() const { return !empty(); } | |
| void print(raw_ostream &OS) const { | |
| ListSeparator LS(", "); | |
| for (WaitEventType Event : wait_events()) { | |
| if (contains(Event)) | |
| OS << LS << getWaitEventTypeName(Event); | |
| } | |
| } | |
| LLVM_DUMP_METHOD void dump() const; | |
| }; | |
| void WaitEventSet::dump() const { | |
| print(dbgs()); | |
| dbgs() << "\n"; | |
| } | |
| class WaitcntBrackets; | |
| // This abstracts the logic for generating and updating S_WAIT* instructions | |
| // away from the analysis that determines where they are needed. This was | |
| // done because the set of counters and instructions for waiting on them | |
| // underwent a major shift with gfx12, sufficiently so that having this | |
| // abstraction allows the main analysis logic to be simpler than it would | |
| // otherwise have had to become. | |
| class WaitcntGenerator { | |
| protected: | |
| const GCNSubtarget &ST; | |
| const SIInstrInfo &TII; | |
| AMDGPU::IsaVersion IV; | |
| AMDGPU::InstCounterType MaxCounter; | |
| bool OptNone; | |
| bool ExpandWaitcntProfiling = false; | |
| const AMDGPU::HardwareLimits &Limits; | |
| public: | |
| WaitcntGenerator() = delete; | |
| WaitcntGenerator(const WaitcntGenerator &) = delete; | |
| WaitcntGenerator(const MachineFunction &MF, | |
| AMDGPU::InstCounterType MaxCounter, | |
| const AMDGPU::HardwareLimits &Limits) | |
| : ST(MF.getSubtarget<GCNSubtarget>()), TII(*ST.getInstrInfo()), | |
| IV(AMDGPU::getIsaVersion(ST.getCPU())), MaxCounter(MaxCounter), | |
| OptNone(MF.getFunction().hasOptNone() || | |
| MF.getTarget().getOptLevel() == CodeGenOptLevel::None), | |
| ExpandWaitcntProfiling( | |
| MF.getFunction().hasFnAttribute("amdgpu-expand-waitcnt-profiling")), | |
| Limits(Limits) {} | |
| // Return true if the current function should be compiled with no | |
| // optimization. | |
| bool isOptNone() const { return OptNone; } | |
| const AMDGPU::HardwareLimits &getLimits() const { return Limits; } | |
| // Edits an existing sequence of wait count instructions according | |
| // to an incoming Waitcnt value, which is itself updated to reflect | |
| // any new wait count instructions which may need to be generated by | |
| // WaitcntGenerator::createNewWaitcnt(). It will return true if any edits | |
| // were made. | |
| // | |
| // This editing will usually be merely updated operands, but it may also | |
| // delete instructions if the incoming Wait value indicates they are not | |
| // needed. It may also remove existing instructions for which a wait | |
| // is needed if it can be determined that it is better to generate new | |
| // instructions later, as can happen on gfx12. | |
| virtual bool | |
| applyPreexistingWaitcnt(WaitcntBrackets &ScoreBrackets, | |
| MachineInstr &OldWaitcntInstr, AMDGPU::Waitcnt &Wait, | |
| MachineBasicBlock::instr_iterator It) const = 0; | |
| // Transform a soft waitcnt into a normal one. | |
| bool promoteSoftWaitCnt(MachineInstr *Waitcnt) const; | |
| // Generates new wait count instructions according to the value of | |
| // Wait, returning true if any new instructions were created. | |
| // ScoreBrackets is used for profiling expansion. | |
| virtual bool createNewWaitcnt(MachineBasicBlock &Block, | |
| MachineBasicBlock::instr_iterator It, | |
| AMDGPU::Waitcnt Wait, | |
| const WaitcntBrackets &ScoreBrackets) = 0; | |
| // Returns the WaitEventSet that corresponds to counter \p T. | |
| virtual const WaitEventSet & | |
| getWaitEvents(AMDGPU::InstCounterType T) const = 0; | |
| /// \returns the counter that corresponds to event \p E. | |
| AMDGPU::InstCounterType getCounterFromEvent(WaitEventType E) const { | |
| for (auto T : AMDGPU::inst_counter_types()) { | |
| if (getWaitEvents(T).contains(E)) | |
| return T; | |
| } | |
| llvm_unreachable("event type has no associated counter"); | |
| } | |
| // Returns a new waitcnt with all counters except VScnt set to 0. If | |
| // IncludeVSCnt is true, VScnt is set to 0, otherwise it is set to ~0u. | |
| // AsyncCnt always defaults to ~0u (don't wait for it). It is only updated | |
| // when a call to @llvm.amdgcn.wait.asyncmark() is processed. | |
| virtual AMDGPU::Waitcnt getAllZeroWaitcnt(bool IncludeVSCnt) const = 0; | |
| virtual ~WaitcntGenerator() = default; | |
| }; | |
| class WaitcntGeneratorPreGFX12 final : public WaitcntGenerator { | |
| static constexpr const WaitEventSet | |
| WaitEventMaskForInstPreGFX12[AMDGPU::NUM_INST_CNTS] = { | |
| WaitEventSet( | |
| {VMEM_ACCESS, VMEM_SAMPLER_READ_ACCESS, VMEM_BVH_READ_ACCESS}), | |
| WaitEventSet({SMEM_ACCESS, LDS_ACCESS, GDS_ACCESS, SQ_MESSAGE}), | |
| WaitEventSet({EXP_GPR_LOCK, GDS_GPR_LOCK, VMW_GPR_LOCK, | |
| EXP_PARAM_ACCESS, EXP_POS_ACCESS, EXP_LDS_ACCESS}), | |
| WaitEventSet({VMEM_WRITE_ACCESS, SCRATCH_WRITE_ACCESS}), | |
| WaitEventSet(), | |
| WaitEventSet(), | |
| WaitEventSet(), | |
| WaitEventSet(), | |
| WaitEventSet(), | |
| WaitEventSet()}; | |
| public: | |
| using WaitcntGenerator::WaitcntGenerator; | |
| bool | |
| applyPreexistingWaitcnt(WaitcntBrackets &ScoreBrackets, | |
| MachineInstr &OldWaitcntInstr, AMDGPU::Waitcnt &Wait, | |
| MachineBasicBlock::instr_iterator It) const override; | |
| bool createNewWaitcnt(MachineBasicBlock &Block, | |
| MachineBasicBlock::instr_iterator It, | |
| AMDGPU::Waitcnt Wait, | |
| const WaitcntBrackets &ScoreBrackets) override; | |
| const WaitEventSet &getWaitEvents(AMDGPU::InstCounterType T) const override { | |
| return WaitEventMaskForInstPreGFX12[T]; | |
| } | |
| AMDGPU::Waitcnt getAllZeroWaitcnt(bool IncludeVSCnt) const override; | |
| }; | |
| class WaitcntGeneratorGFX12Plus final : public WaitcntGenerator { | |
| protected: | |
| bool IsExpertMode; | |
| static constexpr const WaitEventSet | |
| WaitEventMaskForInstGFX12Plus[AMDGPU::NUM_INST_CNTS] = { | |
| WaitEventSet({VMEM_ACCESS, GLOBAL_INV_ACCESS}), | |
| WaitEventSet({LDS_ACCESS, GDS_ACCESS}), | |
| WaitEventSet({EXP_GPR_LOCK, GDS_GPR_LOCK, VMW_GPR_LOCK, | |
| EXP_PARAM_ACCESS, EXP_POS_ACCESS, EXP_LDS_ACCESS}), | |
| WaitEventSet({VMEM_WRITE_ACCESS, SCRATCH_WRITE_ACCESS}), | |
| WaitEventSet({VMEM_SAMPLER_READ_ACCESS}), | |
| WaitEventSet({VMEM_BVH_READ_ACCESS}), | |
| WaitEventSet({SMEM_ACCESS, SQ_MESSAGE, SCC_WRITE}), | |
| WaitEventSet({VMEM_GROUP, SMEM_GROUP}), | |
| WaitEventSet({ASYNC_ACCESS}), | |
| WaitEventSet({VGPR_CSMACC_WRITE, VGPR_DPMACC_WRITE, VGPR_TRANS_WRITE, | |
| VGPR_XDL_WRITE}), | |
| WaitEventSet({VGPR_LDS_READ, VGPR_FLAT_READ, VGPR_VMEM_READ})}; | |
| public: | |
| WaitcntGeneratorGFX12Plus() = delete; | |
| WaitcntGeneratorGFX12Plus(const MachineFunction &MF, | |
| AMDGPU::InstCounterType MaxCounter, | |
| const AMDGPU::HardwareLimits &Limits, | |
| bool IsExpertMode) | |
| : WaitcntGenerator(MF, MaxCounter, Limits), IsExpertMode(IsExpertMode) {} | |
| bool | |
| applyPreexistingWaitcnt(WaitcntBrackets &ScoreBrackets, | |
| MachineInstr &OldWaitcntInstr, AMDGPU::Waitcnt &Wait, | |
| MachineBasicBlock::instr_iterator It) const override; | |
| bool createNewWaitcnt(MachineBasicBlock &Block, | |
| MachineBasicBlock::instr_iterator It, | |
| AMDGPU::Waitcnt Wait, | |
| const WaitcntBrackets &ScoreBrackets) override; | |
| const WaitEventSet &getWaitEvents(AMDGPU::InstCounterType T) const override { | |
| return WaitEventMaskForInstGFX12Plus[T]; | |
| } | |
| AMDGPU::Waitcnt getAllZeroWaitcnt(bool IncludeVSCnt) const override; | |
| }; | |
| // Flags indicating which counters should be flushed in a loop preheader. | |
| struct PreheaderFlushFlags { | |
| bool FlushVmCnt = false; | |
| bool FlushDsCnt = false; | |
| }; | |
| class SIInsertWaitcnts { | |
| DenseMap<const Value *, MachineBasicBlock *> SLoadAddresses; | |
| DenseMap<MachineBasicBlock *, PreheaderFlushFlags> PreheadersToFlush; | |
| MachineLoopInfo &MLI; | |
| MachinePostDominatorTree &PDT; | |
| AliasAnalysis *AA = nullptr; | |
| MachineFunction &MF; | |
| struct BlockInfo { | |
| std::unique_ptr<WaitcntBrackets> Incoming; | |
| bool Dirty = true; | |
| BlockInfo() = default; | |
| BlockInfo(BlockInfo &&) = default; | |
| BlockInfo &operator=(BlockInfo &&) = default; | |
| ~BlockInfo(); | |
| }; | |
| MapVector<MachineBasicBlock *, BlockInfo> BlockInfos; | |
| bool ForceEmitWaitcnt[AMDGPU::NUM_INST_CNTS] = {}; | |
| std::unique_ptr<WaitcntGenerator> WCG; | |
| // Remember call and return instructions in the function. | |
| DenseSet<MachineInstr *> CallInsts; | |
| DenseSet<MachineInstr *> ReturnInsts; | |
| // Remember all S_ENDPGM instructions. The boolean flag is true if there might | |
| // be outstanding stores but definitely no outstanding scratch stores, to help | |
| // with insertion of DEALLOC_VGPRS messages. | |
| DenseMap<MachineInstr *, bool> EndPgmInsts; | |
| AMDGPU::HardwareLimits Limits; | |
| public: | |
| const GCNSubtarget &ST; | |
| const SIInstrInfo &TII; | |
| const SIRegisterInfo &TRI; | |
| const MachineRegisterInfo &MRI; | |
| AMDGPU::InstCounterType SmemAccessCounter; | |
| AMDGPU::InstCounterType MaxCounter; | |
| bool IsExpertMode = false; | |
| SIInsertWaitcnts(MachineLoopInfo &MLI, MachinePostDominatorTree &PDT, | |
| AliasAnalysis *AA, MachineFunction &MF) | |
| : MLI(MLI), PDT(PDT), AA(AA), MF(MF), ST(MF.getSubtarget<GCNSubtarget>()), | |
| TII(*ST.getInstrInfo()), TRI(TII.getRegisterInfo()), | |
| MRI(MF.getRegInfo()) { | |
| (void)ForceExpCounter; | |
| (void)ForceLgkmCounter; | |
| (void)ForceVMCounter; | |
| } | |
| const AMDGPU::HardwareLimits &getLimits() const { return Limits; } | |
| PreheaderFlushFlags getPreheaderFlushFlags(MachineLoop *ML, | |
| const WaitcntBrackets &Brackets); | |
| PreheaderFlushFlags isPreheaderToFlush(MachineBasicBlock &MBB, | |
| const WaitcntBrackets &ScoreBrackets); | |
| bool isVMEMOrFlatVMEM(const MachineInstr &MI) const; | |
| bool isDSRead(const MachineInstr &MI) const; | |
| bool mayStoreIncrementingDSCNT(const MachineInstr &MI) const; | |
| bool run(); | |
| void setForceEmitWaitcnt() { | |
| // For non-debug builds, ForceEmitWaitcnt has been initialized to false; | |
| // For debug builds, get the debug counter info and adjust if need be | |
| #ifndef NDEBUG | |
| if (DebugCounter::isCounterSet(ForceExpCounter) && | |
| DebugCounter::shouldExecute(ForceExpCounter)) { | |
| ForceEmitWaitcnt[AMDGPU::EXP_CNT] = true; | |
| } else { | |
| ForceEmitWaitcnt[AMDGPU::EXP_CNT] = false; | |
| } | |
| if (DebugCounter::isCounterSet(ForceLgkmCounter) && | |
| DebugCounter::shouldExecute(ForceLgkmCounter)) { | |
| ForceEmitWaitcnt[AMDGPU::DS_CNT] = true; | |
| ForceEmitWaitcnt[AMDGPU::KM_CNT] = true; | |
| } else { | |
| ForceEmitWaitcnt[AMDGPU::DS_CNT] = false; | |
| ForceEmitWaitcnt[AMDGPU::KM_CNT] = false; | |
| } | |
| if (DebugCounter::isCounterSet(ForceVMCounter) && | |
| DebugCounter::shouldExecute(ForceVMCounter)) { | |
| ForceEmitWaitcnt[AMDGPU::LOAD_CNT] = true; | |
| ForceEmitWaitcnt[AMDGPU::SAMPLE_CNT] = true; | |
| ForceEmitWaitcnt[AMDGPU::BVH_CNT] = true; | |
| } else { | |
| ForceEmitWaitcnt[AMDGPU::LOAD_CNT] = false; | |
| ForceEmitWaitcnt[AMDGPU::SAMPLE_CNT] = false; | |
| ForceEmitWaitcnt[AMDGPU::BVH_CNT] = false; | |
| } | |
| ForceEmitWaitcnt[AMDGPU::VA_VDST] = false; | |
| ForceEmitWaitcnt[AMDGPU::VM_VSRC] = false; | |
| #endif // NDEBUG | |
| } | |
| // Return the appropriate VMEM_*_ACCESS type for Inst, which must be a VMEM | |
| // instruction. | |
| WaitEventType getVmemWaitEventType(const MachineInstr &Inst) const { | |
| switch (Inst.getOpcode()) { | |
| // FIXME: GLOBAL_INV needs to be tracked with xcnt too. | |
| case AMDGPU::GLOBAL_INV: | |
| return GLOBAL_INV_ACCESS; // tracked using loadcnt, but doesn't write | |
| // VGPRs | |
| case AMDGPU::GLOBAL_WB: | |
| case AMDGPU::GLOBAL_WBINV: | |
| return VMEM_WRITE_ACCESS; // tracked using storecnt | |
| default: | |
| break; | |
| } | |
| // Maps VMEM access types to their corresponding WaitEventType. | |
| static const WaitEventType VmemReadMapping[NUM_VMEM_TYPES] = { | |
| VMEM_ACCESS, VMEM_SAMPLER_READ_ACCESS, VMEM_BVH_READ_ACCESS}; | |
| assert(SIInstrInfo::isVMEM(Inst)); | |
| // LDS DMA loads are also stores, but on the LDS side. On the VMEM side | |
| // these should use VM_CNT. | |
| if (!ST.hasVscnt() || SIInstrInfo::mayWriteLDSThroughDMA(Inst)) | |
| return VMEM_ACCESS; | |
| if (Inst.mayStore() && | |
| (!Inst.mayLoad() || SIInstrInfo::isAtomicNoRet(Inst))) { | |
| if (TII.mayAccessScratch(Inst)) | |
| return SCRATCH_WRITE_ACCESS; | |
| return VMEM_WRITE_ACCESS; | |
| } | |
| if (!ST.hasExtendedWaitCounts() || SIInstrInfo::isFLAT(Inst)) | |
| return VMEM_ACCESS; | |
| return VmemReadMapping[getVmemType(Inst)]; | |
| } | |
| std::optional<WaitEventType> | |
| getExpertSchedulingEventType(const MachineInstr &Inst) const; | |
| bool isAsync(const MachineInstr &MI) const { | |
| if (!SIInstrInfo::isLDSDMA(MI)) | |
| return false; | |
| if (SIInstrInfo::usesASYNC_CNT(MI)) | |
| return true; | |
| const MachineOperand *Async = | |
| TII.getNamedOperand(MI, AMDGPU::OpName::IsAsync); | |
| return Async && (Async->getImm()); | |
| } | |
| bool isNonAsyncLdsDmaWrite(const MachineInstr &MI) const { | |
| return SIInstrInfo::mayWriteLDSThroughDMA(MI) && !isAsync(MI); | |
| } | |
| bool isAsyncLdsDmaWrite(const MachineInstr &MI) const { | |
| return SIInstrInfo::mayWriteLDSThroughDMA(MI) && isAsync(MI); | |
| } | |
| bool shouldUpdateAsyncMark(const MachineInstr &MI, | |
| AMDGPU::InstCounterType T) const { | |
| if (!isAsyncLdsDmaWrite(MI)) | |
| return false; | |
| if (SIInstrInfo::usesASYNC_CNT(MI)) | |
| return T == AMDGPU::ASYNC_CNT; | |
| return T == AMDGPU::LOAD_CNT; | |
| } | |
| bool isVmemAccess(const MachineInstr &MI) const; | |
| bool generateWaitcntInstBefore(MachineInstr &MI, | |
| WaitcntBrackets &ScoreBrackets, | |
| MachineInstr *OldWaitcntInstr, | |
| PreheaderFlushFlags FlushFlags); | |
| bool generateWaitcnt(AMDGPU::Waitcnt Wait, | |
| MachineBasicBlock::instr_iterator It, | |
| MachineBasicBlock &Block, WaitcntBrackets &ScoreBrackets, | |
| MachineInstr *OldWaitcntInstr); | |
| /// \returns all events that correspond to \p Inst. | |
| WaitEventSet getEventsFor(const MachineInstr &Inst) const; | |
| void updateEventWaitcntAfter(MachineInstr &Inst, | |
| WaitcntBrackets *ScoreBrackets); | |
| bool isNextENDPGM(MachineBasicBlock::instr_iterator It, | |
| MachineBasicBlock *Block) const; | |
| bool insertForcedWaitAfter(MachineInstr &Inst, MachineBasicBlock &Block, | |
| WaitcntBrackets &ScoreBrackets); | |
| bool insertWaitcntInBlock(MachineFunction &MF, MachineBasicBlock &Block, | |
| WaitcntBrackets &ScoreBrackets); | |
| /// Removes redundant Soft Xcnt Waitcnts in \p Block emitted by the Memory | |
| /// Legalizer. Returns true if block was modified. | |
| bool removeRedundantSoftXcnts(MachineBasicBlock &Block); | |
| void setSchedulingMode(MachineBasicBlock &MBB, MachineBasicBlock::iterator I, | |
| bool ExpertMode) const; | |
| const WaitEventSet &getWaitEvents(AMDGPU::InstCounterType T) const { | |
| return WCG->getWaitEvents(T); | |
| } | |
| AMDGPU::InstCounterType getCounterFromEvent(WaitEventType E) const { | |
| return WCG->getCounterFromEvent(E); | |
| } | |
| }; | |
| // This objects maintains the current score brackets of each wait counter, and | |
| // a per-register scoreboard for each wait counter. | |
| // | |
| // We also maintain the latest score for every event type that can change the | |
| // waitcnt in order to know if there are multiple types of events within | |
| // the brackets. When multiple types of event happen in the bracket, | |
| // wait count may get decreased out of order, therefore we need to put in | |
| // "s_waitcnt 0" before use. | |
| class WaitcntBrackets { | |
| public: | |
| WaitcntBrackets(const SIInsertWaitcnts *Context) : Context(Context) { | |
| assert(Context->TRI.getNumRegUnits() < REGUNITS_END); | |
| } | |
| #ifndef NDEBUG | |
| ~WaitcntBrackets() { | |
| unsigned NumUnusedVmem = 0, NumUnusedSGPRs = 0; | |
| for (auto &[ID, Val] : VMem) { | |
| if (Val.empty()) | |
| ++NumUnusedVmem; | |
| } | |
| for (auto &[ID, Val] : SGPRs) { | |
| if (Val.empty()) | |
| ++NumUnusedSGPRs; | |
| } | |
| if (NumUnusedVmem || NumUnusedSGPRs) { | |
| errs() << "WaitcntBracket had unused entries at destruction time: " | |
| << NumUnusedVmem << " VMem and " << NumUnusedSGPRs | |
| << " SGPR unused entries\n"; | |
| std::abort(); | |
| } | |
| } | |
| #endif | |
| bool isSmemCounter(AMDGPU::InstCounterType T) const { | |
| return T == Context->SmemAccessCounter || T == AMDGPU::X_CNT; | |
| } | |
| unsigned getOutstanding(AMDGPU::InstCounterType T) const { | |
| return ScoreUBs[T] - ScoreLBs[T]; | |
| } | |
| bool hasPendingVMEM(VMEMID ID, AMDGPU::InstCounterType T) const { | |
| return getVMemScore(ID, T) > getScoreLB(T); | |
| } | |
| /// \Return true if we have no score entries for counter \p T. | |
| bool empty(AMDGPU::InstCounterType T) const { return getScoreRange(T) == 0; } | |
| private: | |
| unsigned getScoreLB(AMDGPU::InstCounterType T) const { | |
| assert(T < AMDGPU::NUM_INST_CNTS); | |
| return ScoreLBs[T]; | |
| } | |
| unsigned getScoreUB(AMDGPU::InstCounterType T) const { | |
| assert(T < AMDGPU::NUM_INST_CNTS); | |
| return ScoreUBs[T]; | |
| } | |
| unsigned getScoreRange(AMDGPU::InstCounterType T) const { | |
| return getScoreUB(T) - getScoreLB(T); | |
| } | |
| unsigned getSGPRScore(MCRegUnit RU, AMDGPU::InstCounterType T) const { | |
| auto It = SGPRs.find(RU); | |
| return It != SGPRs.end() ? It->second.get(T) : 0; | |
| } | |
| unsigned getVMemScore(VMEMID TID, AMDGPU::InstCounterType T) const { | |
| auto It = VMem.find(TID); | |
| return It != VMem.end() ? It->second.Scores[T] : 0; | |
| } | |
| public: | |
| bool merge(const WaitcntBrackets &Other); | |
| bool counterOutOfOrder(AMDGPU::InstCounterType T) const; | |
| void simplifyWaitcnt(AMDGPU::Waitcnt &Wait) const { | |
| simplifyWaitcnt(Wait, Wait); | |
| } | |
| void simplifyWaitcnt(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const; | |
| void simplifyWaitcnt(AMDGPU::InstCounterType T, unsigned &Count) const; | |
| void simplifyWaitcnt(AMDGPU::Waitcnt &Wait, AMDGPU::InstCounterType T) const; | |
| void simplifyXcnt(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const; | |
| void simplifyVmVsrc(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const; | |
| void determineWaitForPhysReg(AMDGPU::InstCounterType T, MCPhysReg Reg, | |
| AMDGPU::Waitcnt &Wait, | |
| const MachineInstr &MI) const; | |
| MCPhysReg determineVGPR16Dependency(const MachineInstr &MI, | |
| AMDGPU::InstCounterType T, | |
| MCPhysReg Reg) const; | |
| void determineWaitForLDSDMA(AMDGPU::InstCounterType T, VMEMID TID, | |
| AMDGPU::Waitcnt &Wait) const; | |
| AMDGPU::Waitcnt determineAsyncWait(unsigned N); | |
| void tryClearSCCWriteEvent(MachineInstr *Inst); | |
| void applyWaitcnt(const AMDGPU::Waitcnt &Wait); | |
| void applyWaitcnt(AMDGPU::InstCounterType T, unsigned Count); | |
| void applyWaitcnt(const AMDGPU::Waitcnt &Wait, AMDGPU::InstCounterType T); | |
| void updateByEvent(WaitEventType E, MachineInstr &MI); | |
| void recordAsyncMark(MachineInstr &MI); | |
| bool hasPendingEvent() const { return !PendingEvents.empty(); } | |
| bool hasPendingEvent(WaitEventType E) const { | |
| return PendingEvents.contains(E); | |
| } | |
| bool hasPendingEvent(AMDGPU::InstCounterType T) const { | |
| bool HasPending = PendingEvents & Context->getWaitEvents(T); | |
| assert(HasPending == !empty(T) && | |
| "Expected pending events iff scoreboard is not empty"); | |
| return HasPending; | |
| } | |
| bool hasMixedPendingEvents(AMDGPU::InstCounterType T) const { | |
| WaitEventSet Events = PendingEvents & Context->getWaitEvents(T); | |
| // Return true if more than one bit is set in Events. | |
| return Events.twoOrMore(); | |
| } | |
| bool hasPendingFlat() const { | |
| return ((LastFlatDsCnt > ScoreLBs[AMDGPU::DS_CNT] && | |
| LastFlatDsCnt <= ScoreUBs[AMDGPU::DS_CNT]) || | |
| (LastFlatLoadCnt > ScoreLBs[AMDGPU::LOAD_CNT] && | |
| LastFlatLoadCnt <= ScoreUBs[AMDGPU::LOAD_CNT])); | |
| } | |
| void setPendingFlat() { | |
| LastFlatLoadCnt = ScoreUBs[AMDGPU::LOAD_CNT]; | |
| LastFlatDsCnt = ScoreUBs[AMDGPU::DS_CNT]; | |
| } | |
| bool hasPendingGDS() const { | |
| return LastGDS > ScoreLBs[AMDGPU::DS_CNT] && | |
| LastGDS <= ScoreUBs[AMDGPU::DS_CNT]; | |
| } | |
| unsigned getPendingGDSWait() const { | |
| return std::min(getScoreUB(AMDGPU::DS_CNT) - LastGDS, | |
| getWaitCountMax(Context->getLimits(), AMDGPU::DS_CNT) - 1); | |
| } | |
| void setPendingGDS() { LastGDS = ScoreUBs[AMDGPU::DS_CNT]; } | |
| // Return true if there might be pending writes to the vgpr-interval by VMEM | |
| // instructions with types different from V. | |
| bool hasOtherPendingVmemTypes(MCPhysReg Reg, VmemType V) const { | |
| for (MCRegUnit RU : regunits(Reg)) { | |
| auto It = VMem.find(toVMEMID(RU)); | |
| if (It != VMem.end() && (It->second.VMEMTypes & ~(1 << V))) | |
| return true; | |
| } | |
| return false; | |
| } | |
| void clearVgprVmemTypes(MCPhysReg Reg) { | |
| for (MCRegUnit RU : regunits(Reg)) { | |
| if (auto It = VMem.find(toVMEMID(RU)); It != VMem.end()) { | |
| It->second.VMEMTypes = 0; | |
| if (It->second.empty()) | |
| VMem.erase(It); | |
| } | |
| } | |
| } | |
| void setStateOnFunctionEntryOrReturn() { | |
| setScoreUB(AMDGPU::STORE_CNT, | |
| getScoreUB(AMDGPU::STORE_CNT) + | |
| getWaitCountMax(Context->getLimits(), AMDGPU::STORE_CNT)); | |
| PendingEvents |= Context->getWaitEvents(AMDGPU::STORE_CNT); | |
| } | |
| ArrayRef<const MachineInstr *> getLDSDMAStores() const { | |
| return LDSDMAStores; | |
| } | |
| bool hasPointSampleAccel(const MachineInstr &MI) const; | |
| bool hasPointSamplePendingVmemTypes(const MachineInstr &MI, | |
| MCPhysReg RU) const; | |
| void print(raw_ostream &) const; | |
| void dump() const { print(dbgs()); } | |
| // Free up memory by removing empty entries from the DenseMap that track event | |
| // scores. | |
| void purgeEmptyTrackingData(); | |
| private: | |
| struct MergeInfo { | |
| unsigned OldLB; | |
| unsigned OtherLB; | |
| unsigned MyShift; | |
| unsigned OtherShift; | |
| }; | |
| using CounterValueArray = std::array<unsigned, AMDGPU::NUM_INST_CNTS>; | |
| void determineWaitForScore(AMDGPU::InstCounterType T, unsigned Score, | |
| AMDGPU::Waitcnt &Wait) const; | |
| static bool mergeScore(const MergeInfo &M, unsigned &Score, | |
| unsigned OtherScore); | |
| bool mergeAsyncMarks(ArrayRef<MergeInfo> MergeInfos, | |
| ArrayRef<CounterValueArray> OtherMarks); | |
| iterator_range<MCRegUnitIterator> regunits(MCPhysReg Reg) const { | |
| assert(Reg != AMDGPU::SCC && "Shouldn't be used on SCC"); | |
| if (!Context->TRI.isInAllocatableClass(Reg)) | |
| return {{}, {}}; | |
| return Context->TRI.regunits(Reg); | |
| } | |
| void setScoreLB(AMDGPU::InstCounterType T, unsigned Val) { | |
| assert(T < AMDGPU::NUM_INST_CNTS); | |
| ScoreLBs[T] = Val; | |
| } | |
| void setScoreUB(AMDGPU::InstCounterType T, unsigned Val) { | |
| assert(T < AMDGPU::NUM_INST_CNTS); | |
| ScoreUBs[T] = Val; | |
| if (T != AMDGPU::EXP_CNT) | |
| return; | |
| if (getScoreRange(AMDGPU::EXP_CNT) > | |
| getWaitCountMax(Context->getLimits(), AMDGPU::EXP_CNT)) | |
| ScoreLBs[AMDGPU::EXP_CNT] = | |
| ScoreUBs[AMDGPU::EXP_CNT] - | |
| getWaitCountMax(Context->getLimits(), AMDGPU::EXP_CNT); | |
| } | |
| void setRegScore(MCPhysReg Reg, AMDGPU::InstCounterType T, unsigned Val) { | |
| const SIRegisterInfo &TRI = Context->TRI; | |
| if (Reg == AMDGPU::SCC) { | |
| SCCScore = Val; | |
| } else if (TRI.isVectorRegister(Context->MRI, Reg)) { | |
| for (MCRegUnit RU : regunits(Reg)) | |
| VMem[toVMEMID(RU)].Scores[T] = Val; | |
| } else if (TRI.isSGPRReg(Context->MRI, Reg)) { | |
| for (MCRegUnit RU : regunits(Reg)) | |
| SGPRs[RU].get(T) = Val; | |
| } else { | |
| llvm_unreachable("Register cannot be tracked/unknown register!"); | |
| } | |
| } | |
| void setVMemScore(VMEMID TID, AMDGPU::InstCounterType T, unsigned Val) { | |
| VMem[TID].Scores[T] = Val; | |
| } | |
| void setScoreByOperand(const MachineOperand &Op, | |
| AMDGPU::InstCounterType CntTy, unsigned Val); | |
| const SIInsertWaitcnts *Context; | |
| unsigned ScoreLBs[AMDGPU::NUM_INST_CNTS] = {0}; | |
| unsigned ScoreUBs[AMDGPU::NUM_INST_CNTS] = {0}; | |
| WaitEventSet PendingEvents; | |
| // Remember the last flat memory operation. | |
| unsigned LastFlatDsCnt = 0; | |
| unsigned LastFlatLoadCnt = 0; | |
| // Remember the last GDS operation. | |
| unsigned LastGDS = 0; | |
| // The score tracking logic is fragmented as follows: | |
| // - VMem: VGPR RegUnits and LDS DMA IDs, see the VMEMID encoding. | |
| // - SGPRs: SGPR RegUnits | |
| // - SCC: Non-allocatable and not general purpose: not a SGPR. | |
| // | |
| // For the VMem case, if the key is within the range of LDS DMA IDs, | |
| // then the corresponding index into the `LDSDMAStores` vector below is: | |
| // Key - LDSDMA_BEGIN - 1 | |
| // This is because LDSDMA_BEGIN is a generic entry and does not have an | |
| // associated MachineInstr. | |
| // | |
| // TODO: Could we track SCC alongside SGPRs so it's not longer a special case? | |
| struct VMEMInfo { | |
| // Scores for all instruction counters. Zero-initialized. | |
| CounterValueArray Scores{}; | |
| // Bitmask of the VmemTypes of VMEM instructions for this VGPR. | |
| unsigned VMEMTypes = 0; | |
| bool empty() const { return all_of(Scores, equal_to(0)) && !VMEMTypes; } | |
| }; | |
| /// Wait cnt scores for every sgpr, the DS_CNT (corresponding to LGKMcnt | |
| /// pre-gfx12) or KM_CNT (gfx12+ only), and X_CNT (gfx1250) are relevant. | |
| class SGPRInfo { | |
| /// Either DS_CNT or KM_CNT score. | |
| unsigned ScoreDsKmCnt = 0; | |
| unsigned ScoreXCnt = 0; | |
| public: | |
| unsigned get(AMDGPU::InstCounterType T) const { | |
| assert( | |
| (T == AMDGPU::DS_CNT || T == AMDGPU::KM_CNT || T == AMDGPU::X_CNT) && | |
| "Invalid counter"); | |
| return T == AMDGPU::X_CNT ? ScoreXCnt : ScoreDsKmCnt; | |
| } | |
| unsigned &get(AMDGPU::InstCounterType T) { | |
| assert( | |
| (T == AMDGPU::DS_CNT || T == AMDGPU::KM_CNT || T == AMDGPU::X_CNT) && | |
| "Invalid counter"); | |
| return T == AMDGPU::X_CNT ? ScoreXCnt : ScoreDsKmCnt; | |
| } | |
| bool empty() const { return !ScoreDsKmCnt && !ScoreXCnt; } | |
| }; | |
| DenseMap<VMEMID, VMEMInfo> VMem; // VGPR + LDS DMA | |
| DenseMap<MCRegUnit, SGPRInfo> SGPRs; | |
| // Reg score for SCC. | |
| unsigned SCCScore = 0; | |
| // The unique instruction that has an SCC write pending, if there is one. | |
| const MachineInstr *PendingSCCWrite = nullptr; | |
| // Store representative LDS DMA operations. The only useful info here is | |
| // alias info. One store is kept per unique AAInfo. | |
| SmallVector<const MachineInstr *> LDSDMAStores; | |
| // State of all counters at each async mark encountered so far. | |
| SmallVector<CounterValueArray> AsyncMarks; | |
| // But in the rare pathological case, a nest of loops that pushes marks | |
| // without waiting on any mark can cause AsyncMarks to grow very large. We cap | |
| // it to a reasonable limit. We can tune this later or potentially introduce a | |
| // user option to control the value. | |
| static constexpr unsigned MaxAsyncMarks = 16; | |
| // Track the upper bound score for async operations that are not part of a | |
| // mark yet. Initialized to all zeros. | |
| CounterValueArray AsyncScore{}; | |
| }; | |
| SIInsertWaitcnts::BlockInfo::~BlockInfo() = default; | |
| class SIInsertWaitcntsLegacy : public MachineFunctionPass { | |
| public: | |
| static char ID; | |
| SIInsertWaitcntsLegacy() : MachineFunctionPass(ID) {} | |
| bool runOnMachineFunction(MachineFunction &MF) override; | |
| StringRef getPassName() const override { | |
| return "SI insert wait instructions"; | |
| } | |
| void getAnalysisUsage(AnalysisUsage &AU) const override { | |
| AU.setPreservesCFG(); | |
| AU.addRequired<MachineLoopInfoWrapperPass>(); | |
| AU.addRequired<MachinePostDominatorTreeWrapperPass>(); | |
| AU.addUsedIfAvailable<AAResultsWrapperPass>(); | |
| AU.addPreserved<AAResultsWrapperPass>(); | |
| MachineFunctionPass::getAnalysisUsage(AU); | |
| } | |
| }; | |
| } // end anonymous namespace | |
| void WaitcntBrackets::setScoreByOperand(const MachineOperand &Op, | |
| AMDGPU::InstCounterType CntTy, | |
| unsigned Score) { | |
| setRegScore(Op.getReg().asMCReg(), CntTy, Score); | |
| } | |
| // Return true if the subtarget is one that enables Point Sample Acceleration | |
| // and the MachineInstr passed in is one to which it might be applied (the | |
| // hardware makes this decision based on several factors, but we can't determine | |
| // this at compile time, so we have to assume it might be applied if the | |
| // instruction supports it). | |
| bool WaitcntBrackets::hasPointSampleAccel(const MachineInstr &MI) const { | |
| if (!Context->ST.hasPointSampleAccel() || !SIInstrInfo::isMIMG(MI)) | |
| return false; | |
| const AMDGPU::MIMGInfo *Info = AMDGPU::getMIMGInfo(MI.getOpcode()); | |
| const AMDGPU::MIMGBaseOpcodeInfo *BaseInfo = | |
| AMDGPU::getMIMGBaseOpcodeInfo(Info->BaseOpcode); | |
| return BaseInfo->PointSampleAccel; | |
| } | |
| // Return true if the subtarget enables Point Sample Acceleration, the supplied | |
| // MachineInstr is one to which it might be applied and the supplied interval is | |
| // one that has outstanding writes to vmem-types different than VMEM_NOSAMPLER | |
| // (this is the type that a point sample accelerated instruction effectively | |
| // becomes) | |
| bool WaitcntBrackets::hasPointSamplePendingVmemTypes(const MachineInstr &MI, | |
| MCPhysReg Reg) const { | |
| if (!hasPointSampleAccel(MI)) | |
| return false; | |
| return hasOtherPendingVmemTypes(Reg, VMEM_NOSAMPLER); | |
| } | |
| void WaitcntBrackets::updateByEvent(WaitEventType E, MachineInstr &Inst) { | |
| AMDGPU::InstCounterType T = Context->getCounterFromEvent(E); | |
| assert(T < Context->MaxCounter); | |
| unsigned UB = getScoreUB(T); | |
| unsigned Increment = 1; | |
| if (T == AMDGPU::VA_VDST && AMDGPU::getHasMatrixScale(Inst.getOpcode())) { | |
| // V_WMMA_SCALE instructions use VOP3PX2 encoding. Hardware treats this as | |
| // two VOP3P instructions and increments VA_VDST twice. | |
| Increment = 2; | |
| } | |
| unsigned CurrScore = UB + Increment; | |
| if (CurrScore == 0) | |
| report_fatal_error("InsertWaitcnt score wraparound"); | |
| // PendingEvents and ScoreUB need to be update regardless if this event | |
| // changes the score of a register or not. | |
| // Examples including vm_cnt when buffer-store or lgkm_cnt when send-message. | |
| PendingEvents.insert(E); | |
| setScoreUB(T, CurrScore); | |
| const SIRegisterInfo &TRI = Context->TRI; | |
| const MachineRegisterInfo &MRI = Context->MRI; | |
| const SIInstrInfo &TII = Context->TII; | |
| if (T == AMDGPU::EXP_CNT) { | |
| // Put score on the source vgprs. If this is a store, just use those | |
| // specific register(s). | |
| if (TII.isDS(Inst) && Inst.mayLoadOrStore()) { | |
| // All GDS operations must protect their address register (same as | |
| // export.) | |
| if (const auto *AddrOp = TII.getNamedOperand(Inst, AMDGPU::OpName::addr)) | |
| setScoreByOperand(*AddrOp, AMDGPU::EXP_CNT, CurrScore); | |
| if (Inst.mayStore()) { | |
| if (const auto *Data0 = | |
| TII.getNamedOperand(Inst, AMDGPU::OpName::data0)) | |
| setScoreByOperand(*Data0, AMDGPU::EXP_CNT, CurrScore); | |
| if (const auto *Data1 = | |
| TII.getNamedOperand(Inst, AMDGPU::OpName::data1)) | |
| setScoreByOperand(*Data1, AMDGPU::EXP_CNT, CurrScore); | |
| } else if (SIInstrInfo::isAtomicRet(Inst) && !SIInstrInfo::isGWS(Inst) && | |
| Inst.getOpcode() != AMDGPU::DS_APPEND && | |
| Inst.getOpcode() != AMDGPU::DS_CONSUME && | |
| Inst.getOpcode() != AMDGPU::DS_ORDERED_COUNT) { | |
| for (const MachineOperand &Op : Inst.all_uses()) { | |
| if (TRI.isVectorRegister(MRI, Op.getReg())) | |
| setScoreByOperand(Op, AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } | |
| } else if (TII.isFLAT(Inst)) { | |
| if (Inst.mayStore()) { | |
| setScoreByOperand(*TII.getNamedOperand(Inst, AMDGPU::OpName::data), | |
| AMDGPU::EXP_CNT, CurrScore); | |
| } else if (SIInstrInfo::isAtomicRet(Inst)) { | |
| setScoreByOperand(*TII.getNamedOperand(Inst, AMDGPU::OpName::data), | |
| AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } else if (TII.isMIMG(Inst)) { | |
| if (Inst.mayStore()) { | |
| setScoreByOperand(Inst.getOperand(0), AMDGPU::EXP_CNT, CurrScore); | |
| } else if (SIInstrInfo::isAtomicRet(Inst)) { | |
| setScoreByOperand(*TII.getNamedOperand(Inst, AMDGPU::OpName::data), | |
| AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } else if (TII.isMTBUF(Inst)) { | |
| if (Inst.mayStore()) | |
| setScoreByOperand(Inst.getOperand(0), AMDGPU::EXP_CNT, CurrScore); | |
| } else if (TII.isMUBUF(Inst)) { | |
| if (Inst.mayStore()) { | |
| setScoreByOperand(Inst.getOperand(0), AMDGPU::EXP_CNT, CurrScore); | |
| } else if (SIInstrInfo::isAtomicRet(Inst)) { | |
| setScoreByOperand(*TII.getNamedOperand(Inst, AMDGPU::OpName::data), | |
| AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } else if (TII.isLDSDIR(Inst)) { | |
| // LDSDIR instructions attach the score to the destination. | |
| setScoreByOperand(*TII.getNamedOperand(Inst, AMDGPU::OpName::vdst), | |
| AMDGPU::EXP_CNT, CurrScore); | |
| } else { | |
| if (TII.isEXP(Inst)) { | |
| // For export the destination registers are really temps that | |
| // can be used as the actual source after export patching, so | |
| // we need to treat them like sources and set the EXP_CNT | |
| // score. | |
| for (MachineOperand &DefMO : Inst.all_defs()) { | |
| if (TRI.isVGPR(MRI, DefMO.getReg())) { | |
| setScoreByOperand(DefMO, AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } | |
| } | |
| for (const MachineOperand &Op : Inst.all_uses()) { | |
| if (TRI.isVectorRegister(MRI, Op.getReg())) | |
| setScoreByOperand(Op, AMDGPU::EXP_CNT, CurrScore); | |
| } | |
| } | |
| } else if (T == AMDGPU::X_CNT) { | |
| WaitEventType OtherEvent = E == SMEM_GROUP ? VMEM_GROUP : SMEM_GROUP; | |
| if (PendingEvents.contains(OtherEvent)) { | |
| // Hardware inserts an implicit xcnt between interleaved | |
| // SMEM and VMEM operations. So there will never be | |
| // outstanding address translations for both SMEM and | |
| // VMEM at the same time. | |
| setScoreLB(T, getScoreUB(T) - 1); | |
| PendingEvents.remove(OtherEvent); | |
| } | |
| for (const MachineOperand &Op : Inst.all_uses()) | |
| setScoreByOperand(Op, T, CurrScore); | |
| } else if (T == AMDGPU::VA_VDST || T == AMDGPU::VM_VSRC) { | |
| // Match the score to the VGPR destination or source registers as | |
| // appropriate | |
| for (const MachineOperand &Op : Inst.operands()) { | |
| if (!Op.isReg() || (T == AMDGPU::VA_VDST && Op.isUse()) || | |
| (T == AMDGPU::VM_VSRC && Op.isDef())) | |
| continue; | |
| if (TRI.isVectorRegister(Context->MRI, Op.getReg())) | |
| setScoreByOperand(Op, T, CurrScore); | |
| } | |
| } else /* LGKM_CNT || EXP_CNT || VS_CNT || NUM_INST_CNTS */ { | |
| // Match the score to the destination registers. | |
| // | |
| // Check only explicit operands. Stores, especially spill stores, include | |
| // implicit uses and defs of their super registers which would create an | |
| // artificial dependency, while these are there only for register liveness | |
| // accounting purposes. | |
| // | |
| // Special cases where implicit register defs exists, such as M0 or VCC, | |
| // but none with memory instructions. | |
| for (const MachineOperand &Op : Inst.defs()) { | |
| if (T == AMDGPU::LOAD_CNT || T == AMDGPU::SAMPLE_CNT || | |
| T == AMDGPU::BVH_CNT) { | |
| if (!TRI.isVectorRegister(MRI, Op.getReg())) // TODO: add wrapper | |
| continue; | |
| if (updateVMCntOnly(Inst)) { | |
| // updateVMCntOnly should only leave us with VGPRs | |
| // MUBUF, MTBUF, MIMG, FlatGlobal, and FlatScratch only have VGPR/AGPR | |
| // defs. That's required for a sane index into `VgprMemTypes` below | |
| assert(TRI.isVectorRegister(MRI, Op.getReg())); | |
| VmemType V = getVmemType(Inst); | |
| unsigned char TypesMask = 1 << V; | |
| // If instruction can have Point Sample Accel applied, we have to flag | |
| // this with another potential dependency | |
| if (hasPointSampleAccel(Inst)) | |
| TypesMask |= 1 << VMEM_NOSAMPLER; | |
| for (MCRegUnit RU : regunits(Op.getReg().asMCReg())) | |
| VMem[toVMEMID(RU)].VMEMTypes |= TypesMask; | |
| } | |
| } | |
| setScoreByOperand(Op, T, CurrScore); | |
| } | |
| if (Inst.mayStore() && | |
| (TII.isDS(Inst) || Context->isNonAsyncLdsDmaWrite(Inst))) { | |
| // MUBUF and FLAT LDS DMA operations need a wait on vmcnt before LDS | |
| // written can be accessed. A load from LDS to VMEM does not need a wait. | |
| // | |
| // The "Slot" is the offset from LDSDMA_BEGIN. If it's non-zero, then | |
| // there is a MachineInstr in LDSDMAStores used to track this LDSDMA | |
| // store. The "Slot" is the index into LDSDMAStores + 1. | |
| unsigned Slot = 0; | |
| for (const auto *MemOp : Inst.memoperands()) { | |
| if (!MemOp->isStore() || | |
| MemOp->getAddrSpace() != AMDGPUAS::LOCAL_ADDRESS) | |
| continue; | |
| // Comparing just AA info does not guarantee memoperands are equal | |
| // in general, but this is so for LDS DMA in practice. | |
| auto AAI = MemOp->getAAInfo(); | |
| // Alias scope information gives a way to definitely identify an | |
| // original memory object and practically produced in the module LDS | |
| // lowering pass. If there is no scope available we will not be able | |
| // to disambiguate LDS aliasing as after the module lowering all LDS | |
| // is squashed into a single big object. | |
| if (!AAI || !AAI.Scope) | |
| break; | |
| for (unsigned I = 0, E = LDSDMAStores.size(); I != E && !Slot; ++I) { | |
| for (const auto *MemOp : LDSDMAStores[I]->memoperands()) { | |
| if (MemOp->isStore() && AAI == MemOp->getAAInfo()) { | |
| Slot = I + 1; | |
| break; | |
| } | |
| } | |
| } | |
| if (Slot) | |
| break; | |
| // The slot may not be valid because it can be >= NUM_LDSDMA which | |
| // means the scoreboard cannot track it. We still want to preserve the | |
| // MI in order to check alias information, though. | |
| LDSDMAStores.push_back(&Inst); | |
| Slot = LDSDMAStores.size(); | |
| break; | |
| } | |
| setVMemScore(LDSDMA_BEGIN, T, CurrScore); | |
| if (Slot && Slot < NUM_LDSDMA) | |
| setVMemScore(LDSDMA_BEGIN + Slot, T, CurrScore); | |
| } | |
| if (Context->shouldUpdateAsyncMark(Inst, T)) { | |
| AsyncScore[T] = CurrScore; | |
| } | |
| if (SIInstrInfo::isSBarrierSCCWrite(Inst.getOpcode())) { | |
| setRegScore(AMDGPU::SCC, T, CurrScore); | |
| PendingSCCWrite = &Inst; | |
| } | |
| } | |
| } | |
| void WaitcntBrackets::recordAsyncMark(MachineInstr &Inst) { | |
| // In the absence of loops, AsyncMarks can grow linearly with the program | |
| // until we encounter an ASYNCMARK_WAIT. We could drop the oldest mark above a | |
| // limit every time we push a new mark, but that seems like unnecessary work | |
| // in practical cases. We do separately truncate the array when processing a | |
| // loop, which should be sufficient. | |
| AsyncMarks.push_back(AsyncScore); | |
| AsyncScore = {}; | |
| LLVM_DEBUG({ | |
| dbgs() << "recordAsyncMark:\n" << Inst; | |
| for (const auto &Mark : AsyncMarks) { | |
| llvm::interleaveComma(Mark, dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| }); | |
| } | |
| void WaitcntBrackets::print(raw_ostream &OS) const { | |
| const GCNSubtarget &ST = Context->ST; | |
| for (auto T : inst_counter_types(Context->MaxCounter)) { | |
| unsigned SR = getScoreRange(T); | |
| switch (T) { | |
| case AMDGPU::LOAD_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "LOAD" : "VM") << "_CNT(" | |
| << SR << "):"; | |
| break; | |
| case AMDGPU::DS_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "DS" : "LGKM") << "_CNT(" | |
| << SR << "):"; | |
| break; | |
| case AMDGPU::EXP_CNT: | |
| OS << " EXP_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::STORE_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "STORE" : "VS") << "_CNT(" | |
| << SR << "):"; | |
| break; | |
| case AMDGPU::SAMPLE_CNT: | |
| OS << " SAMPLE_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::BVH_CNT: | |
| OS << " BVH_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::KM_CNT: | |
| OS << " KM_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::X_CNT: | |
| OS << " X_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::ASYNC_CNT: | |
| OS << " ASYNC_CNT(" << SR << "):"; | |
| break; | |
| case AMDGPU::VA_VDST: | |
| OS << " VA_VDST(" << SR << "): "; | |
| break; | |
| case AMDGPU::VM_VSRC: | |
| OS << " VM_VSRC(" << SR << "): "; | |
| break; | |
| default: | |
| OS << " UNKNOWN(" << SR << "):"; | |
| break; | |
| } | |
| if (SR != 0) { | |
| // Print vgpr scores. | |
| unsigned LB = getScoreLB(T); | |
| SmallVector<VMEMID> SortedVMEMIDs(VMem.keys()); | |
| sort(SortedVMEMIDs); | |
| for (auto ID : SortedVMEMIDs) { | |
| unsigned RegScore = VMem.at(ID).Scores[T]; | |
| if (RegScore <= LB) | |
| continue; | |
| unsigned RelScore = RegScore - LB - 1; | |
| if (ID < REGUNITS_END) { | |
| OS << ' ' << RelScore << ":vRU" << ID; | |
| } else { | |
| assert(ID >= LDSDMA_BEGIN && ID < LDSDMA_END && | |
| "Unhandled/unexpected ID value!"); | |
| OS << ' ' << RelScore << ":LDSDMA" << ID; | |
| } | |
| } | |
| // Also need to print sgpr scores for lgkm_cnt or xcnt. | |
| if (isSmemCounter(T)) { | |
| SmallVector<MCRegUnit> SortedSMEMIDs(SGPRs.keys()); | |
| sort(SortedSMEMIDs); | |
| for (auto ID : SortedSMEMIDs) { | |
| unsigned RegScore = SGPRs.at(ID).get(T); | |
| if (RegScore <= LB) | |
| continue; | |
| unsigned RelScore = RegScore - LB - 1; | |
| OS << ' ' << RelScore << ":sRU" << static_cast<unsigned>(ID); | |
| } | |
| } | |
| if (T == AMDGPU::KM_CNT && SCCScore > 0) | |
| OS << ' ' << SCCScore << ":scc"; | |
| } | |
| OS << '\n'; | |
| } | |
| OS << "Pending Events: "; | |
| if (hasPendingEvent()) { | |
| ListSeparator LS; | |
| for (unsigned I = 0; I != NUM_WAIT_EVENTS; ++I) { | |
| if (hasPendingEvent((WaitEventType)I)) { | |
| OS << LS << WaitEventTypeName[I]; | |
| } | |
| } | |
| } else { | |
| OS << "none"; | |
| } | |
| OS << '\n'; | |
| OS << "Async score: "; | |
| if (AsyncScore.empty()) | |
| OS << "none"; | |
| else | |
| llvm::interleaveComma(AsyncScore, OS); | |
| OS << '\n'; | |
| OS << "Async marks: " << AsyncMarks.size() << '\n'; | |
| for (const auto &Mark : AsyncMarks) { | |
| for (auto T : AMDGPU::inst_counter_types()) { | |
| unsigned MarkedScore = Mark[T]; | |
| switch (T) { | |
| case AMDGPU::LOAD_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "LOAD" : "VM") | |
| << "_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::DS_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "DS" : "LGKM") | |
| << "_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::EXP_CNT: | |
| OS << " EXP_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::STORE_CNT: | |
| OS << " " << (ST.hasExtendedWaitCounts() ? "STORE" : "VS") | |
| << "_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::SAMPLE_CNT: | |
| OS << " SAMPLE_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::BVH_CNT: | |
| OS << " BVH_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::KM_CNT: | |
| OS << " KM_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::X_CNT: | |
| OS << " X_CNT: " << MarkedScore; | |
| break; | |
| case AMDGPU::ASYNC_CNT: | |
| OS << " ASYNC_CNT: " << MarkedScore; | |
| break; | |
| default: | |
| OS << " UNKNOWN: " << MarkedScore; | |
| break; | |
| } | |
| } | |
| OS << '\n'; | |
| } | |
| OS << '\n'; | |
| } | |
| /// Simplify \p UpdateWait by removing waits that are redundant based on the | |
| /// current WaitcntBrackets and any other waits specified in \p CheckWait. | |
| void WaitcntBrackets::simplifyWaitcnt(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const { | |
| simplifyWaitcnt(UpdateWait, AMDGPU::LOAD_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::EXP_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::DS_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::STORE_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::SAMPLE_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::BVH_CNT); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::KM_CNT); | |
| simplifyXcnt(CheckWait, UpdateWait); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::VA_VDST); | |
| simplifyVmVsrc(CheckWait, UpdateWait); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::ASYNC_CNT); | |
| } | |
| void WaitcntBrackets::simplifyWaitcnt(AMDGPU::InstCounterType T, | |
| unsigned &Count) const { | |
| // The number of outstanding events for this type, T, can be calculated | |
| // as (UB - LB). If the current Count is greater than or equal to the number | |
| // of outstanding events, then the wait for this counter is redundant. | |
| if (Count >= getScoreRange(T)) | |
| Count = ~0u; | |
| } | |
| void WaitcntBrackets::simplifyWaitcnt(AMDGPU::Waitcnt &Wait, | |
| AMDGPU::InstCounterType T) const { | |
| unsigned Cnt = Wait.get(T); | |
| simplifyWaitcnt(T, Cnt); | |
| Wait.set(T, Cnt); | |
| } | |
| void WaitcntBrackets::simplifyXcnt(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const { | |
| // Try to simplify xcnt further by checking for joint kmcnt and loadcnt | |
| // optimizations. On entry to a block with multiple predescessors, there may | |
| // be pending SMEM and VMEM events active at the same time. | |
| // In such cases, only clear one active event at a time. | |
| // TODO: Revisit xcnt optimizations for gfx1250. | |
| // Wait on XCNT is redundant if we are already waiting for a load to complete. | |
| // SMEM can return out of order, so only omit XCNT wait if we are waiting till | |
| // zero. | |
| if (CheckWait.get(AMDGPU::KM_CNT) == 0 && hasPendingEvent(SMEM_GROUP)) | |
| UpdateWait.set(AMDGPU::X_CNT, ~0u); | |
| // If we have pending store we cannot optimize XCnt because we do not wait for | |
| // stores. VMEM loads retun in order, so if we only have loads XCnt is | |
| // decremented to the same number as LOADCnt. | |
| if (CheckWait.get(AMDGPU::LOAD_CNT) != ~0u && hasPendingEvent(VMEM_GROUP) && | |
| !hasPendingEvent(AMDGPU::STORE_CNT) && | |
| CheckWait.get(AMDGPU::X_CNT) >= CheckWait.get(AMDGPU::LOAD_CNT)) | |
| UpdateWait.set(AMDGPU::X_CNT, ~0u); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::X_CNT); | |
| } | |
| void WaitcntBrackets::simplifyVmVsrc(const AMDGPU::Waitcnt &CheckWait, | |
| AMDGPU::Waitcnt &UpdateWait) const { | |
| // Waiting for some counters implies waiting for VM_VSRC, since an | |
| // instruction that decrements a counter on completion would have | |
| // decremented VM_VSRC once its VGPR operands had been read. | |
| if (CheckWait.get(AMDGPU::VM_VSRC) >= | |
| std::min({CheckWait.get(AMDGPU::LOAD_CNT), | |
| CheckWait.get(AMDGPU::STORE_CNT), | |
| CheckWait.get(AMDGPU::SAMPLE_CNT), | |
| CheckWait.get(AMDGPU::BVH_CNT), CheckWait.get(AMDGPU::DS_CNT)})) | |
| UpdateWait.set(AMDGPU::VM_VSRC, ~0u); | |
| simplifyWaitcnt(UpdateWait, AMDGPU::VM_VSRC); | |
| } | |
| void WaitcntBrackets::purgeEmptyTrackingData() { | |
| for (auto &[K, V] : make_early_inc_range(VMem)) { | |
| if (V.empty()) | |
| VMem.erase(K); | |
| } | |
| for (auto &[K, V] : make_early_inc_range(SGPRs)) { | |
| if (V.empty()) | |
| SGPRs.erase(K); | |
| } | |
| } | |
| void WaitcntBrackets::determineWaitForScore(AMDGPU::InstCounterType T, | |
| unsigned ScoreToWait, | |
| AMDGPU::Waitcnt &Wait) const { | |
| const unsigned LB = getScoreLB(T); | |
| const unsigned UB = getScoreUB(T); | |
| // If the score falls within the bracket, we need a waitcnt. | |
| if ((UB >= ScoreToWait) && (ScoreToWait > LB)) { | |
| if ((T == AMDGPU::LOAD_CNT || T == AMDGPU::DS_CNT) && hasPendingFlat() && | |
| !Context->ST.hasFlatLgkmVMemCountInOrder()) { | |
| // If there is a pending FLAT operation, and this is a VMem or LGKM | |
| // waitcnt and the target can report early completion, then we need | |
| // to force a waitcnt 0. | |
| addWait(Wait, T, 0); | |
| } else if (counterOutOfOrder(T)) { | |
| // Counter can get decremented out-of-order when there | |
| // are multiple types event in the bracket. Also emit an s_wait counter | |
| // with a conservative value of 0 for the counter. | |
| addWait(Wait, T, 0); | |
| } else { | |
| // If a counter has been maxed out avoid overflow by waiting for | |
| // MAX(CounterType) - 1 instead. | |
| unsigned NeededWait = std::min( | |
| UB - ScoreToWait, getWaitCountMax(Context->getLimits(), T) - 1); | |
| addWait(Wait, T, NeededWait); | |
| } | |
| } | |
| } | |
| AMDGPU::Waitcnt WaitcntBrackets::determineAsyncWait(unsigned N) { | |
| LLVM_DEBUG({ | |
| dbgs() << "Need " << N << " async marks. Found " << AsyncMarks.size() | |
| << ":\n"; | |
| for (const auto &Mark : AsyncMarks) { | |
| llvm::interleaveComma(Mark, dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| }); | |
| if (AsyncMarks.size() == MaxAsyncMarks) { | |
| // Enforcing MaxAsyncMarks here is unnecessary work because the size of | |
| // MaxAsyncMarks is linear when traversing straightline code. But we do | |
| // need to check if truncation may have occured at a merge, and adjust N | |
| // to ensure that a wait is generated. | |
| LLVM_DEBUG(dbgs() << "Possible truncation. Ensuring a non-trivial wait.\n"); | |
| N = std::min(N, (unsigned)MaxAsyncMarks - 1); | |
| } | |
| AMDGPU::Waitcnt Wait; | |
| if (AsyncMarks.size() <= N) { | |
| LLVM_DEBUG(dbgs() << "No additional wait for async mark.\n"); | |
| return Wait; | |
| } | |
| size_t MarkIndex = AsyncMarks.size() - N - 1; | |
| const auto &RequiredMark = AsyncMarks[MarkIndex]; | |
| for (AMDGPU::InstCounterType T : AMDGPU::inst_counter_types()) | |
| determineWaitForScore(T, RequiredMark[T], Wait); | |
| // Immediately remove the waited mark and all older ones | |
| // This happens BEFORE the wait is actually inserted, which is fine | |
| // because we've already extracted the wait requirements | |
| LLVM_DEBUG({ | |
| dbgs() << "Removing " << (MarkIndex + 1) | |
| << " async marks after determining wait\n"; | |
| }); | |
| AsyncMarks.erase(AsyncMarks.begin(), AsyncMarks.begin() + MarkIndex + 1); | |
| LLVM_DEBUG(dbgs() << "Waits to add: " << Wait); | |
| return Wait; | |
| } | |
| // With D16Write32BitVgpr, D16 inst might be clobbered by events running on the | |
| // other half 16bit. | |
| // | |
| // Replace VGPR16 to VGPR32 for wait check if: | |
| // 1. MI is a VALU, and there is a wait event on the other half | |
| // 2. MI is a LdSt, and there is a wait event on the other half from different | |
| // order group | |
| MCPhysReg WaitcntBrackets::determineVGPR16Dependency(const MachineInstr &MI, | |
| AMDGPU::InstCounterType T, | |
| MCPhysReg Reg) const { | |
| const TargetRegisterClass *RC = Context->TRI.getPhysRegBaseClass(Reg); | |
| unsigned Size = Context->TRI.getRegSizeInBits(*RC); | |
| if (Size != 16 || !Context->ST.hasD16Writes32BitVgpr()) | |
| return Reg; | |
| // With D16Writes32BitVgpr, D16 Inst might clobber the whole vgpr32 | |
| // check dependency on the other half | |
| Register Reg32 = Context->TRI.get32BitRegister(Reg); | |
| Register OtherHalf = Context->TRI.getSubReg( | |
| Reg32, | |
| AMDGPU::isHi16Reg(Reg, Context->TRI) ? AMDGPU::lo16 : AMDGPU::hi16); | |
| AMDGPU::Waitcnt Wait; | |
| for (MCRegUnit RU : regunits(OtherHalf)) | |
| determineWaitForScore(T, getVMemScore(toVMEMID(RU), T), Wait); | |
| // No wait on otherhalf | |
| if (!Wait.hasWait()) | |
| return Reg; | |
| if (Context->TII.isVALU(MI)) | |
| return Reg32; | |
| // If hi/lo16 mixed events | |
| WaitEventSet MIEvents = Context->getEventsFor(MI); | |
| WaitEventSet OtherHalfEvents = Context->getWaitEvents(T); | |
| WaitEventSet Events = MIEvents & OtherHalfEvents; | |
| if (Events.twoOrMore()) | |
| return Reg32; | |
| return Reg; | |
| } | |
| void WaitcntBrackets::determineWaitForPhysReg(AMDGPU::InstCounterType T, | |
| MCPhysReg Reg, | |
| AMDGPU::Waitcnt &Wait, | |
| const MachineInstr &MI) const { | |
| if (Reg == AMDGPU::SCC) { | |
| determineWaitForScore(T, SCCScore, Wait); | |
| } else { | |
| bool IsVGPR = Context->TRI.isVectorRegister(Context->MRI, Reg); | |
| if (IsVGPR) | |
| Reg = determineVGPR16Dependency(MI, T, Reg); | |
| for (MCRegUnit RU : regunits(Reg)) | |
| determineWaitForScore( | |
| T, IsVGPR ? getVMemScore(toVMEMID(RU), T) : getSGPRScore(RU, T), | |
| Wait); | |
| } | |
| } | |
| void WaitcntBrackets::determineWaitForLDSDMA(AMDGPU::InstCounterType T, | |
| VMEMID TID, | |
| AMDGPU::Waitcnt &Wait) const { | |
| assert(TID >= LDSDMA_BEGIN && TID < LDSDMA_END); | |
| determineWaitForScore(T, getVMemScore(TID, T), Wait); | |
| } | |
| void WaitcntBrackets::tryClearSCCWriteEvent(MachineInstr *Inst) { | |
| // S_BARRIER_WAIT on the same barrier guarantees that the pending write to | |
| // SCC has landed | |
| if (PendingSCCWrite && | |
| PendingSCCWrite->getOpcode() == AMDGPU::S_BARRIER_SIGNAL_ISFIRST_IMM && | |
| PendingSCCWrite->getOperand(0).getImm() == Inst->getOperand(0).getImm()) { | |
| WaitEventSet SCC_WRITE_PendingEvent(SCC_WRITE); | |
| // If this SCC_WRITE is the only pending KM_CNT event, clear counter. | |
| if ((PendingEvents & Context->getWaitEvents(AMDGPU::KM_CNT)) == | |
| SCC_WRITE_PendingEvent) { | |
| setScoreLB(AMDGPU::KM_CNT, getScoreUB(AMDGPU::KM_CNT)); | |
| } | |
| PendingEvents.remove(SCC_WRITE_PendingEvent); | |
| PendingSCCWrite = nullptr; | |
| } | |
| } | |
| void WaitcntBrackets::applyWaitcnt(const AMDGPU::Waitcnt &Wait) { | |
| for (AMDGPU::InstCounterType T : AMDGPU::inst_counter_types()) | |
| applyWaitcnt(Wait, T); | |
| } | |
| void WaitcntBrackets::applyWaitcnt(AMDGPU::InstCounterType T, unsigned Count) { | |
| const unsigned UB = getScoreUB(T); | |
| if (Count >= UB) | |
| return; | |
| if (Count != 0) { | |
| if (counterOutOfOrder(T)) | |
| return; | |
| setScoreLB(T, std::max(getScoreLB(T), UB - Count)); | |
| } else { | |
| setScoreLB(T, UB); | |
| PendingEvents.remove(Context->getWaitEvents(T)); | |
| } | |
| if (T == AMDGPU::KM_CNT && Count == 0 && hasPendingEvent(SMEM_GROUP)) { | |
| if (!hasMixedPendingEvents(AMDGPU::X_CNT)) | |
| applyWaitcnt(AMDGPU::X_CNT, 0); | |
| else | |
| PendingEvents.remove(SMEM_GROUP); | |
| } | |
| if (T == AMDGPU::LOAD_CNT && hasPendingEvent(VMEM_GROUP) && | |
| !hasPendingEvent(AMDGPU::STORE_CNT)) { | |
| if (!hasMixedPendingEvents(AMDGPU::X_CNT)) | |
| applyWaitcnt(AMDGPU::X_CNT, Count); | |
| else if (Count == 0) | |
| PendingEvents.remove(VMEM_GROUP); | |
| } | |
| } | |
| void WaitcntBrackets::applyWaitcnt(const AMDGPU::Waitcnt &Wait, | |
| AMDGPU::InstCounterType T) { | |
| unsigned Cnt = Wait.get(T); | |
| applyWaitcnt(T, Cnt); | |
| } | |
| // Where there are multiple types of event in the bracket of a counter, | |
| // the decrement may go out of order. | |
| bool WaitcntBrackets::counterOutOfOrder(AMDGPU::InstCounterType T) const { | |
| // Scalar memory read always can go out of order. | |
| if ((T == Context->SmemAccessCounter && hasPendingEvent(SMEM_ACCESS)) || | |
| (T == AMDGPU::X_CNT && hasPendingEvent(SMEM_GROUP))) | |
| return true; | |
| // GLOBAL_INV completes in-order with other LOAD_CNT events (VMEM_ACCESS), | |
| // so having GLOBAL_INV_ACCESS mixed with other LOAD_CNT events doesn't cause | |
| // out-of-order completion. | |
| if (T == AMDGPU::LOAD_CNT) { | |
| WaitEventSet Events = PendingEvents & Context->getWaitEvents(T); | |
| // Remove GLOBAL_INV_ACCESS from the event mask before checking for mixed | |
| // events | |
| Events.remove(GLOBAL_INV_ACCESS); | |
| // Return true only if there are still multiple event types after removing | |
| // GLOBAL_INV | |
| return Events.twoOrMore(); | |
| } | |
| return hasMixedPendingEvents(T); | |
| } | |
| INITIALIZE_PASS_BEGIN(SIInsertWaitcntsLegacy, DEBUG_TYPE, "SI Insert Waitcnts", | |
| false, false) | |
| INITIALIZE_PASS_DEPENDENCY(MachineLoopInfoWrapperPass) | |
| INITIALIZE_PASS_DEPENDENCY(MachinePostDominatorTreeWrapperPass) | |
| INITIALIZE_PASS_END(SIInsertWaitcntsLegacy, DEBUG_TYPE, "SI Insert Waitcnts", | |
| false, false) | |
| char SIInsertWaitcntsLegacy::ID = 0; | |
| char &llvm::SIInsertWaitcntsID = SIInsertWaitcntsLegacy::ID; | |
| FunctionPass *llvm::createSIInsertWaitcntsPass() { | |
| return new SIInsertWaitcntsLegacy(); | |
| } | |
| static bool updateOperandIfDifferent(MachineInstr &MI, AMDGPU::OpName OpName, | |
| unsigned NewEnc) { | |
| int OpIdx = AMDGPU::getNamedOperandIdx(MI.getOpcode(), OpName); | |
| assert(OpIdx >= 0); | |
| MachineOperand &MO = MI.getOperand(OpIdx); | |
| if (NewEnc == MO.getImm()) | |
| return false; | |
| MO.setImm(NewEnc); | |
| return true; | |
| } | |
| /// Determine if \p MI is a gfx12+ single-counter S_WAIT_*CNT instruction, | |
| /// and if so, which counter it is waiting on. | |
| static std::optional<AMDGPU::InstCounterType> | |
| counterTypeForInstr(unsigned Opcode) { | |
| switch (Opcode) { | |
| case AMDGPU::S_WAIT_LOADCNT: | |
| return AMDGPU::LOAD_CNT; | |
| case AMDGPU::S_WAIT_EXPCNT: | |
| return AMDGPU::EXP_CNT; | |
| case AMDGPU::S_WAIT_STORECNT: | |
| return AMDGPU::STORE_CNT; | |
| case AMDGPU::S_WAIT_SAMPLECNT: | |
| return AMDGPU::SAMPLE_CNT; | |
| case AMDGPU::S_WAIT_BVHCNT: | |
| return AMDGPU::BVH_CNT; | |
| case AMDGPU::S_WAIT_DSCNT: | |
| return AMDGPU::DS_CNT; | |
| case AMDGPU::S_WAIT_KMCNT: | |
| return AMDGPU::KM_CNT; | |
| case AMDGPU::S_WAIT_XCNT: | |
| return AMDGPU::X_CNT; | |
| case AMDGPU::S_WAIT_ASYNCCNT: | |
| return AMDGPU::ASYNC_CNT; | |
| default: | |
| return {}; | |
| } | |
| } | |
| bool WaitcntGenerator::promoteSoftWaitCnt(MachineInstr *Waitcnt) const { | |
| unsigned Opcode = SIInstrInfo::getNonSoftWaitcntOpcode(Waitcnt->getOpcode()); | |
| if (Opcode == Waitcnt->getOpcode()) | |
| return false; | |
| Waitcnt->setDesc(TII.get(Opcode)); | |
| return true; | |
| } | |
| /// Combine consecutive S_WAITCNT and S_WAITCNT_VSCNT instructions that | |
| /// precede \p It and follow \p OldWaitcntInstr and apply any extra waits | |
| /// from \p Wait that were added by previous passes. Currently this pass | |
| /// conservatively assumes that these preexisting waits are required for | |
| /// correctness. | |
| bool WaitcntGeneratorPreGFX12::applyPreexistingWaitcnt( | |
| WaitcntBrackets &ScoreBrackets, MachineInstr &OldWaitcntInstr, | |
| AMDGPU::Waitcnt &Wait, MachineBasicBlock::instr_iterator It) const { | |
| assert(isNormalMode(MaxCounter)); | |
| bool Modified = false; | |
| MachineInstr *WaitcntInstr = nullptr; | |
| MachineInstr *WaitcntVsCntInstr = nullptr; | |
| LLVM_DEBUG({ | |
| dbgs() << "PreGFX12::applyPreexistingWaitcnt at: "; | |
| if (It.isEnd()) | |
| dbgs() << "end of block\n"; | |
| else | |
| dbgs() << *It; | |
| }); | |
| for (auto &II : | |
| make_early_inc_range(make_range(OldWaitcntInstr.getIterator(), It))) { | |
| LLVM_DEBUG(dbgs() << "pre-existing iter: " << II); | |
| if (isNonWaitcntMetaInst(II)) { | |
| LLVM_DEBUG(dbgs() << "skipped meta instruction\n"); | |
| continue; | |
| } | |
| unsigned Opcode = SIInstrInfo::getNonSoftWaitcntOpcode(II.getOpcode()); | |
| bool TrySimplify = Opcode != II.getOpcode() && !OptNone; | |
| // Update required wait count. If this is a soft waitcnt (= it was added | |
| // by an earlier pass), it may be entirely removed. | |
| if (Opcode == AMDGPU::S_WAITCNT) { | |
| unsigned IEnc = II.getOperand(0).getImm(); | |
| AMDGPU::Waitcnt OldWait = AMDGPU::decodeWaitcnt(IV, IEnc); | |
| if (TrySimplify) | |
| ScoreBrackets.simplifyWaitcnt(OldWait); | |
| Wait = Wait.combined(OldWait); | |
| // Merge consecutive waitcnt of the same type by erasing multiples. | |
| if (WaitcntInstr || (!Wait.hasWaitExceptStoreCnt() && TrySimplify)) { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } else | |
| WaitcntInstr = &II; | |
| } else if (Opcode == AMDGPU::S_WAITCNT_lds_direct) { | |
| assert(ST.hasVMemToLDSLoad()); | |
| LLVM_DEBUG(dbgs() << "Processing S_WAITCNT_lds_direct: " << II | |
| << "Before: " << Wait << '\n';); | |
| ScoreBrackets.determineWaitForLDSDMA(AMDGPU::LOAD_CNT, LDSDMA_BEGIN, | |
| Wait); | |
| LLVM_DEBUG(dbgs() << "After: " << Wait << '\n';); | |
| // It is possible (but unlikely) that this is the only wait instruction, | |
| // in which case, we exit this loop without a WaitcntInstr to consume | |
| // `Wait`. But that works because `Wait` was passed in by reference, and | |
| // the callee eventually calls createNewWaitcnt on it. We test this | |
| // possibility in an articial MIR test since such a situation cannot be | |
| // recreated by running the memory legalizer. | |
| II.eraseFromParent(); | |
| } else if (Opcode == AMDGPU::WAIT_ASYNCMARK) { | |
| unsigned N = II.getOperand(0).getImm(); | |
| LLVM_DEBUG(dbgs() << "Processing WAIT_ASYNCMARK: " << II << '\n';); | |
| AMDGPU::Waitcnt OldWait = ScoreBrackets.determineAsyncWait(N); | |
| Wait = Wait.combined(OldWait); | |
| } else { | |
| assert(Opcode == AMDGPU::S_WAITCNT_VSCNT); | |
| assert(II.getOperand(0).getReg() == AMDGPU::SGPR_NULL); | |
| unsigned OldVSCnt = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| if (TrySimplify) | |
| ScoreBrackets.simplifyWaitcnt(AMDGPU::STORE_CNT, OldVSCnt); | |
| Wait.set(AMDGPU::STORE_CNT, | |
| std::min(Wait.get(AMDGPU::STORE_CNT), OldVSCnt)); | |
| if (WaitcntVsCntInstr || (!Wait.hasWaitStoreCnt() && TrySimplify)) { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } else | |
| WaitcntVsCntInstr = &II; | |
| } | |
| } | |
| if (WaitcntInstr) { | |
| Modified |= updateOperandIfDifferent(*WaitcntInstr, AMDGPU::OpName::simm16, | |
| AMDGPU::encodeWaitcnt(IV, Wait)); | |
| Modified |= promoteSoftWaitCnt(WaitcntInstr); | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::LOAD_CNT); | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::EXP_CNT); | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::DS_CNT); | |
| Wait.set(AMDGPU::LOAD_CNT, ~0u); | |
| Wait.set(AMDGPU::EXP_CNT, ~0u); | |
| Wait.set(AMDGPU::DS_CNT, ~0u); | |
| LLVM_DEBUG(It.isEnd() ? dbgs() << "applied pre-existing waitcnt\n" | |
| << "New Instr at block end: " | |
| << *WaitcntInstr << '\n' | |
| : dbgs() << "applied pre-existing waitcnt\n" | |
| << "Old Instr: " << *It | |
| << "New Instr: " << *WaitcntInstr << '\n'); | |
| } | |
| if (WaitcntVsCntInstr) { | |
| Modified |= | |
| updateOperandIfDifferent(*WaitcntVsCntInstr, AMDGPU::OpName::simm16, | |
| Wait.get(AMDGPU::STORE_CNT)); | |
| Modified |= promoteSoftWaitCnt(WaitcntVsCntInstr); | |
| ScoreBrackets.applyWaitcnt(AMDGPU::STORE_CNT, Wait.get(AMDGPU::STORE_CNT)); | |
| Wait.set(AMDGPU::STORE_CNT, ~0u); | |
| LLVM_DEBUG(It.isEnd() | |
| ? dbgs() << "applied pre-existing waitcnt\n" | |
| << "New Instr at block end: " << *WaitcntVsCntInstr | |
| << '\n' | |
| : dbgs() << "applied pre-existing waitcnt\n" | |
| << "Old Instr: " << *It | |
| << "New Instr: " << *WaitcntVsCntInstr << '\n'); | |
| } | |
| return Modified; | |
| } | |
| /// Generate S_WAITCNT and/or S_WAITCNT_VSCNT instructions for any | |
| /// required counters in \p Wait | |
| bool WaitcntGeneratorPreGFX12::createNewWaitcnt( | |
| MachineBasicBlock &Block, MachineBasicBlock::instr_iterator It, | |
| AMDGPU::Waitcnt Wait, const WaitcntBrackets &ScoreBrackets) { | |
| assert(isNormalMode(MaxCounter)); | |
| bool Modified = false; | |
| const DebugLoc &DL = Block.findDebugLoc(It); | |
| // Helper to emit expanded waitcnt sequence for profiling. | |
| // Emits waitcnts from (Outstanding-1) down to Target. | |
| // The EmitWaitcnt callback emits a single waitcnt. | |
| auto EmitExpandedWaitcnt = [&](unsigned Outstanding, unsigned Target, | |
| auto EmitWaitcnt) { | |
| do { | |
| EmitWaitcnt(--Outstanding); | |
| } while (Outstanding > Target); | |
| Modified = true; | |
| }; | |
| // Waits for VMcnt, LKGMcnt and/or EXPcnt are encoded together into a | |
| // single instruction while VScnt has its own instruction. | |
| if (Wait.hasWaitExceptStoreCnt()) { | |
| // If profiling expansion is enabled, emit an expanded sequence | |
| if (ExpandWaitcntProfiling) { | |
| // Check if any of the counters to be waited on are out-of-order. | |
| // If so, fall back to normal (non-expanded) behavior since expansion | |
| // would provide misleading profiling information. | |
| bool AnyOutOfOrder = false; | |
| for (auto CT : {AMDGPU::LOAD_CNT, AMDGPU::DS_CNT, AMDGPU::EXP_CNT}) { | |
| unsigned WaitCnt = Wait.get(CT); | |
| if (WaitCnt != ~0u && ScoreBrackets.counterOutOfOrder(CT)) { | |
| AnyOutOfOrder = true; | |
| break; | |
| } | |
| } | |
| if (AnyOutOfOrder) { | |
| // Fall back to non-expanded wait | |
| unsigned Enc = AMDGPU::encodeWaitcnt(IV, Wait); | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT)).addImm(Enc); | |
| Modified = true; | |
| } else { | |
| // All counters are in-order, safe to expand | |
| for (auto CT : {AMDGPU::LOAD_CNT, AMDGPU::DS_CNT, AMDGPU::EXP_CNT}) { | |
| unsigned WaitCnt = Wait.get(CT); | |
| if (WaitCnt == ~0u) | |
| continue; | |
| unsigned Outstanding = std::min(ScoreBrackets.getOutstanding(CT), | |
| getWaitCountMax(getLimits(), CT) - 1); | |
| EmitExpandedWaitcnt(Outstanding, WaitCnt, [&](unsigned Count) { | |
| AMDGPU::Waitcnt W; | |
| W.set(CT, Count); | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT)) | |
| .addImm(AMDGPU::encodeWaitcnt(IV, W)); | |
| }); | |
| } | |
| } | |
| } else { | |
| // Normal behavior: emit single combined waitcnt | |
| unsigned Enc = AMDGPU::encodeWaitcnt(IV, Wait); | |
| [[maybe_unused]] auto SWaitInst = | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT)).addImm(Enc); | |
| Modified = true; | |
| LLVM_DEBUG(dbgs() << "PreGFX12::createNewWaitcnt\n"; | |
| if (It != Block.instr_end()) dbgs() << "Old Instr: " << *It; | |
| dbgs() << "New Instr: " << *SWaitInst << '\n'); | |
| } | |
| } | |
| if (Wait.hasWaitStoreCnt()) { | |
| assert(ST.hasVscnt()); | |
| if (ExpandWaitcntProfiling && Wait.get(AMDGPU::STORE_CNT) != ~0u && | |
| !ScoreBrackets.counterOutOfOrder(AMDGPU::STORE_CNT)) { | |
| // Only expand if counter is not out-of-order | |
| unsigned Outstanding = | |
| std::min(ScoreBrackets.getOutstanding(AMDGPU::STORE_CNT), | |
| getWaitCountMax(getLimits(), AMDGPU::STORE_CNT) - 1); | |
| EmitExpandedWaitcnt( | |
| Outstanding, Wait.get(AMDGPU::STORE_CNT), [&](unsigned Count) { | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT_VSCNT)) | |
| .addReg(AMDGPU::SGPR_NULL, RegState::Undef) | |
| .addImm(Count); | |
| }); | |
| } else { | |
| [[maybe_unused]] auto SWaitInst = | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT_VSCNT)) | |
| .addReg(AMDGPU::SGPR_NULL, RegState::Undef) | |
| .addImm(Wait.get(AMDGPU::STORE_CNT)); | |
| Modified = true; | |
| LLVM_DEBUG(dbgs() << "PreGFX12::createNewWaitcnt\n"; | |
| if (It != Block.instr_end()) dbgs() << "Old Instr: " << *It; | |
| dbgs() << "New Instr: " << *SWaitInst << '\n'); | |
| } | |
| } | |
| return Modified; | |
| } | |
| AMDGPU::Waitcnt | |
| WaitcntGeneratorPreGFX12::getAllZeroWaitcnt(bool IncludeVSCnt) const { | |
| return AMDGPU::Waitcnt(0, 0, 0, IncludeVSCnt && ST.hasVscnt() ? 0 : ~0u); | |
| } | |
| AMDGPU::Waitcnt | |
| WaitcntGeneratorGFX12Plus::getAllZeroWaitcnt(bool IncludeVSCnt) const { | |
| unsigned ExpertVal = IsExpertMode ? 0 : ~0u; | |
| return AMDGPU::Waitcnt(0, 0, 0, IncludeVSCnt ? 0 : ~0u, 0, 0, 0, | |
| ~0u /* XCNT */, ~0u /* ASYNC_CNT */, ExpertVal, | |
| ExpertVal); | |
| } | |
| /// Combine consecutive S_WAIT_*CNT instructions that precede \p It and | |
| /// follow \p OldWaitcntInstr and apply any extra waits from \p Wait that | |
| /// were added by previous passes. Currently this pass conservatively | |
| /// assumes that these preexisting waits are required for correctness. | |
| bool WaitcntGeneratorGFX12Plus::applyPreexistingWaitcnt( | |
| WaitcntBrackets &ScoreBrackets, MachineInstr &OldWaitcntInstr, | |
| AMDGPU::Waitcnt &Wait, MachineBasicBlock::instr_iterator It) const { | |
| assert(!isNormalMode(MaxCounter)); | |
| bool Modified = false; | |
| MachineInstr *CombinedLoadDsCntInstr = nullptr; | |
| MachineInstr *CombinedStoreDsCntInstr = nullptr; | |
| MachineInstr *WaitcntDepctrInstr = nullptr; | |
| MachineInstr *WaitInstrs[AMDGPU::NUM_EXTENDED_INST_CNTS] = {}; | |
| LLVM_DEBUG({ | |
| dbgs() << "GFX12Plus::applyPreexistingWaitcnt at: "; | |
| if (It.isEnd()) | |
| dbgs() << "end of block\n"; | |
| else | |
| dbgs() << *It; | |
| }); | |
| // Accumulate waits that should not be simplified. | |
| AMDGPU::Waitcnt RequiredWait; | |
| for (auto &II : | |
| make_early_inc_range(make_range(OldWaitcntInstr.getIterator(), It))) { | |
| LLVM_DEBUG(dbgs() << "pre-existing iter: " << II); | |
| if (isNonWaitcntMetaInst(II)) { | |
| LLVM_DEBUG(dbgs() << "skipped meta instruction\n"); | |
| continue; | |
| } | |
| // Update required wait count. If this is a soft waitcnt (= it was added | |
| // by an earlier pass), it may be entirely removed. | |
| unsigned Opcode = SIInstrInfo::getNonSoftWaitcntOpcode(II.getOpcode()); | |
| bool TrySimplify = Opcode != II.getOpcode() && !OptNone; | |
| // Don't crash if the programmer used legacy waitcnt intrinsics, but don't | |
| // attempt to do more than that either. | |
| if (Opcode == AMDGPU::S_WAITCNT) | |
| continue; | |
| if (Opcode == AMDGPU::S_WAIT_LOADCNT_DSCNT) { | |
| unsigned OldEnc = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| AMDGPU::Waitcnt OldWait = AMDGPU::decodeLoadcntDscnt(IV, OldEnc); | |
| if (TrySimplify) | |
| Wait = Wait.combined(OldWait); | |
| else | |
| RequiredWait = RequiredWait.combined(OldWait); | |
| // Keep the first wait_loadcnt, erase the rest. | |
| if (CombinedLoadDsCntInstr == nullptr) { | |
| CombinedLoadDsCntInstr = &II; | |
| } else { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } | |
| } else if (Opcode == AMDGPU::S_WAIT_STORECNT_DSCNT) { | |
| unsigned OldEnc = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| AMDGPU::Waitcnt OldWait = AMDGPU::decodeStorecntDscnt(IV, OldEnc); | |
| if (TrySimplify) | |
| Wait = Wait.combined(OldWait); | |
| else | |
| RequiredWait = RequiredWait.combined(OldWait); | |
| // Keep the first wait_storecnt, erase the rest. | |
| if (CombinedStoreDsCntInstr == nullptr) { | |
| CombinedStoreDsCntInstr = &II; | |
| } else { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } | |
| } else if (Opcode == AMDGPU::S_WAITCNT_DEPCTR) { | |
| unsigned OldEnc = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| AMDGPU::Waitcnt OldWait; | |
| OldWait.set(AMDGPU::VA_VDST, AMDGPU::DepCtr::decodeFieldVaVdst(OldEnc)); | |
| OldWait.set(AMDGPU::VM_VSRC, AMDGPU::DepCtr::decodeFieldVmVsrc(OldEnc)); | |
| if (TrySimplify) | |
| ScoreBrackets.simplifyWaitcnt(OldWait); | |
| Wait = Wait.combined(OldWait); | |
| if (WaitcntDepctrInstr == nullptr) { | |
| WaitcntDepctrInstr = &II; | |
| } else { | |
| // S_WAITCNT_DEPCTR requires special care. Don't remove a | |
| // duplicate if it is waiting on things other than VA_VDST or | |
| // VM_VSRC. If that is the case, just make sure the VA_VDST and | |
| // VM_VSRC subfields of the operand are set to the "no wait" | |
| // values. | |
| unsigned Enc = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| Enc = AMDGPU::DepCtr::encodeFieldVmVsrc(Enc, ~0u); | |
| Enc = AMDGPU::DepCtr::encodeFieldVaVdst(Enc, ~0u); | |
| if (Enc != (unsigned)AMDGPU::DepCtr::getDefaultDepCtrEncoding(ST)) { | |
| Modified |= updateOperandIfDifferent(II, AMDGPU::OpName::simm16, Enc); | |
| Modified |= promoteSoftWaitCnt(&II); | |
| } else { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| } else if (Opcode == AMDGPU::S_WAITCNT_lds_direct) { | |
| // Architectures higher than GFX10 do not have direct loads to | |
| // LDS, so no work required here yet. | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } else if (Opcode == AMDGPU::WAIT_ASYNCMARK) { | |
| // Update the Waitcnt, but don't erase the wait.asyncmark() itself. It | |
| // shows up in the assembly as a comment with the original parameter N. | |
| unsigned N = II.getOperand(0).getImm(); | |
| AMDGPU::Waitcnt OldWait = ScoreBrackets.determineAsyncWait(N); | |
| Wait = Wait.combined(OldWait); | |
| } else { | |
| std::optional<AMDGPU::InstCounterType> CT = counterTypeForInstr(Opcode); | |
| assert(CT.has_value()); | |
| unsigned OldCnt = | |
| TII.getNamedOperand(II, AMDGPU::OpName::simm16)->getImm(); | |
| if (TrySimplify) | |
| addWait(Wait, CT.value(), OldCnt); | |
| else | |
| addWait(RequiredWait, CT.value(), OldCnt); | |
| // Keep the first wait of its kind, erase the rest. | |
| if (WaitInstrs[CT.value()] == nullptr) { | |
| WaitInstrs[CT.value()] = &II; | |
| } else { | |
| II.eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| } | |
| ScoreBrackets.simplifyWaitcnt(Wait.combined(RequiredWait), Wait); | |
| Wait = Wait.combined(RequiredWait); | |
| if (CombinedLoadDsCntInstr) { | |
| // Only keep an S_WAIT_LOADCNT_DSCNT if both counters actually need | |
| // to be waited for. Otherwise, let the instruction be deleted so | |
| // the appropriate single counter wait instruction can be inserted | |
| // instead, when new S_WAIT_*CNT instructions are inserted by | |
| // createNewWaitcnt(). As a side effect, resetting the wait counts will | |
| // cause any redundant S_WAIT_LOADCNT or S_WAIT_DSCNT to be removed by | |
| // the loop below that deals with single counter instructions. | |
| // | |
| // A wait for LOAD_CNT or DS_CNT implies a wait for VM_VSRC, since | |
| // instructions that have decremented LOAD_CNT or DS_CNT on completion | |
| // will have needed to wait for their register sources to be available | |
| // first. | |
| if (Wait.get(AMDGPU::LOAD_CNT) != ~0u && Wait.get(AMDGPU::DS_CNT) != ~0u) { | |
| unsigned NewEnc = AMDGPU::encodeLoadcntDscnt(IV, Wait); | |
| Modified |= updateOperandIfDifferent(*CombinedLoadDsCntInstr, | |
| AMDGPU::OpName::simm16, NewEnc); | |
| Modified |= promoteSoftWaitCnt(CombinedLoadDsCntInstr); | |
| ScoreBrackets.applyWaitcnt(AMDGPU::LOAD_CNT, Wait.get(AMDGPU::LOAD_CNT)); | |
| ScoreBrackets.applyWaitcnt(AMDGPU::DS_CNT, Wait.get(AMDGPU::DS_CNT)); | |
| Wait.set(AMDGPU::LOAD_CNT, ~0u); | |
| Wait.set(AMDGPU::DS_CNT, ~0u); | |
| LLVM_DEBUG(It.isEnd() ? dbgs() << "applied pre-existing waitcnt\n" | |
| << "New Instr at block end: " | |
| << *CombinedLoadDsCntInstr << '\n' | |
| : dbgs() << "applied pre-existing waitcnt\n" | |
| << "Old Instr: " << *It << "New Instr: " | |
| << *CombinedLoadDsCntInstr << '\n'); | |
| } else { | |
| CombinedLoadDsCntInstr->eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| if (CombinedStoreDsCntInstr) { | |
| // Similarly for S_WAIT_STORECNT_DSCNT. | |
| if (Wait.get(AMDGPU::STORE_CNT) != ~0u && Wait.get(AMDGPU::DS_CNT) != ~0u) { | |
| unsigned NewEnc = AMDGPU::encodeStorecntDscnt(IV, Wait); | |
| Modified |= updateOperandIfDifferent(*CombinedStoreDsCntInstr, | |
| AMDGPU::OpName::simm16, NewEnc); | |
| Modified |= promoteSoftWaitCnt(CombinedStoreDsCntInstr); | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::STORE_CNT); | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::DS_CNT); | |
| Wait.set(AMDGPU::STORE_CNT, ~0u); | |
| Wait.set(AMDGPU::DS_CNT, ~0u); | |
| LLVM_DEBUG(It.isEnd() ? dbgs() << "applied pre-existing waitcnt\n" | |
| << "New Instr at block end: " | |
| << *CombinedStoreDsCntInstr << '\n' | |
| : dbgs() << "applied pre-existing waitcnt\n" | |
| << "Old Instr: " << *It << "New Instr: " | |
| << *CombinedStoreDsCntInstr << '\n'); | |
| } else { | |
| CombinedStoreDsCntInstr->eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| // Look for an opportunity to convert existing S_WAIT_LOADCNT, | |
| // S_WAIT_STORECNT and S_WAIT_DSCNT into new S_WAIT_LOADCNT_DSCNT | |
| // or S_WAIT_STORECNT_DSCNT. This is achieved by selectively removing | |
| // instructions so that createNewWaitcnt() will create new combined | |
| // instructions to replace them. | |
| if (Wait.get(AMDGPU::DS_CNT) != ~0u) { | |
| // This is a vector of addresses in WaitInstrs pointing to instructions | |
| // that should be removed if they are present. | |
| SmallVector<MachineInstr **, 2> WaitsToErase; | |
| // If it's known that both DScnt and either LOADcnt or STOREcnt (but not | |
| // both) need to be waited for, ensure that there are no existing | |
| // individual wait count instructions for these. | |
| if (Wait.get(AMDGPU::LOAD_CNT) != ~0u) { | |
| WaitsToErase.push_back(&WaitInstrs[AMDGPU::LOAD_CNT]); | |
| WaitsToErase.push_back(&WaitInstrs[AMDGPU::DS_CNT]); | |
| } else if (Wait.get(AMDGPU::STORE_CNT) != ~0u) { | |
| WaitsToErase.push_back(&WaitInstrs[AMDGPU::STORE_CNT]); | |
| WaitsToErase.push_back(&WaitInstrs[AMDGPU::DS_CNT]); | |
| } | |
| for (MachineInstr **WI : WaitsToErase) { | |
| if (!*WI) | |
| continue; | |
| (*WI)->eraseFromParent(); | |
| *WI = nullptr; | |
| Modified = true; | |
| } | |
| } | |
| for (auto CT : inst_counter_types(AMDGPU::NUM_EXTENDED_INST_CNTS)) { | |
| if (!WaitInstrs[CT]) | |
| continue; | |
| unsigned NewCnt = Wait.get(CT); | |
| if (NewCnt != ~0u) { | |
| Modified |= updateOperandIfDifferent(*WaitInstrs[CT], | |
| AMDGPU::OpName::simm16, NewCnt); | |
| Modified |= promoteSoftWaitCnt(WaitInstrs[CT]); | |
| ScoreBrackets.applyWaitcnt(CT, NewCnt); | |
| setNoWait(Wait, CT); | |
| LLVM_DEBUG(It.isEnd() | |
| ? dbgs() << "applied pre-existing waitcnt\n" | |
| << "New Instr at block end: " << *WaitInstrs[CT] | |
| << '\n' | |
| : dbgs() << "applied pre-existing waitcnt\n" | |
| << "Old Instr: " << *It | |
| << "New Instr: " << *WaitInstrs[CT] << '\n'); | |
| } else { | |
| WaitInstrs[CT]->eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| if (WaitcntDepctrInstr) { | |
| // Get the encoded Depctr immediate and override the VA_VDST and VM_VSRC | |
| // subfields with the new required values. | |
| unsigned Enc = | |
| TII.getNamedOperand(*WaitcntDepctrInstr, AMDGPU::OpName::simm16) | |
| ->getImm(); | |
| Enc = AMDGPU::DepCtr::encodeFieldVmVsrc(Enc, Wait.get(AMDGPU::VM_VSRC)); | |
| Enc = AMDGPU::DepCtr::encodeFieldVaVdst(Enc, Wait.get(AMDGPU::VA_VDST)); | |
| ScoreBrackets.applyWaitcnt(AMDGPU::VA_VDST, Wait.get(AMDGPU::VA_VDST)); | |
| ScoreBrackets.applyWaitcnt(AMDGPU::VM_VSRC, Wait.get(AMDGPU::VM_VSRC)); | |
| Wait.set(AMDGPU::VA_VDST, ~0u); | |
| Wait.set(AMDGPU::VM_VSRC, ~0u); | |
| // If that new encoded Depctr immediate would actually still wait | |
| // for anything, update the instruction's operand. Otherwise it can | |
| // just be deleted. | |
| if (Enc != (unsigned)AMDGPU::DepCtr::getDefaultDepCtrEncoding(ST)) { | |
| Modified |= updateOperandIfDifferent(*WaitcntDepctrInstr, | |
| AMDGPU::OpName::simm16, Enc); | |
| LLVM_DEBUG(It.isEnd() ? dbgs() << "applyPreexistingWaitcnt\n" | |
| << "New Instr at block end: " | |
| << *WaitcntDepctrInstr << '\n' | |
| : dbgs() << "applyPreexistingWaitcnt\n" | |
| << "Old Instr: " << *It << "New Instr: " | |
| << *WaitcntDepctrInstr << '\n'); | |
| } else { | |
| WaitcntDepctrInstr->eraseFromParent(); | |
| Modified = true; | |
| } | |
| } | |
| return Modified; | |
| } | |
| /// Generate S_WAIT_*CNT instructions for any required counters in \p Wait | |
| bool WaitcntGeneratorGFX12Plus::createNewWaitcnt( | |
| MachineBasicBlock &Block, MachineBasicBlock::instr_iterator It, | |
| AMDGPU::Waitcnt Wait, const WaitcntBrackets &ScoreBrackets) { | |
| assert(!isNormalMode(MaxCounter)); | |
| bool Modified = false; | |
| const DebugLoc &DL = Block.findDebugLoc(It); | |
| // Helper to emit expanded waitcnt sequence for profiling. | |
| auto EmitExpandedWaitcnt = [&](unsigned Outstanding, unsigned Target, | |
| auto EmitWaitcnt) { | |
| for (unsigned I = Outstanding - 1; I > Target && I != ~0u; --I) | |
| EmitWaitcnt(I); | |
| EmitWaitcnt(Target); | |
| Modified = true; | |
| }; | |
| // For GFX12+, we use separate wait instructions, which makes expansion | |
| // simpler | |
| if (ExpandWaitcntProfiling) { | |
| for (auto CT : inst_counter_types(AMDGPU::NUM_EXTENDED_INST_CNTS)) { | |
| unsigned Count = Wait.get(CT); | |
| if (Count == ~0u) | |
| continue; | |
| // Skip expansion for out-of-order counters - emit normal wait instead | |
| if (ScoreBrackets.counterOutOfOrder(CT)) { | |
| BuildMI(Block, It, DL, TII.get(instrsForExtendedCounterTypes[CT])) | |
| .addImm(Count); | |
| Modified = true; | |
| continue; | |
| } | |
| unsigned Outstanding = std::min(ScoreBrackets.getOutstanding(CT), | |
| getWaitCountMax(getLimits(), CT) - 1); | |
| EmitExpandedWaitcnt(Outstanding, Count, [&](unsigned Val) { | |
| BuildMI(Block, It, DL, TII.get(instrsForExtendedCounterTypes[CT])) | |
| .addImm(Val); | |
| }); | |
| } | |
| return Modified; | |
| } | |
| // Normal behavior (no expansion) | |
| // Check for opportunities to use combined wait instructions. | |
| if (Wait.get(AMDGPU::DS_CNT) != ~0u) { | |
| MachineInstr *SWaitInst = nullptr; | |
| if (Wait.get(AMDGPU::LOAD_CNT) != ~0u) { | |
| unsigned Enc = AMDGPU::encodeLoadcntDscnt(IV, Wait); | |
| SWaitInst = BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAIT_LOADCNT_DSCNT)) | |
| .addImm(Enc); | |
| Wait.set(AMDGPU::LOAD_CNT, ~0u); | |
| Wait.set(AMDGPU::DS_CNT, ~0u); | |
| } else if (Wait.get(AMDGPU::STORE_CNT) != ~0u) { | |
| unsigned Enc = AMDGPU::encodeStorecntDscnt(IV, Wait); | |
| SWaitInst = BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAIT_STORECNT_DSCNT)) | |
| .addImm(Enc); | |
| Wait.set(AMDGPU::STORE_CNT, ~0u); | |
| Wait.set(AMDGPU::DS_CNT, ~0u); | |
| } | |
| if (SWaitInst) { | |
| Modified = true; | |
| LLVM_DEBUG(dbgs() << "GFX12Plus::createNewWaitcnt\n"; | |
| if (It != Block.instr_end()) dbgs() << "Old Instr: " << *It; | |
| dbgs() << "New Instr: " << *SWaitInst << '\n'); | |
| } | |
| } | |
| // Generate an instruction for any remaining counter that needs | |
| // waiting for. | |
| for (auto CT : inst_counter_types(AMDGPU::NUM_EXTENDED_INST_CNTS)) { | |
| unsigned Count = Wait.get(CT); | |
| if (Count == ~0u) | |
| continue; | |
| [[maybe_unused]] auto SWaitInst = | |
| BuildMI(Block, It, DL, TII.get(instrsForExtendedCounterTypes[CT])) | |
| .addImm(Count); | |
| Modified = true; | |
| LLVM_DEBUG(dbgs() << "GFX12Plus::createNewWaitcnt\n"; | |
| if (It != Block.instr_end()) dbgs() << "Old Instr: " << *It; | |
| dbgs() << "New Instr: " << *SWaitInst << '\n'); | |
| } | |
| if (Wait.hasWaitDepctr()) { | |
| assert(IsExpertMode); | |
| unsigned Enc = | |
| AMDGPU::DepCtr::encodeFieldVmVsrc(Wait.get(AMDGPU::VM_VSRC), ST); | |
| Enc = AMDGPU::DepCtr::encodeFieldVaVdst(Enc, Wait.get(AMDGPU::VA_VDST)); | |
| [[maybe_unused]] auto SWaitInst = | |
| BuildMI(Block, It, DL, TII.get(AMDGPU::S_WAITCNT_DEPCTR)).addImm(Enc); | |
| Modified = true; | |
| LLVM_DEBUG(dbgs() << "generateWaitcnt\n"; | |
| if (It != Block.instr_end()) dbgs() << "Old Instr: " << *It; | |
| dbgs() << "New Instr: " << *SWaitInst << '\n'); | |
| } | |
| return Modified; | |
| } | |
| /// Generate s_waitcnt instruction to be placed before cur_Inst. | |
| /// Instructions of a given type are returned in order, | |
| /// but instructions of different types can complete out of order. | |
| /// We rely on this in-order completion | |
| /// and simply assign a score to the memory access instructions. | |
| /// We keep track of the active "score bracket" to determine | |
| /// if an access of a memory read requires an s_waitcnt | |
| /// and if so what the value of each counter is. | |
| /// The "score bracket" is bound by the lower bound and upper bound | |
| /// scores (*_score_LB and *_score_ub respectively). | |
| /// If FlushFlags.FlushVmCnt is true, we want to flush the vmcnt counter here. | |
| /// If FlushFlags.FlushDsCnt is true, we want to flush the dscnt counter here | |
| /// (GFX12+ only, where DS_CNT is a separate counter). | |
| bool SIInsertWaitcnts::generateWaitcntInstBefore( | |
| MachineInstr &MI, WaitcntBrackets &ScoreBrackets, | |
| MachineInstr *OldWaitcntInstr, PreheaderFlushFlags FlushFlags) { | |
| LLVM_DEBUG(dbgs() << "\n*** GenerateWaitcntInstBefore: "; MI.print(dbgs());); | |
| setForceEmitWaitcnt(); | |
| assert(!isNonWaitcntMetaInst(MI)); | |
| AMDGPU::Waitcnt Wait; | |
| const unsigned Opc = MI.getOpcode(); | |
| switch (Opc) { | |
| case AMDGPU::BUFFER_WBINVL1: | |
| case AMDGPU::BUFFER_WBINVL1_SC: | |
| case AMDGPU::BUFFER_WBINVL1_VOL: | |
| case AMDGPU::BUFFER_GL0_INV: | |
| case AMDGPU::BUFFER_GL1_INV: { | |
| // FIXME: This should have already been handled by the memory legalizer. | |
| // Removing this currently doesn't affect any lit tests, but we need to | |
| // verify that nothing was relying on this. The number of buffer invalidates | |
| // being handled here should not be expanded. | |
| Wait.set(AMDGPU::LOAD_CNT, 0); | |
| break; | |
| } | |
| case AMDGPU::SI_RETURN_TO_EPILOG: | |
| case AMDGPU::SI_RETURN: | |
| case AMDGPU::SI_WHOLE_WAVE_FUNC_RETURN: | |
| case AMDGPU::S_SETPC_B64_return: { | |
| // All waits must be resolved at call return. | |
| // NOTE: this could be improved with knowledge of all call sites or | |
| // with knowledge of the called routines. | |
| ReturnInsts.insert(&MI); | |
| AMDGPU::Waitcnt AllZeroWait = | |
| WCG->getAllZeroWaitcnt(/*IncludeVSCnt=*/false); | |
| // On GFX12+, if LOAD_CNT is pending but no VGPRs are waiting for loads | |
| // (e.g., only GLOBAL_INV is pending), we can skip waiting on loadcnt. | |
| // GLOBAL_INV increments loadcnt but doesn't write to VGPRs, so there's | |
| // no need to wait for it at function boundaries. | |
| if (ST.hasExtendedWaitCounts() && | |
| !ScoreBrackets.hasPendingEvent(VMEM_ACCESS)) | |
| AllZeroWait.set(AMDGPU::LOAD_CNT, ~0u); | |
| Wait = AllZeroWait; | |
| break; | |
| } | |
| case AMDGPU::S_ENDPGM: | |
| case AMDGPU::S_ENDPGM_SAVED: { | |
| // In dynamic VGPR mode, we want to release the VGPRs before the wave exits. | |
| // Technically the hardware will do this on its own if we don't, but that | |
| // might cost extra cycles compared to doing it explicitly. | |
| // When not in dynamic VGPR mode, identify S_ENDPGM instructions which may | |
| // have to wait for outstanding VMEM stores. In this case it can be useful | |
| // to send a message to explicitly release all VGPRs before the stores have | |
| // completed, but it is only safe to do this if there are no outstanding | |
| // scratch stores. | |
| EndPgmInsts[&MI] = !ScoreBrackets.empty(AMDGPU::STORE_CNT) && | |
| !ScoreBrackets.hasPendingEvent(SCRATCH_WRITE_ACCESS); | |
| break; | |
| } | |
| case AMDGPU::S_SENDMSG: | |
| case AMDGPU::S_SENDMSGHALT: { | |
| if (ST.hasLegacyGeometry() && | |
| ((MI.getOperand(0).getImm() & AMDGPU::SendMsg::ID_MASK_PreGFX11_) == | |
| AMDGPU::SendMsg::ID_GS_DONE_PreGFX11)) { | |
| // Resolve vm waits before gs-done. | |
| Wait.set(AMDGPU::LOAD_CNT, 0); | |
| break; | |
| } | |
| [[fallthrough]]; | |
| } | |
| default: { | |
| // Export & GDS instructions do not read the EXEC mask until after the | |
| // export is granted (which can occur well after the instruction is issued). | |
| // The shader program must flush all EXP operations on the export-count | |
| // before overwriting the EXEC mask. | |
| if (MI.modifiesRegister(AMDGPU::EXEC, &TRI)) { | |
| // Export and GDS are tracked individually, either may trigger a waitcnt | |
| // for EXEC. | |
| if (ScoreBrackets.hasPendingEvent(EXP_GPR_LOCK) || | |
| ScoreBrackets.hasPendingEvent(EXP_PARAM_ACCESS) || | |
| ScoreBrackets.hasPendingEvent(EXP_POS_ACCESS) || | |
| ScoreBrackets.hasPendingEvent(GDS_GPR_LOCK)) { | |
| Wait.set(AMDGPU::EXP_CNT, 0); | |
| } | |
| } | |
| // Wait for any pending GDS instruction to complete before any | |
| // "Always GDS" instruction. | |
| if (TII.isAlwaysGDS(Opc) && ScoreBrackets.hasPendingGDS()) | |
| addWait(Wait, AMDGPU::DS_CNT, ScoreBrackets.getPendingGDSWait()); | |
| if (MI.isCall()) { | |
| // The function is going to insert a wait on everything in its prolog. | |
| // This still needs to be careful if the call target is a load (e.g. a GOT | |
| // load). We also need to check WAW dependency with saved PC. | |
| CallInsts.insert(&MI); | |
| Wait = AMDGPU::Waitcnt(); | |
| const MachineOperand &CallAddrOp = TII.getCalleeOperand(MI); | |
| if (CallAddrOp.isReg()) { | |
| ScoreBrackets.determineWaitForPhysReg( | |
| SmemAccessCounter, CallAddrOp.getReg().asMCReg(), Wait, MI); | |
| if (const auto *RtnAddrOp = | |
| TII.getNamedOperand(MI, AMDGPU::OpName::dst)) { | |
| ScoreBrackets.determineWaitForPhysReg( | |
| SmemAccessCounter, RtnAddrOp->getReg().asMCReg(), Wait, MI); | |
| } | |
| } | |
| } else if (Opc == AMDGPU::S_BARRIER_WAIT) { | |
| ScoreBrackets.tryClearSCCWriteEvent(&MI); | |
| } else { | |
| // FIXME: Should not be relying on memoperands. | |
| // Look at the source operands of every instruction to see if | |
| // any of them results from a previous memory operation that affects | |
| // its current usage. If so, an s_waitcnt instruction needs to be | |
| // emitted. | |
| // If the source operand was defined by a load, add the s_waitcnt | |
| // instruction. | |
| // | |
| // Two cases are handled for destination operands: | |
| // 1) If the destination operand was defined by a load, add the s_waitcnt | |
| // instruction to guarantee the right WAW order. | |
| // 2) If a destination operand that was used by a recent export/store ins, | |
| // add s_waitcnt on exp_cnt to guarantee the WAR order. | |
| for (const MachineMemOperand *Memop : MI.memoperands()) { | |
| const Value *Ptr = Memop->getValue(); | |
| if (Memop->isStore()) { | |
| if (auto It = SLoadAddresses.find(Ptr); It != SLoadAddresses.end()) { | |
| addWait(Wait, SmemAccessCounter, 0); | |
| if (PDT.dominates(MI.getParent(), It->second)) | |
| SLoadAddresses.erase(It); | |
| } | |
| } | |
| unsigned AS = Memop->getAddrSpace(); | |
| if (AS != AMDGPUAS::LOCAL_ADDRESS && AS != AMDGPUAS::FLAT_ADDRESS) | |
| continue; | |
| // No need to wait before load from VMEM to LDS. | |
| if (TII.mayWriteLDSThroughDMA(MI)) | |
| continue; | |
| // LOAD_CNT is only relevant to vgpr or LDS. | |
| unsigned TID = LDSDMA_BEGIN; | |
| if (Ptr && Memop->getAAInfo()) { | |
| const auto &LDSDMAStores = ScoreBrackets.getLDSDMAStores(); | |
| for (unsigned I = 0, E = LDSDMAStores.size(); I != E; ++I) { | |
| if (MI.mayAlias(AA, *LDSDMAStores[I], true)) { | |
| if ((I + 1) >= NUM_LDSDMA) { | |
| // We didn't have enough slot to track this LDS DMA store, it | |
| // has been tracked using the common RegNo (FIRST_LDS_VGPR). | |
| ScoreBrackets.determineWaitForLDSDMA(AMDGPU::LOAD_CNT, TID, | |
| Wait); | |
| break; | |
| } | |
| ScoreBrackets.determineWaitForLDSDMA(AMDGPU::LOAD_CNT, | |
| TID + I + 1, Wait); | |
| } | |
| } | |
| } else { | |
| ScoreBrackets.determineWaitForLDSDMA(AMDGPU::LOAD_CNT, TID, Wait); | |
| } | |
| if (Memop->isStore()) { | |
| ScoreBrackets.determineWaitForLDSDMA(AMDGPU::EXP_CNT, TID, Wait); | |
| } | |
| } | |
| // Loop over use and def operands. | |
| for (const MachineOperand &Op : MI.operands()) { | |
| if (!Op.isReg()) | |
| continue; | |
| // If the instruction does not read tied source, skip the operand. | |
| if (Op.isTied() && Op.isUse() && TII.doesNotReadTiedSource(MI)) | |
| continue; | |
| MCPhysReg Reg = Op.getReg().asMCReg(); | |
| const bool IsVGPR = TRI.isVectorRegister(MRI, Op.getReg()); | |
| if (IsVGPR) { | |
| // Implicit VGPR defs and uses are never a part of the memory | |
| // instructions description and usually present to account for | |
| // super-register liveness. | |
| // TODO: Most of the other instructions also have implicit uses | |
| // for the liveness accounting only. | |
| if (Op.isImplicit() && MI.mayLoadOrStore()) | |
| continue; | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::VA_VDST, Reg, Wait, MI); | |
| if (Op.isDef()) | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::VM_VSRC, Reg, Wait, | |
| MI); | |
| // RAW always needs an s_waitcnt. WAW needs an s_waitcnt unless the | |
| // previous write and this write are the same type of VMEM | |
| // instruction, in which case they are (in some architectures) | |
| // guaranteed to write their results in order anyway. | |
| // Additionally check instructions where Point Sample Acceleration | |
| // might be applied. | |
| if (Op.isUse() || !updateVMCntOnly(MI) || | |
| ScoreBrackets.hasOtherPendingVmemTypes(Reg, getVmemType(MI)) || | |
| ScoreBrackets.hasPointSamplePendingVmemTypes(MI, Reg) || | |
| !ST.hasVmemWriteVgprInOrder()) { | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::LOAD_CNT, Reg, Wait, | |
| MI); | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::SAMPLE_CNT, Reg, Wait, | |
| MI); | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::BVH_CNT, Reg, Wait, | |
| MI); | |
| ScoreBrackets.clearVgprVmemTypes(Reg); | |
| } | |
| if (Op.isDef() || ScoreBrackets.hasPendingEvent(EXP_LDS_ACCESS)) { | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::EXP_CNT, Reg, Wait, | |
| MI); | |
| } | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::DS_CNT, Reg, Wait, MI); | |
| } else if (Op.getReg() == AMDGPU::SCC) { | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::KM_CNT, Reg, Wait, MI); | |
| } else { | |
| ScoreBrackets.determineWaitForPhysReg(SmemAccessCounter, Reg, Wait, | |
| MI); | |
| } | |
| if (ST.hasWaitXcnt() && Op.isDef()) | |
| ScoreBrackets.determineWaitForPhysReg(AMDGPU::X_CNT, Reg, Wait, MI); | |
| } | |
| } | |
| } | |
| } | |
| // Ensure safety against exceptions from outstanding memory operations while | |
| // waiting for a barrier: | |
| // | |
| // * Some subtargets safely handle backing off the barrier in hardware | |
| // when an exception occurs. | |
| // * Some subtargets have an implicit S_WAITCNT 0 before barriers, so that | |
| // there can be no outstanding memory operations during the wait. | |
| // * Subtargets with split barriers don't need to back off the barrier; it | |
| // is up to the trap handler to preserve the user barrier state correctly. | |
| // | |
| // In all other cases, ensure safety by ensuring that there are no outstanding | |
| // memory operations. | |
| if (Opc == AMDGPU::S_BARRIER && !ST.hasAutoWaitcntBeforeBarrier() && | |
| !ST.hasBackOffBarrier()) { | |
| Wait = Wait.combined(WCG->getAllZeroWaitcnt(/*IncludeVSCnt=*/true)); | |
| } | |
| // TODO: Remove this work-around, enable the assert for Bug 457939 | |
| // after fixing the scheduler. Also, the Shader Compiler code is | |
| // independent of target. | |
| if (SIInstrInfo::isCBranchVCCZRead(MI) && ST.hasReadVCCZBug() && | |
| ScoreBrackets.hasPendingEvent(SMEM_ACCESS)) { | |
| Wait.set(AMDGPU::DS_CNT, 0); | |
| } | |
| // Verify that the wait is actually needed. | |
| ScoreBrackets.simplifyWaitcnt(Wait); | |
| // It is only necessary to insert an S_WAITCNT_DEPCTR instruction that | |
| // waits on VA_VDST if the instruction it would precede is not a VALU | |
| // instruction, since hardware handles VALU->VGPR->VALU hazards in | |
| // expert scheduling mode. | |
| if (TII.isVALU(MI)) | |
| Wait.set(AMDGPU::VA_VDST, ~0u); | |
| // Since the translation for VMEM addresses occur in-order, we can apply the | |
| // XCnt if the current instruction is of VMEM type and has a memory | |
| // dependency with another VMEM instruction in flight. | |
| if (Wait.get(AMDGPU::X_CNT) != ~0u && isVmemAccess(MI)) { | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::X_CNT); | |
| Wait.set(AMDGPU::X_CNT, ~0u); | |
| } | |
| // When forcing emit, we need to skip terminators because that would break the | |
| // terminators of the MBB if we emit a waitcnt between terminators. | |
| if (ForceEmitZeroFlag && !MI.isTerminator()) | |
| Wait = WCG->getAllZeroWaitcnt(/*IncludeVSCnt=*/false); | |
| // If we force waitcnt then update Wait accordingly. | |
| for (AMDGPU::InstCounterType T : AMDGPU::inst_counter_types()) { | |
| if (!ForceEmitWaitcnt[T]) | |
| continue; | |
| Wait.set(T, 0); | |
| } | |
| if (FlushFlags.FlushVmCnt) { | |
| for (AMDGPU::InstCounterType T : | |
| {AMDGPU::LOAD_CNT, AMDGPU::SAMPLE_CNT, AMDGPU::BVH_CNT}) | |
| Wait.set(T, 0); | |
| } | |
| if (FlushFlags.FlushDsCnt && ScoreBrackets.hasPendingEvent(AMDGPU::DS_CNT)) | |
| Wait.set(AMDGPU::DS_CNT, 0); | |
| if (ForceEmitZeroLoadFlag && Wait.get(AMDGPU::LOAD_CNT) != ~0u) | |
| Wait.set(AMDGPU::LOAD_CNT, 0); | |
| return generateWaitcnt(Wait, MI.getIterator(), *MI.getParent(), ScoreBrackets, | |
| OldWaitcntInstr); | |
| } | |
| bool SIInsertWaitcnts::generateWaitcnt(AMDGPU::Waitcnt Wait, | |
| MachineBasicBlock::instr_iterator It, | |
| MachineBasicBlock &Block, | |
| WaitcntBrackets &ScoreBrackets, | |
| MachineInstr *OldWaitcntInstr) { | |
| bool Modified = false; | |
| if (OldWaitcntInstr) | |
| // Try to merge the required wait with preexisting waitcnt instructions. | |
| // Also erase redundant waitcnt. | |
| Modified = | |
| WCG->applyPreexistingWaitcnt(ScoreBrackets, *OldWaitcntInstr, Wait, It); | |
| // ExpCnt can be merged into VINTERP. | |
| if (Wait.get(AMDGPU::EXP_CNT) != ~0u && It != Block.instr_end() && | |
| SIInstrInfo::isVINTERP(*It)) { | |
| MachineOperand *WaitExp = TII.getNamedOperand(*It, AMDGPU::OpName::waitexp); | |
| if (Wait.get(AMDGPU::EXP_CNT) < WaitExp->getImm()) { | |
| WaitExp->setImm(Wait.get(AMDGPU::EXP_CNT)); | |
| Modified = true; | |
| } | |
| // Apply ExpCnt before resetting it, so applyWaitcnt below sees all counts. | |
| ScoreBrackets.applyWaitcnt(Wait, AMDGPU::EXP_CNT); | |
| Wait.set(AMDGPU::EXP_CNT, ~0u); | |
| LLVM_DEBUG(dbgs() << "generateWaitcnt\n" | |
| << "Update Instr: " << *It); | |
| } | |
| if (WCG->createNewWaitcnt(Block, It, Wait, ScoreBrackets)) | |
| Modified = true; | |
| // Any counts that could have been applied to any existing waitcnt | |
| // instructions will have been done so, now deal with any remaining. | |
| ScoreBrackets.applyWaitcnt(Wait); | |
| return Modified; | |
| } | |
| std::optional<WaitEventType> | |
| SIInsertWaitcnts::getExpertSchedulingEventType(const MachineInstr &Inst) const { | |
| if (TII.isVALU(Inst)) { | |
| // Core/Side-, DP-, XDL- and TRANS-MACC VALU instructions complete | |
| // out-of-order with respect to each other, so each of these classes | |
| // has its own event. | |
| if (TII.isXDL(Inst)) | |
| return VGPR_XDL_WRITE; | |
| if (TII.isTRANS(Inst)) | |
| return VGPR_TRANS_WRITE; | |
| if (AMDGPU::isDPMACCInstruction(Inst.getOpcode())) | |
| return VGPR_DPMACC_WRITE; | |
| return VGPR_CSMACC_WRITE; | |
| } | |
| // FLAT and LDS instructions may read their VGPR sources out-of-order | |
| // with respect to each other and all other VMEM instructions, so | |
| // each of these also has a separate event. | |
| if (TII.isFLAT(Inst)) | |
| return VGPR_FLAT_READ; | |
| if (TII.isDS(Inst)) | |
| return VGPR_LDS_READ; | |
| if (TII.isVMEM(Inst) || TII.isVIMAGE(Inst) || TII.isVSAMPLE(Inst)) | |
| return VGPR_VMEM_READ; | |
| // Otherwise, no hazard. | |
| return {}; | |
| } | |
| bool SIInsertWaitcnts::isVmemAccess(const MachineInstr &MI) const { | |
| return (TII.isFLAT(MI) && TII.mayAccessVMEMThroughFlat(MI)) || | |
| (TII.isVMEM(MI) && !AMDGPU::getMUBUFIsBufferInv(MI.getOpcode())); | |
| } | |
| // Return true if the next instruction is S_ENDPGM, following fallthrough | |
| // blocks if necessary. | |
| bool SIInsertWaitcnts::isNextENDPGM(MachineBasicBlock::instr_iterator It, | |
| MachineBasicBlock *Block) const { | |
| auto BlockEnd = Block->getParent()->end(); | |
| auto BlockIter = Block->getIterator(); | |
| while (true) { | |
| if (It.isEnd()) { | |
| if (++BlockIter != BlockEnd) { | |
| It = BlockIter->instr_begin(); | |
| continue; | |
| } | |
| return false; | |
| } | |
| if (!It->isMetaInstruction()) | |
| break; | |
| It++; | |
| } | |
| assert(!It.isEnd()); | |
| return It->getOpcode() == AMDGPU::S_ENDPGM; | |
| } | |
| // Add a wait after an instruction if architecture requirements mandate one. | |
| bool SIInsertWaitcnts::insertForcedWaitAfter(MachineInstr &Inst, | |
| MachineBasicBlock &Block, | |
| WaitcntBrackets &ScoreBrackets) { | |
| AMDGPU::Waitcnt Wait; | |
| bool NeedsEndPGMCheck = false; | |
| if (ST.isPreciseMemoryEnabled() && Inst.mayLoadOrStore()) | |
| Wait = WCG->getAllZeroWaitcnt(Inst.mayStore() && | |
| !SIInstrInfo::isAtomicRet(Inst)); | |
| if (TII.isAlwaysGDS(Inst.getOpcode())) { | |
| Wait.set(AMDGPU::DS_CNT, 0); | |
| NeedsEndPGMCheck = true; | |
| } | |
| ScoreBrackets.simplifyWaitcnt(Wait); | |
| auto SuccessorIt = std::next(Inst.getIterator()); | |
| bool Result = generateWaitcnt(Wait, SuccessorIt, Block, ScoreBrackets, | |
| /*OldWaitcntInstr=*/nullptr); | |
| if (Result && NeedsEndPGMCheck && isNextENDPGM(SuccessorIt, &Block)) { | |
| BuildMI(Block, SuccessorIt, Inst.getDebugLoc(), TII.get(AMDGPU::S_NOP)) | |
| .addImm(0); | |
| } | |
| return Result; | |
| } | |
| WaitEventSet SIInsertWaitcnts::getEventsFor(const MachineInstr &Inst) const { | |
| WaitEventSet Events; | |
| if (IsExpertMode) { | |
| if (const auto ET = getExpertSchedulingEventType(Inst)) | |
| Events.insert(*ET); | |
| } | |
| if (TII.isDS(Inst) && TII.usesLGKM_CNT(Inst)) { | |
| if (TII.isAlwaysGDS(Inst.getOpcode()) || | |
| TII.hasModifiersSet(Inst, AMDGPU::OpName::gds)) { | |
| Events.insert(GDS_ACCESS); | |
| Events.insert(GDS_GPR_LOCK); | |
| } else { | |
| Events.insert(LDS_ACCESS); | |
| } | |
| } else if (TII.isFLAT(Inst)) { | |
| if (SIInstrInfo::isGFX12CacheInvOrWBInst(Inst.getOpcode())) { | |
| Events.insert(getVmemWaitEventType(Inst)); | |
| } else { | |
| assert(Inst.mayLoadOrStore()); | |
| if (TII.mayAccessVMEMThroughFlat(Inst)) { | |
| if (ST.hasWaitXcnt()) | |
| Events.insert(VMEM_GROUP); | |
| Events.insert(getVmemWaitEventType(Inst)); | |
| } | |
| if (TII.mayAccessLDSThroughFlat(Inst)) | |
| Events.insert(LDS_ACCESS); | |
| } | |
| } else if (SIInstrInfo::isVMEM(Inst) && | |
| (!AMDGPU::getMUBUFIsBufferInv(Inst.getOpcode()) || | |
| Inst.getOpcode() == AMDGPU::BUFFER_WBL2)) { | |
| // BUFFER_WBL2 is included here because unlike invalidates, has to be | |
| // followed "S_WAITCNT vmcnt(0)" is needed after to ensure the writeback has | |
| // completed. | |
| if (ST.hasWaitXcnt()) | |
| Events.insert(VMEM_GROUP); | |
| Events.insert(getVmemWaitEventType(Inst)); | |
| if (ST.vmemWriteNeedsExpWaitcnt() && | |
| (Inst.mayStore() || SIInstrInfo::isAtomicRet(Inst))) { | |
| Events.insert(VMW_GPR_LOCK); | |
| } | |
| } else if (TII.isSMRD(Inst)) { | |
| if (ST.hasWaitXcnt()) | |
| Events.insert(SMEM_GROUP); | |
| Events.insert(SMEM_ACCESS); | |
| } else if (SIInstrInfo::isLDSDIR(Inst)) { | |
| Events.insert(EXP_LDS_ACCESS); | |
| } else if (SIInstrInfo::isEXP(Inst)) { | |
| unsigned Imm = TII.getNamedOperand(Inst, AMDGPU::OpName::tgt)->getImm(); | |
| if (Imm >= AMDGPU::Exp::ET_PARAM0 && Imm <= AMDGPU::Exp::ET_PARAM31) | |
| Events.insert(EXP_PARAM_ACCESS); | |
| else if (Imm >= AMDGPU::Exp::ET_POS0 && Imm <= AMDGPU::Exp::ET_POS_LAST) | |
| Events.insert(EXP_POS_ACCESS); | |
| else | |
| Events.insert(EXP_GPR_LOCK); | |
| } else if (SIInstrInfo::isSBarrierSCCWrite(Inst.getOpcode())) { | |
| Events.insert(SCC_WRITE); | |
| } else { | |
| switch (Inst.getOpcode()) { | |
| case AMDGPU::S_SENDMSG: | |
| case AMDGPU::S_SENDMSG_RTN_B32: | |
| case AMDGPU::S_SENDMSG_RTN_B64: | |
| case AMDGPU::S_SENDMSGHALT: | |
| Events.insert(SQ_MESSAGE); | |
| break; | |
| case AMDGPU::S_MEMTIME: | |
| case AMDGPU::S_MEMREALTIME: | |
| case AMDGPU::S_GET_BARRIER_STATE_M0: | |
| case AMDGPU::S_GET_BARRIER_STATE_IMM: | |
| Events.insert(SMEM_ACCESS); | |
| break; | |
| } | |
| } | |
| return Events; | |
| } | |
| void SIInsertWaitcnts::updateEventWaitcntAfter(MachineInstr &Inst, | |
| WaitcntBrackets *ScoreBrackets) { | |
| WaitEventSet InstEvents = getEventsFor(Inst); | |
| for (WaitEventType E : wait_events()) { | |
| if (InstEvents.contains(E)) | |
| ScoreBrackets->updateByEvent(E, Inst); | |
| } | |
| if (TII.isDS(Inst) && TII.usesLGKM_CNT(Inst)) { | |
| if (TII.isAlwaysGDS(Inst.getOpcode()) || | |
| TII.hasModifiersSet(Inst, AMDGPU::OpName::gds)) { | |
| ScoreBrackets->setPendingGDS(); | |
| } | |
| } else if (TII.isFLAT(Inst)) { | |
| if (Inst.mayLoadOrStore() && TII.mayAccessVMEMThroughFlat(Inst) && | |
| TII.mayAccessLDSThroughFlat(Inst) && !SIInstrInfo::isLDSDMA(Inst)) { | |
| // Async/LDSDMA operations have FLAT encoding but do not actually use flat | |
| // pointers. They do have two operands that each access global and LDS, | |
| // thus making it appear at this point that they are using a flat pointer. | |
| // Filter them out, and for the rest, generate a dependency on flat | |
| // pointers so that both VM and LGKM counters are flushed. | |
| ScoreBrackets->setPendingFlat(); | |
| } | |
| if (SIInstrInfo::usesASYNC_CNT(Inst)) { | |
| ScoreBrackets->updateByEvent(ASYNC_ACCESS, Inst); | |
| } | |
| } else if (Inst.isCall()) { | |
| // Act as a wait on everything, but AsyncCnt is never included in such | |
| // blanket waits. | |
| ScoreBrackets->applyWaitcnt(WCG->getAllZeroWaitcnt(/*IncludeVSCnt=*/false)); | |
| ScoreBrackets->setStateOnFunctionEntryOrReturn(); | |
| } else if (TII.isVINTERP(Inst)) { | |
| int64_t Imm = TII.getNamedOperand(Inst, AMDGPU::OpName::waitexp)->getImm(); | |
| ScoreBrackets->applyWaitcnt(AMDGPU::EXP_CNT, Imm); | |
| } | |
| } | |
| bool WaitcntBrackets::mergeScore(const MergeInfo &M, unsigned &Score, | |
| unsigned OtherScore) { | |
| unsigned MyShifted = Score <= M.OldLB ? 0 : Score + M.MyShift; | |
| unsigned OtherShifted = | |
| OtherScore <= M.OtherLB ? 0 : OtherScore + M.OtherShift; | |
| Score = std::max(MyShifted, OtherShifted); | |
| return OtherShifted > MyShifted; | |
| } | |
| bool WaitcntBrackets::mergeAsyncMarks(ArrayRef<MergeInfo> MergeInfos, | |
| ArrayRef<CounterValueArray> OtherMarks) { | |
| bool StrictDom = false; | |
| LLVM_DEBUG(dbgs() << "Merging async marks ..."); | |
| // Early exit: nothing to merge when both sides are empty. | |
| if (AsyncMarks.empty() && OtherMarks.empty()) { | |
| LLVM_DEBUG(dbgs() << " nothing to merge\n"); | |
| return false; | |
| } | |
| LLVM_DEBUG(dbgs() << '\n'); | |
| // Determine maximum length needed after merging | |
| auto MaxSize = (unsigned)std::max(AsyncMarks.size(), OtherMarks.size()); | |
| MaxSize = std::min(MaxSize, MaxAsyncMarks); | |
| // Keep only the most recent marks within our limit. | |
| if (AsyncMarks.size() > MaxSize) | |
| AsyncMarks.erase(AsyncMarks.begin(), | |
| AsyncMarks.begin() + (AsyncMarks.size() - MaxSize)); | |
| // Pad with zero-filled marks if our list is shorter. Zero represents "no | |
| // pending async operations at this checkpoint" and acts as the identity | |
| // element for max() during merging. We pad at the beginning since the marks | |
| // need to be aligned in most-recent order. | |
| constexpr CounterValueArray ZeroMark{}; | |
| AsyncMarks.insert(AsyncMarks.begin(), MaxSize - AsyncMarks.size(), ZeroMark); | |
| LLVM_DEBUG({ | |
| dbgs() << "Before merge:\n"; | |
| for (const auto &Mark : AsyncMarks) { | |
| llvm::interleaveComma(Mark, dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| dbgs() << "Other marks:\n"; | |
| for (const auto &Mark : OtherMarks) { | |
| llvm::interleaveComma(Mark, dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| }); | |
| // Merge element-wise using the existing mergeScore function and the | |
| // appropriate MergeInfo for each counter type. Iterate only while we have | |
| // elements in both vectors. | |
| unsigned OtherSize = OtherMarks.size(); | |
| unsigned OurSize = AsyncMarks.size(); | |
| unsigned MergeCount = std::min(OtherSize, OurSize); | |
| // OtherMarks is empty -> OtherSize == 0 -> MergeCount == 0. | |
| // Our existing marks are the conservative result; return early to avoid | |
| // passing MergeCount == 0 to seq_inclusive which asserts Begin <= End. | |
| if (MergeCount == 0) | |
| return StrictDom; | |
| for (auto Idx : seq_inclusive<unsigned>(1, MergeCount)) { | |
| for (auto T : inst_counter_types(Context->MaxCounter)) { | |
| StrictDom |= mergeScore(MergeInfos[T], AsyncMarks[OurSize - Idx][T], | |
| OtherMarks[OtherSize - Idx][T]); | |
| } | |
| } | |
| LLVM_DEBUG({ | |
| dbgs() << "After merge:\n"; | |
| for (const auto &Mark : AsyncMarks) { | |
| llvm::interleaveComma(Mark, dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| }); | |
| return StrictDom; | |
| } | |
| /// Merge the pending events and associater score brackets of \p Other into | |
| /// this brackets status. | |
| /// | |
| /// Returns whether the merge resulted in a change that requires tighter waits | |
| /// (i.e. the merged brackets strictly dominate the original brackets). | |
| bool WaitcntBrackets::merge(const WaitcntBrackets &Other) { | |
| bool StrictDom = false; | |
| // Check if "other" has keys we don't have, and create default entries for | |
| // those. If they remain empty after merging, we will clean it up after. | |
| for (auto K : Other.VMem.keys()) | |
| VMem.try_emplace(K); | |
| for (auto K : Other.SGPRs.keys()) | |
| SGPRs.try_emplace(K); | |
| // Array to store MergeInfo for each counter type | |
| MergeInfo MergeInfos[AMDGPU::NUM_INST_CNTS]; | |
| for (auto T : inst_counter_types(Context->MaxCounter)) { | |
| // Merge event flags for this counter | |
| const WaitEventSet &EventsForT = Context->getWaitEvents(T); | |
| const WaitEventSet OldEvents = PendingEvents & EventsForT; | |
| const WaitEventSet OtherEvents = Other.PendingEvents & EventsForT; | |
| if (!OldEvents.contains(OtherEvents)) | |
| StrictDom = true; | |
| PendingEvents |= OtherEvents; | |
| // Merge scores for this counter | |
| const unsigned MyPending = ScoreUBs[T] - ScoreLBs[T]; | |
| const unsigned OtherPending = Other.ScoreUBs[T] - Other.ScoreLBs[T]; | |
| const unsigned NewUB = ScoreLBs[T] + std::max(MyPending, OtherPending); | |
| if (NewUB < ScoreLBs[T]) | |
| report_fatal_error("waitcnt score overflow"); | |
| MergeInfo &M = MergeInfos[T]; | |
| M.OldLB = ScoreLBs[T]; | |
| M.OtherLB = Other.ScoreLBs[T]; | |
| M.MyShift = NewUB - ScoreUBs[T]; | |
| M.OtherShift = NewUB - Other.ScoreUBs[T]; | |
| ScoreUBs[T] = NewUB; | |
| if (T == AMDGPU::LOAD_CNT) | |
| StrictDom |= mergeScore(M, LastFlatLoadCnt, Other.LastFlatLoadCnt); | |
| if (T == AMDGPU::DS_CNT) { | |
| StrictDom |= mergeScore(M, LastFlatDsCnt, Other.LastFlatDsCnt); | |
| StrictDom |= mergeScore(M, LastGDS, Other.LastGDS); | |
| } | |
| if (T == AMDGPU::KM_CNT) { | |
| StrictDom |= mergeScore(M, SCCScore, Other.SCCScore); | |
| if (Other.hasPendingEvent(SCC_WRITE)) { | |
| if (!OldEvents.contains(SCC_WRITE)) { | |
| PendingSCCWrite = Other.PendingSCCWrite; | |
| } else if (PendingSCCWrite != Other.PendingSCCWrite) { | |
| PendingSCCWrite = nullptr; | |
| } | |
| } | |
| } | |
| for (auto &[RegID, Info] : VMem) | |
| StrictDom |= mergeScore(M, Info.Scores[T], Other.getVMemScore(RegID, T)); | |
| if (isSmemCounter(T)) { | |
| for (auto &[RegID, Info] : SGPRs) { | |
| auto It = Other.SGPRs.find(RegID); | |
| unsigned OtherScore = (It != Other.SGPRs.end()) ? It->second.get(T) : 0; | |
| StrictDom |= mergeScore(M, Info.get(T), OtherScore); | |
| } | |
| } | |
| } | |
| for (auto &[TID, Info] : VMem) { | |
| if (auto It = Other.VMem.find(TID); It != Other.VMem.end()) { | |
| unsigned char NewVmemTypes = Info.VMEMTypes | It->second.VMEMTypes; | |
| StrictDom |= NewVmemTypes != Info.VMEMTypes; | |
| Info.VMEMTypes = NewVmemTypes; | |
| } | |
| } | |
| StrictDom |= mergeAsyncMarks(MergeInfos, Other.AsyncMarks); | |
| for (auto T : inst_counter_types(Context->MaxCounter)) | |
| StrictDom |= mergeScore(MergeInfos[T], AsyncScore[T], Other.AsyncScore[T]); | |
| purgeEmptyTrackingData(); | |
| return StrictDom; | |
| } | |
| static bool isWaitInstr(MachineInstr &Inst) { | |
| unsigned Opcode = SIInstrInfo::getNonSoftWaitcntOpcode(Inst.getOpcode()); | |
| return Opcode == AMDGPU::S_WAITCNT || | |
| (Opcode == AMDGPU::S_WAITCNT_VSCNT && Inst.getOperand(0).isReg() && | |
| Inst.getOperand(0).getReg() == AMDGPU::SGPR_NULL) || | |
| Opcode == AMDGPU::S_WAIT_LOADCNT_DSCNT || | |
| Opcode == AMDGPU::S_WAIT_STORECNT_DSCNT || | |
| Opcode == AMDGPU::S_WAITCNT_lds_direct || | |
| Opcode == AMDGPU::WAIT_ASYNCMARK || | |
| counterTypeForInstr(Opcode).has_value(); | |
| } | |
| void SIInsertWaitcnts::setSchedulingMode(MachineBasicBlock &MBB, | |
| MachineBasicBlock::iterator I, | |
| bool ExpertMode) const { | |
| const unsigned EncodedReg = AMDGPU::Hwreg::HwregEncoding::encode( | |
| AMDGPU::Hwreg::ID_SCHED_MODE, AMDGPU::Hwreg::HwregOffset::Default, 2); | |
| BuildMI(MBB, I, DebugLoc(), TII.get(AMDGPU::S_SETREG_IMM32_B32)) | |
| .addImm(ExpertMode ? 2 : 0) | |
| .addImm(EncodedReg); | |
| } | |
| namespace { | |
| // TODO: Remove this work-around after fixing the scheduler. | |
| // There are two reasons why vccz might be incorrect; see ST.hasReadVCCZBug() | |
| // and ST.partialVCCWritesUpdateVCCZ(). | |
| // i. VCCZBug: There is a hardware bug on CI/SI where SMRD instruction may | |
| // corrupt vccz bit, so when we detect that an instruction may read from | |
| // a corrupt vccz bit, we need to: | |
| // 1. Insert s_waitcnt lgkm(0) to wait for all outstanding SMRD | |
| // operations to complete. | |
| // 2. Recompute the correct value of vccz by writing the current value | |
| // of vcc back to vcc. | |
| // ii. Partial writes to vcc don't update vccz, so we need to recompute the | |
| // correct value of vccz by reading vcc and writing it back to vcc. | |
| // No waitcnt is needed in this case. | |
| class VCCZWorkaround { | |
| const WaitcntBrackets &ScoreBrackets; | |
| const GCNSubtarget &ST; | |
| const SIInstrInfo &TII; | |
| const SIRegisterInfo &TRI; | |
| bool VCCZCorruptionBug = false; | |
| bool VCCZNotUpdatedByPartialWrites = false; | |
| /// vccz could be incorrect at a basic block boundary if a predecessor wrote | |
| /// to vcc and then issued an smem load, so initialize to true. | |
| bool MustRecomputeVCCZ = true; | |
| public: | |
| VCCZWorkaround(const WaitcntBrackets &ScoreBrackets, const GCNSubtarget &ST, | |
| const SIInstrInfo &TII, const SIRegisterInfo &TRI) | |
| : ScoreBrackets(ScoreBrackets), ST(ST), TII(TII), TRI(TRI) { | |
| VCCZCorruptionBug = ST.hasReadVCCZBug(); | |
| VCCZNotUpdatedByPartialWrites = !ST.partialVCCWritesUpdateVCCZ(); | |
| } | |
| /// If \p MI reads vccz and we must recompute it based on MustRecomputeVCCZ, | |
| /// then emit a vccz recompute instruction before \p MI. This needs to be | |
| /// called on every instruction in the basic block because it also tracks the | |
| /// state and updates MustRecomputeVCCZ accordingly. Returns true if it | |
| /// modified the IR. | |
| bool tryRecomputeVCCZ(MachineInstr &MI) { | |
| // No need to run this if neither bug is present. | |
| if (!VCCZCorruptionBug && !VCCZNotUpdatedByPartialWrites) | |
| return false; | |
| // If MI is an SMEM and it can corrupt vccz on this target, then we need | |
| // both to emit a waitcnt and to recompute vccz. | |
| // But we don't actually emit a waitcnt here. This is done in | |
| // generateWaitcntInstBefore() because it tracks all the necessary waitcnt | |
| // state, and can either skip emitting a waitcnt if there is already one in | |
| // the IR, or emit an "optimized" combined waitcnt. | |
| // If this is an smem read, it could complete and clobber vccz at any time. | |
| MustRecomputeVCCZ |= VCCZCorruptionBug && TII.isSMRD(MI); | |
| // If the target partial vcc writes don't update vccz, and MI is such an | |
| // instruction then we must recompute vccz. | |
| // Note: We are using PartiallyWritesToVCCOpt optional to avoid calling | |
| // `definesRegister()` more than needed, because it's not very cheap. | |
| std::optional<bool> PartiallyWritesToVCCOpt; | |
| auto PartiallyWritesToVCC = [](MachineInstr &MI) { | |
| return MI.definesRegister(AMDGPU::VCC_LO, /*TRI=*/nullptr) || | |
| MI.definesRegister(AMDGPU::VCC_HI, /*TRI=*/nullptr); | |
| }; | |
| if (VCCZNotUpdatedByPartialWrites) { | |
| PartiallyWritesToVCCOpt = PartiallyWritesToVCC(MI); | |
| // If this is a partial VCC write but won't update vccz, then we must | |
| // recompute vccz. | |
| MustRecomputeVCCZ |= *PartiallyWritesToVCCOpt; | |
| } | |
| // If MI is a vcc write with no pending smem, or there is a pending smem | |
| // but the target does not suffer from the vccz corruption bug, then we | |
| // don't need to recompute vccz as this write will recompute it anyway. | |
| if (!ScoreBrackets.hasPendingEvent(SMEM_ACCESS) || !VCCZCorruptionBug) { | |
| // Compute PartiallyWritesToVCCOpt if we haven't done so already. | |
| if (!PartiallyWritesToVCCOpt) | |
| PartiallyWritesToVCCOpt = PartiallyWritesToVCC(MI); | |
| bool FullyWritesToVCC = !*PartiallyWritesToVCCOpt && | |
| MI.definesRegister(AMDGPU::VCC, /*TRI=*/nullptr); | |
| // If we write to the full vcc or we write partially and the target | |
| // updates vccz on partial writes, then vccz will be updated correctly. | |
| bool UpdatesVCCZ = FullyWritesToVCC || (!VCCZNotUpdatedByPartialWrites && | |
| *PartiallyWritesToVCCOpt); | |
| if (UpdatesVCCZ) | |
| MustRecomputeVCCZ = false; | |
| } | |
| // If MI is a branch that reads VCCZ then emit a waitcnt and a vccz | |
| // restore instruction if either is needed. | |
| if (SIInstrInfo::isCBranchVCCZRead(MI) && MustRecomputeVCCZ) { | |
| // Recompute the vccz bit. Any time a value is written to vcc, the vccz | |
| // bit is updated, so we can restore the bit by reading the value of vcc | |
| // and then writing it back to the register. | |
| BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), | |
| TII.get(ST.isWave32() ? AMDGPU::S_MOV_B32 : AMDGPU::S_MOV_B64), | |
| TRI.getVCC()) | |
| .addReg(TRI.getVCC()); | |
| MustRecomputeVCCZ = false; | |
| return true; | |
| } | |
| return false; | |
| } | |
| }; | |
| } // namespace | |
| // Generate s_waitcnt instructions where needed. | |
| bool SIInsertWaitcnts::insertWaitcntInBlock(MachineFunction &MF, | |
| MachineBasicBlock &Block, | |
| WaitcntBrackets &ScoreBrackets) { | |
| bool Modified = false; | |
| LLVM_DEBUG({ | |
| dbgs() << "*** Begin Block: "; | |
| Block.printName(dbgs()); | |
| ScoreBrackets.dump(); | |
| }); | |
| VCCZWorkaround VCCZW(ScoreBrackets, ST, TII, TRI); | |
| // Walk over the instructions. | |
| MachineInstr *OldWaitcntInstr = nullptr; | |
| // NOTE: We may append instrs after Inst while iterating. | |
| for (MachineBasicBlock::instr_iterator Iter = Block.instr_begin(), | |
| E = Block.instr_end(); | |
| Iter != E; ++Iter) { | |
| MachineInstr &Inst = *Iter; | |
| if (isNonWaitcntMetaInst(Inst)) | |
| continue; | |
| // Track pre-existing waitcnts that were added in earlier iterations or by | |
| // the memory legalizer. | |
| if (isWaitInstr(Inst) || | |
| (IsExpertMode && Inst.getOpcode() == AMDGPU::S_WAITCNT_DEPCTR)) { | |
| if (!OldWaitcntInstr) | |
| OldWaitcntInstr = &Inst; | |
| continue; | |
| } | |
| PreheaderFlushFlags FlushFlags; | |
| if (Block.getFirstTerminator() == Inst) | |
| FlushFlags = isPreheaderToFlush(Block, ScoreBrackets); | |
| // Generate an s_waitcnt instruction to be placed before Inst, if needed. | |
| Modified |= generateWaitcntInstBefore(Inst, ScoreBrackets, OldWaitcntInstr, | |
| FlushFlags); | |
| OldWaitcntInstr = nullptr; | |
| if (Inst.getOpcode() == AMDGPU::ASYNCMARK) { | |
| // Asyncmarks record the current wait state and so should not allow | |
| // waitcnts that occur after them to be merged into waitcnts that occur | |
| // before. | |
| ScoreBrackets.recordAsyncMark(Inst); | |
| continue; | |
| } | |
| if (TII.isSMRD(Inst)) { | |
| for (const MachineMemOperand *Memop : Inst.memoperands()) { | |
| // No need to handle invariant loads when avoiding WAR conflicts, as | |
| // there cannot be a vector store to the same memory location. | |
| if (!Memop->isInvariant()) { | |
| const Value *Ptr = Memop->getValue(); | |
| SLoadAddresses.insert(std::pair(Ptr, Inst.getParent())); | |
| } | |
| } | |
| } | |
| updateEventWaitcntAfter(Inst, &ScoreBrackets); | |
| // Note: insertForcedWaitAfter() may add instrs after Iter that need to be | |
| // visited by the loop. | |
| Modified |= insertForcedWaitAfter(Inst, Block, ScoreBrackets); | |
| LLVM_DEBUG({ | |
| Inst.print(dbgs()); | |
| ScoreBrackets.dump(); | |
| }); | |
| // If the target suffers from the vccz bugs, this may emit the necessary | |
| // vccz recompute instruction before \p Inst if needed. | |
| Modified |= VCCZW.tryRecomputeVCCZ(Inst); | |
| } | |
| // Flush counters at the end of the block if needed (for preheaders with no | |
| // terminator). | |
| AMDGPU::Waitcnt Wait; | |
| if (Block.getFirstTerminator() == Block.end()) { | |
| PreheaderFlushFlags FlushFlags = isPreheaderToFlush(Block, ScoreBrackets); | |
| if (FlushFlags.FlushVmCnt) { | |
| if (ScoreBrackets.hasPendingEvent(AMDGPU::LOAD_CNT)) | |
| Wait.set(AMDGPU::LOAD_CNT, 0); | |
| if (ScoreBrackets.hasPendingEvent(AMDGPU::SAMPLE_CNT)) | |
| Wait.set(AMDGPU::SAMPLE_CNT, 0); | |
| if (ScoreBrackets.hasPendingEvent(AMDGPU::BVH_CNT)) | |
| Wait.set(AMDGPU::BVH_CNT, 0); | |
| } | |
| if (FlushFlags.FlushDsCnt && ScoreBrackets.hasPendingEvent(AMDGPU::DS_CNT)) | |
| Wait.set(AMDGPU::DS_CNT, 0); | |
| } | |
| // Combine or remove any redundant waitcnts at the end of the block. | |
| Modified |= generateWaitcnt(Wait, Block.instr_end(), Block, ScoreBrackets, | |
| OldWaitcntInstr); | |
| LLVM_DEBUG({ | |
| dbgs() << "*** End Block: "; | |
| Block.printName(dbgs()); | |
| ScoreBrackets.dump(); | |
| }); | |
| return Modified; | |
| } | |
| bool SIInsertWaitcnts::removeRedundantSoftXcnts(MachineBasicBlock &Block) { | |
| if (Block.size() <= 1) | |
| return false; | |
| // The Memory Legalizer conservatively inserts a soft xcnt before each | |
| // atomic RMW operation. However, for sequences of back-to-back atomic | |
| // RMWs, only the first s_wait_xcnt insertion is necessary. Optimize away | |
| // the redundant soft xcnts. | |
| bool Modified = false; | |
| // Remember the last atomic with a soft xcnt right before it. | |
| MachineInstr *LastAtomicWithSoftXcnt = nullptr; | |
| for (MachineInstr &MI : drop_begin(Block)) { | |
| // Ignore last atomic if non-LDS VMEM and SMEM. | |
| bool IsLDS = | |
| TII.isDS(MI) || (TII.isFLAT(MI) && TII.mayAccessLDSThroughFlat(MI)); | |
| if (!IsLDS && (MI.mayLoad() ^ MI.mayStore())) | |
| LastAtomicWithSoftXcnt = nullptr; | |
| bool IsAtomicRMW = (MI.getDesc().TSFlags & SIInstrFlags::maybeAtomic) && | |
| MI.mayLoad() && MI.mayStore(); | |
| MachineInstr &PrevMI = *MI.getPrevNode(); | |
| // This is an atomic with a soft xcnt. | |
| if (PrevMI.getOpcode() == AMDGPU::S_WAIT_XCNT_soft && IsAtomicRMW) { | |
| // If we have already found an atomic with a soft xcnt, remove this soft | |
| // xcnt as it's redundant. | |
| if (LastAtomicWithSoftXcnt) { | |
| PrevMI.eraseFromParent(); | |
| Modified = true; | |
| } | |
| LastAtomicWithSoftXcnt = &MI; | |
| } | |
| } | |
| return Modified; | |
| } | |
| // Return flags indicating which counters should be flushed in the preheader. | |
| PreheaderFlushFlags | |
| SIInsertWaitcnts::isPreheaderToFlush(MachineBasicBlock &MBB, | |
| const WaitcntBrackets &ScoreBrackets) { | |
| auto [Iterator, IsInserted] = | |
| PreheadersToFlush.try_emplace(&MBB, PreheaderFlushFlags()); | |
| if (!IsInserted) | |
| return Iterator->second; | |
| MachineBasicBlock *Succ = MBB.getSingleSuccessor(); | |
| if (!Succ) | |
| return PreheaderFlushFlags(); | |
| MachineLoop *Loop = MLI.getLoopFor(Succ); | |
| if (!Loop) | |
| return PreheaderFlushFlags(); | |
| if (Loop->getLoopPreheader() == &MBB) { | |
| Iterator->second = getPreheaderFlushFlags(Loop, ScoreBrackets); | |
| return Iterator->second; | |
| } | |
| return PreheaderFlushFlags(); | |
| } | |
| bool SIInsertWaitcnts::isVMEMOrFlatVMEM(const MachineInstr &MI) const { | |
| if (SIInstrInfo::isFLAT(MI)) | |
| return TII.mayAccessVMEMThroughFlat(MI); | |
| return SIInstrInfo::isVMEM(MI); | |
| } | |
| bool SIInsertWaitcnts::isDSRead(const MachineInstr &MI) const { | |
| return SIInstrInfo::isDS(MI) && MI.mayLoad() && !MI.mayStore(); | |
| } | |
| // Check if instruction is a store to LDS that is counted via DSCNT | |
| // (where that counter exists). | |
| bool SIInsertWaitcnts::mayStoreIncrementingDSCNT(const MachineInstr &MI) const { | |
| return MI.mayStore() && SIInstrInfo::isDS(MI); | |
| } | |
| // Return flags indicating which counters should be flushed in the preheader of | |
| // the given loop. We currently decide to flush in the following situations: | |
| // For VMEM (FlushVmCnt): | |
| // 1. The loop contains vmem store(s), no vmem load and at least one use of a | |
| // vgpr containing a value that is loaded outside of the loop. (Only on | |
| // targets with no vscnt counter). | |
| // 2. The loop contains vmem load(s), but the loaded values are not used in the | |
| // loop, and at least one use of a vgpr containing a value that is loaded | |
| // outside of the loop. | |
| // For DS (FlushDsCnt, GFX12+ only): | |
| // 3. The loop contains no DS reads, and at least one use of a vgpr containing | |
| // a value that is DS read outside of the loop. | |
| // 4. The loop contains DS read(s), loaded values are not used in the same | |
| // iteration but in the next iteration (prefetch pattern), and at least one | |
| // use of a vgpr containing a value that is DS read outside of the loop. | |
| // Flushing in preheader reduces wait overhead if the wait requirement in | |
| // iteration 1 would otherwise be more strict (but unfortunately preheader | |
| // flush decision is taken before knowing that). | |
| // 5. (Single-block loops only) The loop has DS prefetch reads with flush point | |
| // tracking. Some DS reads may be used in the same iteration (creating | |
| // "flush points"), but others remain unflushed at the backedge. When a DS | |
| // read is consumed in the same iteration, it and all prior reads are | |
| // "flushed" (FIFO order). No DS writes are allowed in the loop. | |
| // TODO: Find a way to extend to multi-block loops. | |
| PreheaderFlushFlags | |
| SIInsertWaitcnts::getPreheaderFlushFlags(MachineLoop *ML, | |
| const WaitcntBrackets &Brackets) { | |
| PreheaderFlushFlags Flags; | |
| bool HasVMemLoad = false; | |
| bool HasVMemStore = false; | |
| bool UsesVgprVMEMLoadedOutside = false; | |
| bool UsesVgprDSReadOutside = false; | |
| bool VMemInvalidated = false; | |
| // DS optimization only applies to GFX12+ where DS_CNT is separate. | |
| // Tracking status for "no DS read in loop" or "pure DS prefetch | |
| // (use only in next iteration)". | |
| bool TrackSimpleDSOpt = ST.hasExtendedWaitCounts(); | |
| DenseSet<MCRegUnit> VgprUse; | |
| DenseSet<MCRegUnit> VgprDefVMEM; | |
| DenseSet<MCRegUnit> VgprDefDS; | |
| // Track DS reads for prefetch pattern with flush points (single-block only). | |
| // Keeps track of the last DS read (position counted from the top of the loop) | |
| // to each VGPR. Read is considered consumed (and thus needs flushing) if | |
| // the dest register has a use or is overwritten (by any later opertions). | |
| DenseMap<MCRegUnit, unsigned> LastDSReadPositionMap; | |
| unsigned DSReadPosition = 0; | |
| bool IsSingleBlock = ML->getNumBlocks() == 1; | |
| bool TrackDSFlushPoint = ST.hasExtendedWaitCounts() && IsSingleBlock; | |
| unsigned LastDSFlushPosition = 0; | |
| for (MachineBasicBlock *MBB : ML->blocks()) { | |
| for (MachineInstr &MI : *MBB) { | |
| if (isVMEMOrFlatVMEM(MI)) { | |
| HasVMemLoad |= MI.mayLoad(); | |
| HasVMemStore |= MI.mayStore(); | |
| } | |
| // TODO: Can we relax DSStore check? There may be cases where | |
| // these DS stores are drained prior to the end of MBB (or loop). | |
| if (mayStoreIncrementingDSCNT(MI)) { | |
| // Early exit if none of the optimizations are feasible. | |
| // Otherwise, set tracking status appropriately and continue. | |
| if (VMemInvalidated) | |
| return Flags; | |
| TrackSimpleDSOpt = false; | |
| TrackDSFlushPoint = false; | |
| } | |
| bool IsDSRead = isDSRead(MI); | |
| if (IsDSRead) | |
| ++DSReadPosition; | |
| // Helper: if RU has a pending DS read, update LastDSFlushPosition | |
| auto updateDSReadFlushTracking = [&](MCRegUnit RU) { | |
| if (!TrackDSFlushPoint) | |
| return; | |
| if (auto It = LastDSReadPositionMap.find(RU); | |
| It != LastDSReadPositionMap.end()) { | |
| // RU defined by DSRead is used or overwritten. Need to complete | |
| // the read, if not already implied by a later DSRead (to any RU) | |
| // needing to complete in FIFO order. | |
| LastDSFlushPosition = std::max(LastDSFlushPosition, It->second); | |
| } | |
| }; | |
| for (const MachineOperand &Op : MI.all_uses()) { | |
| if (Op.isDebug() || !TRI.isVectorRegister(MRI, Op.getReg())) | |
| continue; | |
| // Vgpr use | |
| for (MCRegUnit RU : TRI.regunits(Op.getReg().asMCReg())) { | |
| // If we find a register that is loaded inside the loop, 1. and 2. | |
| // are invalidated. | |
| if (VgprDefVMEM.contains(RU)) | |
| VMemInvalidated = true; | |
| // Check for DS reads used inside the loop | |
| if (VgprDefDS.contains(RU)) | |
| TrackSimpleDSOpt = false; | |
| // Early exit if all optimizations are invalidated | |
| if (VMemInvalidated && !TrackSimpleDSOpt && !TrackDSFlushPoint) | |
| return Flags; | |
| // Check for flush points (DS read used in same iteration) | |
| updateDSReadFlushTracking(RU); | |
| VgprUse.insert(RU); | |
| // Check if this register has a pending VMEM load from outside the | |
| // loop (value loaded outside and used inside). | |
| VMEMID ID = toVMEMID(RU); | |
| if (Brackets.hasPendingVMEM(ID, AMDGPU::LOAD_CNT) || | |
| Brackets.hasPendingVMEM(ID, AMDGPU::SAMPLE_CNT) || | |
| Brackets.hasPendingVMEM(ID, AMDGPU::BVH_CNT)) | |
| UsesVgprVMEMLoadedOutside = true; | |
| // Check if loaded outside the loop via DS (not VMEM/FLAT). | |
| // Only consider it a DS read if there's no pending VMEM load for | |
| // this register, since FLAT can set both counters. | |
| else if (Brackets.hasPendingVMEM(ID, AMDGPU::DS_CNT)) | |
| UsesVgprDSReadOutside = true; | |
| } | |
| } | |
| // VMem load vgpr def | |
| if (isVMEMOrFlatVMEM(MI) && MI.mayLoad()) { | |
| for (const MachineOperand &Op : MI.all_defs()) { | |
| for (MCRegUnit RU : TRI.regunits(Op.getReg().asMCReg())) { | |
| // If we find a register that is loaded inside the loop, 1. and 2. | |
| // are invalidated. | |
| if (VgprUse.contains(RU)) | |
| VMemInvalidated = true; | |
| VgprDefVMEM.insert(RU); | |
| } | |
| } | |
| // Early exit if all optimizations are invalidated | |
| if (VMemInvalidated && !TrackSimpleDSOpt && !TrackDSFlushPoint) | |
| return Flags; | |
| } | |
| // DS read vgpr def | |
| // Note: Unlike VMEM, we DON'T invalidate when VgprUse.contains(RegNo). | |
| // If USE comes before DEF, it's the prefetch pattern (use value from | |
| // previous iteration, read for next iteration). We should still flush | |
| // in preheader so iteration 1 doesn't need to wait inside the loop. | |
| // Only invalidate when DEF comes before USE (same-iteration consumption, | |
| // checked above when processing uses). | |
| if (IsDSRead || TrackDSFlushPoint) { | |
| for (const MachineOperand &Op : MI.all_defs()) { | |
| if (!TRI.isVectorRegister(MRI, Op.getReg())) | |
| continue; | |
| for (MCRegUnit RU : TRI.regunits(Op.getReg().asMCReg())) { | |
| // Check for overwrite of pending DS read (flush point) by any | |
| // instruction | |
| updateDSReadFlushTracking(RU); | |
| if (IsDSRead) { | |
| VgprDefDS.insert(RU); | |
| if (TrackDSFlushPoint) | |
| LastDSReadPositionMap[RU] = DSReadPosition; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // VMEM flush decision | |
| if (!VMemInvalidated && UsesVgprVMEMLoadedOutside && | |
| ((!ST.hasVscnt() && HasVMemStore && !HasVMemLoad) || | |
| (HasVMemLoad && ST.hasVmemWriteVgprInOrder()))) | |
| Flags.FlushVmCnt = true; | |
| // DS flush decision: | |
| // Simple DS Opt: flush if loop uses DS read values from outside | |
| // and either has no DS reads in the loop, or DS reads whose results | |
| // are not used in the loop. | |
| bool SimpleDSOpt = TrackSimpleDSOpt && UsesVgprDSReadOutside; | |
| // Prefetch with flush points: some DS reads used in same iteration, | |
| // but unflushed reads remain at backedge | |
| bool HasUnflushedDSReads = DSReadPosition > LastDSFlushPosition; | |
| bool DSFlushPointPrefetch = | |
| TrackDSFlushPoint && UsesVgprDSReadOutside && HasUnflushedDSReads; | |
| if (SimpleDSOpt || DSFlushPointPrefetch) | |
| Flags.FlushDsCnt = true; | |
| return Flags; | |
| } | |
| bool SIInsertWaitcntsLegacy::runOnMachineFunction(MachineFunction &MF) { | |
| auto &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI(); | |
| auto &PDT = | |
| getAnalysis<MachinePostDominatorTreeWrapperPass>().getPostDomTree(); | |
| AliasAnalysis *AA = nullptr; | |
| if (auto *AAR = getAnalysisIfAvailable<AAResultsWrapperPass>()) | |
| AA = &AAR->getAAResults(); | |
| return SIInsertWaitcnts(MLI, PDT, AA, MF).run(); | |
| } | |
| PreservedAnalyses | |
| SIInsertWaitcntsPass::run(MachineFunction &MF, | |
| MachineFunctionAnalysisManager &MFAM) { | |
| auto &MLI = MFAM.getResult<MachineLoopAnalysis>(MF); | |
| auto &PDT = MFAM.getResult<MachinePostDominatorTreeAnalysis>(MF); | |
| auto *AA = MFAM.getResult<FunctionAnalysisManagerMachineFunctionProxy>(MF) | |
| .getManager() | |
| .getCachedResult<AAManager>(MF.getFunction()); | |
| if (!SIInsertWaitcnts(MLI, PDT, AA, MF).run()) | |
| return PreservedAnalyses::all(); | |
| return getMachineFunctionPassPreservedAnalyses() | |
| .preserveSet<CFGAnalyses>() | |
| .preserve<AAManager>(); | |
| } | |
| bool SIInsertWaitcnts::run() { | |
| const SIMachineFunctionInfo *MFI = MF.getInfo<SIMachineFunctionInfo>(); | |
| AMDGPU::IsaVersion IV = AMDGPU::getIsaVersion(ST.getCPU()); | |
| // Initialize hardware limits first, as they're needed by the generators. | |
| Limits = AMDGPU::HardwareLimits(IV); | |
| if (ST.hasExtendedWaitCounts()) { | |
| IsExpertMode = ST.hasExpertSchedulingMode() && | |
| (ExpertSchedulingModeFlag.getNumOccurrences() | |
| ? ExpertSchedulingModeFlag | |
| : MF.getFunction() | |
| .getFnAttribute("amdgpu-expert-scheduling-mode") | |
| .getValueAsBool()); | |
| MaxCounter = IsExpertMode ? AMDGPU::NUM_EXPERT_INST_CNTS | |
| : AMDGPU::NUM_EXTENDED_INST_CNTS; | |
| // Initialize WCG per MF. It contains state that depends on MF attributes. | |
| WCG = std::make_unique<WaitcntGeneratorGFX12Plus>(MF, MaxCounter, Limits, | |
| IsExpertMode); | |
| } else { | |
| MaxCounter = AMDGPU::NUM_NORMAL_INST_CNTS; | |
| // Initialize WCG per MF. It contains state that depends on MF attributes. | |
| WCG = std::make_unique<WaitcntGeneratorPreGFX12>( | |
| MF, AMDGPU::NUM_NORMAL_INST_CNTS, Limits); | |
| } | |
| SmemAccessCounter = getCounterFromEvent(SMEM_ACCESS); | |
| bool Modified = false; | |
| MachineBasicBlock &EntryBB = MF.front(); | |
| if (!MFI->isEntryFunction() && | |
| !MF.getFunction().hasFnAttribute(Attribute::Naked)) { | |
| // Wait for any outstanding memory operations that the input registers may | |
| // depend on. We can't track them and it's better to do the wait after the | |
| // costly call sequence. | |
| // TODO: Could insert earlier and schedule more liberally with operations | |
| // that only use caller preserved registers. | |
| MachineBasicBlock::iterator I = EntryBB.begin(); | |
| while (I != EntryBB.end() && I->isMetaInstruction()) | |
| ++I; | |
| if (ST.hasExtendedWaitCounts()) { | |
| BuildMI(EntryBB, I, DebugLoc(), TII.get(AMDGPU::S_WAIT_LOADCNT_DSCNT)) | |
| .addImm(0); | |
| for (auto CT : inst_counter_types(AMDGPU::NUM_EXTENDED_INST_CNTS)) { | |
| if (CT == AMDGPU::LOAD_CNT || CT == AMDGPU::DS_CNT || | |
| CT == AMDGPU::STORE_CNT || CT == AMDGPU::X_CNT || | |
| CT == AMDGPU::ASYNC_CNT) | |
| continue; | |
| if (!ST.hasImageInsts() && | |
| (CT == AMDGPU::EXP_CNT || CT == AMDGPU::SAMPLE_CNT || | |
| CT == AMDGPU::BVH_CNT)) | |
| continue; | |
| BuildMI(EntryBB, I, DebugLoc(), | |
| TII.get(instrsForExtendedCounterTypes[CT])) | |
| .addImm(0); | |
| } | |
| if (IsExpertMode) { | |
| unsigned Enc = AMDGPU::DepCtr::encodeFieldVaVdst(0, ST); | |
| Enc = AMDGPU::DepCtr::encodeFieldVmVsrc(Enc, 0); | |
| BuildMI(EntryBB, I, DebugLoc(), TII.get(AMDGPU::S_WAITCNT_DEPCTR)) | |
| .addImm(Enc); | |
| } | |
| } else { | |
| BuildMI(EntryBB, I, DebugLoc(), TII.get(AMDGPU::S_WAITCNT)).addImm(0); | |
| } | |
| auto NonKernelInitialState = std::make_unique<WaitcntBrackets>(this); | |
| NonKernelInitialState->setStateOnFunctionEntryOrReturn(); | |
| BlockInfos[&EntryBB].Incoming = std::move(NonKernelInitialState); | |
| Modified = true; | |
| } | |
| // Keep iterating over the blocks in reverse post order, inserting and | |
| // updating s_waitcnt where needed, until a fix point is reached. | |
| for (auto *MBB : ReversePostOrderTraversal<MachineFunction *>(&MF)) | |
| BlockInfos.try_emplace(MBB); | |
| std::unique_ptr<WaitcntBrackets> Brackets; | |
| bool Repeat; | |
| do { | |
| Repeat = false; | |
| for (auto BII = BlockInfos.begin(), BIE = BlockInfos.end(); BII != BIE; | |
| ++BII) { | |
| MachineBasicBlock *MBB = BII->first; | |
| BlockInfo &BI = BII->second; | |
| if (!BI.Dirty) | |
| continue; | |
| if (BI.Incoming) { | |
| if (!Brackets) | |
| Brackets = std::make_unique<WaitcntBrackets>(*BI.Incoming); | |
| else | |
| *Brackets = *BI.Incoming; | |
| } else { | |
| if (!Brackets) { | |
| Brackets = std::make_unique<WaitcntBrackets>(this); | |
| } else { | |
| // Reinitialize in-place. N.B. do not do this by assigning from a | |
| // temporary because the WaitcntBrackets class is large and it could | |
| // cause this function to use an unreasonable amount of stack space. | |
| Brackets->~WaitcntBrackets(); | |
| new (Brackets.get()) WaitcntBrackets(this); | |
| } | |
| } | |
| if (ST.hasWaitXcnt()) | |
| Modified |= removeRedundantSoftXcnts(*MBB); | |
| Modified |= insertWaitcntInBlock(MF, *MBB, *Brackets); | |
| BI.Dirty = false; | |
| if (Brackets->hasPendingEvent()) { | |
| BlockInfo *MoveBracketsToSucc = nullptr; | |
| for (MachineBasicBlock *Succ : MBB->successors()) { | |
| auto *SuccBII = BlockInfos.find(Succ); | |
| BlockInfo &SuccBI = SuccBII->second; | |
| if (!SuccBI.Incoming) { | |
| SuccBI.Dirty = true; | |
| if (SuccBII <= BII) { | |
| LLVM_DEBUG(dbgs() << "Repeat on backedge without merge\n"); | |
| Repeat = true; | |
| } | |
| if (!MoveBracketsToSucc) { | |
| MoveBracketsToSucc = &SuccBI; | |
| } else { | |
| SuccBI.Incoming = std::make_unique<WaitcntBrackets>(*Brackets); | |
| } | |
| } else { | |
| LLVM_DEBUG({ | |
| dbgs() << "Try to merge "; | |
| MBB->printName(dbgs()); | |
| dbgs() << " into "; | |
| Succ->printName(dbgs()); | |
| dbgs() << '\n'; | |
| }); | |
| if (SuccBI.Incoming->merge(*Brackets)) { | |
| SuccBI.Dirty = true; | |
| if (SuccBII <= BII) { | |
| LLVM_DEBUG(dbgs() << "Repeat on backedge with merge\n"); | |
| Repeat = true; | |
| } | |
| } | |
| } | |
| } | |
| if (MoveBracketsToSucc) | |
| MoveBracketsToSucc->Incoming = std::move(Brackets); | |
| } | |
| } | |
| } while (Repeat); | |
| if (ST.hasScalarStores()) { | |
| SmallVector<MachineBasicBlock *, 4> EndPgmBlocks; | |
| bool HaveScalarStores = false; | |
| for (MachineBasicBlock &MBB : MF) { | |
| for (MachineInstr &MI : MBB) { | |
| if (!HaveScalarStores && TII.isScalarStore(MI)) | |
| HaveScalarStores = true; | |
| if (MI.getOpcode() == AMDGPU::S_ENDPGM || | |
| MI.getOpcode() == AMDGPU::SI_RETURN_TO_EPILOG) | |
| EndPgmBlocks.push_back(&MBB); | |
| } | |
| } | |
| if (HaveScalarStores) { | |
| // If scalar writes are used, the cache must be flushed or else the next | |
| // wave to reuse the same scratch memory can be clobbered. | |
| // | |
| // Insert s_dcache_wb at wave termination points if there were any scalar | |
| // stores, and only if the cache hasn't already been flushed. This could | |
| // be improved by looking across blocks for flushes in postdominating | |
| // blocks from the stores but an explicitly requested flush is probably | |
| // very rare. | |
| for (MachineBasicBlock *MBB : EndPgmBlocks) { | |
| bool SeenDCacheWB = false; | |
| for (MachineBasicBlock::iterator I = MBB->begin(), E = MBB->end(); | |
| I != E; ++I) { | |
| if (I->getOpcode() == AMDGPU::S_DCACHE_WB) | |
| SeenDCacheWB = true; | |
| else if (TII.isScalarStore(*I)) | |
| SeenDCacheWB = false; | |
| // FIXME: It would be better to insert this before a waitcnt if any. | |
| if ((I->getOpcode() == AMDGPU::S_ENDPGM || | |
| I->getOpcode() == AMDGPU::SI_RETURN_TO_EPILOG) && | |
| !SeenDCacheWB) { | |
| Modified = true; | |
| BuildMI(*MBB, I, I->getDebugLoc(), TII.get(AMDGPU::S_DCACHE_WB)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (IsExpertMode) { | |
| // Enable expert scheduling on function entry. To satisfy ABI requirements | |
| // and to allow calls between function with different expert scheduling | |
| // settings, disable it around calls and before returns. | |
| MachineBasicBlock::iterator I = EntryBB.begin(); | |
| while (I != EntryBB.end() && I->isMetaInstruction()) | |
| ++I; | |
| setSchedulingMode(EntryBB, I, true); | |
| for (MachineInstr *MI : CallInsts) { | |
| MachineBasicBlock &MBB = *MI->getParent(); | |
| setSchedulingMode(MBB, MI, false); | |
| setSchedulingMode(MBB, std::next(MI->getIterator()), true); | |
| } | |
| for (MachineInstr *MI : ReturnInsts) | |
| setSchedulingMode(*MI->getParent(), MI, false); | |
| Modified = true; | |
| } | |
| // Deallocate the VGPRs before previously identified S_ENDPGM instructions. | |
| // This is done in different ways depending on how the VGPRs were allocated | |
| // (i.e. whether we're in dynamic VGPR mode or not). | |
| // Skip deallocation if kernel is waveslot limited vs VGPR limited. A short | |
| // waveslot limited kernel runs slower with the deallocation. | |
| if (!WCG->isOptNone() && MFI->isDynamicVGPREnabled()) { | |
| for (auto [MI, _] : EndPgmInsts) { | |
| BuildMI(*MI->getParent(), MI, MI->getDebugLoc(), | |
| TII.get(AMDGPU::S_ALLOC_VGPR)) | |
| .addImm(0); | |
| Modified = true; | |
| } | |
| } else if (!WCG->isOptNone() && | |
| ST.getGeneration() >= AMDGPUSubtarget::GFX11 && | |
| (MF.getFrameInfo().hasCalls() || | |
| ST.getOccupancyWithNumVGPRs( | |
| TRI.getNumUsedPhysRegs(MRI, AMDGPU::VGPR_32RegClass), | |
| /*IsDynamicVGPR=*/false) < | |
| AMDGPU::IsaInfo::getMaxWavesPerEU(&ST))) { | |
| for (auto [MI, Flag] : EndPgmInsts) { | |
| if (Flag) { | |
| if (ST.requiresNopBeforeDeallocVGPRs()) { | |
| BuildMI(*MI->getParent(), MI, MI->getDebugLoc(), | |
| TII.get(AMDGPU::S_NOP)) | |
| .addImm(0); | |
| } | |
| BuildMI(*MI->getParent(), MI, MI->getDebugLoc(), | |
| TII.get(AMDGPU::S_SENDMSG)) | |
| .addImm(AMDGPU::SendMsg::ID_DEALLOC_VGPRS_GFX11Plus); | |
| Modified = true; | |
| } | |
| } | |
| } | |
| return Modified; | |
| } |