1 //===- Scheduler.cpp ------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/Transforms/Vectorize/SandboxVectorizer/Scheduler.h" 10 #include "llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h" 11 12 namespace llvm::sandboxir { 13 14 // TODO: Check if we can cache top/bottom to reduce compile-time. 15 DGNode *SchedBundle::getTop() const { 16 DGNode *TopN = Nodes.front(); 17 for (auto *N : drop_begin(Nodes)) { 18 if (N->getInstruction()->comesBefore(TopN->getInstruction())) 19 TopN = N; 20 } 21 return TopN; 22 } 23 24 DGNode *SchedBundle::getBot() const { 25 DGNode *BotN = Nodes.front(); 26 for (auto *N : drop_begin(Nodes)) { 27 if (BotN->getInstruction()->comesBefore(N->getInstruction())) 28 BotN = N; 29 } 30 return BotN; 31 } 32 33 void SchedBundle::cluster(BasicBlock::iterator Where) { 34 for (auto *N : Nodes) { 35 auto *I = N->getInstruction(); 36 if (I->getIterator() == Where) 37 ++Where; // Try to maintain bundle order. 38 I->moveBefore(*Where.getNodeParent(), Where); 39 } 40 } 41 42 #ifndef NDEBUG 43 void SchedBundle::dump(raw_ostream &OS) const { 44 for (auto *N : Nodes) 45 OS << *N; 46 } 47 48 void SchedBundle::dump() const { 49 dump(dbgs()); 50 dbgs() << "\n"; 51 } 52 #endif // NDEBUG 53 54 #ifndef NDEBUG 55 void ReadyListContainer::dump(raw_ostream &OS) const { 56 auto ListCopy = List; 57 while (!ListCopy.empty()) { 58 OS << *ListCopy.top() << "\n"; 59 ListCopy.pop(); 60 } 61 } 62 63 void ReadyListContainer::dump() const { 64 dump(dbgs()); 65 dbgs() << "\n"; 66 } 67 #endif // NDEBUG 68 69 void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) { 70 // Find where we should schedule the instructions. 71 assert(ScheduleTopItOpt && "Should have been set by now!"); 72 auto Where = *ScheduleTopItOpt; 73 // Move all instructions in `Bndl` to `Where`. 74 Bndl.cluster(Where); 75 // Update the last scheduled bundle. 76 ScheduleTopItOpt = Bndl.getTop()->getInstruction()->getIterator(); 77 // Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all 78 // dependency predecessors. 79 for (DGNode *N : Bndl) { 80 N->setScheduled(true); 81 for (auto *DepN : N->preds(DAG)) { 82 // TODO: preds() should not return nullptr. 83 if (DepN == nullptr) 84 continue; 85 DepN->decrUnscheduledSuccs(); 86 if (DepN->ready()) 87 ReadyList.insert(DepN); 88 } 89 } 90 } 91 92 SchedBundle *Scheduler::createBundle(ArrayRef<Instruction *> Instrs) { 93 SchedBundle::ContainerTy Nodes; 94 Nodes.reserve(Instrs.size()); 95 for (auto *I : Instrs) 96 Nodes.push_back(DAG.getNode(I)); 97 auto BndlPtr = std::make_unique<SchedBundle>(std::move(Nodes)); 98 auto *Bndl = BndlPtr.get(); 99 Bndls[Bndl] = std::move(BndlPtr); 100 return Bndl; 101 } 102 103 void Scheduler::eraseBundle(SchedBundle *SB) { Bndls.erase(SB); } 104 105 bool Scheduler::tryScheduleUntil(ArrayRef<Instruction *> Instrs) { 106 // Use a set of instructions, instead of `Instrs` for fast lookups. 107 DenseSet<Instruction *> InstrsToDefer(Instrs.begin(), Instrs.end()); 108 // This collects the nodes that correspond to instructions found in `Instrs` 109 // that have just become ready. These nodes won't be scheduled right away. 110 SmallVector<DGNode *, 8> DeferredNodes; 111 112 // Keep scheduling ready nodes until we either run out of ready nodes (i.e., 113 // ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of 114 // which are collected in DeferredNodes) are all ready to schedule. 115 while (!ReadyList.empty()) { 116 auto *ReadyN = ReadyList.pop(); 117 if (InstrsToDefer.contains(ReadyN->getInstruction())) { 118 // If the ready instruction is one of those in `Instrs`, then we don't 119 // schedule it right away. Instead we defer it until we can schedule it 120 // along with the rest of the instructions in `Instrs`, at the same 121 // time in a single scheduling bundle. 122 DeferredNodes.push_back(ReadyN); 123 bool ReadyToScheduleDeferred = DeferredNodes.size() == Instrs.size(); 124 if (ReadyToScheduleDeferred) { 125 scheduleAndUpdateReadyList(*createBundle(Instrs)); 126 return true; 127 } 128 } else { 129 // If the ready instruction is not found in `Instrs`, then we wrap it in a 130 // scheduling bundle and schedule it right away. 131 scheduleAndUpdateReadyList(*createBundle({ReadyN->getInstruction()})); 132 } 133 } 134 assert(DeferredNodes.size() != Instrs.size() && 135 "We should have succesfully scheduled and early-returned!"); 136 return false; 137 } 138 139 Scheduler::BndlSchedState 140 Scheduler::getBndlSchedState(ArrayRef<Instruction *> Instrs) const { 141 assert(!Instrs.empty() && "Expected non-empty bundle"); 142 bool PartiallyScheduled = false; 143 bool FullyScheduled = true; 144 for (auto *I : Instrs) { 145 auto *N = DAG.getNode(I); 146 if (N != nullptr && N->scheduled()) 147 PartiallyScheduled = true; 148 else 149 FullyScheduled = false; 150 } 151 if (FullyScheduled) { 152 // If not all instrs in the bundle are in the same SchedBundle then this 153 // should be considered as partially-scheduled, because we will need to 154 // re-schedule. 155 SchedBundle *SB = DAG.getNode(Instrs[0])->getSchedBundle(); 156 assert(SB != nullptr && "FullyScheduled assumes that there is an SB!"); 157 if (any_of(drop_begin(Instrs), [this, SB](sandboxir::Value *SBV) { 158 return DAG.getNode(cast<sandboxir::Instruction>(SBV)) 159 ->getSchedBundle() != SB; 160 })) 161 FullyScheduled = false; 162 } 163 return FullyScheduled ? BndlSchedState::FullyScheduled 164 : PartiallyScheduled ? BndlSchedState::PartiallyOrDifferentlyScheduled 165 : BndlSchedState::NoneScheduled; 166 } 167 168 void Scheduler::trimSchedule(ArrayRef<Instruction *> Instrs) { 169 Instruction *TopI = &*ScheduleTopItOpt.value(); 170 Instruction *LowestI = VecUtils::getLowest(Instrs); 171 // Destroy the schedule bundles from LowestI all the way to the top. 172 for (auto *I = LowestI, *E = TopI->getPrevNode(); I != E; 173 I = I->getPrevNode()) { 174 auto *N = DAG.getNode(I); 175 if (N == nullptr) 176 continue; 177 if (auto *SB = N->getSchedBundle()) 178 eraseBundle(SB); 179 } 180 // TODO: For now we clear the DAG. Trim view once it gets implemented. 181 Bndls.clear(); 182 DAG.clear(); 183 184 // Since we are scheduling NewRegion from scratch, we clear the ready lists. 185 // The nodes currently in the list may not be ready after clearing the View. 186 ReadyList.clear(); 187 } 188 189 bool Scheduler::trySchedule(ArrayRef<Instruction *> Instrs) { 190 assert(all_of(drop_begin(Instrs), 191 [Instrs](Instruction *I) { 192 return I->getParent() == (*Instrs.begin())->getParent(); 193 }) && 194 "Instrs not in the same BB, should have been rejected by Legality!"); 195 if (ScheduledBB == nullptr) 196 ScheduledBB = Instrs[0]->getParent(); 197 // We don't support crossing BBs for now. 198 if (any_of(Instrs, 199 [this](Instruction *I) { return I->getParent() != ScheduledBB; })) 200 return false; 201 auto SchedState = getBndlSchedState(Instrs); 202 switch (SchedState) { 203 case BndlSchedState::FullyScheduled: 204 // Nothing to do. 205 return true; 206 case BndlSchedState::PartiallyOrDifferentlyScheduled: 207 // If one or more instrs are already scheduled we need to destroy the 208 // top-most part of the schedule that includes the instrs in the bundle and 209 // re-schedule. 210 trimSchedule(Instrs); 211 [[fallthrough]]; 212 case BndlSchedState::NoneScheduled: { 213 // TODO: Set the window of the DAG that we are interested in. 214 // We start scheduling at the bottom instr of Instrs. 215 ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator()); 216 217 // TODO: For now don't cross BBs. 218 if (!DAG.getInterval().empty()) { 219 auto *BB = DAG.getInterval().top()->getParent(); 220 if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; })) 221 return false; 222 } 223 224 // Extend the DAG to include Instrs. 225 Interval<Instruction> Extension = DAG.extend(Instrs); 226 // Add nodes to ready list. 227 for (auto &I : Extension) { 228 auto *N = DAG.getNode(&I); 229 if (N->ready()) 230 ReadyList.insert(N); 231 } 232 // Try schedule all nodes until we can schedule Instrs back-to-back. 233 return tryScheduleUntil(Instrs); 234 } 235 } 236 llvm_unreachable("Unhandled BndlSchedState enum"); 237 } 238 239 #ifndef NDEBUG 240 void Scheduler::dump(raw_ostream &OS) const { 241 OS << "ReadyList:\n"; 242 ReadyList.dump(OS); 243 } 244 void Scheduler::dump() const { dump(dbgs()); } 245 #endif // NDEBUG 246 247 } // namespace llvm::sandboxir 248