| //-- SystemZMachineScheduler.cpp - SystemZ 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SystemZMachineScheduler.h" |
| #include "llvm/CodeGen/MachineLoopInfo.h" |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "machine-scheduler" |
| |
| /// Pre-RA scheduling /// |
| |
| static bool isRegDef(const MachineOperand &MO) { |
| return MO.isReg() && MO.isDef(); |
| } |
| |
| bool SystemZPreRASchedStrategy::definesCmp0Src(const MachineInstr *MI, |
| bool CCDef) const { |
| if (Cmp0SrcReg != SystemZ::NoRegister && MI->getNumOperands() && |
| (MI->getDesc().hasImplicitDefOfPhysReg(SystemZ::CC) || !CCDef)) { |
| const MachineOperand &MO0 = MI->getOperand(0); |
| if (isRegDef(MO0) && MO0.getReg() == Cmp0SrcReg) |
| return true; |
| } |
| return false; |
| } |
| |
| bool SystemZPreRASchedStrategy::closesLiveRange(const SUnit *SU, |
| ScheduleDAGMILive *DAG) const { |
| if (SU->getInstr()->isCopy()) |
| return false; |
| |
| // Extract the PressureChanges that all fp/vector or GR64/GR32/GRH32 regs |
| // affect respectively. misched-prera-pdiffs.mir tests against any future |
| // change in the PressureSets modelling, so simply hard-code them here. |
| int VR16PChange = 0, GRX32PChange = 0; |
| const PressureDiff &PDiff = DAG->getPressureDiff(SU); |
| for (const PressureChange &PC : PDiff) { |
| if (!PC.isValid()) |
| break; |
| if (PC.getPSet() == SystemZ::VR16Bit) |
| VR16PChange = PC.getUnitInc(); |
| else if (PC.getPSet() == SystemZ::GRX32Bit) |
| GRX32PChange = PC.getUnitInc(); |
| } |
| |
| // Return true for a (vreg) def when register pressure is reduced. Prioritize |
| // FP/vector regs over GPRs. |
| return VR16PChange < 0 || (!VR16PChange && GRX32PChange < 0); |
| } |
| |
| bool SystemZPreRASchedStrategy::tryCandidate(SchedCandidate &Cand, |
| SchedCandidate &TryCand, |
| SchedBoundary *Zone) const { |
| assert(Zone && !Zone->isTop() && "Bottom-Up scheduling only."); |
| |
| // Initialize the candidate if needed. |
| if (!Cand.isValid()) { |
| TryCand.Reason = FirstValid; |
| return true; |
| } |
| |
| // Bias physreg defs and copies to their uses and definitions respectively. |
| if (tryBiasPhysRegs(TryCand, Cand, Zone, /*BiasPRegsExtra=*/true)) |
| return TryCand.Reason != NoCand; |
| |
| if (RegionPolicy.ShouldTrackPressure) { |
| auto schedLow = [&](const SUnit *SU) { |
| return SU->getHeight() <= Zone->getScheduledLatency() && |
| SU->getHeight() < LivenessHeightCutOff && closesLiveRange(SU, DAG); |
| }; |
| // One SU closes a live range while preserving the scheduled latency. |
| if (tryGreater(schedLow(TryCand.SU), schedLow(Cand.SU), TryCand, Cand, |
| RegExcess)) |
| return TryCand.Reason != NoCand; |
| } |
| |
| // Do latency scheduling by trying to not increase the scheduled latency |
| // (going bottom-up). This is important for performance, but it is best to |
| // only do it when likely beneficial and not "move everything around": Only |
| // apply this to SUs that have somewhat longer latencies, and don't push an |
| // SU upwards if it's on the critical path. |
| if (!RegionPolicy.DisableLatencyHeuristic && |
| std::max(TryCand.SU->Latency, Cand.SU->Latency) >= 5) |
| if (const SUnit *HigherSU = |
| TryCand.SU->getHeight() > Cand.SU->getHeight() ? TryCand.SU |
| : TryCand.SU->getHeight() < Cand.SU->getHeight() ? Cand.SU |
| : nullptr) |
| if (HigherSU->getHeight() > Zone->getScheduledLatency() && |
| HigherSU->getDepth() < computeRemLatency(*Zone)) { |
| // HigherSU would increase the scheduled latency and there is room |
| // above for it next to the critical path, so wait with it. |
| tryLess(TryCand.SU->getHeight(), Cand.SU->getHeight(), TryCand, Cand, |
| GenericSchedulerBase::BotHeightReduce); |
| return TryCand.Reason != NoCand; |
| } |
| |
| // Weak edges help copy coalescing. |
| if (tryLess(TryCand.SU->WeakSuccsLeft, Cand.SU->WeakSuccsLeft, TryCand, Cand, |
| Weak)) |
| return TryCand.Reason != NoCand; |
| |
| // Help compare with zero elimination. |
| if (tryGreater(definesCmp0Src(TryCand.SU->getInstr()), |
| definesCmp0Src(Cand.SU->getInstr()), TryCand, Cand, Weak)) |
| return TryCand.Reason != NoCand; |
| |
| // Fall through to original instruction order. |
| if (TryCand.SU->NodeNum > Cand.SU->NodeNum) { |
| TryCand.Reason = NodeOrder; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void SystemZPreRASchedStrategy::initPolicy(MachineBasicBlock::iterator Begin, |
| MachineBasicBlock::iterator End, |
| unsigned NumRegionInstrs) { |
| // TopRegionSUs is the number of SUs that is considered to be part of the |
| // "top" of a region. Liveness reduction is not done in regions smaller than |
| // this. The idea is to prioritize latency more after branches and help |
| // liveness only when the decoder is ahead of execution anyway. |
| static const unsigned TopRegionSUs = 36; |
| |
| // Avoid setting up the register pressure tracker unless needed to save |
| // compile time. |
| RegionPolicy.ShouldTrackPressure = NumRegionInstrs > TopRegionSUs; |
| |
| // These heuristics has so far seemed to work better without adding a |
| // top-down boundary. |
| RegionPolicy.OnlyBottomUp = true; |
| |
| BotIdx = NumRegionInstrs - 1; |
| this->NumRegionInstrs = NumRegionInstrs; |
| } |
| |
| void SystemZPreRASchedStrategy::initialize(ScheduleDAGMI *dag) { |
| GenericScheduler::initialize(dag); |
| |
| Cmp0SrcReg = SystemZ::NoRegister; |
| |
| unsigned DAGHeight = 0; |
| for (unsigned Idx = 0, End = DAG->SUnits.size(); Idx != End; ++Idx) |
| DAGHeight = std::max(DAGHeight, DAG->SUnits[Idx].getHeight()); |
| |
| if (RegionPolicy.ShouldTrackPressure) |
| LivenessHeightCutOff = DAGHeight / (DAG->SUnits.size() < 50 ? 4 : 2); |
| |
| // Disable latency reduction if region has many SUs relative to the |
| // overall height. |
| RegionPolicy.DisableLatencyHeuristic = |
| DAG->SUnits.size() >= 3 * std::max(DAGHeight, 1u); |
| } |
| |
| void SystemZPreRASchedStrategy::schedNode(SUnit *SU, bool IsTopNode) { |
| GenericScheduler::schedNode(SU, IsTopNode); |
| |
| const SystemZInstrInfo *TII = static_cast<const SystemZInstrInfo *>(DAG->TII); |
| MachineInstr *MI = SU->getInstr(); |
| if (TII->isCompareZero(*MI)) |
| Cmp0SrcReg = TII->getCompareSourceReg(*MI); |
| else if (MI->getDesc().hasImplicitDefOfPhysReg(SystemZ::CC) || |
| definesCmp0Src(MI, /*CCDef=*/false)) |
| Cmp0SrcReg = SystemZ::NoRegister; |
| } |
| |
| /// Post-RA scheduling /// |
| |
| #ifndef NDEBUG |
| // Print the set of SUs |
| void SystemZPostRASchedStrategy::SUSet:: |
| dump(SystemZHazardRecognizer &HazardRec) const { |
| dbgs() << "{"; |
| for (auto &SU : *this) { |
| HazardRec.dumpSU(SU, dbgs()); |
| if (SU != *rbegin()) |
| dbgs() << ", "; |
| } |
| dbgs() << "}\n"; |
| } |
| #endif |
| |
| // Try to find a single predecessor that would be interesting for the |
| // scheduler in the top-most region of MBB. |
| static MachineBasicBlock *getSingleSchedPred(MachineBasicBlock *MBB, |
| const MachineLoop *Loop) { |
| MachineBasicBlock *PredMBB = nullptr; |
| if (MBB->pred_size() == 1) |
| PredMBB = *MBB->pred_begin(); |
| |
| // The loop header has two predecessors, return the latch, but not for a |
| // single block loop. |
| if (MBB->pred_size() == 2 && Loop != nullptr && Loop->getHeader() == MBB) { |
| for (MachineBasicBlock *Pred : MBB->predecessors()) |
| if (Loop->contains(Pred)) |
| PredMBB = (Pred == MBB ? nullptr : Pred); |
| } |
| |
| assert ((PredMBB == nullptr || !Loop || Loop->contains(PredMBB)) |
| && "Loop MBB should not consider predecessor outside of loop."); |
| |
| return PredMBB; |
| } |
| |
| void SystemZPostRASchedStrategy:: |
| advanceTo(MachineBasicBlock::iterator NextBegin) { |
| MachineBasicBlock::iterator LastEmittedMI = HazardRec->getLastEmittedMI(); |
| MachineBasicBlock::iterator I = |
| ((LastEmittedMI != nullptr && LastEmittedMI->getParent() == MBB) ? |
| std::next(LastEmittedMI) : MBB->begin()); |
| |
| for (; I != NextBegin; ++I) { |
| if (I->isPosition() || I->isDebugInstr()) |
| continue; |
| HazardRec->emitInstruction(&*I); |
| } |
| } |
| |
| void SystemZPostRASchedStrategy::initialize(ScheduleDAGMI *dag) { |
| Available.clear(); // -misched-cutoff. |
| LLVM_DEBUG(HazardRec->dumpState();); |
| } |
| |
| void SystemZPostRASchedStrategy::enterMBB(MachineBasicBlock *NextMBB) { |
| assert ((SchedStates.find(NextMBB) == SchedStates.end()) && |
| "Entering MBB twice?"); |
| LLVM_DEBUG(dbgs() << "** Entering " << printMBBReference(*NextMBB)); |
| |
| MBB = NextMBB; |
| |
| /// Create a HazardRec for MBB, save it in SchedStates and set HazardRec to |
| /// point to it. |
| HazardRec = SchedStates[MBB] = new SystemZHazardRecognizer(TII, &SchedModel); |
| LLVM_DEBUG(const MachineLoop *Loop = MLI->getLoopFor(MBB); |
| if (Loop && Loop->getHeader() == MBB) dbgs() << " (Loop header)"; |
| dbgs() << ":\n";); |
| |
| // Try to take over the state from a single predecessor, if it has been |
| // scheduled. If this is not possible, we are done. |
| MachineBasicBlock *SinglePredMBB = |
| getSingleSchedPred(MBB, MLI->getLoopFor(MBB)); |
| if (SinglePredMBB == nullptr) |
| return; |
| auto It = SchedStates.find(SinglePredMBB); |
| if (It == SchedStates.end()) |
| return; |
| |
| LLVM_DEBUG(dbgs() << "** Continued scheduling from " |
| << printMBBReference(*SinglePredMBB) << "\n";); |
| |
| HazardRec->copyState(It->second); |
| LLVM_DEBUG(HazardRec->dumpState();); |
| |
| // Emit incoming terminator(s). Be optimistic and assume that branch |
| // prediction will generally do "the right thing". |
| for (MachineInstr &MI : SinglePredMBB->terminators()) { |
| LLVM_DEBUG(dbgs() << "** Emitting incoming branch: "; MI.dump();); |
| bool TakenBranch = (MI.isBranch() && |
| (TII->getBranchInfo(MI).isIndirect() || |
| TII->getBranchInfo(MI).getMBBTarget() == MBB)); |
| HazardRec->emitInstruction(&MI, TakenBranch); |
| if (TakenBranch) |
| break; |
| } |
| } |
| |
| void SystemZPostRASchedStrategy::leaveMBB() { |
| LLVM_DEBUG(dbgs() << "** Leaving " << printMBBReference(*MBB) << "\n";); |
| |
| // Advance to first terminator. The successor block will handle terminators |
| // dependent on CFG layout (T/NT branch etc). |
| advanceTo(MBB->getFirstTerminator()); |
| } |
| |
| SystemZPostRASchedStrategy:: |
| SystemZPostRASchedStrategy(const MachineSchedContext *C) |
| : MLI(C->MLI), |
| TII(static_cast<const SystemZInstrInfo *> |
| (C->MF->getSubtarget().getInstrInfo())), |
| MBB(nullptr), HazardRec(nullptr) { |
| const TargetSubtargetInfo *ST = &C->MF->getSubtarget(); |
| SchedModel.init(ST); |
| } |
| |
| SystemZPostRASchedStrategy::~SystemZPostRASchedStrategy() { |
| // Delete hazard recognizers kept around for each MBB. |
| for (auto I : SchedStates) { |
| SystemZHazardRecognizer *hazrec = I.second; |
| delete hazrec; |
| } |
| } |
| |
| void SystemZPostRASchedStrategy::initPolicy(MachineBasicBlock::iterator Begin, |
| MachineBasicBlock::iterator End, |
| unsigned NumRegionInstrs) { |
| // Don't emit the terminators. |
| if (Begin->isTerminator()) |
| return; |
| |
| // Emit any instructions before start of region. |
| advanceTo(Begin); |
| } |
| |
| // Pick the next node to schedule. |
| SUnit *SystemZPostRASchedStrategy::pickNode(bool &IsTopNode) { |
| // Only scheduling top-down. |
| IsTopNode = true; |
| |
| if (Available.empty()) |
| return nullptr; |
| |
| // If only one choice, return it. |
| if (Available.size() == 1) { |
| LLVM_DEBUG(dbgs() << "** Only one: "; |
| HazardRec->dumpSU(*Available.begin(), dbgs()); dbgs() << "\n";); |
| return *Available.begin(); |
| } |
| |
| // All nodes that are possible to schedule are stored in the Available set. |
| LLVM_DEBUG(dbgs() << "** Available: "; Available.dump(*HazardRec);); |
| |
| Candidate Best; |
| for (auto *SU : Available) { |
| |
| // SU is the next candidate to be compared against current Best. |
| Candidate c(SU, *HazardRec); |
| |
| // Remeber which SU is the best candidate. |
| if (Best.SU == nullptr || c < Best) { |
| Best = c; |
| LLVM_DEBUG(dbgs() << "** Best so far: ";); |
| } else |
| LLVM_DEBUG(dbgs() << "** Tried : ";); |
| LLVM_DEBUG(HazardRec->dumpSU(c.SU, dbgs()); c.dumpCosts(); |
| dbgs() << " Height:" << c.SU->getHeight(); dbgs() << "\n";); |
| |
| // Once we know we have seen all SUs that affect grouping or use unbuffered |
| // resources, we can stop iterating if Best looks good. |
| if (!SU->isScheduleHigh && Best.noCost()) |
| break; |
| } |
| |
| assert (Best.SU != nullptr); |
| return Best.SU; |
| } |
| |
| SystemZPostRASchedStrategy::Candidate:: |
| Candidate(SUnit *SU_, SystemZHazardRecognizer &HazardRec) : Candidate() { |
| SU = SU_; |
| |
| // Check the grouping cost. For a node that must begin / end a |
| // group, it is positive if it would do so prematurely, or negative |
| // if it would fit naturally into the schedule. |
| GroupingCost = HazardRec.groupingCost(SU); |
| |
| // Check the resources cost for this SU. |
| ResourcesCost = HazardRec.resourcesCost(SU); |
| } |
| |
| bool SystemZPostRASchedStrategy::Candidate:: |
| operator<(const Candidate &other) { |
| |
| // Check decoder grouping. |
| if (GroupingCost < other.GroupingCost) |
| return true; |
| if (GroupingCost > other.GroupingCost) |
| return false; |
| |
| // Compare the use of resources. |
| if (ResourcesCost < other.ResourcesCost) |
| return true; |
| if (ResourcesCost > other.ResourcesCost) |
| return false; |
| |
| // Higher SU is otherwise generally better. |
| if (SU->getHeight() > other.SU->getHeight()) |
| return true; |
| if (SU->getHeight() < other.SU->getHeight()) |
| return false; |
| |
| // If all same, fall back to original order. |
| if (SU->NodeNum < other.SU->NodeNum) |
| return true; |
| |
| return false; |
| } |
| |
| void SystemZPostRASchedStrategy::schedNode(SUnit *SU, bool IsTopNode) { |
| LLVM_DEBUG(dbgs() << "** Scheduling SU(" << SU->NodeNum << ") "; |
| if (Available.size() == 1) dbgs() << "(only one) "; |
| Candidate c(SU, *HazardRec); c.dumpCosts(); dbgs() << "\n";); |
| |
| // Remove SU from Available set and update HazardRec. |
| Available.erase(SU); |
| HazardRec->EmitInstruction(SU); |
| } |
| |
| void SystemZPostRASchedStrategy::releaseTopNode(SUnit *SU) { |
| // Set isScheduleHigh flag on all SUs that we want to consider first in |
| // pickNode(). |
| const MCSchedClassDesc *SC = HazardRec->getSchedClass(SU); |
| bool AffectsGrouping = (SC->isValid() && (SC->BeginGroup || SC->EndGroup)); |
| SU->isScheduleHigh = (AffectsGrouping || SU->isUnbuffered); |
| |
| // Put all released SUs in the Available set. |
| Available.insert(SU); |
| } |