blob: 377020c914a541783e10862b1d73d98bf8ede545 [file] [edit]
//===- llvm/unittest/ADT/FoldingSetTest.cpp -------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// FoldingSet unit tests.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/ArrayRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <map>
#include <memory>
#include <random>
#include <set>
#include <string>
#include <vector>
using namespace llvm;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::SizeIs;
using testing::UnorderedElementsAre;
namespace {
// Unaligned string test.
TEST(FoldingSetTest, UnalignedStringTest) {
SCOPED_TRACE("UnalignedStringTest");
FoldingSetNodeID a, b;
// An aligned string.
std::string str1 = "a test string";
a.AddString(str1);
// An unaligned string.
std::string str2 = ">" + str1;
b.AddString(str2.c_str() + 1);
EXPECT_EQ(a.computeHash(), b.computeHash());
}
TEST(FoldingSetTest, LongLongComparison) {
struct LongLongContainer : FoldingSetNode {
unsigned long long A, B;
LongLongContainer(unsigned long long A, unsigned long long B)
: A(A), B(B) {}
void Profile(FoldingSetNodeID &ID) const {
ID.AddInteger(A);
ID.AddInteger(B);
}
};
LongLongContainer C1((1ULL << 32) + 1, 1ULL);
LongLongContainer C2(1ULL, (1ULL << 32) + 1);
FoldingSet<LongLongContainer> Set;
EXPECT_EQ(&C1, Set.getOrInsert(&C1));
EXPECT_EQ(&C2, Set.getOrInsert(&C2));
EXPECT_EQ(2U, Set.size());
}
struct TrivialPair : public FoldingSetNode {
unsigned Key = 0;
unsigned Value = 0;
TrivialPair(unsigned K, unsigned V) : FoldingSetNode(), Key(K), Value(V) {}
void Profile(FoldingSetNodeID &ID) const {
ID.AddInteger(Key);
ID.AddInteger(Value);
}
bool operator==(const TrivialPair &RHS) const {
return Key == RHS.Key && Value == RHS.Value;
}
};
TEST(FoldingSetTest, IDComparison) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
Trivial.insert(&T);
FoldingSetInsertToken Token;
FoldingSetNodeID ID;
T.Profile(ID);
TrivialPair *N = Trivial.lookup(ID, Token);
EXPECT_EQ(&T, N);
EXPECT_FALSE(Token);
}
TEST(FoldingSetTest, MissedIDComparison) {
FoldingSet<TrivialPair> Trivial;
TrivialPair S(100, 42);
TrivialPair T(99, 42);
Trivial.insert(&T);
FoldingSetInsertToken Token;
FoldingSetNodeID ID;
S.Profile(ID);
TrivialPair *N = Trivial.lookup(ID, Token);
EXPECT_EQ(nullptr, N);
EXPECT_TRUE(Token);
}
TEST(FoldingSetTest, EraseNodeThatIsPresent) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
Trivial.insert(&T);
EXPECT_EQ(Trivial.size(), 1U);
bool WasThere = Trivial.erase(&T);
EXPECT_TRUE(WasThere);
EXPECT_EQ(0U, Trivial.size());
}
TEST(FoldingSetTest, EraseNodeThatIsAbsent) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
bool WasThere = Trivial.erase(&T);
EXPECT_FALSE(WasThere);
EXPECT_EQ(0U, Trivial.size());
}
TEST(FoldingSetTest, TypedApi) {
FoldingSet<TrivialPair> Set;
TrivialPair T(99, 42), TCopy(99, 42);
FoldingSetNodeID ID;
T.Profile(ID);
FoldingSetInsertToken Token;
EXPECT_EQ(nullptr, Set.lookup(ID, Token));
ASSERT_TRUE(Token);
Set.insert(&T, Token);
EXPECT_EQ(&T, Set.lookup(ID, Token));
EXPECT_FALSE(Token);
EXPECT_EQ(&T, Set.getOrInsert(&TCopy));
EXPECT_TRUE(Set.erase(&T));
EXPECT_FALSE(Set.erase(&T));
}
TEST(FoldingSetTest, GetOrInsertInserting) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
TrivialPair *N = Trivial.getOrInsert(&T);
EXPECT_EQ(&T, N);
}
TEST(FoldingSetTest, GetOrInsertGetting) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
TrivialPair T2(99, 42);
Trivial.insert(&T);
TrivialPair *N = Trivial.getOrInsert(&T2);
EXPECT_EQ(&T, N);
}
TEST(FoldingSetTest, InsertWithToken) {
FoldingSet<TrivialPair> Trivial;
FoldingSetInsertToken Token;
TrivialPair Finder(99, 42);
FoldingSetNodeID ID;
Finder.Profile(ID);
Trivial.lookup(ID, Token);
TrivialPair T(99, 42);
Trivial.insert(&T, Token);
EXPECT_EQ(1U, Trivial.size());
}
TEST(FoldingSetTest, EmptyIsTrue) {
FoldingSet<TrivialPair> Trivial;
EXPECT_TRUE(Trivial.empty());
}
TEST(FoldingSetTest, EmptyIsFalse) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
Trivial.insert(&T);
EXPECT_FALSE(Trivial.empty());
}
TEST(FoldingSetTest, ClearOnEmpty) {
FoldingSet<TrivialPair> Trivial;
Trivial.clear();
EXPECT_TRUE(Trivial.empty());
}
TEST(FoldingSetTest, ClearOnNonEmpty) {
FoldingSet<TrivialPair> Trivial;
TrivialPair T(99, 42);
Trivial.insert(&T);
Trivial.clear();
EXPECT_TRUE(Trivial.empty());
}
// 48 is the most the default 64 buckets hold; 49 is one past it.
TEST(FoldingSetTest, Reserve) {
for (unsigned Size : {0u, 1u, 2u, 48u, 49u}) {
FoldingSet<TrivialPair> Set;
Set.reserve(Size);
std::vector<std::unique_ptr<TrivialPair>> Nodes;
for (unsigned I = 0; I != Size; ++I) {
Nodes.push_back(std::make_unique<TrivialPair>(I, I));
Set.insert(Nodes.back().get());
}
ASSERT_EQ(Size, Set.size());
for (unsigned I = 0; I != Size; ++I) {
FoldingSetNodeID ID;
ID.AddInteger(I);
ID.AddInteger(I);
FoldingSetInsertToken Token;
EXPECT_EQ(Nodes[I].get(), Set.lookup(ID, Token));
}
}
}
TEST(FoldingSetTest, MoveConstructor) {
FoldingSet<TrivialPair> A;
TrivialPair T1(10, 100);
TrivialPair T2(20, 200);
A.insert(&T1);
A.insert(&T2);
EXPECT_THAT(A, SizeIs(2));
FoldingSet<TrivialPair> B(std::move(A));
EXPECT_THAT(B, SizeIs(2));
EXPECT_THAT(B, testing::Not(IsEmpty()));
FoldingSetInsertToken Token;
FoldingSetNodeID ID1, ID2;
T1.Profile(ID1);
T2.Profile(ID2);
EXPECT_EQ(&T1, B.lookup(ID1, Token));
EXPECT_EQ(&T2, B.lookup(ID2, Token));
}
TEST(FoldingSetTest, MoveAssignment) {
FoldingSet<TrivialPair> A;
FoldingSet<TrivialPair> B;
TrivialPair T1(10, 100);
TrivialPair T2(20, 200);
TrivialPair T3(30, 300);
B.insert(&T1);
A.insert(&T2);
A.insert(&T3);
B = std::move(A);
EXPECT_THAT(B, SizeIs(2));
EXPECT_THAT(B, testing::Not(IsEmpty()));
FoldingSetInsertToken Token;
FoldingSetNodeID ID2, ID3;
T2.Profile(ID2);
T3.Profile(ID3);
EXPECT_EQ(&T2, B.lookup(ID2, Token));
EXPECT_EQ(&T3, B.lookup(ID3, Token));
}
TEST(FoldingSetTest, Iterator) {
FoldingSet<TrivialPair> Set;
EXPECT_EQ(Set.begin(), Set.end());
TrivialPair T1(1, 10);
TrivialPair T2(2, 20);
TrivialPair T3(3, 30);
Set.insert(&T1);
Set.insert(&T2);
Set.insert(&T3);
EXPECT_THAT(Set, UnorderedElementsAre(T1, T2, T3));
ASSERT_NE(Set.begin(), Set.end());
auto It = Set.begin();
auto ItCopy = It++;
EXPECT_NE(It, ItCopy);
}
// FoldingSetNode has a non-zero offset here, so operator* must adjust it.
struct NonEmptyBase {
int Dummy = 0;
};
struct MultiplyInheritedNode : public NonEmptyBase, public FoldingSetNode {
void Profile(FoldingSetNodeID &ID) const { ID.AddInteger(0); }
};
TEST(FoldingSetTest, IteratorMultipleInheritance) {
FoldingSet<MultiplyInheritedNode> Set;
MultiplyInheritedNode N;
Set.insert(&N);
EXPECT_EQ(&*Set.begin(), &N);
}
TEST(FoldingSetTest, FoldingSetVectorBasic) {
FoldingSetVector<TrivialPair> Vec;
EXPECT_THAT(Vec, IsEmpty());
EXPECT_THAT(Vec, SizeIs(0));
TrivialPair T1(10, 100);
TrivialPair T1Copy(10, 100);
TrivialPair T2(20, 200);
TrivialPair T3(30, 300);
EXPECT_EQ(&T1, Vec.getOrInsert(&T1));
EXPECT_EQ(&T1, Vec.getOrInsert(&T1Copy));
EXPECT_THAT(Vec, SizeIs(1));
// Insert a new node using an insertion token.
FoldingSetNodeID ID2;
T2.Profile(ID2);
FoldingSetInsertToken Token;
EXPECT_EQ(nullptr, Vec.lookup(ID2, Token));
ASSERT_TRUE(Token);
Vec.insert(&T2, Token);
EXPECT_THAT(Vec, SizeIs(2));
EXPECT_EQ(&T3, Vec.getOrInsert(&T3));
EXPECT_THAT(Vec, SizeIs(3));
EXPECT_THAT(Vec, testing::Not(IsEmpty()));
// Verify deterministic iteration order matching insertion order.
EXPECT_THAT(Vec, ElementsAre(T1, T2, T3));
Vec.clear();
EXPECT_THAT(Vec, IsEmpty());
EXPECT_THAT(Vec, SizeIs(0));
}
struct TestContext {
unsigned Value = 0;
};
struct ContextualPair : public FoldingSetNode {
unsigned Key = 0;
unsigned Value = 0;
ContextualPair(unsigned K, unsigned V) : FoldingSetNode(), Key(K), Value(V) {}
void Profile(FoldingSetNodeID &ID, TestContext Context) const {
ID.AddInteger(Key ^ Context.Value);
ID.AddInteger(Value ^ Context.Value);
}
bool operator==(const ContextualPair &RHS) const {
return Key == RHS.Key && Value == RHS.Value;
}
};
TEST(FoldingSetTest, ContextualFoldingSetBasic) {
TestContext ContextVal{0xABCD};
ContextualFoldingSet<ContextualPair, TestContext> Set(ContextVal);
EXPECT_EQ(ContextVal.Value, Set.getContext().Value);
EXPECT_THAT(Set, IsEmpty());
EXPECT_THAT(Set, SizeIs(0));
ContextualPair T1(10, 100);
ContextualPair T1Copy(10, 100);
ContextualPair T2(20, 200);
EXPECT_EQ(&T1, Set.getOrInsert(&T1));
EXPECT_EQ(&T1, Set.getOrInsert(&T1Copy));
EXPECT_THAT(Set, SizeIs(1));
// Insert a new node using an insertion token.
FoldingSetInsertToken Token;
FoldingSetNodeID ID2;
T2.Profile(ID2, ContextVal);
EXPECT_EQ(nullptr, Set.lookup(ID2, Token));
ASSERT_TRUE(Token);
Set.insert(&T2, Token);
EXPECT_THAT(Set, SizeIs(2));
EXPECT_EQ(&T2, Set.lookup(ID2, Token));
EXPECT_THAT(Set, UnorderedElementsAre(T1, T2));
EXPECT_TRUE(Set.erase(&T1));
EXPECT_THAT(Set, SizeIs(1));
EXPECT_FALSE(Set.erase(&T1));
EXPECT_THAT(Set, UnorderedElementsAre(T2));
Set.clear();
EXPECT_THAT(Set, IsEmpty());
EXPECT_THAT(Set, SizeIs(0));
}
TEST(FoldingSetTest, SelfMoveAssignment) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(10, 100);
Set.insert(&T1);
// Route through a helper lambda to test self-move aliasing without triggering
// -Wself-move.
auto MoveAssign = [](FoldingSet<TrivialPair> &Dest,
FoldingSet<TrivialPair> &&Src) {
Dest = std::move(Src);
};
MoveAssign(Set, std::move(Set));
EXPECT_EQ(1u, Set.size());
EXPECT_FALSE(Set.empty());
}
// Exercise growth and Algorithm R shifting against a reference model.
TEST(FoldingSetTest, InsertEraseStress) {
FoldingSet<TrivialPair> Set;
std::map<unsigned, std::unique_ptr<TrivialPair>> Model;
std::mt19937 Rng(42);
for (unsigned Op = 0; Op != 1000; ++Op) {
unsigned Key = Rng() % 4096;
FoldingSetNodeID ID;
ID.AddInteger(Key);
ID.AddInteger(Key);
auto It = Model.find(Key);
if (Rng() & 1) {
FoldingSetInsertToken Token;
TrivialPair *Found = Set.lookup(ID, Token);
if (It != Model.end()) {
ASSERT_EQ(It->second.get(), Found);
continue;
}
ASSERT_EQ(nullptr, Found);
auto N = std::make_unique<TrivialPair>(Key, Key);
Set.insert(N.get(), Token);
Model.emplace(Key, std::move(N));
} else if (It != Model.end()) {
ASSERT_TRUE(Set.erase(It->second.get()));
ASSERT_FALSE(Set.erase(It->second.get()));
Model.erase(It);
}
ASSERT_EQ(Model.size(), Set.size());
}
for (const auto &KV : Model) {
FoldingSetNodeID ID;
ID.AddInteger(KV.first);
ID.AddInteger(KV.first);
FoldingSetInsertToken Token;
EXPECT_EQ(KV.second.get(), Set.lookup(ID, Token));
}
std::set<TrivialPair *> Visited;
for (TrivialPair &N : Set)
EXPECT_TRUE(Visited.insert(&N).second);
EXPECT_EQ(Model.size(), Visited.size());
}
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
TEST(FoldingSetTest, InsertInvalidatesIterators) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1), T2(2, 2);
Set.insert(&T1);
auto It = Set.begin();
Set.insert(&T2);
EXPECT_DEATH((void)It->Value, "invalid iterator access");
}
TEST(FoldingSetTest, RemoveInvalidatesIterators) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1), T2(2, 2);
Set.insert(&T1);
Set.insert(&T2);
auto It = Set.begin();
Set.erase(&T2);
EXPECT_DEATH((void)It->Value, "invalid iterator access");
}
TEST(FoldingSetTest, RemoveOfAbsentNodeKeepsIterators) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1), Absent(2, 2);
Set.insert(&T1);
auto It = Set.begin();
EXPECT_FALSE(Set.erase(&Absent));
EXPECT_EQ(&T1, &*It);
}
TEST(FoldingSetTest, ClearInvalidatesIterators) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1);
Set.insert(&T1);
auto It = Set.begin();
Set.clear();
EXPECT_DEATH((void)It->Value, "invalid iterator access");
}
TEST(FoldingSetTest, MoveInvalidatesIterators) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1);
Set.insert(&T1);
auto It = Set.begin();
FoldingSet<TrivialPair> Other(std::move(Set));
EXPECT_DEATH((void)It->Value, "invalid iterator access");
}
TEST(FoldingSetTest, IteratorComparability) {
FoldingSet<TrivialPair> Set1, Set2;
TrivialPair T1(1, 1), T2(2, 2);
Set1.insert(&T1);
Set2.insert(&T2);
EXPECT_TRUE(Set1.begin() == Set1.begin());
EXPECT_FALSE(Set1.begin() == Set1.end());
EXPECT_DEATH((void)(Set1.begin() == Set2.begin()), "incomparable iterators");
}
TEST(FoldingSetTest, InsertInvalidatesIteratorComparison) {
FoldingSet<TrivialPair> Set;
TrivialPair T1(1, 1), T2(2, 2);
Set.insert(&T1);
auto It = Set.begin();
Set.insert(&T2);
EXPECT_DEATH((void)(It == Set.end()), "incomparable iterators");
}
#endif
// The insert token is a hash, not a position, so a rehash cannot stale it.
TEST(FoldingSetTest, TokenSurvivesGrowth) {
FoldingSet<TrivialPair> Set;
TrivialPair Late(9999, 9999);
FoldingSetNodeID ID;
Late.Profile(ID);
FoldingSetInsertToken Token;
ASSERT_EQ(nullptr, Set.lookup(ID, Token));
ASSERT_TRUE(Token);
// Force several rehashes while the token is held.
std::vector<std::unique_ptr<TrivialPair>> Nodes;
for (unsigned I = 0; I != 200; ++I) {
Nodes.push_back(std::make_unique<TrivialPair>(I, I));
Set.insert(Nodes.back().get());
}
Set.insert(&Late, Token);
EXPECT_EQ(&Late, Set.lookup(ID, Token));
EXPECT_FALSE(Token);
EXPECT_EQ(201u, Set.size());
}
// FoldingSetTrait::Equals compares against the looked-up ID instead
// of rebuilding the node's profile.
struct EqNode : FoldingSetNode {
unsigned X;
explicit EqNode(unsigned X) : X(X) {}
void Profile(FoldingSetNodeID &ID) const { ID.AddInteger(X); }
void Profile(FoldingSetNodeID &ID, TestContext Ctx) const {
ID.AddInteger(X ^ Ctx.Value);
}
};
unsigned EqCalls = 0;
} // namespace
namespace llvm {
template <> struct FoldingSetTrait<EqNode> : DefaultFoldingSetTrait<EqNode> {
static bool Equals(EqNode &N, const FoldingSetNodeID &ID) {
++EqCalls;
FoldingSetNodeIDRef Ref = ID.getRef();
return Ref.size() == 1 && Ref[0] == N.X;
}
};
template <>
struct ContextualFoldingSetTrait<EqNode, TestContext>
: DefaultContextualFoldingSetTrait<EqNode, TestContext> {
static bool Equals(EqNode &N, const FoldingSetNodeID &ID, TestContext Ctx) {
++EqCalls;
FoldingSetNodeIDRef Ref = ID.getRef();
return Ref.size() == 1 && Ref[0] == (N.X ^ Ctx.Value);
}
};
} // namespace llvm
namespace {
TEST(FoldingSetTest, TraitEqualsOverride) {
FoldingSet<EqNode> Set;
EqNode N(1), NCopy(1);
Set.getOrInsert(&N);
EqCalls = 0;
EXPECT_EQ(&N, Set.getOrInsert(&NCopy));
EXPECT_EQ(1u, EqCalls);
}
TEST(FoldingSetTest, ContextualTraitEqualsOverride) {
ContextualFoldingSet<EqNode, TestContext> Set(TestContext{0xABCD});
EqNode N(1), NCopy(1);
Set.getOrInsert(&N);
EqCalls = 0;
EXPECT_EQ(&N, Set.getOrInsert(&NCopy));
EXPECT_EQ(1u, EqCalls);
}
// FoldingSetNode is a non-first base, so lookup()'s two-step cast must adjust.
struct KeyedPair : NonEmptyBase, FoldingSetNode {
unsigned A, B;
KeyedPair(unsigned A, unsigned B) : A(A), B(B) {}
std::pair<unsigned, unsigned> getKey() const { return {A, B}; }
};
TEST(UniquingSetTest, Basic) {
UniquingSet<KeyedPair> Set;
FoldingSetInsertToken Token;
EXPECT_EQ(nullptr, Set.lookup({1, 2}, Token));
EXPECT_TRUE(bool(Token));
KeyedPair A(1, 2);
Set.insert(&A, Token);
EXPECT_EQ(1u, Set.size());
// insert leaves Token set; the hit must clear it.
EXPECT_EQ(&A, Set.lookup({1, 2}, Token));
EXPECT_FALSE(bool(Token));
EXPECT_EQ(nullptr, Set.lookup({2, 1}, Token));
EXPECT_TRUE(bool(Token));
KeyedPair B(2, 1);
Set.insert(&B, Token);
KeyedPair ADup(1, 2);
EXPECT_EQ(&A, Set.getOrInsert(&ADup));
EXPECT_EQ(2u, Set.size());
std::vector<KeyedPair *> Visited;
for (KeyedPair &N : Set)
Visited.push_back(&N);
EXPECT_THAT(Visited, UnorderedElementsAre(&A, &B));
std::vector<const KeyedPair *> ConstVisited;
for (const KeyedPair &N : std::as_const(Set))
ConstVisited.push_back(&N);
EXPECT_THAT(ConstVisited, UnorderedElementsAre(&A, &B));
EXPECT_TRUE(Set.erase(&A));
EXPECT_FALSE(Set.erase(&A));
KeyedPair C(3, 4);
EXPECT_FALSE(Set.erase(&C));
EXPECT_EQ(1u, Set.size());
EXPECT_EQ(nullptr, Set.lookup({1, 2}, Token));
// getOrInsert inserts an absent node and hands back the one in the set.
EXPECT_EQ(&C, Set.getOrInsert(&C));
EXPECT_EQ(2u, Set.size());
EXPECT_EQ(&C, Set.lookup({3, 4}, Token));
EXPECT_FALSE(bool(Token));
KeyedPair CDup(3, 4);
EXPECT_EQ(&C, Set.getOrInsert(&CDup));
EXPECT_EQ(2u, Set.size());
}
// Every key hashes to NotAHash, which must be remapped so that erase() does not
// read a live node as never-inserted.
struct ZeroHashNode : FoldingSetNode {
unsigned Key;
explicit ZeroHashNode(unsigned Key) : Key(Key) {}
unsigned getKey() const { return Key; }
};
struct ZeroHashInfo : UniquingSetInfo<ZeroHashNode> {
static unsigned getHashValue(const KeyTy &) {
return FoldingSetNodeIDRef::NotAHash;
}
};
TEST(UniquingSetTest, KeyHashingToNotAHash) {
UniquingSet<ZeroHashNode, ZeroHashInfo> Set;
ZeroHashNode A(1), B(2);
FoldingSetInsertToken P;
ASSERT_EQ(nullptr, Set.lookup(1, P));
ASSERT_TRUE(bool(P));
Set.insert(&A, P);
// Same bucket, different key: the probe must walk past A.
ASSERT_EQ(nullptr, Set.lookup(2, P));
ASSERT_TRUE(bool(P));
Set.insert(&B, P);
FoldingSetInsertToken Unused;
EXPECT_EQ(&A, Set.lookup(1, Unused));
EXPECT_EQ(&B, Set.lookup(2, Unused));
EXPECT_EQ(2u, Set.size());
EXPECT_TRUE(Set.erase(&A));
EXPECT_FALSE(Set.erase(&A));
EXPECT_EQ(&B, Set.lookup(2, Unused));
EXPECT_EQ(1u, Set.size());
}
// The default Info must strip cv/ref from getKey()'s return type.
struct RefKeyNode : FoldingSetNode {
std::pair<unsigned, unsigned> Key;
RefKeyNode(unsigned A, unsigned B) : Key(A, B) {}
const std::pair<unsigned, unsigned> &getKey() const { return Key; }
};
static_assert(std::is_same_v<UniquingSetInfo<RefKeyNode>::KeyTy,
std::pair<unsigned, unsigned>>,
"KeyTy must decay to a value type");
// A node with no getKey(): the Info supplies the whole contract itself, and the
// key aliases storage owned by the node.
struct VectorNode : FoldingSetNode {
SmallVector<unsigned, 4> Elts;
explicit VectorNode(ArrayRef<unsigned> E) : Elts(E) {}
};
unsigned ShapeMismatches;
struct VectorNodeInfo {
using KeyTy = ArrayRef<unsigned>;
static KeyTy getKey(const VectorNode &N) { return N.Elts; }
// Hashing only the first element makes keys of differing length collide.
static unsigned getHashValue(const KeyTy &Key) {
return Key.empty() ? 1 : DenseMapInfo<unsigned>::getHashValue(Key[0]);
}
// Compare against the node's storage rather than building a key from it.
static bool isEqual(const KeyTy &Key, const VectorNode &N) {
if (Key.size() != N.Elts.size()) {
++ShapeMismatches;
return false;
}
return Key == KeyTy(N.Elts);
}
};
TEST(UniquingSetTest, StandaloneInfoAliasingKeyAcrossGrowth) {
UniquingSet<VectorNode, VectorNodeInfo> Set;
SmallVector<unsigned, 4> Lookup = {1, 2, 3};
FoldingSetInsertToken Token;
ASSERT_EQ(nullptr, Set.lookup(Lookup, Token));
ASSERT_TRUE(bool(Token));
std::vector<std::unique_ptr<VectorNode>> Nodes;
for (unsigned I = 0; I != 200; ++I) {
SmallVector<unsigned, 4> K = {I, I + 1};
FoldingSetInsertToken P;
ASSERT_EQ(nullptr, Set.lookup(K, P));
Nodes.push_back(std::make_unique<VectorNode>(K));
Set.insert(Nodes.back().get(), P);
}
VectorNode Late(Lookup);
Set.insert(&Late, Token);
EXPECT_EQ(201u, Set.size());
SmallVector<unsigned, 4> Again = {1, 2, 3};
FoldingSetInsertToken Unused;
EXPECT_EQ(&Late, Set.lookup(Again, Unused));
// {1, 2} and {1, 2, 3} are the only keys starting with 1, so both collide
// with this one and isEqual must reject both on length alone.
SmallVector<unsigned, 4> Longer = {1, 2, 3, 4};
ShapeMismatches = 0;
EXPECT_EQ(nullptr, Set.lookup(Longer, Unused));
EXPECT_EQ(2u, ShapeMismatches);
// getOrInsert derives the key through Info::getKey, which lookup never calls.
VectorNode Dup(Lookup);
EXPECT_EQ(&Late, Set.getOrInsert(&Dup));
EXPECT_EQ(201u, Set.size());
EXPECT_TRUE(Set.erase(&Late));
EXPECT_EQ(nullptr, Set.lookup(Again, Unused));
}
TEST(UniquingSetTest, InsertEraseStress) {
UniquingSet<KeyedPair> Set;
std::map<unsigned, std::unique_ptr<KeyedPair>> Model;
std::mt19937 Rng(42);
for (unsigned Op = 0; Op != 1000; ++Op) {
unsigned Key = Rng() % 4096;
auto It = Model.find(Key);
if (Rng() & 1) {
FoldingSetInsertToken Token;
KeyedPair *Found = Set.lookup({Key, Key}, Token);
if (It != Model.end()) {
ASSERT_EQ(It->second.get(), Found);
continue;
}
ASSERT_EQ(nullptr, Found);
auto N = std::make_unique<KeyedPair>(Key, Key);
Set.insert(N.get(), Token);
Model.emplace(Key, std::move(N));
} else if (It != Model.end()) {
ASSERT_TRUE(Set.erase(It->second.get()));
ASSERT_FALSE(Set.erase(It->second.get()));
Model.erase(It);
}
ASSERT_EQ(Model.size(), Set.size());
}
FoldingSetInsertToken P;
for (const auto &KV : Model)
EXPECT_EQ(KV.second.get(), Set.lookup({KV.first, KV.first}, P));
std::set<KeyedPair *> Visited;
for (KeyedPair &N : Set)
EXPECT_TRUE(Visited.insert(&N).second);
EXPECT_EQ(Model.size(), Visited.size());
}
} // namespace