//===- 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; using PU3 = PointerUnion; using PU4 = PointerUnion; 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(nullptr)), f4null(static_cast(nullptr)), l4null(static_cast(nullptr)), d4null(static_cast(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(a)); EXPECT_TRUE(static_cast(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(a)); EXPECT_TRUE(isa(a)); EXPECT_TRUE(isa(b)); EXPECT_FALSE(isa(b)); EXPECT_TRUE(isa(n)); EXPECT_FALSE(isa(n)); EXPECT_TRUE(isa(i3)); EXPECT_TRUE(isa(f3)); EXPECT_TRUE(isa(l3)); EXPECT_TRUE(isa(i4)); EXPECT_TRUE(isa(f4)); EXPECT_TRUE(isa(l4)); EXPECT_TRUE(isa(d4)); EXPECT_TRUE(isa(i4null)); EXPECT_TRUE(isa(f4null)); EXPECT_TRUE(isa(l4null)); EXPECT_TRUE(isa(d4null)); } TEST_F(PointerUnionTest, Get) { EXPECT_EQ(cast(a), &f); EXPECT_EQ(cast(b), &i); EXPECT_EQ(cast(n), static_cast(nullptr)); } template struct alignas(8) Aligned {}; using PU8 = PointerUnion *, 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 *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_EQ(dyn_cast_if_present *>(a), &a0); EXPECT_EQ(*a.getAddrOfPtr1(), &a0); a = &a7; EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_FALSE(isa *>(a)); EXPECT_TRUE(isa *>(a)); EXPECT_EQ(dyn_cast_if_present *>(a), &a7); EXPECT_TRUE(a == PU8(&a7)); EXPECT_TRUE(a != PU8(&a0)); } TEST_F(PointerUnionTest, GetAddrOfPtr1) { EXPECT_TRUE(static_cast(b.getAddrOfPtr1()) == static_cast(&b)); EXPECT_TRUE(static_cast(n.getAddrOfPtr1()) == static_cast(&n)); } TEST_F(PointerUnionTest, NewCastInfra) { // test isa<> EXPECT_TRUE(isa(a)); EXPECT_TRUE(isa(b)); EXPECT_TRUE(isa(c)); EXPECT_TRUE(isa(n)); EXPECT_TRUE(isa(i3)); EXPECT_TRUE(isa(f3)); EXPECT_TRUE(isa(l3)); EXPECT_TRUE(isa(i4)); EXPECT_TRUE(isa(f4)); EXPECT_TRUE(isa(l4)); EXPECT_TRUE(isa(d4)); EXPECT_TRUE(isa(i4null)); EXPECT_TRUE(isa(f4null)); EXPECT_TRUE(isa(l4null)); EXPECT_TRUE(isa(d4null)); EXPECT_FALSE(isa(a)); EXPECT_FALSE(isa(b)); EXPECT_FALSE(isa(c)); EXPECT_FALSE(isa(n)); EXPECT_FALSE(isa(i3)); EXPECT_FALSE(isa(i3)); EXPECT_FALSE(isa(f3)); EXPECT_FALSE(isa(f3)); EXPECT_FALSE(isa(l3)); EXPECT_FALSE(isa(l3)); EXPECT_FALSE(isa(i4)); EXPECT_FALSE(isa(i4)); EXPECT_FALSE(isa(i4)); EXPECT_FALSE(isa(f4)); EXPECT_FALSE(isa(f4)); EXPECT_FALSE(isa(f4)); EXPECT_FALSE(isa(l4)); EXPECT_FALSE(isa(l4)); EXPECT_FALSE(isa(l4)); EXPECT_FALSE(isa(d4)); EXPECT_FALSE(isa(d4)); EXPECT_FALSE(isa(d4)); EXPECT_FALSE(isa(i4null)); EXPECT_FALSE(isa(i4null)); EXPECT_FALSE(isa(i4null)); EXPECT_FALSE(isa(f4null)); EXPECT_FALSE(isa(f4null)); EXPECT_FALSE(isa(f4null)); EXPECT_FALSE(isa(l4null)); EXPECT_FALSE(isa(l4null)); EXPECT_FALSE(isa(l4null)); EXPECT_FALSE(isa(d4null)); EXPECT_FALSE(isa(d4null)); EXPECT_FALSE(isa(d4null)); // test cast<> EXPECT_EQ(cast(a), &f); EXPECT_EQ(cast(b), &i); EXPECT_EQ(cast(c), &i); EXPECT_EQ(cast(i3), &i); EXPECT_EQ(cast(f3), &f); EXPECT_EQ(cast(l3), &l); EXPECT_EQ(cast(i4), &i); EXPECT_EQ(cast(f4), &f); EXPECT_EQ(cast(l4), &l); EXPECT_EQ(cast(d4), &d); // test dyn_cast EXPECT_EQ(dyn_cast(a), nullptr); EXPECT_EQ(dyn_cast(a), &f); EXPECT_EQ(dyn_cast(b), &i); EXPECT_EQ(dyn_cast(b), nullptr); EXPECT_EQ(dyn_cast(c), &i); EXPECT_EQ(dyn_cast(c), nullptr); EXPECT_EQ(dyn_cast_if_present(n), nullptr); EXPECT_EQ(dyn_cast_if_present(n), nullptr); EXPECT_EQ(dyn_cast(i3), &i); EXPECT_EQ(dyn_cast(i3), nullptr); EXPECT_EQ(dyn_cast(i3), nullptr); EXPECT_EQ(dyn_cast(f3), nullptr); EXPECT_EQ(dyn_cast(f3), &f); EXPECT_EQ(dyn_cast(f3), nullptr); EXPECT_EQ(dyn_cast(l3), nullptr); EXPECT_EQ(dyn_cast(l3), nullptr); EXPECT_EQ(dyn_cast(l3), &l); EXPECT_EQ(dyn_cast(i4), &i); EXPECT_EQ(dyn_cast(i4), nullptr); EXPECT_EQ(dyn_cast(i4), nullptr); EXPECT_EQ(dyn_cast(i4), nullptr); EXPECT_EQ(dyn_cast(f4), nullptr); EXPECT_EQ(dyn_cast(f4), &f); EXPECT_EQ(dyn_cast(f4), nullptr); EXPECT_EQ(dyn_cast(f4), nullptr); EXPECT_EQ(dyn_cast(l4), nullptr); EXPECT_EQ(dyn_cast(l4), nullptr); EXPECT_EQ(dyn_cast(l4), &l); EXPECT_EQ(dyn_cast(l4), nullptr); EXPECT_EQ(dyn_cast(d4), nullptr); EXPECT_EQ(dyn_cast(d4), nullptr); EXPECT_EQ(dyn_cast(d4), nullptr); EXPECT_EQ(dyn_cast(d4), &d); EXPECT_EQ(dyn_cast_if_present(i4null), nullptr); EXPECT_EQ(dyn_cast_if_present(i4null), nullptr); EXPECT_EQ(dyn_cast_if_present(i4null), nullptr); EXPECT_EQ(dyn_cast_if_present(i4null), nullptr); EXPECT_EQ(dyn_cast_if_present(f4null), nullptr); EXPECT_EQ(dyn_cast_if_present(f4null), nullptr); EXPECT_EQ(dyn_cast_if_present(f4null), nullptr); EXPECT_EQ(dyn_cast_if_present(f4null), nullptr); EXPECT_EQ(dyn_cast_if_present(l4null), nullptr); EXPECT_EQ(dyn_cast_if_present(l4null), nullptr); EXPECT_EQ(dyn_cast_if_present(l4null), nullptr); EXPECT_EQ(dyn_cast_if_present(l4null), nullptr); EXPECT_EQ(dyn_cast_if_present(d4null), nullptr); EXPECT_EQ(dyn_cast_if_present(d4null), nullptr); EXPECT_EQ(dyn_cast_if_present(d4null), nullptr); EXPECT_EQ(dyn_cast_if_present(d4null), nullptr); // test for const const PU4 constd4(&d); EXPECT_TRUE(isa(constd4)); EXPECT_FALSE(isa(constd4)); EXPECT_EQ(cast(constd4), &d); EXPECT_EQ(dyn_cast(constd4), nullptr); auto *result1 = cast(constd4); static_assert(std::is_same_v, "type mismatch for cast with PointerUnion"); PointerUnion constd2(&d); auto *result2 = cast(constd2); EXPECT_EQ(result2, &d); static_assert(std::is_same_v, "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 // 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 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 { static void *getAsVoidPointer(OverClaimWrapper W) { return W.getOpaqueValue(); } static OverClaimWrapper getFromVoidPointer(void *P) { return OverClaimWrapper::getFromOpaqueValue(P); } // Inner PU 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 // minLowBits = min(3, 2) = 2, tagBits = 1, tagShift = 1 // Tag for LowAlign* (index 1) is in bit 1. // NumLowBitsAvailable = 1 // Outer PU: PointerUnion // InnerPU NumLowBitsAvailable = 1 // OverClaimWrapper NumLowBitsAvailable = 2 (over-claimed) // minLowBits = 1, tagBits = 1, tagShift = 0 using InnerPU = PointerUnion; using OuterPU = PointerUnion; LowAlign low; // Store LowAlign* in the inner PU (tag = 1, in bit 1). InnerPU inner(&low); ASSERT_TRUE(isa(inner)); ASSERT_EQ(cast(inner), &low); // Wrap it and store in the outer PU. OverClaimWrapper wrapper(inner); OuterPU outer(wrapper); ASSERT_TRUE(isa(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(outer); InnerPU extractedInner = extracted.Value; EXPECT_TRUE(isa(extractedInner)) << "Inner PointerUnion tag corrupted during doCast: expected LowAlign*, " "got HighAlign*. doCast must not clear bits beyond " "minLowBitsAvailable()."; EXPECT_EQ(cast(extractedInner), &low); // Also verify the HighAlign* path (tag = 0) works. HighAlign high; InnerPU inner2(&high); OverClaimWrapper wrapper2(inner2); OuterPU outer2(wrapper2); OverClaimWrapper extracted2 = cast(outer2); EXPECT_TRUE(isa(extracted2.Value)); EXPECT_EQ(cast(extracted2.Value), &high); } //===----------------------------------------------------------------------===// // Variable-width encoding PointerUnion tests //===----------------------------------------------------------------------===// template struct alignas(4) Align4 {}; template struct alignas(8) Align8 {}; template 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<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<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<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::NumLowBitsAvailable == 0); static_assert(PointerLikeTypeTraits::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 *>(nullptr)), na1(static_cast *>(nullptr)), na2(static_cast *>(nullptr)), nb0(static_cast *>(nullptr)), nb1(static_cast *>(nullptr)) {} }; TEST_F(PointerUnion2TierTest, Isa) { // Tier 0 types EXPECT_TRUE(isa *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_TRUE(isa *>(pa1)); EXPECT_TRUE(isa *>(pa2)); // Tier 1 types EXPECT_TRUE(isa *>(pb0)); EXPECT_FALSE(isa *>(pb0)); EXPECT_FALSE(isa *>(pb0)); EXPECT_TRUE(isa *>(pb1)); EXPECT_FALSE(isa *>(pb1)); // Null pointers preserve type identity EXPECT_TRUE(isa *>(na0)); EXPECT_TRUE(isa *>(nb1)); EXPECT_FALSE(isa *>(na0)); } TEST_F(PointerUnion2TierTest, Cast) { EXPECT_EQ(cast *>(pa0), &a0); EXPECT_EQ(cast *>(pa1), &a1); EXPECT_EQ(cast *>(pa2), &a2); EXPECT_EQ(cast *>(pb0), &b0); EXPECT_EQ(cast *>(pb1), &b1); } TEST_F(PointerUnion2TierTest, DynCast) { EXPECT_EQ(dyn_cast *>(pa0), &a0); EXPECT_EQ(dyn_cast *>(pa0), nullptr); EXPECT_EQ(dyn_cast *>(pa0), nullptr); EXPECT_EQ(dyn_cast *>(pb0), &b0); EXPECT_EQ(dyn_cast *>(pb0), nullptr); // pb1 has the all-ones tag -- most likely to expose masking bugs. EXPECT_EQ(dyn_cast *>(pb1), &b1); EXPECT_EQ(dyn_cast *>(pb1), nullptr); EXPECT_EQ(dyn_cast *>(pb1), nullptr); EXPECT_EQ(dyn_cast *>(pb1), nullptr); EXPECT_EQ(dyn_cast *>(pb1), nullptr); EXPECT_EQ(dyn_cast_if_present *>(na0), nullptr); EXPECT_EQ(dyn_cast_if_present *>(na0), nullptr); EXPECT_EQ(dyn_cast_if_present *>(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(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 *>(u)); EXPECT_EQ(cast *>(u), &a0); u = &b0; EXPECT_TRUE(isa *>(u)); EXPECT_EQ(cast *>(u), &b0); u = &a2; EXPECT_TRUE(isa *>(u)); u = nullptr; EXPECT_TRUE(u.isNull()); } TEST_F(PointerUnion2TierTest, GetAddrOfPtr1) { EXPECT_TRUE(static_cast(pa0.getAddrOfPtr1()) == static_cast(&pa0)); EXPECT_TRUE(static_cast(null.getAddrOfPtr1()) == static_cast(&null)); } TEST_F(PointerUnion2TierTest, OpaqueValueRoundTrip) { void *opaque = pa0.getOpaqueValue(); PU2Tier restored = PU2Tier::getFromOpaqueValue(opaque); EXPECT_EQ(pa0, restored); EXPECT_EQ(cast *>(restored), &a0); opaque = pb0.getOpaqueValue(); restored = PU2Tier::getFromOpaqueValue(opaque); EXPECT_EQ(pb0, restored); EXPECT_EQ(cast *>(restored), &b0); opaque = pb1.getOpaqueValue(); restored = PU2Tier::getFromOpaqueValue(opaque); EXPECT_EQ(pb1, restored); EXPECT_EQ(cast *>(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 *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_FALSE(isa *>(pa0)); EXPECT_TRUE(isa *>(pb0)); EXPECT_FALSE(isa *>(pb0)); EXPECT_FALSE(isa *>(pb0)); EXPECT_TRUE(isa *>(pc0)); EXPECT_FALSE(isa *>(pc0)); EXPECT_FALSE(isa *>(pc0)); EXPECT_FALSE(isa *>(pc0)); EXPECT_TRUE(isa *>(pc1)); EXPECT_FALSE(isa *>(pc1)); } TEST_F(PointerUnion3TierTest, Cast) { EXPECT_EQ(cast *>(pa0), &a0); EXPECT_EQ(cast *>(pa1), &a1); EXPECT_EQ(cast *>(pa2), &a2); EXPECT_EQ(cast *>(pb0), &b0); EXPECT_EQ(cast *>(pc0), &c0); EXPECT_EQ(cast *>(pc1), &c1); } TEST_F(PointerUnion3TierTest, DynCast) { EXPECT_EQ(dyn_cast *>(pa0), &a0); EXPECT_EQ(dyn_cast *>(pa0), nullptr); EXPECT_EQ(dyn_cast *>(pa0), nullptr); EXPECT_EQ(dyn_cast *>(pb0), &b0); EXPECT_EQ(dyn_cast *>(pb0), nullptr); EXPECT_EQ(dyn_cast *>(pb0), nullptr); EXPECT_EQ(dyn_cast *>(pc0), &c0); EXPECT_EQ(dyn_cast *>(pc0), nullptr); EXPECT_EQ(dyn_cast *>(pc0), nullptr); EXPECT_EQ(dyn_cast *>(pc1), &c1); EXPECT_EQ(dyn_cast *>(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 *>(nullptr)); PU3Tier nb0(static_cast *>(nullptr)); PU3Tier nc0(static_cast *>(nullptr)); PU3Tier nc1(static_cast *>(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 *>(u)); EXPECT_EQ(cast *>(u), &a0); u = &b0; EXPECT_TRUE(isa *>(u)); EXPECT_EQ(cast *>(u), &b0); u = &c1; EXPECT_TRUE(isa *>(u)); EXPECT_EQ(cast *>(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 *>(restored), &b0); opaque = pc0.getOpaqueValue(); restored = PU3Tier::getFromOpaqueValue(opaque); EXPECT_EQ(pc0, restored); EXPECT_EQ(cast *>(restored), &c0); opaque = pc1.getOpaqueValue(); restored = PU3Tier::getFromOpaqueValue(opaque); EXPECT_EQ(pc1, restored); EXPECT_EQ(cast *>(restored), &c1); } TEST_F(PointerUnion3TierTest, ConstCast) { const PU3Tier cpc0(&c0); EXPECT_TRUE(isa *>(cpc0)); EXPECT_FALSE(isa *>(cpc0)); EXPECT_EQ(cast *>(cpc0), &c0); EXPECT_EQ(dyn_cast *>(cpc0), nullptr); } TEST(PointerUnionMultiTierDenseMapTest, BasicOperations) { Align4<0> a0; Align8<0> b0; Align8<1> b1; DenseMap 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 *, Align8<0> *>; static_assert(PointerLikeTypeTraits::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 *>(u)); EXPECT_FALSE(isa *>(u)); EXPECT_EQ(cast *>(u), &a); u = &b; EXPECT_TRUE(isa *>(u)); EXPECT_FALSE(isa *>(u)); EXPECT_EQ(cast *>(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<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 *>(u)); EXPECT_EQ(cast *>(u), &a0); u = &a1; EXPECT_TRUE(isa *>(u)); EXPECT_EQ(cast *>(u), &a1); u = &a2; EXPECT_TRUE(isa *>(u)); EXPECT_EQ(cast *>(u), &a2); u = &c0; EXPECT_TRUE(isa *>(u)); EXPECT_FALSE(isa *>(u)); EXPECT_EQ(cast *>(u), &c0); u = &c1; EXPECT_TRUE(isa *>(u)); EXPECT_FALSE(isa *>(u)); EXPECT_EQ(cast *>(u), &c1); // Typed nulls preserve type identity and are null. JumpPU na0(static_cast *>(nullptr)); JumpPU nc0(static_cast *>(nullptr)); JumpPU nc1(static_cast *>(nullptr)); EXPECT_TRUE(na0.isNull()); EXPECT_TRUE(nc0.isNull()); EXPECT_TRUE(nc1.isNull()); EXPECT_TRUE(isa *>(na0)); EXPECT_TRUE(isa *>(nc0)); EXPECT_TRUE(isa *>(nc1)); EXPECT_NE(na0, nc0); EXPECT_NE(nc0, nc1); } } // end anonymous namespace