Bug 988619 - Handle OOM when sweeping type information, r=jandem a=bkerensa.
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 25 Apr 2014 13:50:22 -0600
changeset 193137 ec1d3e26a17292c34045f915993b57d932879060
parent 193136 1f57fd6e9658a85d465d4517f2e48f1a2bed5289
child 193138 10a8ef2b96c7b3fda6e9f27bac80da0122e97629
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, bkerensa
bugs988619
milestone30.0a2
Bug 988619 - Handle OOM when sweeping type information, r=jandem a=bkerensa.
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jit/Ion.cpp
js/src/jsgc.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -107,28 +107,28 @@ Zone::setGCMaxMallocBytes(size_t value)
 void
 Zone::onTooMuchMalloc()
 {
     if (!gcMallocGCTriggered)
         gcMallocGCTriggered = TriggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
 }
 
 void
-Zone::sweep(FreeOp *fop, bool releaseTypes)
+Zone::sweep(FreeOp *fop, bool releaseTypes, bool *oom)
 {
     /*
      * Periodically release observed types for all scripts. This is safe to
      * do when there are no frames for the zone on the stack.
      */
     if (active)
         releaseTypes = false;
 
     {
         gcstats::AutoPhase ap(fop->runtime()->gcStats, gcstats::PHASE_DISCARD_ANALYSIS);
-        types.sweep(fop, releaseTypes);
+        types.sweep(fop, releaseTypes, oom);
     }
 
     if (!fop->runtime()->debuggerList.isEmpty())
         sweepBreakpoints(fop);
 
     active = false;
 }
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -311,17 +311,17 @@ struct Zone : public JS::shadow::Zone,
         return runtimeFromMainThread()->onOutOfMemory(p, nbytes);
     }
     void reportAllocationOverflow() {
         js_ReportAllocationOverflow(nullptr);
     }
 
     js::types::TypeZone types;
 
-    void sweep(js::FreeOp *fop, bool releaseTypes);
+    void sweep(js::FreeOp *fop, bool releaseTypes, bool *oom);
 
     bool hasMarkedCompartments();
 
   private:
     void sweepBreakpoints(js::FreeOp *fop);
 
 #ifdef JS_ION
     js::jit::JitZone *jitZone_;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2680,17 +2680,21 @@ FinishInvalidationOf(FreeOp *fop, JSScri
     // In all cases, nullptr out script->ion or script->parallelIon to avoid
     // re-entry.
     if (parallel)
         script->setParallelIonScript(nullptr);
     else
         script->setIonScript(nullptr);
 
     types::TypeZone &types = script->zone()->types;
-    ionScript->recompileInfo().compilerOutput(types)->invalidate();
+
+    // Note: If the script is about to be swept, the compiler output may have
+    // already been destroyed.
+    if (types::CompilerOutput *output = ionScript->recompileInfo().compilerOutput(types))
+        output->invalidate();
 
     // If this script has Ion code on the stack, invalidated() will return
     // true. In this case we have to wait until destroying it.
     if (!ionScript->invalidated())
         jit::IonScript::Destroy(fop, ionScript);
 }
 
 void
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3945,17 +3945,23 @@ BeginSweepingZoneGroup(JSRuntime *rt)
         bool releaseTypes = ReleaseObservedTypes(rt);
         for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) {
             gcstats::AutoSCC scc(rt->gcStats, rt->gcZoneGroupIndex);
             c->sweep(&fop, releaseTypes && !c->zone()->isPreservingCode());
         }
 
         for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
             gcstats::AutoSCC scc(rt->gcStats, rt->gcZoneGroupIndex);
-            zone->sweep(&fop, releaseTypes && !zone->isPreservingCode());
+            bool oom = false;
+            zone->sweep(&fop, releaseTypes && !zone->isPreservingCode(), &oom);
+            if (oom) {
+                zone->setPreservingCode(false);
+                zone->discardJitCode(&fop);
+                zone->types.clearAllNewScriptAddendumsOnOOM();
+            }
         }
     }
 
     /*
      * Queue all GC things in all zones for sweeping, either in the
      * foreground or on the background thread.
      *
      * Note that order is important here for the background case.
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -19,16 +19,17 @@
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jsworkers.h"
 #include "prmjtime.h"
 
 #include "gc/Marking.h"
 #ifdef JS_ION
 #include "jit/BaselineJIT.h"
+#include "jit/ExecutionModeInlines.h"
 #include "jit/Ion.h"
 #include "jit/IonAnalysis.h"
 #include "jit/JitCompartment.h"
 #endif
 #include "js/MemoryMetrics.h"
 #include "vm/Opcodes.h"
 #include "vm/Shape.h"
 
@@ -3143,16 +3144,39 @@ TypeObject::clearNewScriptAddendum(Exclu
         }
     } else {
         // Threads with an ExclusiveContext are not allowed to run scripts.
         JS_ASSERT(!cx->perThreadData->activation());
     }
 }
 
 void
+TypeObject::maybeClearNewScriptAddendumOnOOM()
+{
+    if (!isMarked())
+        return;
+
+    if (!addendum || addendum->kind != TypeObjectAddendum::NewScript)
+        return;
+
+    for (unsigned i = 0; i < getPropertyCount(); i++) {
+        Property *prop = getProperty(i);
+        if (!prop)
+            continue;
+        if (prop->types.definiteProperty())
+            prop->types.setNonDataProperty();
+    }
+
+    // This method is called during GC sweeping, so there is no write barrier
+    // that needs to be triggered.
+    js_free(addendum);
+    addendum.unsafeSet(nullptr);
+}
+
+void
 TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx)
 {
 }
 
 void
 TypeObject::print()
 {
     TaggedProto tagged(proto());
@@ -4002,17 +4026,17 @@ ExclusiveContext::getLazyType(const Clas
     return type;
 }
 
 /////////////////////////////////////////////////////////////////////
 // Tracing
 /////////////////////////////////////////////////////////////////////
 
 void
-ConstraintTypeSet::sweep(Zone *zone)
+ConstraintTypeSet::sweep(Zone *zone, bool *oom)
 {
     /*
      * Purge references to type objects that are no longer live. Type sets hold
      * only weak references. For type sets containing more than one object,
      * live entries in the object hash need to be copied to the zone's
      * new arena.
      */
     unsigned objectCount = baseObjectCount();
@@ -4023,19 +4047,25 @@ ConstraintTypeSet::sweep(Zone *zone)
         clearObjects();
         objectCount = 0;
         for (unsigned i = 0; i < oldCapacity; i++) {
             TypeObjectKey *object = oldArray[i];
             if (object && !IsAboutToBeFinalized(object)) {
                 TypeObjectKey **pentry =
                     HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey>
                         (zone->types.typeLifoAlloc, objectSet, objectCount, object);
-                if (!pentry)
-                    CrashAtUnhandlableOOM("OOM in ConstraintTypeSet::sweep");
-                *pentry = object;
+                if (pentry) {
+                    *pentry = object;
+                } else {
+                    *oom = true;
+                    flags |= TYPE_FLAG_ANYOBJECT;
+                    clearObjects();
+                    objectCount = 0;
+                    break;
+                }
             }
         }
         setBaseObjectCount(objectCount);
     } else if (objectCount == 1) {
         TypeObjectKey *object = (TypeObjectKey *) objectSet;
         if (IsAboutToBeFinalized(object)) {
             objectSet = nullptr;
             setBaseObjectCount(0);
@@ -4046,20 +4076,22 @@ ConstraintTypeSet::sweep(Zone *zone)
      * Type constraints only hold weak references. Copy constraints referring
      * to data that is still live into the zone's new arena.
      */
     TypeConstraint *constraint = constraintList;
     constraintList = nullptr;
     while (constraint) {
         TypeConstraint *copy;
         if (constraint->sweep(zone->types, &copy)) {
-            if (!copy)
-                CrashAtUnhandlableOOM("OOM in ConstraintTypeSet::sweep");
-            copy->next = constraintList;
-            constraintList = copy;
+            if (copy) {
+                copy->next = constraintList;
+                constraintList = copy;
+            } else {
+                *oom = true;
+            }
         }
         constraint = constraint->next;
     }
 }
 
 inline void
 TypeObject::clearProperties()
 {
@@ -4070,25 +4102,25 @@ TypeObject::clearProperties()
 /*
  * Before sweeping the arenas themselves, scan all type objects in a
  * compartment to fixup weak references: property type sets referencing dead
  * JS and type objects, and singleton JS objects whose type is not referenced
  * elsewhere. This also releases memory associated with dead type objects,
  * so that type objects do not need later finalization.
  */
 inline void
-TypeObject::sweep(FreeOp *fop)
+TypeObject::sweep(FreeOp *fop, bool *oom)
 {
     if (!isMarked()) {
         if (addendum)
             fop->free_(addendum);
         return;
     }
 
-    js::LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc;
+    LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc;
 
     /*
      * Properties were allocated from the old arena, and need to be copied over
      * to the new one.
      */
     unsigned propertyCount = basePropertyCount();
     if (propertyCount >= 2) {
         unsigned oldCapacity = HashSetCapacity(propertyCount);
@@ -4105,49 +4137,52 @@ TypeObject::sweep(FreeOp *fop)
                      * presence will not be required by jitcode or type constraints
                      * (i.e. for the definite properties analysis). The contents of
                      * these type sets will be regenerated as necessary.
                      */
                     continue;
                 }
 
                 Property *newProp = typeLifoAlloc.new_<Property>(*prop);
-                if (!newProp)
-                    CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
-
-                Property **pentry =
-                    HashSetInsert<jsid,Property,Property>
-                        (typeLifoAlloc, propertySet, propertyCount, prop->id);
-                if (!pentry)
-                    CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
-
-                *pentry = newProp;
-                newProp->types.sweep(zone());
+                if (newProp) {
+                    Property **pentry =
+                        HashSetInsert<jsid,Property,Property>
+                            (typeLifoAlloc, propertySet, propertyCount, prop->id);
+                    if (pentry) {
+                        *pentry = newProp;
+                        newProp->types.sweep(zone(), oom);
+                        continue;
+                    }
+                }
+
+                *oom = true;
+                addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES);
+                clearProperties();
+                return;
             }
         }
         setBasePropertyCount(propertyCount);
     } else if (propertyCount == 1) {
         Property *prop = (Property *) propertySet;
         if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) {
             // Skip, as above.
             clearProperties();
         } else {
             Property *newProp = typeLifoAlloc.new_<Property>(*prop);
-            if (!newProp)
-                CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
-
-            propertySet = (Property **) newProp;
-            newProp->types.sweep(zone());
+            if (newProp) {
+                propertySet = (Property **) newProp;
+                newProp->types.sweep(zone(), oom);
+            } else {
+                *oom = true;
+                addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES);
+                clearProperties();
+                return;
+            }
         }
     }
-
-    if (basePropertyCount() <= SET_ARRAY_SIZE) {
-        for (unsigned i = 0; i < basePropertyCount(); i++)
-            JS_ASSERT(propertySet[i]);
-    }
 }
 
 void
 TypeCompartment::sweep(FreeOp *fop)
 {
     /*
      * Iterate through the array/object type tables and remove all entries
      * referencing collected data. These tables only hold weak references.
@@ -4255,28 +4290,28 @@ JSCompartment::sweepNewTypeObjectTable(T
 TypeCompartment::~TypeCompartment()
 {
     js_delete(arrayTypeTable);
     js_delete(objectTypeTable);
     js_delete(allocationSiteTable);
 }
 
 /* static */ void
-TypeScript::Sweep(FreeOp *fop, JSScript *script)
+TypeScript::Sweep(FreeOp *fop, JSScript *script, bool *oom)
 {
     JSCompartment *compartment = script->compartment();
     JS_ASSERT(compartment->zone()->isGCSweeping());
     JS_ASSERT(compartment->zone()->types.inferenceEnabled);
 
     unsigned num = NumTypeSets(script);
     StackTypeSet *typeArray = script->types->typeArray();
 
     /* Remove constraints and references to dead objects from the persistent type sets. */
     for (unsigned i = 0; i < num; i++)
-        typeArray[i].sweep(compartment->zone());
+        typeArray[i].sweep(compartment->zone(), oom);
 }
 
 void
 TypeScript::destroy()
 {
     js_free(this);
 }
 
@@ -4339,17 +4374,17 @@ TypeZone::TypeZone(Zone *zone)
 
 TypeZone::~TypeZone()
 {
     js_delete(compilerOutputs);
     js_delete(pendingRecompiles);
 }
 
 void
-TypeZone::sweep(FreeOp *fop, bool releaseTypes)
+TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom)
 {
     JS_ASSERT(zone()->isGCSweeping());
 
     JSRuntime *rt = fop->runtime();
 
     /*
      * Clear the analysis pool, but don't release its data yet. While
      * sweeping types any live data will be allocated into the pool.
@@ -4359,31 +4394,33 @@ TypeZone::sweep(FreeOp *fop, bool releas
 
     /* Sweep and find compressed indexes for each compiler output. */
     size_t newCompilerOutputCount = 0;
     if (compilerOutputs) {
         for (size_t i = 0; i < compilerOutputs->length(); i++) {
             CompilerOutput &output = (*compilerOutputs)[i];
             if (output.isValid()) {
                 JSScript *script = output.script();
-                if (IsScriptAboutToBeFinalized(&script))
+                if (IsScriptAboutToBeFinalized(&script)) {
+                    jit::GetIonScript(script, output.mode())->recompileInfoRef() = uint32_t(-1);
                     output.invalidate();
-                else
+                } else {
                     output.setSweepIndex(newCompilerOutputCount++);
+                }
             }
         }
     }
 
     if (inferenceEnabled) {
         gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_DISCARD_TI);
 
         for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) {
             JSScript *script = i.get<JSScript>();
             if (script->types) {
-                types::TypeScript::Sweep(fop, script);
+                types::TypeScript::Sweep(fop, script, oom);
 
                 if (releaseTypes) {
                     script->types->destroy();
                     script->types = nullptr;
 
                     /*
                      * Freeze constraints on stack type sets need to be
                      * regenerated the next time the script is analyzed.
@@ -4405,17 +4442,17 @@ TypeZone::sweep(FreeOp *fop, bool releas
 
     {
         gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_TYPES);
 
         for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT);
              !iter.done(); iter.next())
         {
             TypeObject *object = iter.get<TypeObject>();
-            object->sweep(fop);
+            object->sweep(fop, oom);
         }
 
         for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next())
             comp->types.sweep(fop);
     }
 
     if (compilerOutputs) {
         size_t sweepIndex = 0;
@@ -4440,16 +4477,27 @@ TypeZone::sweep(FreeOp *fop, bool releas
     }
 
     {
         gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_FREE_TI_ARENA);
         rt->freeLifoAlloc.transferFrom(&oldAlloc);
     }
 }
 
+void
+TypeZone::clearAllNewScriptAddendumsOnOOM()
+{
+    for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT);
+         !iter.done(); iter.next())
+    {
+        TypeObject *object = iter.get<TypeObject>();
+        object->maybeClearNewScriptAddendumOnOOM();
+    }
+}
+
 #ifdef DEBUG
 void
 TypeScript::printTypes(JSContext *cx, HandleScript script) const
 {
     JS_ASSERT(script->types == this);
 
     if (!script->hasBaselineScript())
         return;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -602,31 +602,32 @@ class ConstraintTypeSet : public TypeSet
      * Add a type to this set, calling any constraint handlers if this is a new
      * possible type.
      */
     inline void addType(ExclusiveContext *cx, Type type);
 
     /* Add a new constraint to this set. */
     bool addConstraint(JSContext *cx, TypeConstraint *constraint, bool callExisting = true);
 
-    inline void sweep(JS::Zone *zone);
+    inline void sweep(JS::Zone *zone, bool *oom);
 };
 
 class StackTypeSet : public ConstraintTypeSet
 {
   public:
 };
 
 class HeapTypeSet : public ConstraintTypeSet
 {
     inline void newPropertyState(ExclusiveContext *cx);
 
   public:
     /* Mark this type set as representing a non-data property. */
     inline void setNonDataProperty(ExclusiveContext *cx);
+    inline void setNonDataProperty(); // Variant for use during GC.
 
     /* Mark this type set as representing a non-writable property. */
     inline void setNonWritableProperty(ExclusiveContext *cx);
 };
 
 class CompilerConstraintList;
 
 CompilerConstraintList *
@@ -1117,23 +1118,24 @@ struct TypeObject : gc::BarrieredCell<Ty
     void markPropertyNonData(ExclusiveContext *cx, jsid id);
     void markPropertyNonWritable(ExclusiveContext *cx, jsid id);
     void markStateChange(ExclusiveContext *cx);
     void setFlags(ExclusiveContext *cx, TypeObjectFlags flags);
     void markUnknown(ExclusiveContext *cx);
     void clearAddendum(ExclusiveContext *cx);
     void clearNewScriptAddendum(ExclusiveContext *cx);
     void clearTypedObjectAddendum(ExclusiveContext *cx);
+    void maybeClearNewScriptAddendumOnOOM();
     bool isPropertyNonData(jsid id);
     bool isPropertyNonWritable(jsid id);
 
     void print();
 
     inline void clearProperties();
-    inline void sweep(FreeOp *fop);
+    inline void sweep(FreeOp *fop, bool *oom);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     /*
      * Type objects don't have explicit finalizers. Memory owned by a type
      * object pending deletion is released when weak references are sweeped
      * from all the compartment's type objects.
      */
@@ -1279,17 +1281,17 @@ class TypeScript
      */
     static bool FreezeTypeSets(CompilerConstraintList *constraints, JSScript *script,
                                TemporaryTypeSet **pThisTypes,
                                TemporaryTypeSet **pArgTypes,
                                TemporaryTypeSet **pBytecodeTypes);
 
     static void Purge(JSContext *cx, HandleScript script);
 
-    static void Sweep(FreeOp *fop, JSScript *script);
+    static void Sweep(FreeOp *fop, JSScript *script, bool *oom);
     void destroy();
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return mallocSizeOf(this);
     }
 
 #ifdef DEBUG
     void printTypes(JSContext *cx, HandleScript script) const;
@@ -1567,17 +1569,18 @@ struct TypeZone
     bool                         inferenceEnabled;
 
     TypeZone(JS::Zone *zone);
     ~TypeZone();
     void init(JSContext *cx);
 
     JS::Zone *zone() const { return zone_; }
 
-    void sweep(FreeOp *fop, bool releaseTypes);
+    void sweep(FreeOp *fop, bool releaseTypes, bool *oom);
+    void clearAllNewScriptAddendumsOnOOM();
 
     /* 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);
 };
 
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1104,17 +1104,16 @@ TypeSet::addType(Type type, LifoAlloc *a
         TypeObject *nobject = type.typeObject();
         JS_ASSERT(!nobject->singleton());
         if (nobject->unknownProperties())
             goto unknownObject;
     }
 
     if (false) {
     unknownObject:
-        type = Type::AnyObjectType();
         flags |= TYPE_FLAG_ANYOBJECT;
         clearObjects();
     }
 }
 
 inline void
 ConstraintTypeSet::addType(ExclusiveContext *cxArg, Type type)
 {
@@ -1155,22 +1154,28 @@ HeapTypeSet::newPropertyState(ExclusiveC
             constraint = constraint->next;
         }
     } else {
         JS_ASSERT(!constraintList);
     }
 }
 
 inline void
+HeapTypeSet::setNonDataProperty()
+{
+    flags |= TYPE_FLAG_NON_DATA_PROPERTY;
+}
+
+inline void
 HeapTypeSet::setNonDataProperty(ExclusiveContext *cx)
 {
     if (flags & TYPE_FLAG_NON_DATA_PROPERTY)
         return;
 
-    flags |= TYPE_FLAG_NON_DATA_PROPERTY;
+    setNonDataProperty();
     newPropertyState(cx);
 }
 
 inline void
 HeapTypeSet::setNonWritableProperty(ExclusiveContext *cx)
 {
     if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
         return;