blob: 5280ca2d0bfebae1d96b15fd2c8bc9280eec5d6a [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
namespace JsUtil
{
interface IWeakReferenceDictionary
{
virtual void Cleanup() = 0;
};
template <
class TKey,
class TValue,
class SizePolicy = PowerOf2SizePolicy,
template <typename ValueOrKey> class Comparer = DefaultComparer
>
class WeakReferenceDictionary: public BaseDictionary<TKey, RecyclerWeakReference<TValue>*, RecyclerNonLeafAllocator, SizePolicy, Comparer, WeakRefValueDictionaryEntry>,
public IWeakReferenceDictionary
{
typedef BaseDictionary<TKey, RecyclerWeakReference<TValue>*, RecyclerNonLeafAllocator, SizePolicy, Comparer, WeakRefValueDictionaryEntry> Base;
typedef typename Base::EntryType EntryType;
public:
WeakReferenceDictionary(Recycler* recycler, int capacity = 0):
Base(recycler, capacity)
{
Assert(reinterpret_cast<void*>(this) == reinterpret_cast<void*>((IWeakReferenceDictionary*) this));
}
virtual void Cleanup() override
{
this->MapAndRemoveIf([](typename Base::EntryType &entry)
{
return (Base::EntryType::NeedsCleanup(entry));
});
}
private:
using Base::Clone;
using Base::Copy;
PREVENT_COPY(WeakReferenceDictionary);
};
#if ENABLE_WEAK_REFERENCE_REGIONS
template<bool, typename T = void>
struct enable_if {};
template<typename T>
struct enable_if<true, T>
{
typedef T type;
};
template <
class TEntry,
class TWeak,
bool keyIsWeak,
class SizePolicy = PowerOf2SizePolicy,
template <typename ValueOrKey> class Comparer = DefaultComparer,
typename Lock = NoResizeLock,
class AllocType = Recycler // Should always be recycler; this is to sufficiently confuse the RecyclerChecker
>
class SplitWeakDictionary : protected Lock, public IWeakReferenceDictionary
{
private:
template <typename T1, typename T2, bool second>
struct Select;
template <typename T1, typename T2>
struct Select<T1, T2, false>
{
typedef T1 type;
};
template <typename T1, typename T2>
struct Select<T1, T2, true>
{
typedef T2 type;
};
typedef typename Select<TEntry, TWeak, keyIsWeak>::type TBKey;
typedef typename Select<TEntry, TWeak, !keyIsWeak>::type TBValue;
typedef typename AllocatorInfo<Recycler, TEntry>::AllocatorFunc EntryAllocatorFuncType;
enum InsertOperations
{
Insert_Add, // FatalInternalError if the item already exist in debug build
Insert_AddNew, // Ignore add if the item already exist
Insert_Item // Replace the item if it already exist
};
protected:
typedef ValueEntry<TEntry> EntryType;
typedef RecyclerWeakReferenceRegionItem<TWeak> WeakType;
Field(int*) buckets;
Field(EntryType*) entries;
Field(WeakType*) weakRefs;
FieldNoBarrier(Recycler*) alloc;
Field(int) size;
Field(uint) bucketCount;
Field(int) count;
Field(int) freeList;
Field(int) freeCount;
Field(int) modFunctionIndex;
private:
static const int FreeListSentinel = -2;
PREVENT_COPY(SplitWeakDictionary);
class AutoDoResize
{
public:
AutoDoResize(Lock& lock) : lock(lock) { lock.BeginResize(); };
~AutoDoResize() { lock.EndResize(); };
private:
Lock & lock;
};
public:
// Allow SplitWeakDictionary field to be inlined in classes with DEFINE_VTABLE_CTOR_MEMBER_INIT
SplitWeakDictionary(VirtualTableInfoCtorEnum) { }
SplitWeakDictionary(Recycler* allocator, int capacity = 0)
: buckets(nullptr),
entries(nullptr),
weakRefs(nullptr),
alloc(allocator),
size(0),
bucketCount(0),
count(0),
freeList(0),
freeCount(0),
modFunctionIndex(UNKNOWN_MOD_INDEX)
{
Assert(allocator);
Assert(reinterpret_cast<void*>(this) == reinterpret_cast<void*>((IWeakReferenceDictionary*)this));
// If initial capacity is negative or 0, lazy initialization on
// the first insert operation is performed.
if (capacity > 0)
{
Initialize(capacity);
}
}
virtual void Cleanup() override
{
this->MapAndRemoveIf([](EntryType& entry, WeakType& weakRef)
{
return weakRef == nullptr;
});
}
inline int Capacity() const
{
return size;
}
inline int Count() const
{
return count - freeCount;
}
void Clear()
{
if (count > 0)
{
memset(buckets, -1, bucketCount * sizeof(buckets[0]));
memset(entries, 0, sizeof(EntryType) * size);
memset(weakRefs, 0, sizeof(WeakType) * size); // TODO: issues with background threads?
count = 0;
freeCount = 0;
}
}
void ResetNoDelete()
{
this->size = 0;
this->bucketCount = 0;
this->buckets = nullptr;
this->entries = nullptr;
this->weakRefs = nullptr;
this->count = 0;
this->freeCount = 0;
this->modFunctionIndex = UNKNOWN_MOD_INDEX;
}
void Reset()
{
ResetNoDelete();
}
// Returns whether the dictionary was resized or not
bool EnsureCapacity()
{
if (freeCount == 0 && count == size)
{
Resize();
return true;
}
return false;
}
int GetNextIndex()
{
if (freeCount != 0)
{
Assert(freeCount > 0);
Assert(freeList >= 0);
Assert(freeList < count);
return freeList;
}
return count;
}
TBValue Item(const TBKey& key)
{
int i = FindEntry(key);
Assert(i >= 0);
return values[i];
}
TBValue Item(const TBKey& key) const
{
int i = FindEntry(key);
Assert(i >= 0);
return values[i];
}
int Add(const TBKey& key, const TBValue& value)
{
return Insert<Insert_Add>(key, value);
}
int AddNew(const TBKey& key, const TBValue& value)
{
return Insert<Insert_AddNew>(key, value);
}
int Item(const TBKey& key, const TBValue& value)
{
return Insert<Insert_Item>(key, value);
}
bool Contains(KeyValuePair<TBKey, TBValue> keyValuePair)
{
int i = FindEntry(keyValuePair.Key());
TBValue val = this->GetValue(i);
if (i >= 0 && Comparer<TBValue>::Equals(val, keyValuePair.Value()))
{
return true;
}
return false;
}
bool ContainsKey(const TBKey& key) const
{
return FindEntry(key) >= 0;
}
template <typename TLookup>
inline const TBValue& LookupWithKey(const TLookup& key, const TBValue& defaultValue)
{
int i = FindEntryWithKey(key);
if (i >= 0)
{
return this->GetValue(i);
}
return defaultValue;
}
inline const TBValue& Lookup(const TBKey& key, const TBValue& defaultValue)
{
return LookupWithKey<TBKey>(key, defaultValue);
}
template <typename TLookup>
bool TryGetValue(const TLookup& key, TBValue* value)
{
int i = FindEntryWithKey(key);
if (i >= 0)
{
*value = this->GetValue(i);
return true;
}
return false;
}
bool TryGetValueAndRemove(const TBKey& key, TBValue* value)
{
int i, last;
uint targetBucket;
if (FindEntryWithKey(key, &i, &last, &targetBucket))
{
*value = this->GetValue(i);
RemoveAt(i, last, targetBucket);
return true;
}
return false;
}
bool Remove(const TBKey& key)
{
int i, last;
uint targetBucket;
if (FindEntryWithKey(key, &i, &last, &targetBucket))
{
RemoveAt(i, last, targetBucket);
return true;
}
return false;
}
bool Remove(KeyValuePair<TBKey, TBValue> keyValuePair)
{
int i, last;
uint targetBucket;
if (FindEntryWithKey(keyValuePair.Key(), &i, &last, &targetBucket))
{
const TBValue& value = this->GetValue(i);
if (Comparer<TBValue>::Equals(value, keyValuePair.Value()))
{
RemoveAt(i, last, targetBucket);
return true;
}
}
return false;
}
int GetLastIndex()
{
return count - 1;
}
void LockResize()
{
__super::LockResize();
}
void UnlockResize()
{
__super::UnlockResize();
}
protected:
template<class Fn>
void MapAndRemoveIf(Fn fn)
{
for (uint i = 0; i < bucketCount; i++)
{
if (buckets[i] != -1)
{
for (int currentIndex = buckets[i], lastIndex = -1; currentIndex != -1;)
{
// If the predicate says we should remove this item
if (fn(entries[currentIndex], weakRefs[currentIndex]) == true)
{
const int nextIndex = entries[currentIndex].next;
RemoveAt(currentIndex, lastIndex, i);
currentIndex = nextIndex;
}
else
{
lastIndex = currentIndex;
currentIndex = entries[currentIndex].next;
}
}
}
}
}
template <typename TLookup>
static hash_t GetHashCodeWithKey(const TLookup& key)
{
// set last bit to 1 to avoid false positive to make hash appears to be a valid recycler address.
// In the same line, 0 should be use to indicate a non-existing entry.
return TAGHASH(Comparer<TLookup>::GetHashCode(key));
}
static uint GetBucket(hash_t hashCode, int bucketCount, int modFunctionIndex)
{
return SizePolicy::GetBucket(UNTAGHASH(hashCode), bucketCount, modFunctionIndex);
}
uint GetBucket(hash_t hashCode) const
{
return GetBucket(hashCode, this->bucketCount, modFunctionIndex);
}
static bool IsFreeEntry(const EntryType &entry)
{
// A free entry's next index will be (FreeListSentinel - nextIndex), such that it is always <= FreeListSentinel, for fast entry iteration
// allowing for skipping over free entries. -1 is reserved for the end-of-chain marker for a used entry.
return entry.next <= FreeListSentinel;
}
void SetNextFreeEntryIndex(EntryType &freeEntry, const int nextFreeEntryIndex)
{
Assert(!IsFreeEntry(freeEntry));
Assert(nextFreeEntryIndex >= -1);
Assert(nextFreeEntryIndex < count);
// The last entry in the free list chain will have a next of FreeListSentinel to indicate that it is a free entry. The end of the
// free list chain is identified using freeCount.
freeEntry.next = nextFreeEntryIndex >= 0 ? FreeListSentinel - nextFreeEntryIndex : FreeListSentinel;
}
static int GetNextFreeEntryIndex(const EntryType &freeEntry)
{
Assert(IsFreeEntry(freeEntry));
return FreeListSentinel - freeEntry.next;
}
void Initialize(int capacity)
{
// minimum capacity is 4
int initSize = max(capacity, 4);
int modIndex = UNKNOWN_MOD_INDEX;
uint initBucketCount = SizePolicy::GetBucketSize(initSize, &modIndex);
AssertMsg(initBucketCount > 0, "Size returned by policy should be greater than 0");
int* newBuckets = nullptr;
EntryType* newEntries = nullptr;
WeakType* newWeakRefs = nullptr;
Allocate(&newBuckets, &newEntries, &newWeakRefs, initBucketCount, initSize);
// Allocation can throw - assign only after allocation has succeeded.
this->buckets = newBuckets;
this->entries = newEntries;
this->weakRefs = newWeakRefs;
this->bucketCount = initBucketCount;
this->size = initSize;
this->modFunctionIndex = modIndex;
Assert(this->freeCount == 0);
}
inline void RemoveAt(const int i, const int last, const uint targetBucket)
{
if (last < 0)
{
buckets[targetBucket] = entries[i].next;
}
else
{
entries[last].next = entries[i].next;
}
entries[i].Clear();
weakRefs[i].Clear();
SetNextFreeEntryIndex(entries[i], freeCount == 0 ? -1 : freeList);
freeList = i;
freeCount++;
}
inline int FindEntry(const TBKey& key)
{
return FindEntryWithKey<TBKey>(key);
}
template <typename LookupType>
inline int FindEntryWithKey(const LookupType& key)
{
int * localBuckets = buckets;
if (localBuckets != nullptr)
{
hash_t hashCode = GetHashCodeWithKey<LookupType>(key);
uint targetBucket = this->GetBucket(hashCode);
EntryType * localEntries = entries;
for (int i = localBuckets[targetBucket], last = -1; i >= 0;)
{
TBKey k = this->GetKey(i);
if (this->RemoveIfCollected(k, &i, last, targetBucket))
{
continue;
}
if (Comparer<TBKey>::Equals(k, key))
{
return i;
}
last = i;
i = localEntries[i].next;
}
}
return -1;
}
template <typename LookupType>
inline bool FindEntryWithKey(const LookupType& key, int *const i, int *const last, uint *const targetBucket)
{
int * localBuckets = buckets;
if (localBuckets != nullptr)
{
hash_t hashCode = GetHashCodeWithKey<LookupType>(key);
*targetBucket = this->GetBucket(hashCode);
*last = -1;
EntryType * localEntries = entries;
for (*i = localBuckets[*targetBucket]; *i >= 0;)
{
TBKey k = this->GetKey(*i);
if (this->RemoveIfCollected(k, i, *last, *targetBucket))
{
continue;
}
if (Comparer<TBKey>::Equals(k, key))
{
return true;
}
*last = *i;
*i = localEntries[*i].next;
}
}
return false;
}
private:
void Resize()
{
AutoDoResize autoDoResize(*this);
__analysis_assert(count > 1);
int newSize = SizePolicy::GetNextSize(count);
int modIndex = UNKNOWN_MOD_INDEX;
uint newBucketCount = SizePolicy::GetBucketSize(newSize, &modIndex);
__analysis_assume(newSize > count);
int* newBuckets = nullptr;
EntryType* newEntries = nullptr;
WeakType* newWeakRefs = nullptr;
if (newBucketCount == bucketCount)
{
// no need to rehash
newEntries = AllocateEntries(newSize);
newWeakRefs = AllocateWeakRefs(newSize);
CopyArray<EntryType>(newEntries, newSize, entries, count);
CopyArray<WeakType>(newWeakRefs, newSize, weakRefs, count); // TODO: concurrency issues?
this->entries = newEntries;
this->weakRefs = newWeakRefs;
this->size = newSize;
this->modFunctionIndex = modIndex;
return;
}
Allocate(&newBuckets, &newEntries, &newWeakRefs, newBucketCount, newSize);
this->modFunctionIndex = modIndex;
// Need to re-bucket the entries
// Also need to account for whether the weak refs have been collected
// Have to go in order so we can remove entries as appropriate
this->count = 0;
for (uint i = 0; i < this->bucketCount; ++i)
{
if (this->buckets[i] < 0)
{
continue;
}
for (int currentEntry = this->buckets[i]; currentEntry != -1; )
{
if (IsFreeEntry(entries[currentEntry]))
{
// Entry is free; this shouldn't happen, but stop following the chain
AssertMsg(false, "Following bucket chains should not result in free entries");
break;
}
if (this->weakRefs[currentEntry] == nullptr)
{
// The weak ref has been collected; don't bother bringing it to the new collection
currentEntry = this->entries[currentEntry].next;
continue;
}
hash_t hashCode = GetHashCodeWithKey<TBKey>(this->GetKey(currentEntry));
int bucket = GetBucket(hashCode, newBucketCount, modFunctionIndex);
newEntries[count].next = newBuckets[bucket];
newEntries[count].SetValue(this->entries[currentEntry].Value());
newWeakRefs[count] = this->weakRefs[currentEntry];
newBuckets[bucket] = count;
++count;
currentEntry = this->entries[currentEntry].next;
}
}
this->freeCount = 0;
this->freeList = 0;
this->buckets = newBuckets;
this->entries = newEntries;
this->weakRefs = newWeakRefs;
this->bucketCount = newBucketCount;
this->size = newSize;
}
template <InsertOperations op>
int Insert(const TBKey& key, const TBValue& value)
{
int * localBuckets = buckets;
if (localBuckets == nullptr)
{
Initialize(0);
localBuckets = buckets;
}
#if DBG
// Always search and verify
const bool needSearch = true;
#else
const bool needSearch = (op != Insert_Add);
#endif
hash_t hashCode = GetHashCodeWithKey<TBKey>(key);
uint targetBucket = this->GetBucket(hashCode);
if (needSearch)
{
EntryType * localEntries = entries;
for (int i = localBuckets[targetBucket], last = -1; i >= 0;)
{
TBKey k = this->GetKey(i);
if (this->RemoveIfCollected(k, &i, last, targetBucket))
{
continue;
}
if (Comparer<TBKey>::Equals(k, key))
{
Assert(op != Insert_Add);
if (op == Insert_Item)
{
SetValue(value, i);
return i;
}
return -1;
}
last = i;
i = localEntries[i].next;
}
}
// Ideally we'd do cleanup only if weak references have been collected since the last resize
// but that would require us to use an additional field to store the last recycler cleanup id
// that we saw
// We can add that optimization later if we have to.
if (freeCount == 0 && count == size)
{
this->Cleanup();
}
int index;
if (freeCount != 0)
{
Assert(freeCount > 0);
Assert(freeList >= 0);
Assert(freeList < count);
index = freeList;
freeCount--;
if (freeCount != 0)
{
freeList = GetNextFreeEntryIndex(entries[index]);
}
}
else
{
// If there's nothing free, then in general, we set index to count, and increment count
// If we resize, we also need to recalculate the target
// However, if cleanup is supported, then before resize, we should try and clean up and see
// if something got freed, and if it did, reuse that index
if (count == size)
{
Resize();
targetBucket = this->GetBucket(hashCode);
index = count;
count++;
}
else
{
index = count;
count++;
}
Assert(count <= size);
Assert(index < size);
}
SetKeyValue(key, value, index);
entries[index].next = buckets[targetBucket];
buckets[targetBucket] = index;
return index;
}
__ecount(bucketCount) int *AllocateBuckets(DECLSPEC_GUARD_OVERFLOW const uint bucketCount)
{
return
AllocateArray<AllocType, int, false>(
TRACK_ALLOC_INFO(alloc, int, AllocType, 0, bucketCount),
TypeAllocatorFunc<AllocType, int>::GetAllocFunc(),
bucketCount);
}
__ecount(size) EntryType * AllocateEntries(DECLSPEC_GUARD_OVERFLOW int size, const bool zeroAllocate = true)
{
// Note that the choice of leaf/non-leaf node is decided for the EntryType on the basis of TValue. By default, if
// TValue is a pointer, a non-leaf allocation is done. This behavior can be overridden by specializing
// TypeAllocatorFunc for TValue.
return
AllocateArray<Recycler, EntryType, false>(
TRACK_ALLOC_INFO(alloc, EntryType, Recycler, 0, size),
zeroAllocate ? EntryAllocatorFuncType::GetAllocZeroFunc() : EntryAllocatorFuncType::GetAllocFunc(),
size);
}
__ecount(size) WeakType * AllocateWeakRefs(DECLSPEC_GUARD_OVERFLOW int size)
{
return alloc->CreateWeakReferenceRegion<TWeak>(size);
}
void Allocate(__deref_out_ecount(bucketCount) int** ppBuckets, __deref_out_ecount(size) EntryType** ppEntries, __deref_out_ecount(size) WeakType** ppWeakRefs, DECLSPEC_GUARD_OVERFLOW uint bucketCount, DECLSPEC_GUARD_OVERFLOW int size)
{
int *const newBuckets = AllocateBuckets(bucketCount);
Assert(newBuckets); // no-throw allocators are currently not supported
EntryType *newEntries = AllocateEntries(size);
Assert(newEntries); // no-throw allocators are currently not supported
WeakType * newWeakRefs = AllocateWeakRefs(size);
memset(newBuckets, -1, bucketCount * sizeof(newBuckets[0]));
*ppBuckets = newBuckets;
*ppEntries = newEntries;
*ppWeakRefs = newWeakRefs;
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<weakKey, TBKey>::type GetKey(int index) const
{
return this->weakRefs[index];
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<!weakKey, TBKey>::type GetKey(int index) const
{
return this->entries[index].Value();
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<weakKey, TBValue>::type GetValue(int index) const
{
return this->entries[index].Value();
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<!weakKey, TBValue>::type GetValue(int index) const
{
return this->weakRefs[index];
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<weakKey, bool>::type RemoveIfCollected(TBKey const key, int* i, int last, int targetBucket)
{
if (key == nullptr)
{
// Key has been collected
int next = entries[*i].next;
RemoveAt(*i, last, targetBucket);
*i = next;
return true;
}
return false;
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<!weakKey, bool>::type RemoveIfCollected(TBKey const key, int* i, int last, int targetBucket)
{
return false;
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<weakKey, void>::type SetKeyValue(const TBKey& key, const TBValue& value, int index)
{
weakRefs[index] = key;
entries[index].SetValue(value);
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<!weakKey, void>::type SetKeyValue(const TBKey& key, const TBValue& value, int index)
{
entries[index].SetValue(key);
weakRefs[index] = value;
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<weakKey, void>::type SetValue(const TBValue& value, int index)
{
this->entries[index].SetValue(value);
}
template <bool weakKey = keyIsWeak>
inline typename enable_if<!weakKey, void>::type SetValue(const TBValue& value, int index)
{
this->weakRefs[index] = value;
}
};
template <
class TKey,
class TValue,
class SizePolicy = PowerOf2SizePolicy,
template <typename ValueOrKey> class Comparer = DefaultComparer,
typename Lock = NoResizeLock
>
class WeakReferenceRegionDictionary : public SplitWeakDictionary<TKey, TValue, false, SizePolicy, Comparer, Lock, Recycler>
{
typedef SplitWeakDictionary<TKey, TValue, false, SizePolicy, Comparer, Lock, Recycler> Base;
typedef typename Base::EntryType EntryType;
typedef typename Base::WeakType ValueType;
public:
WeakReferenceRegionDictionary(Recycler* allocator, int capacity = 0)
: Base(allocator, capacity)
{
}
};
template <
class TKey,
class TValue,
template <typename ValueOrKey> class Comparer = DefaultComparer,
class SizePolicy = PrimeSizePolicy,
typename Lock = NoResizeLock
>
class WeakReferenceRegionKeyDictionary : public SplitWeakDictionary<TValue, TKey, true, SizePolicy, Comparer, Lock, Recycler>
{
typedef SplitWeakDictionary<TValue, TKey, true, SizePolicy, Comparer, Lock, Recycler> Base;
typedef typename Base::EntryType EntryType;
typedef typename Base::WeakType KeyType;
public:
// Allow WeakReferenceRegionKeyDictionary field to be inlined in classes with DEFINE_VTABLE_CTOR_MEMBER_INIT
WeakReferenceRegionKeyDictionary(VirtualTableInfoCtorEnum v): Base(v) { }
WeakReferenceRegionKeyDictionary(Recycler* allocator, int capacity = 0)
: Base(allocator, capacity)
{
}
template <class Fn>
void Map(Fn fn)
{
this->MapAndRemoveIf([=](EntryType& entry, KeyType& weakRef)
{
if (weakRef == nullptr)
{
return true;
}
fn(weakRef, entry.Value(), weakRef);
return false;
});
}
bool Clean()
{
this->Cleanup();
return this->freeCount > 0;
}
};
#endif // ENABLE_WEAK_REFERENCE_REGIONS
};