Bug 995284 - Make the GC more deterministic in the shell r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Wed, 13 Aug 2014 10:05:33 +0100
changeset 199285 18b669a70e155e885925374716cabd9b062bb8b6
parent 199284 b0bd857a26778e89e38ccf8df4406432f9c03301
child 199286 c771f243139539a4ed14f7d82f46d07f79a9f342
push id27303
push userryanvm@gmail.com
push dateWed, 13 Aug 2014 19:31:02 +0000
treeherdermozilla-central@7d43251dea52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs995284
milestone34.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 995284 - Make the GC more deterministic in the shell r=terrence
js/src/gc/GCRuntime.h
js/src/jsapi.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -269,17 +269,18 @@ class GCRuntime
     // zero. (It will require some care to make sure the two variables stay in
     // sync.)
     bool isFJMinorCollecting() { return fjCollectionCounter > 0; }
     void incFJMinorCollecting() { fjCollectionCounter++; }
     void decFJMinorCollecting() { fjCollectionCounter--; }
 
     bool triggerGC(JS::gcreason::Reason reason);
     bool triggerZoneGC(Zone *zone, JS::gcreason::Reason reason);
-    void maybeGC(Zone *zone);
+    bool maybeGC(Zone *zone);
+    void maybePeriodicFullGC();
     void minorGC(JS::gcreason::Reason reason);
     void minorGC(JSContext *cx, JS::gcreason::Reason reason);
     void gcIfNeeded(JSContext *cx);
     void collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
                  JS::gcreason::Reason reason);
     void gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
     void runDebugGC();
     inline void poke();
@@ -482,17 +483,17 @@ class GCRuntime
     template <class ZoneIterT, class CompartmentIterT> void markGrayReferences();
     void markGrayReferencesInCurrentGroup();
     void beginSweepPhase(bool lastGC);
     void findZoneGroups();
     bool findZoneEdgesForWeakMaps();
     void getNextZoneGroup();
     void endMarkingZoneGroup();
     void beginSweepingZoneGroup();
-    bool releaseObservedTypes();
+    bool shouldReleaseObservedTypes();
     void endSweepingZoneGroup();
     bool sweepPhase(SliceBudget &sliceBudget);
     void endSweepPhase(JSGCInvocationKind gckind, bool lastGC);
     void sweepZones(FreeOp *fop, bool lastGC);
     void decommitArenasFromAvailableList(Chunk **availableListHeadp);
     void decommitArenas();
     void expireChunksAndArenas(bool shouldShrink);
     void sweepBackgroundThings(bool onBackgroundThread);
@@ -562,17 +563,16 @@ class GCRuntime
      * Number of the committed arenas in all GC chunks including empty chunks.
      */
     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire>   numArenasFreeCommitted;
     void                  *verifyPreData;
     void                  *verifyPostData;
     bool                  chunkAllocationSinceLastGC;
     int64_t               nextFullGCTime;
     int64_t               lastGCTime;
-    int64_t               jitReleaseTime;
 
     JSGCMode              mode;
 
     uint64_t              decommitThreshold;
 
     /* During shutdown, the GC needs to clean up every possible object. */
     bool                  cleanUpEverything;
 
@@ -584,16 +584,22 @@ class GCRuntime
 
     /*
      * These flags must be kept separate so that a thread requesting a
      * compartment GC doesn't cancel another thread's concurrent request for a
      * full GC.
      */
     volatile uintptr_t    isNeeded;
 
+    /* Incremented at the start of every major GC. */
+    uint64_t              majorGCNumber;
+
+    /* The major GC number at which to release observed type information. */
+    uint64_t              jitReleaseNumber;
+
     /* Incremented on every GC slice. */
     uint64_t              number;
 
     /* The number at the time of the most recent GC's first slice. */
     uint64_t              startNumber;
 
     /* Whether the currently running GC can finish in multiple slices. */
     bool                  isIncremental;
@@ -619,16 +625,19 @@ class GCRuntime
     js::gc::State         incrementalState;
 
     /* Indicates that the last incremental slice exhausted the mark stack. */
     bool                  lastMarkSlice;
 
     /* Whether any sweeping will take place in the separate GC helper thread. */
     bool                  sweepOnBackgroundThread;
 
+    /* Whether observed type information is being released in the current GC. */
+    bool                  releaseObservedTypes;
+
     /* Whether any black->gray edges were found during marking. */
     bool                  foundBlackGrayEdges;
 
     /* List head of zones to be swept in the background. */
     JS::Zone              *sweepingZones;
 
     /* Index of current zone group (for stats). */
     unsigned              zoneGroupIndex;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1874,17 +1874,19 @@ JS_GC(JSRuntime *rt)
     AssertHeapIsIdle(rt);
     JS::PrepareForFullGC(rt);
     GC(rt, GC_NORMAL, JS::gcreason::API);
 }
 
 JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext *cx)
 {
-    MaybeGC(cx);
+    GCRuntime &gc = cx->runtime()->gc;
+    if (!gc.maybeGC(cx->zone()))
+        gc.maybePeriodicFullGC();
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
     rt->gc.setGCCallback(cb, data);
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1107,22 +1107,23 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
     userAvailableChunkListHead(nullptr),
     maxMallocBytes(0),
     numArenasFreeCommitted(0),
     verifyPreData(nullptr),
     verifyPostData(nullptr),
     chunkAllocationSinceLastGC(false),
     nextFullGCTime(0),
     lastGCTime(0),
-    jitReleaseTime(0),
     mode(JSGC_MODE_INCREMENTAL),
     decommitThreshold(32 * 1024 * 1024),
     cleanUpEverything(false),
     grayBitsValid(false),
     isNeeded(0),
+    majorGCNumber(0),
+    jitReleaseNumber(0),
     number(0),
     startNumber(0),
     isFull(false),
     triggerReason(JS::gcreason::NO_REASON),
 #ifdef DEBUG
     disableStrictProxyCheckingCount(0),
 #endif
     incrementalState(gc::NO_INCREMENTAL),
@@ -1243,18 +1244,21 @@ GCRuntime::initZeal()
     }
 
     setZeal(zeal, frequency);
     return true;
 }
 
 #endif
 
-/* Lifetime for type sets attached to scripts containing observed types. */
-static const int64_t JIT_SCRIPT_RELEASE_TYPES_INTERVAL = 60 * 1000 * 1000;
+/*
+ * Lifetime in number of major GCs for type sets attached to scripts containing
+ * observed types.
+ */
+static const uint64_t JIT_SCRIPT_RELEASE_TYPES_PERIOD = 20;
 
 bool
 GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
 {
     InitMemorySubsystem();
 
     lock = PR_NewLock();
     if (!lock)
@@ -1271,19 +1275,17 @@ GCRuntime::init(uint32_t maxbytes, uint3
 
     /*
      * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes
      * for default backward API compatibility.
      */
     tunables.setParameter(JSGC_MAX_BYTES, maxbytes);
     setMaxMallocBytes(maxbytes);
 
-#ifndef JS_MORE_DETERMINISTIC
-    jitReleaseTime = PRMJ_Now() + JIT_SCRIPT_RELEASE_TYPES_INTERVAL;
-#endif
+    jitReleaseNumber = majorGCNumber + JIT_SCRIPT_RELEASE_TYPES_PERIOD;
 
 #ifdef JSGC_GENERATIONAL
     if (!nursery.init(maxNurseryBytes))
         return false;
 
     if (!nursery.isEnabled()) {
         JS_ASSERT(nursery.nurserySize() == 0);
         ++rt->gc.generationalDisabled;
@@ -2410,57 +2412,61 @@ GCRuntime::triggerZoneGC(Zone *zone, JS:
         return true;
     }
 
     PrepareZoneForGC(zone);
     requestInterrupt(reason);
     return true;
 }
 
-void
-js::MaybeGC(JSContext *cx)
-{
-    cx->runtime()->gc.maybeGC(cx->zone());
-}
-
-void
+bool
 GCRuntime::maybeGC(Zone *zone)
 {
     JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
 #ifdef JS_GC_ZEAL
     if (zealMode == ZealAllocValue || zealMode == ZealPokeValue) {
         JS::PrepareForFullGC(rt);
         GC(rt, GC_NORMAL, JS::gcreason::MAYBEGC);
-        return;
+        return true;
     }
 #endif
 
     if (isNeeded) {
         GCSlice(rt, GC_NORMAL, JS::gcreason::MAYBEGC);
-        return;
+        return true;
     }
 
     double factor = schedulingState.inHighFrequencyGCMode() ? 0.85 : 0.9;
     if (zone->usage.gcBytes() > 1024 * 1024 &&
         zone->usage.gcBytes() >= factor * zone->threshold.gcTriggerBytes() &&
         incrementalState == NO_INCREMENTAL &&
         !isBackgroundSweeping())
     {
         PrepareZoneForGC(zone);
         GCSlice(rt, GC_NORMAL, JS::gcreason::MAYBEGC);
-        return;
-    }
-
-#ifndef JS_MORE_DETERMINISTIC
+        return true;
+    }
+
+    return false;
+}
+
+void
+GCRuntime::maybePeriodicFullGC()
+{
     /*
+     * Trigger a periodic full GC.
+     *
+     * This is a source of non-determinism, but is not called from the shell.
+     *
      * Access to the counters and, on 32 bit, setting gcNextFullGCTime below
      * is not atomic and a race condition could trigger or suppress the GC. We
      * tolerate this.
      */
+#ifndef JS_MORE_DETERMINISTIC
     int64_t now = PRMJ_Now();
     if (nextFullGCTime && nextFullGCTime <= now) {
         if (chunkAllocationSinceLastGC ||
             numArenasFreeCommitted > decommitThreshold)
         {
             JS::PrepareForFullGC(rt);
             GCSlice(rt, GC_SHRINK, JS::gcreason::MAYBEGC);
         } else {
@@ -2876,32 +2882,31 @@ GCHelperState::doSweep()
 
 bool
 GCHelperState::onBackgroundThread()
 {
     return PR_GetCurrentThread() == thread;
 }
 
 bool
-GCRuntime::releaseObservedTypes()
+GCRuntime::shouldReleaseObservedTypes()
 {
     bool releaseTypes = false;
 
 #ifdef JS_GC_ZEAL
     if (zealMode != 0)
         releaseTypes = true;
 #endif
 
-#ifndef JS_MORE_DETERMINISTIC
-    int64_t now = PRMJ_Now();
-    if (now >= jitReleaseTime)
+    /* We may miss the exact target GC due to resets. */
+    if (majorGCNumber >= jitReleaseNumber)
         releaseTypes = true;
+
     if (releaseTypes)
-        jitReleaseTime = now + JIT_SCRIPT_RELEASE_TYPES_INTERVAL;
-#endif
+        jitReleaseNumber = majorGCNumber + JIT_SCRIPT_RELEASE_TYPES_PERIOD;
 
     return releaseTypes;
 }
 
 /*
  * 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,
@@ -4152,34 +4157,33 @@ GCRuntime::beginSweepingZoneGroup()
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
 
         for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_DISCARD_CODE);
             zone->discardJitCode(&fop);
         }
 
-        bool releaseTypes = releaseObservedTypes();
         for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) {
             gcstats::AutoSCC scc(stats, zoneGroupIndex);
-            c->sweep(&fop, releaseTypes && !c->zone()->isPreservingCode());
+            c->sweep(&fop, releaseObservedTypes && !c->zone()->isPreservingCode());
         }
 
         for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
             gcstats::AutoSCC scc(stats, zoneGroupIndex);
 
             // If there is an OOM while sweeping types, the type information
             // will be deoptimized so that it is still correct (i.e.
             // overapproximates the possible types in the zone), but the
             // constraints might not have been triggered on the deoptimization
             // or even copied over completely. In this case, destroy all JIT
             // code and new script information in the zone, the only things
             // whose correctness depends on the type constraints.
             bool oom = false;
-            zone->sweep(&fop, releaseTypes && !zone->isPreservingCode(), &oom);
+            zone->sweep(&fop, releaseObservedTypes && !zone->isPreservingCode(), &oom);
 
             if (oom) {
                 zone->setPreservingCode(false);
                 zone->discardJitCode(&fop);
                 zone->types.clearAllNewScriptsOnOOM();
             }
         }
     }
@@ -4259,16 +4263,18 @@ GCRuntime::beginSweepPhase(bool lastGC)
     JS_ASSERT(!abortSweepAfterCurrentGroup);
 
     computeNonIncrementalMarkingForValidation();
 
     gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
 
     sweepOnBackgroundThread = !lastGC && !TraceEnabled() && CanUseExtraThreads();
 
+    releaseObservedTypes = shouldReleaseObservedTypes();
+
 #ifdef DEBUG
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JS_ASSERT(!c->gcIncomingGrayPointers);
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
                 AssertNotOnGrayList(&e.front().value().get().toObject());
         }
     }
@@ -4981,16 +4987,18 @@ GCRuntime::gcCycle(bool incremental, int
     AutoDisableStoreBuffer adsb(this);
 
     AutoTraceSession session(rt, MajorCollecting);
 
     isNeeded = false;
     interFrameGC = true;
 
     number++;
+    if (incrementalState == NO_INCREMENTAL)
+        majorGCNumber++;
 
     // It's ok if threads other than the main thread have suppressGC set, as
     // they are operating on zones which will not be collected from here.
     JS_ASSERT(!rt->mainThread.suppressGC);
 
     // Assert if this is a GC unsafe region.
     JS::AutoAssertOnGC::VerifyIsSafeToGC(rt);
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -967,19 +967,16 @@ TraceRuntime(JSTracer *trc);
 extern bool
 TriggerGC(JSRuntime *rt, JS::gcreason::Reason reason);
 
 /* Must be called with GC lock taken. */
 extern bool
 TriggerZoneGC(Zone *zone, JS::gcreason::Reason reason);
 
 extern void
-MaybeGC(JSContext *cx);
-
-extern void
 ReleaseAllJITCode(FreeOp *op);
 
 /*
  * Kinds of js_GC invocation.
  */
 typedef enum JSGCInvocationKind {
     /* Normal invocation. */
     GC_NORMAL           = 0,
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -686,17 +686,17 @@ AllocateObjectForCacheHit(JSContext *cx,
             return nullptr;
         }
         return obj;
     }
 #endif
 
     JSObject *obj = AllocateObject<NoGC>(cx, kind, 0, heap);
     if (!obj && allowGC) {
-        MaybeGC(cx);
+        cx->runtime()->gc.maybeGC(cx->zone());
         return nullptr;
     }
 
     return obj;
 }
 
 inline bool
 IsInsideGGCNursery(const js::gc::Cell *cell)