//===- SeedCollectorTest.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/SeedCollector.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/BasicAliasAnalysis.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Dominators.h"
#include "llvm/SandboxIR/Function.h"
#include "llvm/SandboxIR/Instruction.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"

using namespace llvm;

// TODO: gcc-10 has a bug that causes the below line not to compile due to some
// macro-magic in gunit in combination with a class with pure-virtual
// function. Once gcc-10 is no longer supported, replace this function with
// something like the following:
//
// EXPECT_THAT(SB, testing::ElementsAre(St0, St1, St2, St3));
static void
ExpectThatElementsAre(sandboxir::SeedBundle &SR,
                      llvm::ArrayRef<sandboxir::Instruction *> Contents) {
  EXPECT_EQ(range_size(SR), Contents.size());
  auto CI = Contents.begin();
  if (range_size(SR) == Contents.size())
    for (auto &S : SR)
      EXPECT_EQ(S, *CI++);
}

struct SeedBundleTest : 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("LegalityTest", errs());
  }
  BasicBlock *getBasicBlockByName(Function &F, StringRef Name) {
    for (BasicBlock &BB : F)
      if (BB.getName() == Name)
        return &BB;
    llvm_unreachable("Expected to find basic block!");
  }
};

// Stub class to make the abstract base class testable.
class SeedBundleForTest : public sandboxir::SeedBundle {
public:
  using sandboxir::SeedBundle::SeedBundle;
  void insert(sandboxir::Instruction *I, ScalarEvolution &SE) override {
    insertAt(Seeds.end(), I);
  }
};

TEST_F(SeedBundleTest, SeedBundle) {
  parseIR(C, R"IR(
define void @foo(float %v0, i32 %i0, i16 %i1, i8 %i2) {
bb:
  %add0 = fadd float %v0, %v0
  %add1 = fadd float %v0, %v0
  %add2 = add i8 %i2, %i2
  %add3 = add i16 %i1, %i1
  %add4 = add i32 %i0, %i0
  %add5 = add i16 %i1, %i1
  %add6 = add i8 %i2, %i2
  %add7 = add i8 %i2, %i2
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  DataLayout DL(M->getDataLayout());
  auto *BB = &*F.begin();
  auto It = BB->begin();
  auto *I0 = &*It++;
  auto *I1 = &*It++;
  // Assume first two instructions are identical in the number of bits.
  const unsigned IOBits = sandboxir::Utils::getNumBits(I0, DL);
  // Constructor
  SeedBundleForTest SBO(I0);
  EXPECT_EQ(*SBO.begin(), I0);
  // getNumUnusedBits after constructor
  EXPECT_EQ(SBO.getNumUnusedBits(), IOBits);
  // setUsed
  SBO.setUsed(I0);
  // allUsed
  EXPECT_TRUE(SBO.allUsed());
  // isUsed
  EXPECT_TRUE(SBO.isUsed(0));
  // getNumUnusedBits after setUsed
  EXPECT_EQ(SBO.getNumUnusedBits(), 0u);
  // insertAt
  SBO.insertAt(SBO.end(), I1);
  EXPECT_NE(*SBO.begin(), I1);
  // getNumUnusedBits after insertAt
  EXPECT_EQ(SBO.getNumUnusedBits(), IOBits);
  // allUsed
  EXPECT_FALSE(SBO.allUsed());
  // getFirstUnusedElement
  EXPECT_EQ(SBO.getFirstUnusedElementIdx(), 1u);

  SmallVector<sandboxir::Instruction *> Insts;
  // add2 through add7
  Insts.push_back(&*It++);
  Insts.push_back(&*It++);
  Insts.push_back(&*It++);
  Insts.push_back(&*It++);
  Insts.push_back(&*It++);
  Insts.push_back(&*It++);
  unsigned BundleBits = 0;
  for (auto &S : Insts)
    BundleBits += sandboxir::Utils::getNumBits(S);
  // Ensure the instructions are as expected.
  EXPECT_EQ(BundleBits, 88u);
  auto Seeds = Insts;
  // Constructor
  SeedBundleForTest SB1(std::move(Seeds));
  // getNumUnusedBits after constructor
  EXPECT_EQ(SB1.getNumUnusedBits(), BundleBits);
  // setUsed with index
  SB1.setUsed(1);
  // getFirstUnusedElementIdx
  EXPECT_EQ(SB1.getFirstUnusedElementIdx(), 0u);
  SB1.setUsed(unsigned(0));
  // getFirstUnusedElementIdx not at end
  EXPECT_EQ(SB1.getFirstUnusedElementIdx(), 2u);

  // getSlice is (StartIdx, MaxVecRegBits, ForcePowerOf2). It's easier to
  // compare test cases without the parameter-name comments inline.
  auto Slice0 = SB1.getSlice(2, 64, true);
  EXPECT_THAT(Slice0,
              testing::ElementsAre(Insts[2], Insts[3], Insts[4], Insts[5]));
  auto Slice1 = SB1.getSlice(2, 72, true);
  EXPECT_THAT(Slice1,
              testing::ElementsAre(Insts[2], Insts[3], Insts[4], Insts[5]));
  auto Slice2 = SB1.getSlice(2, 80, true);
  EXPECT_THAT(Slice2,
              testing::ElementsAre(Insts[2], Insts[3], Insts[4], Insts[5]));

  SB1.setUsed(2);
  auto Slice3 = SB1.getSlice(3, 64, false);
  EXPECT_THAT(Slice3, testing::ElementsAre(Insts[3], Insts[4], Insts[5]));
  // getSlice empty case
  SB1.setUsed(3);
  auto Slice4 = SB1.getSlice(4, /* MaxVecRegBits */ 8,
                             /* ForcePowerOf2 */ true);
  EXPECT_EQ(Slice4.size(), 0u);
}

TEST_F(SeedBundleTest, MemSeedBundle) {
  parseIR(C, R"IR(
define void @foo(ptr %ptrA, float %val, ptr %ptr) {
bb:
  %gep0 = getelementptr float, ptr %ptr, i32 0
  %gep1 = getelementptr float, ptr %ptr, i32 1
  %gep2 = getelementptr float, ptr %ptr, i32 3
  %gep3 = getelementptr float, ptr %ptr, i32 4
  store float %val, ptr %gep0
  store float %val, ptr %gep1
  store float %val, ptr %gep2
  store float %val, ptr %gep3

  load float, ptr %gep0
  load float, ptr %gep1
  load float, ptr %gep2
  load float, ptr %gep3

  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");

  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto *BB = &*F.begin();
  auto It = std::next(BB->begin(), 4);
  auto *S0 = cast<sandboxir::StoreInst>(&*It++);
  auto *S1 = cast<sandboxir::StoreInst>(&*It++);
  auto *S2 = cast<sandboxir::StoreInst>(&*It++);
  auto *S3 = cast<sandboxir::StoreInst>(&*It++);

  // Single instruction constructor; test insert out of memory order
  sandboxir::StoreSeedBundle SB(S3);
  SB.insert(S1, SE);
  SB.insert(S2, SE);
  SB.insert(S0, SE);
  EXPECT_THAT(SB, testing::ElementsAre(S0, S1, S2, S3));

  // Instruction list constructor; test list out of order
  auto *L0 = cast<sandboxir::LoadInst>(&*It++);
  auto *L1 = cast<sandboxir::LoadInst>(&*It++);
  auto *L2 = cast<sandboxir::LoadInst>(&*It++);
  auto *L3 = cast<sandboxir::LoadInst>(&*It++);
  SmallVector<sandboxir::Instruction *> Loads;
  Loads.push_back(L1);
  Loads.push_back(L3);
  Loads.push_back(L2);
  Loads.push_back(L0);
  sandboxir::LoadSeedBundle LB(std::move(Loads), SE);
  EXPECT_THAT(LB, testing::ElementsAre(L0, L1, L2, L3));
}

TEST_F(SeedBundleTest, Container) {
  parseIR(C, R"IR(
define void @foo(ptr %ptrA, float %val, ptr %ptrB) {
bb:
  %gepA0 = getelementptr float, ptr %ptrA, i32 0
  %gepA1 = getelementptr float, ptr %ptrA, i32 1
  %gepB0 = getelementptr float, ptr %ptrB, i32 0
  %gepB1 = getelementptr float, ptr %ptrB, i32 1
  store float %val, ptr %gepA0
  store float %val, ptr %gepA1
  store float %val, ptr %gepB0
  store float %val, ptr %gepB1
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");

  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto &BB = *F.begin();
  auto It = std::next(BB.begin(), 4);
  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::SeedContainer SC(SE);
  // Check begin() end() when empty.
  EXPECT_EQ(SC.begin(), SC.end());

  SC.insert(S0);
  SC.insert(S1);
  SC.insert(S2);
  SC.insert(S3);
  unsigned Cnt = 0;
  SmallVector<sandboxir::SeedBundle *> Bndls;
  for (auto &SeedBndl : SC) {
    EXPECT_EQ(SeedBndl.size(), 2u);
    ++Cnt;
    Bndls.push_back(&SeedBndl);
  }
  EXPECT_EQ(Cnt, 2u);

  // Mark them "Used" to check if operator++ skips them in the next loop.
  for (auto *SeedBndl : Bndls)
    for (auto Lane : seq<unsigned>(SeedBndl->size()))
      SeedBndl->setUsed(Lane);
  // Check if iterator::operator++ skips used lanes.
  Cnt = 0;
  for (auto &SeedBndl : SC) {
    (void)SeedBndl;
    ++Cnt;
  }
  EXPECT_EQ(Cnt, 0u);
}

TEST_F(SeedBundleTest, ConsecutiveStores) {
  // Where "Consecutive" means the stores address consecutive locations in
  // memory, but not in program order. Check to see that the collector puts them
  // in the proper order for vectorization.
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, float %val) {
bb:
  %ptr0 = getelementptr float, ptr %ptr, i32 0
  %ptr1 = getelementptr float, ptr %ptr, i32 1
  %ptr2 = getelementptr float, ptr %ptr, i32 2
  %ptr3 = getelementptr float, ptr %ptr, i32 3
  store float %val, ptr %ptr0
  store float %val, ptr %ptr2
  store float %val, ptr %ptr1
  store float %val, ptr %ptr3
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto BB = F.begin();
  sandboxir::SeedCollector SC(&*BB, SE);

  // Find the stores
  auto It = std::next(BB->begin(), 4);
  // StX with X as the order by offset in memory
  auto *St0 = &*It++;
  auto *St2 = &*It++;
  auto *St1 = &*It++;
  auto *St3 = &*It++;

  auto StoreSeedsRange = SC.getStoreSeeds();
  auto &SB = *StoreSeedsRange.begin();
  //  Expect just one vector of store seeds
  EXPECT_EQ(range_size(StoreSeedsRange), 1u);
  ExpectThatElementsAre(SB, {St0, St1, St2, St3});
}

TEST_F(SeedBundleTest, StoresWithGaps) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, float %val) {
bb:
  %ptr0 = getelementptr float, ptr %ptr, i32 0
  %ptr1 = getelementptr float, ptr %ptr, i32 3
  %ptr2 = getelementptr float, ptr %ptr, i32 5
  %ptr3 = getelementptr float, ptr %ptr, i32 7
  store float %val, ptr %ptr0
  store float %val, ptr %ptr2
  store float %val, ptr %ptr1
  store float %val, ptr %ptr3
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto BB = F.begin();
  sandboxir::SeedCollector SC(&*BB, SE);

  // Find the stores
  auto It = std::next(BB->begin(), 4);
  // StX with X as the order by offset in memory
  auto *St0 = &*It++;
  auto *St2 = &*It++;
  auto *St1 = &*It++;
  auto *St3 = &*It++;

  auto StoreSeedsRange = SC.getStoreSeeds();
  auto &SB = *StoreSeedsRange.begin();
  // Expect just one vector of store seeds
  EXPECT_EQ(range_size(StoreSeedsRange), 1u);
  ExpectThatElementsAre(SB, {St0, St1, St2, St3});
  // Check that the EraseInstr callback works.

  // TODO: Range_size counts fully used-bundles even though the iterator skips
  // them. Further, iterating over anything other than the Bundles in a
  // SeedContainer includes used seeds. So for now just check that removing all
  // the seeds from a bundle also empties the bundle.
  St0->eraseFromParent();
  St1->eraseFromParent();
  St2->eraseFromParent();
  St3->eraseFromParent();
  size_t nonEmptyBundleCount = 0;
  for (auto &B : SC.getStoreSeeds()) {
    (void)B;
    nonEmptyBundleCount++;
  }
  EXPECT_EQ(nonEmptyBundleCount, 0u);
}

TEST_F(SeedBundleTest, VectorStores) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, <2 x float> %val0, i64 %val1) {
bb:
  %ptr0 = getelementptr float, ptr %ptr, i32 0
  %ptr1 = getelementptr float, ptr %ptr, i32 1
  %ptr2 = getelementptr i64, ptr %ptr, i32 2
  store <2 x float> %val0, ptr %ptr1
  store <2 x float> %val0, ptr %ptr0
  store atomic i64 %val1, ptr %ptr2 unordered, align 8
  store volatile i64 %val1, ptr %ptr2

  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto BB = F.begin();
  sandboxir::SeedCollector SC(&*BB, SE);

  // Find the stores
  auto It = std::next(BB->begin(), 3);
  // StX with X as the order by offset in memory
  auto *St1 = &*It++;
  auto *St0 = &*It++;

  auto StoreSeedsRange = SC.getStoreSeeds();
  EXPECT_EQ(range_size(StoreSeedsRange), 1u);
  auto &SB = *StoreSeedsRange.begin();
  // isValidMemSeed check: The atomic and volatile stores should not
  // be included in the bundle, but the vector stores should be.
  ExpectThatElementsAre(SB, {St0, St1});
}

TEST_F(SeedBundleTest, MixedScalarVectors) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, float %v, <2 x float> %val) {
bb:
  %ptr0 = getelementptr float, ptr %ptr, i32 0
  %ptr1 = getelementptr float, ptr %ptr, i32 1
  %ptr3 = getelementptr float, ptr %ptr, i32 3
  store float %v, ptr %ptr0
  store float %v, ptr %ptr3
  store <2 x float> %val, ptr %ptr1
  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto BB = F.begin();
  sandboxir::SeedCollector SC(&*BB, SE);

  // Find the stores
  auto It = std::next(BB->begin(), 3);
  // StX with X as the order by offset in memory
  auto *St0 = &*It++;
  auto *St3 = &*It++;
  auto *St1 = &*It++;

  auto StoreSeedsRange = SC.getStoreSeeds();
  EXPECT_EQ(range_size(StoreSeedsRange), 1u);
  auto &SB = *StoreSeedsRange.begin();
  // isValidMemSeedCheck here: all of the three stores should be included.
  ExpectThatElementsAre(SB, {St0, St1, St3});
}

TEST_F(SeedBundleTest, VectorLoads) {
  parseIR(C, R"IR(
define void @foo(ptr noalias %ptr, <2 x float> %val0) {
bb:
  %ptr0 = getelementptr float, ptr %ptr, i32 0
  %ptr1 = getelementptr float, ptr %ptr, i32 1
  %r0 = load <2 x float>, ptr %ptr0
  %r1 = load <2 x float>, ptr %ptr1
  %r2 = load atomic i64, ptr %ptr0 unordered, align 8
  %r3 = load volatile i64, ptr %ptr1
  %r4 = load void()*, ptr %ptr1

  ret void
}
)IR");
  Function &LLVMF = *M->getFunction("foo");
  DominatorTree DT(LLVMF);
  TargetLibraryInfoImpl TLII;
  TargetLibraryInfo TLI(TLII);
  DataLayout DL(M->getDataLayout());
  LoopInfo LI(DT);
  AssumptionCache AC(LLVMF);
  ScalarEvolution SE(LLVMF, TLI, AC, DT, LI);

  sandboxir::Context Ctx(C);
  auto &F = *Ctx.createFunction(&LLVMF);
  auto BB = F.begin();
  sandboxir::SeedCollector SC(&*BB, SE);

  // Find the loads
  auto It = std::next(BB->begin(), 2);
  // StX with X as the order by offset in memory
  auto *Ld0 = cast<sandboxir::LoadInst>(&*It++);
  auto *Ld1 = cast<sandboxir::LoadInst>(&*It++);

  auto LoadSeedsRange = SC.getLoadSeeds();
  EXPECT_EQ(range_size(LoadSeedsRange), 2u);
  auto &SB = *LoadSeedsRange.begin();
  // isValidMemSeed check: The atomic and volatile loads should not
  // be included in the bundle, the vector stores should be, but the
  // void-typed load should not.
  ExpectThatElementsAre(SB, {Ld0, Ld1});
}
