testGCWeakCache.cpp (21989B)
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: 3 */ 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/. */ 7 8 #include "gc/Policy.h" 9 #include "gc/Zone.h" 10 #include "js/GCHashTable.h" 11 #include "js/RootingAPI.h" 12 #include "js/SweepingAPI.h" 13 14 #include "jsapi-tests/tests.h" 15 16 using namespace js; 17 18 // Exercise WeakCache<GCHashSet>. 19 BEGIN_TEST(testWeakCacheSet) { 20 AutoLeaveZeal leaveZeal(cx); 21 22 // Test using internal APIs. 23 CHECK(test<HeapPtr<JSObject*>>(cx)); 24 25 // Test using APIs available to embedders. 26 CHECK(test<JS::Heap<JSObject*>>(cx)); 27 28 return true; 29 } 30 31 template <typename T> 32 bool test(JSContext* cx) { 33 JS::RootedObject tenured1(cx, JS_NewPlainObject(cx)); 34 JS::RootedObject tenured2(cx, JS_NewPlainObject(cx)); 35 JS_GC(cx); 36 JS::RootedObject nursery1(cx, JS_NewPlainObject(cx)); 37 JS::RootedObject nursery2(cx, JS_NewPlainObject(cx)); 38 39 using Set = GCHashSet<T, StableCellHasher<T>, SystemAllocPolicy>; 40 using Cache = JS::WeakCache<Set>; 41 Cache cache(JS::GetObjectZone(tenured1)); 42 43 CHECK(cache.put(tenured1)); 44 CHECK(cache.put(tenured2)); 45 CHECK(cache.put(nursery1)); 46 CHECK(cache.put(nursery2)); 47 cache.put(nullptr); // nullptr entries should not be swept. 48 49 // Verify relocation and that we don't sweep too aggressively. 50 JS_GC(cx); 51 CHECK(cache.has(tenured1)); 52 CHECK(cache.has(tenured2)); 53 CHECK(cache.has(nursery1)); 54 CHECK(cache.has(nursery2)); 55 CHECK(cache.has(nullptr)); 56 CHECK_EQUAL(cache.count(), size_t(5)); 57 58 // Unroot two entries and verify that they get removed. 59 tenured2 = nursery2 = nullptr; 60 JS_GC(cx); 61 CHECK(cache.has(tenured1)); 62 CHECK(cache.has(nursery1)); 63 CHECK(cache.has(nullptr)); 64 CHECK_EQUAL(cache.count(), size_t(3)); 65 66 return true; 67 } 68 END_TEST(testWeakCacheSet) 69 70 // Exercise WeakCache<GCHashMap> where the value is not a GC thing. 71 BEGIN_TEST(testWeakCacheMapToNonGCThing) { 72 AutoLeaveZeal leaveZeal(cx); 73 74 uint32_t x = 0; 75 CHECK((test<HeapPtr<JSObject*>, uint32_t>(cx, [&x]() { return x++; }))); 76 77 CHECK((test<HeapPtr<JSObject*>, UniquePtr<uint32_t>>( 78 cx, [&x]() { return MakeUnique<uint32_t>(x++); }))); 79 80 return true; 81 } 82 83 template <typename Key, typename Value, typename ValueFunc> 84 bool test(JSContext* cx, ValueFunc&& valueFunc) { 85 JS::RootedObject tenured1(cx, JS_NewPlainObject(cx)); 86 JS::RootedObject tenured2(cx, JS_NewPlainObject(cx)); 87 JS_GC(cx); 88 JS::RootedObject nursery1(cx, JS_NewPlainObject(cx)); 89 JS::RootedObject nursery2(cx, JS_NewPlainObject(cx)); 90 91 using Map = GCHashMap<Key, Value, StableCellHasher<Key>, SystemAllocPolicy>; 92 using Cache = JS::WeakCache<Map>; 93 Cache cache(JS::GetObjectZone(tenured1)); 94 95 cache.put(tenured1, valueFunc()); 96 cache.put(tenured2, valueFunc()); 97 cache.put(nursery1, valueFunc()); 98 cache.put(nursery2, valueFunc()); 99 cache.put(nullptr, valueFunc()); // nullptr entries should not be swept. 100 101 JS_GC(cx); 102 CHECK(cache.has(tenured1)); 103 CHECK(cache.has(tenured2)); 104 CHECK(cache.has(nursery1)); 105 CHECK(cache.has(nursery2)); 106 CHECK(cache.has(nullptr)); 107 CHECK_EQUAL(cache.count(), size_t(5)); 108 109 tenured2 = nursery2 = nullptr; 110 JS_GC(cx); 111 CHECK(cache.has(tenured1)); 112 CHECK(cache.has(nursery1)); 113 CHECK(cache.has(nullptr)); 114 CHECK_EQUAL(cache.count(), size_t(3)); 115 116 return true; 117 } 118 END_TEST(testWeakCacheMapToNonGCThing) 119 120 // Exercise WeakCache<GCHashMap> where the value is JSObject pointer. 121 BEGIN_TEST(testWeakCacheMapToObject) { 122 AutoLeaveZeal leaveZeal(cx); 123 AutoGCParameter enableIncremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true); 124 125 CHECK((test<HeapPtr<JSObject*>, HeapPtr<JSObject*>>(cx))); 126 CHECK((test<JS::Heap<JSObject*>, JS::Heap<JSObject*>>(cx))); 127 return true; 128 } 129 130 template <typename Key, typename Value> 131 bool test(JSContext* cx) { 132 JS::RootedObject tenured1{cx, JS_NewPlainObject(cx)}; 133 JS::RootedObject tenured2{cx, JS_NewPlainObject(cx)}; 134 JS_GC(cx); 135 JS::RootedObject nursery1{cx, JS_NewPlainObject(cx)}; 136 JS::RootedObject nursery2{cx, JS_NewPlainObject(cx)}; 137 138 using Map = GCHashMap<Key, Value, StableCellHasher<Key>, SystemAllocPolicy>; 139 using Cache = JS::WeakCache<Map>; 140 Cache cache(JS::GetObjectZone(tenured1)); 141 142 cache.put(tenured1, tenured1); 143 cache.put(tenured2, tenured2); 144 cache.put(nursery1, nursery2); // These do not map to themselves. 145 cache.put(nursery2, nursery1); 146 cache.put(nullptr, nullptr); // nullptr entries should not be swept. 147 148 JS_GC(cx); 149 CHECK(cache.has(tenured1)); 150 CHECK(cache.has(tenured2)); 151 CHECK(cache.has(nursery1)); 152 CHECK(cache.has(nursery2)); 153 CHECK(cache.has(nullptr)); 154 CHECK_EQUAL(cache.count(), size_t(5)); 155 156 tenured2 = nursery2 = nullptr; 157 JS_GC(cx); 158 CHECK(cache.has(tenured1)); 159 // nursery1 is gone because value is dead. 160 CHECK(cache.has(nullptr)); 161 CHECK_EQUAL(cache.count(), size_t(2)); 162 163 #ifdef JS_GC_ZEAL 164 // Test nursery objects are handled correctly if they are present during 165 // sweeping. For this to happen they must get added during an incremental GC. 166 JS::SetGCZeal(cx, 10, 100); 167 for (size_t i = 0; i < 1000; i++) { 168 Rooted<JSObject*> key(cx); 169 Rooted<JSObject*> value(cx); 170 key = JS_NewPlainObject(cx); 171 CHECK(key); 172 value = JS_NewPlainObject(cx); 173 CHECK(value); 174 CHECK(cache.put(key, value)); 175 } 176 JS::SetGCZeal(cx, 0, 0); 177 JS_GC(cx); 178 CHECK_EQUAL(cache.count(), size_t(2)); 179 #endif // JS_GC_ZEAL 180 181 return true; 182 } 183 END_TEST(testWeakCacheMapToObject) 184 185 // Exercise WeakCache<GCVector>. 186 BEGIN_TEST(testWeakCacheGCVector) { 187 AutoLeaveZeal leaveZeal(cx); 188 CHECK(test<HeapPtr<JSObject*>>(cx)); 189 CHECK(test<JS::Heap<JSObject*>>(cx)); 190 return true; 191 } 192 193 template <typename Element> 194 bool test(JSContext* cx) { 195 // Create two objects tenured and two in the nursery. If zeal is on, 196 // this may fail and we'll get more tenured objects. That's fine: 197 // the test will continue to work, it will just not test as much. 198 JS::RootedObject tenured1(cx, JS_NewPlainObject(cx)); 199 JS::RootedObject tenured2(cx, JS_NewPlainObject(cx)); 200 JS_GC(cx); 201 JS::RootedObject nursery1(cx, JS_NewPlainObject(cx)); 202 JS::RootedObject nursery2(cx, JS_NewPlainObject(cx)); 203 204 using Vector = JS::WeakCache<GCVector<Element, 0, SystemAllocPolicy>>; 205 Vector cache(JS::GetObjectZone(tenured1)); 206 207 CHECK(cache.append(tenured1)); 208 CHECK(cache.append(tenured2)); 209 CHECK(cache.append(nursery1)); 210 CHECK(cache.append(nursery2)); 211 212 JS_GC(cx); 213 CHECK(cache.get().length() == 4); 214 CHECK(cache.get()[0] == tenured1); 215 CHECK(cache.get()[1] == tenured2); 216 CHECK(cache.get()[2] == nursery1); 217 CHECK(cache.get()[3] == nursery2); 218 219 tenured2 = nursery2 = nullptr; 220 JS_GC(cx); 221 CHECK(cache.get().length() == 2); 222 CHECK(cache.get()[0] == tenured1); 223 CHECK(cache.get()[1] == nursery1); 224 225 return true; 226 } 227 END_TEST(testWeakCacheGCVector) 228 229 #ifdef JS_GC_ZEAL 230 231 // A simple structure that embeds an object pointer. We cripple the hash 232 // implementation so that we can test hash table collisions. 233 struct ObjectEntry { 234 HeapPtr<JSObject*> obj; 235 explicit ObjectEntry(JSObject* o) : obj(o) {} 236 bool operator==(const ObjectEntry& other) const { return obj == other.obj; } 237 bool traceWeak(JSTracer* trc) { 238 return TraceWeakEdge(trc, &obj, "ObjectEntry::obj"); 239 } 240 }; 241 242 namespace js { 243 template <> 244 struct StableCellHasher<ObjectEntry> { 245 using Key = ObjectEntry; 246 using Lookup = JSObject*; 247 248 static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) { 249 if (!StableCellHasher<JSObject*>::maybeGetHash(l, hashOut)) { 250 return false; 251 } 252 // Reduce hash code to single bit to generate hash collisions. 253 *hashOut &= 0x1; 254 return true; 255 } 256 static bool ensureHash(const Lookup& l, HashNumber* hashOut) { 257 if (!StableCellHasher<JSObject*>::ensureHash(l, hashOut)) { 258 return false; 259 } 260 // Reduce hash code to single bit to generate hash collisions. 261 *hashOut &= 0x1; 262 return true; 263 } 264 static HashNumber hash(const Lookup& l) { 265 // Reduce hash code to single bit to generate hash collisions. 266 return StableCellHasher<HeapPtr<JSObject*>>::hash(l) & 0x1; 267 } 268 static bool match(const Key& k, const Lookup& l) { 269 return StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l); 270 } 271 }; 272 } // namespace js 273 274 // A structure that contains a pointer to a JSObject but is keyed based on an 275 // integer. This lets us test replacing dying entries in a set. 276 struct NumberAndObjectEntry { 277 uint32_t number; 278 HeapPtr<JSObject*> obj; 279 280 NumberAndObjectEntry(uint32_t n, JSObject* o) : number(n), obj(o) {} 281 bool operator==(const NumberAndObjectEntry& other) const { 282 return number == other.number && obj == other.obj; 283 } 284 bool traceWeak(JSTracer* trc) { 285 return TraceWeakEdge(trc, &obj, "NumberAndObjectEntry::obj"); 286 } 287 }; 288 289 struct NumberAndObjectLookup { 290 uint32_t number; 291 HeapPtr<JSObject*> obj; 292 293 NumberAndObjectLookup(uint32_t n, JSObject* o) : number(n), obj(o) {} 294 MOZ_IMPLICIT NumberAndObjectLookup(const NumberAndObjectEntry& entry) 295 : number(entry.number), obj(entry.obj) {} 296 }; 297 298 namespace js { 299 template <> 300 struct StableCellHasher<NumberAndObjectEntry> { 301 using Key = NumberAndObjectEntry; 302 using Lookup = NumberAndObjectLookup; 303 304 static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) { 305 if (!StableCellHasher<JSObject*>::maybeGetHash(l.obj, hashOut)) { 306 return false; 307 } 308 *hashOut ^= l.number; 309 return true; 310 } 311 static bool ensureHash(const Lookup& l, HashNumber* hashOut) { 312 if (!StableCellHasher<JSObject*>::ensureHash(l.obj, hashOut)) { 313 return false; 314 } 315 *hashOut ^= l.number; 316 return true; 317 } 318 static HashNumber hash(const Lookup& l) { 319 return StableCellHasher<HeapPtr<JSObject*>>::hash(l.obj) ^ l.number; 320 } 321 static bool match(const Key& k, const Lookup& l) { 322 return k.number == l.number && 323 StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l.obj); 324 } 325 }; 326 } // namespace js 327 328 BEGIN_TEST(testIncrementalWeakCacheSweeping) { 329 AutoLeaveZeal nozeal(cx); 330 331 JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true); 332 JS::SetGCZeal(cx, 17, 1000000); 333 334 CHECK(TestSet()); 335 CHECK(TestMap()); 336 CHECK(TestReplaceDyingInSet()); 337 CHECK(TestReplaceDyingInMap()); 338 CHECK(TestUniqueIDLookups()); 339 340 JS::SetGCZeal(cx, 0, 0); 341 JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, false); 342 343 return true; 344 } 345 346 template <typename Cache> 347 bool GCUntilCacheSweep(JSContext* cx, const Cache& cache) { 348 CHECK(!IsIncrementalGCInProgress(cx)); 349 350 JS::Zone* zone = JS::GetObjectZone(global); 351 JS::PrepareZoneForGC(cx, zone); 352 JS::SliceBudget budget(JS::WorkBudget(1)); 353 cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget); 354 355 CHECK(IsIncrementalGCInProgress(cx)); 356 CHECK(zone->isGCSweeping()); 357 CHECK(cache.needsIncrementalBarrier()); 358 359 return true; 360 } 361 362 template <typename Cache> 363 bool SweepCacheAndFinishGC(JSContext* cx, const Cache& cache) { 364 CHECK(IsIncrementalGCInProgress(cx)); 365 366 PrepareForIncrementalGC(cx); 367 IncrementalGCSlice(cx, JS::GCReason::API, JS::SliceBudget::unlimited()); 368 369 JS::Zone* zone = JS::GetObjectZone(global); 370 CHECK(!IsIncrementalGCInProgress(cx)); 371 CHECK(!zone->isCollecting()); 372 CHECK(!cache.needsIncrementalBarrier()); 373 374 return true; 375 } 376 377 bool TestSet() { 378 using ObjectSet = 379 GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>, 380 TempAllocPolicy>; 381 using Cache = JS::WeakCache<ObjectSet>; 382 Cache cache(JS::GetObjectZone(global), cx); 383 384 // Sweep empty cache. 385 386 CHECK(cache.empty()); 387 JS_GC(cx); 388 CHECK(cache.empty()); 389 390 // Add an entry while sweeping. 391 392 JS::RootedObject obj1(cx, JS_NewPlainObject(cx)); 393 JS::RootedObject obj2(cx, JS_NewPlainObject(cx)); 394 JS::RootedObject obj3(cx, JS_NewPlainObject(cx)); 395 JS::RootedObject obj4(cx, JS_NewPlainObject(cx)); 396 CHECK(obj1); 397 CHECK(obj2); 398 CHECK(obj3); 399 CHECK(obj4); 400 401 CHECK(!cache.has(obj1)); 402 CHECK(cache.put(obj1)); 403 CHECK(cache.count() == 1); 404 CHECK(cache.has(obj1)); 405 CHECK(*cache.lookup(obj1) == obj1); 406 407 CHECK(GCUntilCacheSweep(cx, cache)); 408 409 CHECK(!cache.has(obj2)); 410 CHECK(cache.put(obj2)); 411 CHECK(cache.has(obj2)); 412 CHECK(*cache.lookup(obj2) == obj2); 413 414 CHECK(SweepCacheAndFinishGC(cx, cache)); 415 416 CHECK(cache.count() == 2); 417 CHECK(cache.has(obj1)); 418 CHECK(cache.has(obj2)); 419 420 // Test dying entries are not found while sweeping. 421 422 CHECK(cache.put(obj3)); 423 CHECK(cache.put(obj4)); 424 void* old3 = obj3; 425 void* old4 = obj4; 426 obj3 = obj4 = nullptr; 427 428 CHECK(GCUntilCacheSweep(cx, cache)); 429 430 CHECK(cache.has(obj1)); 431 CHECK(cache.has(obj2)); 432 CHECK(!cache.has(static_cast<JSObject*>(old3))); 433 CHECK(!cache.has(static_cast<JSObject*>(old4))); 434 435 size_t count = 0; 436 for (auto r = cache.all(); !r.empty(); r.popFront()) { 437 CHECK(r.front() == obj1 || r.front() == obj2); 438 count++; 439 } 440 CHECK(count == 2); 441 442 CHECK(SweepCacheAndFinishGC(cx, cache)); 443 444 CHECK(cache.count() == 2); 445 446 // Test lookupForAdd while sweeping. 447 448 obj3 = JS_NewPlainObject(cx); 449 obj4 = JS_NewPlainObject(cx); 450 CHECK(obj3); 451 CHECK(obj4); 452 453 CHECK(cache.lookupForAdd(obj1)); 454 CHECK(*cache.lookupForAdd(obj1) == obj1); 455 456 auto addp = cache.lookupForAdd(obj3); 457 CHECK(!addp); 458 CHECK(cache.add(addp, obj3)); 459 CHECK(cache.has(obj3)); 460 461 CHECK(GCUntilCacheSweep(cx, cache)); 462 463 addp = cache.lookupForAdd(obj4); 464 CHECK(!addp); 465 CHECK(cache.add(addp, obj4)); 466 CHECK(cache.has(obj4)); 467 468 CHECK(SweepCacheAndFinishGC(cx, cache)); 469 470 CHECK(cache.count() == 4); 471 CHECK(cache.has(obj3)); 472 CHECK(cache.has(obj4)); 473 474 // Test remove while sweeping. 475 476 cache.remove(obj3); 477 478 CHECK(GCUntilCacheSweep(cx, cache)); 479 480 cache.remove(obj4); 481 482 CHECK(SweepCacheAndFinishGC(cx, cache)); 483 484 CHECK(cache.count() == 2); 485 CHECK(!cache.has(obj3)); 486 CHECK(!cache.has(obj4)); 487 488 // Test putNew while sweeping. 489 490 CHECK(GCUntilCacheSweep(cx, cache)); 491 492 CHECK(cache.putNew(obj3)); 493 CHECK(cache.putNew(obj4, obj4)); 494 495 CHECK(SweepCacheAndFinishGC(cx, cache)); 496 497 CHECK(cache.count() == 4); 498 CHECK(cache.has(obj3)); 499 CHECK(cache.has(obj4)); 500 501 cache.clear(); 502 503 return true; 504 } 505 506 bool TestMap() { 507 using ObjectMap = 508 GCHashMap<HeapPtr<JSObject*>, uint32_t, 509 StableCellHasher<HeapPtr<JSObject*>>, TempAllocPolicy>; 510 using Cache = JS::WeakCache<ObjectMap>; 511 Cache cache(JS::GetObjectZone(global), cx); 512 513 // Sweep empty cache. 514 515 CHECK(cache.empty()); 516 JS_GC(cx); 517 CHECK(cache.empty()); 518 519 // Add an entry while sweeping. 520 521 JS::RootedObject obj1(cx, JS_NewPlainObject(cx)); 522 JS::RootedObject obj2(cx, JS_NewPlainObject(cx)); 523 JS::RootedObject obj3(cx, JS_NewPlainObject(cx)); 524 JS::RootedObject obj4(cx, JS_NewPlainObject(cx)); 525 CHECK(obj1); 526 CHECK(obj2); 527 CHECK(obj3); 528 CHECK(obj4); 529 530 CHECK(!cache.has(obj1)); 531 CHECK(cache.put(obj1, 1)); 532 CHECK(cache.count() == 1); 533 CHECK(cache.has(obj1)); 534 CHECK(cache.lookup(obj1)->key() == obj1); 535 536 CHECK(GCUntilCacheSweep(cx, cache)); 537 CHECK(cache.needsIncrementalBarrier()); 538 539 CHECK(!cache.has(obj2)); 540 CHECK(cache.put(obj2, 2)); 541 CHECK(cache.has(obj2)); 542 CHECK(cache.lookup(obj2)->key() == obj2); 543 544 CHECK(SweepCacheAndFinishGC(cx, cache)); 545 CHECK(!cache.needsIncrementalBarrier()); 546 547 CHECK(cache.count() == 2); 548 CHECK(cache.has(obj1)); 549 CHECK(cache.has(obj2)); 550 551 // Test iteration. 552 553 CHECK(cache.put(obj3, 3)); 554 CHECK(cache.put(obj4, 4)); 555 void* old3 = obj3; 556 void* old4 = obj4; 557 obj3 = obj4 = nullptr; 558 559 CHECK(GCUntilCacheSweep(cx, cache)); 560 561 CHECK(cache.has(obj1)); 562 CHECK(cache.has(obj2)); 563 CHECK(!cache.has(static_cast<JSObject*>(old3))); 564 CHECK(!cache.has(static_cast<JSObject*>(old4))); 565 566 size_t count = 0; 567 for (auto r = cache.all(); !r.empty(); r.popFront()) { 568 CHECK(r.front().key() == obj1 || r.front().key() == obj2); 569 count++; 570 } 571 CHECK(count == 2); 572 573 CHECK(SweepCacheAndFinishGC(cx, cache)); 574 575 CHECK(cache.count() == 2); 576 577 // Test lookupForAdd while sweeping. 578 579 obj3 = JS_NewPlainObject(cx); 580 obj4 = JS_NewPlainObject(cx); 581 CHECK(obj3); 582 CHECK(obj4); 583 584 CHECK(cache.lookupForAdd(obj1)); 585 CHECK(cache.lookupForAdd(obj1)->key() == obj1); 586 587 auto addp = cache.lookupForAdd(obj3); 588 CHECK(!addp); 589 CHECK(cache.add(addp, obj3, 3)); 590 CHECK(cache.has(obj3)); 591 592 CHECK(GCUntilCacheSweep(cx, cache)); 593 594 addp = cache.lookupForAdd(obj4); 595 CHECK(!addp); 596 CHECK(cache.add(addp, obj4, 4)); 597 CHECK(cache.has(obj4)); 598 599 CHECK(SweepCacheAndFinishGC(cx, cache)); 600 601 CHECK(cache.count() == 4); 602 CHECK(cache.has(obj3)); 603 CHECK(cache.has(obj4)); 604 605 // Test remove while sweeping. 606 607 cache.remove(obj3); 608 609 CHECK(GCUntilCacheSweep(cx, cache)); 610 611 cache.remove(obj4); 612 613 CHECK(SweepCacheAndFinishGC(cx, cache)); 614 615 CHECK(cache.count() == 2); 616 CHECK(!cache.has(obj3)); 617 CHECK(!cache.has(obj4)); 618 619 // Test putNew while sweeping. 620 621 CHECK(GCUntilCacheSweep(cx, cache)); 622 623 CHECK(cache.putNew(obj3, 3)); 624 CHECK(cache.putNew(obj4, 4)); 625 626 CHECK(SweepCacheAndFinishGC(cx, cache)); 627 628 CHECK(cache.count() == 4); 629 CHECK(cache.has(obj3)); 630 CHECK(cache.has(obj4)); 631 632 cache.clear(); 633 634 return true; 635 } 636 637 bool TestReplaceDyingInSet() { 638 // Test replacing dying entries with ones that have the same key using the 639 // various APIs. 640 641 using Cache = JS::WeakCache< 642 GCHashSet<NumberAndObjectEntry, StableCellHasher<NumberAndObjectEntry>, 643 TempAllocPolicy>>; 644 Cache cache(JS::GetObjectZone(global), cx); 645 646 RootedObject value1(cx, JS_NewPlainObject(cx)); 647 RootedObject value2(cx, JS_NewPlainObject(cx)); 648 CHECK(value1); 649 CHECK(value2); 650 651 CHECK(cache.put(NumberAndObjectEntry(1, value1))); 652 CHECK(cache.put(NumberAndObjectEntry(2, value2))); 653 CHECK(cache.put(NumberAndObjectEntry(3, value2))); 654 CHECK(cache.put(NumberAndObjectEntry(4, value2))); 655 CHECK(cache.put(NumberAndObjectEntry(5, value2))); 656 CHECK(cache.put(NumberAndObjectEntry(6, value2))); 657 CHECK(cache.put(NumberAndObjectEntry(7, value2))); 658 659 value2 = nullptr; 660 CHECK(GCUntilCacheSweep(cx, cache)); 661 662 CHECK(!cache.has(NumberAndObjectLookup(2, value1))); 663 CHECK(!cache.has(NumberAndObjectLookup(3, value1))); 664 CHECK(!cache.has(NumberAndObjectLookup(4, value1))); 665 CHECK(!cache.has(NumberAndObjectLookup(5, value1))); 666 CHECK(!cache.has(NumberAndObjectLookup(6, value1))); 667 668 auto ptr = cache.lookupForAdd(NumberAndObjectLookup(2, value1)); 669 CHECK(!ptr); 670 CHECK(cache.add(ptr, NumberAndObjectEntry(2, value1))); 671 672 auto ptr2 = cache.lookupForAdd(NumberAndObjectLookup(3, value1)); 673 CHECK(!ptr2); 674 CHECK(cache.relookupOrAdd(ptr2, NumberAndObjectLookup(3, value1), 675 NumberAndObjectEntry(3, value1))); 676 677 CHECK(cache.put(NumberAndObjectEntry(4, value1))); 678 CHECK(cache.putNew(NumberAndObjectEntry(5, value1))); 679 680 CHECK(cache.putNew(NumberAndObjectLookup(6, value1), 681 NumberAndObjectEntry(6, value1))); 682 683 CHECK(SweepCacheAndFinishGC(cx, cache)); 684 685 CHECK(cache.count() == 6); 686 CHECK(cache.has(NumberAndObjectLookup(1, value1))); 687 CHECK(cache.has(NumberAndObjectLookup(2, value1))); 688 CHECK(cache.has(NumberAndObjectLookup(3, value1))); 689 CHECK(cache.has(NumberAndObjectLookup(4, value1))); 690 CHECK(cache.has(NumberAndObjectLookup(5, value1))); 691 CHECK(cache.has(NumberAndObjectLookup(6, value1))); 692 693 return true; 694 } 695 696 bool TestReplaceDyingInMap() { 697 // Test replacing dying entries with ones that have the same key using the 698 // various APIs. 699 700 using Cache = 701 JS::WeakCache<GCHashMap<uint32_t, HeapPtr<JSObject*>, 702 DefaultHasher<uint32_t>, TempAllocPolicy>>; 703 Cache cache(JS::GetObjectZone(global), cx); 704 705 RootedObject value1(cx, JS_NewPlainObject(cx)); 706 RootedObject value2(cx, JS_NewPlainObject(cx)); 707 CHECK(value1); 708 CHECK(value2); 709 710 CHECK(cache.put(1, value1)); 711 CHECK(cache.put(2, value2)); 712 CHECK(cache.put(3, value2)); 713 CHECK(cache.put(4, value2)); 714 CHECK(cache.put(5, value2)); 715 CHECK(cache.put(6, value2)); 716 717 value2 = nullptr; 718 CHECK(GCUntilCacheSweep(cx, cache)); 719 720 CHECK(!cache.has(2)); 721 CHECK(!cache.has(3)); 722 CHECK(!cache.has(4)); 723 CHECK(!cache.has(5)); 724 CHECK(!cache.has(6)); 725 726 auto ptr = cache.lookupForAdd(2); 727 CHECK(!ptr); 728 CHECK(cache.add(ptr, 2, value1)); 729 730 auto ptr2 = cache.lookupForAdd(3); 731 CHECK(!ptr2); 732 CHECK(cache.add(ptr2, 3, HeapPtr<JSObject*>())); 733 734 auto ptr3 = cache.lookupForAdd(4); 735 CHECK(!ptr3); 736 CHECK(cache.relookupOrAdd(ptr3, 4, value1)); 737 738 CHECK(cache.put(5, value1)); 739 CHECK(cache.putNew(6, value1)); 740 741 CHECK(SweepCacheAndFinishGC(cx, cache)); 742 743 CHECK(cache.count() == 6); 744 CHECK(cache.lookup(1)->value() == value1); 745 CHECK(cache.lookup(2)->value() == value1); 746 CHECK(cache.lookup(3)->value() == nullptr); 747 CHECK(cache.lookup(4)->value() == value1); 748 CHECK(cache.lookup(5)->value() == value1); 749 CHECK(cache.lookup(6)->value() == value1); 750 751 return true; 752 } 753 754 bool TestUniqueIDLookups() { 755 // Test hash table lookups during incremental sweeping where the hash is 756 // generated based on a unique ID. The problem is that the unique ID table 757 // will have already been swept by this point so looking up a dead pointer 758 // in the table will fail. This lookup happens if we try to match a live key 759 // against a dead table entry with the same hash code. 760 761 const size_t DeadFactor = 3; 762 const size_t ObjectCount = 100; 763 764 using Cache = JS::WeakCache< 765 GCHashSet<ObjectEntry, StableCellHasher<ObjectEntry>, TempAllocPolicy>>; 766 Cache cache(JS::GetObjectZone(global), cx); 767 768 Rooted<GCVector<JSObject*, 0, SystemAllocPolicy>> liveObjects(cx); 769 770 for (size_t j = 0; j < ObjectCount; j++) { 771 JSObject* obj = JS_NewPlainObject(cx); 772 CHECK(obj); 773 CHECK(cache.put(obj)); 774 if (j % DeadFactor == 0) { 775 CHECK(liveObjects.get().append(obj)); 776 } 777 } 778 779 CHECK(cache.count() == ObjectCount); 780 781 CHECK(GCUntilCacheSweep(cx, cache)); 782 783 for (size_t j = 0; j < liveObjects.length(); j++) { 784 CHECK(cache.has(liveObjects[j])); 785 } 786 787 CHECK(SweepCacheAndFinishGC(cx, cache)); 788 789 CHECK(cache.count() == liveObjects.length()); 790 791 return true; 792 } 793 794 END_TEST(testIncrementalWeakCacheSweeping) 795 796 #endif // defined JS_GC_ZEAL