Bug 1506479 - Discard TypeScripts in Zone::discardJitCode instead of during type sweeping. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Mon, 12 Nov 2018 02:30:15 +0000
changeset 445848 9018814d9671dfe67ab073869bdeebf05b25b565
parent 445847 d4a258084934eb4bbddd24f592654387d154bb4b
child 445849 f97d54f24e0299ed1a87383ba61519c30a8c243a
child 445873 1dab4c7062aeae80016ca8043a752271b6ab8a94
push id109783
push userapavel@mozilla.com
push dateMon, 12 Nov 2018 10:17:43 +0000
treeherdermozilla-inbound@32fdd47e849a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1506479
milestone65.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 1506479 - Discard TypeScripts in Zone::discardJitCode instead of during type sweeping. r=bhackett I also had to make some small changes to relazification for XDR tests for that to pass. Differential Revision: https://phabricator.services.mozilla.com/D11587
js/src/builtin/TestingFunctions.cpp
js/src/gc/GC.cpp
js/src/gc/GCRuntime.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -935,17 +935,18 @@ IsRelazifiableFunction(JSContext* cx, un
     if (!args[0].isObject() ||
         !args[0].toObject().is<JSFunction>())
     {
         JS_ReportErrorASCII(cx, "The first argument should be a function.");
         return false;
     }
 
     JSFunction* fun = &args[0].toObject().as<JSFunction>();
-    args.rval().setBoolean(fun->hasScript() && fun->nonLazyScript()->isRelazifiable());
+    args.rval().setBoolean(fun->hasScript() &&
+                           fun->nonLazyScript()->isRelazifiableIgnoringJitCode());
     return true;
 }
 
 static bool
 InternalConst(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 0) {
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -2644,17 +2644,17 @@ Zone::prepareForCompacting()
 {
     FreeOp* fop = runtimeFromMainThread()->defaultFreeOp();
     discardJitCode(fop);
 }
 
 void
 GCRuntime::sweepTypesAfterCompacting(Zone* zone)
 {
-    zone->beginSweepTypes(releaseObservedTypes && !zone->isPreservingCode());
+    zone->beginSweepTypes();
 
     AutoClearTypeInferenceStateOnOOM oom(zone);
 
     for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
         AutoSweepTypeScript sweep(script);
     }
     for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next()) {
         AutoSweepObjectGroup sweep(group);
@@ -3961,16 +3961,20 @@ GCRuntime::waitBackgroundSweepEnd()
     }
 }
 
 bool
 GCRuntime::shouldReleaseObservedTypes()
 {
     bool releaseTypes = false;
 
+    if (cleanUpEverything) {
+        releaseTypes = true;
+    }
+
 #ifdef JS_GC_ZEAL
     if (zealModeBits != 0) {
         releaseTypes = true;
     }
 #endif
 
     /* We may miss the exact target GC due to resets. */
     if (majorGCNumber >= jitReleaseNumber) {
@@ -4550,22 +4554,22 @@ GCRuntime::prepareZonesForCollection(JS:
      */
     MOZ_ASSERT_IF(reason == JS::gcreason::DELAYED_ATOMS_GC, atomsZone->isGCMarking());
 
     /* Check that at least one zone is scheduled for collection. */
     return any;
 }
 
 static void
-DiscardJITCodeForGC(JSRuntime* rt)
+DiscardJITCodeForGC(JSRuntime* rt, bool releaseTypes)
 {
     js::CancelOffThreadIonCompile(rt, JS::Zone::Mark);
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::MARK_DISCARD_CODE);
-        zone->discardJitCode(rt->defaultFreeOp());
+        zone->discardJitCode(rt->defaultFreeOp(), /* discardBaselineCode = */ true, releaseTypes);
     }
 }
 
 static void
 RelazifyFunctionsForShrinkingGC(JSRuntime* rt)
 {
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::RELAZIFY_FUNCTIONS);
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
@@ -4662,17 +4666,17 @@ GCRuntime::beginMarkPhase(JS::gcreason::
         Maybe<AutoRunParallelTask> bufferGrayRoots;
         if (isIncremental) {
             bufferGrayRoots.emplace(rt, BufferGrayRoots, gcstats::PhaseKind::BUFFER_GRAY_ROOTS, helperLock);
         }
         AutoUnlockHelperThreadState unlock(helperLock);
 
         // Discard JIT code. For incremental collections, the sweep phase will
         // also discard JIT code.
-        DiscardJITCodeForGC(rt);
+        DiscardJITCodeForGC(rt, shouldReleaseObservedTypes());
 
         /*
          * Relazify functions after discarding JIT code (we can't relazify
          * functions with JIT code) and before the actual mark phase, so that
          * the current GC can collect the JSScripts we're unlinking here.  We do
          * this only when we're performing a shrinking GC, as too much
          * relazification can cause performance issues when we have to reparse
          * the same functions over and over.
@@ -5914,17 +5918,17 @@ GCRuntime::sweepJitDataOnMainThread(Free
             zone->discardJitCode(fop);
         }
     }
 
     {
         gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_TYPES);
         gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
         for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
-            zone->beginSweepTypes(releaseObservedTypes && !zone->isPreservingCode());
+            zone->beginSweepTypes();
         }
     }
 }
 
 using WeakCacheTaskVector = mozilla::Vector<ImmediateSweepWeakCacheTask, 0, SystemAllocPolicy>;
 
 enum WeakCacheLocation
 {
@@ -6196,18 +6200,16 @@ GCRuntime::beginSweepPhase(JS::gcreason:
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
 
     sweepOnBackgroundThread =
         reason != JS::gcreason::DESTROY_RUNTIME &&
         !gcTracer.traceEnabled() &&
         CanUseExtraThreads();
 
-    releaseObservedTypes = shouldReleaseObservedTypes();
-
     AssertNoWrappersInGrayList(rt);
     DropStringWrappers(rt);
 
     groupZonesForSweeping(reason);
 
     sweepActions->assertFinished();
 
     // We must not yield after this point until we start sweeping the first sweep
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -855,19 +855,16 @@ class GCRuntime
     MainThreadData<bool> lastMarkSlice;
 
     /* Whether it's currently safe to yield to the mutator in an incremental GC. */
     MainThreadData<bool> safeToYield;
 
     /* Whether any sweeping will take place in the separate GC helper thread. */
     MainThreadData<bool> sweepOnBackgroundThread;
 
-    /* Whether observed type information is being released in the current GC. */
-    MainThreadData<bool> releaseObservedTypes;
-
     /* Singly linked list of zones to be swept in the background. */
     HelperThreadLockData<ZoneList> backgroundSweepZones;
 
     /*
      * Free LIFO blocks are transferred to this allocator before being freed on
      * the background GC thread after sweeping.
      */
     HelperThreadLockData<LifoAlloc> blocksToFreeAfterSweeping;
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -114,19 +114,19 @@ Zone::init(bool isSystemArg)
 void
 Zone::setNeedsIncrementalBarrier(bool needs)
 {
     MOZ_ASSERT_IF(needs, canCollect());
     needsIncrementalBarrier_ = needs;
 }
 
 void
-Zone::beginSweepTypes(bool releaseTypes)
+Zone::beginSweepTypes()
 {
-    types.beginSweep(releaseTypes);
+    types.beginSweep();
 }
 
 Zone::DebuggerVector*
 Zone::getOrCreateDebuggers(JSContext* cx)
 {
     if (debuggers) {
         return debuggers;
     }
@@ -204,17 +204,17 @@ Zone::sweepBreakpoints(FreeOp* fop)
 void
 Zone::sweepWeakMaps()
 {
     /* Finalize unreachable (key,value) pairs in all weak maps. */
     WeakMapBase::sweepZone(this);
 }
 
 void
-Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode)
+Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode, bool releaseTypes)
 {
     if (!jitZone()) {
         return;
     }
 
     if (isPreservingCode()) {
         return;
     }
@@ -254,16 +254,22 @@ Zone::discardJitCode(FreeOp* fop, bool d
 
         /*
          * Make it impossible to use the control flow graphs cached on the
          * BaselineScript. They get deleted.
          */
         if (script->hasBaselineScript()) {
             script->baselineScript()->setControlFlowGraph(nullptr);
         }
+
+        // Try to release the script's TypeScript. This should happen last
+        // because we can't do this when the script still has JIT code.
+        if (releaseTypes) {
+            script->maybeReleaseTypes();
+        }
     }
 
     /*
      * When scripts contains pointers to nursery things, the store buffer
      * can contain entries that point into the optimized stub space. Since
      * this method can be called outside the context of a GC, this situation
      * could result in us trying to mark invalid store buffer entries.
      *
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -190,17 +190,18 @@ class Zone : public JS::shadow::Zone,
     }
     void clearUsedByHelperThread() {
         MOZ_ASSERT(helperThreadUse_ != HelperThreadUse::None);
         helperThreadUse_ = HelperThreadUse::None;
     }
 
     void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
 
-    void discardJitCode(js::FreeOp* fop, bool discardBaselineCode = true);
+    void discardJitCode(js::FreeOp* fop, bool discardBaselineCode = true,
+                        bool releaseTypes = false);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* typePool,
                                 size_t* regexpZone,
                                 size_t* jitZone,
                                 size_t* baselineStubsOptimized,
                                 size_t* cachedCFG,
                                 size_t* uniqueIdMap,
@@ -216,17 +217,17 @@ class Zone : public JS::shadow::Zone,
     js::gc::ZoneCellIter<T> cellIter(Args&&... args) {
         return js::gc::ZoneCellIter<T>(const_cast<Zone*>(this), std::forward<Args>(args)...);
     }
 
     MOZ_MUST_USE void* onOutOfMemory(js::AllocFunction allocFunc, size_t nbytes,
                                      void* reallocPtr = nullptr);
     void reportAllocationOverflow();
 
-    void beginSweepTypes(bool releaseTypes);
+    void beginSweepTypes();
 
     bool hasMarkedRealms();
 
     void scheduleGC() { MOZ_ASSERT(!RuntimeHeapIsBusy()); gcScheduled_ = true; }
     void unscheduleGC() { gcScheduled_ = false; }
     bool isGCScheduled() { return gcScheduled_; }
 
     void setPreservingCode(bool preserving) { gcPreserveCode_ = preserving; }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -247,17 +247,18 @@ XDRLazyClosedOverBindings(XDRState<mode>
 }
 
 // Code the missing part needed to re-create a LazyScript from a JSScript.
 template<XDRMode mode>
 static XDRResult
 XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript script,
                       HandleScope enclosingScope, MutableHandle<LazyScript*> lazy)
 {
-    MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript());
+    MOZ_ASSERT_IF(mode == XDR_ENCODE,
+                  script->isRelazifiableIgnoringJitCode() && script->maybeLazyScript());
     MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions());
 
     JSContext* cx = xdr->cx();
 
     uint64_t packedFields;
     {
         uint32_t sourceStart = script->sourceStart();
         uint32_t sourceEnd = script->sourceEnd();
@@ -420,17 +421,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
 
         if (script->analyzedArgsUsage() && script->needsArgsObj()) {
             scriptBits |= (1 << NeedsArgsObj);
         }
         MOZ_ASSERT_IF(sourceObjectArg, sourceObjectArg->source() == script->scriptSource());
         if (!sourceObjectArg) {
             scriptBits |= (1 << OwnSource);
         }
-        if (script->isRelazifiable()) {
+        if (script->isRelazifiableIgnoringJitCode()) {
             scriptBits |= (1 << HasLazyScript);
         }
     }
 
     MOZ_TRY(xdr->codeUint32(&prologueLength));
 
     // To fuse allocations, we need lengths of all embedded arrays early.
     MOZ_TRY(xdr->codeUint32(&natoms));
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -2297,23 +2297,29 @@ class JSScript : public js::gc::TenuredC
     }
     static constexpr size_t offsetOfJitCodeSkipArgCheck() {
         return offsetof(JSScript, jitCodeSkipArgCheck_);
     }
     uint8_t* jitCodeRaw() const {
         return jitCodeRaw_;
     }
 
-    bool isRelazifiable() const {
-        return (selfHosted() || lazyScript) && !hasInnerFunctions() && !types_ &&
+    // We don't relazify functions with a TypeScript or JIT code, but some
+    // callers (XDR, testing functions) want to know whether this script is
+    // relazifiable ignoring (or after) discarding JIT code.
+    bool isRelazifiableIgnoringJitCode() const {
+        return (selfHosted() || lazyScript) && !hasInnerFunctions() &&
                !isGenerator() && !isAsync() &&
                !isDefaultClassConstructor() &&
-               !hasBaselineScript() && !hasAnyIonScript() &&
                !hasFlag(MutableFlags::DoNotRelazify);
     }
+    bool isRelazifiable() const {
+        MOZ_ASSERT_IF(hasBaselineScript() || hasIonScript(), types_);
+        return isRelazifiableIgnoringJitCode() && !types_;
+    }
     void setLazyScript(js::LazyScript* lazy) {
         lazyScript = lazy;
     }
     js::LazyScript* maybeLazyScript() {
         return lazyScript;
     }
 
     /*
@@ -2408,19 +2414,19 @@ class JSScript : public js::gc::TenuredC
      * top-level of a file.
      */
     bool isTopLevel() { return code() && !functionNonDelazifying(); }
 
     /* Ensure the script has a TypeScript. */
     inline bool ensureHasTypes(JSContext* cx, js::AutoKeepTypeScripts&);
 
     inline js::TypeScript* types(const js::AutoSweepTypeScript& sweep);
-
     inline bool typesNeedsSweep() const;
 
+    void maybeReleaseTypes();
     void sweepTypes(const js::AutoSweepTypeScript& sweep);
 
     inline js::GlobalObject& global() const;
     js::GlobalObject& uninlinedGlobal() const;
 
     uint32_t bodyScopeIndex() const {
         return bodyScopeIndex_;
     }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -4868,50 +4868,49 @@ JSScript::sweepTypes(const js::AutoSweep
                 continue;
             }
             inlinedCompilations[dest] = inlinedCompilations[i];
             dest++;
         }
         inlinedCompilations.shrinkTo(dest);
     }
 
-    // Destroy all type information attached to the script if desired. We can
-    // only do this if nothing has been compiled for the script, which will be
-    // the case unless the script has been compiled since we started sweeping.
-    if (types.sweepReleaseTypes &&
-        !types.keepTypeScripts &&
-        !hasBaselineScript() &&
-        !hasIonScript())
-    {
-        types_->destroy();
-        types_ = nullptr;
-
-        // Freeze constraints on stack type sets need to be regenerated the
-        // next time the script is analyzed.
-        clearFlag(MutableFlags::HasFreezeConstraints);
-
-        return;
-    }
-
     unsigned num = TypeScript::NumTypeSets(this);
     StackTypeSet* typeArray = types_->typeArray();
 
     // Remove constraints and references to dead objects from stack type sets.
     for (unsigned i = 0; i < num; i++) {
         typeArray[i].sweep(sweep, zone());
     }
 
     if (zone()->types.hadOOMSweepingTypes()) {
         // It's possible we OOM'd while copying freeze constraints, so they
         // need to be regenerated.
         clearFlag(MutableFlags::HasFreezeConstraints);
     }
 }
 
 void
+JSScript::maybeReleaseTypes()
+{
+    if (!types_ || zone()->types.keepTypeScripts || hasBaselineScript()) {
+        return;
+    }
+
+    MOZ_ASSERT(!hasIonScript());
+
+    types_->destroy();
+    types_ = nullptr;
+
+    // Freeze constraints on stack type sets need to be regenerated the
+    // next time the script is analyzed.
+    clearFlag(MutableFlags::HasFreezeConstraints);
+}
+
+void
 TypeScript::destroy()
 {
     js_delete(this);
 }
 
 void
 Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                              size_t* typePool,
@@ -4945,50 +4944,44 @@ Zone::addSizeOfIncludingThis(mozilla::Ma
 }
 
 TypeZone::TypeZone(Zone* zone)
   : zone_(zone),
     typeLifoAlloc_(zone, (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     currentCompilationId_(zone),
     generation(zone, 0),
     sweepTypeLifoAlloc(zone, (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
-    sweepReleaseTypes(zone, false),
     sweepingTypes(zone, false),
     oomSweepingTypes(zone, false),
     keepTypeScripts(zone, false),
     activeAnalysis(zone, nullptr)
 {
 }
 
 TypeZone::~TypeZone()
 {
     MOZ_RELEASE_ASSERT(!sweepingTypes);
     MOZ_ASSERT(!keepTypeScripts);
 }
 
 void
-TypeZone::beginSweep(bool releaseTypes)
+TypeZone::beginSweep()
 {
     MOZ_ASSERT(zone()->isGCSweepingOrCompacting());
-    MOZ_ASSERT(!sweepReleaseTypes);
-
-    sweepReleaseTypes = releaseTypes;
 
     // Clear the analysis pool, but don't release its data yet. While sweeping
     // types any live data will be allocated into the pool.
     sweepTypeLifoAlloc.ref().steal(&typeLifoAlloc());
 
     generation = !generation;
 }
 
 void
 TypeZone::endSweep(JSRuntime* rt)
 {
-    sweepReleaseTypes = false;
-
     rt->gc.freeAllLifoBlocksAfterSweeping(&sweepTypeLifoAlloc.ref());
 }
 
 void
 TypeZone::clearAllNewScriptsOnOOM()
 {
     for (auto iter = zone()->cellIter<ObjectGroup>(); !iter.done(); iter.next()) {
         ObjectGroup* group = iter;
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -374,20 +374,16 @@ class TypeZone
   public:
     // Current generation for sweeping.
     ZoneOrGCTaskOrIonCompileData<uint32_t> generation;
 
     // During incremental sweeping, allocator holding the old type information
     // for the zone.
     ZoneData<LifoAlloc> sweepTypeLifoAlloc;
 
-    // During incremental sweeping, whether to try to destroy all type
-    // information attached to scripts.
-    ZoneData<bool> sweepReleaseTypes;
-
     ZoneData<bool> sweepingTypes;
     ZoneData<bool> oomSweepingTypes;
 
     ZoneData<bool> keepTypeScripts;
 
     // The topmost AutoEnterAnalysis on the stack, if there is one.
     ZoneData<AutoEnterAnalysis*> activeAnalysis;
 
@@ -398,17 +394,17 @@ class TypeZone
 
     LifoAlloc& typeLifoAlloc() {
 #ifdef JS_CRASH_DIAGNOSTICS
         MOZ_RELEASE_ASSERT(CurrentThreadCanAccessZone(zone_));
 #endif
         return typeLifoAlloc_.ref();
     }
 
-    void beginSweep(bool releaseTypes);
+    void beginSweep();
     void endSweep(JSRuntime* rt);
     void clearAllNewScriptsOnOOM();
 
     /* Mark a script as needing recompilation once inference has finished. */
     void addPendingRecompile(JSContext* cx, const RecompileInfo& info);
     void addPendingRecompile(JSContext* cx, JSScript* script);
 
     void processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles);