[INFER] Disable inference and infallibly discard jitcode on OOM, bug 637674.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 18 May 2011 12:34:17 -0700
changeset 75076 9aeb58c8c43f71c13418f2e377e3a9e832e01153
parent 75075 29bd8523ead93a79c69a4ac5749df438c630a9d0
child 75077 4dff743ec04d8058507115006bb93a35c990fa1a
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs637674
milestone6.0a1
[INFER] Disable inference and infallibly discard jitcode on OOM, bug 637674.
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsscript.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/ImmutableSync.cpp
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -798,16 +798,18 @@ js_ReportOutOfMemory(JSContext *cx)
     /*
      * If we are in a builtin called directly from trace, don't report an
      * error. We will retry in the interpreter instead.
      */
     if (JS_ON_TRACE(cx) && !JS_TRACE_MONITOR_ON_TRACE(cx)->bailExit)
         return;
 #endif
 
+    cx->runtime->hadOutOfMemory = true;
+
     JSErrorReport report;
     JSErrorReporter onError = cx->errorReporter;
 
     /* Get the message for this error, but we won't expand any arguments. */
     const JSErrorFormatString *efs =
         js_GetLocalizedErrorMessage(cx, NULL, NULL, JSMSG_OUT_OF_MEMORY);
     const char *msg = efs ? efs->format : "Out of memory";
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -492,16 +492,19 @@ struct JSRuntime {
     /* Per runtime debug hooks -- see jsprvtd.h and jsdbgapi.h. */
     JSDebugHooks        globalDebugHooks;
 
     /*
      * Right now, we only support runtime-wide debugging.
      */
     JSBool              debugMode;
 
+    /* Had an out-of-memory error which did not populate an exception. */
+    JSBool              hadOutOfMemory;
+
 #ifdef JS_TRACER
     /* True if any debug hooks not supported by the JIT are enabled. */
     bool debuggerInhibitsJIT() const {
         return (globalDebugHooks.interruptHook ||
                 globalDebugHooks.callHook);
     }
 #endif
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -556,22 +556,31 @@ JSCompartment::sweep(JSContext *cx, uint
                 ScriptTryDestroyCode(cx, script, false, releaseInterval, counter);
             }
         }
     }
 
 #endif
 
     if (!activeAnalysis && types.inferenceEnabled) {
-        for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) {
+        bool ok = true;
+
+        for (JSCList *cursor = scripts.next; ok && cursor != &scripts; cursor = cursor->next) {
             JSScript *script = reinterpret_cast<JSScript *>(cursor);
-            script->condenseTypes(cx);
+            ok = script->condenseTypes(cx);
         }
 
-        types.condense(cx);
+        if (ok)
+            ok = condenseTypes(cx);
+
+        /*
+         * We should have skipped later condensing only if we disabled type
+         * inference due to an allocation failure while condensing types.
+         */
+        JS_ASSERT(ok == types.inferenceEnabled);
     }
 
     types.sweep(cx);
 
     for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) {
         JSScript *script = reinterpret_cast<JSScript *>(cursor);
         script->sweepAnalysis(cx);
     }
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -401,16 +401,18 @@ struct JS_FRIEND_API(JSCompartment) {
      */
     JSArenaPool                  pool;
     bool                         activeAnalysis;
     bool                         activeInference;
 
     /* Type information about the scripts and objects in this compartment. */
     js::types::TypeCompartment   types;
 
+    bool condenseTypes(JSContext *cx);
+
 #ifdef JS_TRACER
     /* Trace-tree JIT recorder/interpreter state. */
     js::TraceMonitor             traceMonitor;
 #endif
 
     /* Hashed lists of scripts created by eval to garbage-collect. */
     JSScript                     *scriptsToGC[JS_EVAL_CACHE_SIZE];
 
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -477,28 +477,27 @@ public:
     void newType(JSContext *cx, TypeSet*, jstype) { checkAnalysis(cx); }
     void newPropertyState(JSContext *cx, TypeSet*) { checkAnalysis(cx); }
     void newObjectState(JSContext *cx, TypeObject*) { checkAnalysis(cx); }
     void slotsReallocation(JSContext *cx) { checkAnalysis(cx); }
 
     bool condensed() { return true; }
 };
 
-void
+bool
 TypeSet::addCondensed(JSContext *cx, JSScript *script)
 {
     /* Condensed constraints are added during GC, so we need off-the-books allocation. */
     TypeConstraintCondensed *constraint = OffTheBooks::new_<TypeConstraintCondensed>(script);
 
-    if (!constraint) {
-        script->compartment->types.setPendingNukeTypes(cx);
-        return;
-    }
+    if (!constraint)
+        return false;
 
     add(cx, constraint, false);
+    return true;
 }
 
 /* Constraints for reads/writes on object properties. */
 class TypeConstraintProp : public TypeConstraint
 {
 public:
     const jsbytecode *pc;
 
@@ -1008,16 +1007,19 @@ TypeConstraintCall::newType(JSContext *c
         }
 
         return;
     }
 
     JSScript *callee = function->script;
     unsigned nargs = callee->fun->nargs;
 
+    if (!callee->ensureTypeArray(cx))
+        return;
+
     /* Analyze the function if we have not already done so. */
     if (!callee->analyzed) {
         ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
         if (!calleeAnalysis) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
         }
         calleeAnalysis->analyzeTypes(cx);
@@ -1991,44 +1993,84 @@ TypeCompartment::processPendingRecompile
 #endif /* JS_METHODJIT */
 
     cx->delete_(pending);
 }
 
 void
 TypeCompartment::setPendingNukeTypes(JSContext *cx)
 {
+    JS_ASSERT(cx->compartment->activeInference);
     if (!pendingNukeTypes) {
         js_ReportOutOfMemory(cx);
         pendingNukeTypes = true;
     }
 }
 
 void
 TypeCompartment::nukeTypes(JSContext *cx)
 {
+    JSCompartment *compartment = cx->compartment;
+    JS_ASSERT(this == &compartment->types);
+
     /*
      * This is the usual response if we encounter an OOM while adding a type
-     * or resolving type constraints. Release all memory used for type information,
-     * reset the compartment to not use type inference, and recompile all scripts.
+     * or resolving type constraints. Reset the compartment to not use type
+     * inference, and recompile all scripts.
      *
      * Because of the nature of constraint-based analysis (add constraints, and
      * iterate them until reaching a fixpoint), we can't undo an add of a type set,
      * and merely aborting the operation which triggered the add will not be
      * sufficient for correct behavior as we will be leaving the types in an
      * inconsistent state.
      */
     JS_ASSERT(pendingNukeTypes);
     if (pendingRecompiles) {
         cx->free_(pendingRecompiles);
         pendingRecompiles = NULL;
     }
 
-    /* :FIXME: Implement this function. */
-    *((int*)0) = 0;
+    /*
+     * We may or may not be under the GC. In either case don't allocate, and
+     * acquire the GC lock so we can update inferenceEnabled for all contexts.
+     */
+
+#ifdef JS_THREADSAFE
+    Maybe<AutoLockGC> maybeLock;
+    if (!cx->runtime->gcMarkAndSweep)
+        maybeLock.construct(info.runtime);
+#endif
+
+    inferenceEnabled = false;
+
+    /* Update the cached inferenceEnabled bit in all contexts. */
+    for (JSCList *cl = cx->runtime->contextList.next;
+         cl != &cx->runtime->contextList;
+         cl = cl->next) {
+        JSContext *cx = js_ContextFromLinkField(cl);
+        cx->setCompartment(cx->compartment);
+    }
+
+#ifdef JS_METHODJIT
+
+    mjit::ExpandInlineFrames(cx, true);
+
+    /* Throw away all JIT code in the compartment, but leave everything else alone. */
+    for (JSCList *cursor = compartment->scripts.next;
+         cursor != &compartment->scripts;
+         cursor = cursor->next) {
+        JSScript *script = reinterpret_cast<JSScript *>(cursor);
+        if (script->hasJITCode()) {
+            mjit::Recompiler recompiler(cx, script);
+            recompiler.recompile();
+        }
+    }
+
+#endif /* JS_METHODJIT */
+
 }
 
 void
 TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script)
 {
     if (!script->jitNormal && !script->jitCtor) {
         /* Scripts which haven't been compiled yet don't need to be recompiled. */
         return;
@@ -4574,19 +4616,19 @@ types::TypeObject::trace(JSTracer *trc)
     }
 }
 
 /*
  * Condense any constraints on a type set which were generated during analysis
  * of a script, and sweep all type objects and references to type objects
  * which no longer exist.
  */
-void
-TypeSet::CondenseSweepTypeSet(JSContext *cx, TypeCompartment *compartment,
-                              ScriptSet *pcondensed, TypeSet *types)
+bool
+TypeSet::CondenseSweepTypeSet(JSContext *cx, JSCompartment *compartment,
+                              ScriptSet &condensed, TypeSet *types)
 {
     /*
      * This function is called from GC, and cannot malloc any data that could
      * trigger a reentrant GC. The only allocation that can happen here is
      * the construction of condensed constraints and tables for hash sets.
      * Both of these use off-the-books malloc rather than cx->malloc, and thus
      * do not contribute towards the runtime's overall malloc bytes.
      */
@@ -4602,17 +4644,17 @@ TypeSet::CondenseSweepTypeSet(JSContext 
                  * If the object has unknown properties, instead of removing it
                  * replace it with the compartment's empty type object. This is
                  * needed to handle mutable __proto__ --- the type object in
                  * the set may no longer be used but there could be a JSObject
                  * which originally had the type and was changed to a different
                  * type object with unknown properties.
                  */
                 if (object->unknownProperties())
-                    types->objectSet[i] = &compartment->typeEmpty;
+                    types->objectSet[i] = &compartment->types.typeEmpty;
                 else
                     types->objectSet[i] = NULL;
                 removed = true;
             }
         }
         if (removed) {
             /* Reconstruct the type set to re-resolve hash collisions. */
             TypeObject **oldArray = types->objectSet;
@@ -4628,17 +4670,17 @@ TypeSet::CondenseSweepTypeSet(JSContext 
                 }
             }
             cx->free_(oldArray);
         }
     } else if (types->objectCount == 1) {
         TypeObject *object = (TypeObject*) types->objectSet;
         if (!object->marked) {
             if (object->unknownProperties()) {
-                types->objectSet = (TypeObject**) &compartment->typeEmpty;
+                types->objectSet = (TypeObject**) &compartment->types.typeEmpty;
             } else {
                 types->objectSet = NULL;
                 types->objectCount = 0;
             }
         }
     }
 
     TypeConstraint *constraint = types->constraintList;
@@ -4680,55 +4722,57 @@ TypeSet::CondenseSweepTypeSet(JSContext 
             (script->u.object && IsAboutToBeFinalized(cx, script->u.object)) ||
             (script->fun && IsAboutToBeFinalized(cx, script->fun))) {
             if (constraint->condensed())
                 cx->delete_(constraint);
             constraint = next;
             continue;
         }
 
-        if (pcondensed) {
-            ScriptSet::AddPtr p = pcondensed->lookupForAdd(script);
-            if (!p) {
-                if (pcondensed->add(p, script))
-                    types->addCondensed(cx, script);
-                else
-                    compartment->setPendingNukeTypes(cx);
+        ScriptSet::AddPtr p = condensed.lookupForAdd(script);
+        if (!p) {
+            if (!condensed.add(p, script) || !types->addCondensed(cx, script)) {
+                SwitchToCompartment enterCompartment(cx, compartment);
+                AutoEnterTypeInference enter(cx);
+                compartment->types.setPendingNukeTypes(cx);
+                return false;
             }
         }
 
         if (constraint->condensed())
             cx->free_(constraint);
         constraint = next;
     }
 
-    if (pcondensed)
-        pcondensed->clear();
+    condensed.clear();
+    return true;
 }
 
 /* Remove to-be-destroyed objects from the list of instances of a type object. */
 static inline void
 PruneInstanceObjects(TypeObject *object)
 {
     TypeObject **pinstance = &object->instanceList;
     while (*pinstance) {
         if ((*pinstance)->marked)
             pinstance = &(*pinstance)->instanceNext;
         else
             *pinstance = (*pinstance)->instanceNext;
     }
 }
 
-static void
-CondenseTypeObjectList(JSContext *cx, TypeCompartment *compartment, TypeObject *objects)
+static bool
+CondenseTypeObjectList(JSContext *cx, JSCompartment *compartment, TypeObject *objects)
 {
-    TypeSet::ScriptSet condensed(cx), *pcondensed = &condensed;
+    TypeSet::ScriptSet condensed;
     if (!condensed.init()) {
-        compartment->setPendingNukeTypes(cx);
-        pcondensed = NULL;
+        SwitchToCompartment enterCompartment(cx, compartment);
+        AutoEnterTypeInference enter(cx);
+        compartment->types.setPendingNukeTypes(cx);
+        return false;
     }
 
     TypeObject *object = objects;
     while (object) {
         if (!object->marked) {
             /*
              * Leave all constraints and references to to-be-destroyed objects in.
              * We will release all memory when sweeping the object.
@@ -4738,30 +4782,32 @@ CondenseTypeObjectList(JSContext *cx, Ty
         }
 
         PruneInstanceObjects(object);
 
         /* Condense type sets for all properties of the object. */
         unsigned count = object->getPropertyCount();
         for (unsigned i = 0; i < count; i++) {
             Property *prop = object->getProperty(i);
-            if (prop)
-                TypeSet::CondenseSweepTypeSet(cx, compartment, pcondensed, &prop->types);
+            if (prop && !TypeSet::CondenseSweepTypeSet(cx, compartment, condensed, &prop->types))
+                return false;
         }
 
         object = object->next;
     }
+
+    return true;
 }
 
-void
-TypeCompartment::condense(JSContext *cx)
+bool
+JSCompartment::condenseTypes(JSContext *cx)
 {
-    PruneInstanceObjects(&typeEmpty);
-
-    CondenseTypeObjectList(cx, this, objects);
+    PruneInstanceObjects(&types.typeEmpty);
+
+    return CondenseTypeObjectList(cx, this, types.objects);
 }
 
 static void
 DestroyProperty(JSContext *cx, Property *prop)
 {
     prop->types.destroy(cx);
     cx->delete_(prop);
 }
@@ -4863,53 +4909,60 @@ TypeCompartment::~TypeCompartment()
 
     if (arrayTypeTable)
         Foreground::delete_(arrayTypeTable);
 
     if (objectTypeTable)
         Foreground::delete_(objectTypeTable);
 }
 
-void
+bool
 JSScript::condenseTypes(JSContext *cx)
 {
-    CondenseTypeObjectList(cx, &compartment->types, typeObjects);
+    if (!CondenseTypeObjectList(cx, compartment, typeObjects))
+        return false;
 
     if (typeArray) {
-        TypeSet::ScriptSet condensed(cx), *pcondensed = &condensed;
+        TypeSet::ScriptSet condensed;
         if (!condensed.init()) {
+            SwitchToCompartment enterCompartment(cx, compartment);
+            AutoEnterTypeInference enter(cx);
             compartment->types.setPendingNukeTypes(cx);
-            pcondensed = NULL;
+            return false;
         }
 
         unsigned num = nTypeSets + TotalSlots(this) + bindings.countUpvars();
 
         if (isCachedEval ||
             (u.object && IsAboutToBeFinalized(cx, u.object)) ||
             (fun && IsAboutToBeFinalized(cx, fun))) {
             for (unsigned i = 0; i < num; i++)
                 typeArray[i].destroy(cx);
             cx->free_(typeArray);
             typeArray = NULL;
         } else {
-            for (unsigned i = 0; i < num; i++)
-                TypeSet::CondenseSweepTypeSet(cx, &compartment->types, pcondensed, &typeArray[i]);
+            for (unsigned i = 0; i < num; i++) {
+                if (!TypeSet::CondenseSweepTypeSet(cx, compartment, condensed, &typeArray[i]))
+                    return false;
+            }
         }
     }
 
     TypeIntermediate **presult = &intermediateTypes;
     while (*presult) {
         TypeIntermediate *result = *presult;
         if (result->sweep(cx, compartment)) {
             presult = &result->next;
         } else {
             *presult = result->next;
             cx->delete_(result);
         }
     }
+
+    return true;
 }
 
 void
 JSScript::sweepAnalysis(JSContext *cx)
 {
     SweepTypeObjectList(cx, typeObjects);
 
     if (analysis_ && !compartment->activeAnalysis) {
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -372,17 +372,17 @@ class TypeSet
     void addArith(JSContext *cx, JSScript *script,
                   TypeSet *target, TypeSet *other = NULL);
     void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target);
     void addFilterPrimitives(JSContext *cx, JSScript *script,
                              TypeSet *target, bool onlyNullVoid);
     void addSubsetBarrier(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target);
 
     void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target);
-    void addCondensed(JSContext *cx, JSScript *script);
+    bool addCondensed(JSContext *cx, JSScript *script);
 
     /*
      * Make an intermediate type set with the specified debugging name,
      * not embedded in another structure.
      */
     static inline TypeSet* make(JSContext *cx, const char *name);
 
     /*
@@ -430,21 +430,21 @@ class TypeSet
      * Clone (possibly NULL) source onto target; if any new types are added to
      * source in the future, the script will be recompiled.
      */
     static void Clone(JSContext *cx, TypeSet *source, ClonedTypeSet *target);
 
     /* Set of scripts which condensed constraints have been generated for. */
     typedef HashSet<JSScript *,
                     DefaultHasher<JSScript *>,
-                    ContextAllocPolicy> ScriptSet;
+                    SystemAllocPolicy> ScriptSet;
 
-    static void
-    CondenseSweepTypeSet(JSContext *cx, TypeCompartment *compartment,
-                         ScriptSet *pcondensed, TypeSet *types);
+    static bool
+    CondenseSweepTypeSet(JSContext *cx, JSCompartment *compartment,
+                         ScriptSet &condensed, TypeSet *types);
 
   private:
     inline void markUnknown(JSContext *cx);
 };
 
 /* A type set captured for use by JIT compilers. */
 struct ClonedTypeSet
 {
@@ -891,17 +891,16 @@ struct TypeCompartment
     void setPendingNukeTypes(JSContext *cx);
 
     /* Mark a script as needing recompilation once inference has finished. */
     void addPendingRecompile(JSContext *cx, JSScript *script);
 
     /* Monitor future effects on a bytecode. */
     void monitorBytecode(JSContext *cx, JSScript *script, uint32 offset);
 
-    void condense(JSContext *cx);
     void sweep(JSContext *cx);
 };
 
 enum SpewChannel {
     ISpewOps,      /* ops: New constraints and types. */
     ISpewResult,   /* result: Final type sets. */
     SPEW_COUNT
 };
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -577,17 +577,17 @@ struct JSScript {
 #ifdef DEBUG
     /* Check that correct types were inferred for the values pushed by this bytecode. */
     void typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp);
 #endif
 
     /* Get the default 'new' object for a given standard class, per the script's global. */
     inline js::types::TypeObject *getTypeNewObject(JSContext *cx, JSProtoKey key);
 
-    void condenseTypes(JSContext *cx);
+    bool condenseTypes(JSContext *cx);
     void sweepAnalysis(JSContext *cx);
 
     /* Get a type object for an allocation site in this script. */
     inline js::types::TypeObject *
     getTypeInitObject(JSContext *cx, const jsbytecode *pc, bool isArray);
 
     /* Monitor a bytecode pushing an unexpected value. */
     inline void typeMonitorOverflow(JSContext *cx, const jsbytecode *pc);
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -659,17 +659,19 @@ mjit::TryCompile(JSContext *cx, StackFra
          * Treat this the same way as a static overflow and wait for another
          * attempt to compile the script.
          */
         JITScriptStatus status = fp->script()->getJITStatus(fp->isConstructing());
         JS_ASSERT(status != JITScript_Invalid);
         return (status == JITScript_Valid) ? Compile_Okay : Compile_Retry;
     }
 
-    JS_ASSERT_IF(status == Compile_Error, cx->isExceptionPending());
+    /* Non-OOM errors should have an associated exception. */
+    JS_ASSERT_IF(status == Compile_Error,
+                 cx->isExceptionPending() || cx->runtime->hadOutOfMemory);
 
     return status;
 }
 
 CompileStatus
 mjit::Compiler::generatePrologue()
 {
     invokeLabel = masm.label();
--- a/js/src/methodjit/ImmutableSync.cpp
+++ b/js/src/methodjit/ImmutableSync.cpp
@@ -49,17 +49,18 @@ using namespace js::mjit;
 
 ImmutableSync::ImmutableSync()
   : cx(NULL), entries(NULL), frame(NULL), avail(Registers::AvailRegs), generation(0)
 {
 }
 
 ImmutableSync::~ImmutableSync()
 {
-    cx->free_(entries);
+    if (cx)
+        cx->free_(entries);
 }
 
 bool
 ImmutableSync::init(JSContext *cx, const FrameState &frame, uint32 nentries)
 {
     this->cx = cx;
     this->frame = &frame;