| //===--- AMDGPUBarrierLatency.cpp - AMDGPU Barrier Latency ----------------===// | |
| // | |
| // 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 This file contains a DAG scheduling mutation to add latency to: | |
| /// 1. Barrier edges between ATOMIC_FENCE instructions and preceding | |
| /// memory accesses potentially affected by the fence. | |
| /// This encourages the scheduling of more instructions before | |
| /// ATOMIC_FENCE instructions. ATOMIC_FENCE instructions may | |
| /// introduce wait counting or indicate an impending S_BARRIER | |
| /// wait. Having more instructions in-flight across these | |
| /// constructs improves latency hiding. | |
| /// 2. Barrier edges from S_BARRIER_SIGNAL to S_BARRIER_WAIT. | |
| /// This encourages independent work to be scheduled between | |
| /// signal and wait, hiding barrier synchronization latency. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "AMDGPUBarrierLatency.h" | |
| #include "GCNSubtarget.h" | |
| #include "SIInstrInfo.h" | |
| #include "llvm/CodeGen/ScheduleDAGInstrs.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/TargetParser/AtomicScope.h" | |
| using namespace llvm; | |
| static cl::opt<unsigned> BarrierSignalWaitLatencyOpt( | |
| "amdgpu-barrier-signal-wait-latency", | |
| cl::desc("Synthetic latency between S_BARRIER_SIGNAL and S_BARRIER_WAIT " | |
| "to encourage scheduling independent work between them"), | |
| cl::init(16), cl::Hidden); | |
| namespace { | |
| class BarrierLatency : public ScheduleDAGMutation { | |
| private: | |
| SmallSet<SyncScope::ID, 4> IgnoredScopes; | |
| public: | |
| BarrierLatency(MachineFunction *MF) { | |
| LLVMContext &Context = MF->getFunction().getContext(); | |
| const Triple &TT = MF->getSubtarget<GCNSubtarget>().getTargetTriple(); | |
| auto ScopeID = [&](AtomicScope Scope, bool OneAS) { | |
| return Context.getOrInsertSyncScopeID( | |
| *getAtomicScopeIRString(TT, Scope, OneAS)); | |
| }; | |
| IgnoredScopes.insert(SyncScope::SingleThread); | |
| IgnoredScopes.insert(ScopeID(AtomicScope::Wavefront, /*OneAS=*/false)); | |
| IgnoredScopes.insert(ScopeID(AtomicScope::Wavefront, /*OneAS=*/true)); | |
| IgnoredScopes.insert(ScopeID(AtomicScope::Single, /*OneAS=*/true)); | |
| const GCNSubtarget &ST = MF->getSubtarget<GCNSubtarget>(); | |
| bool TgSplit = | |
| ST.hasTgSplitSupport() && AMDGPU::isTgSplitEnabled(MF->getFunction()); | |
| if (!ST.requiresWaitOnWorkgroupReleaseFence(TgSplit)) { | |
| // Prior to GFX10 workgroup scope does not normally require waitcnts | |
| IgnoredScopes.insert(ScopeID(AtomicScope::Workgroup, /*OneAS=*/false)); | |
| } | |
| } | |
| void apply(ScheduleDAGInstrs *DAG) override; | |
| }; | |
| void addLatencyToEdge(SDep &PredDep, SUnit &SU, unsigned Latency) { | |
| SUnit *PredSU = PredDep.getSUnit(); | |
| SDep ForwardD = PredDep; | |
| ForwardD.setSUnit(&SU); | |
| for (SDep &SuccDep : PredSU->Succs) { | |
| if (SuccDep == ForwardD) { | |
| SuccDep.setLatency(SuccDep.getLatency() + Latency); | |
| break; | |
| } | |
| } | |
| PredDep.setLatency(PredDep.getLatency() + Latency); | |
| PredSU->setDepthDirty(); | |
| SU.setDepthDirty(); | |
| } | |
| void setLatencyForEdge(SDep &PredDep, SUnit &SU, unsigned Latency) { | |
| SUnit *PredSU = PredDep.getSUnit(); | |
| SDep ForwardD = PredDep; | |
| ForwardD.setSUnit(&SU); | |
| for (SDep &SuccDep : PredSU->Succs) { | |
| if (SuccDep == ForwardD) { | |
| SuccDep.setLatency(Latency); | |
| break; | |
| } | |
| } | |
| PredDep.setLatency(Latency); | |
| PredSU->setDepthDirty(); | |
| SU.setDepthDirty(); | |
| } | |
| void BarrierLatency::apply(ScheduleDAGInstrs *DAG) { | |
| const SIInstrInfo *TII = static_cast<const SIInstrInfo *>(DAG->TII); | |
| constexpr unsigned FenceLatency = 2000; | |
| const unsigned BarrierSignalWaitLatency = BarrierSignalWaitLatencyOpt; | |
| SmallVector<SUnit *, 8> RegionTDM; | |
| SmallVector<SUnit *, 8> RegionAsync; | |
| const TargetSchedModel *SchedModel = DAG->getSchedModel(); | |
| for (SUnit &SU : DAG->SUnits) { | |
| const MachineInstr *MI = SU.getInstr(); | |
| unsigned Op = MI->getOpcode(); | |
| if (Op == AMDGPU::ATOMIC_FENCE) { | |
| // Update latency on barrier edges of ATOMIC_FENCE. | |
| // Ignore scopes not expected to have any latency. | |
| SyncScope::ID SSID = | |
| static_cast<SyncScope::ID>(MI->getOperand(1).getImm()); | |
| if (IgnoredScopes.contains(SSID)) | |
| continue; | |
| for (SDep &PredDep : SU.Preds) { | |
| if (!PredDep.isBarrier()) | |
| continue; | |
| SUnit *PredSU = PredDep.getSUnit(); | |
| MachineInstr *MI = PredSU->getInstr(); | |
| // Only consider memory loads | |
| if (!MI->mayLoad() || MI->mayStore()) | |
| continue; | |
| addLatencyToEdge(PredDep, SU, | |
| SchedModel ? SchedModel->computeInstrLatency(MI, false) | |
| : FenceLatency); | |
| } | |
| } else if (Op == AMDGPU::S_BARRIER_WAIT) { | |
| for (SDep &PredDep : SU.Preds) { | |
| SUnit *PredSU = PredDep.getSUnit(); | |
| const MachineInstr *PredMI = PredSU->getInstr(); | |
| if (TII->isBarrierStart(PredMI->getOpcode())) { | |
| addLatencyToEdge(PredDep, SU, BarrierSignalWaitLatency); | |
| } | |
| } | |
| } else if (TII->isLDSDMA(*MI)) { | |
| if (SIInstrFlags::usesTENSOR_CNT(*MI)) | |
| RegionTDM.push_back(&SU); | |
| else if (SIInstrFlags::usesASYNC_CNT(*MI)) | |
| RegionAsync.push_back(&SU); | |
| } else if (Op == AMDGPU::S_WAIT_TENSORCNT || | |
| Op == AMDGPU::S_WAIT_ASYNCCNT) { | |
| auto needWaitFor = [&](SmallVectorImpl<SUnit *> &RegionLDSDMA, SUnit *SU, | |
| int64_t Count) { | |
| if (RegionLDSDMA.size() <= static_cast<uint64_t>(Count)) { | |
| return false; | |
| } | |
| int64_t Counter = 0; | |
| auto I = RegionLDSDMA.rbegin(), E = RegionLDSDMA.rend(); | |
| for (; I != E; I++) { | |
| if (Counter >= Count) | |
| return true; | |
| if (SU->NodeNum == (*I)->NodeNum) | |
| return false; | |
| ++Counter; | |
| } | |
| llvm_unreachable("Malformed RegionLDSDMA"); | |
| }; | |
| int64_t WaitVal = MI->getOperand(0).getImm(); | |
| for (SDep &PredDep : SU.Preds) { | |
| if (PredDep.getKind() != SDep::Kind::Data) | |
| continue; | |
| Register DepReg = PredDep.getReg(); | |
| bool IsAsync = Op == AMDGPU::S_WAIT_ASYNCCNT; | |
| Register LDSDMACnt = IsAsync ? AMDGPU::ASYNCcnt : AMDGPU::TENSORcnt; | |
| if (DepReg != LDSDMACnt) | |
| continue; | |
| SUnit *PredSU = PredDep.getSUnit(); | |
| // The data dep can be carried by a non-LDSDMA SU | |
| // (e.g. an intervening COPY or pseudo). Such predecessors are not | |
| // tracked, so needWaitFor cannot reason about them. | |
| const MachineInstr &PredMI = *PredSU->getInstr(); | |
| if (IsAsync ? !SIInstrFlags::usesASYNC_CNT(PredMI) | |
| : !SIInstrFlags::usesTENSOR_CNT(PredMI)) | |
| continue; | |
| if (!needWaitFor(Op == AMDGPU::S_WAIT_ASYNCCNT ? RegionAsync | |
| : RegionTDM, | |
| PredSU, WaitVal)) { | |
| setLatencyForEdge(PredDep, SU, 1); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } // end namespace | |
| std::unique_ptr<ScheduleDAGMutation> | |
| llvm::createAMDGPUBarrierLatencyDAGMutation(MachineFunction *MF) { | |
| return std::make_unique<BarrierLatency>(MF); | |
| } |