Bug 1366340 - Delete parsing zones immediately after their contents are merged r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Jul 2017 10:42:50 +0100
changeset 419282 451efff6cf90982653a0d469256b1b86281eed94
parent 419281 2b4d38213053a91f845b5b9ccd890fb8ab3be1b0
child 419283 2a698a65f75938cf408d2fcc3d3135556e908624
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1366340
milestone56.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 1366340 - Delete parsing zones immediately after their contents are merged r=sfink
js/src/gc/GCRuntime.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/gc/ZoneGroup.cpp
js/src/gc/ZoneGroup.h
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/vm/HelperThreads.cpp
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -932,17 +932,18 @@ class GCRuntime
     void bufferGrayRoots();
 
     /*
      * Concurrent sweep infrastructure.
      */
     void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
     void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
 
-  private:
+    // Delete an empty zone group after its contents have been merged.
+    void deleteEmptyZoneGroup(ZoneGroup* group);
 
   private:
     enum IncrementalResult
     {
         Reset = 0,
         Ok
     };
 
@@ -1091,17 +1092,20 @@ class GCRuntime
   public:
     JSRuntime* const rt;
 
     /* Embedders can use this zone and group however they wish. */
     UnprotectedData<JS::Zone*> systemZone;
     UnprotectedData<ZoneGroup*> systemZoneGroup;
 
     // List of all zone groups (protected by the GC lock).
-    ActiveThreadOrGCTaskData<ZoneGroupVector> groups;
+  private:
+    ActiveThreadOrGCTaskData<ZoneGroupVector> groups_;
+  public:
+    ZoneGroupVector& groups() { return groups_.ref(); }
 
     // The unique atoms zone, which has no zone group.
     WriteOnceData<Zone*> atomsZone;
 
   private:
     UnprotectedData<gcstats::Statistics> stats_;
   public:
     gcstats::Statistics& stats() { return stats_.ref(); }
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -68,31 +68,34 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
     AutoLockGC lock(rt);
     threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock);
     setGCMaxMallocBytes(rt->gc.maxMallocBytesAllocated() * 0.9);
     jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8);
 }
 
 Zone::~Zone()
 {
+    MOZ_ASSERT(compartments_.ref().empty());
+
     JSRuntime* rt = runtimeFromAnyThread();
     if (this == rt->gc.systemZone)
         rt->gc.systemZone = nullptr;
 
     js_delete(debuggers.ref());
     js_delete(jitZone_.ref());
 
 #ifdef DEBUG
     // Avoid assertion destroying the weak map list if the embedding leaked GC things.
     if (!rt->gc.shutdownCollectedEverything())
         gcWeakMapList().clear();
 #endif
 }
 
-bool Zone::init(bool isSystemArg)
+bool
+Zone::init(bool isSystemArg)
 {
     isSystem = isSystemArg;
     return uniqueIds().init() &&
            gcSweepGroupEdges().init() &&
            gcWeakKeys().init() &&
            typeDescrObjects().init() &&
            markedAtoms().init() &&
            atomCache().init() &&
@@ -371,16 +374,31 @@ Zone::addTypeDescrObject(JSContext* cx, 
     if (!typeDescrObjects().put(obj)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
+void
+Zone::deleteEmptyCompartment(JSCompartment* comp)
+{
+    MOZ_ASSERT(comp->zone() == this);
+    MOZ_ASSERT(arenas.checkEmptyArenaLists());
+    for (auto& i : compartments()) {
+        if (i == comp) {
+            compartments().erase(&i);
+            comp->destroy(runtimeFromActiveCooperatingThread()->defaultFreeOp());
+            return;
+        }
+    }
+    MOZ_CRASH("Compartment not found");
+}
+
 ZoneList::ZoneList()
   : head(nullptr), tail(nullptr)
 {}
 
 ZoneList::ZoneList(Zone* zone)
   : head(zone), tail(zone)
 {
     MOZ_RELEASE_ASSERT(!zone->isOnList());
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -156,16 +156,17 @@ namespace JS {
 // to delete the last compartment in a live zone.
 struct Zone : public JS::shadow::Zone,
               public js::gc::GraphNodeBase<JS::Zone>,
               public js::MallocProvider<JS::Zone>
 {
     explicit Zone(JSRuntime* rt, js::ZoneGroup* group);
     ~Zone();
     MOZ_MUST_USE bool init(bool isSystem);
+    void destroy(js::FreeOp *fop);
 
   private:
     js::ZoneGroup* const group_;
   public:
     js::ZoneGroup* group() const {
         return group_;
     }
 
@@ -617,16 +618,19 @@ struct Zone : public JS::shadow::Zone,
 
     bool keepShapeTables() const {
         return keepShapeTables_;
     }
     void setKeepShapeTables(bool b) {
         keepShapeTables_ = b;
     }
 
+    // Delete an empty compartment after its contents have been merged.
+    void deleteEmptyCompartment(JSCompartment* comp);
+
   private:
     js::ZoneGroupData<js::jit::JitZone*> jitZone_;
 
     js::ActiveThreadData<bool> gcScheduled_;
     js::ZoneGroupData<bool> gcPreserveCode_;
     js::ZoneGroupData<bool> keepShapeTables_;
 
     // Allow zones to be linked into a list
@@ -649,18 +653,18 @@ namespace js {
 class ZoneGroupsIter
 {
     gc::AutoEnterIteration iterMarker;
     ZoneGroup** it;
     ZoneGroup** end;
 
   public:
     explicit ZoneGroupsIter(JSRuntime* rt) : iterMarker(&rt->gc) {
-        it = rt->gc.groups.ref().begin();
-        end = rt->gc.groups.ref().end();
+        it = rt->gc.groups().begin();
+        end = rt->gc.groups().end();
 
         if (!done() && (*it)->usedByHelperThread)
             next();
     }
 
     bool done() const { return it == end; }
 
     void next() {
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -125,9 +125,25 @@ ZoneGroup::ionLazyLinkListAdd(jit::IonBu
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime),
                "Should only be mutated by the active thread.");
     MOZ_ASSERT(this == builder->script()->zone()->group());
     ionLazyLinkList().insertFront(builder);
     ionLazyLinkListSize_++;
 }
 
+void
+ZoneGroup::deleteEmptyZone(Zone* zone)
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
+    MOZ_ASSERT(zone->group() == this);
+    MOZ_ASSERT(zone->compartments().empty());
+    for (auto& i : zones()) {
+        if (i == zone) {
+            zones().erase(&i);
+            zone->destroy(runtime->defaultFreeOp());
+            return;
+        }
+    }
+    MOZ_CRASH("Zone not found");
+}
+
 } // namespace js
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -71,16 +71,19 @@ class ZoneGroup
     inline gc::StoreBuffer& storeBuffer();
 
     inline bool isCollecting();
     inline bool isGCScheduled();
 
     // See the useExclusiveLocking field above.
     void setUseExclusiveLocking() { useExclusiveLocking = true; }
 
+    // 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_;
 
   public:
     void* addressOfIonBailAfter() { return &ionBailAfter_; }
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -858,16 +858,17 @@ struct JSCompartment
     bool isAccessValid() const { return validAccessPtr ? *validAccessPtr : true; }
     void setValidAccessPtr(bool* accessp) { validAccessPtr = accessp; }
 
   public:
     JSCompartment(JS::Zone* zone, const JS::CompartmentOptions& options);
     ~JSCompartment();
 
     MOZ_MUST_USE bool init(JSContext* maybecx);
+    void destroy(js::FreeOp* fop);
 
     MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
 
     MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec);
     MOZ_MUST_USE bool rewrap(JSContext* cx, JS::MutableHandleObject obj, JS::HandleObject existing);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1215,21 +1215,22 @@ GCRuntime::finish()
 #endif
 
     /* Delete all remaining zones. */
     if (rt->gcInitialized) {
         AutoSetThreadIsSweeping threadIsSweeping;
         for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
             for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
                 js_delete(comp.get());
+            zone->compartments().clear();
             js_delete(zone.get());
         }
     }
 
-    groups.ref().clear();
+    groups().clear();
 
     FreeChunkPool(rt, fullChunks_.ref());
     FreeChunkPool(rt, availableChunks_.ref());
     FreeChunkPool(rt, emptyChunks_.ref());
 
     FinishTrace();
 
     for (ZoneGroupsIter group(rt); !group.done(); group.next())
@@ -3478,63 +3479,80 @@ UniqueIdGCPolicy::needsSweep(Cell** cell
 }
 
 void
 JS::Zone::sweepUniqueIds(js::FreeOp* fop)
 {
     uniqueIds().sweep();
 }
 
+void
+JSCompartment::destroy(FreeOp* fop)
+{
+    JSRuntime* rt = fop->runtime();
+    if (auto callback = rt->destroyCompartmentCallback)
+        callback(fop, this);
+    if (principals())
+        JS_DropPrincipals(TlsContext.get(), principals());
+    fop->delete_(this);
+    rt->gc.stats().sweptCompartment();
+}
+
+void
+Zone::destroy(FreeOp* fop)
+{
+    fop->delete_(this);
+    fop->runtime()->gc.stats().sweptZone();
+}
+
 /*
  * It's simpler if we preserve the invariant that every zone has at least one
  * compartment. If we know we're deleting the entire zone, then
  * SweepCompartments is allowed to delete all compartments. In this case,
  * |keepAtleastOne| is false. If some objects remain in the zone so that it
  * cannot be deleted, then we set |keepAtleastOne| to true, which prohibits
  * SweepCompartments from deleting every compartment. Instead, it preserves an
  * arbitrary compartment in the zone.
  */
 void
 Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime)
 {
-    JSRuntime* rt = runtimeFromActiveCooperatingThread();
-    JSDestroyCompartmentCallback callback = rt->destroyCompartmentCallback;
+    MOZ_ASSERT(!compartments().empty());
+
+    mozilla::DebugOnly<JSRuntime*> rt = runtimeFromActiveCooperatingThread();
 
     JSCompartment** read = compartments().begin();
     JSCompartment** end = compartments().end();
     JSCompartment** write = read;
     bool foundOne = false;
     while (read < end) {
         JSCompartment* comp = *read++;
         MOZ_ASSERT(!rt->isAtomsCompartment(comp));
 
         /*
          * Don't delete the last compartment if all the ones before it were
          * deleted and keepAtleastOne is true.
          */
         bool dontDelete = read == end && !foundOne && keepAtleastOne;
         if ((!comp->marked && !dontDelete) || destroyingRuntime) {
-            if (callback)
-                callback(fop, comp);
-            if (comp->principals())
-                JS_DropPrincipals(TlsContext.get(), comp->principals());
-            js_delete(comp);
-            rt->gc.stats().sweptCompartment();
+            comp->destroy(fop);
         } else {
             *write++ = comp;
             foundOne = true;
         }
     }
     compartments().shrinkTo(write - compartments().begin());
     MOZ_ASSERT_IF(keepAtleastOne, !compartments().empty());
 }
 
 void
 GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
 {
+    MOZ_ASSERT(!group->zones().empty());
+
     Zone** read = group->zones().begin();
     Zone** end = group->zones().end();
     Zone** write = read;
 
     while (read < end) {
         Zone* zone = *read++;
 
         if (zone->wasGCStarted()) {
@@ -3553,18 +3571,17 @@ GCRuntime::sweepZones(FreeOp* fop, ZoneG
 #ifdef DEBUG
                 if (!zone->arenas.checkEmptyArenaLists())
                     arenasEmptyAtShutdown = false;
 #endif
 
                 zone->sweepCompartments(fop, false, destroyingRuntime);
                 MOZ_ASSERT(zone->compartments().empty());
                 MOZ_ASSERT_IF(arenasEmptyAtShutdown, zone->typeDescrObjects().empty());
-                fop->delete_(zone);
-                stats().sweptZone();
+                zone->destroy(fop);
                 continue;
             }
             zone->sweepCompartments(fop, true, destroyingRuntime);
         }
         *write++ = zone;
     }
     group->zones().shrinkTo(write - group->zones().begin());
 }
@@ -3575,32 +3592,32 @@ GCRuntime::sweepZoneGroups(FreeOp* fop, 
     MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
     MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
 
     if (rt->gc.numActiveZoneIters)
         return;
 
     assertBackgroundSweepingFinished();
 
-    ZoneGroup** read = groups.ref().begin();
-    ZoneGroup** end = groups.ref().end();
+    ZoneGroup** read = groups().begin();
+    ZoneGroup** end = groups().end();
     ZoneGroup** write = read;
 
     while (read < end) {
         ZoneGroup* group = *read++;
         sweepZones(fop, group, destroyingRuntime);
 
         if (group->zones().empty()) {
             MOZ_ASSERT(numActiveZoneIters == 0);
             fop->delete_(group);
         } else {
             *write++ = group;
         }
     }
-    groups.ref().shrinkTo(write - groups.ref().begin());
+    groups().shrinkTo(write - groups().begin());
 }
 
 #ifdef DEBUG
 static const char*
 AllocKindToAscii(AllocKind kind)
 {
     switch(kind) {
 #define MAKE_CASE(allocKind, traceKind, type, sizedType) \
@@ -7362,17 +7379,17 @@ js::NewCompartment(JSContext* cx, JSPrin
         if (zoneSpec == JS::SystemZone) {
             MOZ_RELEASE_ASSERT(!rt->gc.systemZone);
             rt->gc.systemZone = zone;
             zone->isSystem = true;
         }
     }
 
     if (groupHolder) {
-        if (!rt->gc.groups.ref().append(group)) {
+        if (!rt->gc.groups().append(group)) {
             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;
@@ -7392,16 +7409,20 @@ gc::MergeCompartments(JSCompartment* sou
     // The source compartment must be specifically flagged as mergable.  This
     // also implies that the compartment is not visible to the debugger.
     MOZ_ASSERT(source->creationOptions_.mergeable());
     MOZ_ASSERT(source->creationOptions_.invisibleToDebugger());
 
     MOZ_ASSERT(source->creationOptions().addonIdOrNull() ==
                target->creationOptions().addonIdOrNull());
 
+    MOZ_ASSERT(!source->hasBeenEntered());
+    MOZ_ASSERT(source->zone()->compartments().length() == 1);
+    MOZ_ASSERT(source->zone()->group()->zones().length() == 1);
+
     JSContext* cx = source->runtimeFromActiveCooperatingThread()->activeContextFromOwnThread();
 
     MOZ_ASSERT(!source->zone()->wasGCStarted());
     MOZ_ASSERT(!target->zone()->wasGCStarted());
     JS::AutoAssertNoGC nogc(cx);
 
     AutoTraceSession session(cx->runtime());
 
@@ -7484,16 +7505,42 @@ gc::MergeCompartments(JSCompartment* sou
             JSScript* key = r.front().key();
             const char* value = r.front().value();
             if (!target->scriptNameMap->putNew(key, value))
                 oomUnsafe.crash("Failed to add an entry in the script name map.");
         }
 
         source->scriptNameMap->clear();
     }
+
+    // The source compartment is now completely empty, and is the only
+    // compartment in its zone, which is the only zone in its group. Delete
+    // compartment, zone and group without waiting for this to be cleaned up by
+    // a full GC.
+
+    Zone* sourceZone = source->zone();
+    ZoneGroup* sourceGroup = sourceZone->group();
+    sourceZone->deleteEmptyCompartment(source);
+    sourceGroup->deleteEmptyZone(sourceZone);
+    cx->runtime()->gc.deleteEmptyZoneGroup(sourceGroup);
+}
+
+void
+GCRuntime::deleteEmptyZoneGroup(ZoneGroup* group)
+{
+    MOZ_ASSERT(group->zones().empty());
+    MOZ_ASSERT(groups().length() > 1);
+    for (auto& i : groups()) {
+        if (i == group) {
+            groups().erase(&i);
+            js_delete(group);
+            return;
+        }
+    }
+    MOZ_CRASH("ZoneGroup not found");
 }
 
 void
 GCRuntime::runDebugGC()
 {
 #ifdef JS_GC_ZEAL
     if (TlsContext.get()->suppressGC)
         return;
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1593,19 +1593,20 @@ GlobalHelperThreadState::mergeParseTaskC
         JS::FinishIncrementalGC(cx, JS::gcreason::API);
 
     // After we call LeaveParseTaskZone() it's not safe to GC until we have
     // finished merging the contents of the parse task's compartment into the
     // destination compartment.
     JS::AutoAssertNoGC nogc(cx);
 
     LeaveParseTaskZone(cx->runtime(), parseTask);
-    AutoCompartment ac(cx, parseTask->parseGlobal);
 
     {
+        AutoCompartment ac(cx, parseTask->parseGlobal);
+
         // Generator functions don't have Function.prototype as prototype but a
         // different function object, so the IdentifyStandardPrototype trick
         // below won't work.  Just special-case it.
         GlobalObject* parseGlobal = &parseTask->parseGlobal->as<GlobalObject>();
         JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
 
         // Module objects don't have standard prototypes either.
         JSObject* moduleProto = parseGlobal->maybeGetModulePrototype();