blob: 06c1ef6b6d5ae7bb75e8c4865b51948f85789999 [file] [log] [blame]
//===- 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<Instruction *> Instrs) {
SchedBundle::ContainerTy Nodes;
Nodes.reserve(Instrs.size());
for (auto *I : Instrs)
Nodes.push_back(DAG.getNode(I));
auto BndlPtr = std::make_unique<SchedBundle>(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<Instruction *> Instrs) {
// Use a set of instructions, instead of `Instrs` for fast lookups.
DenseSet<Instruction *> 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<DGNode *, 8> 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<Instruction *> 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<sandboxir::Instruction>(SBV))
->getSchedBundle() != SB;
}))
FullyScheduled = false;
}
return FullyScheduled ? BndlSchedState::FullyScheduled
: PartiallyScheduled ? BndlSchedState::PartiallyOrDifferentlyScheduled
: BndlSchedState::NoneScheduled;
}
void Scheduler::trimSchedule(ArrayRef<Instruction *> 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 (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<Instruction *> 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<Instruction> 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