Dictionary.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #pragma once
  6. namespace JsUtil
  7. {
  8. template <class TKey, class TValue> class WeakRefDictionaryEntry
  9. {
  10. public:
  11. static const int INVALID_HASH_VALUE = 0;
  12. hash_t hash; // Lower 31 bits of hash code << 1 | 1, 0 if unused
  13. int next; // Index of next entry, -1 if last
  14. Field(const RecyclerWeakReference<TKey>*) key; // Key of entry- this entry holds a weak reference to the key
  15. TValue value; // Value of entry
  16. };
  17. // TODO: convert to BaseDictionary- easier now to have custom dictionary since this does compacting
  18. // and weak reference resolution
  19. template <class TKey, class TValue, class KeyComparer = DefaultComparer<const TKey*>, bool cleanOnInsert = true> class WeaklyReferencedKeyDictionary
  20. {
  21. public:
  22. typedef WeakRefDictionaryEntry<TKey, Field(TValue)> EntryType;
  23. typedef TKey KeyType;
  24. typedef TValue ValueType;
  25. typedef void (*EntryRemovalCallbackMethodType)(const EntryType& e, void* cookie);
  26. struct EntryRemovalCallback
  27. {
  28. FieldNoBarrier(EntryRemovalCallbackMethodType) fnCallback;
  29. Field(void*) cookie;
  30. };
  31. private:
  32. Field(int) size;
  33. Field(int*) buckets;
  34. Field(EntryType *) entries;
  35. Field(int) count;
  36. Field(int) version;
  37. Field(int) freeList;
  38. Field(int) freeCount;
  39. FieldNoBarrier(Recycler*) recycler;
  40. FieldNoBarrier(EntryRemovalCallback) entryRemovalCallback;
  41. Field(uint) lastWeakReferenceCleanupId;
  42. Field(bool) disableCleanup;
  43. Field(int) modFunctionIndex;
  44. public:
  45. // Allow WeaklyReferencedKeyDictionary field to be inlined in classes with DEFINE_VTABLE_CTOR_MEMBER_INIT
  46. WeaklyReferencedKeyDictionary(VirtualTableInfoCtorEnum) { }
  47. WeaklyReferencedKeyDictionary(Recycler* recycler, int capacity = 0, EntryRemovalCallback* pEntryRemovalCallback = nullptr):
  48. buckets(nullptr),
  49. size(0),
  50. entries(nullptr),
  51. count(0),
  52. version(0),
  53. freeList(0),
  54. freeCount(0),
  55. recycler(recycler),
  56. lastWeakReferenceCleanupId(recycler->GetWeakReferenceCleanupId()),
  57. disableCleanup(false),
  58. modFunctionIndex(UNKNOWN_MOD_INDEX)
  59. {
  60. if (pEntryRemovalCallback != nullptr)
  61. {
  62. this->entryRemovalCallback.fnCallback = pEntryRemovalCallback->fnCallback;
  63. this->entryRemovalCallback.cookie = pEntryRemovalCallback->cookie;
  64. }
  65. else
  66. {
  67. this->entryRemovalCallback.fnCallback = nullptr;
  68. }
  69. if (capacity > 0) { Initialize(capacity); }
  70. }
  71. ~WeaklyReferencedKeyDictionary()
  72. {
  73. }
  74. inline int Count()
  75. {
  76. return count - freeCount;
  77. }
  78. TValue Item(TKey* key)
  79. {
  80. int i = FindEntry(key);
  81. if (i >= 0) return entries[i].value;
  82. Js::Throw::FatalInternalError();
  83. }
  84. void Item(TKey* key, const TValue value)
  85. {
  86. Insert(key, value, false);
  87. }
  88. const TValue& GetValueAt(const int& index) const
  89. {
  90. if (index >= 0 && index < count)
  91. {
  92. return entries[index].value;
  93. }
  94. Js::Throw::FatalInternalError();
  95. }
  96. bool TryGetValue(const TKey* key, TValue* value)
  97. {
  98. int i = FindEntry<TKey>(key);
  99. if (i >= 0)
  100. {
  101. *value = entries[i].value;
  102. return true;
  103. }
  104. return false;
  105. }
  106. bool TryGetValueAndRemove(const TKey* key, TValue* value)
  107. {
  108. if (buckets == nullptr) return false;
  109. hash_t hash = GetHashCode(key);
  110. uint targetBucket = PrimePolicy::GetBucket(hash, size, modFunctionIndex);
  111. int last = -1;
  112. int i = 0;
  113. if ((i = FindEntry<TKey>(key, hash, targetBucket, last)) != -1)
  114. {
  115. *value = entries[i].value;
  116. RemoveEntry(i, last, targetBucket);
  117. return true;
  118. }
  119. return false;
  120. }
  121. template <typename TLookup>
  122. inline TValue Lookup(const TLookup* key, TValue defaultValue, __out TKey const** pKeyOut)
  123. {
  124. int i = FindEntry(key);
  125. if (i >= 0)
  126. {
  127. (*pKeyOut) = entries[i].key->Get();
  128. return entries[i].value;
  129. }
  130. (*pKeyOut) = nullptr;
  131. return defaultValue;
  132. }
  133. inline TValue Lookup(const TKey* key, TValue defaultValue)
  134. {
  135. int i = FindEntry(key);
  136. if (i >= 0)
  137. {
  138. return entries[i].value;
  139. }
  140. return defaultValue;
  141. }
  142. const RecyclerWeakReference<TKey>* Add(TKey* key, TValue value)
  143. {
  144. return Insert(key, value, true);
  145. }
  146. const RecyclerWeakReference<TKey>* UncheckedAdd(TKey* key, TValue value)
  147. {
  148. return Insert(key, value, true, false);
  149. }
  150. const RecyclerWeakReference<TKey>* UncheckedAdd(const RecyclerWeakReference<TKey>* weakRef, TValue value)
  151. {
  152. return UncheckedInsert(weakRef, value);
  153. }
  154. template<class Fn>
  155. void Map(Fn fn)
  156. {
  157. for(int i = 0; i < size; i++)
  158. {
  159. if(buckets[i] != -1)
  160. {
  161. for(int previousIndex = -1, currentIndex = buckets[i]; currentIndex != -1;)
  162. {
  163. EntryType &currentEntry = entries[currentIndex];
  164. TKey * key = currentEntry.key->Get();
  165. if(key != nullptr)
  166. {
  167. fn(key, currentEntry.value, currentEntry.key);
  168. // Keep the entry
  169. previousIndex = currentIndex;
  170. currentIndex = currentEntry.next;
  171. }
  172. else
  173. {
  174. // Remove the entry
  175. const int nextIndex = currentEntry.next;
  176. RemoveEntry(currentIndex, previousIndex, i);
  177. currentIndex = nextIndex;
  178. }
  179. }
  180. }
  181. }
  182. }
  183. void SetDisableCleanup(bool disableCleanup)
  184. {
  185. this->disableCleanup = disableCleanup;
  186. }
  187. bool GetDisableCleanup()
  188. {
  189. return this->disableCleanup;
  190. }
  191. bool Clean()
  192. {
  193. if (!disableCleanup && recycler->GetWeakReferenceCleanupId() != this->lastWeakReferenceCleanupId)
  194. {
  195. Map([](TKey * key, TValue value, const RecyclerWeakReference<TKey>* weakRef) {});
  196. this->lastWeakReferenceCleanupId = recycler->GetWeakReferenceCleanupId();
  197. }
  198. return freeCount > 0;
  199. }
  200. void Clear()
  201. {
  202. if (count > 0)
  203. {
  204. for (int i = 0; i < size; i++) buckets[i] = -1;
  205. ClearArray(entries, size);
  206. freeList = -1;
  207. count = 0;
  208. freeCount = 0;
  209. modFunctionIndex = UNKNOWN_MOD_INDEX;
  210. }
  211. }
  212. void EnsureCapacity()
  213. {
  214. if (freeCount == 0 && count == size)
  215. {
  216. if (cleanOnInsert && Clean())
  217. {
  218. Assert(freeCount > 0);
  219. }
  220. else
  221. {
  222. Resize();
  223. }
  224. }
  225. }
  226. private:
  227. const RecyclerWeakReference<TKey>* UncheckedInsert(const RecyclerWeakReference<TKey>* weakRef, TValue value)
  228. {
  229. if (buckets == nullptr) Initialize(0);
  230. int hash = GetHashCode(weakRef->FastGet());
  231. uint bucket = PrimePolicy::GetBucket(hash, size, modFunctionIndex);
  232. Assert(FindEntry(weakRef->FastGet()) == -1);
  233. return Insert(weakRef, value, hash, bucket);
  234. }
  235. const RecyclerWeakReference<TKey>* Insert(TKey* key, TValue value, bool add, bool checkForExisting = true)
  236. {
  237. if (buckets == nullptr) Initialize(0);
  238. hash_t hash = GetHashCode(key);
  239. uint bucket = PrimePolicy::GetBucket(hash, size, modFunctionIndex);
  240. if (checkForExisting)
  241. {
  242. int previous = -1;
  243. int i = FindEntry(key, hash, bucket, previous);
  244. if (i != -1)
  245. {
  246. if (add)
  247. {
  248. Js::Throw::FatalInternalError();
  249. }
  250. entries[i].value = value;
  251. version++;
  252. return entries[i].key;
  253. }
  254. }
  255. // We know we need to insert- so first try creating the weak reference, before adding it to
  256. // the dictionary. If we OOM here, we still leave the dictionary as we found it.
  257. const RecyclerWeakReference<TKey>* weakRef = recycler->CreateWeakReferenceHandle<TKey>(key);
  258. return Insert(weakRef, value, hash, bucket);
  259. }
  260. const RecyclerWeakReference<TKey>* Insert(const RecyclerWeakReference<TKey>* weakRef, TValue value, int hash, uint bucket)
  261. {
  262. int index;
  263. if (freeCount > 0)
  264. {
  265. index = freeList;
  266. freeList = entries[index].next;
  267. freeCount--;
  268. }
  269. else
  270. {
  271. if (count == size)
  272. {
  273. if (cleanOnInsert && Clean())
  274. {
  275. index = freeList;
  276. freeList = entries[index].next;
  277. freeCount--;
  278. }
  279. else
  280. {
  281. Resize();
  282. bucket = PrimePolicy::GetBucket(hash, size, modFunctionIndex);
  283. index = count;
  284. count++;
  285. }
  286. }
  287. else
  288. {
  289. index = count;
  290. count++;
  291. }
  292. }
  293. entries[index].next = buckets[bucket];
  294. entries[index].key = weakRef;
  295. entries[index].hash = hash;
  296. entries[index].value = value;
  297. buckets[bucket] = index;
  298. version++;
  299. return entries[index].key;
  300. }
  301. void Resize()
  302. {
  303. int modIndex = UNKNOWN_MOD_INDEX;
  304. int newSize = PrimePolicy::GetSize(count * 2, &modIndex);
  305. if (newSize <= count)
  306. {
  307. // throw OOM if we can't increase the dictionary size
  308. Js::Throw::OutOfMemory();
  309. }
  310. int* newBuckets = RecyclerNewArrayLeaf(recycler, int, newSize);
  311. for (int i = 0; i < newSize; i++) newBuckets[i] = -1;
  312. EntryType* newEntries = RecyclerNewArray(recycler, EntryType, newSize);
  313. CopyArray<EntryType, Field(const RecyclerWeakReference<TKey>*)>(newEntries, newSize, entries, count);
  314. AnalysisAssert(count < newSize);
  315. modFunctionIndex = modIndex;
  316. for (int i = 0; i < count; i++)
  317. {
  318. uint bucket = PrimePolicy::GetBucket(newEntries[i].hash, newSize, modFunctionIndex);
  319. newEntries[i].next = newBuckets[bucket];
  320. newBuckets[bucket] = i;
  321. }
  322. buckets = newBuckets;
  323. size = newSize;
  324. entries = newEntries;
  325. }
  326. template <typename TLookup>
  327. inline hash_t GetHashCode(const TLookup* key)
  328. {
  329. return TAGHASH(KeyComparer::GetHashCode(key));
  330. }
  331. template <typename TLookup>
  332. inline int FindEntry(const TLookup* key)
  333. {
  334. if (buckets != nullptr)
  335. {
  336. hash_t hash = GetHashCode(key);
  337. uint bucket = PrimePolicy::GetBucket(hash, size, modFunctionIndex);
  338. int previous = -1;
  339. return FindEntry(key, hash, bucket, previous);
  340. }
  341. return -1;
  342. }
  343. template <typename TLookup>
  344. inline int FindEntry(const TLookup* key, hash_t const hash, uint& bucket, int& previous)
  345. {
  346. if (buckets != nullptr)
  347. {
  348. BOOL inSweep = this->recycler->IsSweeping();
  349. previous = -1;
  350. for (int i = buckets[bucket]; i >= 0; )
  351. {
  352. if (entries[i].hash == hash)
  353. {
  354. TKey* strongRef = nullptr;
  355. if (!inSweep)
  356. {
  357. // Quickly check for null if we're not in sweep- if it's null, it's definitely been collected
  358. // so remove
  359. strongRef = entries[i].key->FastGet();
  360. }
  361. else
  362. {
  363. // If we're in sweep, use the slower Get which checks if the object is getting collected
  364. // This could return null too but we won't clean it up now, we'll clean it up later
  365. strongRef = entries[i].key->Get();
  366. }
  367. if (strongRef == nullptr)
  368. {
  369. i = RemoveEntry(i, previous, bucket);
  370. continue;
  371. }
  372. else
  373. {
  374. // if we get here, strongRef is not null
  375. if (KeyComparer::Equals(strongRef, key))
  376. return i;
  377. }
  378. }
  379. previous = i;
  380. i = entries[i].next;
  381. }
  382. }
  383. return -1;
  384. }
  385. void Initialize(int capacity)
  386. {
  387. int modIndex = UNKNOWN_MOD_INDEX;
  388. int size = PrimePolicy::GetSize(capacity, &modIndex);
  389. int* buckets = RecyclerNewArrayLeaf(recycler, int, size);
  390. EntryType * entries = RecyclerNewArray(recycler, EntryType, size);
  391. // No need for auto pointers here since these are both recycler
  392. // allocated objects
  393. if (buckets != nullptr && entries != nullptr)
  394. {
  395. this->size = size;
  396. this->buckets = buckets;
  397. for (int i = 0; i < size; i++) buckets[i] = -1;
  398. this->entries = entries;
  399. this->freeList = -1;
  400. this->modFunctionIndex = modIndex;
  401. }
  402. }
  403. int RemoveEntry(int i, int previous, uint bucket)
  404. {
  405. int next = entries[i].next;
  406. if (previous < 0) // Previous < 0 => first node
  407. {
  408. buckets[bucket] = entries[i].next;
  409. }
  410. else
  411. {
  412. entries[previous].next = entries[i].next;
  413. }
  414. if (this->entryRemovalCallback.fnCallback != nullptr)
  415. {
  416. this->entryRemovalCallback.fnCallback(entries[i], this->entryRemovalCallback.cookie);
  417. }
  418. entries[i].next = freeList;
  419. entries[i].key = nullptr;
  420. entries[i].hash = EntryType::INVALID_HASH_VALUE;
  421. // Hold onto the pid here so that we can reuse it
  422. // entries[i].value = nullptr;
  423. freeList = i;
  424. freeCount++;
  425. version++;
  426. return next;
  427. }
  428. };
  429. }