[llvm] [AMDGPU] Sink async DMA out of s_cbranch_execz then-blocks (PR #196374)
Juan Manuel Martinez CaamaƱo via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 31 01:08:00 PDT 2026
================
@@ -0,0 +1,253 @@
+//===-- SISinkAsyncDMA.cpp - Sink async DMA out of execz then-blocks ------===//
+//
+// 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
+/// LLVM lowers a divergent branch around global_load_async_to_lds /
+/// global_store_async_from_lds with an S_CBRANCH_EXECZ. Fully-masked waves
+/// therefore skip the DMA entirely, which makes the ASYNCcnt observed at the
+/// join point depend on whether the wave took the branch. Software-pipelined
+/// kernels must then use conservative async waitcnts.
+///
+/// This pass sinks each async DMA out of such a then-block into the join
+/// block, immediately before the EXEC-mask restore (the S_OR that ends the
+/// control-flow region). EXEC at the sunk slot is the same masked value that
+/// guarded the then-block, so per-lane behavior is unchanged, but every wave
+/// now issues the DMA and ASYNCcnt becomes deterministic.
+///
+/// This runs right after SILowerControlFlow so the EXEC restore anchor exists,
+/// and before waitcnt insertion so the improved counts can be used.
+///
+/// This is determinism-only: it never relaxes or rewrites a wait (an
+/// s_wait_asynccnt 0 stays 0). Correctness never depends on the transform
+/// firing; with the pass disabled the code is still correct, just conservative.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SISinkAsyncDMA.h"
+#include "AMDGPU.h"
+#include "AMDGPULaneMaskUtils.h"
+#include "GCNSubtarget.h"
+#include "MCTargetDesc/AMDGPUMCTargetDesc.h"
+#include "SIInstrInfo.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/CodeGen/LiveVariables.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "si-sink-async-dma"
+
+namespace {
+
+class SISinkAsyncDMA {
+ const SIInstrInfo *TII = nullptr;
+ const SIRegisterInfo *TRI = nullptr;
+ MachineRegisterInfo *MRI = nullptr;
+ LiveVariables *LV = nullptr;
+ const AMDGPU::LaneMaskConstants &LMC;
+
+ bool sinkFromBlock(MachineBasicBlock &MBB);
+
+public:
+ SISinkAsyncDMA(const GCNSubtarget *ST, LiveVariables *LV)
+ : TII(ST->getInstrInfo()), TRI(&TII->getRegisterInfo()), LV(LV),
+ LMC(AMDGPU::LaneMaskConstants::get(*ST)) {}
+
+ bool run(MachineFunction &MF);
+};
+
+class SISinkAsyncDMALegacy : public MachineFunctionPass {
+public:
+ static char ID;
+
+ SISinkAsyncDMALegacy() : MachineFunctionPass(ID) {}
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+
+ StringRef getPassName() const override {
+ return "SI sink async DMA out of execz then-blocks";
+ }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesCFG();
+ AU.addUsedIfAvailable<LiveVariablesWrapperPass>();
+ AU.addPreserved<LiveVariablesWrapperPass>();
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+};
+
+} // namespace
+
+char SISinkAsyncDMALegacy::ID = 0;
+
+INITIALIZE_PASS(SISinkAsyncDMALegacy, DEBUG_TYPE,
+ "SI sink async DMA out of execz then-blocks", false, false)
+
+char &llvm::SISinkAsyncDMALegacyID = SISinkAsyncDMALegacy::ID;
+
+/// Return the S_OR EXEC restore at the top of \p JoinBB, if present.
+static MachineInstr *findExecRestore(MachineBasicBlock &JoinBB,
+ const AMDGPU::LaneMaskConstants &LMC) {
+ auto I = JoinBB.getFirstNonDebugInstr();
+ return I != JoinBB.end() && I->getOpcode() == LMC.OrOpc &&
+ I->getOperand(0).getReg() == LMC.ExecReg &&
+ I->getOperand(1).getReg() == LMC.ExecReg
+ ? &*I
+ : nullptr;
+}
+
+static bool isAsyncDMA(const MachineInstr &MI) {
+ return SIInstrInfo::isLDSDMA(MI) && SIInstrInfo::usesASYNC_CNT(MI);
+}
+
+bool SISinkAsyncDMA::sinkFromBlock(MachineBasicBlock &MBB) {
+ if (MBB.succ_size() != 2)
+ return false;
+
+ MachineBasicBlock *TBB = nullptr;
+ MachineBasicBlock *FBB = nullptr;
+ SmallVector<MachineOperand, 4> Cond;
+ if (TII->analyzeBranch(MBB, TBB, FBB, Cond) || !TBB || Cond.empty())
+ return false;
+
+ auto CondBr = find_if(MBB.terminators(), [](const MachineInstr &MI) {
+ return MI.isConditionalBranch();
+ });
+ if (CondBr == MBB.terminators().end() ||
+ CondBr->getOpcode() != AMDGPU::S_CBRANCH_EXECZ)
+ return false;
+
+ MachineBasicBlock *JoinBB = TBB;
+ MachineBasicBlock *S0 = *MBB.succ_begin();
+ MachineBasicBlock *S1 = *std::next(MBB.succ_begin());
+ MachineBasicBlock *ThenBB = (S0 == JoinBB) ? S1 : S0;
+ if (ThenBB == JoinBB || ThenBB->succ_size() != 1 ||
+ *ThenBB->succ_begin() != JoinBB)
+ return false;
+
+ // A third incoming edge could reach the DMA under an unrelated EXEC.
+ if (JoinBB->pred_size() != 2)
+ return false;
+
+ MachineInstr *ExecRestore = findExecRestore(*JoinBB, LMC);
+ if (!ExecRestore)
+ return false;
+
+ SmallVector<MachineInstr *, 4> ToSink;
+ for (MachineInstr &TMI : *ThenBB) {
+ if (TMI.isMetaInstruction() || TMI.isTerminator())
+ continue;
+ if (TII->hasUnwantedEffectsWhenEXECEmpty(TMI))
+ return false;
+ if (isAsyncDMA(TMI)) {
+ ToSink.push_back(&TMI);
+ continue;
+ }
+ if (TMI.modifiesRegister(AMDGPU::EXEC, TRI) ||
+ TMI.hasUnmodeledSideEffects())
+ return false;
+
+ if (ToSink.empty())
+ continue;
+ if (TMI.mayStore())
+ return false;
+ if (TMI.mayLoad() && !TMI.isDereferenceableInvariantLoad())
+ return false;
+
+ // Reject dependencies with earlier DMAs that would cross this instruction.
+ for (const MachineOperand &MO : TMI.operands()) {
+ if (!MO.isReg() || !MO.getReg() || (!MO.isDef() && !MO.readsReg()) ||
+ TRI->regsOverlap(MO.getReg(), AMDGPU::EXEC))
+ continue;
+ if (any_of(ToSink, [&](const MachineInstr *DmaMI) {
+ return DmaMI->readsRegister(MO.getReg(), TRI) ||
+ DmaMI->modifiesRegister(MO.getReg(), TRI);
+ }))
----------------
jmmartinez wrote:
You could put all these Read/Write registers in a set?
https://github.com/llvm/llvm-project/pull/196374
More information about the llvm-commits
mailing list