testGCHeapBarriers.cpp (23943B)
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/AllocKind.h" 9 #include "gc/Cell.h" 10 #include "gc/GCInternals.h" 11 #include "gc/GCRuntime.h" 12 #include "js/ArrayBuffer.h" // JS::NewArrayBuffer 13 #include "js/experimental/TypedData.h" 14 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty 15 #include "js/RootingAPI.h" 16 #include "jsapi-tests/tests.h" 17 #include "vm/Runtime.h" 18 #include "vm/TypedArrayObject.h" 19 20 #include "gc/Heap-inl.h" 21 #include "vm/JSContext-inl.h" 22 23 using namespace js; 24 25 static js::gc::CellColor GetColor(JSObject* obj) { return obj->color(); } 26 static js::gc::CellColor GetColor(const JS::ArrayBufferOrView& view) { 27 return view.asObjectUnbarriered()->color(); 28 } 29 30 [[maybe_unused]] static bool IsInsideNursery(gc::Cell* cell) { 31 return !cell->isTenured(); 32 } 33 [[maybe_unused]] static bool IsInsideNursery( 34 const JS::ArrayBufferOrView& view) { 35 return IsInsideNursery(view.asObjectUnbarriered()); 36 } 37 38 // A heap-allocated structure containing one of our barriered pointer wrappers 39 // to test. 40 template <typename W, typename T> 41 struct TestStruct { 42 W wrapper; 43 44 void trace(JSTracer* trc) { 45 TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper"); 46 } 47 48 TestStruct() {} 49 explicit TestStruct(T init) : wrapper(init) {} 50 }; 51 52 template <typename T> 53 static T CreateNurseryGCThing(JSContext* cx) = delete; 54 55 template <> 56 JSObject* CreateNurseryGCThing(JSContext* cx) { 57 JS::RootedObject obj(cx, JS_NewPlainObject(cx)); 58 if (!obj) { 59 return nullptr; 60 } 61 JS_DefineProperty(cx, obj, "x", 42, 0); 62 MOZ_ASSERT(IsInsideNursery(obj)); 63 return obj; 64 } 65 66 template <> 67 JSFunction* CreateNurseryGCThing(JSContext* cx) { 68 /* 69 * We don't actually use the function as a function, so here we cheat and 70 * cast a JSObject. 71 */ 72 return static_cast<JSFunction*>(CreateNurseryGCThing<JSObject*>(cx)); 73 } 74 75 template <> 76 JS::Uint8Array CreateNurseryGCThing(JSContext* cx) { 77 JS::Rooted<JS::Uint8Array> arr(cx, JS::Uint8Array::create(cx, 100)); 78 JS::RootedObject obj(cx, arr.asObject()); 79 JS_DefineProperty(cx, obj, "x", 42, 0); 80 MOZ_ASSERT(IsInsideNursery(obj)); 81 return arr; 82 } 83 84 template <typename T> 85 static T CreateTenuredGCThing(JSContext* cx) = delete; 86 87 template <> 88 JSObject* CreateTenuredGCThing(JSContext* cx) { 89 // Use ArrayBuffers because they have finalizers, which allows using them in 90 // TenuredHeap<> without awkward conversations about nursery allocatability. 91 // Note that at some point ArrayBuffers might become nursery allocated at 92 // which point this test will have to change. 93 JSObject* obj = JS::NewArrayBuffer(cx, 20); 94 MOZ_ASSERT(!IsInsideNursery(obj)); 95 MOZ_ASSERT(obj->getClass()->hasFinalize() && 96 !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE)); 97 return obj; 98 } 99 100 template <> 101 JS::ArrayBuffer CreateTenuredGCThing(JSContext* cx) { 102 return JS::ArrayBuffer::fromObject(CreateTenuredGCThing<JSObject*>(cx)); 103 } 104 105 template <> 106 JS::Uint8Array CreateTenuredGCThing(JSContext* cx) { 107 // Use internal APIs that lets us specify the InitialHeap so we can ensure 108 // that this is tenured. 109 JSObject* obj = js::NewUint8ArrayWithLength(cx, 100, gc::Heap::Tenured); 110 MOZ_ASSERT(!IsInsideNursery(obj)); 111 return JS::Uint8Array::fromObject(obj); 112 } 113 114 template <typename T> 115 void* CreateHiddenTenuredGCThing(JSContext* cx) { 116 return CreateTenuredGCThing<T>(cx); 117 } 118 119 template <> 120 void* CreateHiddenTenuredGCThing<JS::ArrayBuffer>(JSContext* cx) { 121 return CreateTenuredGCThing<JS::ArrayBuffer>(cx).asObjectUnbarriered(); 122 } 123 124 template <> 125 void* CreateHiddenTenuredGCThing<JS::Uint8Array>(JSContext* cx) { 126 return CreateTenuredGCThing<JS::Uint8Array>(cx).asObjectUnbarriered(); 127 } 128 129 static uintptr_t UnbarrieredCastToInt(gc::Cell* cell) { 130 return reinterpret_cast<uintptr_t>(cell); 131 } 132 static uintptr_t UnbarrieredCastToInt(const JS::ArrayBufferOrView& view) { 133 return UnbarrieredCastToInt(view.asObjectUnbarriered()); 134 } 135 136 template <typename T> 137 T RecoverHiddenGCThing(void* ptr) { 138 return reinterpret_cast<T>(ptr); 139 } 140 141 template <> 142 JS::ArrayBuffer RecoverHiddenGCThing(void* ptr) { 143 return JS::ArrayBuffer::fromObject(RecoverHiddenGCThing<JSObject*>(ptr)); 144 } 145 146 template <> 147 JS::Uint8Array RecoverHiddenGCThing(void* ptr) { 148 return JS::Uint8Array::fromObject(RecoverHiddenGCThing<JSObject*>(ptr)); 149 } 150 151 static void MakeGray(JSObject* obj) { 152 gc::TenuredCell* cell = &obj->asTenured(); 153 cell->unmark(); 154 cell->markIfUnmarked(gc::MarkColor::Gray); 155 MOZ_ASSERT(obj->isMarkedGray()); 156 } 157 158 static void MakeGray(const JS::ArrayBufferOrView& view) { 159 MakeGray(view.asObjectUnbarriered()); 160 } 161 162 // Test post-barrier implementation on wrapper types. The following wrapper 163 // types have post barriers: 164 // - JS::Heap 165 // - GCPtr 166 // - HeapPtr 167 // - WeakHeapPtr 168 BEGIN_TEST(testGCHeapPostBarriers) { 169 AutoLeaveZeal nozeal(cx); 170 AutoGCParameter disableSemispace(cx, JSGC_SEMISPACE_NURSERY_ENABLED, 0); 171 172 /* Sanity check - objects start in the nursery and then become tenured. */ 173 JS_GC(cx); 174 JS::RootedObject obj(cx, CreateNurseryGCThing<JSObject*>(cx)); 175 CHECK(IsInsideNursery(obj.get())); 176 JS_GC(cx); 177 CHECK(!IsInsideNursery(obj.get())); 178 JS::RootedObject tenuredObject(cx, obj); 179 180 /* JSObject and JSFunction objects are nursery allocated. */ 181 CHECK(TestHeapPostBarriersForType<JSObject*>()); 182 CHECK(TestHeapPostBarriersForType<JSFunction*>()); 183 CHECK(TestHeapPostBarriersForType<JS::Uint8Array>()); 184 // Bug 1599378: Add string tests. 185 186 return true; 187 } 188 189 [[nodiscard]] bool TestCanAccessObject(JSObject* obj) { 190 JS::RootedObject rootedObj(cx, obj); 191 JS::RootedValue value(cx); 192 CHECK(JS_GetProperty(cx, rootedObj, "x", &value)); 193 CHECK(value.isInt32()); 194 CHECK(value.toInt32() == 42); 195 return true; 196 } 197 [[nodiscard]] bool TestCanAccessObject(const JS::ArrayBufferOrView& view) { 198 return TestCanAccessObject(view.asObject()); 199 } 200 201 template <typename T> 202 [[nodiscard]] bool TestHeapPostBarriersForType() { 203 CHECK((TestHeapPostBarriersForWrapper<js::GCPtr, T>())); 204 CHECK((TestHeapPostBarriersForMovableWrapper<JS::Heap, T>())); 205 CHECK((TestHeapPostBarriersForMovableWrapper<js::HeapPtr, T>())); 206 CHECK((TestHeapPostBarriersForMovableWrapper<js::WeakHeapPtr, T>())); 207 return true; 208 } 209 210 template <template <typename> class W, typename T> 211 [[nodiscard]] bool TestHeapPostBarriersForMovableWrapper() { 212 CHECK((TestHeapPostBarriersForWrapper<W, T>())); 213 CHECK((TestHeapPostBarrierMoveConstruction<W<T>, T>())); 214 CHECK((TestHeapPostBarrierMoveAssignment<W<T>, T>())); 215 return true; 216 } 217 218 template <template <typename> class W, typename T> 219 [[nodiscard]] bool TestHeapPostBarriersForWrapper() { 220 CHECK((TestHeapPostBarrierConstruction<W<T>, T>())); 221 CHECK((TestHeapPostBarrierConstruction<const W<T>, T>())); 222 CHECK((TestHeapPostBarrierUpdate<W<T>, T>())); 223 if constexpr (!std::is_same_v<W<T>, GCPtr<T>>) { 224 // It is not allowed to delete heap memory containing GCPtrs on 225 // initialization failure like this and doing so will cause an assertion to 226 // fail in the GCPtr destructor (although we disable this in some places in 227 // this test). 228 CHECK((TestHeapPostBarrierInitFailure<W<T>, T>())); 229 CHECK((TestHeapPostBarrierInitFailure<const W<T>, T>())); 230 } 231 return true; 232 } 233 234 template <typename W, typename T> 235 [[nodiscard]] bool TestHeapPostBarrierConstruction() { 236 T initialObj = CreateNurseryGCThing<T>(cx); 237 CHECK(initialObj); 238 CHECK(IsInsideNursery(initialObj)); 239 uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj); 240 241 { 242 // We don't root our structure because that would end up tracing it on minor 243 // GC and we're testing that heap post barrier works for things that aren't 244 // roots. 245 JS::AutoSuppressGCAnalysis noAnalysis(cx); 246 247 auto* testStruct = js_new<TestStruct<W, T>>(initialObj); 248 CHECK(testStruct); 249 250 auto& wrapper = testStruct->wrapper; 251 CHECK(wrapper == initialObj); 252 253 cx->minorGC(JS::GCReason::API); 254 255 CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt); 256 CHECK(!IsInsideNursery(wrapper.get())); 257 CHECK(TestCanAccessObject(wrapper.get())); 258 259 // Disable the check that GCPtrs are only destroyed by the GC. What happens 260 // on destruction isn't relevant to the test. 261 gc::AutoSetThreadIsFinalizing threadIsFinalizing; 262 263 js_delete(testStruct); 264 } 265 266 cx->minorGC(JS::GCReason::API); 267 268 return true; 269 } 270 271 template <typename W, typename T> 272 [[nodiscard]] bool TestHeapPostBarrierUpdate() { 273 // Normal case - allocate a heap object, write a nursery pointer into it and 274 // check that it gets updated on minor GC. 275 276 T initialObj = CreateNurseryGCThing<T>(cx); 277 CHECK(initialObj); 278 CHECK(IsInsideNursery(initialObj)); 279 uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj); 280 281 { 282 // We don't root our structure because that would end up tracing it on minor 283 // GC and we're testing that heap post barrier works for things that aren't 284 // roots. 285 JS::AutoSuppressGCAnalysis noAnalysis(cx); 286 287 auto* testStruct = js_new<TestStruct<W, T>>(); 288 CHECK(testStruct); 289 290 auto& wrapper = testStruct->wrapper; 291 CHECK(!wrapper.get()); 292 293 wrapper = initialObj; 294 CHECK(wrapper == initialObj); 295 296 cx->minorGC(JS::GCReason::API); 297 298 CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt); 299 CHECK(!IsInsideNursery(wrapper.get())); 300 CHECK(TestCanAccessObject(wrapper.get())); 301 302 // Disable the check that GCPtrs are only destroyed by the GC. What happens 303 // on destruction isn't relevant to the test. 304 gc::AutoSetThreadIsFinalizing threadIsFinalizing; 305 306 js_delete(testStruct); 307 } 308 309 cx->minorGC(JS::GCReason::API); 310 311 return true; 312 } 313 314 template <typename W, typename T> 315 [[nodiscard]] bool TestHeapPostBarrierInitFailure() { 316 // Failure case - allocate a heap object, write a nursery pointer into it 317 // and fail to complete initialization. 318 319 T initialObj = CreateNurseryGCThing<T>(cx); 320 CHECK(initialObj); 321 CHECK(IsInsideNursery(initialObj)); 322 323 { 324 // We don't root our structure because that would end up tracing it on minor 325 // GC and we're testing that heap post barrier works for things that aren't 326 // roots. 327 JS::AutoSuppressGCAnalysis noAnalysis(cx); 328 329 auto testStruct = cx->make_unique<TestStruct<W, T>>(initialObj); 330 CHECK(testStruct); 331 332 auto& wrapper = testStruct->wrapper; 333 CHECK(wrapper == initialObj); 334 335 // testStruct deleted here, as if we left this block due to an error. 336 } 337 338 cx->minorGC(JS::GCReason::API); 339 340 return true; 341 } 342 343 template <typename W, typename T> 344 [[nodiscard]] bool TestHeapPostBarrierMoveConstruction() { 345 T initialObj = CreateNurseryGCThing<T>(cx); 346 CHECK(initialObj); 347 CHECK(IsInsideNursery(initialObj)); 348 uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj); 349 350 { 351 // We don't root our structure because that would end up tracing it on minor 352 // GC and we're testing that heap post barrier works for things that aren't 353 // roots. 354 JS::AutoSuppressGCAnalysis noAnalysis(cx); 355 356 W wrapper1(initialObj); 357 CHECK(wrapper1 == initialObj); 358 359 W wrapper2(std::move(wrapper1)); 360 CHECK(wrapper2 == initialObj); 361 362 cx->minorGC(JS::GCReason::API); 363 364 CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt); 365 CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt); 366 CHECK(!IsInsideNursery(wrapper2.get())); 367 CHECK(TestCanAccessObject(wrapper2.get())); 368 } 369 370 cx->minorGC(JS::GCReason::API); 371 372 return true; 373 } 374 375 template <typename W, typename T> 376 [[nodiscard]] bool TestHeapPostBarrierMoveAssignment() { 377 T initialObj = CreateNurseryGCThing<T>(cx); 378 CHECK(initialObj); 379 CHECK(IsInsideNursery(initialObj)); 380 uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj); 381 382 { 383 // We don't root our structure because that would end up tracing it on minor 384 // GC and we're testing that heap post barrier works for things that aren't 385 // roots. 386 JS::AutoSuppressGCAnalysis noAnalysis(cx); 387 388 W wrapper1(initialObj); 389 CHECK(wrapper1 == initialObj); 390 391 W wrapper2; 392 wrapper2 = std::move(wrapper1); 393 CHECK(wrapper2 == initialObj); 394 395 cx->minorGC(JS::GCReason::API); 396 397 CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt); 398 CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt); 399 CHECK(!IsInsideNursery(wrapper2.get())); 400 CHECK(TestCanAccessObject(wrapper2.get())); 401 } 402 403 cx->minorGC(JS::GCReason::API); 404 405 return true; 406 } 407 408 END_TEST(testGCHeapPostBarriers) 409 410 // Test read barrier implementation on wrapper types. The following wrapper 411 // types have read barriers: 412 // - JS::Heap 413 // - JS::TenuredHeap 414 // - WeakHeapPtr 415 // 416 // Also check that equality comparisons on wrappers do not trigger the read 417 // barrier. 418 BEGIN_TEST(testGCHeapReadBarriers) { 419 AutoLeaveZeal nozeal(cx); 420 421 CHECK((TestWrapperType<JS::Heap<JSObject*>, JSObject*>())); 422 CHECK((TestWrapperType<JS::TenuredHeap<JSObject*>, JSObject*>())); 423 CHECK((TestWrapperType<WeakHeapPtr<JSObject*>, JSObject*>())); 424 CHECK((TestWrapperType<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>())); 425 CHECK((TestWrapperType<JS::Heap<JS::Uint8Array>, JS::Uint8Array>())); 426 427 return true; 428 } 429 430 template <typename WrapperT, typename ObjectT> 431 [[nodiscard]] bool TestWrapperType() { 432 // Check that the read barrier normally marks gray things black. 433 CHECK((TestReadBarrierUnmarksGray<WrapperT, ObjectT>())); 434 435 // Check that the read barrier marks gray and white things black during an 436 // incremental GC. 437 CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(true))); 438 CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(false))); 439 440 // Allocate test objects and make them gray. We will make sure they stay 441 // gray. (For most reads, the barrier will unmark gray.) 442 Rooted<ObjectT> obj1(cx, CreateTenuredGCThing<ObjectT>(cx)); 443 Rooted<ObjectT> obj2(cx, CreateTenuredGCThing<ObjectT>(cx)); 444 MakeGray(obj1); 445 MakeGray(obj2); 446 447 WrapperT wrapper1(obj1); 448 WrapperT wrapper2(obj2); 449 const ObjectT constobj1 = obj1; 450 const ObjectT constobj2 = obj2; 451 CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(obj1, obj2, wrapper1, 452 wrapper2))); 453 CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(constobj1, constobj2, 454 wrapper1, wrapper2))); 455 456 return true; 457 } 458 459 template <typename WrapperT, typename ObjectT> 460 void Access(const WrapperT& wrapper) { 461 if constexpr (std::is_base_of_v<JS::ArrayBufferOrView, ObjectT>) { 462 (void)wrapper.asObject(); 463 } else { 464 (void)*wrapper; 465 } 466 } 467 468 template <typename WrapperT, typename ObjectT> 469 [[nodiscard]] bool TestReadBarrierUnmarksGray() { 470 Rooted<ObjectT> obj(cx, CreateTenuredGCThing<ObjectT>(cx)); 471 WrapperT wrapper(obj); 472 473 CHECK(GetColor(obj) == gc::CellColor::White); 474 475 Access<WrapperT, ObjectT>(wrapper); 476 477 CHECK(GetColor(obj) == gc::CellColor::White); 478 479 MakeGray(obj); 480 Access<WrapperT, ObjectT>(wrapper); 481 482 CHECK(GetColor(obj) == gc::CellColor::Black); 483 484 return true; 485 } 486 487 // Execute thunk |f| between two slices of an incremental GC controlled by zeal 488 // mode |mode|. 489 template <typename F> 490 [[nodiscard]] bool CallDuringIncrementalGC(uint32_t mode, F&& f) { 491 #ifndef JS_GC_ZEAL 492 fprintf(stderr, "This test requires building with --enable-gczeal\n"); 493 #else 494 AutoGCParameter incremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true); 495 496 const int64_t BudgetMS = 10000; // 10S should be long enough for anyone. 497 498 JS::SetGCZeal(cx, mode, 0); 499 JS::PrepareZoneForGC(cx, js::GetContextZone(cx)); 500 JS::SliceBudget budget{JS::TimeBudget(BudgetMS)}; 501 JS::StartIncrementalGC(cx, JS::GCOptions(), JS::GCReason::DEBUG_GC, budget); 502 CHECK(JS::IsIncrementalGCInProgress(cx)); 503 504 CHECK(f()); 505 506 JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC); 507 #endif 508 509 return true; 510 } 511 512 template <typename WrapperT, typename ObjectT> 513 [[nodiscard]] bool TestReadBarrierMarksBlack(bool fromWhite) { 514 AutoLeaveZeal noZeal(cx); 515 516 // Create an object and hide it from the hazard analysis. 517 void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx); 518 CHECK(ptr); 519 520 CHECK(CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool { 521 CHECK(JS::IsIncrementalBarrierNeeded(cx)); 522 523 auto obj = RecoverHiddenGCThing<ObjectT>(ptr); 524 525 WrapperT wrapper(obj); 526 527 CHECK(GetColor(obj) == gc::CellColor::White); 528 if (!fromWhite) { 529 MakeGray(obj); 530 } 531 532 Access<WrapperT, ObjectT>(wrapper); 533 534 CHECK(GetColor(obj) == gc::CellColor::Black); 535 536 return true; 537 })); 538 539 return true; 540 } 541 542 template <typename WrapperT, typename ObjectT> 543 [[nodiscard]] bool TestUnbarrieredOperations(ObjectT obj, ObjectT obj2, 544 WrapperT& wrapper, 545 WrapperT& wrapper2) { 546 (void)bool(wrapper); 547 (void)bool(wrapper2); 548 CHECK(GetColor(obj) == gc::CellColor::Gray); 549 CHECK(GetColor(obj2) == gc::CellColor::Gray); 550 551 int x = 0; 552 553 CHECK(GetColor(obj) == gc::CellColor::Gray); 554 CHECK(GetColor(obj2) == gc::CellColor::Gray); 555 x += obj == obj2; 556 CHECK(GetColor(obj) == gc::CellColor::Gray); 557 CHECK(GetColor(obj2) == gc::CellColor::Gray); 558 x += obj == wrapper2; 559 CHECK(GetColor(obj) == gc::CellColor::Gray); 560 CHECK(GetColor(obj2) == gc::CellColor::Gray); 561 x += wrapper == obj2; 562 CHECK(GetColor(obj) == gc::CellColor::Gray); 563 CHECK(GetColor(obj2) == gc::CellColor::Gray); 564 x += wrapper == wrapper2; 565 CHECK(GetColor(obj) == gc::CellColor::Gray); 566 CHECK(GetColor(obj2) == gc::CellColor::Gray); 567 568 CHECK(x == 0); 569 570 x += obj != obj2; 571 CHECK(GetColor(obj) == gc::CellColor::Gray); 572 CHECK(GetColor(obj2) == gc::CellColor::Gray); 573 x += obj != wrapper2; 574 CHECK(GetColor(obj) == gc::CellColor::Gray); 575 CHECK(GetColor(obj2) == gc::CellColor::Gray); 576 x += wrapper != obj2; 577 CHECK(GetColor(obj) == gc::CellColor::Gray); 578 CHECK(GetColor(obj2) == gc::CellColor::Gray); 579 x += wrapper != wrapper2; 580 CHECK(GetColor(obj) == gc::CellColor::Gray); 581 CHECK(GetColor(obj2) == gc::CellColor::Gray); 582 583 CHECK(x == 4); 584 585 return true; 586 } 587 588 END_TEST(testGCHeapReadBarriers) 589 590 using ObjectVector = Vector<JSObject*, 0, SystemAllocPolicy>; 591 592 // Test pre-barrier implementation on wrapper types. The following wrapper types 593 // have a pre-barrier: 594 // - GCPtr 595 // - HeapPtr 596 // - PreBarriered 597 BEGIN_TEST(testGCHeapPreBarriers) { 598 AutoLeaveZeal nozeal(cx); 599 600 AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true); 601 602 // Create a bunch of objects. These are unrooted and will be used to test 603 // whether barriers have fired by checking whether they have been marked 604 // black. 605 size_t objectCount = 100; // Increase this if necessary when adding tests. 606 ObjectVector testObjects; 607 for (size_t i = 0; i < objectCount; i++) { 608 JSObject* obj = CreateTenuredGCThing<JSObject*>(cx); 609 CHECK(obj); 610 CHECK(testObjects.append(obj)); 611 } 612 613 // Start an incremental GC so we can detect if we cause barriers to fire, as 614 // these will mark objects black. 615 JS::PrepareForFullGC(cx); 616 JS::SliceBudget budget(JS::WorkBudget(1)); 617 gc::GCRuntime* gc = &cx->runtime()->gc; 618 gc->startDebugGC(JS::GCOptions::Normal, budget); 619 while (gc->state() != gc::State::Mark) { 620 gc->debugGCSlice(budget); 621 } 622 MOZ_ASSERT(cx->zone()->needsIncrementalBarrier()); 623 624 CHECK(TestWrapper<HeapPtr<JSObject*>>(testObjects)); 625 CHECK(TestWrapper<PreBarriered<JSObject*>>(testObjects)); 626 627 // GCPtr is different because 1) it doesn't support move operations as it's 628 // supposed to be part of a GC thing and 2) it doesn't perform a pre-barrier 629 // in its destructor because these are only destroyed as part of a GC where 630 // the barrier is unnecessary. 631 CHECK(TestGCPtr(testObjects)); 632 633 gc::FinishGC(cx, JS::GCReason::API); 634 635 return true; 636 } 637 638 template <typename Wrapper> 639 [[nodiscard]] bool TestWrapper(ObjectVector& testObjects) { 640 CHECK(TestCopyConstruction<Wrapper>(testObjects.popCopy())); 641 CHECK(TestMoveConstruction<Wrapper>(testObjects.popCopy())); 642 CHECK(TestAssignment<Wrapper>(testObjects.popCopy(), testObjects.popCopy())); 643 CHECK(TestMoveAssignment<Wrapper>(testObjects.popCopy(), 644 testObjects.popCopy())); 645 return true; 646 } 647 648 template <typename Wrapper> 649 [[nodiscard]] bool TestCopyConstruction(JSObject* obj) { 650 CHECK(GetColor(obj) == gc::CellColor::White); 651 652 { 653 Wrapper wrapper1(obj); 654 Wrapper wrapper2(wrapper1); 655 CHECK(wrapper1 == obj); 656 CHECK(wrapper2 == obj); 657 CHECK(GetColor(obj) == gc::CellColor::White); 658 } 659 660 // Check destructor performs pre-barrier. 661 CHECK(GetColor(obj) == gc::CellColor::Black); 662 663 return true; 664 } 665 666 template <typename Wrapper> 667 [[nodiscard]] bool TestMoveConstruction(JSObject* obj) { 668 CHECK(GetColor(obj) == gc::CellColor::White); 669 670 { 671 Wrapper wrapper1(obj); 672 MakeGray(obj); // Check that we allow move of gray GC thing. 673 Wrapper wrapper2(std::move(wrapper1)); 674 CHECK(!wrapper1); 675 CHECK(wrapper2 == obj); 676 CHECK(GetColor(obj) == gc::CellColor::Gray); 677 } 678 679 // Check destructor performs pre-barrier. 680 CHECK(GetColor(obj) == gc::CellColor::Black); 681 682 return true; 683 } 684 685 template <typename Wrapper> 686 [[nodiscard]] bool TestAssignment(JSObject* obj1, JSObject* obj2) { 687 CHECK(GetColor(obj1) == gc::CellColor::White); 688 CHECK(GetColor(obj2) == gc::CellColor::White); 689 690 { 691 Wrapper wrapper1(obj1); 692 Wrapper wrapper2(obj2); 693 694 wrapper2 = wrapper1; 695 696 CHECK(wrapper1 == obj1); 697 CHECK(wrapper2 == obj1); 698 CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired. 699 CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired. 700 } 701 702 // Check destructor performs pre-barrier. 703 CHECK(GetColor(obj1) == gc::CellColor::Black); 704 705 return true; 706 } 707 708 template <typename Wrapper> 709 [[nodiscard]] bool TestMoveAssignment(JSObject* obj1, JSObject* obj2) { 710 CHECK(GetColor(obj1) == gc::CellColor::White); 711 CHECK(GetColor(obj2) == gc::CellColor::White); 712 713 { 714 Wrapper wrapper1(obj1); 715 Wrapper wrapper2(obj2); 716 717 MakeGray(obj1); // Check we allow move of gray thing. 718 wrapper2 = std::move(wrapper1); 719 720 CHECK(!wrapper1); 721 CHECK(wrapper2 == obj1); 722 CHECK(GetColor(obj1) == gc::CellColor::Gray); // No barrier fired. 723 CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired. 724 } 725 726 // Check destructor performs pre-barrier. 727 CHECK(GetColor(obj1) == gc::CellColor::Black); 728 729 return true; 730 } 731 732 [[nodiscard]] bool TestGCPtr(ObjectVector& testObjects) { 733 CHECK(TestGCPtrCopyConstruction(testObjects.popCopy())); 734 CHECK(TestGCPtrAssignment(testObjects.popCopy(), testObjects.popCopy())); 735 return true; 736 } 737 738 [[nodiscard]] bool TestGCPtrCopyConstruction(JSObject* obj) { 739 CHECK(GetColor(obj) == gc::CellColor::White); 740 741 { 742 // Let us destroy GCPtrs ourselves for testing purposes. 743 gc::AutoSetThreadIsFinalizing threadIsFinalizing; 744 745 GCPtr<JSObject*> wrapper1(obj); 746 GCPtr<JSObject*> wrapper2(wrapper1); 747 CHECK(wrapper1 == obj); 748 CHECK(wrapper2 == obj); 749 CHECK(GetColor(obj) == gc::CellColor::White); 750 } 751 752 // GCPtr doesn't perform pre-barrier in destructor. 753 CHECK(GetColor(obj) == gc::CellColor::White); 754 755 return true; 756 } 757 758 [[nodiscard]] bool TestGCPtrAssignment(JSObject* obj1, JSObject* obj2) { 759 CHECK(GetColor(obj1) == gc::CellColor::White); 760 CHECK(GetColor(obj2) == gc::CellColor::White); 761 762 { 763 // Let us destroy GCPtrs ourselves for testing purposes. 764 gc::AutoSetThreadIsFinalizing threadIsFinalizing; 765 766 GCPtr<JSObject*> wrapper1(obj1); 767 GCPtr<JSObject*> wrapper2(obj2); 768 769 wrapper2 = wrapper1; 770 771 CHECK(wrapper1 == obj1); 772 CHECK(wrapper2 == obj1); 773 CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired. 774 CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired. 775 } 776 777 // GCPtr doesn't perform pre-barrier in destructor. 778 CHECK(GetColor(obj1) == gc::CellColor::White); 779 780 return true; 781 } 782 783 END_TEST(testGCHeapPreBarriers)