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.
858 lines
27 KiB
C++
858 lines
27 KiB
C++
//===- 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.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ADT/PointerUnion.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "gtest/gtest.h"
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
using PU = PointerUnion<int *, float *>;
|
|
using PU3 = PointerUnion<int *, float *, long long *>;
|
|
using PU4 = PointerUnion<int *, float *, long long *, double *>;
|
|
|
|
struct PointerUnionTest : public testing::Test {
|
|
float f;
|
|
int i;
|
|
double d;
|
|
long long l;
|
|
|
|
PU a, b, c, n;
|
|
PU3 i3, f3, l3;
|
|
PU4 i4, f4, l4, d4;
|
|
PU4 i4null, f4null, l4null, d4null;
|
|
|
|
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(static_cast<int *>(nullptr)),
|
|
f4null(static_cast<float *>(nullptr)),
|
|
l4null(static_cast<long long *>(nullptr)),
|
|
d4null(static_cast<double *>(nullptr)) {}
|
|
};
|
|
|
|
TEST_F(PointerUnionTest, Comparison) {
|
|
EXPECT_TRUE(a == a);
|
|
EXPECT_FALSE(a != a);
|
|
EXPECT_TRUE(a != b);
|
|
EXPECT_FALSE(a == b);
|
|
EXPECT_TRUE(b == c);
|
|
EXPECT_FALSE(b != c);
|
|
EXPECT_TRUE(b != n);
|
|
EXPECT_FALSE(b == n);
|
|
EXPECT_TRUE(i3 == i3);
|
|
EXPECT_FALSE(i3 != i3);
|
|
EXPECT_TRUE(i3 != f3);
|
|
EXPECT_TRUE(f3 != l3);
|
|
EXPECT_TRUE(i4 == i4);
|
|
EXPECT_FALSE(i4 != i4);
|
|
EXPECT_TRUE(i4 != f4);
|
|
EXPECT_TRUE(i4 != l4);
|
|
EXPECT_TRUE(f4 != l4);
|
|
EXPECT_TRUE(l4 != d4);
|
|
EXPECT_TRUE(i4null != f4null);
|
|
EXPECT_TRUE(i4null != l4null);
|
|
EXPECT_TRUE(i4null != d4null);
|
|
}
|
|
|
|
TEST_F(PointerUnionTest, Null) {
|
|
EXPECT_FALSE(a.isNull());
|
|
EXPECT_FALSE(b.isNull());
|
|
EXPECT_TRUE(n.isNull());
|
|
EXPECT_FALSE(!a);
|
|
EXPECT_FALSE(!b);
|
|
EXPECT_TRUE(!n);
|
|
// workaround an issue with EXPECT macros and explicit bool
|
|
EXPECT_TRUE(static_cast<bool>(a));
|
|
EXPECT_TRUE(static_cast<bool>(b));
|
|
EXPECT_FALSE(n);
|
|
|
|
EXPECT_NE(n, b);
|
|
EXPECT_EQ(b, c);
|
|
b = nullptr;
|
|
EXPECT_EQ(n, b);
|
|
EXPECT_NE(b, c);
|
|
EXPECT_FALSE(i3.isNull());
|
|
EXPECT_FALSE(f3.isNull());
|
|
EXPECT_FALSE(l3.isNull());
|
|
EXPECT_FALSE(i4.isNull());
|
|
EXPECT_FALSE(f4.isNull());
|
|
EXPECT_FALSE(l4.isNull());
|
|
EXPECT_FALSE(d4.isNull());
|
|
EXPECT_TRUE(i4null.isNull());
|
|
EXPECT_TRUE(f4null.isNull());
|
|
EXPECT_TRUE(l4null.isNull());
|
|
EXPECT_TRUE(d4null.isNull());
|
|
}
|
|
|
|
TEST_F(PointerUnionTest, Is) {
|
|
EXPECT_FALSE(isa<int *>(a));
|
|
EXPECT_TRUE(isa<float *>(a));
|
|
EXPECT_TRUE(isa<int *>(b));
|
|
EXPECT_FALSE(isa<float *>(b));
|
|
EXPECT_TRUE(isa<int *>(n));
|
|
EXPECT_FALSE(isa<float *>(n));
|
|
EXPECT_TRUE(isa<int *>(i3));
|
|
EXPECT_TRUE(isa<float *>(f3));
|
|
EXPECT_TRUE(isa<long long *>(l3));
|
|
EXPECT_TRUE(isa<int *>(i4));
|
|
EXPECT_TRUE(isa<float *>(f4));
|
|
EXPECT_TRUE(isa<long long *>(l4));
|
|
EXPECT_TRUE(isa<double *>(d4));
|
|
EXPECT_TRUE(isa<int *>(i4null));
|
|
EXPECT_TRUE(isa<float *>(f4null));
|
|
EXPECT_TRUE(isa<long long *>(l4null));
|
|
EXPECT_TRUE(isa<double *>(d4null));
|
|
}
|
|
|
|
TEST_F(PointerUnionTest, Get) {
|
|
EXPECT_EQ(cast<float *>(a), &f);
|
|
EXPECT_EQ(cast<int *>(b), &i);
|
|
EXPECT_EQ(cast<int *>(n), static_cast<int *>(nullptr));
|
|
}
|
|
|
|
template<int I> struct alignas(8) Aligned {};
|
|
|
|
using PU8 =
|
|
PointerUnion<Aligned<0> *, Aligned<1> *, Aligned<2> *, Aligned<3> *,
|
|
Aligned<4> *, Aligned<5> *, Aligned<6> *, Aligned<7> *>;
|
|
|
|
TEST_F(PointerUnionTest, ManyElements) {
|
|
Aligned<0> a0;
|
|
Aligned<7> a7;
|
|
|
|
PU8 a = &a0;
|
|
EXPECT_TRUE(isa<Aligned<0> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<1> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<2> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<3> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<4> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<5> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<6> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<7> *>(a));
|
|
EXPECT_EQ(dyn_cast_if_present<Aligned<0> *>(a), &a0);
|
|
EXPECT_EQ(*a.getAddrOfPtr1(), &a0);
|
|
|
|
a = &a7;
|
|
EXPECT_FALSE(isa<Aligned<0> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<1> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<2> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<3> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<4> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<5> *>(a));
|
|
EXPECT_FALSE(isa<Aligned<6> *>(a));
|
|
EXPECT_TRUE(isa<Aligned<7> *>(a));
|
|
EXPECT_EQ(dyn_cast_if_present<Aligned<7> *>(a), &a7);
|
|
|
|
EXPECT_TRUE(a == PU8(&a7));
|
|
EXPECT_TRUE(a != PU8(&a0));
|
|
}
|
|
|
|
TEST_F(PointerUnionTest, GetAddrOfPtr1) {
|
|
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) {
|
|
// test isa<>
|
|
EXPECT_TRUE(isa<float *>(a));
|
|
EXPECT_TRUE(isa<int *>(b));
|
|
EXPECT_TRUE(isa<int *>(c));
|
|
EXPECT_TRUE(isa<int *>(n));
|
|
EXPECT_TRUE(isa<int *>(i3));
|
|
EXPECT_TRUE(isa<float *>(f3));
|
|
EXPECT_TRUE(isa<long long *>(l3));
|
|
EXPECT_TRUE(isa<int *>(i4));
|
|
EXPECT_TRUE(isa<float *>(f4));
|
|
EXPECT_TRUE(isa<long long *>(l4));
|
|
EXPECT_TRUE(isa<double *>(d4));
|
|
EXPECT_TRUE(isa<int *>(i4null));
|
|
EXPECT_TRUE(isa<float *>(f4null));
|
|
EXPECT_TRUE(isa<long long *>(l4null));
|
|
EXPECT_TRUE(isa<double *>(d4null));
|
|
EXPECT_FALSE(isa<int *>(a));
|
|
EXPECT_FALSE(isa<float *>(b));
|
|
EXPECT_FALSE(isa<float *>(c));
|
|
EXPECT_FALSE(isa<float *>(n));
|
|
EXPECT_FALSE(isa<float *>(i3));
|
|
EXPECT_FALSE(isa<long long *>(i3));
|
|
EXPECT_FALSE(isa<int *>(f3));
|
|
EXPECT_FALSE(isa<long long *>(f3));
|
|
EXPECT_FALSE(isa<int *>(l3));
|
|
EXPECT_FALSE(isa<float *>(l3));
|
|
EXPECT_FALSE(isa<float *>(i4));
|
|
EXPECT_FALSE(isa<long long *>(i4));
|
|
EXPECT_FALSE(isa<double *>(i4));
|
|
EXPECT_FALSE(isa<int *>(f4));
|
|
EXPECT_FALSE(isa<long long *>(f4));
|
|
EXPECT_FALSE(isa<double *>(f4));
|
|
EXPECT_FALSE(isa<int *>(l4));
|
|
EXPECT_FALSE(isa<float *>(l4));
|
|
EXPECT_FALSE(isa<double *>(l4));
|
|
EXPECT_FALSE(isa<int *>(d4));
|
|
EXPECT_FALSE(isa<float *>(d4));
|
|
EXPECT_FALSE(isa<long long *>(d4));
|
|
EXPECT_FALSE(isa<float *>(i4null));
|
|
EXPECT_FALSE(isa<long long *>(i4null));
|
|
EXPECT_FALSE(isa<double *>(i4null));
|
|
EXPECT_FALSE(isa<int *>(f4null));
|
|
EXPECT_FALSE(isa<long long *>(f4null));
|
|
EXPECT_FALSE(isa<double *>(f4null));
|
|
EXPECT_FALSE(isa<int *>(l4null));
|
|
EXPECT_FALSE(isa<float *>(l4null));
|
|
EXPECT_FALSE(isa<double *>(l4null));
|
|
EXPECT_FALSE(isa<int *>(d4null));
|
|
EXPECT_FALSE(isa<float *>(d4null));
|
|
EXPECT_FALSE(isa<long long *>(d4null));
|
|
|
|
// test cast<>
|
|
EXPECT_EQ(cast<float *>(a), &f);
|
|
EXPECT_EQ(cast<int *>(b), &i);
|
|
EXPECT_EQ(cast<int *>(c), &i);
|
|
EXPECT_EQ(cast<int *>(i3), &i);
|
|
EXPECT_EQ(cast<float *>(f3), &f);
|
|
EXPECT_EQ(cast<long long *>(l3), &l);
|
|
EXPECT_EQ(cast<int *>(i4), &i);
|
|
EXPECT_EQ(cast<float *>(f4), &f);
|
|
EXPECT_EQ(cast<long long *>(l4), &l);
|
|
EXPECT_EQ(cast<double *>(d4), &d);
|
|
|
|
// test dyn_cast
|
|
EXPECT_EQ(dyn_cast<int *>(a), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(a), &f);
|
|
EXPECT_EQ(dyn_cast<int *>(b), &i);
|
|
EXPECT_EQ(dyn_cast<float *>(b), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(c), &i);
|
|
EXPECT_EQ(dyn_cast<float *>(c), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<int *>(n), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<float *>(n), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(i3), &i);
|
|
EXPECT_EQ(dyn_cast<float *>(i3), nullptr);
|
|
EXPECT_EQ(dyn_cast<long long *>(i3), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(f3), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(f3), &f);
|
|
EXPECT_EQ(dyn_cast<long long *>(f3), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(l3), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(l3), nullptr);
|
|
EXPECT_EQ(dyn_cast<long long *>(l3), &l);
|
|
EXPECT_EQ(dyn_cast<int *>(i4), &i);
|
|
EXPECT_EQ(dyn_cast<float *>(i4), nullptr);
|
|
EXPECT_EQ(dyn_cast<long long *>(i4), nullptr);
|
|
EXPECT_EQ(dyn_cast<double *>(i4), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(f4), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(f4), &f);
|
|
EXPECT_EQ(dyn_cast<long long *>(f4), nullptr);
|
|
EXPECT_EQ(dyn_cast<double *>(f4), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(l4), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(l4), nullptr);
|
|
EXPECT_EQ(dyn_cast<long long *>(l4), &l);
|
|
EXPECT_EQ(dyn_cast<double *>(l4), nullptr);
|
|
EXPECT_EQ(dyn_cast<int *>(d4), nullptr);
|
|
EXPECT_EQ(dyn_cast<float *>(d4), nullptr);
|
|
EXPECT_EQ(dyn_cast<long long *>(d4), nullptr);
|
|
EXPECT_EQ(dyn_cast<double *>(d4), &d);
|
|
EXPECT_EQ(dyn_cast_if_present<int *>(i4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<float *>(i4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<long long *>(i4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<double *>(i4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<int *>(f4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<float *>(f4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<long long *>(f4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<double *>(f4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<int *>(l4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<float *>(l4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<long long *>(l4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<double *>(l4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<int *>(d4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<float *>(d4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<long long *>(d4null), nullptr);
|
|
EXPECT_EQ(dyn_cast_if_present<double *>(d4null), nullptr);
|
|
|
|
// test for const
|
|
const PU4 constd4(&d);
|
|
EXPECT_TRUE(isa<double *>(constd4));
|
|
EXPECT_FALSE(isa<int *>(constd4));
|
|
EXPECT_EQ(cast<double *>(constd4), &d);
|
|
EXPECT_EQ(dyn_cast<long long *>(constd4), nullptr);
|
|
|
|
auto *result1 = cast<double *>(constd4);
|
|
static_assert(std::is_same_v<double *, decltype(result1)>,
|
|
"type mismatch for cast with PointerUnion");
|
|
|
|
PointerUnion<int *, const double *> constd2(&d);
|
|
auto *result2 = cast<const double *>(constd2);
|
|
EXPECT_EQ(result2, &d);
|
|
static_assert(std::is_same_v<const double *, decltype(result2)>,
|
|
"type mismatch for cast with PointerUnion");
|
|
}
|
|
|
|
// Regression test: doCast must mask with minLowBitsAvailable(), not
|
|
// To::NumLowBitsAvailable, to avoid clearing inner PointerUnion tag bits.
|
|
// This reproduces the 32-bit crash from PR #187950 on any platform by
|
|
// using types whose alignment mimics the 32-bit DeclLink layout:
|
|
// OuterPU<InnerPU, OverClaimWrapper>
|
|
// where OverClaimWrapper's PLTT claims more low bits than its inner
|
|
// PointerUnion actually has spare.
|
|
|
|
struct alignas(8) HighAlign {
|
|
int x;
|
|
};
|
|
struct alignas(4) LowAlign {
|
|
int x;
|
|
};
|
|
|
|
// Wrapper around a PointerUnion that over-claims NumLowBitsAvailable,
|
|
// mimicking LazyGenerationalUpdatePtr's PLTT on 32-bit.
|
|
struct OverClaimWrapper {
|
|
PointerUnion<HighAlign *, LowAlign *> Value;
|
|
|
|
OverClaimWrapper() = default;
|
|
explicit OverClaimWrapper(decltype(Value) V) : Value(V) {}
|
|
|
|
void *getOpaqueValue() { return Value.getOpaqueValue(); }
|
|
static OverClaimWrapper getFromOpaqueValue(void *P) {
|
|
return OverClaimWrapper(decltype(Value)::getFromOpaqueValue(P));
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
namespace llvm {
|
|
template <> struct PointerLikeTypeTraits<OverClaimWrapper> {
|
|
static void *getAsVoidPointer(OverClaimWrapper W) {
|
|
return W.getOpaqueValue();
|
|
}
|
|
static OverClaimWrapper getFromVoidPointer(void *P) {
|
|
return OverClaimWrapper::getFromOpaqueValue(P);
|
|
}
|
|
// Inner PU<HighAlign*(3 bits), LowAlign*(2 bits)> has tagShift=1, so only
|
|
// 1 spare bit. Claiming 2 bits mimics the LGUP over-claim on 32-bit.
|
|
static constexpr int NumLowBitsAvailable = 2;
|
|
};
|
|
} // namespace llvm
|
|
|
|
namespace {
|
|
|
|
TEST(PointerUnionNestedTest, NestedTagPreservation) {
|
|
// Inner PU: PointerUnion<HighAlign*, LowAlign*>
|
|
// minLowBits = min(3, 2) = 2, tagBits = 1, tagShift = 1
|
|
// Tag for LowAlign* (index 1) is in bit 1.
|
|
// NumLowBitsAvailable = 1
|
|
|
|
// Outer PU: PointerUnion<InnerPU, OverClaimWrapper>
|
|
// InnerPU NumLowBitsAvailable = 1
|
|
// OverClaimWrapper NumLowBitsAvailable = 2 (over-claimed)
|
|
// minLowBits = 1, tagBits = 1, tagShift = 0
|
|
|
|
using InnerPU = PointerUnion<HighAlign *, LowAlign *>;
|
|
using OuterPU = PointerUnion<InnerPU, OverClaimWrapper>;
|
|
|
|
LowAlign low;
|
|
|
|
// Store LowAlign* in the inner PU (tag = 1, in bit 1).
|
|
InnerPU inner(&low);
|
|
ASSERT_TRUE(isa<LowAlign *>(inner));
|
|
ASSERT_EQ(cast<LowAlign *>(inner), &low);
|
|
|
|
// Wrap it and store in the outer PU.
|
|
OverClaimWrapper wrapper(inner);
|
|
OuterPU outer(wrapper);
|
|
ASSERT_TRUE(isa<OverClaimWrapper>(outer));
|
|
|
|
// Extract the wrapper back. Before the fix, doCast would clear bit 1
|
|
// (the inner PU's tag), corrupting the type discriminator.
|
|
OverClaimWrapper extracted = cast<OverClaimWrapper>(outer);
|
|
InnerPU extractedInner = extracted.Value;
|
|
|
|
EXPECT_TRUE(isa<LowAlign *>(extractedInner))
|
|
<< "Inner PointerUnion tag corrupted during doCast: expected LowAlign*, "
|
|
"got HighAlign*. doCast must not clear bits beyond "
|
|
"minLowBitsAvailable().";
|
|
EXPECT_EQ(cast<LowAlign *>(extractedInner), &low);
|
|
|
|
// Also verify the HighAlign* path (tag = 0) works.
|
|
HighAlign high;
|
|
InnerPU inner2(&high);
|
|
OverClaimWrapper wrapper2(inner2);
|
|
OuterPU outer2(wrapper2);
|
|
OverClaimWrapper extracted2 = cast<OverClaimWrapper>(outer2);
|
|
EXPECT_TRUE(isa<HighAlign *>(extracted2.Value));
|
|
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
|