Bug 1449135 part 3 - Remove cooperative scheduling; bake in JSContext* in JIT code. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 27 Mar 2018 18:00:27 +0200
changeset 463801 678d0894d29131576a16ff9376b6e77479ba98f5
parent 463800 7db16aa2e0ee8652ec873597649dcb4c1eca7d22
child 463802 e9c980945ea67dd94a2ebee210f12077ad8e1a7d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1449135
milestone61.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 1449135 part 3 - Remove cooperative scheduling; bake in JSContext* in JIT code. r=luke
js/public/RootingAPI.h
js/src/gc/GC.cpp
js/src/gc/Nursery.cpp
js/src/gc/RootMarking.cpp
js/src/gc/Verifier.cpp
js/src/gc/ZoneGroup.cpp
js/src/gc/ZoneGroup.h
js/src/irregexp/NativeRegExpMacroAssembler.cpp
js/src/irregexp/RegExpStack.h
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineDebugModeOSR.h
js/src/jit/BaselineJIT.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CompileWrappers.cpp
js/src/jit/CompileWrappers.h
js/src/jit/ExecutableAllocator.cpp
js/src/jit/Ion.cpp
js/src/jit/JitFrames.cpp
js/src/jit/JitFrames.h
js/src/jit/Lowering.cpp
js/src/jit/MacroAssembler.cpp
js/src/jit/arm64/MacroAssembler-arm64-inl.h
js/src/jit/arm64/MacroAssembler-arm64.h
js/src/jit/shared/LIR-shared.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfriendapi.cpp
js/src/jspubtd.h
js/src/shell/js.cpp
js/src/threading/ProtectedData.cpp
js/src/vm/Debugger.cpp
js/src/vm/GeckoProfiler-inl.h
js/src/vm/GeckoProfiler.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/JSCompartment.cpp
js/src/vm/JSCompartment.h
js/src/vm/JSContext-inl.h
js/src/vm/JSContext.cpp
js/src/vm/JSContext.h
js/src/vm/JSScript.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SavedStacks.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/vm/TypeInference.cpp
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -869,18 +869,18 @@ class JS_PUBLIC_API(AutoGCRooter)
 
     ~AutoGCRooter() {
         MOZ_ASSERT(this == *stackTop);
         *stackTop = down;
     }
 
     /* Implemented in gc/RootMarking.cpp. */
     inline void trace(JSTracer* trc);
-    static void traceAll(const js::CooperatingContext& target, JSTracer* trc);
-    static void traceAllWrappers(const js::CooperatingContext& target, JSTracer* trc);
+    static void traceAll(JSContext* cx, JSTracer* trc);
+    static void traceAllWrappers(JSContext* cx, JSTracer* trc);
 
   protected:
     AutoGCRooter * const down;
 
     /*
      * Discriminates actual subclass of this being used.  If non-negative, the
      * subclass roots an array of values of the length stored in this field.
      * If negative, meaning is indicated by the corresponding value in the enum
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -3239,17 +3239,17 @@ GCRuntime::triggerGC(JS::gcreason::Reaso
      */
     if (!CurrentThreadCanAccessRuntime(rt))
         return false;
 
     /* GC is already running. */
     if (JS::CurrentThreadIsHeapCollecting())
         return false;
 
-    JS::PrepareForFullGC(rt->activeContextFromOwnThread());
+    JS::PrepareForFullGC(rt->mainContextFromOwnThread());
     requestMajorGC(reason);
     return true;
 }
 
 void
 GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock)
 {
     MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting());
@@ -3336,17 +3336,17 @@ GCRuntime::triggerZoneGC(Zone* zone, JS:
 
 void
 GCRuntime::maybeGC(Zone* zone)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
 #ifdef JS_GC_ZEAL
     if (hasZealMode(ZealMode::Alloc) || hasZealMode(ZealMode::RootsChange)) {
-        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
+        JS::PrepareForFullGC(rt->mainContextFromOwnThread());
         gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
         return;
     }
 #endif
 
     if (gcIfRequested())
         return;
 
@@ -3961,21 +3961,20 @@ GCRuntime::purgeRuntime()
         comp->purge();
 
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         zone->atomCache().clearAndShrink();
         zone->externalStringCache().purge();
         zone->functionToStringCache().purge();
     }
 
-    for (const CooperatingContext& target : rt->cooperatingContexts()) {
-        freeUnusedLifoBlocksAfterSweeping(&target.context()->tempLifoAlloc());
-        target.context()->interpreterStack().purge(rt);
-        target.context()->frontendCollectionPool().purge();
-    }
+    JSContext* cx = rt->mainContextFromOwnThread();
+    freeUnusedLifoBlocksAfterSweeping(&cx->tempLifoAlloc());
+    cx->interpreterStack().purge(rt);
+    cx->frontendCollectionPool().purge();
 
     rt->caches().purge();
 
     if (auto cache = rt->maybeThisRuntimeSharedImmutableStrings())
         cache->purge();
 
     MOZ_ASSERT(unmarkGrayStack.empty());
     unmarkGrayStack.clearAndFree();
@@ -7015,18 +7014,17 @@ GCRuntime::incrementalCollectSlice(Slice
         incrementalState = State::Mark;
 
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalRootsThenFinish))
             break;
 
         MOZ_FALLTHROUGH;
 
       case State::Mark:
-        for (const CooperatingContext& target : rt->cooperatingContexts())
-            AutoGCRooter::traceAllWrappers(target, &marker);
+        AutoGCRooter::traceAllWrappers(rt->mainContextFromOwnThread(), &marker);
 
         /* If we needed delayed marking for gray roots, then collect until done. */
         if (isIncremental && !hasValidGrayRootsBuffer()) {
             budget.makeUnlimited();
             isIncremental = false;
             stats().nonincremental(AbortReason::GrayRootBufferingFailed);
         }
 
@@ -7463,17 +7461,17 @@ GCRuntime::maybeDoCycleCollection()
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         ++compartmentsTotal;
         GlobalObject* global = c->unsafeUnbarrieredMaybeGlobal();
         if (global && global->isMarkedGray())
             ++compartmentsGray;
     }
     double grayFraction = double(compartmentsGray) / double(compartmentsTotal);
     if (grayFraction > ExcessiveGrayCompartments || compartmentsGray > LimitGrayCompartments)
-        callDoCycleCollectionCallback(rt->activeContextFromOwnThread());
+        callDoCycleCollectionCallback(rt->mainContextFromOwnThread());
 }
 
 void
 GCRuntime::checkCanCallAPI()
 {
     MOZ_RELEASE_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
@@ -7551,17 +7549,17 @@ GCRuntime::collect(bool nonincrementalBy
          *    not collected (see the large comment in beginMarkPhase)
          */
         repeat = false;
         if (!isIncrementalGCInProgress()) {
             if (wasReset) {
                 repeat = true;
             } else if (rootsRemoved && IsShutdownGC(reason)) {
                 /* Need to re-schedule all zones for GC. */
-                JS::PrepareForFullGC(rt->activeContextFromOwnThread());
+                JS::PrepareForFullGC(rt->mainContextFromOwnThread());
                 repeat = true;
                 reason = JS::gcreason::ROOTS_REMOVED;
             } else if (shouldRepeatForDeadZone(reason)) {
                 repeat = true;
                 reason = JS::gcreason::COMPARTMENT_REVIVED;
             }
          }
     } while (repeat);
@@ -7667,36 +7665,36 @@ ZonesSelected(JSRuntime* rt)
     return false;
 }
 
 void
 GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget)
 {
     MOZ_ASSERT(!isIncrementalGCInProgress());
     if (!ZonesSelected(rt))
-        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
+        JS::PrepareForFullGC(rt->mainContextFromOwnThread());
     invocationKind = gckind;
     collect(false, budget, JS::gcreason::DEBUG_GC);
 }
 
 void
 GCRuntime::debugGCSlice(SliceBudget& budget)
 {
     MOZ_ASSERT(isIncrementalGCInProgress());
     if (!ZonesSelected(rt))
-        JS::PrepareForIncrementalGC(rt->activeContextFromOwnThread());
+        JS::PrepareForIncrementalGC(rt->mainContextFromOwnThread());
     collect(false, budget, JS::gcreason::DEBUG_GC);
 }
 
 /* Schedule a full GC unless a zone will already be collected. */
 void
 js::PrepareForDebugGC(JSRuntime* rt)
 {
     if (!ZonesSelected(rt))
-        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
+        JS::PrepareForFullGC(rt->mainContextFromOwnThread());
 }
 
 void
 GCRuntime::onOutOfMallocMemory()
 {
     // Stop allocating new chunks.
     allocTask.cancel(GCParallelTask::CancelAndWait);
 
@@ -7859,20 +7857,17 @@ js::NewCompartment(JSContext* cx, JSPrin
         group = rt->gc.systemZoneGroup;
         break;
       case JS::NewZoneInExistingZoneGroup:
         group = static_cast<ZoneGroup*>(options.creationOptions().zonePointer());
         MOZ_ASSERT(group);
         break;
     }
 
-    if (group) {
-        // Take over ownership of the group while we create the compartment/zone.
-        group->enter(cx);
-    } else {
+    if (!group) {
         MOZ_ASSERT(!zone);
         group = cx->new_<ZoneGroup>(rt);
         if (!group)
             return nullptr;
 
         groupHolder.reset(group);
 
         if (!group->init()) {
@@ -7932,23 +7927,21 @@ js::NewCompartment(JSContext* cx, JSPrin
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // Lazily set the runtime's system zone group.
         if (zoneSpec == JS::SystemZone || zoneSpec == JS::NewZoneInSystemZoneGroup) {
             MOZ_RELEASE_ASSERT(!rt->gc.systemZoneGroup);
             rt->gc.systemZoneGroup = group;
-            group->setUseExclusiveLocking();
         }
     }
 
     zoneHolder.forget();
     groupHolder.forget();
-    group->leave();
     return compartment.forget();
 }
 
 void
 gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
 {
     JSRuntime* rt = source->runtimeFromActiveCooperatingThread();
     rt->gc.mergeCompartments(source, target);
@@ -7964,17 +7957,17 @@ GCRuntime::mergeCompartments(JSCompartme
     // also implies that the compartment is not visible to the debugger.
     MOZ_ASSERT(source->creationOptions_.mergeable());
     MOZ_ASSERT(source->creationOptions_.invisibleToDebugger());
 
     MOZ_ASSERT(!source->hasBeenEntered());
     MOZ_ASSERT(source->zone()->compartments().length() == 1);
     MOZ_ASSERT(source->zone()->group()->zones().length() == 1);
 
-    JSContext* cx = rt->activeContextFromOwnThread();
+    JSContext* cx = rt->mainContextFromOwnThread();
 
     MOZ_ASSERT(!source->zone()->wasGCStarted());
     JS::AutoAssertNoGC nogc(cx);
 
     AutoTraceSession session(rt);
 
     // Cleanup tables and other state in the source compartment that will be
     // meaningless after merging into the target compartment.
@@ -8227,17 +8220,16 @@ void PreventGCDuringInteractiveDebug()
 
 #endif
 
 void
 js::ReleaseAllJITCode(FreeOp* fop)
 {
     js::CancelOffThreadIonCompile(fop->runtime());
 
-    JSRuntime::AutoProhibitActiveContextChange apacc(fop->runtime());
     for (ZonesIter zone(fop->runtime(), SkipAtoms); !zone.done(); zone.next()) {
         zone->setPreservingCode(false);
         zone->discardJitCode(fop);
     }
 }
 
 void
 ArenaLists::adoptArenas(JSRuntime* rt, ArenaLists* fromArenaLists, bool targetZoneIsCollecting)
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -742,27 +742,26 @@ js::Nursery::collect(JS::gcreason::Reaso
     bool shouldPretenure = (validPromotionRate && promotionRate > 0.6) ||
         IsFullStoreBufferReason(reason);
 
     if (shouldPretenure) {
         JSContext* cx = TlsContext.get();
         for (auto& entry : tenureCounts.entries) {
             if (entry.count >= 3000) {
                 ObjectGroup* group = entry.group;
-                if (group->canPreTenure() && group->zone()->group()->canEnterWithoutYielding(cx)) {
+                if (group->canPreTenure()) {
                     AutoCompartment ac(cx, group);
                     group->setShouldPreTenure(cx);
                     pretenureCount++;
                 }
             }
         }
     }
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         if (shouldPretenure && zone->allocNurseryStrings && zone->tenuredStrings >= 30 * 1000) {
-            JSRuntime::AutoProhibitActiveContextChange apacc(rt);
             CancelOffThreadIonCompile(zone);
             bool preserving = zone->isPreservingCode();
             zone->setPreservingCode(false);
             zone->discardJitCode(rt->defaultFreeOp());
             zone->setPreservingCode(preserving);
             for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
                 if (jit::JitCompartment* jitComp = c->jitCompartment()) {
                     jitComp->discardStubs();
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -75,19 +75,19 @@ JS_FOR_EACH_TRACEKIND(TRACE_ROOTS)
 
 void
 JS::RootingContext::traceStackRoots(JSTracer* trc)
 {
     TraceStackRoots(trc, stackRoots_);
 }
 
 static void
-TraceExactStackRoots(const CooperatingContext& target, JSTracer* trc)
+TraceExactStackRoots(JSContext* cx, JSTracer* trc)
 {
-    target.context()->traceStackRoots(trc);
+    cx->traceStackRoots(trc);
 }
 
 template <typename T, TraceFunction<T> TraceFn = TraceNullableRoot>
 static inline void
 TracePersistentRootedList(JSTracer* trc, mozilla::LinkedList<PersistentRooted<void*>>& list,
                          const char* name)
 {
     for (PersistentRooted<void*>* r : list)
@@ -191,26 +191,26 @@ AutoGCRooter::trace(JSTracer* trc)
     }
 
     MOZ_ASSERT(tag_ >= 0);
     if (Value* vp = static_cast<AutoArrayRooter*>(this)->array)
         TraceRootRange(trc, tag_, vp, "JS::AutoArrayRooter.array");
 }
 
 /* static */ void
-AutoGCRooter::traceAll(const CooperatingContext& target, JSTracer* trc)
+AutoGCRooter::traceAll(JSContext* cx, JSTracer* trc)
 {
-    for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down)
+    for (AutoGCRooter* gcr = cx->autoGCRooters_; gcr; gcr = gcr->down)
         gcr->trace(trc);
 }
 
 /* static */ void
-AutoGCRooter::traceAllWrappers(const CooperatingContext& target, JSTracer* trc)
+AutoGCRooter::traceAllWrappers(JSContext* cx, JSTracer* trc)
 {
-    for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down) {
+    for (AutoGCRooter* gcr = cx->autoGCRooters_; gcr; gcr = gcr->down) {
         if (gcr->tag_ == WRAPVECTOR || gcr->tag_ == WRAPPER)
             gcr->trace(trc);
     }
 }
 
 void
 StackShape::trace(JSTracer* trc)
 {
@@ -314,47 +314,45 @@ void
 js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
                                       AutoTraceSession& session)
 {
     MOZ_ASSERT(!TlsContext.get()->suppressGC);
 
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK);
 
-        JSContext* cx = TlsContext.get();
-        for (const CooperatingContext& target : rt->cooperatingContexts()) {
-            // Trace active interpreter and JIT stack roots.
-            TraceInterpreterActivations(cx, target, trc);
-            jit::TraceJitActivations(cx, target, trc);
+        JSContext* cx = rt->mainContextFromOwnThread();
+
+        // Trace active interpreter and JIT stack roots.
+        TraceInterpreterActivations(cx, trc);
+        jit::TraceJitActivations(cx, trc);
 
-            // Trace legacy C stack roots.
-            AutoGCRooter::traceAll(target, trc);
+        // Trace legacy C stack roots.
+        AutoGCRooter::traceAll(cx, trc);
 
-            // Trace C stack roots.
-            TraceExactStackRoots(target, trc);
-        }
+        // Trace C stack roots.
+        TraceExactStackRoots(cx, trc);
 
         for (RootRange r = rootsHash.ref().all(); !r.empty(); r.popFront()) {
             const RootEntry& entry = r.front();
             TraceRoot(trc, entry.key(), entry.value());
         }
     }
 
     // Trace runtime global roots.
     TracePersistentRooted(rt, trc);
 
     // Trace the self-hosting global compartment.
     rt->traceSelfHostingGlobal(trc);
 
     // Trace the shared Intl data.
     rt->traceSharedIntlData(trc);
 
-    // Trace anything in any of the cooperating threads.
-    for (const CooperatingContext& target : rt->cooperatingContexts())
-        target.context()->trace(trc);
+    // Trace the JSContext.
+    rt->mainContextFromOwnThread()->trace(trc);
 
     // Trace all compartment roots, but not the compartment itself; it is
     // traced via the parent pointer if traceRoots actually traces anything.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
 
     // Trace helper thread roots.
     HelperThreadState().trace(trc, session);
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -176,18 +176,17 @@ NextNode(VerifyNode* node)
 void
 gc::GCRuntime::startVerifyPreBarriers()
 {
     if (verifyPreData || isIncrementalGCInProgress())
         return;
 
     if (IsIncrementalGCUnsafe(rt) != AbortReason::None ||
         TlsContext.get()->keepAtoms ||
-        rt->hasHelperThreadZones() ||
-        rt->cooperatingContexts().length() != 1)
+        rt->hasHelperThreadZones())
     {
         return;
     }
 
     number++;
 
     VerifyPreTracer* trc = js_new<VerifyPreTracer>(rt);
     if (!trc)
@@ -329,17 +328,17 @@ gc::GCRuntime::endVerifyPreBarriers()
 {
     VerifyPreTracer* trc = verifyPreData;
 
     if (!trc)
         return;
 
     MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt));
 
-    AutoPrepareForTracing prep(rt->activeContextFromOwnThread());
+    AutoPrepareForTracing prep(rt->mainContextFromOwnThread());
 
     bool compartmentCreated = false;
 
     /* We need to disable barriers before tracing, which may invoke barriers. */
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (!zone->needsIncrementalBarrier())
             compartmentCreated = true;
 
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -12,18 +12,17 @@
 #include "vm/JSContext.h"
 
 using namespace js;
 
 namespace js {
 
 ZoneGroup::ZoneGroup(JSRuntime* runtime)
   : runtime(runtime),
-    ownerContext_(TlsContext.get()),
-    enterCount(1),
+    helperThreadOwnerContext_(nullptr),
     zones_(this),
     helperThreadUse(HelperThreadUse::None),
 #ifdef DEBUG
     ionBailAfter_(this, 0),
 #endif
     jitZoneGroup(this, nullptr),
     debuggerList_(this),
     numFinishedBuilders(0),
@@ -55,61 +54,28 @@ ZoneGroup::~ZoneGroup()
 
     js_delete(jitZoneGroup.ref());
 
     if (this == runtime->gc.systemZoneGroup)
         runtime->gc.systemZoneGroup = nullptr;
 }
 
 void
-ZoneGroup::enter(JSContext* cx)
+ZoneGroup::setHelperThreadOwnerContext(JSContext* cx)
 {
-    if (ownerContext().context() == cx) {
-        MOZ_ASSERT(enterCount);
-    } else {
-        if (useExclusiveLocking()) {
-            MOZ_ASSERT(!usedByHelperThread());
-            while (ownerContext().context() != nullptr) {
-                cx->yieldToEmbedding();
-            }
-        }
-        MOZ_RELEASE_ASSERT(ownerContext().context() == nullptr);
-        MOZ_ASSERT(enterCount == 0);
-        ownerContext_ = CooperatingContext(cx);
-        if (cx->generationalDisabled)
-            nursery().disable();
-
-        // Finish any Ion compilations in this zone group, in case compilation
-        // finished for some script in this group while no thread was in this
-        // group.
-        jit::AttachFinishedCompilations(this, nullptr);
-    }
-    enterCount++;
-}
-
-void
-ZoneGroup::leave()
-{
-    MOZ_ASSERT(ownedByCurrentThread());
-    MOZ_ASSERT(enterCount);
-    if (--enterCount == 0)
-        ownerContext_ = CooperatingContext(nullptr);
+    MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
+    helperThreadOwnerContext_ = cx;
 }
 
 bool
-ZoneGroup::canEnterWithoutYielding(JSContext* cx)
+ZoneGroup::ownedByCurrentHelperThread()
 {
-    return ownerContext().context() == cx || ownerContext().context() == nullptr;
-}
-
-bool
-ZoneGroup::ownedByCurrentThread()
-{
+    MOZ_ASSERT(usedByHelperThread());
     MOZ_ASSERT(TlsContext.get());
-    return ownerContext().context() == TlsContext.get();
+    return helperThreadOwnerContext_ == TlsContext.get();
 }
 
 ZoneGroup::IonBuilderList&
 ZoneGroup::ionLazyLinkList()
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime),
                "Should only be mutated by the active thread.");
     return ionLazyLinkList_.ref();
@@ -151,31 +117,8 @@ ZoneGroup::deleteEmptyZone(Zone* zone)
             zone->destroy(runtime->defaultFreeOp());
             return;
         }
     }
     MOZ_CRASH("Zone not found");
 }
 
 } // namespace js
-
-JS::AutoRelinquishZoneGroups::AutoRelinquishZoneGroups(JSContext* cx)
-  : cx(cx)
-{
-    MOZ_ASSERT(cx == TlsContext.get());
-
-    AutoEnterOOMUnsafeRegion oomUnsafe;
-    for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
-        while (group->ownerContext().context() == cx) {
-            group->leave();
-            if (!enterList.append(group))
-                oomUnsafe.crash("AutoRelinquishZoneGroups");
-        }
-    }
-}
-
-JS::AutoRelinquishZoneGroups::~AutoRelinquishZoneGroups()
-{
-    for (size_t i = 0; i < enterList.length(); i++) {
-        ZoneGroup* group = static_cast<ZoneGroup*>(enterList[i]);
-        group->enter(cx);
-    }
-}
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -28,34 +28,23 @@ typedef Vector<JS::Zone*, 4, SystemAlloc
 // entered the zone group.
 
 class ZoneGroup
 {
   public:
     JSRuntime* const runtime;
 
   private:
-    // The context with exclusive access to this zone group.
-    UnprotectedData<CooperatingContext> ownerContext_;
-
-    // The number of times the context has entered this zone group.
-    UnprotectedData<size_t> enterCount;
-
-    // If this flag is true, then we may need to block before entering this zone
-    // group. Blocking happens using JSContext::yieldToEmbedding.
-    UnprotectedData<bool> useExclusiveLocking_;
+    // The helper thread context with exclusive access to this zone group, if
+    // usedByHelperThread(), or nullptr when on the main thread.
+    UnprotectedData<JSContext*> helperThreadOwnerContext_;
 
   public:
-    CooperatingContext& ownerContext() { return ownerContext_.ref(); }
-    void* addressOfOwnerContext() { return &ownerContext_.ref().cx; }
-
-    void enter(JSContext* cx);
-    void leave();
-    bool canEnterWithoutYielding(JSContext* cx);
-    bool ownedByCurrentThread();
+    bool ownedByCurrentHelperThread();
+    void setHelperThreadOwnerContext(JSContext* cx);
 
     // All zones in the group.
   private:
     ZoneGroupOrGCTaskData<ZoneVector> zones_;
   public:
     ZoneVector& zones() { return zones_.ref(); }
 
   private:
@@ -96,20 +85,16 @@ class ZoneGroup
     bool init();
 
     inline Nursery& nursery();
     inline gc::StoreBuffer& storeBuffer();
 
     inline bool isCollecting();
     inline bool isGCScheduled();
 
-    // See the useExclusiveLocking_ field above.
-    void setUseExclusiveLocking() { useExclusiveLocking_ = true; }
-    bool useExclusiveLocking() { return useExclusiveLocking_; }
-
     // Delete an empty zone after its contents have been merged.
     void deleteEmptyZone(Zone* zone);
 
 #ifdef DEBUG
   private:
     // The number of possible bailing places encounters before forcefully bailing
     // in that place. Zero means inactive.
     ZoneGroupData<uint32_t> ionBailAfter_;
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -162,19 +162,17 @@ NativeRegExpMacroAssembler::GenerateCode
     // Actually emit code to start a new stack frame.
     masm.reserveStack(frameSize);
     masm.checkStackAlignment();
 
     // Check if we have space on the stack. Use the *NoInterrupt stack limit to
     // avoid failing repeatedly when the regex code is called from Ion JIT code,
     // see bug 1208819.
     Label stack_ok;
-    void* context_addr = cx->zone()->group()->addressOfOwnerContext();
-    masm.loadPtr(AbsoluteAddress(context_addr), temp0);
-    Address limit_addr(temp0, offsetof(JSContext, jitStackLimitNoInterrupt));
+    AbsoluteAddress limit_addr(cx->addressOfJitStackLimitNoInterrupt());
     masm.branchStackPtrRhs(Assembler::Below, limit_addr, &stack_ok);
 
     // Exit with an exception. There is not enough space on the stack
     // for our working registers.
     masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
     masm.jump(&return_temp0);
 
     masm.bind(&stack_ok);
@@ -279,19 +277,17 @@ NativeRegExpMacroAssembler::GenerateCode
                        ImmWord(register_offset(num_saved_registers_)), &init_loop);
     } else {
         // Unroll the loop.
         for (int i = 0; i < num_saved_registers_; i++)
             masm.storePtr(temp0, register_location(i));
     }
 
     // Initialize backtrack stack pointer.
-    size_t baseOffset = offsetof(JSContext, regexpStack) + RegExpStack::offsetOfBase();
-    masm.loadPtr(AbsoluteAddress(context_addr), backtrack_stack_pointer);
-    masm.loadPtr(Address(backtrack_stack_pointer, baseOffset), backtrack_stack_pointer);
+    masm.loadPtr(AbsoluteAddress(cx->regexpStack.ref().addressOfBase()), backtrack_stack_pointer);
     masm.storePtr(backtrack_stack_pointer,
                   Address(masm.getStackPointer(), offsetof(FrameData, backtrackStackBase)));
 
     masm.jump(&start_label_);
 
     // Exit code:
     if (success_label_.used()) {
         MOZ_ASSERT(num_saved_registers_ > 0);
@@ -486,20 +482,17 @@ NativeRegExpMacroAssembler::GenerateCode
         Label return_from_overflow_handler;
         masm.branchTest32(Assembler::Zero, temp0, temp0, &return_from_overflow_handler);
 
         // Otherwise, store the new backtrack stack base and recompute the new
         // top of the stack.
         Address backtrackStackBaseAddress(temp2, offsetof(FrameData, backtrackStackBase));
         masm.subPtr(backtrackStackBaseAddress, backtrack_stack_pointer);
 
-        void* context_addr = cx->zone()->group()->addressOfOwnerContext();
-        size_t baseOffset = offsetof(JSContext, regexpStack) + RegExpStack::offsetOfBase();
-        masm.loadPtr(AbsoluteAddress(context_addr), temp1);
-        masm.loadPtr(Address(temp1, baseOffset), temp1);
+        masm.loadPtr(AbsoluteAddress(cx->regexpStack.ref().addressOfBase()), temp1);
         masm.storePtr(temp1, backtrackStackBaseAddress);
         masm.addPtr(temp1, backtrack_stack_pointer);
 
         // Resume execution in calling code.
         masm.bind(&return_from_overflow_handler);
         masm.abiret();
     }
 
@@ -570,20 +563,18 @@ NativeRegExpMacroAssembler::AdvanceRegis
 
 void
 NativeRegExpMacroAssembler::Backtrack()
 {
     JitSpew(SPEW_PREFIX "Backtrack");
 
     // Check for an interrupt.
     Label noInterrupt;
-    void* contextAddr = cx->zone()->group()->addressOfOwnerContext();
-    masm.loadPtr(AbsoluteAddress(contextAddr), temp0);
     masm.branch32(Assembler::Equal,
-                  Address(temp0, offsetof(JSContext, interruptRegExpJit_)),
+                  AbsoluteAddress(cx->addressOfInterruptRegExpJit()),
                   Imm32(0),
                   &noInterrupt);
     masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
     masm.jump(&exit_label_);
     masm.bind(&noInterrupt);
 
     // Pop code location from backtrack stack and jump to location.
     PopBacktrack(temp0);
@@ -1140,21 +1131,20 @@ NativeRegExpMacroAssembler::PopBacktrack
 }
 
 void
 NativeRegExpMacroAssembler::CheckBacktrackStackLimit()
 {
     JitSpew(SPEW_PREFIX "CheckBacktrackStackLimit");
 
     Label no_stack_overflow;
-    void* context_addr = cx->zone()->group()->addressOfOwnerContext();
-    size_t limitOffset = offsetof(JSContext, regexpStack) + RegExpStack::offsetOfLimit();
-    masm.loadPtr(AbsoluteAddress(context_addr), temp1);
-    masm.branchPtr(Assembler::AboveOrEqual, Address(temp1, limitOffset),
-                   backtrack_stack_pointer, &no_stack_overflow);
+    masm.branchPtr(Assembler::AboveOrEqual,
+                   AbsoluteAddress(cx->regexpStack.ref().addressOfLimit()),
+                   backtrack_stack_pointer,
+                   &no_stack_overflow);
 
     // Copy the stack pointer before the call() instruction modifies it.
     masm.moveStackPtrTo(temp2);
 
     masm.call(&stack_overflow_label_);
     masm.bind(&no_stack_overflow);
 
     // Exit with an exception if the call failed.
--- a/js/src/irregexp/RegExpStack.h
+++ b/js/src/irregexp/RegExpStack.h
@@ -77,16 +77,19 @@ class RegExpStack
 
     // Attempts to grow the stack by at least kStackLimitSlack entries.
     bool grow();
 
     // Address of allocated memory.
     static size_t offsetOfBase() { return offsetof(RegExpStack, base_); }
     static size_t offsetOfLimit() { return offsetof(RegExpStack, limit_); }
 
+    void* addressOfBase() { return &base_; }
+    void* addressOfLimit() { return &limit_; }
+
     void* base() { return base_; }
     void* limit() { return limit_; }
 
   private:
     // Artificial limit used when no memory has been allocated.
     static const uintptr_t kMemoryTop = static_cast<uintptr_t>(-1);
 
     // Minimal size of allocated stack area, in bytes.
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -548,20 +548,18 @@ BaselineCompiler::emitStackCheck(bool ea
     Label forceCall;
     if (!earlyCheck && needsEarlyStackCheck()) {
         masm.branchTest32(Assembler::NonZero,
                           frame.addressOfFlags(),
                           Imm32(BaselineFrame::OVER_RECURSED),
                           &forceCall);
     }
 
-    void* contextAddr = cx->zone()->group()->addressOfOwnerContext();
-    masm.loadPtr(AbsoluteAddress(contextAddr), R0.scratchReg());
     masm.branchPtr(Assembler::BelowOrEqual,
-                   Address(R0.scratchReg(), offsetof(JSContext, jitStackLimit)), R1.scratchReg(),
+                   AbsoluteAddress(cx->addressOfJitStackLimit()), R1.scratchReg(),
                    &skipCall);
 
     if (!earlyCheck && needsEarlyStackCheck())
         masm.bind(&forceCall);
 
     prepareVMCall();
     pushArg(Imm32(earlyCheck));
     pushArg(Imm32(tolerance));
@@ -697,20 +695,18 @@ static const VMFunction InterruptCheckIn
     FunctionInfo<InterruptCheckFn>(InterruptCheck, "InterruptCheck");
 
 bool
 BaselineCompiler::emitInterruptCheck()
 {
     frame.syncStack(0);
 
     Label done;
-    void* context = cx->zone()->group()->addressOfOwnerContext();
-    masm.loadPtr(AbsoluteAddress(context), R0.scratchReg());
     masm.branch32(Assembler::Equal,
-                  Address(R0.scratchReg(), offsetof(JSContext, interrupt_)), Imm32(0),
+                  AbsoluteAddress(cx->addressOfInterrupt()), Imm32(0),
                   &done);
 
     prepareVMCall();
     if (!callVM(InterruptCheckInfo))
         return false;
 
     masm.bind(&done);
     return true;
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -339,17 +339,17 @@ static void
 SpewPatchStubFrame(ICStub* oldStub, ICStub* newStub)
 {
     JitSpew(JitSpew_BaselineDebugModeOSR,
             "Patch   stub %p -> %p on BaselineStub frame (%s)",
             oldStub, newStub, newStub ? ICStub::KindString(newStub->kind()) : "exception handler");
 }
 
 static void
-PatchBaselineFramesForDebugMode(JSContext* cx, const CooperatingContext& target,
+PatchBaselineFramesForDebugMode(JSContext* cx,
                                 const Debugger::ExecutionObservableSet& obs,
                                 const ActivationIterator& activation,
                                 DebugModeOSREntryVector& entries, size_t* start)
 {
     //
     // Recompile Patching Overview
     //
     // When toggling debug mode with live baseline scripts on the stack, we
@@ -415,17 +415,17 @@ PatchBaselineFramesForDebugMode(JSContex
                 // the baseline frame here, we resume right after the IC
                 // returns.
                 //
                 // Since we're using the same IC stub code, we can resume
                 // directly to the IC resume address.
                 uint8_t* retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
                 DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
-                    target, prev->returnAddress(), retAddr);
+                    cx, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
             if (kind == ICEntry::Kind_Invalid) {
                 // Case H above.
                 //
@@ -444,17 +444,17 @@ PatchBaselineFramesForDebugMode(JSContex
                 uint8_t* retAddr;
                 if (cx->runtime()->geckoProfiler().enabled())
                     retAddr = bl->nativeCodeForPC(script, pc);
                 else
                     retAddr = nullptr;
                 SpewPatchBaselineFrameFromExceptionHandler(prev->returnAddress(), retAddr,
                                                            script, pc);
                 DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
-                    target, prev->returnAddress(), retAddr);
+                    cx, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
             // Case F above.
             //
             // We undo a previous recompile by handling cases B, C, D, E, I or J
@@ -845,25 +845,23 @@ bool
 jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext* cx,
                                                  const Debugger::ExecutionObservableSet& obs,
                                                  Debugger::IsObserving observing)
 {
     // First recompile the active scripts on the stack and patch the live
     // frames.
     Vector<DebugModeOSREntry> entries(cx);
 
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (ActivationIterator iter(cx, target); !iter.done(); ++iter) {
-            if (iter->isJit()) {
-                if (!CollectJitStackScripts(cx, obs, iter, entries))
-                    return false;
-            } else if (iter->isInterpreter()) {
-                if (!CollectInterpreterStackScripts(cx, obs, iter, entries))
-                    return false;
-            }
+    for (ActivationIterator iter(cx); !iter.done(); ++iter) {
+        if (iter->isJit()) {
+            if (!CollectJitStackScripts(cx, obs, iter, entries))
+                return false;
+        } else if (iter->isInterpreter()) {
+            if (!CollectInterpreterStackScripts(cx, obs, iter, entries))
+                return false;
         }
     }
 
     if (entries.empty())
         return true;
 
     // When the profiler is enabled, we need to have suppressed sampling,
     // since the basline jit scripts are in a state of flux.
@@ -902,23 +900,21 @@ jit::RecompileOnStackBaselineScriptsForD
 
     for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
         const DebugModeOSREntry& entry = iter.entry();
         if (entry.recompiled())
             BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), entry.oldBaselineScript);
     }
 
     size_t processed = 0;
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (ActivationIterator iter(cx, target); !iter.done(); ++iter) {
-            if (iter->isJit())
-                PatchBaselineFramesForDebugMode(cx, target, obs, iter, entries, &processed);
-            else if (iter->isInterpreter())
-                SkipInterpreterFrameEntries(obs, iter, &processed);
-        }
+    for (ActivationIterator iter(cx); !iter.done(); ++iter) {
+        if (iter->isJit())
+            PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed);
+        else if (iter->isInterpreter())
+            SkipInterpreterFrameEntries(obs, iter, &processed);
     }
     MOZ_ASSERT(processed == entries.length());
 
     return true;
 }
 
 void
 BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc, Value* vp)
@@ -1177,15 +1173,15 @@ JitRuntime::generateBaselineDebugModeOSR
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler");
 #endif
 
     return code;
 }
 
 /* static */ void
-DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(const CooperatingContext& cx,
+DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(JSContext* cx,
                                                        uint8_t* oldAddr, uint8_t* newAddr)
 {
     DebugModeOSRVolatileJitFrameIter* iter;
-    for (iter = cx.context()->liveVolatileJitFrameIter_; iter; iter = iter->prev)
+    for (iter = cx->liveVolatileJitFrameIter_; iter; iter = iter->prev)
         iter->asJSJit().exchangeReturnAddressIfMatch(oldAddr, newAddr);
 }
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -100,17 +100,17 @@ class DebugModeOSRVolatileJitFrameIter :
         *stack = this;
     }
 
     ~DebugModeOSRVolatileJitFrameIter() {
         MOZ_ASSERT(*stack == this);
         *stack = prev;
     }
 
-    static void forwardLiveIterators(const CooperatingContext& target,
+    static void forwardLiveIterators(JSContext* cx,
                                      uint8_t* oldAddr, uint8_t* newAddr);
 };
 
 //
 // Auxiliary info to help the DebugModeOSRHandler fix up state.
 //
 struct BaselineDebugModeOSRInfo
 {
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1213,15 +1213,13 @@ MarkActiveBaselineScripts(JSContext* cx,
 }
 
 void
 jit::MarkActiveBaselineScripts(Zone* zone)
 {
     if (zone->isAtomsZone())
         return;
     JSContext* cx = TlsContext.get();
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (JitActivationIterator iter(cx, target); !iter.done(); ++iter) {
-            if (iter->compartment()->zone() == zone)
-                MarkActiveBaselineScripts(cx, iter);
-        }
+    for (JitActivationIterator iter(cx); !iter.done(); ++iter) {
+        if (iter->compartment()->zone() == zone)
+            MarkActiveBaselineScripts(cx, iter);
     }
 }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5257,23 +5257,19 @@ CodeGenerator::visitCheckOverRecursed(LC
     // C functions may then violate the limit without any checking.
     //
     // Since Ion frames exist on the C stack, the stack limit may be
     // dynamically set by JS_SetThreadStackLimit() and JS_SetNativeStackQuota().
 
     CheckOverRecursedFailure* ool = new(alloc()) CheckOverRecursedFailure(lir);
     addOutOfLineCode(ool, lir->mir());
 
-    Register temp = ToRegister(lir->temp());
-
     // Conditional forward (unlikely) branch to failure.
-    const void* contextAddr = gen->compartment->zone()->addressOfJSContext();
-    masm.loadPtr(AbsoluteAddress(contextAddr), temp);
-    masm.branchStackPtrRhs(Assembler::AboveOrEqual,
-                           Address(temp, offsetof(JSContext, jitStackLimit)), ool->entry());
+    const void* limitAddr = gen->runtime->addressOfJitStackLimit();
+    masm.branchStackPtrRhs(Assembler::AboveOrEqual, AbsoluteAddress(limitAddr), ool->entry());
     masm.bind(ool->rejoin());
 }
 
 typedef bool (*DefVarFn)(JSContext*, HandlePropertyName, unsigned, HandleObject);
 static const VMFunction DefVarInfo = FunctionInfo<DefVarFn>(DefVar, "DefVar");
 
 void
 CodeGenerator::visitDefVar(LDefVar* lir)
@@ -12912,22 +12908,18 @@ CodeGenerator::visitInterruptCheck(LInte
 
         lir->setOolEntry(ool->entry());
         masm.bind(ool->rejoin());
         return;
     }
 
     OutOfLineCode* ool = oolCallVM(InterruptCheckInfo, lir, ArgList(), StoreNothing());
 
-    Register temp = ToRegister(lir->temp());
-
-    const void* contextAddr = gen->compartment->zone()->addressOfJSContext();
-    masm.loadPtr(AbsoluteAddress(contextAddr), temp);
-    masm.branch32(Assembler::NotEqual, Address(temp, offsetof(JSContext, interrupt_)),
-                  Imm32(0), ool->entry());
+    const void* interruptAddr = gen->runtime->addressOfInterrupt();
+    masm.branch32(Assembler::NotEqual, AbsoluteAddress(interruptAddr), Imm32(0), ool->entry());
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitWasmInterruptCheck(LWasmInterruptCheck* lir)
 {
     MOZ_ASSERT(gen->compilingWasm());
 
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -97,19 +97,31 @@ CompileRuntime::positiveInfinityValue()
 
 const WellKnownSymbols&
 CompileRuntime::wellKnownSymbols()
 {
     return *runtime()->wellKnownSymbols;
 }
 
 const void*
-CompileRuntime::addressOfActiveJSContext()
+CompileRuntime::mainContextPtr()
+{
+    return runtime()->mainContextFromAnyThread();
+}
+
+const void*
+CompileRuntime::addressOfJitStackLimit()
 {
-    return runtime()->addressOfActiveContext();
+    return runtime()->mainContextFromAnyThread()->addressOfJitStackLimit();
+}
+
+const void*
+CompileRuntime::addressOfInterrupt()
+{
+    return runtime()->mainContextFromAnyThread()->addressOfInterrupt();
 }
 
 #ifdef DEBUG
 bool
 CompileRuntime::isInsideNursery(gc::Cell* cell)
 {
     return UninlinedIsInsideNursery(cell);
 }
@@ -155,22 +167,16 @@ CompileZone::isAtomsZone()
 const void*
 CompileZone::addressOfIonBailAfter()
 {
     return zone()->group()->addressOfIonBailAfter();
 }
 #endif
 
 const void*
-CompileZone::addressOfJSContext()
-{
-    return zone()->group()->addressOfOwnerContext();
-}
-
-const void*
 CompileZone::addressOfNeedsIncrementalBarrier()
 {
     return zone()->addressOfNeedsIncrementalBarrier();
 }
 
 const void*
 CompileZone::addressOfFreeList(gc::AllocKind allocKind)
 {
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -41,17 +41,20 @@ class CompileRuntime
     bool profilingScripts();
 
     const JSAtomState& names();
     const PropertyName* emptyString();
     const StaticStrings& staticStrings();
     const Value& NaNValue();
     const Value& positiveInfinityValue();
     const WellKnownSymbols& wellKnownSymbols();
-    const void* addressOfActiveJSContext();
+
+    const void* mainContextPtr();
+    const void* addressOfJitStackLimit();
+    const void* addressOfInterrupt();
 
 #ifdef DEBUG
     bool isInsideNursery(gc::Cell* cell);
 #endif
 
     // DOM callbacks must be threadsafe (and will hopefully be removed soon).
     const DOMCallbacks* DOMcallbacks();
 
@@ -67,17 +70,16 @@ class CompileZone
 
     CompileRuntime* runtime();
     bool isAtomsZone();
 
 #ifdef DEBUG
     const void* addressOfIonBailAfter();
 #endif
 
-    const void* addressOfJSContext();
     const void* addressOfNeedsIncrementalBarrier();
     const void* addressOfFreeList(gc::AllocKind allocKind);
     const void* addressOfNurseryPosition();
     const void* addressOfStringNurseryPosition();
     const void* addressOfNurseryCurrentEnd();
     const void* addressOfStringNurseryCurrentEnd();
 
     bool nurseryExists();
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -300,17 +300,17 @@ ExecutableAllocator::reprotectAll(Protec
         reprotectPool(rt_, r.front(), protection);
 }
 
 /* static */ void
 ExecutableAllocator::reprotectPool(JSRuntime* rt, ExecutablePool* pool, ProtectionSetting protection)
 {
     // Don't race with reprotectAll called from the signal handler.
     MOZ_ASSERT(rt->jitRuntime()->preventBackedgePatching() ||
-               rt->activeContext()->handlingJitInterrupt());
+               rt->mainContextFromAnyThread()->handlingJitInterrupt());
 
     char* start = pool->m_allocation.pages;
     if (!ReprotectRegion(start, pool->m_freePtr - start, protection))
         MOZ_CRASH();
 }
 
 /* static */ void
 ExecutableAllocator::poisonCode(JSRuntime* rt, JitPoisonRangeVector& ranges)
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2939,22 +2939,20 @@ jit::InvalidateAll(FreeOp* fop, Zone* zo
     // The caller should previously have cancelled off thread compilation.
 #ifdef DEBUG
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         MOZ_ASSERT(!HasOffThreadIonCompile(comp));
 #endif
     if (zone->isAtomsZone())
         return;
     JSContext* cx = TlsContext.get();
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (JitActivationIterator iter(cx, target); !iter.done(); ++iter) {
-            if (iter->compartment()->zone() == zone) {
-                JitSpew(JitSpew_IonInvalidate, "Invalidating all frames for GC");
-                InvalidateActivation(fop, iter, true);
-            }
+    for (JitActivationIterator iter(cx); !iter.done(); ++iter) {
+        if (iter->compartment()->zone() == zone) {
+            JitSpew(JitSpew_IonInvalidate, "Invalidating all frames for GC");
+            InvalidateActivation(fop, iter, true);
         }
     }
 }
 
 
 void
 jit::Invalidate(TypeZone& types, FreeOp* fop,
                 const RecompileInfoVector& invalid, bool resetUses,
@@ -2987,28 +2985,19 @@ jit::Invalidate(TypeZone& types, FreeOp*
         numInvalidations++;
     }
 
     if (!numInvalidations) {
         JitSpew(JitSpew_IonInvalidate, " No IonScript invalidation.");
         return;
     }
 
-    // This method can be called both during GC and during the course of normal
-    // script execution. In the former case this class will already be on the
-    // stack, and in the latter case the invalidations will all be on the
-    // current thread's stack, but the assertion under ActivationIterator can't
-    // tell that this is a thread local use of the iterator.
-    JSRuntime::AutoProhibitActiveContextChange apacc(fop->runtime());
-
     JSContext* cx = TlsContext.get();
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (JitActivationIterator iter(cx, target); !iter.done(); ++iter)
-            InvalidateActivation(fop, iter, false);
-    }
+    for (JitActivationIterator iter(cx); !iter.done(); ++iter)
+        InvalidateActivation(fop, iter, false);
 
     // Drop the references added above. If a script was never active, its
     // IonScript will be immediately destroyed. Otherwise, it will be held live
     // until its last invalidated frame is destroyed.
     for (size_t i = 0; i < invalid.length(); i++) {
         CompilerOutput* co = invalid[i].compilerOutput(types);
         if (!co)
             continue;
@@ -3350,19 +3339,18 @@ jit::JitSupportsAtomics()
 
 // If you change these, please also change the comment in TempAllocator.
 /* static */ const size_t TempAllocator::BallastSize            = 16 * 1024;
 /* static */ const size_t TempAllocator::PreferredLifoChunkSize = 32 * 1024;
 
 static void
 RedirectIonBackedgesToInterruptCheck(JSContext* cx)
 {
-    // Jitcode may only be modified on the runtime's active thread.
-    if (cx != cx->runtime()->activeContext())
-        return;
+    // Jitcode may only be modified on the runtime's main thread.
+    MOZ_ASSERT(cx == cx->runtime()->mainContextFromAnyThread());
 
     // The faulting thread is suspended so we can access cx fields that can
     // normally only be accessed by the cx's active thread.
     AutoNoteSingleThreadedRegion anstr;
 
     Zone* zone = cx->zoneRaw();
     if (zone && !zone->isAtomsZone()) {
         jit::JitRuntime* jitRuntime = cx->runtime()->jitRuntime();
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1296,33 +1296,31 @@ TraceJitActivation(JSTracer* trc, JitAct
         } else {
             MOZ_ASSERT(frames.isWasm());
             frames.asWasm().instance()->trace(trc);
         }
     }
 }
 
 void
-TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
+TraceJitActivations(JSContext* cx, JSTracer* trc)
 {
-    for (JitActivationIterator activations(cx, target); !activations.done(); ++activations)
+    for (JitActivationIterator activations(cx); !activations.done(); ++activations)
         TraceJitActivation(trc, activations->asJit());
 }
 
 void
 UpdateJitActivationsForMinorGC(JSRuntime* rt)
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapMinorCollecting());
     JSContext* cx = TlsContext.get();
-    for (const CooperatingContext& target : rt->cooperatingContexts()) {
-        for (JitActivationIterator activations(cx, target); !activations.done(); ++activations) {
-            for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter) {
-                if (iter.frame().type() == JitFrame_IonJS)
-                    UpdateIonJSFrameForMinorGC(iter.frame());
-            }
+    for (JitActivationIterator activations(cx); !activations.done(); ++activations) {
+        for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter) {
+            if (iter.frame().type() == JitFrame_IonJS)
+                UpdateIonJSFrameForMinorGC(iter.frame());
         }
     }
 }
 
 void
 GetPcScript(JSContext* cx, JSScript** scriptRes, jsbytecode** pcRes)
 {
     JitSpew(JitSpew_IonSnapshots, "Recover PC & Script from the last frame.");
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -280,17 +280,17 @@ struct ResumeFromException
 
     BaselineBailoutInfo* bailoutInfo;
 };
 
 void HandleException(ResumeFromException* rfe);
 
 void EnsureBareExitFrame(JitActivation* act, JitFrameLayout* frame);
 
-void TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc);
+void TraceJitActivations(JSContext* cx, JSTracer* trc);
 
 void UpdateJitActivationsForMinorGC(JSRuntime* rt);
 
 static inline uint32_t
 EncodeFrameHeaderSize(size_t headerSize)
 {
     MOZ_ASSERT((headerSize % sizeof(uintptr_t)) == 0);
 
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -179,17 +179,17 @@ LIRGenerator::visitTableSwitch(MTableSwi
         tempInt = temp(LDefinition::GENERAL);
     }
     add(newLTableSwitch(index, tempInt, tableswitch));
 }
 
 void
 LIRGenerator::visitCheckOverRecursed(MCheckOverRecursed* ins)
 {
-    LCheckOverRecursed* lir = new(alloc()) LCheckOverRecursed(temp());
+    LCheckOverRecursed* lir = new(alloc()) LCheckOverRecursed();
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
 LIRGenerator::visitDefVar(MDefVar* ins)
 {
     LDefVar* lir = new(alloc()) LDefVar(useRegisterAtStart(ins->environmentChain()));
@@ -2728,17 +2728,17 @@ LIRGenerator::visitHomeObjectSuperBase(M
     auto lir = new(alloc()) LHomeObjectSuperBase(useRegister(ins->homeObject()));
     define(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
 LIRGenerator::visitInterruptCheck(MInterruptCheck* ins)
 {
-    LInstruction* lir = new(alloc()) LInterruptCheck(temp());
+    LInstruction* lir = new(alloc()) LInterruptCheck();
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
 LIRGenerator::visitWasmInterruptCheck(MWasmInterruptCheck* ins)
 {
     auto* lir = new(alloc()) LWasmInterruptCheck(useRegisterAtStart(ins->tlsPtr()));
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1773,29 +1773,17 @@ MacroAssembler::typeOfObject(Register ob
 
     jump(isCallable);
 }
 
 void
 MacroAssembler::loadJSContext(Register dest)
 {
     JitContext* jcx = GetJitContext();
-    CompileCompartment* compartment = jcx->compartment;
-    if (compartment->zone()->isAtomsZone()) {
-        // If we are in the atoms zone then we are generating a runtime wide
-        // trampoline which can run in any zone. Load the context which is
-        // currently running using cooperative scheduling in the runtime.
-        // (This will need to be fixed when we have preemptive scheduling,
-        // bug 1323066).
-        loadPtr(AbsoluteAddress(jcx->runtime->addressOfActiveJSContext()), dest);
-    } else {
-        // If we are in a specific zone then the current context will be stored
-        // in the containing zone group.
-        loadPtr(AbsoluteAddress(compartment->zone()->addressOfJSContext()), dest);
-    }
+    movePtr(ImmPtr(jcx->runtime->mainContextPtr()), dest);
 }
 
 void
 MacroAssembler::guardGroupHasUnanalyzedNewScript(Register group, Register scratch, Label* fail)
 {
     Label noNewScript;
     load32(Address(group, ObjectGroup::offsetOfFlags()), scratch);
     and32(Imm32(OBJECT_FLAG_ADDENDUM_MASK), scratch);
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -2113,16 +2113,28 @@ MacroAssemblerCompat::branchStackPtrRhs(
     const ARMRegister scratch = temps.AcquireX();
     Ldr(scratch, toMemOperand(lhs));
     // Cmp disallows SP as the rhs, so flip the operands and invert the
     // condition.
     Cmp(GetStackPointer64(), scratch);
     B(label, Assembler::InvertCondition(cond));
 }
 
+void
+MacroAssemblerCompat::branchStackPtrRhs(Condition cond, AbsoluteAddress lhs, Label* label)
+{
+    vixl::UseScratchRegisterScope temps(this);
+    const ARMRegister scratch = temps.AcquireX();
+    movePtr(ImmPtr(lhs.addr), scratch.asUnsized());
+    // Cmp disallows SP as the rhs, so flip the operands and invert the
+    // condition.
+    Cmp(GetStackPointer64(), scratch);
+    B(label, Assembler::InvertCondition(cond));
+}
+
 // If source is a double, load into dest.
 // If source is int32, convert to double and store in dest.
 // Else, branch to failure.
 void
 MacroAssemblerCompat::ensureDouble(const ValueOperand& source, FloatRegister dest, Label* failure)
 {
     Label isDouble, done;
 
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -981,16 +981,17 @@ class MacroAssemblerCompat : public vixl
 
     inline void loadStackPtr(const Address& src);
     inline void storeStackPtr(const Address& dest);
 
     // StackPointer testing functions.
     inline void branchTestStackPtr(Condition cond, Imm32 rhs, Label* label);
     inline void branchStackPtr(Condition cond, Register rhs, Label* label);
     inline void branchStackPtrRhs(Condition cond, Address lhs, Label* label);
+    inline void branchStackPtrRhs(Condition cond, AbsoluteAddress lhs, Label* label);
 
     void testPtr(Register lhs, Register rhs) {
         Tst(ARMRegister(lhs, 64), Operand(ARMRegister(rhs, 64)));
     }
     void test32(Register lhs, Register rhs) {
         Tst(ARMRegister(lhs, 32), Operand(ARMRegister(rhs, 32)));
     }
     void test32(const Address& addr, Imm32 imm) {
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1476,30 +1476,24 @@ class LInitPropGetterSetter : public LCa
         return getOperand(1);
     }
 
     MInitPropGetterSetter* mir() const {
         return mir_->toInitPropGetterSetter();
     }
 };
 
-class LCheckOverRecursed : public LInstructionHelper<0, 0, 1>
+class LCheckOverRecursed : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(CheckOverRecursed)
 
-    explicit LCheckOverRecursed(const LDefinition& temp)
-      : LInstructionHelper(classOpcode)
-    {
-        setTemp(0, temp);
-    }
-
-    const LDefinition* temp() {
-        return getTemp(0);
-    }
+    LCheckOverRecursed()
+      : LInstructionHelper(classOpcode)
+    {}
 
     MCheckOverRecursed* mir() const {
         return mir_->toCheckOverRecursed();
     }
 };
 
 class LWasmTrap : public LInstructionHelper<0, 0, 0>
 {
@@ -1612,60 +1606,53 @@ class LRotateI64 : public details::Rotat
     static const size_t Input = 0;
     static const size_t Count = INT64_PIECES;
 
     const LInt64Allocation input() { return getInt64Operand(Input); }
     const LDefinition* temp() { return getTemp(0); }
     LAllocation* count() { return getOperand(Count); }
 };
 
-class LInterruptCheck : public LInstructionHelper<0, 0, 1>
+class LInterruptCheck : public LInstructionHelper<0, 0, 0>
 {
     Label* oolEntry_;
 
     // Whether this is an implicit interrupt check. Implicit interrupt checks
     // use a patchable backedge and signal handlers instead of an explicit
     // cx->interrupt check.
     bool implicit_;
 
   public:
     LIR_HEADER(InterruptCheck)
 
-    explicit LInterruptCheck(const LDefinition& temp)
+    LInterruptCheck()
       : LInstructionHelper(classOpcode),
         oolEntry_(nullptr),
         implicit_(false)
-    {
-        setTemp(0, temp);
-    }
+    {}
 
     Label* oolEntry() {
         MOZ_ASSERT(implicit_);
         return oolEntry_;
     }
 
     void setOolEntry(Label* oolEntry) {
         MOZ_ASSERT(implicit_);
         oolEntry_ = oolEntry;
     }
     MInterruptCheck* mir() const {
         return mir_->toInterruptCheck();
     }
 
     void setImplicit() {
         implicit_ = true;
-        setTemp(0, LDefinition::BogusTemp());
     }
     bool implicit() const {
         return implicit_;
     }
-
-    const LDefinition* temp() {
-        return getTemp(0);
-    }
 };
 
 class LWasmInterruptCheck : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(WasmInterruptCheck)
 
     explicit LWasmInterruptCheck(const LAllocation& tlsData)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -481,31 +481,28 @@ JS_NewContext(uint32_t maxbytes, uint32_
 
     return NewContext(maxbytes, maxNurseryBytes, parentRuntime);
 }
 
 JS_PUBLIC_API(JSContext*)
 JS_NewCooperativeContext(JSContext* siblingContext)
 {
     MOZ_CRASH("Cooperative scheduling is unsupported");
-    return NewCooperativeContext(siblingContext);
 }
 
 JS_PUBLIC_API(void)
 JS_YieldCooperativeContext(JSContext* cx)
 {
     MOZ_CRASH("Cooperative scheduling is unsupported");
-    YieldCooperativeContext(cx);
 }
 
 JS_PUBLIC_API(void)
 JS_ResumeCooperativeContext(JSContext* cx)
 {
     MOZ_CRASH("Cooperative scheduling is unsupported");
-    ResumeCooperativeContext(cx);
 }
 
 JS_PUBLIC_API(void)
 JS_DestroyContext(JSContext* cx)
 {
     DestroyContext(cx);
 }
 
@@ -577,25 +574,16 @@ JS_GetParentRuntime(JSContext* cx)
 }
 
 JS_PUBLIC_API(JSRuntime*)
 JS_GetRuntime(JSContext* cx)
 {
     return cx->runtime();
 }
 
-JS_PUBLIC_API(void)
-JS::SetSingleThreadedExecutionCallbacks(JSContext* cx,
-                                        BeginSingleThreadedExecutionCallback begin,
-                                        EndSingleThreadedExecutionCallback end)
-{
-    cx->runtime()->beginSingleThreadedExecutionCallback = begin;
-    cx->runtime()->endSingleThreadedExecutionCallback = end;
-}
-
 JS_PUBLIC_API(JS::ContextOptions&)
 JS::ContextOptionsRef(JSContext* cx)
 {
     return cx->options();
 }
 
 JS_PUBLIC_API(bool)
 JS::InitSelfHostedCode(JSContext* cx)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -860,33 +860,16 @@ JS_ResumeCooperativeContext(JSContext* c
 
 // Create a new context on this thread for cooperative multithreading in the
 // same runtime as siblingContext. Called on a runtime (as indicated by
 // siblingContet) which has no active context, on success the new context will
 // become the runtime's active context.
 extern JS_PUBLIC_API(JSContext*)
 JS_NewCooperativeContext(JSContext* siblingContext);
 
-namespace JS {
-
-// Class to relinquish exclusive access to all zone groups in use by this
-// thread. This allows other cooperative threads to enter the zone groups
-// and modify their contents.
-struct AutoRelinquishZoneGroups
-{
-    explicit AutoRelinquishZoneGroups(JSContext* cx);
-    ~AutoRelinquishZoneGroups();
-
-  private:
-    JSContext* cx;
-    mozilla::Vector<void*> enterList;
-};
-
-} // namespace JS
-
 // Destroy a context allocated with JS_NewContext or JS_NewCooperativeContext.
 // The context must be the current active context in the runtime, and after
 // this call the runtime will have no active context.
 extern JS_PUBLIC_API(void)
 JS_DestroyContext(JSContext* cx);
 
 JS_PUBLIC_API(void*)
 JS_GetContextPrivate(JSContext* cx);
@@ -904,41 +887,16 @@ extern JS_PUBLIC_API(void)
 JS_BeginRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_EndRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetFutexCanWait(JSContext* cx);
 
-namespace JS {
-
-// Single threaded execution callbacks are used to notify API clients that a
-// feature is in use on a context's runtime that is not yet compatible with
-// cooperatively multithreaded execution.
-//
-// Between a call to BeginSingleThreadedExecutionCallback and a corresponding
-// call to EndSingleThreadedExecutionCallback, only one thread at a time may
-// enter compartments in the runtime. The begin callback may yield as necessary
-// to permit other threads to finish up what they're doing, while the end
-// callback may not yield or otherwise operate on the runtime (it may be called
-// during GC).
-//
-// These callbacks may be left unspecified for runtimes which only ever have a
-// single context.
-typedef void (*BeginSingleThreadedExecutionCallback)(JSContext* cx);
-typedef void (*EndSingleThreadedExecutionCallback)(JSContext* cx);
-
-extern JS_PUBLIC_API(void)
-SetSingleThreadedExecutionCallbacks(JSContext* cx,
-                                    BeginSingleThreadedExecutionCallback begin,
-                                    EndSingleThreadedExecutionCallback end);
-
-} // namespace JS
-
 namespace js {
 
 void
 AssertHeapIsIdle();
 
 } /* namespace js */
 
 class MOZ_RAII JSAutoRequest
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1265,17 +1265,17 @@ js::GetAnyCompartmentInZone(JS::Zone* zo
     CompartmentsInZoneIter comp(zone);
     MOZ_ASSERT(!comp.done());
     return comp.get();
 }
 
 void
 JS::ObjectPtr::finalize(JSRuntime* rt)
 {
-    if (IsIncrementalBarrierNeeded(rt->activeContextFromOwnThread()))
+    if (IsIncrementalBarrierNeeded(rt->mainContextFromOwnThread()))
         IncrementalPreWriteBarrier(value);
     value = nullptr;
 }
 
 void
 JS::ObjectPtr::finalize(JSContext* cx)
 {
     finalize(cx->runtime());
@@ -1539,18 +1539,17 @@ JS_FRIEND_API(void)
 js::SetCooperativeYieldCallback(JSContext* cx, YieldCallback callback)
 {
     cx->setYieldCallback(callback);
 }
 
 JS_FRIEND_API(bool)
 js::SystemZoneAvailable(JSContext* cx)
 {
-    CooperatingContext& owner = cx->runtime()->gc.systemZoneGroup->ownerContext();
-    return owner.context() == nullptr;
+    return true;
 }
 
 static LogCtorDtor sLogCtor = nullptr;
 static LogCtorDtor sLogDtor = nullptr;
 
 JS_FRIEND_API(void)
 js::SetLogCtorDtorFunctions(LogCtorDtor ctor, LogCtorDtor dtor)
 {
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -88,18 +88,16 @@ typedef JSConstScalarSpec<double> JSCons
 typedef JSConstScalarSpec<int32_t> JSConstIntegerSpec;
 
 namespace js {
 namespace gc {
 class AutoTraceSession;
 class StoreBuffer;
 } // namespace gc
 
-class CooperatingContext;
-
 inline JSCompartment* GetContextCompartment(const JSContext* cx);
 inline JS::Zone* GetContextZone(const JSContext* cx);
 
 // Whether the current thread is permitted access to any part of the specified
 // runtime or zone.
 JS_FRIEND_API(bool)
 CurrentThreadCanAccessRuntime(const JSRuntime* rt);
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5459,35 +5459,22 @@ HasCopyOnWriteElements(JSContext* cx, un
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(args.get(0).isObject() &&
                            args[0].toObject().isNative() &&
                            args[0].toObject().as<NativeObject>().denseElementsAreCopyOnWrite());
     return true;
 }
 
-// Set the profiling stack for each cooperating context in a runtime.
-static bool
-EnsureAllContextProfilingStacks(JSContext* cx)
-{
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        ShellContext* sc = GetShellContext(target.context());
-        if (!EnsureGeckoProfilingStackInstalled(target.context(), sc))
-            return false;
-    }
-
-    return true;
-}
-
 static bool
 EnableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!EnsureAllContextProfilingStacks(cx))
+    if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx)))
         return false;
 
     cx->runtime()->geckoProfiler().enableSlowAssertions(false);
     cx->runtime()->geckoProfiler().enable(true);
 
     args.rval().setUndefined();
     return true;
 }
@@ -5504,17 +5491,17 @@ EnableGeckoProfilingWithSlowAssertions(J
         if (cx->runtime()->geckoProfiler().slowAssertionsEnabled())
             return true;
 
         // Slow assertions are off.  Disable profiling before re-enabling
         // with slow assertions on.
         cx->runtime()->geckoProfiler().enable(false);
     }
 
-    if (!EnsureAllContextProfilingStacks(cx))
+    if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx)))
         return false;
 
     cx->runtime()->geckoProfiler().enableSlowAssertions(true);
     cx->runtime()->geckoProfiler().enable(true);
 
     return true;
 }
 
--- a/js/src/threading/ProtectedData.cpp
+++ b/js/src/threading/ProtectedData.cpp
@@ -74,17 +74,17 @@ void
 CheckZoneGroup<Helper>::check() const
 {
     if (OnHelperThread<Helper>())
         return;
 
     JSContext* cx = TlsContext.get();
     if (group) {
         if (group->usedByHelperThread()) {
-            MOZ_ASSERT(group->ownedByCurrentThread());
+            MOZ_ASSERT(group->ownedByCurrentHelperThread());
         } else {
             // This check is disabled on windows for the same reason as in
             // CheckActiveThread.
 #ifndef XP_WIN
             // In a cooperatively scheduled runtime the active thread is
             // permitted access to all zone groups --- even those it has not
             // entered --- for GC and similar purposes. Since all other
             // cooperative threads are suspended, these accesses are threadsafe
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -713,19 +713,19 @@ Debugger::~Debugger()
     /*
      * We don't have to worry about locking here since Debugger is not
      * background finalized.
      */
     JSContext* cx = TlsContext.get();
     if (onNewGlobalObjectWatchersLink.mPrev ||
         onNewGlobalObjectWatchersLink.mNext ||
         cx->runtime()->onNewGlobalObjectWatchers().begin() == JSRuntime::WatchersList::Iterator(this))
+    {
         cx->runtime()->onNewGlobalObjectWatchers().remove(this);
-
-    cx->runtime()->endSingleThreadedExecution(cx);
+    }
 }
 
 bool
 Debugger::init(JSContext* cx)
 {
     if (!debuggees.init() ||
         !debuggeeZones.init() ||
         !frames.init() ||
@@ -2606,34 +2606,32 @@ UpdateExecutionObservabilityOfScriptsInZ
         }
     }
 
     // Code below this point must be infallible to ensure the active bit of
     // BaselineScripts is in a consistent state.
     //
     // Mark active baseline scripts in the observable set so that they don't
     // get discarded. They will be recompiled.
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (JitActivationIterator actIter(cx, target); !actIter.done(); ++actIter) {
-            if (actIter->compartment()->zone() != zone)
-                continue;
-
-            for (OnlyJSJitFrameIter iter(actIter); !iter.done(); ++iter) {
-                const jit::JSJitFrameIter& frame = iter.frame();
-                switch (frame.type()) {
-                  case JitFrame_BaselineJS:
-                    MarkBaselineScriptActiveIfObservable(frame.script(), obs);
-                    break;
-                  case JitFrame_IonJS:
-                    MarkBaselineScriptActiveIfObservable(frame.script(), obs);
-                    for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more(); ++inlineIter)
-                        MarkBaselineScriptActiveIfObservable(inlineIter.script(), obs);
-                    break;
-                  default:;
-                }
+    for (JitActivationIterator actIter(cx); !actIter.done(); ++actIter) {
+        if (actIter->compartment()->zone() != zone)
+            continue;
+
+        for (OnlyJSJitFrameIter iter(actIter); !iter.done(); ++iter) {
+            const jit::JSJitFrameIter& frame = iter.frame();
+            switch (frame.type()) {
+              case JitFrame_BaselineJS:
+                MarkBaselineScriptActiveIfObservable(frame.script(), obs);
+                break;
+              case JitFrame_IonJS:
+                MarkBaselineScriptActiveIfObservable(frame.script(), obs);
+                for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more(); ++inlineIter)
+                    MarkBaselineScriptActiveIfObservable(inlineIter.script(), obs);
+                break;
+              default:;
             }
         }
     }
 
     // Iterate through the scripts again and finish discarding
     // BaselineScripts. This must be done as a separate phase as we can only
     // discard the BaselineScript on scripts that have no IonScript.
     for (size_t i = 0; i < scripts.length(); i++) {
@@ -3934,34 +3932,21 @@ Debugger::construct(JSContext* cx, unsig
     RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(cx, &Debugger::class_, proto,
                                                              TenuredObject));
     if (!obj)
         return false;
     for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
         obj->setReservedSlot(slot, proto->getReservedSlot(slot));
     obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
 
-    // Debuggers currently require single threaded execution. A debugger may be
-    // used to debug content in other zone groups, and may be used to observe
-    // all activity in the runtime via hooks like OnNewGlobalObject.
-    if (!cx->runtime()->beginSingleThreadedExecution(cx)) {
-        JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in Debugger");
-        return false;
-    }
-
     Debugger* debugger;
     {
         /* Construct the underlying C++ object. */
         auto dbg = cx->make_unique<Debugger>(cx, obj.get());
-        if (!dbg) {
-            JS::AutoSuppressGCAnalysis nogc; // Suppress warning about |dbg|.
-            cx->runtime()->endSingleThreadedExecution(cx);
-            return false;
-        }
-        if (!dbg->init(cx))
+        if (!dbg || !dbg->init(cx))
             return false;
 
         debugger = dbg.release();
         obj->setPrivate(debugger); // owns the released pointer
     }
 
     /* Add the initial debuggees, if any. */
     for (unsigned i = 0; i < args.length(); i++) {
--- a/js/src/vm/GeckoProfiler-inl.h
+++ b/js/src/vm/GeckoProfiler-inl.h
@@ -37,17 +37,16 @@ class MOZ_RAII AutoSuppressProfilerSampl
   public:
     explicit AutoSuppressProfilerSampling(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     ~AutoSuppressProfilerSampling();
 
   private:
     JSContext* cx_;
     bool previouslyEnabled_;
-    JSRuntime::AutoProhibitActiveContextChange prohibitContextChange_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 MOZ_ALWAYS_INLINE
 GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSContext* cx,
                                                    JSScript* script
                                                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : profiler_(&cx->geckoProfiler())
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -83,23 +83,18 @@ GetTopProfilingJitFrame(Activation* act)
     jit::JSJitProfilingFrameIterator jitIter((jit::CommonFrameLayout*) iter.frame().fp());
     MOZ_ASSERT(!jitIter.done());
     return jitIter.fp();
 }
 
 void
 GeckoProfilerRuntime::enable(bool enabled)
 {
-#ifdef DEBUG
-    // All cooperating contexts must have profile stacks installed before the
-    // profiler can be enabled. Cooperating threads created while the profiler
-    // is enabled must have stacks set before they execute any JS.
-    for (const CooperatingContext& target : rt->cooperatingContexts())
-        MOZ_ASSERT(target.context()->geckoProfiler().installed());
-#endif
+    JSContext* cx = rt->mainContextFromAnyThread();
+    MOZ_ASSERT(cx->geckoProfiler().installed());
 
     if (enabled_ == enabled)
         return;
 
     /*
      * Ensure all future generated code will be instrumented, or that all
      * currently instrumented code is discarded
      */
@@ -107,58 +102,54 @@ GeckoProfilerRuntime::enable(bool enable
 
     // This function is called when the Gecko profiler makes a new Sampler
     // (and thus, a new circular buffer). Set all current entries in the
     // JitcodeGlobalTable as expired and reset the buffer range start.
     if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable())
         rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired();
     rt->setProfilerSampleBufferRangeStart(0);
 
-    // Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true.
-    for (const CooperatingContext& target : rt->cooperatingContexts()) {
-        if (target.context()->jitActivation) {
-            target.context()->jitActivation->setLastProfilingFrame(nullptr);
-            target.context()->jitActivation->setLastProfilingCallSite(nullptr);
-        }
+    // Ensure that lastProfilingFrame is null for the main thread.
+    if (cx->jitActivation) {
+        cx->jitActivation->setLastProfilingFrame(nullptr);
+        cx->jitActivation->setLastProfilingCallSite(nullptr);
     }
 
     enabled_ = enabled;
 
     /* Toggle Gecko Profiler-related jumps on baseline jitcode.
      * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not
      * jitcode for scripts with active frames on the stack.  These scripts need to have
      * their profiler state toggled so they behave properly.
      */
     jit::ToggleBaselineProfiling(rt, enabled);
 
     /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on
      * stack.
      */
-    for (const CooperatingContext& target : rt->cooperatingContexts()) {
-        if (target.context()->jitActivation) {
-            // Walk through all activations, and set their lastProfilingFrame appropriately.
-            if (enabled) {
-                Activation* act = target.context()->activation();
-                void* lastProfilingFrame = GetTopProfilingJitFrame(act);
+    if (cx->jitActivation) {
+        // Walk through all activations, and set their lastProfilingFrame appropriately.
+        if (enabled) {
+            Activation* act = cx->activation();
+            void* lastProfilingFrame = GetTopProfilingJitFrame(act);
 
-                jit::JitActivation* jitActivation = target.context()->jitActivation;
-                while (jitActivation) {
-                    jitActivation->setLastProfilingFrame(lastProfilingFrame);
-                    jitActivation->setLastProfilingCallSite(nullptr);
+            jit::JitActivation* jitActivation = cx->jitActivation;
+            while (jitActivation) {
+                jitActivation->setLastProfilingFrame(lastProfilingFrame);
+                jitActivation->setLastProfilingCallSite(nullptr);
 
-                    jitActivation = jitActivation->prevJitActivation();
-                    lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
-                }
-            } else {
-                jit::JitActivation* jitActivation = target.context()->jitActivation;
-                while (jitActivation) {
-                    jitActivation->setLastProfilingFrame(nullptr);
-                    jitActivation->setLastProfilingCallSite(nullptr);
-                    jitActivation = jitActivation->prevJitActivation();
-                }
+                jitActivation = jitActivation->prevJitActivation();
+                lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
+            }
+        } else {
+            jit::JitActivation* jitActivation = cx->jitActivation;
+            while (jitActivation) {
+                jitActivation->setLastProfilingFrame(nullptr);
+                jitActivation->setLastProfilingCallSite(nullptr);
+                jitActivation = jitActivation->prevJitActivation();
             }
         }
     }
 
     // WebAssembly code does not need to be released, but profiling string
     // labels have to be generated so that they are available during async
     // profiling stack iteration.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
@@ -426,22 +417,18 @@ ProfileEntry::script() const
     MOZ_ASSERT(isJs());
     auto script = reinterpret_cast<JSScript*>(spOrScript.operator void*());
     if (!script)
         return nullptr;
 
     // If profiling is supressed then we can't trust the script pointers to be
     // valid as they could be in the process of being moved by a compacting GC
     // (although it's still OK to get the runtime from them).
-    //
-    // We only need to check the active context here, as
-    // AutoSuppressProfilerSampling prohibits the runtime's active context from
-    // being changed while it exists.
-    JSContext* cx = script->runtimeFromAnyThread()->activeContext();
-    if (!cx || !cx->isProfilerSamplingEnabled())
+    JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread();
+    if (!cx->isProfilerSamplingEnabled())
         return nullptr;
 
     MOZ_ASSERT(!IsForwarded(script));
     return script;
 }
 
 JS_FRIEND_API(jsbytecode*)
 ProfileEntry::pc() const
@@ -485,18 +472,17 @@ js::RegisterContextProfilingEventMarker(
 {
     MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
     cx->runtime()->geckoProfiler().setEventMarker(fn);
 }
 
 AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx
                                                            MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx_(cx),
-    previouslyEnabled_(cx->isProfilerSamplingEnabled()),
-    prohibitContextChange_(cx->runtime())
+    previouslyEnabled_(cx->isProfilerSamplingEnabled())
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     if (previouslyEnabled_)
         cx_->disableProfilerSampling();
 }
 
 AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling()
 {
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -2,16 +2,17 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/HelperThreads.h"
 
 #include "mozilla/Maybe.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/Unused.h"
 
 #include "builtin/Promise.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "jit/IonBuilder.h"
 #include "js/Utility.h"
 #include "threading/CpuCount.h"
@@ -1824,29 +1825,26 @@ HelperThread::handleIonWorkload(AutoLock
         jit::JitContext jctx(jit::CompileRuntime::get(rt),
                              jit::CompileCompartment::get(builder->script()->compartment()),
                              &builder->alloc());
         builder->setBackgroundCodegen(jit::CompileBackEnd(builder));
     }
 
     FinishOffThreadIonCompile(builder, locked);
 
-    // Ping any thread currently operating on the compiled script's zone group
-    // so that the compiled code can be incorporated at the next interrupt
-    // callback. Don't interrupt Ion code for this, as this incorporation can
-    // be delayed indefinitely without affecting performance as long as the
-    // active thread is actually executing Ion code.
+    // Ping the main thread so that the compiled code can be incorporated at the
+    // next interrupt callback. Don't interrupt Ion code for this, as this
+    // incorporation can be delayed indefinitely without affecting performance
+    // as long as the main thread is actually executing Ion code.
     //
     // This must happen before the current task is reset. DestroyContext
     // cancels in progress Ion compilations before destroying its target
     // context, and after we reset the current task we are no longer considered
     // to be Ion compiling.
-    JSContext* target = builder->script()->zoneFromAnyThread()->group()->ownerContext().context();
-    if (target)
-        target->requestInterrupt(JSContext::RequestInterruptCanWait);
+    rt->mainContextFromAnyThread()->requestInterrupt(JSContext::RequestInterruptCanWait);
 
     currentTask.reset();
 
     // Notify the active thread in case it is waiting for the compilation to finish.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
 }
 
 void
@@ -1915,16 +1913,23 @@ HelperThread::handleParseWorkload(AutoLo
     currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());
     ParseTask* task = parseTask();
 
     {
         AutoUnlockHelperThreadState unlock(locked);
         AutoSetContextRuntime ascr(task->parseGlobal->runtimeFromAnyThread());
 
         JSContext* cx = TlsContext.get();
+
+        ZoneGroup* zoneGroup = task->parseGlobal->zoneFromAnyThread()->group();
+        zoneGroup->setHelperThreadOwnerContext(cx);
+        auto resetOwnerContext = mozilla::MakeScopeExit([&] {
+            zoneGroup->setHelperThreadOwnerContext(nullptr);
+        });
+
         AutoCompartment ac(cx, task->parseGlobal);
 
         task->parse(cx);
 
         cx->frontendCollectionPool().purge();
     }
 
     // The callback is invoked while we are still off thread.
--- a/js/src/vm/JSCompartment.cpp
+++ b/js/src/vm/JSCompartment.cpp
@@ -53,17 +53,16 @@ JSCompartment::JSCompartment(Zone* zone,
     marked(true),
     warnedAboutExprClosure(false),
     warnedAboutStringGenericsMethods(0),
 #ifdef DEBUG
     firedOnNewGlobalObject(false),
 #endif
     global_(nullptr),
     enterCompartmentDepth(0),
-    globalHolds(0),
     performanceMonitoring(runtime_),
     data(nullptr),
     realmData(nullptr),
     allocationMetadataBuilder(nullptr),
     lastAnimationTime(0),
     regExps(),
     arraySpeciesLookup(),
     globalWriteBarriered(0),
--- a/js/src/vm/JSCompartment.h
+++ b/js/src/vm/JSCompartment.h
@@ -629,37 +629,29 @@ struct JSCompartment
     void mark() { marked = true; }
 
   private:
     friend struct JSRuntime;
     friend struct JSContext;
     js::ReadBarrieredGlobalObject global_;
 
     unsigned                     enterCompartmentDepth;
-    unsigned                     globalHolds;
 
   public:
     js::PerformanceGroupHolder performanceMonitoring;
 
     void enter() {
         enterCompartmentDepth++;
     }
     void leave() {
         enterCompartmentDepth--;
     }
     bool hasBeenEntered() const { return !!enterCompartmentDepth; }
 
-    void holdGlobal() {
-        globalHolds++;
-    }
-    void releaseGlobal() {
-        MOZ_ASSERT(globalHolds > 0);
-        globalHolds--;
-    }
-    bool shouldTraceGlobal() const { return globalHolds > 0 || hasBeenEntered(); }
+    bool shouldTraceGlobal() const { return hasBeenEntered(); }
 
     JS::Zone* zone() { return zone_; }
     const JS::Zone* zone() const { return zone_; }
 
     const JS::CompartmentCreationOptions& creationOptions() const { return creationOptions_; }
     JS::CompartmentBehaviors& behaviors() { return behaviors_; }
     const JS::CompartmentBehaviors& behaviors() const { return behaviors_; }
 
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -474,19 +474,16 @@ JSContext::runningWithTrustedPrincipals(
 }
 
 inline void
 JSContext::enterNonAtomsCompartment(JSCompartment* c)
 {
     enterCompartmentDepth_++;
 
     MOZ_ASSERT(!c->zone()->isAtomsZone());
-    c->holdGlobal();
-    enterZoneGroup(c->zone()->group());
-    c->releaseGlobal();
 
     c->enter();
     setCompartment(c, nullptr);
 }
 
 inline void
 JSContext::enterAtomsCompartment(JSCompartment* c,
                                  const js::AutoLockForExclusiveAccess& lock)
@@ -521,21 +518,18 @@ JSContext::leaveCompartment(
 {
     MOZ_ASSERT(hasEnteredCompartment());
     enterCompartmentDepth_--;
 
     // Only call leave() after we've setCompartment()-ed away from the current
     // compartment.
     JSCompartment* startingCompartment = compartment_;
     setCompartment(oldCompartment, maybeLock);
-    if (startingCompartment) {
+    if (startingCompartment)
         startingCompartment->leave();
-        if (!startingCompartment->zone()->isAtomsZone())
-            leaveZoneGroup(startingCompartment->zone()->group());
-    }
 }
 
 inline void
 JSContext::setCompartment(JSCompartment* comp,
                           const js::AutoLockForExclusiveAccess* maybeLock /* = nullptr */)
 {
     // Only one thread can be in the atoms compartment at a time.
     MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp), maybeLock != nullptr);
@@ -546,39 +540,25 @@ JSContext::setCompartment(JSCompartment*
     MOZ_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp),
                   !comp->zone()->isAtomsZone());
 
     // Both the current and the new compartment should be properly marked as
     // entered at this point.
     MOZ_ASSERT_IF(compartment_, compartment_->hasBeenEntered());
     MOZ_ASSERT_IF(comp, comp->hasBeenEntered());
 
-    // This context must have exclusive access to the zone's group.
+    // This thread must have exclusive access to the zone.
     MOZ_ASSERT_IF(comp && !comp->zone()->isAtomsZone(),
-                  comp->zone()->group()->ownedByCurrentThread());
+                  CurrentThreadCanAccessZone(comp->zone()));
 
     compartment_ = comp;
     zone_ = comp ? comp->zone() : nullptr;
     arenas_ = zone_ ? &zone_->arenas : nullptr;
 }
 
-inline void
-JSContext::enterZoneGroup(js::ZoneGroup* group)
-{
-    MOZ_ASSERT(this == js::TlsContext.get());
-    group->enter(this);
-}
-
-inline void
-JSContext::leaveZoneGroup(js::ZoneGroup* group)
-{
-    MOZ_ASSERT(this == js::TlsContext.get());
-    group->leave();
-}
-
 inline JSScript*
 JSContext::currentScript(jsbytecode** ppc,
                          MaybeAllowCrossCompartment allowCrossCompartment) const
 {
     if (ppc)
         *ppc = nullptr;
 
     js::Activation* act = activation();
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -173,49 +173,16 @@ js::NewContext(uint32_t maxBytes, uint32
         js_delete(cx);
         js_delete(runtime);
         return nullptr;
     }
 
     return cx;
 }
 
-JSContext*
-js::NewCooperativeContext(JSContext* siblingContext)
-{
-    MOZ_RELEASE_ASSERT(!TlsContext.get());
-
-    JSRuntime* runtime = siblingContext->runtime();
-
-    JSContext* cx = js_new<JSContext>(runtime, JS::ContextOptions());
-    if (!cx || !cx->init(ContextKind::Cooperative)) {
-        js_delete(cx);
-        return nullptr;
-    }
-
-    runtime->setNewbornActiveContext(cx);
-    return cx;
-}
-
-void
-js::YieldCooperativeContext(JSContext* cx)
-{
-    MOZ_ASSERT(cx == TlsContext.get());
-    MOZ_ASSERT(cx->runtime()->activeContext() == cx);
-    cx->runtime()->setActiveContext(nullptr);
-}
-
-void
-js::ResumeCooperativeContext(JSContext* cx)
-{
-    MOZ_ASSERT(cx == TlsContext.get());
-    MOZ_ASSERT(cx->runtime()->activeContext() == nullptr);
-    cx->runtime()->setActiveContext(cx);
-}
-
 static void
 FreeJobQueueHandling(JSContext* cx)
 {
     if (!cx->jobQueue)
         return;
 
     cx->jobQueue->reset();
     FreeOp* fop = cx->defaultFreeOp();
@@ -238,39 +205,24 @@ js::DestroyContext(JSContext* cx)
     // Cancel all off thread Ion compiles before destroying a cooperative
     // context. Completed Ion compiles may try to interrupt arbitrary
     // cooperative contexts which they have read off the owner context of a
     // zone group. See HelperThread::handleIonWorkload.
     CancelOffThreadIonCompile(cx->runtime());
 
     FreeJobQueueHandling(cx);
 
-    if (cx->runtime()->cooperatingContexts().length() == 1) {
-        // Flush promise tasks executing in helper threads early, before any parts
-        // of the JSRuntime that might be visible to helper threads are torn down.
-        cx->runtime()->offThreadPromiseState.ref().shutdown(cx);
+    // Flush promise tasks executing in helper threads early, before any parts
+    // of the JSRuntime that might be visible to helper threads are torn down.
+    cx->runtime()->offThreadPromiseState.ref().shutdown(cx);
 
-        // Destroy the runtime along with its last context.
-        cx->runtime()->destroyRuntime();
-        js_delete(cx->runtime());
-        js_delete_poison(cx);
-    } else {
-        DebugOnly<bool> found = false;
-        for (size_t i = 0; i < cx->runtime()->cooperatingContexts().length(); i++) {
-            CooperatingContext& target = cx->runtime()->cooperatingContexts()[i];
-            if (cx == target.context()) {
-                cx->runtime()->cooperatingContexts().erase(&target);
-                found = true;
-                break;
-            }
-        }
-        MOZ_ASSERT(found);
-
-        cx->runtime()->deleteActiveContext(cx);
-    }
+    // Destroy the runtime along with its last context.
+    cx->runtime()->destroyRuntime();
+    js_delete(cx->runtime());
+    js_delete_poison(cx);
 }
 
 void
 JS::RootingContext::checkNoGCRooters() {
 #ifdef DEBUG
     for (auto const& stackRootPtr : stackRoots_)
         MOZ_ASSERT(stackRootPtr == nullptr);
 #endif
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -218,19 +218,16 @@ struct JSContext : public JS::RootingCon
 
   public:
     template <typename T>
     inline void enterCompartmentOf(const T& target);
     inline void enterNullCompartment();
     inline void leaveCompartment(JSCompartment* oldCompartment,
                                  const js::AutoLockForExclusiveAccess* maybeLock = nullptr);
 
-    inline void enterZoneGroup(js::ZoneGroup* group);
-    inline void leaveZoneGroup(js::ZoneGroup* group);
-
     void setHelperThread(js::HelperThread* helperThread);
     js::HelperThread* helperThread() const { return helperThread_; }
 
     bool isNurseryAllocSuppressed() const {
         return nurserySuppressions_;
     }
 
     // Threads may freely access any data in their compartment and zone.
@@ -853,16 +850,29 @@ struct JSContext : public JS::RootingCon
     void finishHandlingJitInterrupt() {
         MOZ_ASSERT(handlingJitInterrupt_);
         handlingJitInterrupt_ = false;
     }
     bool handlingJitInterrupt() const {
         return handlingJitInterrupt_;
     }
 
+    void* addressOfInterrupt() {
+        return &interrupt_;
+    }
+    void* addressOfInterruptRegExpJit() {
+        return &interruptRegExpJit_;
+    }
+    void* addressOfJitStackLimit() {
+        return &jitStackLimit;
+    }
+    void* addressOfJitStackLimitNoInterrupt() {
+        return &jitStackLimitNoInterrupt;
+    }
+
     /* Futex state, used by Atomics.wait() and Atomics.wake() on the Atomics object */
     js::FutexThread fx;
 
     // Buffer for OSR from baseline to Ion. To avoid holding on to this for
     // too long, it's also freed in EnterBaseline (after returning from JIT code).
     js::ThreadLocalData<uint8_t*> osrTempData_;
 
     uint8_t* allocateOsrTempData(size_t size);
@@ -930,20 +940,20 @@ JSContext::boolToResult(bool ok)
         MOZ_ASSERT(!isExceptionPending());
         MOZ_ASSERT(!isPropagatingForcedReturn());
         return JS::Ok();
     }
     return JS::Result<>(reportedError);
 }
 
 inline JSContext*
-JSRuntime::activeContextFromOwnThread()
+JSRuntime::mainContextFromOwnThread()
 {
-    MOZ_ASSERT(activeContext() == js::TlsContext.get());
-    return activeContext();
+    MOZ_ASSERT(mainContextFromAnyThread() == js::TlsContext.get());
+    return mainContextFromAnyThread();
 }
 
 namespace js {
 
 struct MOZ_RAII AutoResolving {
   public:
     enum Kind {
         LOOKUP,
@@ -981,25 +991,16 @@ struct MOZ_RAII AutoResolving {
 
 /*
  * Create and destroy functions for JSContext, which is manually allocated
  * and exclusively owned.
  */
 extern JSContext*
 NewContext(uint32_t maxBytes, uint32_t maxNurseryBytes, JSRuntime* parentRuntime);
 
-extern JSContext*
-NewCooperativeContext(JSContext* siblingContext);
-
-extern void
-YieldCooperativeContext(JSContext* cx);
-
-extern void
-ResumeCooperativeContext(JSContext* cx);
-
 extern void
 DestroyContext(JSContext* cx);
 
 enum ErrorArgumentsType {
     ArgumentsAreUnicode,
     ArgumentsAreASCII,
     ArgumentsAreLatin1,
     ArgumentsAreUTF8
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4123,37 +4123,34 @@ JSScript::argumentsOptimizationFailed(JS
      * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are
      * three things that need fixup:
      *  - there may be any number of activations of this script that don't have
      *    an argsObj that now need one.
      *  - jit code compiled (and possible active on the stack) with the static
      *    assumption of !script->needsArgsObj();
      *  - type inference data for the script assuming script->needsArgsObj
      */
-    JSRuntime::AutoProhibitActiveContextChange apacc(cx->runtime());
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (AllScriptFramesIter i(cx, target); !i.done(); ++i) {
-            /*
-             * We cannot reliably create an arguments object for Ion activations of
-             * this script.  To maintain the invariant that "script->needsArgsObj
-             * implies fp->hasArgsObj", the Ion bail mechanism will create an
-             * arguments object right after restoring the BaselineFrame and before
-             * entering Baseline code (in jit::FinishBailoutToBaseline).
-             */
-            if (i.isIon())
-                continue;
-            AbstractFramePtr frame = i.abstractFramePtr();
-            if (frame.isFunctionFrame() && frame.script() == script) {
-                /* We crash on OOM since cleaning up here would be complicated. */
-                AutoEnterOOMUnsafeRegion oomUnsafe;
-                ArgumentsObject* argsobj = ArgumentsObject::createExpected(cx, frame);
-                if (!argsobj)
-                    oomUnsafe.crash("JSScript::argumentsOptimizationFailed");
-                SetFrameArgumentsObject(cx, frame, script, argsobj);
-            }
+    for (AllScriptFramesIter i(cx); !i.done(); ++i) {
+        /*
+         * We cannot reliably create an arguments object for Ion activations of
+         * this script.  To maintain the invariant that "script->needsArgsObj
+         * implies fp->hasArgsObj", the Ion bail mechanism will create an
+         * arguments object right after restoring the BaselineFrame and before
+         * entering Baseline code (in jit::FinishBailoutToBaseline).
+         */
+        if (i.isIon())
+            continue;
+        AbstractFramePtr frame = i.abstractFramePtr();
+        if (frame.isFunctionFrame() && frame.script() == script) {
+            /* We crash on OOM since cleaning up here would be complicated. */
+            AutoEnterOOMUnsafeRegion oomUnsafe;
+            ArgumentsObject* argsobj = ArgumentsObject::createExpected(cx, frame);
+            if (!argsobj)
+                oomUnsafe.crash("JSScript::argumentsOptimizationFailed");
+            SetFrameArgumentsObject(cx, frame, script, argsobj);
         }
     }
 
     return true;
 }
 
 bool
 JSScript::formalIsAliased(unsigned argSlot)
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -87,22 +87,17 @@ ReturnZeroSize(const void* p)
 }
 
 JSRuntime::JSRuntime(JSRuntime* parentRuntime)
   : parentRuntime(parentRuntime),
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
     initialized_(false),
 #endif
-    activeContext_(nullptr),
-    activeContextChangeProhibited_(0),
-    singleThreadedExecutionRequired_(0),
-    startingSingleThreadedExecution_(false),
-    beginSingleThreadedExecutionCallback(nullptr),
-    endSingleThreadedExecutionCallback(nullptr),
+    mainContext_(nullptr),
     profilerSampleBufferRangeStart_(0),
     telemetryCallback(nullptr),
     consumeStreamCallback(nullptr),
     readableStreamDataRequestCallback(nullptr),
     readableStreamWriteIntoReadRequestCallback(nullptr),
     readableStreamCancelCallback(nullptr),
     readableStreamClosedCallback(nullptr),
     readableStreamErroredCallback(nullptr),
@@ -206,19 +201,17 @@ JSRuntime::init(JSContext* cx, uint32_t 
 #ifdef DEBUG
     MOZ_ASSERT(!initialized_);
     initialized_ = true;
 #endif
 
     if (CanUseExtraThreads() && !EnsureHelperThreadsInitialized())
         return false;
 
-    activeContext_ = cx;
-    if (!cooperatingContexts().append(cx))
-        return false;
+    mainContext_ = cx;
 
     defaultFreeOp_ = js_new<js::FreeOp>(this);
     if (!defaultFreeOp_)
         return false;
 
     if (!gc.init(maxbytes, maxNurseryBytes))
         return false;
 
@@ -343,99 +336,16 @@ JSRuntime::destroyRuntime()
     js_free(defaultLocale);
     js_delete(jitRuntime_.ref());
 
 #ifdef DEBUG
     initialized_ = false;
 #endif
 }
 
-static void
-CheckCanChangeActiveContext(JSRuntime* rt)
-{
-    // The runtime might not currently have an active context, in which case
-    // the accesses below to ActiveThreadData data would not normally be
-    // allowed. Suppress protected data checks so these accesses will be
-    // tolerated --- if the active context is null then we're about to set it
-    // to the current thread.
-    AutoNoteSingleThreadedRegion anstr;
-
-    MOZ_RELEASE_ASSERT(!rt->activeContextChangeProhibited());
-    MOZ_RELEASE_ASSERT(!rt->activeContext() || rt->gc.canChangeActiveContext(rt->activeContext()));
-
-    if (rt->singleThreadedExecutionRequired()) {
-        for (ZoneGroupsIter group(rt); !group.done(); group.next())
-            MOZ_RELEASE_ASSERT(group->ownerContext().context() == nullptr);
-    }
-}
-
-void
-JSRuntime::setActiveContext(JSContext* cx)
-{
-    CheckCanChangeActiveContext(this);
-    MOZ_ASSERT_IF(cx, cx->isCooperativelyScheduled());
-
-    activeContext_ = cx;
-}
-
-void
-JSRuntime::setNewbornActiveContext(JSContext* cx)
-{
-    CheckCanChangeActiveContext(this);
-
-    activeContext_ = cx;
-
-    AutoEnterOOMUnsafeRegion oomUnsafe;
-    if (!cooperatingContexts().append(cx))
-        oomUnsafe.crash("Add cooperating context");
-}
-
-void
-JSRuntime::deleteActiveContext(JSContext* cx)
-{
-    CheckCanChangeActiveContext(this);
-    MOZ_ASSERT(cx == activeContext());
-
-    js_delete_poison(cx);
-    activeContext_ = nullptr;
-}
-
-bool
-JSRuntime::beginSingleThreadedExecution(JSContext* cx)
-{
-    if (singleThreadedExecutionRequired_ == 0) {
-        if (startingSingleThreadedExecution_)
-            return false;
-        startingSingleThreadedExecution_ = true;
-        if (beginSingleThreadedExecutionCallback)
-            beginSingleThreadedExecutionCallback(cx);
-        MOZ_ASSERT(startingSingleThreadedExecution_);
-        startingSingleThreadedExecution_ = false;
-    }
-
-    singleThreadedExecutionRequired_++;
-
-    for (ZoneGroupsIter group(this); !group.done(); group.next()) {
-        MOZ_RELEASE_ASSERT(group->ownedByCurrentThread() ||
-                           group->ownerContext().context() == nullptr);
-    }
-
-    return true;
-}
-
-void
-JSRuntime::endSingleThreadedExecution(JSContext* cx)
-{
-    MOZ_ASSERT(singleThreadedExecutionRequired_);
-    if (--singleThreadedExecutionRequired_ == 0) {
-        if (endSingleThreadedExecutionCallback)
-            endSingleThreadedExecutionCallback(cx);
-    }
-}
-
 void
 JSRuntime::addTelemetry(int id, uint32_t sample, const char* key)
 {
     if (telemetryCallback)
         (*telemetryCallback)(id, sample, key);
 }
 
 void
@@ -469,27 +379,25 @@ JSRuntime::addSizeOfIncludingThis(mozill
     }
 
     if (!parentRuntime) {
         rtSizes->atomsTable += mallocSizeOf(staticStrings);
         rtSizes->atomsTable += mallocSizeOf(commonNames);
         rtSizes->atomsTable += permanentAtoms->sizeOfIncludingThis(mallocSizeOf);
     }
 
-    for (const CooperatingContext& target : cooperatingContexts()) {
-        JSContext* cx = target.context();
-        rtSizes->contexts += mallocSizeOf(cx);
-        rtSizes->contexts += cx->sizeOfExcludingThis(mallocSizeOf);
-        rtSizes->temporary += cx->tempLifoAlloc().sizeOfExcludingThis(mallocSizeOf);
-        rtSizes->interpreterStack += cx->interpreterStack().sizeOfExcludingThis(mallocSizeOf);
+    JSContext* cx = mainContextFromAnyThread();
+    rtSizes->contexts += mallocSizeOf(cx);
+    rtSizes->contexts += cx->sizeOfExcludingThis(mallocSizeOf);
+    rtSizes->temporary += cx->tempLifoAlloc().sizeOfExcludingThis(mallocSizeOf);
+    rtSizes->interpreterStack += cx->interpreterStack().sizeOfExcludingThis(mallocSizeOf);
 #ifdef JS_TRACE_LOGGING
-        if (cx->traceLogger)
-            rtSizes->tracelogger += cx->traceLogger->sizeOfIncludingThis(mallocSizeOf);
+    if (cx->traceLogger)
+        rtSizes->tracelogger += cx->traceLogger->sizeOfIncludingThis(mallocSizeOf);
 #endif
-    }
 
     if (MathCache* cache = caches().maybeGetMathCache())
         rtSizes->mathCache += cache->sizeOfIncludingThis(mallocSizeOf);
 
     rtSizes->uncompressedSourceCache +=
         caches().uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->gc.nurseryCommitted += gc.nursery().sizeOfHeapCommitted();
@@ -624,17 +532,17 @@ JSContext::handleInterrupt()
 }
 
 bool
 JSRuntime::setDefaultLocale(const char* locale)
 {
     if (!locale)
         return false;
     resetDefaultLocale();
-    defaultLocale = JS_strdup(activeContextFromOwnThread(), locale);
+    defaultLocale = JS_strdup(mainContextFromOwnThread(), locale);
     return defaultLocale != nullptr;
 }
 
 void
 JSRuntime::resetDefaultLocale()
 {
     js_free(defaultLocale);
     defaultLocale = nullptr;
@@ -647,17 +555,17 @@ JSRuntime::getDefaultLocale()
         return defaultLocale;
 
     const char* locale = setlocale(LC_ALL, nullptr);
 
     // convert to a well-formed BCP 47 language tag
     if (!locale || !strcmp(locale, "C"))
         locale = "und";
 
-    char* lang = JS_strdup(activeContextFromOwnThread(), locale);
+    char* lang = JS_strdup(mainContextFromOwnThread(), locale);
     if (!lang)
         return nullptr;
 
     char* p;
     if ((p = strchr(lang, '.')))
         *p = '\0';
     while ((p = strchr(lang, '_')))
         *p = '-';
@@ -903,25 +811,25 @@ JSRuntime::clearUsedByHelperThread(Zone*
     JSContext* cx = TlsContext.get();
     if (gc.fullGCForAtomsRequested() && cx->canCollectAtoms())
         gc.triggerFullGCForAtoms(cx);
 }
 
 bool
 js::CurrentThreadCanAccessRuntime(const JSRuntime* rt)
 {
-    return rt->activeContext() == TlsContext.get();
+    return rt->mainContextFromAnyThread() == TlsContext.get();
 }
 
 bool
 js::CurrentThreadCanAccessZone(Zone* zone)
 {
     // Helper thread zones can only be used by their owning thread.
     if (zone->usedByHelperThread())
-        return zone->group()->ownedByCurrentThread();
+        return zone->group()->ownedByCurrentHelperThread();
 
     // Other zones can only be accessed by the runtime's active context.
     return CurrentThreadCanAccessRuntime(zone->runtime_);
 }
 
 #ifdef DEBUG
 bool
 js::CurrentThreadIsPerformingGC()
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -100,43 +100,28 @@ class CompileRuntime;
 typedef vixl::Simulator Simulator;
 #elif defined(JS_SIMULATOR)
 class Simulator;
 #endif
 } // namespace jit
 
 // JS Engine Threading
 //
-// Multiple threads may interact with a JS runtime. JS has run-to-completion
-// semantics, which means that scripts cannot observe changes in behavior
-// due to activities performed on other threads (there is an exception to this
-// for shared array buffers and related APIs).
-//
-// The main way we ensure that run-to-completion semantics are preserved is
-// by dividing content into zone groups. Pieces of web content will be in the
-// the same zone group if they have the same tab/origin or can otherwise
-// observe changes in each other via Window.opener and so forth. When a thread
-// executes JS in a zone group, it acquires that group --- including exclusive
-// access to most of the group's content --- and does not relinquish control of
-// the zone group until the script finishes executing.
-//
 // Threads interacting with a runtime are divided into two categories:
 //
-// - Cooperating threads are capable of running JS. At most one cooperating
-//   thread may be |active| at a time in a runtime, but they may yield control
-//   to each other so that their execution is interleaved. As described above,
-//   each thread owns the zone groups it is operating on so that this
-//   interleaving does not cause observable changes in a script's behavior.
+// - The main thread is capable of running JS. There's at most one main thread
+//   per runtime.
 //
 // - Helper threads do not run JS, and are controlled or triggered by activity
-//   in the cooperating threads. Helper threads may have exclusive access to
-//   zone groups created for them, for parsing and similar tasks, but their
-//   activities do not cause observable changes in script behaviors. Activity
-//   on helper threads may be referred to as happening 'off thread' or on a
-//   background thread in some parts of the VM.
+//   on the main thread (or main threads, since all runtimes in a process share
+//   helper threads). Helper threads may have exclusive access to zone groups
+//   created for them, for parsing and similar tasks, but their activities do
+//   not cause observable changes in script behaviors. Activity on helper
+//   threads may be referred to as happening 'off thread' or on a background
+//   thread in some parts of the VM.
 
 } /* namespace js */
 
 namespace JS {
 struct RuntimeSizes;
 } // namespace JS
 
 /* Various built-in or commonly-used names pinned on first context. */
@@ -273,79 +258,25 @@ struct JSRuntime : public js::MallocProv
     AutoUpdateChildRuntimeCount updateChildRuntimeCount;
 #endif
 
   private:
 #ifdef DEBUG
     js::WriteOnceData<bool> initialized_;
 #endif
 
-    // The context for the thread which currently has exclusive access to most
-    // contents of the runtime. When execution on the runtime is cooperatively
-    // scheduled, this is the thread which is currently running.
-    mozilla::Atomic<JSContext*, mozilla::ReleaseAcquire> activeContext_;
-
-    // All contexts participating in cooperative scheduling. All threads other
-    // than |activeContext_| are suspended.
-    js::ActiveThreadData<js::Vector<js::CooperatingContext, 4, js::SystemAllocPolicy>> cooperatingContexts_;
-
-    // Count of AutoProhibitActiveContextChange instances on the active context.
-    js::ActiveThreadData<size_t> activeContextChangeProhibited_;
-
-    // Count of beginSingleThreadedExecution() calls that have occurred with no
-    // matching endSingleThreadedExecution().
-    js::ActiveThreadData<size_t> singleThreadedExecutionRequired_;
-
-    // Whether some thread has called beginSingleThreadedExecution() and we are
-    // in the associated callback (which may execute JS on other threads).
-    js::ActiveThreadData<bool> startingSingleThreadedExecution_;
+    // The JSContext* for the runtime's main thread. Immutable after this is set
+    // in JSRuntime::init.
+    JSContext* mainContext_;
 
   public:
-    JSContext* activeContext() const { return activeContext_; }
-    const void* addressOfActiveContext() { return &activeContext_; }
-
-    void setActiveContext(JSContext* cx);
-    void setNewbornActiveContext(JSContext* cx);
-    void deleteActiveContext(JSContext* cx);
-
-    inline JSContext* activeContextFromOwnThread();
-
-    js::Vector<js::CooperatingContext, 4, js::SystemAllocPolicy>& cooperatingContexts() {
-        return cooperatingContexts_.ref();
-    }
-
-    class MOZ_RAII AutoProhibitActiveContextChange
-    {
-        JSRuntime* rt;
+    JSContext* mainContextFromAnyThread() const { return mainContext_; }
+    const void* addressOfMainContext() { return &mainContext_; }
 
-      public:
-        explicit AutoProhibitActiveContextChange(JSRuntime* rt)
-          : rt(rt)
-        {
-            rt->activeContextChangeProhibited_++;
-        }
-
-        ~AutoProhibitActiveContextChange()
-        {
-            rt->activeContextChangeProhibited_--;
-        }
-    };
-
-    bool activeContextChangeProhibited() { return activeContextChangeProhibited_; }
-    bool singleThreadedExecutionRequired() { return singleThreadedExecutionRequired_; }
-
-    js::ActiveThreadData<JS::BeginSingleThreadedExecutionCallback> beginSingleThreadedExecutionCallback;
-    js::ActiveThreadData<JS::EndSingleThreadedExecutionCallback> endSingleThreadedExecutionCallback;
-
-    // Ensure there is only a single thread interacting with this runtime.
-    // beginSingleThreadedExecution() returns false if some context has already
-    // started forcing this runtime to be single threaded. Calls to these
-    // functions must be balanced.
-    bool beginSingleThreadedExecution(JSContext* cx);
-    void endSingleThreadedExecution(JSContext* cx);
+    inline JSContext* mainContextFromOwnThread();
 
     /*
      * The start of the range stored in the profiler sample buffer, as measured
      * after the most recent sample.
      * All JitcodeGlobalTable entries referenced from a given sample are
      * assigned the buffer position of the START of the sample. The buffer
      * entries that reference the JitcodeGlobalTable entries will only ever be
      * read from the buffer while the entire sample is still inside the buffer;
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -399,17 +399,17 @@ SavedFrame::protoAccessors[] = {
 
 /* static */ void
 SavedFrame::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onActiveCooperatingThread());
     JSPrincipals* p = obj->as<SavedFrame>().getPrincipals();
     if (p) {
         JSRuntime* rt = obj->runtimeFromActiveCooperatingThread();
-        JS_DropPrincipals(rt->activeContextFromOwnThread(), p);
+        JS_DropPrincipals(rt->mainContextFromOwnThread(), p);
     }
 }
 
 JSAtom*
 SavedFrame::getSource()
 {
     const Value& v = getReservedSlot(JSSLOT_SOURCE);
     JSString* s = v.toString();
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -401,19 +401,19 @@ TraceInterpreterActivation(JSTracer* trc
 {
     for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) {
         InterpreterFrame* fp = frames.frame();
         fp->trace(trc, frames.sp(), frames.pc());
     }
 }
 
 void
-js::TraceInterpreterActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
+js::TraceInterpreterActivations(JSContext* cx, JSTracer* trc)
 {
-    for (ActivationIterator iter(cx, target); !iter.done(); ++iter) {
+    for (ActivationIterator iter(cx); !iter.done(); ++iter) {
         Activation* act = iter.activation();
         if (act->isInterpreter())
             TraceInterpreterActivation(trc, act->asInterpreter());
     }
 }
 
 /*****************************************************************************/
 
@@ -730,52 +730,29 @@ FrameIter::Data::Data(JSContext* cx, Deb
     state_(DONE),
     pc_(nullptr),
     interpFrames_(nullptr),
     activations_(cx),
     ionInlineFrameNo_(0)
 {
 }
 
-FrameIter::Data::Data(JSContext* cx, const CooperatingContext& target,
-                      DebuggerEvalOption debuggerEvalOption)
-  : cx_(cx),
-    debuggerEvalOption_(debuggerEvalOption),
-    principals_(nullptr),
-    state_(DONE),
-    pc_(nullptr),
-    interpFrames_(nullptr),
-    activations_(cx, target),
-    ionInlineFrameNo_(0)
-{
-}
-
 FrameIter::Data::Data(const FrameIter::Data& other)
   : cx_(other.cx_),
     debuggerEvalOption_(other.debuggerEvalOption_),
     principals_(other.principals_),
     state_(other.state_),
     pc_(other.pc_),
     interpFrames_(other.interpFrames_),
     activations_(other.activations_),
     jitFrames_(other.jitFrames_),
     ionInlineFrameNo_(other.ionInlineFrameNo_)
 {
 }
 
-FrameIter::FrameIter(JSContext* cx, const CooperatingContext& target,
-                     DebuggerEvalOption debuggerEvalOption)
-  : data_(cx, target, debuggerEvalOption),
-    ionInlineFrames_(cx, (js::jit::JSJitFrameIter*) nullptr)
-{
-    // settleOnActivation can only GC if principals are given.
-    JS::AutoSuppressGCAnalysis nogc;
-    settleOnActivation();
-}
-
 FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption)
   : data_(cx, debuggerEvalOption, nullptr),
     ionInlineFrames_(cx, (js::jit::JSJitFrameIter*) nullptr)
 {
     // settleOnActivation can only GC if principals are given.
     JS::AutoSuppressGCAnalysis nogc;
     settleOnActivation();
 }
@@ -1805,32 +1782,16 @@ Activation::unregisterProfiling()
 }
 
 ActivationIterator::ActivationIterator(JSContext* cx)
   : activation_(cx->activation_)
 {
     MOZ_ASSERT(cx == TlsContext.get());
 }
 
-ActivationIterator::ActivationIterator(JSContext* cx, const CooperatingContext& target)
-{
-    MOZ_ASSERT(cx == TlsContext.get());
-
-    // If target was specified --- even if it is the same as cx itself --- then
-    // we must be in a scope where changes of the active context are prohibited.
-    // Otherwise our state would be corrupted if the target thread resumed
-    // execution while we are iterating over its state.
-    MOZ_ASSERT(cx->runtime()->activeContextChangeProhibited() ||
-               !cx->runtime()->gc.canChangeActiveContext(cx));
-
-    // Tolerate a null target context, in case we are iterating over the
-    // activations for a zone group that is not in use by any thread.
-    activation_ = target.context() ? target.context()->activation_.ref() : nullptr;
-}
-
 ActivationIterator&
 ActivationIterator::operator++()
 {
     MOZ_ASSERT(activation_);
     activation_ = activation_->prev();
     return *this;
 }
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -908,34 +908,17 @@ class InterpreterStack
 
     inline void purge(JSRuntime* rt);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return allocator_.sizeOfExcludingThis(mallocSizeOf);
     }
 };
 
-// CooperatingContext is a wrapper for a JSContext that is participating in
-// cooperative scheduling and may be different from the current thread. It is
-// in place to make it clearer when we might be operating on another thread,
-// and harder to accidentally pass in another thread's context to an API that
-// expects the current thread's context.
-class CooperatingContext
-{
-    JSContext* cx;
-
-  public:
-    explicit CooperatingContext(JSContext* cx) : cx(cx) {}
-    JSContext* context() const { return cx; }
-
-    // For &cx. The address should not be taken for other CooperatingContexts.
-    friend class ZoneGroup;
-};
-
-void TraceInterpreterActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc);
+void TraceInterpreterActivations(JSContext* cx, JSTracer* trc);
 
 /*****************************************************************************/
 
 /** Base class for all function call args. */
 class AnyInvokeArgs : public JS::CallArgs
 {
 };
 
@@ -1601,21 +1584,16 @@ class InterpreterActivation : public Act
 class ActivationIterator
 {
   protected:
     Activation* activation_;
 
   public:
     explicit ActivationIterator(JSContext* cx);
 
-    // ActivationIterator can be used to iterate over a different thread's
-    // activations, for use by the GC, invalidation, and other operations that
-    // don't have a user-visible effect on the target thread's JS behavior.
-    ActivationIterator(JSContext* cx, const CooperatingContext& target);
-
     ActivationIterator& operator++();
 
     Activation* operator->() const {
         return activation_;
     }
     Activation* activation() const {
         return activation_;
     }
@@ -1847,22 +1825,16 @@ class JitActivationIterator : public Act
 
   public:
     explicit JitActivationIterator(JSContext* cx)
       : ActivationIterator(cx)
     {
         settle();
     }
 
-    JitActivationIterator(JSContext* cx, const CooperatingContext& target)
-      : ActivationIterator(cx, target)
-    {
-        settle();
-    }
-
     JitActivationIterator& operator++() {
         ActivationIterator::operator++();
         settle();
         return *this;
     }
 };
 
 } // namespace jit
@@ -2043,23 +2015,21 @@ class FrameIter
 
         InterpreterFrameIterator interpFrames_;
         ActivationIterator activations_;
 
         JitFrameIter jitFrames_;
         unsigned ionInlineFrameNo_;
 
         Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption, JSPrincipals* principals);
-        Data(JSContext* cx, const CooperatingContext& target, DebuggerEvalOption debuggerEvalOption);
         Data(const Data& other);
     };
 
     explicit FrameIter(JSContext* cx,
                        DebuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK);
-    FrameIter(JSContext* cx, const CooperatingContext&, DebuggerEvalOption);
     FrameIter(JSContext* cx, DebuggerEvalOption, JSPrincipals*);
     FrameIter(const FrameIter& iter);
     MOZ_IMPLICIT FrameIter(const Data& data);
     MOZ_IMPLICIT FrameIter(AbstractFramePtr frame);
 
     bool done() const { return data_.state_ == DONE; }
 
     // -------------------------------------------------------
@@ -2217,24 +2187,16 @@ class ScriptFrameIter : public FrameIter
     explicit ScriptFrameIter(JSContext* cx,
                              DebuggerEvalOption debuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK)
       : FrameIter(cx, debuggerEvalOption)
     {
         settle();
     }
 
     ScriptFrameIter(JSContext* cx,
-                     const CooperatingContext& target,
-                     DebuggerEvalOption debuggerEvalOption)
-       : FrameIter(cx, target, debuggerEvalOption)
-    {
-        settle();
-    }
-
-    ScriptFrameIter(JSContext* cx,
                     DebuggerEvalOption debuggerEvalOption,
                     JSPrincipals* prin)
       : FrameIter(cx, debuggerEvalOption, prin)
     {
         settle();
     }
 
     ScriptFrameIter(const ScriptFrameIter& iter) : FrameIter(iter) { settle(); }
@@ -2346,20 +2308,16 @@ class AllFramesIter : public FrameIter
  * See also AllFramesIter and ScriptFrameIter.
  */
 class AllScriptFramesIter : public ScriptFrameIter
 {
   public:
     explicit AllScriptFramesIter(JSContext* cx)
       : ScriptFrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK)
     {}
-
-    explicit AllScriptFramesIter(JSContext* cx, const CooperatingContext& target)
-      : ScriptFrameIter(cx, target, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK)
-    {}
 };
 
 /* Popular inline definitions. */
 
 inline JSScript*
 FrameIter::script() const
 {
     MOZ_ASSERT(!done());
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3974,102 +3974,99 @@ TypeNewScript::rollbackPartiallyInitiali
 
     if (!initializerList)
         return false;
 
     bool found = false;
 
     RootedFunction function(cx, this->function());
     Vector<uint32_t, 32> pcOffsets(cx);
-    JSRuntime::AutoProhibitActiveContextChange apacc(cx->runtime());
-    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
-        for (AllScriptFramesIter iter(cx, target); !iter.done(); ++iter) {
-            {
-                AutoEnterOOMUnsafeRegion oomUnsafe;
-                if (!pcOffsets.append(iter.script()->pcToOffset(iter.pc())))
-                    oomUnsafe.crash("rollbackPartiallyInitializedObjects");
-            }
-
-            if (!iter.isConstructing() || !iter.matchCallee(cx, function))
-                continue;
-
-            // Derived class constructors initialize their this-binding later and
-            // we shouldn't run the definite properties analysis on them.
-            MOZ_ASSERT(!iter.script()->isDerivedClassConstructor());
-
-            Value thisv = iter.thisArgument(cx);
-            if (!thisv.isObject() ||
-                thisv.toObject().hasLazyGroup() ||
-                thisv.toObject().group() != group)
-            {
-                continue;
-            }
-
-            if (thisv.toObject().is<UnboxedPlainObject>()) {
-                AutoEnterOOMUnsafeRegion oomUnsafe;
-                if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
-                    oomUnsafe.crash("rollbackPartiallyInitializedObjects");
-            }
-
-            // Found a matching frame.
-            RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
-
-            // Whether all identified 'new' properties have been initialized.
-            bool finished = false;
-
-            // If not finished, number of properties that have been added.
-            uint32_t numProperties = 0;
-
-            // Whether the current SETPROP is within an inner frame which has
-            // finished entirely.
-            bool pastProperty = false;
-
-            // Index in pcOffsets of the outermost frame.
-            int callDepth = pcOffsets.length() - 1;
-
-            // Index in pcOffsets of the frame currently being checked for a SETPROP.
-            int setpropDepth = callDepth;
-
-            for (Initializer* init = initializerList;; init++) {
-                if (init->kind == Initializer::SETPROP) {
-                    if (!pastProperty && pcOffsets[setpropDepth] < init->offset) {
-                        // Have not yet reached this setprop.
-                        break;
-                    }
-                    // This setprop has executed, reset state for the next one.
-                    numProperties++;
-                    pastProperty = false;
-                    setpropDepth = callDepth;
-                } else if (init->kind == Initializer::SETPROP_FRAME) {
-                    if (!pastProperty) {
-                        if (pcOffsets[setpropDepth] < init->offset) {
-                            // Have not yet reached this inner call.
-                            break;
-                        } else if (pcOffsets[setpropDepth] > init->offset) {
-                            // Have advanced past this inner call.
-                            pastProperty = true;
-                        } else if (setpropDepth == 0) {
-                            // Have reached this call but not yet in it.
-                            break;
-                        } else {
-                            // Somewhere inside this inner call.
-                            setpropDepth--;
-                        }
-                    }
-                } else {
-                    MOZ_ASSERT(init->kind == Initializer::DONE);
-                    finished = true;
+    for (AllScriptFramesIter iter(cx); !iter.done(); ++iter) {
+        {
+            AutoEnterOOMUnsafeRegion oomUnsafe;
+            if (!pcOffsets.append(iter.script()->pcToOffset(iter.pc())))
+                oomUnsafe.crash("rollbackPartiallyInitializedObjects");
+        }
+
+        if (!iter.isConstructing() || !iter.matchCallee(cx, function))
+            continue;
+
+        // Derived class constructors initialize their this-binding later and
+        // we shouldn't run the definite properties analysis on them.
+        MOZ_ASSERT(!iter.script()->isDerivedClassConstructor());
+
+        Value thisv = iter.thisArgument(cx);
+        if (!thisv.isObject() ||
+            thisv.toObject().hasLazyGroup() ||
+            thisv.toObject().group() != group)
+        {
+            continue;
+        }
+
+        if (thisv.toObject().is<UnboxedPlainObject>()) {
+            AutoEnterOOMUnsafeRegion oomUnsafe;
+            if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
+                oomUnsafe.crash("rollbackPartiallyInitializedObjects");
+        }
+
+        // Found a matching frame.
+        RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
+
+        // Whether all identified 'new' properties have been initialized.
+        bool finished = false;
+
+        // If not finished, number of properties that have been added.
+        uint32_t numProperties = 0;
+
+        // Whether the current SETPROP is within an inner frame which has
+        // finished entirely.
+        bool pastProperty = false;
+
+        // Index in pcOffsets of the outermost frame.
+        int callDepth = pcOffsets.length() - 1;
+
+        // Index in pcOffsets of the frame currently being checked for a SETPROP.
+        int setpropDepth = callDepth;
+
+        for (Initializer* init = initializerList;; init++) {
+            if (init->kind == Initializer::SETPROP) {
+                if (!pastProperty && pcOffsets[setpropDepth] < init->offset) {
+                    // Have not yet reached this setprop.
                     break;
                 }
+                // This setprop has executed, reset state for the next one.
+                numProperties++;
+                pastProperty = false;
+                setpropDepth = callDepth;
+            } else if (init->kind == Initializer::SETPROP_FRAME) {
+                if (!pastProperty) {
+                    if (pcOffsets[setpropDepth] < init->offset) {
+                        // Have not yet reached this inner call.
+                        break;
+                    } else if (pcOffsets[setpropDepth] > init->offset) {
+                        // Have advanced past this inner call.
+                        pastProperty = true;
+                    } else if (setpropDepth == 0) {
+                        // Have reached this call but not yet in it.
+                        break;
+                    } else {
+                        // Somewhere inside this inner call.
+                        setpropDepth--;
+                    }
+                }
+            } else {
+                MOZ_ASSERT(init->kind == Initializer::DONE);
+                finished = true;
+                break;
             }
-
-            if (!finished) {
-                (void) NativeObject::rollbackProperties(cx, obj, numProperties);
-                found = true;
-            }
+        }
+
+        if (!finished) {
+            (void) NativeObject::rollbackProperties(cx, obj, numProperties);
+            found = true;
         }
     }
 
     return found;
 }
 
 void
 TypeNewScript::trace(JSTracer* trc)
@@ -4614,17 +4611,16 @@ AutoClearTypeInferenceStateOnOOM::AutoCl
 
 AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM()
 {
     zone->types.setSweepingTypes(false);
 
     if (oom) {
         JSRuntime* rt = zone->runtimeFromActiveCooperatingThread();
         js::CancelOffThreadIonCompile(rt);
-        JSRuntime::AutoProhibitActiveContextChange apacc(rt);
         zone->setPreservingCode(false);
         zone->discardJitCode(rt->defaultFreeOp(), /* discardBaselineCode = */ false);
         zone->types.clearAllNewScriptsOnOOM();
     }
 }
 
 #ifdef DEBUG
 void