Bug 1568821 - Pass wasSwept parameter when freeing malloc memory r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 29 Jul 2019 08:33:56 +0000
changeset 485057 1934fb86120cf251f9b0b31a64bb62b223fb16ed
parent 485056 47a2920eadfd5484ff04bde8a3ab51ad99af802b
child 485058 9bed62de3d168ab05dc701a9fea4c0ee9c3f351f
push id113797
push userccoroiu@mozilla.com
push dateMon, 29 Jul 2019 21:45:35 +0000
treeherdermozilla-inbound@927abd2c418b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1568821
milestone70.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 1568821 - Pass wasSwept parameter when freeing malloc memory r=sfink Now ensure that we know whether malloc memory was freed by the collector or the mutator. Most of the time we can get the |wasSwept| state from from the FreeOp, so mostly this is passing the FreeOp through in more places. That's not always possible though since some code uses the runtime's default FreeOp, so this sets the approriate flag on the default free while we're sweeping too. Differential Revision: https://phabricator.services.mozilla.com/D39393
js/src/gc/FreeOp-inl.h
js/src/gc/FreeOp.h
js/src/gc/GC.cpp
js/src/gc/Zone.cpp
js/src/gc/ZoneAllocator.h
js/src/jit/BaselineJIT.cpp
js/src/jit/Ion.cpp
js/src/jit/JitScript.cpp
js/src/jsapi.cpp
js/src/shell/OSObject.cpp
js/src/threading/ProtectedData.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/JSContext.cpp
js/src/vm/JSContext.h
js/src/vm/JSScript-inl.h
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/ObjectGroup.cpp
js/src/vm/ObjectGroup.h
js/src/vm/Runtime.cpp
js/src/vm/Shape.cpp
js/src/vm/Shape.h
js/src/vm/StringType-inl.h
js/src/vm/TypeInference.cpp
--- a/js/src/gc/FreeOp-inl.h
+++ b/js/src/gc/FreeOp-inl.h
@@ -12,25 +12,25 @@
 #include "gc/ZoneAllocator.h"
 #include "js/RefCounted.h"
 
 namespace js {
 
 inline void FreeOp::free_(gc::Cell* cell, void* p, size_t nbytes,
                           MemoryUse use) {
   if (p) {
-    RemoveCellMemory(cell, nbytes, use);
+    removeCellMemory(cell, nbytes, use);
     js_free(p);
   }
 }
 
 inline void FreeOp::freeLater(gc::Cell* cell, void* p, size_t nbytes,
                               MemoryUse use) {
   if (p) {
-    RemoveCellMemory(cell, nbytes, use);
+    removeCellMemory(cell, nbytes, use);
     queueForFreeLater(p);
   }
 }
 
 inline void FreeOp::queueForFreeLater(void* p) {
   // Default FreeOps are not constructed on the stack, and will hold onto the
   // pointers to free indefinitely.
   MOZ_ASSERT(!isDefaultFreeOp());
@@ -42,16 +42,21 @@ inline void FreeOp::queueForFreeLater(vo
     oomUnsafe.crash("FreeOp::freeLater");
   }
 }
 
 template <class T>
 inline void FreeOp::release(gc::Cell* cell, T* p, size_t nbytes,
                             MemoryUse use) {
   if (p) {
-    RemoveCellMemory(cell, nbytes, use);
+    removeCellMemory(cell, nbytes, use);
     p->Release();
   }
 }
 
+inline void FreeOp::removeCellMemory(gc::Cell* cell, size_t nbytes,
+                                     MemoryUse use) {
+  RemoveCellMemory(cell, nbytes, use, isCollecting());
+}
+
 }  // namespace js
 
 #endif  // gc_FreeOp_inl_h
--- a/js/src/gc/FreeOp.h
+++ b/js/src/gc/FreeOp.h
@@ -15,43 +15,51 @@
 #include "js/MemoryFunctions.h"       // JSFreeOp
 #include "js/Utility.h"               // AutoEnterOOMUnsafeRegion, js_free
 #include "js/Vector.h"                // js::Vector
 
 struct JSRuntime;
 
 namespace js {
 
+namespace gc {
+class AutoSetThreadIsPerformingGC;
+} // namespace gc
+
 /*
  * A FreeOp can do one thing: free memory. For convenience, it has delete_
  * convenience methods that also call destructors.
  *
  * FreeOp is passed to finalizers and other sweep-phase hooks so that we do not
  * need to pass a JSContext to those hooks.
  */
 class FreeOp : public JSFreeOp {
   Vector<void*, 0, SystemAllocPolicy> freeLaterList;
   jit::JitPoisonRangeVector jitPoisonRanges;
   const bool isDefault;
+  bool isCollecting_;
+
+  friend class gc::AutoSetThreadIsPerformingGC;
 
  public:
   static FreeOp* get(JSFreeOp* fop) { return static_cast<FreeOp*>(fop); }
 
   explicit FreeOp(JSRuntime* maybeRuntime, bool isDefault = false);
   ~FreeOp();
 
   bool onMainThread() const { return runtime_ != nullptr; }
 
   bool maybeOnHelperThread() const {
     // Sometimes background finalization happens on the main thread so
     // runtime_ being null doesn't always mean we are off thread.
     return !runtime_;
   }
 
   bool isDefaultFreeOp() const { return isDefault; }
+  bool isCollecting() const { return isCollecting_; }
 
   // Deprecated. Where possible, memory should be tracked against the owning GC
   // thing by calling js::AddCellMemory and the memory freed with free_() below.
   void freeUntracked(void* p) { js_free(p); }
 
   // Free memory associated with a GC thing and update the memory accounting.
   //
   // The memory should have been associated with the GC thing using
@@ -139,15 +147,19 @@ class FreeOp : public JSFreeOp {
   // update the memory accounting.
   //
   // The memory should have been associated with the GC thing using
   // js::InitReservedSlot or js::InitObjectPrivate, or possibly
   // js::AddCellMemory.
   template <class T>
   void release(gc::Cell* cell, T* p, size_t nbytes, MemoryUse use);
 
+  // Update the memory accounting for a GC for memory freed by some other
+  // method.
+  void removeCellMemory(gc::Cell* cell, size_t nbytes, MemoryUse use);
+
  private:
   void queueForFreeLater(void* p);
 };
 
 }  // namespace js
 
 #endif  // gc_FreeOp_h
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -5646,83 +5646,90 @@ static void UpdateAtomsBitmap(JSRuntime*
   // sweeping; they are likely to be much smaller than the main atoms table.
   runtime->symbolRegistry().sweep();
   for (RealmsIter realm(runtime); !realm.done(); realm.next()) {
     realm->sweepVarNames();
   }
 }
 
 static void SweepCCWrappers(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
   for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next()) {
     c->sweepCrossCompartmentWrappers();
   }
 }
 
 static void SweepObjectGroups(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
   for (SweepGroupRealmsIter r(runtime); !r.done(); r.next()) {
     r->sweepObjectGroups();
   }
 }
 
 static void SweepMisc(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
   for (SweepGroupRealmsIter r(runtime); !r.done(); r.next()) {
     r->sweepGlobalObject();
     r->sweepTemplateObjects();
     r->sweepSavedStacks();
     r->sweepSelfHostingScriptSource();
     r->sweepObjectRealm();
     r->sweepRegExps();
   }
 }
 
 static void SweepCompressionTasks(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
 
   // Attach finished compression tasks.
   AutoLockHelperThreadState lock;
   AttachFinishedCompressions(runtime, lock);
 
   // Sweep pending tasks that are holding onto should-be-dead ScriptSources.
   auto& pending = HelperThreadState().compressionPendingList(lock);
   for (size_t i = 0; i < pending.length(); i++) {
     if (pending[i]->shouldCancel()) {
       HelperThreadState().remove(pending, &i);
     }
   }
 }
 
 void js::gc::SweepLazyScripts(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
   for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) {
     for (auto i = zone->cellIter<LazyScript>(); !i.done(); i.next()) {
       WeakHeapPtrScript* edge = &i.unbarrieredGet()->script_;
       if (*edge && IsAboutToBeFinalized(edge)) {
         *edge = nullptr;
       }
     }
   }
 }
 
 static void SweepWeakMaps(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   JSRuntime* runtime = task->runtime();
   for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) {
     /* No need to look up any more weakmap keys from this sweep group. */
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (!zone->gcWeakKeys().clear()) {
       oomUnsafe.crash("clearing weak keys in beginSweepingSweepGroup()");
     }
 
     zone->sweepWeakMaps();
   }
 }
 
 static void SweepUniqueIds(GCParallelTask* task) {
+  AutoSetThreadIsSweeping threadIsSweeping;
   for (SweepGroupZonesIter zone(task->runtime()); !zone.done(); zone.next()) {
     zone->sweepUniqueIds();
   }
 }
 
 void GCRuntime::startTask(GCParallelTask& task, gcstats::PhaseKind phase,
                           AutoLockHelperThreadState& locked) {
   if (!CanUseExtraThreads() || !task.startWithLockHeld(locked)) {
@@ -6140,33 +6147,34 @@ IncrementalProgress GCRuntime::markUntil
   return marker.markUntilBudgetExhausted(sliceBudget) ? Finished : NotFinished;
 }
 
 void GCRuntime::drainMarkStack() {
   auto unlimited = SliceBudget::unlimited();
   MOZ_RELEASE_ASSERT(marker.markUntilBudgetExhausted(unlimited));
 }
 
-static void SweepThing(Shape* shape) {
+static void SweepThing(FreeOp* fop, Shape* shape) {
   if (!shape->isMarkedAny()) {
-    shape->sweep();
-  }
-}
-
-static void SweepThing(JSScript* script) { AutoSweepJitScript sweep(script); }
-
-static void SweepThing(ObjectGroup* group) {
+    shape->sweep(fop);
+  }
+}
+
+static void SweepThing(FreeOp* fop, JSScript* script) { AutoSweepJitScript sweep(script); }
+
+static void SweepThing(FreeOp* fop, ObjectGroup* group) {
   AutoSweepObjectGroup sweep(group);
 }
 
 template <typename T>
-static bool SweepArenaList(Arena** arenasToSweep, SliceBudget& sliceBudget) {
+static bool SweepArenaList(FreeOp* fop, Arena** arenasToSweep,
+                           SliceBudget& sliceBudget) {
   while (Arena* arena = *arenasToSweep) {
     for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) {
-      SweepThing(i.get<T>());
+      SweepThing(fop, i.get<T>());
     }
 
     *arenasToSweep = (*arenasToSweep)->next;
     AllocKind kind = MapTypeToFinalizeKind<T>::kind;
     sliceBudget.step(Arena::thingsPerArena(kind));
     if (sliceBudget.isOverBudget()) {
       return false;
     }
@@ -6187,21 +6195,22 @@ IncrementalProgress GCRuntime::sweepType
 
   gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_COMPARTMENTS);
   gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES);
 
   ArenaLists& al = sweepZone->arenas;
 
   AutoClearTypeInferenceStateOnOOM oom(sweepZone);
 
-  if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate.ref(), budget)) {
+  if (!SweepArenaList<JSScript>(fop, &al.gcScriptArenasToUpdate.ref(),
+                                budget)) {
     return NotFinished;
   }
 
-  if (!SweepArenaList<ObjectGroup>(&al.gcObjectGroupArenasToUpdate.ref(),
+  if (!SweepArenaList<ObjectGroup>(fop, &al.gcObjectGroupArenasToUpdate.ref(),
                                    budget)) {
     return NotFinished;
   }
 
   // Finish sweeping type information in the zone.
   {
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_TYPES_END);
     sweepZone->types.endSweep(rt);
@@ -6341,16 +6350,17 @@ class IncrementalSweepWeakCacheTask
                             lock_);
   }
 
   ~IncrementalSweepWeakCacheTask() {
     runtime()->gc.joinTask(*this, gcstats::PhaseKind::SWEEP_WEAK_CACHES, lock_);
   }
 
   void run() {
+    AutoSetThreadIsSweeping threadIsSweeping;
     do {
       MOZ_ASSERT(cache_->needsIncrementalBarrier());
       size_t steps = cache_->sweep();
       cache_->setNeedsIncrementalBarrier(false);
 
       AutoLockHelperThreadState lock;
       budget_.step(steps);
       if (budget_.isOverBudget()) {
@@ -6411,21 +6421,22 @@ IncrementalProgress GCRuntime::finalizeA
 IncrementalProgress GCRuntime::sweepShapeTree(FreeOp* fop,
                                               SliceBudget& budget) {
   // Remove dead shapes from the shape tree, but don't finalize them yet.
 
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_SHAPE);
 
   ArenaLists& al = sweepZone->arenas;
 
-  if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate.ref(), budget)) {
+  if (!SweepArenaList<Shape>(fop, &al.gcShapeArenasToUpdate.ref(), budget)) {
     return NotFinished;
   }
 
-  if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate.ref(),
+  if (!SweepArenaList<AccessorShape>(fop,
+                                     &al.gcAccessorShapeArenasToUpdate.ref(),
                                      budget)) {
     return NotFinished;
   }
 
   return Finished;
 }
 
 // An iterator for a standard container that provides an STL-like begin()/end()
@@ -7076,17 +7087,16 @@ namespace {
  */
 class AutoDisableBarriers {
  public:
   explicit AutoDisableBarriers(JSRuntime* rt);
   ~AutoDisableBarriers();
 
  private:
   JSRuntime* runtime;
-  AutoSetThreadIsPerformingGC performingGC;
 };
 
 } /* anonymous namespace */
 
 AutoDisableBarriers::AutoDisableBarriers(JSRuntime* rt) : runtime(rt) {
   for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
     /*
      * Clear needsIncrementalBarrier early so we don't do any write
@@ -7139,16 +7149,17 @@ static bool ShouldSweepOnBackgroundThrea
   return reason != JS::GCReason::DESTROY_RUNTIME && !gcTracer.traceEnabled() &&
          CanUseExtraThreads();
 }
 
 void GCRuntime::incrementalSlice(SliceBudget& budget,
                                  const MaybeInvocationKind& gckind,
                                  JS::GCReason reason, AutoGCSession& session) {
   AutoDisableBarriers disableBarriers(rt);
+  AutoSetThreadIsPerformingGC performingGC;
 
   bool destroyingRuntime = (reason == JS::GCReason::DESTROY_RUNTIME);
 
   initialState = incrementalState;
 
 #ifdef JS_GC_ZEAL
   /*
    * Do the incremental collection type specified by zeal mode if the
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -81,16 +81,24 @@ void js::ZoneAllocator::updateGCThreshol
 }
 
 js::gc::TriggerKind js::ZoneAllocator::shouldTriggerGCForTooMuchMalloc() {
   auto& gc = runtimeFromAnyThread()->gc;
   return std::max(gcMallocCounter.shouldTriggerGC(gc.tunables),
                   jitCodeCounter.shouldTriggerGC(gc.tunables));
 }
 
+void ZoneAllocPolicy::decMemory(size_t nbytes) {
+  // Unfortunately we don't have enough context here to know whether we're being
+  // called on behalf of the collector so we have to do a TLS lookup to find
+  // out.
+  JSContext* cx = TlsContext.get();
+  zone_->decPolicyMemory(this, nbytes, cx->defaultFreeOp()->isCollecting());
+}
+
 JS::Zone::Zone(JSRuntime* rt)
     : ZoneAllocator(rt),
       // Note: don't use |this| before initializing helperThreadUse_!
       // ProtectedData checks in CheckZone::check may read this field.
       helperThreadUse_(HelperThreadUse::None),
       helperThreadOwnerContext_(nullptr),
       debuggers(this, nullptr),
       uniqueIds_(this),
@@ -333,17 +341,17 @@ void Zone::discardJitCode(FreeOp* fop,
     // need to let it warm back up to get information such as which
     // opcodes are setting array holes or accessing getter properties.
     script->resetWarmUpCounterForGC();
 
     // Try to release the script's JitScript. This should happen after
     // releasing JIT code because we can't do this when the script still has
     // JIT code.
     if (discardJitScripts) {
-      script->maybeReleaseJitScript();
+      script->maybeReleaseJitScript(fop);
     }
 
     if (jit::JitScript* jitScript = script->jitScript()) {
       // If we did not release the JitScript, we need to purge optimized IC
       // stubs because the optimizedStubSpace will be purged below.
       if (discardBaselineCode) {
         jitScript->purgeOptimizedStubs(script);
 
--- a/js/src/gc/ZoneAllocator.h
+++ b/js/src/gc/ZoneAllocator.h
@@ -82,16 +82,17 @@ class ZoneAllocator : public JS::shadow:
     gcMallocTracker.trackMemory(cell, nbytes, use);
 #endif
   }
 
   void removeCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use,
                         bool wasSwept = false) {
     MOZ_ASSERT(cell);
     MOZ_ASSERT(nbytes);
+    MOZ_ASSERT_IF(CurrentThreadIsGCSweeping(), wasSwept);
 
     gcMallocBytes.removeBytes(nbytes, wasSwept);
 
 #ifdef DEBUG
     gcMallocTracker.untrackMemory(cell, nbytes, use);
 #endif
   }
 
@@ -119,18 +120,20 @@ class ZoneAllocator : public JS::shadow:
 
 #ifdef DEBUG
     gcMallocTracker.incPolicyMemory(policy, nbytes);
 #endif
 
     maybeMallocTriggerZoneGC();
   }
   void decPolicyMemory(js::ZoneAllocPolicy* policy, size_t nbytes,
-                       bool wasSwept = false) {
+                       bool wasSwept) {
     MOZ_ASSERT(nbytes);
+    MOZ_ASSERT_IF(CurrentThreadIsGCSweeping(), wasSwept);
+
     gcMallocBytes.removeBytes(nbytes, wasSwept);
 
 #ifdef DEBUG
     gcMallocTracker.decPolicyMemory(policy, nbytes);
 #endif
   }
 
   // Check malloc allocation threshold and trigger a zone GC if necessary.
@@ -285,17 +288,17 @@ class ZoneAllocPolicy : public MallocPro
     zone()->incPolicyMemory(this, nbytes);
   }
 
  private:
   ZoneAllocator* zone() const {
     MOZ_ASSERT(zone_);
     return zone_;
   }
-  void decMemory(size_t nbytes) { zone_->decPolicyMemory(this, nbytes); }
+  void decMemory(size_t nbytes);
 };
 
 // Functions for memory accounting on the zone.
 
 // Associate malloc memory with a GC thing. This call should be matched by a
 // following call to RemoveCellMemory with the same size and use. The total
 // amount of malloc memory associated with a zone is used to trigger GC.
 //
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1039,17 +1039,17 @@ void BaselineInterpreter::toggleCodeCove
   toggleCodeCoverageInstrumentationUnchecked(enable);
 }
 
 void jit::FinishDiscardBaselineScript(FreeOp* fop, JSScript* script) {
   MOZ_ASSERT(script->hasBaselineScript());
   MOZ_ASSERT(!script->jitScript()->active());
 
   BaselineScript* baseline = script->baselineScript();
-  script->setBaselineScript(fop->runtime(), nullptr);
+  script->setBaselineScript(fop, nullptr);
   BaselineScript::Destroy(fop, baseline);
 }
 
 void jit::AddSizeOfBaselineData(JSScript* script,
                                 mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* data) {
   if (script->hasBaselineScript()) {
     script->baselineScript()->addSizeOfIncludingThis(mallocSizeOf, data);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2822,17 +2822,17 @@ void jit::Invalidate(JSContext* cx, JSSc
 
 void jit::FinishInvalidation(FreeOp* fop, JSScript* script) {
   if (!script->hasIonScript()) {
     return;
   }
 
   // In all cases, null out script->ion to avoid re-entry.
   IonScript* ion = script->ionScript();
-  script->setIonScript(fop->runtime(), nullptr);
+  script->setIonScript(fop, nullptr);
 
   // If this script has Ion code on the stack, invalidated() will return
   // true. In this case we have to wait until destroying it.
   if (!ion->invalidated()) {
     jit::IonScript::Destroy(fop, ion);
   }
 }
 
@@ -3012,28 +3012,28 @@ size_t jit::SizeOfIonData(JSScript* scri
   }
 
   return result;
 }
 
 void jit::DestroyJitScripts(FreeOp* fop, JSScript* script) {
   if (script->hasIonScript()) {
     IonScript* ion = script->ionScript();
-    script->clearIonScript();
+    script->clearIonScript(fop);
     jit::IonScript::Destroy(fop, ion);
   }
 
   if (script->hasBaselineScript()) {
     BaselineScript* baseline = script->baselineScript();
-    script->clearBaselineScript();
+    script->clearBaselineScript(fop);
     jit::BaselineScript::Destroy(fop, baseline);
   }
 
   if (script->hasJitScript()) {
-    script->releaseJitScript();
+    script->releaseJitScript(fop);
   }
 }
 
 void jit::TraceJitScripts(JSTracer* trc, JSScript* script) {
   if (script->hasIonScript()) {
     jit::IonScript::Trace(trc, script->ionScript());
   }
 
--- a/js/src/jit/JitScript.cpp
+++ b/js/src/jit/JitScript.cpp
@@ -13,16 +13,17 @@
 
 #include "jit/BaselineIC.h"
 #include "jit/BytecodeAnalysis.h"
 #include "vm/JSScript.h"
 #include "vm/Stack.h"
 #include "vm/TypeInference.h"
 #include "wasm/WasmInstance.h"
 
+#include "gc/FreeOp-inl.h"
 #include "jit/JSJitFrameIter-inl.h"
 #include "vm/JSScript-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 /* static */
@@ -156,33 +157,33 @@ bool JSScript::createJitScript(JSContext
     InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u %p", InferSpewColor(types),
               types, InferSpewColorReset(), i, this);
   }
 #endif
 
   return true;
 }
 
-void JSScript::maybeReleaseJitScript() {
+void JSScript::maybeReleaseJitScript(FreeOp* fop) {
   if (!jitScript_ || zone()->types.keepJitScripts || hasBaselineScript() ||
       jitScript_->active()) {
     return;
   }
 
-  releaseJitScript();
+  releaseJitScript(fop);
 }
 
-void JSScript::releaseJitScript() {
+void JSScript::releaseJitScript(FreeOp* fop) {
   MOZ_ASSERT(!hasIonScript());
 
-  RemoveCellMemory(this, jitScript_->allocBytes(), MemoryUse::JitScript);
+  fop->removeCellMemory(this, jitScript_->allocBytes(), MemoryUse::JitScript);
 
   JitScript::Destroy(zone(), jitScript_);
   jitScript_ = nullptr;
-  updateJitCodeRaw(runtimeFromMainThread());
+  updateJitCodeRaw(fop->runtime());
 }
 
 void JitScript::CachedIonData::trace(JSTracer* trc) {
   TraceNullableEdge(trc, &templateEnv, "jitscript-iondata-template-env");
 }
 
 void JitScript::trace(JSTracer* trc) {
   if (hasCachedIonData()) {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1167,17 +1167,18 @@ JS_PUBLIC_API void JS::AddAssociatedMemo
 
 JS_PUBLIC_API void JS::RemoveAssociatedMemory(JSObject* obj, size_t nbytes,
                                               JS::MemoryUse use) {
   MOZ_ASSERT(obj);
   if (!nbytes) {
     return;
   }
 
-  obj->zoneFromAnyThread()->removeCellMemory(obj, nbytes, js::MemoryUse(use));
+  JSRuntime* rt = obj->runtimeFromAnyThread();
+  rt->defaultFreeOp()->removeCellMemory(obj, nbytes, js::MemoryUse(use));
 }
 
 #undef JS_AddRoot
 
 JS_PUBLIC_API bool JS_AddExtraGCRootsTracer(JSContext* cx,
                                             JSTraceDataOp traceOp, void* data) {
   return cx->runtime()->gc.addBlackRootsTracer(traceOp, data);
 }
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -428,17 +428,17 @@ class FileObject : public NativeObject {
     InitReservedSlot(obj, FILE_SLOT, file, MemoryUse::FileObjectFile);
     file->acquire();
     return obj;
   }
 
   static void finalize(FreeOp* fop, JSObject* obj) {
     FileObject* fileObj = &obj->as<FileObject>();
     RCFile* file = fileObj->rcFile();
-    RemoveCellMemory(obj, sizeof(*file), MemoryUse::FileObjectFile);
+    fop->removeCellMemory(obj, sizeof(*file), MemoryUse::FileObjectFile);
     if (file->release()) {
       fop->deleteUntracked(file);
     }
   }
 
   bool isOpen() {
     RCFile* file = rcFile();
     return file && file->isOpen();
--- a/js/src/threading/ProtectedData.cpp
+++ b/js/src/threading/ProtectedData.cpp
@@ -25,17 +25,18 @@ static inline bool OnHelperThread() {
       Helper == AllowedHelperThread::GCTaskOrIonCompile) {
     if (CurrentThreadIsIonCompiling()) {
       return true;
     }
   }
 
   if (Helper == AllowedHelperThread::GCTask ||
       Helper == AllowedHelperThread::GCTaskOrIonCompile) {
-    if (TlsContext.get()->performingGC) {
+    JSContext* cx = TlsContext.get();
+    if (cx->defaultFreeOp()->isCollecting()) {
       return true;
     }
   }
 
   return false;
 }
 
 void CheckThreadLocal::check() const {
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -951,21 +951,22 @@ void ArrayBufferObject::releaseData(Free
       // There's nothing to release if there's no data.
       MOZ_ASSERT(dataPointer() == nullptr);
       break;
     case USER_OWNED:
       // User-owned data is released by, well, the user.
       break;
     case MAPPED:
       gc::DeallocateMappedContent(dataPointer(), byteLength());
-      RemoveCellMemory(this, associatedBytes(), MemoryUse::ArrayBufferContents);
+      fop->removeCellMemory(this, associatedBytes(),
+                            MemoryUse::ArrayBufferContents);
       break;
     case WASM:
       WasmArrayRawBuffer::Release(dataPointer());
-      RemoveCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents);
+      fop->removeCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents);
       break;
     case EXTERNAL:
       if (freeInfo()->freeFunc) {
         // The analyzer can't know for sure whether the embedder-supplied
         // free function will GC. We give the analyzer a hint here.
         // (Doing a GC in the free function is considered a programmer
         // error.)
         JS::AutoSuppressGCAnalysis nogc;
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1245,17 +1245,16 @@ JSContext::JSContext(JSRuntime* runtime,
 #endif
       autoFlushICache_(this, nullptr),
       dtoaState(this, nullptr),
       suppressGC(this, 0),
       gcSweeping(this, false),
 #ifdef DEBUG
       ionCompiling(this, false),
       ionCompilingSafeForMinorGC(this, false),
-      performingGC(this, false),
       isTouchingGrayThings(this, false),
       noNurseryAllocationCheck(this, 0),
       disableStrictProxyCheckingCount(this, 0),
 #endif
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
       runningOOMTest(this, false),
 #endif
       enableAccessValidation(this, false),
@@ -1516,8 +1515,9 @@ AutoUnsafeCallWithABI::~AutoUnsafeCallWi
   MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI);
   if (!nested_) {
     cx_->hasAutoUnsafeCallWithABI = false;
     cx_->inUnsafeCallWithABI = false;
   }
   MOZ_ASSERT_IF(checkForPendingException_, !JS_IsExceptionPending(cx_));
 }
 #endif
+
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -534,21 +534,16 @@ struct JSContext : public JS::RootingCon
   // Whether this thread is actively Ion compiling.
   js::ContextData<bool> ionCompiling;
 
   // Whether this thread is actively Ion compiling in a context where a minor
   // GC could happen simultaneously. If this is true, this thread cannot use
   // any pointers into the nursery.
   js::ContextData<bool> ionCompilingSafeForMinorGC;
 
-  // Whether this thread is currently performing GC.  This thread could be the
-  // main thread or a helper thread while the main thread is running the
-  // collector.
-  js::ContextData<bool> performingGC;
-
   // Whether this thread is currently manipulating possibly-gray GC things.
   js::ContextData<size_t> isTouchingGrayThings;
 
   js::ContextData<size_t> noNurseryAllocationCheck;
 
   /*
    * If this is 0, all cross-compartment proxies must be registered in the
    * wrapper map. This checking must be disabled temporarily while creating
@@ -1303,51 +1298,45 @@ class MOZ_RAII AutoUnsafeCallWithABI {
 #else
   explicit AutoUnsafeCallWithABI(
       UnsafeABIStrictness unused_ = UnsafeABIStrictness::NoExceptions) {}
 #endif
 };
 
 namespace gc {
 
-// In debug builds, set/unset the performing GC flag for the current thread.
-struct MOZ_RAII AutoSetThreadIsPerformingGC {
-#ifdef DEBUG
+// Set/unset the performing GC flag for the current thread.
+class MOZ_RAII AutoSetThreadIsPerformingGC {
+  JSContext* cx;
+
+ public:
   AutoSetThreadIsPerformingGC() : cx(TlsContext.get()) {
-    MOZ_ASSERT(!cx->performingGC);
-    cx->performingGC = true;
+    FreeOp* fop = cx->defaultFreeOp();
+    MOZ_ASSERT(!fop->isCollecting());
+    fop->isCollecting_ = true;
   }
 
   ~AutoSetThreadIsPerformingGC() {
-    MOZ_ASSERT(cx->performingGC);
-    cx->performingGC = false;
+    FreeOp* fop = cx->defaultFreeOp();
+    MOZ_ASSERT(fop->isCollecting());
+    fop->isCollecting_ = false;
   }
-
- private:
-  JSContext* cx;
-#else
-  AutoSetThreadIsPerformingGC() {}
-#endif
 };
 
 // In debug builds, set/reset the GC sweeping flag for the current thread.
 struct MOZ_RAII AutoSetThreadIsSweeping {
-#ifdef DEBUG
   AutoSetThreadIsSweeping() : cx(TlsContext.get()), prevState(cx->gcSweeping) {
     cx->gcSweeping = true;
   }
 
   ~AutoSetThreadIsSweeping() { cx->gcSweeping = prevState; }
 
  private:
   JSContext* cx;
   bool prevState;
-#else
-  AutoSetThreadIsSweeping() {}
-#endif
 };
 
 }  // namespace gc
 
 } /* namespace js */
 
 #define CHECK_THREAD(cx)                            \
   MOZ_ASSERT_IF(cx && !cx->isHelperThreadContext(), \
--- a/js/src/vm/JSScript-inl.h
+++ b/js/src/vm/JSScript-inl.h
@@ -157,39 +157,44 @@ inline js::Shape* JSScript::initialEnvir
   }
   return nullptr;
 }
 
 inline JSPrincipals* JSScript::principals() { return realm()->principals(); }
 
 inline void JSScript::setBaselineScript(
     JSRuntime* rt, js::jit::BaselineScript* baselineScript) {
+  setBaselineScript(rt->defaultFreeOp(), baselineScript);
+}
+
+inline void JSScript::setBaselineScript(
+    js::FreeOp* fop, js::jit::BaselineScript* baselineScript) {
   if (hasBaselineScript()) {
     js::jit::BaselineScript::writeBarrierPre(zone(), baseline);
-    clearBaselineScript();
+    clearBaselineScript(fop);
   }
   MOZ_ASSERT(!ion || ion == ION_DISABLED_SCRIPT);
 
   baseline = baselineScript;
   if (hasBaselineScript()) {
     AddCellMemory(this, baseline->allocBytes(), js::MemoryUse::BaselineScript);
   }
   resetWarmUpResetCounter();
-  updateJitCodeRaw(rt);
+  updateJitCodeRaw(fop->runtime());
 }
 
-inline void JSScript::clearBaselineScript() {
+inline void JSScript::clearBaselineScript(js::FreeOp* fop) {
   MOZ_ASSERT(hasBaselineScript());
-  RemoveCellMemory(this, baseline->allocBytes(), js::MemoryUse::BaselineScript);
+  fop->removeCellMemory(this, baseline->allocBytes(), js::MemoryUse::BaselineScript);
   baseline = nullptr;
 }
 
-inline void JSScript::clearIonScript() {
+inline void JSScript::clearIonScript(js::FreeOp* fop) {
   MOZ_ASSERT(hasIonScript());
-  RemoveCellMemory(this, ion->allocBytes(), js::MemoryUse::IonScript);
+  fop->removeCellMemory(this, ion->allocBytes(), js::MemoryUse::IonScript);
   ion = nullptr;
 }
 
 inline bool JSScript::ensureHasAnalyzedArgsUsage(JSContext* cx) {
   if (analyzedArgsUsage()) {
     return true;
   }
   return js::jit::AnalyzeArgumentsUsage(cx, this);
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1508,28 +1508,32 @@ js::PCCounts* ScriptCounts::getThrowCoun
 
 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
   return mallocSizeOf(this) + pcCounts_.sizeOfExcludingThis(mallocSizeOf) +
          throwCounts_.sizeOfExcludingThis(mallocSizeOf) +
          ionCounts_->sizeOfIncludingThis(mallocSizeOf);
 }
 
 void JSScript::setIonScript(JSRuntime* rt, js::jit::IonScript* ionScript) {
+  setIonScript(rt->defaultFreeOp(), ionScript);
+}
+
+void JSScript::setIonScript(FreeOp* fop, js::jit::IonScript* ionScript) {
   MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT,
                 !baselineScript()->hasPendingIonBuilder());
   if (hasIonScript()) {
     js::jit::IonScript::writeBarrierPre(zone(), ion);
-    clearIonScript();
+    clearIonScript(fop);
   }
   ion = ionScript;
   MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
   if (hasIonScript()) {
     AddCellMemory(this, ion->allocBytes(), js::MemoryUse::IonScript);
   }
-  updateJitCodeRaw(rt);
+  updateJitCodeRaw(fop->runtime());
 }
 
 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
   MOZ_ASSERT(containsPC(pc));
   return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
 }
 
 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -2620,33 +2620,36 @@ class JSScript : public js::BaseScript {
 
   js::jit::IonScript* ionScript() const {
     MOZ_ASSERT(hasIonScript());
     return ion;
   }
   js::jit::IonScript* maybeIonScript() const { return ion; }
   js::jit::IonScript* const* addressOfIonScript() const { return &ion; }
   void setIonScript(JSRuntime* rt, js::jit::IonScript* ionScript);
-  inline void clearIonScript();
+  void setIonScript(js::FreeOp* fop, js::jit::IonScript* ionScript);
+  inline void clearIonScript(js::FreeOp* fop);
 
   bool hasBaselineScript() const {
     bool res = baseline && baseline != BASELINE_DISABLED_SCRIPT;
     MOZ_ASSERT_IF(!res, !ion || ion == ION_DISABLED_SCRIPT);
     return res;
   }
   bool canBaselineCompile() const {
     return baseline != BASELINE_DISABLED_SCRIPT;
   }
   js::jit::BaselineScript* baselineScript() const {
     MOZ_ASSERT(hasBaselineScript());
     return baseline;
   }
   inline void setBaselineScript(JSRuntime* rt,
                                 js::jit::BaselineScript* baselineScript);
-  inline void clearBaselineScript();
+  inline void setBaselineScript(js::FreeOp* fop,
+                                js::jit::BaselineScript* baselineScript);
+  inline void clearBaselineScript(js::FreeOp* fop);
 
   void updateJitCodeRaw(JSRuntime* rt);
 
   static size_t offsetOfBaselineScript() {
     return offsetof(JSScript, baseline);
   }
   static size_t offsetOfIonScript() { return offsetof(JSScript, ion); }
 
@@ -2752,18 +2755,18 @@ class JSScript : public js::BaseScript {
   bool isTopLevel() { return code() && !functionNonDelazifying(); }
 
   /* Ensure the script has a JitScript. */
   inline bool ensureHasJitScript(JSContext* cx, js::jit::AutoKeepJitScripts&);
 
   bool hasJitScript() const { return jitScript_ != nullptr; }
   js::jit::JitScript* jitScript() { return jitScript_; }
 
-  void maybeReleaseJitScript();
-  void releaseJitScript();
+  void maybeReleaseJitScript(js::FreeOp* fop);
+  void releaseJitScript(js::FreeOp* fop);
 
   inline js::GlobalObject& global() const;
   inline bool hasGlobal(const js::GlobalObject* global) const;
   js::GlobalObject& uninlinedGlobal() const;
 
   uint32_t bodyScopeIndex() const {
     return immutableScriptData()->bodyScopeIndex;
   }
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -89,26 +89,26 @@ static inline size_t AddendumAllocSize(O
   if (kind == ObjectGroup::Addendum_PreliminaryObjects) {
     return sizeof(PreliminaryObjectArrayWithTemplate);
   }
   // Other addendum kinds point to GC memory tracked elsewhere.
   return 0;
 }
 
 void ObjectGroup::setAddendum(AddendumKind kind, void* addendum,
-                              bool writeBarrier /* = true */) {
+                              bool isSweeping /* = flase */) {
   MOZ_ASSERT(!needsSweep());
   MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
 
   RemoveCellMemory(this, AddendumAllocSize(addendumKind(), addendum_),
-                   MemoryUse::ObjectGroupAddendum);
+                   MemoryUse::ObjectGroupAddendum, isSweeping);
 
-  if (writeBarrier) {
-    // Manually trigger barriers if we are clearing new script or
-    // preliminary object information. Other addendums are immutable.
+  if (!isSweeping) {
+    // Trigger a write barrier if we are clearing new script or preliminary
+    // object information outside of sweeping. Other addendums are immutable.
     AutoSweepObjectGroup sweep(this);
     switch (addendumKind()) {
       case Addendum_PreliminaryObjects:
         PreliminaryObjectArrayWithTemplate::writeBarrierPre(
             maybePreliminaryObjects(sweep));
         break;
       case Addendum_NewScript:
         TypeNewScript::writeBarrierPre(newScript(sweep));
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -231,31 +231,31 @@ class ObjectGroup : public gc::TenuredCe
     // PreliminaryObjectArrayWithTemplate.
     Addendum_PreliminaryObjects,
 
     // When used by typed objects, the addendum stores a TypeDescr.
     Addendum_TypeDescr
   };
 
  private:
-  void setAddendum(AddendumKind kind, void* addendum, bool writeBarrier = true);
+  void setAddendum(AddendumKind kind, void* addendum, bool isSweeping = false);
 
   AddendumKind addendumKind() const {
     return (AddendumKind)((flags_ & OBJECT_FLAG_ADDENDUM_MASK) >>
                           OBJECT_FLAG_ADDENDUM_SHIFT);
   }
 
   TypeNewScript* newScriptDontCheckGeneration() const {
     if (addendumKind() == Addendum_NewScript) {
       return reinterpret_cast<TypeNewScript*>(addendum_);
     }
     return nullptr;
   }
 
-  void detachNewScript(bool writeBarrier, ObjectGroup* replacement);
+  void detachNewScript(bool isSweeping, ObjectGroup* replacement);
 
   ObjectGroupFlags flagsDontCheckGeneration() const { return flags_; }
 
  public:
   inline ObjectGroupFlags flags(const AutoSweepObjectGroup&);
   inline void addFlags(const AutoSweepObjectGroup&, ObjectGroupFlags flags);
   inline void clearFlags(const AutoSweepObjectGroup&, ObjectGroupFlags flags);
   inline TypeNewScript* newScript(const AutoSweepObjectGroup& sweep);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -563,17 +563,17 @@ const char* JSRuntime::getDefaultLocale(
   return defaultLocale.ref().get();
 }
 
 void JSRuntime::traceSharedIntlData(JSTracer* trc) {
   sharedIntlData.ref().trace(trc);
 }
 
 FreeOp::FreeOp(JSRuntime* maybeRuntime, bool isDefault)
-    : JSFreeOp(maybeRuntime), isDefault(isDefault) {
+    : JSFreeOp(maybeRuntime), isDefault(isDefault), isCollecting_(!isDefault) {
   MOZ_ASSERT_IF(maybeRuntime, CurrentThreadCanAccessRuntime(maybeRuntime));
 }
 
 FreeOp::~FreeOp() {
   for (size_t i = 0; i < freeLaterList.length(); i++) {
     freeUntracked(freeLaterList[i]);
   }
 
@@ -831,17 +831,18 @@ bool js::CurrentThreadCanAccessZone(Zone
   }
 
   // Other zones can only be accessed by the runtime's active context.
   return CurrentThreadCanAccessRuntime(zone->runtime_);
 }
 
 #ifdef DEBUG
 bool js::CurrentThreadIsPerformingGC() {
-  return TlsContext.get()->performingGC;
+  JSContext* cx = TlsContext.get();
+  return cx->defaultFreeOp()->isCollecting();
 }
 #endif
 
 JS_FRIEND_API void JS::SetJSContextProfilerSampleBufferRangeStart(
     JSContext* cx, uint64_t rangeStart) {
   cx->runtime()->setProfilerSampleBufferRangeStart(rangeStart);
 }
 
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -726,17 +726,18 @@ Shape* NativeObject::addDataPropertyInte
 
   if (table) {
     shape->updateDictionaryTable(table, entry, keep);
   }
 
   return shape;
 }
 
-static MOZ_ALWAYS_INLINE Shape* PropertyTreeReadBarrier(Shape* parent,
+static MOZ_ALWAYS_INLINE Shape* PropertyTreeReadBarrier(JSContext* cx,
+                                                        Shape* parent,
                                                         Shape* shape) {
   JS::Zone* zone = shape->zone();
   if (zone->needsIncrementalBarrier()) {
     // We need a read barrier for the shape tree, since these are weak
     // pointers.
     Shape* tmp = shape;
     TraceManuallyBarrieredEdge(zone->barrierTracer(), &tmp, "read barrier");
     MOZ_ASSERT(tmp == shape);
@@ -749,17 +750,17 @@ static MOZ_ALWAYS_INLINE Shape* Property
       UnmarkGrayShapeRecursively(shape);
     }
     return shape;
   }
 
   // The shape we've found is unreachable and due to be finalized, so
   // remove our weak reference to it and don't use it.
   MOZ_ASSERT(parent->isMarkedAny());
-  parent->removeChild(shape);
+  parent->removeChild(cx->defaultFreeOp(), shape);
 
   return nullptr;
 }
 
 /* static */
 Shape* NativeObject::addEnumerableDataProperty(JSContext* cx,
                                                HandleNativeObject obj,
                                                HandleId id) {
@@ -788,17 +789,17 @@ Shape* NativeObject::addEnumerableDataPr
     if (kid->propidRaw() != id || kid->isAccessorShape() ||
         kid->attributes() != JSPROP_ENUMERATE ||
         kid->base()->unowned() != lastProperty->base()->unowned()) {
       break;
     }
 
     MOZ_ASSERT(kid->isDataProperty());
 
-    kid = PropertyTreeReadBarrier(lastProperty, kid);
+    kid = PropertyTreeReadBarrier(cx, lastProperty, kid);
     if (!kid) {
       break;
     }
 
     if (!obj->setLastProperty(cx, kid)) {
       return nullptr;
     }
     return kid;
@@ -1788,17 +1789,17 @@ bool PropertyTree::insertChild(JSContext
     ReportOutOfMemory(cx);
     return false;
   }
 
   child->setParent(parent);
   return true;
 }
 
-void Shape::removeChild(Shape* child) {
+void Shape::removeChild(FreeOp* fop, Shape* child) {
   MOZ_ASSERT(!child->inDictionary());
   MOZ_ASSERT(child->parent == this);
 
   KidsPointer* kidp = &kids;
 
   if (kidp->isShape()) {
     MOZ_ASSERT(kidp->toShape() == child);
     kidp->setNull();
@@ -1819,18 +1820,17 @@ void Shape::removeChild(Shape* child) {
   MOZ_ASSERT(hash->count() == oldCount - 1);
 
   if (hash->count() == 1) {
     /* Convert from HASH form back to SHAPE form. */
     KidsHash::Range r = hash->all();
     Shape* otherChild = r.front();
     MOZ_ASSERT((r.popFront(), r.empty())); /* No more elements! */
     kidp->setShape(otherChild);
-    js_delete(hash);
-    RemoveCellMemory(this, sizeof(KidsHash), MemoryUse::ShapeKids);
+    fop->delete_(this, hash, MemoryUse::ShapeKids);
   }
 }
 
 MOZ_ALWAYS_INLINE Shape* PropertyTree::inlinedGetChild(
     JSContext* cx, Shape* parent, Handle<StackShape> child) {
   MOZ_ASSERT(parent);
 
   Shape* existingShape = nullptr;
@@ -1853,17 +1853,17 @@ MOZ_ALWAYS_INLINE Shape* PropertyTree::i
     if (KidsHash::Ptr p = kidp->toHash()->lookup(child)) {
       existingShape = *p;
     }
   } else {
     /* If kidp->isNull(), we always insert. */
   }
 
   if (existingShape) {
-    existingShape = PropertyTreeReadBarrier(parent, existingShape);
+    existingShape = PropertyTreeReadBarrier(cx, parent, existingShape);
     if (existingShape) {
       return existingShape;
     }
   }
 
   RootedShape parentRoot(cx, parent);
   Shape* shape = Shape::new_(cx, child, parentRoot->numFixedSlots());
   if (!shape) {
@@ -1877,33 +1877,33 @@ MOZ_ALWAYS_INLINE Shape* PropertyTree::i
   return shape;
 }
 
 Shape* PropertyTree::getChild(JSContext* cx, Shape* parent,
                               Handle<StackShape> child) {
   return inlinedGetChild(cx, parent, child);
 }
 
-void Shape::sweep() {
+void Shape::sweep(FreeOp* fop) {
   /*
    * We detach the child from the parent if the parent is reachable.
    *
    * This test depends on shape arenas not being freed until after we finish
    * incrementally sweeping them. If that were not the case the parent pointer
    * could point to a marked cell that had been deallocated and then
    * reallocated, since allocating a cell in a zone that is being marked will
    * set the mark bit for that cell.
    */
   if (parent && parent->isMarkedAny()) {
     if (inDictionary()) {
       if (parent->listp == &parent) {
         parent->listp = nullptr;
       }
     } else {
-      parent->removeChild(this);
+      parent->removeChild(fop, this);
     }
   }
 }
 
 void Shape::finalize(FreeOp* fop) {
   if (!inDictionary() && kids.isHash()) {
     fop->delete_(this, kids.toHash(), MemoryUse::ShapeKids);
   }
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -1348,19 +1348,19 @@ class Shape : public gc::TenuredCell {
   }
 
 #ifdef DEBUG
   void dump(js::GenericPrinter& out) const;
   void dump() const;
   void dumpSubtree(int level, js::GenericPrinter& out) const;
 #endif
 
-  void sweep();
+  void sweep(FreeOp* fop);
   void finalize(FreeOp* fop);
-  void removeChild(Shape* child);
+  void removeChild(FreeOp* fop, Shape* child);
 
   static const JS::TraceKind TraceKind = JS::TraceKind::Shape;
 
   void traceChildren(JSTracer* trc);
 
   MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, jsid id);
   MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
 
--- a/js/src/vm/StringType-inl.h
+++ b/js/src/vm/StringType-inl.h
@@ -461,15 +461,15 @@ inline void JSExternalString::finalize(j
   if (!JSString::isExternal()) {
     // This started out as an external string, but was turned into a
     // non-external string by JSExternalString::ensureFlat.
     asFlat().finalize(fop);
     return;
   }
 
   size_t nbytes = (length() + 1) * sizeof(char16_t);
-  js::RemoveCellMemory(this, nbytes, js::MemoryUse::StringContents);
+  fop->removeCellMemory(this, nbytes, js::MemoryUse::StringContents);
 
   const JSStringFinalizer* fin = externalFinalizer();
   fin->finalize(fin, const_cast<char16_t*>(rawTwoByteChars()));
 }
 
 #endif /* vm_StringType_inl_h */
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3045,17 +3045,17 @@ void ObjectGroup::markUnknown(const Auto
       prop->types.addType(sweep, cx, TypeSet::UnknownType());
       prop->types.setNonDataProperty(sweep, cx);
     }
   }
 
   clearProperties(sweep);
 }
 
-void ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement) {
+void ObjectGroup::detachNewScript(bool isSweeping, ObjectGroup* replacement) {
   // Clear the TypeNewScript from this ObjectGroup and, if it has been
   // analyzed, remove it from the newObjectGroups table so that it will not be
   // produced by calling 'new' on the associated function anymore.
   // The TypeNewScript is not actually destroyed.
   AutoSweepObjectGroup sweep(this);
   TypeNewScript* newScript = this->newScript(sweep);
   MOZ_ASSERT(newScript);
 
@@ -3074,17 +3074,17 @@ void ObjectGroup::detachNewScript(bool w
                                           replacement);
     } else {
       objectGroups.removeDefaultNewGroup(nullptr, proto, associated);
     }
   } else {
     MOZ_ASSERT(!replacement);
   }
 
-  setAddendum(Addendum_None, nullptr, writeBarrier);
+  setAddendum(Addendum_None, nullptr, isSweeping);
 }
 
 void ObjectGroup::maybeClearNewScriptOnOOM() {
   MOZ_ASSERT(zone()->isGCSweepingOrCompacting());
 
   if (!isMarkedAny()) {
     return;
   }
@@ -3093,17 +3093,17 @@ void ObjectGroup::maybeClearNewScriptOnO
   TypeNewScript* newScript = this->newScript(sweep);
   if (!newScript) {
     return;
   }
 
   addFlags(sweep, OBJECT_FLAG_NEW_SCRIPT_CLEARED);
 
   // This method is called during GC sweeping, so don't trigger pre barriers.
-  detachNewScript(/* writeBarrier = */ false, nullptr);
+  detachNewScript(/* isSweeping = */ true, nullptr);
 
   js_delete(newScript);
 }
 
 void ObjectGroup::clearNewScript(JSContext* cx,
                                  ObjectGroup* replacement /* = nullptr*/) {
   AutoSweepObjectGroup sweep(this);
   TypeNewScript* newScript = this->newScript(sweep);
@@ -3117,17 +3117,17 @@ void ObjectGroup::clearNewScript(JSConte
     // Invalidate any Ion code constructing objects of this type.
     setFlags(sweep, cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED);
 
     // Mark the constructing function as having its 'new' script cleared, so we
     // will not try to construct another one later.
     newScript->function()->setNewScriptCleared();
   }
 
-  detachNewScript(/* writeBarrier = */ true, replacement);
+  detachNewScript(/* isSweeping = */ false, replacement);
 
   if (!cx->isHelperThreadContext()) {
     bool found = newScript->rollbackPartiallyInitializedObjects(cx, this);
 
     // If we managed to rollback any partially initialized objects, then
     // any definite properties we added due to analysis of the new script
     // are now invalid, so remove them. If there weren't any partially
     // initialized objects then we don't need to change type information,
@@ -4506,20 +4506,22 @@ AutoClearTypeInferenceStateOnOOM::AutoCl
     : zone(zone) {
   MOZ_RELEASE_ASSERT(CurrentThreadCanAccessZone(zone));
   MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
   zone->types.setSweepingTypes(true);
 }
 
 AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM() {
   if (zone->types.hadOOMSweepingTypes()) {
+    gc::AutoSetThreadIsSweeping threadIsSweeping;
     JSRuntime* rt = zone->runtimeFromMainThread();
+    FreeOp fop(rt);
     js::CancelOffThreadIonCompile(rt);
     zone->setPreservingCode(false);
-    zone->discardJitCode(rt->defaultFreeOp(), Zone::KeepBaselineCode);
+    zone->discardJitCode(&fop, Zone::KeepBaselineCode);
     zone->types.clearAllNewScriptsOnOOM();
   }
 
   zone->types.setSweepingTypes(false);
 }
 
 JS::ubi::Node::Size JS::ubi::Concrete<js::ObjectGroup>::size(
     mozilla::MallocSizeOf mallocSizeOf) const {