Bug 988619 - Handle OOM when sweeping type information, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 14 Apr 2014 18:22:17 -0600
changeset 196924 21aca7217e7aeb664e3d1af35a8818264fc81af5
parent 196923 ba29a8ec29736943b536d354f9de6b69cdd93983
child 196925 2d7d03c701d3d20b34fd5d4eba3037bc5a2d245d
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs988619
milestone31.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 988619 - Handle OOM when sweeping type information, r=jandem.
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
@@ -100,28 +100,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
@@ -310,17 +310,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
@@ -2798,17 +2798,21 @@ jit::Invalidate(JSContext *cx, JSScript 
 {
     return Invalidate(cx, script, SequentialExecution, resetUses, cancelOffThread);
 }
 
 static void
 FinishInvalidationOf(FreeOp *fop, JSScript *script, IonScript *ionScript)
 {
     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);
 }
 
 template <ExecutionMode mode>
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3973,17 +3973,32 @@ 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());
+
+            // 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 addendums in the zone, the only things whose
+            // correctness depends on the type constraints.
+            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
@@ -32,16 +32,18 @@
 #include "vm/Opcodes.h"
 #include "vm/Shape.h"
 
 #include "jsatominlines.h"
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
+#include "jit/ExecutionModeInlines.h"
+
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 using namespace js::analyze;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::PodArrayZero;
@@ -3116,16 +3118,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.setNonDataPropertyIgnoringConstraints();
+    }
+
+    // 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());
@@ -3938,17 +3963,17 @@ ExclusiveContext::getSingletonType(const
     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();
@@ -3959,19 +3984,24 @@ 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();
+                    return;
+                }
             }
         }
         setBaseObjectCount(objectCount);
     } else if (objectCount == 1) {
         TypeObjectKey *object = (TypeObjectKey *) objectSet;
         if (IsAboutToBeFinalized(object)) {
             objectSet = nullptr;
             setBaseObjectCount(0);
@@ -3982,20 +4012,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()
 {
@@ -4006,25 +4038,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);
@@ -4041,49 +4073,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.
@@ -4191,27 +4226,27 @@ 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());
 
     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);
 }
 
@@ -4273,17 +4308,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.
@@ -4293,31 +4328,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++);
+                }
             }
         }
     }
 
     {
         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) {
                     if (script->hasParallelIonScript()) {
                         // It's possible that we preserved the parallel
                         // IonScript. The heuristic for their preservation is
                         // independent of general JIT code preservation.
                         MOZ_ASSERT(jit::ShouldPreserveParallelJITCode(rt, script));
                         script->parallelIonScript()->recompileInfoRef().shouldSweep(*this);
@@ -4346,17 +4383,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;
@@ -4381,16 +4418,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 setNonDataPropertyIgnoringConstraints(); // 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;
@@ -1571,17 +1573,18 @@ struct TypeZone
     /* Pending recompilations to perform before execution of JIT code can resume. */
     Vector<RecompileInfo> *pendingRecompiles;
 
     TypeZone(JS::Zone *zone);
     ~TypeZone();
 
     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
@@ -1073,17 +1073,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)
 {
@@ -1124,22 +1123,28 @@ HeapTypeSet::newPropertyState(ExclusiveC
             constraint = constraint->next;
         }
     } else {
         JS_ASSERT(!constraintList);
     }
 }
 
 inline void
+HeapTypeSet::setNonDataPropertyIgnoringConstraints()
+{
+    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;
+    setNonDataPropertyIgnoringConstraints();
     newPropertyState(cx);
 }
 
 inline void
 HeapTypeSet::setNonWritableProperty(ExclusiveContext *cx)
 {
     if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
         return;