10eae32dcSDimitry Andric //===- VLIWMachineScheduler.cpp - VLIW-Focused Scheduling Pass ------------===// 20eae32dcSDimitry Andric // 30eae32dcSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40eae32dcSDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50eae32dcSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60eae32dcSDimitry Andric // 70eae32dcSDimitry Andric //===----------------------------------------------------------------------===// 80eae32dcSDimitry Andric // 90eae32dcSDimitry Andric // MachineScheduler schedules machine instructions after phi elimination. It 100eae32dcSDimitry Andric // preserves LiveIntervals so it can be invoked before register allocation. 110eae32dcSDimitry Andric // 120eae32dcSDimitry Andric //===----------------------------------------------------------------------===// 130eae32dcSDimitry Andric 140eae32dcSDimitry Andric #include "llvm/CodeGen/VLIWMachineScheduler.h" 150eae32dcSDimitry Andric #include "llvm/ADT/SmallVector.h" 160eae32dcSDimitry Andric #include "llvm/CodeGen/DFAPacketizer.h" 170eae32dcSDimitry Andric #include "llvm/CodeGen/MachineBasicBlock.h" 180eae32dcSDimitry Andric #include "llvm/CodeGen/MachineFunction.h" 190eae32dcSDimitry Andric #include "llvm/CodeGen/MachineInstr.h" 200eae32dcSDimitry Andric #include "llvm/CodeGen/MachineLoopInfo.h" 210eae32dcSDimitry Andric #include "llvm/CodeGen/RegisterClassInfo.h" 220eae32dcSDimitry Andric #include "llvm/CodeGen/RegisterPressure.h" 230eae32dcSDimitry Andric #include "llvm/CodeGen/ScheduleDAG.h" 240eae32dcSDimitry Andric #include "llvm/CodeGen/ScheduleHazardRecognizer.h" 250eae32dcSDimitry Andric #include "llvm/CodeGen/TargetInstrInfo.h" 260eae32dcSDimitry Andric #include "llvm/CodeGen/TargetOpcodes.h" 270eae32dcSDimitry Andric #include "llvm/CodeGen/TargetRegisterInfo.h" 280eae32dcSDimitry Andric #include "llvm/CodeGen/TargetSchedule.h" 290eae32dcSDimitry Andric #include "llvm/CodeGen/TargetSubtargetInfo.h" 300eae32dcSDimitry Andric #include "llvm/Support/CommandLine.h" 310eae32dcSDimitry Andric #include "llvm/Support/Debug.h" 320eae32dcSDimitry Andric #include "llvm/Support/raw_ostream.h" 330eae32dcSDimitry Andric #include <algorithm> 340eae32dcSDimitry Andric #include <cassert> 350eae32dcSDimitry Andric #include <iomanip> 360eae32dcSDimitry Andric #include <limits> 370eae32dcSDimitry Andric #include <memory> 380eae32dcSDimitry Andric #include <sstream> 390eae32dcSDimitry Andric 400eae32dcSDimitry Andric using namespace llvm; 410eae32dcSDimitry Andric 420eae32dcSDimitry Andric #define DEBUG_TYPE "machine-scheduler" 430eae32dcSDimitry Andric 440eae32dcSDimitry Andric static cl::opt<bool> IgnoreBBRegPressure("ignore-bb-reg-pressure", cl::Hidden, 4581ad6265SDimitry Andric cl::init(false)); 460eae32dcSDimitry Andric 470eae32dcSDimitry Andric static cl::opt<bool> UseNewerCandidate("use-newer-candidate", cl::Hidden, 4881ad6265SDimitry Andric cl::init(true)); 490eae32dcSDimitry Andric 500eae32dcSDimitry Andric static cl::opt<unsigned> SchedDebugVerboseLevel("misched-verbose-level", 5181ad6265SDimitry Andric cl::Hidden, cl::init(1)); 520eae32dcSDimitry Andric 530eae32dcSDimitry Andric // Check if the scheduler should penalize instructions that are available to 540eae32dcSDimitry Andric // early due to a zero-latency dependence. 550eae32dcSDimitry Andric static cl::opt<bool> CheckEarlyAvail("check-early-avail", cl::Hidden, 5681ad6265SDimitry Andric cl::init(true)); 570eae32dcSDimitry Andric 580eae32dcSDimitry Andric // This value is used to determine if a register class is a high pressure set. 590eae32dcSDimitry Andric // We compute the maximum number of registers needed and divided by the total 600eae32dcSDimitry Andric // available. Then, we compare the result to this value. 610eae32dcSDimitry Andric static cl::opt<float> RPThreshold("vliw-misched-reg-pressure", cl::Hidden, 620eae32dcSDimitry Andric cl::init(0.75f), 630eae32dcSDimitry Andric cl::desc("High register pressure threhold.")); 640eae32dcSDimitry Andric 650eae32dcSDimitry Andric VLIWResourceModel::VLIWResourceModel(const TargetSubtargetInfo &STI, 660eae32dcSDimitry Andric const TargetSchedModel *SM) 670eae32dcSDimitry Andric : TII(STI.getInstrInfo()), SchedModel(SM) { 680eae32dcSDimitry Andric ResourcesModel = createPacketizer(STI); 690eae32dcSDimitry Andric 700eae32dcSDimitry Andric // This hard requirement could be relaxed, 710eae32dcSDimitry Andric // but for now do not let it proceed. 720eae32dcSDimitry Andric assert(ResourcesModel && "Unimplemented CreateTargetScheduleState."); 730eae32dcSDimitry Andric 740eae32dcSDimitry Andric Packet.reserve(SchedModel->getIssueWidth()); 750eae32dcSDimitry Andric Packet.clear(); 760eae32dcSDimitry Andric ResourcesModel->clearResources(); 770eae32dcSDimitry Andric } 780eae32dcSDimitry Andric 790eae32dcSDimitry Andric void VLIWResourceModel::reset() { 800eae32dcSDimitry Andric Packet.clear(); 810eae32dcSDimitry Andric ResourcesModel->clearResources(); 820eae32dcSDimitry Andric } 830eae32dcSDimitry Andric 840eae32dcSDimitry Andric VLIWResourceModel::~VLIWResourceModel() { delete ResourcesModel; } 850eae32dcSDimitry Andric 860eae32dcSDimitry Andric /// Return true if there is a dependence between SUd and SUu. 870eae32dcSDimitry Andric bool VLIWResourceModel::hasDependence(const SUnit *SUd, const SUnit *SUu) { 880eae32dcSDimitry Andric if (SUd->Succs.size() == 0) 890eae32dcSDimitry Andric return false; 900eae32dcSDimitry Andric 910eae32dcSDimitry Andric for (const auto &S : SUd->Succs) { 920eae32dcSDimitry Andric // Since we do not add pseudos to packets, might as well 930eae32dcSDimitry Andric // ignore order dependencies. 940eae32dcSDimitry Andric if (S.isCtrl()) 950eae32dcSDimitry Andric continue; 960eae32dcSDimitry Andric 970eae32dcSDimitry Andric if (S.getSUnit() == SUu && S.getLatency() > 0) 980eae32dcSDimitry Andric return true; 990eae32dcSDimitry Andric } 1000eae32dcSDimitry Andric return false; 1010eae32dcSDimitry Andric } 1020eae32dcSDimitry Andric 1030eae32dcSDimitry Andric /// Check if scheduling of this SU is possible 1040eae32dcSDimitry Andric /// in the current packet. 1050eae32dcSDimitry Andric /// It is _not_ precise (statefull), it is more like 1060eae32dcSDimitry Andric /// another heuristic. Many corner cases are figured 1070eae32dcSDimitry Andric /// empirically. 1080eae32dcSDimitry Andric bool VLIWResourceModel::isResourceAvailable(SUnit *SU, bool IsTop) { 1090eae32dcSDimitry Andric if (!SU || !SU->getInstr()) 1100eae32dcSDimitry Andric return false; 1110eae32dcSDimitry Andric 1120eae32dcSDimitry Andric // First see if the pipeline could receive this instruction 1130eae32dcSDimitry Andric // in the current cycle. 1140eae32dcSDimitry Andric switch (SU->getInstr()->getOpcode()) { 1150eae32dcSDimitry Andric default: 1160eae32dcSDimitry Andric if (!ResourcesModel->canReserveResources(*SU->getInstr())) 1170eae32dcSDimitry Andric return false; 1180eae32dcSDimitry Andric break; 1190eae32dcSDimitry Andric case TargetOpcode::EXTRACT_SUBREG: 1200eae32dcSDimitry Andric case TargetOpcode::INSERT_SUBREG: 1210eae32dcSDimitry Andric case TargetOpcode::SUBREG_TO_REG: 1220eae32dcSDimitry Andric case TargetOpcode::REG_SEQUENCE: 1230eae32dcSDimitry Andric case TargetOpcode::IMPLICIT_DEF: 1240eae32dcSDimitry Andric case TargetOpcode::COPY: 1250eae32dcSDimitry Andric case TargetOpcode::INLINEASM: 1260eae32dcSDimitry Andric case TargetOpcode::INLINEASM_BR: 1270eae32dcSDimitry Andric break; 1280eae32dcSDimitry Andric } 1290eae32dcSDimitry Andric 1300eae32dcSDimitry Andric // Now see if there are no other dependencies to instructions already 1310eae32dcSDimitry Andric // in the packet. 1320eae32dcSDimitry Andric if (IsTop) { 133*0fca6ea1SDimitry Andric for (const SUnit *U : Packet) 134*0fca6ea1SDimitry Andric if (hasDependence(U, SU)) 1350eae32dcSDimitry Andric return false; 1360eae32dcSDimitry Andric } else { 137*0fca6ea1SDimitry Andric for (const SUnit *U : Packet) 138*0fca6ea1SDimitry Andric if (hasDependence(SU, U)) 1390eae32dcSDimitry Andric return false; 1400eae32dcSDimitry Andric } 1410eae32dcSDimitry Andric return true; 1420eae32dcSDimitry Andric } 1430eae32dcSDimitry Andric 1440eae32dcSDimitry Andric /// Keep track of available resources. 1450eae32dcSDimitry Andric bool VLIWResourceModel::reserveResources(SUnit *SU, bool IsTop) { 1460eae32dcSDimitry Andric bool startNewCycle = false; 1470eae32dcSDimitry Andric // Artificially reset state. 1480eae32dcSDimitry Andric if (!SU) { 1490eae32dcSDimitry Andric reset(); 1500eae32dcSDimitry Andric TotalPackets++; 1510eae32dcSDimitry Andric return false; 1520eae32dcSDimitry Andric } 1530eae32dcSDimitry Andric // If this SU does not fit in the packet or the packet is now full 1540eae32dcSDimitry Andric // start a new one. 1550eae32dcSDimitry Andric if (!isResourceAvailable(SU, IsTop) || 1560eae32dcSDimitry Andric Packet.size() >= SchedModel->getIssueWidth()) { 1570eae32dcSDimitry Andric reset(); 1580eae32dcSDimitry Andric TotalPackets++; 1590eae32dcSDimitry Andric startNewCycle = true; 1600eae32dcSDimitry Andric } 1610eae32dcSDimitry Andric 1620eae32dcSDimitry Andric switch (SU->getInstr()->getOpcode()) { 1630eae32dcSDimitry Andric default: 1640eae32dcSDimitry Andric ResourcesModel->reserveResources(*SU->getInstr()); 1650eae32dcSDimitry Andric break; 1660eae32dcSDimitry Andric case TargetOpcode::EXTRACT_SUBREG: 1670eae32dcSDimitry Andric case TargetOpcode::INSERT_SUBREG: 1680eae32dcSDimitry Andric case TargetOpcode::SUBREG_TO_REG: 1690eae32dcSDimitry Andric case TargetOpcode::REG_SEQUENCE: 1700eae32dcSDimitry Andric case TargetOpcode::IMPLICIT_DEF: 1710eae32dcSDimitry Andric case TargetOpcode::KILL: 1720eae32dcSDimitry Andric case TargetOpcode::CFI_INSTRUCTION: 1730eae32dcSDimitry Andric case TargetOpcode::EH_LABEL: 1740eae32dcSDimitry Andric case TargetOpcode::COPY: 1750eae32dcSDimitry Andric case TargetOpcode::INLINEASM: 1760eae32dcSDimitry Andric case TargetOpcode::INLINEASM_BR: 1770eae32dcSDimitry Andric break; 1780eae32dcSDimitry Andric } 1790eae32dcSDimitry Andric Packet.push_back(SU); 1800eae32dcSDimitry Andric 1810eae32dcSDimitry Andric #ifndef NDEBUG 1820eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Packet[" << TotalPackets << "]:\n"); 1830eae32dcSDimitry Andric for (unsigned i = 0, e = Packet.size(); i != e; ++i) { 1840eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "\t[" << i << "] SU("); 1850eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << Packet[i]->NodeNum << ")\t"); 1860eae32dcSDimitry Andric LLVM_DEBUG(Packet[i]->getInstr()->dump()); 1870eae32dcSDimitry Andric } 1880eae32dcSDimitry Andric #endif 1890eae32dcSDimitry Andric 1900eae32dcSDimitry Andric return startNewCycle; 1910eae32dcSDimitry Andric } 1920eae32dcSDimitry Andric 1930eae32dcSDimitry Andric DFAPacketizer * 1940eae32dcSDimitry Andric VLIWResourceModel::createPacketizer(const TargetSubtargetInfo &STI) const { 1950eae32dcSDimitry Andric return STI.getInstrInfo()->CreateTargetScheduleState(STI); 1960eae32dcSDimitry Andric } 1970eae32dcSDimitry Andric 1980eae32dcSDimitry Andric /// schedule - Called back from MachineScheduler::runOnMachineFunction 1990eae32dcSDimitry Andric /// after setting up the current scheduling region. [RegionBegin, RegionEnd) 2000eae32dcSDimitry Andric /// only includes instructions that have DAG nodes, not scheduling boundaries. 2010eae32dcSDimitry Andric void VLIWMachineScheduler::schedule() { 2020eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "********** MI Converging Scheduling VLIW " 2030eae32dcSDimitry Andric << printMBBReference(*BB) << " " << BB->getName() 2040eae32dcSDimitry Andric << " in_func " << BB->getParent()->getName() 2050eae32dcSDimitry Andric << " at loop depth " << MLI->getLoopDepth(BB) << " \n"); 2060eae32dcSDimitry Andric 2070eae32dcSDimitry Andric buildDAGWithRegPressure(); 2080eae32dcSDimitry Andric 2090eae32dcSDimitry Andric Topo.InitDAGTopologicalSorting(); 2100eae32dcSDimitry Andric 2110eae32dcSDimitry Andric // Postprocess the DAG to add platform-specific artificial dependencies. 21206c3fb27SDimitry Andric postProcessDAG(); 2130eae32dcSDimitry Andric 2140eae32dcSDimitry Andric SmallVector<SUnit *, 8> TopRoots, BotRoots; 2150eae32dcSDimitry Andric findRootsAndBiasEdges(TopRoots, BotRoots); 2160eae32dcSDimitry Andric 2170eae32dcSDimitry Andric // Initialize the strategy before modifying the DAG. 2180eae32dcSDimitry Andric SchedImpl->initialize(this); 2190eae32dcSDimitry Andric 2200eae32dcSDimitry Andric LLVM_DEBUG({ 2210eae32dcSDimitry Andric unsigned maxH = 0; 2220eae32dcSDimitry Andric for (const SUnit &SU : SUnits) 2230eae32dcSDimitry Andric if (SU.getHeight() > maxH) 2240eae32dcSDimitry Andric maxH = SU.getHeight(); 2250eae32dcSDimitry Andric dbgs() << "Max Height " << maxH << "\n"; 2260eae32dcSDimitry Andric }); 2270eae32dcSDimitry Andric LLVM_DEBUG({ 2280eae32dcSDimitry Andric unsigned maxD = 0; 2290eae32dcSDimitry Andric for (const SUnit &SU : SUnits) 2300eae32dcSDimitry Andric if (SU.getDepth() > maxD) 2310eae32dcSDimitry Andric maxD = SU.getDepth(); 2320eae32dcSDimitry Andric dbgs() << "Max Depth " << maxD << "\n"; 2330eae32dcSDimitry Andric }); 2340eae32dcSDimitry Andric LLVM_DEBUG(dump()); 2350eae32dcSDimitry Andric if (ViewMISchedDAGs) 2360eae32dcSDimitry Andric viewGraph(); 2370eae32dcSDimitry Andric 2380eae32dcSDimitry Andric initQueues(TopRoots, BotRoots); 2390eae32dcSDimitry Andric 2400eae32dcSDimitry Andric bool IsTopNode = false; 2410eae32dcSDimitry Andric while (true) { 2420eae32dcSDimitry Andric LLVM_DEBUG( 2430eae32dcSDimitry Andric dbgs() << "** VLIWMachineScheduler::schedule picking next node\n"); 2440eae32dcSDimitry Andric SUnit *SU = SchedImpl->pickNode(IsTopNode); 2450eae32dcSDimitry Andric if (!SU) 2460eae32dcSDimitry Andric break; 2470eae32dcSDimitry Andric 2480eae32dcSDimitry Andric if (!checkSchedLimit()) 2490eae32dcSDimitry Andric break; 2500eae32dcSDimitry Andric 2510eae32dcSDimitry Andric scheduleMI(SU, IsTopNode); 2520eae32dcSDimitry Andric 2530eae32dcSDimitry Andric // Notify the scheduling strategy after updating the DAG. 2540eae32dcSDimitry Andric SchedImpl->schedNode(SU, IsTopNode); 2550eae32dcSDimitry Andric 2560eae32dcSDimitry Andric updateQueues(SU, IsTopNode); 2570eae32dcSDimitry Andric } 2580eae32dcSDimitry Andric assert(CurrentTop == CurrentBottom && "Nonempty unscheduled zone."); 2590eae32dcSDimitry Andric 2600eae32dcSDimitry Andric placeDebugValues(); 2610eae32dcSDimitry Andric 2620eae32dcSDimitry Andric LLVM_DEBUG({ 2630eae32dcSDimitry Andric dbgs() << "*** Final schedule for " 2640eae32dcSDimitry Andric << printMBBReference(*begin()->getParent()) << " ***\n"; 2650eae32dcSDimitry Andric dumpSchedule(); 2660eae32dcSDimitry Andric dbgs() << '\n'; 2670eae32dcSDimitry Andric }); 2680eae32dcSDimitry Andric } 2690eae32dcSDimitry Andric 2700eae32dcSDimitry Andric void ConvergingVLIWScheduler::initialize(ScheduleDAGMI *dag) { 2710eae32dcSDimitry Andric DAG = static_cast<VLIWMachineScheduler *>(dag); 2720eae32dcSDimitry Andric SchedModel = DAG->getSchedModel(); 2730eae32dcSDimitry Andric 2740eae32dcSDimitry Andric Top.init(DAG, SchedModel); 2750eae32dcSDimitry Andric Bot.init(DAG, SchedModel); 2760eae32dcSDimitry Andric 2770eae32dcSDimitry Andric // Initialize the HazardRecognizers. If itineraries don't exist, are empty, or 2780eae32dcSDimitry Andric // are disabled, then these HazardRecs will be disabled. 2790eae32dcSDimitry Andric const InstrItineraryData *Itin = DAG->getSchedModel()->getInstrItineraries(); 2800eae32dcSDimitry Andric const TargetSubtargetInfo &STI = DAG->MF.getSubtarget(); 2810eae32dcSDimitry Andric const TargetInstrInfo *TII = STI.getInstrInfo(); 2820eae32dcSDimitry Andric delete Top.HazardRec; 2830eae32dcSDimitry Andric delete Bot.HazardRec; 2840eae32dcSDimitry Andric Top.HazardRec = TII->CreateTargetMIHazardRecognizer(Itin, DAG); 2850eae32dcSDimitry Andric Bot.HazardRec = TII->CreateTargetMIHazardRecognizer(Itin, DAG); 2860eae32dcSDimitry Andric 2870eae32dcSDimitry Andric delete Top.ResourceModel; 2880eae32dcSDimitry Andric delete Bot.ResourceModel; 2890eae32dcSDimitry Andric Top.ResourceModel = createVLIWResourceModel(STI, DAG->getSchedModel()); 2900eae32dcSDimitry Andric Bot.ResourceModel = createVLIWResourceModel(STI, DAG->getSchedModel()); 2910eae32dcSDimitry Andric 2920eae32dcSDimitry Andric const std::vector<unsigned> &MaxPressure = 2930eae32dcSDimitry Andric DAG->getRegPressure().MaxSetPressure; 29404eeddc0SDimitry Andric HighPressureSets.assign(MaxPressure.size(), false); 2950eae32dcSDimitry Andric for (unsigned i = 0, e = MaxPressure.size(); i < e; ++i) { 2960eae32dcSDimitry Andric unsigned Limit = DAG->getRegClassInfo()->getRegPressureSetLimit(i); 2970eae32dcSDimitry Andric HighPressureSets[i] = 2980eae32dcSDimitry Andric ((float)MaxPressure[i] > ((float)Limit * RPThreshold)); 2990eae32dcSDimitry Andric } 3000eae32dcSDimitry Andric 3010eae32dcSDimitry Andric assert((!ForceTopDown || !ForceBottomUp) && 3020eae32dcSDimitry Andric "-misched-topdown incompatible with -misched-bottomup"); 3030eae32dcSDimitry Andric } 3040eae32dcSDimitry Andric 3050eae32dcSDimitry Andric VLIWResourceModel *ConvergingVLIWScheduler::createVLIWResourceModel( 3060eae32dcSDimitry Andric const TargetSubtargetInfo &STI, const TargetSchedModel *SchedModel) const { 3070eae32dcSDimitry Andric return new VLIWResourceModel(STI, SchedModel); 3080eae32dcSDimitry Andric } 3090eae32dcSDimitry Andric 3100eae32dcSDimitry Andric void ConvergingVLIWScheduler::releaseTopNode(SUnit *SU) { 3110eae32dcSDimitry Andric for (const SDep &PI : SU->Preds) { 3120eae32dcSDimitry Andric unsigned PredReadyCycle = PI.getSUnit()->TopReadyCycle; 3130eae32dcSDimitry Andric unsigned MinLatency = PI.getLatency(); 3140eae32dcSDimitry Andric #ifndef NDEBUG 3150eae32dcSDimitry Andric Top.MaxMinLatency = std::max(MinLatency, Top.MaxMinLatency); 3160eae32dcSDimitry Andric #endif 3170eae32dcSDimitry Andric if (SU->TopReadyCycle < PredReadyCycle + MinLatency) 3180eae32dcSDimitry Andric SU->TopReadyCycle = PredReadyCycle + MinLatency; 3190eae32dcSDimitry Andric } 3200eae32dcSDimitry Andric 3210eae32dcSDimitry Andric if (!SU->isScheduled) 3220eae32dcSDimitry Andric Top.releaseNode(SU, SU->TopReadyCycle); 3230eae32dcSDimitry Andric } 3240eae32dcSDimitry Andric 3250eae32dcSDimitry Andric void ConvergingVLIWScheduler::releaseBottomNode(SUnit *SU) { 3260eae32dcSDimitry Andric assert(SU->getInstr() && "Scheduled SUnit must have instr"); 3270eae32dcSDimitry Andric 3280eae32dcSDimitry Andric for (SUnit::succ_iterator I = SU->Succs.begin(), E = SU->Succs.end(); I != E; 3290eae32dcSDimitry Andric ++I) { 3300eae32dcSDimitry Andric unsigned SuccReadyCycle = I->getSUnit()->BotReadyCycle; 3310eae32dcSDimitry Andric unsigned MinLatency = I->getLatency(); 3320eae32dcSDimitry Andric #ifndef NDEBUG 3330eae32dcSDimitry Andric Bot.MaxMinLatency = std::max(MinLatency, Bot.MaxMinLatency); 3340eae32dcSDimitry Andric #endif 3350eae32dcSDimitry Andric if (SU->BotReadyCycle < SuccReadyCycle + MinLatency) 3360eae32dcSDimitry Andric SU->BotReadyCycle = SuccReadyCycle + MinLatency; 3370eae32dcSDimitry Andric } 3380eae32dcSDimitry Andric 3390eae32dcSDimitry Andric if (!SU->isScheduled) 3400eae32dcSDimitry Andric Bot.releaseNode(SU, SU->BotReadyCycle); 3410eae32dcSDimitry Andric } 3420eae32dcSDimitry Andric 3430eae32dcSDimitry Andric ConvergingVLIWScheduler::VLIWSchedBoundary::~VLIWSchedBoundary() { 3440eae32dcSDimitry Andric delete ResourceModel; 3450eae32dcSDimitry Andric delete HazardRec; 3460eae32dcSDimitry Andric } 3470eae32dcSDimitry Andric 3480eae32dcSDimitry Andric /// Does this SU have a hazard within the current instruction group. 3490eae32dcSDimitry Andric /// 3500eae32dcSDimitry Andric /// The scheduler supports two modes of hazard recognition. The first is the 3510eae32dcSDimitry Andric /// ScheduleHazardRecognizer API. It is a fully general hazard recognizer that 3520eae32dcSDimitry Andric /// supports highly complicated in-order reservation tables 3530eae32dcSDimitry Andric /// (ScoreboardHazardRecognizer) and arbitrary target-specific logic. 3540eae32dcSDimitry Andric /// 3550eae32dcSDimitry Andric /// The second is a streamlined mechanism that checks for hazards based on 3560eae32dcSDimitry Andric /// simple counters that the scheduler itself maintains. It explicitly checks 3570eae32dcSDimitry Andric /// for instruction dispatch limitations, including the number of micro-ops that 3580eae32dcSDimitry Andric /// can dispatch per cycle. 3590eae32dcSDimitry Andric /// 3600eae32dcSDimitry Andric /// TODO: Also check whether the SU must start a new group. 3610eae32dcSDimitry Andric bool ConvergingVLIWScheduler::VLIWSchedBoundary::checkHazard(SUnit *SU) { 3620eae32dcSDimitry Andric if (HazardRec->isEnabled()) 3630eae32dcSDimitry Andric return HazardRec->getHazardType(SU) != ScheduleHazardRecognizer::NoHazard; 3640eae32dcSDimitry Andric 3650eae32dcSDimitry Andric unsigned uops = SchedModel->getNumMicroOps(SU->getInstr()); 3660eae32dcSDimitry Andric if (IssueCount + uops > SchedModel->getIssueWidth()) 3670eae32dcSDimitry Andric return true; 3680eae32dcSDimitry Andric 3690eae32dcSDimitry Andric return false; 3700eae32dcSDimitry Andric } 3710eae32dcSDimitry Andric 3720eae32dcSDimitry Andric void ConvergingVLIWScheduler::VLIWSchedBoundary::releaseNode( 3730eae32dcSDimitry Andric SUnit *SU, unsigned ReadyCycle) { 3740eae32dcSDimitry Andric if (ReadyCycle < MinReadyCycle) 3750eae32dcSDimitry Andric MinReadyCycle = ReadyCycle; 3760eae32dcSDimitry Andric 3770eae32dcSDimitry Andric // Check for interlocks first. For the purpose of other heuristics, an 3780eae32dcSDimitry Andric // instruction that cannot issue appears as if it's not in the ReadyQueue. 3790eae32dcSDimitry Andric if (ReadyCycle > CurrCycle || checkHazard(SU)) 3800eae32dcSDimitry Andric 3810eae32dcSDimitry Andric Pending.push(SU); 3820eae32dcSDimitry Andric else 3830eae32dcSDimitry Andric Available.push(SU); 3840eae32dcSDimitry Andric } 3850eae32dcSDimitry Andric 3860eae32dcSDimitry Andric /// Move the boundary of scheduled code by one cycle. 3870eae32dcSDimitry Andric void ConvergingVLIWScheduler::VLIWSchedBoundary::bumpCycle() { 3880eae32dcSDimitry Andric unsigned Width = SchedModel->getIssueWidth(); 3890eae32dcSDimitry Andric IssueCount = (IssueCount <= Width) ? 0 : IssueCount - Width; 3900eae32dcSDimitry Andric 3910eae32dcSDimitry Andric assert(MinReadyCycle < std::numeric_limits<unsigned>::max() && 3920eae32dcSDimitry Andric "MinReadyCycle uninitialized"); 3930eae32dcSDimitry Andric unsigned NextCycle = std::max(CurrCycle + 1, MinReadyCycle); 3940eae32dcSDimitry Andric 3950eae32dcSDimitry Andric if (!HazardRec->isEnabled()) { 3960eae32dcSDimitry Andric // Bypass HazardRec virtual calls. 3970eae32dcSDimitry Andric CurrCycle = NextCycle; 3980eae32dcSDimitry Andric } else { 3990eae32dcSDimitry Andric // Bypass getHazardType calls in case of long latency. 4000eae32dcSDimitry Andric for (; CurrCycle != NextCycle; ++CurrCycle) { 4010eae32dcSDimitry Andric if (isTop()) 4020eae32dcSDimitry Andric HazardRec->AdvanceCycle(); 4030eae32dcSDimitry Andric else 4040eae32dcSDimitry Andric HazardRec->RecedeCycle(); 4050eae32dcSDimitry Andric } 4060eae32dcSDimitry Andric } 4070eae32dcSDimitry Andric CheckPending = true; 4080eae32dcSDimitry Andric 4090eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "*** Next cycle " << Available.getName() << " cycle " 4100eae32dcSDimitry Andric << CurrCycle << '\n'); 4110eae32dcSDimitry Andric } 4120eae32dcSDimitry Andric 4130eae32dcSDimitry Andric /// Move the boundary of scheduled code by one SUnit. 4140eae32dcSDimitry Andric void ConvergingVLIWScheduler::VLIWSchedBoundary::bumpNode(SUnit *SU) { 4150eae32dcSDimitry Andric bool startNewCycle = false; 4160eae32dcSDimitry Andric 4170eae32dcSDimitry Andric // Update the reservation table. 4180eae32dcSDimitry Andric if (HazardRec->isEnabled()) { 4190eae32dcSDimitry Andric if (!isTop() && SU->isCall) { 4200eae32dcSDimitry Andric // Calls are scheduled with their preceding instructions. For bottom-up 4210eae32dcSDimitry Andric // scheduling, clear the pipeline state before emitting. 4220eae32dcSDimitry Andric HazardRec->Reset(); 4230eae32dcSDimitry Andric } 4240eae32dcSDimitry Andric HazardRec->EmitInstruction(SU); 4250eae32dcSDimitry Andric } 4260eae32dcSDimitry Andric 4270eae32dcSDimitry Andric // Update DFA model. 4280eae32dcSDimitry Andric startNewCycle = ResourceModel->reserveResources(SU, isTop()); 4290eae32dcSDimitry Andric 4300eae32dcSDimitry Andric // Check the instruction group dispatch limit. 4310eae32dcSDimitry Andric // TODO: Check if this SU must end a dispatch group. 4320eae32dcSDimitry Andric IssueCount += SchedModel->getNumMicroOps(SU->getInstr()); 4330eae32dcSDimitry Andric if (startNewCycle) { 4340eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "*** Max instrs at cycle " << CurrCycle << '\n'); 4350eae32dcSDimitry Andric bumpCycle(); 4360eae32dcSDimitry Andric } else 4370eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "*** IssueCount " << IssueCount << " at cycle " 4380eae32dcSDimitry Andric << CurrCycle << '\n'); 4390eae32dcSDimitry Andric } 4400eae32dcSDimitry Andric 4410eae32dcSDimitry Andric /// Release pending ready nodes in to the available queue. This makes them 4420eae32dcSDimitry Andric /// visible to heuristics. 4430eae32dcSDimitry Andric void ConvergingVLIWScheduler::VLIWSchedBoundary::releasePending() { 4440eae32dcSDimitry Andric // If the available queue is empty, it is safe to reset MinReadyCycle. 4450eae32dcSDimitry Andric if (Available.empty()) 4460eae32dcSDimitry Andric MinReadyCycle = std::numeric_limits<unsigned>::max(); 4470eae32dcSDimitry Andric 4480eae32dcSDimitry Andric // Check to see if any of the pending instructions are ready to issue. If 4490eae32dcSDimitry Andric // so, add them to the available queue. 4500eae32dcSDimitry Andric for (unsigned i = 0, e = Pending.size(); i != e; ++i) { 4510eae32dcSDimitry Andric SUnit *SU = *(Pending.begin() + i); 4520eae32dcSDimitry Andric unsigned ReadyCycle = isTop() ? SU->TopReadyCycle : SU->BotReadyCycle; 4530eae32dcSDimitry Andric 4540eae32dcSDimitry Andric if (ReadyCycle < MinReadyCycle) 4550eae32dcSDimitry Andric MinReadyCycle = ReadyCycle; 4560eae32dcSDimitry Andric 4570eae32dcSDimitry Andric if (ReadyCycle > CurrCycle) 4580eae32dcSDimitry Andric continue; 4590eae32dcSDimitry Andric 4600eae32dcSDimitry Andric if (checkHazard(SU)) 4610eae32dcSDimitry Andric continue; 4620eae32dcSDimitry Andric 4630eae32dcSDimitry Andric Available.push(SU); 4640eae32dcSDimitry Andric Pending.remove(Pending.begin() + i); 4650eae32dcSDimitry Andric --i; 4660eae32dcSDimitry Andric --e; 4670eae32dcSDimitry Andric } 4680eae32dcSDimitry Andric CheckPending = false; 4690eae32dcSDimitry Andric } 4700eae32dcSDimitry Andric 4710eae32dcSDimitry Andric /// Remove SU from the ready set for this boundary. 4720eae32dcSDimitry Andric void ConvergingVLIWScheduler::VLIWSchedBoundary::removeReady(SUnit *SU) { 4730eae32dcSDimitry Andric if (Available.isInQueue(SU)) 4740eae32dcSDimitry Andric Available.remove(Available.find(SU)); 4750eae32dcSDimitry Andric else { 4760eae32dcSDimitry Andric assert(Pending.isInQueue(SU) && "bad ready count"); 4770eae32dcSDimitry Andric Pending.remove(Pending.find(SU)); 4780eae32dcSDimitry Andric } 4790eae32dcSDimitry Andric } 4800eae32dcSDimitry Andric 4810eae32dcSDimitry Andric /// If this queue only has one ready candidate, return it. As a side effect, 4820eae32dcSDimitry Andric /// advance the cycle until at least one node is ready. If multiple instructions 4830eae32dcSDimitry Andric /// are ready, return NULL. 4840eae32dcSDimitry Andric SUnit *ConvergingVLIWScheduler::VLIWSchedBoundary::pickOnlyChoice() { 4850eae32dcSDimitry Andric if (CheckPending) 4860eae32dcSDimitry Andric releasePending(); 4870eae32dcSDimitry Andric 4880eae32dcSDimitry Andric auto AdvanceCycle = [this]() { 4890eae32dcSDimitry Andric if (Available.empty()) 4900eae32dcSDimitry Andric return true; 4910eae32dcSDimitry Andric if (Available.size() == 1 && Pending.size() > 0) 4920eae32dcSDimitry Andric return !ResourceModel->isResourceAvailable(*Available.begin(), isTop()) || 4930eae32dcSDimitry Andric getWeakLeft(*Available.begin(), isTop()) != 0; 4940eae32dcSDimitry Andric return false; 4950eae32dcSDimitry Andric }; 4960eae32dcSDimitry Andric for (unsigned i = 0; AdvanceCycle(); ++i) { 4970eae32dcSDimitry Andric assert(i <= (HazardRec->getMaxLookAhead() + MaxMinLatency) && 4980eae32dcSDimitry Andric "permanent hazard"); 4990eae32dcSDimitry Andric (void)i; 5000eae32dcSDimitry Andric ResourceModel->reserveResources(nullptr, isTop()); 5010eae32dcSDimitry Andric bumpCycle(); 5020eae32dcSDimitry Andric releasePending(); 5030eae32dcSDimitry Andric } 5040eae32dcSDimitry Andric if (Available.size() == 1) 5050eae32dcSDimitry Andric return *Available.begin(); 5060eae32dcSDimitry Andric return nullptr; 5070eae32dcSDimitry Andric } 5080eae32dcSDimitry Andric 5090eae32dcSDimitry Andric #ifndef NDEBUG 5100eae32dcSDimitry Andric void ConvergingVLIWScheduler::traceCandidate(const char *Label, 5110eae32dcSDimitry Andric const ReadyQueue &Q, SUnit *SU, 5120eae32dcSDimitry Andric int Cost, PressureChange P) { 5130eae32dcSDimitry Andric dbgs() << Label << " " << Q.getName() << " "; 5140eae32dcSDimitry Andric if (P.isValid()) 5150eae32dcSDimitry Andric dbgs() << DAG->TRI->getRegPressureSetName(P.getPSet()) << ":" 5160eae32dcSDimitry Andric << P.getUnitInc() << " "; 5170eae32dcSDimitry Andric else 5180eae32dcSDimitry Andric dbgs() << " "; 5190eae32dcSDimitry Andric dbgs() << "cost(" << Cost << ")\t"; 5200eae32dcSDimitry Andric DAG->dumpNode(*SU); 5210eae32dcSDimitry Andric } 5220eae32dcSDimitry Andric 5230eae32dcSDimitry Andric // Very detailed queue dump, to be used with higher verbosity levels. 5240eae32dcSDimitry Andric void ConvergingVLIWScheduler::readyQueueVerboseDump( 5250eae32dcSDimitry Andric const RegPressureTracker &RPTracker, SchedCandidate &Candidate, 5260eae32dcSDimitry Andric ReadyQueue &Q) { 5270eae32dcSDimitry Andric RegPressureTracker &TempTracker = const_cast<RegPressureTracker &>(RPTracker); 5280eae32dcSDimitry Andric 5290eae32dcSDimitry Andric dbgs() << ">>> " << Q.getName() << "\n"; 5300eae32dcSDimitry Andric for (ReadyQueue::iterator I = Q.begin(), E = Q.end(); I != E; ++I) { 5310eae32dcSDimitry Andric RegPressureDelta RPDelta; 5320eae32dcSDimitry Andric TempTracker.getMaxPressureDelta((*I)->getInstr(), RPDelta, 5330eae32dcSDimitry Andric DAG->getRegionCriticalPSets(), 5340eae32dcSDimitry Andric DAG->getRegPressure().MaxSetPressure); 5350eae32dcSDimitry Andric std::stringstream dbgstr; 5360eae32dcSDimitry Andric dbgstr << "SU(" << std::setw(3) << (*I)->NodeNum << ")"; 5370eae32dcSDimitry Andric dbgs() << dbgstr.str(); 5380eae32dcSDimitry Andric SchedulingCost(Q, *I, Candidate, RPDelta, true); 5390eae32dcSDimitry Andric dbgs() << "\t"; 5400eae32dcSDimitry Andric (*I)->getInstr()->dump(); 5410eae32dcSDimitry Andric } 5420eae32dcSDimitry Andric dbgs() << "\n"; 5430eae32dcSDimitry Andric } 5440eae32dcSDimitry Andric #endif 5450eae32dcSDimitry Andric 5460eae32dcSDimitry Andric /// isSingleUnscheduledPred - If SU2 is the only unscheduled predecessor 5470eae32dcSDimitry Andric /// of SU, return true (we may have duplicates) 5480eae32dcSDimitry Andric static inline bool isSingleUnscheduledPred(SUnit *SU, SUnit *SU2) { 5490eae32dcSDimitry Andric if (SU->NumPredsLeft == 0) 5500eae32dcSDimitry Andric return false; 5510eae32dcSDimitry Andric 5520eae32dcSDimitry Andric for (auto &Pred : SU->Preds) { 5530eae32dcSDimitry Andric // We found an available, but not scheduled, predecessor. 5540eae32dcSDimitry Andric if (!Pred.getSUnit()->isScheduled && (Pred.getSUnit() != SU2)) 5550eae32dcSDimitry Andric return false; 5560eae32dcSDimitry Andric } 5570eae32dcSDimitry Andric 5580eae32dcSDimitry Andric return true; 5590eae32dcSDimitry Andric } 5600eae32dcSDimitry Andric 5610eae32dcSDimitry Andric /// isSingleUnscheduledSucc - If SU2 is the only unscheduled successor 5620eae32dcSDimitry Andric /// of SU, return true (we may have duplicates) 5630eae32dcSDimitry Andric static inline bool isSingleUnscheduledSucc(SUnit *SU, SUnit *SU2) { 5640eae32dcSDimitry Andric if (SU->NumSuccsLeft == 0) 5650eae32dcSDimitry Andric return false; 5660eae32dcSDimitry Andric 5670eae32dcSDimitry Andric for (auto &Succ : SU->Succs) { 5680eae32dcSDimitry Andric // We found an available, but not scheduled, successor. 5690eae32dcSDimitry Andric if (!Succ.getSUnit()->isScheduled && (Succ.getSUnit() != SU2)) 5700eae32dcSDimitry Andric return false; 5710eae32dcSDimitry Andric } 5720eae32dcSDimitry Andric return true; 5730eae32dcSDimitry Andric } 5740eae32dcSDimitry Andric 5750eae32dcSDimitry Andric /// Check if the instruction changes the register pressure of a register in the 5760eae32dcSDimitry Andric /// high pressure set. The function returns a negative value if the pressure 5770eae32dcSDimitry Andric /// decreases and a positive value is the pressure increases. If the instruction 5780eae32dcSDimitry Andric /// doesn't use a high pressure register or doesn't change the register 5790eae32dcSDimitry Andric /// pressure, then return 0. 5800eae32dcSDimitry Andric int ConvergingVLIWScheduler::pressureChange(const SUnit *SU, bool isBotUp) { 5810eae32dcSDimitry Andric PressureDiff &PD = DAG->getPressureDiff(SU); 582fcaf7f86SDimitry Andric for (const auto &P : PD) { 5830eae32dcSDimitry Andric if (!P.isValid()) 5840eae32dcSDimitry Andric continue; 585bdd1243dSDimitry Andric // The pressure differences are computed bottom-up, so the comparison for 5860eae32dcSDimitry Andric // an increase is positive in the bottom direction, but negative in the 5870eae32dcSDimitry Andric // top-down direction. 5880eae32dcSDimitry Andric if (HighPressureSets[P.getPSet()]) 5890eae32dcSDimitry Andric return (isBotUp ? P.getUnitInc() : -P.getUnitInc()); 5900eae32dcSDimitry Andric } 5910eae32dcSDimitry Andric return 0; 5920eae32dcSDimitry Andric } 5930eae32dcSDimitry Andric 5940eae32dcSDimitry Andric /// Single point to compute overall scheduling cost. 5950eae32dcSDimitry Andric /// TODO: More heuristics will be used soon. 5960eae32dcSDimitry Andric int ConvergingVLIWScheduler::SchedulingCost(ReadyQueue &Q, SUnit *SU, 5970eae32dcSDimitry Andric SchedCandidate &Candidate, 5980eae32dcSDimitry Andric RegPressureDelta &Delta, 5990eae32dcSDimitry Andric bool verbose) { 6000eae32dcSDimitry Andric // Initial trivial priority. 6010eae32dcSDimitry Andric int ResCount = 1; 6020eae32dcSDimitry Andric 6030eae32dcSDimitry Andric // Do not waste time on a node that is already scheduled. 6040eae32dcSDimitry Andric if (!SU || SU->isScheduled) 6050eae32dcSDimitry Andric return ResCount; 6060eae32dcSDimitry Andric 6070eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() 6080eae32dcSDimitry Andric << ((Q.getID() == TopQID) ? "(top|" : "(bot|")); 6090eae32dcSDimitry Andric // Forced priority is high. 6100eae32dcSDimitry Andric if (SU->isScheduleHigh) { 6110eae32dcSDimitry Andric ResCount += PriorityOne; 6120eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "H|"); 6130eae32dcSDimitry Andric } 6140eae32dcSDimitry Andric 6150eae32dcSDimitry Andric unsigned IsAvailableAmt = 0; 6160eae32dcSDimitry Andric // Critical path first. 6170eae32dcSDimitry Andric if (Q.getID() == TopQID) { 6180eae32dcSDimitry Andric if (Top.isLatencyBound(SU)) { 6190eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "LB|"); 6200eae32dcSDimitry Andric ResCount += (SU->getHeight() * ScaleTwo); 6210eae32dcSDimitry Andric } 6220eae32dcSDimitry Andric 6230eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) { 6240eae32dcSDimitry Andric std::stringstream dbgstr; 6250eae32dcSDimitry Andric dbgstr << "h" << std::setw(3) << SU->getHeight() << "|"; 6260eae32dcSDimitry Andric dbgs() << dbgstr.str(); 6270eae32dcSDimitry Andric }); 6280eae32dcSDimitry Andric 6290eae32dcSDimitry Andric // If resources are available for it, multiply the 6300eae32dcSDimitry Andric // chance of scheduling. 6310eae32dcSDimitry Andric if (Top.ResourceModel->isResourceAvailable(SU, true)) { 6320eae32dcSDimitry Andric IsAvailableAmt = (PriorityTwo + PriorityThree); 6330eae32dcSDimitry Andric ResCount += IsAvailableAmt; 6340eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "A|"); 6350eae32dcSDimitry Andric } else 6360eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << " |"); 6370eae32dcSDimitry Andric } else { 6380eae32dcSDimitry Andric if (Bot.isLatencyBound(SU)) { 6390eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "LB|"); 6400eae32dcSDimitry Andric ResCount += (SU->getDepth() * ScaleTwo); 6410eae32dcSDimitry Andric } 6420eae32dcSDimitry Andric 6430eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) { 6440eae32dcSDimitry Andric std::stringstream dbgstr; 6450eae32dcSDimitry Andric dbgstr << "d" << std::setw(3) << SU->getDepth() << "|"; 6460eae32dcSDimitry Andric dbgs() << dbgstr.str(); 6470eae32dcSDimitry Andric }); 6480eae32dcSDimitry Andric 6490eae32dcSDimitry Andric // If resources are available for it, multiply the 6500eae32dcSDimitry Andric // chance of scheduling. 6510eae32dcSDimitry Andric if (Bot.ResourceModel->isResourceAvailable(SU, false)) { 6520eae32dcSDimitry Andric IsAvailableAmt = (PriorityTwo + PriorityThree); 6530eae32dcSDimitry Andric ResCount += IsAvailableAmt; 6540eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "A|"); 6550eae32dcSDimitry Andric } else 6560eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << " |"); 6570eae32dcSDimitry Andric } 6580eae32dcSDimitry Andric 6590eae32dcSDimitry Andric unsigned NumNodesBlocking = 0; 6600eae32dcSDimitry Andric if (Q.getID() == TopQID) { 6610eae32dcSDimitry Andric // How many SUs does it block from scheduling? 6620eae32dcSDimitry Andric // Look at all of the successors of this node. 6630eae32dcSDimitry Andric // Count the number of nodes that 6640eae32dcSDimitry Andric // this node is the sole unscheduled node for. 6650eae32dcSDimitry Andric if (Top.isLatencyBound(SU)) 6660eae32dcSDimitry Andric for (const SDep &SI : SU->Succs) 6670eae32dcSDimitry Andric if (isSingleUnscheduledPred(SI.getSUnit(), SU)) 6680eae32dcSDimitry Andric ++NumNodesBlocking; 6690eae32dcSDimitry Andric } else { 6700eae32dcSDimitry Andric // How many unscheduled predecessors block this node? 6710eae32dcSDimitry Andric if (Bot.isLatencyBound(SU)) 6720eae32dcSDimitry Andric for (const SDep &PI : SU->Preds) 6730eae32dcSDimitry Andric if (isSingleUnscheduledSucc(PI.getSUnit(), SU)) 6740eae32dcSDimitry Andric ++NumNodesBlocking; 6750eae32dcSDimitry Andric } 6760eae32dcSDimitry Andric ResCount += (NumNodesBlocking * ScaleTwo); 6770eae32dcSDimitry Andric 6780eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) { 6790eae32dcSDimitry Andric std::stringstream dbgstr; 6800eae32dcSDimitry Andric dbgstr << "blk " << std::setw(2) << NumNodesBlocking << ")|"; 6810eae32dcSDimitry Andric dbgs() << dbgstr.str(); 6820eae32dcSDimitry Andric }); 6830eae32dcSDimitry Andric 6840eae32dcSDimitry Andric // Factor in reg pressure as a heuristic. 6850eae32dcSDimitry Andric if (!IgnoreBBRegPressure) { 6860eae32dcSDimitry Andric // Decrease priority by the amount that register pressure exceeds the limit. 6870eae32dcSDimitry Andric ResCount -= (Delta.Excess.getUnitInc() * PriorityOne); 6880eae32dcSDimitry Andric // Decrease priority if register pressure exceeds the limit. 6890eae32dcSDimitry Andric ResCount -= (Delta.CriticalMax.getUnitInc() * PriorityOne); 6900eae32dcSDimitry Andric // Decrease priority slightly if register pressure would increase over the 6910eae32dcSDimitry Andric // current maximum. 6920eae32dcSDimitry Andric ResCount -= (Delta.CurrentMax.getUnitInc() * PriorityTwo); 6930eae32dcSDimitry Andric // If there are register pressure issues, then we remove the value added for 6940eae32dcSDimitry Andric // the instruction being available. The rationale is that we really don't 6950eae32dcSDimitry Andric // want to schedule an instruction that causes a spill. 6960eae32dcSDimitry Andric if (IsAvailableAmt && pressureChange(SU, Q.getID() != TopQID) > 0 && 6970eae32dcSDimitry Andric (Delta.Excess.getUnitInc() || Delta.CriticalMax.getUnitInc() || 6980eae32dcSDimitry Andric Delta.CurrentMax.getUnitInc())) 6990eae32dcSDimitry Andric ResCount -= IsAvailableAmt; 7000eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) { 7010eae32dcSDimitry Andric dbgs() << "RP " << Delta.Excess.getUnitInc() << "/" 7020eae32dcSDimitry Andric << Delta.CriticalMax.getUnitInc() << "/" 7030eae32dcSDimitry Andric << Delta.CurrentMax.getUnitInc() << ")|"; 7040eae32dcSDimitry Andric }); 7050eae32dcSDimitry Andric } 7060eae32dcSDimitry Andric 7070eae32dcSDimitry Andric // Give preference to a zero latency instruction if the dependent 7080eae32dcSDimitry Andric // instruction is in the current packet. 7090eae32dcSDimitry Andric if (Q.getID() == TopQID && getWeakLeft(SU, true) == 0) { 7100eae32dcSDimitry Andric for (const SDep &PI : SU->Preds) { 7110eae32dcSDimitry Andric if (!PI.getSUnit()->getInstr()->isPseudo() && PI.isAssignedRegDep() && 7120eae32dcSDimitry Andric PI.getLatency() == 0 && 7130eae32dcSDimitry Andric Top.ResourceModel->isInPacket(PI.getSUnit())) { 7140eae32dcSDimitry Andric ResCount += PriorityThree; 7150eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "Z|"); 7160eae32dcSDimitry Andric } 7170eae32dcSDimitry Andric } 7180eae32dcSDimitry Andric } else if (Q.getID() == BotQID && getWeakLeft(SU, false) == 0) { 7190eae32dcSDimitry Andric for (const SDep &SI : SU->Succs) { 7200eae32dcSDimitry Andric if (!SI.getSUnit()->getInstr()->isPseudo() && SI.isAssignedRegDep() && 7210eae32dcSDimitry Andric SI.getLatency() == 0 && 7220eae32dcSDimitry Andric Bot.ResourceModel->isInPacket(SI.getSUnit())) { 7230eae32dcSDimitry Andric ResCount += PriorityThree; 7240eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "Z|"); 7250eae32dcSDimitry Andric } 7260eae32dcSDimitry Andric } 7270eae32dcSDimitry Andric } 7280eae32dcSDimitry Andric 7290eae32dcSDimitry Andric // If the instruction has a non-zero latency dependence with an instruction in 7300eae32dcSDimitry Andric // the current packet, then it should not be scheduled yet. The case occurs 7310eae32dcSDimitry Andric // when the dependent instruction is scheduled in a new packet, so the 7320eae32dcSDimitry Andric // scheduler updates the current cycle and pending instructions become 7330eae32dcSDimitry Andric // available. 7340eae32dcSDimitry Andric if (CheckEarlyAvail) { 7350eae32dcSDimitry Andric if (Q.getID() == TopQID) { 7360eae32dcSDimitry Andric for (const auto &PI : SU->Preds) { 7370eae32dcSDimitry Andric if (PI.getLatency() > 0 && 7380eae32dcSDimitry Andric Top.ResourceModel->isInPacket(PI.getSUnit())) { 7390eae32dcSDimitry Andric ResCount -= PriorityOne; 7400eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "D|"); 7410eae32dcSDimitry Andric } 7420eae32dcSDimitry Andric } 7430eae32dcSDimitry Andric } else { 7440eae32dcSDimitry Andric for (const auto &SI : SU->Succs) { 7450eae32dcSDimitry Andric if (SI.getLatency() > 0 && 7460eae32dcSDimitry Andric Bot.ResourceModel->isInPacket(SI.getSUnit())) { 7470eae32dcSDimitry Andric ResCount -= PriorityOne; 7480eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) dbgs() << "D|"); 7490eae32dcSDimitry Andric } 7500eae32dcSDimitry Andric } 7510eae32dcSDimitry Andric } 7520eae32dcSDimitry Andric } 7530eae32dcSDimitry Andric 7540eae32dcSDimitry Andric LLVM_DEBUG(if (verbose) { 7550eae32dcSDimitry Andric std::stringstream dbgstr; 7560eae32dcSDimitry Andric dbgstr << "Total " << std::setw(4) << ResCount << ")"; 7570eae32dcSDimitry Andric dbgs() << dbgstr.str(); 7580eae32dcSDimitry Andric }); 7590eae32dcSDimitry Andric 7600eae32dcSDimitry Andric return ResCount; 7610eae32dcSDimitry Andric } 7620eae32dcSDimitry Andric 7630eae32dcSDimitry Andric /// Pick the best candidate from the top queue. 7640eae32dcSDimitry Andric /// 7650eae32dcSDimitry Andric /// TODO: getMaxPressureDelta results can be mostly cached for each SUnit during 7660eae32dcSDimitry Andric /// DAG building. To adjust for the current scheduling location we need to 7670eae32dcSDimitry Andric /// maintain the number of vreg uses remaining to be top-scheduled. 7680eae32dcSDimitry Andric ConvergingVLIWScheduler::CandResult 7690eae32dcSDimitry Andric ConvergingVLIWScheduler::pickNodeFromQueue(VLIWSchedBoundary &Zone, 7700eae32dcSDimitry Andric const RegPressureTracker &RPTracker, 7710eae32dcSDimitry Andric SchedCandidate &Candidate) { 7720eae32dcSDimitry Andric ReadyQueue &Q = Zone.Available; 7730eae32dcSDimitry Andric LLVM_DEBUG(if (SchedDebugVerboseLevel > 1) 7740eae32dcSDimitry Andric readyQueueVerboseDump(RPTracker, Candidate, Q); 7750eae32dcSDimitry Andric else Q.dump();); 7760eae32dcSDimitry Andric 7770eae32dcSDimitry Andric // getMaxPressureDelta temporarily modifies the tracker. 7780eae32dcSDimitry Andric RegPressureTracker &TempTracker = const_cast<RegPressureTracker &>(RPTracker); 7790eae32dcSDimitry Andric 7800eae32dcSDimitry Andric // BestSU remains NULL if no top candidates beat the best existing candidate. 7810eae32dcSDimitry Andric CandResult FoundCandidate = NoCand; 7820eae32dcSDimitry Andric for (ReadyQueue::iterator I = Q.begin(), E = Q.end(); I != E; ++I) { 7830eae32dcSDimitry Andric RegPressureDelta RPDelta; 7840eae32dcSDimitry Andric TempTracker.getMaxPressureDelta((*I)->getInstr(), RPDelta, 7850eae32dcSDimitry Andric DAG->getRegionCriticalPSets(), 7860eae32dcSDimitry Andric DAG->getRegPressure().MaxSetPressure); 7870eae32dcSDimitry Andric 7880eae32dcSDimitry Andric int CurrentCost = SchedulingCost(Q, *I, Candidate, RPDelta, false); 7890eae32dcSDimitry Andric 7900eae32dcSDimitry Andric // Initialize the candidate if needed. 7910eae32dcSDimitry Andric if (!Candidate.SU) { 7920eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("DCAND", Q, *I, CurrentCost)); 7930eae32dcSDimitry Andric Candidate.SU = *I; 7940eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 7950eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 7960eae32dcSDimitry Andric FoundCandidate = NodeOrder; 7970eae32dcSDimitry Andric continue; 7980eae32dcSDimitry Andric } 7990eae32dcSDimitry Andric 8000eae32dcSDimitry Andric // Choose node order for negative cost candidates. There is no good 8010eae32dcSDimitry Andric // candidate in this case. 8020eae32dcSDimitry Andric if (CurrentCost < 0 && Candidate.SCost < 0) { 8030eae32dcSDimitry Andric if ((Q.getID() == TopQID && (*I)->NodeNum < Candidate.SU->NodeNum) || 8040eae32dcSDimitry Andric (Q.getID() == BotQID && (*I)->NodeNum > Candidate.SU->NodeNum)) { 8050eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("NCAND", Q, *I, CurrentCost)); 8060eae32dcSDimitry Andric Candidate.SU = *I; 8070eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 8080eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 8090eae32dcSDimitry Andric FoundCandidate = NodeOrder; 8100eae32dcSDimitry Andric } 8110eae32dcSDimitry Andric continue; 8120eae32dcSDimitry Andric } 8130eae32dcSDimitry Andric 8140eae32dcSDimitry Andric // Best cost. 8150eae32dcSDimitry Andric if (CurrentCost > Candidate.SCost) { 8160eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("CCAND", Q, *I, CurrentCost)); 8170eae32dcSDimitry Andric Candidate.SU = *I; 8180eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 8190eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 8200eae32dcSDimitry Andric FoundCandidate = BestCost; 8210eae32dcSDimitry Andric continue; 8220eae32dcSDimitry Andric } 8230eae32dcSDimitry Andric 8240eae32dcSDimitry Andric // Choose an instruction that does not depend on an artificial edge. 8250eae32dcSDimitry Andric unsigned CurrWeak = getWeakLeft(*I, (Q.getID() == TopQID)); 8260eae32dcSDimitry Andric unsigned CandWeak = getWeakLeft(Candidate.SU, (Q.getID() == TopQID)); 8270eae32dcSDimitry Andric if (CurrWeak != CandWeak) { 8280eae32dcSDimitry Andric if (CurrWeak < CandWeak) { 8290eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("WCAND", Q, *I, CurrentCost)); 8300eae32dcSDimitry Andric Candidate.SU = *I; 8310eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 8320eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 8330eae32dcSDimitry Andric FoundCandidate = Weak; 8340eae32dcSDimitry Andric } 8350eae32dcSDimitry Andric continue; 8360eae32dcSDimitry Andric } 8370eae32dcSDimitry Andric 8380eae32dcSDimitry Andric if (CurrentCost == Candidate.SCost && Zone.isLatencyBound(*I)) { 8390eae32dcSDimitry Andric unsigned CurrSize, CandSize; 8400eae32dcSDimitry Andric if (Q.getID() == TopQID) { 8410eae32dcSDimitry Andric CurrSize = (*I)->Succs.size(); 8420eae32dcSDimitry Andric CandSize = Candidate.SU->Succs.size(); 8430eae32dcSDimitry Andric } else { 8440eae32dcSDimitry Andric CurrSize = (*I)->Preds.size(); 8450eae32dcSDimitry Andric CandSize = Candidate.SU->Preds.size(); 8460eae32dcSDimitry Andric } 8470eae32dcSDimitry Andric if (CurrSize > CandSize) { 8480eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("SPCAND", Q, *I, CurrentCost)); 8490eae32dcSDimitry Andric Candidate.SU = *I; 8500eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 8510eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 8520eae32dcSDimitry Andric FoundCandidate = BestCost; 8530eae32dcSDimitry Andric } 8540eae32dcSDimitry Andric // Keep the old candidate if it's a better candidate. That is, don't use 8550eae32dcSDimitry Andric // the subsequent tie breaker. 8560eae32dcSDimitry Andric if (CurrSize != CandSize) 8570eae32dcSDimitry Andric continue; 8580eae32dcSDimitry Andric } 8590eae32dcSDimitry Andric 8600eae32dcSDimitry Andric // Tie breaker. 8610eae32dcSDimitry Andric // To avoid scheduling indeterminism, we need a tie breaker 8620eae32dcSDimitry Andric // for the case when cost is identical for two nodes. 8630eae32dcSDimitry Andric if (UseNewerCandidate && CurrentCost == Candidate.SCost) { 8640eae32dcSDimitry Andric if ((Q.getID() == TopQID && (*I)->NodeNum < Candidate.SU->NodeNum) || 8650eae32dcSDimitry Andric (Q.getID() == BotQID && (*I)->NodeNum > Candidate.SU->NodeNum)) { 8660eae32dcSDimitry Andric LLVM_DEBUG(traceCandidate("TCAND", Q, *I, CurrentCost)); 8670eae32dcSDimitry Andric Candidate.SU = *I; 8680eae32dcSDimitry Andric Candidate.RPDelta = RPDelta; 8690eae32dcSDimitry Andric Candidate.SCost = CurrentCost; 8700eae32dcSDimitry Andric FoundCandidate = NodeOrder; 8710eae32dcSDimitry Andric continue; 8720eae32dcSDimitry Andric } 8730eae32dcSDimitry Andric } 8740eae32dcSDimitry Andric 8750eae32dcSDimitry Andric // Fall through to original instruction order. 8760eae32dcSDimitry Andric // Only consider node order if Candidate was chosen from this Q. 8770eae32dcSDimitry Andric if (FoundCandidate == NoCand) 8780eae32dcSDimitry Andric continue; 8790eae32dcSDimitry Andric } 8800eae32dcSDimitry Andric return FoundCandidate; 8810eae32dcSDimitry Andric } 8820eae32dcSDimitry Andric 8830eae32dcSDimitry Andric /// Pick the best candidate node from either the top or bottom queue. 8840eae32dcSDimitry Andric SUnit *ConvergingVLIWScheduler::pickNodeBidrectional(bool &IsTopNode) { 8850eae32dcSDimitry Andric // Schedule as far as possible in the direction of no choice. This is most 8860eae32dcSDimitry Andric // efficient, but also provides the best heuristics for CriticalPSets. 8870eae32dcSDimitry Andric if (SUnit *SU = Bot.pickOnlyChoice()) { 8880eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Picked only Bottom\n"); 8890eae32dcSDimitry Andric IsTopNode = false; 8900eae32dcSDimitry Andric return SU; 8910eae32dcSDimitry Andric } 8920eae32dcSDimitry Andric if (SUnit *SU = Top.pickOnlyChoice()) { 8930eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Picked only Top\n"); 8940eae32dcSDimitry Andric IsTopNode = true; 8950eae32dcSDimitry Andric return SU; 8960eae32dcSDimitry Andric } 8970eae32dcSDimitry Andric SchedCandidate BotCand; 8980eae32dcSDimitry Andric // Prefer bottom scheduling when heuristics are silent. 8990eae32dcSDimitry Andric CandResult BotResult = 9000eae32dcSDimitry Andric pickNodeFromQueue(Bot, DAG->getBotRPTracker(), BotCand); 9010eae32dcSDimitry Andric assert(BotResult != NoCand && "failed to find the first candidate"); 9020eae32dcSDimitry Andric 9030eae32dcSDimitry Andric // If either Q has a single candidate that provides the least increase in 9040eae32dcSDimitry Andric // Excess pressure, we can immediately schedule from that Q. 9050eae32dcSDimitry Andric // 9060eae32dcSDimitry Andric // RegionCriticalPSets summarizes the pressure within the scheduled region and 9070eae32dcSDimitry Andric // affects picking from either Q. If scheduling in one direction must 9080eae32dcSDimitry Andric // increase pressure for one of the excess PSets, then schedule in that 9090eae32dcSDimitry Andric // direction first to provide more freedom in the other direction. 9100eae32dcSDimitry Andric if (BotResult == SingleExcess || BotResult == SingleCritical) { 9110eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Bottom Node\n"); 9120eae32dcSDimitry Andric IsTopNode = false; 9130eae32dcSDimitry Andric return BotCand.SU; 9140eae32dcSDimitry Andric } 9150eae32dcSDimitry Andric // Check if the top Q has a better candidate. 9160eae32dcSDimitry Andric SchedCandidate TopCand; 9170eae32dcSDimitry Andric CandResult TopResult = 9180eae32dcSDimitry Andric pickNodeFromQueue(Top, DAG->getTopRPTracker(), TopCand); 9190eae32dcSDimitry Andric assert(TopResult != NoCand && "failed to find the first candidate"); 9200eae32dcSDimitry Andric 9210eae32dcSDimitry Andric if (TopResult == SingleExcess || TopResult == SingleCritical) { 9220eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Top Node\n"); 9230eae32dcSDimitry Andric IsTopNode = true; 9240eae32dcSDimitry Andric return TopCand.SU; 9250eae32dcSDimitry Andric } 9260eae32dcSDimitry Andric // If either Q has a single candidate that minimizes pressure above the 9270eae32dcSDimitry Andric // original region's pressure pick it. 9280eae32dcSDimitry Andric if (BotResult == SingleMax) { 9290eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Bottom Node SingleMax\n"); 9300eae32dcSDimitry Andric IsTopNode = false; 9310eae32dcSDimitry Andric return BotCand.SU; 9320eae32dcSDimitry Andric } 9330eae32dcSDimitry Andric if (TopResult == SingleMax) { 9340eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Top Node SingleMax\n"); 9350eae32dcSDimitry Andric IsTopNode = true; 9360eae32dcSDimitry Andric return TopCand.SU; 9370eae32dcSDimitry Andric } 9380eae32dcSDimitry Andric if (TopCand.SCost > BotCand.SCost) { 9390eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Top Node Cost\n"); 9400eae32dcSDimitry Andric IsTopNode = true; 9410eae32dcSDimitry Andric return TopCand.SU; 9420eae32dcSDimitry Andric } 9430eae32dcSDimitry Andric // Otherwise prefer the bottom candidate in node order. 9440eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "Prefered Bottom in Node order\n"); 9450eae32dcSDimitry Andric IsTopNode = false; 9460eae32dcSDimitry Andric return BotCand.SU; 9470eae32dcSDimitry Andric } 9480eae32dcSDimitry Andric 9490eae32dcSDimitry Andric /// Pick the best node to balance the schedule. Implements MachineSchedStrategy. 9500eae32dcSDimitry Andric SUnit *ConvergingVLIWScheduler::pickNode(bool &IsTopNode) { 9510eae32dcSDimitry Andric if (DAG->top() == DAG->bottom()) { 9520eae32dcSDimitry Andric assert(Top.Available.empty() && Top.Pending.empty() && 9530eae32dcSDimitry Andric Bot.Available.empty() && Bot.Pending.empty() && "ReadyQ garbage"); 9540eae32dcSDimitry Andric return nullptr; 9550eae32dcSDimitry Andric } 9560eae32dcSDimitry Andric SUnit *SU; 9570eae32dcSDimitry Andric if (ForceTopDown) { 9580eae32dcSDimitry Andric SU = Top.pickOnlyChoice(); 9590eae32dcSDimitry Andric if (!SU) { 9600eae32dcSDimitry Andric SchedCandidate TopCand; 9610eae32dcSDimitry Andric CandResult TopResult = 9620eae32dcSDimitry Andric pickNodeFromQueue(Top, DAG->getTopRPTracker(), TopCand); 9630eae32dcSDimitry Andric assert(TopResult != NoCand && "failed to find the first candidate"); 9640eae32dcSDimitry Andric (void)TopResult; 9650eae32dcSDimitry Andric SU = TopCand.SU; 9660eae32dcSDimitry Andric } 9670eae32dcSDimitry Andric IsTopNode = true; 9680eae32dcSDimitry Andric } else if (ForceBottomUp) { 9690eae32dcSDimitry Andric SU = Bot.pickOnlyChoice(); 9700eae32dcSDimitry Andric if (!SU) { 9710eae32dcSDimitry Andric SchedCandidate BotCand; 9720eae32dcSDimitry Andric CandResult BotResult = 9730eae32dcSDimitry Andric pickNodeFromQueue(Bot, DAG->getBotRPTracker(), BotCand); 9740eae32dcSDimitry Andric assert(BotResult != NoCand && "failed to find the first candidate"); 9750eae32dcSDimitry Andric (void)BotResult; 9760eae32dcSDimitry Andric SU = BotCand.SU; 9770eae32dcSDimitry Andric } 9780eae32dcSDimitry Andric IsTopNode = false; 9790eae32dcSDimitry Andric } else { 9800eae32dcSDimitry Andric SU = pickNodeBidrectional(IsTopNode); 9810eae32dcSDimitry Andric } 9820eae32dcSDimitry Andric if (SU->isTopReady()) 9830eae32dcSDimitry Andric Top.removeReady(SU); 9840eae32dcSDimitry Andric if (SU->isBottomReady()) 9850eae32dcSDimitry Andric Bot.removeReady(SU); 9860eae32dcSDimitry Andric 9870eae32dcSDimitry Andric LLVM_DEBUG(dbgs() << "*** " << (IsTopNode ? "Top" : "Bottom") 9880eae32dcSDimitry Andric << " Scheduling instruction in cycle " 9890eae32dcSDimitry Andric << (IsTopNode ? Top.CurrCycle : Bot.CurrCycle) << " (" 9900eae32dcSDimitry Andric << reportPackets() << ")\n"; 9910eae32dcSDimitry Andric DAG->dumpNode(*SU)); 9920eae32dcSDimitry Andric return SU; 9930eae32dcSDimitry Andric } 9940eae32dcSDimitry Andric 9950eae32dcSDimitry Andric /// Update the scheduler's state after scheduling a node. This is the same node 9960eae32dcSDimitry Andric /// that was just returned by pickNode(). However, VLIWMachineScheduler needs 9970eae32dcSDimitry Andric /// to update it's state based on the current cycle before MachineSchedStrategy 9980eae32dcSDimitry Andric /// does. 9990eae32dcSDimitry Andric void ConvergingVLIWScheduler::schedNode(SUnit *SU, bool IsTopNode) { 10000eae32dcSDimitry Andric if (IsTopNode) { 10010eae32dcSDimitry Andric Top.bumpNode(SU); 10020eae32dcSDimitry Andric SU->TopReadyCycle = Top.CurrCycle; 10030eae32dcSDimitry Andric } else { 10040eae32dcSDimitry Andric Bot.bumpNode(SU); 10050eae32dcSDimitry Andric SU->BotReadyCycle = Bot.CurrCycle; 10060eae32dcSDimitry Andric } 10070eae32dcSDimitry Andric } 1008