Introduce custom hash table data structures. (#3940)

The hash table design is heavily based on Abseil's ["Swiss
Tables"][swiss-tables] design. It uses an array of bytes storing
metadata about each entry and an array of entries where each is a pair
of key and value. The metadata byte consists of 7-bits of hash of the
key (distinct from the bits used to index the table), and one bit
indicating the presence of a special entry -- either empty or deleted.

[swiss-tables]: https://abseil.io/about/design/swisstables

There are a large range of optimizations and other nuanced aspects of
this hash table design and implementation, a good point to understand
that context is `raw_hashtable.h` which has an overview of the design
and references to various other files for relevant details.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
This commit is contained in:
Chandler Carruth
2024-06-08 01:50:02 +00:00
committed by GitHub
co-authored by josh11b
parent ffc3327959
commit 21a81bc59e
24 changed files with 6426 additions and 2 deletions
+627
View File
@@ -0,0 +1,627 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "common/map.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <initializer_list>
#include <type_traits>
#include <utility>
#include <vector>
#include "common/raw_hashtable_test_helpers.h"
namespace Carbon::Testing {
namespace {
using RawHashtable::FixedHashKeyContext;
using RawHashtable::IndexKeyContext;
using RawHashtable::TestData;
using RawHashtable::TestKeyContext;
using ::testing::Pair;
using ::testing::UnorderedElementsAreArray;
template <typename MapT, typename MatcherRangeT>
void ExpectMapElementsAre(MapT&& m, MatcherRangeT element_matchers) {
// Now collect the elements into a container.
using KeyT = typename std::remove_reference<MapT>::type::KeyT;
using ValueT = typename std::remove_reference<MapT>::type::ValueT;
std::vector<std::pair<KeyT, ValueT>> map_entries;
m.ForEach([&map_entries](KeyT& k, ValueT& v) {
map_entries.push_back({k, v});
});
// Use the GoogleMock unordered container matcher to validate and show errors
// on wrong elements.
EXPECT_THAT(map_entries, UnorderedElementsAreArray(element_matchers));
}
// Allow directly using an initializer list.
template <typename MapT, typename MatcherT>
void ExpectMapElementsAre(MapT&& m,
std::initializer_list<MatcherT> element_matchers) {
std::vector<MatcherT> element_matchers_storage = element_matchers;
ExpectMapElementsAre(m, element_matchers_storage);
}
template <typename ValueCB, typename RangeT, typename... RangeTs>
auto MakeKeyValues(ValueCB value_cb, RangeT&& range, RangeTs&&... ranges) {
using KeyT = typename RangeT::value_type;
using ValueT = decltype(value_cb(std::declval<KeyT>()));
std::vector<std::pair<KeyT, ValueT>> elements;
auto add_range = [&](RangeT&& r) {
for (const auto&& e : r) {
elements.push_back({e, value_cb(e)});
}
};
add_range(std::forward<RangeT>(range));
(add_range(std::forward<RangeT>(ranges)), ...);
return elements;
}
template <typename MapT>
class MapTest : public ::testing::Test {};
using Types = ::testing::Types<
Map<int, int>, Map<int, int, 16>, Map<int, int, 64>,
Map<int, int, 0, TestKeyContext>, Map<int, int, 16, TestKeyContext>,
Map<int, int, 64, TestKeyContext>, Map<TestData, TestData>,
Map<TestData, TestData, 16>, Map<TestData, TestData, 0, TestKeyContext>,
Map<TestData, TestData, 16, TestKeyContext>>;
TYPED_TEST_SUITE(MapTest, Types);
TYPED_TEST(MapTest, Basic) {
TypeParam m;
EXPECT_FALSE(m.Contains(42));
EXPECT_EQ(nullptr, m[42]);
EXPECT_TRUE(m.Insert(1, 100).is_inserted());
ASSERT_TRUE(m.Contains(1));
auto result = m.Lookup(1);
EXPECT_TRUE(result);
EXPECT_EQ(1, result.key());
EXPECT_EQ(100, result.value());
EXPECT_EQ(100, *m[1]);
// Reinsertion doesn't change the value.
auto i_result = m.Insert(1, 101);
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(100, i_result.value());
EXPECT_EQ(100, *m[1]);
// Update does change the value.
i_result = m.Update(1, 101);
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(101, i_result.value());
EXPECT_EQ(101, *m[1]);
// Verify all the elements.
ExpectMapElementsAre(m, {Pair(1, 101)});
// Fill up a bunch to ensure we trigger growth a few times.
for (int i : llvm::seq(2, 512)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
// Immediately do a basic check of all elements to pin down when an
// insertion corrupts the rest of the table.
ExpectMapElementsAre(
m,
MakeKeyValues([](int k) { return k * 100 + static_cast<int>(k == 1); },
llvm::seq_inclusive(1, i)));
}
for (int i : llvm::seq(1, 512)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_FALSE(m.Insert(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + static_cast<int>(i == 1), *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
}
EXPECT_FALSE(m.Contains(513));
// Verify all the elements.
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(1, 512)));
}
TYPED_TEST(MapTest, FactoryAPI) {
TypeParam m;
EXPECT_TRUE(m.Insert(1, [] { return 100; }).is_inserted());
ASSERT_TRUE(m.Contains(1));
EXPECT_EQ(100, *m[1]);
// Reinsertion doesn't invoke the callback.
EXPECT_FALSE(m.Insert(1, []() -> int {
llvm_unreachable("Should never be called!");
}).is_inserted());
// Update does invoke the callback.
auto i_result = m.Update(1, [] { return 101; });
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(101, i_result.value());
EXPECT_EQ(101, *m[1]);
}
TYPED_TEST(MapTest, Copy) {
using MapT = TypeParam;
MapT m;
// Make sure we exceed the small size for some of the map types, but not all
// of them, so we cover all the combinations of copying between small and
// large.
for (int i : llvm::seq(1, 24)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Insert(i, i * 100).is_inserted());
}
MapT other_m1 = m;
ExpectMapElementsAre(
other_m1, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 24)));
// Add some more elements to the original.
for (int i : llvm::seq(24, 32)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Insert(i, i * 100).is_inserted());
}
// The first copy doesn't change.
ExpectMapElementsAre(
other_m1, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 24)));
// A new copy does.
MapT other_m2 = m;
ExpectMapElementsAre(
other_m2, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 32)));
}
TYPED_TEST(MapTest, Move) {
using MapT = TypeParam;
MapT m;
// Make sure we exceed the small size for some of the map types, but not all
// of them, so we cover all the combinations of moving between small and
// large.
for (int i : llvm::seq(1, 24)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Insert(i, i * 100).is_inserted());
}
MapT other_m1 = std::move(m);
ExpectMapElementsAre(
other_m1, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 24)));
// Add some more elements.
for (int i : llvm::seq(24, 32)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(other_m1.Insert(i, i * 100).is_inserted());
}
ExpectMapElementsAre(
other_m1, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 32)));
}
TYPED_TEST(MapTest, Conversions) {
using MapT = TypeParam;
using KeyT = MapT::KeyT;
using ValueT = MapT::ValueT;
using KeyContextT = MapT::KeyContextT;
MapT m;
ASSERT_TRUE(m.Insert(1, 101).is_inserted());
ASSERT_TRUE(m.Insert(2, 102).is_inserted());
ASSERT_TRUE(m.Insert(3, 103).is_inserted());
ASSERT_TRUE(m.Insert(4, 104).is_inserted());
MapView<KeyT, ValueT, KeyContextT> mv = m;
MapView<const KeyT, ValueT, KeyContextT> cmv = m;
MapView<KeyT, const ValueT, KeyContextT> cmv2 = m;
MapView<const KeyT, const ValueT, KeyContextT> cmv3 = m;
EXPECT_TRUE(mv.Contains(1));
EXPECT_EQ(101, *mv[1]);
EXPECT_TRUE(cmv.Contains(2));
EXPECT_EQ(102, *cmv[2]);
EXPECT_TRUE(cmv2.Contains(3));
EXPECT_EQ(103, *cmv2[3]);
EXPECT_TRUE(cmv3.Contains(4));
EXPECT_EQ(104, *cmv3[4]);
}
// This test is largely exercising the underlying `RawHashtable` implementation
// with complex growth, erasure, and re-growth.
TYPED_TEST(MapTest, ComplexOpSequence) {
// Use a small size as well to cover more growth scenarios.
TypeParam m;
EXPECT_FALSE(m.Contains(42));
EXPECT_EQ(nullptr, m[42]);
EXPECT_TRUE(m.Insert(1, 100).is_inserted());
ASSERT_TRUE(m.Contains(1));
auto result = m.Lookup(1);
EXPECT_TRUE(result);
EXPECT_EQ(1, result.key());
EXPECT_EQ(100, result.value());
EXPECT_EQ(100, *m[1]);
// Reinsertion doesn't change the value.
auto i_result = m.Insert(1, 101);
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(100, i_result.value());
EXPECT_EQ(100, *m[1]);
// Update does change the value.
i_result = m.Update(1, 101);
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(101, i_result.value());
EXPECT_EQ(101, *m[1]);
// Verify all the elements.
ExpectMapElementsAre(m, {Pair(1, 101)});
// Fill up the small buffer but don't overflow it.
for (int i : llvm::seq(2, 5)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
}
for (int i : llvm::seq(1, 5)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100 + static_cast<int>(i == 1), *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + static_cast<int>(i == 1), *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
}
EXPECT_FALSE(m.Contains(5));
// Verify all the elements.
ExpectMapElementsAre(
m, {Pair(1, 101), Pair(2, 201), Pair(3, 301), Pair(4, 401)});
// Erase some entries from the small buffer.
EXPECT_FALSE(m.Erase(42));
EXPECT_TRUE(m.Erase(2));
EXPECT_EQ(101, *m[1]);
EXPECT_EQ(nullptr, m[2]);
EXPECT_EQ(301, *m[3]);
EXPECT_EQ(401, *m[4]);
EXPECT_TRUE(m.Erase(1));
EXPECT_EQ(nullptr, m[1]);
EXPECT_EQ(nullptr, m[2]);
EXPECT_EQ(301, *m[3]);
EXPECT_EQ(401, *m[4]);
EXPECT_TRUE(m.Erase(4));
EXPECT_EQ(nullptr, m[1]);
EXPECT_EQ(nullptr, m[2]);
EXPECT_EQ(301, *m[3]);
EXPECT_EQ(nullptr, m[4]);
// Fill them back in, but with a different order and going back to the
// original value.
EXPECT_TRUE(m.Insert(1, 100).is_inserted());
EXPECT_TRUE(m.Insert(2, 200).is_inserted());
EXPECT_TRUE(m.Insert(4, 400).is_inserted());
EXPECT_EQ(100, *m[1]);
EXPECT_EQ(200, *m[2]);
EXPECT_EQ(301, *m[3]);
EXPECT_EQ(400, *m[4]);
// Then update their values to match.
EXPECT_FALSE(m.Update(1, 101).is_inserted());
EXPECT_FALSE(m.Update(2, 201).is_inserted());
EXPECT_FALSE(m.Update(4, 401).is_inserted());
// Now fill up the first metadata group.
for (int i : llvm::seq(5, 14)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
}
for (int i : llvm::seq(1, 14)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100 + static_cast<int>(i < 5), *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + static_cast<int>(i < 5), *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + 2, *m[i]);
}
EXPECT_FALSE(m.Contains(42));
// Verify all the elements by walking the entire map.
ExpectMapElementsAre(
m, {Pair(1, 102), Pair(2, 202), Pair(3, 302), Pair(4, 402), Pair(5, 502),
Pair(6, 602), Pair(7, 702), Pair(8, 802), Pair(9, 902),
Pair(10, 1002), Pair(11, 1102), Pair(12, 1202), Pair(13, 1302)});
// Now fill up several more groups.
for (int i : llvm::seq(14, 100)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
}
for (int i : llvm::seq(1, 100)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100 + 2 * static_cast<int>(i < 14), *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + 2 * static_cast<int>(i < 14), *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 3).is_inserted());
EXPECT_EQ(i * 100 + 3, *m[i]);
}
EXPECT_FALSE(m.Contains(420));
// Check walking the entire container.
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 3; }, llvm::seq(1, 100)));
// Clear back to empty.
m.Clear();
EXPECT_FALSE(m.Contains(42));
EXPECT_EQ(nullptr, m[42]);
// Refill but with both overlapping and different values.
for (int i : llvm::seq(50, 150)) {
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
}
for (int i : llvm::seq(50, 150)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100, *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100, *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
}
EXPECT_FALSE(m.Contains(42));
EXPECT_FALSE(m.Contains(420));
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(50, 150)));
EXPECT_FALSE(m.Erase(42));
EXPECT_TRUE(m.Contains(73));
EXPECT_TRUE(m.Erase(73));
EXPECT_FALSE(m.Contains(73));
for (int i : llvm::seq(102, 136)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Contains(i));
EXPECT_TRUE(m.Erase(i));
EXPECT_FALSE(m.Contains(i));
}
for (int i : llvm::seq(50, 150)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
if (i == 73 || (i >= 102 && i < 136)) {
continue;
}
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100 + 1, *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + 2, *m[i]);
}
EXPECT_TRUE(m.Insert(73, 73 * 100 + 3).is_inserted());
EXPECT_EQ(73 * 100 + 3, *m[73]);
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 2 + (k == 73); },
llvm::seq(50, 102), llvm::seq(136, 150)));
// Reset back to empty and small.
m.Reset();
EXPECT_FALSE(m.Contains(42));
EXPECT_EQ(nullptr, m[42]);
// Refill but with both overlapping and different values, now triggering
// growth too. Also, use update instead of insert.
for (int i : llvm::seq(75, 175)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Update(i, i * 100).is_inserted());
}
for (int i : llvm::seq(75, 175)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100, *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100, *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 1).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
}
EXPECT_FALSE(m.Contains(42));
EXPECT_FALSE(m.Contains(420));
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(75, 175)));
EXPECT_FALSE(m.Erase(42));
EXPECT_TRUE(m.Contains(93));
EXPECT_TRUE(m.Erase(93));
EXPECT_FALSE(m.Contains(93));
for (int i : llvm::seq(102, 136)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Contains(i));
EXPECT_TRUE(m.Erase(i));
EXPECT_FALSE(m.Contains(i));
}
for (int i : llvm::seq(75, 175)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
if (i == 93 || (i >= 102 && i < 136)) {
continue;
}
ASSERT_TRUE(m.Contains(i));
EXPECT_EQ(i * 100 + 1, *m[i]);
EXPECT_FALSE(m.Insert(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + 1, *m[i]);
EXPECT_FALSE(m.Update(i, i * 100 + 2).is_inserted());
EXPECT_EQ(i * 100 + 2, *m[i]);
}
EXPECT_TRUE(m.Insert(93, 93 * 100 + 3).is_inserted());
EXPECT_EQ(93 * 100 + 3, *m[93]);
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 2 + (k == 93); },
llvm::seq(75, 102), llvm::seq(136, 175)));
}
template <typename MapT>
class MapCollisionTest : public ::testing::Test {};
using CollisionTypes = ::testing::Types<
Map<int, int, 16,
FixedHashKeyContext<7, /*FixIndexBits*/ true, /*FixTagBits*/ false, 0>>,
Map<int, int, 16,
FixedHashKeyContext<7, /*FixIndexBits*/ false, /*FixTagBits*/ true, 0>>,
Map<int, int, 16,
FixedHashKeyContext<7, /*FixIndexBits*/ true, /*FixTagBits*/ true, 0>>,
Map<int, int, 16,
FixedHashKeyContext<7, /*FixIndexBits*/ true, /*FixTagBits*/ true,
~static_cast<uint64_t>(0)>>>;
TYPED_TEST_SUITE(MapCollisionTest, CollisionTypes);
TYPED_TEST(MapCollisionTest, Basic) {
TypeParam m;
// Fill the map through a couple of growth steps, verifying at each step. Note
// that because this is a collision test, we synthesize actively harmful
// hashes in terms of collisions and so this test is essentially quadratic. We
// need to keep it relatively small.
for (int i : llvm::seq(1, 256)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100).is_inserted());
// Immediately do a basic check of all elements to pin down when an
// insertion corrupts the rest of the table.
ExpectMapElementsAre(m, MakeKeyValues([](int k) { return k * 100; },
llvm::seq_inclusive(1, i)));
}
EXPECT_FALSE(m.Contains(257));
// Erase and re-fill from the back.
for (int i : llvm::seq(192, 256)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Erase(i));
}
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100; }, llvm::seq(1, 192)));
for (int i : llvm::seq(192, 256)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100 + 1).is_inserted());
}
ExpectMapElementsAre(m,
MakeKeyValues([](int k) { return k * 100 + (k >= 192); },
llvm::seq(1, 256)));
// Erase and re-fill from the front.
for (int i : llvm::seq(1, 64)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Erase(i));
}
ExpectMapElementsAre(m,
MakeKeyValues([](int k) { return k * 100 + (k >= 192); },
llvm::seq(64, 256)));
for (int i : llvm::seq(1, 64)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100 + 1).is_inserted());
}
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + (k < 64) + (k >= 192); },
llvm::seq(1, 256)));
// Erase and re-fill from the middle.
for (int i : llvm::seq(64, 192)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Erase(i));
}
ExpectMapElementsAre(m, MakeKeyValues([](int k) { return k * 100 + 1; },
llvm::seq(1, 64), llvm::seq(192, 256)));
for (int i : llvm::seq(64, 192)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100 + 1).is_inserted());
}
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(1, 256)));
// Erase and re-fill from both the back and front.
for (auto s : {llvm::seq(192, 256), llvm::seq(1, 64)}) {
for (int i : s) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Erase(i));
}
}
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(64, 192)));
for (auto s : {llvm::seq(192, 256), llvm::seq(1, 64)}) {
for (int i : s) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100 + 2).is_inserted());
}
}
ExpectMapElementsAre(
m,
MakeKeyValues([](int k) { return k * 100 + 1 + (k < 64) + (k >= 192); },
llvm::seq(1, 256)));
// And update the middle elements in place.
for (int i : llvm::seq(64, 192)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_FALSE(m.Update(i, i * 100 + 2).is_inserted());
}
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 2; }, llvm::seq(1, 256)));
}
TEST(MapContextTest, Basic) {
llvm::SmallVector<TestData> keys;
for (int i : llvm::seq(0, 513)) {
keys.push_back(i * 100000);
}
IndexKeyContext<TestData> key_context(keys);
Map<ssize_t, int, 0, IndexKeyContext<TestData>> m;
EXPECT_FALSE(m.Contains(42, key_context));
EXPECT_TRUE(m.Insert(1, 100, key_context).is_inserted());
ASSERT_TRUE(m.Contains(1, key_context));
auto result = m.Lookup(TestData(100000), key_context);
EXPECT_TRUE(result);
EXPECT_EQ(1, result.key());
EXPECT_EQ(100, result.value());
// Reinsertion doesn't change the value. Also, double check a temporary
// context.
auto i_result = m.Insert(1, 101, IndexKeyContext<TestData>(keys));
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(100, i_result.value());
// Update does change the value.
i_result = m.Update(1, 101, key_context);
EXPECT_FALSE(i_result.is_inserted());
EXPECT_EQ(101, i_result.value());
// Verify all the elements.
ExpectMapElementsAre(m, {Pair(1, 101)});
// Fill up a bunch to ensure we trigger growth a few times.
for (int i : llvm::seq(2, 512)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(m.Insert(i, i * 100, key_context).is_inserted());
// Immediately do a basic check of all elements to pin down when an
// insertion corrupts the rest of the table.
for (int j : llvm::seq(1, i)) {
SCOPED_TRACE(llvm::formatv("Assert key: {0}", j).str());
ASSERT_EQ(j * 100 + static_cast<int>(j == 1),
m.Lookup(j, key_context).value());
ASSERT_EQ(j * 100 + static_cast<int>(j == 1),
m.Lookup(TestData(j * 100000), key_context).value());
}
}
for (int i : llvm::seq(1, 512)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_FALSE(m.Insert(i, i * 100 + 1, key_context).is_inserted());
EXPECT_EQ(i * 100 + static_cast<int>(i == 1),
m.Lookup(i, key_context).value());
EXPECT_FALSE(m.Update(i, i * 100 + 1, key_context).is_inserted());
EXPECT_EQ(i * 100 + 1, m.Lookup(i, key_context).value());
}
EXPECT_FALSE(m.Contains(0, key_context));
EXPECT_FALSE(m.Contains(512, key_context));
// Verify all the elements.
ExpectMapElementsAre(
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(1, 512)));
}
} // namespace
} // namespace Carbon::Testing