blob: 93069e71dffe175139eeb4eb9cbc7c87f43a568d [file]
//===- GCNVOPDUtils.cpp - GCN VOPD Utils ------------------------===//
//
// 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 the AMDGPU DAG scheduling
/// mutation to pair VOPD instructions back to back. It also contains
// subroutines useful in the creation of VOPD instructions
//
//===----------------------------------------------------------------------===//
#include "GCNVOPDUtils.h"
#include "AMDGPUSubtarget.h"
#include "GCNSubtarget.h"
#include "MCTargetDesc/AMDGPUMCTargetDesc.h"
#include "SIInstrInfo.h"
#include "Utils/AMDGPUBaseInfo.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/MacroFusion.h"
#include "llvm/CodeGen/ScheduleDAG.h"
#include "llvm/CodeGen/ScheduleDAGMutation.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/MC/MCInst.h"
using namespace llvm;
#define DEBUG_TYPE "gcn-vopd-utils"
bool llvm::checkVOPDRegConstraints(const SIInstrInfo &TII,
const MachineInstr &MIX,
const MachineInstr &MIY, bool IsVOPD3,
bool AllowSameVGPR) {
namespace VOPD = AMDGPU::VOPD;
const MachineFunction *MF = MIX.getMF();
const GCNSubtarget &ST = MF->getSubtarget<GCNSubtarget>();
if (IsVOPD3 && !ST.hasVOPD3())
return false;
if (!IsVOPD3 && (TII.isVOP3(MIX) || TII.isVOP3(MIY)))
return false;
if (TII.isDPP(MIX) || TII.isDPP(MIY))
return false;
const SIRegisterInfo *TRI = ST.getRegisterInfo();
const MachineRegisterInfo &MRI = MF->getRegInfo();
// Literals also count against scalar bus limit
SmallVector<const MachineOperand *> UniqueLiterals;
auto addLiteral = [&](const MachineOperand &Op) {
for (auto &Literal : UniqueLiterals) {
if (Literal->isIdenticalTo(Op))
return;
}
UniqueLiterals.push_back(&Op);
};
SmallSet<Register, 4> UniqueScalarRegs;
auto getVRegIdx = [&](unsigned OpcodeIdx, unsigned OperandIdx) {
const MachineInstr &MI = (OpcodeIdx == VOPD::X) ? MIX : MIY;
const MachineOperand &Operand = MI.getOperand(OperandIdx);
if (Operand.isReg() && TRI->isVectorRegister(MRI, Operand.getReg()))
return Operand.getReg();
return Register();
};
auto InstInfo = AMDGPU::getVOPDInstInfo(MIX.getDesc(), MIY.getDesc());
for (auto CompIdx : VOPD::COMPONENTS) {
const MachineInstr &MI = (CompIdx == VOPD::X) ? MIX : MIY;
const MachineOperand &Src0 = *TII.getNamedOperand(MI, AMDGPU::OpName::src0);
if (Src0.isReg()) {
if (!TRI->isVectorRegister(MRI, Src0.getReg())) {
UniqueScalarRegs.insert(Src0.getReg());
}
} else if (!TII.isInlineConstant(Src0)) {
if (IsVOPD3)
return false;
addLiteral(Src0);
}
if (InstInfo[CompIdx].hasMandatoryLiteral()) {
if (IsVOPD3)
return false;
auto CompOprIdx = InstInfo[CompIdx].getMandatoryLiteralCompOperandIndex();
addLiteral(MI.getOperand(CompOprIdx));
}
if (MI.getDesc().hasImplicitUseOfPhysReg(AMDGPU::VCC))
UniqueScalarRegs.insert(AMDGPU::VCC_LO);
if (IsVOPD3) {
for (auto OpName : {AMDGPU::OpName::src1, AMDGPU::OpName::src2}) {
const MachineOperand *Src = TII.getNamedOperand(MI, OpName);
if (!Src)
continue;
if (OpName == AMDGPU::OpName::src2) {
if (AMDGPU::hasNamedOperand(MI.getOpcode(), AMDGPU::OpName::bitop3))
continue;
if (MI.getOpcode() == AMDGPU::V_CNDMASK_B32_e64) {
UniqueScalarRegs.insert(Src->getReg());
continue;
}
}
if (!Src->isReg() || !TRI->isVGPR(MRI, Src->getReg()))
return false;
}
for (auto OpName : {AMDGPU::OpName::clamp, AMDGPU::OpName::omod,
AMDGPU::OpName::op_sel}) {
if (TII.hasModifiersSet(MI, OpName))
return false;
}
// Neg is allowed, other modifiers are not. NB: even though sext has the
// same value as neg, there are no combinable instructions with sext.
for (auto OpName :
{AMDGPU::OpName::src0_modifiers, AMDGPU::OpName::src1_modifiers,
AMDGPU::OpName::src2_modifiers}) {
const MachineOperand *Mods = TII.getNamedOperand(MI, OpName);
if (Mods && (Mods->getImm() & ~SISrcMods::NEG))
return false;
}
}
}
if (UniqueLiterals.size() > 1)
return false;
if ((UniqueLiterals.size() + UniqueScalarRegs.size()) > 2)
return false;
// On GFX1170+ if both OpX and OpY are V_MOV_B32 then OPY uses SRC2
// source-cache.
bool SkipSrc = (ST.hasGFX11_7Insts() || ST.hasGFX12Insts()) &&
MIX.getOpcode() == AMDGPU::V_MOV_B32_e32 &&
MIY.getOpcode() == AMDGPU::V_MOV_B32_e32;
if (InstInfo.hasInvalidOperand(getVRegIdx, *TRI, SkipSrc, AllowSameVGPR,
IsVOPD3))
return false;
if (IsVOPD3) {
// BITOP3 can be converted to DUAL_BITOP2 only if src2 is zero.
// MIX check is only relevant to scheduling?
if (AMDGPU::hasNamedOperand(MIX.getOpcode(), AMDGPU::OpName::bitop3)) {
const MachineOperand &Src2 =
*TII.getNamedOperand(MIX, AMDGPU::OpName::src2);
if (!Src2.isImm() || Src2.getImm())
return false;
}
if (AMDGPU::hasNamedOperand(MIY.getOpcode(), AMDGPU::OpName::bitop3)) {
const MachineOperand &Src2 =
*TII.getNamedOperand(MIY, AMDGPU::OpName::src2);
if (!Src2.isImm() || Src2.getImm())
return false;
}
}
LLVM_DEBUG(dbgs() << "VOPD Reg Constraints Passed\n\tX: " << MIX
<< "\n\tY: " << MIY << "\n");
return true;
}
/// Core pair-eligibility check for a single VOPD encoding variant (VOPD or
/// VOPD3). Returns the X/Y assignment on success, or std::nullopt otherwise.
static std::optional<VOPDMatchInfo>
tryMatchVOPDPairVariant(const SIInstrInfo &TII, unsigned EncodingFamily,
MachineInstr &FirstMI, MachineInstr &SecondMI,
bool IsVOPD3) {
unsigned Opc = FirstMI.getOpcode();
unsigned Opc2 = SecondMI.getOpcode();
AMDGPU::CanBeVOPD FirstCanBeVOPD =
AMDGPU::getCanBeVOPD(Opc, EncodingFamily, IsVOPD3);
AMDGPU::CanBeVOPD SecondCanBeVOPD =
AMDGPU::getCanBeVOPD(Opc2, EncodingFamily, IsVOPD3);
if (!(FirstCanBeVOPD.X && SecondCanBeVOPD.Y) &&
!(FirstCanBeVOPD.Y && SecondCanBeVOPD.X))
return std::nullopt;
// If SecondMI depends on FirstMI they cannot execute at the same time.
if (TII.hasRAWDependency(FirstMI, SecondMI))
return std::nullopt;
const GCNSubtarget &ST = TII.getSubtarget();
bool AllowSameVGPR = ST.hasGFX12Insts();
if (FirstCanBeVOPD.X && SecondCanBeVOPD.Y) {
if (checkVOPDRegConstraints(TII, FirstMI, SecondMI, IsVOPD3, AllowSameVGPR))
return VOPDMatchInfo{&FirstMI, &SecondMI, IsVOPD3};
}
if (FirstCanBeVOPD.Y && SecondCanBeVOPD.X) {
// AllowSameVGPR relaxes the VGPR bank overlap check for source operands.
// Only enable it when there is no antidependency.
bool IsAntiDep = TII.hasRAWDependency(SecondMI, FirstMI);
AllowSameVGPR &= !IsAntiDep;
if (IsAntiDep && !TII.isVOPDAntidependencyAllowed(SecondMI))
return std::nullopt;
if (checkVOPDRegConstraints(TII, SecondMI, FirstMI, IsVOPD3, AllowSameVGPR))
return VOPDMatchInfo{&SecondMI, &FirstMI, IsVOPD3};
}
return std::nullopt;
}
std::optional<VOPDMatchInfo> llvm::tryMatchVOPDPair(const SIInstrInfo &TII,
MachineInstr &FirstMI,
MachineInstr &SecondMI) {
const GCNSubtarget &ST = TII.getSubtarget();
unsigned EncodingFamily = AMDGPU::getVOPDEncodingFamily(ST);
if (auto Match = tryMatchVOPDPairVariant(TII, EncodingFamily, FirstMI,
SecondMI, /*IsVOPD3=*/false))
return Match;
if (ST.hasVOPD3())
return tryMatchVOPDPairVariant(TII, EncodingFamily, FirstMI, SecondMI,
/*IsVOPD3=*/true);
return std::nullopt;
}
/// Check if the instr pair, FirstMI and SecondMI, should be scheduled
/// together. Given SecondMI, when FirstMI is unspecified, then check if
/// SecondMI may be part of a fused pair at all.
static bool shouldScheduleVOPDAdjacent(const TargetInstrInfo &TII,
const TargetSubtargetInfo &TSI,
const MachineInstr *FirstMI,
const MachineInstr &SecondMI) {
const SIInstrInfo &STII = static_cast<const SIInstrInfo &>(TII);
const GCNSubtarget &ST = STII.getSubtarget();
// One instruction case: just check whether SecondMI is eligible at all.
if (!FirstMI) {
unsigned EncodingFamily = AMDGPU::getVOPDEncodingFamily(ST);
unsigned Opc2 = SecondMI.getOpcode();
auto checkCanBeVOPD = [&](bool VOPD3) {
AMDGPU::CanBeVOPD CanBeVOPD =
AMDGPU::getCanBeVOPD(Opc2, EncodingFamily, VOPD3);
return CanBeVOPD.Y || CanBeVOPD.X;
};
return checkCanBeVOPD(false) || (ST.hasVOPD3() && checkCanBeVOPD(true));
}
#ifdef EXPENSIVE_CHECKS
assert([&]() -> bool {
for (auto MII = MachineBasicBlock::const_iterator(FirstMI);
MII != FirstMI->getParent()->instr_end(); ++MII) {
if (&*MII == &SecondMI)
return true;
}
return false;
}() && "Expected FirstMI to precede SecondMI");
#endif
return tryMatchVOPDPair(STII, *const_cast<MachineInstr *>(FirstMI),
const_cast<MachineInstr &>(SecondMI))
.has_value();
}
/// Collect all load (dependents if \p Forward else dependencies) that connect
/// to the \p Head SU.
/// \p Visited should allocate enough bits for the number of SUnits, but its
/// value can otherwise be uninitialized.
static void collectLoads(SmallPtrSet<SUnit *, 8> &Loads, BitVector &Visited,
SUnit &Head, bool Forward, bool StopAtLoads) {
if (Head.isBoundaryNode())
return;
Visited.reset();
SmallVector<SUnit *> Stack;
Stack.push_back(&Head);
while (!Stack.empty()) {
SUnit *SU = Stack.pop_back_val();
const SmallVector<SDep, 4> &Deps = Forward ? SU->Succs : SU->Preds;
for (const SDep &Edge : Deps) {
if (StopAtLoads && Edge.getKind() != SDep::Data)
continue;
SUnit *Dep = Edge.getSUnit();
if (Dep->isBoundaryNode() || Visited.test(Dep->NodeNum))
continue;
Visited.set(Dep->NodeNum);
if (Dep->isInstr() && Dep->getInstr()->mayLoad()) {
Loads.insert(Dep);
if (StopAtLoads)
continue;
}
Stack.push_back(Dep);
}
}
}
/// Checks whether fusing SU \p I with SU \p J would force the loads preceding
/// \p J to complete before loads depending on \p I.
///
/// \p ILoadSuccs should hold all first load successors of \p I (via
/// collectLoads with StopAtLoads=true). For set bits in \p LoadPredsComputed,
/// the corresponding set in \p LoadPredsCache should hold all transitive load
/// dependencies (via collectLoads with StopAtLoads=false). The \p Scratch
/// bitvector should allocate enough bits for the number of SUnits.
static bool loadsMayOverlap(
[[maybe_unused]] SUnit &I, const SmallPtrSet<SUnit *, 8> &ILoadSuccs,
SUnit &J, BitVector &LoadPredsComputed,
SmallVector<SmallPtrSet<SUnit *, 8>> &LoadPredsCache, BitVector &Scratch) {
if (ILoadSuccs.empty())
return false;
SmallPtrSet<SUnit *, 8> &JLoadPreds = LoadPredsCache[J.NodeNum];
if (!LoadPredsComputed.test(J.NodeNum)) {
collectLoads(JLoadPreds, Scratch, J, /*Forward=*/false,
/*StopAtLoads=*/true);
LoadPredsComputed.set(J.NodeNum);
}
if (JLoadPreds.empty())
return false;
for (SUnit *ILoad : ILoadSuccs) {
SmallPtrSet<SUnit *, 8> &ILoadDeps = LoadPredsCache[ILoad->NodeNum];
if (!LoadPredsComputed.test(ILoad->NodeNum)) {
collectLoads(ILoadDeps, Scratch, *ILoad, /*Forward=*/false,
/*StopAtLoads=*/false);
LoadPredsComputed.set(ILoad->NodeNum);
}
for (SUnit *JLoad : JLoadPreds) {
if (ILoad == JLoad) {
LLVM_DEBUG(
dbgs() << "Will not pair SU(" << I.NodeNum << ") with SU("
<< J.NodeNum << ")\n"
<< " Fusion would introduce a cyclic dependency with SU("
<< ILoad->NodeNum << ")\n");
return true;
}
if (!ILoadDeps.contains(JLoad)) {
LLVM_DEBUG(dbgs() << "Will not pair SU(" << I.NodeNum << ") with SU("
<< J.NodeNum << ")\n"
<< " Fusion may force SU(" << JLoad->NodeNum
<< ") to complete its load before dispatching SU("
<< ILoad->NodeNum << ")\n");
return true;
}
}
}
return false;
}
namespace {
/// Adapts design from MacroFusion
/// Puts valid candidate instructions back-to-back so they can easily
/// be turned into VOPD instructions
/// Greedily pairs instruction candidates. O(n^2) algorithm.
struct VOPDPairingMutation : ScheduleDAGMutation {
MacroFusionPredTy shouldScheduleAdjacent; // NOLINT: function pointer
VOPDPairingMutation(
MacroFusionPredTy shouldScheduleAdjacent) // NOLINT: function pointer
: shouldScheduleAdjacent(shouldScheduleAdjacent) {}
void apply(ScheduleDAGInstrs *DAG) override {
const TargetInstrInfo &TII = *DAG->TII;
const GCNSubtarget &ST = DAG->MF.getSubtarget<GCNSubtarget>();
if (!AMDGPU::hasVOPD(ST) || !ST.isWave32()) {
LLVM_DEBUG(dbgs() << "Target does not support VOPDPairingMutation\n");
return;
}
BitVector VOPDCapable(DAG->SUnits.size());
unsigned IIdx = 0;
// Pre-compute whether each individual instruction can be VOPD
for (auto ISUI = DAG->SUnits.begin(), E = DAG->SUnits.end(); ISUI != E;
++ISUI, ++IIdx) {
const MachineInstr *IMI = ISUI->getInstr();
if (shouldScheduleAdjacent(TII, ST, nullptr, *IMI) &&
hasLessThanNumFused(*ISUI, 2))
VOPDCapable[IIdx] = true;
}
IIdx = 0;
SmallPtrSet<SUnit *, 8> ILoadSuccs;
// Cache collected load predecessors.
// For VOPDCapable nodes, this caches collectLoads with StopAtLoads=true
// For loads, this caches collectLoads with StopAtLoads=false
BitVector LoadPredsComputed(DAG->SUnits.size());
SmallVector<SmallPtrSet<SUnit *, 8>> LoadPredsCache(DAG->SUnits.size());
BitVector Scratch(DAG->SUnits.size());
for (auto ISUI = DAG->SUnits.begin(), E = DAG->SUnits.end(); ISUI != E;
++ISUI, ++IIdx) {
if (!VOPDCapable[IIdx])
continue;
const MachineInstr *IMI = ISUI->getInstr();
ILoadSuccs.clear();
collectLoads(ILoadSuccs, Scratch, *ISUI, /*Forward=*/true,
/*StopAtLoads=*/true);
unsigned JIdx = IIdx + 1;
for (auto JSUI = ISUI + 1; JSUI != E; ++JSUI, ++JIdx) {
if (!VOPDCapable[JIdx] || JSUI->isBoundaryNode())
continue;
const MachineInstr *JMI = JSUI->getInstr();
if (!hasLessThanNumFused(*JSUI, 2) ||
!shouldScheduleAdjacent(TII, ST, IMI, *JMI))
continue;
if (loadsMayOverlap(*ISUI, ILoadSuccs, *JSUI, LoadPredsComputed,
LoadPredsCache, Scratch))
continue;
if (fuseInstructionPair(*DAG, *ISUI, *JSUI)) {
// Clear to prevent future checks/fusing
VOPDCapable[JIdx] = false;
break;
}
}
}
LLVM_DEBUG(dbgs() << "Completed VOPDPairingMutation\n");
}
};
} // namespace
std::unique_ptr<ScheduleDAGMutation> llvm::createVOPDPairingMutation() {
return std::make_unique<VOPDPairingMutation>(shouldScheduleVOPDAdjacent);
}