[INFER] Remove isTypeCallerMonitored and rejoin from native calls triggering recompilation, bug 638977.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 05 Mar 2011 17:13:40 -0800
changeset 74716 2f248906118d82d3002e2246a9a3ab352ed39d87
parent 74715 7411eaf29ba31ecae384add3a664653873ca97f9
child 74717 89f6b05273e846ac63968037a1259c19ae29b712
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs638977
milestone2.0b12pre
[INFER] Remove isTypeCallerMonitored and rejoin from native calls triggering recompilation, bug 638977.
js/src/jit-test/tests/jaeger/recompile/bug638977.js
js/src/jsarray.cpp
js/src/jscntxt.h
js/src/jsinfer.cpp
js/src/jsinferinlines.h
js/src/methodjit/Compiler.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/jaeger/recompile/bug638977.js
@@ -0,0 +1,6 @@
+function f() {
+    gc();
+    [].unshift(false);
+}
+f();
+f();
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1397,24 +1397,49 @@ array_toLocaleString(JSContext *cx, uint
 
     /*
      *  Passing comma here as the separator. Need a way to get a
      *  locale-specific version.
      */
     return array_toString_sub(cx, obj, JS_TRUE, NULL, vp);
 }
 
+static inline bool
+InitArrayTypes(JSContext *cx, TypeObject *type, const Value *vector, unsigned count)
+{
+    if (cx->typeInferenceEnabled() && !type->unknownProperties) {
+        AutoEnterTypeInference enter(cx);
+
+        TypeSet *types = type->getProperty(cx, JSID_VOID, true);
+        if (!types)
+            return JS_FALSE;
+
+        for (unsigned i = 0; i < count; i++) {
+            if (vector[i].isMagic(JS_ARRAY_HOLE))
+                continue;
+            jstype valtype = GetValueType(cx, vector[i]);
+            types->addType(cx, valtype);
+        }
+
+        return cx->compartment->types.checkPendingRecompiles(cx);
+    }
+    return true;
+}
+
 static JSBool
-InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Value *vector)
+InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Value *vector, bool updateTypes)
 {
     JS_ASSERT(count < MAXINDEX);
 
     if (count == 0)
         return JS_TRUE;
 
+    if (updateTypes && !InitArrayTypes(cx, obj->getType(), vector, count))
+        return JS_FALSE;
+
     /*
      * Optimize for dense arrays so long as adding the given set of elements
      * wouldn't otherwise make the array slow.
      */
     do {
         if (!obj->isDenseArray())
             break;
         if (js_PrototypeHasIndexedProperties(cx, obj))
@@ -1474,16 +1499,19 @@ InitArrayObject(JSContext *cx, JSObject 
     JS_ASSERT(obj->isArray());
 
     JS_ASSERT(obj->isDenseArray());
     if (!obj->setArrayLength(cx, length))
         return false;
     if (!vector || !length)
         return true;
 
+    if (!InitArrayTypes(cx, obj->getType(), vector, length))
+        return false;
+
     /* Avoid ensureDenseArrayElements to skip sparse array checks there. */
     if (!obj->ensureSlots(cx, length))
         return false;
     obj->setDenseArrayInitializedLength(length);
     bool hole = false;
     for (jsuint i = 0; i < length; i++) {
         obj->setDenseArrayElement(i, vector[i]);
         hole |= vector[i].isMagic(JS_ARRAY_HOLE);
@@ -2036,17 +2064,17 @@ js::array_sort(JSContext *cx, uintN argc
         }
 
         /*
          * We no longer need to root the scratch space for the merge sort, so
          * unroot it now to make the job of a potential GC under
          * InitArrayElements easier.
          */
         tvr.changeLength(newlen);
-        if (!InitArrayElements(cx, obj, 0, newlen, vec))
+        if (!InitArrayElements(cx, obj, 0, newlen, vec, false))
             return false;
     }
 
     /* Set undefs that sorted after the rest of elements. */
     while (undefs != 0) {
         --undefs;
         if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !SetArrayElement(cx, obj, newlen++, UndefinedValue())) {
@@ -2068,17 +2096,17 @@ js::array_sort(JSContext *cx, uintN argc
  */
 static JSBool
 array_push_slowly(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
 {
     jsuint length;
 
     if (!js_GetLengthProperty(cx, obj, &length))
         return JS_FALSE;
-    if (!InitArrayElements(cx, obj, length, argc, argv))
+    if (!InitArrayElements(cx, obj, length, argc, argv, true))
         return JS_FALSE;
 
     /* Per ECMA-262, return the new array length. */
     jsdouble newlength = length + jsdouble(argc);
     rval->setNumber(newlength);
 
     /* watch for length overflowing to a double. */
     if (!rval->isInt32() && !cx->markTypeCallerOverflow())
@@ -2095,16 +2123,19 @@ array_push1_dense(JSContext* cx, JSObjec
         JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, 1);
         if (result != JSObject::ED_OK) {
             if (result == JSObject::ED_FAILED)
                 return false;
             JS_ASSERT(result == JSObject::ED_SPARSE);
             break;
         }
 
+        if (cx->typeInferenceEnabled() && !cx->addTypePropertyId(obj->getType(), JSID_VOID, v))
+            return false;
+
         obj->setDenseArrayLength(length + 1);
         obj->setDenseArrayElement(length, v);
         rval->setNumber(obj->getArrayLength());
         return true;
     } while (false);
 
     if (!obj->makeDenseArraySlow(cx))
         return false;
@@ -2171,19 +2202,16 @@ JS_DEFINE_CALLINFO_3(extern, BOOL_FAIL, 
 
 static JSBool
 array_push(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *obj = ToObject(cx, &vp[1]);
     if (!obj)
         return false;
 
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(obj->getType()))
-        return false;
-
     /* Insist on one argument and obj of the expected class. */
     if (argc != 1 || !obj->isDenseArray())
         return array_push_slowly(cx, obj, argc, vp + 2, vp);
 
     return array_push1_dense(cx, obj, vp[2], vp);
 }
 
 static JSBool
@@ -2307,19 +2335,16 @@ array_unshift(JSContext *cx, uintN argc,
     JSObject *obj = ToObject(cx, &vp[1]);
     if (!obj)
         return false;
 
     jsuint length;
     if (!js_GetLengthProperty(cx, obj, &length))
         return JS_FALSE;
 
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(obj->getType()))
-        return false;
-
     newlen = length;
     if (argc > 0) {
         /* Slide up the array to make room for argc at the bottom. */
         argv = JS_ARGV(cx, vp);
         if (length > 0) {
             bool optimized = false;
             do {
                 if (!obj->isDenseArray())
@@ -2350,17 +2375,17 @@ array_unshift(JSContext *cx, uintN argc,
                         !SetOrDeleteArrayElement(cx, obj, upperIndex, hole, tvr.value())) {
                         return JS_FALSE;
                     }
                 } while (last != 0);
             }
         }
 
         /* Copy from argv to the bottom of the array. */
-        if (!InitArrayElements(cx, obj, 0, argc, argv))
+        if (!InitArrayElements(cx, obj, 0, argc, argv, true))
             return JS_FALSE;
 
         newlen += argc;
     }
     if (!js_SetLengthProperty(cx, obj, newlen))
         return JS_FALSE;
 
     /* Follow Perl by returning the new array length. */
@@ -2397,19 +2422,16 @@ array_splice(JSContext *cx, uintN argc, 
          * Make a new type object for the return value.  This is an unexpected
          * result of the call so mark it at the callsite.
          */
         type = cx->getTypeNewObject(JSProto_Array);
         if (!type || !cx->markTypeCallerUnexpected((jstype) type))
             return false;
     }
 
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(type))
-        return false;
-
     /* Create a new array value to return. */
     JSObject *obj2 = NewDenseEmptyArray(cx);
     if (!obj2)
         return JS_FALSE;
     obj2->setType(type);
     vp->setObject(*obj2);
 
     /* Nothing to do if no args.  Otherwise get length. */
@@ -2465,16 +2487,19 @@ array_splice(JSContext *cx, uintN argc, 
                 return JS_FALSE;
         } else {
             for (last = begin; last < end; last++) {
                 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
                     !GetElement(cx, obj, last, &hole, tvr.addr())) {
                     return JS_FALSE;
                 }
 
+                if (!cx->addTypePropertyId(obj2->getType(), JSID_VOID, tvr.value()))
+                    return JS_FALSE;
+
                 /* Copy tvr.value() to the new array unless it's a hole. */
                 if (!hole && !SetArrayElement(cx, obj2, last - begin, tvr.value()))
                     return JS_FALSE;
             }
 
             if (!js_SetLengthProperty(cx, obj2, count))
                 return JS_FALSE;
         }
@@ -2549,69 +2574,69 @@ array_splice(JSContext *cx, uintN argc, 
 
     if (length < origlength && !js_SuppressDeletedIndexProperties(cx, obj, length, origlength))
         return JS_FALSE;
 
     /*
      * Copy from argv into the hole to complete the splice, and update length in
      * case we deleted elements from the end.
      */
-    return InitArrayElements(cx, obj, begin, argc, argv) &&
+    return InitArrayElements(cx, obj, begin, argc, argv, true) &&
            js_SetLengthProperty(cx, obj, length);
 }
 
 /*
  * Python-esque sequence operations.
  */
 static JSBool
 array_concat(JSContext *cx, uintN argc, Value *vp)
 {
     /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
     Value *p = JS_ARGV(cx, vp) - 1;
 
     /* Get the type object to use for the result. */
     TypeObject *ntype = cx->getTypeCallerInitObject(true);
     if (!ntype)
         return false;
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(ntype))
-        return false;
 
     /* Create a new Array object and root it using *vp. */
     JSObject *aobj = ToObject(cx, &vp[1]);
     if (!aobj)
         return false;
 
     JSObject *nobj;
     jsuint length;
     if (aobj->isDenseArray()) {
         length = aobj->getArrayLength();
+        Value *vector = aobj->getDenseArrayElements();
         jsuint initlen = aobj->getDenseArrayInitializedLength();
-        nobj = NewDenseCopiedArray(cx, initlen, aobj->getDenseArrayElements());
+        nobj = NewDenseCopiedArray(cx, initlen, vector);
         if (!nobj)
             return JS_FALSE;
         nobj->setType(ntype);
         if (!nobj->setArrayLength(cx, length))
             return JS_FALSE;
+        if (!InitArrayTypes(cx, ntype, vector, length))
+            return JS_FALSE;
+        if (!aobj->isPackedDenseArray() && !nobj->setDenseArrayNotPacked(cx))
+            return JS_FALSE;
         vp->setObject(*nobj);
         if (argc == 0)
             return JS_TRUE;
         argc--;
         p++;
     } else {
         nobj = NewDenseEmptyArray(cx);
         if (!nobj)
             return JS_FALSE;
         nobj->setType(ntype);
         vp->setObject(*nobj);
         length = 0;
     }
 
-    if (aobj->isDenseArray() && !aobj->isPackedDenseArray() && !nobj->setDenseArrayNotPacked(cx))
-        return false;
-
     AutoValueRooter tvr(cx);
 
     /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
     for (uintN i = 0; i <= argc; i++) {
         if (!JS_CHECK_OPERATION_LIMIT(cx))
             return false;
         const Value &v = p[i];
         if (v.isObject()) {
@@ -2626,34 +2651,33 @@ array_concat(JSContext *cx, uintN argc, 
                     return false;
                 for (jsuint slot = 0; slot < alength; slot++) {
                     JSBool hole;
                     if (!JS_CHECK_OPERATION_LIMIT(cx) ||
                         !GetElement(cx, aobj, slot, &hole, tvr.addr())) {
                         return false;
                     }
 
+                    if (!hole && !cx->addTypePropertyId(ntype, JSID_VOID, tvr.value()))
+                        return false;
+
                     /*
                      * Per ECMA 262, 15.4.4.4, step 9, ignore nonexistent
                      * properties.
                      */
                     if (!hole &&
                         !SetArrayElement(cx, nobj, length+slot, tvr.value())) {
                         return false;
                     }
                 }
                 length += alength;
                 continue;
             }
         }
 
-        /*
-         * The type handler for Array.concat only handles array arguments.
-         * Inform type inference of any non-array arguments passed in.
-         */
         if (!cx->addTypePropertyId(ntype, JSID_VOID, v))
             return false;
 
         if (!SetArrayElement(cx, nobj, length, v))
             return false;
         length++;
     }
 
@@ -2721,19 +2745,16 @@ array_slice(JSContext *cx, uintN argc, V
          * Make a new type object for the return value.  This is an unexpected
          * result of the call so mark it at the callsite.
          */
         type = cx->getTypeNewObject(JSProto_Array);
         if (!type || !cx->markTypeCallerUnexpected((jstype) type))
             return false;
     }
 
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(type))
-        return false;
-
     if (obj->isDenseArray() && end <= obj->getDenseArrayCapacity() &&
         !js_PrototypeHasIndexedProperties(cx, obj)) {
         nobj = NewDenseCopiedArray(cx, end - begin, obj->getDenseArrayElements() + begin);
         if (!nobj)
             return JS_FALSE;
         nobj->setType(type);
         if (!obj->isPackedDenseArray() && !nobj->setDenseArrayNotPacked(cx))
             return JS_FALSE;
@@ -2749,16 +2770,18 @@ array_slice(JSContext *cx, uintN argc, V
     vp->setObject(*nobj);
 
     AutoValueRooter tvr(cx);
     for (slot = begin; slot < end; slot++) {
         if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !GetElement(cx, obj, slot, &hole, tvr.addr())) {
             return JS_FALSE;
         }
+        if (!hole && !cx->addTypePropertyId(nobj->getType(), JSID_VOID, tvr.value()))
+            return false;
         if (!hole && !SetArrayElement(cx, nobj, slot - begin, tvr.value()))
             return JS_FALSE;
     }
 
     return JS_TRUE;
 }
 
 #if JS_HAS_ARRAY_EXTRAS
@@ -2954,25 +2977,16 @@ array_extra(JSContext *cx, ArrayExtraMod
     }
 
     if (length == 0)
         return JS_TRUE;
 
     Value thisv = (argc > 1 && !REDUCE_MODE(mode)) ? argv[1] : UndefinedValue();
 
     /*
-     * If the callsite is being monitored for type inference, and we are modifying
-     * the output array, monitor any reads on the array in the future.
-     */
-    if (cx->isTypeCallerMonitored() && (mode == MAP || mode == FILTER) &&
-        !cx->markTypeObjectUnknownProperties(newtype)) {
-        return false;
-    }
-
-    /*
      * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
      * requires 4 args (accum, value, index, array).
      */
     argc = 3 + REDUCE_MODE(mode);
 
     InvokeSessionGuard session;
     if (!session.start(cx, ObjectValue(*callable), thisv, argc))
         return JS_FALSE;
@@ -3362,36 +3376,34 @@ js_Array(JSContext *cx, uintN argc, Valu
 
     TypeObject *type = cx->getTypeCallerInitObject(true);
     if (!type)
         return JS_FALSE;
 
     if (argc == 0) {
         obj = NewDenseEmptyArray(cx);
     } else if (argc > 1) {
+        if (!InitArrayTypes(cx, type, vp + 2, argc))
+            return false;
         obj = NewDenseCopiedArray(cx, argc, vp + 2);
     } else if (!vp[2].isNumber()) {
-        /* Unexpected case for type inference. */
         if (!cx->addTypeProperty(type, NULL, vp[2]))
             return false;
-
         obj = NewDenseCopiedArray(cx, 1, vp + 2);
     } else {
         jsuint length;
         if (!ValueToLength(cx, vp + 2, &length))
             return JS_FALSE;
         obj = NewDenseUnallocatedArray(cx, length);
     }
 
     if (!obj)
         return JS_FALSE;
 
     obj->setType(type);
-    if (cx->isTypeCallerMonitored() && !cx->markTypeObjectUnknownProperties(type))
-        return false;
 
     /* If the length calculation overflowed, make sure that is marked for the new type. */
     if (obj->getArrayLength() > INT32_MAX && !obj->setArrayLength(cx, obj->getArrayLength()))
         return false;
 
     vp->setObject(*obj);
     return JS_TRUE;
 }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2151,19 +2151,16 @@ public:
 
     /* Set the type information for fun to the specified script. */
     inline void setTypeFunctionScript(JSFunction *fun, JSScript *script);
 
     /* Get a type object for the immediate allocation site in this context. */
     inline js::types::TypeObject *
     getTypeCallerInitObject(bool isArray);
 
-    /* Whether the immediate caller is being monitored for side effects. */
-    inline bool isTypeCallerMonitored();
-
     /* Mark the immediate allocation site as having produced an unexpected value. */
     inline bool markTypeCallerUnexpected(js::types::jstype type);
     inline bool markTypeCallerUnexpected(const js::Value &value);
     inline bool markTypeCallerOverflow();
 
     /*
      * Monitor a javascript call, either on entry to the interpreter or made
      * from within the interpreter.
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -453,16 +453,17 @@ TypeSet::addBaseSubset(JSContext *cx, Ty
 class TypeConstraintCondensed : public TypeConstraint
 {
 public:
     TypeConstraintCondensed(JSScript *script)
         : TypeConstraint("condensed", script)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
+    void arrayNotPacked(JSContext *cx, bool notDense);
 
     bool condensed() { return true; }
 };
 
 void
 TypeSet::addCondensed(JSContext *cx, JSScript *script)
 {
     TypeConstraintCondensed *constraint =
@@ -1213,16 +1214,24 @@ TypeConstraintCondensed::newType(JSConte
          * describe what the compiler depends on are in place.
          */
         return;
     }
 
     AnalyzeScriptTypes(cx, script);
 }
 
+void
+TypeConstraintCondensed::arrayNotPacked(JSContext *cx, bool notDense)
+{
+    if (script->types)
+        return;
+    AnalyzeScriptTypes(cx, script);
+}
+
 /*
  * Constraint which triggers recompilation of a script if a possible new JSValueType
  * tag is realized for a type set.
  */
 class TypeConstraintFreezeTypeTag : public TypeConstraint
 {
 public:
     /*
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -231,29 +231,16 @@ JSContext::getTypeCallerInitObject(bool 
         JSStackFrame *caller = js_GetScriptedCaller(this, NULL);
         if (caller)
             return caller->script()->getTypeInitObject(this, caller->pc(this), isArray);
     }
     return getTypeNewObject(isArray ? JSProto_Array : JSProto_Object);
 }
 
 inline bool
-JSContext::isTypeCallerMonitored()
-{
-    if (typeInferenceEnabled()) {
-        JSStackFrame *caller = js_GetScriptedCaller(this, NULL);
-        if (!caller)
-            return true;
-        JSScript *script = caller->script();
-        return !script->types || script->types->monitored(caller->pc(this) - script->code);
-    }
-    return false;
-}
-
-inline bool
 JSContext::markTypeCallerUnexpected(js::types::jstype type)
 {
     if (!typeInferenceEnabled())
         return true;
     JSStackFrame *caller = js_GetScriptedCaller(this, NULL);
     if (!caller)
         return true;
     return caller->script()->typeMonitorResult(this, caller->pc(this), type);
@@ -269,41 +256,41 @@ inline bool
 JSContext::markTypeCallerOverflow()
 {
     return markTypeCallerUnexpected(js::types::TYPE_DOUBLE);
 }
 
 inline bool
 JSContext::addTypeProperty(js::types::TypeObject *obj, const char *name, js::types::jstype type)
 {
-    if (typeInferenceEnabled()) {
+    if (typeInferenceEnabled() && !obj->unknownProperties) {
         jsid id = JSID_VOID;
         if (name) {
             JSAtom *atom = js_Atomize(this, name, strlen(name), 0);
             if (!atom)
                 return false;
             id = ATOM_TO_JSID(atom);
         }
         return addTypePropertyId(obj, id, type);
     }
     return true;
 }
 
 inline bool
 JSContext::addTypeProperty(js::types::TypeObject *obj, const char *name, const js::Value &value)
 {
-    if (typeInferenceEnabled())
+    if (typeInferenceEnabled() && !obj->unknownProperties)
         return addTypeProperty(obj, name, js::types::GetValueType(this, value));
     return true;
 }
 
 inline bool
 JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::jstype type)
 {
-    if (!typeInferenceEnabled())
+    if (!typeInferenceEnabled() || obj->unknownProperties)
         return true;
 
     /* Convert string index properties into the common index property. */
     id = js::types::MakeTypeId(this, id);
 
     js::types::AutoEnterTypeInference enter(this);
 
     js::types::TypeSet *types = obj->getProperty(this, id, true);
@@ -316,17 +303,17 @@ JSContext::addTypePropertyId(js::types::
     types->addType(this, type);
 
     return compartment->types.checkPendingRecompiles(this);
 }
 
 inline bool
 JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, const js::Value &value)
 {
-    if (typeInferenceEnabled())
+    if (typeInferenceEnabled() && !obj->unknownProperties)
         return addTypePropertyId(obj, id, js::types::GetValueType(this, value));
     return true;
 }
 
 inline js::types::TypeObject *
 JSContext::getTypeEmpty()
 {
     if (!compartment->types.typeEmpty)
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2700,16 +2700,21 @@ mjit::Compiler::emitUncachedCall(uint32 
     masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg);
 
     frame.popn(argc + 2);
 
     frame.takeReg(JSReturnReg_Type);
     frame.takeReg(JSReturnReg_Data);
     frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data, knownPushedType(0), pushedTypeSet(0));
 
+    if (recompiling) {
+        /* Native call case for recompilation. */
+        OOL_STUBCALL(JS_FUNC_TO_DATA_PTR(void *, callingNew ? ic::NativeNew : ic::NativeCall));
+    }
+
     stubcc.linkExitDirect(notCompiled, stubcc.masm.label());
     stubcc.rejoin(Changes(1));
     callPatches.append(callPatch);
 }
 
 static bool
 IsLowerableFunCallOrApply(jsbytecode *pc)
 {