[llvm][ADT] Add variable-width tag encoding to PointerUnion (#188167)
PointerUnion stores a fixed-width `ceil(log2(N))`-bit tag in the low bits of the pointer. This works only when every member type provides at least that many low bits — if the least-aligned type doesn't, compilation fails, even though the higher-aligned types may have plenty of spare bits going to waste. Introduce a variable-length escape-encoded tag that exploits the extra low bits of higher-aligned types, analogous to UTF-8: types are grouped into tiers by NumLowBitsAvailable; each non-final tier reserves one code as an escape prefix, and the next tier extends the tag into the newly available bits. This allows PointerUnion to hold more type variants than a fixed-width tag permits. The fixed-width path is used when the minimum alignment already provides enough bits (the common case); the variable-width path activates only when it doesn't, and requires types to be listed in non-decreasing NumLowBitsAvailable order. I need this for https://github.com/llvm/llvm-project/pull/186923 which requires a 6-member PointerUnion in MLIR TypeRange/ValueRange. On 32-bit systems, some members only provide 2 low bits, insufficient for a 3-bit fixed-width tag.
This commit is contained in:
parent
d362bd7b9d
commit
6e916d0598
@ -16,6 +16,12 @@ void DerivedWithVirtual::func() {}
|
||||
struct alignas(8) Z {};
|
||||
struct Derived : public Z {};
|
||||
|
||||
// Types for variable-width tag encoding test.
|
||||
// 3 x alignof(4) + 2 x alignof(8) requires escape-coded tags because
|
||||
// ceil(log2(5)) = 3 > min(NumLowBitsAvailable) = 2.
|
||||
template <int I> struct alignas(4) Align4 {};
|
||||
template <int I> struct alignas(8) Align8 {};
|
||||
|
||||
int main() {
|
||||
int a = 5;
|
||||
float f = 4.0;
|
||||
@ -50,4 +56,16 @@ int main() {
|
||||
llvm::PointerUnion<Local *, float *> local_float(&local);
|
||||
|
||||
puts("Break here");
|
||||
|
||||
// Variable-width tag encoding: formatter should fall back to void*.
|
||||
Align4<0> a4_0;
|
||||
Align8<0> a8_0;
|
||||
llvm::PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *,
|
||||
Align8<1> *>
|
||||
varwidth(&a4_0);
|
||||
llvm::PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *,
|
||||
Align8<1> *>
|
||||
varwidth_tier1(&a8_0);
|
||||
|
||||
puts("Break here");
|
||||
}
|
||||
|
||||
@ -40,6 +40,13 @@ continue
|
||||
p &local
|
||||
v -T local_float
|
||||
|
||||
continue
|
||||
|
||||
p &a4_0
|
||||
p &a8_0
|
||||
v -T varwidth
|
||||
v -T varwidth_tier1
|
||||
|
||||
#--- checks
|
||||
# CHECK: (lldb) p &f
|
||||
# CHECK-NEXT: (float *) [[PTR_F:0x[0-9a-zA-Z]+]]
|
||||
@ -122,3 +129,21 @@ v -T local_float
|
||||
# CHECK-NEXT: (llvm::PointerUnion<Local *, float *>) local_float = {
|
||||
# CHECK-NEXT: (Local *) Pointer = [[PTR_LOCAL]]
|
||||
# CHECK-NEXT: }
|
||||
|
||||
# CHECK: (lldb) p &a4_0
|
||||
# CHECK-NEXT: (Align4<0> *) [[PTR_A4:0x[0-9a-zA-Z]+]]
|
||||
|
||||
# CHECK: (lldb) p &a8_0
|
||||
# CHECK-NEXT: (Align8<0> *) [[PTR_A8:0x[0-9a-zA-Z]+]]
|
||||
|
||||
# Variable-width tag encoding: formatter falls back to void* with
|
||||
# tag bits stripped, since fixed-width decoding doesn't apply.
|
||||
# CHECK: (lldb) v -T varwidth
|
||||
# CHECK-NEXT: (llvm::PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align8<1> *>) varwidth = {
|
||||
# CHECK-NEXT: (void *) Pointer = [[PTR_A4]]
|
||||
# CHECK-NEXT: }
|
||||
|
||||
# CHECK: (lldb) v -T varwidth_tier1
|
||||
# CHECK-NEXT: (llvm::PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align8<1> *>) varwidth_tier1 = {
|
||||
# CHECK-NEXT: (void *) Pointer = [[PTR_A8]]
|
||||
# CHECK-NEXT: }
|
||||
|
||||
@ -22,10 +22,16 @@ using namespace llvm;
|
||||
namespace llvm {
|
||||
namespace {
|
||||
|
||||
// Aligned slot types with controlled NumLowBitsAvailable (3 low bits).
|
||||
// Aligned slot types with controlled NumLowBitsAvailable.
|
||||
template <int N> struct alignas(4) A4 {
|
||||
int data;
|
||||
}; // 2 bits.
|
||||
template <int N> struct alignas(8) A8 {
|
||||
int data;
|
||||
};
|
||||
}; // 3 bits.
|
||||
template <int N> struct alignas(32) A32 {
|
||||
int data;
|
||||
}; // 5 bits.
|
||||
|
||||
template <typename T> T &getSlot() {
|
||||
static T S{};
|
||||
@ -63,12 +69,69 @@ TypeList<A8<Is>...> makeA8TypesImpl(std::index_sequence<Is...>);
|
||||
template <size_t N>
|
||||
using A8Types = decltype(makeA8TypesImpl(std::make_index_sequence<N>{}));
|
||||
|
||||
// Splits N types into two tiers: N0 types in tier 0 (A4, 2-bit) and
|
||||
// N1 types in tier 1 (A32, 5-bit). Tier 0 gets up to 3 types (the max
|
||||
// for 2 low bits minus one escape code).
|
||||
template <size_t N> struct TwoTierSplit {
|
||||
static constexpr size_t N0 = N < 4 ? N - 1 : 3;
|
||||
static constexpr size_t N1 = N - N0;
|
||||
};
|
||||
|
||||
// Splits N types into three tiers: N0 types in tier 0 (A4, 2-bit),
|
||||
// N1 = 1 type in tier 1 (A8, 3-bit), and N2 types in tier 2 (A32, 5-bit).
|
||||
// Tier 0 gets up to 3 types; tier 1 always gets exactly 1.
|
||||
template <size_t N> struct ThreeTierSplit {
|
||||
static constexpr size_t N0 = N < 5 ? N - 2 : 3;
|
||||
static constexpr size_t N1 = 1;
|
||||
static constexpr size_t N2 = N - N0 - N1;
|
||||
};
|
||||
|
||||
// A4A32Types<N> = TypeList<A4<0>, ..., A4<N0-1>, A32<0>, ..., A32<N1-1>>.
|
||||
// Used to generate randomized arrays with a mix of A4 and A32 types.
|
||||
template <size_t... I0, size_t... I1>
|
||||
TypeList<A4<I0>..., A32<I1>...> makeA4A32TypesImpl(std::index_sequence<I0...>,
|
||||
std::index_sequence<I1...>);
|
||||
template <size_t N>
|
||||
using A4A32Types = decltype(makeA4A32TypesImpl(
|
||||
std::make_index_sequence<TwoTierSplit<N>::N0>{},
|
||||
std::make_index_sequence<TwoTierSplit<N>::N1>{}));
|
||||
|
||||
// A4A8A32Types<N> = TypeList<A4<0>, ..., A8<0>, ..., A32<0>, ...>.
|
||||
// Used to generate randomized arrays with a mix of A4, A8, and A32 types.
|
||||
template <size_t... I0, size_t... I1, size_t... I2>
|
||||
TypeList<A4<I0>..., A8<I1>..., A32<I2>...>
|
||||
makeA4A8A32TypesImpl(std::index_sequence<I0...>, std::index_sequence<I1...>,
|
||||
std::index_sequence<I2...>);
|
||||
template <size_t N>
|
||||
using A4A8A32Types = decltype(makeA4A8A32TypesImpl(
|
||||
std::make_index_sequence<ThreeTierSplit<N>::N0>{},
|
||||
std::make_index_sequence<ThreeTierSplit<N>::N1>{},
|
||||
std::make_index_sequence<ThreeTierSplit<N>::N2>{}));
|
||||
|
||||
// Union type aliases.
|
||||
template <size_t... Is>
|
||||
auto makePtrUnion(std::index_sequence<Is...>) -> PointerUnion<A8<Is> *...>;
|
||||
|
||||
template <size_t N>
|
||||
using PtrUnion = decltype(makePtrUnion(std::make_index_sequence<N>{}));
|
||||
|
||||
template <size_t... I0, size_t... I1>
|
||||
auto makePtrUnion2T(std::index_sequence<I0...>, std::index_sequence<I1...>)
|
||||
-> PointerUnion<A4<I0> *..., A32<I1> *...>;
|
||||
template <size_t N>
|
||||
using PtrUnion2T =
|
||||
decltype(makePtrUnion2T(std::make_index_sequence<TwoTierSplit<N>::N0>{},
|
||||
std::make_index_sequence<TwoTierSplit<N>::N1>{}));
|
||||
|
||||
template <size_t... I0, size_t... I1, size_t... I2>
|
||||
auto makePtrUnion3T(std::index_sequence<I0...>, std::index_sequence<I1...>,
|
||||
std::index_sequence<I2...>)
|
||||
-> PointerUnion<A4<I0> *..., A8<I1> *..., A32<I2> *...>;
|
||||
template <size_t N>
|
||||
using PtrUnion3T =
|
||||
decltype(makePtrUnion3T(std::make_index_sequence<ThreeTierSplit<N>::N0>{},
|
||||
std::make_index_sequence<ThreeTierSplit<N>::N1>{},
|
||||
std::make_index_sequence<ThreeTierSplit<N>::N2>{}));
|
||||
|
||||
// Isa: random type mix, uniform distribution (~1/N hit rate for each type).
|
||||
template <typename UnionT, typename QueryT, typename TL>
|
||||
static void BM_Isa(benchmark::State &State) {
|
||||
@ -104,7 +167,9 @@ static void BM_IsNull(benchmark::State &State) {
|
||||
} // namespace
|
||||
} // namespace llvm
|
||||
|
||||
// Registration -- N = 2, 4, 8.
|
||||
// Registration -- N = 2, 4, 8. PtrUnion3T uses N = 3, 4, 8.
|
||||
|
||||
// Isa: PtrUnion (fixed-width tag).
|
||||
BENCHMARK((BM_Isa<PtrUnion<2>, A8<0>, A8Types<2>>))->Name("Isa/PU/2/First");
|
||||
BENCHMARK((BM_Isa<PtrUnion<2>, A8<1>, A8Types<2>>))->Name("Isa/PU/2/Last");
|
||||
|
||||
@ -114,8 +179,60 @@ BENCHMARK((BM_Isa<PtrUnion<4>, A8<3>, A8Types<4>>))->Name("Isa/PU/4/Last");
|
||||
BENCHMARK((BM_Isa<PtrUnion<8>, A8<0>, A8Types<8>>))->Name("Isa/PU/8/First");
|
||||
BENCHMARK((BM_Isa<PtrUnion<8>, A8<7>, A8Types<8>>))->Name("Isa/PU/8/Last");
|
||||
|
||||
// Isa: PtrUnion2T (variable-width, 2 alignment tiers).
|
||||
// Note: PtrUnion2T<N> uses variable-width encoding only when N > 3 (when
|
||||
// fixed-width tags don't fit in 2 bits). PtrUnion2T<2> is still fixed-width.
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<2>, A4<0>, A4A32Types<2>>))
|
||||
->Name("Isa/PU2T/2/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<2>, A32<0>, A4A32Types<2>>))
|
||||
->Name("Isa/PU2T/2/Tier1");
|
||||
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<4>, A4<0>, A4A32Types<4>>))
|
||||
->Name("Isa/PU2T/4/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<4>, A32<0>, A4A32Types<4>>))
|
||||
->Name("Isa/PU2T/4/Tier1");
|
||||
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<8>, A4<0>, A4A32Types<8>>))
|
||||
->Name("Isa/PU2T/8/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion2T<8>, A32<4>, A4A32Types<8>>))
|
||||
->Name("Isa/PU2T/8/Tier1");
|
||||
|
||||
// Isa: PtrUnion3T (variable-width, 3 alignment tiers).
|
||||
// Note: PtrUnion3T<N> uses variable-width encoding only when N > 4 (when
|
||||
// fixed-width tags don't fit in 2 bits). PtrUnion3T<3> and <4> are
|
||||
// still fixed-width.
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<3>, A4<0>, A4A8A32Types<3>>))
|
||||
->Name("Isa/PU3T/3/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<3>, A8<0>, A4A8A32Types<3>>))
|
||||
->Name("Isa/PU3T/3/Tier1");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<3>, A32<0>, A4A8A32Types<3>>))
|
||||
->Name("Isa/PU3T/3/Tier2");
|
||||
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<4>, A4<0>, A4A8A32Types<4>>))
|
||||
->Name("Isa/PU3T/4/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<4>, A8<0>, A4A8A32Types<4>>))
|
||||
->Name("Isa/PU3T/4/Tier1");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<4>, A32<0>, A4A8A32Types<4>>))
|
||||
->Name("Isa/PU3T/4/Tier2");
|
||||
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<8>, A4<0>, A4A8A32Types<8>>))
|
||||
->Name("Isa/PU3T/8/Tier0");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<8>, A8<0>, A4A8A32Types<8>>))
|
||||
->Name("Isa/PU3T/8/Tier1");
|
||||
BENCHMARK((BM_Isa<PtrUnion3T<8>, A32<3>, A4A8A32Types<8>>))
|
||||
->Name("Isa/PU3T/8/Tier2");
|
||||
|
||||
// IsNull: all suites.
|
||||
BENCHMARK((BM_IsNull<PtrUnion<2>, A8Types<2>>))->Name("IsNull/PU/2");
|
||||
BENCHMARK((BM_IsNull<PtrUnion<4>, A8Types<4>>))->Name("IsNull/PU/4");
|
||||
BENCHMARK((BM_IsNull<PtrUnion<8>, A8Types<8>>))->Name("IsNull/PU/8");
|
||||
|
||||
BENCHMARK((BM_IsNull<PtrUnion2T<2>, A4A32Types<2>>))->Name("IsNull/PU2T/2");
|
||||
BENCHMARK((BM_IsNull<PtrUnion2T<4>, A4A32Types<4>>))->Name("IsNull/PU2T/4");
|
||||
BENCHMARK((BM_IsNull<PtrUnion2T<8>, A4A32Types<8>>))->Name("IsNull/PU2T/8");
|
||||
|
||||
BENCHMARK((BM_IsNull<PtrUnion3T<3>, A4A8A32Types<3>>))->Name("IsNull/PU3T/3");
|
||||
BENCHMARK((BM_IsNull<PtrUnion3T<4>, A4A8A32Types<4>>))->Name("IsNull/PU3T/4");
|
||||
BENCHMARK((BM_IsNull<PtrUnion3T<8>, A4A8A32Types<8>>))->Name("IsNull/PU3T/8");
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//===- llvm/ADT/PointerUnion.h - Discriminated Union of 2 Ptrs --*- C++ -*-===//
|
||||
//===- llvm/ADT/PointerUnion.h - Pointer Type Union -------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
@ -21,9 +21,11 @@
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/PointerLikeTypeTraits.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace llvm {
|
||||
|
||||
@ -40,6 +42,90 @@ template <typename... Ts> constexpr int lowBitsAvailable() {
|
||||
{static_cast<int>(PointerLikeTypeTraits<Ts>::NumLowBitsAvailable)...});
|
||||
}
|
||||
|
||||
/// True if all types have enough low bits for a fixed-width tag.
|
||||
template <typename... PTs> constexpr bool useFixedWidthTags() {
|
||||
return lowBitsAvailable<PTs...>() >= bitsRequired(sizeof...(PTs));
|
||||
}
|
||||
|
||||
/// True if types are in non-decreasing NumLowBitsAvailable order.
|
||||
// TODO: Switch to llvm::is_sorted when it becomes constexpr.
|
||||
template <typename... PTs> constexpr bool typesInNonDecreasingBitOrder() {
|
||||
int Bits[] = {PointerLikeTypeTraits<PTs>::NumLowBitsAvailable...};
|
||||
for (size_t I = 1; I < sizeof...(PTs); ++I)
|
||||
if (Bits[I] < Bits[I - 1])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Tag descriptor for one type in the union.
|
||||
struct TagEntry {
|
||||
uintptr_t Value; // Bit pattern stored in the low bits.
|
||||
uintptr_t Mask; // Mask covering all tag bits for this entry.
|
||||
};
|
||||
|
||||
/// Compute fixed-width tag table (all types have enough bits for the tag).
|
||||
/// For example, with 4 types and 3 available bits, the tag is 2 bits wide
|
||||
/// (values 0-3) and each entry has the same mask of 0x3.
|
||||
template <typename... PTs>
|
||||
constexpr std::array<TagEntry, sizeof...(PTs)> computeFixedTags() {
|
||||
constexpr size_t N = sizeof...(PTs);
|
||||
constexpr uintptr_t TagMask = (uintptr_t(1) << bitsRequired(N)) - 1;
|
||||
std::array<TagEntry, N> Result = {};
|
||||
for (size_t I = 0; I < N; ++I) {
|
||||
Result[I].Value = uintptr_t(I);
|
||||
Result[I].Mask = TagMask;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// Compute variable-width tag table, or return std::nullopt if the types
|
||||
/// don't fit. Types must be in non-decreasing NumLowBitsAvailable order.
|
||||
/// Groups types by available bits into tiers; each non-final tier reserves
|
||||
/// its highest code as an escape prefix.
|
||||
///
|
||||
/// Example with 3 tiers (2-bit, 3-bit, 5-bit types):
|
||||
/// Tier 0 (2 bits): codes 0b00, 0b01, 0b10; escape = 0b11
|
||||
/// Tier 1 (3 bits): codes 0b011, escape = 0b111
|
||||
/// Tier 2 (5 bits): codes 0b00111, 0b01111, 0b10111, 0b11111
|
||||
template <typename... PTs>
|
||||
constexpr std::optional<std::array<TagEntry, sizeof...(PTs)>>
|
||||
computeExtendedTags() {
|
||||
constexpr size_t N = sizeof...(PTs);
|
||||
std::array<TagEntry, N> Result = {};
|
||||
int Bits[] = {PointerLikeTypeTraits<PTs>::NumLowBitsAvailable...};
|
||||
uintptr_t EscapePrefix = 0;
|
||||
int PrevBits = 0;
|
||||
size_t I = 0;
|
||||
// Walk tiers (groups of types with the same NumLowBitsAvailable). For each
|
||||
// tier, assign tag values using the new bits introduced by this tier,
|
||||
// prefixed by the accumulated escape codes from previous tiers. Non-final
|
||||
// tiers reserve their highest code as an escape to the next tier.
|
||||
while (I < N) {
|
||||
int TierBits = Bits[I];
|
||||
if (TierBits < PrevBits)
|
||||
return std::nullopt;
|
||||
int NewBits = TierBits - PrevBits;
|
||||
size_t TierEnd = I;
|
||||
while (TierEnd < N && Bits[TierEnd] == TierBits)
|
||||
++TierEnd;
|
||||
bool IsLastTier = (TierEnd == N);
|
||||
size_t TypesInTier = TierEnd - I;
|
||||
size_t Capacity =
|
||||
IsLastTier ? (size_t(1) << NewBits) : ((size_t(1) << NewBits) - 1);
|
||||
if (TypesInTier > Capacity)
|
||||
return std::nullopt;
|
||||
for (size_t J = 0; J < TypesInTier; ++J) {
|
||||
Result[I + J].Value = EscapePrefix | (uintptr_t(J) << PrevBits);
|
||||
Result[I + J].Mask = (uintptr_t(1) << TierBits) - 1;
|
||||
}
|
||||
uintptr_t EscapeCode = (uintptr_t(1) << NewBits) - 1;
|
||||
EscapePrefix |= EscapeCode << PrevBits;
|
||||
PrevBits = TierBits;
|
||||
I = TierEnd;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// CRTP base that generates non-template constructors and assignment operators
|
||||
/// for each type in the union. Non-template constructors allow implicit
|
||||
/// conversions (derived-to-base, non-const-to-const).
|
||||
@ -82,6 +168,13 @@ public:
|
||||
/// This implementation is extremely efficient in space due to leveraging the
|
||||
/// low bits of the pointer, while exposing a natural and type-safe API.
|
||||
///
|
||||
/// When all types have enough alignment for a fixed-width tag,
|
||||
/// the tag is placed in the high end of the available low bits, leaving spare
|
||||
/// low bits for nesting in PointerIntPair or SmallPtrSet. When types have
|
||||
/// heterogeneous alignment, a variable-length escape-encoded tag
|
||||
/// is used; in that case, types must be listed in non-decreasing
|
||||
/// NumLowBitsAvailable order.
|
||||
///
|
||||
/// Common use patterns would be something like this:
|
||||
/// PointerUnion<int*, float*> P;
|
||||
/// P = (int*)0;
|
||||
@ -97,6 +190,7 @@ template <typename... PTs>
|
||||
class PointerUnion
|
||||
: public pointer_union_detail::PointerUnionMembers<PointerUnion<PTs...>, 0,
|
||||
PTs...> {
|
||||
static_assert(sizeof...(PTs) > 0, "PointerUnion must have at least one type");
|
||||
static_assert(TypesAreDistinct<PTs...>::value,
|
||||
"PointerUnion alternative types cannot be repeated");
|
||||
|
||||
@ -111,6 +205,11 @@ class PointerUnion
|
||||
// These are constexpr functions rather than static constexpr data members
|
||||
// so that alignof() on potentially incomplete types is not evaluated at
|
||||
// class-definition time.
|
||||
|
||||
static constexpr bool useFixedWidthTags() {
|
||||
return pointer_union_detail::useFixedWidthTags<PTs...>();
|
||||
}
|
||||
|
||||
static constexpr int minLowBitsAvailable() {
|
||||
return pointer_union_detail::lowBitsAvailable<PTs...>();
|
||||
}
|
||||
@ -119,22 +218,55 @@ class PointerUnion
|
||||
return pointer_union_detail::bitsRequired(sizeof...(PTs));
|
||||
}
|
||||
|
||||
/// The tag is shifted to the high end of the available low bits so that
|
||||
/// the lowest bits remain free for nesting in PointerIntPair or SmallPtrSet.
|
||||
static constexpr int tagShift() { return minLowBitsAvailable() - tagBits(); }
|
||||
/// When using fixed-width tags, the tag is shifted to the high end of the
|
||||
/// available low bits so that the lowest bits remain free for nesting. With
|
||||
/// variable-width encoding mode, the tag starts at bit 0.
|
||||
static constexpr int tagShift() {
|
||||
return useFixedWidthTags() ? (minLowBitsAvailable() - tagBits()) : 0;
|
||||
}
|
||||
|
||||
static constexpr uintptr_t tagMask() {
|
||||
return (uintptr_t(1) << tagBits()) - 1;
|
||||
using TagTable = std::array<pointer_union_detail::TagEntry, sizeof...(PTs)>;
|
||||
|
||||
/// Returns the tag lookup table for this union's encoding scheme.
|
||||
static constexpr TagTable getTagTable() {
|
||||
if constexpr (useFixedWidthTags()) {
|
||||
return pointer_union_detail::computeFixedTags<PTs...>();
|
||||
} else {
|
||||
static_assert(
|
||||
pointer_union_detail::typesInNonDecreasingBitOrder<PTs...>(),
|
||||
"Variable-width PointerUnion types must be in non-decreasing "
|
||||
"NumLowBitsAvailable order");
|
||||
constexpr auto Table =
|
||||
pointer_union_detail::computeExtendedTags<PTs...>();
|
||||
static_assert(Table.has_value(),
|
||||
"Too many types for the available low bits");
|
||||
return *Table;
|
||||
}
|
||||
}
|
||||
|
||||
// Variable-width isNull: check membership in the sparse set of tag values.
|
||||
// A single threshold comparison does not work here because lower-tier
|
||||
// non-null pointers can encode to values below higher-tier thresholds.
|
||||
template <size_t... Is>
|
||||
static constexpr bool isNullVariableImpl(uintptr_t V,
|
||||
std::index_sequence<Is...>) {
|
||||
constexpr TagTable Table = getTagTable();
|
||||
static_assert(tagShift() == 0,
|
||||
"isNullVariableImpl assumes tag starts at bit 0");
|
||||
return ((V == Table[Is].Value) || ...);
|
||||
}
|
||||
|
||||
template <typename T> static uintptr_t encode(T V) {
|
||||
constexpr TagTable Table = getTagTable();
|
||||
constexpr int Shift = tagShift();
|
||||
constexpr auto Tag = uintptr_t(FirstIndexOfType<T, PTs...>::value);
|
||||
constexpr size_t Idx = FirstIndexOfType<T, PTs...>::value;
|
||||
static_assert(Table[0].Value == 0,
|
||||
"First type must have tag value 0 for getAddrOfPtr1");
|
||||
uintptr_t PtrInt = reinterpret_cast<uintptr_t>(
|
||||
PointerLikeTypeTraits<T>::getAsVoidPointer(V));
|
||||
assert((PtrInt & (tagMask() << Shift)) == 0 &&
|
||||
assert((PtrInt & (Table[Idx].Mask << Shift)) == 0 &&
|
||||
"Pointer low bits collide with tag");
|
||||
return PtrInt | (Tag << Shift);
|
||||
return PtrInt | (Table[Idx].Value << Shift);
|
||||
}
|
||||
|
||||
public:
|
||||
@ -152,8 +284,13 @@ public:
|
||||
/// Test if the pointer held in the union is null, regardless of
|
||||
/// which type it is.
|
||||
bool isNull() const {
|
||||
return (static_cast<uintptr_t>(this->Val.asInt()) >>
|
||||
minLowBitsAvailable()) == 0;
|
||||
if constexpr (useFixedWidthTags()) {
|
||||
return (static_cast<uintptr_t>(this->Val.asInt()) >>
|
||||
minLowBitsAvailable()) == 0;
|
||||
} else {
|
||||
return isNullVariableImpl(static_cast<uintptr_t>(this->Val.asInt()),
|
||||
std::index_sequence_for<PTs...>{});
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const { return !isNull(); }
|
||||
@ -232,16 +369,23 @@ struct CastInfo<To, PointerUnion<PTs...>>
|
||||
using From = PointerUnion<PTs...>;
|
||||
|
||||
static inline bool isPossible(From &F) {
|
||||
constexpr std::array<pointer_union_detail::TagEntry, sizeof...(PTs)> Table =
|
||||
From::getTagTable();
|
||||
constexpr int Shift = From::tagShift();
|
||||
constexpr auto Tag = uintptr_t(FirstIndexOfType<To, PTs...>::value);
|
||||
constexpr size_t Idx = FirstIndexOfType<To, PTs...>::value;
|
||||
auto V = reinterpret_cast<uintptr_t>(F.getOpaqueValue());
|
||||
return ((V >> Shift) & From::tagMask()) == Tag;
|
||||
constexpr uintptr_t TagMask = Table[Idx].Mask << Shift;
|
||||
constexpr uintptr_t TagValue = Table[Idx].Value << Shift;
|
||||
return (V & TagMask) == TagValue;
|
||||
}
|
||||
|
||||
static To doCast(From &F) {
|
||||
assert(isPossible(F) && "cast to an incompatible type!");
|
||||
constexpr uintptr_t PtrMask =
|
||||
~((uintptr_t(1) << From::minLowBitsAvailable()) - 1);
|
||||
constexpr std::array<pointer_union_detail::TagEntry, sizeof...(PTs)> Table =
|
||||
From::getTagTable();
|
||||
constexpr int Shift = From::tagShift();
|
||||
constexpr size_t Idx = FirstIndexOfType<To, PTs...>::value;
|
||||
constexpr uintptr_t PtrMask = ~(uintptr_t(Table[Idx].Mask) << Shift);
|
||||
void *Ptr = reinterpret_cast<void *>(
|
||||
reinterpret_cast<uintptr_t>(F.getOpaqueValue()) & PtrMask);
|
||||
return PointerLikeTypeTraits<To>::getFromVoidPointer(Ptr);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//===- llvm/unittest/ADT/PointerUnionTest.cpp - Optional unit tests -------===//
|
||||
//===- llvm/unittest/ADT/PointerUnionTest.cpp - PointerUnion unit tests ---===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
@ -7,6 +7,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ADT/PointerUnion.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "gtest/gtest.h"
|
||||
using namespace llvm;
|
||||
|
||||
@ -29,9 +30,11 @@ struct PointerUnionTest : public testing::Test {
|
||||
|
||||
PointerUnionTest()
|
||||
: f(3.14f), i(42), d(3.14), l(42), a(&f), b(&i), c(&i), n(), i3(&i),
|
||||
f3(&f), l3(&l), i4(&i), f4(&f), l4(&l), d4(&d), i4null((int *)nullptr),
|
||||
f4null((float *)nullptr), l4null((long long *)nullptr),
|
||||
d4null((double *)nullptr) {}
|
||||
f3(&f), l3(&l), i4(&i), f4(&f), l4(&l), d4(&d),
|
||||
i4null(static_cast<int *>(nullptr)),
|
||||
f4null(static_cast<float *>(nullptr)),
|
||||
l4null(static_cast<long long *>(nullptr)),
|
||||
d4null(static_cast<double *>(nullptr)) {}
|
||||
};
|
||||
|
||||
TEST_F(PointerUnionTest, Comparison) {
|
||||
@ -66,8 +69,8 @@ TEST_F(PointerUnionTest, Null) {
|
||||
EXPECT_FALSE(!b);
|
||||
EXPECT_TRUE(!n);
|
||||
// workaround an issue with EXPECT macros and explicit bool
|
||||
EXPECT_TRUE((bool)a);
|
||||
EXPECT_TRUE((bool)b);
|
||||
EXPECT_TRUE(static_cast<bool>(a));
|
||||
EXPECT_TRUE(static_cast<bool>(b));
|
||||
EXPECT_FALSE(n);
|
||||
|
||||
EXPECT_NE(n, b);
|
||||
@ -111,7 +114,7 @@ TEST_F(PointerUnionTest, Is) {
|
||||
TEST_F(PointerUnionTest, Get) {
|
||||
EXPECT_EQ(cast<float *>(a), &f);
|
||||
EXPECT_EQ(cast<int *>(b), &i);
|
||||
EXPECT_EQ(cast<int *>(n), (int *)nullptr);
|
||||
EXPECT_EQ(cast<int *>(n), static_cast<int *>(nullptr));
|
||||
}
|
||||
|
||||
template<int I> struct alignas(8) Aligned {};
|
||||
@ -152,8 +155,10 @@ TEST_F(PointerUnionTest, ManyElements) {
|
||||
}
|
||||
|
||||
TEST_F(PointerUnionTest, GetAddrOfPtr1) {
|
||||
EXPECT_TRUE((void *)b.getAddrOfPtr1() == (void *)&b);
|
||||
EXPECT_TRUE((void *)n.getAddrOfPtr1() == (void *)&n);
|
||||
EXPECT_TRUE(static_cast<void *>(b.getAddrOfPtr1()) ==
|
||||
static_cast<void *>(&b));
|
||||
EXPECT_TRUE(static_cast<void *>(n.getAddrOfPtr1()) ==
|
||||
static_cast<void *>(&n));
|
||||
}
|
||||
|
||||
TEST_F(PointerUnionTest, NewCastInfra) {
|
||||
@ -383,4 +388,470 @@ TEST(PointerUnionNestedTest, NestedTagPreservation) {
|
||||
EXPECT_EQ(cast<HighAlign *>(extracted2.Value), &high);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Variable-width encoding PointerUnion tests
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
template <int I> struct alignas(4) Align4 {};
|
||||
template <int I> struct alignas(8) Align8 {};
|
||||
template <int I> struct alignas(16) Align16 {};
|
||||
|
||||
TEST(PointerUnionEncodingTest, ExtendedTagsFit) {
|
||||
// Positive: 3 x 2-bit + 2 x 3-bit types.
|
||||
EXPECT_TRUE(
|
||||
(pointer_union_detail::computeExtendedTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align8<1> *>()
|
||||
.has_value()));
|
||||
// Negative: 4 x 2-bit types need 4 codes but only 3 are available
|
||||
// (2^2 - 1 escape = 3).
|
||||
EXPECT_FALSE(
|
||||
(pointer_union_detail::computeExtendedTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align4<3> *, Align8<0> *>()
|
||||
.has_value()));
|
||||
}
|
||||
|
||||
TEST(PointerUnionEncodingTest, ComputeExtendedTags) {
|
||||
// 2-tier union: 3 x 2-bit + 2 x 3-bit.
|
||||
auto Tags = *pointer_union_detail::computeExtendedTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align8<1> *>();
|
||||
// Tier 0 (2-bit): codes 0b00, 0b01, 0b10; escape = 0b11.
|
||||
EXPECT_EQ(Tags[0].Value, 0b00u);
|
||||
EXPECT_EQ(Tags[0].Mask, 0b11u);
|
||||
EXPECT_EQ(Tags[1].Value, 0b01u);
|
||||
EXPECT_EQ(Tags[2].Value, 0b10u);
|
||||
// Tier 1 (3-bit): codes 0b011, 0b111; mask = 0b111.
|
||||
EXPECT_EQ(Tags[3].Value, 0b011u);
|
||||
EXPECT_EQ(Tags[3].Mask, 0b111u);
|
||||
EXPECT_EQ(Tags[4].Value, 0b111u);
|
||||
}
|
||||
|
||||
TEST(PointerUnionEncodingTest, ComputeExtendedTags3Tier) {
|
||||
// 3-tier union: 3 x 2-bit + 1 x 3-bit + 2 x 4-bit.
|
||||
auto Tags =
|
||||
*pointer_union_detail::computeExtendedTags<Align4<0> *, Align4<1> *,
|
||||
Align4<2> *, Align8<0> *,
|
||||
Align16<0> *, Align16<1> *>();
|
||||
// Tier 0 (2-bit): codes 0b00, 0b01, 0b10; escape = 0b11.
|
||||
EXPECT_EQ(Tags[0].Value, 0b00u);
|
||||
EXPECT_EQ(Tags[0].Mask, 0b11u);
|
||||
EXPECT_EQ(Tags[1].Value, 0b01u);
|
||||
EXPECT_EQ(Tags[2].Value, 0b10u);
|
||||
// Tier 1 (3-bit): code 0b011; escape = 0b111. Mask = 0b111.
|
||||
EXPECT_EQ(Tags[3].Value, 0b011u);
|
||||
EXPECT_EQ(Tags[3].Mask, 0b111u);
|
||||
// Tier 2 (4-bit): codes 0b0111, 0b1111. Mask = 0b1111.
|
||||
EXPECT_EQ(Tags[4].Value, 0b0111u);
|
||||
EXPECT_EQ(Tags[4].Mask, 0b1111u);
|
||||
EXPECT_EQ(Tags[5].Value, 0b1111u);
|
||||
EXPECT_EQ(Tags[5].Mask, 0b1111u);
|
||||
}
|
||||
|
||||
// 2-tier: 3 x 2-bit + 2 x 3-bit types.
|
||||
using PU2Tier = PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *,
|
||||
Align8<1> *>;
|
||||
|
||||
// 3-tier: 3 x 2-bit + 1 x 3-bit + 2 x 4-bit types.
|
||||
using PU3Tier = PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *,
|
||||
Align16<0> *, Align16<1> *>;
|
||||
|
||||
// Variable-width unions still fit in a single pointer.
|
||||
static_assert(sizeof(PU2Tier) == sizeof(void *));
|
||||
static_assert(sizeof(PU3Tier) == sizeof(void *));
|
||||
|
||||
// These unions actually use variable-width encoding (fixed-width tags don't
|
||||
// fit because 5 types need 3 tag bits but Align4 only provides 2).
|
||||
static_assert(
|
||||
!pointer_union_detail::useFixedWidthTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align8<1> *>());
|
||||
static_assert(!pointer_union_detail::useFixedWidthTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align8<0> *, Align16<0> *,
|
||||
Align16<1> *>());
|
||||
|
||||
// NumLowBitsAvailable is 0 for variable-width PointerUnion.
|
||||
static_assert(PointerLikeTypeTraits<PU2Tier>::NumLowBitsAvailable == 0);
|
||||
static_assert(PointerLikeTypeTraits<PU3Tier>::NumLowBitsAvailable == 0);
|
||||
|
||||
struct PointerUnion2TierTest : public testing::Test {
|
||||
Align4<0> a0;
|
||||
Align4<1> a1;
|
||||
Align4<2> a2;
|
||||
Align8<0> b0;
|
||||
Align8<1> b1;
|
||||
|
||||
PU2Tier pa0, pa1, pa2, pb0, pb1, null;
|
||||
PU2Tier na0, na1, na2, nb0, nb1;
|
||||
|
||||
PointerUnion2TierTest()
|
||||
: pa0(&a0), pa1(&a1), pa2(&a2), pb0(&b0), pb1(&b1), null(),
|
||||
na0(static_cast<Align4<0> *>(nullptr)),
|
||||
na1(static_cast<Align4<1> *>(nullptr)),
|
||||
na2(static_cast<Align4<2> *>(nullptr)),
|
||||
nb0(static_cast<Align8<0> *>(nullptr)),
|
||||
nb1(static_cast<Align8<1> *>(nullptr)) {}
|
||||
};
|
||||
|
||||
TEST_F(PointerUnion2TierTest, Isa) {
|
||||
// Tier 0 types
|
||||
EXPECT_TRUE(isa<Align4<0> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align4<1> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align4<2> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align8<1> *>(pa0));
|
||||
|
||||
EXPECT_TRUE(isa<Align4<1> *>(pa1));
|
||||
EXPECT_TRUE(isa<Align4<2> *>(pa2));
|
||||
|
||||
// Tier 1 types
|
||||
EXPECT_TRUE(isa<Align8<0> *>(pb0));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(pb0));
|
||||
EXPECT_FALSE(isa<Align8<1> *>(pb0));
|
||||
|
||||
EXPECT_TRUE(isa<Align8<1> *>(pb1));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(pb1));
|
||||
|
||||
// Null pointers preserve type identity
|
||||
EXPECT_TRUE(isa<Align4<0> *>(na0));
|
||||
EXPECT_TRUE(isa<Align8<1> *>(nb1));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(na0));
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, Cast) {
|
||||
EXPECT_EQ(cast<Align4<0> *>(pa0), &a0);
|
||||
EXPECT_EQ(cast<Align4<1> *>(pa1), &a1);
|
||||
EXPECT_EQ(cast<Align4<2> *>(pa2), &a2);
|
||||
EXPECT_EQ(cast<Align8<0> *>(pb0), &b0);
|
||||
EXPECT_EQ(cast<Align8<1> *>(pb1), &b1);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, DynCast) {
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pa0), &a0);
|
||||
EXPECT_EQ(dyn_cast<Align4<1> *>(pa0), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(pa0), nullptr);
|
||||
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(pb0), &b0);
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pb0), nullptr);
|
||||
|
||||
// pb1 has the all-ones tag -- most likely to expose masking bugs.
|
||||
EXPECT_EQ(dyn_cast<Align8<1> *>(pb1), &b1);
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pb1), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align4<1> *>(pb1), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align4<2> *>(pb1), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(pb1), nullptr);
|
||||
|
||||
EXPECT_EQ(dyn_cast_if_present<Align4<0> *>(na0), nullptr);
|
||||
EXPECT_EQ(dyn_cast_if_present<Align8<0> *>(na0), nullptr);
|
||||
EXPECT_EQ(dyn_cast_if_present<Align8<0> *>(nb0), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, Null) {
|
||||
EXPECT_FALSE(pa0.isNull());
|
||||
EXPECT_FALSE(pb0.isNull());
|
||||
EXPECT_TRUE(null.isNull());
|
||||
EXPECT_TRUE(!null);
|
||||
EXPECT_TRUE(static_cast<bool>(pa0));
|
||||
|
||||
EXPECT_TRUE(na0.isNull());
|
||||
EXPECT_TRUE(na1.isNull());
|
||||
EXPECT_TRUE(na2.isNull());
|
||||
EXPECT_TRUE(nb0.isNull());
|
||||
EXPECT_TRUE(nb1.isNull());
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, NullDiscrimination) {
|
||||
// Null pointers of different types have different opaque values.
|
||||
EXPECT_NE(na0, na1);
|
||||
EXPECT_NE(na0, na2);
|
||||
EXPECT_NE(na0, nb0);
|
||||
EXPECT_NE(na1, nb0);
|
||||
EXPECT_NE(nb0, nb1);
|
||||
|
||||
// Default-constructed is null of first type.
|
||||
EXPECT_EQ(null, na0);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, Comparison) {
|
||||
EXPECT_EQ(pa0, pa0);
|
||||
EXPECT_NE(pa0, pa1);
|
||||
EXPECT_NE(pa0, pb0);
|
||||
|
||||
PU2Tier other(&a0);
|
||||
EXPECT_EQ(pa0, other);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, Assignment) {
|
||||
PU2Tier u;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
|
||||
u = &a0;
|
||||
EXPECT_TRUE(isa<Align4<0> *>(u));
|
||||
EXPECT_EQ(cast<Align4<0> *>(u), &a0);
|
||||
|
||||
u = &b0;
|
||||
EXPECT_TRUE(isa<Align8<0> *>(u));
|
||||
EXPECT_EQ(cast<Align8<0> *>(u), &b0);
|
||||
|
||||
u = &a2;
|
||||
EXPECT_TRUE(isa<Align4<2> *>(u));
|
||||
|
||||
u = nullptr;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, GetAddrOfPtr1) {
|
||||
EXPECT_TRUE(static_cast<void *>(pa0.getAddrOfPtr1()) ==
|
||||
static_cast<void *>(&pa0));
|
||||
EXPECT_TRUE(static_cast<void *>(null.getAddrOfPtr1()) ==
|
||||
static_cast<void *>(&null));
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion2TierTest, OpaqueValueRoundTrip) {
|
||||
void *opaque = pa0.getOpaqueValue();
|
||||
PU2Tier restored = PU2Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pa0, restored);
|
||||
EXPECT_EQ(cast<Align4<0> *>(restored), &a0);
|
||||
|
||||
opaque = pb0.getOpaqueValue();
|
||||
restored = PU2Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pb0, restored);
|
||||
EXPECT_EQ(cast<Align8<0> *>(restored), &b0);
|
||||
|
||||
opaque = pb1.getOpaqueValue();
|
||||
restored = PU2Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pb1, restored);
|
||||
EXPECT_EQ(cast<Align8<1> *>(restored), &b1);
|
||||
}
|
||||
|
||||
// 3-tier tests
|
||||
|
||||
struct PointerUnion3TierTest : public testing::Test {
|
||||
Align4<0> a0;
|
||||
Align4<1> a1;
|
||||
Align4<2> a2;
|
||||
Align8<0> b0;
|
||||
Align16<0> c0;
|
||||
Align16<1> c1;
|
||||
|
||||
PU3Tier pa0, pa1, pa2, pb0, pc0, pc1, null;
|
||||
|
||||
PointerUnion3TierTest()
|
||||
: pa0(&a0), pa1(&a1), pa2(&a2), pb0(&b0), pc0(&c0), pc1(&c1), null() {}
|
||||
};
|
||||
|
||||
TEST_F(PointerUnion3TierTest, Isa) {
|
||||
EXPECT_TRUE(isa<Align4<0> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(pa0));
|
||||
EXPECT_FALSE(isa<Align16<0> *>(pa0));
|
||||
|
||||
EXPECT_TRUE(isa<Align8<0> *>(pb0));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(pb0));
|
||||
EXPECT_FALSE(isa<Align16<0> *>(pb0));
|
||||
|
||||
EXPECT_TRUE(isa<Align16<0> *>(pc0));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(pc0));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(pc0));
|
||||
EXPECT_FALSE(isa<Align16<1> *>(pc0));
|
||||
|
||||
EXPECT_TRUE(isa<Align16<1> *>(pc1));
|
||||
EXPECT_FALSE(isa<Align16<0> *>(pc1));
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, Cast) {
|
||||
EXPECT_EQ(cast<Align4<0> *>(pa0), &a0);
|
||||
EXPECT_EQ(cast<Align4<1> *>(pa1), &a1);
|
||||
EXPECT_EQ(cast<Align4<2> *>(pa2), &a2);
|
||||
EXPECT_EQ(cast<Align8<0> *>(pb0), &b0);
|
||||
EXPECT_EQ(cast<Align16<0> *>(pc0), &c0);
|
||||
EXPECT_EQ(cast<Align16<1> *>(pc1), &c1);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, DynCast) {
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pa0), &a0);
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(pa0), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align16<0> *>(pa0), nullptr);
|
||||
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(pb0), &b0);
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pb0), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align16<0> *>(pb0), nullptr);
|
||||
|
||||
EXPECT_EQ(dyn_cast<Align16<0> *>(pc0), &c0);
|
||||
EXPECT_EQ(dyn_cast<Align16<1> *>(pc0), nullptr);
|
||||
EXPECT_EQ(dyn_cast<Align4<0> *>(pc0), nullptr);
|
||||
|
||||
EXPECT_EQ(dyn_cast<Align16<1> *>(pc1), &c1);
|
||||
EXPECT_EQ(dyn_cast<Align16<0> *>(pc1), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, Null) {
|
||||
EXPECT_TRUE(null.isNull());
|
||||
EXPECT_FALSE(pa0.isNull());
|
||||
EXPECT_FALSE(pb0.isNull());
|
||||
EXPECT_FALSE(pc0.isNull());
|
||||
EXPECT_FALSE(pc1.isNull());
|
||||
|
||||
PU3Tier na0(static_cast<Align4<0> *>(nullptr));
|
||||
PU3Tier nb0(static_cast<Align8<0> *>(nullptr));
|
||||
PU3Tier nc0(static_cast<Align16<0> *>(nullptr));
|
||||
PU3Tier nc1(static_cast<Align16<1> *>(nullptr));
|
||||
EXPECT_TRUE(na0.isNull());
|
||||
EXPECT_TRUE(nb0.isNull());
|
||||
EXPECT_TRUE(nc0.isNull());
|
||||
EXPECT_TRUE(nc1.isNull());
|
||||
|
||||
// Null discrimination across all three tiers.
|
||||
EXPECT_NE(na0, nb0);
|
||||
EXPECT_NE(nb0, nc0);
|
||||
EXPECT_NE(nc0, nc1);
|
||||
EXPECT_NE(na0, nc0);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, Assignment) {
|
||||
PU3Tier u;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
|
||||
u = &a0;
|
||||
EXPECT_TRUE(isa<Align4<0> *>(u));
|
||||
EXPECT_EQ(cast<Align4<0> *>(u), &a0);
|
||||
|
||||
u = &b0;
|
||||
EXPECT_TRUE(isa<Align8<0> *>(u));
|
||||
EXPECT_EQ(cast<Align8<0> *>(u), &b0);
|
||||
|
||||
u = &c1;
|
||||
EXPECT_TRUE(isa<Align16<1> *>(u));
|
||||
EXPECT_EQ(cast<Align16<1> *>(u), &c1);
|
||||
|
||||
u = nullptr;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, OpaqueValueRoundTrip) {
|
||||
// pb0's tag (0b011) contains the tier-0 escape prefix (0b11) in its low 2
|
||||
// bits.
|
||||
void *opaque = pb0.getOpaqueValue();
|
||||
PU3Tier restored = PU3Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pb0, restored);
|
||||
EXPECT_EQ(cast<Align8<0> *>(restored), &b0);
|
||||
|
||||
opaque = pc0.getOpaqueValue();
|
||||
restored = PU3Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pc0, restored);
|
||||
EXPECT_EQ(cast<Align16<0> *>(restored), &c0);
|
||||
|
||||
opaque = pc1.getOpaqueValue();
|
||||
restored = PU3Tier::getFromOpaqueValue(opaque);
|
||||
EXPECT_EQ(pc1, restored);
|
||||
EXPECT_EQ(cast<Align16<1> *>(restored), &c1);
|
||||
}
|
||||
|
||||
TEST_F(PointerUnion3TierTest, ConstCast) {
|
||||
const PU3Tier cpc0(&c0);
|
||||
EXPECT_TRUE(isa<Align16<0> *>(cpc0));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(cpc0));
|
||||
EXPECT_EQ(cast<Align16<0> *>(cpc0), &c0);
|
||||
EXPECT_EQ(dyn_cast<Align8<0> *>(cpc0), nullptr);
|
||||
}
|
||||
|
||||
TEST(PointerUnionMultiTierDenseMapTest, BasicOperations) {
|
||||
Align4<0> a0;
|
||||
Align8<0> b0;
|
||||
Align8<1> b1;
|
||||
|
||||
DenseMap<PU2Tier, int> map;
|
||||
PU2Tier ka(&a0), kb(&b0), kb1(&b1);
|
||||
|
||||
map[ka] = 1;
|
||||
map[kb] = 2;
|
||||
map[kb1] = 3;
|
||||
|
||||
EXPECT_EQ(map[ka], 1);
|
||||
EXPECT_EQ(map[kb], 2);
|
||||
EXPECT_EQ(map[kb1], 3);
|
||||
|
||||
EXPECT_EQ(map.count(ka), 1u);
|
||||
map.erase(ka);
|
||||
EXPECT_EQ(map.count(ka), 0u);
|
||||
EXPECT_EQ(map.count(kb), 1u);
|
||||
}
|
||||
|
||||
TEST(PointerUnionMixedAlignFixedWidth, BasicOperations) {
|
||||
// Align4 provides 2 low bits, Align8 provides 3. Two types need 1 tag bit,
|
||||
// so all types have enough bits for fixed-width encoding with spare bits.
|
||||
using MixedPU = PointerUnion<Align4<0> *, Align8<0> *>;
|
||||
static_assert(PointerLikeTypeTraits<MixedPU>::NumLowBitsAvailable > 0,
|
||||
"Mixed-alignment 2-type union should have spare low bits");
|
||||
|
||||
Align4<0> a;
|
||||
Align8<0> b;
|
||||
|
||||
MixedPU u;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
|
||||
u = &a;
|
||||
EXPECT_TRUE(isa<Align4<0> *>(u));
|
||||
EXPECT_FALSE(isa<Align8<0> *>(u));
|
||||
EXPECT_EQ(cast<Align4<0> *>(u), &a);
|
||||
|
||||
u = &b;
|
||||
EXPECT_TRUE(isa<Align8<0> *>(u));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(u));
|
||||
EXPECT_EQ(cast<Align8<0> *>(u), &b);
|
||||
|
||||
u = nullptr;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
}
|
||||
|
||||
TEST(PointerUnionLargeTierJump, BasicOperations) {
|
||||
// 3 x 2-bit + 2 x 4-bit: skips the 3-bit tier entirely (tier jump 2->4).
|
||||
using JumpPU = PointerUnion<Align4<0> *, Align4<1> *, Align4<2> *,
|
||||
Align16<0> *, Align16<1> *>;
|
||||
static_assert(
|
||||
!pointer_union_detail::useFixedWidthTags<
|
||||
Align4<0> *, Align4<1> *, Align4<2> *, Align16<0> *, Align16<1> *>(),
|
||||
"Should use variable-width encoding");
|
||||
|
||||
Align4<0> a0;
|
||||
Align4<1> a1;
|
||||
Align4<2> a2;
|
||||
Align16<0> c0;
|
||||
Align16<1> c1;
|
||||
|
||||
JumpPU u;
|
||||
EXPECT_TRUE(u.isNull());
|
||||
|
||||
u = &a0;
|
||||
EXPECT_TRUE(isa<Align4<0> *>(u));
|
||||
EXPECT_EQ(cast<Align4<0> *>(u), &a0);
|
||||
|
||||
u = &a1;
|
||||
EXPECT_TRUE(isa<Align4<1> *>(u));
|
||||
EXPECT_EQ(cast<Align4<1> *>(u), &a1);
|
||||
|
||||
u = &a2;
|
||||
EXPECT_TRUE(isa<Align4<2> *>(u));
|
||||
EXPECT_EQ(cast<Align4<2> *>(u), &a2);
|
||||
|
||||
u = &c0;
|
||||
EXPECT_TRUE(isa<Align16<0> *>(u));
|
||||
EXPECT_FALSE(isa<Align4<0> *>(u));
|
||||
EXPECT_EQ(cast<Align16<0> *>(u), &c0);
|
||||
|
||||
u = &c1;
|
||||
EXPECT_TRUE(isa<Align16<1> *>(u));
|
||||
EXPECT_FALSE(isa<Align16<0> *>(u));
|
||||
EXPECT_EQ(cast<Align16<1> *>(u), &c1);
|
||||
|
||||
// Typed nulls preserve type identity and are null.
|
||||
JumpPU na0(static_cast<Align4<0> *>(nullptr));
|
||||
JumpPU nc0(static_cast<Align16<0> *>(nullptr));
|
||||
JumpPU nc1(static_cast<Align16<1> *>(nullptr));
|
||||
EXPECT_TRUE(na0.isNull());
|
||||
EXPECT_TRUE(nc0.isNull());
|
||||
EXPECT_TRUE(nc1.isNull());
|
||||
EXPECT_TRUE(isa<Align4<0> *>(na0));
|
||||
EXPECT_TRUE(isa<Align16<0> *>(nc0));
|
||||
EXPECT_TRUE(isa<Align16<1> *>(nc1));
|
||||
EXPECT_NE(na0, nc0);
|
||||
EXPECT_NE(nc0, nc1);
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user