//===- 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) { N->setScheduled(true); for (auto *DepN : N->preds(DAG)) { // TODO: preds() should not return nullptr. if (DepN == nullptr) continue; DepN->decrUnscheduledSuccs(); if (DepN->ready()) ReadyList.insert(DepN); } } } 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) { // Use a set of instructions, instead of `Instrs` for fast lookups. DenseSet InstrsToDefer(Instrs.begin(), Instrs.end()); // This collects the nodes that correspond to instructions found in `Instrs` // that have just become ready. These nodes won't be scheduled right away. SmallVector DeferredNodes; // 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. while (!ReadyList.empty()) { auto *ReadyN = ReadyList.pop(); if (InstrsToDefer.contains(ReadyN->getInstruction())) { // If the ready instruction is one of those in `Instrs`, then we don't // schedule it right away. Instead we defer it until we can schedule it // along with the rest of the instructions in `Instrs`, at the same // time in a single scheduling bundle. DeferredNodes.push_back(ReadyN); bool ReadyToScheduleDeferred = DeferredNodes.size() == Instrs.size(); if (ReadyToScheduleDeferred) { scheduleAndUpdateReadyList(*createBundle(Instrs)); return true; } } else { // If the ready instruction is not found in `Instrs`, then we wrap it in a // scheduling bundle and schedule it right away. scheduleAndUpdateReadyList(*createBundle({ReadyN->getInstruction()})); } } assert(DeferredNodes.size() != Instrs.size() && "We should have succesfully scheduled and early-returned!"); return false; } Scheduler::BndlSchedState Scheduler::getBndlSchedState(ArrayRef Instrs) const { assert(!Instrs.empty() && "Expected non-empty bundle"); bool PartiallyScheduled = false; bool FullyScheduled = true; for (auto *I : Instrs) { auto *N = DAG.getNode(I); if (N != nullptr && N->scheduled()) PartiallyScheduled = true; else FullyScheduled = false; } if (FullyScheduled) { // If not all instrs in the bundle are in the same SchedBundle then this // should be considered as partially-scheduled, because we will need to // re-schedule. SchedBundle *SB = DAG.getNode(Instrs[0])->getSchedBundle(); assert(SB != nullptr && "FullyScheduled assumes that there is an SB!"); if (any_of(drop_begin(Instrs), [this, SB](sandboxir::Value *SBV) { return DAG.getNode(cast(SBV)) ->getSchedBundle() != SB; })) FullyScheduled = false; } return FullyScheduled ? BndlSchedState::FullyScheduled : PartiallyScheduled ? BndlSchedState::PartiallyOrDifferentlyScheduled : BndlSchedState::NoneScheduled; } void Scheduler::trimSchedule(ArrayRef Instrs) { Instruction *TopI = &*ScheduleTopItOpt.value(); Instruction *LowestI = VecUtils::getLowest(Instrs); // Destroy the 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; if (auto *SB = N->getSchedBundle()) eraseBundle(SB); } // TODO: For now we clear the DAG. Trim view once it gets implemented. Bndls.clear(); DAG.clear(); // Since we are scheduling NewRegion from scratch, we clear the ready lists. // The nodes currently in the list may not be ready after clearing the View. ReadyList.clear(); } 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!"); 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::PartiallyOrDifferentlyScheduled: // 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. trimSchedule(Instrs); [[fallthrough]]; case BndlSchedState::NoneScheduled: { // TODO: Set the window of the DAG that we are interested in. // We start scheduling at the bottom instr of Instrs. ScheduleTopItOpt = std::next(VecUtils::getLowest(Instrs)->getIterator()); // 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; } // 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); } void Scheduler::dump() const { dump(dbgs()); } #endif // NDEBUG } // namespace llvm::sandboxir