//===- llvm/ADT/PointerSumType.h --------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_ADT_POINTERSUMTYPE_H
#define LLVM_ADT_POINTERSUMTYPE_H
#include "llvm/ADT/bit.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/Support/PointerLikeTypeTraits.h"
#include <cassert>
#include <cstdint>
#include <type_traits>
namespace llvm {
/// A compile time pair of an integer tag and the pointer-like type which it
/// indexes within a sum type. Also allows the user to specify a particular
/// traits class for pointer types with custom behavior such as over-aligned
/// allocation.
template <uintptr_t N, typename PointerArgT,
typename TraitsArgT = PointerLikeTypeTraits<PointerArgT>>
struct PointerSumTypeMember {
enum { Tag = N };
using PointerT = PointerArgT;
using TraitsT = TraitsArgT;
};
namespace detail {
template <typename TagT, typename... MemberTs> struct PointerSumTypeHelper;
} // end namespace detail
/// A sum type over pointer-like types.
///
/// This is a normal tagged union across pointer-like types that uses the low
/// bits of the pointers to store the tag.
///
/// Each member of the sum type is specified by passing a \c
/// PointerSumTypeMember specialization in the variadic member argument list.
/// This allows the user to control the particular tag value associated with
/// a particular type, use the same type for multiple different tags, and
/// customize the pointer-like traits used for a particular member. Note that
/// these *must* be specializations of \c PointerSumTypeMember, no other type
/// will suffice, even if it provides a compatible interface.
///
/// This type implements all of the comparison operators and even hash table
/// support by comparing the underlying storage of the pointer values. It
/// doesn't support delegating to particular members for comparisons.
///
/// It also default constructs to a zero tag with a null pointer, whatever that
/// would be. This means that the zero value for the tag type is significant
/// and may be desirable to set to a state that is particularly desirable to
/// default construct.
///
/// Having a supported zero-valued tag also enables getting the address of a
/// pointer stored with that tag provided it is stored in its natural bit
/// representation. This works because in the case of a zero-valued tag, the
/// pointer's value is directly stored into this object and we can expose the
/// address of that internal storage. This is especially useful when building an
/// `ArrayRef` of a single pointer stored in a sum type.
///
/// There is no support for constructing or accessing with a dynamic tag as
/// that would fundamentally violate the type safety provided by the sum type.
template <typename TagT, typename... MemberTs> class PointerSumType {
using HelperT = detail::PointerSumTypeHelper<TagT, MemberTs...>;
// We keep both the raw value and the min tag value's pointer in a union. When
// the minimum tag value is zero, this allows code below to cleanly expose the
// address of the zero-tag pointer instead of just the zero-tag pointer
// itself. This is especially useful when building `ArrayRef`s out of a single
// pointer. However, we have to carefully access the union due to the active
// member potentially changing. When we *store* a new value, we directly
// access the union to allow us to store using the obvious types. However,
// when we *read* a value, we copy the underlying storage out to avoid relying
// on one member or the other being active.
union StorageT {
// Ensure we get a null default constructed value. We don't use a member
// initializer because some compilers seem to not implement those correctly
// for a union.
StorageT() : Value(0) {}
uintptr_t Value;
typename HelperT::template Lookup<HelperT::MinTag>::PointerT MinTagPointer;
};
StorageT Storage;
public:
constexpr PointerSumType() = default;
/// A typed setter to a given tagged member of the sum type.
template <TagT N>
void set(typename HelperT::template Lookup<N>::PointerT Pointer) {
void *V = HelperT::template Lookup<N>::TraitsT::getAsVoidPointer(Pointer);
assert((reinterpret_cast<uintptr_t>(V) & HelperT::TagMask) == 0 &&
"Pointer is insufficiently aligned to store the discriminant!");
Storage.Value = reinterpret_cast<uintptr_t>(V) | N;
}
/// A typed constructor for a specific tagged member of the sum type.
template <TagT N>
static PointerSumType
create(typename HelperT::template Lookup<N>::PointerT Pointer) {
PointerSumType Result;
Result.set<N>(Pointer);
return Result;
}
/// Clear the value to null with the min tag type.
void clear() { set<HelperT::MinTag>(nullptr); }
TagT getTag() const {
return static_cast<TagT>(getOpaqueValue() & HelperT::TagMask);
}
template <TagT N> bool is() const { return N == getTag(); }
template <TagT N> typename HelperT::template Lookup<N>::PointerT get() const {
void *P = is<N>() ? getVoidPtr() : nullptr;
return HelperT::template Lookup<N>::TraitsT::getFromVoidPointer(P);
}
template <TagT N>
typename HelperT::template Lookup<N>::PointerT cast() const {
assert(is<N>() && "This instance has a different active member.");
return HelperT::template Lookup<N>::TraitsT::getFromVoidPointer(
getVoidPtr());
}
/// If the tag is zero and the pointer's value isn't changed when being
/// stored, get the address of the stored value type-punned to the zero-tag's
/// pointer type.
typename HelperT::template Lookup<HelperT::MinTag>::PointerT const *
getAddrOfZeroTagPointer() const {
return const_cast<PointerSumType *>(this)->getAddrOfZeroTagPointer();
}
/// If the tag is zero and the pointer's value isn't changed when being
/// stored, get the address of the stored value type-punned to the zero-tag's
/// pointer type.
typename HelperT::template Lookup<HelperT::MinTag>::PointerT *
getAddrOfZeroTagPointer() {
static_assert(HelperT::MinTag == 0, "Non-zero minimum tag value!");
assert(is<HelperT::MinTag>() && "The active tag is not zero!");
// Store the initial value of the pointer when read out of our storage.
auto InitialPtr = get<HelperT::MinTag>();
// Now update the active member of the union to be the actual pointer-typed
// member so that accessing it indirectly through the returned address is
// valid.
Storage.MinTagPointer = InitialPtr;
// Finally, validate that this was a no-op as expected by reading it back
// out using the same underlying-storage read as above.
assert(InitialPtr == get<HelperT::MinTag>() &&
"Switching to typed storage changed the pointer returned!");
// Now we can correctly return an address to typed storage.
return &Storage.MinTagPointer;
}
explicit operator bool() const {
return getOpaqueValue() & HelperT::PointerMask;
}
bool operator==(const PointerSumType &R) const {
return getOpaqueValue() == R.getOpaqueValue();
}
bool operator!=(const PointerSumType &R) const {
return getOpaqueValue() != R.getOpaqueValue();
}
bool operator<(const PointerSumType &R) const {
return getOpaqueValue() < R.getOpaqueValue();
}
bool operator>(const PointerSumType &R) const {
return getOpaqueValue() > R.getOpaqueValue();
}
bool operator<=(const PointerSumType &R) const {
return getOpaqueValue() <= R.getOpaqueValue();
}
bool operator>=(const PointerSumType &R) const {
return getOpaqueValue() >= R.getOpaqueValue();
}
uintptr_t getOpaqueValue() const {
// Read the underlying storage of the union, regardless of the active
// member.
return bit_cast<uintptr_t>(Storage);
}
protected:
void *getVoidPtr() const {
return reinterpret_cast<void *>(getOpaqueValue() & HelperT::PointerMask);
}
};
namespace detail {
/// A helper template for implementing \c PointerSumType. It provides fast
/// compile-time lookup of the member from a particular tag value, along with
/// useful constants and compile time checking infrastructure..
template <typename TagT, typename... MemberTs>
struct PointerSumTypeHelper : MemberTs... {
// First we use a trick to allow quickly looking up information about
// a particular member of the sum type. This works because we arranged to
// have this type derive from all of the member type templates. We can select
// the matching member for a tag using type deduction during overload
// resolution.
template <TagT N, typename PointerT, typename TraitsT>
static PointerSumTypeMember<N, PointerT, TraitsT>
LookupOverload(PointerSumTypeMember<N, PointerT, TraitsT> *);
template <TagT N> static void LookupOverload(...);
template <TagT N> struct Lookup {
// Compute a particular member type by resolving the lookup helper overload.
using MemberT = decltype(
LookupOverload<N>(static_cast<PointerSumTypeHelper *>(nullptr)));
/// The Nth member's pointer type.
using PointerT = typename MemberT::PointerT;
/// The Nth member's traits type.
using TraitsT = typename MemberT::TraitsT;
};
// Next we need to compute the number of bits available for the discriminant
// by taking the min of the bits available for each member. Much of this
// would be amazingly easier with good constexpr support.
template <uintptr_t V, uintptr_t... Vs>
struct Min : std::integral_constant<
uintptr_t, (V < Min<Vs...>::value ? V : Min<Vs...>::value)> {
};
template <uintptr_t V>
struct Min<V> : std::integral_constant<uintptr_t, V> {};
enum { NumTagBits = Min<MemberTs::TraitsT::NumLowBitsAvailable...>::value };
// Also compute the smallest discriminant and various masks for convenience.
constexpr static TagT MinTag =
static_cast<TagT>(Min<MemberTs::Tag...>::value);
enum : uint64_t {
PointerMask = static_cast<uint64_t>(-1) << NumTagBits,
TagMask = ~PointerMask
};
// Finally we need a recursive template to do static checks of each
// member.
template <typename MemberT, typename... InnerMemberTs>
struct Checker : Checker<InnerMemberTs...> {
static_assert(MemberT::Tag < (1 << NumTagBits),
"This discriminant value requires too many bits!");
};
template <typename MemberT> struct Checker<MemberT> : std::true_type {
static_assert(MemberT::Tag < (1 << NumTagBits),
"This discriminant value requires too many bits!");
};
static_assert(Checker<MemberTs...>::value,
"Each member must pass the checker.");
};
} // end namespace detail
// Teach DenseMap how to use PointerSumTypes as keys.
template <typename TagT, typename... MemberTs>
struct DenseMapInfo<PointerSumType<TagT, MemberTs...>> {
using SumType = PointerSumType<TagT, MemberTs...>;
using HelperT = detail::PointerSumTypeHelper<TagT, MemberTs...>;
enum { SomeTag = HelperT::MinTag };
using SomePointerT =
typename HelperT::template Lookup<HelperT::MinTag>::PointerT;
using SomePointerInfo = DenseMapInfo<SomePointerT>;
static inline SumType getEmptyKey() {
return SumType::template create<SomeTag>(SomePointerInfo::getEmptyKey());
}
static inline SumType getTombstoneKey() {
return SumType::template create<SomeTag>(
SomePointerInfo::getTombstoneKey());
}
static unsigned getHashValue(const SumType &Arg) {
uintptr_t OpaqueValue = Arg.getOpaqueValue();
return DenseMapInfo<uintptr_t>::getHashValue(OpaqueValue);
}
static bool isEqual(const SumType &LHS, const SumType &RHS) {
return LHS == RHS;
}
};
} // end namespace llvm
#endif // LLVM_ADT_POINTERSUMTYPE_H