[llvm] [AMDGPU] Sink async DMA out of s_cbranch_execz then-blocks (PR #196374)
Matt Arsenault via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 18 04:46:30 PDT 2026
================
@@ -0,0 +1,229 @@
+//===-- 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, so fully-masked waves
+/// skip the DMA entirely and the ASYNCcnt observed at the join depends on
+/// whether the wave took the branch. Software-pipelined kernels then have to
+/// use a conservative async waitcnt.
+///
+/// This pass sinks each such DMA into the join, immediately before SI_ELSE or
+/// SI_END_CF:
+///
+/// MBB MBB SI_IF sets EXEC to the then-block
+/// / \ / \ mask before the branch, so both
+/// ThenBB | ThenBB | edges carry it and per-lane
+/// [DMA] | ==> \ / behavior is unchanged. But every
+/// \ / JoinBB wave now issues the DMA, so
+/// JoinBB [DMA] ASYNCcnt at the join no longer
+/// [SI_END_CF] | depends on the branch.
+/// [SI_END_CF]
+///
+/// The join is split so that SI_END_CF starts a block of its own, because
+/// SILowerControlFlow emits the EXEC restore at the top of the block holding
+/// it, which would otherwise place it above the sunk DMAs.
+
+//
+//===----------------------------------------------------------------------===//
+
+#include "SISinkAsyncDMA.h"
+#include "AMDGPU.h"
+#include "GCNSubtarget.h"
+#include "MCTargetDesc/AMDGPUMCTargetDesc.h"
+#include "SIInstrInfo.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineSSAUpdater.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;
+
+ bool sinkFromBlock(MachineBasicBlock &MBB);
+
+public:
+ 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";
+ }
+
+ MachineFunctionProperties getRequiredProperties() const override {
+ return MachineFunctionProperties().setIsSSA();
+ }
+
+ MachineFunctionProperties getClearedProperties() const override {
+ return MachineFunctionProperties().setNoPHIs();
+ }
+};
+
+} // 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;
+
+static bool isAsyncDMA(const MachineInstr &MI) {
+ return SIInstrInfo::isLDSDMA(MI) && SIInstrInfo::usesASYNC_CNT(MI);
+}
+
+static bool isAsyncMarker(const MachineInstr &MI) {
+ return MI.getOpcode() == AMDGPU::ASYNCMARK ||
+ MI.getOpcode() == AMDGPU::WAIT_ASYNCMARK;
+}
+
+bool SISinkAsyncDMA::sinkFromBlock(MachineBasicBlock &MBB) {
+ if (MBB.succ_size() != 2)
+ return false;
+
+ // A region head ends in SI_IF or SI_ELSE ($dst, $cond, $target), which define
+ // the mask the region restores in $dst and the join block in $target.
+ auto ControlMI = MBB.getFirstTerminator();
+ if (ControlMI == MBB.end() || (ControlMI->getOpcode() != AMDGPU::SI_IF &&
+ ControlMI->getOpcode() != AMDGPU::SI_ELSE))
+ return false;
+
+ Register SavedExec = ControlMI->getOperand(0).getReg();
+ MachineBasicBlock *JoinBB = ControlMI->getOperand(2).getMBB();
+
+ if (!MBB.isSuccessor(JoinBB) || JoinBB->pred_size() != 2)
+ return false;
+
+ auto ThenIt = find_if(MBB.successors(),
+ [JoinBB](MachineBasicBlock *S) { return S != JoinBB; });
+ if (ThenIt == MBB.succ_end() || (*ThenIt)->getSingleSuccessor() != JoinBB)
+ return false;
+ MachineBasicBlock *ThenBB = *ThenIt;
+
+ auto Boundary = JoinBB->getFirstNonPHI();
+ while (Boundary != JoinBB->end() && Boundary->isMetaInstruction())
+ ++Boundary;
+ if (Boundary == JoinBB->end())
+ return false;
----------------
arsenm wrote:
Manual control flow parsing is annoying, can this use AnalyzeBranch?
https://github.com/llvm/llvm-project/pull/196374
More information about the llvm-commits
mailing list