//===--- ImmutablePointerSet.h ----------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This file contains an implementation of a bump ptr allocated immutable
/// pointer set.
///
/// The target of this data structure are sets of pointers (with N < 100) that
/// are propagated through many basic blocks. These pointer sets will be merged
/// and copied far more than being created from an array.
///
/// Thus we assume the following constraints:
///
/// 1. Our set operations are purely additive. Given a set, one can only add
/// elements to it. One can not remove elements to it. This means we only
/// support construction of sets from arrays and concatenation of pointer sets.
///
/// 2. Our sets must always be ordered and be able to be iterated over
/// efficiently in that order.
///
/// 3. An O(log(n)) set contains method.
///
/// Beyond these constraints, we would like for our data structure to have the
/// following properties for performance reasons:
///
/// 1. Its memory should be bump ptr allocated. We like fast allocation.
///
/// 2. No destructors need to be called when the bump ptr allocator is being
/// destroyed. We like fast destruction and do not want to have to iterate over
/// potentially many of these sets and invoke destructors.
///
/// Thus our design is to represent our sets as bump ptr allocated arrays whose
/// elements are sorted and uniqued. The actual uniquing of the arrays
/// themselves is performed via folding set node.
///
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_IMMUTABLEPOINTERSET_H
#define SWIFT_BASIC_IMMUTABLEPOINTERSET_H

#include "swift/Basic/STLExtras.h"
#include "swift/Basic/NullablePtr.h"
#include "llvm/Support/Allocator.h"
#include "llvm/ADT/FoldingSet.h"
#include <algorithm>
#include <type_traits>

namespace swift {

template <typename PtrTy> class ImmutablePointerSetFactory;

/// An immutable set of pointers. It is backed by a tail allocated sorted array
/// ref.
template <typename T> class ImmutablePointerSet : public llvm::FoldingSetNode {
  using PtrTy = typename std::add_pointer<T>::type;
  friend class ImmutablePointerSetFactory<T>;

  NullablePtr<ImmutablePointerSetFactory<T>> ParentFactory;
  ArrayRef<PtrTy> Data;

  ImmutablePointerSet(ImmutablePointerSetFactory<T> *ParentFactory,
                      ArrayRef<PtrTy> NewData)
      : ParentFactory(ParentFactory), Data(NewData) {}

public:
  ~ImmutablePointerSet() = default;
  ImmutablePointerSet(const ImmutablePointerSet &) = default;
  ImmutablePointerSet(ImmutablePointerSet &&) = default;

  ImmutablePointerSet &operator=(const ImmutablePointerSet &) = default;
  ImmutablePointerSet &operator=(ImmutablePointerSet &&) = default;

  bool operator==(const ImmutablePointerSet<T> &P) const {
    // If this and P have different sizes, we can not be equivalent.
    if (size() != P.size())
      return false;

    // Ok, we now know that both have the same size. If one is empty, the other
    // must be as well, implying equality.
    if (empty())
      return true;

    // Ok, both sets are not empty and the same number of elements. Compare
    // element wise.
    return std::equal(begin(), end(), P.begin());
  }

  bool operator!=(const ImmutablePointerSet<T> &P) const {
    return !(*this == P);
  }

  unsigned count(PtrTy Ptr) const {
    // This returns the first element >= Ptr. Since we know that our array is
    // sorted and uniqued, Ptr must be that element.
    auto LowerBound = std::lower_bound(begin(), end(), Ptr);

    // If Ptr is > than everything in the array, then we obviously have 0.
    if (LowerBound == end())
      return 0;

    // Then check if Ptr is > or Ptr is ==. We only have Ptr if we have == to.
    return *LowerBound == Ptr;
  }

  using iterator = typename ArrayRef<PtrTy>::iterator;
  iterator begin() const { return Data.begin(); }
  iterator end() const { return Data.end(); }

  unsigned size() const { return Data.size(); }
  bool empty() const { return Data.empty(); }

  void Profile(llvm::FoldingSetNodeID &ID) const {
    assert(!Data.empty() && "Should not profile empty ImmutablePointerSet");
    for (PtrTy P : Data) {
      ID.AddPointer(P);
    }
  }

  ImmutablePointerSet<T> *merge(ImmutablePointerSet<T> *Other) {
    if (empty())
      return Other;
    if (Other->empty())
      return this;
    assert(Other->ParentFactory.get() == ParentFactory.get());
    return ParentFactory.get()->merge(this, Other);
  }

  bool hasEmptyIntersection(const ImmutablePointerSet<T> *Other) const {
    // If we are empty or Other is empty, then there are automatically no
    // elements that could be in the intersection. Return true.
    if (empty() || Other->empty())
      return true;

    // Ok, at this point we know that both self and Other are non-empty. They
    // can only have a non-empty intersection if they have elements in common.
    // Sadly it seems the STL does not have such a predicate that is
    // non-constructive in the algorithm library, so we implement it ourselves.
    auto LHSI = begin();
    auto LHSE = end();
    auto RHSI = Other->begin();
    auto RHSE = Other->end();

    // Our implementation is to perform a sorted merge like traversal of both
    // lists, always advancing the iterator with a smaller value. If we ever hit
    // an equality in between our iterators, we have a non-empty intersection.
    //
    // Until either of our iterators hits the end of our target arrays...
    while (LHSI != LHSE && RHSI != RHSE) {
      // If LHSI is equivalent to RHSI, then we have a non-empty intersection...
      // Early exit.
      if (*LHSI == *RHSI)
        return false;

      // Otherwise, if *LHSI is less than *RHSI, advance LHSI.
      if (*LHSI < *RHSI) {
        ++LHSI;
        continue;
      }

      // Otherwise, We know that *RHSI < *LHSI. Advance RHSI.
      ++RHSI;
    }

    // We did not have any overlapping intersection.
    return true;
  }
};

template <typename T> class ImmutablePointerSetFactory {
  using PtrTy = typename std::add_pointer<T>::type;

  using PtrSet = ImmutablePointerSet<T>;
  // This is computed out-of-line so that ImmutablePointerSetFactory is
  // treated as a complete type.
  static const unsigned AllocAlignment;

  llvm::BumpPtrAllocator &Allocator;
  llvm::FoldingSetVector<PtrSet> Set;
  static PtrSet EmptyPtrSet;

public:
  ImmutablePointerSetFactory(llvm::BumpPtrAllocator &A) : Allocator(A), Set() {}
  ImmutablePointerSetFactory(const ImmutablePointerSetFactory &) = delete;
  ImmutablePointerSetFactory(ImmutablePointerSetFactory &&) = delete;
  ImmutablePointerSetFactory &
  operator=(const ImmutablePointerSetFactory &) = delete;
  ImmutablePointerSetFactory &operator=(ImmutablePointerSetFactory &&) = delete;

  // We use a sentinel value here so that we can create an empty value
  // statically.
  static PtrSet *getEmptySet() { return &EmptyPtrSet; }

  void clear() { Set.clear(); }

  /// Given a sorted and uniqued list \p Array, return the ImmutablePointerSet
  /// containing Array. Asserts if \p Array is not sorted and uniqued.
  PtrSet *get(ArrayRef<PtrTy> Array) {
    if (Array.empty())
      return ImmutablePointerSetFactory::getEmptySet();

    // We expect our users to sort/unique the input array. This is because doing
    // it here would either require us to allocate more memory than we need or
    // write into the input Array, which we don't want.
    assert(is_sorted_and_uniqued(Array));

    llvm::FoldingSetNodeID ID;
    for (PtrTy Ptr : Array) {
      ID.AddPointer(Ptr);
    }

    void *InsertPt;
    if (auto *PSet = Set.FindNodeOrInsertPos(ID, InsertPt)) {
      return PSet;
    }

    size_t NumElts = Array.size();
    size_t MemSize = sizeof(PtrSet) + sizeof(PtrTy) * NumElts;

    // Allocate the memory.
    auto *Mem =
        reinterpret_cast<PtrSet *>(Allocator.Allocate(MemSize, AllocAlignment));

    // Copy in the pointers into the tail allocated memory. We do not need to do
    // any sorting/uniquing ourselves since we assume that our users perform
    // this task for us.
    MutableArrayRef<PtrTy> DataMem(reinterpret_cast<PtrTy *>(&Mem[1]), NumElts);
    std::copy(Array.begin(), Array.end(), DataMem.begin());

    // Allocate the new node and insert it into the Set.
    auto *NewNode = new (Mem) PtrSet(this, DataMem);
    Set.InsertNode(NewNode, InsertPt);
    return NewNode;
  }

  PtrSet *merge(PtrSet *S1, ArrayRef<PtrTy> S2) {
    if (S1->empty())
      return get(S2);

    if (S2.empty())
      return S1;

    // We assume that S2 is sorted and uniqued.
    assert(is_sorted_and_uniqued(S2));

    // If S1 and S2 have the same size, do a quick check to see if they
    // equal. If so, we can bail early and just return S1.
    if (S1->size() == S2.size() &&
        std::equal(S1->begin(), S1->end(), S2.begin()))
      return S1;

    llvm::FoldingSetNodeID ID;

    // We know that both of our pointer sets are sorted, so we can essentially
    // perform a sorted set merging algorithm to create the ID. We also count
    // the number of unique elements for allocation purposes.
    unsigned NumElts = 0;
    set_union_for_each(*S1, S2, [&ID, &NumElts](const PtrTy Ptr) -> void {
      ID.AddPointer(Ptr);
      NumElts++;
    });

    // If we find our ID then continue.
    void *InsertPt;
    if (auto *PSet = Set.FindNodeOrInsertPos(ID, InsertPt)) {
      return PSet;
    }

    unsigned MemSize = sizeof(PtrSet) + sizeof(PtrTy) * NumElts;

    // Allocate the memory.
    auto *Mem =
        reinterpret_cast<PtrSet *>(Allocator.Allocate(MemSize, AllocAlignment));

    // Copy in the union of the two pointer sets into the tail allocated
    // memory. Since we know that our sorted arrays are uniqued, we can use
    // set_union to get the uniqued sorted array that we want.
    MutableArrayRef<PtrTy> DataMem(reinterpret_cast<PtrTy *>(&Mem[1]), NumElts);
    std::set_union(S1->begin(), S1->end(), S2.begin(), S2.end(),
                   DataMem.begin());

    // Allocate the new node, insert it into the Set, and return it.
    auto *NewNode = new (Mem) PtrSet(this, DataMem);
    Set.InsertNode(NewNode, InsertPt);
    return NewNode;
  }

  PtrSet *merge(PtrSet *S1, PtrSet *S2) {
    // If either S1 or S2 are the empty PtrSet, just return S2 or S1.
    if (S1->empty())
      return S2;
    if (S2->empty())
      return S1;

    // We know that all of our PtrSets are uniqued. So if S1 and S2 are the same
    // set, their pointers must also be the same set. In such a case, we return
    // early returning S1 without any loss of generality.
    if (S1 == S2)
      return S1;

    llvm::FoldingSetNodeID ID;

    // We know that both of our pointer sets are sorted, so we can essentially
    // perform a sorted set merging algorithm to create the ID. We also count
    // the number of unique elements for allocation purposes.
    unsigned NumElts = 0;
    set_union_for_each(*S1, *S2, [&ID, &NumElts](const PtrTy Ptr) -> void {
      ID.AddPointer(Ptr);
      NumElts++;
    });

    // If we find our ID then continue.
    void *InsertPt;
    if (auto *PSet = Set.FindNodeOrInsertPos(ID, InsertPt)) {
      return PSet;
    }

    unsigned MemSize = sizeof(PtrSet) + sizeof(PtrTy) * NumElts;

    // Allocate the memory.
    auto *Mem =
        reinterpret_cast<PtrSet *>(Allocator.Allocate(MemSize, AllocAlignment));

    // Copy in the union of the two pointer sets into the tail allocated
    // memory. Since we know that our sorted arrays are uniqued, we can use
    // set_union to get the uniqued sorted array that we want.
    MutableArrayRef<PtrTy> DataMem(reinterpret_cast<PtrTy *>(&Mem[1]), NumElts);
    std::set_union(S1->begin(), S1->end(), S2->begin(), S2->end(),
                   DataMem.begin());

    // Allocate the new node, insert it into the Set, and return it.
    auto *NewNode = new (Mem) PtrSet(this, DataMem);
    Set.InsertNode(NewNode, InsertPt);
    return NewNode;
  }
};

template <typename T>
ImmutablePointerSet<T> ImmutablePointerSetFactory<T>::EmptyPtrSet =
    ImmutablePointerSet<T>(nullptr, {});

template <typename T>
constexpr unsigned ImmutablePointerSetFactory<T>::AllocAlignment =
    (alignof(PtrSet) > alignof(PtrTy)) ? alignof(PtrSet) : alignof(PtrTy);

} // end swift namespace

#endif // SWIFT_BASIC_IMMUTABLEPOINTERSET_H
