Files
swift-mirror/unittests/Basic/PointerIntEnumTest.cpp
Michael Gottesman 1c5ffe6dea Change PointerIntEnum to a new better representation.
The big differences here are that:

1. We no longer use the 4096 trick.

2. Now we store all indices inline so no mallocing is required and the
value is trivially copyable. We allow for much larger indices to be
stored inline which makes having an unrepresentable index a much smaller
issue. For instance on a 32 bit platform, in NewProjection, we are able
to represent an index of up to (1 << 26) - 1, which should be more than
enough to handle any interesting case.

3. We can now have up to 7 ptr cases and many more index cases (with each extra
bit needed to represent the index cases lowering the representable range of
indices).

The whole data structure is much simpler and easier to understand as a
bonus. A high level description of the ADT is 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 constructable and destructable 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 NewProjectionKind : 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,
   };
2016-01-06 18:20:26 -08:00

240 lines
7.5 KiB
C++

#include "swift/Basic/PointerIntEnum.h"
#include "llvm/ADT/ArrayRef.h"
#include "gtest/gtest.h"
using namespace swift;
namespace {
enum class EnumTy : unsigned {
Ptr1 = 0,
Ptr2 = 1,
Ptr3 = 2,
FirstPointerKind = Ptr1,
LastPointerKind = Ptr3,
// Index Projection Kinds
FirstIndexKind = 7,
Index1 = PointerIntEnumIndexKindValue<0, EnumTy>::value,
Index2 = PointerIntEnumIndexKindValue<1, EnumTy>::value,
Index3 = PointerIntEnumIndexKindValue<2, EnumTy>::value,
Index4 = PointerIntEnumIndexKindValue<3, EnumTy>::value,
Index5 = PointerIntEnumIndexKindValue<4, EnumTy>::value,
LastIndexKind = Index5,
};
using PointerIntEnumTy =
PointerIntEnum<EnumTy, void *, 3, 4, llvm::PointerLikeTypeTraits<void *>>;
static_assert(std::is_trivially_copyable<PointerIntEnumTy>::value,
"PointerIntEnum type should be trivially copyable");
static constexpr uintptr_t InvalidStorage = uintptr_t(0) - 1;
} // end anonymous namespace
TEST(PointerIntEnumTest, DefaultConstructorYieldsInvalid) {
PointerIntEnumTy Enum;
EXPECT_FALSE(Enum.isValid());
EXPECT_TRUE(Enum.getStorage() == InvalidStorage);
}
TEST(PointerIntEnumTest, PointerConstructor) {
int *data = new int[1];
PointerIntEnumTy Enum(EnumTy::Ptr1, data);
EXPECT_TRUE(Enum.isValid());
EXPECT_EQ(*Enum.getKind(), EnumTy::Ptr1);
EXPECT_EQ(Enum.getPointer(), data);
// Make sure that the value is laid out correctly in memory.
uintptr_t Value = uintptr_t(data) | uintptr_t(EnumTy::Ptr1);
EXPECT_EQ(Enum.getStorage(), Value);
delete[] data;
}
TEST(PointerIntEnumTest, IndexConstructor) {
// First test a case that we can represent.
{
PointerIntEnumTy Enum(EnumTy::Index3, 0xBEEF);
EXPECT_TRUE(Enum.isValid());
EXPECT_EQ(*Enum.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum.getIndex(), uintptr_t(0xBEEF));
// Make sure that the value is laid out correctly in memory.
uintptr_t Value = (uintptr_t(0xBEEF) << 7) | uintptr_t(EnumTy::Index3);
EXPECT_EQ(Enum.getStorage(), Value);
}
// Then test the boundary from representable index to unrepresentable index.
uintptr_t MaxIndex = (uintptr_t(1) << (sizeof(uintptr_t) * CHAR_BIT - 7)) - 2;
{
PointerIntEnumTy Enum(EnumTy::Index3, MaxIndex + 1);
EXPECT_FALSE(Enum.isValid());
EXPECT_FALSE(Enum.getKind());
EXPECT_EQ(Enum.getStorage(), InvalidStorage);
}
{
PointerIntEnumTy Enum(EnumTy::Index4, MaxIndex);
EXPECT_TRUE(Enum.isValid());
EXPECT_EQ(*Enum.getKind(), EnumTy::Index4);
EXPECT_EQ(Enum.getIndex(), MaxIndex);
// Make sure that the value is laid out correctly in memory.
uintptr_t Value = (uintptr_t(MaxIndex) << 7) | uintptr_t(EnumTy::Index4);
EXPECT_EQ(Enum.getStorage(), Value);
}
}
TEST(PointerIntEnumTest, CopyConstructorAssignment) {
PointerIntEnumTy IntEnum(EnumTy::Index3, 0xBEEF);
uintptr_t IntEnumStorageValue =
(uintptr_t(0xBEEF) << 7) | uintptr_t(EnumTy::Index3);
int *data = new int[1];
PointerIntEnumTy PtrEnum(EnumTy::Ptr2, data);
uintptr_t PtrEnumStorageValue = uintptr_t(data) | uintptr_t(EnumTy::Ptr2);
PointerIntEnumTy Enum2(IntEnum);
PointerIntEnumTy Enum3 = IntEnum;
EXPECT_TRUE(Enum2.isValid());
EXPECT_EQ(*Enum2.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum2.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum2.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum2);
EXPECT_NE(PtrEnum, Enum2);
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum3.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum3.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum3);
EXPECT_NE(PtrEnum, Enum3);
Enum3 = PtrEnum;
PointerIntEnumTy Enum4(PtrEnum);
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Ptr2);
EXPECT_EQ(Enum3.getPointer(), data);
EXPECT_EQ(Enum3.getStorage(), PtrEnumStorageValue);
EXPECT_EQ(Enum3, PtrEnum);
EXPECT_NE(Enum3, IntEnum);
EXPECT_TRUE(Enum4.isValid());
EXPECT_EQ(*Enum4.getKind(), EnumTy::Ptr2);
EXPECT_EQ(Enum4.getPointer(), data);
EXPECT_EQ(Enum4.getStorage(), PtrEnumStorageValue);
EXPECT_EQ(Enum4, PtrEnum);
EXPECT_NE(Enum4, IntEnum);
// Round trip Enum3
Enum3 = IntEnum;
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum3.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum3.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum3);
EXPECT_NE(PtrEnum, Enum3);
delete[] data;
}
// We have a trivial move constructor, so we copy when we move.
TEST(PointerIntEnumTest, MoveConstructorAssignment) {
PointerIntEnumTy IntEnum(EnumTy::Index3, 0xBEEF);
uintptr_t IntEnumStorageValue =
(uintptr_t(0xBEEF) << 7) | uintptr_t(EnumTy::Index3);
int *data = new int[1];
PointerIntEnumTy PtrEnum(EnumTy::Ptr2, data);
uintptr_t PtrEnumStorageValue = uintptr_t(data) | uintptr_t(EnumTy::Ptr2);
PointerIntEnumTy Enum2(std::move(IntEnum));
PointerIntEnumTy Enum3 = std::move(IntEnum);
EXPECT_TRUE(Enum2.isValid());
EXPECT_EQ(*Enum2.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum2.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum2.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum2);
EXPECT_NE(PtrEnum, Enum2);
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum3.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum3.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum3);
EXPECT_NE(PtrEnum, Enum3);
Enum3 = std::move(PtrEnum);
PointerIntEnumTy Enum4(std::move(PtrEnum));
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Ptr2);
EXPECT_EQ(Enum3.getPointer(), data);
EXPECT_EQ(Enum3.getStorage(), PtrEnumStorageValue);
EXPECT_EQ(Enum3, PtrEnum);
EXPECT_NE(Enum3, IntEnum);
EXPECT_TRUE(Enum4.isValid());
EXPECT_EQ(*Enum4.getKind(), EnumTy::Ptr2);
EXPECT_EQ(Enum4.getPointer(), data);
EXPECT_EQ(Enum4.getStorage(), PtrEnumStorageValue);
EXPECT_EQ(Enum4, PtrEnum);
EXPECT_NE(Enum4, IntEnum);
// Round trip Enum3
Enum3 = std::move(IntEnum);
EXPECT_TRUE(Enum3.isValid());
EXPECT_EQ(*Enum3.getKind(), EnumTy::Index3);
EXPECT_EQ(Enum3.getIndex(), uintptr_t(0xBEEF));
EXPECT_EQ(Enum3.getStorage(), IntEnumStorageValue);
EXPECT_EQ(IntEnum, Enum3);
EXPECT_NE(PtrEnum, Enum3);
delete[] data;
}
TEST(PointerIntEnumTest, Comparisons) {
PointerIntEnumTy IndexCase1(EnumTy::Index1, 5);
// Make sure that enums with different cases but the same value always compare
// different.
PointerIntEnumTy IndexCase2(EnumTy::Index2, 5);
EXPECT_NE(IndexCase1, IndexCase2);
// Make sure that enums with the same case and the same value compare equal.
PointerIntEnumTy IndexCase3(EnumTy::Index1, 5);
EXPECT_EQ(IndexCase1, IndexCase3);
// Make sure that enums with the same case, but different values do not
// compare equal.
PointerIntEnumTy IndexCase4(EnumTy::Index1, 6);
EXPECT_NE(IndexCase1, IndexCase4);
int *data1 = new int[1];
int *data2 = new int[1];
PointerIntEnumTy PtrCase1(EnumTy::Ptr1, data1);
// Test that pointer enums with different cases but the same value compare
// different.
PointerIntEnumTy PtrCase2(EnumTy::Ptr2, data1);
EXPECT_NE(PtrCase1, PtrCase2);
// Test that pointer enums with the same case and data are equal.
PointerIntEnumTy PtrCase3(EnumTy::Ptr1, data1);
EXPECT_EQ(PtrCase1, PtrCase3);
// Test that pointer enums with the same case but different data are not
// equal.
PointerIntEnumTy PtrCase4(EnumTy::Ptr1, data2);
EXPECT_NE(PtrCase1, PtrCase4);
// Test that pointers and indices compare differently.
EXPECT_NE(IndexCase1, PtrCase1);
delete[] data2;
delete[] data1;
}