//===- SchedulerTest.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/ADT/SmallVector.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/BasicAliasAnalysis.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Dominators.h"
#include "llvm/SandboxIR/Context.h"
#include "llvm/SandboxIR/Function.h"
#include "llvm/SandboxIR/Instruction.h"
#include "llvm/Support/SourceMgr.h"
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"

using namespace llvm;

struct SchedulerTest : public testing::Test {
  LLVMContext C;
  std::unique_ptr<Module> M;
  std::unique_ptr<AssumptionCache> AC;
  std::unique_ptr<DominatorTree> DT;
  std::unique_ptr<BasicAAResult> BAA;
  std::unique_ptr<AAResults> AA;
  std::unique_ptr<TargetLibraryInfoImpl> TLII;
  std::unique_ptr<TargetLibraryInfo> TLI;

  void parseIR(LLVMContext &C, const char *IR) {
    SMDiagnostic Err;
    M = parseAssemblyString(IR, Err, C);
    if (!M) {
      Err.print("SchedulerTest", errs());
      return;
    }

    TLII = std::make_unique<TargetLibraryInfoImpl>(M->getTargetTriple());
    TLI = std::make_unique<TargetLibraryInfo>(*TLII);
  }

  AAResults &getAA(llvm::Function &LLVMF) {
    AA = std::make_unique<AAResults>(*TLI);
    AC = std::make_unique<AssumptionCache>(LLVMF);
    DT = std::make_unique<DominatorTree>(LLVMF);
    BAA = std::make_unique<BasicAAResult>(M->getDataLayout(), LLVMF, *TLI, *AC,
                                          DT.get());
    AA->addAAResult(*BAA);
    return *AA;
  }
};

static sandboxir::BasicBlock *getBasicBlockByName(sandboxir::Function *F,
                                                  StringRef Name) {
  for (sandboxir::BasicBlock &BB : *F)
    if (BB.getName() == Name)
      return &BB;
  llvm_unreachable("Expected to find basic block!");
}

TEST_F(SchedulerTest, SchedBundle) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
  store i8 %v0, ptr %ptr
  %other = add i8 %v0, %v1
  store i8 %v1, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *Other = &*It++;
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
  DAG.extend({&*BB->begin(), BB->getTerminator()});
  auto *SN0 = DAG.getNode(S0);
  auto *SN1 = DAG.getNode(S1);
  sandboxir::SchedBundle Bndl({SN0, SN1});

  // Check getTop().
  EXPECT_EQ(Bndl.getTop(), SN0);
  // Check getBot().
  EXPECT_EQ(Bndl.getBot(), SN1);
  // Check cluster().
  Bndl.cluster(S1->getIterator());
  {
    auto It = BB->begin();
    EXPECT_EQ(&*It++, Other);
    EXPECT_EQ(&*It++, S0);
    EXPECT_EQ(&*It++, S1);
    EXPECT_EQ(&*It++, Ret);
    S0->moveBefore(Other);
  }

  Bndl.cluster(S0->getIterator());
  {
    auto It = BB->begin();
    EXPECT_EQ(&*It++, S0);
    EXPECT_EQ(&*It++, S1);
    EXPECT_EQ(&*It++, Other);
    EXPECT_EQ(&*It++, Ret);
    S1->moveAfter(Other);
  }

  Bndl.cluster(Other->getIterator());
  {
    auto It = BB->begin();
    EXPECT_EQ(&*It++, S0);
    EXPECT_EQ(&*It++, S1);
    EXPECT_EQ(&*It++, Other);
    EXPECT_EQ(&*It++, Ret);
    S1->moveAfter(Other);
  }

  Bndl.cluster(Ret->getIterator());
  {
    auto It = BB->begin();
    EXPECT_EQ(&*It++, Other);
    EXPECT_EQ(&*It++, S0);
    EXPECT_EQ(&*It++, S1);
    EXPECT_EQ(&*It++, Ret);
    Other->moveBefore(S1);
  }

  Bndl.cluster(BB->end());
  {
    auto It = BB->begin();
    EXPECT_EQ(&*It++, Other);
    EXPECT_EQ(&*It++, Ret);
    EXPECT_EQ(&*It++, S0);
    EXPECT_EQ(&*It++, S1);
    Ret->moveAfter(S1);
    Other->moveAfter(S0);
  }
  // Check iterators.
  EXPECT_THAT(Bndl, testing::ElementsAre(SN0, SN1));
  EXPECT_THAT((const sandboxir::SchedBundle &)Bndl,
              testing::ElementsAre(SN0, SN1));
}

// Check that when we erase a DAG node its SchedBundle gets updated.
TEST_F(SchedulerTest, SchedBundleEraseDGNode) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr, i8 %v0, i8 %v1, i8 %v2, i8 %v3) {
  store i8 %v0, ptr %ptr
  store i8 %v1, ptr %ptr
  store i8 %v2, ptr %ptr
  store i8 %v3, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S2 = cast<sandboxir::StoreInst>(&*It++);
  auto *S3 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
  DAG.extend({&*BB->begin(), BB->getTerminator()});
  auto *SN0 = DAG.getNode(S0);
  auto *SN1 = DAG.getNode(S1);
  auto *SN2 = DAG.getNode(S2);
  auto *SN3 = DAG.getNode(S3);
  {
    // Check the common case, when the bundle contains unique nodes.
    sandboxir::SchedBundle Bndl({SN0, SN1});
    S0->eraseFromParent();
    EXPECT_THAT(Bndl, testing::ElementsAre(SN1));
  }
  {
    // Check corner case when the node appears more than once.
    sandboxir::SchedBundle Bndl({SN2, SN3, SN2});
    S2->eraseFromParent();
    EXPECT_THAT(Bndl, testing::ElementsAre(SN3));
  }
}

// Check that assigning a bundle to a DAG Node that is already assigned to a
// bundle, removes the node from the old bundle.
TEST_F(SchedulerTest, SchedBundleReassign) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr, i8 %v0, i8 %v1, i8 %v2) {
  store i8 %v0, ptr %ptr
  store i8 %v1, ptr %ptr
  store i8 %v2, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S2 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
  DAG.extend({&*BB->begin(), BB->getTerminator()});
  auto *SN0 = DAG.getNode(S0);
  auto *SN1 = DAG.getNode(S1);
  auto *SN2 = DAG.getNode(S2);
  sandboxir::SchedBundle BndlOld({SN0, SN1});
  sandboxir::SchedBundle BndlNew({SN0, SN2});
  EXPECT_THAT(BndlOld, testing::ElementsAre(SN1));
  EXPECT_THAT(BndlNew, testing::ElementsAre(SN0, SN2));
}

TEST_F(SchedulerTest, Basic) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
  store i8 %v0, ptr %ptr
  store i8 %v1, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  {
    // Schedule all instructions in sequence.
    sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
    EXPECT_TRUE(Sched.trySchedule({Ret}));
    EXPECT_TRUE(Sched.trySchedule({S1}));
    EXPECT_TRUE(Sched.trySchedule({S0}));
  }
  {
    // Skip instructions.
    sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
    EXPECT_TRUE(Sched.trySchedule({Ret}));
    EXPECT_TRUE(Sched.trySchedule({S0}));
  }
  {
    // Try invalid scheduling. Dependency S0->S1.
    sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
    EXPECT_TRUE(Sched.trySchedule({Ret}));
    EXPECT_FALSE(Sched.trySchedule({S0, S1}));
  }
}

TEST_F(SchedulerTest, Bundles) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1) {
  %ld0 = load i8, ptr %ptr0
  %ld1 = load i8, ptr %ptr1
  store i8 %ld0, ptr %ptr0
  store i8 %ld1, ptr %ptr1
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({Ret}));
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
}

TEST_F(SchedulerTest, TrimSchedule) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %arg) {
  %zext = zext i8 0 to i32
  %ld0 = load i8, ptr %ptr0
  %ld1 = load i8, ptr %ptr1
  %add0 = add i8 %ld0, %ld0
  %add1 = add i8 %ld1, %ld1
  store i8 %add0, ptr %ptr0
  store i8 %add1, ptr %ptr1
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *Z = cast<sandboxir::CastInst>(&*It++);
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *Add0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({Ret}));
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
  // At this point Add0 and Add1 should have been individually scheduled
  // as singleton bundles, but {S0,S1} and {L0,L1} as vector bundles.
  // Check if rescheduling works.
  EXPECT_TRUE(Sched.trySchedule({Add0, Add1}));
  // These should fail because {L0,L1} is a vector bundle.
  EXPECT_FALSE(Sched.trySchedule({L0, Z}));
  EXPECT_FALSE(Sched.trySchedule({L1, Z}));
  // This should succeed because it matches the original vec bundle.
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
}

// Make sure that instructions in  SchedBundles are always scheduled
// back-to-back
TEST_F(SchedulerTest, SchedBundleBackToBack) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr, i16 %arg) {
  %gep0 = getelementptr i32, ptr %ptr, i64 0
  %gep1 = getelementptr i32, ptr %ptr, i64 1
  %zextX = zext i16 0 to i32
  %zext1 = zext i16 0 to i32
  %zext0 = zext i16 %arg to i32
  %shl1 = shl i32 %zextX, 0
  %shl0 = shl i32 %zext1, 0
  %sub1 = sub i32 %zext1, %shl1
  %sub0 = sub i32 %zext0, %shl0
  store i32 %sub1, ptr %gep1
  store i32 %sub0, ptr %gep0
  ret void
})IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  [[maybe_unused]] auto *Gep0 = cast<sandboxir::GetElementPtrInst>(&*It++);
  [[maybe_unused]] auto *Gep1 = cast<sandboxir::GetElementPtrInst>(&*It++);
  [[maybe_unused]] auto *ZextX = cast<sandboxir::CastInst>(&*It++);
  auto *Zext1 = cast<sandboxir::CastInst>(&*It++);
  auto *Zext0 = cast<sandboxir::CastInst>(&*It++);
  auto *Shl1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Shl0 = cast<sandboxir::BinaryOperator>(&*It++);
  [[maybe_unused]] auto *Sub1 = cast<sandboxir::BinaryOperator>(&*It++);
  [[maybe_unused]] auto *Sub0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({Zext0, Zext1}));
  EXPECT_TRUE(Sched.trySchedule({Shl0, Shl1}));
  auto BackToBack = [](sandboxir::Instruction *I1, sandboxir::Instruction *I2) {
    return I1->getNextNode() == I2 || I2->getNextNode() == I1;
  };
  EXPECT_TRUE(BackToBack(S0, S1));
  EXPECT_TRUE(BackToBack(Zext0, Zext1));
  EXPECT_TRUE(BackToBack(Shl0, Shl1));
}

// Test that an instruction can't belong in two bundles!
TEST_F(SchedulerTest, CheckBundles) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, ptr noalias %ptr2) {
  %L0 = load i8, ptr %ptr0
  %L1 = load i8, ptr %ptr1 ; This belongs in 2 bundles!
  %L2 = load i8, ptr %ptr2
  %add0 = add i8 %L0, %L1
  %add1 = add i8 %L1, %L2
  store i8 %add0, ptr %ptr0
  store i8 %add1, ptr %ptr1
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *L2 = cast<sandboxir::LoadInst>(&*It++);
  auto *Add0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({Add0, Add1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
  // This should fail because L1 is already part of {L0,L1}
  EXPECT_FALSE(Sched.trySchedule({L1, L2}));
  EXPECT_FALSE(Sched.trySchedule({L2, L1}));
}

// Try schedule a bundle {L1,L2} where L1 is already scheduled in {L0,L1}
// but L2 is not in the DAG at all
TEST_F(SchedulerTest, CheckBundles2) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, ptr noalias %ptr2) {
  %L2 = load i8, ptr %ptr2 ; This is not in the DAG
  %L1 = load i8, ptr %ptr1 ; This belongs in 2 bundles!
  %L0 = load i8, ptr %ptr0
  %add1 = add i8 %L1, %L2
  %add0 = add i8 %L0, %L1
  store i8 %add1, ptr %ptr1
  store i8 %add0, ptr %ptr0
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L2 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Add0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({Add0, Add1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
  // This should fail because L1 is already part of {L0,L1}.
  EXPECT_FALSE(Sched.trySchedule({L1, L2}));
  EXPECT_FALSE(Sched.trySchedule({L2, L1}));
}

// Try schedule a bundle {L1,L2} where L1 is already scheduled in {L0,L1}
// but L2 is in the DAG but isn't scheduled.
TEST_F(SchedulerTest, CheckBundles3) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, ptr noalias %ptr2) {
  %L2 = load i8, ptr %ptr2 ; This is not in the DAG
  %L1 = load i8, ptr %ptr1 ; This belongs in 2 bundles!
  %L0 = load i8, ptr %ptr0
  %add1 = add i8 %L1, %L2
  %add0 = add i8 %L0, %L1
  store i8 %add1, ptr %ptr1
  store i8 %add0, ptr %ptr0
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L2 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Add0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({Add0, Add1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
  // Add L2 to the DAG, but don't schedule it.
  auto &DAG = sandboxir::SchedulerInternalsAttorney::getDAG(Sched);
  DAG.extend(L2);
  // This should fail because L1 is already part of {L0,L1}.
  EXPECT_FALSE(Sched.trySchedule({L1, L2}));
  EXPECT_FALSE(Sched.trySchedule({L2, L1}));
}

// Check that Scheduler::getBndlSchedState() works correctly.
TEST_F(SchedulerTest, GetBndlSchedState) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, ptr noalias %ptr2) {
  %L2 = load i8, ptr %ptr2 ; This is not in the DAG
  %L1 = load i8, ptr %ptr1 ; This belongs in 2 bundles!
  %L0 = load i8, ptr %ptr0
  %add1 = add i8 %L1, %L2
  %add0 = add i8 %L0, %L1
  store i8 %add1, ptr %ptr1
  store i8 %add0, ptr %ptr0
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L2 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *Add0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  auto &DAG = sandboxir::SchedulerInternalsAttorney::getDAG(Sched);
  auto GetBndlSchedState = [&Sched](ArrayRef<sandboxir::Instruction *> Instrs) {
    return sandboxir::SchedulerInternalsAttorney::getBndlSchedState(Sched,
                                                                    Instrs);
  };
  using BndlSchedState = sandboxir::SchedulerInternalsAttorney::BndlSchedState;
  // Check when instructions are not in the DAG.
  EXPECT_EQ(GetBndlSchedState({S0}), BndlSchedState::NoneScheduled);
  EXPECT_EQ(GetBndlSchedState({S0, S1}), BndlSchedState::NoneScheduled);
  EXPECT_EQ(GetBndlSchedState({S0, S1}), BndlSchedState::NoneScheduled);
  // Check when instructions are in the DAG.
  DAG.extend({S0, S1});
  EXPECT_EQ(GetBndlSchedState({S0}), BndlSchedState::NoneScheduled);
  EXPECT_EQ(GetBndlSchedState({S0, S1}), BndlSchedState::NoneScheduled);
  EXPECT_EQ(GetBndlSchedState({S0, S1}), BndlSchedState::NoneScheduled);
  // One instruction in the DAG and the other not in the DAG.
  EXPECT_EQ(GetBndlSchedState({S0, Add0}), BndlSchedState::NoneScheduled);

  // Check with scheduled instructions.
  Sched.clear(); // Manually extending the DAG messes with the scheduler.
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  // Check fully scheduled.
  EXPECT_EQ(GetBndlSchedState({S0, S1}), BndlSchedState::FullyScheduled);
  // Check scheduled + not in DAG.
  EXPECT_EQ(GetBndlSchedState({S0, Add0}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, S0}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, S1}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, Add1}), BndlSchedState::NoneScheduled);
  // Extend DAG such that Add0 and Add1 are in the DAG but are not scheduled.
  DAG.extend({Add0, Add1});
  // Check both in DAG but not scheduled.
  EXPECT_EQ(GetBndlSchedState({Add0, Add1}), BndlSchedState::NoneScheduled);
  // Check scheduled + in DAG but not scheduled.
  EXPECT_EQ(GetBndlSchedState({S0, Add0}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, S0}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, S1}), BndlSchedState::AlreadyScheduled);

  Sched.clear(); // Manually extending the DAG messes with the scheduler.
  // Schedule instructions towards the top so that intermediate instructions
  // (namely Add0, Add1) get temporarily scheduled in singleton bundles.
  EXPECT_TRUE(Sched.trySchedule({S0, S1}));
  EXPECT_TRUE(Sched.trySchedule({L0, L1}));
  // Check fully scheduled.
  EXPECT_EQ(GetBndlSchedState({L0, L1}), BndlSchedState::FullyScheduled);
  // Check both singletons.
  EXPECT_EQ(GetBndlSchedState({Add0, Add1}),
            BndlSchedState::TemporarilyScheduled);
  // Check single singleton.
  EXPECT_EQ(GetBndlSchedState({Add0}), BndlSchedState::TemporarilyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add1}), BndlSchedState::TemporarilyScheduled);
  // Check singleton + scheduled.
  EXPECT_EQ(GetBndlSchedState({L0, S1}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({S1, L0}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({L0, Add1}), BndlSchedState::AlreadyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add1, L0}), BndlSchedState::AlreadyScheduled);
  // Check singleton + not in DAG.
  EXPECT_EQ(GetBndlSchedState({Add1, L2}),
            BndlSchedState::TemporarilyScheduled);
  EXPECT_EQ(GetBndlSchedState({L2, Add0}),
            BndlSchedState::TemporarilyScheduled);

  // Check duplicates.
  // TODO: Should duplicates be allowed?
  EXPECT_EQ(GetBndlSchedState({L2, L2}), BndlSchedState::NoneScheduled);
  EXPECT_EQ(GetBndlSchedState({S0, S0}), BndlSchedState::FullyScheduled);
  EXPECT_EQ(GetBndlSchedState({Add0, Add1}),
            BndlSchedState::TemporarilyScheduled);
}

// Check scheduling in the following order: {A0,A1},{B0,B1},{C0,C1},{D0,D1}
// assuming program order: B0,B1,C0,C1,D0,D1,E0,D1.
// This will effectively schedule nodes below already scheduled nodes, which
// can expose issues in the code that adds nodes to the ready list.
// For example, we schedule {D0,D1} while {C0,C1} are scheduled and there is
// a dependency D0->C0 and D1->C1.
//
//                   {A0,A1}  {B0,B1}  {C0,C1}  {D0,D1}
//   B0,B1                    | S
//   |\                       |
//   | C0,C1                  |        | S      | S
//   |  | \                   |                 |
//   |  |  D0,D1              |                 | S
//   | /                      |
//   A0,A1             | S    | S
//                 +------------------------+
//                 | Legend   |: DAG        |
//                 |          S: Scheduled  |
TEST_F(SchedulerTest, ScheduledPredecessors) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptrA0, ptr noalias %ptrA1,
                 ptr noalias %ptrB0, ptr noalias %ptrB1,
                 ptr noalias %ptrD0, ptr noalias %ptrD1) {
  %B0 = load i8, ptr %ptrB0
  %B1 = load i8, ptr %ptrB1
  %C0 = add i8 %B0, 0
  %C1 = add i8 %B1, 1
  store i8 %C0, ptr %ptrD0
  store i8 %C1, ptr %ptrD1
  store i8 %B0, ptr %ptrA0
  store i8 %B1, ptr %ptrA1
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *B1 = cast<sandboxir::LoadInst>(&*It++);
  auto *B0 = cast<sandboxir::LoadInst>(&*It++);
  auto *C1 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *C0 = cast<sandboxir::BinaryOperator>(&*It++);
  auto *D1 = cast<sandboxir::StoreInst>(&*It++);
  auto *D0 = cast<sandboxir::StoreInst>(&*It++);
  auto *A1 = cast<sandboxir::StoreInst>(&*It++);
  auto *A0 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
  (void)Ret;

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  EXPECT_TRUE(Sched.trySchedule({A0, A1}));
  // NOTE: We schedule the intermediate nodes between {A0,A1} and {B0,B1} by
  // hand one by one to make sure they are scheduled in that order because
  // the scheduler may reorder them a bit if we let it do it.
  EXPECT_TRUE(Sched.trySchedule(D0));
  EXPECT_TRUE(Sched.trySchedule(D1));
  EXPECT_TRUE(Sched.trySchedule(C0));
  EXPECT_TRUE(Sched.trySchedule(C1));
  EXPECT_TRUE(Sched.trySchedule({B0, B1}));
  // At this point all nodes must have been scheduled from B0,B1 to A0,A1.
  // The ones in between are scheduled as single-instruction nodes.
  // So when we attempt to schedule {C0,C1} we will need to reschedule.
  // At this point we will trim the schedule from {C0,C1} upwards.
  EXPECT_TRUE(Sched.trySchedule({C0, C1}));
  // Now the schedule should only contain {C0,C1} which should be marked as
  // "scheduled".
  // {D0,D1} are below {C0,C1}, so we grow the DAG downwards, while
  // {C0,C1} are marked as "scheduled" above them.
  EXPECT_TRUE(Sched.trySchedule({D0, D1}));
}

TEST_F(SchedulerTest, DontCrossBBs) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) {
bb0:
  %add0 = add i8 %v0, 0
  %add1 = add i8 %v1, 1
  br label %bb1

bb1:
  store i8 %add0, ptr %ptr0
  store i8 %add1, ptr %ptr1
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB0 = getBasicBlockByName(F, "bb0");
  auto *BB1 = getBasicBlockByName(F, "bb1");
  auto It = BB0->begin();
  auto *Add0 = &*It++;
  auto *Add1 = &*It++;

  It = BB1->begin();
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  {
    // Schedule bottom-up
    sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
    EXPECT_TRUE(Sched.trySchedule({Ret}));
    EXPECT_TRUE(Sched.trySchedule({S0, S1}));
    // Scheduling across blocks should fail.
    EXPECT_FALSE(Sched.trySchedule({Add0, Add1}));
  }
  {
    // Schedule top-down
    sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
    EXPECT_TRUE(Sched.trySchedule({Add0, Add1}));
    // Scheduling across blocks should fail.
    EXPECT_FALSE(Sched.trySchedule({S0, S1}));
  }
}

TEST_F(SchedulerTest, NotifyCreateInst) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, ptr noalias %ptr1, ptr noalias %ptr2) {
  %ld0 = load i8, ptr %ptr
  store i8 %ld0, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
  auto *Ptr1 = F->getArg(1);
  auto *Ptr2 = F->getArg(2);

  sandboxir::Scheduler Sched(getAA(*LLVMF), Ctx);
  // Schedule Ret and S0. The top of schedule should be at S0.
  EXPECT_TRUE(Sched.trySchedule({Ret}));
  EXPECT_TRUE(Sched.trySchedule({S0}));
  auto &DAG = sandboxir::SchedulerInternalsAttorney::getDAG(Sched);
  DAG.extend({L0});
  auto *L0N = DAG.getNode(L0);
  EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 0u);
  // We should have DAG nodes for all instructions at this point

  // Now create a new instruction below S0.
  sandboxir::StoreInst *NewS1 =
      sandboxir::StoreInst::create(L0, Ptr1, Align(8), Ret->getIterator(),
                                   /*IsVolatile=*/false, Ctx);
  // Check that it is marked as "scheduled".
  auto *NewS1N = DAG.getNode(NewS1);
  EXPECT_TRUE(NewS1N->scheduled());
  // Check that L0's UnscheduledSuccs are still == 0 since NewS1 is "scheduled".
  EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 0u);

  // Now create a new instruction above S0.
  sandboxir::StoreInst *NewS2 =
      sandboxir::StoreInst::create(L0, Ptr2, Align(8), S0->getIterator(),
                                   /*IsVolatile=*/false, Ctx);
  // Check that it is not marked as "scheduled".
  auto *NewS2N = DAG.getNode(NewS2);
  EXPECT_FALSE(NewS2N->scheduled());
  // Check that L0's UnscheduledSuccs got updated because of NewS2.
  EXPECT_EQ(L0N->getNumUnscheduledSuccs(), 1u);

  sandboxir::ReadyListContainer ReadyList;
  // Check empty().
  EXPECT_TRUE(ReadyList.empty());
}

TEST_F(SchedulerTest, ReadyList) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr) {
  %ld0 = load i8, ptr %ptr
  store i8 %ld0, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB = &*F->begin();
  auto It = BB->begin();
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
  DAG.extend({&*BB->begin(), BB->getTerminator()});
  auto *L0N = DAG.getNode(L0);
  auto *S0N = DAG.getNode(S0);
  auto *RetN = DAG.getNode(Ret);

  sandboxir::ReadyListContainer ReadyList;
  // Check empty().
  EXPECT_TRUE(ReadyList.empty());
  // Check insert(), pop().
  ReadyList.insert(L0N);
  EXPECT_FALSE(ReadyList.empty());
  EXPECT_EQ(ReadyList.pop(), L0N);
  // Check clear().
  ReadyList.insert(L0N);
  EXPECT_FALSE(ReadyList.empty());
  ReadyList.clear();
  EXPECT_TRUE(ReadyList.empty());
  // Check remove().
  EXPECT_TRUE(ReadyList.empty());
  ReadyList.remove(L0N); // Removing a non-existing node should be valid.
  ReadyList.insert(L0N);
  ReadyList.insert(S0N);
  ReadyList.insert(RetN);
  ReadyList.remove(S0N);
  DenseSet<sandboxir::DGNode *> Nodes;
  Nodes.insert(ReadyList.pop());
  Nodes.insert(ReadyList.pop());
  EXPECT_TRUE(ReadyList.empty());
  EXPECT_THAT(Nodes, testing::UnorderedElementsAre(L0N, RetN));
}

TEST_F(SchedulerTest, ReadyListPriorities) {
  parseIR(C, R"IR(
define void @foo(ptr %ptr) {
bb0:
  br label %bb1

bb1:
  %phi0 = phi i8 [0, %bb0], [1, %bb1]
  %phi1 = phi i8 [0, %bb0], [1, %bb1]
  %ld0 = load i8, ptr %ptr
  store i8 %ld0, ptr %ptr
  ret void
}
)IR");
  llvm::Function *LLVMF = &*M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto *F = Ctx.createFunction(LLVMF);
  auto *BB1 = getBasicBlockByName(F, "bb1");
  auto It = BB1->begin();
  auto *Phi0 = cast<sandboxir::PHINode>(&*It++);
  auto *Phi1 = cast<sandboxir::PHINode>(&*It++);
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *Ret = cast<sandboxir::ReturnInst>(&*It++);

  sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
  DAG.extend({&*BB1->begin(), BB1->getTerminator()});
  auto *Phi0N = DAG.getNode(Phi0);
  auto *Phi1N = DAG.getNode(Phi1);
  auto *L0N = DAG.getNode(L0);
  auto *S0N = DAG.getNode(S0);
  auto *RetN = DAG.getNode(Ret);

  sandboxir::ReadyListContainer ReadyList;
  // Check PHI vs non-PHI.
  ReadyList.insert(S0N);
  ReadyList.insert(Phi0N);
  EXPECT_EQ(ReadyList.pop(), Phi0N);
  EXPECT_EQ(ReadyList.pop(), S0N);
  ReadyList.insert(Phi0N);
  ReadyList.insert(S0N);
  EXPECT_EQ(ReadyList.pop(), Phi0N);
  EXPECT_EQ(ReadyList.pop(), S0N);
  // Check PHI vs terminator.
  ReadyList.insert(RetN);
  ReadyList.insert(Phi1N);
  EXPECT_EQ(ReadyList.pop(), Phi1N);
  EXPECT_EQ(ReadyList.pop(), RetN);
  ReadyList.insert(Phi1N);
  ReadyList.insert(RetN);
  EXPECT_EQ(ReadyList.pop(), Phi1N);
  EXPECT_EQ(ReadyList.pop(), RetN);
  // Check terminator vs non-terminator.
  ReadyList.insert(RetN);
  ReadyList.insert(L0N);
  EXPECT_EQ(ReadyList.pop(), L0N);
  EXPECT_EQ(ReadyList.pop(), RetN);
  ReadyList.insert(L0N);
  ReadyList.insert(RetN);
  EXPECT_EQ(ReadyList.pop(), L0N);
  EXPECT_EQ(ReadyList.pop(), RetN);
  // Check all, program order.
  ReadyList.insert(RetN);
  ReadyList.insert(L0N);
  ReadyList.insert(Phi1N);
  ReadyList.insert(S0N);
  ReadyList.insert(Phi0N);
  EXPECT_EQ(ReadyList.pop(), Phi0N);
  EXPECT_EQ(ReadyList.pop(), Phi1N);
  EXPECT_EQ(ReadyList.pop(), L0N);
  EXPECT_EQ(ReadyList.pop(), S0N);
  EXPECT_EQ(ReadyList.pop(), RetN);
}
