//===- llvm/unittest/ADT/ValueMapTest.cpp - ValueMap unit tests -*- C++ -*-===//
//
// 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/IR/ValueMap.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "gtest/gtest.h"

using namespace llvm;

namespace {

// Test fixture
template<typename T>
class ValueMapTest : public testing::Test {
protected:
  LLVMContext Context;
  Constant *ConstantV;
  std::unique_ptr<BitCastInst> BitcastV;
  std::unique_ptr<BinaryOperator> AddV;

  ValueMapTest()
      : ConstantV(ConstantInt::get(Type::getInt32Ty(Context), 0)),
        BitcastV(new BitCastInst(ConstantV, Type::getInt32Ty(Context))),
        AddV(BinaryOperator::CreateAdd(ConstantV, ConstantV)) {}
};

// Run everything on Value*, a subtype to make sure that casting works as
// expected, and a const subtype to make sure we cast const correctly.
typedef ::testing::Types<Value, Instruction, const Instruction> KeyTypes;
TYPED_TEST_SUITE(ValueMapTest, KeyTypes, );

TYPED_TEST(ValueMapTest, Null) {
  ValueMap<TypeParam*, int> VM1;
  VM1[nullptr] = 7;
  EXPECT_EQ(7, VM1.lookup(nullptr));
}

TYPED_TEST(ValueMapTest, FollowsValue) {
  ValueMap<TypeParam*, int> VM;
  VM[this->BitcastV.get()] = 7;
  EXPECT_EQ(7, VM.lookup(this->BitcastV.get()));
  EXPECT_EQ(0u, VM.count(this->AddV.get()));
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  EXPECT_EQ(7, VM.lookup(this->AddV.get()));
  EXPECT_EQ(0u, VM.count(this->BitcastV.get()));
  this->AddV.reset();
  EXPECT_EQ(0u, VM.count(this->AddV.get()));
  EXPECT_EQ(0u, VM.count(this->BitcastV.get()));
  EXPECT_EQ(0U, VM.size());
}

TYPED_TEST(ValueMapTest, OperationsWork) {
  ValueMap<TypeParam*, int> VM;
  ValueMap<TypeParam*, int> VM2(16);  (void)VM2;
  typename ValueMapConfig<TypeParam*>::ExtraData Data;
  ValueMap<TypeParam*, int> VM3(Data, 16);  (void)VM3;
  EXPECT_TRUE(VM.empty());

  VM[this->BitcastV.get()] = 7;

  // Find:
  typename ValueMap<TypeParam*, int>::iterator I =
    VM.find(this->BitcastV.get());
  ASSERT_TRUE(I != VM.end());
  EXPECT_EQ(this->BitcastV.get(), I->first);
  EXPECT_EQ(7, I->second);
  EXPECT_TRUE(VM.find(this->AddV.get()) == VM.end());

  // Const find:
  const ValueMap<TypeParam*, int> &CVM = VM;
  typename ValueMap<TypeParam*, int>::const_iterator CI =
    CVM.find(this->BitcastV.get());
  ASSERT_TRUE(CI != CVM.end());
  EXPECT_EQ(this->BitcastV.get(), CI->first);
  EXPECT_EQ(7, CI->second);
  EXPECT_TRUE(CVM.find(this->AddV.get()) == CVM.end());

  // Insert:
  std::pair<typename ValueMap<TypeParam*, int>::iterator, bool> InsertResult1 =
    VM.insert(std::make_pair(this->AddV.get(), 3));
  EXPECT_EQ(this->AddV.get(), InsertResult1.first->first);
  EXPECT_EQ(3, InsertResult1.first->second);
  EXPECT_TRUE(InsertResult1.second);
  EXPECT_EQ(1u, VM.count(this->AddV.get()));
  std::pair<typename ValueMap<TypeParam*, int>::iterator, bool> InsertResult2 =
    VM.insert(std::make_pair(this->AddV.get(), 5));
  EXPECT_EQ(this->AddV.get(), InsertResult2.first->first);
  EXPECT_EQ(3, InsertResult2.first->second);
  EXPECT_FALSE(InsertResult2.second);

  // Erase:
  VM.erase(InsertResult2.first);
  EXPECT_EQ(0U, VM.count(this->AddV.get()));
  EXPECT_EQ(1U, VM.count(this->BitcastV.get()));
  VM.erase(this->BitcastV.get());
  EXPECT_EQ(0U, VM.count(this->BitcastV.get()));
  EXPECT_EQ(0U, VM.size());

  // Range insert:
  SmallVector<std::pair<Instruction*, int>, 2> Elems;
  Elems.push_back(std::make_pair(this->AddV.get(), 1));
  Elems.push_back(std::make_pair(this->BitcastV.get(), 2));
  VM.insert(Elems.begin(), Elems.end());
  EXPECT_EQ(1, VM.lookup(this->AddV.get()));
  EXPECT_EQ(2, VM.lookup(this->BitcastV.get()));
}

template<typename ExpectedType, typename VarType>
void CompileAssertHasType(VarType) {
  static_assert(std::is_same<ExpectedType, VarType>::value,
                "Not the same type");
}

TYPED_TEST(ValueMapTest, Iteration) {
  ValueMap<TypeParam*, int> VM;
  VM[this->BitcastV.get()] = 2;
  VM[this->AddV.get()] = 3;
  size_t size = 0;
  for (typename ValueMap<TypeParam*, int>::iterator I = VM.begin(), E = VM.end();
       I != E; ++I) {
    ++size;
    std::pair<TypeParam*, int> value = *I; (void)value;
    CompileAssertHasType<TypeParam*>(I->first);
    if (I->second == 2) {
      EXPECT_EQ(this->BitcastV.get(), I->first);
      I->second = 5;
    } else if (I->second == 3) {
      EXPECT_EQ(this->AddV.get(), I->first);
      I->second = 6;
    } else {
      ADD_FAILURE() << "Iterated through an extra value.";
    }
  }
  EXPECT_EQ(2U, size);
  EXPECT_EQ(5, VM[this->BitcastV.get()]);
  EXPECT_EQ(6, VM[this->AddV.get()]);

  size = 0;
  // Cast to const ValueMap to avoid a bug in DenseMap's iterators.
  const ValueMap<TypeParam*, int>& CVM = VM;
  for (typename ValueMap<TypeParam*, int>::const_iterator I = CVM.begin(),
         E = CVM.end(); I != E; ++I) {
    ++size;
    std::pair<TypeParam*, int> value = *I;  (void)value;
    CompileAssertHasType<TypeParam*>(I->first);
    if (I->second == 5) {
      EXPECT_EQ(this->BitcastV.get(), I->first);
    } else if (I->second == 6) {
      EXPECT_EQ(this->AddV.get(), I->first);
    } else {
      ADD_FAILURE() << "Iterated through an extra value.";
    }
  }
  EXPECT_EQ(2U, size);
}

TYPED_TEST(ValueMapTest, DefaultCollisionBehavior) {
  // By default, we overwrite the old value with the replaced value.
  ValueMap<TypeParam*, int> VM;
  VM[this->BitcastV.get()] = 7;
  VM[this->AddV.get()] = 9;
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  EXPECT_EQ(0u, VM.count(this->BitcastV.get()));
  EXPECT_EQ(9, VM.lookup(this->AddV.get()));
}

TYPED_TEST(ValueMapTest, ConfiguredCollisionBehavior) {
  // TODO: Implement this when someone needs it.
}

template<typename KeyT, typename MutexT>
struct LockMutex : ValueMapConfig<KeyT, MutexT> {
  struct ExtraData {
    MutexT *M;
    bool *CalledRAUW;
    bool *CalledDeleted;
  };
  static void onRAUW(const ExtraData &Data, KeyT Old, KeyT New) {
    *Data.CalledRAUW = true;
    EXPECT_FALSE(Data.M->try_lock()) << "Mutex should already be locked.";
  }
  static void onDelete(const ExtraData &Data, KeyT Old) {
    *Data.CalledDeleted = true;
    EXPECT_FALSE(Data.M->try_lock()) << "Mutex should already be locked.";
  }
  static MutexT *getMutex(const ExtraData &Data) { return Data.M; }
};
// FIXME: These tests started failing on Windows.
#if LLVM_ENABLE_THREADS && !defined(_WIN32)
TYPED_TEST(ValueMapTest, LocksMutex) {
  std::mutex M;
  bool CalledRAUW = false, CalledDeleted = false;
  typedef LockMutex<TypeParam*, std::mutex> ConfigType;
  typename ConfigType::ExtraData Data = {&M, &CalledRAUW, &CalledDeleted};
  ValueMap<TypeParam*, int, ConfigType> VM(Data);
  VM[this->BitcastV.get()] = 7;
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  this->AddV.reset();
  EXPECT_TRUE(CalledRAUW);
  EXPECT_TRUE(CalledDeleted);
}
#endif

template<typename KeyT>
struct NoFollow : ValueMapConfig<KeyT> {
  enum { FollowRAUW = false };
};

TYPED_TEST(ValueMapTest, NoFollowRAUW) {
  ValueMap<TypeParam*, int, NoFollow<TypeParam*> > VM;
  VM[this->BitcastV.get()] = 7;
  EXPECT_EQ(7, VM.lookup(this->BitcastV.get()));
  EXPECT_EQ(0u, VM.count(this->AddV.get()));
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  EXPECT_EQ(7, VM.lookup(this->BitcastV.get()));
  EXPECT_EQ(0, VM.lookup(this->AddV.get()));
  this->AddV.reset();
  EXPECT_EQ(7, VM.lookup(this->BitcastV.get()));
  EXPECT_EQ(0, VM.lookup(this->AddV.get()));
  this->BitcastV.reset();
  EXPECT_EQ(0, VM.lookup(this->BitcastV.get()));
  EXPECT_EQ(0, VM.lookup(this->AddV.get()));
  EXPECT_EQ(0U, VM.size());
}

template<typename KeyT>
struct CountOps : ValueMapConfig<KeyT> {
  struct ExtraData {
    int *Deletions;
    int *RAUWs;
  };

  static void onRAUW(const ExtraData &Data, KeyT Old, KeyT New) {
    ++*Data.RAUWs;
  }
  static void onDelete(const ExtraData &Data, KeyT Old) {
    ++*Data.Deletions;
  }
};

TYPED_TEST(ValueMapTest, CallsConfig) {
  int Deletions = 0, RAUWs = 0;
  typename CountOps<TypeParam*>::ExtraData Data = {&Deletions, &RAUWs};
  ValueMap<TypeParam*, int, CountOps<TypeParam*> > VM(Data);
  VM[this->BitcastV.get()] = 7;
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  EXPECT_EQ(0, Deletions);
  EXPECT_EQ(1, RAUWs);
  this->AddV.reset();
  EXPECT_EQ(1, Deletions);
  EXPECT_EQ(1, RAUWs);
  this->BitcastV.reset();
  EXPECT_EQ(1, Deletions);
  EXPECT_EQ(1, RAUWs);
}

template<typename KeyT>
struct ModifyingConfig : ValueMapConfig<KeyT> {
  // We'll put a pointer here back to the ValueMap this key is in, so
  // that we can modify it (and clobber *this) before the ValueMap
  // tries to do the same modification.  In previous versions of
  // ValueMap, that exploded.
  typedef ValueMap<KeyT, int, ModifyingConfig<KeyT> > **ExtraData;

  static void onRAUW(ExtraData Map, KeyT Old, KeyT New) {
    (*Map)->erase(Old);
  }
  static void onDelete(ExtraData Map, KeyT Old) {
    (*Map)->erase(Old);
  }
};
TYPED_TEST(ValueMapTest, SurvivesModificationByConfig) {
  ValueMap<TypeParam*, int, ModifyingConfig<TypeParam*> > *MapAddress;
  ValueMap<TypeParam*, int, ModifyingConfig<TypeParam*> > VM(&MapAddress);
  MapAddress = &VM;
  // Now the ModifyingConfig can modify the Map inside a callback.
  VM[this->BitcastV.get()] = 7;
  this->BitcastV->replaceAllUsesWith(this->AddV.get());
  EXPECT_EQ(0u, VM.count(this->BitcastV.get()));
  EXPECT_EQ(0u, VM.count(this->AddV.get()));
  VM[this->AddV.get()] = 7;
  this->AddV.reset();
  EXPECT_EQ(0u, VM.count(this->AddV.get()));
}

} // end namespace
