[INFER] Direct instance property accesses, bug 649376.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 12 Apr 2011 20:39:16 -0700
changeset 74932 440e9b0a41afbc0dc486df51fba261c5b74538dd
parent 74931 a4131835b866364816d60729d80f27c294180b1f
child 74933 2ad374159c492587ad0cbf7d52078dc9693b0aee
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs649376
milestone2.2a1pre
[INFER] Direct instance property accesses, bug 649376.
js/src/jit-test/tests/jaeger/propertyOptimize-1.js
js/src/jit-test/tests/jaeger/propertyOptimize-2.js
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FrameState-inl.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/jaeger/propertyOptimize-1.js
@@ -0,0 +1,29 @@
+
+function Foo(x)
+{
+  this.f = x + 10;
+}
+
+function Bar()
+{
+  this.g = 0;
+}
+
+Bar.prototype = Foo.prototype;
+
+var x = new Foo(0);
+var y = new Bar();
+
+assertEq(10, eval("x.f"));
+assertEq(undefined, eval("y.f"));
+
+function Other(x)
+{
+  this.f = x + 10;
+}
+
+var a = new Other(0);
+var b = Object.create(Other.prototype);
+
+assertEq(10, eval("a.f"));
+assertEq(undefined, eval("b.f"));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/jaeger/propertyOptimize-2.js
@@ -0,0 +1,16 @@
+
+function Foo(x)
+{
+  this.f = x + 10;
+}
+
+var x = new Foo(0);
+assertEq(10, eval("x.f"));
+
+called = false;
+Object.defineProperty(Foo.prototype, 'f', {set: function() { called = true; }});
+
+var y = new Foo(0);
+assertEq(10, eval("x.f"));
+assertEq(undefined, eval("y.f"));
+assertEq(called, true);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2324,17 +2324,18 @@ class AutoGCRooter {
         XML =          -9, /* js::AutoXMLRooter */
         OBJECT =      -10, /* js::AutoObjectRooter */
         ID =          -11, /* js::AutoIdRooter */
         VALVECTOR =   -12, /* js::AutoValueVector */
         DESCRIPTOR =  -13, /* js::AutoPropertyDescriptorRooter */
         STRING =      -14, /* js::AutoStringRooter */
         IDVECTOR =    -15, /* js::AutoIdVector */
         BINDINGS =    -16, /* js::Bindings */
-        SHAPEVECTOR = -17  /* js::AutoShapeVector */
+        SHAPEVECTOR = -17, /* js::AutoShapeVector */
+        TYPE =        -18  /* js::types::AutoTypeRooter */
     };
 
     private:
     /* No copy or assignment semantics. */
     AutoGCRooter(AutoGCRooter &ida);
     void operator=(AutoGCRooter &ida);
 };
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1606,16 +1606,23 @@ AutoGCRooter::trace(JSTracer *trc)
         MarkShapeRange(trc, vector.length(), vector.begin(), "js::AutoShapeVector.vector");
         return;
       }
 
       case BINDINGS: {
         static_cast<js::AutoBindingsRooter *>(this)->bindings.trace(trc);
         return;
       }
+
+      case TYPE: {
+        types::TypeObject *type = static_cast<types::AutoTypeRooter *>(this)->type;
+        if (!type->marked)
+            type->trace(trc);
+        return;
+      }
     }
 
     JS_ASSERT(tag >= 0);
     MarkValueRange(trc, tag, static_cast<AutoArrayRooter *>(this)->array, "js::AutoArrayRooter.array");
 }
 
 namespace js {
 
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -55,16 +55,17 @@
 #include "jsstr.h"
 #include "jstl.h"
 #include "jsiter.h"
 
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Retcon.h"
 
 #include "jsatominlines.h"
+#include "jsgcinlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #ifdef JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
@@ -377,21 +378,21 @@ TypeSet::addTypeSet(JSContext *cx, Clone
     } else if (types->objectCount == 1) {
         addType(cx, (jstype) types->objectSet);
     }
 }
 
 inline void
 TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting)
 {
-    JS_ASSERT_IF(!constraint->condensed() && !constraint->baseSubset(),
+    JS_ASSERT_IF(!constraint->condensed() && !constraint->persistentObject(),
                  constraint->script->compartment == cx->compartment);
     JS_ASSERT_IF(!constraint->condensed(), cx->compartment->types.inferenceDepth);
     JS_ASSERT_IF(typeFlags & TYPE_FLAG_INTERMEDIATE_SET,
-                 !constraint->baseSubset() && !constraint->condensed());
+                 !constraint->persistentObject() && !constraint->condensed());
 
     if (!constraint) {
         /* OOM failure while constructing the constraint. */
         cx->compartment->types.setPendingNukeTypes(cx);
     }
 
     InferSpew(ISpewOps, "addConstraint: T%p C%p %s",
               this, constraint, constraint->kind());
@@ -427,16 +428,19 @@ TypeSet::add(JSContext *cx, TypeConstrai
 void
 TypeSet::print(JSContext *cx)
 {
     if (typeFlags & TYPE_FLAG_OWN_PROPERTY)
         printf(" [own]");
     if (typeFlags & TYPE_FLAG_CONFIGURED_PROPERTY)
         printf(" [configured]");
 
+    if (isDefiniteProperty())
+        printf(" [definite:%d]", definiteSlot());
+
     if (baseFlags() == 0 && !objectCount) {
         printf(" missing");
         return;
     }
 
     if (typeFlags & TYPE_FLAG_UNKNOWN)
         printf(" unknown");
 
@@ -507,25 +511,47 @@ public:
 
     TypeConstraintBaseSubset(TypeObject *object, TypeSet *target)
         : TypeConstraint("baseSubset", (JSScript *) 0x1),
           object(object), target(target)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
 
-    TypeObject * baseSubset() { return object; }
+    TypeObject * persistentObject() { return object; }
 };
 
 void
 TypeSet::addBaseSubset(JSContext *cx, TypeObject *obj, TypeSet *target)
 {
     add(cx, cx->new_<TypeConstraintBaseSubset>(obj, target));
 }
 
+/* Constraint clearing out newScript and definite properties from an object. */
+class TypeConstraintBaseClearDefinite : public TypeConstraint
+{
+public:
+    TypeObject *object;
+
+    TypeConstraintBaseClearDefinite(TypeObject *object)
+        : TypeConstraint("baseClearDefinite", (JSScript *) 0x1),
+          object(object)
+    {}
+
+    void newType(JSContext *cx, TypeSet *source, jstype type);
+
+    TypeObject * persistentObject() { return object; }
+};
+
+void
+TypeSet::addBaseClearDefinite(JSContext *cx, TypeObject *object)
+{
+    add(cx, cx->new_<TypeConstraintBaseClearDefinite>(object));
+}
+
 /* Condensed constraint marking a script dependent on this type set. */
 class TypeConstraintCondensed : public TypeConstraint
 {
 public:
     TypeConstraintCondensed(JSScript *script)
         : TypeConstraint("condensed", script)
     {}
 
@@ -788,16 +814,28 @@ TypeConstraintSubset::newType(JSContext 
 }
 
 void
 TypeConstraintBaseSubset::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     target->addType(cx, type);
 }
 
+void
+TypeConstraintBaseClearDefinite::newType(JSContext *cx, TypeSet *source, jstype type)
+{
+    /*
+     * If the type could represent a setter, clear out the newScript shape and
+     * definite property information from the target object. Any type set with
+     * a getter/setter becomes unknown, so just watch for this type.
+     */
+    if (type == TYPE_UNKNOWN && object->newScript)
+        object->clearNewScript(cx);
+}
+
 /* Get the object to use for a property access on type. */
 static inline TypeObject *
 GetPropertyObject(JSContext *cx, JSScript *script, jstype type)
 {
     if (TypeIsObject(type))
         return (TypeObject*) type;
 
     /*
@@ -1747,16 +1785,29 @@ TypeCompartment::newInitializerTypeObjec
         return NULL;
 
     if (isArray)
         res->initializerArray = true;
     else
         res->initializerObject = true;
     res->initializerOffset = offset;
 
+    if (JSOp(script->code[offset]) == JSOP_NEWOBJECT) {
+        /*
+         * This object is always constructed the same way and will not be
+         * observed by other code before all properties have been added. Mark
+         * all the properties as definite properties of the object.
+         */
+        JS_ASSERT(!script->hasSharps);
+        JSObject *baseobj = script->getObject(GET_SLOTNO(script->code + offset));
+
+        if (!res->addDefiniteProperties(cx, baseobj, false))
+            return NULL;
+    }
+
     return res;
 }
 
 static inline jsid
 GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
 {
     unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
     return MakeTypeId(cx, ATOM_TO_JSID(script->getAtom(index)));
@@ -2301,17 +2352,21 @@ TypeCompartment::fixArrayType(JSContext 
     ArrayTableKey key;
     key.type = type;
     key.proto = obj->getProto();
     ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
 
     if (p) {
         obj->setType(p->value);
     } else {
-        TypeObject *objType = newTypeObject(cx, NULL, "TableArray", false, true, obj->getProto());
+        static unsigned count = 0;
+        char *name = (char *) alloca(20);
+        JS_snprintf(name, 20, "TableArray:%u", ++count);
+
+        TypeObject *objType = newTypeObject(cx, NULL, name, false, true, obj->getProto());
         if (!objType) {
             js_ReportOutOfMemory(cx);
             return false;
         }
         obj->setType(objType);
 
         if (!cx->addTypePropertyId(objType, JSID_VOID, type))
             return false;
@@ -2330,29 +2385,33 @@ TypeCompartment::fixArrayType(JSContext 
  * fixed) as the key in the object table, but since all references in the table
  * are weak the hash entries would usually be collected on GC even if objects
  * with the new type/shape are still live.
  */
 struct ObjectTableKey
 {
     jsid *ids;
     uint32 nslots;
+    uint32 nfixed;
     JSObject *proto;
 
     typedef JSObject * Lookup;
 
     static inline uint32 hash(JSObject *obj) {
         return (uint32) (JSID_BITS(obj->lastProperty()->id) ^
-                         obj->slotSpan() ^
+                         obj->slotSpan() ^ obj->numFixedSlots() ^
                          ((uint32)(size_t)obj->getProto() >> 2));
     }
 
     static inline bool match(const ObjectTableKey &v, JSObject *obj) {
-        if (obj->slotSpan() != v.nslots || obj->getProto() != v.proto)
+        if (obj->slotSpan() != v.nslots ||
+            obj->numFixedSlots() != v.nfixed ||
+            obj->getProto() != v.proto) {
             return false;
+        }
         const Shape *shape = obj->lastProperty();
         while (!JSID_IS_EMPTY(shape->id)) {
             if (shape->id != v.ids[shape->slot])
                 return false;
             shape = shape->previous();
         }
         return true;
     }
@@ -2426,17 +2485,21 @@ TypeCompartment::fixObjectType(JSContext
         JSObject *xobj = NewBuiltinClassInstance(cx, &js_ObjectClass,
                                                  (gc::FinalizeKind) obj->finalizeKind());
         if (!xobj) {
             js_ReportOutOfMemory(cx);
             return false;
         }
         AutoObjectRooter xvr(cx, xobj);
 
-        TypeObject *objType = newTypeObject(cx, NULL, "TableObject", false, false, obj->getProto());
+        static unsigned count = 0;
+        char *name = (char *) alloca(20);
+        JS_snprintf(name, 20, "TableObject:%u", ++count);
+
+        TypeObject *objType = newTypeObject(cx, NULL, name, false, false, obj->getProto());
         if (!objType) {
             js_ReportOutOfMemory(cx);
             return false;
         }
         xobj->setType(objType);
 
         jsid *ids = (jsid *) cx->calloc_(obj->slotSpan() * sizeof(jsid));
         if (!ids)
@@ -2460,19 +2523,23 @@ TypeCompartment::fixObjectType(JSContext
             if (!js_DefineNativeProperty(cx, xobj, ids[i], UndefinedValue(), NULL, NULL,
                                          JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
                 return false;
             }
         }
         JS_ASSERT(!xobj->inDictionaryMode());
         const Shape *newShape = xobj->lastProperty();
 
+        if (!objType->addDefiniteProperties(cx, xobj, false))
+            return false;
+
         ObjectTableKey key;
         key.ids = ids;
         key.nslots = obj->slotSpan();
+        key.nfixed = obj->numFixedSlots();
         key.proto = obj->getProto();
         JS_ASSERT(ObjectTableKey::match(key, obj));
 
         ObjectTableEntry entry;
         entry.object = objType;
         entry.newShape = (Shape *) newShape;
         entry.types = types;
 
@@ -2578,16 +2645,67 @@ TypeObject::addProperty(JSContext *cx, j
 
         /* Pull in this property from all prototypes up the chain. */
         getFromPrototypes(cx, base);
     }
 
     return true;
 }
 
+bool
+TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknown)
+{
+    if (unknownProperties())
+        return true;
+
+    /*
+     * Mark all properties of obj as definite properties of this type. Return
+     * false if there is a setter/getter for any of the properties in the
+     * type's prototype.
+     */
+    AutoEnterTypeInference enter(cx);
+
+    const Shape *shape = obj->lastProperty();
+    while (!JSID_IS_EMPTY(shape->id)) {
+        jsid id = MakeTypeId(cx, shape->id);
+        if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot) &&
+            shape->slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
+            TypeSet *types = getProperty(cx, id, true);
+            if (!types)
+                return false;
+            types->setDefinite(shape->slot);
+
+            if (clearUnknown && proto) {
+                /*
+                 * Ensure that if any of the properties named here could have
+                 * a setter in the direct prototype (and thus its transitive
+                 * prototypes), the definite properties and new shape attached
+                 * to this object get cleared out. clearUnknown is set if the
+                 * definite properties are affected by prototype setters
+                 * (i.e. objects from scripted 'new', but not objects from
+                 * initializers).
+                 */
+                TypeSet *parentTypes = proto->getType()->getProperty(cx, id, false);
+                if (!parentTypes || parentTypes->unknown())
+                    return false;
+                parentTypes->addBaseClearDefinite(cx, this);
+            }
+        } else {
+            /*
+             * We should have filtered these properties out before adding them
+             * to the shape associated with the new type.
+             */
+            JS_ASSERT(!clearUnknown);
+        }
+        shape = shape->previous();
+    }
+
+    return cx->compartment->types.checkPendingRecompiles(cx);
+}
+
 void
 TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags)
 {
     JS_ASSERT(cx->compartment->types.inferenceDepth);
     JS_ASSERT((this->flags & flags) != flags);
 
     this->flags |= flags;
 
@@ -2630,16 +2748,44 @@ TypeObject::markUnknown(JSContext *cx)
         if (prop) {
             prop->types.addType(cx, TYPE_UNKNOWN);
             prop->types.setOwnProperty(cx, true);
         }
     }
 }
 
 void
+TypeObject::clearNewScript(JSContext *cx)
+{
+    JS_ASSERT(newScript);
+    newScript = NULL;
+
+    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
+     * bits on the object's properties, just mark such properties as having
+     * been deleted/reconfigured, which will have the same effect on JITs
+     * wanting to use the definite bits to optimize property accesses.
+     */
+    for (unsigned i = 0; i < getPropertyCount(); i++) {
+        Property *prop = getProperty(i);
+        if (!prop)
+            continue;
+        if (prop->types.isDefiniteProperty())
+            prop->types.setOwnProperty(cx, true);
+    }
+
+    cx->compartment->types.checkPendingRecompiles(cx); // :XXX: handle failure
+}
+
+void
 TypeObject::print(JSContext *cx)
 {
     printf("%s : %s", name(), proto ? proto->getType()->name() : "(null)");
 
     if (unknownProperties()) {
         printf(" unknown");
     } else {
         if (!hasFlags(OBJECT_FLAG_NON_PACKED_ARRAY))
@@ -3769,16 +3915,182 @@ AnalyzeScriptNew(JSContext *cx, JSScript
 
     TypeFunction *funType = script->fun->getType()->asFunction();
     TypeSet *prototypeTypes = funType->getProperty(cx, id_prototype(cx), false);
     if (!prototypeTypes)
         return;
     prototypeTypes->addNewObject(cx, script, funType, script->thisTypes());
 }
 
+JSObject *
+AnalyzeScriptProperties(JSContext *cx, JSScript *script)
+{
+    /*
+     * When invoking 'new' on the specified script, try to find some properties
+     * which will definitely be added to the created object before it has a
+     * chance to escape and be accessed elsewhere. This analysis is a forward
+     * scan through the script looking for assignments to 'this.f'. Any
+     * branching kills it, along with any use of 'this' other than for property
+     * assignments.
+     */
+
+    /* Strawman object to add properties to and watch for duplicates. */
+    JSObject *baseobj = NewBuiltinClassInstance(cx, &js_ObjectClass, gc::FINALIZE_OBJECT16);
+    if (!baseobj)
+        return NULL;
+
+    /* Number of added properties. */
+    unsigned numProperties = 0;
+
+    /* If 'this' is on the stack, index of its stack slot. */
+    unsigned thisSlot = unsigned(-1);
+
+    unsigned offset = 0;
+    unsigned depth = 0;
+    while (offset < script->length) {
+        jsbytecode *pc = script->code + offset;
+        JSOp op = JSOp(*pc);
+
+        unsigned nuses = analyze::GetUseCount(script, offset);
+        unsigned ndefs = analyze::GetDefCount(script, offset);
+
+        bool poppedThis = false;
+        if (thisSlot != unsigned(-1) && thisSlot >= depth - nuses) {
+            if (op != JSOP_SETPROP || thisSlot != depth - 2) {
+                /*
+                 * 'this' escapes here and may be accessed before subsequent
+                 * properties are added to the object.
+                 */
+                return baseobj;
+            }
+            poppedThis = true;
+            thisSlot = unsigned(-1);
+        }
+
+        depth -= nuses;
+        depth += ndefs;
+
+        switch (JSOp(*pc)) {
+
+          case JSOP_THIS:
+            thisSlot = depth - 1;
+            break;
+
+          case JSOP_SETPROP: {
+            if (!poppedThis)
+                return baseobj;
+            jsid id = GetAtomId(cx, script, pc, 0);
+            if (JSID_IS_VOID(id))
+                return baseobj;
+            if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx))
+                return baseobj;
+            if (!js_DefineNativeProperty(cx, baseobj, id, UndefinedValue(), NULL, NULL,
+                                         JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
+                return NULL;
+            }
+            numProperties++;
+            if (baseobj->slotSpan() != numProperties) {
+                /* Set a duplicate property. */
+                return baseobj;
+            }
+            if (baseobj->inDictionaryMode())
+                return NULL;
+            if (numProperties >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
+                /* Maximum number of definite properties added. */
+                return baseobj;
+            }
+            break;
+          }
+
+          /* Whitelist of other ops that can be used while initializing 'this' properties. */
+          case JSOP_POP:
+          case JSOP_NOP:
+          case JSOP_LINENO:
+          case JSOP_POPN:
+          case JSOP_VOID:
+          case JSOP_PUSH:
+          case JSOP_ZERO:
+          case JSOP_ONE:
+          case JSOP_INT8:
+          case JSOP_INT32:
+          case JSOP_UINT16:
+          case JSOP_UINT24:
+          case JSOP_BITAND:
+          case JSOP_BITOR:
+          case JSOP_BITXOR:
+          case JSOP_BITNOT:
+          case JSOP_RSH:
+          case JSOP_LSH:
+          case JSOP_URSH:
+          case JSOP_FALSE:
+          case JSOP_TRUE:
+          case JSOP_EQ:
+          case JSOP_NE:
+          case JSOP_LT:
+          case JSOP_LE:
+          case JSOP_GT:
+          case JSOP_GE:
+          case JSOP_NOT:
+          case JSOP_STRICTEQ:
+          case JSOP_STRICTNE:
+          case JSOP_IN:
+          case JSOP_DOUBLE:
+          case JSOP_STRING:
+          case JSOP_REGEXP:
+          case JSOP_DUP:
+          case JSOP_DUP2:
+          case JSOP_GETGLOBAL:
+          case JSOP_GETGNAME:
+          case JSOP_GETARG:
+          case JSOP_SETARG:
+          case JSOP_INCARG:
+          case JSOP_DECARG:
+          case JSOP_ARGINC:
+          case JSOP_ARGDEC:
+          case JSOP_GETLOCAL:
+          case JSOP_SETLOCAL:
+          case JSOP_SETLOCALPOP:
+          case JSOP_INCLOCAL:
+          case JSOP_DECLOCAL:
+          case JSOP_LOCALINC:
+          case JSOP_LOCALDEC:
+          case JSOP_GETPROP:
+          case JSOP_GETARGPROP:
+          case JSOP_GETLOCALPROP:
+          case JSOP_GETELEM:
+          case JSOP_LENGTH:
+          case JSOP_ADD:
+          case JSOP_SUB:
+          case JSOP_MUL:
+          case JSOP_MOD:
+          case JSOP_DIV:
+          case JSOP_NEG:
+          case JSOP_POS:
+          case JSOP_NEWINIT:
+          case JSOP_NEWARRAY:
+          case JSOP_NEWOBJECT:
+          case JSOP_ENDINIT:
+          case JSOP_INITELEM:
+          case JSOP_HOLE:
+          case JSOP_INITPROP:
+          case JSOP_INITMETHOD:
+            break;
+
+          default:
+            return baseobj;
+        }
+
+        offset += analyze::GetBytecodeLength(pc);
+    }
+
+    /* We should have bailed out on a JSOP_STOP or similar. */
+    JS_NOT_REACHED("Mystery!");
+    return baseobj;
+}
+
 /////////////////////////////////////////////////////////////////////
 // Printing
 /////////////////////////////////////////////////////////////////////
 
 #ifdef DEBUG
 
 void
 PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
@@ -4189,36 +4501,69 @@ JSScript::typeCheckBytecode(JSContext *c
 
 #endif
 
 /////////////////////////////////////////////////////////////////////
 // JSObject
 /////////////////////////////////////////////////////////////////////
 
 void
-JSObject::makeNewType(JSContext *cx)
+JSObject::makeNewType(JSContext *cx, JSScript *newScript)
 {
     JS_ASSERT(!newType);
 
     js::types::TypeObject *type = cx->newTypeObject(getType()->name(), "new", this);
     if (!type)
         return;
 
-    if (cx->typeInferenceEnabled() && !getType()->unknownProperties()) {
-        js::types::AutoEnterTypeInference enter(cx);
-
+    if (!cx->typeInferenceEnabled()) {
+        newType = type;
+        setDelegate();
+        return;
+    }
+
+    js::types::AutoEnterTypeInference enter(cx);
+
+    if (!getType()->unknownProperties()) {
         /* Update the possible 'new' types for all prototype objects sharing the same type object. */
         js::types::TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true);
         if (types)
             types->addType(cx, (js::types::jstype) type);
-
-        if (!cx->compartment->types.checkPendingRecompiles(cx))
-            return;
     }
 
+    if (newScript && !type->unknownProperties()) {
+        JSObject *baseobj = js::types::AnalyzeScriptProperties(cx, newScript);
+        if (baseobj && baseobj->slotSpan() > 0) {
+            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());
+
+            /*
+             * The base object was created with a different type and
+             * finalize kind than we will use for subsequent new objects.
+             * Generate an object with the appropriate final shape.
+             */
+            baseobj = NewReshapedObject(cx, type, baseobj->getParent(), kind,
+                                        (const js::Shape *) baseobj->lastProperty());
+            if (!baseobj)
+                return;
+
+            if (!type->addDefiniteProperties(cx, baseobj, true))
+                return;
+
+            type->newScript = newScript;
+            type->newScriptFinalizeKind = unsigned(kind);
+            type->newScriptShape = (js::Shape *) baseobj->lastProperty();
+        }
+    }
+
+    if (!cx->compartment->types.checkPendingRecompiles(cx))
+        return;
+
     newType = type;
     setDelegate();
 }
 
 /////////////////////////////////////////////////////////////////////
 // Tracing
 /////////////////////////////////////////////////////////////////////
 
@@ -4256,16 +4601,21 @@ types::TypeObject::trace(JSTracer *trc)
         }
     }
 
     if (proto)
         gc::MarkObject(trc, *proto, "type_proto");
 
     if (singleton)
         gc::MarkObject(trc, *singleton, "type_singleton");
+
+    if (newScript) {
+        js_TraceScript(trc, newScript);
+        gc::MarkShape(trc, newScriptShape, "new_shape");
+    }
 }
 
 /*
  * Condense any constraints on a type set which were generated during analysis
  * of a script, and sweep all type objects and references to type objects
  * which no longer exist.
  */
 void
@@ -4338,17 +4688,17 @@ TypeSet::CondenseSweepTypeSet(JSContext 
      * condensed constraints for, in the condensed table. We reuse the
      * same table for each type set to avoid extra initialization cost,
      * but the table is emptied after each set is processed.
      */
 
     while (constraint) {
         TypeConstraint *next = constraint->next;
 
-        TypeObject *object = constraint->baseSubset();
+        TypeObject *object = constraint->persistentObject();
         if (object) {
             /*
              * Constraint propagating data between objects. If the target
              * is not being collected (these are weak references) then
              * keep the constraint.
              */
             if (object->marked) {
                 constraint->next = types->constraintList;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -192,21 +192,22 @@ public:
      * Whether this is an input type constraint condensed from the original
      * constraints generated during analysis of the associated script.
      * If this type set changes then the script will be reanalyzed/recompiled
      * should the type set change at all in the future.
      */
     virtual bool condensed() { return false; }
 
     /*
-     * If this is a persistent subset constraint, the object being propagated
-     * into. Such constraints describe relationships between TypeObject
-     * properties which are independent of the analysis of any script.
+     * If this is a persistent constraint other than a condensed constraint,
+     * the target object of the constraint. Such constraints describe
+     * relationships between TypeObjects which are independent of the analysis
+     * of any script.
      */
-    virtual TypeObject * baseSubset() { return NULL; }
+    virtual TypeObject * persistentObject() { return NULL; }
 };
 
 /*
  * Coarse kinds of a set of objects. These form the following lattice:
  *
  *            NONE
  *       ____/    \_____
  *      /               \
@@ -236,29 +237,43 @@ enum {
     TYPE_FLAG_BOOLEAN   = 1 << TYPE_BOOLEAN,
     TYPE_FLAG_INT32     = 1 << TYPE_INT32,
     TYPE_FLAG_DOUBLE    = 1 << TYPE_DOUBLE,
     TYPE_FLAG_STRING    = 1 << TYPE_STRING,
 
     TYPE_FLAG_UNKNOWN   = 1 << TYPE_UNKNOWN,
 
     /* Flag for type sets which are cleared on GC. */
-    TYPE_FLAG_INTERMEDIATE_SET    = 0x1000,
+    TYPE_FLAG_INTERMEDIATE_SET    = 0x0100,
 
-    /* For object property type sets, whether this property has been directly written. */
-    TYPE_FLAG_OWN_PROPERTY        = 0x2000,
+    /* Flags for type sets which are on object properties. */
+
+    /* Whether this property has ever been directly written. */
+    TYPE_FLAG_OWN_PROPERTY        = 0x0200,
 
     /*
-     * For object property type sets, whether the property has ever been
-     * deleted or reconfigured as non-writable.
+     * Whether the property has ever been deleted or reconfigured to behave
+     * differently from a normal native property (e.g. made non-writable or
+     * given a scripted getter or setter).
      */
-    TYPE_FLAG_CONFIGURED_PROPERTY = 0x4000,
+    TYPE_FLAG_CONFIGURED_PROPERTY = 0x0400,
+
+    /*
+     * Whether the property is definitely in a particular inline slot on all
+     * objects from which it has not been deleted or reconfigured. Implies
+     * OWN_PROPERTY and unlike OWN/CONFIGURED property, this cannot change.
+     */
+    TYPE_FLAG_DEFINITE_PROPERTY   = 0x0800,
+
+    /* If the property is definite, mask and shift storing the slot. */
+    TYPE_FLAG_DEFINITE_MASK       = 0xf000,
+    TYPE_FLAG_DEFINITE_SHIFT      = 12,
 
     /* Mask of non-type flags on a type set. */
-    TYPE_FLAG_BASE_MASK           = 0x7000
+    TYPE_FLAG_BASE_MASK           = 0xffffff00
 };
 
 /* Vector of the above flags. */
 typedef uint32 TypeFlags;
 
 /* Information about the set of types associated with an lvalue. */
 class TypeSet
 {
@@ -275,32 +290,31 @@ class TypeSet
     TypeConstraint *constraintList;
 
     TypeSet()
         : typeFlags(0), objectSet(NULL), objectCount(0), constraintList(NULL)
     {}
 
     void print(JSContext *cx);
 
-    void setIntermediate() { typeFlags |= TYPE_FLAG_INTERMEDIATE_SET; }
-    void setOwnProperty(bool configurable) {
-        typeFlags |= TYPE_FLAG_OWN_PROPERTY;
-        if (configurable)
-            typeFlags |= TYPE_FLAG_CONFIGURED_PROPERTY;
-    }
-
     inline void destroy(JSContext *cx);
 
     /* Whether this set contains a specific type. */
     inline bool hasType(jstype type);
 
     TypeFlags baseFlags() { return typeFlags & ~TYPE_FLAG_BASE_MASK; }
     bool hasAnyFlag(TypeFlags flags) { return typeFlags & flags; }
     bool unknown() { return typeFlags & TYPE_FLAG_UNKNOWN; }
 
+    bool isDefiniteProperty() { return typeFlags & TYPE_FLAG_DEFINITE_PROPERTY; }
+    unsigned definiteSlot() {
+        JS_ASSERT(isDefiniteProperty());
+        return typeFlags >> TYPE_FLAG_DEFINITE_SHIFT;
+    }
+
     /*
      * Add a type to this set, calling any constraint handlers if this is a new
      * possible type.
      */
     inline void addType(JSContext *cx, jstype type);
 
     /* Add all types in a cloned set to this set. */
     void addTypeSet(JSContext *cx, ClonedTypeSet *types);
@@ -311,16 +325,27 @@ class TypeSet
     /*
      * Iterate through the objects in this set. getObjectCount overapproximates
      * in the hash case (see SET_ARRAY_SIZE in jsinferinlines.h), and getObject
      * may return NULL.
      */
     inline unsigned getObjectCount();
     inline TypeObject *getObject(unsigned i);
 
+    void setIntermediate() { typeFlags |= TYPE_FLAG_INTERMEDIATE_SET; }
+    void setOwnProperty(bool configurable) {
+        typeFlags |= TYPE_FLAG_OWN_PROPERTY;
+        if (configurable)
+            typeFlags |= TYPE_FLAG_CONFIGURED_PROPERTY;
+    }
+    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,
                         TypeSet *target, jsid id);
     void addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
                         TypeSet *target, jsid id);
     void addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target);
@@ -328,16 +353,17 @@ class TypeSet
     void addArith(JSContext *cx, JSScript *script,
                   TypeSet *target, TypeSet *other = NULL);
     void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target);
     void addFilterPrimitives(JSContext *cx, JSScript *script,
                              TypeSet *target, bool onlyNullVoid);
     void addMonitorRead(JSContext *cx, JSScript *script, TypeSet *target);
 
     void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target);
+    void addBaseClearDefinite(JSContext *cx, TypeObject *object);
     void addCondensed(JSContext *cx, JSScript *script);
 
     /*
      * Make an intermediate type set with the specified debugging name,
      * not embedded in another structure.
      */
     static inline TypeSet* make(JSContext *cx, const char *name);
 
@@ -473,16 +499,25 @@ struct TypeObject
 
     /* Whether this is a function object, and may be cast into TypeFunction. */
     bool isFunction;
 
     /* Mark bit for GC. */
     bool marked;
 
     /*
+     * If non-NULL, objects of this type have always been constructed using
+     * 'new' on the specified script. Moreover the given finalize kind and
+     * initial shape should also be used for the object.
+     */
+    JSScript *newScript;
+    /* gc::FinalizeKind */ unsigned newScriptFinalizeKind;
+    Shape *newScriptShape;
+
+    /*
      * Whether this is an Object or Array keyed to an offset in the script containing
      * this in its objects list.
      */
     bool initializerObject;
     bool initializerArray;
     uint32 initializerOffset;
 
     /*
@@ -559,19 +594,21 @@ struct TypeObject
     void splicePrototype(JSContext *cx, JSObject *proto);
 
     inline unsigned getPropertyCount();
     inline Property *getProperty(unsigned i);
 
     /* Helpers */
 
     bool addProperty(JSContext *cx, jsid id, Property **pprop);
+    bool addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknown);
     void addPrototype(JSContext *cx, TypeObject *proto);
     void setFlags(JSContext *cx, TypeObjectFlags flags);
     void markUnknown(JSContext *cx);
+    void clearNewScript(JSContext *cx);
     void storeToInstances(JSContext *cx, Property *base);
     void getFromPrototypes(JSContext *cx, Property *base);
 
     void print(JSContext *cx);
     void trace(JSTracer *trc);
 };
 
 /*
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1089,17 +1089,17 @@ struct TypeObjectKey {
 inline void
 TypeSet::destroy(JSContext *cx)
 {
     JS_ASSERT(!(typeFlags & TYPE_FLAG_INTERMEDIATE_SET));
     if (objectCount >= 2)
         ::js_free(objectSet);
     while (constraintList) {
         TypeConstraint *next = constraintList->next;
-        if (constraintList->condensed() || constraintList->baseSubset())
+        if (constraintList->condensed() || constraintList->persistentObject())
             ::js_free(constraintList);
         constraintList = next;
     }
 }
 
 inline bool
 TypeSet::hasType(jstype type)
 {
@@ -1164,17 +1164,17 @@ TypeSet::addType(JSContext *cx, jstype t
         }
     }
 
     InferSpew(ISpewOps, "addType: T%p %s", this, TypeString(type));
 
     /* Propagate the type to all constraints. */
     TypeConstraint *constraint = constraintList;
     while (constraint) {
-        JS_ASSERT_IF(!constraint->baseSubset(),
+        JS_ASSERT_IF(!constraint->persistentObject(),
                      constraint->script->compartment == cx->compartment);
         cx->compartment->types.addPending(cx, constraint, this, type);
         constraint = constraint->next;
     }
 
     cx->compartment->types.resolvePending(cx);
 }
 
@@ -1186,17 +1186,17 @@ TypeSet::setOwnProperty(JSContext *cx, b
     if ((typeFlags & nflags) == nflags)
         return;
 
     typeFlags |= nflags;
 
     /* Propagate the change to all constraints. */
     TypeConstraint *constraint = constraintList;
     while (constraint) {
-        JS_ASSERT_IF(!constraint->baseSubset(),
+        JS_ASSERT_IF(!constraint->persistentObject(),
                      constraint->script->compartment == cx->compartment);
         constraint->newPropertyState(cx, this);
         constraint = constraint->next;
     }
 }
 
 inline unsigned
 TypeSet::getObjectCount()
@@ -1363,16 +1363,17 @@ TypeObject::name()
 #else
     return NULL;
 #endif
 }
 
 inline TypeObject::TypeObject(jsid name, JSObject *proto)
     : proto(proto), emptyShapes(NULL),
       flags(0), isFunction(false), marked(false),
+      newScript(NULL), newScriptFinalizeKind(0), newScriptShape(NULL),
       initializerObject(false), initializerArray(false), initializerOffset(0),
       contribution(0), propertySet(NULL), propertyCount(0),
       instanceList(NULL), instanceNext(NULL), next(NULL),
       singleton(NULL)
 {
 #ifdef DEBUG
     this->name_ = name;
 #endif
@@ -1406,11 +1407,28 @@ SweepClonedTypes(ClonedTypeSet *types)
         TypeObject *obj = (TypeObject *) types->objectSet;
         if (!obj->marked) {
             types->objectSet = NULL;
             types->objectCount = 0;
         }
     }
 }
 
+class AutoTypeRooter : private AutoGCRooter {
+  public:
+    AutoTypeRooter(JSContext *cx, TypeObject *type
+                   JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : AutoGCRooter(cx, TYPE), type(type)
+    {
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    friend void AutoGCRooter::trace(JSTracer *trc);
+    friend void MarkRuntime(JSTracer *trc);
+
+  private:
+    TypeObject *type;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
 } } /* namespace js::types */
 
 #endif // jsinferinlines_h___
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2887,16 +2887,51 @@ js_Object(JSContext *cx, uintN argc, Val
         TypeObject *type = cx->getTypeCallerInitObject(false);
         if (!type || !obj->setTypeAndEmptyShape(cx, type))
             return JS_FALSE;
     }
     vp->setObject(*obj);
     return JS_TRUE;
 }
 
+JSObject *
+js::NewReshapedObject(JSContext *cx, TypeObject *type, JSObject *parent,
+                      gc::FinalizeKind kind, const Shape *shape)
+{
+    JSObject *res = NewObjectWithType(cx, type, parent, kind);
+    if (!res)
+        return NULL;
+
+    if (JSID_IS_EMPTY(shape->id))
+        return res;
+
+    /* Get all the ids in the object, in order. */
+    js::AutoIdVector ids(cx);
+    for (unsigned i = 0; i <= shape->slot; i++) {
+        if (!ids.append(JSID_VOID))
+            return NULL;
+    }
+    const js::Shape *nshape = shape;
+    while (!JSID_IS_EMPTY(nshape->id)) {
+        ids[nshape->slot] = nshape->id;
+        nshape = nshape->previous();
+    }
+
+    /* Construct the new shape. */
+    for (unsigned i = 0; i < ids.length(); i++) {
+        if (!js_DefineNativeProperty(cx, res, ids[i], js::UndefinedValue(), NULL, NULL,
+                                     JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
+            return NULL;
+        }
+    }
+    JS_ASSERT(!res->inDictionaryMode());
+
+    return res;
+}
+
 JSObject*
 js_CreateThis(JSContext *cx, JSObject *callee)
 {
     Class *clasp = callee->getClass();
 
     Class *newclasp = &js_ObjectClass;
     if (clasp == &js_FunctionClass) {
         JSFunction *fun = callee->getFunctionPrivate();
@@ -2915,48 +2950,72 @@ js_CreateThis(JSContext *cx, JSObject *c
     if (obj)
         obj->syncSpecialEquality();
     return obj;
 }
 
 JSObject *
 js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto)
 {
-    /* Caller must ensure that proto's new type is not marked as an array. */
+    if (proto) {
+        JSScript *calleeScript = callee->getFunctionPrivate()->script();
+        types::TypeObject *type = proto->getNewType(cx, calleeScript);
+
+        if (type && type->newScript) {
+            JS_ASSERT(type->newScript == calleeScript);
+            gc::FinalizeKind kind = gc::FinalizeKind(type->newScriptFinalizeKind);
+            JSObject *res = NewObjectWithType(cx, type, callee->getParent(), kind);
+            if (res)
+                res->setMap(type->newScriptShape);
+            return res;
+        }
+    }
+
     gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass);
     return NewNonFunction<WithProto::Class>(cx, &js_ObjectClass, proto, callee->getParent(), kind);
 }
 
 JSObject *
 js_CreateThisForFunction(JSContext *cx, JSObject *callee, bool newType)
 {
     Value protov;
     if (!callee->getProperty(cx,
                              ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom),
                              &protov)) {
         return NULL;
     }
     JSObject *proto;
-    if (protov.isObject()) {
+    if (protov.isObject())
         proto = &protov.toObject();
-        TypeObject *type = proto->getNewType(cx);
-        if (!type)
-            return NULL;
-    } else {
+    else
         proto = NULL;
-    }
     JSObject *obj = js_CreateThisForFunctionWithProto(cx, callee, proto);
+
     if (obj && newType) {
+        /*
+         * Make a new type and a new object with the type, reshaped according
+         * to any properties already added by CreateThisForFunctionWithProto.
+         */
         JS_ASSERT(cx->typeInferenceEnabled());
-        types::TypeObject *type = cx->newTypeObject("SpecializedThis", obj->getProto());
-        if (!type || !obj->setTypeAndUniqueShape(cx, type))
+
+        static unsigned count = 0;
+        char *name = (char *) alloca(30);
+        JS_snprintf(name, 30, "SpecializedThis:%u", ++count);
+
+        types::TypeObject *type = cx->newTypeObject(name, obj->getProto());
+        types::AutoTypeRooter root(cx, type);
+
+        obj = NewReshapedObject(cx, type, obj->getParent(), gc::FinalizeKind(obj->finalizeKind()),
+                                (const Shape *) obj->lastProperty());
+        if (!obj)
             return NULL;
         if (!callee->getFunctionPrivate()->script()->typeSetThis(cx, (types::jstype) type))
             return NULL;
     }
+
     return obj;
 }
 
 #ifdef JS_TRACER
 
 static JS_ALWAYS_INLINE JSObject*
 NewObjectWithClassProto(JSContext *cx, Class *clasp, JSObject *proto,
                         /*gc::FinalizeKind*/ unsigned _kind)
@@ -3033,19 +3092,16 @@ js_CreateThisFromTrace(JSContext *cx, JS
     JS_ASSERT(!shape->isMethod());
 #endif
 
     JSObject *parent = ctor->getParent();
     JSObject *proto;
     const Value &protov = ctor->getSlotRef(protoSlot);
     if (protov.isObject()) {
         proto = &protov.toObject();
-        TypeObject *type = proto->getNewType(cx);
-        if (!type)
-            return NULL;
     } else {
         /*
          * GetInterpretedFunctionPrototype found that ctor.prototype is
          * primitive. Use Object.prototype for proto, per ES5 13.2.2 step 7.
          */
         if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
             return NULL;
     }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -649,19 +649,20 @@ struct JSObject : js::gc::Cell {
     inline js::Value *getRawSlot(size_t slot, js::Value *slots);
 
     /* Whether a slot is at a fixed offset from this object. */
     inline bool isFixedSlot(size_t slot);
 
     /* Index into the dynamic slots array to use for a dynamic slot. */
     inline size_t dynamicSlotIndex(size_t slot);
 
+    inline size_t numFixedSlots() const;
+
   private:
     inline js::Value* fixedSlots() const;
-    inline size_t numFixedSlots() const;
     inline bool hasSlotsArray() const;
 
   public:
     /* Minimum size for dynamically allocated slots. */
     static const uint32 SLOT_CAPACITY_MIN = 8;
 
     bool allocSlots(JSContext *cx, size_t nslots);
     bool growSlots(JSContext *cx, size_t nslots);
@@ -784,18 +785,18 @@ struct JSObject : js::gc::Cell {
     js::types::TypeObject* getType() const { return type; }
 
     inline bool clearType(JSContext *cx);
     inline void setType(js::types::TypeObject *newType);
     inline bool setTypeAndUniqueShape(JSContext *cx, js::types::TypeObject *newType);
     inline bool setTypeAndEmptyShape(JSContext *cx, js::types::TypeObject *newType);
     inline void setTypeAndShape(js::types::TypeObject *newType, const js::Shape *newShape);
 
-    inline js::types::TypeObject *getNewType(JSContext *cx);
-    void makeNewType(JSContext *cx);
+    inline js::types::TypeObject *getNewType(JSContext *cx, JSScript *script = NULL);
+    void makeNewType(JSContext *cx, JSScript *script);
 
     JSObject * getProto() const {
         return type->proto;
     }
 
     JSObject *getParent() const {
         return parent;
     }
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -853,22 +853,26 @@ JSObject::getWithThis() const
 
 inline void
 JSObject::setWithThis(JSObject *thisp)
 {
     getFixedSlotRef(JSSLOT_WITH_THIS).setObject(*thisp);
 }
 
 inline js::types::TypeObject *
-JSObject::getNewType(JSContext *cx)
+JSObject::getNewType(JSContext *cx, JSScript *script)
 {
     if (isDenseArray() && !makeDenseArraySlow(cx))
         return NULL;
-    if (!newType)
-        makeNewType(cx);
+    if (newType) {
+        if (newType->newScript != script && newType->newScript)
+            newType->clearNewScript(cx);
+    } else {
+        makeNewType(cx, script);
+    }
     return newType;
 }
 
 inline bool
 JSObject::clearType(JSContext *cx)
 {
     js::types::TypeObject *newType = cx->getTypeEmpty();
     if (!newType)
@@ -1366,16 +1370,49 @@ template <WithProto::e withProto>
 static JS_ALWAYS_INLINE JSObject *
 NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent)
 {
     gc::FinalizeKind kind = gc::GetGCObjectKind(JSCLASS_RESERVED_SLOTS(clasp));
     return NewObject<withProto>(cx, clasp, proto, parent, kind);
 }
 
 /*
+ * Create a plain object with the specified type. This bypasses getNewType to
+ * avoid losing creation site information for objects made by scripted 'new'.
+ */
+static JS_ALWAYS_INLINE JSObject *
+NewObjectWithType(JSContext *cx, types::TypeObject *type, JSObject *parent, gc::FinalizeKind kind)
+{
+    JSObject* obj = js_NewGCObject(cx, kind);
+    if (!obj)
+        goto out;
+
+    /*
+     * Default parent to the parent of the prototype, which was set from
+     * the parent of the prototype's constructor.
+     */
+    obj->init(cx, &js_ObjectClass, type,
+              (!parent && type->proto) ? type->proto->getParent() : parent,
+              NULL, false);
+
+    if (!InitScopeForObject(cx, obj, &js_ObjectClass, type, kind)) {
+        obj = NULL;
+        goto out;
+    }
+
+out:
+    Probes::createObject(cx, obj);
+    return obj;
+}
+
+extern JSObject *
+NewReshapedObject(JSContext *cx, types::TypeObject *type, JSObject *parent,
+                  gc::FinalizeKind kind, const Shape *shape);
+
+/*
  * As for gc::GetGCObjectKind, where numSlots is a guess at the final size of
  * the object, zero if the final size is unknown. This should only be used for
  * objects that do not require any fixed slots.
  */
 static inline gc::FinalizeKind
 GuessObjectGCKind(size_t numSlots, bool isArray)
 {
     if (numSlots)
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1917,34 +1917,40 @@ mjit::Compiler::generateMethod()
 
           BEGIN_CASE(JSOP_ELEMDEC)
             jsop_eleminc(op, STRICT_VARIANT(stubs::ElemDec));
           END_CASE(JSOP_ELEMDEC)
 
           BEGIN_CASE(JSOP_GETTHISPROP)
             /* Push thisv onto stack. */
             jsop_this();
+            if (cx->typeInferenceEnabled())
+                frame.extra(frame.peek(-1)).types = script->thisTypes();
             if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)), knownPushedType(0)))
                 return Compile_Error;
           END_CASE(JSOP_GETTHISPROP);
 
           BEGIN_CASE(JSOP_GETARGPROP)
           {
             /* Push arg onto stack. */
             uint32 arg = GET_SLOTNO(PC);
             frame.pushArg(arg, knownArgumentType(arg));
+            if (cx->typeInferenceEnabled())
+                frame.extra(frame.peek(-1)).types = script->argTypes(arg);
             if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[ARGNO_LEN])), knownPushedType(0)))
                 return Compile_Error;
           }
           END_CASE(JSOP_GETARGPROP)
 
           BEGIN_CASE(JSOP_GETLOCALPROP)
           {
             uint32 local = GET_SLOTNO(PC);
             frame.pushLocal(local, knownLocalType(local));
+            if (cx->typeInferenceEnabled() && local < script->nfixed)
+                frame.extra(frame.peek(-1)).types = script->localTypes(local);
             if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[SLOTNO_LEN])), knownPushedType(0)))
                 return Compile_Error;
           }
           END_CASE(JSOP_GETLOCALPROP)
 
           BEGIN_CASE(JSOP_GETPROP)
             if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)), knownPushedType(0)))
                 return Compile_Error;
@@ -2375,24 +2381,32 @@ mjit::Compiler::generateMethod()
             iterNext();
           END_CASE(JSOP_FORELEM)
 
           BEGIN_CASE(JSOP_BINDNAME)
             jsop_bindname(script->getAtom(fullAtomIndex(PC)), true);
           END_CASE(JSOP_BINDNAME)
 
           BEGIN_CASE(JSOP_SETPROP)
-            if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true))
+          {
+            jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH];
+            bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next);
+            if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop))
                 return Compile_Error;
+          }
           END_CASE(JSOP_SETPROP)
 
           BEGIN_CASE(JSOP_SETNAME)
           BEGIN_CASE(JSOP_SETMETHOD)
-            if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true))
+          {
+            jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH];
+            bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next);
+            if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop))
                 return Compile_Error;
+          }
           END_CASE(JSOP_SETNAME)
 
           BEGIN_CASE(JSOP_THROW)
             prepareStubCall(Uses(1));
             INLINE_STUBCALL_NO_REJOIN(stubs::Throw);
             frame.pop();
           END_CASE(JSOP_THROW)
 
@@ -4358,16 +4372,49 @@ mjit::Compiler::jsop_getprop(JSAtom *ato
     if (top->isNotType(JSVAL_TYPE_OBJECT)) {
         jsop_getprop_slow(atom, usePropCache);
         return true;
     }
 
     frame.forgetMismatchedObject(top);
 
     /*
+     * 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);
+    types::TypeSet *types = frame.extra(top).types;
+    if ((op == JSOP_GETPROP || op == JSOP_GETTHISPROP || op == JSOP_GETARGPROP || op == JSOP_GETLOCALPROP) &&
+        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);
+        if (!propertyTypes)
+            return false;
+        if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
+            types->addFreeze(cx);
+            uint32 slot = propertyTypes->definiteSlot();
+            if (!top->isTypeKnown()) {
+                Jump notObject = frame.testObject(Assembler::NotEqual, top);
+                stubcc.linkExit(notObject, Uses(1));
+                stubcc.leave();
+                OOL_STUBCALL(stubs::GetProp);
+            }
+            RegisterID reg = frame.tempRegForData(top);
+            frame.pop();
+            frame.push(Address(reg, JSObject::getFixedSlotOffset(slot)), knownType);
+            if (!top->isTypeKnown())
+                stubcc.rejoin(Changes(1));
+            return true;
+        }
+    }
+
+    /*
      * These two must be loaded first. The objReg because the string path
      * wants to read it, and the shapeReg because it could cause a spill that
      * the string path wouldn't sink back.
      */
     RegisterID objReg = Registers::ReturnReg;
     RegisterID shapeReg = Registers::ReturnReg;
     if (atom == cx->runtime->atomState.lengthAtom) {
         objReg = frame.copyDataIntoReg(top);
@@ -4862,32 +4909,64 @@ mjit::Compiler::jsop_callprop(JSAtom *at
     }
 
     if (top->isTypeKnown())
         return jsop_callprop_obj(atom);
     return jsop_callprop_generic(atom);
 }
 
 bool
-mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache)
+mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache, bool popGuaranteed)
 {
     REJOIN_SITE_2(usePropCache
                   ? STRICT_VARIANT(stubs::SetName)
                   : STRICT_VARIANT(stubs::SetPropNoCache),
                   ic::SetProp);
 
     FrameEntry *lhs = frame.peek(-2);
     FrameEntry *rhs = frame.peek(-1);
 
     /* If the incoming type will never PIC, take slow path. */
     if (lhs->isTypeKnown() && lhs->getKnownType() != JSVAL_TYPE_OBJECT) {
         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.
+     */
+    types::TypeSet *types = frame.extra(lhs).types;
+    if (JSOp(*PC) == JSOP_SETPROP && 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);
+        if (!propertyTypes)
+            return false;
+        if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
+            types->addFreeze(cx);
+            uint32 slot = propertyTypes->definiteSlot();
+            if (!lhs->isTypeKnown()) {
+                Jump notObject = frame.testObject(Assembler::NotEqual, lhs);
+                stubcc.linkExit(notObject, Uses(2));
+                stubcc.leave();
+                masm.move(ImmPtr(atom), Registers::ArgReg1);
+                OOL_STUBCALL(STRICT_VARIANT(stubs::SetName));
+            }
+            RegisterID reg = frame.tempRegForData(lhs);
+            frame.storeTo(rhs, Address(reg, JSObject::getFixedSlotOffset(slot)), popGuaranteed);
+            frame.shimmy(1);
+            if (!lhs->isTypeKnown())
+                stubcc.rejoin(Changes(1));
+            return true;
+        }
+    }
+
     JSOp op = JSOp(*PC);
 
     ic::PICInfo::Kind kind = (op == JSOP_SETMETHOD)
                              ? ic::PICInfo::SETMETHOD
                              : ic::PICInfo::SET;
     PICGenInfo pic(kind, op, usePropCache);
     pic.atom = atom;
 
@@ -5405,17 +5484,17 @@ mjit::Compiler::jsop_nameinc(JSOp op, Vo
         // OBJ V 1
 
         /* Use sub since it calls ValueToNumber instead of string concat. */
         frame.syncAt(-3);
         if (!jsop_binary(JSOP_SUB, stubs::Sub, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
             return Compile_Retry;
         // OBJ N+1
 
-        if (!jsop_setprop(atom, false))
+        if (!jsop_setprop(atom, false, pop))
             return Compile_Error;
         // N+1
 
         if (pop)
             frame.pop();
     } else {
         /* The pre-value is observed, making this more tricky. */
 
@@ -5434,17 +5513,17 @@ mjit::Compiler::jsop_nameinc(JSOp op, Vo
         frame.push(Int32Value(-amt));
         // N OBJ N 1
 
         frame.syncAt(-3);
         if (!jsop_binary(JSOP_ADD, stubs::Add, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
             return Compile_Retry;
         // N OBJ N+1
 
-        if (!jsop_setprop(atom, false))
+        if (!jsop_setprop(atom, false, true))
             return Compile_Error;
         // N N+1
 
         frame.pop();
         // N
     }
 
     if (pop)
@@ -5494,17 +5573,17 @@ mjit::Compiler::jsop_propinc(JSOp op, Vo
         frame.syncAt(-4);
         if (!jsop_binary(JSOP_SUB, stubs::Sub, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
             return Compile_Retry;
         // OBJ * V+1
 
         frame.shimmy(1);
         // OBJ V+1
 
-        if (!jsop_setprop(atom, false))
+        if (!jsop_setprop(atom, false, pop))
             return Compile_Error;
         // V+1
 
         if (pop)
             frame.pop();
     } else {
         /* The pre-value is observed, making this more tricky. */
 
@@ -5530,17 +5609,17 @@ mjit::Compiler::jsop_propinc(JSOp op, Vo
         // OBJ N N+1
 
         frame.dupAt(-3);
         // OBJ N N+1 OBJ
 
         frame.dupAt(-2);
         // OBJ N N+1 OBJ N+1
 
-        if (!jsop_setprop(atom, false))
+        if (!jsop_setprop(atom, false, true))
             return Compile_Error;
         // OBJ N N+1 N+1
 
         frame.popn(2);
         // OBJ N
 
         frame.shimmy(1);
         // N
@@ -6878,17 +6957,17 @@ mjit::Compiler::jsop_forprop(JSAtom *ato
     iterNext();
 
     // Before: ITER OBJ ITER VALUE
     // After:  ITER OBJ VALUE
     frame.shimmy(1);
 
     // Before: ITER OBJ VALUE
     // After:  ITER VALUE
-    jsop_setprop(atom, false);
+    jsop_setprop(atom, false, true);
 
     // Before: ITER VALUE
     // After:  ITER
     frame.pop();
 }
 
 void
 mjit::Compiler::jsop_forname(JSAtom *atom)
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -685,17 +685,17 @@ class Compiler : public BaseCompiler
     void jsop_bindgname();
     void jsop_setelem_slow();
     void jsop_getelem_slow();
     void jsop_callelem_slow();
     void jsop_unbrand();
     bool jsop_getprop(JSAtom *atom, JSValueType type,
                       bool typeCheck = true, bool usePropCache = true);
     bool jsop_length();
-    bool jsop_setprop(JSAtom *atom, 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_instanceof();
     void jsop_name(JSAtom *atom, JSValueType type);
--- a/js/src/methodjit/FrameState-inl.h
+++ b/js/src/methodjit/FrameState-inl.h
@@ -315,28 +315,33 @@ FrameState::push(Address address, JSValu
         RegisterID dataReg = reuseBase ? address.base : allocReg();
         masm.loadPayload(address, dataReg);
         pushTypedPayload(knownType, dataReg);
         return;
     }
 
     // Prevent us from clobbering this reg.
     bool free = a->freeRegs.hasReg(address.base);
+    bool needsPin = !free && regstate(address.base).fe();
     if (free)
         a->freeRegs.takeReg(address.base);
+    if (needsPin)
+        pinReg(address.base);
 
     RegisterID typeReg = allocReg();
 
     masm.loadTypeTag(address, typeReg);
 
     // Allow re-use of the base register. This could avoid a spill, and
     // is safe because the following allocReg() won't actually emit any
     // writes to the register.
     if (free)
         a->freeRegs.putReg(address.base);
+    if (needsPin)
+        unpinReg(address.base);
 
     RegisterID dataReg = reuseBase ? address.base : allocReg();
     masm.loadPayload(address, dataReg);
 
 #endif
 
     pushRegs(typeReg, dataReg, knownType);
 }