blob: e00d0134bec62a280594424708b667ece99e5a6b [file] [log] [blame]
//===--- PointerIntEnum.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
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_POINTERINTENUM_H
#define SWIFT_BASIC_POINTERINTENUM_H
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/PointerLikeTypeTraits.h"
#include <cassert>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <type_traits>
#include <utility>
namespace swift {
/// A tiny meta function to compute the log2 of a compile time constant.
///
/// *NOTE* This will be in an updated version of LLVM so this should be removed
/// at that point in time.
template <size_t N>
struct ConstantLog2
: std::integral_constant<size_t, ConstantLog2<N / 2>::value + 1> {};
template <> struct ConstantLog2<1> : std::integral_constant<size_t, 0> {};
/// A meta function for computing at compile time cleanly the value for an index
/// kind's value without using cpp macros.
template <unsigned Value, typename EnumTy>
struct PointerIntEnumIndexKindValue
: std::integral_constant<unsigned,
(Value << unsigned(ConstantLog2<
size_t(EnumTy::FirstIndexKind) + 1>::value)) |
unsigned(EnumTy::FirstIndexKind)> {};
/// A pointer sized ADT that is able to compactly represent a Swift like enum
/// that can contain both Integer and Pointer payloads. It attempts to optimize
/// for the case of being able to represent as many pointer cases as possible
/// while allowing for indices to be stored as well. Without any loss of
/// generality assume that T* is our stored pointer. Then this is done as
/// follows:
///
/// 1. A PointerIntEnum for which bits [0, (num_tagged_bits(T*)-1)] are not all
/// set to 1 represent an enum with a pointer case. This means that one can have
/// at most ((1 << num_tagged_bits(T*)) - 2) enum cases associated with
/// pointers.
///
/// 2. A PointerIntEnum for which bits [0, (num_tagged_bits(T*)-1)] are all set
/// is either an invalid PointerIntEnum or an index.
///
/// 3. A PointerIntEnum with all bits set is an invalid PointerIntEnum.
///
/// 4. A PointerIntEnum for which bits [0, (num_tagged_bits(T*)-1)] are all set
/// but for which the upper bits are not all set is an index enum. The case bits
/// for the index PointerIntEnum are stored in bits [num_tagged_bits(T*),
/// num_tagged_bits(T*) + num_index_case_bits]. Then the actual index is stored
/// in the remaining top bits. For the case in which this is used in swift
/// currently, we use 3 index bits meaning that on a 32 bit system we have 26
/// bits for representing indices meaning we can represent indices up to
/// 67_108_862. Any index larger than that will result in an invalid
/// PointerIntEnum. On 64 bit we have many more bits than that.
///
/// By using this representation, we can make PointerIntEnum a true value type
/// that is trivially constructible and destructible without needing to malloc
/// memory.
///
/// In order for all of this to work, the user of this needs to construct an
/// enum with the appropriate case structure that allows the data structure to
/// determine what cases are pointer and which are indices. For instance the one
/// used by Projection in swift is:
///
/// enum class ProjectionKind : unsigned {
/// // PointerProjectionKinds
/// Upcast = 0,
/// RefCast = 1,
/// BitwiseCast = 2,
/// FirstPointerKind = Upcast,
/// LastPointerKind = BitwiseCast,
///
///
/// // This needs to be set to ((1 << num_tagged_bits(T*)) - 1). It
/// // represents the first NonPointerKind.
/// FirstIndexKind = 7,
///
/// // Index Projection Kinds
/// Struct = PointerIntEnumIndexKindValue<0, EnumTy>::value,
/// Tuple = PointerIntEnumIndexKindValue<1, EnumTy>::value,
/// Index = PointerIntEnumIndexKindValue<2, EnumTy>::value,
/// Class = PointerIntEnumIndexKindValue<3, EnumTy>::value,
/// Enum = PointerIntEnumIndexKindValue<4, EnumTy>::value,
/// LastIndexKind = Enum,
/// };
///
template <typename EnumTy, typename PointerTy, unsigned NumPointerKindBits,
unsigned NumIndexKindBits,
typename PtrTraits = llvm::PointerLikeTypeTraits<PointerTy>>
class PointerIntEnum {
// Make sure that the enum fits our requirements.
static_assert(unsigned(EnumTy::FirstIndexKind) ==
((1U << NumPointerKindBits) - 1U),
"Invalid Enum");
static_assert(unsigned(EnumTy::FirstIndexKind) <=
unsigned(EnumTy::LastIndexKind),
"Invalid Enum");
static_assert(unsigned(EnumTy::FirstPointerKind) <=
unsigned(EnumTy::LastPointerKind),
"Invalid Enum");
static_assert(unsigned(EnumTy::LastPointerKind) <
unsigned(EnumTy::FirstIndexKind),
"Invalid Enum");
/// The offset in bits where an index would be stored.
static constexpr unsigned IndexShiftOffset =
NumIndexKindBits + NumPointerKindBits;
/// The number of bits in a PointerIntEnum that can be used to store indices.
static constexpr unsigned NumIndexBits =
sizeof(uintptr_t) * CHAR_BIT - IndexShiftOffset;
/// The maximum index that can be stored for an index PointerIntEnum case.
static constexpr uintptr_t MaxIndex = (uintptr_t(1) << NumIndexBits) - 2;
/// The bit representation of an Invalid PointerIntEnum's storage.
static constexpr uintptr_t InvalidStorage = uintptr_t(0) - 1;
/// The pointer sized type used for the actual storage.
uintptr_t Storage;
public:
PointerIntEnum() : Storage(InvalidStorage) {}
/// Initialize this PointerIntEnum with the kind \p Kind and the Pointer \p
/// Ptr.
PointerIntEnum(EnumTy Kind, uintptr_t NewIndex) {
// If we can not represent this index, make the PointerIntEnum invalid.
if (NewIndex > MaxIndex) {
Storage = InvalidStorage;
return;
}
Storage = uintptr_t(Kind) | (NewIndex << IndexShiftOffset);
}
/// Initialize this PointerIntEnum with the kind \p Kind and the Pointer \p
/// Ptr.
PointerIntEnum(EnumTy Kind, PointerTy Ptr) {
void *VoidPtr = PtrTraits::getAsVoidPointer(Ptr);
// Make sure the pointer is at least aligned to NumPointerKindBits.
assert((uintptr_t(VoidPtr) & ((1 << NumPointerKindBits) - 1)) == 0);
// Make sure that Kind is a PointerKind.
assert(unsigned(Kind) >= unsigned(EnumTy::FirstPointerKind));
assert(unsigned(Kind) <= unsigned(EnumTy::LastPointerKind));
Storage = uintptr_t(VoidPtr) | uintptr_t(Kind);
}
PointerIntEnum(PointerIntEnum &&P) = default;
PointerIntEnum(const PointerIntEnum &P) = default;
~PointerIntEnum() = default;
PointerIntEnum &operator=(const PointerIntEnum &P) = default;
PointerIntEnum &operator=(PointerIntEnum &&P) = default;
bool isValid() const { return Storage != InvalidStorage; }
bool operator==(const PointerIntEnum &Other) const {
// Just compare the raw storage.
return Other.Storage == Storage;
}
bool operator!=(const PointerIntEnum &Other) const {
return !(*this == Other);
}
/// \returns the kind of the enum if the enum is valid. Returns None if the
/// enum is invalid.
Optional<EnumTy> getKind() const {
if (!isValid())
return None;
// Check if the bottom pointer bits are all not set. If that is true then we
// know that we have a pointer kind.
unsigned PointerBits = Storage & uintptr_t(EnumTy::FirstIndexKind);
if (PointerBits != unsigned(EnumTy::FirstIndexKind)) {
return EnumTy(PointerBits);
}
// Otherwise, we have an index kind. Just mask off the actual index bits and
// return the kind.
unsigned Mask = (1 << IndexShiftOffset) - 1;
unsigned MaskedStorage = Storage & Mask;
return EnumTy(MaskedStorage);
}
/// \returns the index stored in the enum if the enum has an index
/// payload. Asserts if the PointerIntEnum is invalid or has a pointer
/// payload.
uintptr_t getIndex() const {
assert(isValid());
assert(unsigned(*getKind()) >= unsigned(EnumTy::FirstIndexKind));
return Storage >> IndexShiftOffset;
}
/// \returns the pointer stored in the enum if the enum has a pointer
/// payload. Asserts if the PointerIntEnum is invalid or has an index payload.
PointerTy getPointer() const {
assert(isValid());
assert(unsigned(*getKind()) <= unsigned(EnumTy::LastPointerKind));
uintptr_t Value = Storage & ~(uintptr_t(EnumTy::FirstIndexKind));
return PtrTraits::getFromVoidPointer((void *)(Value));
}
/// Return the raw storage of the type. Used for testing purposes.
uintptr_t getStorage() const { return Storage; }
};
} // end swift namespace
#endif // SWIFT_BASIC_POINTERINTENUM_H