//===- IntervalTest.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/Interval.h"
#include "llvm/AsmParser/Parser.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 IntervalTest : public testing::Test {
  LLVMContext C;
  std::unique_ptr<Module> M;

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

TEST_F(IntervalTest, Basic) {
  parseIR(C, R"IR(
define void @foo(i8 %v0) {
  %add0 = add i8 %v0, %v0
  %add1 = add i8 %v0, %v0
  %add2 = add i8 %v0, %v0
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  auto *I2 = &*It++;
  auto *Ret = &*It++;

  sandboxir::Interval<sandboxir::Instruction> Intvl(I0, Ret);
#ifndef NDEBUG
  EXPECT_DEATH(sandboxir::Interval<sandboxir::Instruction>(I1, I0),
               ".*before.*");
#endif // NDEBUG
  // Check Interval<sandboxir::Instruction>(ArrayRef), from(), to().
  {
    sandboxir::Interval<sandboxir::Instruction> Intvl(
        SmallVector<sandboxir::Instruction *>({I0, Ret}));
    EXPECT_EQ(Intvl.top(), I0);
    EXPECT_EQ(Intvl.bottom(), Ret);
  }
  {
    sandboxir::Interval<sandboxir::Instruction> Intvl(
        SmallVector<sandboxir::Instruction *>({Ret, I0}));
    EXPECT_EQ(Intvl.top(), I0);
    EXPECT_EQ(Intvl.bottom(), Ret);
  }
  {
    sandboxir::Interval<sandboxir::Instruction> Intvl(
        SmallVector<sandboxir::Instruction *>({I0, I0}));
    EXPECT_EQ(Intvl.top(), I0);
    EXPECT_EQ(Intvl.bottom(), I0);
  }

  // Check empty().
  EXPECT_FALSE(Intvl.empty());
  sandboxir::Interval<sandboxir::Instruction> Empty;
  EXPECT_TRUE(Empty.empty());
  sandboxir::Interval<sandboxir::Instruction> One(I0, I0);
  EXPECT_FALSE(One.empty());
  // Check contains().
  for (auto &I : *BB) {
    EXPECT_TRUE(Intvl.contains(&I));
    EXPECT_FALSE(Empty.contains(&I));
  }
  EXPECT_FALSE(One.contains(I1));
  EXPECT_FALSE(One.contains(I2));
  EXPECT_FALSE(One.contains(Ret));
  // Check touches().
  {
    sandboxir::Interval<sandboxir::Instruction> Intvl(I2, I2);
    EXPECT_TRUE(Intvl.touches(I1));
    EXPECT_TRUE(Intvl.contains(I2));
    EXPECT_FALSE(Intvl.touches(I2));
    EXPECT_TRUE(Intvl.touches(Ret));
    EXPECT_FALSE(Intvl.touches(I0));
  }
  // Check iterator.
  auto BBIt = BB->begin();
  for (auto &I : Intvl)
    EXPECT_EQ(&I, &*BBIt++);
  {
    // Check equality.
    EXPECT_TRUE(Empty == Empty);
    EXPECT_FALSE(Empty == One);
    EXPECT_TRUE(One == One);
    sandboxir::Interval<sandboxir::Instruction> Intvl1(I0, I2);
    sandboxir::Interval<sandboxir::Instruction> Intvl2(I0, I2);
    EXPECT_TRUE(Intvl1 == Intvl1);
    EXPECT_TRUE(Intvl1 == Intvl2);
  }
  {
    // Check inequality.
    EXPECT_FALSE(Empty != Empty);
    EXPECT_TRUE(Empty != One);
    EXPECT_FALSE(One != One);
    sandboxir::Interval<sandboxir::Instruction> Intvl1(I0, I2);
    sandboxir::Interval<sandboxir::Instruction> Intvl2(I0, I2);
    EXPECT_FALSE(Intvl1 != Intvl1);
    EXPECT_FALSE(Intvl1 != Intvl2);
  }
  {
    // Check disjoint().
    EXPECT_TRUE(Empty.disjoint(Empty));
    EXPECT_TRUE(One.disjoint(Empty));
    EXPECT_TRUE(Empty.disjoint(One));
    sandboxir::Interval<sandboxir::Instruction> Intvl1(I0, I2);
    sandboxir::Interval<sandboxir::Instruction> Intvl2(I1, Ret);
    EXPECT_FALSE(Intvl1.disjoint(Intvl2));
    sandboxir::Interval<sandboxir::Instruction> Intvl3(I2, I2);
    EXPECT_FALSE(Intvl1.disjoint(Intvl3));
    EXPECT_TRUE(Intvl1.disjoint(Empty));
  }
  {
    // Check comesBefore().
    sandboxir::Interval<sandboxir::Instruction> Intvl1(I0, I0);
    sandboxir::Interval<sandboxir::Instruction> Intvl2(I2, I2);
    EXPECT_TRUE(Intvl1.comesBefore(Intvl2));
    EXPECT_FALSE(Intvl2.comesBefore(Intvl1));

    sandboxir::Interval<sandboxir::Instruction> Intvl12(I1, I2);
    EXPECT_TRUE(Intvl1.comesBefore(Intvl12));
    EXPECT_FALSE(Intvl12.comesBefore(Intvl1));
    {
#ifndef NDEBUG
      // Check comesBefore() with non-disjoint intervals.
      sandboxir::Interval<sandboxir::Instruction> Intvl1(I0, I2);
      sandboxir::Interval<sandboxir::Instruction> Intvl2(I2, I2);
      EXPECT_DEATH(Intvl1.comesBefore(Intvl2), ".*disjoint.*");
#endif // NDEBUG
    }
  }
}

// Helper function for returning a vector of instruction pointers from a range
// of references.
template <typename RangeT>
static SmallVector<sandboxir::Instruction *> getPtrVec(RangeT Range) {
  SmallVector<sandboxir::Instruction *> PtrVec(llvm::make_pointer_range(Range));
  return PtrVec;
}

TEST_F(IntervalTest, Difference) {
  parseIR(C, R"IR(
define void @foo(i8 %v0) {
  %I0 = add i8 %v0, %v0
  %I1 = add i8 %v0, %v0
  %I2 = add i8 %v0, %v0
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  auto *I2 = &*It++;
  auto *Ret = &*It++;

  {
    // Check [I0,Ret] - []
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> Empty;
    auto Diffs = I0Ret - Empty;
    EXPECT_EQ(Diffs.size(), 1u);
    const sandboxir::Interval<sandboxir::Instruction> &Diff = Diffs[0];
    EXPECT_THAT(getPtrVec(Diff), testing::ElementsAre(I0, I1, I2, Ret));

    // Check getSingleDiff().
    EXPECT_EQ(I0Ret.getSingleDiff(Empty), Diff);
  }
  {
    // Check [] - [I0,Ret]
    sandboxir::Interval<sandboxir::Instruction> Empty;
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    auto Diffs = Empty - I0Ret;
    EXPECT_EQ(Diffs.size(), 1u);
    const sandboxir::Interval<sandboxir::Instruction> &Diff = Diffs[0];
    EXPECT_TRUE(Diff.empty());

    // Check getSingleDiff().
    EXPECT_EQ(Empty.getSingleDiff(I0Ret), Diff);
  }
  {
    // Check [I0,Ret] - [I0].
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    auto Diffs = I0Ret - I0I0;
    EXPECT_EQ(Diffs.size(), 1u);
    const sandboxir::Interval<sandboxir::Instruction> &Diff = Diffs[0];
    EXPECT_THAT(getPtrVec(Diff), testing::ElementsAre(I1, I2, Ret));

    // Check getSingleDiff().
    EXPECT_EQ(I0Ret.getSingleDiff(I0I0), Diff);
  }
  {
    // Check [I0,Ret] - [I1].
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> I1I1(I1, I1);
    auto Diffs = I0Ret - I1I1;
    EXPECT_EQ(Diffs.size(), 2u);
    const sandboxir::Interval<sandboxir::Instruction> &Diff0 = Diffs[0];
    EXPECT_THAT(getPtrVec(Diff0), testing::ElementsAre(I0));
    const sandboxir::Interval<sandboxir::Instruction> &Diff1 = Diffs[1];
    EXPECT_THAT(getPtrVec(Diff1), testing::ElementsAre(I2, Ret));

#ifndef NDEBUG
    // Check getSingleDiff().
    EXPECT_DEATH(I0Ret.getSingleDiff(I1I1), ".*single.*");
#endif // NDEBUG
  }
}

TEST_F(IntervalTest, Intersection) {
  parseIR(C, R"IR(
define void @foo(i8 %v0) {
  %I0 = add i8 %v0, %v0
  %I1 = add i8 %v0, %v0
  %I2 = add i8 %v0, %v0
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  [[maybe_unused]] auto *I2 = &*It++;
  auto *Ret = &*It++;

  {
    // Check [I0,Ret] ^ []
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> Empty;
    auto Intersection = I0Ret.intersection(Empty);
    EXPECT_TRUE(Intersection.empty());
  }
  {
    // Check [] ^ [I0,Ret]
    sandboxir::Interval<sandboxir::Instruction> Empty;
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    auto Intersection = Empty.intersection(I0Ret);
    EXPECT_TRUE(Intersection.empty());
  }
  {
    // Check [I0,Ret] ^ [I0]
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    auto Intersection = I0Ret.intersection(I0I0);
    EXPECT_THAT(getPtrVec(Intersection), testing::ElementsAre(I0));
  }
  {
    // Check [I0] ^ [I0,Ret]
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    auto Intersection = I0I0.intersection(I0Ret);
    EXPECT_THAT(getPtrVec(Intersection), testing::ElementsAre(I0));
  }
  {
    // Check [I0,Ret] ^ [I1].
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    sandboxir::Interval<sandboxir::Instruction> I1I1(I1, I1);
    auto Intersection = I0Ret.intersection(I1I1);
    EXPECT_THAT(getPtrVec(Intersection), testing::ElementsAre(I1));
  }
}

TEST_F(IntervalTest, UnionInterval) {
  parseIR(C, R"IR(
define void @foo(i8 %v0) {
  %I0 = add i8 %v0, %v0
  %I1 = add i8 %v0, %v0
  %I2 = add i8 %v0, %v0
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  [[maybe_unused]] auto *I2 = &*It++;
  auto *Ret = &*It++;

  {
    // Check [I0] unionInterval [I2].
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    sandboxir::Interval<sandboxir::Instruction> I2I2(I2, I2);
    auto SingleUnion = I0I0.getUnionInterval(I2I2);
    EXPECT_THAT(getPtrVec(SingleUnion), testing::ElementsAre(I0, I1, I2));
  }
  {
    // Check [I0] unionInterval Empty.
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    sandboxir::Interval<sandboxir::Instruction> Empty;
    auto SingleUnion = I0I0.getUnionInterval(Empty);
    EXPECT_THAT(getPtrVec(SingleUnion), testing::ElementsAre(I0));
  }
  {
    // Check [I0,I1] unionInterval [I1].
    sandboxir::Interval<sandboxir::Instruction> I0I1(I0, I1);
    sandboxir::Interval<sandboxir::Instruction> I1I1(I1, I1);
    auto SingleUnion = I0I1.getUnionInterval(I1I1);
    EXPECT_THAT(getPtrVec(SingleUnion), testing::ElementsAre(I0, I1));
  }
  {
    // Check [I2,Ret] unionInterval [I0].
    sandboxir::Interval<sandboxir::Instruction> I2Ret(I2, Ret);
    sandboxir::Interval<sandboxir::Instruction> I0I0(I0, I0);
    auto SingleUnion = I2Ret.getUnionInterval(I0I0);
    EXPECT_THAT(getPtrVec(SingleUnion), testing::ElementsAre(I0, I1, I2, Ret));
  }
}

TEST_F(IntervalTest, NotifyMoveInstr) {
  parseIR(C, R"IR(
define void @foo(i8 %v0) {
  %I0 = add i8 %v0, %v0
  %I1 = add i8 %v0, %v0
  %I2 = add i8 %v0, %v0
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  auto *I2 = &*It++;
  auto *Ret = &*It++;
  {
    // Assert that we don't try to move external instr to the interval.
    sandboxir::Interval<sandboxir::Instruction> I2Ret(I2, Ret);
#ifndef NDEBUG
    EXPECT_DEATH(I2Ret.notifyMoveInstr(I0, Ret->getIterator()), ".*interval.*");
#endif // NDEBUG
  }
  {
    // Assert that we don't move before self.
    sandboxir::Interval<sandboxir::Instruction> I2Ret(I2, Ret);
#ifndef NDEBUG
    EXPECT_DEATH(I2Ret.notifyMoveInstr(Ret, Ret->getIterator()), ".*self.*");
#endif // NDEBUG
  }
  {
    // Single-element interval.
    sandboxir::Interval<sandboxir::Instruction> I2I2(I2, I2);
    I2I2.notifyMoveInstr(I2, Ret->getIterator());
    EXPECT_EQ(I2I2.top(), I2);
    EXPECT_EQ(I2I2.bottom(), I2);
  }
  {
    // Two-element interval swap.
    sandboxir::Interval<sandboxir::Instruction> I1I2(I1, I2);
    I1I2.notifyMoveInstr(I2, I1->getIterator());
    I2->moveBefore(I1);
    EXPECT_EQ(I1I2.top(), I2);
    EXPECT_EQ(I1I2.bottom(), I1);

    I2->moveAfter(I1);
  }
  {
    // Move to same position.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(I0, I1->getIterator());
    I0->moveBefore(I1);
    EXPECT_EQ(I0Ret.top(), I0);
    EXPECT_EQ(I0Ret.bottom(), Ret);
  }
  {
    // Move internal to internal.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(I2, I1->getIterator());
    I2->moveBefore(I1);
    EXPECT_EQ(I0Ret.top(), I0);
    EXPECT_EQ(I0Ret.bottom(), Ret);

    I2->moveAfter(I1);
  }
  {
    // Move internal before top.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(I2, I0->getIterator());
    I2->moveBefore(I0);
    EXPECT_EQ(I0Ret.top(), I2);
    EXPECT_EQ(I0Ret.bottom(), Ret);

    I2->moveAfter(I1);
  }
  {
    // Move internal to bottom.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(I2, BB->end());
    I2->moveAfter(Ret);
    EXPECT_EQ(I0Ret.top(), I0);
    EXPECT_EQ(I0Ret.bottom(), I2);

    I2->moveAfter(I1);
  }
  {
    // Move bottom before internal.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(Ret, I2->getIterator());
    Ret->moveBefore(I2);
    EXPECT_EQ(I0Ret.top(), I0);
    EXPECT_EQ(I0Ret.bottom(), I2);

    Ret->moveAfter(I2);
  }
  {
    // Move bottom before top.
    sandboxir::Interval<sandboxir::Instruction> I0Ret(I0, Ret);
    I0Ret.notifyMoveInstr(Ret, I0->getIterator());
    Ret->moveBefore(I0);
    EXPECT_EQ(I0Ret.top(), Ret);
    EXPECT_EQ(I0Ret.bottom(), I2);

    Ret->moveAfter(I2);
  }
}
