Add some optimized data structures for maps on a small number of

keys, especially enumerated keys.
This commit is contained in:
John McCall
2024-01-28 19:32:00 -05:00
parent c98d93d75f
commit e345c85e26
7 changed files with 1174 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
//===--- EnumMap.h - A map optimized for having enum keys -------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// This file defines the EnumMap class template, which is a map data
/// structure optimized for working with enumerated keys. It is built on
/// top of SmallMap, but it replaces the default large map with a flat
/// heap-allocated array of indexes into the elements, which is reasonable
/// for small-ish enums.
///
/// Currently the map requires the key type to be an enum type.
/// The expectation is that the enum has a small number of enumerators
/// which are all in the range 0..<NumValues. NumValues must be provided
/// by specializing the EnumTraits class.
///
/// The elements of the map remain insertion-ordered for the lifetime of
/// the map. There are currently no operations to remove elements.
/// Iterators are invalidated by insertion.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_ENUMMAP_H
#define SWIFT_BASIC_ENUMMAP_H
#include "swift/Basic/EnumTraits.h"
#include "swift/Basic/SmallMap.h"
#include "llvm/ADT/SmallVector.h"
#include <type_traits>
namespace swift {
/// The maximum number of elements that the map can have before
/// it flips from brute-force searching the keys to using a sparse
/// array.
static constexpr size_t DefaultEnumMapDirectSearchLimit =
DefaultSmallMapDirectSearchLimit;
/// The primary customization point for an EnumMap.
///
/// template <>
/// struct EnumMapTraits<MyKey> {
/// using IndexType = <some integer type>;
/// struct LargeMapStorage {
/// std::optional<IndexType> find(IndexType) const;
/// std::pair<IndexType, bool> insert(IndexType key, IndexType value);
/// };
/// };
template <class Key, class KeyTraits = EnumTraits<Key>>
struct EnumMapTraits;
template <class Key, class Value,
size_t DirectSearchLimit = DefaultEnumMapDirectSearchLimit,
class MapTraits = EnumMapTraits<Key>,
class ElementStorage = llvm::SmallVector<Value>>
class EnumMap {
using IndexType = typename MapTraits::IndexType;
// EnumMapTraits is currently designed to be usable directly as a
// SmallMapTraits.
using MapType =
SmallMap<IndexType, Value, DirectSearchLimit, MapTraits, ElementStorage>;
MapType map;
public:
bool empty() const { return map.empty(); }
size_t size() const { return map.size(); }
using iterator = typename MapType::iterator;
iterator begin() { return map.begin(); }
iterator end() { return map.end(); }
using const_iterator = typename MapType::const_iterator;
const_iterator begin() const { return map.begin(); }
const_iterator end() const { return map.end(); }
/// Look up a key in the map. Returns end() if the entry is not found.
const_iterator find(Key key) const {
return map.find(IndexType(key));
}
/// Try to insert the given key/value pair. If there's already an element
/// with this key, return false and an iterator for the existing element.
/// Otherwise, return true and an iterator for the new element.
///
/// The value in the set will be constructed by emplacing it with the
/// given arguments.
template <class... Args>
std::pair<iterator, bool> insert(Key key, Args &&...valueArgs) {
return map.insert(IndexType(key), std::forward<Args>(valueArgs)...);
}
};
namespace EnumMapImpl {
template <size_t N,
bool SmallEnoughForUInt8 = (N < (1U << 8)),
bool SmallEnoughForUInt16 = (N < (1U << 16))>
struct SufficientIntFor;
template <size_t N>
struct SufficientIntFor<N, true, true> {
using type = uint8_t;
};
template <size_t N>
struct SufficientIntFor<N, false, true> {
using type = uint16_t;
};
template <size_t N>
struct SufficientIntFor<N, false, false> {
static_assert(N < (1ULL << 32), "just how large is this \"enum\" exactly");
using type = uint32_t;
};
/// A map from integers in 0..<N to integers in 0..<N, implemented as a
/// flat array of integers in 0...N, with zero meaning a missing entry.
///
/// This is a great implementation for N <= 255, where the
/// entire flat array is <= 256 bytes. It gets increasingly marginal
/// for N up to ~1K or so (unless we really expect to have entries
/// for a large proportion of the enum). Past that, we should probably
/// be falling back on something like a hashtable, because needing tens
/// of kilobytes to hold as few as 17 entries is objectively unreasonable.
template <size_t N>
class FlatMap {
public:
using IndexType = typename SufficientIntFor<N>::type;
using StoredIndexType = typename SufficientIntFor<N + 1>::type;
private:
StoredIndexType *ptr;
public:
FlatMap() : ptr(new StoredIndexType[N]) {
memset(ptr, 0, N * sizeof(StoredIndexType));
}
FlatMap(FlatMap &&other)
: ptr(other.ptr) {
other.ptr = nullptr;
}
FlatMap &operator=(FlatMap &&other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
FlatMap(const FlatMap &other)
: ptr(new StoredIndexType[N]) {
memcpy(ptr, other.ptr, N * sizeof(StoredIndexType));
}
FlatMap &operator=(const FlatMap &other) {
memcpy(ptr, other.ptr, N * sizeof(StoredIndexType));
}
~FlatMap() {
delete ptr;
}
std::pair<IndexType, bool> insert(IndexType key, IndexType value) {
assert(key < N);
StoredIndexType &entry = ptr[key];
if (entry == 0) {
entry = StoredIndexType(value) + 1;
return std::make_pair(value, true);
} else {
return std::make_pair(IndexType(entry - 1), false);
}
}
std::optional<IndexType> find(IndexType key) const {
assert(key < N);
StoredIndexType entry = ptr[key];
if (entry == 0) {
return std::nullopt;
} else {
return IndexType(entry - 1);
}
};
};
} // end namespace EnumMapImpl
/// The default implementation of EnumMapTraits.
template <class Key_, class KeyTraits_>
struct EnumMapTraits {
using Key = Key_;
using KeyTraits = KeyTraits_;
using LargeMapStorage = EnumMapImpl::FlatMap<KeyTraits::NumValues>;
using IndexType = typename LargeMapStorage::IndexType;
};
} // end namespace swift
#endif

View File

@@ -0,0 +1,34 @@
//===--- EnumTraits.h - Traits for densely-packed enums ---------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// This file defines the EnumTraits concept, which can be used to
/// communicate information about an enum type's enumerators that currently
/// can't be recovered from the compiler.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_ENUMTRAITS_H
#define SWIFT_BASIC_ENUMTRAITS_H
namespace swift {
/// A simple traits concept for recording the number of cases in an enum.
///
/// template <> class EnumTraits<WdigetKind> {
/// static constexpr size_t NumValues = NumWidgetKinds;
/// };
template <class E>
struct EnumTraits;
} // end namespace swift
#endif

View File

@@ -0,0 +1,352 @@
//===--- SmallMap.h - A map optimized for having few entries ----*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// This file defines the SmallMap data structure, which is optimized for
/// a small number of keys. The values of the map are stored in a dynamic
/// array (such a SmallVector). Iterating the map iterates these values
/// in insertion order. If the number of entries is small (not more than
/// the "direct search limit"), the keys are stored in an inline array
/// that is parallel to the elements array, and lookups brute-force search
/// this array for the key and then use the element with the same index. If
/// the number of entries grows beyond that limit, the map fall back to a
/// "large" map of keys to indexes, which defaults to a DenseMap<Key, size_t>.
///
/// There are currently no operations to remove elements.
/// Iterators are invalidated by insertion.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_SMALLMAP_H
#define SWIFT_BASIC_SMALLMAP_H
#include "swift/Basic/Range.h"
#include "swift/Basic/UninitializedArray.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include <optional>
#include <type_traits>
#include <utility>
namespace swift {
/// The maximum number of elements that the map can have before
/// it flips from brute-force searching the keys to using the
/// large map structure.
static constexpr size_t DefaultSmallMapDirectSearchLimit = 16;
/// The primary customization point for a SmallMap.
///
/// template <>
/// struct SmallMapTraits<MyKey> {
/// using IndexType = <some integer type>;
/// struct LargeMapStorage {
/// std::optional<IndexType> find(const MyKey &key) const;
/// std::pair<IndexType, bool> insert(MyKey &&key, IndexType value);
/// };
/// };
template <class Key>
struct SmallMapTraits;
template <class Key, class Value,
size_t DirectSearchLimit = DefaultSmallMapDirectSearchLimit,
class MapTraits = SmallMapTraits<Key>,
class ElementStorage = llvm::SmallVector<Value>>
class SmallMap {
using IndexType = typename MapTraits::IndexType;
using LargeMapStorage = typename MapTraits::LargeMapStorage;
using SmallMapStorage = UninitializedArray<Key, DirectSearchLimit>;
static_assert(std::is_integral_v<IndexType>,
"index type must be an integer type");
ElementStorage elements;
union {
LargeMapStorage largeMap;
SmallMapStorage smallMap;
};
bool isLarge() const {
// This only works because there are no operations to remove entries.
return elements.size() > DirectSearchLimit;
}
template <class... Args>
void initializeLargeMap(Args &&...args) {
::new ((void*) &largeMap) LargeMapStorage(std::forward<Args>(args)...);
}
void destroyLargeMap() {
largeMap.~LargeMapStorage();
}
void initializeSmallMap() {
::new ((void*) &smallMap) SmallMapStorage();
}
void destroySmallMap(size_t numElements) {
smallMap.destroy(numElements);
smallMap.~SmallMapStorage();
}
public:
SmallMap() {
initializeSmallMap();
}
SmallMap(SmallMap &&other)
: elements(std::move(other.elements)) {
// Make sure that the other object has an element count of zero.
other.elements.clear();
assert(!other.isLarge());
auto newSize = size();
bool newIsLarge = isLarge();
// Destructively move the other object's map storage to this object.
// Postcondition: the other object's map storage is in the small
// map state with zero initialized objects.
if (newIsLarge) {
initializeLargeMap(std::move(other.largeMap));
other.destroyLargeMap();
other.initializeSmallMap();
} else {
initializeSmallMap();
smallMap.destructiveMoveInitialize(std::move(other.smallMap), newSize);
}
}
SmallMap(const SmallMap &other)
: elements(other.elements) {
auto newSize = size();
bool newIsLarge = isLarge();
if (newIsLarge) {
initializeLargeMap(other.largeMap);
} else {
initializeSmallMap();
smallMap.copyInitialize(other.smallMap, newSize);
}
}
SmallMap &operator=(SmallMap &&other) {
size_t oldSize = size();
bool oldIsLarge = isLarge();
elements = std::move(other.elements);
size_t newSize = size();
bool newIsLarge = isLarge();
// Make sure that the other object has an element count of zero.
other.elements.clear();
assert(!other.isLarge());
// Move the other object's map storage to this object.
// Postcondition: the other object's map storage is in the small
// map state with zero initialized objects.
// large -> large
if (oldIsLarge && newIsLarge) {
largeMap = std::move(other.largeMap);
other.destroyLargeMap();
other.initializeSmallMap();
// large -> small
} else if (oldIsLarge) {
destroyLargeMap();
initializeSmallMap();
smallMap.destructiveMoveInitialize(std::move(other.smallMap), newSize);
// small -> large
} else if (newIsLarge) {
destroySmallMap(oldSize);
initializeLargeMap(std::move(other.largeMap));
other.destroyLargeMap();
other.initializeSmallMap();
// small -> small
} else {
smallMap.destructiveMoveAssign(std::move(other.smallMap), oldSize, newSize);
}
return *this;
}
SmallMap &operator=(const SmallMap &other) {
size_t oldSize = size();
bool oldIsLarge = isLarge();
// Copy the other object's elements to this object.
elements = other.elements;
size_t newSize = size();
bool newIsLarge = isLarge();
// Copy the other object's map storage to this object:
// large -> large
if (oldIsLarge && newIsLarge) {
largeMap = other.largeMap;
// large -> small
} else if (oldIsLarge) {
destroyLargeMap();
initializeSmallMap();
smallMap.copyInitialize(other.smallMap, newSize);
// small -> large
} else if (newIsLarge) {
destroySmallMap(oldSize);
initializeLargeMap(other.largeMap);
// small -> small
} else {
smallMap.copyAssign(other.smallMap, oldSize, newSize);
}
return *this;
}
~SmallMap() {
if (isLarge()) {
destroyLargeMap();
} else {
destroySmallMap(size());
}
}
bool empty() const { return elements.empty(); }
size_t size() const { return elements.size(); }
using iterator = typename ElementStorage::iterator;
iterator begin() { return elements.begin(); }
iterator end() { return elements.end(); }
using const_iterator = typename ElementStorage::const_iterator;
const_iterator begin() const { return elements.begin(); }
const_iterator end() const { return elements.end(); }
/// Look up a key in the map. Returns end() if the entry is not found.
const_iterator find(const Key &key) const {
if (isLarge()) {
std::optional<IndexType> result = largeMap.find(key);
if (result)
return elements.begin() + *result;
return elements.end();
}
size_t n = elements.size();
for (size_t i : range(n))
if (smallMap[i] == key)
return elements.begin() + i;
return elements.end();
}
/// Try to insert the given key/value pair. If there's already an element
/// with this key, return false and an iterator for the existing element.
/// Otherwise, return true and an iterator for the new element.
///
/// The value in the set will be constructed by emplacing it with the
/// given arguments.
template <class KeyT, class... Args>
std::pair<iterator, bool> insert(KeyT &&key, Args &&...valueArgs) {
// The current number of elements, and therefore also the index of
// the new element if we create one.
auto n = elements.size();
if (isLarge()) {
// Try to insert a map entry pointing to the potential new element.
auto result = largeMap.insert(std::forward<KeyT>(key), n);
// If we successfully inserted, emplace the new element.
if (result.second) {
assert(result.first == n);
elements.emplace_back(std::forward<Args>(valueArgs)...);
return {elements.begin() + n, true};
}
// Otherwise, return the existing value.
return {elements.begin() + result.first, false};
}
// Search the small map for the key.
for (size_t i : range(n))
if (smallMap[i] == key)
return {elements.begin() + i, false};
// If that didn't match, we have to insert. Emplace the new element.
elements.emplace_back(std::forward<Args>(valueArgs)...);
// If we aren't crossing the large-map threshold, just emplace the
// new key.
if (n < DirectSearchLimit) {
smallMap.emplace(n, std::forward<KeyT>(key));
return {elements.begin() + n, true};
}
// Otherwise, we need to transition the map from small to large.
// Move the small map aside.
assert(isLarge());
SmallMapStorage smallMapCopy;
smallMapCopy.destructiveMoveInitialize(std::move(smallMap), n);
destroySmallMap(0); // formally end lifetime
// Initialize the large map with the existing mappings taken from
// the moved-aside small map.
initializeLargeMap();
for (size_t i : range(n)) {
auto result = largeMap.insert(std::move(smallMapCopy[i]), i);
assert(result.second && result.first == i); (void) result;
}
// Add the new mapping.
auto result = largeMap.insert(std::forward<KeyT>(key), n);
assert(result.second && result.first == n); (void) result;
// Destroy the elements of the copied small map, which we moved
// into the large map but didn't *destructively* move.
smallMapCopy.destroy(n);
return {elements.begin() + n, true};
}
};
namespace SmallMapImpl {
template <class Key>
struct DefaultSmallMapTraits {
using IndexType = size_t;
struct LargeMapStorage {
llvm::DenseMap<Key, IndexType> map;
std::optional<IndexType> find(const Key &key) const {
auto it = map.find(key);
if (it == map.end()) return std::nullopt;
return it->second;
}
template <class KeyArg>
std::pair<IndexType, bool> insert(KeyArg &&key, IndexType value) {
auto result = map.insert(std::make_pair(std::forward<KeyArg>(key), value));
return std::make_pair(result.first->second, result.second);
}
};
};
} // end namespace SmallMapImpl
template <class Key>
struct SmallMapTraits : SmallMapImpl::DefaultSmallMapTraits<Key> {};
} // end namespace swift
#endif

View File

@@ -0,0 +1,156 @@
//===--- UninitializedArray.h - Array of uninitialized objects --*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// This file defines the UninitializedArray "data structure", which
/// can hold an uninitialized array of values and provides explicit
/// operations to copy, move, and destroy them.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_UNINITIALIZEDARRAY_H
#define SWIFT_BASIC_UNINITIALIZEDARRAY_H
#include <assert.h>
#include <memory>
namespace swift {
/// An array of uninitialized elements. The user is responsible for
/// ensuring that it's used properly.
template <class T, size_t N>
class UninitializedArray {
union {
T elements[N];
};
public:
UninitializedArray() {}
UninitializedArray(const UninitializedArray &other) = delete;
UninitializedArray &operator=(const UninitializedArray &other) = delete;
UninitializedArray(UninitializedArray &&other) = delete;
UninitializedArray &operator=(UninitializedArray &&other) = delete;
~UninitializedArray() {}
using iterator = T *;
using const_iterator = const T *;
iterator begin() { return elements; }
const_iterator begin() const { return elements; }
// We intentionally don't provide end() because it's too easy to use it
// accidentally when there's no guarantee that those elements exist.
template <class... Args>
T &emplace(size_t i, Args &&...args) {
assert(i < N);
return *::new ((void*) &elements[i]) T(std::forward<Args>(args)...);
}
T &operator[](size_t i) {
assert(i < N);
return elements[i];
}
const T &operator[](size_t i) const {
assert(i < N);
return elements[i];
}
/// Given that this array contains no initialized elements and the other
/// array contains at least newSize initialized elements, fill this array
/// with newSize initialized elements copied from the other array.
void copyInitialize(const UninitializedArray &other, size_t newSize) {
assert(newSize <= N);
std::uninitialized_copy(other.begin(), other.begin() + newSize, begin());
}
/// Given that this array contains oldSize initialized elements and the other
/// array contains at least newSize initialized elements, fill this array
/// with newSize initialized elements copied from the other array.
void copyAssign(const UninitializedArray &other,
size_t oldSize, size_t newSize) {
assert(oldSize <= N);
assert(newSize <= N);
auto commonSize = std::min(oldSize, newSize);
auto thisBegin = begin();
auto otherBegin = other.begin();
// Copy-assign the common prefix.
std::copy(otherBegin, otherBegin + commonSize, thisBegin);
// If there are more elements in the other array, copy-initialize those
// elements into this array starting after the common prefix.
if (oldSize < newSize) {
std::uninitialized_copy(otherBegin + commonSize, otherBegin + newSize,
thisBegin + commonSize);
// Otherwise, if there were more elements in this array, destroy the
// excess elements.
} else if (oldSize > newSize) {
std::destroy(thisBegin + commonSize, thisBegin + oldSize);
}
}
/// Given that this array contains no initialized elements and the other
/// array contains exactly newSize initialized elements, fill this array
/// with newSize initialized elements destructively moved from the other
/// array. The other array is left with no initialized elements.
void destructiveMoveInitialize(UninitializedArray &&other, size_t newSize) {
assert(newSize <= N);
auto it = std::move_iterator(other.begin());
std::uninitialized_copy(it, it + newSize, begin());
std::destroy(other.begin(), other.begin() + newSize);
}
/// Given that this array contains oldSize initialized elements and the other
/// array contains exactly newSize initialized elements, fill this array with
/// newSize initialized elements destructively moved from the other array.
/// The other array is left with no initialized elements.
void destructiveMoveAssign(UninitializedArray &&other,
size_t oldSize, size_t newSize) {
assert(oldSize <= N);
assert(newSize <= N);
auto commonSize = std::min(oldSize, newSize);
auto thisBegin = begin();
auto otherBegin = std::move_iterator(other.begin());
// Move-assign the common prefix. Note that we use a move_iterator to
// cause all these "copies" to be moves.
std::copy(otherBegin, otherBegin + commonSize, thisBegin);
// If there are more elements in the new array, move-initialize those
// elements starting after the common prefix.
if (oldSize < newSize) {
std::uninitialized_copy(otherBegin + commonSize, otherBegin + newSize,
thisBegin + commonSize);
// Otherwise, if there were more elements in this array, destroy the
// excess elements.
} else if (oldSize > newSize) {
std::destroy(thisBegin + commonSize, thisBegin + oldSize);
}
// Destroy all of the elements in the other array.
std::destroy(other.begin(), other.begin() + oldSize);
}
/// Given thait this array contains exactly oldSize initialized elements,
/// destroy those elements, leaving it with no initialized elements.
void destroy(size_t oldSize) {
assert(oldSize <= N);
std::destroy(begin(), begin() + oldSize);
}
};
} // end namespace swift
#endif

View File

@@ -14,6 +14,7 @@ add_swift_unittest(SwiftBasicTests
DemangleTest.cpp
DiverseStackTest.cpp
EditorPlaceholderTest.cpp
EnumMapTest.cpp
EncodedSequenceTest.cpp
ExponentialGrowthAppendingBinaryByteStreamTests.cpp
FileSystemTest.cpp
@@ -28,6 +29,7 @@ add_swift_unittest(SwiftBasicTests
PointerIntEnumTest.cpp
PrefixMapTest.cpp
RangeTest.cpp
SmallMapTest.cpp
SourceManagerTest.cpp
StableHasher.cpp
STLExtrasTest.cpp

View File

@@ -0,0 +1,154 @@
#include "swift/Basic/EnumMap.h"
#include "llvm/ADT/DenseSet.h"
#include "gtest/gtest.h"
using namespace swift;
namespace {
constexpr size_t MissingValue = ~(size_t) 0;
enum class A : uint16_t {
lowerBound = 0,
numValues = 1000
};
} // end anonymous namespace
template <>
struct swift::EnumTraits<A> {
static constexpr size_t NumValues = (size_t) A::numValues;
};
namespace {
static void insertSuccess(EnumMap<A, size_t> &map, size_t key, size_t value) {
auto result = map.insert(A(key), value);
EXPECT_TRUE(result.second);
EXPECT_NE(map.end(), result.first);
EXPECT_EQ(value, *result.first);
}
static void insertFailure(EnumMap<A, size_t> &map, size_t key, size_t value,
size_t actualValue) {
auto result = map.insert(A(key), value);
EXPECT_FALSE(result.second);
EXPECT_NE(map.end(), result.first);
EXPECT_EQ(actualValue, *result.first);
}
static void lookupSuccess(EnumMap<A, size_t> &map, size_t key, size_t value) {
auto result = map.find(A(key));
EXPECT_NE(map.end(), result);
EXPECT_EQ(value, *result);
}
static void lookupFailure(EnumMap<A, size_t> &map, size_t key) {
auto result = map.find(A(key));
EXPECT_EQ(map.end(), result);
}
#define INSERT_SUCCESS(KEY, VALUE) \
insertSuccess(map, KEY, VALUE)
#define INSERT_FAILURE(KEY, VALUE, ACTUAL) \
insertFailure(map, KEY, VALUE, ACTUAL)
#define LOOKUP_SUCCESS(KEY, VALUE) \
lookupSuccess(map, KEY, VALUE)
#define LOOKUP_FAILURE(KEY) \
lookupFailure(map, KEY)
struct entry {
size_t key;
size_t value;
};
static const entry globalEntries[] = {
{ 218, 110145 },
{ 361, 927012 },
{ 427, 608227 },
{ 861, 158552 },
{ 101, 466452 },
{ 391, 920472 },
{ 960, 522979 },
{ 36, 433291 },
{ 432, 110883 },
{ 752, 903125 },
{ 549, 887829 },
{ 475, 748953 },
{ 295, 214526 },
{ 533, 896211 },
{ 961, 684099 },
{ 230, 387362 },
{ 988, 205038 },
{ 980, 838945 },
{ 43, 319398 },
{ 704, 960347 },
{ 270, 837198 },
{ 611, 310181 },
{ 638, 44564 },
{ 193, 210584 },
{ 281, 620103 },
{ 682, 462845 },
{ 419, 85019 },
{ 812, 541739 },
{ 580, 266684 },
{ 559, 101634 },
{ 506, 639451 },
{ 96, 782184 },
{ 996, 927190 },
{ 392, 586071 },
{ 928, 50086 },
{ 976, 681150 },
{ 953, 172478 },
{ 863, 512828 },
{ 569, 947708 },
{ 139, 131866 },
{ 628, 884682 },
{ 877, 636903 },
{ 49, 871169 },
{ 172, 524694 },
{ 768, 211821 },
{ 104, 126356 },
{ 552, 262470 },
{ 343, 857409 },
{ 426, 535485 },
{ 84, 954703 },
{ 239, 889527 },
};
} // end anonymous namespace
TEST(EnumMap, Basic) {
EnumMap<A, size_t> map;
auto entries = llvm::makeArrayRef(globalEntries);
for (size_t iteration : range(entries.size())) {
EXPECT_EQ(iteration, map.size());
EXPECT_EQ(iteration == 0, map.empty());
// Check that previous entries are still there.
for (size_t i : range(iteration)) {
LOOKUP_SUCCESS(entries[i].key, entries[i].value);
INSERT_FAILURE(entries[i].key, MissingValue, entries[i].value);
}
// Check that later entries are not there.
for (size_t i : range(iteration, entries.size())) {
LOOKUP_FAILURE(entries[i].key);
}
INSERT_SUCCESS(entries[iteration].key, entries[iteration].value);
LOOKUP_SUCCESS(entries[iteration].key, entries[iteration].value);
}
EXPECT_EQ(entries.size(), map.size());
size_t i = 0;
for (auto &value : map) {
EXPECT_EQ(entries[i].value, value);
i++;
}
EXPECT_EQ(entries.size(), i);
}

View File

@@ -0,0 +1,273 @@
#include "swift/Basic/SmallMap.h"
#include "llvm/ADT/DenseSet.h"
#include "gtest/gtest.h"
using namespace swift;
namespace {
constexpr size_t MissingValue = ~(size_t) 0;
constexpr size_t EmptyKey = ~(size_t) 1;
constexpr size_t TombstoneKey = ~(size_t) 2;
template <class T>
struct Tracker {
llvm::DenseSet<T> set;
Tracker() = default;
Tracker(const Tracker &) = delete;
Tracker &operator=(const Tracker &) = delete;
bool empty() const {
return set.empty();
}
void insert(T value) {
EXPECT_TRUE(set.insert(value).second);
}
void check(T value) {
EXPECT_TRUE(set.contains(value));
}
void remove(T value) {
EXPECT_TRUE(set.erase(value));
}
};
class A {
Tracker<const A *> *tracker;
size_t value;
explicit A(size_t specialValue) : tracker(nullptr), value(specialValue) {}
void assignTrackers(const A &other) {
if (tracker)
tracker->check(this);
if (other.tracker)
other.tracker->check(&other);
if (tracker != other.tracker) {
if (tracker)
tracker->remove(this);
if (other.tracker)
other.tracker->insert(this);
tracker = other.tracker;
}
}
public:
static A getEmptyKey() {
return A(EmptyKey);
}
static A getTombstoneKey() {
return A(TombstoneKey);
}
A(Tracker<const A *> *tracker, size_t value) : tracker(tracker), value(value) {
if (tracker)
tracker->insert(this);
}
A(const A &other) : tracker(other.tracker), value(other.value) {
if (tracker) {
tracker->insert(this);
tracker->check(&other);
}
}
A(A &&other) : tracker(other.tracker), value(other.value) {
if (tracker) {
tracker->insert(this);
tracker->check(&other);
}
}
A &operator=(const A &other) {
assignTrackers(other);
value = other.value;
return *this;
}
A &operator=(A &&other) {
assignTrackers(other);
value = other.value;
return *this;
}
~A() {
if (tracker)
tracker->remove(this);
}
size_t getValue() const {
if (tracker)
tracker->check(this);
return value;
}
friend bool operator==(const A &lhs, const A &rhs) {
return lhs.getValue() == rhs.getValue();
}
};
static void insertSuccess(SmallMap<A, A> &map, Tracker<const A*> &tracker,
size_t key, size_t value) {
auto result = map.insert(A(&tracker, key), A(&tracker, value));
EXPECT_TRUE(result.second);
EXPECT_NE(map.end(), result.first);
EXPECT_EQ(value, result.first->getValue());
}
static void insertFailure(SmallMap<A, A> &map, Tracker<const A*> &tracker,
size_t key, size_t value, size_t actualValue) {
auto result = map.insert(A(&tracker, key), A(&tracker, value));
EXPECT_FALSE(result.second);
EXPECT_NE(map.end(), result.first);
EXPECT_EQ(actualValue, result.first->getValue());
}
static void lookupSuccess(SmallMap<A, A> &map, Tracker<const A*> &tracker,
size_t key, size_t value) {
auto result = map.find(A(&tracker, key));
EXPECT_NE(map.end(), result);
EXPECT_EQ(value, result->getValue());
}
static void lookupFailure(SmallMap<A, A> &map, Tracker<const A*> &tracker,
size_t key) {
auto result = map.find(A(&tracker, key));
EXPECT_EQ(map.end(), result);
}
#define INSERT_SUCCESS(KEY, VALUE) \
insertSuccess(map, tracker, KEY, VALUE)
#define INSERT_FAILURE(KEY, VALUE, ACTUAL) \
insertFailure(map, tracker, KEY, VALUE, ACTUAL)
#define LOOKUP_SUCCESS(KEY, VALUE) \
lookupSuccess(map, tracker, KEY, VALUE)
#define LOOKUP_FAILURE(KEY) \
lookupFailure(map, tracker, KEY)
struct entry {
size_t key;
size_t value;
};
static const entry globalEntries[] = {
{ 833286, 244010 },
{ 21885, 583865 },
{ 98803, 373843 },
{ 757849, 280197 },
{ 544837, 319456 },
{ 301715, 409382 },
{ 214164, 173603 },
{ 90472, 679461 },
{ 454735, 523445 },
{ 726077, 442142 },
{ 757356, 26085 },
{ 83528, 609269 },
{ 25506, 528950 },
{ 66693, 225472 },
{ 850311, 274721 },
{ 575211, 385129 },
{ 496336, 530893 },
{ 753928, 460664 },
{ 569603, 263213 },
{ 863114, 294890 },
{ 289913, 871387 },
{ 567663, 970826 },
{ 54922, 182147 },
{ 234275, 516764 },
{ 521608, 771620 },
{ 38169, 832007 },
{ 777822, 704626 },
{ 608984, 769469 },
{ 696833, 136927 },
{ 336429, 615964 },
{ 203555, 147525 },
{ 759946, 740892 },
{ 702926, 137033 },
{ 86701, 400847 },
{ 177435, 145944 },
{ 424806, 194239 },
{ 628673, 279972 },
{ 843621, 449262 },
{ 372083, 860665 },
{ 642760, 534411 },
{ 777604, 996069 },
{ 942048, 227549 },
{ 43009, 551907 },
{ 814924, 532395 },
{ 480414, 327500 },
{ 49853, 745810 },
{ 157379, 947358 },
{ 313310, 851746 },
{ 957411, 179233 },
{ 32217, 35134 },
{ 684458, 208518 },
{ 944720, 998758 },
{ 533638, 728837 },
{ 670556, 946584 },
{ 466090, 456504 },
{ 213558, 326747 },
{ 967293, 15416 },
{ 370014, 356011 },
};
} // end anonymous namespace
TEST(SmallMap, Basic) {
Tracker<const A *> tracker;
{
SmallMap<A, A> map;
auto entries = llvm::makeArrayRef(globalEntries);
for (size_t iteration : range(entries.size())) {
EXPECT_EQ(iteration, map.size());
EXPECT_EQ(iteration == 0, map.empty());
// Check that previous entries are still there.
for (size_t i : range(iteration)) {
LOOKUP_SUCCESS(entries[i].key, entries[i].value);
INSERT_FAILURE(entries[i].key, MissingValue, entries[i].value);
}
// Check that later entries are not there.
for (size_t i : range(iteration, entries.size())) {
LOOKUP_FAILURE(entries[i].key);
}
INSERT_SUCCESS(entries[iteration].key, entries[iteration].value);
LOOKUP_SUCCESS(entries[iteration].key, entries[iteration].value);
}
EXPECT_EQ(entries.size(), map.size());
size_t i = 0;
for (auto &value : map) {
EXPECT_EQ(entries[i].value, value.getValue());
i++;
}
EXPECT_EQ(entries.size(), i);
}
EXPECT_TRUE(tracker.empty());
}
template <>
struct llvm::DenseMapInfo<A> {
static inline A getEmptyKey() {
return A::getEmptyKey();
}
static inline A getTombstoneKey() {
return A::getTombstoneKey();
}
static unsigned getHashValue(const A &val) {
return val.getValue();
}
static bool isEqual(const A &lhs, const A &rhs) {
return lhs == rhs;
}
};