Bug 1607444 - WIP: Only collect the self hosting zone once at runtime initialization draft
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 16 Jan 2020 15:36:31 +0000
changeset 2598868 2ea66637e60a83c94ba7bad1ff95b71ec0223f44
parent 2598867 2cb5bf731794e716bff635b3d693effb43c7225c
child 2598869 7ee4b4a50a4ec44dd6c0541ace35720a2842600d
push id476910
push userjcoppeard@mozilla.com
push dateThu, 16 Jan 2020 18:31:49 +0000
treeherdertry@7ee4b4a50a4e [default view] [failures only]
bugs1607444
milestone74.0a1
Bug 1607444 - WIP: Only collect the self hosting zone once at runtime initialization This mostly works but provokes a couple of jittest failures related to atom marking later on. Differential Revision: https://phabricator.services.mozilla.com/D60153
js/public/GCAPI.h
js/src/gc/Allocator.cpp
js/src/gc/GC.cpp
js/src/gc/GC.h
js/src/gc/GCRuntime.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jsapi.cpp
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -459,19 +459,19 @@ namespace JS {
   D(TOO_MUCH_WASM_MEMORY, 23)              \
   D(DISABLE_GENERATIONAL_GC, 24)           \
   D(FINISH_GC, 25)                         \
   D(PREPARE_FOR_TRACING, 26)               \
   D(INCREMENTAL_ALLOC_TRIGGER, 27)         \
   D(FULL_CELL_PTR_STR_BUFFER, 28)          \
   D(TOO_MUCH_JIT_CODE, 29)                 \
   D(FULL_CELL_PTR_BIGINT_BUFFER, 30)       \
+  D(INIT_SELF_HOSTING, 31)                 \
                                            \
   /* These are reserved for future use. */ \
-  D(RESERVED7, 31)                         \
   D(RESERVED8, 32)                         \
                                            \
   /* Reasons from Firefox */               \
   D(DOM_WINDOW_UTILS, 33)                  \
   D(COMPONENT_UTILS, 34)                   \
   D(MEM_PRESSURE, 35)                      \
   D(CC_WAITING, 36)                        \
   D(CC_FORCED, 37)                         \
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -406,16 +406,18 @@ bool GCRuntime::checkAllocatorState(JSCo
 
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
   MOZ_ASSERT_IF(cx->zone()->isAtomsZone(),
                 kind == AllocKind::ATOM || kind == AllocKind::FAT_INLINE_ATOM ||
                     kind == AllocKind::SYMBOL || kind == AllocKind::JITCODE ||
                     kind == AllocKind::SCOPE);
   MOZ_ASSERT_IF(!cx->zone()->isAtomsZone(),
                 kind != AllocKind::ATOM && kind != AllocKind::FAT_INLINE_ATOM);
+  MOZ_ASSERT_IF(cx->zone()->isSelfHostingZone(),
+                !rt->parentRuntime && !selfHostingZoneFrozen);
   MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
 #endif
 
   // Crash if we perform a GC action when it is not safe.
   if (allowGC && !cx->suppressGC) {
     cx->verifyIsSafeToGC();
   }
 
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -1262,16 +1262,31 @@ bool GCRuntime::init(uint32_t maxbytes) 
 
   if (!initSweepActions()) {
     return false;
   }
 
   return true;
 }
 
+void GCRuntime::freezeSelfHostingZone() {
+  MOZ_ASSERT(!selfHostingZoneFrozen);
+  MOZ_ASSERT(!isIncrementalGCInProgress());
+
+  for (ZonesIter zone(this, WithAtoms); !zone.done(); zone.next()) {
+    MOZ_ASSERT(!zone->isGCScheduled());
+    if (zone->isSelfHostingZone()) {
+      zone->scheduleGC();
+    }
+  }
+
+  gc(GC_SHRINK, JS::GCReason::INIT_SELF_HOSTING);
+  selfHostingZoneFrozen = true;
+}
+
 void GCRuntime::finish() {
   // Wait for nursery background free to end and disable it to release memory.
   if (nursery().isEnabled()) {
     nursery().disable();
   }
 
   // Wait until the background finalization and allocation stops and the
   // helper thread shuts down before we forcefully release any remaining GC
@@ -1708,18 +1723,26 @@ AutoDisableCompactingGC::AutoDisableComp
   }
 }
 
 AutoDisableCompactingGC::~AutoDisableCompactingGC() {
   MOZ_ASSERT(cx->compactingDisabledCount > 0);
   --cx->compactingDisabledCount;
 }
 
-static bool CanRelocateZone(Zone* zone) {
-  return !zone->isAtomsZone() && !zone->isSelfHostingZone();
+bool GCRuntime::canRelocateZone(Zone* zone) const {
+  if (zone->isAtomsZone()) {
+    return false;
+  }
+
+  if (zone->isSelfHostingZone() && selfHostingZoneFrozen) {
+    return false;
+  }
+
+  return true;
 }
 
 Arena* ArenaList::removeRemainingArenas(Arena** arenap) {
   // This is only ever called to remove arenas that are after the cursor, so
   // we don't need to update it.
 #ifdef DEBUG
   for (Arena* arena = *arenap; arena; arena = arena->next) {
     MOZ_ASSERT(cursorp_ != &arena->next);
@@ -2022,17 +2045,17 @@ bool ArenaLists::relocateArenas(Arena*& 
 }
 
 bool GCRuntime::relocateArenas(Zone* zone, JS::GCReason reason,
                                Arena*& relocatedListOut,
                                SliceBudget& sliceBudget) {
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT_MOVE);
 
   MOZ_ASSERT(!zone->isPreservingCode());
-  MOZ_ASSERT(CanRelocateZone(zone));
+  MOZ_ASSERT(canRelocateZone(zone));
 
   js::CancelOffThreadIonCompile(rt, JS::Zone::Compact);
 
   if (!zone->arenas.relocateArenas(relocatedListOut, reason, sliceBudget,
                                    stats())) {
     return false;
   }
 
@@ -3845,17 +3868,17 @@ bool GCRuntime::prepareZonesForCollectio
 
   for (ZonesIter zone(this, WithAtoms); !zone.done(); zone.next()) {
     /* Set up which zones will be collected. */
     bool shouldCollect = ShouldCollectZone(zone, reason);
     if (shouldCollect) {
       MOZ_ASSERT(zone->canCollect());
       any = true;
       zone->changeGCState(Zone::NoGC, Zone::MarkBlackOnly);
-    } else {
+    } else if (zone->canCollect()) {
       *isFullOut = false;
     }
 
     zone->setWasCollected(shouldCollect);
     zone->setPreservingCode(false);
   }
 
   // Discard JIT code more aggressively if the process is approaching its
@@ -3915,33 +3938,33 @@ void GCRuntime::relazifyFunctionsForShri
     RelazifyFunctions(zone, AllocKind::FUNCTION);
     RelazifyFunctions(zone, AllocKind::FUNCTION_EXTENDED);
   }
 }
 
 void GCRuntime::purgeShapeCachesForShrinkingGC() {
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE_SHAPE_CACHES);
   for (GCZonesIter zone(this); !zone.done(); zone.next()) {
-    if (!CanRelocateZone(zone) || zone->keepShapeCaches()) {
+    if (!canRelocateZone(zone) || zone->keepShapeCaches()) {
       continue;
     }
     for (auto baseShape = zone->cellIterUnsafe<BaseShape>(); !baseShape.done();
          baseShape.next()) {
       baseShape->maybePurgeCache(rt->defaultFreeOp());
     }
   }
 }
 
 // The debugger keeps track of the URLs for the sources of each realm's scripts.
 // These URLs are purged on shrinking GCs.
 void GCRuntime::purgeSourceURLsForShrinkingGC() {
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE_SOURCE_URLS);
   for (GCZonesIter zone(this); !zone.done(); zone.next()) {
     // URLs are not tracked for realms in the system zone.
-    if (!CanRelocateZone(zone) || zone->isSystem) {
+    if (!canRelocateZone(zone) || zone->isSystem) {
       continue;
     }
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
       for (RealmsInCompartmentIter realm(comp); !realm.done(); realm.next()) {
         GlobalObject* global = realm.get()->unsafeUnbarrieredMaybeGlobal();
         if (global) {
           global->clearSourceURLSHolder();
         }
@@ -6125,17 +6148,17 @@ void GCRuntime::endSweepPhase(bool destr
 void GCRuntime::beginCompactPhase() {
   MOZ_ASSERT(!isBackgroundSweeping());
   assertBackgroundSweepingFinished();
 
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT);
 
   MOZ_ASSERT(zonesToMaybeCompact.ref().isEmpty());
   for (GCZonesIter zone(this); !zone.done(); zone.next()) {
-    if (CanRelocateZone(zone)) {
+    if (canRelocateZone(zone)) {
       zonesToMaybeCompact.ref().append(zone);
     }
   }
 
   MOZ_ASSERT(!relocatedArenasToRelease);
   startedCompacting = true;
 }
 
--- a/js/src/gc/GC.h
+++ b/js/src/gc/GC.h
@@ -132,16 +132,18 @@ namespace gc {
 void FinishGC(JSContext* cx, JS::GCReason = JS::GCReason::FINISH_GC);
 
 /*
  * Merge all contents of source into target. This can only be used if source is
  * the only realm in its zone.
  */
 void MergeRealms(JS::Realm* source, JS::Realm* target);
 
+void CollectSelfHostingZone(JSContext* cx);
+
 enum VerifierType { PreBarrierVerifier };
 
 #ifdef JS_GC_ZEAL
 
 extern const char ZealModeHelpText[];
 
 /* Check that write barriers have been used correctly. See gc/Verifier.cpp. */
 void VerifyBarriers(JSRuntime* rt, VerifierType type);
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -256,16 +256,19 @@ class GCRuntime {
  public:
   explicit GCRuntime(JSRuntime* rt);
   MOZ_MUST_USE bool init(uint32_t maxbytes);
   void finishRoots();
   void finish();
 
   JS::HeapState heapState() const { return heapState_; }
 
+  void freezeSelfHostingZone();
+  bool isSelfHostingZoneFrozen() const { return selfHostingZoneFrozen; }
+
   inline bool hasZealMode(ZealMode mode);
   inline void clearZealMode(ZealMode mode);
   inline bool upcomingZealousGC();
   inline bool needZealousGC();
   inline bool hasIncrementalTwoSliceZealMode();
 
   MOZ_MUST_USE bool addRoot(Value* vp, const char* name);
   void removeRoot(Value* vp);
@@ -729,16 +732,17 @@ class GCRuntime {
   bool shouldCompact();
   void beginCompactPhase();
   IncrementalProgress compactPhase(JS::GCReason reason,
                                    SliceBudget& sliceBudget,
                                    AutoGCSession& session);
   void endCompactPhase();
   void sweepTypesAfterCompacting(Zone* zone);
   void sweepZoneAfterCompacting(MovingTracer* trc, Zone* zone);
+  bool canRelocateZone(Zone* zone) const;
   MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::GCReason reason,
                                    Arena*& relocatedListOut,
                                    SliceBudget& sliceBudget);
   void updateTypeDescrObjects(MovingTracer* trc, Zone* zone);
   void updateCellPointers(Zone* zone, AllocKinds kinds, size_t bgTaskCount);
   void updateAllCellPointers(MovingTracer* trc, Zone* zone);
   void updateZonePointersToRelocatedCells(Zone* zone);
   void updateRuntimePointersToRelocatedCells(AutoGCSession& session);
@@ -857,16 +861,22 @@ class GCRuntime {
    *   javascript.options.mem.gc_incremental.
    */
   MainThreadData<JSGCMode> mode;
 
   mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
                   mozilla::recordreplay::Behavior::DontPreserve>
       numActiveZoneIters;
 
+  /*
+   * The self hosting zone is collected once after initialization. We don't
+   * allow allocation after this point and we don't collect it again.
+   */
+  WriteOnceData<bool> selfHostingZoneFrozen;
+
   /* During shutdown, the GC needs to clean up every possible object. */
   MainThreadData<bool> cleanUpEverything;
 
   // Gray marking must be done after all black marking is complete. However,
   // we do not have write barriers on XPConnect roots. Therefore, XPConnect
   // roots must be accumulated in the first slice of incremental GC. We
   // accumulate these roots in each zone's gcGrayRoots vector and then mark
   // them later, after black marking is complete for each compartment. This
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -197,17 +197,16 @@ Zone::~Zone() {
 
 bool Zone::init(bool isSystemArg) {
   isSystem = isSystemArg;
   regExps_.ref() = make_unique<RegExpZone>(this);
   return regExps_.ref() && gcWeakKeys().init() && gcNurseryWeakKeys().init();
 }
 
 void Zone::setNeedsIncrementalBarrier(bool needs) {
-  MOZ_ASSERT_IF(needs, canCollect());
   needsIncrementalBarrier_ = needs;
 }
 
 void Zone::beginSweepTypes() { types.beginSweep(); }
 
 template <class Pred>
 static void EraseIf(js::gc::WeakEntryVector& entries, Pred pred) {
   auto* begin = entries.begin();
@@ -493,16 +492,21 @@ bool Zone::hasMarkedRealms() {
 
 bool Zone::canCollect() {
   // The atoms zone cannot be collected while off-thread parsing is taking
   // place.
   if (isAtomsZone()) {
     return !runtimeFromAnyThread()->hasHelperThreadZones();
   }
 
+  // We don't collect the self hosting zone after it has been initialized.
+  if (isSelfHostingZone()) {
+    return !runtimeFromAnyThread()->gc.isSelfHostingZoneFrozen();
+  }
+
   // Zones that will be or are currently used by other threads cannot be
   // collected.
   return !createdForHelperThread();
 }
 
 void Zone::notifyObservingDebuggers() {
   AutoAssertNoGC nogc;
   MOZ_ASSERT(JS::RuntimeHeapIsCollecting(),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -271,17 +271,18 @@ class Zone : public js::ZoneAllocator, p
 
   // Whether this zone can currently be collected. This doesn't take account
   // of AutoKeepAtoms for the atoms zone.
   bool canCollect();
 
   void changeGCState(GCState prev, GCState next) {
     MOZ_ASSERT(RuntimeHeapIsBusy());
     MOZ_ASSERT(gcState() == prev);
-    MOZ_ASSERT_IF(next != NoGC, canCollect());
+    MOZ_ASSERT(canCollect());
+    //MOZ_ASSERT_IF(next != NoGC, canCollect());
     gcState_ = next;
   }
 
   bool isCollecting() const {
     MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtimeFromMainThread()));
     return isCollectingFromAnyThread();
   }
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -455,18 +455,24 @@ JS_PUBLIC_API bool JS::InitSelfHostedCod
     return false;
   }
 #endif
 
   if (!rt->initSelfHosting(cx)) {
     return false;
   }
 
-  if (!rt->parentRuntime && !rt->initMainAtomsTables(cx)) {
-    return false;
+  if (!rt->parentRuntime) {
+    if (!rt->initMainAtomsTables(cx)) {
+      return false;
+    }
+
+    // Garbage collect the self hosting zone once when it is created. It should
+    // not be modified after this point.
+    rt->gc.freezeSelfHostingZone();
   }
 
   return true;
 }
 
 JS_PUBLIC_API const char* JS_GetImplementationVersion(void) {
   return "JavaScript-C" MOZILLA_VERSION;
 }