//===- Scheduler.cpp ------------------------------------------------------===// // // 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 "llvm/Transforms/Vectorize/SandboxVectorizer/Scheduler.h" #include "llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h" namespace llvm::sandboxir { // TODO: Check if we can cache top/bottom to reduce compile-time. DGNode *SchedBundle::getTop() const { DGNode *TopN = Nodes.front(); for (auto *N : drop_begin(Nodes)) { if (N->getInstruction()->comesBefore(TopN->getInstruction())) TopN = N; } return TopN; } DGNode *SchedBundle::getBot() const { DGNode *BotN = Nodes.front(); for (auto *N : drop_begin(Nodes)) { if (BotN->getInstruction()->comesBefore(N->getInstruction())) BotN = N; } return BotN; } void SchedBundle::cluster(BasicBlock::iterator Where) { for (auto *N : Nodes) { auto *I = N->getInstruction(); if (I->getIterator() == Where) ++Where; // Try to maintain bundle order. I->moveBefore(*Where.getNodeParent(), Where); } } #ifndef NDEBUG void SchedBundle::dump(raw_ostream &OS) const { for (auto *N : Nodes) OS << *N; } void SchedBundle::dump() const { dump(dbgs()); dbgs() << "\n"; } #endif // NDEBUG #ifndef NDEBUG void ReadyListContainer::dump(raw_ostream &OS) const { auto ListCopy = List; while (!ListCopy.empty()) { OS << *ListCopy.top() << "\n"; ListCopy.pop(); } } void ReadyListContainer::dump() const { dump(dbgs()); dbgs() << "\n"; } #endif // NDEBUG void Scheduler::scheduleAndUpdateReadyList(SchedBundle &Bndl) { // Find where we should schedule the instructions. assert(ScheduleTopItOpt && "Should have been set by now!"); auto Where = *ScheduleTopItOpt; // Move all instructions in `Bndl` to `Where`. Bndl.cluster(Where); // Update the last scheduled bundle. ScheduleTopItOpt = Bndl.getTop()->getInstruction()->getIterator(); // Set nodes as "scheduled" and decrement the UnsceduledSuccs counter of all // dependency predecessors. for (DGNode *N : Bndl) { for (auto *DepN : N->preds(DAG)) { DepN->decrUnscheduledSuccs(); if (DepN->ready() && !DepN->scheduled()) ReadyList.insert(DepN); } N->setScheduled(true); } } void Scheduler::notifyCreateInstr(Instruction *I) { // The DAG notifier should have run by now. auto *N = DAG.getNode(I); // If there is no DAG node for `I` it means that this is out of scope for the // DAG and as such out of scope for the scheduler too, so nothing to do. if (N == nullptr) return; // If the instruction is inserted below the top-of-schedule then we mark it as // "scheduled". bool IsScheduled = ScheduleTopItOpt && *ScheduleTopItOpt != I->getParent()->end() && (*ScheduleTopItOpt.value()).comesBefore(I); if (IsScheduled) N->setScheduled(true); // If the new instruction is above the top of schedule we need to remove its // dependency predecessors from the ready list and increment their // `UnscheduledSuccs` counters. if (!IsScheduled) { for (auto *PredN : N->preds(DAG)) { ReadyList.remove(PredN); PredN->incrUnscheduledSuccs(); } } } SchedBundle *Scheduler::createBundle(ArrayRef Instrs) { SchedBundle::ContainerTy Nodes; Nodes.reserve(Instrs.size()); for (auto *I : Instrs) Nodes.push_back(DAG.getNode(I)); auto BndlPtr = std::make_unique(std::move(Nodes)); auto *Bndl = BndlPtr.get(); Bndls[Bndl] = std::move(BndlPtr); return Bndl; } void Scheduler::eraseBundle(SchedBundle *SB) { Bndls.erase(SB); } bool Scheduler::tryScheduleUntil(ArrayRef Instrs) { // Create a bundle for Instrs. If it turns out the schedule is infeasible we // will dismantle it. auto *InstrsSB = createBundle(Instrs); // Keep scheduling ready nodes until we either run out of ready nodes (i.e., // ReadyList is empty), or all nodes that correspond to `Instrs` (the nodes of // which are collected in DeferredNodes) are all ready to schedule. SmallVector Retry; bool KeepScheduling = true; while (KeepScheduling) { enum class TryScheduleRes { Success, ///> We successfully scheduled the bundle. Failure, ///> We failed to schedule the bundle. Finished, ///> We successfully scheduled the bundle and it is the last /// bundle to be scheduled. }; /// TryScheduleNode() attempts to schedule all DAG nodes in the bundle that /// ReadyN is in. If it's not in a bundle it will create a singleton bundle /// and will try to schedule it. auto TryScheduleBndl = [this, InstrsSB](DGNode *ReadyN) -> TryScheduleRes { auto *SB = ReadyN->getSchedBundle(); if (SB == nullptr) { // If ReadyN does not belong to a bundle, create a singleton bundle // and schedule it. auto *SingletonSB = createBundle({ReadyN->getInstruction()}); scheduleAndUpdateReadyList(*SingletonSB); return TryScheduleRes::Success; } if (SB->ready()) { // Remove the rest of the bundle from the ready list. // TODO: Perhaps change the Scheduler + ReadyList to operate on // SchedBundles instead of DGNodes. for (auto *N : *SB) { if (N != ReadyN) ReadyList.remove(N); } // If all nodes in the bundle are ready. scheduleAndUpdateReadyList(*SB); if (SB == InstrsSB) // We just scheduled InstrsSB bundle, so we are done scheduling. return TryScheduleRes::Finished; return TryScheduleRes::Success; } return TryScheduleRes::Failure; }; while (!ReadyList.empty()) { auto *ReadyN = ReadyList.pop(); auto Res = TryScheduleBndl(ReadyN); switch (Res) { case TryScheduleRes::Success: // We successfully scheduled ReadyN, keep scheduling. continue; case TryScheduleRes::Failure: // We failed to schedule ReadyN, defer it to later and keep scheduling // other ready instructions. Retry.push_back(ReadyN); continue; case TryScheduleRes::Finished: // We successfully scheduled the instruction bundle, so we are done. return true; } llvm_unreachable("Unhandled TrySchedule() result"); } // Try to schedule nodes from the Retry list. KeepScheduling = false; for (auto *N : make_early_inc_range(Retry)) { auto Res = TryScheduleBndl(N); if (Res == TryScheduleRes::Success) { Retry.erase(find(Retry, N)); KeepScheduling = true; } } } eraseBundle(InstrsSB); return false; } Scheduler::BndlSchedState Scheduler::getBndlSchedState(ArrayRef Instrs) const { assert(!Instrs.empty() && "Expected non-empty bundle"); auto *N0 = DAG.getNode(Instrs[0]); auto *SB0 = N0 != nullptr ? N0->getSchedBundle() : nullptr; bool AllUnscheduled = SB0 == nullptr; bool FullyScheduled = SB0 != nullptr && !SB0->isSingleton(); for (auto *I : drop_begin(Instrs)) { auto *N = DAG.getNode(I); auto *SB = N != nullptr ? N->getSchedBundle() : nullptr; if (SB != nullptr) { // We found a scheduled instr, so there is now way all are unscheduled. AllUnscheduled = false; if (SB->isSingleton()) { // We found an instruction in a temporarily scheduled singleton. There // is no way that all instructions are scheduled in the same bundle. FullyScheduled = false; } } if (SB != SB0) { // Either one of SB, SB0 is null, or they are in different bundles, so // Instrs are definitely not in the same vector bundle. FullyScheduled = false; // One of SB, SB0 are in a vector bundle and they differ. if ((SB != nullptr && !SB->isSingleton()) || (SB0 != nullptr && !SB0->isSingleton())) return BndlSchedState::AlreadyScheduled; } } return AllUnscheduled ? BndlSchedState::NoneScheduled : FullyScheduled ? BndlSchedState::FullyScheduled : BndlSchedState::TemporarilyScheduled; } void Scheduler::trimSchedule(ArrayRef Instrs) { // | Legend: N: DGNode // N <- DAGInterval.top() | B: SchedBundle // N | *: Contains instruction in Instrs // B <- TopI (Top of schedule) +------------------------------------------- // B // B * // B // B * <- LowestI (Lowest in Instrs) // B // N // N // N <- DAGInterval.bottom() // Instruction *TopI = &*ScheduleTopItOpt.value(); Instruction *LowestI = VecUtils::getLowest(Instrs); // Destroy the singleton schedule bundles from LowestI all the way to the top. for (auto *I = LowestI, *E = TopI->getPrevNode(); I != E; I = I->getPrevNode()) { auto *N = DAG.getNode(I); if (N == nullptr) continue; auto *SB = N->getSchedBundle(); if (SB->isSingleton()) eraseBundle(SB); } // The DAG Nodes contain state like the number of UnscheduledSuccs and the // Scheduled flag. We need to reset their state. We need to do this for all // nodes from LowestI to the top of the schedule. DAG Nodes that are above the // top of schedule that depend on nodes that got reset need to have their // UnscheduledSuccs adjusted. Interval ResetIntvl(TopI, LowestI); for (Instruction &I : ResetIntvl) { auto *N = DAG.getNode(&I); N->resetScheduleState(); // Recompute UnscheduledSuccs for nodes not only in ResetIntvl but even for // nodes above the top of schedule. for (auto *PredN : N->preds(DAG)) PredN->incrUnscheduledSuccs(); } // Refill the ready list by visiting all nodes from the top of DAG to LowestI. ReadyList.clear(); Interval RefillIntvl(DAG.getInterval().top(), LowestI); for (Instruction &I : RefillIntvl) { auto *N = DAG.getNode(&I); if (N->ready()) ReadyList.insert(N); } } bool Scheduler::trySchedule(ArrayRef Instrs) { assert(all_of(drop_begin(Instrs), [Instrs](Instruction *I) { return I->getParent() == (*Instrs.begin())->getParent(); }) && "Instrs not in the same BB, should have been rejected by Legality!"); // TODO: For now don't cross BBs. if (!DAG.getInterval().empty()) { auto *BB = DAG.getInterval().top()->getParent(); if (any_of(Instrs, [BB](auto *I) { return I->getParent() != BB; })) return false; } if (ScheduledBB == nullptr) ScheduledBB = Instrs[0]->getParent(); // We don't support crossing BBs for now. if (any_of(Instrs, [this](Instruction *I) { return I->getParent() != ScheduledBB; })) return false; auto SchedState = getBndlSchedState(Instrs); switch (SchedState) { case BndlSchedState::FullyScheduled: // Nothing to do. return true; case BndlSchedState::AlreadyScheduled: // Instructions are part of a different vector schedule, so we can't // schedule \p Instrs in the same bundle (without destroying the existing // schedule). return false; case BndlSchedState::TemporarilyScheduled: // If one or more instrs are already scheduled we need to destroy the // top-most part of the schedule that includes the instrs in the bundle and // re-schedule. DAG.extend(Instrs); trimSchedule(Instrs); ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator()); return tryScheduleUntil(Instrs); case BndlSchedState::NoneScheduled: { // TODO: Set the window of the DAG that we are interested in. if (!ScheduleTopItOpt) // We start scheduling at the bottom instr of Instrs. ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator()); // Extend the DAG to include Instrs. Interval Extension = DAG.extend(Instrs); // Add nodes to ready list. for (auto &I : Extension) { auto *N = DAG.getNode(&I); if (N->ready()) ReadyList.insert(N); } // Try schedule all nodes until we can schedule Instrs back-to-back. return tryScheduleUntil(Instrs); } } llvm_unreachable("Unhandled BndlSchedState enum"); } #ifndef NDEBUG void Scheduler::dump(raw_ostream &OS) const { OS << "ReadyList:\n"; ReadyList.dump(OS); OS << "Top of schedule: "; if (ScheduleTopItOpt) OS << **ScheduleTopItOpt; else OS << "Empty"; OS << "\n"; } void Scheduler::dump() const { dump(dbgs()); } #endif // NDEBUG } // namespace llvm::sandboxir