Analysis and recompilation tuning for SS, bug 608746.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 20 Nov 2010 15:45:52 -0800
changeset 74624 d86864d9ebac9d88056963d0673a766349e69f8c
parent 74623 c6120a2f9a9b29e3cb598beafcb95db01f1d70ce
child 74625 0581e178dcd8e7093ea6c4ace18d72b73eb0c9a5
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs608746
milestone2.0b8pre
Analysis and recompilation tuning for SS, bug 608746.
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jscntxt.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsmath.cpp
js/src/jsscript.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/Retcon.cpp
js/src/shell/js.cpp
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -464,17 +464,17 @@ Script::analyze(JSContext *cx)
         code->pushedArray = ArenaArray<types::TypeStack>(pool, ndefs);
         PodZero(code->pushedArray, ndefs);
 
         for (unsigned i = 0; i < ndefs; i++) {
             code->pushedArray[i].types.setPool(&pool);
             code->pushedArray[i].setInnerStack(stack);
             stack = &code->pushedArray[i];
 
-            types::InferSpew(types::ISpewOps, "pushed #%u:%05u %u T%u\n",
+            types::InferSpew(types::ISpewOps, "pushed #%u:%05u %u T%u",
                              id, offset, i, stack->types.id());
         }
 
         /* Track the initializer stack and compute new objects for encountered initializers. */
         if (op == JSOP_NEWINIT || op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT) {
             bool newArray = (op == JSOP_NEWARRAY) || (op == JSOP_NEWINIT && pc[1] == JSProto_Array);
 
             types::TypeObject *object;
@@ -754,26 +754,31 @@ Script::analyze(JSContext *cx)
         }
     }
 
     JS_ASSERT(!failed());
     JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
 
 #ifdef JS_TYPE_INFERENCE
     /* Generate type constraints for the script. */
+
+    AnalyzeState state;
+    state.init(cx, script);
+
     offset = 0;
-    TypeState state;
     while (offset < script->length) {
-        analyze::Bytecode *code = maybeCode(offset);
+        Bytecode *code = maybeCode(offset);
 
         jsbytecode *pc = script->code + offset;
         UntrapOpcode untrap(cx, script, pc);
 
         offset += GetBytecodeLength(pc);
 
         if (code && code->analyzed)
             analyzeTypes(cx, code, state);
     }
+
+    state.destroy(cx);
 #endif
 }
 
 } /* namespace analyze */
 } /* namespace js */
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -134,22 +134,16 @@ struct Bytecode
     types::TypeObject *initObject;
 
     /*
      * For inc/dec operations, whether the operation dynamically overflowed to double.
      * Should one of these overflow, we reanalyze the affected bytecode.
      */
     bool hasIncDecOverflow : 1;
 
-    /*
-     * For logging, whether we've generated warnings due to a mismatch between the
-     * actual and inferred types at this bytecode.
-     */
-    bool missingTypes : 1;
-
     /* Pool which constraints on this instruction should use. */
     inline JSArenaPool &pool();
 
     /* Get the type set for the Nth value popped by this instruction. */
     inline types::TypeSet *popped(unsigned num);
 
     /* Get the type set for the Nth value pushed by this instruction. */
     inline types::TypeSet *pushed(unsigned num);
@@ -158,17 +152,19 @@ struct Bytecode
     inline void setFixed(JSContext *cx, unsigned num, types::jstype type);
 
     /*
      * Get the new object at this bytecode. propagation will be performed from
      * either Object or Array, per isArray.
      */
     inline types::TypeObject* getInitObject(JSContext *cx, bool isArray);
 
+#ifdef DEBUG
     void print(JSContext *cx);
+#endif
 
 #endif /* JS_TYPE_INFERENCE */
 
 };
 
 /* Information about a script. */
 class Script
 {
@@ -344,47 +340,125 @@ class Script
      * Get the non-eval script which this one is nested in, returning this script
      * if it was not produced as the result of an eval.
      */
     inline Script *evalParent();
 
     /* Bytecode where this script is nested. */
     inline Bytecode *parentCode();
 
-    void print(JSContext *cx);
+    /* Gather statistics off this script and print it if necessary. */
+    void finish(JSContext *cx);
 
     /* Helpers */
 
-    /* Temporary state for handling opcodes with fused behavior. */
-    struct TypeState {
+    /* Inference state destroyed after the initial pass through the function. */
+
+    struct AnalyzeStateStack {
+        /* Whether this node is the iterator for a 'for each' loop. */
+        bool isForEach;
+
+        /* Variable set for any scope name binding pushed on this stack node. */
+        types::VariableSet *scopeVars;
+
+        /* Any value pushed by a JSOP_DOUBLE. */
+        bool hasDouble;
+        double doubleValue;
+
+        /* Whether this is or could be the constant zero. */
+        bool isZero;
+
+        /* Whether this is another constant. */
+        bool isConstant;
+    };
+
+    struct AnalyzeState {
+        AnalyzeStateStack *stack;
+
+        /* Current stack depth. */
+        unsigned stackDepth;
+
         /* Last opcode was JSOP_GETTER or JSOP_SETTER. */
         bool hasGetSet;
 
         /* Last opcode was JSOP_HOLE. */
         bool hasHole;
 
-        TypeState()
-            : hasGetSet(false), hasHole(false)
+        /* Locals thought to be zero/constants. */
+        bool zeroLocals[4];
+        uint32 constLocals[4];
+        unsigned numConstLocals;
+
+        AnalyzeState()
+            : stack(NULL), stackDepth(0), hasGetSet(false), hasHole(false), numConstLocals(0)
         {}
+
+        bool init(JSContext *cx, JSScript *script)
+        {
+            if (script->nslots) {
+                stack = (AnalyzeStateStack *)
+                    cx->calloc(script->nslots * sizeof(AnalyzeStateStack));
+                return (stack != NULL);
+            }
+            return true;
+        }
+
+        void destroy(JSContext *cx)
+        {
+            cx->free(stack);
+        }
+
+        AnalyzeStateStack &popped(unsigned i) {
+            JS_ASSERT(i < stackDepth);
+            return stack[stackDepth - 1 - i];
+        }
+
+        const AnalyzeStateStack &popped(unsigned i) const {
+            JS_ASSERT(i < stackDepth);
+            return stack[stackDepth - 1 - i];
+        }
+
+        void addConstLocal(uint32 local, bool zero) {
+            if (numConstLocals == JS_ARRAY_LENGTH(constLocals))
+                return;
+            if (maybeLocalConst(local, false))
+                return;
+            zeroLocals[numConstLocals] = zero;
+            constLocals[numConstLocals++] = local;
+        }
+
+        bool maybeLocalConst(uint32 local, bool zero) {
+            for (unsigned i = 0; i < numConstLocals; i++) {
+                if (constLocals[i] == local)
+                    return !zero || zeroLocals[i];
+            }
+            return false;
+        }
+
+        void clearLocal(uint32 local) {
+            for (unsigned i = 0; i < numConstLocals; i++) {
+                if (constLocals[i] == local) {
+                    constLocals[i] = constLocals[--numConstLocals];
+                    return;
+                }
+            }
+        }
     };
 
     /* Analyzes a bytecode, generating type constraints describing its behavior. */
-    void analyzeTypes(JSContext *cx, Bytecode *codeType, TypeState &state);
+    void analyzeTypes(JSContext *cx, Bytecode *code, AnalyzeState &state);
 
-    /*
-     * Get the name to use for the local with specified index.  Stack indicates the
-     * point of the access, for looking up let variables.
-     */
-    inline jsid getLocalId(unsigned index, types::TypeStack *stack);
+    /* Get the name to use for the local with specified index. */
+    inline jsid getLocalId(unsigned index, Bytecode *code);
 
     /* Get the name to use for the argument with the specified index. */
     inline jsid getArgumentId(unsigned index);
 
     /* Get the type set to use for a stack slot at a fixed stack depth. */
-    inline types::TypeSet *getStackTypes(unsigned index, types::TypeStack *stack);
+    inline types::TypeSet *getStackTypes(unsigned index, Bytecode *code);
 
     /* Get any known type tag for an argument or local variable. */
     inline JSValueType knownArgumentTypeTag(JSContext *cx, JSScript *script, unsigned arg);
     inline JSValueType knownLocalTypeTag(JSContext *cx, JSScript *script, unsigned local);
 
     void trace(JSTracer *trc);
 
 #endif /* JS_TYPE_INFERENCE */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4298,19 +4298,18 @@ JS_TypeHandlerDynamic(JSContext *cx, JST
 JS_PUBLIC_API(void)
 JS_TypeHandlerMissing(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
 #ifdef JS_TYPE_INFERENCE
     TypeFunction *fun = Valueify(jsfun);
     TypeCallsite *site = Valueify(jssite);
 
     /* Don't mark the return type as anything, and add a warning. */
-    cx->compartment->types.warnings = true;
-    InferSpew(ISpewDynamic, "warning: Call to unimplemented handler at #%u:%05u: %s\n",
-              site->code->script->id, site->code->offset, TypeIdString(cx, fun->name));
+    TypeFailure(cx, "Call to unimplemented handler at #%u:%05u: %s",
+                site->code->script->id, site->code->offset, TypeIdString(fun->name));
 #endif
 }
 
 JS_PUBLIC_API(void) JS_TypeHandlerVoid(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
 #ifdef JS_TYPE_INFERENCE
     TypeCallsite *site = Valueify(jssite);
     if (site->returnTypes)
@@ -4871,17 +4870,17 @@ JS_MakeTypeObject(JSContext *cx, const c
 {
 #ifdef JS_TYPE_INFERENCE
     TypeObject *type = cx->getTypeObject(name, isArray, false);
     TypeObject *proto = cx->getFixedTypeObject(isArray ? TYPE_OBJECT_ARRAY_PROTOTYPE : TYPE_OBJECT_OBJECT_PROTOTYPE);
     if (proto)
         proto->addPropagate(cx, type);
 
     if (monitorNeeded)
-        cx->monitorTypeObject(type);
+        cx->markTypeObjectUnknownProperties(type);
 
     return (JSTypeObject*) type;
 #endif
     return NULL;
 }
 
 JS_PUBLIC_API(JSTypeObject *)
 JS_MakeTypeFunction(JSContext *cx, const char *name, JSTypeHandler handler)
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2130,17 +2130,17 @@ static JSBool
 array_push(JSContext *cx, uintN argc, Value *vp)
 {
     /* Insist on one argument and obj of the expected class. */
     JSObject *obj = ComputeThisFromVp(cx, vp);
     if (!obj)
         return JS_FALSE;
 
     if (cx->isTypeCallerMonitored())
-        cx->monitorTypeObject(obj->getTypeObject());
+        cx->markTypeObjectUnknownProperties(obj->getTypeObject());
 
     if (argc != 1 || !obj->isDenseArray())
         return array_push_slowly(cx, obj, argc, vp + 2, vp);
 
     return array_push1_dense(cx, obj, vp[2], vp);
 }
 
 static JSBool
@@ -2258,17 +2258,17 @@ array_unshift(JSContext *cx, uintN argc,
     JSBool hole;
     jsdouble last, newlen;
 
     JSObject *obj = ComputeThisFromVp(cx, vp);
     if (!obj || !js_GetLengthProperty(cx, obj, &length))
         return JS_FALSE;
 
     if (cx->isTypeCallerMonitored())
-        cx->monitorTypeObject(obj->getTypeObject());
+        cx->markTypeObjectUnknownProperties(obj->getTypeObject());
 
     newlen = length;
     if (argc > 0) {
         /* Slide up the array to make room for argc at the bottom. */
         argv = JS_ARGV(cx, vp);
         if (length > 0) {
             if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
                 !INDEX_TOO_SPARSE(obj, unsigned(newlen + argc))) {
@@ -2320,23 +2320,23 @@ array_splice(JSContext *cx, uintN argc, 
 
 #ifdef JS_TYPE_INFERENCE
     if (!objType || !objType->hasArrayPropagation) {
         /*
          * Make a new type object for the return value.  This is an unexpected
          * result of the call so mark it at the callsite.
          */
         objType = cx->getTypeCallerInitObject(true);
-        cx->monitorTypeObject(objType);
+        cx->markTypeObjectUnknownProperties(objType);
         cx->markTypeCallerUnexpected((jstype) objType);
     }
 #endif
 
     if (cx->isTypeCallerMonitored())
-        cx->monitorTypeObject(objType);
+        cx->markTypeObjectUnknownProperties(objType);
 
     /*
      * Create a new array value to return.  Our ECMA v2 proposal specs
      * that splice always returns an array value, even when given no
      * arguments.  We think this is best because it eliminates the need
      * for callers to do an extra test to handle the empty splice case.
      */
     JSObject *obj2 = js_NewArrayObject(cx, 0, NULL, objType);
@@ -2482,17 +2482,17 @@ array_concat(JSContext *cx, uintN argc, 
 {
     /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
     Value *p = JS_ARGV(cx, vp) - 1;
 
     /* Get the type object to use for the result. */
     TypeObject *ntype = cx->getTypeCallerInitObject(true);
 
     if (cx->isTypeCallerMonitored())
-        cx->monitorTypeObject(ntype);
+        cx->markTypeObjectUnknownProperties(ntype);
 
     /* Create a new Array object and root it using *vp. */
     JSObject *aobj = ComputeThisFromVp(cx, vp);
     JSObject *nobj;
     jsuint length;
     if (aobj->isDenseArray()) {
         /*
          * Clone aobj but pass the minimum of its length and capacity, to
@@ -2618,23 +2618,23 @@ array_slice(JSContext *cx, uintN argc, V
 
 #ifdef JS_TYPE_INFERENCE
     if (!objType->hasArrayPropagation) {
         /*
          * Make a new type object for the return value.  This is an unexpected
          * result of the call so mark it at the callsite.
          */
         objType = cx->getTypeCallerInitObject(true);
-        cx->monitorTypeObject(objType);
+        cx->markTypeObjectUnknownProperties(objType);
         cx->markTypeCallerUnexpected((jstype) objType);
     }
 #endif
 
     if (cx->isTypeCallerMonitored())
-        cx->monitorTypeObject(objType);
+        cx->markTypeObjectUnknownProperties(objType);
 
     if (obj->isDenseArray() && end <= obj->getDenseArrayCapacity() &&
         !js_PrototypeHasIndexedProperties(cx, obj)) {
         nobj = js_NewArrayObject(cx, end - begin, obj->getDenseArrayElements() + begin, objType);
         if (!nobj)
             return JS_FALSE;
         vp->setObject(*nobj);
         return JS_TRUE;
@@ -2843,17 +2843,17 @@ array_extra(JSContext *cx, ArrayExtraMod
 
     Value thisv = (argc > 1 && !REDUCE_MODE(mode)) ? argv[1] : UndefinedValue();
 
     /*
      * If the callsite is being monitored for type inference, and we are modifying
      * the output array, monitor any reads on the array in the future.
      */
     if (cx->isTypeCallerMonitored() && (mode == MAP || mode == FILTER))
-        cx->monitorTypeObject(newtype);
+        cx->markTypeObjectUnknownProperties(newtype);
 
     /*
      * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
      * requires 4 args (accum, value, index, array).
      */
     argc = 3 + REDUCE_MODE(mode);
 
     InvokeSessionGuard session;
@@ -3302,17 +3302,17 @@ js_Array(JSContext *cx, uintN argc, Valu
     } else {
         length = ValueIsLength(cx, vp + 2);
         if (vp[2].isNull())
             return JS_FALSE;
         vector = NULL;
     }
 
     if (cx->isTypeCallerMonitored() && vector)
-        cx->monitorTypeObject(type);
+        cx->markTypeObjectUnknownProperties(type);
 
     /* Whether called with 'new' or not, use a new Array object. */
     JSObject *obj = NewDenseArrayObject(cx, type, length);
     if (!obj)
         return JS_FALSE;
     vp->setObject(*obj);
 
     return InitArrayObject(cx, obj, length, vector);
@@ -3376,16 +3376,17 @@ static void array_TypeNew(JSContext *cx,
     TypeSet *indexTypes = object->indexTypes(cx);
 
     // ignore the case where the call is passed a single argument. this is
     // expected to be the array length, but if it isn't we will catch it
     // in the Array native itself.
     if (site->argumentCount > 1) {
         for (size_t ind = 0; ind < site->argumentCount; ind++)
             site->argumentTypes[ind]->addSubset(cx, site->pool(), indexTypes);
+        object->possiblePackedArray = true;
     }
 #endif
 }
 
 JSObject *
 js_InitArrayClass(JSContext *cx, JSObject *obj)
 {
     JSObject *proto = js_InitClass(cx, obj, NULL, &js_ArrayClass, js_Array, 1, array_TypeNew,
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2450,23 +2450,20 @@ public:
     inline void addTypeProperty(js::types::TypeObject *obj, const char *name, const js::Value &value);
     inline void addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::jstype type);
     inline void addTypePropertyId(js::types::TypeObject *obj, jsid id, const js::Value &value);
 
     /* Alias two properties in the type information for obj. */
     inline void aliasTypeProperties(js::types::TypeObject *obj, jsid first, jsid second);
 
     /* Mark an array type as being not packed and, possibly, not dense. */
-    inline void markTypeArrayNotPacked(js::types::TypeObject *obj, bool notDense);
-
-    /*
-     * Monitor future reads from the a type object.  Instances may have properties
-     * the inference does not know about.
-     */
-    inline void monitorTypeObject(js::types::TypeObject *obj);
+    inline void markTypeArrayNotPacked(js::types::TypeObject *obj, bool notDense, bool dynamic = true);
+
+    /* Monitor all properties of a type object as unknown. */
+    inline void markTypeObjectUnknownProperties(js::types::TypeObject *obj);
 
   private:
     /* To silence MSVC warning about using 'this' in a member initializer. */
     JSContext *thisInInitializer() { return this; }
 };
 
 #ifdef JS_THREADSAFE
 # define JS_THREAD_ID(cx)       ((cx)->thread ? (cx)->thread->id : 0)
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -54,16 +54,17 @@
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jstl.h"
 #include "jsiter.h"
 
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Retcon.h"
 
+#include "jsatominlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include <zlib.h>
 
 #ifdef JS_HAS_XML_SUPPORT
 #include "jsxml.h"
@@ -125,18 +126,16 @@ static const char *js_CodeNameTwo[] = {
 #include "jsopcode.tbl"
 #undef OPDEF
 };
 
 /////////////////////////////////////////////////////////////////////
 // Logging
 /////////////////////////////////////////////////////////////////////
 
-#ifdef DEBUG
-
 static bool InferSpewActive(SpewChannel channel)
 {
     static bool active[SPEW_COUNT];
     static bool checked = false;
     if (!checked) {
         checked = true;
         PodArrayZero(active);
         const char *env = getenv("INFERFLAGS");
@@ -151,70 +150,82 @@ static bool InferSpewActive(SpewChannel 
         if (strstr(env, "full")) {
             for (unsigned i = 0; i < SPEW_COUNT; i++)
                 active[i] = true;
         }
     }
     return active[channel];
 }
 
+#ifdef DEBUG
+
+const char *
+TypeString(jstype type)
+{
+    switch (type) {
+      case TYPE_UNDEFINED:
+        return "void";
+      case TYPE_NULL:
+        return "null";
+      case TYPE_BOOLEAN:
+        return "bool";
+      case TYPE_INT32:
+        return "int";
+      case TYPE_DOUBLE:
+        return "float";
+      case TYPE_STRING:
+        return "string";
+      case TYPE_UNKNOWN:
+        return "unknown";
+      default: {
+        JS_ASSERT(TypeIsObject(type));
+        TypeObject *object = (TypeObject *) type;
+        return TypeIdString(object->name);
+      }
+    }
+}
+
 void InferSpew(SpewChannel channel, const char *fmt, ...)
 {
     if (!InferSpewActive(channel))
         return;
 
     va_list ap;
     va_start(ap, fmt);
     fprintf(stdout, "[infer] ");
     vfprintf(stdout, fmt, ap);
     fprintf(stdout, "\n");
     va_end(ap);
 }
 
-void InferSpewType(SpewChannel channel, JSContext *cx, jstype type, const char *fmt, ...)
+#endif
+
+void TypeFailure(JSContext *cx, const char *fmt, ...)
 {
-    JS_ASSERT(type);
-
-    if (!InferSpewActive(channel))
-        return;
-
     va_list ap;
     va_start(ap, fmt);
-    fprintf(stdout, "[infer] ");
+    fprintf(stdout, "[infer failure] ");
     vfprintf(stdout, fmt, ap);
+    fprintf(stdout, "\n");
     va_end(ap);
 
-    TypeSet types(&cx->compartment->types.pool);
-    PodZero(&types);
-
-    if (type == TYPE_UNKNOWN) {
-        types.typeFlags = TYPE_FLAG_UNKNOWN;
-    } else if (TypeIsPrimitive(type)) {
-        types.typeFlags = 1 << type;
-    } else {
-        types.typeFlags = TYPE_FLAG_OBJECT;
-        types.objectSet = (TypeObject**) type;
-        types.objectCount = 1;
-    }
-
-    types.print(cx);
-
-    fprintf(stdout, "\n");
+    cx->compartment->types.finish(cx, cx->compartment);
+
+    fflush(stdout);
+    *((int*)NULL) = 0;  /* Type warnings */
 }
 
-#endif
-
 /////////////////////////////////////////////////////////////////////
 // TypeSet
 /////////////////////////////////////////////////////////////////////
 
 inline void
 TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting)
 {
-    InferSpew(ISpewOps, "addConstraint: T%u C%u %s\n",
+    InferSpew(ISpewOps, "addConstraint: T%u C%u %s",
               id(), constraint->id(), constraint->kind());
 
     JS_ASSERT(constraint->next == NULL);
     constraint->next = constraintList;
     constraintList = constraint;
 
     if (!callExisting)
         return;
@@ -272,21 +283,21 @@ TypeSet::print(JSContext *cx)
     if (typeFlags & TYPE_FLAG_OBJECT) {
         printf(" object[%u]", objectCount);
 
         if (objectCount >= 2) {
             unsigned objectCapacity = HashSetCapacity(objectCount);
             for (unsigned i = 0; i < objectCapacity; i++) {
                 TypeObject *object = objectSet[i];
                 if (object)
-                    printf(" %s", TypeIdString(cx, object->name));
+                    printf(" %s", TypeIdString(object->name));
             }
         } else if (objectCount == 1) {
             TypeObject *object = (TypeObject*) objectSet;
-            printf(" %s", TypeIdString(cx, object->name));
+            printf(" %s", TypeIdString(object->name));
         }
     }
 }
 
 /* Standard subset constraint, propagate all types from one set to another. */
 class TypeConstraintSubset : public TypeConstraint
 {
 public:
@@ -528,19 +539,21 @@ public:
         : TypeConstraint("generator"), target(target)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
 };
 
 /* Update types with the possible values bound by the for loop in code. */
 static inline void
-SetForTypes(JSContext *cx, analyze::Bytecode *code, TypeSet *types)
+SetForTypes(JSContext *cx, const analyze::Script::AnalyzeState &state,
+            analyze::Bytecode *code, TypeSet *types)
 {
-    if (code->pushedArray[0].group()->isForEach)
+    JS_ASSERT(code->stackDepth == state.stackDepth);
+    if (state.popped(0).isForEach)
         types->addType(cx, TYPE_UNKNOWN);
     else
         types->addType(cx, TYPE_STRING);
 
     code->popped(0)->add(cx, ArenaNew<TypeConstraintGenerator>(code->pool(), types));
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -594,18 +607,18 @@ GetPropertyObject(JSContext *cx, jstype 
  * here, whether via x.f, x[f], or global name accesses.
  */
 static inline void
 PropertyAccess(JSContext *cx, analyze::Bytecode *code, TypeObject *object,
                bool assign, TypeSet *target, jsid id)
 {
     JS_ASSERT_IF(!target, assign);
 
-    /* Reads from monitored objects are unknown, writes to monitored objects are ignored. */
-    if (object->monitored) {
+    /* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */
+    if (object->unknownProperties()) {
         if (!assign)
             target->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
     /* Monitor assigns on the 'prototype' property. */
     if (assign && id == id_prototype(cx)) {
         cx->compartment->types.monitorBytecode(cx, code);
@@ -618,17 +631,17 @@ PropertyAccess(JSContext *cx, analyze::B
             cx->compartment->types.monitorBytecode(cx, code);
         else
             target->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
     /* Mark arrays with possible non-integer properties as not dense. */
     if (assign && !JSID_IS_VOID(id))
-        cx->markTypeArrayNotPacked(object, true);
+        cx->markTypeArrayNotPacked(object, true, false);
 
     TypeSet *types = object->properties(cx).getVariable(cx, id);
 
     /* Capture the effects of a standard property access. */
     if (target) {
         if (assign)
             target->addSubset(cx, code->pool(), types);
         else
@@ -661,35 +674,34 @@ TypeConstraintProp::newType(JSContext *c
     if (object)
         PropertyAccess(cx, code, object, assign, target, id);
 }
 
 void
 TypeConstraintElem::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     switch (type) {
+      case TYPE_UNDEFINED:
       case TYPE_INT32:
       case TYPE_DOUBLE:
         /*
          * Integer index access, these are all covered by the JSID_VOID property.
-         * We are optimistically treat double accesses as getting an integer property,
-         * This needs to be checked at runtime.
+         * We are optimistically treat undefined accesses as not actually occurring,
+         * and double accesses as getting an integer property. These need to be checked
+         * at runtime.
          */
         if (assign)
             object->addSetProperty(cx, code, target, JSID_VOID);
         else
             object->addGetProperty(cx, code, target, JSID_VOID);
         break;
       default:
         /*
          * Access to a potentially arbitrary element.  Monitor assignments to unknown
-         * elements, and treat reads of unknown elements as unknown.  TODO: we need to
-         * identify hashmap uses either ahead of time or on the fly, as currently we
-         * will make separate entries in the variable set of the properties for every
-         * element added.
+         * elements, and treat reads of unknown elements as unknown.
          */
         if (assign)
             cx->compartment->types.monitorBytecode(cx, code);
         else
             target->addType(cx, TYPE_UNKNOWN);
     }
 };
 
@@ -948,16 +960,31 @@ TypeConstraintGenerator::newType(JSConte
 {
     if (type == TYPE_UNKNOWN)
         target->addType(cx, TYPE_UNKNOWN);
 
     if (type == (jstype) cx->getFixedTypeObject(TYPE_OBJECT_NEW_ITERATOR))
         target->addType(cx, TYPE_UNKNOWN);
 }
 
+/* Constraint marking incoming arrays as possibly packed. */
+class TypeConstraintPossiblyPacked : public TypeConstraint
+{
+public:
+    TypeConstraintPossiblyPacked() : TypeConstraint("possiblyPacked") {}
+
+    void newType(JSContext *cx, TypeSet *source, jstype type)
+    {
+        if (type != TYPE_UNKNOWN && TypeIsObject(type)) {
+            TypeObject *object = (TypeObject *) type;
+            object->possiblePackedArray = true;
+        }
+    }
+};
+
 /////////////////////////////////////////////////////////////////////
 // Freeze constraints
 /////////////////////////////////////////////////////////////////////
 
 /*
  * Constraint which triggers recompilation of a script if a possible new JSValueType
  * tag is realized for a type set.
  */
@@ -1030,16 +1057,28 @@ TypeSet::getKnownTypeTag(JSContext *cx, 
 
     return type;
 }
 
 /* Compute the meet of kind with the kind of object, per the ObjectKind lattice. */
 static inline ObjectKind
 CombineObjectKind(TypeObject *object, ObjectKind kind)
 {
+    /*
+     * Our initial guess is that all arrays are packed, but if the array is
+     * created through [], Array() or Array(N) and we don't see later code
+     * which looks to be filling it in starting at zero, consider it not packed.
+     * All requests for the kind of an object go through here, so there are
+     * no FreezeObjectKind constraints to update if we unset isPackedArray here.
+     */
+    if (object->isPackedArray && !object->possiblePackedArray) {
+        InferSpew(ISpewDynamic, "Possible unpacked array: %s", TypeIdString(object->name));
+        object->isPackedArray = false;
+    }
+
     ObjectKind nkind;
     if (object->isFunction)
         nkind = object->asFunction()->script ? OBJECT_SCRIPTED_FUNCTION : OBJECT_NATIVE_FUNCTION;
     else if (object->isPackedArray)
         nkind = OBJECT_PACKED_ARRAY;
     else if (object->isDenseArray)
         nkind = OBJECT_DENSE_ARRAY;
     else
@@ -1312,17 +1351,20 @@ JS_STATIC_ASSERT(JS_ARRAY_LENGTH(fixedTy
 void
 TypeCompartment::init()
 {
     PodZero(this);
 
     JS_InitArenaPool(&pool, "typeinfer", 512, 8, NULL);
 
     objectNameTable = new ObjectNameTable();
-    bool success = objectNameTable->init();
+#ifdef DEBUG
+    bool success =
+#endif
+        objectNameTable->init();
     JS_ASSERT(success);
 }
 
 TypeCompartment::~TypeCompartment()
 {
     /* fclose(out); */
     JS_FinishArenaPool(&pool);
 
@@ -1349,18 +1391,22 @@ TypeCompartment::makeFixedTypeObject(JSC
     bool isFunction = (which <= TYPE_OBJECT_FUNCTION_LAST);
 
     TypeObject *type = cx->getTypeObject(fixedTypeObjectNames[which], isArray, isFunction);
     fixedTypeObjects[which] = type;
 
     if (which <= TYPE_OBJECT_BASE_LAST) {
         if (which == TYPE_OBJECT_EMPTY_FUNCTION)
             cx->addTypePrototype(type, cx->getFixedTypeObject(TYPE_OBJECT_FUNCTION_PROTOTYPE));
+        else if (which == TYPE_OBJECT_OBJECT_PROTOTYPE)
+            type->hasObjectPropagation = true;
+        else if (which == TYPE_OBJECT_ARRAY_PROTOTYPE)
+            type->hasArrayPropagation = true;
     } else if (which <= TYPE_OBJECT_MONITOR_LAST) {
-        cx->monitorTypeObject(type);
+        cx->markTypeObjectUnknownProperties(type);
     } else if (which <= TYPE_OBJECT_ARRAY_LAST) {
         cx->addTypePrototype(type, cx->getFixedTypeObject(TYPE_OBJECT_ARRAY_PROTOTYPE));
     } else {
         cx->addTypePrototype(type, cx->getFixedTypeObject(TYPE_OBJECT_OBJECT_PROTOTYPE));
     }
 
     return type;
 }
@@ -1425,18 +1471,18 @@ TypeCompartment::addDynamicType(JSContex
 
 void
 TypeCompartment::addDynamicPush(JSContext *cx, analyze::Bytecode &code,
                                 unsigned index, jstype type)
 {
     js::types::TypeSet *types = code.pushed(index);
     JS_ASSERT(!types->hasType(type));
 
-    InferSpewType(ISpewDynamic, cx, type, "MonitorResult: #%u:%05u %u:",
-                  code.script->id, code.offset, index);
+    InferSpew(ISpewDynamic, "MonitorResult: #%u:%05u %u: %s",
+              code.script->id, code.offset, index, TypeString(type));
 
     interpreting = false;
     uint64_t startTime = currentTime();
 
     types->addType(cx, type);
 
     /*
      * For inc/dec ops, we need to go back and reanalyze the affected opcode
@@ -1446,17 +1492,17 @@ TypeCompartment::addDynamicPush(JSContex
      */
     JSOp op = JSOp(code.script->getScript()->code[code.offset]);
     const JSCodeSpec *cs = &js_CodeSpec[op];
     if (cs->format & (JOF_INC | JOF_DEC)) {
         JS_ASSERT(!code.hasIncDecOverflow);
         code.hasIncDecOverflow = true;
 
         /* Inc/dec ops do not use the temporary analysis state. */
-        analyze::Script::TypeState state;
+        analyze::Script::AnalyzeState state;
         code.script->analyzeTypes(cx, &code, state);
     }
 
     uint64_t endTime = currentTime();
     analysisTime += (endTime - startTime);
     interpreting = true;
 
     if (hasPendingRecompiles())
@@ -1496,25 +1542,38 @@ TypeCompartment::addPendingRecompile(JSC
     if (!pendingRecompiles)
         pendingRecompiles = new Vector<JSScript*>(ContextAllocPolicy(cx));
 
     for (unsigned i = 0; i < pendingRecompiles->length(); i++) {
         if (script == (*pendingRecompiles)[i])
             return;
     }
 
+    recompilations++;
     pendingRecompiles->append(script);
 }
 
 void
 TypeCompartment::dynamicAssign(JSContext *cx, JSObject *obj, jsid id, const Value &rval)
 {
     jstype rvtype = GetValueType(cx, rval);
     TypeObject *object = obj->getTypeObject();
 
+    /*
+     * Extra propagation for writes of the prototype property. Do this even if the
+     * object has unknown properties, because the 'new' object for the function may
+     * have known properties. :TODO: might be simpler for unknown function to imply
+     * unknown new object.
+     */
+    if (id == id_prototype(cx) && TypeIsObject(rvtype) && object->isFunction)
+        cx->addTypePrototype(object->asFunction()->getNewObject(cx), (TypeObject *) rvtype);
+
+    if (object->unknownProperties())
+        return;
+
     TypeSet *assignTypes;
 
     /*
      * :XXX: Does this need to be moved to a more general place? We aren't considering
      * call objects in, e.g. addTypeProperty, but call objects might not be able to
      * flow there as they do not escape to scripts.
      */
     if (obj->isCall() || obj->isBlock()) {
@@ -1522,32 +1581,41 @@ TypeCompartment::dynamicAssign(JSContext
         while (!obj->isCall())
             obj = obj->getParent();
         analyze::Script *script = obj->getCallObjCalleeFunction()->script()->analysis;
         JS_ASSERT(!script->isEval());
 
         assignTypes = script->localTypes.getVariable(cx, id);
     } else {
         id = MakeTypeId(id);
+
+        if (!JSID_IS_VOID(id) && id != id_prototype(cx) && id != id___proto__(cx)) {
+            /*
+             * Monitor any object which has had dynamic assignments to string properties,
+             * to avoid making large numbers of type properties for hashmap-style objects.
+             * :FIXME: this is too aggressive for things like prototype library initialization.
+             */
+            cx->markTypeObjectUnknownProperties(object);
+            if (hasPendingRecompiles())
+                processPendingRecompiles(cx);
+            return;
+        }
+
         assignTypes = object->properties(cx).getVariable(cx, id);
 
-        /* Extra propagation for writes of the prototype property. */
-        if (id == id_prototype(cx) && TypeIsObject(rvtype) && object->isFunction)
-            cx->addTypePrototype(object->asFunction()->getNewObject(cx), (TypeObject *) rvtype);
-
         /* Extra propagation for writes of the __proto__ property. */
         if (id == id___proto__(cx) && TypeIsObject(rvtype))
             cx->addTypePrototype(object, (TypeObject *) rvtype);
     }
 
     if (assignTypes->hasType(rvtype))
         return;
 
-    InferSpewType(ISpewDynamic, cx, rvtype, "MonitorAssign: %s %s:",
-                  TypeIdString(cx, object->name), TypeIdString(cx, id));
+    InferSpew(ISpewDynamic, "MonitorAssign: %s %s: %s",
+              TypeIdString(object->name), TypeIdString(id), TypeString(rvtype));
     addDynamicType(cx, assignTypes, rvtype);
 }
 
 void
 TypeCompartment::monitorBytecode(JSContext *cx, analyze::Bytecode *code)
 {
     if (code->monitorNeeded)
         return;
@@ -1592,76 +1660,63 @@ TypeCompartment::monitorBytecode(JSConte
       case JSOP_SETCALL:
       case JSOP_EVAL:
       case JSOP_FUNCALL:
       case JSOP_FUNAPPLY:
       case JSOP_NEW:
         code->setFixed(cx, 0, TYPE_UNKNOWN);
         break;
       default:
-        warnings = true;
-        InferSpew(ISpewDynamic, "warning: Monitoring unknown bytecode: %s\n", js_CodeNameTwo[op]);
-        break;
+        TypeFailure(cx, "Monitoring unknown bytecode: %s", js_CodeNameTwo[op]);
     }
 
-    InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u\n", code->script->id, code->offset);
+    InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u", code->script->id, code->offset);
 
     code->monitorNeeded = true;
 
     JSScript *script = code->script->getScript();
     if (script->hasJITCode())
         cx->compartment->types.addPendingRecompile(cx, script);
 }
 
 void
 TypeCompartment::finish(JSContext *cx, JSCompartment *compartment)
 {
     JS_ASSERT(this == &compartment->types);
 
-    if (ignoreWarnings)
-        warnings = false;
-
-    // for debugging regressions.
-    if (warnings) {
-        printf("Type warnings generated, bailing out...\n");
-        fflush(stdout);
-        *((int*)NULL) = 0;  /* Type warnings */
-    }
-
-#ifdef DEBUG
-    if (!InferSpewActive(ISpewResult))
+    if (!InferSpewActive(ISpewResult) || JS_CLIST_IS_EMPTY(&compartment->scripts))
         return;
 
     for (JSScript *script = (JSScript *)compartment->scripts.next;
          &script->links != &compartment->scripts;
          script = (JSScript *)script->links.next) {
         if (script->analysis)
-            script->analysis->print(cx);
+            script->analysis->finish(cx);
     }
 
+#ifdef DEBUG
     TypeObject *object = objects;
     while (object) {
         object->print(cx);
         object = object->next;
     }
+#endif
 
     double millis = analysisTime / 1000.0;
 
-    printf("\nWarnings: %s\n", warnings ? "yes" : "no");
-
     printf("Counts: ");
     for (unsigned count = 0; count < TYPE_COUNT_LIMIT; count++) {
         if (count)
             printf("/");
         printf("%u", typeCounts[count]);
     }
     printf(" (%u over)\n", typeCountOver);
 
+    printf("Recompilations: %u\n", recompilations);
     printf("Time: %.2f ms\n", millis);
-#endif
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeStack
 /////////////////////////////////////////////////////////////////////
 
 void
 TypeStack::merge(JSContext *cx, TypeStack *one, TypeStack *two)
@@ -1677,49 +1732,45 @@ TypeStack::merge(JSContext *cx, TypeStac
     /* Check if the classes are already the same. */
     if (one == two)
         return;
 
     /* There should not be any types or constraints added to the stack types. */
     JS_ASSERT(one->types.typeFlags == 0 && one->types.constraintList == NULL);
     JS_ASSERT(two->types.typeFlags == 0 && two->types.constraintList == NULL);
 
-    /* The nodes in a class must have consistent depth. */
-    JS_ASSERT(one->stackDepth == two->stackDepth);
-
     /* Merge any inner portions of the stack for the two nodes. */
     if (one->innerStack)
         merge(cx, one->innerStack, two->innerStack);
 
-    /* Check that additional information is consistent between the nodes. */
-    JS_ASSERT(one->boundWith == two->boundWith);
-    JS_ASSERT(one->isForEach == two->isForEach);
-    JS_ASSERT(one->ignoreTypeTag == two->ignoreTypeTag);
-    JS_ASSERT(one->letVariable == two->letVariable);
-    JS_ASSERT(one->scopeVars == two->scopeVars);
-
-    InferSpew(ISpewOps, "merge: T%u T%u\n", one->types.id(), two->types.id());
+    InferSpew(ISpewOps, "merge: T%u T%u", one->types.id(), two->types.id());
 
     /* one has now been merged into two, do the actual join. */
     PodZero(one);
     one->mergedGroup = two;
-    two->hasMerged = true;
 }
 
 /////////////////////////////////////////////////////////////////////
 // VariableSet
 /////////////////////////////////////////////////////////////////////
 
 bool
 VariableSet::addPropagate(JSContext *cx, VariableSet *target,
                           bool excludePrototype)
 {
     if (!HashSetInsert(cx, propagateSet, propagateCount, target))
         return false;
 
+    if (unknown) {
+        /* Just mark the target as unknown. */
+        if (!target->unknown)
+            target->markUnknown(cx);
+        return true;
+    }
+
     /* Push all existing variables into the target, except (optionally) 'prototype'. */
     Variable *var = variables;
     while (var) {
         bool skip = (excludePrototype && var->id == id_prototype(cx));
 
         /*
          * Also exclude certain properties propagated into the Function prototype
          * from the Object prototype.
@@ -1737,48 +1788,85 @@ VariableSet::addPropagate(JSContext *cx,
 
         var = var->next;
     }
 
     return true;
 }
 
 void
+VariableSet::markUnknown(JSContext *cx)
+{
+    JS_ASSERT(!unknown);
+    unknown = true;
+
+    /*
+     * Existing constraints may have already been added to this set, which we need
+     * to do the right thing for.  We can't ensure that we will mark all unknown
+     * objects before they have been accessed, as the __proto__ of a known object
+     * could be dynamically set to an unknown object, and we can decide to ignore
+     * properties of an object during analysis (i.e. hashmaps). Adding unknown for
+     * any properties accessed already accounts for possible values read from them.
+     */
+
+    js::types::Variable *var = variables;
+    while (var) {
+        var->types.addType(cx, js::types::TYPE_UNKNOWN);
+        var = var->next;
+    }
+
+    /* Mark existing sets being propagated into as unknown. */
+
+    if (propagateCount >= 2) {
+        unsigned capacity = HashSetCapacity(propagateCount);
+        for (unsigned i = 0; i < capacity; i++) {
+            VariableSet *target = propagateSet[i];
+            if (target && !target->unknown)
+                target->markUnknown(cx);
+        }
+    } else if (propagateCount == 1) {
+        VariableSet *target = (VariableSet *) propagateSet;
+        if (!target->unknown)
+            target->markUnknown(cx);
+    }
+}
+
+void
 VariableSet::print(JSContext *cx)
 {
     if (variables == NULL) {
         printf(" {}\n");
         return;
     }
 
     printf(" {");
 
     Variable *var = variables;
     while (var) {
-        printf("\n    %s:", TypeIdString(cx, var->id));
+        printf("\n    %s:", TypeIdString(var->id));
         var->types.print(cx);
         var = var->next;
     }
 
     printf("\n}\n");
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeObject
 /////////////////////////////////////////////////////////////////////
 
 TypeObject::TypeObject(JSContext *cx, JSArenaPool *pool, jsid name, bool isArray)
-    : name(name), isFunction(false), monitored(false),
+    : name(name), isFunction(false),
       propertySet(pool), propertiesFilled(false), next(NULL),
       hasObjectPropagation(false), hasArrayPropagation(false), isInitObject(false),
       isDenseArray(isArray), isPackedArray(isArray)
 {
 #ifdef DEBUG
     propertySet.name_ = name;
-    InferSpew(ISpewOps, "newObject: %s\n", TypeIdString(cx, name));
+    InferSpew(ISpewOps, "newObject: %s", TypeIdString(name));
 #endif
 }
 
 bool
 TypeObject::addPropagate(JSContext *cx, TypeObject *target,
                          bool excludePrototype)
 {
     bool added = properties(cx).addPropagate(cx, &target->properties(cx), excludePrototype);
@@ -1833,17 +1921,17 @@ TypeFunction::fillProperties(JSContext *
     /* The prototype inherits properties from Object.prototype. */
     TypeObject *objectProto = cx->getFixedTypeObject(TYPE_OBJECT_OBJECT_PROTOTYPE);
     objectProto->addPropagate(cx, prototypeObject);
 }
 
 void
 TypeObject::print(JSContext *cx)
 {
-    printf("%s", TypeIdString(cx, name));
+    printf("%s", TypeIdString(name));
 
     if (isFunction && !propertiesFilled)
         printf("\n");
     else
         properties(cx).print(cx);
     printf("\n");
 }
 
@@ -1852,17 +1940,17 @@ TypeObject::print(JSContext *cx)
 /////////////////////////////////////////////////////////////////////
 
 TypeFunction::TypeFunction(JSContext *cx, JSArenaPool *pool, jsid name)
     : TypeObject(cx, pool, name, false), handler(NULL), script(NULL),
       prototypeObject(NULL), newObject(NULL), returnTypes(pool),
       isBuiltin(false), isGeneric(false)
 {
     isFunction = true;
-    InferSpew(ISpewOps, "newFunction: %s return T%u\n", TypeIdString(cx, name), returnTypes.id());
+    InferSpew(ISpewOps, "newFunction: %s return T%u", TypeIdString(name), returnTypes.id());
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeScript
 /////////////////////////////////////////////////////////////////////
 
 } } /* namespace js::types */
 
@@ -1930,55 +2018,48 @@ JSScript::typeCheckBytecode(JSContext *c
         return;
 
     js::analyze::Bytecode &code = analysis->getCode(pc);
     JS_ASSERT(code.analyzed);
 
     int useCount = js::analyze::GetUseCount(this, code.offset);
     JSOp op = (JSOp) *pc;
 
-    if (!useCount || code.missingTypes)
+    if (!useCount)
         return;
 
     js::types::TypeStack *stack = code.inStack->group();
     for (int i = 0; i < useCount; i++) {
         const js::Value &val = sp[-1 - i];
         js::types::jstype type = js::types::GetValueType(cx, val);
 
         if (js::types::TypeIsObject(type) && !val.isMagic(JS_ARRAY_HOLE)) {
             /* Make sure information about the array status of this object is right. */
             JS_ASSERT(val.isObject());
             js::types::TypeObject *object = (js::types::TypeObject *) type;
 
             JS_ASSERT_IF(object->isPackedArray, object->isDenseArray);
             if (object->isDenseArray) {
                 if (!val.toObject().isDenseArray() ||
                     (object->isPackedArray && !val.toObject().isPackedDenseArray())) {
-                    js::types::InferSpew(js::types::ISpewDynamic,
-                        "warning: Object not %s array at #%u:%05u popped %u: %s\n",
+                    js::types::TypeFailure(cx, "Object not %s array at #%u:%05u popped %u: %s",
                         object->isPackedArray ? "packed" : "dense",
                         analysis->id, code.offset, i,
-                        js::types::TypeIdString(cx, object->name));
-                    cx->compartment->types.warnings = true;
-                    object->isDenseArray = object->isPackedArray = false;
+                        js::types::TypeIdString(object->name));
                 }
             }
         }
 
         bool matches = IgnorePopped(op, i) || stack->types.hasType(type);
 
         stack = stack->innerStack ? stack->innerStack->group() : NULL;
 
         if (!matches) {
-            js::types::InferSpewType(js::types::ISpewDynamic, cx, type,
-                                     "warning: Missing type at #%u:%05u popped %u: ",
-                                     analysis->id, code.offset, i);
-
-            cx->compartment->types.warnings = true;
-            code.missingTypes = true;
+            js::types::TypeFailure(cx, "Missing type at #%u:%05u popped %u: %s",
+                                   analysis->id, code.offset, i, js::types::TypeString(type));
             return;
         }
     }
 }
 
 namespace js {
 namespace analyze {
 
@@ -2149,20 +2230,27 @@ GetAtomId(JSContext *cx, Script *script,
 static inline jsid
 GetGlobalId(JSContext *cx, Script *script, const jsbytecode *pc)
 {
     unsigned index = GET_SLOTNO(pc);
     return MakeTypeId(ATOM_TO_JSID(script->getScript()->getGlobalAtom(index)));
 }
 
 static inline JSObject *
-GetScriptObject(JSContext *cx, Script *script, const jsbytecode *pc, unsigned offset)
+GetScriptObject(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
 {
-    unsigned index = js_GetIndexFromBytecode(cx, script->getScript(), (jsbytecode*) pc, offset);
-    return script->getScript()->getObject(index);
+    unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
+    return script->getObject(index);
+}
+
+static inline const Value &
+GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc)
+{
+    unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, 0);
+    return script->getConst(index);
 }
 
 /* Get the script and id of a variable referred to by an UPVAR opcode. */
 static inline Script *
 GetUpvarVariable(JSContext *cx, Bytecode *code, unsigned index, jsid *id)
 {
     JSUpvarArray *uva = code->script->getScript()->upvars();
 
@@ -2178,23 +2266,23 @@ GetUpvarVariable(JSContext *cx, Bytecode
 
     Script *newScript = newCode->script;
 
     /*
      * Get the name of the variable being referenced.  It is either an argument,
      * a local or the function itself.
      */
     if (!newScript->fun)
-        *id = newScript->getLocalId(newScript->getScript()->nfixed + slot, newCode->inStack);
+        *id = newScript->getLocalId(newScript->getScript()->nfixed + slot, newCode);
     else if (slot < newScript->argCount())
         *id = newScript->getArgumentId(slot);
     else if (slot == UpvarCookie::CALLEE_SLOT)
         *id = ATOM_TO_JSID(newScript->fun->atom);
     else
-        *id = newScript->getLocalId(slot - newScript->argCount(), newCode->inStack);
+        *id = newScript->getLocalId(slot - newScript->argCount(), newCode);
 
     JS_ASSERT(!JSID_IS_VOID(*id));
     return newScript->evalParent();
 }
 
 /* Constraint which preserves primitives and converts objects to strings. */
 class TypeConstraintToString : public TypeConstraint
 {
@@ -2238,25 +2326,42 @@ CheckNextTest(JSContext *cx, Bytecode *c
 /* Propagate the specified types into the Nth value pushed by an instruction. */
 static inline void
 MergePushed(JSContext *cx, JSArenaPool &pool, Bytecode *code, unsigned num, TypeSet *types)
 {
     types->addSubset(cx, pool, code->pushed(num));
 }
 
 void
-Script::analyzeTypes(JSContext *cx, Bytecode *code, TypeState &state)
+Script::analyzeTypes(JSContext *cx, Bytecode *code, AnalyzeState &state)
 {
     unsigned offset = code->offset;
 
     JS_ASSERT(code->analyzed);
     jsbytecode *pc = script->code + offset;
     JSOp op = (JSOp)*pc;
 
-    InferSpew(ISpewOps, "analyze: #%u:%05u\n", id, offset);
+    InferSpew(ISpewOps, "analyze: #%u:%05u", id, offset);
+
+    if (code->stackDepth > state.stackDepth && state.stack) {
+#ifdef DEBUG
+        /*
+         * Check that we aren't destroying any useful information. This should only
+         * occur around exception handling bytecode.
+         */
+        for (unsigned i = state.stackDepth; i < code->stackDepth; i++) {
+            JS_ASSERT(!state.stack[i].isForEach);
+            JS_ASSERT(!state.stack[i].scopeVars);
+            JS_ASSERT(!state.stack[i].hasDouble);
+        }
+#endif
+        unsigned ndefs = code->stackDepth - state.stackDepth;
+        memset(&state.stack[state.stackDepth], 0, ndefs * sizeof(AnalyzeStateStack));
+    }
+    state.stackDepth = code->stackDepth;
 
     /* Add type constraints for the various opcodes. */
     switch (op) {
 
         /* Nop bytecodes. */
       case JSOP_POP:
       case JSOP_NOP:
       case JSOP_TRACE:
@@ -2369,28 +2474,20 @@ Script::analyzeTypes(JSContext *cx, Byte
       case JSOP_OR:
       case JSOP_ORX:
       case JSOP_AND:
       case JSOP_ANDX:
         /* OR/AND push whichever operand determined the result. */
         code->popped(0)->addSubset(cx, pool, code->pushed(0));
         break;
 
-      case JSOP_DUP: {
+      case JSOP_DUP:
         MergePushed(cx, pool, code, 0, code->popped(0));
         MergePushed(cx, pool, code, 1, code->popped(0));
-
-        /* Propagate any scope information on the stack value.  blech. */
-        TypeStack *stack = code->inStack->group();
-        if (stack->scopeVars) {
-            code->pushedArray[0].group()->scopeVars = stack->scopeVars;
-            code->pushedArray[1].group()->scopeVars = stack->scopeVars;
-        }
         break;
-      }
 
       case JSOP_DUP2:
         MergePushed(cx, pool, code, 0, code->popped(1));
         MergePushed(cx, pool, code, 1, code->popped(0));
         MergePushed(cx, pool, code, 2, code->popped(1));
         MergePushed(cx, pool, code, 3, code->popped(0));
         break;
 
@@ -2419,22 +2516,25 @@ Script::analyzeTypes(JSContext *cx, Byte
             id = GetAtomId(cx, this, pc, 0);
             vars = SearchScope(cx, this, code->inStack, id);
             break;
         }
 
         if (vars == GetGlobalProperties(cx)) {
             /*
              * This might be a lazily loaded property of the global object.
-             * Resolve it now.
+             * Resolve it now. Subtract this from the total analysis time.
              */
+            uint64_t startTime = cx->compartment->types.currentTime();
             JSObject *obj;
             JSProperty *prop;
             js_LookupPropertyWithFlags(cx, cx->globalObject, id,
                                        JSRESOLVE_QUALIFIED, &obj, &prop);
+            uint64_t endTime = cx->compartment->types.currentTime();
+            cx->compartment->types.analysisTime -= (endTime - startTime);
 
             /* Handle as a property access. */
             PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
         } else if (vars) {
             /* Definitely a local variable. */
             TypeSet *types = vars->getVariable(cx, id);
             types->addSubset(cx, *vars->pool, code->pushed(0));
         } else {
@@ -2443,57 +2543,48 @@ Script::analyzeTypes(JSContext *cx, Byte
         }
 
         if (op == JSOP_CALLGLOBAL || op == JSOP_CALLGNAME || op == JSOP_CALLNAME)
             code->setFixed(cx, 1, vars ? TYPE_UNDEFINED : TYPE_UNKNOWN);
         CheckNextTest(cx, code, pc);
         break;
       }
 
-      case JSOP_BINDGNAME: {
-        jsid id = GetAtomId(cx, this, pc, 0);
-        TypeStack *stack = code->pushedArray[0].group();
-        stack->scopeVars = GetGlobalProperties(cx);
+      case JSOP_BINDGNAME:
+      case JSOP_BINDNAME:
+        /* Handled below. */
         break;
-      }
-
-      case JSOP_BINDNAME: {
-        jsid id = GetAtomId(cx, this, pc, 0);
-        TypeStack *stack = code->pushedArray[0].group();
-        stack->scopeVars = SearchScope(cx, this, code->inStack, id);
-        break;
-      }
 
       case JSOP_SETGNAME:
       case JSOP_SETNAME: {
         jsid id = GetAtomId(cx, this, pc, 0);
 
-        TypeStack *stack = code->inStack->group()->innerStack->group();
-        if (stack->scopeVars && stack->scopeVars == GetGlobalProperties(cx)) {
+        const AnalyzeStateStack &stack = state.popped(1);
+        if (stack.scopeVars && stack.scopeVars == GetGlobalProperties(cx)) {
             PropertyAccess(cx, code, cx->getGlobalTypeObject(), true, code->popped(0), id);
-        } else if (stack->scopeVars) {
-            TypeSet *types = stack->scopeVars->getVariable(cx, id);
+        } else if (stack.scopeVars) {
+            TypeSet *types = stack.scopeVars->getVariable(cx, id);
             code->popped(0)->addSubset(cx, pool, types);
         } else {
             cx->compartment->types.monitorBytecode(cx, code);
         }
 
         MergePushed(cx, pool, code, 0, code->popped(0));
         break;
       }
 
       case JSOP_GETXPROP: {
         jsid id = GetAtomId(cx, this, pc, 0);
 
-        VariableSet *vars = code->inStack->group()->scopeVars;
-        if (vars && vars == GetGlobalProperties(cx)) {
+        const AnalyzeStateStack &stack = state.popped(0);
+        if (stack.scopeVars && stack.scopeVars == GetGlobalProperties(cx)) {
             PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
-        } else if (vars) {
-            TypeSet *types = vars->getVariable(cx, id);
-            types->addSubset(cx, *vars->pool, code->pushed(0));
+        } else if (stack.scopeVars) {
+            TypeSet *types = stack.scopeVars->getVariable(cx, id);
+            types->addSubset(cx, *stack.scopeVars->pool, code->pushed(0));
         } else {
             code->setFixed(cx, 0, TYPE_UNKNOWN);
         }
 
         break;
       }
 
       case JSOP_INCNAME:
@@ -2612,35 +2703,39 @@ Script::analyzeTypes(JSContext *cx, Byte
         code->setFixed(cx, 0, TYPE_UNKNOWN);
         break;
 
       case JSOP_GETLOCAL:
       case JSOP_SETLOCAL:
       case JSOP_SETLOCALPOP:
       case JSOP_CALLLOCAL: {
         uint32 local = GET_SLOTNO(pc);
-        jsid id = getLocalId(local, code->inStack);
+        jsid id = getLocalId(local, code);
 
         TypeSet *types;
         JSArenaPool *pool;
         if (!JSID_IS_VOID(id)) {
             types = evalParent()->localTypes.getVariable(cx, id);
             pool = &evalParent()->pool;
         } else {
-            types = getStackTypes(GET_SLOTNO(pc), code->inStack);
+            types = getStackTypes(GET_SLOTNO(pc), code);
             pool = &this->pool;
         }
 
         if (op != JSOP_SETLOCALPOP) {
             MergePushed(cx, *pool, code, 0, types);
             if (op == JSOP_CALLLOCAL)
                 code->setFixed(cx, 1, TYPE_UNDEFINED);
         }
 
         if (op == JSOP_SETLOCAL || op == JSOP_SETLOCALPOP) {
+            state.clearLocal(local);
+            if (state.popped(0).isConstant)
+                state.addConstLocal(local, state.popped(0).isZero);
+
             code->popped(0)->addSubset(cx, this->pool, types);
         } else {
             /*
              * Add void type if the variable might be undefined.  TODO: monitor for
              * undefined read instead?  localDefined returns false for
              * variables which could have a legitimate use-before-def, for let
              * variables and variables exceeding the LOCAL_LIMIT threshold.
              */
@@ -2651,19 +2746,21 @@ Script::analyzeTypes(JSContext *cx, Byte
         break;
       }
 
       case JSOP_INCLOCAL:
       case JSOP_DECLOCAL:
       case JSOP_LOCALINC:
       case JSOP_LOCALDEC: {
         uint32 local = GET_SLOTNO(pc);
-        jsid id = getLocalId(local, code->inStack);
+        jsid id = getLocalId(local, code);
+
+        state.clearLocal(local);
+
         TypeSet *types = evalParent()->localTypes.getVariable(cx, id);
-
         types->addArith(cx, evalParent()->pool, code, types);
         MergePushed(cx, evalParent()->pool, code, 0, types);
 
         if (code->hasIncDecOverflow)
             types->addType(cx, TYPE_DOUBLE);
         break;
       }
 
@@ -2737,17 +2834,17 @@ Script::analyzeTypes(JSContext *cx, Byte
         jsid propid = GetAtomId(cx, this, pc, SLOTNO_LEN);
         types->addGetProperty(cx, code, code->pushed(0), propid);
 
         CheckNextTest(cx, code, pc);
         break;
       }
 
       case JSOP_GETLOCALPROP: {
-        jsid id = getLocalId(GET_SLOTNO(pc), code->inStack);
+        jsid id = getLocalId(GET_SLOTNO(pc), code);
         TypeSet *types = evalParent()->localTypes.getVariable(cx, id);
 
         jsid propid = GetAtomId(cx, this, pc, SLOTNO_LEN);
         types->addGetProperty(cx, code, code->pushed(0), propid);
 
         CheckNextTest(cx, code, pc);
         break;
       }
@@ -2758,16 +2855,28 @@ Script::analyzeTypes(JSContext *cx, Byte
 
         CheckNextTest(cx, code, pc);
 
         if (op == JSOP_CALLELEM)
             code->popped(1)->addFilterPrimitives(cx, pool, code->pushed(1), true);
         break;
 
       case JSOP_SETELEM:
+        if (state.popped(1).isZero) {
+            /*
+             * Initializing the array with what looks like it could be zero.
+             * This is sensitive to the order in which bytecodes are emitted
+             * for common loop forms: '(for i = 0;; i++) a[i] = ...' and
+             * 'i = 0; while () { a[i] = ...; i++ }. In the bytecode the increment
+             * will appear after the initialization, and we are looking for arrays
+             * initialized between the two statements.
+             */
+            code->popped(2)->add(cx, ArenaNew<TypeConstraintPossiblyPacked>(pool));
+        }
+
         code->popped(1)->addSetElem(cx, code, code->popped(2), code->popped(0));
         MergePushed(cx, pool, code, 0, code->popped(0));
         break;
 
       case JSOP_INCELEM:
       case JSOP_DECELEM:
       case JSOP_ELEMINC:
       case JSOP_ELEMDEC:
@@ -2798,19 +2907,23 @@ Script::analyzeTypes(JSContext *cx, Byte
         code->popped(0)->addArith(cx, pool, code, code->pushed(0), code->popped(1));
         code->popped(1)->addArith(cx, pool, code, code->pushed(0), code->popped(0));
         break;
 
       case JSOP_SUB:
       case JSOP_MUL:
       case JSOP_MOD:
       case JSOP_DIV:
-        /* :TODO: Add heuristics for guessing when dividing two ints produces a double. */
         code->popped(0)->addArith(cx, pool, code, code->pushed(0));
         code->popped(1)->addArith(cx, pool, code, code->pushed(0));
+        if (op == JSOP_DIV) {
+            /* Guess that divisions other than x/n produce doubles. */
+            if (!state.popped(0).isConstant)
+                code->setFixed(cx, 0, TYPE_DOUBLE);
+        }
         break;
 
       case JSOP_NEG:
       case JSOP_POS:
         code->popped(0)->addArith(cx, pool, code, code->pushed(0));
         break;
 
       case JSOP_LAMBDA:
@@ -2819,29 +2932,29 @@ Script::analyzeTypes(JSContext *cx, Byte
       case JSOP_DEFFUN_FC:
       case JSOP_DEFLOCALFUN:
       case JSOP_DEFLOCALFUN_FC: {
         unsigned funOffset = 0;
         if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC)
             funOffset = SLOTNO_LEN;
 
         unsigned off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
-        JSObject *obj = GetScriptObject(cx, this, pc, off);
+        JSObject *obj = GetScriptObject(cx, script, pc, off);
         TypeFunction *function = obj->getTypeObject()->asFunction();
 
         /* Remember where this script was defined. */
         function->script->analysis->parent = script;
         function->script->analysis->parentpc = pc;
 
         TypeSet *res = NULL;
 
         if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) {
             res = code->pushed(0);
         } else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) {
-            jsid id = getLocalId(GET_SLOTNO(pc), code->inStack);
+            jsid id = getLocalId(GET_SLOTNO(pc), code);
             res = evalParent()->localTypes.getVariable(cx, id);
         } else {
             /* Watch for functions defined at the top level of an eval, see DEFVAR below. */
             JSAtom *atom = obj->getFunctionPrivate()->atom;
             JS_ASSERT(atom);
             jsid id = ATOM_TO_JSID(atom);
             if (parent) {
                 if (this->fun) {
@@ -2893,31 +3006,46 @@ Script::analyzeTypes(JSContext *cx, Byte
         break;
       }
 
       case JSOP_NEWINIT:
       case JSOP_NEWARRAY:
       case JSOP_NEWOBJECT: {
         TypeObject *object = code->initObject;
         JS_ASSERT(object);
+
+        if (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array)) {
+            jsbytecode *next = pc + GetBytecodeLength(pc);
+            if (JSOp(*next) != JSOP_ENDINIT)
+                object->possiblePackedArray = true;
+        }
+
         code->pushed(0)->addType(cx, (jstype) object);
         break;
       }
 
       case JSOP_ENDINIT:
         break;
 
       case JSOP_INITELEM: {
         TypeObject *object = code->initObject;
         JS_ASSERT(object);
 
         code->pushed(0)->addType(cx, (jstype) object);
 
-        /* TODO: broken for float indexes? */
-        TypeSet *types = object->indexTypes(cx);
+        TypeSet *types;
+        if (state.popped(1).hasDouble) {
+            Value val = DoubleValue(state.popped(1).doubleValue);
+            jsid id;
+            if (!js_InternNonIntElementId(cx, NULL, val, &id))
+                JS_NOT_REACHED("Bad");
+            types = object->properties(cx).getVariable(cx, id);
+        } else {
+            types = object->indexTypes(cx);
+        }
 
         if (state.hasGetSet)
             types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_GETSET));
         else if (state.hasHole)
             cx->markTypeArrayNotPacked(object, false);
         else
             code->popped(0)->addSubset(cx, pool, types);
         state.hasGetSet = false;
@@ -2960,91 +3088,84 @@ Script::analyzeTypes(JSContext *cx, Byte
          * Scope lookups can occur on the value being pushed here.  We don't track
          * the value or its properties, and just monitor all name opcodes contained
          * by the with.
          */
         code->pushedArray[0].group()->boundWith = true;
         break;
 
       case JSOP_ENTERBLOCK: {
-        JSObject *obj = GetScriptObject(cx, this, pc, 0);
+        JSObject *obj = GetScriptObject(cx, script, pc, 0);
         unsigned defCount = GetDefCount(script, offset);
 
         const Shape *shape = obj->lastProperty();
         for (unsigned i = 0; i < defCount; i++) {
             code->pushedArray[i].group()->letVariable = shape->id;
             shape = shape->previous();
         }
         break;
       }
 
-      case JSOP_ITER: {
-        uintN flags = pc[1];
-        TypeStack *stack = code->pushedArray[0].group();
-        if (flags & JSITER_FOREACH)
-            stack->isForEach = true;
-        stack->ignoreTypeTag = true;
-
+      case JSOP_ITER:
         /*
          * The actual pushed value is an iterator object, which we don't care about.
          * Propagate the target of the iteration itself so that we'll be able to detect
          * when an object of Iterator class flows to the JSOP_FOR* opcode, which could
          * be a generator that produces arbitrary values with 'for in' syntax.
          */
         MergePushed(cx, pool, code, 0, code->popped(0));
-
+        code->pushedArray[0].group()->ignoreTypeTag = true;
         break;
-      }
 
       case JSOP_MOREITER:
         code->pushedArray[0].group()->ignoreTypeTag = true;
         MergePushed(cx, pool, code, 0, code->popped(0));
         code->setFixed(cx, 1, TYPE_BOOLEAN);
         break;
 
       case JSOP_FORNAME: {
         jsid id = GetAtomId(cx, this, pc, 0);
         VariableSet *vars = SearchScope(cx, this, code->inStack, id);
 
         if (vars)
-            SetForTypes(cx, code, vars->getVariable(cx, id));
+            SetForTypes(cx, state, code, vars->getVariable(cx, id));
         else
             cx->compartment->types.monitorBytecode(cx, code);
         break;
       }
 
       case JSOP_FORGLOBAL: {
         jsid id = GetGlobalId(cx, this, pc);
-        SetForTypes(cx, code, GetGlobalProperties(cx)->getVariable(cx, id));
+        SetForTypes(cx, state, code, GetGlobalProperties(cx)->getVariable(cx, id));
         break;
       }
 
       case JSOP_FORLOCAL: {
-        jsid id = getLocalId(GET_SLOTNO(pc), code->inStack);
+        jsid id = getLocalId(GET_SLOTNO(pc), code);
         JS_ASSERT(!JSID_IS_VOID(id));
 
-        SetForTypes(cx, code, evalParent()->localTypes.getVariable(cx, id));
+        SetForTypes(cx, state, code, evalParent()->localTypes.getVariable(cx, id));
         break;
       }
 
       case JSOP_FORARG: {
         jsid id = getArgumentId(GET_ARGNO(pc));
         JS_ASSERT(!JSID_IS_VOID(id));
 
-        SetForTypes(cx, code, localTypes.getVariable(cx, id));
+        SetForTypes(cx, state, code, localTypes.getVariable(cx, id));
         break;
       }
 
       case JSOP_FORPROP:
       case JSOP_ENUMELEM:
         cx->compartment->types.monitorBytecode(cx, code);
         break;
 
       case JSOP_ARRAYPUSH: {
-        TypeSet *types = getStackTypes(GET_SLOTNO(pc), code->inStack);
+        TypeSet *types = getStackTypes(GET_SLOTNO(pc), code);
         types->addSetProperty(cx, code, code->popped(0), JSID_VOID);
         break;
       }
 
       case JSOP_THROW:
         /* There will be a monitor on the bytecode catching the exception. */
         break;
 
@@ -3138,17 +3259,16 @@ Script::analyzeTypes(JSContext *cx, Byte
         break;
 
       case JSOP_FILTER:
         /* Note: the second value pushed by filter is a hole, and not modelled. */
         MergePushed(cx, pool, code, 0, code->popped(0));
         code->pushedArray[0].group()->boundWith = true;
 
         /* Name binding inside filters is currently broken :FIXME: bug 605200. */
-        cx->compartment->types.ignoreWarnings = true;
         break;
 
       case JSOP_ENDFILTER:
         MergePushed(cx, pool, code, 0, code->popped(1));
         break;
 
       case JSOP_DEFSHARP: {
         /*
@@ -3182,25 +3302,95 @@ Script::analyzeTypes(JSContext *cx, Byte
         break;
       }
 
       case JSOP_CALLEE:
         code->setFixed(cx, 0, (jstype) function());
         break;
 
       default:
-        cx->compartment->types.warnings = true;
-        InferSpew(ISpewDynamic, "warning: Unknown bytecode: %s\n", js_CodeNameTwo[op]);
+        TypeFailure(cx, "Unknown bytecode: %s", js_CodeNameTwo[op]);
+    }
+
+    /* Compute temporary analysis state after the bytecode. */
+
+    if (!state.stack)
+        return;
+
+    if (op == JSOP_DUP) {
+        state.stack[code->stackDepth] = state.stack[code->stackDepth - 1];
+        state.stackDepth = code->stackDepth + 1;
+    } else if (op == JSOP_DUP2) {
+        state.stack[code->stackDepth]     = state.stack[code->stackDepth - 2];
+        state.stack[code->stackDepth + 1] = state.stack[code->stackDepth - 1];
+        state.stackDepth = code->stackDepth + 2;
+    } else {
+        unsigned nuses = GetUseCount(script, offset);
+        unsigned ndefs = GetDefCount(script, offset);
+        memset(&state.stack[code->stackDepth - nuses], 0, ndefs * sizeof(AnalyzeStateStack));
+        state.stackDepth = code->stackDepth - nuses + ndefs;
+    }
+
+    switch (op) {
+      case JSOP_BINDGNAME: {
+        AnalyzeStateStack &stack = state.popped(0);
+        stack.scopeVars = GetGlobalProperties(cx);
+        break;
+      }
+
+      case JSOP_BINDNAME: {
+        jsid id = GetAtomId(cx, this, pc, 0);
+        AnalyzeStateStack &stack = state.popped(0);
+        stack.scopeVars = SearchScope(cx, this, code->inStack, id);
+        break;
+      }
+
+      case JSOP_ITER: {
+        uintN flags = pc[1];
+        if (flags & JSITER_FOREACH)
+            state.popped(0).isForEach = true;
+        break;
+      }
+
+      case JSOP_DOUBLE: {
+        AnalyzeStateStack &stack = state.popped(0);
+        stack.hasDouble = true;
+        stack.doubleValue = GetScriptConst(cx, script, pc).toDouble();
+        break;
+      }
+
+      case JSOP_ZERO:
+        state.popped(0).isZero = true;
+        /* FALLTHROUGH */
+      case JSOP_ONE:
+      case JSOP_INT8:
+      case JSOP_INT32:
+      case JSOP_UINT16:
+      case JSOP_UINT24:
+        state.popped(0).isConstant = true;
+        break;
+
+      case JSOP_GETLOCAL:
+        if (state.maybeLocalConst(GET_SLOTNO(pc), false)) {
+            state.popped(0).isConstant = true;
+            if (state.maybeLocalConst(GET_SLOTNO(pc), true))
+                state.popped(0).isZero = true;
+        }
+        break;
+
+      default:;
     }
 }
 
 /////////////////////////////////////////////////////////////////////
 // Printing
 /////////////////////////////////////////////////////////////////////
 
+#ifdef DEBUG
+
 void
 Bytecode::print(JSContext *cx)
 {
     jsbytecode *pc = script->getScript()->code + offset;
 
     JSOp op = (JSOp)*pc;
     JS_ASSERT(op < JSOP_LIMIT);
 
@@ -3225,17 +3415,17 @@ Bytecode::print(JSContext *cx)
       }
 
       case JOF_ATOM: {
         if (op == JSOP_DOUBLE) {
             printf("%s", name);
         } else {
             jsid id = GetAtomId(cx, script, pc, 0);
             if (JSID_IS_STRING(id))
-                printf("%s %s", name, TypeIdString(cx, id));
+                printf("%s %s", name, TypeIdString(id));
             else
                 printf("%s (index)", name);
         }
         break;
       }
 
       case JOF_OBJECT:
         printf("%s (object)", name);
@@ -3250,43 +3440,43 @@ Bytecode::print(JSContext *cx)
         break;
 
       case JOF_UINT16:
         printf("%s %d", name, GET_UINT16(pc));
         break;
 
       case JOF_QARG: {
         jsid id = script->getArgumentId(GET_ARGNO(pc));
-        printf("%s %s", name, TypeIdString(cx, id));
+        printf("%s %s", name, TypeIdString(id));
         break;
       }
 
       case JOF_GLOBAL:
-        printf("%s %s", name, TypeIdString(cx, GetGlobalId(cx, script, pc)));
+        printf("%s %s", name, TypeIdString(GetGlobalId(cx, script, pc)));
         break;
 
       case JOF_LOCAL:
         if ((op != JSOP_ARRAYPUSH) && (analyzed || (GET_SLOTNO(pc) < script->getScript()->nfixed))) {
-            jsid id = script->getLocalId(GET_SLOTNO(pc), inStack);
-            printf("%s %d %s", name, GET_SLOTNO(pc), TypeIdString(cx, id));
+            jsid id = script->getLocalId(GET_SLOTNO(pc), this);
+            printf("%s %d %s", name, GET_SLOTNO(pc), TypeIdString(id));
         } else {
             printf("%s %d", name, GET_SLOTNO(pc));
         }
         break;
 
       case JOF_SLOTATOM: {
         jsid id = GetAtomId(cx, script, pc, SLOTNO_LEN);
 
         jsid slotid = JSID_VOID;
         if (op == JSOP_GETARGPROP)
             slotid = script->getArgumentId(GET_ARGNO(pc));
         if (op == JSOP_GETLOCALPROP && (analyzed || (GET_SLOTNO(pc) < script->getScript()->nfixed)))
-            slotid = script->getLocalId(GET_SLOTNO(pc), inStack);
-
-        printf("%s %u %s %s", name, GET_SLOTNO(pc), TypeIdString(cx, slotid), TypeIdString(cx, id));
+            slotid = script->getLocalId(GET_SLOTNO(pc), this);
+
+        printf("%s %u %s %s", name, GET_SLOTNO(pc), TypeIdString(slotid), TypeIdString(id));
         break;
       }
 
       case JOF_SLOTOBJECT:
         printf("%s %u (object)", name, GET_SLOTNO(pc));
         break;
 
       case JOF_UINT24:
@@ -3306,18 +3496,20 @@ Bytecode::print(JSContext *cx)
         printf("%s %d", name, (jsint)GET_INT32(pc));
         break;
 
       default:
         JS_NOT_REACHED("Unknown opcode type");
     }
 }
 
+#endif
+
 void
-Script::print(JSContext *cx)
+Script::finish(JSContext *cx)
 {
     if (failed() || !codeArray)
         return;
 
     TypeCompartment *compartment = &script->compartment->types;
 
     /*
      * Check if there are warnings for used values with unknown types, and build
@@ -3361,16 +3553,18 @@ Script::print(JSContext *cx)
             } else {
                 compartment->typeCounts[typeCount-1]++;
             }
 
             stack = stack->innerStack ? stack->innerStack->group() : NULL;
         }
     }
 
+#ifdef DEBUG
+
     if (parent) {
         if (fun)
             printf("Function");
         else
             printf("Eval");
 
         printf(" #%u @%u\n", id, parent->analysis->id);
     } else {
@@ -3381,17 +3575,17 @@ Script::print(JSContext *cx)
         printf("(unused)\n");
         return;
     }
 
     /* Print out points where variables became unconditionally defined. */
     printf("defines:");
     for (unsigned i = 0; i < localCount(); i++) {
         if (locals[i] != LOCAL_USE_BEFORE_DEF && locals[i] != LOCAL_CONDITIONALLY_DEFINED)
-            printf(" %s@%u", TypeIdString(cx, getLocalId(i, NULL)), locals[i]);
+            printf(" %s@%u", TypeIdString(getLocalId(i, NULL)), locals[i]);
     }
     printf("\n");
 
     printf("locals:");
     localTypes.print(cx);
 
     int id_count = 0;
 
@@ -3403,17 +3597,17 @@ Script::print(JSContext *cx)
         printf("#%u:%05u:  ", id, offset);
         code->print(cx);
         printf("\n");
 
         if (code->defineCount) {
             printf("  defines:");
             for (unsigned i = 0; i < code->defineCount; i++) {
                 uint32 local = code->defineArray[i];
-                printf(" %s", TypeIdString(cx, getLocalId(local, NULL)));
+                printf(" %s", TypeIdString(getLocalId(local, NULL)));
             }
             printf("\n");
         }
 
         TypeStack *stack;
         unsigned useCount = GetUseCount(script, offset);
         if (useCount) {
             printf("  use:");
@@ -3461,16 +3655,19 @@ Script::print(JSContext *cx)
 
     printf("\n");
 
     TypeObject *object = objects;
     while (object) {
         object->print(cx);
         object = object->next;
     }
+
+#endif /* DEBUG */
+
 }
 
 } } /* namespace js::analyze */
 
 /////////////////////////////////////////////////////////////////////
 // Tracing
 /////////////////////////////////////////////////////////////////////
 
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -285,48 +285,40 @@ struct TypeStack
 {
     /*
      * Unique node for the equivalence class of this stack node, NULL if this
      * is the class node itself.  These are collected as a union find structure.
      * If non-NULL the remainder of this structure is empty.
      */
     TypeStack *mergedGroup;
 
-    /* Identifier for this class within the script. filled in during printing. */
-    int id;
-
-    /* Number of nodes beneath this one in the stack. */
-    unsigned stackDepth;
-
     /* Equivalence class for the node beneath this one in the stack. */
     TypeStack *innerStack;
 
     /* Possible types for values at this stack node. */
     TypeSet types;
 
-    /* Whether any other stack nodes have been merged into this one. */
-    bool hasMerged;
-
-    /* Whether the values at this node are bound by a 'with'. */
+    /*
+     * Any let variable associated with this stack node, and whether the values
+     * at this node are bound by a 'with'.  For resolving ambiguous cross-script
+     * local variable lookups. :TODO: remove.
+     */
+    jsid letVariable;
     bool boundWith;
 
-    /* Whether this node is the iterator for a 'for each' or 'for in' loop. */
-    bool isForEach;
-
     /*
      * Whether to ignore the type tag of this stack entry downstream; it may not
      * represent the actual values in this slot.
      */
     bool ignoreTypeTag;
 
-    /* The name of any 'let' variable stored by this node. */
-    jsid letVariable;
-
-    /* Variable set for any scope name binding pushed on this stack node. */
-    VariableSet *scopeVars;
+#ifdef DEBUG
+    /* Identifier for this class within the script. filled in during printing. */
+    int id;
+#endif
 
     /* Get the representative node for the equivalence class of this node. */
     inline TypeStack* group();
 
     /* Set the inner stack of this node. */
     inline void setInnerStack(TypeStack *inner);
 
     /* Merge the equivalence classes for two stack nodes together. */
@@ -414,33 +406,39 @@ struct VariableSet
      * Other variable sets which should receive all variables added to this set.
      * For handling prototypes.
      */
     VariableSet **propagateSet;
     unsigned propagateCount;
 
     JSArenaPool *pool;
 
+    /* Whether the variables in this set are unknown. */
+    bool unknown;
+
     VariableSet(JSArenaPool *pool)
-        : variables(NULL), propagateSet(NULL), propagateCount(NULL), pool(pool)
+        : variables(NULL), propagateSet(NULL), propagateCount(NULL), pool(pool), unknown(false)
     {
         JS_ASSERT(pool);
     }
 
     /* Get or make the types for the specified id. */
     inline TypeSet* getVariable(JSContext *cx, jsid id);
 
     /*
      * Mark target as receiving all variables and type information added to this
      * set (whether it is currently there or will be added in the future).
      * If excludePrototype is set the 'prototype' variable is omitted from the
      * propagation.  Returns whether there was already a propagation to target.
      */
     bool addPropagate(JSContext *cx, VariableSet *target, bool excludePrototype);
 
+    /* Mark all existing and future properties of this set as unknown. */
+    void markUnknown(JSContext *cx);
+
     void print(JSContext *cx);
 };
 
 /* Type information about an object accessed by a script. */
 struct TypeObject
 {
     /*
      * Name of this object.  This is unique among all objects in the compartment;
@@ -448,22 +446,16 @@ struct TypeObject
      * this object instead.
      */
     jsid name;
 
     /* Whether this is a function object, and may be cast into TypeFunction. */
     bool isFunction;
 
     /*
-     * Whether all reads from this object need to be monitored.  This includes
-     * all property and element accesses, and for functions all calls to the function.
-     */
-    bool monitored;
-
-    /*
      * Properties of this object.  This is filled in lazily for function objects
      * to avoid unnecessary property and prototype object creation.  Don't access
      * this directly, use properties() below.
      */
     VariableSet propertySet;
     bool propertiesFilled;
 
     /* Link in the list of objects in the property set's pool. */
@@ -484,16 +476,23 @@ struct TypeObject
     bool isInitObject;
 
     /* Whether all objects this represents are dense arrays. */
     bool isDenseArray;
 
     /* Whether all objects this represents are packed arrays (implies isDenseArray). */
     bool isPackedArray;
 
+    /*
+     * Whether this object is thought to be a possible packed array: either it came
+     * from a [a,b,c] initializer, an Array(a,b,c) call, or is another array for
+     * which we've seen what looks like initialization code. This is pure heuristic.
+     */
+    bool possiblePackedArray;
+
     /* Make an object with the specified name. */
     TypeObject(JSContext *cx, JSArenaPool *pool, jsid id, bool isArray);
 
     /* Propagate properties from this object to target. */
     bool addPropagate(JSContext *cx, TypeObject *target, bool excludePrototype = true);
 
     /* Coerce this object to a function. */
     TypeFunction* asFunction()
@@ -502,16 +501,19 @@ struct TypeObject
         return (TypeFunction *) this;
     }
 
     JSArenaPool & pool() { return *propertySet.pool; }
 
     /* Get the properties of this object, filled in lazily. */
     inline VariableSet& properties(JSContext *cx);
 
+    /* Whether the properties of this object are unknown. */
+    bool unknownProperties() { return propertySet.unknown; }
+
     /* Get the type set for all integer index properties of this object. */
     inline TypeSet* indexTypes(JSContext *cx);
 
     void print(JSContext *cx);
 };
 
 /* Type information about an interpreted or native function. */
 struct TypeFunction : public TypeObject
@@ -741,38 +743,29 @@ struct TypeCompartment
     unsigned pendingCapacity;
 
     /* Whether we are currently resolving the pending worklist. */
     bool resolving;
 
     /* Logging fields */
 
     /*
-     * Whether any warnings were emitted.  These are nonfatal but (generally)
-     * indicate unhandled constructs leading to analysis unsoundness.
-     */
-    bool warnings;
-
-    /*
-     * Whether to ignore generated warnings.  For handling regressions with
-     * shell functions we don't model.
-     */
-    bool ignoreWarnings;
-
-    /*
      * The total time (in microseconds) spent generating inference structures
      * and performing analysis.
      */
     uint64_t analysisTime;
 
     /* Counts of stack type sets with some number of possible operand types. */
     static const unsigned TYPE_COUNT_LIMIT = 4;
     unsigned typeCounts[TYPE_COUNT_LIMIT];
     unsigned typeCountOver;
 
+    /* Number of recompilations triggered. */
+    unsigned recompilations;
+
     void init();
     ~TypeCompartment();
 
     uint64 currentTime()
     {
 #ifndef _MSC_VER
         timeval current;
         gettimeofday(&current, NULL);
@@ -821,27 +814,29 @@ enum SpewChannel {
     ISpewDynamic,  /* dynamic: Dynamic type changes and inference entry points. */
     ISpewOps,      /* ops: New constraints and types. */
     ISpewResult,   /* result: Final type sets. */
     SPEW_COUNT
 };
 
 #ifdef DEBUG
 
-/* Spew with INFERFLAGS = full or base */
 void InferSpew(SpewChannel which, const char *fmt, ...);
-void InferSpewType(SpewChannel which, JSContext *cx, jstype type, const char *fmt, ...);
+const char * TypeString(jstype type);
 
 #else
 
 inline void InferSpew(SpewChannel which, const char *fmt, ...) {}
-inline void InferSpewType(SpewChannel which, JSContext *cx, jstype type, const char *fmt, ...) {}
+inline const char * TypeString(jstype type) { return NULL; }
 
 #endif
 
+/* Print a warning, dump state and abort the program. */
+void TypeFailure(JSContext *cx, const char *fmt, ...);
+
 } /* namespace types */
 } /* namespace js */
 
 static JS_ALWAYS_INLINE js::types::TypeObject *
 Valueify(JSTypeObject *jstype) { return (js::types::TypeObject*) jstype; }
 
 static JS_ALWAYS_INLINE js::types::TypeFunction *
 Valueify(JSTypeFunction *jstype) { return (js::types::TypeFunction*) jstype; }
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -126,17 +126,17 @@ MakeTypeId(jsid id)
     }
 
     JS_NOT_REACHED("Unknown id");
     return JSID_VOID;
 }
 
 /* Convert an id for printing during debug. */
 static inline const char *
-TypeIdString(JSContext *cx, jsid id)
+TypeIdString(jsid id)
 {
 #ifdef DEBUG
     if (JSID_IS_VOID(id))
         return "(index)";
     return js_GetStringBytes(JSID_TO_ATOM(id));
 #else
     return NULL;
 #endif
@@ -334,19 +334,20 @@ JSContext::addTypePropertyId(js::types::
     id = js::types::MakeTypeId(id);
 
     js::types::TypeSet *types = obj->properties(this).getVariable(this, id);
 
     if (types->hasType(type))
         return;
 
     if (compartment->types.interpreting) {
-        js::types::InferSpewType(js::types::ISpewDynamic, this, type, "AddBuiltin: %s %s:",
-                                 js::types::TypeIdString(this, obj->name),
-                                 js::types::TypeIdString(this, id));
+        js::types::InferSpew(js::types::ISpewDynamic, "AddBuiltin: %s %s: %s",
+                             js::types::TypeIdString(obj->name),
+                             js::types::TypeIdString(id),
+                             js::types::TypeString(type));
         compartment->types.addDynamicType(this, types, type);
     } else {
         types->addType(this, type);
     }
 #endif
 }
 
 inline void
@@ -368,64 +369,55 @@ JSContext::aliasTypeProperties(js::types
     js::types::TypeSet *secondTypes = obj->properties(this).getVariable(this, second);
 
     firstTypes->addSubset(this, obj->pool(), secondTypes);
     secondTypes->addSubset(this, obj->pool(), firstTypes);
 #endif
 }
 
 inline void
-JSContext::markTypeArrayNotPacked(js::types::TypeObject *obj, bool notDense)
+JSContext::markTypeArrayNotPacked(js::types::TypeObject *obj, bool notDense, bool dynamic)
 {
 #ifdef JS_TYPE_INFERENCE
     if (notDense) {
         if (!obj->isDenseArray)
             return;
         obj->isDenseArray = false;
     } else if (!obj->isPackedArray) {
         return;
     }
     obj->isPackedArray = false;
 
+    if (dynamic) {
+        js::types::InferSpew(js::types::ISpewDynamic, "%s: %s",
+                             notDense ? "NonDenseArray" : "NonPackedArray",
+                             js::types::TypeIdString(obj->name));
+    }
+
     /* All constraints listening to changes in packed/dense status are on the element types. */
     js::types::TypeSet *elementTypes = obj->properties(this).getVariable(this, JSID_VOID);
     js::types::TypeConstraint *constraint = elementTypes->constraintList;
     while (constraint) {
         constraint->arrayNotPacked(this, notDense);
         constraint = constraint->next;
     }
 
-    if (compartment->types.hasPendingRecompiles())
+    if (dynamic && compartment->types.hasPendingRecompiles())
         compartment->types.processPendingRecompiles(this);
 #endif
 }
 
 void
-JSContext::monitorTypeObject(js::types::TypeObject *obj)
+JSContext::markTypeObjectUnknownProperties(js::types::TypeObject *obj)
 {
 #ifdef JS_TYPE_INFERENCE
-    if (obj->monitored)
+    if (obj->unknownProperties())
         return;
 
-    /*
-     * Existing property constraints may have already been added to this object,
-     * which we need to do the right thing for.  We can't ensure that we will
-     * mark all monitored objects before they have been accessed, as the __proto__
-     * of a non-monitored object could be dynamically set to a monitored object.
-     * Adding unknown for any properties accessed already accounts for possible
-     * values read from them.
-     */
-
-    js::types::Variable *var = obj->properties(this).variables;
-    while (var) {
-        var->types.addType(this, js::types::TYPE_UNKNOWN);
-        var = var->next;
-    }
-
-    obj->monitored = true;
+    obj->properties(this).markUnknown(this);
 #endif
 }
 
 inline void
 JSContext::typeMonitorCall(JSScript *caller, const jsbytecode *callerpc,
                            const js::CallArgs &args, bool constructing, bool force)
 {
     JS_ASSERT_IF(caller == NULL, force);
@@ -458,18 +450,18 @@ JSContext::typeMonitorCall(JSScript *cal
     unsigned arg = 0;
     for (; arg < args.argc(); arg++) {
         js::types::jstype type = js::types::GetValueType(this, args[arg]);
 
         jsid id = script->getArgumentId(arg);
         if (!JSID_IS_VOID(id)) {
             js::types::TypeSet *types = script->localTypes.getVariable(this, id);
             if (!types->hasType(type)) {
-                js::types::InferSpewType(js::types::ISpewDynamic, this, type,
-                                         "AddArg: #%u %u:", script->id, arg);
+                js::types::InferSpew(js::types::ISpewDynamic, "AddArg: #%u %u: %s",
+                                     script->id, arg, js::types::TypeString(type));
                 compartment->types.addDynamicType(this, types, type);
             }
         } else {
             /*
              * More actuals than formals to this call.  We can ignore this case,
              * the value can only be accessed through the arguments object, which
              * is monitored.
              */
@@ -501,26 +493,28 @@ JSContext::typeMonitorEntry(JSScript *sc
 
     if (force) {
         js::types::jstype type;
         if (constructing)
             type = (js::types::jstype) analysis->function()->getNewObject(this);
         else
             type = js::types::GetValueType(this, thisv);
         if (!analysis->thisTypes.hasType(type)) {
-            js::types::InferSpewType(js::types::ISpewDynamic, this, type,
-                                     "AddThis: #%u:", analysis->id);
+            js::types::InferSpew(js::types::ISpewDynamic, "AddThis: #%u: %s",
+                                 analysis->id, js::types::TypeString(type));
             compartment->types.addDynamicType(this, &analysis->thisTypes, type);
         }
     }
 
     if (!analysis->hasAnalyzed()) {
         compartment->types.interpreting = false;
         uint64_t startTime = compartment->types.currentTime();
 
+        js::types::InferSpew(js::types::ISpewDynamic, "EntryPoint: #%lu", analysis->id);
+
         analysis->analyze(this);
 
         uint64_t endTime = compartment->types.currentTime();
         compartment->types.analysisTime += (endTime - startTime);
         compartment->types.interpreting = true;
 
         if (compartment->types.hasPendingRecompiles())
             compartment->types.processPendingRecompiles(this);
@@ -601,31 +595,35 @@ JSScript::typeMonitorAssign(JSContext *c
 {
 #ifdef JS_TYPE_INFERENCE
     if (!analysis->failed()) {
         js::analyze::Bytecode &code = analysis->getCode(pc);
         if (!code.monitorNeeded)
             return;
     }
 
-    cx->compartment->types.dynamicAssign(cx, obj, id, rval);
+    if (!obj->getTypeObject()->unknownProperties() ||
+        id == ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom)) {
+        cx->compartment->types.dynamicAssign(cx, obj, id, rval);
+    }
 #endif
 }
 
 inline void
 JSScript::typeSetArgument(JSContext *cx, unsigned arg, const js::Value &value)
 {
 #ifdef JS_TYPE_INFERENCE
     jsid id = analysis->getArgumentId(arg);
     if (!JSID_IS_VOID(id)) {
         js::types::TypeSet *argTypes = analysis->localTypes.getVariable(cx, id);
         js::types::jstype type = js::types::GetValueType(cx, value);
         if (!argTypes->hasType(type)) {
-            js::types::InferSpewType(js::types::ISpewDynamic, cx, type, "SetArgument: #%u %s:",
-                                     analysis->id, js::types::TypeIdString(cx, id));
+            js::types::InferSpew(js::types::ISpewDynamic, "SetArgument: #%u %s: %s",
+                                 analysis->id, js::types::TypeIdString(id),
+                                 js::types::TypeString(type));
             cx->compartment->types.addDynamicType(cx, argTypes, type);
         }
     }
 #endif
 }
 
 /////////////////////////////////////////////////////////////////////
 // JSObject
@@ -722,29 +720,28 @@ Bytecode::getInitObject(JSContext *cx, b
     return initObject;
 }
 
 /////////////////////////////////////////////////////////////////////
 // analyze::Script
 /////////////////////////////////////////////////////////////////////
 
 inline jsid
-Script::getLocalId(unsigned index, types::TypeStack *stack)
+Script::getLocalId(unsigned index, Bytecode *code)
 {
     if (index >= script->nfixed) {
-        /*
-         * This is an access on a let variable, we need the stack to figure out
-         * the name of the accessed variable.  If multiple let variables have
-         * the same name, we flatten their types together.
-         */
-        stack = stack ? stack->group() : NULL;
-        while (stack && (stack->stackDepth != index - script->nfixed)) {
-            stack = stack->innerStack;
-            stack = stack ? stack->group() : NULL;
-        }
+        if (!code)
+            return JSID_VOID;
+
+        JS_ASSERT(index - script->nfixed < code->stackDepth);
+        unsigned diff = code->stackDepth - (index - script->nfixed);
+        types::TypeStack *stack = code->inStack;
+        for (unsigned i = 1; i < diff; i++)
+            stack = stack->group()->innerStack;
+        JS_ASSERT(stack);
 
         if (stack && JSID_TO_STRING(stack->letVariable) != NULL)
             return stack->letVariable;
 
         /*
          * This can show up when the accessed value is not from a 'var' or 'let'
          * but is just an access to a fixed slot.  There is no name, get the
          * types using getLocalTypes below.
@@ -769,29 +766,26 @@ Script::getArgumentId(unsigned index)
      */
     if (index >= argCount() || !localNames[index])
         return JSID_VOID;
 
     return ATOM_TO_JSID(JS_LOCAL_NAME_TO_ATOM(localNames[index]));
 }
 
 inline types::TypeSet*
-Script::getStackTypes(unsigned index, types::TypeStack *stack)
+Script::getStackTypes(unsigned index, Bytecode *code)
 {
     JS_ASSERT(index >= script->nfixed);
+    JS_ASSERT(index - script->nfixed < code->stackDepth);
 
-    stack = stack->group();
-    while (stack && (stack->stackDepth != index - script->nfixed)) {
-        stack = stack->innerStack;
-        stack = stack ? stack->group() : NULL;
-    }
-
-    /* This should not be used for accessing a let variable's stack slot. */
-    JS_ASSERT(stack && !JSID_IS_VOID(stack->letVariable));
-    return &stack->types;
+    types::TypeStack *stack = code->inStack;
+    unsigned diff = code->stackDepth - (index - script->nfixed) - 1;
+    for (unsigned i = 0; i < diff; i++)
+        stack = stack->group()->innerStack;
+    return &stack->group()->types;
 }
 
 inline JSValueType
 Script::knownArgumentTypeTag(JSContext *cx, JSScript *script, unsigned arg)
 {
     jsid id = getArgumentId(arg);
     if (!JSID_IS_VOID(id) && !argEscapes(arg)) {
         types::TypeSet *types = localTypes.getVariable(cx, id);
@@ -821,17 +815,17 @@ Script::knownLocalTypeTag(JSContext *cx,
 namespace types {
 
 inline void
 TypeCompartment::addPending(JSContext *cx, TypeConstraint *constraint, TypeSet *source, jstype type)
 {
     JS_ASSERT(this == &cx->compartment->types);
     JS_ASSERT(type);
 
-    InferSpewType(ISpewOps, cx, type, "pending: C%u", constraint->id());
+    InferSpew(ISpewOps, "pending: C%u %s", constraint->id(), TypeString(type));
 
     if (pendingCount == pendingCapacity)
         growPendingArray();
 
     PendingWork &pending = pendingArray[pendingCount++];
     pending.constraint = constraint;
     pending.source = source;
     pending.type = type;
@@ -847,17 +841,18 @@ TypeCompartment::resolvePending(JSContex
         return;
     }
 
     resolving = true;
 
     /* Handle all pending type registrations. */
     while (pendingCount) {
         const PendingWork &pending = pendingArray[--pendingCount];
-        InferSpewType(ISpewOps, cx, pending.type, "resolve: C%u ", pending.constraint->id());
+        InferSpew(ISpewOps, "resolve: C%u %s",
+                  pending.constraint->id(), TypeString(pending.type));
         pending.constraint->newType(cx, pending.source, pending.type);
     }
 
     resolving = false;
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeSet
@@ -1056,17 +1051,17 @@ UseDuplicateObjects(TypeObject *object)
 const unsigned OBJECT_THRESHOLD = unsigned(-1);
 const unsigned TYPESET_THRESHOLD = unsigned(-1);
 
 inline void
 TypeSet::addType(JSContext *cx, jstype type)
 {
     JS_ASSERT(type);
     JS_ASSERT_IF(typeFlags & TYPE_FLAG_UNKNOWN, typeFlags == TYPE_FLAG_UNKNOWN);
-    InferSpewType(ISpewOps, cx, type, "addType: T%u ", id());
+    InferSpew(ISpewOps, "addType: T%u %s", id(), TypeString(type));
 
     if (typeFlags & TYPE_FLAG_UNKNOWN)
         return;
 
     if (type == TYPE_UNKNOWN) {
         typeFlags = TYPE_FLAG_UNKNOWN;
     } else if (TypeIsPrimitive(type)) {
         TypeFlags flag = 1 << type;
@@ -1119,17 +1114,17 @@ TypeSet::addType(JSContext *cx, jstype t
 
     cx->compartment->types.resolvePending(cx);
 }
 
 inline TypeSet *
 TypeSet::make(JSContext *cx, JSArenaPool &pool, const char *name)
 {
     TypeSet *res = ArenaNew<TypeSet>(pool, &pool);
-    InferSpew(ISpewOps, "intermediate %s T%u\n", name, res->id());
+    InferSpew(ISpewOps, "intermediate %s T%u", name, res->id());
 
     return res;
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeStack
 /////////////////////////////////////////////////////////////////////
 
@@ -1144,17 +1139,16 @@ TypeStack::group()
     return res;
 }
 
 inline void
 TypeStack::setInnerStack(TypeStack *inner)
 {
     JS_ASSERT(!mergedGroup);
     innerStack = inner;
-    stackDepth = inner ? (inner->group()->stackDepth + 1) : 0;
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeCallsite
 /////////////////////////////////////////////////////////////////////
 
 inline
 TypeCallsite::TypeCallsite(analyze::Bytecode *code, bool isNew, unsigned argumentCount)
@@ -1210,18 +1204,27 @@ VariableSet::getVariable(JSContext *cx, 
         res = res->next;
     }
 
     /* Make a new variable and thread it onto our list. */
     res = ArenaNew<Variable>(*pool, pool, id);
     res->next = variables;
     variables = res;
 
-    InferSpew(ISpewOps, "addVariable: %s %s T%u\n",
-              TypeIdString(cx, name()), TypeIdString(cx, id), res->types.id());
+    InferSpew(ISpewOps, "addVariable: %s %s T%u",
+              TypeIdString(name()), TypeIdString(id), res->types.id());
+
+    if (unknown) {
+        /*
+         * Immediately mark the variable as unknown. Ideally we won't be doing this
+         * too often, but we don't assert !unknown to avoid extra complexity in
+         * other code accessing variable sets.
+         */
+        res->types.addType(cx, TYPE_UNKNOWN);
+    }
 
     /* Propagate the variable to any other sets receiving our variables. */
     if (propagateCount >= 2) {
         unsigned capacity = HashSetCapacity(propagateCount);
         for (unsigned i = 0; i < capacity; i++) {
             VariableSet *target = propagateSet[i];
             if (target) {
                 TypeSet *targetTypes = target->getVariable(cx, id);
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -477,16 +477,17 @@ powi(jsdouble x, jsint y)
 
 static JSBool
 math_pow(JSContext *cx, uintN argc, Value *vp)
 {
     jsdouble x, y, z;
 
     if (argc <= 1) {
         vp->setDouble(js_NaN);
+        cx->markTypeCallerOverflow();
         return JS_TRUE;
     }
     if (!ValueToNumber(cx, vp[2], &x))
         return JS_FALSE;
     if (!ValueToNumber(cx, vp[3], &y))
         return JS_FALSE;
     /*
      * Special case for square roots. Note that pow(x, 0.5) != sqrt(x)
@@ -517,16 +518,19 @@ math_pow(JSContext *cx, uintN argc, Valu
     }
 
     if (vp[3].isInt32())
         z = powi(x, vp[3].toInt32());
     else
         z = pow(x, y);
 
     vp->setNumber(z);
+    if (vp->isDouble() && vp[2].isInt32() && vp[3].isInt32())
+        cx->markTypeCallerOverflow();
+
     return JS_TRUE;
 }
 
 static const int64 RNG_MULTIPLIER = 0x5DEECE66DLL;
 static const int64 RNG_ADDEND = 0xBLL;
 static const int64 RNG_MASK = (1LL << 48) - 1;
 static const jsdouble RNG_DSCALE = jsdouble(1LL << 53);
 
@@ -870,17 +874,17 @@ static JSFunctionSpec math_static_method
     JS_TN("atan2",          math_atan2,           2, 0, &math_atan2_trcinfo, JS_TypeHandlerFloat),
     JS_TN("ceil",           js_math_ceil,         1, 0, &js_math_ceil_trcinfo, JS_TypeHandlerInt),
     JS_TN("cos",            math_cos,             1, 0, &math_cos_trcinfo, JS_TypeHandlerFloat),
     JS_TN("exp",            math_exp,             1, 0, &math_exp_trcinfo, JS_TypeHandlerFloat),
     JS_TN("floor",          js_math_floor,        1, 0, &js_math_floor_trcinfo, JS_TypeHandlerInt),
     JS_TN("log",            math_log,             1, 0, &math_log_trcinfo, JS_TypeHandlerFloat),
     JS_TN("max",            js_math_max,          2, 0, &js_math_max_trcinfo, math_TypeArith),
     JS_TN("min",            js_math_min,          2, 0, &js_math_min_trcinfo, math_TypeArith),
-    JS_TN("pow",            math_pow,             2, 0, &math_pow_trcinfo, JS_TypeHandlerFloat),
+    JS_TN("pow",            math_pow,             2, 0, &math_pow_trcinfo, math_TypeArith),
     JS_TN("random",         math_random,          0, 0, &math_random_trcinfo, JS_TypeHandlerFloat),
     JS_TN("round",          js_math_round,        1, 0, &js_math_round_trcinfo, JS_TypeHandlerInt),
     JS_TN("sin",            math_sin,             1, 0, &math_sin_trcinfo, JS_TypeHandlerFloat),
     JS_TN("sqrt",           math_sqrt,            1, 0, &math_sqrt_trcinfo, JS_TypeHandlerFloat),
     JS_TN("tan",            math_tan,             1, 0, &math_tan_trcinfo, JS_TypeHandlerFloat),
     JS_FS_END
 };
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1819,14 +1819,14 @@ JSScript::makeAnalysis(JSContext *cx)
     analysis->id = ++cx->compartment->types.scriptCount;
     analysis->localTypes.pool = &analysis->pool;
     analysis->thisTypes.setPool(&analysis->pool);
 
 #ifdef DEBUG
     char name[40];
     JS_snprintf(name, sizeof(name), "#%u:locals", analysis->id);
     analysis->localTypes.name_ = ATOM_TO_JSID(js_Atomize(cx, name, strlen(name), 0));
-    types::InferSpew(types::ISpewOps, "newScript: %s\n", name);
+    types::InferSpew(types::ISpewOps, "newScript: %s", name);
 #endif
 #endif /* JS_TYPE_INFERENCE */
 
     return analysis;
 }
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2464,16 +2464,22 @@ mjit::Compiler::emitUncachedCall(uint32 
 
     frame.takeReg(JSReturnReg_Type);
     frame.takeReg(JSReturnReg_Data);
     frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data, knownPushedType(0));
 
     stubcc.linkExitDirect(notCompiled, stubcc.masm.label());
     stubcc.rejoin(Changes(1));
     callPatches.append(callPatch);
+
+    if (recompiling) {
+        /* In case we recompiled this call to an uncached call. */
+        OOL_STUBCALL(JS_FUNC_TO_DATA_PTR(void *, callingNew ? ic::New : ic::Call));
+        stubcc.rejoin(Changes(1));
+    }
 }
 
 static bool
 IsLowerableFunCallOrApply(jsbytecode *pc)
 {
 #ifdef JS_TYPE_INFERENCE
     /* :FIXME: see canUseApplyTricks */
     return false;
@@ -5063,18 +5069,17 @@ mjit::Compiler::fixDoubleTypes(Uses uses
         if (type == JSVAL_TYPE_DOUBLE) {
             FrameEntry *fe = frame.getLocal(i);
             if (!fe->isType(JSVAL_TYPE_DOUBLE))
                 frame.ensureDouble(fe);
         }
     }
     analyze::Bytecode &opinfo = analysis->getCode(PC);
     for (uint32 i = 0; i < opinfo.stackDepth - uses.nuses; i++) {
-        types::TypeStack *stack = opinfo.inStack;
-        types::TypeSet *types = analysis->getStackTypes(script->nfixed + i, stack);
+        types::TypeSet *types = analysis->getStackTypes(script->nfixed + i, &opinfo);
         JSValueType type = types->getKnownTypeTag(cx, script);
         if (type == JSVAL_TYPE_DOUBLE) {
             FrameEntry *fe = frame.getLocal(script->nfixed + i);
             if (!fe->isType(JSVAL_TYPE_DOUBLE))
                 frame.ensureDouble(fe);
         }
     }
 #endif
--- a/js/src/methodjit/Retcon.cpp
+++ b/js/src/methodjit/Retcon.cpp
@@ -120,16 +120,18 @@ Recompiler::stealNative(JITScript *jit, 
     JS_ASSERT(ic.fastGuardedNative);
 
     JSC::ExecutablePool *&pool = ic.pools[ic::CallICInfo::Pool_NativeStub];
 
     if (!pool) {
         /* Already stole this stub. */
         PatchableNative native;
         native.pc = NULL;
+        native.guardedNative = NULL;
+        native.pool = NULL;
         return native;
     }
 
     PatchableNative native;
     native.pc = pc;
     native.guardedNative = ic.fastGuardedNative;
     native.pool = pool;
     native.nativeStart = ic.nativeStart;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4203,23 +4203,16 @@ Deserialize(JSContext *cx, uintN argc, j
     }
 
     if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength, &v))
         return false;
     JS_SET_RVAL(cx, vp, v);
     return true;
 }
 
-static void type_Bailout(JSContext *cx, JSTypeFunction *fun, JSTypeCallsite *site)
-{
-#ifdef JS_TYPE_INFERENCE
-    cx->compartment->types.ignoreWarnings = true;
-#endif
-}
-
 /* We use a mix of JS_FS and JS_FN to test both kinds of natives. */
 static JSFunctionSpec shell_functions[] = {
     JS_FN_TYPE("version",        Version,        0,0, JS_TypeHandlerInt),
     JS_FN_TYPE("revertVersion",  RevertVersion,  0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("options",        Options,        0,0, JS_TypeHandlerString),
     JS_FN_TYPE("load",           Load,           1,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("readline",       ReadLine,       0,0, JS_TypeHandlerString),
     JS_FN_TYPE("print",          Print,          0,0, JS_TypeHandlerVoid),
@@ -4262,22 +4255,22 @@ static JSFunctionSpec shell_functions[] 
     JS_FN_TYPE("stats",          DumpStats,      1,0, JS_TypeHandlerVoid),
 #endif
 #ifdef TEST_CVTARGS
     JS_FN_TYPE("cvtargs",        ConvertArgs,    0,0, JS_TypeHandlerVoid),
 #endif
     JS_FN_TYPE("build",          BuildDate,      0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("clear",          Clear,          0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("intern",         Intern,         1,0, JS_TypeHandlerVoid),
-    JS_FN_TYPE("clone",          Clone,          1,0, type_Bailout),
-    JS_FN_TYPE("getpda",         GetPDA,         1,0, JS_TypeHandlerMissing),
+    JS_FN_TYPE("clone",          Clone,          1,0, JS_TypeHandlerDynamic),
+    JS_FN_TYPE("getpda",         GetPDA,         1,0, JS_TypeHandlerDynamic),
     JS_FN_TYPE("getslx",         GetSLX,         1,0, JS_TypeHandlerInt),
     JS_FN_TYPE("toint32",        ToInt32,        1,0, JS_TypeHandlerInt),
-    JS_FN_TYPE("evalcx",         EvalInContext,  1,0, type_Bailout),
-    JS_FN_TYPE("evalInFrame",    EvalInFrame,    2,0, type_Bailout),
+    JS_FN_TYPE("evalcx",         EvalInContext,  1,0, JS_TypeHandlerDynamic),
+    JS_FN_TYPE("evalInFrame",    EvalInFrame,    2,0, JS_TypeHandlerDynamic),
     JS_FN_TYPE("shapeOf",        ShapeOf,        1,0, JS_TypeHandlerInt),
 #ifdef MOZ_SHARK
     JS_FN_TYPE("startShark",     js_StartShark,      0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("stopShark",      js_StopShark,       0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("connectShark",   js_ConnectShark,    0,0, JS_TypeHandlerVoid),
     JS_FN_TYPE("disconnectShark",js_DisconnectShark, 0,0, JS_TypeHandlerVoid),
 #endif
 #ifdef MOZ_CALLGRIND
@@ -4656,17 +4649,17 @@ its_bindMethod(JSContext *cx, uintN argc
     
     if (!JS_DefinePropertyById(cx, thisobj, id, *vp, NULL, NULL, JSPROP_ENUMERATE))
         return JS_FALSE;
 
     return JS_SetParent(cx, method, thisobj);
 }
 
 static JSFunctionSpec its_methods[] = {
-    {"bindMethod",      its_bindMethod, 2,0, type_Bailout},
+    {"bindMethod",      its_bindMethod, 2,0, JS_TypeHandlerMissing},
     {NULL,NULL,0,0}
 };
 
 #ifdef JSD_LOWLEVEL_SOURCE
 /*
  * This facilitates sending source to JSD (the debugger system) in the shell
  * where the source is loaded using the JSFILE hack in jsscan. The function
  * below is used as a callback for the jsdbgapi JS_SetSourceHandler hook.