summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testGCWeakCache.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jsapi-tests/testGCWeakCache.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/testGCWeakCache.cpp')
-rw-r--r--js/src/jsapi-tests/testGCWeakCache.cpp697
1 files changed, 697 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testGCWeakCache.cpp b/js/src/jsapi-tests/testGCWeakCache.cpp
new file mode 100644
index 0000000000..592487d284
--- /dev/null
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -0,0 +1,697 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gc/Policy.h"
+#include "gc/SweepingAPI.h"
+#include "gc/Zone.h"
+#include "js/GCHashTable.h"
+#include "js/RootingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+// Exercise WeakCache<GCHashSet>.
+BEGIN_TEST(testWeakCacheSet) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectSet =
+ GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
+ SystemAllocPolicy>;
+ using Cache = WeakCache<ObjectSet>;
+ Cache cache(JS::GetObjectZone(tenured1));
+
+ cache.put(tenured1);
+ cache.put(tenured2);
+ cache.put(nursery1);
+ cache.put(nursery2);
+
+ // Verify relocation and that we don't sweep too aggressively.
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ // Unroot two entries and verify that they get removed.
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.count() == 2);
+
+ return true;
+}
+END_TEST(testWeakCacheSet)
+
+// Exercise WeakCache<GCHashMap>.
+BEGIN_TEST(testWeakCacheMap) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectMap = js::GCHashMap<HeapPtr<JSObject*>, uint32_t,
+ js::StableCellHasher<HeapPtr<JSObject*>>>;
+ using Cache = WeakCache<ObjectMap>;
+ Cache cache(JS::GetObjectZone(tenured1), cx);
+
+ cache.put(tenured1, 1);
+ cache.put(tenured2, 2);
+ cache.put(nursery1, 3);
+ cache.put(nursery2, 4);
+
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.count() == 2);
+
+ return true;
+}
+END_TEST(testWeakCacheMap)
+
+// Exercise WeakCache<GCVector>.
+BEGIN_TEST(testWeakCacheGCVector) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectVector = WeakCache<GCVector<HeapPtr<JSObject*>>>;
+ ObjectVector cache(JS::GetObjectZone(tenured1), cx);
+
+ CHECK(cache.append(tenured1));
+ CHECK(cache.append(tenured2));
+ CHECK(cache.append(nursery1));
+ CHECK(cache.append(nursery2));
+
+ JS_GC(cx);
+ CHECK(cache.get().length() == 4);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == tenured2);
+ CHECK(cache.get()[2] == nursery1);
+ CHECK(cache.get()[3] == nursery2);
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.get().length() == 2);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == nursery1);
+
+ return true;
+}
+END_TEST(testWeakCacheGCVector)
+
+#ifdef JS_GC_ZEAL
+
+// A simple structure that embeds an object pointer. We cripple the hash
+// implementation so that we can test hash table collisions.
+struct ObjectEntry {
+ HeapPtr<JSObject*> obj;
+ explicit ObjectEntry(JSObject* o) : obj(o) {}
+ bool operator==(const ObjectEntry& other) const { return obj == other.obj; }
+ bool traceWeak(JSTracer* trc) {
+ return TraceWeakEdge(trc, &obj, "ObjectEntry::obj");
+ }
+};
+
+namespace js {
+template <>
+struct StableCellHasher<ObjectEntry> {
+ using Key = ObjectEntry;
+ using Lookup = JSObject*;
+
+ static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::maybeGetHash(l, hashOut)) {
+ return false;
+ }
+ // Reduce hash code to single bit to generate hash collisions.
+ *hashOut &= 0x1;
+ return true;
+ }
+ static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::ensureHash(l, hashOut)) {
+ return false;
+ }
+ // Reduce hash code to single bit to generate hash collisions.
+ *hashOut &= 0x1;
+ return true;
+ }
+ static HashNumber hash(const Lookup& l) {
+ // Reduce hash code to single bit to generate hash collisions.
+ return StableCellHasher<HeapPtr<JSObject*>>::hash(l) & 0x1;
+ }
+ static bool match(const Key& k, const Lookup& l) {
+ return StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l);
+ }
+};
+} // namespace js
+
+// A structure that contains a pointer to a JSObject but is keyed based on an
+// integer. This lets us test replacing dying entries in a set.
+struct NumberAndObjectEntry {
+ uint32_t number;
+ HeapPtr<JSObject*> obj;
+
+ NumberAndObjectEntry(uint32_t n, JSObject* o) : number(n), obj(o) {}
+ bool operator==(const NumberAndObjectEntry& other) const {
+ return number == other.number && obj == other.obj;
+ }
+ bool traceWeak(JSTracer* trc) {
+ return TraceWeakEdge(trc, &obj, "NumberAndObjectEntry::obj");
+ }
+};
+
+struct NumberAndObjectLookup {
+ uint32_t number;
+ HeapPtr<JSObject*> obj;
+
+ NumberAndObjectLookup(uint32_t n, JSObject* o) : number(n), obj(o) {}
+ MOZ_IMPLICIT NumberAndObjectLookup(const NumberAndObjectEntry& entry)
+ : number(entry.number), obj(entry.obj) {}
+};
+
+namespace js {
+template <>
+struct StableCellHasher<NumberAndObjectEntry> {
+ using Key = NumberAndObjectEntry;
+ using Lookup = NumberAndObjectLookup;
+
+ static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::maybeGetHash(l.obj, hashOut)) {
+ return false;
+ }
+ *hashOut ^= l.number;
+ return true;
+ }
+ static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::ensureHash(l.obj, hashOut)) {
+ return false;
+ }
+ *hashOut ^= l.number;
+ return true;
+ }
+ static HashNumber hash(const Lookup& l) {
+ return StableCellHasher<HeapPtr<JSObject*>>::hash(l.obj) ^ l.number;
+ }
+ static bool match(const Key& k, const Lookup& l) {
+ return k.number == l.number &&
+ StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l.obj);
+ }
+};
+} // namespace js
+
+BEGIN_TEST(testIncrementalWeakCacheSweeping) {
+ AutoLeaveZeal nozeal(cx);
+
+ JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ JS_SetGCZeal(cx, 17, 1000000);
+
+ CHECK(TestSet());
+ CHECK(TestMap());
+ CHECK(TestReplaceDyingInSet());
+ CHECK(TestReplaceDyingInMap());
+ CHECK(TestUniqueIDLookups());
+
+ JS_SetGCZeal(cx, 0, 0);
+ JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, false);
+
+ return true;
+}
+
+template <typename Cache>
+bool GCUntilCacheSweep(JSContext* cx, const Cache& cache) {
+ CHECK(!IsIncrementalGCInProgress(cx));
+
+ JS::Zone* zone = JS::GetObjectZone(global);
+ JS::PrepareZoneForGC(cx, zone);
+ SliceBudget budget(WorkBudget(1));
+ cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
+
+ CHECK(IsIncrementalGCInProgress(cx));
+ CHECK(zone->isGCSweeping());
+ CHECK(cache.needsIncrementalBarrier());
+
+ return true;
+}
+
+template <typename Cache>
+bool SweepCacheAndFinishGC(JSContext* cx, const Cache& cache) {
+ CHECK(IsIncrementalGCInProgress(cx));
+
+ PrepareForIncrementalGC(cx);
+ IncrementalGCSlice(cx, JS::GCReason::API, SliceBudget::unlimited());
+
+ JS::Zone* zone = JS::GetObjectZone(global);
+ CHECK(!IsIncrementalGCInProgress(cx));
+ CHECK(!zone->isCollecting());
+ CHECK(!cache.needsIncrementalBarrier());
+
+ return true;
+}
+
+bool TestSet() {
+ using ObjectSet =
+ GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
+ TempAllocPolicy>;
+ using Cache = WeakCache<ObjectSet>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ // Sweep empty cache.
+
+ CHECK(cache.empty());
+ JS_GC(cx);
+ CHECK(cache.empty());
+
+ // Add an entry while sweeping.
+
+ JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+ CHECK(obj1);
+ CHECK(obj2);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(!cache.has(obj1));
+ CHECK(cache.put(obj1));
+ CHECK(cache.count() == 1);
+ CHECK(cache.has(obj1));
+ CHECK(*cache.lookup(obj1) == obj1);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(obj2));
+ CHECK(cache.put(obj2));
+ CHECK(cache.has(obj2));
+ CHECK(*cache.lookup(obj2) == obj2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+
+ // Test dying entries are not found while sweeping.
+
+ CHECK(cache.put(obj3));
+ CHECK(cache.put(obj4));
+ void* old3 = obj3;
+ void* old4 = obj4;
+ obj3 = obj4 = nullptr;
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+ CHECK(!cache.has(static_cast<JSObject*>(old3)));
+ CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+ size_t count = 0;
+ for (auto r = cache.all(); !r.empty(); r.popFront()) {
+ CHECK(r.front() == obj1 || r.front() == obj2);
+ count++;
+ }
+ CHECK(count == 2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+
+ // Test lookupForAdd while sweeping.
+
+ obj3 = JS_NewPlainObject(cx);
+ obj4 = JS_NewPlainObject(cx);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(cache.lookupForAdd(obj1));
+ CHECK(*cache.lookupForAdd(obj1) == obj1);
+
+ auto addp = cache.lookupForAdd(obj3);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj3));
+ CHECK(cache.has(obj3));
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ addp = cache.lookupForAdd(obj4);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj4));
+ CHECK(cache.has(obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ // Test remove while sweeping.
+
+ cache.remove(obj3);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ cache.remove(obj4);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(!cache.has(obj3));
+ CHECK(!cache.has(obj4));
+
+ // Test putNew while sweeping.
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.putNew(obj3));
+ CHECK(cache.putNew(obj4, obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ cache.clear();
+
+ return true;
+}
+
+bool TestMap() {
+ using ObjectMap =
+ GCHashMap<HeapPtr<JSObject*>, uint32_t,
+ StableCellHasher<HeapPtr<JSObject*>>, TempAllocPolicy>;
+ using Cache = WeakCache<ObjectMap>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ // Sweep empty cache.
+
+ CHECK(cache.empty());
+ JS_GC(cx);
+ CHECK(cache.empty());
+
+ // Add an entry while sweeping.
+
+ JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+ CHECK(obj1);
+ CHECK(obj2);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(!cache.has(obj1));
+ CHECK(cache.put(obj1, 1));
+ CHECK(cache.count() == 1);
+ CHECK(cache.has(obj1));
+ CHECK(cache.lookup(obj1)->key() == obj1);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+ CHECK(cache.needsIncrementalBarrier());
+
+ CHECK(!cache.has(obj2));
+ CHECK(cache.put(obj2, 2));
+ CHECK(cache.has(obj2));
+ CHECK(cache.lookup(obj2)->key() == obj2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+ CHECK(!cache.needsIncrementalBarrier());
+
+ CHECK(cache.count() == 2);
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+
+ // Test iteration.
+
+ CHECK(cache.put(obj3, 3));
+ CHECK(cache.put(obj4, 4));
+ void* old3 = obj3;
+ void* old4 = obj4;
+ obj3 = obj4 = nullptr;
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+ CHECK(!cache.has(static_cast<JSObject*>(old3)));
+ CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+ size_t count = 0;
+ for (auto r = cache.all(); !r.empty(); r.popFront()) {
+ CHECK(r.front().key() == obj1 || r.front().key() == obj2);
+ count++;
+ }
+ CHECK(count == 2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+
+ // Test lookupForAdd while sweeping.
+
+ obj3 = JS_NewPlainObject(cx);
+ obj4 = JS_NewPlainObject(cx);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(cache.lookupForAdd(obj1));
+ CHECK(cache.lookupForAdd(obj1)->key() == obj1);
+
+ auto addp = cache.lookupForAdd(obj3);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj3, 3));
+ CHECK(cache.has(obj3));
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ addp = cache.lookupForAdd(obj4);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj4, 4));
+ CHECK(cache.has(obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ // Test remove while sweeping.
+
+ cache.remove(obj3);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ cache.remove(obj4);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(!cache.has(obj3));
+ CHECK(!cache.has(obj4));
+
+ // Test putNew while sweeping.
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.putNew(obj3, 3));
+ CHECK(cache.putNew(obj4, 4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ cache.clear();
+
+ return true;
+}
+
+bool TestReplaceDyingInSet() {
+ // Test replacing dying entries with ones that have the same key using the
+ // various APIs.
+
+ using Cache = WeakCache<
+ GCHashSet<NumberAndObjectEntry, StableCellHasher<NumberAndObjectEntry>,
+ TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ RootedObject value1(cx, JS_NewPlainObject(cx));
+ RootedObject value2(cx, JS_NewPlainObject(cx));
+ CHECK(value1);
+ CHECK(value2);
+
+ CHECK(cache.put(NumberAndObjectEntry(1, value1)));
+ CHECK(cache.put(NumberAndObjectEntry(2, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(3, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(4, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(5, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(6, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(7, value2)));
+
+ value2 = nullptr;
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(NumberAndObjectLookup(2, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(3, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(4, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(5, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(6, value1)));
+
+ auto ptr = cache.lookupForAdd(NumberAndObjectLookup(2, value1));
+ CHECK(!ptr);
+ CHECK(cache.add(ptr, NumberAndObjectEntry(2, value1)));
+
+ auto ptr2 = cache.lookupForAdd(NumberAndObjectLookup(3, value1));
+ CHECK(!ptr2);
+ CHECK(cache.relookupOrAdd(ptr2, NumberAndObjectLookup(3, value1),
+ NumberAndObjectEntry(3, value1)));
+
+ CHECK(cache.put(NumberAndObjectEntry(4, value1)));
+ CHECK(cache.putNew(NumberAndObjectEntry(5, value1)));
+
+ CHECK(cache.putNew(NumberAndObjectLookup(6, value1),
+ NumberAndObjectEntry(6, value1)));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 6);
+ CHECK(cache.has(NumberAndObjectLookup(1, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(2, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(3, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(4, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(5, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(6, value1)));
+
+ return true;
+}
+
+bool TestReplaceDyingInMap() {
+ // Test replacing dying entries with ones that have the same key using the
+ // various APIs.
+
+ using Cache = WeakCache<GCHashMap<uint32_t, HeapPtr<JSObject*>,
+ DefaultHasher<uint32_t>, TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ RootedObject value1(cx, JS_NewPlainObject(cx));
+ RootedObject value2(cx, JS_NewPlainObject(cx));
+ CHECK(value1);
+ CHECK(value2);
+
+ CHECK(cache.put(1, value1));
+ CHECK(cache.put(2, value2));
+ CHECK(cache.put(3, value2));
+ CHECK(cache.put(4, value2));
+ CHECK(cache.put(5, value2));
+ CHECK(cache.put(6, value2));
+
+ value2 = nullptr;
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(2));
+ CHECK(!cache.has(3));
+ CHECK(!cache.has(4));
+ CHECK(!cache.has(5));
+ CHECK(!cache.has(6));
+
+ auto ptr = cache.lookupForAdd(2);
+ CHECK(!ptr);
+ CHECK(cache.add(ptr, 2, value1));
+
+ auto ptr2 = cache.lookupForAdd(3);
+ CHECK(!ptr2);
+ CHECK(cache.add(ptr2, 3, HeapPtr<JSObject*>()));
+
+ auto ptr3 = cache.lookupForAdd(4);
+ CHECK(!ptr3);
+ CHECK(cache.relookupOrAdd(ptr3, 4, value1));
+
+ CHECK(cache.put(5, value1));
+ CHECK(cache.putNew(6, value1));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 6);
+ CHECK(cache.lookup(1)->value() == value1);
+ CHECK(cache.lookup(2)->value() == value1);
+ CHECK(cache.lookup(3)->value() == nullptr);
+ CHECK(cache.lookup(4)->value() == value1);
+ CHECK(cache.lookup(5)->value() == value1);
+ CHECK(cache.lookup(6)->value() == value1);
+
+ return true;
+}
+
+bool TestUniqueIDLookups() {
+ // Test hash table lookups during incremental sweeping where the hash is
+ // generated based on a unique ID. The problem is that the unique ID table
+ // will have already been swept by this point so looking up a dead pointer
+ // in the table will fail. This lookup happens if we try to match a live key
+ // against a dead table entry with the same hash code.
+
+ const size_t DeadFactor = 3;
+ const size_t ObjectCount = 100;
+
+ using Cache = WeakCache<
+ GCHashSet<ObjectEntry, StableCellHasher<ObjectEntry>, TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ Rooted<GCVector<JSObject*, 0, SystemAllocPolicy>> liveObjects(cx);
+
+ for (size_t j = 0; j < ObjectCount; j++) {
+ JSObject* obj = JS_NewPlainObject(cx);
+ CHECK(obj);
+ CHECK(cache.put(obj));
+ if (j % DeadFactor == 0) {
+ CHECK(liveObjects.get().append(obj));
+ }
+ }
+
+ CHECK(cache.count() == ObjectCount);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ for (size_t j = 0; j < liveObjects.length(); j++) {
+ CHECK(cache.has(liveObjects[j]));
+ }
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == liveObjects.length());
+
+ return true;
+}
+
+END_TEST(testIncrementalWeakCacheSweeping)
+
+#endif // defined JS_GC_ZEAL