Bug 1652962 - Allow FinalizationQueueObject to outlive FinalizationRegistry object r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Sat, 18 Jul 2020 08:04:17 +0000
changeset 541116 1f396812eae85fe830cc9f5c54f470e74eafe720
parent 541115 d6950b175d80c84baa2c1b5b5702a7ecda6b60b2
child 541117 53b5c91f375b8fe622e01330d4051616e02f4942
push id37614
push userccoroiu@mozilla.com
push dateSun, 19 Jul 2020 09:04:14 +0000
treeherdermozilla-central@e785ebabf7e1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1652962
milestone80.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1652962 - Allow FinalizationQueueObject to outlive FinalizationRegistry object r=sfink This rearranges things so that edge from FinalizationQueueObject to FinalizationRegistryObject is weak, and hence the latter can outlive the former. The weak edge from FinalizationRecordObject to FinalizationRegistryObject is replaced with a strong edge to FinalizationQueueObject. This removes the requirement to trace anything during sweeping. The FinalizationQueueObject is kept alive by the strong edges from FinalizationRecordObjects. As a result, they may live for an extra GC cycle more than they would otherwise (as the record objects did before), but this is not visible to users. This means we can remove GC support for doing extra marking work pushed during sweeping that was needed for this one case. This also means we can remove the set of active records that needs to be maintained. Differential Revision: https://phabricator.services.mozilla.com/D83760
js/src/builtin/FinalizationRegistryObject.cpp
js/src/builtin/FinalizationRegistryObject.h
js/src/gc/FinalizationRegistry.cpp
js/src/gc/GC.cpp
js/src/gc/GCEnum.h
--- a/js/src/builtin/FinalizationRegistryObject.cpp
+++ b/js/src/builtin/FinalizationRegistryObject.cpp
@@ -18,128 +18,59 @@
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
 ///////////////////////////////////////////////////////////////////////////
 // FinalizationRecordObject
 
 const JSClass FinalizationRecordObject::class_ = {
-    "FinalizationRecord", JSCLASS_HAS_RESERVED_SLOTS(SlotCount), &classOps_,
-    JS_NULL_CLASS_SPEC};
-
-const JSClassOps FinalizationRecordObject::classOps_ = {
-    nullptr,                          // addProperty
-    nullptr,                          // delProperty
-    nullptr,                          // enumerate
-    nullptr,                          // newEnumerate
-    nullptr,                          // resolve
-    nullptr,                          // mayResolve
-    nullptr,                          // finalize
-    nullptr,                          // call
-    nullptr,                          // hasInstance
-    nullptr,                          // construct
-    FinalizationRecordObject::trace,  // trace
-};
+    "FinalizationRecord", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
 
 /* static */
 FinalizationRecordObject* FinalizationRecordObject::create(
-    JSContext* cx, HandleFinalizationRegistryObject registry,
-    HandleValue heldValue) {
-  MOZ_ASSERT(registry);
+    JSContext* cx, HandleFinalizationQueueObject queue, HandleValue heldValue) {
+  MOZ_ASSERT(queue);
 
   auto record = NewObjectWithGivenProto<FinalizationRecordObject>(cx, nullptr);
   if (!record) {
     return nullptr;
   }
 
-  MOZ_ASSERT(registry->compartment() == record->compartment());
+  MOZ_ASSERT(queue->compartment() == record->compartment());
 
-  record->initReservedSlot(WeakRegistrySlot, PrivateValue(registry));
+  record->initReservedSlot(QueueSlot, ObjectValue(*queue));
   record->initReservedSlot(HeldValueSlot, heldValue);
 
   return record;
 }
 
-FinalizationRegistryObject* FinalizationRecordObject::registryDuringGC(
-    gc::GCRuntime* gc) const {
-  MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
-
-  FinalizationRegistryObject* registry = registryUnbarriered();
-
-  // Perform a manual read barrier. This is the only place where the GC itself
-  // needs to perform a read barrier so we must work around our assertions that
-  // this doesn't happen.
-  if (registry->zone()->isGCMarking()) {
-    FinalizationRegistryObject* tmp = registry;
-    TraceManuallyBarrieredEdge(&gc->marker, &tmp,
-                               "FinalizationRegistry read barrier");
-    MOZ_ASSERT(tmp == registry);
-  } else if (registry->isMarkedGray()) {
-    gc::UnmarkGrayGCThingUnchecked(gc->rt, JS::GCCellPtr(registry));
-  }
-
-  return registry;
-}
-
-FinalizationRegistryObject* FinalizationRecordObject::registryUnbarriered()
-    const {
-  Value value = getReservedSlot(WeakRegistrySlot);
+FinalizationQueueObject* FinalizationRecordObject::queue() const {
+  Value value = getReservedSlot(QueueSlot);
   if (value.isUndefined()) {
     return nullptr;
   }
-  return static_cast<FinalizationRegistryObject*>(value.toPrivate());
+  return &value.toObject().as<FinalizationQueueObject>();
 }
 
 Value FinalizationRecordObject::heldValue() const {
   return getReservedSlot(HeldValueSlot);
 }
 
 bool FinalizationRecordObject::isActive() const {
-  MOZ_ASSERT_IF(!registryUnbarriered(), heldValue().isUndefined());
-  return registryUnbarriered();
+  MOZ_ASSERT_IF(!queue(), heldValue().isUndefined());
+  return queue();
 }
 
 void FinalizationRecordObject::clear() {
-  MOZ_ASSERT(registryUnbarriered());
-  setReservedSlot(WeakRegistrySlot, UndefinedValue());
+  MOZ_ASSERT(queue());
+  setReservedSlot(QueueSlot, UndefinedValue());
   setReservedSlot(HeldValueSlot, UndefinedValue());
 }
 
-bool FinalizationRecordObject::sweep() {
-  FinalizationRegistryObject* obj = registryUnbarriered();
-  MOZ_ASSERT(obj);
-
-  if (IsAboutToBeFinalizedUnbarriered(&obj)) {
-    clear();
-    return false;
-  }
-
-  return true;
-}
-
-/* static */
-void FinalizationRecordObject::trace(JSTracer* trc, JSObject* obj) {
-  if (!trc->traceWeakEdges()) {
-    return;
-  }
-
-  auto record = &obj->as<FinalizationRecordObject>();
-  FinalizationRegistryObject* registry = record->registryUnbarriered();
-  if (!registry) {
-    return;
-  }
-
-  TraceManuallyBarrieredEdge(trc, &registry,
-                             "FinalizationRecordObject weak registry");
-  if (registry != record->registryUnbarriered()) {
-    record->setReservedSlot(WeakRegistrySlot, PrivateValue(registry));
-  }
-}
-
 ///////////////////////////////////////////////////////////////////////////
 // FinalizationRegistrationsObject
 
 const JSClass FinalizationRegistrationsObject::class_ = {
     "FinalizationRegistrations",
     JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE,
     &classOps_, JS_NULL_CLASS_SPEC};
 
@@ -308,71 +239,55 @@ bool FinalizationRegistryObject::constru
   }
 
   Rooted<UniquePtr<ObjectWeakMap>> registrations(
       cx, cx->make_unique<ObjectWeakMap>(cx));
   if (!registrations) {
     return false;
   }
 
-  Rooted<UniquePtr<FinalizationRecordSet>> activeRecords(
-      cx, cx->make_unique<FinalizationRecordSet>(cx->zone()));
-  if (!activeRecords) {
+  RootedFinalizationQueueObject queue(
+      cx, FinalizationQueueObject::create(cx, cleanupCallback));
+  if (!queue) {
     return false;
   }
 
   RootedFinalizationRegistryObject registry(
       cx, NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
   if (!registry) {
     return false;
   }
 
+  registry->initReservedSlot(QueueSlot, ObjectValue(*queue));
   InitReservedSlot(registry, RegistrationsSlot, registrations.release(),
                    MemoryUse::FinalizationRegistryRegistrations);
-  InitReservedSlot(registry, ActiveRecords, activeRecords.release(),
-                   MemoryUse::FinalizationRegistryRecordSet);
-
-  FinalizationQueueObject* queue =
-      FinalizationQueueObject::create(cx, registry, cleanupCallback);
-  if (!queue) {
-    return false;
-  }
-
-  registry->initReservedSlot(QueueSlot, ObjectValue(*queue));
 
   if (!cx->runtime()->gc.addFinalizationRegistry(cx, registry)) {
     return false;
   }
 
+  queue->setHasRegistry(true);
+
   args.rval().setObject(*registry);
   return true;
 }
 
 /* static */
 void FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj) {
   auto registry = &obj->as<FinalizationRegistryObject>();
 
   // Trace the registrations weak map. At most this traces the
   // FinalizationRegistrationsObject values of the map; the contents of those
   // objects are weakly held and are not traced.
   if (ObjectWeakMap* registrations = registry->registrations()) {
     registrations->trace(trc);
   }
-
-  // The active record set is weakly held and is not traced. For moving GC this
-  // is updated in sweep(), which is called for all FinalizationRegistryObjects
-  // in a zone.
 }
 
 void FinalizationRegistryObject::sweep() {
-  // Sweep the set of active records. These may die if CCWs to record objects
-  // get nuked.
-  MOZ_ASSERT(activeRecords());
-  activeRecords()->sweep();
-
   // Sweep the contents of the registrations weak map's values.
   MOZ_ASSERT(registrations());
   for (ObjectValueWeakMap::Enum e(registrations()->valueMap()); !e.empty();
        e.popFront()) {
     auto registrations =
         &e.front().value().toObject().as<FinalizationRegistrationsObject>();
     registrations->sweep();
     if (registrations->isEmpty()) {
@@ -380,37 +295,22 @@ void FinalizationRegistryObject::sweep()
     }
   }
 }
 
 /* static */
 void FinalizationRegistryObject::finalize(JSFreeOp* fop, JSObject* obj) {
   auto registry = &obj->as<FinalizationRegistryObject>();
 
-  // Clear the weak pointer to the registry in all remaining records.
-
-  // FinalizationRegistries are foreground finalized whereas record objects are
-  // background finalized, so record objects are guaranteed to still be
-  // accessible at this point.
-  MOZ_ASSERT(registry->getClass()->flags & JSCLASS_FOREGROUND_FINALIZE);
-
-  FinalizationRecordSet* allRecords = registry->activeRecords();
-  for (auto r = allRecords->all(); !r.empty(); r.popFront()) {
-    auto record = &r.front()->as<FinalizationRecordObject>();
-    MOZ_ASSERT(!(record->getClass()->flags & JSCLASS_FOREGROUND_FINALIZE));
-    MOZ_ASSERT(record->zone() == registry->zone());
-    if (record->isActive()) {
-      record->clear();
-    }
-  }
+  // The queue's flag should have been updated by
+  // GCRuntime::sweepFinalizationRegistries.
+  MOZ_ASSERT_IF(registry->queue(), !registry->queue()->hasRegistry());
 
   fop->delete_(obj, registry->registrations(),
                MemoryUse::FinalizationRegistryRegistrations);
-  fop->delete_(obj, registry->activeRecords(),
-               MemoryUse::FinalizationRegistryRecordSet);
 }
 
 FinalizationQueueObject* FinalizationRegistryObject::queue() const {
   Value value = getReservedSlot(QueueSlot);
   if (value.isUndefined()) {
     return nullptr;
   }
   return &value.toObject().as<FinalizationQueueObject>();
@@ -419,24 +319,16 @@ FinalizationQueueObject* FinalizationReg
 ObjectWeakMap* FinalizationRegistryObject::registrations() const {
   Value value = getReservedSlot(RegistrationsSlot);
   if (value.isUndefined()) {
     return nullptr;
   }
   return static_cast<ObjectWeakMap*>(value.toPrivate());
 }
 
-FinalizationRecordSet* FinalizationRegistryObject::activeRecords() const {
-  Value value = getReservedSlot(ActiveRecords);
-  if (value.isUndefined()) {
-    return nullptr;
-  }
-  return static_cast<FinalizationRecordSet*>(value.toPrivate());
-}
-
 // FinalizationRegistry.prototype.register(target, heldValue [, unregisterToken
 // ])
 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.register
 /* static */
 bool FinalizationRegistryObject::register_(JSContext* cx, unsigned argc,
                                            Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -485,31 +377,23 @@ bool FinalizationRegistryObject::registe
   }
 
   RootedObject unregisterToken(cx);
   if (!args.get(2).isUndefined()) {
     unregisterToken = &args[2].toObject();
   }
 
   // Create the finalization record representing this target and heldValue.
+  Rooted<FinalizationQueueObject*> queue(cx, registry->queue());
   Rooted<FinalizationRecordObject*> record(
-      cx, FinalizationRecordObject::create(cx, registry, heldValue));
+      cx, FinalizationRecordObject::create(cx, queue, heldValue));
   if (!record) {
     return false;
   }
 
-  // Add the record to the list of records with live targets.
-  if (!registry->activeRecords()->put(record)) {
-    ReportOutOfMemory(cx);
-    return false;
-  }
-
-  auto recordsGuard = mozilla::MakeScopeExit(
-      [&] { registry->activeRecords()->remove(record); });
-
   // Add the record to the registrations if an unregister token was supplied.
   if (unregisterToken &&
       !addRegistration(cx, registry, unregisterToken, record)) {
     return false;
   }
 
   auto registrationsGuard = mozilla::MakeScopeExit([&] {
     if (unregisterToken) {
@@ -544,17 +428,16 @@ bool FinalizationRegistryObject::registe
 
   // Register the record with the target.
   gc::GCRuntime* gc = &cx->runtime()->gc;
   if (!gc->registerWithFinalizationRegistry(cx, unwrappedTarget,
                                             wrappedRecord)) {
     return false;
   }
 
-  recordsGuard.release();
   registrationsGuard.release();
   args.rval().setUndefined();
   return true;
 }
 
 /* static */
 bool FinalizationRegistryObject::preserveDOMWrapper(JSContext* cx,
                                                     HandleObject obj) {
@@ -665,39 +548,34 @@ bool FinalizationRegistryObject::unregis
   if (obj) {
     auto* records = obj->as<FinalizationRegistrationsObject>().records();
     MOZ_ASSERT(records);
     MOZ_ASSERT(!records->empty());
     for (FinalizationRecordObject* record : *records) {
       if (unregisterRecord(record)) {
         removed = true;
       }
-      MOZ_ASSERT(!registry->activeRecords()->has(record));
     }
     registry->registrations()->remove(unregisterToken);
   }
 
   // 7. Return removed.
   args.rval().setBoolean(removed);
   return true;
 }
 
 /* static */
 bool FinalizationRegistryObject::unregisterRecord(
     FinalizationRecordObject* record) {
   if (!record->isActive()) {
     return false;
   }
 
-  FinalizationRegistryObject* registry = record->registryUnbarriered();
-  MOZ_ASSERT(registry);
-
   // Clear the fields of this record; it will be removed from the target's
   // list when it is next swept.
-  registry->activeRecords()->remove(record);
   record->clear();
   return true;
 }
 
 // FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.cleanupSome
 bool FinalizationRegistryObject::cleanupSome(JSContext* cx, unsigned argc,
                                              Value* vp) {
@@ -758,19 +636,17 @@ const JSClassOps FinalizationQueueObject
     nullptr,                            // call
     nullptr,                            // hasInstance
     nullptr,                            // construct
     FinalizationQueueObject::trace,     // trace
 };
 
 /* static */
 FinalizationQueueObject* FinalizationQueueObject::create(
-    JSContext* cx, HandleFinalizationRegistryObject registry,
-    HandleObject cleanupCallback) {
-  MOZ_ASSERT(registry);
+    JSContext* cx, HandleObject cleanupCallback) {
   MOZ_ASSERT(cleanupCallback);
 
   Rooted<UniquePtr<FinalizationRecordVector>> recordsToBeCleanedUp(
       cx, cx->make_unique<FinalizationRecordVector>(cx->zone()));
   if (!recordsToBeCleanedUp) {
     return nullptr;
   }
 
@@ -792,25 +668,25 @@ FinalizationQueueObject* FinalizationQue
   }
 
   FinalizationQueueObject* queue =
       NewObjectWithGivenProto<FinalizationQueueObject>(cx, nullptr);
   if (!queue) {
     return nullptr;
   }
 
-  queue->initReservedSlot(RegistrySlot, ObjectValue(*registry));
   queue->initReservedSlot(CleanupCallbackSlot, ObjectValue(*cleanupCallback));
   queue->initReservedSlot(IncumbentObjectSlot, ObjectValue(*incumbentObject));
   InitReservedSlot(queue, RecordsToBeCleanedUpSlot,
                    recordsToBeCleanedUp.release(),
                    MemoryUse::FinalizationRegistryRecordVector);
   queue->initReservedSlot(IsQueuedForCleanupSlot, BooleanValue(false));
   queue->initReservedSlot(DoCleanupFunctionSlot,
                           ObjectValue(*doCleanupFunction));
+  queue->initReservedSlot(HasRegistrySlot, BooleanValue(false));
 
   doCleanupFunction->setExtendedSlot(DoCleanupFunction_QueueSlot,
                                      ObjectValue(*queue));
 
   return queue;
 }
 
 /* static */
@@ -825,22 +701,28 @@ void FinalizationQueueObject::trace(JSTr
 /* static */
 void FinalizationQueueObject::finalize(JSFreeOp* fop, JSObject* obj) {
   auto queue = &obj->as<FinalizationQueueObject>();
 
   fop->delete_(obj, queue->recordsToBeCleanedUp(),
                MemoryUse::FinalizationRegistryRecordVector);
 }
 
-inline FinalizationRegistryObject* FinalizationQueueObject::registry() const {
-  Value value = getReservedSlot(RegistrySlot);
-  if (value.isUndefined()) {
-    return nullptr;
-  }
-  return static_cast<FinalizationRegistryObject*>(&value.toObject());
+void FinalizationQueueObject::setHasRegistry(bool newValue) {
+  MOZ_ASSERT(hasRegistry() != newValue);
+
+  // Suppress our assertions about touching grey things. It's OK for us to set a
+  // boolean slot even if this object is gray.
+  AutoTouchingGrayThings atgt;
+
+  setReservedSlot(HasRegistrySlot, BooleanValue(newValue));
+}
+
+bool FinalizationQueueObject::hasRegistry() const {
+  return getReservedSlot(HasRegistrySlot).toBoolean();
 }
 
 inline JSObject* FinalizationQueueObject::cleanupCallback() const {
   Value value = getReservedSlot(CleanupCallbackSlot);
   if (value.isUndefined()) {
     return nullptr;
   }
   return &value.toObject();
@@ -927,28 +809,26 @@ bool FinalizationQueueObject::cleanupQue
   //    following steps,
   //    a. Choose any such cell.
   //    b. Remove cell from finalizationRegistry.[[Cells]].
   //    c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
 
   RootedValue heldValue(cx);
   RootedValue rval(cx);
   FinalizationRecordVector* records = queue->recordsToBeCleanedUp();
-  FinalizationRecordSet* activeRecords = queue->registry()->activeRecords();
   while (!records->empty()) {
     FinalizationRecordObject* record = records->popCopy();
 
     // Skip over records that have been unregistered.
     if (!record->isActive()) {
       continue;
     }
 
     heldValue.set(record->heldValue());
 
-    activeRecords->remove(record);
     record->clear();
 
     if (!Call(cx, callback, UndefinedHandleValue, heldValue, &rval)) {
       return false;
     }
   }
 
   return true;
--- a/js/src/builtin/FinalizationRegistryObject.h
+++ b/js/src/builtin/FinalizationRegistryObject.h
@@ -99,41 +99,31 @@ using RootedFinalizationQueueObject = Ro
 //
 // A finalization record represents the registered interest of a finalization
 // registry in a target's finalization.
 //
 // Finalization records are initially 'active' but may be cleared and become
 // inactive. This happens when:
 //  - the heldValue is passed to the registry's cleanup callback
 //  - the registry's unregister method removes the registration
-//  - the FinalizationRegistry dies
 class FinalizationRecordObject : public NativeObject {
-  enum { WeakRegistrySlot = 0, HeldValueSlot, SlotCount };
+  enum { QueueSlot = 0, HeldValueSlot, SlotCount };
 
  public:
   static const JSClass class_;
 
-  static FinalizationRecordObject* create(
-      JSContext* cx, HandleFinalizationRegistryObject registry,
-      HandleValue heldValue);
+  static FinalizationRecordObject* create(JSContext* cx,
+                                          HandleFinalizationQueueObject queue,
+                                          HandleValue heldValue);
 
-  // Read weak registry pointer and perform read barrier during GC.
-  FinalizationRegistryObject* registryDuringGC(gc::GCRuntime* gc) const;
-
-  FinalizationRegistryObject* registryUnbarriered() const;
-
+  FinalizationQueueObject* queue() const;
   Value heldValue() const;
   bool isActive() const;
+
   void clear();
-  bool sweep();
-
- private:
-  static const JSClassOps classOps_;
-
-  static void trace(JSTracer* trc, JSObject* obj);
 };
 
 // A vector of weakly-held FinalizationRecordObjects.
 using WeakFinalizationRecordVector =
     GCVector<WeakHeapPtr<FinalizationRecordObject*>, 1, js::ZoneAllocPolicy>;
 
 // A JS object containing a vector of weakly-held FinalizationRecordObjects,
 // which holds the records corresponding to the registrations for a particular
@@ -165,30 +155,26 @@ class FinalizationRegistrationsObject : 
 
   static void trace(JSTracer* trc, JSObject* obj);
   static void finalize(JSFreeOp* fop, JSObject* obj);
 };
 
 using FinalizationRecordVector =
     GCVector<HeapPtr<FinalizationRecordObject*>, 1, js::ZoneAllocPolicy>;
 
-using FinalizationRecordSet =
-    GCHashSet<HeapPtrObject, MovableCellHasher<HeapPtrObject>, ZoneAllocPolicy>;
-
 // The JS FinalizationRegistry object itself.
 class FinalizationRegistryObject : public NativeObject {
-  enum { QueueSlot = 0, RegistrationsSlot, ActiveRecords, SlotCount };
+  enum { QueueSlot = 0, RegistrationsSlot, SlotCount };
 
  public:
   static const JSClass class_;
   static const JSClass protoClass_;
 
   FinalizationQueueObject* queue() const;
   ObjectWeakMap* registrations() const;
-  FinalizationRecordSet* activeRecords() const;
 
   void sweep();
 
   static bool unregisterRecord(FinalizationRecordObject* record);
 
   static bool cleanupQueuedRecords(JSContext* cx,
                                    HandleFinalizationRegistryObject registry,
                                    HandleObject callback = nullptr);
@@ -216,45 +202,46 @@ class FinalizationRegistryObject : publi
   static void trace(JSTracer* trc, JSObject* obj);
   static void finalize(JSFreeOp* fop, JSObject* obj);
 };
 
 // Contains information about the cleanup callback and the records queued to
 // be cleaned up. This is not exposed to content JS.
 class FinalizationQueueObject : public NativeObject {
   enum {
-    RegistrySlot = 0,
-    CleanupCallbackSlot,
+    CleanupCallbackSlot = 0,
     IncumbentObjectSlot,
     RecordsToBeCleanedUpSlot,
     IsQueuedForCleanupSlot,
     DoCleanupFunctionSlot,
+    HasRegistrySlot,
     SlotCount
   };
 
   enum DoCleanupFunctionSlots {
     DoCleanupFunction_QueueSlot = 0,
   };
 
  public:
   static const JSClass class_;
 
-  FinalizationRegistryObject* registry() const;
   JSObject* cleanupCallback() const;
   JSObject* incumbentObject() const;
   FinalizationRecordVector* recordsToBeCleanedUp() const;
   bool isQueuedForCleanup() const;
   JSFunction* doCleanupFunction() const;
+  bool hasRegistry() const;
 
   void queueRecordToBeCleanedUp(FinalizationRecordObject* record);
   void setQueuedForCleanup(bool value);
 
-  static FinalizationQueueObject* create(
-      JSContext* cx, HandleFinalizationRegistryObject registry,
-      HandleObject cleanupCallback);
+  void setHasRegistry(bool newValue);
+
+  static FinalizationQueueObject* create(JSContext* cx,
+                                         HandleObject cleanupCallback);
 
   static bool cleanupQueuedRecords(JSContext* cx,
                                    HandleFinalizationQueueObject registry,
                                    HandleObject callback = nullptr);
 
  private:
   static const JSClassOps classOps_;
 
--- a/js/src/gc/FinalizationRegistry.cpp
+++ b/js/src/gc/FinalizationRegistry.cpp
@@ -48,19 +48,19 @@ bool GCRuntime::registerWithFinalization
   if (!ptr->value().append(record)) {
     ReportOutOfMemory(cx);
     return false;
   }
   return true;
 }
 
 void GCRuntime::markFinalizationRegistryRoots(JSTracer* trc) {
-  // The held values for all finalization records store in zone maps are marked
-  // as roots. Finalization records store a finalization registry as a weak
-  // pointer in a private value, which does not get marked.
+  // All finalization records stored in the zone maps are marked as roots.
+  // Records can be removed from these maps during sweeping in which case they
+  // die in the next collection.
   for (GCZonesIter zone(this); !zone.done(); zone.next()) {
     Zone::FinalizationRecordMap& map = zone->finalizationRecordMap();
     for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
       e.front().value().trace(trc);
     }
   }
 }
 
@@ -75,44 +75,46 @@ static FinalizationRecordObject* UnwrapF
   return &obj->as<FinalizationRecordObject>();
 }
 
 void GCRuntime::sweepFinalizationRegistries(Zone* zone) {
   // Sweep finalization registry data and queue finalization records for cleanup
   // for any entries whose target is dying and remove them from the map.
 
   Zone::FinalizationRegistrySet& set = zone->finalizationRegistries();
-  set.sweep();
-  for (auto r = set.all(); !r.empty(); r.popFront()) {
-    r.front()->as<FinalizationRegistryObject>().sweep();
+  for (Zone::FinalizationRegistrySet::Enum e(set); !e.empty(); e.popFront()) {
+    if (IsAboutToBeFinalized(&e.mutableFront())) {
+      e.front()->as<FinalizationRegistryObject>().queue()->setHasRegistry(
+          false);
+      e.removeFront();
+    } else {
+      e.front()->as<FinalizationRegistryObject>().sweep();
+    }
   }
 
   Zone::FinalizationRecordMap& map = zone->finalizationRecordMap();
   for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
     FinalizationRecordVector& records = e.front().value();
 
     // Update any pointers moved by the GC.
     records.sweep();
 
     // Sweep finalization records and remove records for:
     records.eraseIf([](JSObject* obj) {
       FinalizationRecordObject* record = UnwrapFinalizationRecord(obj);
-      return !record ||              // Nuked CCW to record.
-             !record->isActive() ||  // Unregistered record or dead finalization
-                                     // registry in previous sweep group.
-             !record->sweep();       // Dead finalization registry in this sweep
-                                     // group.
+      return !record ||                        // Nuked CCW to record.
+             !record->isActive() ||            // Unregistered record.
+             !record->queue()->hasRegistry();  // Dead finalization registry.
     });
 
     // Queue finalization records for targets that are dying.
     if (IsAboutToBeFinalized(&e.front().mutableKey())) {
       for (JSObject* obj : records) {
         FinalizationRecordObject* record = UnwrapFinalizationRecord(obj);
-        FinalizationRegistryObject* registry = record->registryDuringGC(this);
-        FinalizationQueueObject* queue = registry->queue();
+        FinalizationQueueObject* queue = record->queue();
         queue->queueRecordToBeCleanedUp(record);
         queueFinalizationRegistryForCleanup(queue);
       }
       e.removeFront();
     }
   }
 }
 
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -6123,17 +6123,16 @@ bool GCRuntime::initSweepActions() {
 
   sweepActions.ref() = RepeatForSweepGroup(
       rt,
       Sequence(
           Call(&GCRuntime::markGrayReferencesInCurrentGroup),
           Call(&GCRuntime::endMarkingSweepGroup),
           Call(&GCRuntime::beginSweepingSweepGroup),
           MaybeYield(ZealMode::IncrementalMultipleSlices),
-          Call(&GCRuntime::markDuringSweeping),
           MaybeYield(ZealMode::YieldBeforeSweepingAtoms),
           Call(&GCRuntime::sweepAtomsTable),
           MaybeYield(ZealMode::YieldBeforeSweepingCaches),
           Call(&GCRuntime::sweepWeakCaches),
           ForEachZoneInSweepGroup(
               rt, &sweepZone.ref(),
               Sequence(MaybeYield(ZealMode::YieldBeforeSweepingTypes),
                        Call(&GCRuntime::sweepTypeInformation),
--- a/js/src/gc/GCEnum.h
+++ b/js/src/gc/GCEnum.h
@@ -135,17 +135,16 @@ enum class ZealMode {
   _(Debugger)                              \
   _(DebuggerFrameGeneratorInfo)            \
   _(DebuggerFrameIterData)                 \
   _(DebuggerOnStepHandler)                 \
   _(DebuggerOnPopHandler)                  \
   _(RealmInstrumentation)                  \
   _(ICUObject)                             \
   _(FinalizationRegistryRecordVector)      \
-  _(FinalizationRegistryRecordSet)         \
   _(FinalizationRegistryRegistrations)     \
   _(FinalizationRecordVector)              \
   _(ZoneAllocPolicy)                       \
   _(SharedArrayRawBuffer)                  \
   _(XDRBufferElements)
 
 #define JS_FOR_EACH_MEMORY_USE(_)  \
   JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \