1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10 #include "js/GCHashTable.h"
11 #include "js/RootingAPI.h"
12 #include "js/SweepingAPI.h"
14 #include "jsapi-tests/tests.h"
18 // Exercise WeakCache<GCHashSet>.
19 BEGIN_TEST(testWeakCacheSet
) {
20 // Create two objects tenured and two in the nursery. If zeal is on,
21 // this may fail and we'll get more tenured objects. That's fine:
22 // the test will continue to work, it will just not test as much.
23 JS::RootedObject
tenured1(cx
, JS_NewPlainObject(cx
));
24 JS::RootedObject
tenured2(cx
, JS_NewPlainObject(cx
));
26 JS::RootedObject
nursery1(cx
, JS_NewPlainObject(cx
));
27 JS::RootedObject
nursery2(cx
, JS_NewPlainObject(cx
));
30 GCHashSet
<HeapPtr
<JSObject
*>, StableCellHasher
<HeapPtr
<JSObject
*>>,
32 using Cache
= JS::WeakCache
<ObjectSet
>;
33 Cache
cache(JS::GetObjectZone(tenured1
));
40 // Verify relocation and that we don't sweep too aggressively.
42 CHECK(cache
.has(tenured1
));
43 CHECK(cache
.has(tenured2
));
44 CHECK(cache
.has(nursery1
));
45 CHECK(cache
.has(nursery2
));
47 // Unroot two entries and verify that they get removed.
48 tenured2
= nursery2
= nullptr;
50 CHECK(cache
.has(tenured1
));
51 CHECK(cache
.has(nursery1
));
52 CHECK(cache
.count() == 2);
56 END_TEST(testWeakCacheSet
)
58 // Exercise WeakCache<GCHashMap>.
59 BEGIN_TEST(testWeakCacheMap
) {
60 // Create two objects tenured and two in the nursery. If zeal is on,
61 // this may fail and we'll get more tenured objects. That's fine:
62 // the test will continue to work, it will just not test as much.
63 JS::RootedObject
tenured1(cx
, JS_NewPlainObject(cx
));
64 JS::RootedObject
tenured2(cx
, JS_NewPlainObject(cx
));
66 JS::RootedObject
nursery1(cx
, JS_NewPlainObject(cx
));
67 JS::RootedObject
nursery2(cx
, JS_NewPlainObject(cx
));
69 using ObjectMap
= js::GCHashMap
<HeapPtr
<JSObject
*>, uint32_t,
70 js::StableCellHasher
<HeapPtr
<JSObject
*>>>;
71 using Cache
= JS::WeakCache
<ObjectMap
>;
72 Cache
cache(JS::GetObjectZone(tenured1
), cx
);
74 cache
.put(tenured1
, 1);
75 cache
.put(tenured2
, 2);
76 cache
.put(nursery1
, 3);
77 cache
.put(nursery2
, 4);
80 CHECK(cache
.has(tenured1
));
81 CHECK(cache
.has(tenured2
));
82 CHECK(cache
.has(nursery1
));
83 CHECK(cache
.has(nursery2
));
85 tenured2
= nursery2
= nullptr;
87 CHECK(cache
.has(tenured1
));
88 CHECK(cache
.has(nursery1
));
89 CHECK(cache
.count() == 2);
93 END_TEST(testWeakCacheMap
)
95 // Exercise WeakCache<GCVector>.
96 BEGIN_TEST(testWeakCacheGCVector
) {
97 // Create two objects tenured and two in the nursery. If zeal is on,
98 // this may fail and we'll get more tenured objects. That's fine:
99 // the test will continue to work, it will just not test as much.
100 JS::RootedObject
tenured1(cx
, JS_NewPlainObject(cx
));
101 JS::RootedObject
tenured2(cx
, JS_NewPlainObject(cx
));
103 JS::RootedObject
nursery1(cx
, JS_NewPlainObject(cx
));
104 JS::RootedObject
nursery2(cx
, JS_NewPlainObject(cx
));
106 using ObjectVector
= JS::WeakCache
<GCVector
<HeapPtr
<JSObject
*>>>;
107 ObjectVector
cache(JS::GetObjectZone(tenured1
), cx
);
109 CHECK(cache
.append(tenured1
));
110 CHECK(cache
.append(tenured2
));
111 CHECK(cache
.append(nursery1
));
112 CHECK(cache
.append(nursery2
));
115 CHECK(cache
.get().length() == 4);
116 CHECK(cache
.get()[0] == tenured1
);
117 CHECK(cache
.get()[1] == tenured2
);
118 CHECK(cache
.get()[2] == nursery1
);
119 CHECK(cache
.get()[3] == nursery2
);
121 tenured2
= nursery2
= nullptr;
123 CHECK(cache
.get().length() == 2);
124 CHECK(cache
.get()[0] == tenured1
);
125 CHECK(cache
.get()[1] == nursery1
);
129 END_TEST(testWeakCacheGCVector
)
133 // A simple structure that embeds an object pointer. We cripple the hash
134 // implementation so that we can test hash table collisions.
136 HeapPtr
<JSObject
*> obj
;
137 explicit ObjectEntry(JSObject
* o
) : obj(o
) {}
138 bool operator==(const ObjectEntry
& other
) const { return obj
== other
.obj
; }
139 bool traceWeak(JSTracer
* trc
) {
140 return TraceWeakEdge(trc
, &obj
, "ObjectEntry::obj");
146 struct StableCellHasher
<ObjectEntry
> {
147 using Key
= ObjectEntry
;
148 using Lookup
= JSObject
*;
150 static bool maybeGetHash(const Lookup
& l
, HashNumber
* hashOut
) {
151 if (!StableCellHasher
<JSObject
*>::maybeGetHash(l
, hashOut
)) {
154 // Reduce hash code to single bit to generate hash collisions.
158 static bool ensureHash(const Lookup
& l
, HashNumber
* hashOut
) {
159 if (!StableCellHasher
<JSObject
*>::ensureHash(l
, hashOut
)) {
162 // Reduce hash code to single bit to generate hash collisions.
166 static HashNumber
hash(const Lookup
& l
) {
167 // Reduce hash code to single bit to generate hash collisions.
168 return StableCellHasher
<HeapPtr
<JSObject
*>>::hash(l
) & 0x1;
170 static bool match(const Key
& k
, const Lookup
& l
) {
171 return StableCellHasher
<HeapPtr
<JSObject
*>>::match(k
.obj
, l
);
176 // A structure that contains a pointer to a JSObject but is keyed based on an
177 // integer. This lets us test replacing dying entries in a set.
178 struct NumberAndObjectEntry
{
180 HeapPtr
<JSObject
*> obj
;
182 NumberAndObjectEntry(uint32_t n
, JSObject
* o
) : number(n
), obj(o
) {}
183 bool operator==(const NumberAndObjectEntry
& other
) const {
184 return number
== other
.number
&& obj
== other
.obj
;
186 bool traceWeak(JSTracer
* trc
) {
187 return TraceWeakEdge(trc
, &obj
, "NumberAndObjectEntry::obj");
191 struct NumberAndObjectLookup
{
193 HeapPtr
<JSObject
*> obj
;
195 NumberAndObjectLookup(uint32_t n
, JSObject
* o
) : number(n
), obj(o
) {}
196 MOZ_IMPLICIT
NumberAndObjectLookup(const NumberAndObjectEntry
& entry
)
197 : number(entry
.number
), obj(entry
.obj
) {}
202 struct StableCellHasher
<NumberAndObjectEntry
> {
203 using Key
= NumberAndObjectEntry
;
204 using Lookup
= NumberAndObjectLookup
;
206 static bool maybeGetHash(const Lookup
& l
, HashNumber
* hashOut
) {
207 if (!StableCellHasher
<JSObject
*>::maybeGetHash(l
.obj
, hashOut
)) {
210 *hashOut
^= l
.number
;
213 static bool ensureHash(const Lookup
& l
, HashNumber
* hashOut
) {
214 if (!StableCellHasher
<JSObject
*>::ensureHash(l
.obj
, hashOut
)) {
217 *hashOut
^= l
.number
;
220 static HashNumber
hash(const Lookup
& l
) {
221 return StableCellHasher
<HeapPtr
<JSObject
*>>::hash(l
.obj
) ^ l
.number
;
223 static bool match(const Key
& k
, const Lookup
& l
) {
224 return k
.number
== l
.number
&&
225 StableCellHasher
<HeapPtr
<JSObject
*>>::match(k
.obj
, l
.obj
);
230 BEGIN_TEST(testIncrementalWeakCacheSweeping
) {
231 AutoLeaveZeal
nozeal(cx
);
233 JS_SetGCParameter(cx
, JSGC_INCREMENTAL_GC_ENABLED
, true);
234 JS_SetGCZeal(cx
, 17, 1000000);
238 CHECK(TestReplaceDyingInSet());
239 CHECK(TestReplaceDyingInMap());
240 CHECK(TestUniqueIDLookups());
242 JS_SetGCZeal(cx
, 0, 0);
243 JS_SetGCParameter(cx
, JSGC_INCREMENTAL_GC_ENABLED
, false);
248 template <typename Cache
>
249 bool GCUntilCacheSweep(JSContext
* cx
, const Cache
& cache
) {
250 CHECK(!IsIncrementalGCInProgress(cx
));
252 JS::Zone
* zone
= JS::GetObjectZone(global
);
253 JS::PrepareZoneForGC(cx
, zone
);
254 SliceBudget
budget(WorkBudget(1));
255 cx
->runtime()->gc
.startDebugGC(JS::GCOptions::Normal
, budget
);
257 CHECK(IsIncrementalGCInProgress(cx
));
258 CHECK(zone
->isGCSweeping());
259 CHECK(cache
.needsIncrementalBarrier());
264 template <typename Cache
>
265 bool SweepCacheAndFinishGC(JSContext
* cx
, const Cache
& cache
) {
266 CHECK(IsIncrementalGCInProgress(cx
));
268 PrepareForIncrementalGC(cx
);
269 IncrementalGCSlice(cx
, JS::GCReason::API
, SliceBudget::unlimited());
271 JS::Zone
* zone
= JS::GetObjectZone(global
);
272 CHECK(!IsIncrementalGCInProgress(cx
));
273 CHECK(!zone
->isCollecting());
274 CHECK(!cache
.needsIncrementalBarrier());
281 GCHashSet
<HeapPtr
<JSObject
*>, StableCellHasher
<HeapPtr
<JSObject
*>>,
283 using Cache
= JS::WeakCache
<ObjectSet
>;
284 Cache
cache(JS::GetObjectZone(global
), cx
);
286 // Sweep empty cache.
288 CHECK(cache
.empty());
290 CHECK(cache
.empty());
292 // Add an entry while sweeping.
294 JS::RootedObject
obj1(cx
, JS_NewPlainObject(cx
));
295 JS::RootedObject
obj2(cx
, JS_NewPlainObject(cx
));
296 JS::RootedObject
obj3(cx
, JS_NewPlainObject(cx
));
297 JS::RootedObject
obj4(cx
, JS_NewPlainObject(cx
));
303 CHECK(!cache
.has(obj1
));
304 CHECK(cache
.put(obj1
));
305 CHECK(cache
.count() == 1);
306 CHECK(cache
.has(obj1
));
307 CHECK(*cache
.lookup(obj1
) == obj1
);
309 CHECK(GCUntilCacheSweep(cx
, cache
));
311 CHECK(!cache
.has(obj2
));
312 CHECK(cache
.put(obj2
));
313 CHECK(cache
.has(obj2
));
314 CHECK(*cache
.lookup(obj2
) == obj2
);
316 CHECK(SweepCacheAndFinishGC(cx
, cache
));
318 CHECK(cache
.count() == 2);
319 CHECK(cache
.has(obj1
));
320 CHECK(cache
.has(obj2
));
322 // Test dying entries are not found while sweeping.
324 CHECK(cache
.put(obj3
));
325 CHECK(cache
.put(obj4
));
328 obj3
= obj4
= nullptr;
330 CHECK(GCUntilCacheSweep(cx
, cache
));
332 CHECK(cache
.has(obj1
));
333 CHECK(cache
.has(obj2
));
334 CHECK(!cache
.has(static_cast<JSObject
*>(old3
)));
335 CHECK(!cache
.has(static_cast<JSObject
*>(old4
)));
338 for (auto r
= cache
.all(); !r
.empty(); r
.popFront()) {
339 CHECK(r
.front() == obj1
|| r
.front() == obj2
);
344 CHECK(SweepCacheAndFinishGC(cx
, cache
));
346 CHECK(cache
.count() == 2);
348 // Test lookupForAdd while sweeping.
350 obj3
= JS_NewPlainObject(cx
);
351 obj4
= JS_NewPlainObject(cx
);
355 CHECK(cache
.lookupForAdd(obj1
));
356 CHECK(*cache
.lookupForAdd(obj1
) == obj1
);
358 auto addp
= cache
.lookupForAdd(obj3
);
360 CHECK(cache
.add(addp
, obj3
));
361 CHECK(cache
.has(obj3
));
363 CHECK(GCUntilCacheSweep(cx
, cache
));
365 addp
= cache
.lookupForAdd(obj4
);
367 CHECK(cache
.add(addp
, obj4
));
368 CHECK(cache
.has(obj4
));
370 CHECK(SweepCacheAndFinishGC(cx
, cache
));
372 CHECK(cache
.count() == 4);
373 CHECK(cache
.has(obj3
));
374 CHECK(cache
.has(obj4
));
376 // Test remove while sweeping.
380 CHECK(GCUntilCacheSweep(cx
, cache
));
384 CHECK(SweepCacheAndFinishGC(cx
, cache
));
386 CHECK(cache
.count() == 2);
387 CHECK(!cache
.has(obj3
));
388 CHECK(!cache
.has(obj4
));
390 // Test putNew while sweeping.
392 CHECK(GCUntilCacheSweep(cx
, cache
));
394 CHECK(cache
.putNew(obj3
));
395 CHECK(cache
.putNew(obj4
, obj4
));
397 CHECK(SweepCacheAndFinishGC(cx
, cache
));
399 CHECK(cache
.count() == 4);
400 CHECK(cache
.has(obj3
));
401 CHECK(cache
.has(obj4
));
410 GCHashMap
<HeapPtr
<JSObject
*>, uint32_t,
411 StableCellHasher
<HeapPtr
<JSObject
*>>, TempAllocPolicy
>;
412 using Cache
= JS::WeakCache
<ObjectMap
>;
413 Cache
cache(JS::GetObjectZone(global
), cx
);
415 // Sweep empty cache.
417 CHECK(cache
.empty());
419 CHECK(cache
.empty());
421 // Add an entry while sweeping.
423 JS::RootedObject
obj1(cx
, JS_NewPlainObject(cx
));
424 JS::RootedObject
obj2(cx
, JS_NewPlainObject(cx
));
425 JS::RootedObject
obj3(cx
, JS_NewPlainObject(cx
));
426 JS::RootedObject
obj4(cx
, JS_NewPlainObject(cx
));
432 CHECK(!cache
.has(obj1
));
433 CHECK(cache
.put(obj1
, 1));
434 CHECK(cache
.count() == 1);
435 CHECK(cache
.has(obj1
));
436 CHECK(cache
.lookup(obj1
)->key() == obj1
);
438 CHECK(GCUntilCacheSweep(cx
, cache
));
439 CHECK(cache
.needsIncrementalBarrier());
441 CHECK(!cache
.has(obj2
));
442 CHECK(cache
.put(obj2
, 2));
443 CHECK(cache
.has(obj2
));
444 CHECK(cache
.lookup(obj2
)->key() == obj2
);
446 CHECK(SweepCacheAndFinishGC(cx
, cache
));
447 CHECK(!cache
.needsIncrementalBarrier());
449 CHECK(cache
.count() == 2);
450 CHECK(cache
.has(obj1
));
451 CHECK(cache
.has(obj2
));
455 CHECK(cache
.put(obj3
, 3));
456 CHECK(cache
.put(obj4
, 4));
459 obj3
= obj4
= nullptr;
461 CHECK(GCUntilCacheSweep(cx
, cache
));
463 CHECK(cache
.has(obj1
));
464 CHECK(cache
.has(obj2
));
465 CHECK(!cache
.has(static_cast<JSObject
*>(old3
)));
466 CHECK(!cache
.has(static_cast<JSObject
*>(old4
)));
469 for (auto r
= cache
.all(); !r
.empty(); r
.popFront()) {
470 CHECK(r
.front().key() == obj1
|| r
.front().key() == obj2
);
475 CHECK(SweepCacheAndFinishGC(cx
, cache
));
477 CHECK(cache
.count() == 2);
479 // Test lookupForAdd while sweeping.
481 obj3
= JS_NewPlainObject(cx
);
482 obj4
= JS_NewPlainObject(cx
);
486 CHECK(cache
.lookupForAdd(obj1
));
487 CHECK(cache
.lookupForAdd(obj1
)->key() == obj1
);
489 auto addp
= cache
.lookupForAdd(obj3
);
491 CHECK(cache
.add(addp
, obj3
, 3));
492 CHECK(cache
.has(obj3
));
494 CHECK(GCUntilCacheSweep(cx
, cache
));
496 addp
= cache
.lookupForAdd(obj4
);
498 CHECK(cache
.add(addp
, obj4
, 4));
499 CHECK(cache
.has(obj4
));
501 CHECK(SweepCacheAndFinishGC(cx
, cache
));
503 CHECK(cache
.count() == 4);
504 CHECK(cache
.has(obj3
));
505 CHECK(cache
.has(obj4
));
507 // Test remove while sweeping.
511 CHECK(GCUntilCacheSweep(cx
, cache
));
515 CHECK(SweepCacheAndFinishGC(cx
, cache
));
517 CHECK(cache
.count() == 2);
518 CHECK(!cache
.has(obj3
));
519 CHECK(!cache
.has(obj4
));
521 // Test putNew while sweeping.
523 CHECK(GCUntilCacheSweep(cx
, cache
));
525 CHECK(cache
.putNew(obj3
, 3));
526 CHECK(cache
.putNew(obj4
, 4));
528 CHECK(SweepCacheAndFinishGC(cx
, cache
));
530 CHECK(cache
.count() == 4);
531 CHECK(cache
.has(obj3
));
532 CHECK(cache
.has(obj4
));
539 bool TestReplaceDyingInSet() {
540 // Test replacing dying entries with ones that have the same key using the
543 using Cache
= JS::WeakCache
<
544 GCHashSet
<NumberAndObjectEntry
, StableCellHasher
<NumberAndObjectEntry
>,
546 Cache
cache(JS::GetObjectZone(global
), cx
);
548 RootedObject
value1(cx
, JS_NewPlainObject(cx
));
549 RootedObject
value2(cx
, JS_NewPlainObject(cx
));
553 CHECK(cache
.put(NumberAndObjectEntry(1, value1
)));
554 CHECK(cache
.put(NumberAndObjectEntry(2, value2
)));
555 CHECK(cache
.put(NumberAndObjectEntry(3, value2
)));
556 CHECK(cache
.put(NumberAndObjectEntry(4, value2
)));
557 CHECK(cache
.put(NumberAndObjectEntry(5, value2
)));
558 CHECK(cache
.put(NumberAndObjectEntry(6, value2
)));
559 CHECK(cache
.put(NumberAndObjectEntry(7, value2
)));
562 CHECK(GCUntilCacheSweep(cx
, cache
));
564 CHECK(!cache
.has(NumberAndObjectLookup(2, value1
)));
565 CHECK(!cache
.has(NumberAndObjectLookup(3, value1
)));
566 CHECK(!cache
.has(NumberAndObjectLookup(4, value1
)));
567 CHECK(!cache
.has(NumberAndObjectLookup(5, value1
)));
568 CHECK(!cache
.has(NumberAndObjectLookup(6, value1
)));
570 auto ptr
= cache
.lookupForAdd(NumberAndObjectLookup(2, value1
));
572 CHECK(cache
.add(ptr
, NumberAndObjectEntry(2, value1
)));
574 auto ptr2
= cache
.lookupForAdd(NumberAndObjectLookup(3, value1
));
576 CHECK(cache
.relookupOrAdd(ptr2
, NumberAndObjectLookup(3, value1
),
577 NumberAndObjectEntry(3, value1
)));
579 CHECK(cache
.put(NumberAndObjectEntry(4, value1
)));
580 CHECK(cache
.putNew(NumberAndObjectEntry(5, value1
)));
582 CHECK(cache
.putNew(NumberAndObjectLookup(6, value1
),
583 NumberAndObjectEntry(6, value1
)));
585 CHECK(SweepCacheAndFinishGC(cx
, cache
));
587 CHECK(cache
.count() == 6);
588 CHECK(cache
.has(NumberAndObjectLookup(1, value1
)));
589 CHECK(cache
.has(NumberAndObjectLookup(2, value1
)));
590 CHECK(cache
.has(NumberAndObjectLookup(3, value1
)));
591 CHECK(cache
.has(NumberAndObjectLookup(4, value1
)));
592 CHECK(cache
.has(NumberAndObjectLookup(5, value1
)));
593 CHECK(cache
.has(NumberAndObjectLookup(6, value1
)));
598 bool TestReplaceDyingInMap() {
599 // Test replacing dying entries with ones that have the same key using the
603 JS::WeakCache
<GCHashMap
<uint32_t, HeapPtr
<JSObject
*>,
604 DefaultHasher
<uint32_t>, TempAllocPolicy
>>;
605 Cache
cache(JS::GetObjectZone(global
), cx
);
607 RootedObject
value1(cx
, JS_NewPlainObject(cx
));
608 RootedObject
value2(cx
, JS_NewPlainObject(cx
));
612 CHECK(cache
.put(1, value1
));
613 CHECK(cache
.put(2, value2
));
614 CHECK(cache
.put(3, value2
));
615 CHECK(cache
.put(4, value2
));
616 CHECK(cache
.put(5, value2
));
617 CHECK(cache
.put(6, value2
));
620 CHECK(GCUntilCacheSweep(cx
, cache
));
622 CHECK(!cache
.has(2));
623 CHECK(!cache
.has(3));
624 CHECK(!cache
.has(4));
625 CHECK(!cache
.has(5));
626 CHECK(!cache
.has(6));
628 auto ptr
= cache
.lookupForAdd(2);
630 CHECK(cache
.add(ptr
, 2, value1
));
632 auto ptr2
= cache
.lookupForAdd(3);
634 CHECK(cache
.add(ptr2
, 3, HeapPtr
<JSObject
*>()));
636 auto ptr3
= cache
.lookupForAdd(4);
638 CHECK(cache
.relookupOrAdd(ptr3
, 4, value1
));
640 CHECK(cache
.put(5, value1
));
641 CHECK(cache
.putNew(6, value1
));
643 CHECK(SweepCacheAndFinishGC(cx
, cache
));
645 CHECK(cache
.count() == 6);
646 CHECK(cache
.lookup(1)->value() == value1
);
647 CHECK(cache
.lookup(2)->value() == value1
);
648 CHECK(cache
.lookup(3)->value() == nullptr);
649 CHECK(cache
.lookup(4)->value() == value1
);
650 CHECK(cache
.lookup(5)->value() == value1
);
651 CHECK(cache
.lookup(6)->value() == value1
);
656 bool TestUniqueIDLookups() {
657 // Test hash table lookups during incremental sweeping where the hash is
658 // generated based on a unique ID. The problem is that the unique ID table
659 // will have already been swept by this point so looking up a dead pointer
660 // in the table will fail. This lookup happens if we try to match a live key
661 // against a dead table entry with the same hash code.
663 const size_t DeadFactor
= 3;
664 const size_t ObjectCount
= 100;
666 using Cache
= JS::WeakCache
<
667 GCHashSet
<ObjectEntry
, StableCellHasher
<ObjectEntry
>, TempAllocPolicy
>>;
668 Cache
cache(JS::GetObjectZone(global
), cx
);
670 Rooted
<GCVector
<JSObject
*, 0, SystemAllocPolicy
>> liveObjects(cx
);
672 for (size_t j
= 0; j
< ObjectCount
; j
++) {
673 JSObject
* obj
= JS_NewPlainObject(cx
);
675 CHECK(cache
.put(obj
));
676 if (j
% DeadFactor
== 0) {
677 CHECK(liveObjects
.get().append(obj
));
681 CHECK(cache
.count() == ObjectCount
);
683 CHECK(GCUntilCacheSweep(cx
, cache
));
685 for (size_t j
= 0; j
< liveObjects
.length(); j
++) {
686 CHECK(cache
.has(liveObjects
[j
]));
689 CHECK(SweepCacheAndFinishGC(cx
, cache
));
691 CHECK(cache
.count() == liveObjects
.length());
696 END_TEST(testIncrementalWeakCacheSweeping
)
698 #endif // defined JS_GC_ZEAL