| //=-- SystemZHazardRecognizer.h - SystemZ Hazard Recognizer -----*- 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file defines a hazard recognizer for the SystemZ scheduler. | |
| // | |
| // This class is used by the SystemZ scheduling strategy to maintain | |
| // the state during scheduling, and provide cost functions for | |
| // scheduling candidates. This includes: | |
| // | |
| // * Decoder grouping. A decoder group can maximally hold 3 uops, and | |
| // instructions that always begin a new group should be scheduled when | |
| // the current decoder group is empty. | |
| // * Processor resources usage. It is beneficial to balance the use of | |
| // resources. | |
| // | |
| // A goal is to consider all instructions, also those outside of any | |
| // scheduling region. Such instructions are "advanced" past and include | |
| // single instructions before a scheduling region, branches etc. | |
| // | |
| // A block that has only one predecessor continues scheduling with the state | |
| // of it (which may be updated by emitting branches). | |
| // | |
| // ===---------------------------------------------------------------------===// | |
| #include "SystemZHazardRecognizer.h" | |
| #include "llvm/ADT/Statistic.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "machine-scheduler" | |
| // This is the limit of processor resource usage at which the | |
| // scheduler should try to look for other instructions (not using the | |
| // critical resource). | |
| static cl::opt<int> ProcResCostLim("procres-cost-lim", cl::Hidden, | |
| cl::desc("The OOO window for processor " | |
| "resources during scheduling."), | |
| cl::init(8)); | |
| unsigned SystemZHazardRecognizer:: | |
| getNumDecoderSlots(SUnit *SU) const { | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| if (!SC->isValid()) | |
| return 0; // IMPLICIT_DEF / KILL -- will not make impact in output. | |
| assert((SC->NumMicroOps != 2 || (SC->BeginGroup && !SC->EndGroup)) && | |
| "Only cracked instruction can have 2 uops."); | |
| assert((SC->NumMicroOps < 3 || (SC->BeginGroup && SC->EndGroup)) && | |
| "Expanded instructions always group alone."); | |
| assert((SC->NumMicroOps < 3 || (SC->NumMicroOps % 3 == 0)) && | |
| "Expanded instructions fill the group(s)."); | |
| return SC->NumMicroOps; | |
| } | |
| unsigned SystemZHazardRecognizer::getCurrCycleIdx(SUnit *SU) const { | |
| unsigned Idx = CurrGroupSize; | |
| if (GrpCount % 2) | |
| Idx += 3; | |
| if (SU != nullptr && !fitsIntoCurrentGroup(SU)) { | |
| if (Idx == 1 || Idx == 2) | |
| Idx = 3; | |
| else if (Idx == 4 || Idx == 5) | |
| Idx = 0; | |
| } | |
| return Idx; | |
| } | |
| ScheduleHazardRecognizer::HazardType SystemZHazardRecognizer:: | |
| getHazardType(SUnit *SU, int Stalls) { | |
| return (fitsIntoCurrentGroup(SU) ? NoHazard : Hazard); | |
| } | |
| void SystemZHazardRecognizer::Reset() { | |
| CurrGroupSize = 0; | |
| CurrGroupHas4RegOps = false; | |
| clearProcResCounters(); | |
| GrpCount = 0; | |
| LastFPdOpCycleIdx = UINT_MAX; | |
| LastEmittedMI = nullptr; | |
| LLVM_DEBUG(CurGroupDbg = "";); | |
| } | |
| bool | |
| SystemZHazardRecognizer::fitsIntoCurrentGroup(SUnit *SU) const { | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| if (!SC->isValid()) | |
| return true; | |
| // A cracked instruction only fits into schedule if the current | |
| // group is empty. | |
| if (SC->BeginGroup) | |
| return (CurrGroupSize == 0); | |
| // An instruction with 4 register operands will not fit in last slot. | |
| assert ((CurrGroupSize < 2 || !CurrGroupHas4RegOps) && | |
| "Current decoder group is already full!"); | |
| if (CurrGroupSize == 2 && has4RegOps(SU->getInstr())) | |
| return false; | |
| // Since a full group is handled immediately in EmitInstruction(), | |
| // SU should fit into current group. NumSlots should be 1 or 0, | |
| // since it is not a cracked or expanded instruction. | |
| assert ((getNumDecoderSlots(SU) <= 1) && (CurrGroupSize < 3) && | |
| "Expected normal instruction to fit in non-full group!"); | |
| return true; | |
| } | |
| bool SystemZHazardRecognizer::has4RegOps(const MachineInstr *MI) const { | |
| const MCInstrDesc &MID = MI->getDesc(); | |
| unsigned Count = 0; | |
| for (unsigned OpIdx = 0; OpIdx < MID.getNumOperands(); OpIdx++) { | |
| const TargetRegisterClass *RC = TII->getRegClass(MID, OpIdx); | |
| if (RC == nullptr) | |
| continue; | |
| if (OpIdx >= MID.getNumDefs() && | |
| MID.getOperandConstraint(OpIdx, MCOI::TIED_TO) != -1) | |
| continue; | |
| Count++; | |
| } | |
| return Count >= 4; | |
| } | |
| void SystemZHazardRecognizer::nextGroup() { | |
| if (CurrGroupSize == 0) | |
| return; | |
| LLVM_DEBUG(dumpCurrGroup("Completed decode group")); | |
| LLVM_DEBUG(CurGroupDbg = "";); | |
| int NumGroups = ((CurrGroupSize > 3) ? (CurrGroupSize / 3) : 1); | |
| assert((CurrGroupSize <= 3 || CurrGroupSize % 3 == 0) && | |
| "Current decoder group bad."); | |
| // Reset counter for next group. | |
| CurrGroupSize = 0; | |
| CurrGroupHas4RegOps = false; | |
| GrpCount += ((unsigned) NumGroups); | |
| // Decrease counters for execution units. | |
| for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i) | |
| ProcResourceCounters[i] = ((ProcResourceCounters[i] > NumGroups) | |
| ? (ProcResourceCounters[i] - NumGroups) | |
| : 0); | |
| // Clear CriticalResourceIdx if it is now below the threshold. | |
| if (CriticalResourceIdx != UINT_MAX && | |
| (ProcResourceCounters[CriticalResourceIdx] <= | |
| ProcResCostLim)) | |
| CriticalResourceIdx = UINT_MAX; | |
| LLVM_DEBUG(dumpState();); | |
| } | |
| #ifndef NDEBUG // Debug output | |
| void SystemZHazardRecognizer::dumpSU(SUnit *SU, raw_ostream &OS) const { | |
| OS << *SU << ":"; | |
| OS << TII->getName(SU->getInstr()->getOpcode()); | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| if (!SC->isValid()) | |
| return; | |
| for (TargetSchedModel::ProcResIter | |
| PI = SchedModel->getWriteProcResBegin(SC), | |
| PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI) { | |
| const MCProcResourceDesc &PRD = | |
| *SchedModel->getProcResource(PI->ProcResourceIdx); | |
| std::string FU(PRD.Name); | |
| // trim e.g. Z13_FXaUnit -> FXa | |
| FU = FU.substr(FU.find('_') + 1); | |
| size_t Pos = FU.find("Unit"); | |
| if (Pos != std::string::npos) | |
| FU.resize(Pos); | |
| if (FU == "LS") // LSUnit -> LSU | |
| FU = "LSU"; | |
| OS << "/" << FU; | |
| if (PI->ReleaseAtCycle> 1) | |
| OS << "(" << PI->ReleaseAtCycle << "cyc)"; | |
| } | |
| if (SC->NumMicroOps > 1) | |
| OS << "/" << SC->NumMicroOps << "uops"; | |
| if (SC->BeginGroup && SC->EndGroup) | |
| OS << "/GroupsAlone"; | |
| else if (SC->BeginGroup) | |
| OS << "/BeginsGroup"; | |
| else if (SC->EndGroup) | |
| OS << "/EndsGroup"; | |
| if (SU->isUnbuffered) | |
| OS << "/Unbuffered"; | |
| if (has4RegOps(SU->getInstr())) | |
| OS << "/4RegOps"; | |
| } | |
| void SystemZHazardRecognizer::dumpCurrGroup(std::string Msg) const { | |
| dbgs() << "++ " << Msg; | |
| dbgs() << ": "; | |
| if (CurGroupDbg.empty()) | |
| dbgs() << " <empty>\n"; | |
| else { | |
| dbgs() << "{ " << CurGroupDbg << " }"; | |
| dbgs() << " (" << CurrGroupSize << " decoder slot" | |
| << (CurrGroupSize > 1 ? "s":"") | |
| << (CurrGroupHas4RegOps ? ", 4RegOps" : "") | |
| << ")\n"; | |
| } | |
| } | |
| void SystemZHazardRecognizer::dumpProcResourceCounters() const { | |
| bool any = false; | |
| for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i) | |
| if (ProcResourceCounters[i] > 0) { | |
| any = true; | |
| break; | |
| } | |
| if (!any) | |
| return; | |
| dbgs() << "++ | Resource counters: "; | |
| for (unsigned i = 0; i < SchedModel->getNumProcResourceKinds(); ++i) | |
| if (ProcResourceCounters[i] > 0) | |
| dbgs() << SchedModel->getProcResource(i)->Name | |
| << ":" << ProcResourceCounters[i] << " "; | |
| dbgs() << "\n"; | |
| if (CriticalResourceIdx != UINT_MAX) | |
| dbgs() << "++ | Critical resource: " | |
| << SchedModel->getProcResource(CriticalResourceIdx)->Name | |
| << "\n"; | |
| } | |
| void SystemZHazardRecognizer::dumpState() const { | |
| dumpCurrGroup("| Current decoder group"); | |
| dbgs() << "++ | Current cycle index: " | |
| << getCurrCycleIdx() << "\n"; | |
| dumpProcResourceCounters(); | |
| if (LastFPdOpCycleIdx != UINT_MAX) | |
| dbgs() << "++ | Last FPd cycle index: " << LastFPdOpCycleIdx << "\n"; | |
| } | |
| #endif //NDEBUG | |
| void SystemZHazardRecognizer::clearProcResCounters() { | |
| ProcResourceCounters.assign(SchedModel->getNumProcResourceKinds(), 0); | |
| CriticalResourceIdx = UINT_MAX; | |
| } | |
| static inline bool isBranchRetTrap(MachineInstr *MI) { | |
| return (MI->isBranch() || MI->isReturn() || | |
| MI->getOpcode() == SystemZ::CondTrap); | |
| } | |
| // Update state with SU as the next scheduled unit. | |
| void SystemZHazardRecognizer:: | |
| EmitInstruction(SUnit *SU) { | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| LLVM_DEBUG(dbgs() << "++ HazardRecognizer emitting "; dumpSU(SU, dbgs()); | |
| dbgs() << "\n";); | |
| LLVM_DEBUG(dumpCurrGroup("Decode group before emission");); | |
| // If scheduling an SU that must begin a new decoder group, move on | |
| // to next group. | |
| if (!fitsIntoCurrentGroup(SU)) | |
| nextGroup(); | |
| LLVM_DEBUG(raw_string_ostream cgd(CurGroupDbg); | |
| if (CurGroupDbg.length()) cgd << ", "; dumpSU(SU, cgd);); | |
| LastEmittedMI = SU->getInstr(); | |
| // After returning from a call, we don't know much about the state. | |
| if (SU->isCall) { | |
| LLVM_DEBUG(dbgs() << "++ Clearing state after call.\n";); | |
| Reset(); | |
| LastEmittedMI = SU->getInstr(); | |
| return; | |
| } | |
| // Increase counter for execution unit(s). | |
| for (TargetSchedModel::ProcResIter | |
| PI = SchedModel->getWriteProcResBegin(SC), | |
| PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI) { | |
| // Don't handle FPd together with the other resources. | |
| if (SchedModel->getProcResource(PI->ProcResourceIdx)->BufferSize == 1) | |
| continue; | |
| int &CurrCounter = | |
| ProcResourceCounters[PI->ProcResourceIdx]; | |
| CurrCounter += PI->ReleaseAtCycle; | |
| // Check if this is now the new critical resource. | |
| if ((CurrCounter > ProcResCostLim) && | |
| (CriticalResourceIdx == UINT_MAX || | |
| (PI->ProcResourceIdx != CriticalResourceIdx && | |
| CurrCounter > | |
| ProcResourceCounters[CriticalResourceIdx]))) { | |
| LLVM_DEBUG( | |
| dbgs() << "++ New critical resource: " | |
| << SchedModel->getProcResource(PI->ProcResourceIdx)->Name | |
| << "\n";); | |
| CriticalResourceIdx = PI->ProcResourceIdx; | |
| } | |
| } | |
| // Make note of an instruction that uses a blocking resource (FPd). | |
| if (SU->isUnbuffered) { | |
| LastFPdOpCycleIdx = getCurrCycleIdx(SU); | |
| LLVM_DEBUG(dbgs() << "++ Last FPd cycle index: " << LastFPdOpCycleIdx | |
| << "\n";); | |
| } | |
| // Insert SU into current group by increasing number of slots used | |
| // in current group. | |
| CurrGroupSize += getNumDecoderSlots(SU); | |
| CurrGroupHas4RegOps |= has4RegOps(SU->getInstr()); | |
| unsigned GroupLim = (CurrGroupHas4RegOps ? 2 : 3); | |
| assert((CurrGroupSize <= GroupLim || CurrGroupSize == getNumDecoderSlots(SU)) | |
| && "SU does not fit into decoder group!"); | |
| // Check if current group is now full/ended. If so, move on to next | |
| // group to be ready to evaluate more candidates. | |
| if (CurrGroupSize >= GroupLim || SC->EndGroup) | |
| nextGroup(); | |
| } | |
| int SystemZHazardRecognizer::groupingCost(SUnit *SU) const { | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| if (!SC->isValid()) | |
| return 0; | |
| // If SU begins new group, it can either break a current group early | |
| // or fit naturally if current group is empty (negative cost). | |
| if (SC->BeginGroup) { | |
| if (CurrGroupSize) | |
| return 3 - CurrGroupSize; | |
| return -1; | |
| } | |
| // Similarly, a group-ending SU may either fit well (last in group), or | |
| // end the group prematurely. | |
| if (SC->EndGroup) { | |
| unsigned ResultingGroupSize = (CurrGroupSize + getNumDecoderSlots(SU)); | |
| if (ResultingGroupSize < 3) | |
| return (3 - ResultingGroupSize); | |
| return -1; | |
| } | |
| // An instruction with 4 register operands will not fit in last slot. | |
| if (CurrGroupSize == 2 && has4RegOps(SU->getInstr())) | |
| return 1; | |
| // Most instructions can be placed in any decoder slot. | |
| return 0; | |
| } | |
| bool SystemZHazardRecognizer::isFPdOpPreferred_distance(SUnit *SU) const { | |
| assert (SU->isUnbuffered); | |
| // If this is the first FPd op, it should be scheduled high. | |
| if (LastFPdOpCycleIdx == UINT_MAX) | |
| return true; | |
| // If this is not the first PFd op, it should go into the other side | |
| // of the processor to use the other FPd unit there. This should | |
| // generally happen if two FPd ops are placed with 2 other | |
| // instructions between them (modulo 6). | |
| unsigned SUCycleIdx = getCurrCycleIdx(SU); | |
| if (LastFPdOpCycleIdx > SUCycleIdx) | |
| return ((LastFPdOpCycleIdx - SUCycleIdx) == 3); | |
| return ((SUCycleIdx - LastFPdOpCycleIdx) == 3); | |
| } | |
| int SystemZHazardRecognizer:: | |
| resourcesCost(SUnit *SU) { | |
| int Cost = 0; | |
| const MCSchedClassDesc *SC = getSchedClass(SU); | |
| if (!SC->isValid()) | |
| return 0; | |
| // For a FPd op, either return min or max value as indicated by the | |
| // distance to any prior FPd op. | |
| if (SU->isUnbuffered) | |
| Cost = (isFPdOpPreferred_distance(SU) ? INT_MIN : INT_MAX); | |
| // For other instructions, give a cost to the use of the critical resource. | |
| else if (CriticalResourceIdx != UINT_MAX) { | |
| for (TargetSchedModel::ProcResIter | |
| PI = SchedModel->getWriteProcResBegin(SC), | |
| PE = SchedModel->getWriteProcResEnd(SC); PI != PE; ++PI) | |
| if (PI->ProcResourceIdx == CriticalResourceIdx) | |
| Cost = PI->ReleaseAtCycle; | |
| } | |
| return Cost; | |
| } | |
| void SystemZHazardRecognizer::emitInstruction(MachineInstr *MI, | |
| bool TakenBranch) { | |
| // Make a temporary SUnit. | |
| SUnit SU(MI, 0); | |
| // Set interesting flags. | |
| SU.isCall = MI->isCall(); | |
| const MCSchedClassDesc *SC = SchedModel->resolveSchedClass(MI); | |
| for (const MCWriteProcResEntry &PRE : | |
| make_range(SchedModel->getWriteProcResBegin(SC), | |
| SchedModel->getWriteProcResEnd(SC))) { | |
| switch (SchedModel->getProcResource(PRE.ProcResourceIdx)->BufferSize) { | |
| case 0: | |
| SU.hasReservedResource = true; | |
| break; | |
| case 1: | |
| SU.isUnbuffered = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| unsigned GroupSizeBeforeEmit = CurrGroupSize; | |
| EmitInstruction(&SU); | |
| if (!TakenBranch && isBranchRetTrap(MI)) { | |
| // NT Branch on second slot ends group. | |
| if (GroupSizeBeforeEmit == 1) | |
| nextGroup(); | |
| } | |
| if (TakenBranch && CurrGroupSize > 0) | |
| nextGroup(); | |
| assert ((!MI->isTerminator() || isBranchRetTrap(MI)) && | |
| "Scheduler: unhandled terminator!"); | |
| } | |
| void SystemZHazardRecognizer:: | |
| copyState(SystemZHazardRecognizer *Incoming) { | |
| // Current decoder group | |
| CurrGroupSize = Incoming->CurrGroupSize; | |
| LLVM_DEBUG(CurGroupDbg = Incoming->CurGroupDbg;); | |
| // Processor resources | |
| ProcResourceCounters = Incoming->ProcResourceCounters; | |
| CriticalResourceIdx = Incoming->CriticalResourceIdx; | |
| // FPd | |
| LastFPdOpCycleIdx = Incoming->LastFPdOpCycleIdx; | |
| GrpCount = Incoming->GrpCount; | |
| } |