[INFER] Fast path reads of holes from dense arrays, bug 619343.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 15 Dec 2010 18:21:45 -0800
changeset 74661 6e0795e82953f4f462fc19dbfb3a2011341e01f2
parent 74660 8492590010d7d06670817d2c4cd6e5d9e62201da
child 74662 a2630fc3cd9001dcdb391077cc62b38a550ab7ff
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs619343
milestone2.0b8pre
[INFER] Fast path reads of holes from dense arrays, bug 619343.
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastOps.cpp
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1267,66 +1267,45 @@ TypeSet::getKnownObjectKind(JSContext *c
          */
         add(cx, ArenaNew<TypeConstraintFreezeObjectKind>(script->analysis->pool,
                                                          kind, script), true);
     }
 
     return kind;
 }
 
-/*
- * Constraint which triggers recompilation of a script if a getter or setter is added
- * to a type set.
- */
-class TypeConstraintFreezeGetSet : public TypeConstraint
+/* Constraint which triggers recompilation if any type is added to a type set. */
+class TypeConstraintFreezeNonEmpty : public TypeConstraint
 {
 public:
     JSScript *script;
-    bool hasGetSet;
-
-    TypeConstraintFreezeGetSet(JSScript *script)
-        : TypeConstraint("freezeGetSet"),
-          script(script), hasGetSet(false)
+    bool hasType;
+
+    TypeConstraintFreezeNonEmpty(JSScript *script)
+        : TypeConstraint("freezeNonEmpty"),
+          script(script), hasType(false)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type)
     {
-        if (hasGetSet)
+        if (hasType)
             return;
 
-        if (type != TYPE_UNKNOWN) {
-            if (!TypeIsObject(type))
-                return;
-            if (cx->getFixedTypeObject(TYPE_OBJECT_GETSET) != (TypeObject *) type)
-                return;
-        }
-
-        hasGetSet = true;
-
+        hasType = true;
         cx->compartment->types.addPendingRecompile(cx, script);
     }
 };
 
 bool
-TypeSet::hasGetterSetter(JSContext *cx, JSScript *script)
+TypeSet::knownNonEmpty(JSContext *cx, JSScript *script)
 {
-    TypeObject *getset = cx->getFixedTypeObject(TYPE_OBJECT_GETSET);
-
-    if (objectCount >= 2) {
-        unsigned objectCapacity = HashSetCapacity(objectCount);
-        for (unsigned i = 0; i < objectCapacity; i++) {
-            if (getset == objectSet[i])
-                return true;
-        }
-    } else if (objectCount == 1) {
-        if (getset == (TypeObject *) objectSet)
-            return true;
-    }
-
-    add(cx, ArenaNew<TypeConstraintFreezeGetSet>(script->analysis->pool, script), true);
+    if (typeFlags != 0)
+        return true;
+
+    add(cx, ArenaNew<TypeConstraintFreezeNonEmpty>(script->analysis->pool, script), false);
 
     return false;
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeCompartment
 /////////////////////////////////////////////////////////////////////
 
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -266,18 +266,18 @@ struct TypeSet
      * set change in the future so that another type tag is possible, mark script
      * for recompilation.
      */
     JSValueType getKnownTypeTag(JSContext *cx, JSScript *script);
 
     /* Get information about the kinds of objects in this type set. */
     ObjectKind getKnownObjectKind(JSContext *cx, JSScript *script);
 
-    /* Get whether this type set contains any scripted getter or setter. */
-    bool hasGetterSetter(JSContext *cx, JSScript *script);
+    /* Get whether this type set is non-empty. */
+    bool knownNonEmpty(JSContext *cx, JSScript *script);
 };
 
 /*
  * Type information for a value pushed onto the stack at some execution point.
  * Stack nodes form equivalence classes: if at any time two stack nodes might
  * be at the same depth in the stack, they are considered equivalent.
  */
 struct TypeStack
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -5412,16 +5412,34 @@ mjit::Compiler::knownPushedType(uint32 p
 {
 #ifdef JS_TYPE_INFERENCE
     types::TypeSet *types = analysis->getCode(PC).pushed(pushed);
     return types->getKnownTypeTag(cx, script);
 #endif
     return JSVAL_TYPE_UNKNOWN;
 }
 
+bool
+mjit::Compiler::mayPushUndefined(uint32 pushed)
+{
+#ifdef JS_TYPE_INFERENCE
+    /*
+     * This should only be used when the compiler is checking if it is OK to push
+     * undefined without going to a stub that can trigger recompilation.
+     * If this returns false and undefined subsequently becomes a feasible
+     * value pushed by the bytecode, recompilation will *NOT* be triggered.
+     */
+    types::TypeSet *types = analysis->getCode(PC).pushed(pushed);
+    return types->hasType(types::TYPE_UNDEFINED);
+#else
+    JS_NOT_REACHED("mayPushUndefined without JS_TYPE_INFERENCE");
+    return false;
+#endif
+}
+
 void
 mjit::Compiler::markPushedOverflow(uint32 pushed)
 {
 #ifdef JS_TYPE_INFERENCE
     types::TypeSet *types = analysis->getCode(PC).pushed(pushed);
     JS_ASSERT(!types->hasType(types::TYPE_DOUBLE));
     types::InferSpew(types::ISpewDynamic, "StaticOverflow: #%u", analysis->id);
     cx->compartment->types.addDynamicType(cx, types, types::TYPE_DOUBLE);
@@ -5434,20 +5452,20 @@ mjit::Compiler::knownPoppedObjectKind(ui
 #ifdef JS_TYPE_INFERENCE
     types::TypeSet *types = analysis->getCode(PC).popped(popped);
     return types->getKnownObjectKind(cx, script);
 #endif
     return types::OBJECT_UNKNOWN;
 }
 
 bool
-mjit::Compiler::arrayPrototypeHasIndexedSetter()
+mjit::Compiler::arrayPrototypeHasIndexedProperty()
 {
 #ifdef JS_TYPE_INFERENCE
     types::TypeSet *arrayTypes =
         cx->getFixedTypeObject(types::TYPE_OBJECT_ARRAY_PROTOTYPE)->getProperty(cx, JSID_VOID, false);
     types::TypeSet *objectTypes =
         cx->getFixedTypeObject(types::TYPE_OBJECT_OBJECT_PROTOTYPE)->getProperty(cx, JSID_VOID, false);
-    return arrayTypes->hasGetterSetter(cx, script)
-        || objectTypes->hasGetterSetter(cx, script);
+    return arrayTypes->knownNonEmpty(cx, script)
+        || objectTypes->knownNonEmpty(cx, script);
 #endif
     return true;
 }
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -339,17 +339,18 @@ class Compiler : public BaseCompiler
     /* Analysis helpers. */
     void fixDoubleTypes(Uses uses);
     void restoreAnalysisTypes(uint32 stackDepth);
     JSValueType knownThisType();
     JSValueType knownArgumentType(uint32 arg);
     JSValueType knownLocalType(uint32 local);
     JSValueType knownPushedType(uint32 pushed);
     js::types::ObjectKind knownPoppedObjectKind(uint32 popped);
-    bool arrayPrototypeHasIndexedSetter();
+    bool arrayPrototypeHasIndexedProperty();
+    bool mayPushUndefined(uint32 pushed);
     void markPushedOverflow(uint32 pushed);
     void markLocalOverflow(uint32 local);
     void markArgumentOverflow(uint32 arg);
 
     /* Non-emitting helpers. */
     uint32 fullAtomIndex(jsbytecode *pc);
     bool jumpInScript(Jump j, jsbytecode *pc);
     bool compareTwoValues(JSContext *cx, JSOp op, const Value &lhs, const Value &rhs);
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -1275,17 +1275,17 @@ mjit::Compiler::jsop_setelem()
     if (!IsCacheableSetElem(obj, id, value) || analysis->monitored(PC)) {
         jsop_setelem_slow();
         return true;
     }
 
     types::ObjectKind kind = knownPoppedObjectKind(2);
     if (id->mightBeType(JSVAL_TYPE_INT32) &&
         (kind == types::OBJECT_DENSE_ARRAY || kind == types::OBJECT_PACKED_ARRAY) &&
-        !arrayPrototypeHasIndexedSetter()) {
+        !arrayPrototypeHasIndexedProperty()) {
         // this is definitely a dense array, generate code directly without
         // using an inline cache.
         jsop_setelem_dense();
         return true;
     }
 
     SetElementICInfo ic = SetElementICInfo(JSOp(*PC));
 
@@ -1481,51 +1481,70 @@ mjit::Compiler::jsop_getelem_dense(bool 
     MaybeRegisterID typeReg;
     if (!isPacked || type == JSVAL_TYPE_UNKNOWN || type == JSVAL_TYPE_DOUBLE)
         typeReg = frame.allocReg();
 
     frame.unpinReg(objReg);
     if (!key.isConstant() && !frame.haveSameBacking(id, obj))
         frame.unpinReg(key.reg());
 
+    // If we know the result of the GETELEM may be undefined, then misses on the
+    // initialized length or hole checks can just produce an undefined value.
+    // We checked in the caller that prototypes do not have indexed properties.
+    bool allowUndefined = mayPushUndefined(0);
+
     // Guard on the array's initialized length.
     Jump initlenGuard = masm.guardArrayExtent(offsetof(JSObject, initializedLength),
                                               objReg, key, Assembler::BelowOrEqual);
-    stubcc.linkExit(initlenGuard, Uses(2));
+    if (!allowUndefined)
+        stubcc.linkExit(initlenGuard, Uses(2));
 
     masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), dataReg);
 
     // Get the slot, skipping the hole check if the array is known to be packed.
     Jump holeCheck;
     if (key.isConstant()) {
         Address slot(dataReg, key.index() * sizeof(Value));
         holeCheck = masm.fastArrayLoadSlot(slot, !isPacked, typeReg, dataReg);
     } else {
         JS_ASSERT(key.reg() != dataReg);
         BaseIndex slot(dataReg, key.reg(), masm.JSVAL_SCALE);
         holeCheck = masm.fastArrayLoadSlot(slot, !isPacked, typeReg, dataReg);
     }
 
     if (!isPacked) {
-        stubcc.linkExit(holeCheck, Uses(2));
+        if (!allowUndefined)
+            stubcc.linkExit(holeCheck, Uses(2));
         if (type != JSVAL_TYPE_UNKNOWN && type != JSVAL_TYPE_DOUBLE)
             frame.freeReg(typeReg.reg());
     }
 
     stubcc.leave();
     OOL_STUBCALL(stubs::GetElem);
 
     frame.popn(2);
 
     if (type == JSVAL_TYPE_UNKNOWN || type == JSVAL_TYPE_DOUBLE)
         frame.pushRegs(typeReg.reg(), dataReg, type);
     else
         frame.pushTypedPayload(type, dataReg);
 
     stubcc.rejoin(Changes(2));
+
+    if (allowUndefined) {
+        stubcc.linkExitDirect(initlenGuard, stubcc.masm.label());
+        if (!isPacked)
+            stubcc.linkExitDirect(holeCheck, stubcc.masm.label());
+        JS_ASSERT(type == JSVAL_TYPE_UNKNOWN || type == JSVAL_TYPE_UNDEFINED);
+        if (type == JSVAL_TYPE_UNDEFINED)
+            stubcc.masm.loadValuePayload(UndefinedValue(), dataReg);
+        else
+            stubcc.masm.loadValueAsComponents(UndefinedValue(), typeReg.reg(), dataReg);
+        stubcc.linkRejoin(stubcc.masm.jump());
+    }
 }
 
 bool
 mjit::Compiler::jsop_getelem(bool isCall)
 {
     FrameEntry *obj = frame.peek(-2);
     FrameEntry *id = frame.peek(-1);
 
@@ -1535,17 +1554,18 @@ mjit::Compiler::jsop_getelem(bool isCall
         else
             jsop_getelem_slow();
         return true;
     }
 
     types::ObjectKind kind = knownPoppedObjectKind(1);
 
     if (!isCall && id->mightBeType(JSVAL_TYPE_INT32) &&
-        (kind == types::OBJECT_DENSE_ARRAY || kind == types::OBJECT_PACKED_ARRAY)) {
+        (kind == types::OBJECT_DENSE_ARRAY || kind == types::OBJECT_PACKED_ARRAY) &&
+        !arrayPrototypeHasIndexedProperty()) {
         // this is definitely a dense array, generate code directly without
         // using an inline cache.
         jsop_getelem_dense(kind == types::OBJECT_PACKED_ARRAY);
         return true;
     }
 
     GetElementICInfo ic = GetElementICInfo(JSOp(*PC));