| //===-- R600MachineScheduler.cpp - R600 Scheduler Interface -*- C++ -*-----===// |
| // |
| // 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 |
| /// R600 Machine Scheduler interface |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "R600MachineScheduler.h" |
| #include "MCTargetDesc/R600MCTargetDesc.h" |
| #include "R600Subtarget.h" |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "machine-scheduler" |
| |
| void R600SchedStrategy::initialize(ScheduleDAGMI *dag) { |
| assert(dag->hasVRegLiveness() && "R600SchedStrategy needs vreg liveness"); |
| DAG = static_cast<ScheduleDAGMILive*>(dag); |
| const R600Subtarget &ST = DAG->MF.getSubtarget<R600Subtarget>(); |
| TII = static_cast<const R600InstrInfo*>(DAG->TII); |
| TRI = static_cast<const R600RegisterInfo*>(DAG->TRI); |
| VLIW5 = !ST.hasCaymanISA(); |
| MRI = &DAG->MRI; |
| CurInstKind = IDOther; |
| CurEmitted = 0; |
| OccupiedSlotsMask = 31; |
| InstKindLimit[IDAlu] = TII->getMaxAlusPerClause(); |
| InstKindLimit[IDOther] = 32; |
| InstKindLimit[IDFetch] = ST.getTexVTXClauseSize(); |
| AluInstCount = 0; |
| FetchInstCount = 0; |
| } |
| |
| void R600SchedStrategy::MoveUnits(std::vector<SUnit *> &QSrc, |
| std::vector<SUnit *> &QDst) |
| { |
| llvm::append_range(QDst, QSrc); |
| QSrc.clear(); |
| } |
| |
| static unsigned getWFCountLimitedByGPR(unsigned GPRCount) { |
| assert (GPRCount && "GPRCount cannot be 0"); |
| return 248 / GPRCount; |
| } |
| |
| SUnit* R600SchedStrategy::pickNode(bool &IsTopNode) { |
| SUnit *SU = nullptr; |
| NextInstKind = IDOther; |
| |
| IsTopNode = false; |
| |
| // check if we might want to switch current clause type |
| bool AllowSwitchToAlu = (CurEmitted >= InstKindLimit[CurInstKind]) || |
| (Available[CurInstKind].empty()); |
| bool AllowSwitchFromAlu = (CurEmitted >= InstKindLimit[CurInstKind]) && |
| (!Available[IDFetch].empty() || !Available[IDOther].empty()); |
| |
| if (CurInstKind == IDAlu && !Available[IDFetch].empty()) { |
| // We use the heuristic provided by AMD Accelerated Parallel Processing |
| // OpenCL Programming Guide : |
| // The approx. number of WF that allows TEX inst to hide ALU inst is : |
| // 500 (cycles for TEX) / (AluFetchRatio * 8 (cycles for ALU)) |
| float ALUFetchRationEstimate = |
| (AluInstCount + AvailablesAluCount() + Pending[IDAlu].size()) / |
| (FetchInstCount + Available[IDFetch].size()); |
| if (ALUFetchRationEstimate == 0) { |
| AllowSwitchFromAlu = true; |
| } else { |
| unsigned NeededWF = 62.5f / ALUFetchRationEstimate; |
| LLVM_DEBUG(dbgs() << NeededWF << " approx. Wavefronts Required\n"); |
| // We assume the local GPR requirements to be "dominated" by the requirement |
| // of the TEX clause (which consumes 128 bits regs) ; ALU inst before and |
| // after TEX are indeed likely to consume or generate values from/for the |
| // TEX clause. |
| // Available[IDFetch].size() * 2 : GPRs required in the Fetch clause |
| // We assume that fetch instructions are either TnXYZW = TEX TnXYZW (need |
| // one GPR) or TmXYZW = TnXYZW (need 2 GPR). |
| // (TODO : use RegisterPressure) |
| // If we are going too use too many GPR, we flush Fetch instruction to lower |
| // register pressure on 128 bits regs. |
| unsigned NearRegisterRequirement = 2 * Available[IDFetch].size(); |
| if (NeededWF > getWFCountLimitedByGPR(NearRegisterRequirement)) |
| AllowSwitchFromAlu = true; |
| } |
| } |
| |
| if (!SU && ((AllowSwitchToAlu && CurInstKind != IDAlu) || |
| (!AllowSwitchFromAlu && CurInstKind == IDAlu))) { |
| // try to pick ALU |
| SU = pickAlu(); |
| if (!SU && !PhysicalRegCopy.empty()) { |
| SU = PhysicalRegCopy.front(); |
| PhysicalRegCopy.erase(PhysicalRegCopy.begin()); |
| } |
| if (SU) { |
| if (CurEmitted >= InstKindLimit[IDAlu]) |
| CurEmitted = 0; |
| NextInstKind = IDAlu; |
| } |
| } |
| |
| if (!SU) { |
| // try to pick FETCH |
| SU = pickOther(IDFetch); |
| if (SU) |
| NextInstKind = IDFetch; |
| } |
| |
| // try to pick other |
| if (!SU) { |
| SU = pickOther(IDOther); |
| if (SU) |
| NextInstKind = IDOther; |
| } |
| |
| LLVM_DEBUG(if (SU) { |
| dbgs() << " ** Pick node **\n"; |
| DAG->dumpNode(*SU); |
| } else { |
| dbgs() << "NO NODE \n"; |
| for (const SUnit &S : DAG->SUnits) |
| if (!S.isScheduled) |
| DAG->dumpNode(S); |
| }); |
| |
| return SU; |
| } |
| |
| void R600SchedStrategy::schedNode(SUnit *SU, bool IsTopNode) { |
| if (NextInstKind != CurInstKind) { |
| LLVM_DEBUG(dbgs() << "Instruction Type Switch\n"); |
| if (NextInstKind != IDAlu) |
| OccupiedSlotsMask |= 31; |
| CurEmitted = 0; |
| CurInstKind = NextInstKind; |
| } |
| |
| if (CurInstKind == IDAlu) { |
| AluInstCount ++; |
| switch (getAluKind(SU)) { |
| case AluT_XYZW: |
| CurEmitted += 4; |
| break; |
| case AluDiscarded: |
| break; |
| default: { |
| ++CurEmitted; |
| for (MachineInstr::mop_iterator It = SU->getInstr()->operands_begin(), |
| E = SU->getInstr()->operands_end(); It != E; ++It) { |
| MachineOperand &MO = *It; |
| if (MO.isReg() && MO.getReg() == R600::ALU_LITERAL_X) |
| ++CurEmitted; |
| } |
| } |
| } |
| } else { |
| ++CurEmitted; |
| } |
| |
| LLVM_DEBUG(dbgs() << CurEmitted << " Instructions Emitted in this clause\n"); |
| |
| if (CurInstKind != IDFetch) { |
| MoveUnits(Pending[IDFetch], Available[IDFetch]); |
| } else |
| FetchInstCount++; |
| } |
| |
| static bool |
| isPhysicalRegCopy(MachineInstr *MI) { |
| if (MI->getOpcode() != R600::COPY) |
| return false; |
| |
| return !MI->getOperand(1).getReg().isVirtual(); |
| } |
| |
| void R600SchedStrategy::releaseTopNode(SUnit *SU) { |
| LLVM_DEBUG(dbgs() << "Top Releasing "; DAG->dumpNode(*SU)); |
| } |
| |
| void R600SchedStrategy::releaseBottomNode(SUnit *SU) { |
| LLVM_DEBUG(dbgs() << "Bottom Releasing "; DAG->dumpNode(*SU)); |
| if (isPhysicalRegCopy(SU->getInstr())) { |
| PhysicalRegCopy.push_back(SU); |
| return; |
| } |
| |
| int IK = getInstKind(SU); |
| |
| // There is no export clause, we can schedule one as soon as its ready |
| if (IK == IDOther) |
| Available[IDOther].push_back(SU); |
| else |
| Pending[IK].push_back(SU); |
| |
| } |
| |
| bool R600SchedStrategy::regBelongsToClass(Register Reg, |
| const TargetRegisterClass *RC) const { |
| if (!Reg.isVirtual()) { |
| return RC->contains(Reg); |
| } else { |
| return MRI->getRegClass(Reg) == RC; |
| } |
| } |
| |
| R600SchedStrategy::AluKind R600SchedStrategy::getAluKind(SUnit *SU) const { |
| MachineInstr *MI = SU->getInstr(); |
| |
| if (TII->isTransOnly(*MI)) |
| return AluTrans; |
| |
| switch (MI->getOpcode()) { |
| case R600::PRED_X: |
| return AluPredX; |
| case R600::INTERP_PAIR_XY: |
| case R600::INTERP_PAIR_ZW: |
| case R600::INTERP_VEC_LOAD: |
| case R600::DOT_4: |
| return AluT_XYZW; |
| case R600::COPY: |
| if (MI->getOperand(1).isUndef()) { |
| // MI will become a KILL, don't considers it in scheduling |
| return AluDiscarded; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| // Does the instruction take a whole IG ? |
| // XXX: Is it possible to add a helper function in R600InstrInfo that can |
| // be used here and in R600PacketizerList::isSoloInstruction() ? |
| if(TII->isVector(*MI) || |
| TII->isCubeOp(MI->getOpcode()) || |
| TII->isReductionOp(MI->getOpcode()) || |
| MI->getOpcode() == R600::GROUP_BARRIER) { |
| return AluT_XYZW; |
| } |
| |
| if (TII->isLDSInstr(MI->getOpcode())) { |
| return AluT_X; |
| } |
| |
| // Is the result already assigned to a channel ? |
| unsigned DestSubReg = MI->getOperand(0).getSubReg(); |
| switch (DestSubReg) { |
| case R600::sub0: |
| return AluT_X; |
| case R600::sub1: |
| return AluT_Y; |
| case R600::sub2: |
| return AluT_Z; |
| case R600::sub3: |
| return AluT_W; |
| default: |
| break; |
| } |
| |
| // Is the result already member of a X/Y/Z/W class ? |
| Register DestReg = MI->getOperand(0).getReg(); |
| if (regBelongsToClass(DestReg, &R600::R600_TReg32_XRegClass) || |
| regBelongsToClass(DestReg, &R600::R600_AddrRegClass)) |
| return AluT_X; |
| if (regBelongsToClass(DestReg, &R600::R600_TReg32_YRegClass)) |
| return AluT_Y; |
| if (regBelongsToClass(DestReg, &R600::R600_TReg32_ZRegClass)) |
| return AluT_Z; |
| if (regBelongsToClass(DestReg, &R600::R600_TReg32_WRegClass)) |
| return AluT_W; |
| if (regBelongsToClass(DestReg, &R600::R600_Reg128RegClass)) |
| return AluT_XYZW; |
| |
| // LDS src registers cannot be used in the Trans slot. |
| if (TII->readsLDSSrcReg(*MI)) |
| return AluT_XYZW; |
| |
| return AluAny; |
| } |
| |
| int R600SchedStrategy::getInstKind(SUnit* SU) { |
| int Opcode = SU->getInstr()->getOpcode(); |
| |
| if (TII->usesTextureCache(Opcode) || TII->usesVertexCache(Opcode)) |
| return IDFetch; |
| |
| if (TII->isALUInstr(Opcode)) { |
| return IDAlu; |
| } |
| |
| switch (Opcode) { |
| case R600::PRED_X: |
| case R600::COPY: |
| case R600::CONST_COPY: |
| case R600::INTERP_PAIR_XY: |
| case R600::INTERP_PAIR_ZW: |
| case R600::INTERP_VEC_LOAD: |
| case R600::DOT_4: |
| return IDAlu; |
| default: |
| return IDOther; |
| } |
| } |
| |
| SUnit *R600SchedStrategy::PopInst(std::vector<SUnit *> &Q, bool AnyALU) { |
| if (Q.empty()) |
| return nullptr; |
| for (std::vector<SUnit *>::reverse_iterator It = Q.rbegin(), E = Q.rend(); |
| It != E; ++It) { |
| SUnit *SU = *It; |
| InstructionsGroupCandidate.push_back(SU->getInstr()); |
| if (TII->fitsConstReadLimitations(InstructionsGroupCandidate) && |
| (!AnyALU || !TII->isVectorOnly(*SU->getInstr()))) { |
| InstructionsGroupCandidate.pop_back(); |
| Q.erase((It + 1).base()); |
| return SU; |
| } else { |
| InstructionsGroupCandidate.pop_back(); |
| } |
| } |
| return nullptr; |
| } |
| |
| void R600SchedStrategy::LoadAlu() { |
| std::vector<SUnit *> &QSrc = Pending[IDAlu]; |
| for (unsigned i = 0, e = QSrc.size(); i < e; ++i) { |
| AluKind AK = getAluKind(QSrc[i]); |
| AvailableAlus[AK].push_back(QSrc[i]); |
| } |
| QSrc.clear(); |
| } |
| |
| void R600SchedStrategy::PrepareNextSlot() { |
| LLVM_DEBUG(dbgs() << "New Slot\n"); |
| assert(OccupiedSlotsMask && "Slot wasn't filled"); |
| OccupiedSlotsMask = 0; |
| // if (HwGen == AMDGPUSubtarget::NORTHERN_ISLANDS) |
| // OccupiedSlotsMask |= 16; |
| InstructionsGroupCandidate.clear(); |
| LoadAlu(); |
| } |
| |
| void R600SchedStrategy::AssignSlot(MachineInstr* MI, unsigned Slot) { |
| int DstIndex = TII->getOperandIdx(MI->getOpcode(), R600::OpName::dst); |
| if (DstIndex == -1) { |
| return; |
| } |
| Register DestReg = MI->getOperand(DstIndex).getReg(); |
| // PressureRegister crashes if an operand is def and used in the same inst |
| // and we try to constraint its regclass |
| for (MachineInstr::mop_iterator It = MI->operands_begin(), |
| E = MI->operands_end(); It != E; ++It) { |
| MachineOperand &MO = *It; |
| if (MO.isReg() && !MO.isDef() && |
| MO.getReg() == DestReg) |
| return; |
| } |
| // Constrains the regclass of DestReg to assign it to Slot |
| switch (Slot) { |
| case 0: |
| MRI->constrainRegClass(DestReg, &R600::R600_TReg32_XRegClass); |
| break; |
| case 1: |
| MRI->constrainRegClass(DestReg, &R600::R600_TReg32_YRegClass); |
| break; |
| case 2: |
| MRI->constrainRegClass(DestReg, &R600::R600_TReg32_ZRegClass); |
| break; |
| case 3: |
| MRI->constrainRegClass(DestReg, &R600::R600_TReg32_WRegClass); |
| break; |
| } |
| } |
| |
| SUnit *R600SchedStrategy::AttemptFillSlot(unsigned Slot, bool AnyAlu) { |
| static const AluKind IndexToID[] = {AluT_X, AluT_Y, AluT_Z, AluT_W}; |
| SUnit *SlotedSU = PopInst(AvailableAlus[IndexToID[Slot]], AnyAlu); |
| if (SlotedSU) |
| return SlotedSU; |
| SUnit *UnslotedSU = PopInst(AvailableAlus[AluAny], AnyAlu); |
| if (UnslotedSU) |
| AssignSlot(UnslotedSU->getInstr(), Slot); |
| return UnslotedSU; |
| } |
| |
| unsigned R600SchedStrategy::AvailablesAluCount() const { |
| return AvailableAlus[AluAny].size() + AvailableAlus[AluT_XYZW].size() + |
| AvailableAlus[AluT_X].size() + AvailableAlus[AluT_Y].size() + |
| AvailableAlus[AluT_Z].size() + AvailableAlus[AluT_W].size() + |
| AvailableAlus[AluTrans].size() + AvailableAlus[AluDiscarded].size() + |
| AvailableAlus[AluPredX].size(); |
| } |
| |
| SUnit* R600SchedStrategy::pickAlu() { |
| while (AvailablesAluCount() || !Pending[IDAlu].empty()) { |
| if (!OccupiedSlotsMask) { |
| // Bottom up scheduling : predX must comes first |
| if (!AvailableAlus[AluPredX].empty()) { |
| OccupiedSlotsMask |= 31; |
| return PopInst(AvailableAlus[AluPredX], false); |
| } |
| // Flush physical reg copies (RA will discard them) |
| if (!AvailableAlus[AluDiscarded].empty()) { |
| OccupiedSlotsMask |= 31; |
| return PopInst(AvailableAlus[AluDiscarded], false); |
| } |
| // If there is a T_XYZW alu available, use it |
| if (!AvailableAlus[AluT_XYZW].empty()) { |
| OccupiedSlotsMask |= 15; |
| return PopInst(AvailableAlus[AluT_XYZW], false); |
| } |
| } |
| bool TransSlotOccupied = OccupiedSlotsMask & 16; |
| if (!TransSlotOccupied && VLIW5) { |
| if (!AvailableAlus[AluTrans].empty()) { |
| OccupiedSlotsMask |= 16; |
| return PopInst(AvailableAlus[AluTrans], false); |
| } |
| SUnit *SU = AttemptFillSlot(3, true); |
| if (SU) { |
| OccupiedSlotsMask |= 16; |
| return SU; |
| } |
| } |
| for (int Chan = 3; Chan > -1; --Chan) { |
| bool isOccupied = OccupiedSlotsMask & (1 << Chan); |
| if (!isOccupied) { |
| SUnit *SU = AttemptFillSlot(Chan, false); |
| if (SU) { |
| OccupiedSlotsMask |= (1 << Chan); |
| InstructionsGroupCandidate.push_back(SU->getInstr()); |
| return SU; |
| } |
| } |
| } |
| PrepareNextSlot(); |
| } |
| return nullptr; |
| } |
| |
| SUnit* R600SchedStrategy::pickOther(int QID) { |
| SUnit *SU = nullptr; |
| std::vector<SUnit *> &AQ = Available[QID]; |
| |
| if (AQ.empty()) { |
| MoveUnits(Pending[QID], AQ); |
| } |
| if (!AQ.empty()) { |
| SU = AQ.back(); |
| AQ.pop_back(); |
| } |
| return SU; |
| } |