Bug 454039 - TM: don't abort TraceRecorder::record_JSOP_SETPROP on cache miss (r=mrbkap).
authorBrendan Eich <brendan@mozilla.org>
Tue, 09 Sep 2008 22:22:52 -0700
changeset 19093 95bc5b468ebecda4a728dbefbadbcf34939d68de
parent 19092 3bee5d8ea9ac07704f4fd34cd4b511911e6588a7
child 19094 ac2f0278900c5c1b9f22d9f80f9cdf6e94a42afa
push id1930
push usermrbkap@mozilla.com
push dateWed, 10 Sep 2008 06:40:47 +0000
treeherderautoland@ee61af1469cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs454039
milestone1.9.1b1pre
Bug 454039 - TM: don't abort TraceRecorder::record_JSOP_SETPROP on cache miss (r=mrbkap).
js/src/jsinterp.cpp
js/src/jstracer.cpp
js/src/jstracer.h
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2505,17 +2505,17 @@ js_Interpret(JSContext *cx)
 # include "jsopcode.tbl"
 # undef OPDEF
     };
 
     METER_OP_INIT(op);      /* to nullify first METER_OP_PAIR */
 
 # ifdef JS_TRACER
 #  define CHECK_RECORDER()  JS_BEGIN_MACRO                                    \
-                                JS_ASSERT(!JS_TRACE_MONITOR(cx).recorder ^    \
+                                JS_ASSERT(!TRACE_RECORDER(cx) ^               \
                                           (jumpTable == recordingJumpTable)); \
                             JS_END_MACRO
 # else
 #  define CHECK_RECORDER()  ((void)0)
 # endif
 
 # define DO_OP()            JS_BEGIN_MACRO                                    \
                                 CHECK_RECORDER();                             \
@@ -2564,21 +2564,21 @@ js_Interpret(JSContext *cx)
 # define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP)
 # define END_EMPTY_CASES    goto advance_pc_by_one;
 
 #endif /* !JS_THREADED_INTERP */
 
 #ifdef JS_TRACER
     /* We had better not be entering the interpreter from JIT-compiled code. */
     TraceRecorder *tr = NULL;
-    if (JS_TRACE_MONITOR(cx).onTrace) {
-        tr = JS_TRACE_MONITOR(cx).recorder;
-        JS_TRACE_MONITOR(cx).recorder = NULL;
+    if (JS_ON_TRACE(cx)) {
+        tr = TRACE_RECORDER(cx);
+        SET_TRACE_RECORDER(cx, NULL);
     }
-#endif    
+#endif
 
     /* Check for too deep of a native thread stack. */
     JS_CHECK_RECURSION(cx, return JS_FALSE);
 
     rt = cx->runtime;
 
     /* Set registerized frame pointer and derived script pointer. */
     fp = cx->fp;
@@ -2690,42 +2690,41 @@ js_Interpret(JSContext *cx)
      * not have to reload it each time through the interpreter loop -- we hope
      * the compiler can keep it in a register when it is non-null.
      */
 #if JS_THREADED_INTERP
 #ifdef JS_TRACER
 # define LOAD_INTERRUPT_HANDLER(cx)                                           \
     ((void) (jumpTable = (cx)->debugHooks->interruptHandler                   \
                          ? interruptJumpTable                                 \
-                         : JS_TRACE_MONITOR(cx).recorder                      \
+                         : TRACE_RECORDER(cx)                                 \
                          ? recordingJumpTable                                 \
                          : normalJumpTable))
 # define ENABLE_TRACER(flag)                                                  \
     JS_BEGIN_MACRO                                                            \
         bool flag_ = (flag);                                                  \
-        JS_ASSERT(flag_ == !!JS_TRACE_MONITOR(cx).recorder);                  \
+        JS_ASSERT(flag_ == !!TRACE_RECORDER(cx));                             \
         jumpTable = flag_ ? recordingJumpTable : normalJumpTable;             \
     JS_END_MACRO
 #else /* !JS_TRACER */
 # define LOAD_INTERRUPT_HANDLER(cx)                                           \
     ((void) (jumpTable = (cx)->debugHooks->interruptHandler                   \
                          ? interruptJumpTable                                 \
                          : normalJumpTable))
 # define ENABLE_TRACER(flag) ((void)0)
 #endif /* !JS_TRACER */
 #else /* !JS_THREADED_INTERP */
 #ifdef JS_TRACER
 # define LOAD_INTERRUPT_HANDLER(cx)                                           \
     ((void) (switchMask = ((cx)->debugHooks->interruptHandler ||              \
-                           JS_TRACE_MONITOR(cx).recorder)                     \
-                          ? 0 : 255))
+                           TRACE_RECORDER(cx)) ? 0 : 255))
 # define ENABLE_TRACER(flag)                                                  \
     JS_BEGIN_MACRO                                                            \
         bool flag_ = (flag);                                                  \
-        JS_ASSERT(flag_ == !!JS_TRACE_MONITOR(cx).recorder);                  \
+        JS_ASSERT(flag_ == !!TRACE_RECORDER(cx));                             \
         switchMask = flag_ ? 0 : 255;                                         \
     JS_END_MACRO
 #else /* !JS_TRACER */
 # define LOAD_INTERRUPT_HANDLER(cx)                                           \
     ((void) (switchMask = ((cx)->debugHooks->interruptHandler                 \
                            ? 0 : 255)))
 # define ENABLE_TRACER(flag) ((void)0)
 #endif /* !JS_TRACER */
@@ -3017,17 +3016,17 @@ js_Interpret(JSContext *cx)
                 /* Restore the calling script's interpreter registers. */
                 script = fp->script;
                 atoms = script->atomMap.vector;
 
                 /* Resume execution in the calling frame. */
                 inlineCallCount--;
                 if (JS_LIKELY(ok)) {
 #ifdef JS_TRACER
-                    if (JS_TRACE_MONITOR(cx).recorder)
+                    if (TRACE_RECORDER(cx))
                         RECORD(LeaveFrame);
 #endif
                     JS_ASSERT(js_CodeSpec[*regs.pc].length == JSOP_CALL_LENGTH);
                     len = JSOP_CALL_LENGTH;
                     DO_NEXT_OP(len);
                 }
                 goto error;
             }
@@ -3261,17 +3260,16 @@ js_Interpret(JSContext *cx)
               default:
                 JS_ASSERT(op == JSOP_FORNAME);
 
                 /*
                  * We find property here after the iterator call to ensure
                  * that we take into account side effects of the iterator
                  * call. See bug 372331.
                  */
-
                 if (!js_FindProperty(cx, id, &obj, &obj2, &prop))
                     goto error;
                 if (prop)
                     OBJ_DROP_PROPERTY(cx, obj2, prop);
 
               set_for_property:
                 /* Set the variable obj[id] to refer to rval. */
                 fp->flags |= JSFRAME_ASSIGNING;
@@ -4434,31 +4432,32 @@ js_Interpret(JSContext *cx)
                      * of property additions. And second:
                      *
                      *   o.p = x;
                      *
                      * in a frequently executed method or loop body, where p
                      * will (possibly after the first iteration) always exist
                      * in native object o.
                      */
-                    entry = &cache->table[PROPERTY_CACHE_HASH_PC(regs.pc,
-                                                                 kshape)];
+                    entry = &cache->table[PROPERTY_CACHE_HASH_PC(regs.pc, kshape)];
                     PCMETER(cache->tests++);
                     PCMETER(cache->settests++);
                     if (entry->kpc == regs.pc && entry->kshape == kshape) {
                         JSScope *scope;
 
                         JS_LOCK_OBJ(cx, obj);
                         scope = OBJ_SCOPE(obj);
                         if (scope->shape == kshape) {
                             JS_ASSERT(PCVAL_IS_SPROP(entry->vword));
                             sprop = PCVAL_TO_SPROP(entry->vword);
                             JS_ASSERT(!(sprop->attrs & JSPROP_READONLY));
                             JS_ASSERT(!SCOPE_IS_SEALED(OBJ_SCOPE(obj)));
 
+                            TRACE_2(SetPropHit, kshape, sprop);
+
                             if (scope->object == obj) {
                                 /*
                                  * Fastest path: the cached sprop is already
                                  * in scope. Just NATIVE_SET and break to get
                                  * out of the do-while(0).
                                  */
                                 if (sprop == scope->lastProp ||
                                     SCOPE_HAS_PROPERTY(scope, sprop)) {
@@ -4590,20 +4589,24 @@ js_Interpret(JSContext *cx)
                         if (obj == obj2 && !PCVAL_IS_OBJECT(entry->vword))
                             break;
                     }
                 }
 
                 if (!atom)
                     LOAD_ATOM(0);
                 id = ATOM_TO_JSID(atom);
-                if (entry
-                    ? !js_SetPropertyHelper(cx, obj, id, &rval, &entry)
-                    : !OBJ_SET_PROPERTY(cx, obj, id, &rval)) {
-                    goto error;
+                if (entry) {
+                    if (!js_SetPropertyHelper(cx, obj, id, &rval, &entry))
+                        goto error;
+                    if (entry)
+                        TRACE_1(SetPropMiss, entry);
+                } else {
+                    if (!OBJ_SET_PROPERTY(cx, obj, id, &rval))
+                        goto error;
                 }
             } while (0);
           END_SET_CASE_STORE_RVAL(JSOP_SETPROP, 2);
 
           BEGIN_CASE(JSOP_GETELEM)
             /* Open-coded ELEMENT_OP optimized for strings and dense arrays. */
             lval = FETCH_OPND(-2);
             rval = FETCH_OPND(-1);
@@ -4903,17 +4906,17 @@ js_Interpret(JSContext *cx)
                     newifp->callerRegs = regs;
                     fp->regs = &newifp->callerRegs;
                     regs.sp = newsp;
                     regs.pc = script->code;
                     newifp->frame.regs = &regs;
                     cx->fp = fp = &newifp->frame;
 
 #ifdef JS_TRACER
-                    if (JS_TRACE_MONITOR(cx).recorder)
+                    if (TRACE_RECORDER(cx))
                         RECORD(EnterFrame);
 #endif
 
                     inlineCallCount++;
                     JS_RUNTIME_METER(rt, inlineCalls);
 
 #ifdef INCLUDE_MOZILLA_DTRACE
                     /* DTrace function entry, inlines */
@@ -6116,16 +6119,18 @@ js_Interpret(JSContext *cx)
                     /*
                      * Detect a repeated property name and force a miss to
                      * share the strict warning code and cope with complexity
                      * managed by js_AddScopeProperty.
                      */
                     if (sprop->parent != scope->lastProp)
                         goto do_initprop_miss;
 
+                    TRACE_2(SetPropHit, kshape, sprop);
+
                     /*
                      * Otherwise this entry must be for a direct property of
                      * obj, not a proto-property, and there cannot have been
                      * any deletions of prior properties.
                      */
                     JS_ASSERT(PCVCAP_MAKE(sprop->shape, 0, 0) == entry->vcap);
                     JS_ASSERT(!SCOPE_HAD_MIDDLE_DELETE(scope));
                     JS_ASSERT(!scope->table ||
@@ -6181,16 +6186,18 @@ js_Interpret(JSContext *cx)
 
                 /* Set the property named by obj[id] to rval. */
                 if (!js_CheckRedeclaration(cx, obj, id, JSPROP_INITIALIZER,
                                            NULL, NULL)) {
                     goto error;
                 }
                 if (!js_SetPropertyHelper(cx, obj, id, &rval, &entry))
                     goto error;
+                if (entry)
+                    TRACE_1(SetPropMiss, entry);
             } while (0);
 
             /* Common tail for property cache hit and miss cases. */
             regs.sp--;
           END_CASE(JSOP_INITPROP);
 
           BEGIN_CASE(JSOP_INITELEM)
             /* Pop the element's value into rval. */
@@ -7021,17 +7028,17 @@ js_Interpret(JSContext *cx)
      *
      * We must not be in an inline frame. The check above ensures that for the
      * error case and for a normal return, the code jumps directly to parent's
      * frame pc.
      */
     JS_ASSERT(inlineCallCount == 0);
     JS_ASSERT(fp->regs == &regs);
 #ifdef JS_TRACER
-    if (JS_TRACE_MONITOR(cx).recorder)
+    if (TRACE_RECORDER(cx))
         js_AbortRecording(cx, regs.pc, "recording out of js_Interpret");
 #endif
     if (JS_UNLIKELY(fp->flags & JSFRAME_YIELDING)) {
         JSGenerator *gen;
 
         gen = FRAME_TO_GENERATOR(fp);
         gen->savedRegs = regs;
         gen->frame.regs = &gen->savedRegs;
@@ -7046,22 +7053,22 @@ js_Interpret(JSContext *cx)
     /* Undo the remaining effects committed on entry to js_Interpret. */
     if (script->staticDepth < JS_DISPLAY_SIZE)
         cx->display[script->staticDepth] = fp->displaySave;
     JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled == fp->pcDisabledSave);
     if (cx->version == currentVersion && currentVersion != originalVersion)
         js_SetVersion(cx, originalVersion);
     --cx->interpLevel;
 
-#ifdef JS_TRACER    
+#ifdef JS_TRACER
     if (tr) {
-        JS_TRACE_MONITOR(cx).recorder = tr;
+        SET_TRACE_RECORDER(cx, tr);
         tr->deepAbort();
     }
-#endif    
+#endif
     return ok;
 
   atom_not_defined:
     {
         const char *printable;
 
         printable = js_AtomToPrintableString(cx, atom);
         if (printable)
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -2521,35 +2521,36 @@ js_MonitorLoopEdge(JSContext* cx, jsbyte
         return false;
     default:
         /* No, this was an unusual exit (i.e. out of memory/GC), so just resume interpretation. */
         return false;
     }
 }
 
 bool
-js_MonitorRecording(JSContext* cx)
-{
-    TraceRecorder *tr = JS_TRACE_MONITOR(cx).recorder;
+js_MonitorRecording(TraceRecorder* tr)
+{
+    JSContext* cx = tr->cx;
 
     // Clear one-shot flag used to communicate between record_JSOP_CALL and record_EnterFrame.
     tr->applyingArguments = false;
 
     // Process deepAbort() requests now.
     if (tr->wasDeepAborted()) {
         js_AbortRecording(cx, NULL, "deep abort requested");
         return false;
     }
 
     jsbytecode* pc = cx->fp->regs->pc;
+
     /* If we hit a break, end the loop and generate an always taken loop exit guard. For other
        downward gotos (like if/else) continue recording. */
-    if ((*pc == JSOP_GOTO) || (*pc == JSOP_GOTOX)) {
+    if (*pc == JSOP_GOTO || *pc == JSOP_GOTOX) {
         jssrcnote* sn = js_GetSrcNote(cx->fp->script, pc);
-        if ((sn != NULL) && (SN_TYPE(sn) == SRC_BREAK)) {
+        if (sn && SN_TYPE(sn) == SRC_BREAK) {
             AUDIT(breakLoopExits);
             tr->endLoop(JS_TRACE_MONITOR(cx).fragmento);
             js_DeleteRecorder(cx);
             return false; /* done recording */
         }
     }
 
     /* An explicit return from callDepth 0 should end the loop, not abort it. */
@@ -3301,16 +3302,19 @@ TraceRecorder::map_is_native(JSObjectMap
 
 #undef OP
     ABORT_TRACE("non-native map");
 }
 
 bool
 TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2, jsuword& pcval)
 {
+    jsbytecode* pc = cx->fp->regs->pc;
+    JS_ASSERT(*pc != JSOP_INITPROP && *pc != JSOP_SETNAME && *pc != JSOP_SETPROP);
+
     // Mimic the interpreter's special case for dense arrays by skipping up one
     // hop along the proto chain when accessing a named (not indexed) property,
     // typically to find Array.prototype methods.
     JSObject* aobj = obj;
     if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
         aobj = OBJ_GET_PROTO(cx, obj);
         obj_ins = stobj_get_fslot(obj_ins, JSSLOT_PROTO);
     }
@@ -3322,17 +3326,17 @@ TraceRecorder::test_property_cache(JSObj
     // (newObjectMap == js_ObjectOps.newObjectMap) which is required to use
     // native objects (those whose maps are scopes), or even more narrow
     // conditions required because the cache miss case will call a particular
     // object-op (js_GetProperty, js_SetProperty).
     //
     // We parameterize using offsetof and guard on match against the hook at
     // the given offset in js_ObjectOps. TraceRecorder::record_JSOP_SETPROP
     // guards the js_SetProperty case.
-    uint32 format = js_CodeSpec[*cx->fp->regs->pc].format;
+    uint32 format = js_CodeSpec[*pc].format;
     uint32 mode = JOF_MODE(format);
 
     // No need to guard native-ness of global object.
     JS_ASSERT(OBJ_IS_NATIVE(globalObj));
     if (aobj != globalObj) {
         size_t op_offset = 0;
         if (mode == JOF_PROP || mode == JOF_VARPROP) {
             JS_ASSERT(!(format & JOF_SET));
@@ -3342,24 +3346,24 @@ TraceRecorder::test_property_cache(JSObj
         }
 
         if (!map_is_native(aobj->map, map_ins, ops_ins, op_offset))
             return false;
     }
 
     JSAtom* atom;
     JSPropCacheEntry* entry;
-    PROPERTY_CACHE_TEST(cx, cx->fp->regs->pc, aobj, obj2, entry, atom);
+    PROPERTY_CACHE_TEST(cx, pc, aobj, obj2, entry, atom);
     if (atom) {
         // Miss: pre-fill the cache for the interpreter, as well as for our needs.
         // FIXME: 452357 - correctly propagate exceptions into the interpreter from
         // js_FindPropertyHelper, js_LookupPropertyWithFlags, and elsewhere.
         jsid id = ATOM_TO_JSID(atom);
         JSProperty* prop;
-        if (JOF_OPMODE(*cx->fp->regs->pc) == JOF_NAME) {
+        if (JOF_OPMODE(*pc) == JOF_NAME) {
             JS_ASSERT(aobj == obj);
             if (js_FindPropertyHelper(cx, id, &obj, &obj2, &prop, &entry) < 0)
                 ABORT_TRACE("failed to find name");
         } else {
             int protoIndex = js_LookupPropertyWithFlags(cx, aobj, id, 0, &obj2, &prop);
             if (protoIndex < 0)
                 ABORT_TRACE("failed to lookup property");
 
@@ -3369,34 +3373,19 @@ TraceRecorder::test_property_cache(JSObj
             }
         }
 
         if (!prop) {
             // Propagate obj from js_FindPropertyHelper to record_JSOP_BINDNAME
             // via our obj2 out-parameter. If we are recording JSOP_SETNAME and
             // the global it's assigning does not yet exist, create it.
             obj2 = obj;
-            if (JSOp(*cx->fp->regs->pc) == JSOP_SETNAME) {
-                jsval v = JSVAL_VOID;
-                if (!js_SetPropertyHelper(cx, obj, id, &v, &entry))
-                    return false;
-                if (!entry || !PCVAL_IS_SPROP(entry->vword))
-                    ABORT_TRACE("can't create cacheable global for JSOP_SETNAME");
-                JSScopeProperty* sprop = PCVAL_TO_SPROP(entry->vword);
-                if (!SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)))
-                    ABORT_TRACE("can't create slot-ful global for JSOP_SETNAME");
-                pcval = SLOT_TO_PCVAL(sprop->slot);
-
-                // We are adding to the global object, so update its saved shape.
-                JS_ASSERT(obj == globalObj);
-                traceMonitor->globalShape = OBJ_SHAPE(obj);
-            } else {
-                // Use PCVAL_NULL to return "no such property" to our caller.
-                pcval = PCVAL_NULL;
-            }
+
+            // Use PCVAL_NULL to return "no such property" to our caller.
+            pcval = PCVAL_NULL;
             return true;
         }
 
         OBJ_DROP_PROPERTY(cx, obj2, prop);
         if (!entry)
             ABORT_TRACE("failed to fill property cache");
     }
 
@@ -3415,20 +3404,20 @@ TraceRecorder::test_property_cache(JSObj
         if (aobj != globalObj) {
             LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)),
                                       "shape");
             guard(true, addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), "guard(kshape)"),
                   MISMATCH_EXIT);
         }
     } else {
 #ifdef DEBUG
-        JSOp op = JSOp(*cx->fp->regs->pc);
+        JSOp op = JSOp(*pc);
         ptrdiff_t pcoff = (op == JSOP_GETARGPROP) ? ARGNO_LEN :
                           (op == JSOP_GETLOCALPROP) ? SLOTNO_LEN : 0;
-        jsatomid index = js_GetIndexFromBytecode(cx, cx->fp->script, cx->fp->regs->pc, pcoff);
+        jsatomid index = js_GetIndexFromBytecode(cx, cx->fp->script, pc, pcoff);
         JS_ASSERT(entry->kpc == (jsbytecode*) atoms[index]);
         JS_ASSERT(entry->kshape == jsuword(aobj));
 #endif
         if (aobj != globalObj) {
             guard(true, addName(lir->ins2i(LIR_eq, obj_ins, entry->kshape), "guard(kobj)"),
                   MISMATCH_EXIT);
         }
     }
@@ -4388,70 +4377,89 @@ bool
 TraceRecorder::record_JSOP_GETPROP()
 {
     return getProp(stackval(-1));
 }
 
 bool
 TraceRecorder::record_JSOP_SETPROP()
 {
-    jsval& r = stackval(-1);
     jsval& l = stackval(-2);
-
     if (JSVAL_IS_PRIMITIVE(l))
         ABORT_TRACE("primitive this for SETPROP");
 
     JSObject* obj = JSVAL_TO_OBJECT(l);
-
     if (obj->map->ops->setProperty != js_SetProperty)
         ABORT_TRACE("non-native JSObjectOps::setProperty");
-
-    LIns* obj_ins = get(&l);
-
-    JSPropertyCache* cache = &JS_PROPERTY_CACHE(cx);
-    uint32 kshape = OBJ_SHAPE(obj);
+    return true;
+}
+
+bool
+TraceRecorder::record_SetPropHit(uint32 kshape, JSScopeProperty* sprop)
+{
     jsbytecode* pc = cx->fp->regs->pc;
-
-    JSPropCacheEntry* entry = &cache->table[PROPERTY_CACHE_HASH_PC(pc, kshape)];
-    if (entry->kpc != pc || entry->kshape != kshape)
-        ABORT_TRACE("cache miss");
-    JS_ASSERT(PCVAL_IS_SPROP(entry->vword));
-
+    jsval& r = stackval(-1);
+    jsval& l = stackval(-2);
+
+    JS_ASSERT(!JSVAL_IS_PRIMITIVE(l));
+    JSObject* obj = JSVAL_TO_OBJECT(l);
+    LIns* obj_ins = get(&l);
+
+    if (obj == globalObj) {
+        JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)));
+        uint32 slot = sprop->slot;
+        if (!lazilyImportGlobalSlot(slot))
+            ABORT_TRACE("lazy import of global slot failed");
+
+        LIns* r_ins = get(&r);
+        set(&STOBJ_GET_SLOT(obj, slot), r_ins);
+
+        JS_ASSERT(*pc != JSOP_INITPROP);
+        if (pc[JSOP_SETPROP_LENGTH] != JSOP_POP)
+            set(&l, r_ins);
+        return true;
+    }
+
+    // The global object's shape is guarded at trace entry, all others need a guard here.
     LIns* map_ins = lir->insLoad(LIR_ldp, obj_ins, (int)offsetof(JSObject, map));
     LIns* ops_ins;
     if (!map_is_native(obj->map, map_ins, ops_ins, offsetof(JSObjectOps, setProperty)))
         return false;
 
-    // The global object's shape is guarded at trace entry.
-    if (obj != globalObj) {
-        LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape");
-        guard(true, addName(lir->ins2i(LIR_eq, shape_ins, kshape), "guard(shape)"), MISMATCH_EXIT);
-    }
+    LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape");
+    guard(true, addName(lir->ins2i(LIR_eq, shape_ins, kshape), "guard(shape)"), MISMATCH_EXIT);
 
     JSScope* scope = OBJ_SCOPE(obj);
-    JSScopeProperty* sprop = PCVAL_TO_SPROP(entry->vword);
     if (scope->object != obj || !SCOPE_HAS_PROPERTY(scope, sprop)) {
         LIns* args[] = { INS_CONSTPTR(sprop), obj_ins, cx_ins };
         LIns* ok_ins = lir->insCall(F_AddProperty, args);
         guard(false, lir->ins_eq0(ok_ins), MISMATCH_EXIT);
     }
 
     LIns* dslots_ins = NULL;
     LIns* v_ins = get(&r);
     LIns* boxed_ins = v_ins;
     if (!box_jsval(r, boxed_ins))
         return false;
     if (!native_set(obj_ins, sprop, dslots_ins, boxed_ins))
         return false;
-    if (*pc == JSOP_SETPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP)
-        stack(-2, v_ins);
+
+    if (*pc != JSOP_INITPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP)
+        set(&l, v_ins);
     return true;
 }
 
 bool
+TraceRecorder::record_SetPropMiss(JSPropCacheEntry* entry)
+{
+    JS_ASSERT(PCVAL_IS_SPROP(entry->vword));
+    return record_SetPropHit(entry->kshape, PCVAL_TO_SPROP(entry->vword));
+}
+
+bool
 TraceRecorder::record_JSOP_GETELEM()
 {
     jsval& r = stackval(-1);
     jsval& l = stackval(-2);
 
     if (JSVAL_IS_STRING(l) && JSVAL_IS_INT(r)) {
         int i;
 
@@ -5363,18 +5371,18 @@ TraceRecorder::record_JSOP_ENDINIT()
         }
     }
     return true;
 }
 
 bool
 TraceRecorder::record_JSOP_INITPROP()
 {
-    // The common code avoids stacking the RHS if op is not JSOP_SETPROP.
-    return record_JSOP_SETPROP();
+    // All the action is in record_SetPropHit.
+    return true;
 }
 
 bool
 TraceRecorder::record_JSOP_INITELEM()
 {
     return record_JSOP_SETELEM();
 }
 
@@ -5452,30 +5460,29 @@ TraceRecorder::record_JSOP_ITER()
     }
 
     ABORT_TRACE("for-in on a primitive value");
 }
 
 bool
 TraceRecorder::forInLoop(jsval* vp)
 {
-    if (!JSVAL_IS_STRING(*vp))
-        ABORT_TRACE("for-in loop variable changed type from string");
     jsval& iterobj_val = stackval(-1);
     if (!JSVAL_IS_PRIMITIVE(iterobj_val)) {
         LIns* args[] = { get(&iterobj_val), cx_ins };
         LIns* v_ins = lir->insCall(F_FastCallIteratorNext, args);
         guard(false, lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_ERROR_COOKIE)), OOM_EXIT);
 
         LIns* flag_ins = lir->ins_eq0(lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_HOLE)));
         LIns* iter_ins = get(vp);
-        if (!box_jsval(JSVAL_STRING, iter_ins))
+        jsval expected = JSVAL_IS_VOID(*vp) ? JSVAL_STRING : JSVAL_TAG(*vp);
+        if (!box_jsval(expected, iter_ins))
             return false;
         iter_ins = lir->ins_choose(flag_ins, v_ins, iter_ins, true);
-        if (!unbox_jsval(JSVAL_STRING, iter_ins))
+        if (!unbox_jsval(expected, iter_ins))
             return false;
         set(vp, iter_ins);
         stack(0, flag_ins);
         return true;
     }
 
     ABORT_TRACE("for-in on a primitive value");
 }
@@ -5549,36 +5556,28 @@ TraceRecorder::record_JSOP_BINDNAME()
 
     stack(0, obj_ins);
     return true;
 }
 
 bool
 TraceRecorder::record_JSOP_SETNAME()
 {
-    jsval& r = stackval(-1);
     jsval& l = stackval(-2);
     JS_ASSERT(!JSVAL_IS_PRIMITIVE(l));
 
     /*
      * Trace cases that are global code or in lightweight functions scoped by
      * the global object only.
      */
     JSObject* obj = JSVAL_TO_OBJECT(l);
     if (obj != cx->fp->scopeChain || obj != globalObj)
-        return false;
-
-    jsval* vp;
-    if (!name(vp))
-        return false;
-    LIns* r_ins = get(&r);
-    set(vp, r_ins);
-
-    if (cx->fp->regs->pc[JSOP_SETNAME_LENGTH] != JSOP_POP)
-        stack(-2, r_ins);
+        ABORT_TRACE("JSOP_SETNAME left operand is not the global object");
+
+    // The rest of the work is in record_SetPropHit.
     return true;
 }
 
 bool
 TraceRecorder::record_JSOP_THROW()
 {
     return false;
 }
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -329,17 +329,17 @@ class TraceRecorder {
     bool guardShapelessCallee(jsval& callee);
     bool interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc);
     bool forInLoop(jsval* vp);
 
     void trackCfgMerges(jsbytecode* pc);
     void fuseIf(jsbytecode* pc, bool cond, nanojit::LIns* x);
 
 public:
-    friend bool js_MonitorRecording(JSContext* cx);
+    friend bool js_MonitorRecording(TraceRecorder* tr);
 
     TraceRecorder(JSContext* cx, nanojit::GuardRecord*, nanojit::Fragment*, TreeInfo*,
             unsigned ngslots, uint8* globalTypeMap, uint8* stackTypeMap, 
             nanojit::GuardRecord* expectedInnerExit);
     ~TraceRecorder();
 
     uint8 determineSlotType(jsval* vp) const;
     nanojit::SideExit* snapshot(nanojit::ExitType exitType);
@@ -353,44 +353,66 @@ public:
     bool selectCallablePeerFragment(nanojit::Fragment** first);
     void prepareTreeCall(nanojit::Fragment* inner);
     void emitTreeCall(nanojit::Fragment* inner, nanojit::GuardRecord* lr);
     unsigned getCallDepth() const;
     bool trackLoopEdges();
     
     bool record_EnterFrame();
     bool record_LeaveFrame();
+    bool record_SetPropHit(uint32 kshape, JSScopeProperty* sprop);
+    bool record_SetPropMiss(JSPropCacheEntry* entry);
 
     void deepAbort() { deepAborted = true; }
     bool wasDeepAborted() { return deepAborted; }
 
 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format)               \
     bool record_##op();
 # include "jsopcode.tbl"
 #undef OPDEF
 };
 
 #define TRACING_ENABLED(cx)       JS_HAS_OPTION(cx, JSOPTION_JIT)
+#define TRACE_RECORDER(cx)        (JS_TRACE_MONITOR(cx).recorder)
+#define SET_TRACE_RECORDER(cx,tr) (JS_TRACE_MONITOR(cx).recorder = (tr))
 
-#define RECORD(x)                                                             \
+// See jsinterp.cpp for the ENABLE_TRACER definition.
+#define RECORD_ARGS(x,args)                                                   \
     JS_BEGIN_MACRO                                                            \
-        TraceRecorder* r = JS_TRACE_MONITOR(cx).recorder;                     \
-        if (!js_MonitorRecording(cx)) {                                       \
+        TraceRecorder* tr_ = TRACE_RECORDER(cx);                              \
+        if (!js_MonitorRecording(tr_))                                        \
             ENABLE_TRACER(0);                                                 \
-        } else if (!r->record_##x()) {                                        \
+        else                                                                  \
+            TRACE_ARGS_(tr_,x,args);                                          \
+    JS_END_MACRO
+
+#define TRACE_ARGS_(tr,x,args)                                                \
+    JS_BEGIN_MACRO                                                            \
+        if (!tr->record_##x args) {                                           \
             js_AbortRecording(cx, NULL, #x);                                  \
             ENABLE_TRACER(0);                                                 \
         }                                                                     \
     JS_END_MACRO
 
+#define TRACE_ARGS(x,args)                                                    \
+    JS_BEGIN_MACRO                                                            \
+        TraceRecorder* tr_ = TRACE_RECORDER(cx);                              \
+        if (tr_)                                                              \
+            TRACE_ARGS_(tr_, x, args);                                        \
+    JS_END_MACRO
+
+#define RECORD(x)               RECORD_ARGS(x, ())
+#define TRACE_1(x,a)            TRACE_ARGS(x, (a))
+#define TRACE_2(x,a,b)          TRACE_ARGS(x, (a, b))
+
 extern bool
 js_MonitorLoopEdge(JSContext* cx, jsbytecode* oldpc, uintN& inlineCallCount);
 
 extern bool
-js_MonitorRecording(JSContext* cx);
+js_MonitorRecording(TraceRecorder *tr);
 
 extern void
 js_AbortRecording(JSContext* cx, jsbytecode* abortpc, const char* reason);
 
 extern void
 js_InitJIT(JSTraceMonitor *tm);
 
 extern void