//===- ExternalUnion.h - A union with an external discriminator -*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the ExternalUnion class, which allows clients to
// conveniently define unions of possibly non-trivial types whose
// discriminator will be provided externally.
//
// It's the client's responsibility to call the appropriate
// "special members" within its own special members.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_EXTERNALUNION_H
#define SWIFT_BASIC_EXTERNALUNION_H

#include "llvm/Support/ErrorHandling.h"
#include "swift/Basic/type_traits.h"
#include <cassert>
#include <cstdint>
#include <utility>

namespace swift {

namespace ExternalUnionImpl {

/// A helper class for finding the index of a type in a parameter pack.
/// 'indexOf<T, List...>::value' will either be the index or -1.
template <class T, class... Members>
struct indexOf;

template <class T>
struct indexOf<T> {
  constexpr static const int value = -1;
};

template <class T, class U, class... Members>
struct indexOf<T, U, Members...> {
private:
  constexpr static const int indexInTail = indexOf<T, Members...>::value;
public:
  constexpr static const int value =
    (std::is_same<T, U>::value ? 0 :
     indexInTail != -1 ? indexInTail + 1 : -1);
};

/// A helper class for deriving information and rule-of-five operations
/// for the storage of a union.
template <class... Members>
struct MembersHelper;

template <unsigned NumMembers>
struct OptimalKindTypeHelper;

} // end namespace ExternalUnionImpl

/// A class used to define the list of member types which need to be
/// stored in an ExternalUnion.  As a special case, you may use 'void'
/// to indicate that an empty state is required in the union.
template <class... Members>
struct ExternalUnionMembers {
  // (private to the implementation)
  using Info = ExternalUnionImpl::MembersHelper<Members...>;

  enum : bool {
    is_copy_constructible = Info::is_copy_constructible,
    is_nothrow_copy_constructible = Info::is_nothrow_copy_constructible,
    is_move_constructible = Info::is_move_constructible,
    is_nothrow_move_constructible = Info::is_nothrow_move_constructible,
    is_copy_assignable = Info::is_copy_assignable,
    is_nothrow_copy_assignable = Info::is_nothrow_copy_assignable,
    is_move_assignable = Info::is_move_assignable,
    is_nothrow_move_assignable = Info::is_nothrow_move_assignable,
  };

  /// The type of indices into the union member type list.
  enum Index : unsigned {};

  /// Return the index for the given type, asserting that it is a member
  /// of the list.
  template <class T>
  static constexpr Index indexOf() {
    static_assert(ExternalUnionImpl::indexOf<T, Members...>::value != -1,
                  "type not registered in union");
    return Index(ExternalUnionImpl::indexOf<T, Members...>::value);
  }

  /// Return the index for the given type or -1 if it is not a member
  /// of the list.  If it is not -1, it may be safely converted to an Index.
  template <class T>
  static constexpr int maybeIndexOf() {
    return ExternalUnionImpl::indexOf<T, Members...>::value;
  }

  template <class T>
  static constexpr bool contains() {
    return ExternalUnionImpl::indexOf<T, Members...>::value != -1;
  }
};

/// An external union that uses the member-list index as the user-facing
/// discriminator kind.
///
/// This type can be used directly, but it's generally better to use
/// ExternalUnion instead.  If nothing else, it's not a good idea to
/// cement the assumption that you won't have two cases that need the
/// same storage.
///
/// The external union itself is a trivial type, and it is the
/// responsibility of the client to call the "special member functions"
/// at the appropriate time.
template <class Members>
class BasicExternalUnion {

  /// The value storage.
  alignas(Members::Info::alignment) char Storage[Members::Info::size];

  template <class T>
  static constexpr int maybeIndexOfMember() {
    return Members::template maybeIndexOf<T>();
  }

public:
  enum : bool {
    union_is_trivially_copyable = Members::Info::is_trivially_copyable
  };

  using Index = typename Members::Index;

  /// Construct a union member in-place.
  template <class T, class... Args>
  T &emplaceWithoutIndex(Args &&... args) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");

    return *(::new ((void*) &Storage) T(std::forward<Args>(args)...));
  }

  /// Construct a union member in-place.
  template <class T, class... Args>
  T &emplace(Index index, Args &&... args) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");
    assert(index == Index(typeIndex) && "current kind is wrong for value");

    return *(::new ((void*) &Storage) T(std::forward<Args>(args)...));
  }

  /// Construct a union member in-place using list-initialization ({}).
  template <class T, class... Args>
  T &emplaceAggregateWithoutIndex(Args &&... args) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");

    return *(::new ((void*) &Storage) T{std::forward<Args>(args)...});
  }

  /// Construct a union member in-place using list-initialization ({}).
  template <class T, class... Args>
  T &emplaceAggregate(Index index, Args &&... args) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");
    assert(index == Index(typeIndex) && "current kind is wrong for value");

    return *(::new ((void*) &Storage) T{std::forward<Args>(args)...});
  }

  /// Return a reference to a union member.
  template <class T>
  T &getWithoutIndex() {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");

    return reinterpret_cast<T &>(Storage);
  }

  /// Return a reference to a union member.
  template <class T>
  const T &getWithoutIndex() const {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");

    return reinterpret_cast<const T &>(Storage);
  }

  /// Return a reference to a union member, asserting that the current
  /// kind matches the type being extracted.
  template <class T>
  T &get(Index index) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");
    assert(index == Index(typeIndex) && "current kind is wrong for access");

    return reinterpret_cast<T &>(Storage);
  }

  /// Return a reference to a union member, asserting that the current
  /// kind matches the type being extracted.
  template <class T>
  const T &get(Index index) const {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");
    assert(index == Index(typeIndex) && "current kind is wrong for access");

    return reinterpret_cast<const T &>(Storage);
  }

  /// Destruct the current union member.
  template <class T>
  void resetToEmptyWithoutIndex() {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");

    reinterpret_cast<T&>(Storage).T::~T();
  }

  /// Destroy the current union member.
  template <class T>
  void resetToEmpty(Index oldIndex, Index newIndex) {
    constexpr int typeIndex = maybeIndexOfMember<T>();
    static_assert(typeIndex != -1, "type not in union");
    constexpr int voidTypeIndex = maybeIndexOfMember<void>();
    static_assert(voidTypeIndex != -1, "union has not empty storage");
    assert(oldIndex == Index(typeIndex) && "current kind is wrong for value");
    assert(newIndex == Index(voidTypeIndex) && "new kind is not in union");

    reinterpret_cast<T&>(Storage).T::~T();
  }

  /// Copy-construct the union from another union.
  void copyConstruct(Index index, const BasicExternalUnion &other) {
    Members::Info::copyConstruct(Storage, unsigned(index), other.Storage);
  }

  /// Move-construct the union from another union.
  void moveConstruct(Index index, BasicExternalUnion &&other) {
    Members::Info::moveConstruct(Storage, unsigned(index), other.Storage);
  }

  /// Copy-assign the union from another union.
  void copyAssign(Index thisIndex, Index otherIndex,
                  const BasicExternalUnion &other) {
    if (this == &other) {
      // do nothing
    } else if (thisIndex == otherIndex) {
      Members::Info::copyAssignSame(unsigned(thisIndex),
                                    Storage, other.Storage);
    } else {
      destruct(thisIndex);
      copyConstruct(otherIndex, other);
    }
  }

  /// Move-assign the union from another union.
  void moveAssign(Index thisIndex, Index otherIndex,
                  BasicExternalUnion &&other) {
    assert(this != &other && "move-constructing value into itself?");

    if (thisIndex == otherIndex) {
      Members::Info::moveAssignSame(unsigned(thisIndex),
                                    Storage, other.Storage);
    } else {
      destruct(thisIndex);
      moveConstruct(otherIndex, std::move(other));
    }
  }

  /// Destroy the union from another union.
  void destruct(Index index) {
    Members::Info::destruct(unsigned(index), Storage);
  }
};

/// An external union whose membership is determined by a kind type
/// whose members are not necessarily 1-1 with the members of the union.
///
/// Clients must provide a function which translates the kind type
/// into an index into the union's members list.  The expected pattern
/// here is something like this:
///
///   using Members = ExternalUnionMembers<void, int, std::string>;
///   static Members::Index getIndexForKind(Kind kind) {
///     switch (kind) {
///     case Kind::Nothing: return Members::indexOf<void>();
///     case Kind::Happy: return Members::indexOf<int>();
///     case Kind::Sad: return Members::indexOf<int>();
///     case Kind::Funny: return Members::indexOf<std::string>();
///     case Kind::Angry: return Members::indexOf<std::string>();
///     }
///     llvm_unreachable("bad kind");
///   }
///   ExternalUnion<Kind, Members, getIndexForKind> Storage;
///
template <class Kind, class Members,
          typename Members::Index (&GetIndexForKind)(Kind)>
class ExternalUnion {
  BasicExternalUnion<Members> Union;

public:
  enum : bool {
    union_is_trivially_copyable = decltype(Union)::union_is_trivially_copyable
  };

  /// Construct a union member in-place.
  template <class T, class... Args>
  T &emplace(Kind kind, Args &&... args) {
#ifndef NDEBUG
    return Union.template emplace<T>(GetIndexForKind(kind),
                                     std::forward<Args>(args)...);
#else
    return Union.template emplaceWithoutIndex<T>(std::forward<Args>(args)...);
#endif
  }

  /// Construct a union member in-place using list-initialization ({}).
  template <class T, class... Args>
  T &emplaceAggregate(Kind kind, Args &&... args) {
#ifndef NDEBUG
    return Union.template emplaceAggregate<T>(GetIndexForKind(kind),
                                     std::forward<Args>(args)...);
#else
    return Union.template emplaceAggregateWithoutIndex<T>(
                                     std::forward<Args>(args)...);
#endif
  }

  /// Destroy the current member of the union and switch to a member
  /// that has no storage.
  template <class T>
  void resetToEmpty(Kind curKind, Kind newKind) {
#ifndef NDEBUG
    return Union.template resetToEmpty<T>(GetIndexForKind(curKind),
                                          GetIndexForKind(newKind));
#else
    return Union.template resetToEmptyWithoutIndex<T>();
#endif
  }

  /// Return a reference to a union member, asserting that the current
  /// kind is right.
  template <class T>
  T &get(Kind kind) {
#ifndef NDEBUG
    return Union.template get<T>(GetIndexForKind(kind));
#else
    return Union.template getWithoutIndex<T>();
#endif
  }

  /// Return a reference to a union member, asserting that the current
  /// kind is right.
  template <class T>
  const T &get(Kind kind) const {
#ifndef NDEBUG
    return Union.template get<T>(GetIndexForKind(kind));
#else
    return Union.template getWithoutIndex<T>();
#endif
  }

  /// Copy-construct the union from another union.
  void copyConstruct(Kind kind, const ExternalUnion &other) {
    Union.copyConstruct(GetIndexForKind(kind), other.Union);
  }

  /// Move-construct the union from another union.
  void moveConstruct(Kind kind, ExternalUnion &&other) {
    Union.moveConstruct(GetIndexForKind(kind), std::move(other.Union));
  }

  /// Copy-assign the union from another union.
  void copyAssign(Kind thisKind, Kind otherKind, const ExternalUnion &other) {
    Union.copyAssign(GetIndexForKind(thisKind), GetIndexForKind(otherKind),
                     other.Union);
  }

  /// Move-assign the union from another union.
  void moveAssign(Kind thisKind, Kind otherKind, ExternalUnion &&other) {
    Union.moveAssign(GetIndexForKind(thisKind), GetIndexForKind(otherKind),
                     std::move(other.Union));
  }

  /// Destroy the union from another union.
  void destruct(Kind kind) {
    Union.destruct(GetIndexForKind(kind));
  }
};

template <class KindHelper, class Members>
class SimpleExternalUnionBase
  : public ExternalUnion<typename KindHelper::Kind, Members,
              KindHelper::template coerceKindToIndex<typename Members::Index>> {
public:
  using Kind = typename KindHelper::Kind;

  template <class T>
  static constexpr Kind kindForMember() {
    return KindHelper::coerceIndexToKind(Members::template indexOf<T>());
  }

  template <class T>
  T *dyn_cast(Kind kind) {
    return (kind == kindForMember<T>()
              ? &this->template get<T>(kind) : nullptr);
  }

  template <class T>
  const T *dyn_cast(Kind kind) const {
    return (kind == kindForMember<T>()
              ? &this->template get<T>(kind) : nullptr);
  }
};

/// A particularly simple form of ExternalUnion suitable for unions where
/// the kind only exists to distinguish between cases of the union.
///
/// Recommended usage:
///   using Union = SimpleExternalUnion<void, int, std::string>;
///   Union::Kind Kind : 2 = Union::kindForMember<void>();
///   ...
///   Union Storage;
template <class... Members>
class SimpleExternalUnion
    : public SimpleExternalUnionBase<
        ExternalUnionImpl::OptimalKindTypeHelper<sizeof...(Members)>,
        ExternalUnionMembers<Members...> > {
};

namespace ExternalUnionImpl {

/// The MembersHelper base case.
template <>
struct MembersHelper<> {
  enum : bool {
    is_trivially_copyable = true,
    is_copy_constructible = true,
    is_nothrow_copy_constructible = true,
    is_move_constructible = true,
    is_nothrow_move_constructible = true,
    is_copy_assignable = true,
    is_nothrow_copy_assignable = true,
    is_move_assignable = true,
    is_nothrow_move_assignable = true,
  };

  enum : size_t {
    size = 1,
    alignment = 1
  };

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyConstruct(void *self, int index, const void *other) {
    assert(false && "bad index");
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveConstruct(void *self, int index, void *other) {
    assert(false && "bad index");
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyAssignSame(int index, void *self, const void *other) {
    assert(false && "bad index");
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveAssignSame(int index, void *self, void *other) {
    assert(false && "bad index");
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void destruct(int index, void *self) {
    assert(false && "bad index");
  }
};

template <class T>
struct UnionMemberInfo;

/// The  helper class for defining special members.
template <class H, class... T>
struct MembersHelper<H, T...> {
private:
  using Member = UnionMemberInfo<H>;
  using Others = MembersHelper<T...>;

public:
  enum : bool {
    is_trivially_copyable =
      Member::is_trivially_copyable &&
      Others::is_trivially_copyable,
    is_copy_constructible =
      Member::is_copy_constructible &&
      Others::is_copy_constructible,
    is_nothrow_copy_constructible =
      Member::is_nothrow_copy_constructible &&
      Others::is_nothrow_copy_constructible,
    is_move_constructible =
      Member::is_move_constructible &&
      Others::is_move_constructible,
    is_nothrow_move_constructible =
      Member::is_nothrow_move_constructible &&
      Others::is_nothrow_move_constructible,
    is_copy_assignable =
      Member::is_copy_assignable &&
      Others::is_copy_assignable,
    is_nothrow_copy_assignable =
      Member::is_nothrow_copy_assignable &&
      Others::is_nothrow_copy_assignable,
    is_move_assignable = 
      Member::is_move_assignable &&
      Others::is_move_assignable,
    is_nothrow_move_assignable =
      Member::is_nothrow_move_assignable &&
      Others::is_nothrow_move_assignable,
  };

  enum : size_t {
    size = Member::size > Others::size
         ? Member::size : Others::size,
    alignment = Member::alignment > Others::alignment
              ? Member::alignment : Others::alignment
  };

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyConstruct(void *self, unsigned index, const void *other) {
    if (index == 0) {
      Member::copyConstruct(self, other);
    } else {
      Others::copyConstruct(self, index - 1, other);
    }
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveConstruct(void *self, unsigned index, void *other) {
    if (index == 0) {
      Member::moveConstruct(self, other);
    } else {
      Others::moveConstruct(self, index - 1, other);
    }
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyAssignSame(unsigned index, void *self, const void *other) {
    if (index == 0) {
      Member::copyAssignSame(self, other);
    } else {
      Others::copyAssignSame(index - 1, self, other);
    }
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveAssignSame(unsigned index, void *self, void *other) {
    if (index == 0) {
      Member::moveAssignSame(self, other);
    } else {
      Others::moveAssignSame(index - 1, self, other);
    }
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void destruct(int index, void *self) {
    if (index == 0) {
      Member::destruct(self);
    } else {
      Others::destruct(index - 1, self);
    }
  }
};

/// The standard implementation of UnionMemberInfo.
template <class T>
struct UnionMemberInfo {
  enum : bool {
    is_trivially_copyable = IsTriviallyCopyable<T>::value,
    is_copy_constructible = std::is_copy_constructible<T>::value,
    is_nothrow_copy_constructible =
      std::is_nothrow_copy_constructible<T>::value,
    is_move_constructible = std::is_move_constructible<T>::value,
    is_nothrow_move_constructible =
      std::is_nothrow_move_constructible<T>::value,
    is_copy_assignable = std::is_copy_assignable<T>::value,
    is_nothrow_copy_assignable =
      std::is_nothrow_copy_assignable<T>::value,
    is_move_assignable = std::is_move_assignable<T>::value,
    is_nothrow_move_assignable =
      std::is_nothrow_move_assignable<T>::value,
  };

  enum : size_t {
    size = sizeof(T),
    alignment = alignof(T)
  };

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyConstruct(void *self, const void *other) {
    ::new (self) T(*static_cast<const T *>(other));
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveConstruct(void *self, void *other) {
    ::new (self) T(std::move(*static_cast<T *>(other)));
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyAssignSame(void *self, const void *other) {
    *static_cast<T*>(self) = *static_cast<const T *>(other);
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveAssignSame(void *self, void *other) {
    *static_cast<T*>(self) = std::move(*static_cast<T *>(other));
  }

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void destruct(void *self) {
    static_cast<T*>(self)->T::~T();
  }
};

/// An explicit specialization of UnionMemberInfo for 'void', which
/// represents the empty state.
template <>
struct UnionMemberInfo<void> {
  enum : bool {
    is_trivially_copyable = true,
    is_copy_constructible = true,
    is_nothrow_copy_constructible = true,
    is_move_constructible = true,
    is_nothrow_move_constructible = true,
    is_copy_assignable = true,
    is_nothrow_copy_assignable = true,
    is_move_assignable = true,
    is_nothrow_move_assignable = true,
  };

  enum : size_t {
    size = 0,
    alignment = 1
  };

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyConstruct(void *self, const void *other) {}

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveConstruct(void *self, void *other) {}

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void copyAssignSame(void *self, const void *other) {}

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void moveAssignSame(void *self, void *other) {}

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  static void destruct(void *self) {}
};

template <unsigned NumMembers,
          bool FitsInUInt8 = (NumMembers < (1 << sizeof(uint8_t))),
          bool FitsInUInt16 = (NumMembers < (1 << sizeof(uint16_t)))>
struct OptimalUnderlyingType;

template <unsigned NumMembers>
struct OptimalUnderlyingType<NumMembers, true, true> {
  using type = uint8_t;
};

template <unsigned NumMembers>
struct OptimalUnderlyingType<NumMembers, false, true> {
  using type = uint16_t;
};

template <unsigned NumMembers>
struct OptimalUnderlyingType<NumMembers, false, false> {
  using type = unsigned;
};

template <unsigned NumMembers>
struct OptimalKindTypeHelper {
private:
  using UnderlyingType = typename OptimalUnderlyingType<NumMembers>::type;
public:
  enum Kind : UnderlyingType {};

  template <class IndexType>
  static constexpr IndexType coerceKindToIndex(Kind kind) {
    return IndexType(UnderlyingType(kind));
  }

  template <class IndexType>
  static constexpr Kind coerceIndexToKind(IndexType index) {
    return Kind(UnderlyingType(index));
  }
};

} // end namespace ExternalUnionImpl
} // end namespace swift

#endif // SWIFT_BASIC_CLUSTEREDBITVECTOR_H
