[INFER] Analysis and compiler improvements for polymorphic call sites, bug 653962.
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 20 May 2011 19:33:06 -0700
changeset 75087 0b58cbabd2cc9872b75f86b16321320d66f5cda1
parent 75086 f96d9ed26fc8eeb1fe18497175e52702d372ccc9
child 75088 07412de099f6559db55251af1477d43f8f1164d7
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs653962
milestone6.0a1
[INFER] Analysis and compiler improvements for polymorphic call sites, bug 653962.
js/src/jsanalyze.h
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsmath.cpp
js/src/jsobj.cpp
js/src/jsstr.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FrameState.cpp
js/src/methodjit/StubCalls.cpp
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -966,26 +966,37 @@ class ScriptAnalysis
         return array + which;
     }
     types::TypeSet *pushedTypes(const jsbytecode *pc, uint32 which) {
         return pushedTypes(pc - script->code, which);
     }
 
     types::TypeBarrier *typeBarriers(uint32 offset) {
         if (getCode(offset).typeBarriers)
-            pruneTypeBarriers(NULL, offset);
+            pruneTypeBarriers(offset);
         return getCode(offset).typeBarriers;
     }
     types::TypeBarrier *typeBarriers(const jsbytecode *pc) {
         return typeBarriers(pc - script->code);
     }
     void addTypeBarrier(JSContext *cx, const jsbytecode *pc,
                         types::TypeSet *target, types::jstype type);
 
-    void pruneTypeBarriers(JSContext *removecx, uint32 offset);
+    /* Remove obsolete type barriers at the given offset. */
+    void pruneTypeBarriers(uint32 offset);
+
+    /*
+     * Remove still-active type barriers at the given offset. If 'all' is set,
+     * then all barriers are removed, otherwise only those deemed excessive
+     * are removed.
+     */
+    void breakTypeBarriers(JSContext *cx, uint32 offset, bool all);
+
+    /* Break all type barriers used in computing v. */
+    void breakTypeBarriersSSA(JSContext *cx, const SSAValue &v);
 
     inline void addPushedType(JSContext *cx, uint32 offset, uint32 which, types::jstype type);
 
     types::TypeSet *getValueTypes(const SSAValue &v) {
         switch (v.kind()) {
           case SSAValue::PUSHED:
             return pushedTypes(v.pushedOffset(), v.pushedIndex());
           case SSAValue::VAR:
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4332,115 +4332,94 @@ JS_CloneFunctionObject(JSContext *cx, JS
 
     return clone;
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerDynamic(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes)
-        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_UNKNOWN);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerVoid(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_UNDEFINED);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_UNDEFINED);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerNull(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_NULL);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_NULL);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerBool(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_BOOLEAN);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_BOOLEAN);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerInt(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_INT32);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_INT32);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerFloat(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_DOUBLE);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_DOUBLE);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerString(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_STRING);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_STRING);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeFunction *fun = Valueify(jsfun);
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->returnTypes)
-        return;
-
     TypeSet *prototypeTypes =
         fun->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), true);
     if (!prototypeTypes)
         return;
     prototypeTypes->addNewObject(cx, site->script, fun, site->returnTypes);
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerThis(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        if (site->thisTypes)
-            site->thisTypes->addSubset(cx, site->script, site->returnTypes);
-        else
-            site->returnTypes->addType(cx, site->thisType);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->thisTypes->addSubset(cx, site->script, site->returnTypes);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_GetFunctionObject(JSFunction *fun)
 {
     return FUN_OBJECT(fun);
 }
 
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3217,131 +3217,96 @@ array_every(JSContext *cx, uintN argc, V
  * on arrays are both handled by write barriers within the natives.
  */
 
 static void
 array_TypeSort(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->forceThisTypes(cx))
-        return;
-
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->thisTypes->addSubset(cx, site->script, site->returnTypes);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->thisTypes->addSubset(cx, site->script, site->returnTypes);
 }
 
 static void
 array_TypeInsert(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (site->returnTypes) {
-        /* The return type is an integer (array length). */
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->returnTypes->addType(cx, TYPE_INT32);
-    }
-
-    if (!site->forceThisTypes(cx))
-        return;
+    /* The return type is an integer (array length). */
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->returnTypes->addType(cx, TYPE_INT32);
 
     for (size_t ind = 0; ind < site->argumentCount; ind++) {
         site->thisTypes->addSetProperty(cx, site->script, site->pc,
                                         site->argumentTypes[ind], JSID_VOID);
     }
 }
 
 static void
 array_TypeRemove(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->returnTypes)
-        return;
-
     if (site->isNew)
         site->returnTypes->addType(cx, TYPE_UNKNOWN);
-
-    if (!site->forceThisTypes(cx))
-        return;
     site->thisTypes->addGetProperty(cx, site->script, site->pc, site->returnTypes, JSID_VOID);
 }
 
 static void
 array_TypeSplice(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->forceThisTypes(cx))
-        return;
-
-    if (site->returnTypes) {
-        /* Treat the returned array the same as the 'this' array. */
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->thisTypes->addSubset(cx, site->script, site->returnTypes);
-    }
+    /* Treat the returned array the same as the 'this' array. */
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->thisTypes->addSubset(cx, site->script, site->returnTypes);
 
     /* All arguments beyond the first two are new array elements. */
     for (size_t ind = 2; ind < site->argumentCount; ind++) {
         site->thisTypes->addSetProperty(cx, site->script, site->pc,
                                         site->argumentTypes[ind], JSID_VOID);
     }
 }
 
 static void
 array_TypeConcat(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
     if (!site->hasGlobal()) {
-        if (site->returnTypes)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
-    if (!site->forceThisTypes(cx))
-        return;
-
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->thisTypes->addSubset(cx, site->script, site->returnTypes);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->thisTypes->addSubset(cx, site->script, site->returnTypes);
 }
 
 static void
 array_TypeSlice(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->forceThisTypes(cx))
-        return;
-
-    if (site->returnTypes) {
-        if (site->isNew)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
-        site->thisTypes->addFilterPrimitives(cx, site->script, site->returnTypes, false);
-    }
+    if (site->isNew)
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
+    site->thisTypes->addFilterPrimitives(cx, site->script, site->returnTypes, false);
 }
 
 /* Handler for all higher order array builtins. */
 static void
 array_TypeExtra(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite,
                 ArrayExtraMode mode)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->returnTypes)
-        return;
-
     if (site->isNew)
         site->returnTypes->addType(cx, TYPE_UNKNOWN);
 
     switch (mode) {
 
       case FOREACH:
         site->returnTypes->addType(cx, TYPE_UNDEFINED);
         break;
@@ -3499,26 +3464,24 @@ js_Array(JSContext *cx, uintN argc, Valu
  * the resulting array.
  */
 static void
 array_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
     if (!site->hasGlobal()) {
-        if (site->returnTypes)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
     TypeObject *object = site->getInitObject(cx, true);
     if (!object)
         return;
-    if (site->returnTypes)
-        site->returnTypes->addType(cx, (jstype) object);
+    site->returnTypes->addType(cx, (jstype) object);
 
     if (object->unknownProperties())
         return;
 
     TypeSet *indexTypes = object->getProperty(cx, JSID_VOID, true);
     if (!indexTypes)
         return;
 
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -494,51 +494,51 @@ TypeSet::addCondensed(JSContext *cx, JSS
     add(cx, constraint, false);
     return true;
 }
 
 /* Constraints for reads/writes on object properties. */
 class TypeConstraintProp : public TypeConstraint
 {
 public:
-    const jsbytecode *pc;
+    jsbytecode *pc;
 
     /*
      * If assign is true, the target is used to update a property of the object.
      * If assign is false, the target is assigned the value of the property.
      */
     bool assign;
     TypeSet *target;
 
     /* Property being accessed. */
     jsid id;
 
-    TypeConstraintProp(JSScript *script, const jsbytecode *pc,
+    TypeConstraintProp(JSScript *script, jsbytecode *pc,
                        TypeSet *target, jsid id, bool assign)
         : TypeConstraint("prop", script), pc(pc),
           assign(assign), target(target), id(id)
     {
         JS_ASSERT(script && pc);
 
         /* If the target is NULL, this is as an inc/dec on the property. */
         JS_ASSERT_IF(!target, assign);
     }
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
 };
 
 void
-TypeSet::addGetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+TypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
                         TypeSet *target, jsid id)
 {
     add(cx, ArenaNew<TypeConstraintProp>(cx->compartment->pool, script, pc, target, id, false));
 }
 
 void
-TypeSet::addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+TypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
                         TypeSet *target, jsid id)
 {
     add(cx, ArenaNew<TypeConstraintProp>(cx->compartment->pool, script, pc, target, id, true));
 }
 
 /* Constraints for determining the 'this' object at sites invoked using 'new'. */
 class TypeConstraintNewObject : public TypeConstraint
 {
@@ -622,16 +622,53 @@ public:
 };
 
 void
 TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target)
 {
     add(cx, ArenaNew<TypeConstraintTransformThis>(cx->compartment->pool, script, target));
 }
 
+/*
+ * Constraint which adds a particular type to the 'this' types of all
+ * discovered scripted functions.
+ */
+class TypeConstraintPropagateThis : public TypeConstraint
+{
+public:
+    jstype type;
+
+    TypeConstraintPropagateThis(JSScript *script, jstype type)
+        : TypeConstraint("propagatethis", script), type(type)
+    {}
+
+    void newType(JSContext *cx, TypeSet *source, jstype type);
+};
+
+void
+TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type)
+{
+    /*
+     * If this will definitely be popped by a JSOP_NEW, don't add a constraint
+     * to modify the 'this' types of callees. The initial 'this' value will be
+     * outright ignored.
+     */
+    SSAValue calleev = SSAValue::PushedValue(pc - script->code, 0);
+    SSAUseChain *uses = script->analysis(cx)->useChain(calleev);
+
+    if (uses && !uses->next && uses->popped) {
+        jsbytecode *callpc = script->code + uses->offset;
+        UntrapOpcode untrap(cx, script, callpc);
+        if (JSOp(*callpc) == JSOP_NEW)
+            return;
+    }
+
+    add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, type));
+}
+
 /* Subset constraint which filters out primitive types. */
 class TypeConstraintFilterPrimitive : public TypeConstraint
 {
 public:
     TypeSet *target;
 
     /* Primitive types other than null and undefined are passed through. */
     bool onlyNullVoid;
@@ -655,54 +692,80 @@ public:
 
 void
 TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid)
 {
     add(cx, ArenaNew<TypeConstraintFilterPrimitive>(cx->compartment->pool,
                                                     script, target, onlyNullVoid));
 }
 
-/*
- * Cheesy limit on the number of objects we will tolerate in an observed type
- * set before refusing to add new type barriers for objects.
- * :FIXME: this heuristic sucks, and doesn't handle calls.
- */
-static const uint32 BARRIER_OBJECT_LIMIT = 10;
-
 void
-ScriptAnalysis::pruneTypeBarriers(JSContext *removecx, uint32 offset)
+ScriptAnalysis::pruneTypeBarriers(uint32 offset)
 {
     TypeBarrier **pbarrier = &getCode(offset).typeBarriers;
     while (*pbarrier) {
         TypeBarrier *barrier = *pbarrier;
         if (barrier->target->hasType(barrier->type)) {
             /* Barrier is now obsolete, it can be removed. */
             *pbarrier = barrier->next;
-        } else if (removecx && TypeIsObject(barrier->type) &&
-                   barrier->target->getObjectCount() >= BARRIER_OBJECT_LIMIT) {
+        } else {
+            pbarrier = &barrier->next;
+        }
+    }
+}
+
+/*
+ * Cheesy limit on the number of objects we will tolerate in an observed type
+ * set before refusing to add new type barriers for objects.
+ * :FIXME: this heuristic sucks, and doesn't handle calls.
+ */
+static const uint32 BARRIER_OBJECT_LIMIT = 10;
+
+void ScriptAnalysis::breakTypeBarriers(JSContext *cx, uint32 offset, bool all)
+{
+    TypeBarrier **pbarrier = &getCode(offset).typeBarriers;
+    while (*pbarrier) {
+        TypeBarrier *barrier = *pbarrier;
+        if (barrier->target->hasType(barrier->type) ) {
+            /* Barrier is now obsolete, it can be removed. */
+            *pbarrier = barrier->next;
+        } else if (all || (TypeIsObject(barrier->type) &&
+                           barrier->target->getObjectCount() >= BARRIER_OBJECT_LIMIT)) {
             /* Force removal of the barrier. */
-            barrier->target->addType(removecx, barrier->type);
+            barrier->target->addType(cx, barrier->type);
             *pbarrier = barrier->next;
         } else {
             pbarrier = &barrier->next;
         }
     }
 }
 
+void ScriptAnalysis::breakTypeBarriersSSA(JSContext *cx, const SSAValue &v)
+{
+    if (v.kind() != SSAValue::PUSHED)
+        return;
+
+    uint32 offset = v.pushedOffset();
+    if (JSOp(script->code[offset]) == JSOP_GETPROP)
+        breakTypeBarriersSSA(cx, poppedValue(offset, 0));
+
+    breakTypeBarriers(cx, offset, true);
+}
+
 /*
  * Subset constraint for property reads and argument passing which can add type
  * barriers on the read instead of passing types along.
  */
 class TypeConstraintSubsetBarrier : public TypeConstraint
 {
 public:
-    const jsbytecode *pc;
+    jsbytecode *pc;
     TypeSet *target;
 
-    TypeConstraintSubsetBarrier(JSScript *script, const jsbytecode *pc, TypeSet *target)
+    TypeConstraintSubsetBarrier(JSScript *script, jsbytecode *pc, TypeSet *target)
         : TypeConstraint("subsetBarrier", script), pc(pc), target(target)
     {
         JS_ASSERT(!target->intermediate());
     }
 
     void newType(JSContext *cx, TypeSet *source, jstype type)
     {
         if (!target->hasType(type)) {
@@ -710,17 +773,17 @@ public:
             return;
         }
 
         target->addType(cx, type);
     }
 };
 
 void
-TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target)
+TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target)
 {
     add(cx, ArenaNew<TypeConstraintSubsetBarrier>(cx->compartment->pool, script, pc, target));
 }
 
 /*
  * Type constraint which marks the result of 'for in' loops as unknown if the
  * iterated value could be a generator.
  */
@@ -797,17 +860,17 @@ GetPropertyObject(JSContext *cx, JSScrip
     return object;
 }
 
 /*
  * Handle a property access on a specific object. All property accesses go through
  * here, whether via x.f, x[f], or global name accesses.
  */
 static inline void
-PropertyAccess(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeObject *object,
+PropertyAccess(JSContext *cx, JSScript *script, jsbytecode *pc, TypeObject *object,
                bool assign, TypeSet *target, jsid id)
 {
     JS_ASSERT_IF(!target, assign);
 
     /* Monitor assigns on the 'prototype' property. */
     if (assign && id == id_prototype(cx)) {
         cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         return;
@@ -862,18 +925,29 @@ TypeConstraintProp::newType(JSContext *c
         if (assign)
             cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         else
             target->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
     TypeObject *object = GetPropertyObject(cx, script, type);
-    if (object)
+    if (object) {
         PropertyAccess(cx, script, pc, object, assign, target, id);
+
+        UntrapOpcode untrap(cx, script, pc);
+        if (!object->unknownProperties() &&
+            (JSOp(*pc) == JSOP_CALLPROP || JSOp(*pc) == JSOP_CALLELEM)) {
+            JS_ASSERT(!assign);
+            TypeSet *types = object->getProperty(cx, id, false);
+            if (!types)
+                return;
+            types->addPropagateThis(cx, script, pc, type);
+        }
+    }
 }
 
 void
 TypeConstraintNewObject::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     if (type == TYPE_UNKNOWN) {
         target->addType(cx, TYPE_UNKNOWN);
         return;
@@ -906,17 +980,17 @@ TypeConstraintNewObject::newType(JSConte
         target->addType(cx, (jstype) object);
     }
 }
 
 void
 TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     JSScript *script = callsite->script;
-    const jsbytecode *pc = callsite->pc;
+    jsbytecode *pc = callsite->pc;
 
     if (type == TYPE_UNKNOWN) {
         /* Monitor calls on unknown functions. */
         cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         return;
     }
 
     if (!TypeIsObject(type))
@@ -1004,45 +1078,66 @@ TypeConstraintCall::newType(JSContext *c
     }
 
     /* Add void type for any formals in the callee not supplied at the call site. */
     for (unsigned i = callsite->argumentCount; i < nargs; i++) {
         TypeSet *types = callee->argTypes(i);
         types->addType(cx, TYPE_UNDEFINED);
     }
 
-    /* Add a binding for the receiver object of the call. */
     if (callsite->isNew) {
+        /* Mark the callee as having been invoked with 'new'. */
         callee->typeSetNewCalled(cx);
 
         /*
          * If the script does not return a value then the pushed value is the new
          * object (typical case).
          */
-        if (callsite->returnTypes) {
-            callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
-            callee->returnTypes()->addFilterPrimitives(cx, script,
-                                                       callsite->returnTypes, false);
-        }
+        callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
+        callee->returnTypes()->addFilterPrimitives(cx, script, callsite->returnTypes, false);
     } else {
-        if (callsite->thisTypes) {
-            /* Add a binding for the receiver object of the call. */
-            callsite->thisTypes->addSubset(cx, script, callee->thisTypes());
-        } else {
-            JS_ASSERT(callsite->thisType != TYPE_NULL);
-            callee->thisTypes()->addType(cx, callsite->thisType);
-        }
-
-        /* Add a binding for the return value of the call. */
-        if (callsite->returnTypes)
-            callee->returnTypes()->addSubset(cx, script, callsite->returnTypes);
+        /*
+         * Add a binding for the return value of the call. We don't add a
+         * binding for the receiver object, as this is done with PropagateThis
+         * constraints added by the original JSOP_CALL* op. The type sets we
+         * manipulate here have lost any correlations between particular types
+         * in the 'this' and 'callee' sets, which we want to maintain for
+         * polymorphic JSOP_CALLPROP invocations.
+         */
+        callee->returnTypes()->addSubset(cx, script, callsite->returnTypes);
     }
 }
 
 void
+TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, jstype type)
+{
+    /*
+     * Ignore callees that are calling natives or where the callee is unknown;
+     * the latter will be marked as monitored by a TypeConstraintCall.
+     */
+    if (type == TYPE_UNKNOWN || !TypeIsObject(type))
+        return;
+
+    TypeObject *object = (TypeObject*) type;
+    if (object->unknownProperties() || !object->isFunction)
+        return;
+    TypeFunction *function = object->asFunction();
+
+    if (!function->script)
+        return;
+
+    JSScript *callee = function->script;
+
+    if (!callee->ensureTypeArray(cx))
+        return;
+
+    callee->thisTypes()->addType(cx, this->type);
+}
+
+void
 TypeConstraintArith::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     /*
      * We only model a subset of the arithmetic behavior that is actually
      * possible. The following need to be watched for at runtime:
      *
      * 1. Operations producing a double where no operand was a double.
      * 2. Operations producing a string where no operand was a string (addition only).
@@ -2691,17 +2786,29 @@ TypeObject::markUnknown(JSContext *cx)
             prop->types.setOwnProperty(cx, true);
         }
     }
 }
 
 void
 TypeObject::clearNewScript(JSContext *cx)
 {
-    JS_ASSERT(newScript);
+    JS_ASSERT(!newScriptCleared);
+    newScriptCleared = true;
+
+    /*
+     * It is possible for the object to not have a new script yet but to have
+     * one added in the future. When analyzing properties of new scripts we mix
+     * in adding constraints to trigger clearNewScript with changes to the
+     * type sets themselves (from breakTypeBarriers). It is possible that we
+     * could trigger one of these constraints before AnalyzeNewScriptProperties
+     * has finished, in which case we want to make sure that call fails.
+     */
+    if (!newScript)
+        return;
 
     AutoEnterTypeInference enter(cx);
 
     /*
      * Any definite properties we added due to analysis of the new script when
      * the type object was created are now invalid: objects with the same type
      * can be created by using 'new' on a different script or through some
      * other mechanism (e.g. Object.create). Rather than clear out the definite
@@ -3131,18 +3238,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
         if (id == ATOM_TO_JSID(cx->runtime->atomState.NaNAtom))
             seen->addType(cx, TYPE_DOUBLE);
         if (id == ATOM_TO_JSID(cx->runtime->atomState.InfinityAtom))
             seen->addType(cx, TYPE_DOUBLE);
 
         /* Handle as a property access. */
         PropertyAccess(cx, script, pc, script->getGlobalType(), false, seen, id);
 
-        if (op == JSOP_CALLGLOBAL || op == JSOP_CALLGNAME)
+        if (op == JSOP_CALLGLOBAL || op == JSOP_CALLGNAME) {
             pushed[1].addType(cx, TYPE_UNKNOWN);
+            pushed[0].addPropagateThis(cx, script, pc, TYPE_UNKNOWN);
+        }
 
         if (CheckNextTest(pc))
             pushed[0].addType(cx, TYPE_UNDEFINED);
 
         /*
          * GETGNAME can refer to non-global names if EvaluateInStackFrame
          * introduces new bindings.
          */
@@ -3167,18 +3276,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
       case JSOP_NAME:
       case JSOP_CALLNAME: {
         /*
          * The first value pushed by NAME/CALLNAME must always be added to the
          * bytecode types, we don't model these opcodes with inference.
          */
         TypeSet *seen = script->bytecodeTypes(pc);
         seen->addSubset(cx, script, &pushed[0]);
-        if (op == JSOP_CALLNAME)
+        if (op == JSOP_CALLNAME) {
             pushed[1].addType(cx, TYPE_UNKNOWN);
+            pushed[0].addPropagateThis(cx, script, pc, TYPE_UNKNOWN);
+        }
         break;
       }
 
       case JSOP_BINDGNAME:
       case JSOP_BINDNAME:
         break;
 
       case JSOP_SETGNAME: {
@@ -3208,26 +3319,30 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
         break;
       }
 
       case JSOP_GETFCSLOT:
       case JSOP_CALLFCSLOT: {
         unsigned index = GET_UINT16(pc);
         TypeSet *types = script->upvarTypes(index);
         types->addSubset(cx, script, &pushed[0]);
-        if (op == JSOP_CALLFCSLOT)
+        if (op == JSOP_CALLFCSLOT) {
             pushed[1].addType(cx, TYPE_UNDEFINED);
+            pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
+        }
         break;
       }
 
       case JSOP_GETUPVAR_DBG:
       case JSOP_CALLUPVAR_DBG:
         pushed[0].addType(cx, TYPE_UNKNOWN);
-        if (op == JSOP_CALLUPVAR_DBG)
+        if (op == JSOP_CALLUPVAR_DBG) {
             pushed[1].addType(cx, TYPE_UNDEFINED);
+            pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
+        }
         break;
 
       case JSOP_GETARG:
       case JSOP_CALLARG:
       case JSOP_GETLOCAL:
       case JSOP_CALLLOCAL: {
         uint32 slot = GetBytecodeSlot(script, pc);
         if (trackSlot(slot)) {
@@ -3239,18 +3354,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
             poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
         } else if (slot < TotalSlots(script)) {
             TypeSet *types = script->slotTypes(slot);
             types->addSubset(cx, script, &pushed[0]);
         } else {
             /* Local 'let' variable. Punt on types for these, for now. */
             pushed[0].addType(cx, TYPE_UNKNOWN);
         }
-        if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL)
+        if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) {
             pushed[1].addType(cx, TYPE_UNDEFINED);
+            pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
+        }
         break;
       }
 
       case JSOP_SETARG:
       case JSOP_SETLOCAL:
       case JSOP_SETLOCALPOP: {
         uint32 slot = GetBytecodeSlot(script, pc);
         if (!trackSlot(slot) && slot < TotalSlots(script)) {
@@ -3305,17 +3422,24 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
         break;
       }
 
       case JSOP_LENGTH:
       case JSOP_GETPROP:
       case JSOP_CALLPROP: {
         jsid id = GetAtomId(cx, script, pc, 0);
         TypeSet *seen = script->bytecodeTypes(pc);
+
+        /*
+         * For JSOP_CALLPROP, this will inspect the pc and add PropagateThis
+         * constraints. Different types for the receiver may be correlated with
+         * different callee scripts, and we want to retain such correlations.
+         */
         poppedTypes(pc, 0)->addGetProperty(cx, script, pc, seen, id);
+
         seen->addSubset(cx, script, &pushed[0]);
         if (op == JSOP_CALLPROP)
             poppedTypes(pc, 0)->addFilterPrimitives(cx, script, &pushed[1], true);
         if (CheckNextTest(pc))
             pushed[0].addType(cx, TYPE_UNDEFINED);
         break;
       }
 
@@ -3331,17 +3455,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
       /*
        * We only consider ELEM accesses on integers below. Any element access
        * which is accessing a non-integer property must be monitored.
        */
 
       case JSOP_GETELEM:
       case JSOP_CALLELEM: {
         TypeSet *seen = script->bytecodeTypes(pc);
+
+        /* Ditto the JSOP_CALLPROP case for propagating 'this'. */
         poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID);
+
         seen->addSubset(cx, script, &pushed[0]);
         if (op == JSOP_CALLELEM)
             poppedTypes(pc, 1)->addFilterPrimitives(cx, script, &pushed[1], true);
         if (CheckNextTest(pc))
             pushed[0].addType(cx, TYPE_UNDEFINED);
         break;
       }
 
@@ -3859,17 +3986,17 @@ class TypeConstraintClearDefiniteSingle 
 public:
     TypeObject *object;
 
     TypeConstraintClearDefiniteSingle(JSScript *script, TypeObject *object)
         : TypeConstraint("baseClearDefinite", script), object(object)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type) {
-        if (object->newScript && !source->getSingleObject())
+        if (!object->newScriptCleared && !source->getSingleObject())
             object->clearNewScript(cx);
     }
 };
 
 /*
  * Mark an intermediate type set such that changes will clear the definite
  * properties on a type object.
  */
@@ -4068,32 +4195,39 @@ AnalyzeNewScriptProperties(JSContext *cx
             SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
             if (calleev.kind() != SSAValue::PUSHED)
                 return false;
             jsbytecode *calleepc = script->code + calleev.pushedOffset();
             UntrapOpcode untrapCallee(cx, script, calleepc);
             if (JSOp(*calleepc) != JSOP_CALLPROP || calleev.pushedIndex() != 0)
                 return false;
 
+            /*
+             * This code may not have run yet, break any type barriers involved
+             * in performing the call (for the greater good!).
+             */
+            analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0));
+            analysis->breakTypeBarriers(cx, calleepc - script->code, true);
+
             TypeSet *funcallTypes = analysis->pushedTypes(calleepc, 0);
             TypeSet *scriptTypes = analysis->pushedTypes(calleepc, 1);
 
             /* Need to definitely be calling Function.call on a specific script. */
             TypeObject *funcallObj = funcallTypes->getSingleObject();
             if (!funcallObj || !funcallObj->singleton ||
                 !funcallObj->singleton->isFunction() ||
                 funcallObj->singleton->getFunctionPrivate()->maybeNative() != js_fun_call) {
                 return false;
             }
             TypeObject *scriptObj = scriptTypes->getSingleObject();
             if (!scriptObj || !scriptObj->isFunction || !scriptObj->asFunction()->script)
                 return false;
 
             /*
-             * Add constraints to clear definite properties from the type
+             * Generate constraints to clear definite properties from the type
              * should the Function.call or callee itself change in the future.
              */
             TypeIntermediateClearDefinite *funcallTrap =
                 cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 0, type);
             TypeIntermediateClearDefinite *calleeTrap =
                 cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 1, type);
             if (!funcallTrap || !calleeTrap) {
                 cx->compartment->types.setPendingNukeTypes(cx);
@@ -4495,17 +4629,17 @@ JSObject::makeNewType(JSContext *cx, JSS
     if (newScript && !type->unknownProperties()) {
         /* Strawman object to add properties to and watch for duplicates. */
         JSObject *baseobj = NewBuiltinClassInstance(cx, &js_ObjectClass, gc::FINALIZE_OBJECT16);
         if (!baseobj)
             return;
 
         Vector<TypeNewScript::Initializer> initializerList(cx);
         AnalyzeNewScriptProperties(cx, type, newScript, &baseobj, &initializerList);
-        if (baseobj && baseobj->slotSpan() > 0) {
+        if (baseobj && baseobj->slotSpan() > 0 && !type->newScriptCleared) {
             js::gc::FinalizeKind kind = js::gc::GetGCObjectKind(baseobj->slotSpan());
 
             /* We should not have overflowed the maximum number of fixed slots for an object. */
             JS_ASSERT(js::gc::GetGCKindSlots(kind) >= baseobj->slotSpan());
 
             TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0);
 
             /*
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -352,28 +352,29 @@ class TypeSet
     void setDefinite(unsigned slot) {
         JS_ASSERT(slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT));
         typeFlags |= TYPE_FLAG_DEFINITE_PROPERTY | (slot << TYPE_FLAG_DEFINITE_SHIFT);
     }
 
     /* Add specific kinds of constraints to this set. */
     inline void add(JSContext *cx, TypeConstraint *constraint, bool callExisting = true);
     void addSubset(JSContext *cx, JSScript *script, TypeSet *target);
-    void addGetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+    void addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
                         TypeSet *target, jsid id);
-    void addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+    void addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
                         TypeSet *target, jsid id);
     void addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target);
     void addCall(JSContext *cx, TypeCallsite *site);
     void addArith(JSContext *cx, JSScript *script,
                   TypeSet *target, TypeSet *other = NULL);
     void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target);
+    void addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type);
     void addFilterPrimitives(JSContext *cx, JSScript *script,
                              TypeSet *target, bool onlyNullVoid);
-    void addSubsetBarrier(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target);
+    void addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target);
 
     void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target);
     bool addCondensed(JSContext *cx, JSScript *script);
 
     /*
      * Make an intermediate type set with the specified debugging name,
      * not embedded in another structure.
      */
@@ -570,16 +571,19 @@ struct TypeObject
     TypeObjectFlags flags;
 
     /* Whether this is a function object, and may be cast into TypeFunction. */
     bool isFunction;
 
     /* Mark bit for GC. */
     bool marked;
 
+    /* If set, newScript information should not be installed on this object. */
+    bool newScriptCleared;
+
     /*
      * If non-NULL, objects of this type have always been constructed using
      * 'new' on the specified script, which adds some number of properties to
      * the object in a definite order before the object escapes.
      */
     TypeNewScript *newScript;
 
     /*
@@ -728,40 +732,34 @@ struct TypeFunction : public TypeObject
 /*
  * Type information about a callsite. this is separated from the bytecode
  * information itself so we can handle higher order functions not called
  * directly via a bytecode.
  */
 struct TypeCallsite
 {
     JSScript *script;
-    const jsbytecode *pc;
+    jsbytecode *pc;
 
     /* Whether this is a 'NEW' call. */
     bool isNew;
 
     /* Types of each argument to the call. */
     TypeSet **argumentTypes;
     unsigned argumentCount;
 
     /* Types of the this variable. */
     TypeSet *thisTypes;
 
-    /* Any definite type for 'this'. */
-    jstype thisType;
-
     /* Type set receiving the return value of this call. */
     TypeSet *returnTypes;
 
-    inline TypeCallsite(JSContext *cx, JSScript *script, const jsbytecode *pc,
+    inline TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc,
                         bool isNew, unsigned argumentCount);
 
-    /* Force creation of thisTypes. */
-    inline bool forceThisTypes(JSContext *cx);
-
     /* Get the new object at this callsite. */
     inline TypeObject* getInitObject(JSContext *cx, bool isArray);
 
     inline bool hasGlobal();
 };
 
 struct ArrayTableKey;
 typedef HashMap<ArrayTableKey,TypeObject*,ArrayTableKey,SystemAllocPolicy> ArrayTypeTable;
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -87,16 +87,18 @@ GetValueType(JSContext *cx, const Value 
 /*
  * Get the canonical representation of an id to use when doing inference.  This
  * maintains the constraint that if two different jsids map to the same property
  * in JS (e.g. 3 and "3"), they have the same type representation.
  */
 inline jsid
 MakeTypeId(JSContext *cx, jsid id)
 {
+    JS_ASSERT(!JSID_IS_EMPTY(id));
+
     /*
      * All integers must map to the aggregate property for index types, including
      * negative integers.
      */
     if (JSID_IS_INT(id))
         return JSID_VOID;
 
     /*
@@ -1232,36 +1234,25 @@ TypeSet::make(JSContext *cx, const char 
     return res;
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeCallsite
 /////////////////////////////////////////////////////////////////////
 
 inline
-TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, const jsbytecode *pc,
+TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc,
                            bool isNew, unsigned argumentCount)
     : script(script), pc(pc), isNew(isNew), argumentCount(argumentCount),
-      thisTypes(NULL), thisType(0), returnTypes(NULL)
+      thisTypes(NULL), returnTypes(NULL)
 {
     /* Caller must check for failure. */
     argumentTypes = ArenaArray<TypeSet*>(cx->compartment->pool, argumentCount);
 }
 
-inline bool
-TypeCallsite::forceThisTypes(JSContext *cx)
-{
-    if (thisTypes)
-        return true;
-    thisTypes = TypeSet::make(cx, "site_this");
-    if (thisTypes)
-        thisTypes->addType(cx, thisType);
-    return thisTypes != NULL;
-}
-
 inline TypeObject *
 TypeCallsite::getInitObject(JSContext *cx, bool isArray)
 {
     TypeObject *type = script->getTypeInitObject(cx, pc, isArray);
     if (!type)
         cx->compartment->types.setPendingNukeTypes(cx);
     return type;
 }
@@ -1276,16 +1267,17 @@ TypeCallsite::hasGlobal()
 // TypeObject
 /////////////////////////////////////////////////////////////////////
 
 inline TypeSet *
 TypeObject::getProperty(JSContext *cx, jsid id, bool assign)
 {
     JS_ASSERT(cx->compartment->activeInference);
     JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id));
+    JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == MakeTypeId(cx, id));
     JS_ASSERT_IF(JSID_IS_STRING(id), JSID_TO_STRING(id) != NULL);
     JS_ASSERT(!unknownProperties());
 
     Property **pprop = HashSetInsert<jsid,Property,Property>
                            (cx, propertySet, propertyCount, id, false);
     if (!pprop || (!*pprop && !addProperty(cx, id, pprop)))
         return NULL;
 
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -862,19 +862,16 @@ JS_DEFINE_TRCINFO_1(js_math_ceil,
     (1, (static, DOUBLE, math_ceil_tn, DOUBLE,          1, nanojit::ACCSET_NONE)))
 
 #endif /* JS_TRACER */
 
 static void math_TypeArith(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     types::TypeCallsite *site = Valueify(jssite);
 
-    if (!site->returnTypes)
-        return;
-
     if (site->isNew)
         site->returnTypes->addType(cx, types::TYPE_UNKNOWN);
 
     /* The zero-argument case will be handled as an overflow in the actual natives. */
     for (size_t ind = 0; ind < site->argumentCount; ind++)
         site->argumentTypes[ind]->addArith(cx, site->script, site->returnTypes);
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3899,22 +3899,20 @@ Class js_BlockClass = {
 static void object_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
     if (site->argumentCount == 0) {
         TypeObject *object = site->getInitObject(cx, false);
         if (!object)
             return;
-        if (site->returnTypes)
-            site->returnTypes->addType(cx, (jstype) object);
+        site->returnTypes->addType(cx, (jstype) object);
     } else {
         /* The value is converted to an object, don't keep track of the return type. */
-        if (site->returnTypes)
-            site->returnTypes->addType(cx, TYPE_UNKNOWN);
+        site->returnTypes->addType(cx, TYPE_UNKNOWN);
     }
 }
 
 JSObject *
 js_InitObjectClass(JSContext *cx, JSObject *obj)
 {
     JSObject *proto = js_InitClass(cx, obj, NULL, &js_ObjectClass, js_Object, 1,
                                    object_TypeNew,
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3198,19 +3198,16 @@ js_String_getelem(JSContext* cx, JSStrin
 JS_DEFINE_TRCINFO_1(str_concat,
     (3, (extern, STRING_RETRY, js_ConcatStrings, CONTEXT, THIS_STRING, STRING,
          1, nanojit::ACCSET_NONE)))
 
 static void type_StringSplit(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
     TypeCallsite *site = Valueify(jssite);
 
-    if (!site->returnTypes)
-        return;
-
     if (!site->hasGlobal()) {
         site->returnTypes->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
     if (site->isNew)
         site->returnTypes->addType(cx, TYPE_UNKNOWN);
 
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -4205,22 +4205,23 @@ mjit::Compiler::jsop_getprop(JSAtom *ato
     }
 
     /*
      * Check if we are accessing a known type which always has the property
      * in a particular inline slot. Get the property directly in this case,
      * without using an IC.
      */
     JSOp op = JSOp(*PC);
+    jsid id = ATOM_TO_JSID(atom);
     types::TypeSet *types = frame.extra(top).types;
     if (op == JSOP_GETPROP && types && !types->unknown() && types->getObjectCount() == 1 &&
-        !types->getObject(0)->unknownProperties()) {
+        !types->getObject(0)->unknownProperties() && id == types::MakeTypeId(cx, id)) {
         JS_ASSERT(usePropCache);
         types::TypeObject *object = types->getObject(0);
-        types::TypeSet *propertyTypes = object->getProperty(cx, ATOM_TO_JSID(atom), false);
+        types::TypeSet *propertyTypes = object->getProperty(cx, id, false);
         if (!propertyTypes)
             return false;
         if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
             types->addFreeze(cx);
             uint32 slot = propertyTypes->definiteSlot();
             bool isObject = top->isTypeKnown();
             if (!isObject) {
                 Jump notObject = frame.testObject(Assembler::NotEqual, top);
@@ -4700,20 +4701,165 @@ mjit::Compiler::testSingletonPropertyTyp
     JSObject *proto;
     if (!js_GetClassPrototype(cx, globalObj, key, &proto, NULL))
         return NULL;
 
     return testSingletonProperty(proto, id);
 }
 
 bool
+mjit::Compiler::jsop_callprop_dispatch(JSAtom *atom)
+{
+    /*
+     * Check for a CALLPROP which is a dynamic dispatch: every value it can
+     * push is a singleton, and the pushed value is determined by the type of
+     * the object being accessed. Return true if the CALLPROP has been fully
+     * processed, false if no code was generated.
+     */
+    FrameEntry *top = frame.peek(-1);
+    if (top->isNotType(JSVAL_TYPE_OBJECT))
+        return false;
+
+    jsid id = ATOM_TO_JSID(atom);
+    if (id != types::MakeTypeId(cx, id))
+        return false;
+
+    types::TypeSet *pushedTypes = pushedTypeSet(0);
+    if (pushedTypes->unknown() || pushedTypes->baseFlags() != 0)
+        return false;
+
+    /* Check every pushed value is a singleton. */
+    for (unsigned i = 0; i < pushedTypes->getObjectCount(); i++) {
+        types::TypeObject *object = pushedTypes->getObject(i);
+        if (object && !object->singleton)
+            return false;
+    }
+
+    types::TypeSet *objTypes = analysis->poppedTypes(PC, 0);
+    if (objTypes->unknown() || objTypes->getObjectCount() == 0)
+        return false;
+
+    pushedTypes->addFreeze(cx);
+
+    /* Map each type in the object to the resulting pushed value. */
+    Vector<JSObject *> results(CompilerAllocPolicy(cx, *this));
+
+    /*
+     * For each type of the base object, check it has no 'own' property for the
+     * accessed id and that its prototype does have such a property.
+     */
+    uint32 last = 0;
+    for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
+        types::TypeObject *object = objTypes->getObject(i);
+        if (!object) {
+            results.append(NULL);
+            continue;
+        }
+        if (object->unknownProperties() || !object->proto)
+            return false;
+        types::TypeSet *ownTypes = object->getProperty(cx, id, false);
+        if (ownTypes->isOwnProperty(cx, false))
+            return false;
+
+        if (!testSingletonProperty(object->proto, id))
+            return false;
+
+        types::TypeSet *protoTypes = object->proto->getType()->getProperty(cx, id, false);
+        JSObject *singleton = protoTypes->getSingleton(cx);
+        if (!singleton)
+            return false;
+
+        results.append(singleton);
+        last = i;
+    }
+
+    if (oomInVector)
+        return false;
+
+    objTypes->addFreeze(cx);
+
+    /* Done filtering, now generate code which dispatches on the type. */
+
+    if (!top->isType(JSVAL_TYPE_OBJECT)) {
+        Jump notObject = frame.testObject(Assembler::NotEqual, top);
+        stubcc.linkExit(notObject, Uses(1));
+    }
+
+    RegisterID reg = frame.tempRegForData(top);
+    frame.pinReg(reg);
+    RegisterID pushreg = frame.allocReg();
+    frame.unpinReg(reg);
+
+    Address typeAddress(reg, offsetof(JSObject, type));
+
+    Vector<Jump> rejoins(CompilerAllocPolicy(cx, *this));
+    MaybeJump lastMiss;
+
+    for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
+        types::TypeObject *object = objTypes->getObject(i);
+        if (!object) {
+            JS_ASSERT(results[i] == NULL);
+            continue;
+        }
+        if (lastMiss.isSet())
+            lastMiss.get().linkTo(masm.label(), &masm);
+
+        /*
+         * Check that the pushed result is actually in the known pushed types
+         * for the bytecode; this bytecode may have type barriers. Redirect to
+         * the stub to update said pushed types.
+         */
+        if (!pushedTypes->hasType((types::jstype) results[i]->getType())) {
+            JS_ASSERT(hasTypeBarriers(PC));
+            if (i == last) {
+                stubcc.linkExit(masm.jump(), Uses(1));
+                break;
+            } else {
+                lastMiss.setJump(masm.branchPtr(Assembler::NotEqual, typeAddress, ImmPtr(object)));
+                stubcc.linkExit(masm.jump(), Uses(1));
+                continue;
+            }
+        }
+
+        if (i == last) {
+            masm.move(ImmPtr(results[i]), pushreg);
+            break;
+        } else {
+            lastMiss.setJump(masm.branchPtr(Assembler::NotEqual, typeAddress, ImmPtr(object)));
+            masm.move(ImmPtr(results[i]), pushreg);
+            rejoins.append(masm.jump());
+        }
+    }
+
+    for (unsigned i = 0; i < rejoins.length(); i++)
+        rejoins[i].linkTo(masm.label(), &masm);
+
+    stubcc.leave();
+    stubcc.masm.move(ImmPtr(atom), Registers::ArgReg1);
+    OOL_STUBCALL(stubs::CallProp, REJOIN_FALLTHROUGH);
+
+    frame.dup();
+    // THIS THIS
+
+    frame.pushTypedPayload(JSVAL_TYPE_OBJECT, pushreg);
+    // THIS THIS FUN
+
+    frame.shift(-2);
+    // FUN THIS
+
+    stubcc.rejoin(Changes(2));
+    return true;
+}
+
+bool
 mjit::Compiler::jsop_callprop(JSAtom *atom)
 {
     FrameEntry *top = frame.peek(-1);
 
+    /* If the CALLPROP will definitely be fetching a particular value, nop it. */
     bool testObject;
     JSObject *singleton = pushedSingleton(0);
     if (singleton && singleton->isFunction() && !hasTypeBarriers(PC) &&
         testSingletonPropertyTypes(top, ATOM_TO_JSID(atom), &testObject)) {
         if (testObject) {
             Jump notObject = frame.testObject(Assembler::NotEqual, top);
             stubcc.linkExit(notObject, Uses(1));
             stubcc.leave();
@@ -4733,16 +4879,22 @@ mjit::Compiler::jsop_callprop(JSAtom *at
         // FUN THIS
 
         if (testObject)
             stubcc.rejoin(Changes(2));
 
         return true;
     }
 
+    /* Check for a dynamic dispatch. */
+    if (cx->typeInferenceEnabled()) {
+        if (jsop_callprop_dispatch(atom))
+            return true;
+    }
+
     /* If the incoming type will never PIC, take slow path. */
     if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_OBJECT) {
         if (top->getKnownType() == JSVAL_TYPE_STRING)
             return jsop_callprop_str(atom);
         return jsop_callprop_slow(atom);
     }
 
     if (top->isTypeKnown())
@@ -4761,23 +4913,24 @@ mjit::Compiler::jsop_setprop(JSAtom *ato
         jsop_setprop_slow(atom, usePropCache);
         return true;
     }
 
     /*
      * Set the property directly if we are accessing a known object which
      * always has the property in a particular inline slot.
      */
+    jsid id = ATOM_TO_JSID(atom);
     types::TypeSet *types = frame.extra(lhs).types;
-    if (JSOp(*PC) == JSOP_SETPROP &&
+    if (JSOp(*PC) == JSOP_SETPROP && id == types::MakeTypeId(cx, id) &&
         types && !types->unknown() && types->getObjectCount() == 1 &&
         !types->getObject(0)->unknownProperties()) {
         JS_ASSERT(usePropCache);
         types::TypeObject *object = types->getObject(0);
-        types::TypeSet *propertyTypes = object->getProperty(cx, ATOM_TO_JSID(atom), false);
+        types::TypeSet *propertyTypes = object->getProperty(cx, id, false);
         if (!propertyTypes)
             return false;
         if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
             types->addFreeze(cx);
             uint32 slot = propertyTypes->definiteSlot();
             bool isObject = lhs->isTypeKnown();
             if (!isObject) {
                 Jump notObject = frame.testObject(Assembler::NotEqual, lhs);
@@ -5674,30 +5827,31 @@ mjit::Compiler::jsop_getgname(uint32 ind
     /* Optimize singletons like Math for JSOP_CALLPROP. */
     JSObject *obj = pushedSingleton(0);
     if (obj && !hasTypeBarriers(PC) && testSingletonProperty(globalObj, ATOM_TO_JSID(atom))) {
         frame.push(ObjectValue(*obj));
         return;
     }
 
     /* Get the type of the global. */
+    jsid id = ATOM_TO_JSID(atom);
     JSValueType type = JSVAL_TYPE_UNKNOWN;
-    if (cx->typeInferenceEnabled() && globalObj->isGlobal() &&
+    if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) &&
         !globalObj->getType()->unknownProperties()) {
         /*
          * Get the type tag for the global either straight off it (in case this
          * is an INCGNAME op, and the pushed type set is wrong) or from the
          * pushed type set (if this is a GETGNAME op, and the property may have
          * a type barrier on it).
          */
         types::TypeSet *types;
         if (JSOp(*PC) == JSOP_GETGNAME || JSOp(*PC) == JSOP_CALLGNAME)
             types = pushedTypeSet(0);
         else
-            types = globalObj->getType()->getProperty(cx, ATOM_TO_JSID(atom), false);
+            types = globalObj->getType()->getProperty(cx, id, false);
         if (!types)
             return;
         type = types->getKnownTypeTag(cx);
 
         const js::Shape *shape = globalObj->nativeLookup(ATOM_TO_JSID(atom));
         if (shape && shape->hasDefaultGetterOrIsMethod() && shape->hasSlot()) {
             Value *value = &globalObj->getSlotRef(shape->slot);
             if (!value->isUndefined() && !types->isOwnProperty(cx, true)) {
@@ -5890,25 +6044,26 @@ void
 mjit::Compiler::jsop_setgname(JSAtom *atom, bool usePropertyCache, bool popGuaranteed)
 {
     if (monitored(PC)) {
         /* Global accesses are monitored only for a few names like __proto__. */
         jsop_setgname_slow(atom, usePropertyCache);
         return;
     }
 
-    if (cx->typeInferenceEnabled() && globalObj->isGlobal() &&
+    jsid id = ATOM_TO_JSID(atom);
+    if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) &&
         !globalObj->getType()->unknownProperties()) {
         /*
          * Note: object branding is disabled when inference is enabled. With
          * branding there is no way to ensure that a non-function property
          * can't get a function later and cause the global object to become
          * branded, requiring a shape change if it changes again.
          */
-        types::TypeSet *types = globalObj->getType()->getProperty(cx, ATOM_TO_JSID(atom), false);
+        types::TypeSet *types = globalObj->getType()->getProperty(cx, id, false);
         if (!types)
             return;
         const js::Shape *shape = globalObj->nativeLookup(ATOM_TO_JSID(atom));
         if (shape && !shape->isMethod() && shape->hasDefaultSetter() &&
             shape->writable() && shape->hasSlot() && !types->isOwnProperty(cx, true)) {
             watchGlobalReallocation();
             Value *value = &globalObj->getSlotRef(shape->slot);
             RegisterID reg = frame.allocReg();
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -641,16 +641,17 @@ class Compiler : public BaseCompiler
                       bool typeCheck = true, bool usePropCache = true);
     bool jsop_setprop(JSAtom *atom, bool usePropCache, bool popGuaranteed);
     void jsop_setprop_slow(JSAtom *atom, bool usePropCache = true);
     bool jsop_callprop_slow(JSAtom *atom);
     bool jsop_callprop(JSAtom *atom);
     bool jsop_callprop_obj(JSAtom *atom);
     bool jsop_callprop_str(JSAtom *atom);
     bool jsop_callprop_generic(JSAtom *atom);
+    bool jsop_callprop_dispatch(JSAtom *atom);
     bool jsop_instanceof();
     void jsop_name(JSAtom *atom, JSValueType type);
     bool jsop_xname(JSAtom *atom);
     void enterBlock(JSObject *obj);
     void leaveBlock();
     void emitEval(uint32 argc);
     void jsop_arguments();
     bool jsop_tableswitch(jsbytecode *pc);
--- a/js/src/methodjit/FrameState.cpp
+++ b/js/src/methodjit/FrameState.cpp
@@ -707,32 +707,42 @@ FrameState::syncForAllocation(RegisterAl
     if (inlineReturn)
         topEntry = a->parent->sp - (GET_ARGC(a->parent->PC) + 2);
 
     for (uint32 i = tracker.nentries - 1; i < tracker.nentries; i--) {
         FrameEntry *fe = tracker[i];
 
         if (deadEntry(fe, uses.nuses))
             continue;
-        if (topEntry && fe >= topEntry && !isTemporary(fe)) {
+        if (inlineReturn && fe >= topEntry && !isTemporary(fe)) {
             /*
              * The return value has already been stored, so there is no need to
              * keep any of the entries for this frame or for values popped once
              * the call returns intact. Forcibly evict any registers for these,
              * so that we don't emit sync code for them if we need a register
              * in syncFe below.
              */
             forgetAllRegs(fe);
             fe->resetSynced();
             continue;
         }
 
         /* Force syncs for locals which are dead at the current PC. */
         if (isLocal(fe) && !a->analysis->slotEscapes(entrySlot(fe))) {
-            Lifetime *lifetime = variableLive(fe, a->PC);
+            Lifetime *lifetime = a->analysis->liveness(entrySlot(fe)).live(a->PC - a->script->code);
+            if (!lifetime)
+                fakeSync(fe);
+        }
+
+        /* If returning from a script, fake syncs for dead locals in the immediate parent. */
+        if (inlineReturn && fe >= a->parent->locals &&
+            fe - a->parent->locals < a->parent->script->nfixed &&
+            !a->parent->analysis->slotEscapes(frameSlot(a->parent, fe))) {
+            const LifetimeVariable &var = a->parent->analysis->liveness(frameSlot(a->parent, fe));
+            Lifetime *lifetime = var.live(a->parent->PC - a->parent->script->code);
             if (!lifetime)
                 fakeSync(fe);
         }
 
         if (!fe->isCopy() && alloc->hasAnyReg(fe - entries)) {
             /* Types are always synced, except for known doubles. */
             if (!fe->isType(JSVAL_TYPE_DOUBLE))
                 syncType(fe);
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -2747,24 +2747,24 @@ stubs::TypeBarrierHelper(VMFrame &f, uin
 {
     JS_ASSERT(which == 0 || which == 1);
 
     /* The actual pushed value is at sp[0], fix up the stack. See finishBarrier. */
     Value &result = f.regs.sp[-1 - (int)which];
     result = f.regs.sp[0];
 
     /*
-     * Prune type barriers at this bytecode if we have added many objects to
+     * Break type barriers at this bytecode if we have added many objects to
      * the target already. This isn't needed if inference results for the
      * script have been destroyed, as we will reanalyze and prune type barriers
      * as they are regenerated.
      */
     if (f.script()->hasAnalysis() && f.script()->analysis(f.cx)->ranInference()) {
         AutoEnterTypeInference enter(f.cx);
-        f.script()->analysis(f.cx)->pruneTypeBarriers(f.cx, f.pc() - f.script()->code);
+        f.script()->analysis(f.cx)->breakTypeBarriers(f.cx, f.pc() - f.script()->code, false);
     }
 
     f.script()->typeMonitor(f.cx, f.pc(), result);
 }
 
 void JS_FASTCALL
 stubs::NegZeroHelper(VMFrame &f)
 {