|  | //===-- 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); | 
|  | 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; | 
|  | } | 
|  | InstructionsGroupCandidate.pop_back(); | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | void R600SchedStrategy::LoadAlu() { | 
|  | std::vector<SUnit *> &QSrc = Pending[IDAlu]; | 
|  | for (SUnit *SU : QSrc) { | 
|  | AluKind AK = getAluKind(SU); | 
|  | AvailableAlus[AK].push_back(SU); | 
|  | } | 
|  | 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 (const MachineOperand &MO : MI->all_uses()) | 
|  | if (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; | 
|  | } |