Bug 903519 - Disable nursery strings in a Zone if too many get tenured, r=sfink
☠☠ backed out by 65e92478e09d ☠ ☠
authorJan de Mooij <jandemooij@gmail.com>
Mon, 02 Oct 2017 16:42:38 -0700
changeset 453165 bb2cc298a15583b524102372d42f722aae5d505a
parent 453164 cc56f32ddae8443542abcb00ff93bd535e7e7069
child 453166 3f72f8747e2938a5b0e6632818e85d79e5f670a2
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs903519
milestone59.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 903519 - Disable nursery strings in a Zone if too many get tenured, r=sfink
js/src/gc/Allocator.cpp
js/src/gc/Nursery.cpp
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jit/CompileWrappers.cpp
js/src/jit/JitCompartment.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -174,17 +174,21 @@ js::AllocateString(JSContext* cx, Initia
             ReportOutOfMemory(cx);
         return str;
     }
 
     JSRuntime* rt = cx->runtime();
     if (!rt->gc.checkAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
-    if (cx->nursery().isEnabled() && heap != TenuredHeap && cx->nursery().canAllocateStrings()) {
+    if (cx->nursery().isEnabled() &&
+        heap != TenuredHeap &&
+        cx->nursery().canAllocateStrings() &&
+        cx->zone()->allocNurseryStrings)
+    {
         auto str = static_cast<StringAllocT*>(rt->gc.tryNewNurseryString<allowGC>(cx, size, kind));
         if (str)
             return str;
 
         // Our most common non-jit allocation path is NoGC; thus, if we fail the
         // alloc and cannot GC, we *must* return nullptr here so that the caller
         // will do a CanGC allocation to clear the nursery. Failing to do so will
         // cause all allocations on this path to land in Tenured, and we will not
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -729,31 +729,50 @@ js::Nursery::collect(JS::gcreason::Reaso
     // If we are promoting the nursery, or exhausted the store buffer with
     // pointers to nursery things, which will force a collection well before
     // the nursery is full, look for object groups that are getting promoted
     // excessively and try to pretenure them.
     startProfile(ProfileKey::Pretenure);
     bool validPromotionRate;
     const float promotionRate = calcPromotionRate(&validPromotionRate);
     uint32_t pretenureCount = 0;
-    if (validPromotionRate) {
-        if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) {
-            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)) {
-                        AutoCompartment ac(cx, group);
-                        group->setShouldPreTenure(cx);
-                        pretenureCount++;
-                    }
+    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)) {
+                    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();
+                    jitComp->stringsCanBeInNursery = false;
+                }
+            }
+            zone->allocNurseryStrings = false;
+        }
+        zone->tenuredStrings = 0;
+    }
     endProfile(ProfileKey::Pretenure);
 
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
     // because gcBytes >= gcMaxBytes.
     if (rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
         disable();
     // Disable the nursery if the user changed the configuration setting.  The
@@ -1077,17 +1096,17 @@ js::Nursery::setStartPosition()
 {
     currentStartChunk_ = currentChunk_;
     currentStartPosition_ = position();
 }
 
 void
 js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason)
 {
-    static const double GrowThreshold   = 0.05;
+    static const double GrowThreshold   = 0.03;
     static const double ShrinkThreshold = 0.01;
     unsigned newMaxNurseryChunks;
 
     // Shrink the nursery to its minimum size of we ran out of memory or
     // received a memory pressure event.
     if (gc::IsOOMReason(reason)) {
         minimizeAllocableSpace();
         return;
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -41,16 +41,18 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
     regExps(this),
     markedAtoms_(group),
     atomCache_(group),
     externalStringCache_(group),
     functionToStringCache_(group),
     usage(&rt->gc.usage),
     threshold(),
     gcDelayBytes(0),
+    tenuredStrings(group, 0),
+    allocNurseryStrings(group, true),
     propertyTree_(group, this),
     baseShapes_(group, this),
     initialShapes_(group, this),
     nurseryShapes_(group),
     data(group, nullptr),
     isSystem(group, false),
 #ifdef DEBUG
     gcLastSweepGroupIndex(group, 0),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -516,16 +516,19 @@ struct Zone : public JS::shadow::Zone,
 
     // Thresholds used to trigger GC.
     js::gc::ZoneHeapThreshold threshold;
 
     // Amount of data to allocate before triggering a new incremental slice for
     // the current GC.
     js::UnprotectedData<size_t> gcDelayBytes;
 
+    js::ZoneGroupData<uint32_t> tenuredStrings;
+    js::ZoneGroupData<bool> allocNurseryStrings;
+
   private:
     // Shared Shape property tree.
     js::ZoneGroupData<js::PropertyTree> propertyTree_;
   public:
     js::PropertyTree& propertyTree() { return propertyTree_.ref(); }
 
   private:
     // Set of all unowned base shapes in the Zone.
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -199,17 +199,19 @@ const void*
 CompileZone::addressOfStringNurseryCurrentEnd()
 {
     return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd();
 }
 
 bool
 CompileZone::canNurseryAllocateStrings()
 {
-    return nurseryExists() && zone()->group()->nursery().canAllocateStrings();
+    return nurseryExists() &&
+        zone()->group()->nursery().canAllocateStrings() &&
+        zone()->allocNurseryStrings;
 }
 
 bool
 CompileZone::nurseryExists()
 {
     return zone()->group()->nursery().exists();
 }
 
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -605,16 +605,23 @@ class JitCompartment
     MOZ_MUST_USE bool ensureIonStubsExist(JSContext* cx);
 
     void sweep(FreeOp* fop, JSCompartment* compartment);
 
     JitCode* stringConcatStubNoBarrier() const {
         return stringConcatStub_;
     }
 
+    void discardStubs() {
+        stringConcatStub_ = nullptr;
+        regExpMatcherStub_ = nullptr;
+        regExpSearcherStub_ = nullptr;
+        regExpTesterStub_ = nullptr;
+    }
+
     JitCode* regExpMatcherStubNoBarrier() const {
         return regExpMatcherStub_;
     }
 
     MOZ_MUST_USE bool ensureRegExpMatcherStubExists(JSContext* cx) {
         if (regExpMatcherStub_)
             return true;
         regExpMatcherStub_ = generateRegExpMatcherStub(cx);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -219,32 +219,34 @@ FinishOffThreadIonCompile(jit::IonBuilde
 
 static JSRuntime*
 GetSelectorRuntime(const CompilationSelector& selector)
 {
     struct Matcher
     {
         JSRuntime* match(JSScript* script)    { return script->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(JSCompartment* comp) { return comp->runtimeFromActiveCooperatingThread(); }
+        JSRuntime* match(Zone* zone)          { return zone->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(ZonesInState zbs)    { return zbs.runtime; }
         JSRuntime* match(JSRuntime* runtime)  { return runtime; }
         JSRuntime* match(AllCompilations all) { return nullptr; }
         JSRuntime* match(CompilationsUsingNursery cun) { return cun.runtime; }
     };
 
     return selector.match(Matcher());
 }
 
 static bool
 JitDataStructuresExist(const CompilationSelector& selector)
 {
     struct Matcher
     {
         bool match(JSScript* script)    { return !!script->compartment()->jitCompartment(); }
         bool match(JSCompartment* comp) { return !!comp->jitCompartment(); }
+        bool match(Zone* zone)          { return !!zone->jitZone(); }
         bool match(ZonesInState zbs)    { return zbs.runtime->hasJitRuntime(); }
         bool match(JSRuntime* runtime)  { return runtime->hasJitRuntime(); }
         bool match(AllCompilations all) { return true; }
         bool match(CompilationsUsingNursery cun) { return cun.runtime->hasJitRuntime(); }
     };
 
     return selector.match(Matcher());
 }
@@ -253,16 +255,17 @@ static bool
 IonBuilderMatches(const CompilationSelector& selector, jit::IonBuilder* builder)
 {
     struct BuilderMatches
     {
         jit::IonBuilder* builder_;
 
         bool match(JSScript* script)    { return script == builder_->script(); }
         bool match(JSCompartment* comp) { return comp == builder_->script()->compartment(); }
+        bool match(Zone* zone)          { return zone == builder_->script()->zone(); }
         bool match(JSRuntime* runtime)  { return runtime == builder_->script()->runtimeFromAnyThread(); }
         bool match(AllCompilations all) { return true; }
         bool match(ZonesInState zbs)    {
             return zbs.runtime == builder_->script()->runtimeFromAnyThread() &&
                    zbs.state == builder_->script()->zoneFromAnyThread()->gcState();
         }
         bool match(CompilationsUsingNursery cun) {
             return cun.runtime == builder_->script()->runtimeFromAnyThread() &&
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -497,16 +497,17 @@ bool
 StartOffThreadIonFree(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock);
 
 struct AllCompilations {};
 struct ZonesInState { JSRuntime* runtime; JS::Zone::GCState state; };
 struct CompilationsUsingNursery { JSRuntime* runtime; };
 
 using CompilationSelector = mozilla::Variant<JSScript*,
                                              JSCompartment*,
+                                             Zone*,
                                              ZonesInState,
                                              JSRuntime*,
                                              CompilationsUsingNursery,
                                              AllCompilations>;
 
 /*
  * Cancel scheduled or in progress Ion compilations.
  */
@@ -521,16 +522,22 @@ CancelOffThreadIonCompile(JSScript* scri
 
 inline void
 CancelOffThreadIonCompile(JSCompartment* comp)
 {
     CancelOffThreadIonCompile(CompilationSelector(comp), true);
 }
 
 inline void
+CancelOffThreadIonCompile(Zone* zone)
+{
+    CancelOffThreadIonCompile(CompilationSelector(zone), true);
+}
+
+inline void
 CancelOffThreadIonCompile(JSRuntime* runtime, JS::Zone::GCState state)
 {
     CancelOffThreadIonCompile(CompilationSelector(ZonesInState{runtime, state}), true);
 }
 
 inline void
 CancelOffThreadIonCompile(JSRuntime* runtime)
 {