[INFER] Improved robustness and tracking of 'new' object properties across scripts, bug 653962.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 12 May 2011 20:07:23 -0700
changeset 75043 8fbd8f8614654baaeaff228b47c06a545905cc2c
parent 75042 a9744127fee40095cf88d77d6feed9fabdff436e
child 75044 b38e4d16be978b6ec4afb09e7996adbdf1eade67
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs653962
milestone6.0a1
[INFER] Improved robustness and tracking of 'new' object properties across scripts, bug 653962.
js/src/jit-test/tests/jaeger/propertyOptimize-3.js
js/src/jsanalyze.cpp
js/src/jsanalyze.h
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/jsscope.cpp
js/src/jsscript.cpp
js/src/jsscript.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/jaeger/propertyOptimize-3.js
@@ -0,0 +1,67 @@
+
+// Properties cleared in the middle of a single function constructor.
+
+function foo(x, y) {
+  this.f = 0;
+  this.g = x + y;
+  this.h = 2;
+}
+
+var called = false;
+var a = 0;
+var b = {valueOf: function() { Object.defineProperty(Object.prototype, 'g', {set: function() { called = true }}) }};
+var c = new foo(a, b);
+
+assertEq(called, true);
+assertEq(c.g, undefined);
+
+// Properties cleared in the middle of a constructor callee.
+
+function foo2(x, y) {
+  this.a = 0;
+  this.b = 1;
+  bar2.call(this, x, y);
+  this.c = 2;
+}
+function bar2(x, y) {
+  this.d = x + y;
+  this.e = 3;
+}
+
+var called2 = false;
+var xa = 0;
+var xb = {valueOf: function() { Object.defineProperty(Object.prototype, 'e', {set: function() { called2 = true }}) }};
+var xc = new foo2(xa, xb);
+
+assertEq(called2, true);
+assertEq(xc.e, undefined);
+assertEq(xc.c, 2);
+
+// Properties cleared after a constructor callee.
+
+function foo3() {
+  this.aa = 0;
+  this.bb = 1;
+  bar3.call(this);
+  this.cc = 2;
+  baz();
+  xbar3.call(this);
+  this.dd = 3;
+}
+function bar3() {
+  this.ee = 4;
+}
+function xbar3() {
+  this.ff = 5;
+}
+function baz() {
+  eval("xbar3.call = function() { called3 = true }");
+}
+
+var called3 = false;
+var c3 = new foo3();
+assertEq(c3.cc, 2);
+assertEq(c3.ee, 4);
+assertEq(c3.ff, undefined);
+assertEq(c3.dd, 3);
+assertEq(called3, true);
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -410,16 +410,17 @@ ScriptAnalysis::analyzeBytecode(JSContex
              * There is no jump over this bytecode, nor a containing try block.
              * Either this bytecode will definitely be executed, or an exception
              * will be thrown which the script does not catch.  Either way,
              * any variables definitely defined at this bytecode will stay
              * defined throughout the rest of the script.  We just need to
              * remember the offset where the variable became unconditionally
              * defined, rather than continue to maintain it in define arrays.
              */
+            code->unconditional = true;
             for (unsigned i = 0; i < defineCount; i++) {
                 uint32 local = defineArray[i];
                 JS_ASSERT_IF(definedLocals[local] != LOCAL_CONDITIONALLY_DEFINED &&
                              definedLocals[local] != LOCAL_USE_BEFORE_DEF,
                              definedLocals[local] <= offset);
                 if (definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED)
                     setLocal(local, offset);
             }
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -108,16 +108,22 @@ class Bytecode
     bool fallthrough : 1;
 
     /* Whether this instruction is the fall through point of a conditional jump. */
     bool jumpFallthrough : 1;
 
     /* Whether this instruction can be branched to from a switch statement. Implies jumpTarget. */
     bool switchTarget : 1;
 
+    /*
+     * Whether this instruction must always execute, unless the script throws
+     * an exception which it does not later catch.
+     */
+    bool unconditional : 1;
+
     /* Whether this instruction has been analyzed to get its output defines and stack. */
     bool analyzed : 1;
 
     /* Whether this is a catch/finally entry point. */
     bool exceptionEntry : 1;
 
     /* Whether this is in a try block. */
     bool inTryBlock : 1;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -60,23 +60,25 @@
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Retcon.h"
 
 #include "jsatominlines.h"
 #include "jsgcinlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
+#include "vm/Stack-inl.h"
 
 #ifdef JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
 using namespace js;
 using namespace js::types;
+using namespace js::analyze;
 
 static inline jsid
 id_prototype(JSContext *cx) {
     return ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
 }
 
 static inline jsid
 id_arguments(JSContext *cx) {
@@ -386,41 +388,37 @@ TypeSet::print(JSContext *cx)
         for (unsigned i = 0; i < count; i++) {
             TypeObject *object = getObject(i);
             if (object)
                 printf(" %s", object->name());
         }
     }
 }
 
-class TypeConstraintInput : public TypeConstraint
-{
-public:
-    TypeConstraintInput(JSScript *script)
-        : TypeConstraint("input", script)
-    {}
-
-    bool input() { return true; }
-
-    void newType(JSContext *cx, TypeSet *source, jstype type);
-};
+/////////////////////////////////////////////////////////////////////
+// TypeSet constraints
+/////////////////////////////////////////////////////////////////////
 
 /* Standard subset constraint, propagate all types from one set to another. */
 class TypeConstraintSubset : public TypeConstraint
 {
 public:
     TypeSet *target;
 
     TypeConstraintSubset(JSScript *script, TypeSet *target)
         : TypeConstraint("subset", script), target(target)
     {
         JS_ASSERT(target);
     }
 
-    void newType(JSContext *cx, TypeSet *source, jstype type);
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        /* Basic subset constraint, move all types to the target. */
+        target->addType(cx, type);
+    }
 };
 
 void
 TypeSet::addSubset(JSContext *cx, JSScript *script, TypeSet *target)
 {
     add(cx, ArenaNew<TypeConstraintSubset>(cx->compartment->pool, script, target));
 }
 
@@ -431,49 +429,30 @@ public:
     TypeObject *object;
     TypeSet *target;
 
     TypeConstraintBaseSubset(TypeObject *object, TypeSet *target)
         : TypeConstraint("baseSubset", (JSScript *) 0x1),
           object(object), target(target)
     {}
 
-    void newType(JSContext *cx, TypeSet *source, jstype type);
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        target->addType(cx, type);
+    }
 
     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)
     {}
 
@@ -658,17 +637,27 @@ public:
 
     /* Primitive types other than null and undefined are passed through. */
     bool onlyNullVoid;
 
     TypeConstraintFilterPrimitive(JSScript *script, TypeSet *target, bool onlyNullVoid)
         : TypeConstraint("filter", script), target(target), onlyNullVoid(onlyNullVoid)
     {}
 
-    void newType(JSContext *cx, TypeSet *source, jstype type);
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        if (onlyNullVoid) {
+            if (type == TYPE_NULL || type == TYPE_UNDEFINED)
+                return;
+        } else if (type != TYPE_UNKNOWN && TypeIsPrimitive(type)) {
+            return;
+        }
+
+        target->addType(cx, type);
+    }
 };
 
 void
 TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid)
 {
     add(cx, ArenaNew<TypeConstraintFilterPrimitive>(cx->compartment->pool,
                                                     script, target, onlyNullVoid));
 }
@@ -681,17 +670,21 @@ class TypeConstraintMonitorRead : public
 {
 public:
     TypeSet *target;
 
     TypeConstraintMonitorRead(JSScript *script, TypeSet *target)
         : TypeConstraint("monitorRead", script), target(target)
     {}
 
-    void newType(JSContext *cx, TypeSet *source, jstype type);
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        /* :XXX: Not doing any handling for polymorphism yet. */
+        target->addType(cx, type);
+    }
 };
 
 void
 TypeSet::addMonitorRead(JSContext *cx, JSScript *script, TypeSet *target)
 {
     add(cx, ArenaNew<TypeConstraintMonitorRead>(cx->compartment->pool, script, target));
 }
 
@@ -703,48 +696,43 @@ class TypeConstraintGenerator : public T
 {
 public:
     TypeSet *target;
 
     TypeConstraintGenerator(JSScript *script, TypeSet *target)
         : TypeConstraint("generator", script), target(target)
     {}
 
-    void newType(JSContext *cx, TypeSet *source, jstype type);
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        if (type == TYPE_UNKNOWN) {
+            target->addType(cx, TYPE_UNKNOWN);
+            return;
+        }
+
+        if (TypeIsPrimitive(type))
+            return;
+
+        /*
+         * Watch for 'for in' on Iterator and Generator objects, which can
+         * produce values other than strings.
+         */
+        TypeObject *object = (TypeObject *) type;
+        if (object->proto) {
+            Class *clasp = object->proto->getClass();
+            if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass)
+                target->addType(cx, TYPE_UNKNOWN);
+        }
+    }
 };
 
 /////////////////////////////////////////////////////////////////////
 // TypeConstraint
 /////////////////////////////////////////////////////////////////////
 
-void
-TypeConstraintSubset::newType(JSContext *cx, TypeSet *source, jstype type)
-{
-    /* Basic subset constraint, move all types to the target. */
-    target->addType(cx, type);
-}
-
-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;
 
     /*
@@ -958,17 +946,17 @@ TypeConstraintCall::newType(JSContext *c
         return;
     }
 
     JSScript *callee = function->script;
     unsigned nargs = callee->fun->nargs;
 
     /* Analyze the function if we have not already done so. */
     if (!callee->analyzed) {
-        analyze::ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
+        ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
         if (!calleeAnalysis) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
         }
         calleeAnalysis->analyzeTypes(cx);
     }
 
     /* Add bindings for the arguments of the call. */
@@ -1107,58 +1095,16 @@ TypeConstraintTransformThis::newType(JSC
     if (!object) {
         cx->compartment->types.setPendingNukeTypes(cx);
         return;
     }
 
     target->addType(cx, (jstype) object);
 }
 
-void
-TypeConstraintFilterPrimitive::newType(JSContext *cx, TypeSet *source, jstype type)
-{
-    if (onlyNullVoid) {
-        if (type == TYPE_NULL || type == TYPE_UNDEFINED)
-            return;
-    } else if (type != TYPE_UNKNOWN && TypeIsPrimitive(type)) {
-        return;
-    }
-
-    target->addType(cx, type);
-}
-
-void
-TypeConstraintMonitorRead::newType(JSContext *cx, TypeSet *source, jstype type)
-{
-    target->addType(cx, type);
-}
-
-void
-TypeConstraintGenerator::newType(JSContext *cx, TypeSet *source, jstype type)
-{
-    if (type == TYPE_UNKNOWN) {
-        target->addType(cx, TYPE_UNKNOWN);
-        return;
-    }
-
-    if (TypeIsPrimitive(type))
-        return;
-
-    /*
-     * Watch for 'for in' on Iterator and Generator objects, which can produce
-     * values other than strings.
-     */
-    TypeObject *object = (TypeObject *) type;
-    if (object->proto) {
-        Class *clasp = object->proto->getClass();
-        if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass)
-            target->addType(cx, TYPE_UNKNOWN);
-    }
-}
-
 /////////////////////////////////////////////////////////////////////
 // Freeze constraints
 /////////////////////////////////////////////////////////////////////
 
 /* Constraint which marks all types as pushed by some bytecode. */
 class TypeConstraintPushAll : public TypeConstraint
 {
 public:
@@ -1662,17 +1608,17 @@ TypeCompartment::newInitializerTypeObjec
     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.
          */
         JSObject *baseobj = script->getObject(GET_SLOTNO(script->code + offset));
 
-        if (!res->addDefiniteProperties(cx, baseobj, false))
+        if (!res->addDefiniteProperties(cx, baseobj))
             return NULL;
     }
 
     return res;
 }
 
 static inline jsid
 GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
@@ -1776,16 +1722,55 @@ TypeCompartment::dynamicCall(JSContext *
     for (; arg < args.argc() && arg < nargs; arg++)
         script->typeSetArgument(cx, arg, args[arg]);
 
     /* Watch for fewer actuals than formals to the call. */
     for (; arg < nargs; arg++)
         script->typeSetArgument(cx, arg, UndefinedValue());
 }
 
+/* Intermediate type information for a dynamic type pushed in a script. */
+class TypeIntermediatePushed : public TypeIntermediate
+{
+    uint32 offset;
+    jstype type;
+
+  public:
+    TypeIntermediatePushed(uint32 offset, jstype type)
+        : offset(offset), type(type)
+    {}
+
+    void replay(JSContext *cx, JSScript *script)
+    {
+        TypeSet *pushed = script->analysis(cx)->pushedTypes(offset);
+        pushed->addType(cx, type);
+    }
+
+    bool hasDynamicResult(uint32 offset, jstype type) {
+        return this->offset == offset && this->type == type;
+    }
+
+    bool sweep(JSContext *cx, JSCompartment *compartment)
+    {
+        if (!TypeIsObject(type))
+            return true;
+
+        TypeObject *object = (TypeObject *) type;
+        if (object->marked)
+            return true;
+
+        if (object->unknownProperties()) {
+            type = (jstype) &compartment->types.typeEmpty;
+            return true;
+        }
+
+        return false;
+    }
+};
+
 void
 TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jstype type)
 {
     JS_ASSERT(cx->typeInferenceEnabled());
     AutoEnterTypeInference enter(cx);
 
     /*
      * For inc/dec ops, we need to go back and reanalyze the affected opcode
@@ -1824,77 +1809,73 @@ TypeCompartment::dynamicPush(JSContext *
           case JSOP_ARGINC:
           case JSOP_ARGDEC: {
             /*
              * Just mark the slot's type as holding the new type. This captures
              * the effect if the slot is not being tracked, and if the slot
              * doesn't escape we will update the pushed types below to capture
              * the slot's value after this write.
              */
-            uint32 slot = analyze::GetBytecodeSlot(script, pc);
-            if (slot < analyze::TotalSlots(script)) {
+            uint32 slot = GetBytecodeSlot(script, pc);
+            if (slot < TotalSlots(script)) {
                 if (!script->ensureVarTypes(cx))
                     return;
                 TypeSet *types = script->slotTypes(slot);
                 types->addType(cx, type);
             }
             break;
           }
 
           default:;
         }
     }
 
     if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
         /*
          * If the pushed set already has this type, we don't need to ensure
-         * there is a TypeResult. Either there already is a TypeResult, or the
+         * there is a TypeIntermediate. Either there already is one, or the
          * type could be determined from the script's other input type sets.
          */
         TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
         if (pushed->hasType(type))
             return;
     } else {
-        /* Scan all TypeResults on the script to check for a duplicate. */
-        TypeResult *result, **presult = &script->typeResults;
+        /* Scan all intermediate types on the script to check for a dupe. */
+        TypeIntermediate *result, **presult = &script->intermediateTypes;
         while (*presult) {
             result = *presult;
-            if (result->offset == offset && result->type == type) {
-                if (presult != &script->typeResults) {
-                    /* Move this result to the head of the list, maintain LRU order. */
+            if (result->hasDynamicResult(offset, type)) {
+                if (presult != &script->intermediateTypes) {
+                    /* Move to the head of the list, maintain LRU order. */
                     *presult = result->next;
-                    result->next = script->typeResults;
-                    script->typeResults = result;
+                    result->next = script->intermediateTypes;
+                    script->intermediateTypes = result;
                 }
                 return;
             }
             presult = &result->next;
         }
     }
 
     InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
                script->id(), offset, TypeString(type));
 
-    TypeResult *result = (TypeResult *) cx->calloc_(sizeof(TypeResult));
+    TypeIntermediatePushed *result = cx->new_<TypeIntermediatePushed>(offset, type);
     if (!result) {
         setPendingNukeTypes(cx);
         return;
     }
-
-    result->offset = offset;
-    result->type = type;
-    result->next = script->typeResults;
-    script->typeResults = result;
+    script->addIntermediateType(result);
 
     if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
         TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
         pushed->addType(cx, type);
     } else if (script->analyzed) {
         /* Any new dynamic result triggers reanalysis and recompilation. */
-        analyze::ScriptAnalysis *analysis = script->analysis(cx);
+        ScriptAnalysis *analysis = script->analysis(cx);
         if (!analysis) {
             setPendingNukeTypes(cx);
             return;
         }
         analysis->analyzeTypes(cx);
     }
 
     /*
@@ -2390,17 +2371,17 @@ TypeCompartment::fixObjectType(JSContext
                                          JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
                 cx->compartment->types.setPendingNukeTypes(cx);
                 return;
             }
         }
         JS_ASSERT(!xobj->inDictionaryMode());
         const Shape *newShape = xobj->lastProperty();
 
-        if (!objType->addDefiniteProperties(cx, xobj, false))
+        if (!objType->addDefiniteProperties(cx, xobj))
             return;
 
         ObjectTableKey key;
         key.ids = ids;
         key.nslots = obj->slotSpan();
         key.nfixed = obj->numFixedSlots();
         key.proto = obj->getProto();
         JS_ASSERT(ObjectTableKey::match(key, obj));
@@ -2528,17 +2509,17 @@ 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)
+TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj)
 {
     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.
@@ -2549,38 +2530,16 @@ TypeObject::addDefiniteProperties(JSCont
     while (!JSID_IS_EMPTY(shape->propid)) {
         jsid id = MakeTypeId(cx, shape->propid);
         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 true;
 }
 
 void
@@ -2633,17 +2592,16 @@ TypeObject::markUnknown(JSContext *cx)
         }
     }
 }
 
 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
@@ -2653,16 +2611,93 @@ TypeObject::clearNewScript(JSContext *cx
      */
     for (unsigned i = 0; i < getPropertyCount(); i++) {
         Property *prop = getProperty(i);
         if (!prop)
             continue;
         if (prop->types.isDefiniteProperty())
             prop->types.setOwnProperty(cx, true);
     }
+
+#ifdef JS_METHODJIT
+    mjit::ExpandInlineFrames(cx, true);
+#endif
+
+    /*
+     * If we cleared the new script while in the middle of initializing an
+     * object, it will still have the new script's shape and reflect the no
+     * longer correct state of the object once its initialization is completed.
+     * We can't really detect the possibility of this statically, but the new
+     * script keeps track of where each property is initialized so we can walk
+     * the stack and fix up any such objects.
+     */
+    for (AllFramesIter iter(cx); !iter.done(); ++iter) {
+        StackFrame *fp = iter.fp();
+        if (fp->isScriptFrame() && fp->isConstructing() &&
+            fp->script() == newScript->script && fp->thisValue().isObject() &&
+            fp->thisValue().toObject().type == this) {
+            JSObject *obj = &fp->thisValue().toObject();
+            JSInlinedSite *inline_;
+            jsbytecode *pc = fp->pc(cx, NULL, &inline_);
+            JS_ASSERT(!inline_);
+
+            /* Number of properties that have been added. */
+            uint32 numProperties = 0;
+
+            /*
+             * If non-zero, we are scanning initializers in a call which has
+             * already finished.
+             */
+            size_t depth = 0;
+
+            for (TypeNewScript::Initializer *init = newScript->initializerList;; init++) {
+                uint32 offset = uint32(pc - fp->script()->code);
+                if (init->kind == TypeNewScript::Initializer::SETPROP) {
+                    if (!depth && init->offset > offset) {
+                        /* Advanced past all properties which have been initialized. */
+                        break;
+                    }
+                    numProperties++;
+                } else if (init->kind == TypeNewScript::Initializer::FRAME_PUSH) {
+                    if (depth) {
+                        depth++;
+                    } else if (init->offset > offset) {
+                        /* Advanced past all properties which have been initialized. */
+                        break;
+                    } else if (init->offset == offset) {
+                        StackSegment &seg = cx->stack.space().containingSegment(fp);
+                        if (seg.currentFrame() == fp)
+                            break;
+                        fp = seg.computeNextFrame(fp);
+                        pc = fp->pc(cx, NULL, &inline_);
+                        JS_ASSERT(!inline_);
+                    } else {
+                        /* This call has already finished. */
+                        depth = 1;
+                    }
+                } else if (init->kind == TypeNewScript::Initializer::FRAME_POP) {
+                    if (depth) {
+                        depth--;
+                    } else {
+                        /* This call has not finished yet. */
+                        break;
+                    }
+                } else {
+                    JS_ASSERT(init->kind == init->kind != TypeNewScript::Initializer::DONE);
+                    JS_ASSERT(numProperties == obj->slotSpan());
+                    break;
+                }
+            }
+
+            obj->rollbackProperties(numProperties);
+        }
+    }
+
+    cx->free_(newScript);
+    newScript = NULL;
 }
 
 void
 TypeObject::print(JSContext *cx)
 {
     printf("%s : %s", name(), proto ? proto->getType()->name() : "(null)");
 
     if (unknownProperties()) {
@@ -2742,17 +2777,17 @@ BytecodeNoFallThrough(JSOp op)
 
 /*
  * If the bytecode immediately following code/pc is a test of the value
  * pushed by code, that value should be marked as possibly void.
  */
 static inline bool
 CheckNextTest(jsbytecode *pc)
 {
-    jsbytecode *next = pc + analyze::GetBytecodeLength(pc);
+    jsbytecode *next = pc + GetBytecodeLength(pc);
     switch ((JSOp)*next) {
       case JSOP_IFEQ:
       case JSOP_IFNE:
       case JSOP_NOT:
       case JSOP_OR:
       case JSOP_ORX:
       case JSOP_AND:
       case JSOP_ANDX:
@@ -2773,17 +2808,17 @@ GetInitializerType(JSContext *cx, JSScri
     JSOp op = JSOp(*pc);
     JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT);
 
     bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array));
     return script->getTypeInitObject(cx, pc, isArray);
 }
 
 inline void
-analyze::ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types)
+ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types)
 {
     /* Find the initial ITER opcode which constructed the active iterator. */
     const SSAValue &iterv = poppedValue(pc, 0);
     jsbytecode *iterpc = script->code + iterv.pushedOffset();
     JS_ASSERT(JSOp(*iterpc) == JSOP_ITER);
 
     uintN flags = iterpc[1];
     if (flags & JSITER_FOREACH) {
@@ -2799,29 +2834,29 @@ analyze::ScriptAnalysis::setForTypes(JSC
     types->addType(cx, TYPE_STRING);
 
     pushedTypes(iterpc, 0)->add(cx,
         ArenaNew<TypeConstraintGenerator>(cx->compartment->pool, script, types));
 }
 
 /* Analyze type information for a single bytecode. */
 bool
-analyze::ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
+ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
                                               TypeInferenceState &state)
 {
     jsbytecode *pc = script->code + offset;
     JSOp op = (JSOp)*pc;
 
     Bytecode &code = getCode(offset);
     JS_ASSERT(!code.pushedTypes);
 
     InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset);
 
-    unsigned defCount = analyze::GetDefCount(script, offset);
-    if (analyze::ExtendedDef(pc))
+    unsigned defCount = GetDefCount(script, offset);
+    if (ExtendedDef(pc))
         defCount++;
 
     TypeSet *pushed = ArenaArray<TypeSet>(cx->compartment->pool, defCount);
     if (!pushed)
         return false;
     PodZero(pushed, defCount);
     code.pushedTypes = pushed;
 
@@ -3301,17 +3336,17 @@ analyze::ScriptAnalysis::analyzeTypesByt
         break;
 
       case JSOP_CALL:
       case JSOP_EVAL:
       case JSOP_FUNCALL:
       case JSOP_FUNAPPLY:
       case JSOP_NEW: {
         /* Construct the base call information about this site. */
-        unsigned argCount = analyze::GetUseCount(script, offset) - 2;
+        unsigned argCount = GetUseCount(script, offset) - 2;
         TypeCallsite *callsite = ArenaNew<TypeCallsite>(cx->compartment->pool,
                                                         cx, script, pc, op == JSOP_NEW, argCount);
         if (!callsite || (argCount && !callsite->argumentTypes)) {
             cx->compartment->types.setPendingNukeTypes(cx);
             break;
         }
         callsite->thisTypes = poppedTypes(pc, argCount);
         callsite->returnTypes = &pushed[0];
@@ -3577,17 +3612,17 @@ analyze::ScriptAnalysis::analyzeTypesByt
       default:
         TypeFailure(cx, "Unknown bytecode at #%u:%05u", script->id(), offset);
     }
 
     return true;
 }
 
 void
-analyze::ScriptAnalysis::analyzeTypes(JSContext *cx)
+ScriptAnalysis::analyzeTypes(JSContext *cx)
 {
     JS_ASSERT(!ranInference() && !failed());
 
     /*
      * Refuse to analyze the types in a script which is compileAndGo but is
      * running against a global with a cleared scope. Per GlobalObject::clear,
      * we won't be running anymore compileAndGo code against the global
      * (moreover, after clearing our analysis results will be wrong for the
@@ -3632,52 +3667,51 @@ analyze::ScriptAnalysis::analyzeTypes(JS
     /* Make sure the initial type set of all local vars includes void. */
     for (unsigned i = 0; i < script->nfixed; i++)
         script->localTypes(i)->addType(cx, TYPE_UNDEFINED);
 
     TypeInferenceState state(cx);
 
     unsigned offset = 0;
     while (offset < script->length) {
-        analyze::Bytecode *code = maybeCode(offset);
+        Bytecode *code = maybeCode(offset);
 
         jsbytecode *pc = script->code + offset;
-        analyze::UntrapOpcode untrap(cx, script, pc);
+        UntrapOpcode untrap(cx, script, pc);
 
         if (code && !analyzeTypesBytecode(cx, offset, state)) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
         }
 
-        offset += analyze::GetBytecodeLength(pc);
+        offset += GetBytecodeLength(pc);
     }
 
     for (unsigned i = 0; i < state.phiNodes.length(); i++) {
         SSAPhiNode *node = state.phiNodes[i];
         for (unsigned j = 0; j < node->length; j++) {
             const SSAValue &v = node->options[j];
             getValueTypes(v)->addSubset(cx, script, &node->types);
         }
     }
 
     /*
-     * Sync with any dynamic types previously generated either because
-     * we ran the interpreter some before analyzing or because we
-     * are reanalyzing after a GC.
+     * Replay any intermediate type information which has been generated for
+     * the script either because we ran the interpreter some before analyzing
+     * or because we are reanalyzing after a GC.
      */
-    TypeResult *result = script->typeResults;
+    TypeIntermediate *result = script->intermediateTypes;
     while (result) {
-        TypeSet *pushed = pushedTypes(result->offset);
-        pushed->addType(cx, result->type);
+        result->replay(cx, script);
         result = result->next;
     }
 }
 
 void
-analyze::ScriptAnalysis::analyzeTypesNew(JSContext *cx)
+ScriptAnalysis::analyzeTypesNew(JSContext *cx)
 {
     JS_ASSERT(script->calledWithNew && script->fun);
 
     /*
      * Compute the 'this' type when called with 'new'. We do not distinguish regular
      * from 'new' calls to the function.
      */
 
@@ -3690,201 +3724,315 @@ analyze::ScriptAnalysis::analyzeTypesNew
 
     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)
+/*
+ * Persistent constraint clearing out newScript and definite properties from
+ * an object should a property on another object get a setter.
+ */
+class TypeConstraintClearDefiniteSetter : public TypeConstraint
+{
+public:
+    TypeObject *object;
+
+    TypeConstraintClearDefiniteSetter(TypeObject *object)
+        : TypeConstraint("baseClearDefinite", (JSScript *) 0x1), object(object)
+    {}
+
+    void newType(JSContext *cx, TypeSet *source, jstype type) {
+        if (!object->newScript)
+            return;
+        /*
+         * Clear out the newScript shape and definite property information from
+         * an object if the source type set could be a setter (its type set
+         * becomes unknown).
+         */
+        if (type == TYPE_UNKNOWN)
+            object->clearNewScript(cx);
+    }
+
+    TypeObject * persistentObject() { return object; }
+};
+
+/*
+ * Constraint which clears definite properties on an object should a type set
+ * contain any types other than a single object.
+ */
+class TypeConstraintClearDefiniteSingle : public TypeConstraint
+{
+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())
+            object->clearNewScript(cx);
+    }
+};
+
+/*
+ * Mark an intermediate type set such that changes will clear the definite
+ * properties on a type object.
+ */
+class TypeIntermediateClearDefinite : public TypeIntermediate
+{
+    uint32 offset;
+    uint32 which;
+    TypeObject *object;
+
+  public:
+    TypeIntermediateClearDefinite(uint32 offset, uint32 which, TypeObject *object)
+        : offset(offset), which(which), object(object)
+    {}
+
+    void replay(JSContext *cx, JSScript *script)
+    {
+        TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, which);
+        pushed->add(cx, ArenaNew<TypeConstraintClearDefiniteSingle>(cx->compartment->pool, script, object));
+    }
+
+    bool sweep(JSContext *cx, JSCompartment *compartment)
+    {
+        return object->marked;
+    }
+};
+
+static bool
+AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSScript *script, JSObject **pbaseobj,
+                           Vector<TypeNewScript::Initializer> *initializerList)
 {
     /*
      * 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.
+     * chance to escape and be accessed elsewhere.
+     *
+     * Returns true if the entire script was analyzed (pbaseobj has been
+     * preserved), false if we had to bail out part way through (pbaseobj may
+     * have been cleared).
      */
 
-    /* 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) {
+    ScriptAnalysis *analysis = script->analysis(cx);
+    if (analysis && !analysis->ranInference())
+        analysis->analyzeTypes(cx);
+    if (!analysis || analysis->OOM()) {
+        *pbaseobj = NULL;
+        cx->compartment->types.setPendingNukeTypes(cx);
+        return false;
+    }
+
+    /*
+     * Offset of the last bytecode which popped 'this' and which we have
+     * processed. For simplicity, we scan for places where 'this' is pushed
+     * and immediately analyze the place where that pushed value is popped.
+     * This runs the risk of doing things out of order, if the script looks
+     * something like 'this.f  = (this.g = ...)', so we watch and bail out if
+     * a 'this' is pushed before the previous 'this' value was popped.
+     */
+    uint32 lastThisPopped = 0;
+
+    unsigned nextOffset = 0;
+    while (nextOffset < script->length) {
+        unsigned offset = nextOffset;
         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);
+        nextOffset += GetBytecodeLength(pc);
+
+        /* 'this' can escape through a call to eval. */
+        if (op == JSOP_EVAL)
+            return false;
+
+        /*
+         * We are only interested in places where 'this' is popped. The new
+         * 'this' value cannot escape and be accessed except through such uses.
+         */
+        if (op != JSOP_THIS)
+            continue;
+
+        SSAValue thisv = SSAValue::PushedValue(offset, 0);
+        SSAUseChain *uses = analysis->useChain(thisv);
+
+        JS_ASSERT(uses);
+        if (uses->next || !uses->popped) {
+            /* 'this' value popped in more than one place. */
+            return false;
+        }
+
+        /* Only handle 'this' values popped in unconditional code. */
+
+        /* Maintain ordering property on how 'this' is used, as described above. */
+        if (offset < lastThisPopped) {
+            *pbaseobj = NULL;
+            return false;
         }
-
-        depth -= nuses;
-        depth += ndefs;
-
-        switch (JSOp(*pc)) {
-
-          case JSOP_THIS:
-            thisSlot = depth - 1;
-            break;
-
-          case JSOP_SETPROP: {
-            if (!poppedThis)
-                return baseobj;
+        lastThisPopped = uses->offset;
+
+        pc = script->code + uses->offset;
+        op = JSOp(*pc);
+
+        JSObject *obj = *pbaseobj;
+
+        if (op == JSOP_SETPROP && uses->u.which == 1) {
             jsid id = GetAtomId(cx, script, pc, 0);
-            if (JSID_IS_VOID(id))
-                return baseobj;
+            if (MakeTypeId(cx, id) != id)
+                return false;
             if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx))
-                return baseobj;
-            if (!js_DefineNativeProperty(cx, baseobj, id, UndefinedValue(), NULL, NULL,
+                return false;
+
+            unsigned slotSpan = obj->slotSpan();
+            if (!js_DefineNativeProperty(cx, obj, id, UndefinedValue(), NULL, NULL,
                                          JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
-                return NULL;
+                cx->compartment->types.setPendingNukeTypes(cx);
+                *pbaseobj = NULL;
+                return false;
+            }
+
+            if (obj->inDictionaryMode()) {
+                *pbaseobj = NULL;
+                return false;
             }
-            numProperties++;
-            if (baseobj->slotSpan() != numProperties) {
+
+            if (obj->slotSpan() == slotSpan) {
                 /* Set a duplicate property. */
-                return baseobj;
+                return false;
             }
-            if (baseobj->inDictionaryMode())
-                return NULL;
-            if (numProperties >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
+
+            TypeNewScript::Initializer setprop(TypeNewScript::Initializer::SETPROP, uses->offset);
+            if (!initializerList->append(setprop)) {
+                cx->compartment->types.setPendingNukeTypes(cx);
+                *pbaseobj = NULL;
+                return false;
+            }
+
+            if (obj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
                 /* Maximum number of definite properties added. */
-                return baseobj;
+                return false;
             }
-            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_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:
-          case JSOP_CALL:
-          case JSOP_NEW:
-            break;
-
-          default:
-            return baseobj;
+
+            /*
+             * Ensure that if the properties named here could have a setter in
+             * the direct prototype (and thus its transitive prototypes), the
+             * definite properties get cleared from the shape.
+             */
+            TypeSet *parentTypes = type->proto->getType()->getProperty(cx, id, false);
+            if (!parentTypes || parentTypes->unknown())
+                return false;
+            parentTypes->add(cx, cx->new_<TypeConstraintClearDefiniteSetter>(type));
+        } else if (op == JSOP_FUNCALL && uses->u.which == GET_ARGC(pc) - 1) {
+            /*
+             * Passed as the first parameter to Function.call. Follow control
+             * into the callee, and add any definite properties it assigns to
+             * the object as well. :TODO: This is narrow pattern matching on
+             * the inheritance patterns seen in the v8-deltablue benchmark, and
+             * needs robustness against other ways initialization can cross
+             * script boundaries.
+             *
+             * Add constraints ensuring we are calling Function.call on a
+             * particular script, removing definite properties from the result
+             */
+
+            /* Callee/this must have been pushed by a CALLPROP. */
+            SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
+            if (calleev.kind() != SSAValue::PUSHED ||
+                JSOp(script->code[calleev.pushedOffset()]) != JSOP_CALLPROP ||
+                calleev.pushedIndex() != 0) {
+                return false;
+            }
+
+            TypeSet *funcallTypes = analysis->pushedTypes(calleev.pushedOffset(), 0);
+            TypeSet *scriptTypes = analysis->pushedTypes(calleev.pushedOffset(), 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
+             * 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);
+                *pbaseobj = NULL;
+                return false;
+            }
+            script->addIntermediateType(funcallTrap);
+            script->addIntermediateType(calleeTrap);
+            funcallTrap->replay(cx, script);
+            calleeTrap->replay(cx, script);
+
+            TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset);
+            if (!initializerList->append(pushframe)) {
+                cx->compartment->types.setPendingNukeTypes(cx);
+                *pbaseobj = NULL;
+                return false;
+            }
+
+            if (!AnalyzeNewScriptProperties(cx, type, scriptObj->asFunction()->script,
+                                            pbaseobj, initializerList)) {
+                return false;
+            }
+
+            TypeNewScript::Initializer popframe(TypeNewScript::Initializer::FRAME_POP, 0);
+            if (!initializerList->append(popframe)) {
+                cx->compartment->types.setPendingNukeTypes(cx);
+                *pbaseobj = NULL;
+                return false;
+            }
+
+            /*
+             * The callee never lets the 'this' value escape, continue looking
+             * for definite properties in the remainder of this script.
+             */
+        } else {
+            /* Unhandled use of 'this'. */
+            return false;
         }
-
-        offset += analyze::GetBytecodeLength(pc);
     }
 
-    /* We should have bailed out on a JSOP_STOP or similar. */
-    JS_NOT_REACHED("Mystery!");
-    return baseobj;
+    return true;
 }
 
 /////////////////////////////////////////////////////////////////////
 // Printing
 /////////////////////////////////////////////////////////////////////
 
 void
-analyze::ScriptAnalysis::printTypes(JSContext *cx)
+ScriptAnalysis::printTypes(JSContext *cx)
 {
     AutoEnterAnalysis enter(cx);
     TypeCompartment *compartment = &script->compartment->types;
 
     /*
      * Check if there are warnings for used values with unknown types, and build
      * statistics about the size of type sets found for stack values.
      */
     for (unsigned offset = 0; offset < script->length; offset++) {
         if (!maybeCode(offset))
             continue;
 
-        unsigned defCount = analyze::GetDefCount(script, offset);
+        unsigned defCount = GetDefCount(script, offset);
         if (!defCount)
             continue;
 
         for (unsigned i = 0; i < defCount; i++) {
             TypeSet *types = pushedTypes(offset, i);
 
             if (types->unknown()) {
                 compartment->typeCountOver++;
@@ -3950,17 +4098,17 @@ analyze::ScriptAnalysis::printTypes(JSCo
     printf("\n");
 
     for (unsigned offset = 0; offset < script->length; offset++) {
         if (!maybeCode(offset))
             continue;
 
         PrintBytecode(cx, script, script->code + offset);
 
-        unsigned defCount = analyze::GetDefCount(script, offset);
+        unsigned defCount = GetDefCount(script, offset);
         for (unsigned i = 0; i < defCount; i++) {
             printf("  type %d:", i);
             pushedTypes(offset, i)->print(cx);
             printf("\n");
         }
 
         if (monitoredTypes(offset))
             printf("  monitored\n");
@@ -4141,17 +4289,17 @@ JSScript::typeSetFunction(JSContext *cx,
 void
 JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp)
 {
     AutoEnterTypeInference enter(cx);
 
     if (!(analysis_ && analysis_->ranInference()))
         return;
 
-    int defCount = js::analyze::GetDefCount(this, pc - code);
+    int defCount = GetDefCount(this, pc - code);
 
     for (int i = 0; i < defCount; i++) {
         const js::Value &val = sp[-defCount + i];
         TypeSet *types = analysis_->pushedTypes(pc, i);
         if (IgnorePushed(pc, i))
             continue;
 
         jstype type = GetValueType(cx, val);
@@ -4210,39 +4358,60 @@ JSObject::makeNewType(JSContext *cx, JSS
     if (!getType()->unknownProperties()) {
         /* Update the possible 'new' types for all prototype objects sharing the same type object. */
         TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true);
         if (types)
             types->addType(cx, (jstype) type);
     }
 
     if (newScript && !type->unknownProperties()) {
-        JSObject *baseobj = AnalyzeScriptProperties(cx, newScript);
+        /* 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) {
             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);
+
             /*
              * 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,
                                         baseobj->lastProperty());
-            if (!baseobj)
-                return;
-
-            if (!type->addDefiniteProperties(cx, baseobj, true))
+            if (!baseobj ||
+                !type->addDefiniteProperties(cx, baseobj) ||
+                !initializerList.append(done)) {
+                cx->compartment->types.setPendingNukeTypes(cx);
                 return;
-
-            type->newScript = newScript;
-            type->newScriptFinalizeKind = unsigned(kind);
-            type->newScriptShape = baseobj->lastProperty();
+            }
+
+            size_t numBytes = sizeof(TypeNewScript)
+                            + (initializerList.length() * sizeof(TypeNewScript::Initializer));
+            type->newScript = (TypeNewScript *) cx->calloc_(numBytes);
+            if (!type->newScript) {
+                cx->compartment->types.setPendingNukeTypes(cx);
+                return;
+            }
+
+            type->newScript->script = newScript;
+            type->newScript->finalizeKind = unsigned(kind);
+            type->newScript->shape = baseobj->lastProperty();
+
+            type->newScript->initializerList = (TypeNewScript::Initializer *)
+                ((char *) type->newScript + sizeof(TypeNewScript));
+            PodCopy(type->newScript->initializerList, initializerList.begin(), initializerList.length());
         }
     }
 
     newType = type;
     setDelegate();
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -4282,18 +4451,18 @@ 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");
+        js_TraceScript(trc, newScript->script);
+        gc::MarkShape(trc, newScript->shape, "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.
  */
@@ -4608,32 +4777,25 @@ JSScript::condenseTypes(JSContext *cx)
             cx->free_(varTypes);
             varTypes = NULL;
         } else {
             for (unsigned i = 0; i < num; i++)
                 TypeSet::CondenseSweepTypeSet(cx, &compartment->types, pcondensed, &varTypes[i]);
         }
     }
 
-    TypeResult **presult = &typeResults;
+    TypeIntermediate **presult = &intermediateTypes;
     while (*presult) {
-        TypeResult *result = *presult;
-        if (TypeIsObject(result->type)) {
-            TypeObject *object = (TypeObject *) result->type;
-            if (!object->marked) {
-                if (!object->unknownProperties()) {
-                    *presult = result->next;
-                    cx->free_(result);
-                    continue;
-                } else {
-                    result->type = (jstype) &compartment->types.typeEmpty;
-                }
-            }
+        TypeIntermediate *result = *presult;
+        if (result->sweep(cx, compartment)) {
+            presult = &result->next;
+        } else {
+            *presult = result->next;
+            cx->delete_(result);
         }
-        presult = &result->next;
     }
 }
 
 void
 JSScript::sweepAnalysis(JSContext *cx)
 {
     SweepTypeObjectList(cx, typeObjects);
 
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1,9 +1,9 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
+//* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
 /* vim: set ts=40 sw=4 et tw=99: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
@@ -104,35 +104,38 @@ TypeIsObject(jstype type)
 
 /* Get the type of a jsval, or zero for an unknown special value. */
 inline jstype GetValueType(JSContext *cx, const Value &val);
 
 /*
  * Type inference memory management overview.
  *
  * Inference constructs a global web of constraints relating the contents of
- * type sets particular to various scripts and type objects within a compartment.
- * There are two issues at hand to manage inference memory: collecting
- * the constraints, and collecting type sets (on TypeObject destruction).
+ * type sets particular to various scripts and type objects within a
+ * compartment. There are two issues at hand to manage inference memory:
+ * collecting the constraints, and collecting type sets on TypeObject
+ * destruction.
  *
- * The constraints and types generated during analysis of a script depend entirely on
- * that script's input type sets --- the types of its arguments, upvar locals,
- * callee return values, object properties, and dynamic types (overflows, undefined
- * reads, etc.). On a GC, we collect the analysis information for all scripts
- * which have been analyzed, destroying the type constraints and intermediate
- * type sets associated with stack values, and add new condensed constraints to
- * the script's inputs which will trigger reanalysis and recompilation should
- * that input change in the future.
+ * The constraints and types generated during analysis of a script depend
+ * entirely on that script's input type sets --- the types of its arguments,
+ * upvar locals, callee return values, object properties, and dynamic types
+ * (overflows, undefined reads, etc.). On a GC, we collect the analysis
+ * information for all scripts which have been analyzed, destroying the type
+ * constraints and intermediate type sets associated with stack values, and add
+ * condensed constraints to the script's inputs which will trigger reanalysis
+ * and recompilation should that input change in the future.
  *
  * TypeObjects are collected when either the script they are associated with is
  * destroyed or their prototype JSObject is destroyed.
  *
- * If a GC happens while we are in the middle of analysis or working with a TypeScript
- * or TypeObject, we do not destroy/condense analysis information or collect any
- * TypeObjects or JSScripts. This is controlled with AutoEnterTypeInference.
+ * If a GC happens while we are in the middle of or working with analysis
+ * information (both type information and transient information stored in
+ * ScriptAnalysis), we do not destroy/condense analysis information or collect
+ * TypeObjects or JSScripts. This is controlled with AutoEnterAnalysis and/or
+ * AutoEnterTypeInference.
  */
 
 /*
  * A constraint which listens to additions to a type set and propagates those
  * changes to other type sets.
  */
 class TypeConstraint
 {
@@ -326,16 +329,19 @@ 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);
 
+    /* If this type set definitely represents only a particular type object, get that object. */
+    inline TypeObject *getSingleObject();
+
     void setIntermediate() { JS_ASSERT(!typeFlags); 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));
@@ -354,17 +360,16 @@ 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);
 
@@ -431,16 +436,45 @@ class TypeSet
 /* A type set captured for use by JIT compilers. */
 struct ClonedTypeSet
 {
     TypeFlags typeFlags;
     TypeObject **objectSet;
     unsigned objectCount;
 };
 
+/*
+ * Handler which persists information about the intermediate types in a script
+ * (those appearing on stack values, which are destroyed on GC). These are
+ * attached to the JSScript and persist until it is destroyed; every time the
+ * types in the script are analyzed these are replayed to reconstruct
+ * the intermediate info they store.
+ *
+ * This is mostly for dynamic type results like integer overflows or holes read
+ * out of objects, but also allows specialized type constraints on intermediate
+ * values to be regenerated after GC.
+ */
+class TypeIntermediate
+{
+  public:
+    /* Next intermediate information for the script. */
+    TypeIntermediate *next;
+
+    TypeIntermediate() : next(NULL) {}
+
+    /* Replay this type information on a script whose types have just been analyzed. */
+    virtual void replay(JSContext *cx, JSScript *script) = 0;
+
+    /* Sweep this intermediate info, returning false to unlink and destroy this. */
+    virtual bool sweep(JSContext *cx, JSCompartment *compartment) = 0;
+
+    /* Whether this subsumes a dynamic type pushed by the bytecode at offset. */
+    virtual bool hasDynamicResult(uint32 offset, jstype type) { return false; }
+};
+
 /* Type information about a property. */
 struct Property
 {
     /* Identifier for this property, JSID_VOID for the aggregate integer index property. */
     jsid id;
 
     /* Possible types for this property, including types inherited from prototypes. */
     TypeSet types;
@@ -448,16 +482,53 @@ struct Property
     Property(jsid id)
         : id(id)
     {}
 
     static uint32 keyBits(jsid id) { return (uint32) JSID_BITS(id); }
     static jsid getKey(Property *p) { return p->id; }
 };
 
+/*
+ * Information attached to a TypeObject if it is always constructed using 'new'
+ * on a particular script.
+ */
+struct TypeNewScript
+{
+    JSScript *script;
+
+    /* Finalize kind to use for newly constructed objects. */
+    /* gc::FinalizeKind */ unsigned finalizeKind;
+
+    /* Shape to use for newly constructed objects. */
+    const Shape *shape;
+
+    /*
+     * Order in which properties become initialized. We need this in case a
+     * scripted setter is added to one of the object's prototypes while it is in
+     * the middle of being initialized, so we can walk the stack and fixup any
+     * objects which look for in-progress objects which were prematurely set
+     * with their final shape. Initialization can traverse stack frames,
+     * in which case FRAME_PUSH/FRAME_POP are used.
+     */
+    struct Initializer {
+        enum Kind {
+            SETPROP,
+            FRAME_PUSH,
+            FRAME_POP,
+            DONE
+        } kind;
+        uint32 offset;
+        Initializer(Kind kind, uint32 offset)
+          : kind(kind), offset(offset)
+        {}
+    };
+    Initializer *initializerList;
+};
+
 /* Type information about an object accessed by a script. */
 struct TypeObject
 {
 #ifdef DEBUG
     /* Name of this object. */
     jsid name_;
 #endif
 
@@ -473,22 +544,20 @@ 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.
+     * 'new' on the specified script, which adds some number of properties to
+     * the object in a definite order before the object escapes.
      */
-    JSScript *newScript;
-    /* gc::FinalizeKind */ unsigned newScriptFinalizeKind;
-    const Shape *newScriptShape;
+    TypeNewScript *newScript;
 
     /*
      * 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;
@@ -568,17 +637,17 @@ 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);
+    bool addDefiniteProperties(JSContext *cx, JSObject *obj);
     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);
@@ -641,36 +710,16 @@ struct TypeCallsite
     inline bool forceThisTypes(JSContext *cx);
 
     /* Get the new object at this callsite. */
     inline TypeObject* getInitObject(JSContext *cx, bool isArray);
 
     inline bool hasGlobal();
 };
 
-/*
- * Type information about a dynamic value pushed by a script's opcode.
- * These are associated with each JSScript and persist after the
- * TypeScript is destroyed by GCs.
- */
-struct TypeResult
-{
-    /*
-     * Offset pushing the value. TypeResults are only generated for
-     * the first stack slot actually pushed by a bytecode.
-     */
-    uint32 offset;
-
-    /* Type which was pushed. */
-    jstype type;
-
-    /* Next dynamic result for the script. */
-    TypeResult *next;
-};
-
 struct ArrayTableKey;
 typedef HashMap<ArrayTableKey,TypeObject*,ArrayTableKey,SystemAllocPolicy> ArrayTypeTable;
 
 struct ObjectTableKey;
 struct ObjectTableEntry;
 typedef HashMap<ObjectTableKey,ObjectTableEntry,ObjectTableKey,SystemAllocPolicy> ObjectTypeTable;
 
 /* Type information for a compartment. */
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1141,16 +1141,24 @@ TypeSet::getObject(unsigned i)
 {
     if (objectCount == 1) {
         JS_ASSERT(i == 0);
         return (TypeObject *) objectSet;
     }
     return objectSet[i];
 }
 
+inline TypeObject *
+TypeSet::getSingleObject()
+{
+    if (!baseFlags() && objectCount == 1)
+        return getObject(0);
+    return NULL;
+}
+
 inline TypeSet *
 TypeSet::make(JSContext *cx, const char *name)
 {
     JS_ASSERT(cx->compartment->activeInference);
 
     TypeSet *res = ArenaNew<TypeSet>(cx->compartment->pool);
     if (!res) {
         cx->compartment->types.setPendingNukeTypes(cx);
@@ -1256,18 +1264,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),
+      newScript(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
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2981,21 +2981,21 @@ js_CreateThis(JSContext *cx, JSObject *c
 JSObject *
 js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto)
 {
     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);
+            JS_ASSERT(type->newScript->script == calleeScript);
+            gc::FinalizeKind kind = gc::FinalizeKind(type->newScript->finalizeKind);
             JSObject *res = NewObjectWithType(cx, type, callee->getParent(), kind);
             if (res)
-                res->setMap((Shape *) type->newScriptShape);
+                res->setMap((Shape *) type->newScript->shape);
             return res;
         }
     }
 
     gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass);
     return NewNonFunction<WithProto::Class>(cx, &js_ObjectClass, proto, callee->getParent(), kind);
 }
 
@@ -4337,31 +4337,31 @@ JSObject::allocSlots(JSContext *cx, size
     JS_ASSERT(newcap >= oldcap && !hasSlotsArray());
 
     /*
      * If we are allocating slots for an object whose type is always created
      * by calling 'new' on a particular script, bump the GC kind for that
      * script to give these objects a larger number of fixed slots.
      */
     if (type->newScript) {
-        unsigned newScriptSlots = gc::GetGCKindSlots(gc::FinalizeKind(type->newScriptFinalizeKind));
+        unsigned newScriptSlots = gc::GetGCKindSlots(gc::FinalizeKind(type->newScript->finalizeKind));
         if (newScriptSlots == numFixedSlots()) {
             /*
              * Bump by two to keep BACKGROUND consistent.
              * :XXX: this FINALIZE_*_BACKGROUND stuff needs an abstraction.
              */
-            unsigned newKind = type->newScriptFinalizeKind + 2;
+            unsigned newKind = type->newScript->finalizeKind + 2;
             if (newKind <= gc::FINALIZE_OBJECT_LAST) {
                 JSObject *obj = NewReshapedObject(cx, type, getParent(),
-                                                  gc::FinalizeKind(newKind), type->newScriptShape);
+                                                  gc::FinalizeKind(newKind), type->newScript->shape);
                 if (!obj)
                     return false;
 
-                type->newScriptFinalizeKind = newKind;
-                type->newScriptShape = obj->lastProperty();
+                type->newScript->finalizeKind = newKind;
+                type->newScript->shape = obj->lastProperty();
             }
         }
     }
 
     if (newcap > NSLOTS_LIMIT) {
         if (!JS_ON_TRACE(cx))
             js_ReportAllocationOverflow(cx);
         return false;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -684,16 +684,18 @@ struct JSObject : js::gc::Cell {
     bool ensureClassReservedSlotsForEmptyObject(JSContext *cx);
 
     inline bool ensureClassReservedSlots(JSContext *cx);
 
     inline uint32 slotSpan() const;
 
     inline bool containsSlot(uint32 slot) const;
 
+    void rollbackProperties(uint32 slotSpan);
+
     js::Value& getSlotRef(uintN slot) {
         JS_ASSERT(slot < capacity);
         size_t fixed = numFixedSlots();
         if (slot < fixed)
             return fixedSlots()[slot];
         return slots[slot - fixed];
     }
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -773,17 +773,17 @@ JSObject::setWithThis(JSObject *thisp)
 }
 
 inline js::types::TypeObject *
 JSObject::getNewType(JSContext *cx, JSScript *script)
 {
     if (isDenseArray() && !makeDenseArraySlow(cx))
         return NULL;
     if (newType) {
-        if (newType->newScript != script && newType->newScript)
+        if (newType->newScript && newType->newScript->script != script)
             newType->clearNewScript(cx);
     } else {
         makeNewType(cx, script);
     }
     return newType;
 }
 
 inline bool
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -1246,16 +1246,27 @@ JSObject::clear(JSContext *cx)
     setMap(shape);
 
     LeaveTraceIfGlobalObject(cx, this);
     JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
     CHECK_SHAPE_CONSISTENCY(this);
 }
 
 void
+JSObject::rollbackProperties(uint32 slotSpan)
+{
+    /* Remove properties from this object until it has a matching slot span. */
+    JS_ASSERT(!hasSlotsArray() && slotSpan <= this->slotSpan());
+    while (this->slotSpan() != slotSpan) {
+        JS_ASSERT(lastProp->hasSlot() && getSlot(lastProp->slot).isUndefined());
+        removeLastProperty();
+    }
+}
+
+void
 JSObject::generateOwnShape(JSContext *cx)
 {
 #ifdef JS_TRACER
     JS_ASSERT_IF(!parent && JS_ON_TRACE(cx), JS_TRACE_MONITOR_ON_TRACE(cx)->bailExit);
     LeaveTraceIfGlobalObject(cx, this);
 
     /*
      * If we are recording, here is where we forget already-guarded shapes.
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1503,20 +1503,20 @@ DestroyScript(JSContext *cx, JSScript *s
     types::TypeObject *obj = script->typeObjects;
     while (obj) {
         types::TypeObject *next = obj->next;
         obj->next = script->compartment->types.objects;
         script->compartment->types.objects = obj;
         obj = next;
     }
 
-    types::TypeResult *result = script->typeResults;
+    types::TypeIntermediate *result = script->intermediateTypes;
     while (result) {
-        types::TypeResult *next = result->next;
-        cx->free_(result);
+        types::TypeIntermediate *next = result->next;
+        cx->delete_(result);
         result = next;
     }
 
     cx->free_(script->varTypes);
 
 #if defined(JS_METHODJIT)
     mjit::ReleaseScriptCode(cx, script, true);
     mjit::ReleaseScriptCode(cx, script, false);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -525,18 +525,22 @@ struct JSScript {
     js::GlobalObject *global;
 
     /* Lazily constructed types of rval/this/args/vars/upvars for this script. */
     js::types::TypeSet *varTypes;
 
     /* Any type objects associated with this script, including initializer objects. */
     js::types::TypeObject *typeObjects;
 
-    /* Any possibly unexpected values pushed by opcodes in this script. */
-    js::types::TypeResult *typeResults;
+    /* Persistent information about stack types in this script. */
+    js::types::TypeIntermediate *intermediateTypes;
+    void addIntermediateType(js::types::TypeIntermediate *type) {
+        type->next = intermediateTypes;
+        intermediateTypes = type;
+    }
 
     /* Bytecode analysis and type inference results for this script. Destroyed on GC. */
   private:
     js::analyze::ScriptAnalysis *analysis_;
     void makeAnalysis(JSContext *cx);
   public:
 
     bool hasAnalysis() { return analysis_ != NULL; }