[JAEGER] Start filling out the register allocator and stub call mechanism.
authorDavid Anderson <danderson@mozilla.com>
Thu, 27 May 2010 21:26:19 -0700
changeset 52611 4b73e56e7acba439f9119356c9d0e68862609546
parent 52610 bc48920f686328efa2cf293305feb79bdb214ace
child 52612 d907911777bbc7df41486d480540338477d96d78
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone1.9.3a5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
[JAEGER] Start filling out the register allocator and stub call mechanism.
js/src/methodjit/BaseAssembler.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/MachineRegs.h
js/src/methodjit/RematInfo.h
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/methodjit/StubCompiler.cpp
js/src/methodjit/StubCompiler.h
js/src/methodjit/nunbox/Assembler.h
js/src/methodjit/nunbox/FastOps.cpp
js/src/methodjit/nunbox/FrameEntry.h
js/src/methodjit/nunbox/FrameState.cpp
js/src/methodjit/nunbox/FrameState.h
--- a/js/src/methodjit/BaseAssembler.h
+++ b/js/src/methodjit/BaseAssembler.h
@@ -39,56 +39,165 @@
  * ***** END LICENSE BLOCK ***** */
 #if !defined jsjaeger_baseassembler_h__ && defined JS_METHODJIT
 #define jsjaeger_baseassembler_h__
 
 #include "jscntxt.h"
 #include "jstl.h"
 #include "assembler/assembler/MacroAssemblerCodeRef.h"
 #include "assembler/assembler/MacroAssembler.h"
+#include "assembler/assembler/RepatchBuffer.h"
+#include "assembler/moco/MocoStubs.h"
+#include "methodjit/MethodJIT.h"
+#include "methodjit/MachineRegs.h"
 
 namespace js {
 namespace mjit {
 
+struct FrameAddress : JSC::MacroAssembler::Address
+{
+    FrameAddress(int32 offset)
+      : Address(JSC::MacroAssembler::stackPointerRegister, offset)
+    { }
+};
+
 class BaseAssembler : public JSC::MacroAssembler
 {
     struct CallPatch {
         CallPatch(ptrdiff_t distance, void *fun)
           : distance(distance), fun(fun)
         { }
 
         ptrdiff_t distance;
         JSC::FunctionPtr fun;
     };
 
+    /* Need a temp reg that is not ArgReg1. */
+#if defined(JS_CPU_X86) || defined(JS_CPU_ARM)
+    static const RegisterID ClobberInCall = JSC::X86Registers::ecx;
+#elif defined(JS_CPU_ARM)
+    static const RegisterID ClobberInCall = JSC::ARMRegisters::r2;
+#endif
+
     /* :TODO: OOM */
     Label startLabel;
     Vector<CallPatch, 64, SystemAllocPolicy> callPatches;
 
   public:
     BaseAssembler()
       : callPatches(SystemAllocPolicy())
     {
         startLabel = label();
     }
 
+    /*
+     * FpReg is used to home the current JSStackFrame*.
+     */
+#if defined(JS_CPU_X86) || defined(JS_CPU_X64)
+    static const RegisterID FpReg = JSC::X86Registers::ebx;
+#elif defined(JS_CPU_ARM)
+    static const RegisterID FpReg = JSC::X86Registers::r11;
+#endif
+
+    /*
+     * Prepares for a stub call.
+     */
+    void * getCallTarget(void *fun) {
+#ifdef JS_CPU_ARM
+        /*
+         * Insert a veneer for ARM to allow it to catch exceptions. There is no
+         * reliable way to determine the location of the return address on the
+         * stack, so it cannot be hijacked.
+         *
+         * :TODO: It wouldn't surprise me if GCC always pushes LR first. In that
+         * case, this looks like the x86-style call, and we can hijack the stack
+         * slot accordingly, thus avoiding the cost of a veneer. This should be
+         * investigated.
+         */
+
+        void *pfun = JS_FUNC_TO_DATA_PTR(void *, JaegerStubVeneer);
+
+        /*
+         * We put the real target address into IP, as this won't conflict with
+         * the EABI argument-passing mechanism. Technically, this isn't ABI-
+         * compliant.
+         */
+        move(Imm32(intptr_t(fun)), ARMRegisters::ip);
+#else
+        /*
+         * Architectures that push the return address to an easily-determined
+         * location on the stack can hijack C++'s return mechanism by overwriting
+         * that address, so a veneer is not required.
+         */
+        void *pfun = fun;
+#endif
+        return pfun;
+    }
+
+
+#define STUB_CALL_TYPE(type)                                    \
+    Call stubCall(type stub, jsbytecode *pc, uint32 fd) {       \
+        return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub),      \
+                        pc, fd);                                \
+    }
+
+    STUB_CALL_TYPE(JSObjStub);
+
+#undef STUB_CALL_TYPE
+
+    Call stubCall(void *ptr, jsbytecode *pc, uint32 frameDepth) {
+        JS_STATIC_ASSERT(ClobberInCall != Registers::ArgReg1);
+
+        void *pfun = getCallTarget(ptr);
+
+        /* PC -> regs->pc :( */
+        storePtr(ImmPtr(pc),
+                 FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, pc)));
+
+        /* sp = fp + slots() + stackDepth */
+        addPtr(Imm32(sizeof(JSStackFrame) + frameDepth * sizeof(jsval)),
+               FpReg,
+               ClobberInCall);
+
+        /* regs->sp = sp */
+        storePtr(ClobberInCall,
+                 FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, sp)));
+        
+        /* VMFrame -> ArgReg0 */
+        move(MacroAssembler::stackPointerRegister, Registers::ArgReg0);
+
+        return call(pfun);
+    }
+
     Call call(void *fun) {
 #if defined(_MSC_VER) && defined(_M_X64)
         masm.subPtr(JSC::MacroAssembler::Imm32(32),
                     JSC::MacroAssembler::stackPointerRegister);
 #endif
 
         Call cl = JSC::MacroAssembler::call();
 
 #if defined(_MSC_VER) && defined(_M_X64)
         masm.addPtr(JSC::MacroAssembler::Imm32(32),
                     JSC::MacroAssembler::stackPointerRegister);
 #endif
 
         callPatches.append(CallPatch(differenceBetween(startLabel, cl), fun));
         return cl;
     }
+
+    void finalize(uint8 *ncode) {
+        JSC::JITCode jc(ncode, size());
+        JSC::CodeBlock cb(jc);
+        JSC::RepatchBuffer repatchBuffer(&cb);
+
+        for (size_t i = 0; i < callPatches.length(); i++) {
+            JSC::MacroAssemblerCodePtr cp(ncode + callPatches[i].distance);
+            repatchBuffer.relink(JSC::CodeLocationCall(cp), callPatches[i].fun);
+        }
+    }
 };
 
 } /* namespace js */
 } /* namespace mjit */
 
 #endif
+
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -34,17 +34,18 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "MethodJIT.h"
 #include "Compiler.h"
-#include "assembler/assembler/LinkBuffer.h"
+#include "StubCalls.h"
+#include "assembler/jit/ExecutableAllocator.h"
 
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::mjit;
 
 #if defined(JS_METHODJIT_SPEW)
 static const char *OpcodeNames[] = {
@@ -120,29 +121,43 @@ mjit::Compiler::Compile()
     CHECK_STATUS(finishThisUp());
 
 #ifdef JS_METHODJIT_SPEW
     prof.stop();
     JaegerSpew(JSpew_Prof, "compilation took %d us\n", prof.time_us());
 #endif
 
     JaegerSpew(JSpew_Scripts, "successfully compiled (code \"%p\") (size \"%ld\")\n",
-               (void*)script->ncode, 0); //cr.m_size);
+               (void*)script->ncode, masm.size() + stubcc.size()); //cr.m_size);
 
     return Compile_Okay;
 }
 
 #undef CHECK_STATUS
 
 mjit::Compiler::~Compiler()
 {
     cx->free(jumpMap);
 }
 
 CompileStatus
+mjit::TryCompile(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain)
+{
+    Compiler cc(cx, script, fun, scopeChain);
+
+    JS_ASSERT(!script->ncode);
+
+    CompileStatus status = cc.Compile();
+    if (status != Compile_Okay)
+        script->ncode = JS_UNJITTABLE_METHOD;
+
+    return status;
+}
+
+CompileStatus
 mjit::Compiler::generatePrologue()
 {
 #ifdef JS_CPU_ARM
     /*
      * Unlike x86/x64, the return address is not pushed on the stack. To
      * compensate, we store the LR back into the stack on entry. This means
      * it's really done twice when called via the trampoline, but it's only
      * one instruction so probably not a big deal.
@@ -152,16 +167,55 @@ mjit::Compiler::generatePrologue()
      * except for inline calls.
      */
     masm.storePtr(ARMRegisters::lr, FrameAddress(offsetof(VMFrame, scriptedReturn)));
 #endif
 
     return Compile_Okay;
 }
 
+CompileStatus
+mjit::Compiler::generateEpilogue()
+{
+    return Compile_Okay;
+}
+
+CompileStatus
+mjit::Compiler::finishThisUp()
+{
+    for (size_t i = 0; i < branchPatches.length(); i++) {
+        Label label = labelOf(branchPatches[i].pc);
+        branchPatches[i].jump.linkTo(label, &masm);
+    }
+
+    JSC::ExecutablePool *execPool = getExecPool(masm.size() + stubcc.size());
+    if (!execPool)
+        return Compile_Abort;
+
+    uint8 *result = (uint8 *)execPool->alloc(masm.size() + stubcc.size());
+    JSC::ExecutableAllocator::makeWritable(result, masm.size() + stubcc.size());
+    memcpy(result, masm.buffer(), masm.size());
+    memcpy(result + masm.size(), stubcc.buffer(), stubcc.size());
+
+    /* Patch up stub calls. */
+    masm.finalize(result);
+    stubcc.finalize(result + masm.size());
+
+    JSC::ExecutableAllocator::makeExecutable(result, masm.size() + stubcc.size());
+    JSC::ExecutableAllocator::cacheFlush(result, masm.size() + stubcc.size());
+
+    script->ncode = result;
+#ifdef DEBUG
+    script->jitLength = masm.size() + stubcc.size();
+#endif
+    script->execPool = execPool;
+
+    return Compile_Okay;
+}
+
 #define BEGIN_CASE(name)        case name:
 #define END_CASE(name)                      \
     JS_BEGIN_MACRO                          \
         PC += name##_LENGTH;                \
     JS_END_MACRO;                           \
     break;
 
 CompileStatus
@@ -240,24 +294,31 @@ mjit::Compiler::generateMethod()
           BEGIN_CASE(JSOP_UINT16)
             frame.push(Value(Int32Tag((int32_t) GET_UINT16(PC))));
           END_CASE(JSOP_UINT16)
 
           BEGIN_CASE(JSOP_BINDNAME)
             jsop_bindname(fullAtomIndex(PC));
           END_CASE(JSOP_BINDNAME)
 
+          BEGIN_CASE(JSOP_SETNAME)
+            prepareStubCall();
+            masm.move(Imm32(fullAtomIndex(PC)), Registers::ArgReg1);
+            stubCall(stubs::SetName, Uses(2), Defs(1));
+            frame.pop();
+          END_CASE(JSOP_SETNAME)
+
           BEGIN_CASE(JSOP_UINT24)
             frame.push(Value(Int32Tag((int32_t) GET_UINT24(PC))));
           END_CASE(JSOP_UINT24)
 
           BEGIN_CASE(JSOP_STOP)
             /* Safe point! */
             cg.storeJsval(Value(UndefinedTag()),
-                          Address(FrameState::FpReg,
+                          Address(Assembler::FpReg,
                                   offsetof(JSStackFrame, rval)));
             emitReturn();
             goto done;
           END_CASE(JSOP_STOP)
 
           BEGIN_CASE(JSOP_INT8)
             frame.push(Value(Int32Tag(GET_INT8(PC))));
           END_CASE(JSOP_INT8)
@@ -330,61 +391,16 @@ mjit::Compiler::jumpInScript(Jump j, jsb
     /* :TODO: OOM failure possible here. */
 
     if (pc < PC)
         j.linkTo(jumpMap[uint32(pc - script->code)], &masm);
     else
         branchPatches.append(BranchPatch(j, pc));
 }
 
-CompileStatus
-mjit::Compiler::generateEpilogue()
-{
-    return Compile_Okay;
-}
-
-CompileStatus
-mjit::Compiler::finishThisUp()
-{
-    for (size_t i = 0; i < branchPatches.length(); i++) {
-        Label label = labelOf(branchPatches[i].pc);
-        branchPatches[i].jump.linkTo(label, &masm);
-    }
-
-    JSC::ExecutablePool *execPool = getExecPool(masm.size());
-    if (!execPool)
-        return Compile_Abort;
-
-    JSC::LinkBuffer patchBuffer(&masm, execPool);
-    JSC::MacroAssemblerCodeRef cr = patchBuffer.finalizeCode();
-
-    script->ncode = cr.m_code.executableAddress();
-#ifdef DEBUG
-    script->jitLength = masm.size();
-#endif
-    script->execPool = cr.m_executablePool;
-    cr.m_executablePool = NULL;
-
-    return Compile_Okay;
-}
-
-CompileStatus
-mjit::TryCompile(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain)
-{
-    Compiler cc(cx, script, fun, scopeChain);
-
-    JS_ASSERT(!script->ncode);
-
-    CompileStatus status = cc.Compile();
-    if (status != Compile_Okay)
-        script->ncode = JS_UNJITTABLE_METHOD;
-
-    return status;
-}
-
 void
 mjit::Compiler::jsop_setglobal(uint32 index)
 {
     JS_ASSERT(globalObj);
     uint32 slot = script->getGlobalSlot(index);
 
     FrameEntry *fe = frame.peek(-1);
     bool popped = PC[JSOP_SETGLOBAL_LENGTH] == JSOP_POP;
@@ -425,8 +441,28 @@ void
 mjit::Compiler::emitReturn()
 {
 #if defined(JS_CPU_ARM)
     masm.loadPtr(FrameAddress(offsetof(VMFrame, scriptedReturn)), ARMRegisters::lr);
 #endif
     masm.ret();
 }
 
+void
+mjit::Compiler::prepareStubCall()
+{
+    JaegerSpew(JSpew_Insns, " ---- SLOW CALL, SYNCING FRAME ---- \n");
+    frame.sync();
+    JaegerSpew(JSpew_Insns, " ---- KILLING TEMP REGS ---- \n");
+    frame.killSyncedRegs(Registers::TempRegs);
+    JaegerSpew(JSpew_Insns, " ---- FRAME SYNCING DONE ---- \n");
+}
+
+JSC::MacroAssembler::Call
+mjit::Compiler::stubCall(void *ptr, Uses uses, Defs defs)
+{
+    frame.forget(uses.nuses);
+    JaegerSpew(JSpew_Insns, " ---- CALLING STUB ---- \n");
+    Call cl = masm.stubCall(ptr, PC, frame.stackDepth() + script->nfixed);
+    JaegerSpew(JSpew_Insns, " ---- END SLOW CALL ---- \n");
+    return cl;
+}
+
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -48,30 +48,46 @@
 #include "StubCompiler.h"
 
 namespace js {
 namespace mjit {
 
 class Compiler
 {
     typedef JSC::MacroAssembler::Label Label;
+    typedef JSC::MacroAssembler::Imm32 Imm32;
     typedef JSC::MacroAssembler::ImmPtr ImmPtr;
     typedef JSC::MacroAssembler::RegisterID RegisterID;
     typedef JSC::MacroAssembler::Address Address;
     typedef JSC::MacroAssembler::Jump Jump;
+    typedef JSC::MacroAssembler::Call Call;
 
     struct BranchPatch {
         BranchPatch(const Jump &j, jsbytecode *pc)
           : jump(j), pc(pc)
         { }
 
         Jump jump;
         jsbytecode *pc;
     };
 
+    struct Uses {
+        Uses(uint32 nuses)
+          : nuses(nuses)
+        { }
+        uint32 nuses;
+    };
+
+    struct Defs {
+        Defs(uint32 ndefs)
+          : ndefs(ndefs)
+        { }
+        uint32 ndefs;
+    };
+
     JSContext *cx;
     JSScript *script;
     JSObject *scopeChain;
     JSObject *globalObj;
     JSFunction *fun;
     BytecodeAnalyzer analysis;
     Label *jumpMap;
     jsbytecode *PC;
@@ -87,16 +103,17 @@ class Compiler
     enum { LengthAtomIndex = uint32(-2) };
 
     Compiler(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain);
     ~Compiler();
 
     CompileStatus Compile();
 
     jsbytecode *getPC() { return PC; }
+    Label getLabel() { return masm.label(); }
 
   private:
     CompileStatus generatePrologue();
     CompileStatus generateMethod();
     CompileStatus generateEpilogue();
     CompileStatus finishThisUp();
 
     /* Non-emitting helpers. */
@@ -105,14 +122,27 @@ class Compiler
     void jumpInScript(Jump j, jsbytecode *pc);
     JSC::ExecutablePool *getExecPool(size_t size);
 
     /* Opcode handlers. */
     void jsop_bindname(uint32 index);
     void jsop_setglobal(uint32 index);
     void jsop_getglobal(uint32 index);
     void emitReturn();
+
+#define STUB_CALL_TYPE(type)                                            \
+    Call stubCall(type stub, Uses uses, Defs defs) {                    \
+        return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub), uses, defs); \
+    }
+
+    STUB_CALL_TYPE(JSObjStub);
+    STUB_CALL_TYPE(VoidStubUInt32);
+
+#undef STUB_CALL_TYPE
+    void prepareStubCall();
+    Call stubCall(void *ptr, Uses uses, Defs defs);
 };
 
 } /* namespace js */
 } /* namespace mjit */
 
 #endif
+
--- a/js/src/methodjit/MachineRegs.h
+++ b/js/src/methodjit/MachineRegs.h
@@ -142,16 +142,26 @@ struct Registers {
     Registers()
       : freeMask(AvailRegs)
     { }
 
     Registers(uint32 freeMask)
       : freeMask(freeMask)
     { }
 
+    Registers(const Registers &other)
+      : freeMask(other.freeMask)
+    { }
+
+    Registers & operator =(const Registers &other)
+    {
+        freeMask = other.freeMask;
+        return *this;
+    }
+
     void reset() {
         freeMask = AvailRegs;
     }
 
     bool anyRegsFree() {
         return !!freeMask;
     }
 
--- a/js/src/methodjit/RematInfo.h
+++ b/js/src/methodjit/RematInfo.h
@@ -69,16 +69,17 @@ struct RematInfo {
         synced_ = false;
     }
 
     void setMemory() {
         synced_ = true;
         location_ = PhysLoc_Memory;
     }
 
+    void setSynced() { synced_ = true; }
     void setConstant() { location_ = PhysLoc_Constant; }
 
     bool isCopy() { return location_ == PhysLoc_Copy; }
     bool isConstant() { return location_ == PhysLoc_Constant; }
     bool inRegister() { return location_ == PhysLoc_Register; }
     bool inMemory() { return location_ == PhysLoc_Memory; }
     RegisterID reg() { return reg_; }
 
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -288,8 +288,239 @@ js_InternalThrow(VMFrame &f)
         *f.oldRegs = f.regs;
         f.cx->setCurrentRegs(f.oldRegs);
         return NULL;
     }
 
     return cx->fp->script->pcToNative(pc);
 }
 
+#define NATIVE_SET(cx,obj,sprop,entry,vp)                                     \
+    JS_BEGIN_MACRO                                                            \
+        if (sprop->hasDefaultSetter() &&                                      \
+            (sprop)->slot != SPROP_INVALID_SLOT &&                            \
+            !obj->scope()->brandedOrHasMethodBarrier()) {                     \
+            /* Fast path for, e.g., plain Object instance properties. */      \
+            obj->setSlot(sprop->slot, *vp);                                   \
+        } else {                                                              \
+            if (!js_NativeSet(cx, obj, sprop, false, vp))                     \
+                THROW();                                                      \
+        }                                                                     \
+    JS_END_MACRO
+
+static inline JSObject *
+ValueToObject(JSContext *cx, Value *vp)
+{
+    if (vp->isObject())
+        return &vp->asObject();
+    if (!js_ValueToNonNullObject(cx, *vp, vp))
+        return NULL;
+    return &vp->asObject();
+}
+
+void JS_FASTCALL
+mjit::stubs::SetName(VMFrame &f, uint32 index)
+{
+    JSContext *cx = f.cx;
+
+    Value &rref = f.regs.sp[-1];
+    Value &lref = f.regs.sp[-2];
+    JSObject *obj = ValueToObject(cx, &lref);
+    if (!obj)
+        THROW();
+
+    do {
+        PropertyCache *cache = &JS_PROPERTY_CACHE(cx);
+
+        /*
+         * Probe the property cache, specializing for two important
+         * set-property cases. First:
+         *
+         *   function f(a, b, c) {
+         *     var o = {p:a, q:b, r:c};
+         *     return o;
+         *   }
+         *
+         * or similar real-world cases, which evolve a newborn native
+         * object predicatably through some bounded number 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.
+         */
+        PropertyCacheEntry *entry;
+        JSObject *obj2;
+        JSAtom *atom;
+        if (cache->testForSet(cx, f.regs.pc, obj, &entry, &obj2, &atom)) {
+            /*
+             * Fast property cache hit, only partially confirmed by
+             * testForSet. We know that the entry applies to regs.pc and
+             * that obj's shape matches.
+             *
+             * The entry predicts either a new property to be added
+             * directly to obj by this set, or on an existing "own"
+             * property, or on a prototype property that has a setter.
+             */
+            JS_ASSERT(entry->vword.isSprop());
+            JSScopeProperty *sprop = entry->vword.toSprop();
+            JS_ASSERT_IF(sprop->isDataDescriptor(), sprop->writable());
+            JS_ASSERT_IF(sprop->hasSlot(), entry->vcapTag() == 0);
+
+            JSScope *scope = obj->scope();
+            JS_ASSERT(!scope->sealed());
+
+            /*
+             * Fastest path: check whether the cached sprop is already
+             * in scope and call NATIVE_SET and break to get out of the
+             * do-while(0). But we can call NATIVE_SET only if obj owns
+             * scope or sprop is shared.
+             */
+            bool checkForAdd;
+            if (!sprop->hasSlot()) {
+                if (entry->vcapTag() == 0 ||
+                    ((obj2 = obj->getProto()) &&
+                     obj2->isNative() &&
+                     obj2->shape() == entry->vshape())) {
+                    goto fast_set_propcache_hit;
+                }
+
+                /* The cache entry doesn't apply. vshape mismatch. */
+                checkForAdd = false;
+            } else if (!scope->isSharedEmpty()) {
+                if (sprop == scope->lastProperty() || scope->hasProperty(sprop)) {
+                  fast_set_propcache_hit:
+                    PCMETER(cache->pchits++);
+                    PCMETER(cache->setpchits++);
+                    NATIVE_SET(cx, obj, sprop, entry, &rref);
+                    break;
+                }
+                checkForAdd = sprop->hasSlot() && sprop->parent == scope->lastProperty();
+            } else {
+                /*
+                 * We check that cx own obj here and will continue to
+                 * own it after js_GetMutableScope returns so we can
+                 * continue to skip JS_UNLOCK_OBJ calls.
+                 */
+                JS_ASSERT(CX_OWNS_OBJECT_TITLE(cx, obj));
+                scope = js_GetMutableScope(cx, obj);
+                JS_ASSERT(CX_OWNS_OBJECT_TITLE(cx, obj));
+                if (!scope)
+                    THROW();
+                checkForAdd = !sprop->parent;
+            }
+
+            uint32 slot;
+            if (checkForAdd &&
+                entry->vshape() == cx->runtime->protoHazardShape &&
+                sprop->hasDefaultSetter() &&
+                (slot = sprop->slot) == scope->freeslot) {
+                /*
+                 * Fast path: adding a plain old property that was once
+                 * at the frontier of the property tree, whose slot is
+                 * next to claim among the allocated slots in obj,
+                 * where scope->table has not been created yet.
+                 *
+                 * We may want to remove hazard conditions above and
+                 * inline compensation code here, depending on
+                 * real-world workloads.
+                 */
+                PCMETER(cache->pchits++);
+                PCMETER(cache->addpchits++);
+
+                /*
+                 * Beware classes such as Function that use the
+                 * reserveSlots hook to allocate a number of reserved
+                 * slots that may vary with obj.
+                 */
+                if (slot < obj->numSlots() &&
+                    !obj->getClass()->reserveSlots) {
+                    ++scope->freeslot;
+                } else {
+                    if (!js_AllocSlot(cx, obj, &slot))
+                        THROW();
+                }
+
+                /*
+                 * If this obj's number of reserved slots differed, or
+                 * if something created a hash table for scope, we must
+                 * pay the price of JSScope::putProperty.
+                 *
+                 * (A reserveSlots hook can cause scopes of the same
+                 * shape to have different freeslot values. This is
+                 * what causes the slot != sprop->slot case. See
+                 * js_GetMutableScope.)
+                 */
+                if (slot != sprop->slot || scope->table) {
+                    JSScopeProperty *sprop2 =
+                        scope->putProperty(cx, sprop->id,
+                                           sprop->getter(), sprop->setter(),
+                                           slot, sprop->attributes(),
+                                           sprop->getFlags(), sprop->shortid);
+                    if (!sprop2) {
+                        js_FreeSlot(cx, obj, slot);
+                        THROW();
+                    }
+                    sprop = sprop2;
+                } else {
+                    scope->extend(cx, sprop);
+                }
+
+                /*
+                 * No method change check here because here we are
+                 * adding a new property, not updating an existing
+                 * slot's value that might contain a method of a
+                 * branded scope.
+                 */
+                TRACE_2(SetPropHit, entry, sprop);
+                obj->lockedSetSlot(slot, rref);
+
+                /*
+                 * Purge the property cache of the id we may have just
+                 * shadowed in obj's scope and proto chains. We do this
+                 * after unlocking obj's scope to avoid lock nesting.
+                 */
+                js_PurgeScopeChain(cx, obj, sprop->id);
+                break;
+            }
+            PCMETER(cache->setpcmisses++);
+            atom = NULL;
+        } else if (!atom) {
+            /*
+             * Slower property cache hit, fully confirmed by testForSet (in
+             * the slow path, via fullTest).
+             */
+            JSScopeProperty *sprop = NULL;
+            if (obj == obj2) {
+                sprop = entry->vword.toSprop();
+                JS_ASSERT(sprop->writable());
+                JS_ASSERT(!obj2->scope()->sealed());
+                NATIVE_SET(cx, obj, sprop, entry, &rref);
+            }
+            if (sprop)
+                break;
+        }
+
+        if (!atom)
+            atom = f.fp->script->getAtom(index);
+        jsid id = ATOM_TO_JSID(atom);
+        if (entry && JS_LIKELY(obj->map->ops->setProperty == js_SetProperty)) {
+            uintN defineHow;
+            JSOp op = JSOp(*f.regs.pc);
+            if (op == JSOP_SETMETHOD)
+                defineHow = JSDNP_CACHE_RESULT | JSDNP_SET_METHOD;
+            else if (op == JSOP_SETNAME)
+                defineHow = JSDNP_CACHE_RESULT | JSDNP_UNQUALIFIED;
+            else
+                defineHow = JSDNP_CACHE_RESULT;
+            if (!js_SetPropertyHelper(cx, obj, id, defineHow, &rref))
+                THROW();
+        } else {
+            if (!obj->setProperty(cx, id, &rref))
+                THROW();
+        }
+    } while (0);
+
+    f.regs.sp[-2] = f.regs.sp[-1];
+}
+
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -44,16 +44,17 @@
 #include "MethodJIT.h"
 
 namespace js {
 namespace mjit {
 namespace stubs {
 
 void * JS_FASTCALL Return(VMFrame &f);
 JSObject * JS_FASTCALL BindName(VMFrame &f);
+void JS_FASTCALL SetName(VMFrame &f, uint32 index);
 
 }}} /* namespace stubs,mjit,js */
 
 extern "C" void *
 js_InternalThrow(js::VMFrame &f);
 
 #endif /* jslogic_h__ */
 
--- a/js/src/methodjit/StubCompiler.cpp
+++ b/js/src/methodjit/StubCompiler.cpp
@@ -52,84 +52,42 @@ StubCompiler::StubCompiler(JSContext *cx
 void
 StubCompiler::linkExit(Jump j)
 {
     /* :TODO: oom check */
     exits.append(ExitPatch(j, masm.label()));
 }
 
 void
-StubCompiler::syncAndSpill()
+StubCompiler::leave()
 {
-    frame.sync(masm);
+    JaegerSpew(JSpew_Insns, " ---- BEGIN STUB SPILL CODE ---- \n");
+    frame.sync(masm, snapshot);
+    JaegerSpew(JSpew_Insns, " ---- END STUB SPILL CODE ---- \n");
+    JaegerSpew(JSpew_Insns, " ---- BEGIN STUB CALL CODE ---- \n");
 }
 
-void *
-StubCompiler::getCallTarget(void *fun)
+void
+StubCompiler::rejoin(uint32 invalidationDepth)
 {
-#ifdef JS_CPU_ARM
-    /*
-     * Insert a veneer for ARM to allow it to catch exceptions. There is no
-     * reliable way to determine the location of the return address on the
-     * stack, so it cannot be hijacked.
-     *
-     * :TODO: It wouldn't surprise me if GCC always pushes LR first. In that
-     * case, this looks like the x86-style call, and we can hijack the stack
-     * slot accordingly, thus avoiding the cost of a veneer. This should be
-     * investigated.
-     */
-
-    void *pfun = JS_FUNC_TO_DATA_PTR(void *, JaegerStubVeneer);
-
-    /*
-     * We put the real target address into IP, as this won't conflict with
-     * the EABI argument-passing mechanism. Technically, this isn't ABI-
-     * compliant.
-     */
-    masm.move(Imm32(intptr_t(fun)), ARMRegisters::ip);
-#else
-    /*
-     * Architectures that push the return address to an easily-determined
-     * location on the stack can hijack C++'s return mechanism by overwriting
-     * that address, so a veneer is not required.
-     */
-    void *pfun = fun;
-#endif
-    return pfun;
+    JaegerSpew(JSpew_Insns, " ---- BEGIN STUB RESTORE CODE ---- \n");
+    frame.merge(masm, snapshot, invalidationDepth);
+    JaegerSpew(JSpew_Insns, " ---- END STUB RESTORE CODE ---- \n");
 }
 
 typedef JSC::MacroAssembler::RegisterID RegisterID;
 typedef JSC::MacroAssembler::ImmPtr ImmPtr;
 typedef JSC::MacroAssembler::Imm32 Imm32;
 
-/* Need a temp reg that is not ArgReg1. */
-#if defined(JS_CPU_X86) || defined(JS_CPU_ARM)
-static const RegisterID ClobberInCall = JSC::X86Registers::ecx;
-#elif defined(JS_CPU_ARM)
-static const RegisterID ClobberInCall = JSC::ARMRegisters::r2;
-#endif
-
-JS_STATIC_ASSERT(ClobberInCall != Registers::ArgReg1);
-
 JSC::MacroAssembler::Call
-StubCompiler::scall(void *ptr)
+StubCompiler::stubCall(void *ptr)
 {
-    void *pfun = getCallTarget(ptr);
-
-    /* PC -> regs->pc :( */
-    masm.storePtr(ImmPtr(cc.getPC()),
-                  FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, pc)));
-
-    /* sp = fp + slots() + stackDepth */
-    masm.addPtr(Imm32(sizeof(JSStackFrame) +
-                (frame.stackDepth() + script->nfixed) * sizeof(jsval)),
-                FrameState::FpReg, ClobberInCall);
-
-    /* regs->sp = sp */
-    masm.storePtr(ClobberInCall,
-                  FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, sp)));
-    
-    /* VMFrame -> ArgReg0 */
-    masm.move(Assembler::stackPointerRegister, Registers::ArgReg0);
-
-    return masm.call(pfun);
+    Call cl = masm.stubCall(ptr, cc.getPC(), frame.stackDepth() + script->nfixed);
+    JaegerSpew(JSpew_Insns, " ---- END STUB CALL CODE ---- \n");
+    return cl;
 }
 
+void
+StubCompiler::finalize(uint8* ncode)
+{
+    masm.finalize(ncode);
+}
+
--- a/js/src/methodjit/StubCompiler.h
+++ b/js/src/methodjit/StubCompiler.h
@@ -47,20 +47,16 @@
 #include "methodjit/nunbox/Assembler.h"
 #include "CodeGenIncludes.h"
 
 namespace js {
 namespace mjit {
 
 class Compiler;
 
-struct StubCallInfo {
-    uint32 numSpills;
-};
-
 class StubCompiler
 {
     typedef JSC::MacroAssembler::Call Call;
     typedef JSC::MacroAssembler::Jump Jump;
     typedef JSC::MacroAssembler::Label Label;
 
     struct ExitPatch {
         ExitPatch(Jump from, Label to)
@@ -72,27 +68,58 @@ class StubCompiler
     };
 
     JSContext *cx;
     Compiler &cc;
     FrameState &frame;
     JSScript *script;
     Assembler masm;
     Vector<ExitPatch, 64, SystemAllocPolicy> exits;
+    RegSnapshot snapshot;
 
   public:
     StubCompiler(JSContext *cx, mjit::Compiler &cc, FrameState &frame, JSScript *script);
-    void linkExit(Jump j);
-    void syncAndSpill();
-    Call call(JSObjStub stub) {
-        return scall(JS_FUNC_TO_DATA_PTR(void *, stub));
+
+    size_t size() {
+        return masm.size();
+    }
+
+    uint8 *buffer() {
+        return masm.buffer();
+    }
+
+#define STUB_CALL_TYPE(type)                                    \
+    Call call(type stub) {                                      \
+        return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub));     \
     }
 
+    STUB_CALL_TYPE(JSObjStub);
+
+#undef STUB_CALL_TYPE
+
+    /* Patches a jump target into the slow path. */
+    void linkExit(Jump j);
+
+    /*
+     * Emits code into the slow-path stream that sync all outstanding state
+     * to memory.
+     */
+    void leave();
+
+    /*
+     * Rejoins slow-path code back to the fast-path. The invalidation param
+     * specifies how many stack slots below sp must not be reloaded from
+     * registers.
+     */
+    void rejoin(uint32 invalidationDepth);
+
+    /* Finish all native code patching. */
+    void finalize(uint8 *ncode);
+
   private:
-    Call scall(void *ptr);
-    void *getCallTarget(void *fun);
+    Call stubCall(void *ptr);
 };
 
 } /* namepsace mjit */
 } /* namespace js */
 
 #endif /* jsstub_compiler_h__ */
 
--- a/js/src/methodjit/nunbox/Assembler.h
+++ b/js/src/methodjit/nunbox/Assembler.h
@@ -42,15 +42,46 @@
 
 #include "methodjit/BaseAssembler.h"
 
 namespace js {
 namespace mjit {
 
 class Assembler : public BaseAssembler
 {
+    static const uint32 PAYLOAD_OFFSET = 0;
+    static const uint32 TAG_OFFSET     = 4;
+
   public:
+    Address payloadOf(Address address) {
+        return address;
+    }
+
+    void loadTypeTag(Address address, RegisterID reg) {
+        load32(Address(address.base, address.offset + TAG_OFFSET), reg);
+    }
+
+    void storeTypeTag(Imm32 imm, Address address) {
+        store32(imm, Address(address.base, address.offset + TAG_OFFSET));
+    }
+
+    void storeTypeTag(RegisterID reg, Address address) {
+        store32(reg, Address(address.base, address.offset + TAG_OFFSET));
+    }
+
+    void loadData32(Address address, RegisterID reg) {
+        load32(Address(address.base, address.offset + PAYLOAD_OFFSET), reg);
+    }
+
+    void storeData32(Imm32 imm, Address address) {
+        store32(imm, Address(address.base, address.offset + PAYLOAD_OFFSET));
+    }
+
+    void storeData32(RegisterID reg, Address address) {
+        store32(reg, Address(address.base, address.offset + PAYLOAD_OFFSET));
+    }
 };
 
 } /* namespace js */
 } /* namespace mjit */
 
 #endif
+
--- a/js/src/methodjit/nunbox/FastOps.cpp
+++ b/js/src/methodjit/nunbox/FastOps.cpp
@@ -41,31 +41,27 @@
 #include "methodjit/Compiler.h"
 #include "methodjit/StubCalls.h"
 
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::mjit;
 
-static const uint32 TAG_OFFSET     = 4;
-static const uint32 PAYLOAD_OFFSET = 0;
-
 void
 mjit::Compiler::jsop_bindname(uint32 index)
 {
     RegisterID reg = frame.allocReg();
-    masm.loadPtr(Address(FrameState::FpReg, offsetof(JSStackFrame, scopeChain)), reg);
+    masm.loadPtr(Address(Assembler::FpReg, offsetof(JSStackFrame, scopeChain)), reg);
 
     Address address(reg, offsetof(JSObject, fslots) + JSSLOT_PARENT * sizeof(jsval));
 
-    masm.load32(Address(address.base, address.offset + PAYLOAD_OFFSET), reg);
-    Jump j = masm.branchTestPtr(Assembler::Zero, reg, reg);
+    Jump j = masm.branchPtr(Assembler::NotEqual, masm.payloadOf(address), ImmPtr(0));
 
-    {
-        stubcc.linkExit(j);
-        stubcc.syncAndSpill();
-        stubcc.call(stubs::BindName);
-    }
+    stubcc.linkExit(j);
+    stubcc.leave();
+    stubcc.call(stubs::BindName);
 
     frame.pushObject(reg);
+
+    stubcc.rejoin(1);
 }
 
--- a/js/src/methodjit/nunbox/FrameEntry.h
+++ b/js/src/methodjit/nunbox/FrameEntry.h
@@ -67,21 +67,22 @@ class FrameEntry
         return Valueify(v_.asBits);
     }
 
     bool isTypeConstant() {
         return type.isConstant();
     }
 
     uint32 getTypeTag() {
-#if 0
-        return v_.mask;
-#else
         return v_.s.mask32;
-#endif
+    }
+
+    uint32 getPayload32() {
+        JS_ASSERT(!Valueify(v_.asBits).isDouble());
+        return v_.s.payload.u32;
     }
 
     uint32 copyOf() {
         JS_ASSERT(type.isCopy() || data.isCopy());
         return index_;
     }
 
   private:
--- a/js/src/methodjit/nunbox/FrameState.cpp
+++ b/js/src/methodjit/nunbox/FrameState.cpp
@@ -102,61 +102,133 @@ FrameState::invalidate(FrameEntry *fe)
     if (!fe->type.synced()) {
         JS_NOT_REACHED("wat");
     }
     if (!fe->data.synced()) {
         JS_NOT_REACHED("wat2");
     }
     fe->type.setMemory();
     fe->data.setMemory();
-    fe->copies = 0;
+}
+
+void
+FrameState::reset(FrameEntry *fe)
+{
+    fe->type.setMemory();
+    fe->data.setMemory();
 }
 
 void
 FrameState::flush()
 {
     for (FrameEntry *fe = base; fe < sp; fe++)
         invalidate(fe);
 }
 
 void
-FrameState::syncRegister(Assembler &masm, RegisterID reg, const RegState &state) const
+FrameState::killSyncedRegs(uint32 mask)
 {
-    JS_NOT_REACHED("bleh");
+    /* Subtract any regs we haven't allocated. */
+    Registers regs(mask & ~(regalloc.freeMask));
+
+    while (regs.anyRegsFree()) {
+        RegisterID reg = regs.allocReg();
+        if (!regstate[reg].tracked)
+            continue;
+        regstate[reg].tracked = false;
+        regalloc.freeReg(reg);
+        FrameEntry &fe = base[regstate[reg].index];
+        RematInfo *mat;
+        if (regstate[reg].part == RegState::Part_Type)
+            mat = &fe.type;
+        else
+            mat = &fe.data;
+        JS_ASSERT(mat->inRegister() && mat->reg() == reg);
+        JS_ASSERT(mat->synced());
+        mat->setMemory();
+    }
 }
 
 void
 FrameState::syncType(FrameEntry *fe, Assembler &masm) const
 {
+    JS_ASSERT(!fe->type.synced() && !fe->type.inMemory());
+    if (fe->type.isConstant())
+        masm.storeTypeTag(Imm32(fe->getTypeTag()), addressOf(fe));
+    else
+        masm.storeTypeTag(fe->type.reg(), addressOf(fe));
+    fe->type.setSynced();
 }
 
 void
 FrameState::syncData(FrameEntry *fe, Assembler &masm) const
 {
+    JS_ASSERT(!fe->data.synced() && !fe->data.inMemory());
+    if (fe->data.isConstant())
+        masm.storeData32(Imm32(fe->getPayload32()), addressOf(fe));
+    else
+        masm.storeData32(fe->data.reg(), addressOf(fe));
+    fe->data.setSynced();
 }
 
 void
-FrameState::sync(Assembler &masm) const
+FrameState::sync(Assembler &masm, RegSnapshot &snapshot) const
 {
     for (FrameEntry *fe = base; fe < sp; fe++) {
         if (fe->type.needsSync())
             syncType(fe, masm);
         if (fe->data.needsSync())
             syncData(fe, masm);
     }
+
+    JS_STATIC_ASSERT(sizeof(snapshot.regs) == sizeof(regstate));
+    JS_STATIC_ASSERT(sizeof(RegState) == sizeof(uint32));
+    memcpy(snapshot.regs, regstate, sizeof(regstate));
+    snapshot.alloc = regalloc;
 }
 
 void
-FrameState::restoreTempRegs(Assembler &masm) const
+FrameState::merge(Assembler &masm, const RegSnapshot &snapshot, uint32 invalidationDepth) const
 {
-#if 0
-    /* Get a mask of all allocated registers that must be synced. */
-    Registers temps = regalloc.freeMask & Registers::TempRegs;
+    uint32 threshold = uint32(sp - base) - invalidationDepth;
+
+    /*
+     * We must take care not to accidentally clobber registers while merging
+     * state. For example, if slot entry #1 has moved from EAX to EDX, and
+     * slot entry #2 has moved from EDX to EAX, then the transition cannot
+     * be completed with:
+     *      mov eax, edx
+     *      mov edx, eax
+     *
+     * This case doesn't need to be super fast, but we do want to detect it
+     * quickly. To do this, we create a register allocator with the snapshot's
+     * used registers, and incrementally free them.
+     */
+    Registers depends(snapshot.alloc);
+
+    for (uint32 i = 0; i < MacroAssembler::TotalRegisters; i++) {
+        if (!regstate[i].tracked)
+            continue;
 
-    while (temps.anyRegsFree()) {
-        RegisterID reg = temps.allocReg();
-        if (!regstate[reg].tracked)
-            continue;
-        syncRegister(masm, reg, regstate[reg]);
+        if (regstate[i].index >= threshold) {
+            /*
+             * If the register is within the invalidation threshold, do a
+             * normal load no matter what. This is guaranteed to be safe
+             * because nothing above the invalidation threshold will yet
+             * be in a register.
+             */
+            FrameEntry *fe = &base[regstate[i].index];
+            Address address = addressOf(fe);
+            RegisterID reg = RegisterID(i);
+            if (regstate[i].part == RegState::Part_Type) {
+                masm.loadTypeTag(address, reg);
+            } else {
+                /* :TODO: assert better */
+                JS_ASSERT(fe->isTypeConstant());
+                masm.loadData32(address, reg);
+            }
+        } else if (regstate[i].index != snapshot.regs[i].index ||
+                   regstate[i].part != snapshot.regs[i].part) {
+            JS_NOT_REACHED("say WAT");
+        }
     }
-#endif
 }
 
--- a/js/src/methodjit/nunbox/FrameState.h
+++ b/js/src/methodjit/nunbox/FrameState.h
@@ -47,176 +47,201 @@
 
 namespace js {
 namespace mjit {
 
 enum TypeInfo {
     Type_Unknown
 };
 
-struct FrameAddress : JSC::MacroAssembler::Address
-{
-    FrameAddress(int32 offset)
-      : Address(JSC::MacroAssembler::stackPointerRegister, offset)
+struct RegState {
+    enum ValuePart {
+        Part_Type,
+        Part_Data
+    };
+
+    RegState()
+      : tracked(false)
     { }
+
+    RegState(ValuePart part, uint32 index, bool spillable)
+      : index(index), part(part), spillable(spillable), tracked(true)
+    { }
+
+    uint16 index   : 16;
+    ValuePart part : 1;
+    bool spillable : 1;
+    bool tracked   : 1;
+};
+
+struct RegSnapshot {
+    RegState regs[JSC::MacroAssembler::TotalRegisters];
+    Registers alloc;
 };
 
 class FrameState
 {
     typedef JSC::MacroAssembler::RegisterID RegisterID;
     typedef JSC::MacroAssembler::Address Address;
+    typedef JSC::MacroAssembler::Imm32 Imm32;
     typedef JSC::MacroAssembler MacroAssembler;
 
-    struct RegState {
-        enum ValuePart {
-            Part_Type,
-            Part_Data
-        };
-
-        uint32     index;
-        ValuePart  part;
-        bool       spillable;
-        bool       tracked;
-    };
-
   public:
     FrameState(JSContext *cx, JSScript *script, Assembler &masm)
       : cx(cx), script(script), masm(masm), base(NULL)
     { }
     ~FrameState();
 
-#if defined(JS_CPU_X86) || defined(JS_CPU_X64)
-    static const RegisterID FpReg = JSC::X86Registers::ebx;
-#elif defined(JS_CPU_ARM)
-    static const RegisterID FpReg = JSC::X86Registers::r11;
-#endif
-
   public:
     bool init(uint32 nargs);
 
     RegisterID allocReg() {
         if (!regalloc.anyRegsFree())
             evictSomething();
         RegisterID reg = regalloc.allocReg();
         regstate[reg].tracked = false;
         return reg;
     }
 
     void freeReg(RegisterID reg) {
         JS_ASSERT(!regstate[reg].tracked);
         regalloc.freeReg(reg);
     }
 
+    void incSp() {
+        sp++;
+    }
+
+    void decSp() {
+        sp--;
+    }
+
     /*
      * Push a type register, unsycned, with unknown payload.
      */
     void pushUnknownType(RegisterID reg) {
         sp[0].type.setRegister(reg);
         sp[0].data.setMemory();
         sp[0].copies = 0;
-        regstate[reg].tracked = true;
-        regstate[reg].index = uint32(sp - base);
-        regstate[reg].part = RegState::Part_Type;
-        sp++;
+        regstate[reg] = RegState(RegState::Part_Type, uint32(sp - base), true);
+        incSp();
     }
 
     void push(const Value &v) {
         push(Jsvalify(v));
     }
 
     void push(const jsval &v) {
         sp[0].setConstant(v);
         sp[0].copies = 0;
-        sp++;
+        incSp();
         JS_ASSERT(sp - locals <= script->nslots);
     }
 
     void pushObject(RegisterID reg) {
         sp[0].type.setConstant();
         sp[0].v_.s.mask32 = JSVAL_MASK32_NONFUNOBJ;
         sp[0].data.setRegister(reg);
         sp[0].copies = 0;
-        regstate[reg].tracked = true;
-        regstate[reg].part = RegState::Part_Data;
-        regstate[reg].index = uint32(sp - base);
-        sp++;
+        regstate[reg] = RegState(RegState::Part_Data, uint32(sp - base), true);
+        incSp();
     }
 
     FrameEntry *peek(int32 depth) {
         JS_ASSERT(depth < 0);
         JS_ASSERT(sp + depth >= locals + script->nfixed);
         return &sp[depth];
     }
 
     void pop() {
         FrameEntry *vi = peek(-1);
         if (!vi->isConstant()) {
             if (vi->type.inRegister())
                 regalloc.freeReg(vi->type.reg());
             if (vi->data.inRegister())
                 regalloc.freeReg(vi->data.reg());
         }
-        sp--;
+        decSp();
     }
 
     Address topOfStack() {
-        return Address(FpReg, sizeof(JSStackFrame) +
-                              (script->nfixed + stackDepth()) * sizeof(Value));
+        return Address(Assembler::FpReg, sizeof(JSStackFrame) +
+                       (script->nfixed + stackDepth()) * sizeof(Value));
     }
 
     uint32 stackDepth() {
         return sp - (locals + script->nfixed);
     }
 
     RegisterID tempRegForType(FrameEntry *fe) {
         JS_ASSERT(!fe->type.isConstant());
         if (fe->type.inRegister())
             return fe->type.reg();
         JS_NOT_REACHED("wat");
     }
 
-    Address addressOf(FrameEntry *fe) {
+    Address addressOf(FrameEntry *fe) const {
         JS_ASSERT(fe >= locals);
         if (fe >= locals) {
-            return Address(FpReg, sizeof(JSStackFrame) +
-                                  (fe - locals) * script->nfixed);
+            return Address(Assembler::FpReg, sizeof(JSStackFrame) +
+                           (fe - locals) * sizeof(Value));
         }
-        return Address(FpReg, 0);
+        return Address(Assembler::FpReg, 0);
     }
 
     void forceStackDepth(uint32 newDepth) {
         uint32 oldDepth = stackDepth();
         FrameEntry *spBase = locals + script->nfixed;
         sp = spBase + newDepth;
         if (oldDepth <= newDepth)
             return;
         memset(spBase, 0, sizeof(FrameEntry) * (newDepth - oldDepth));
     }
 
+    void forget(uint32 depth) {
+        JS_ASSERT(sp - depth  >= locals + script->nfixed);
+    }
+
+    void sync() {
+        for (FrameEntry *fe = base; fe < sp; fe++) {
+            if (fe->type.needsSync()) {
+                syncType(fe, masm);
+                fe->type.setSynced();
+            }
+            if (fe->data.needsSync()) {
+                syncData(fe, masm);
+                fe->type.setSynced();
+            }
+        }
+    }
+
     void flush();
+    void killSyncedRegs(uint32 mask);
     void assertValidRegisterState();
-    void sync(Assembler &masm) const;
+    void sync(Assembler &masm, RegSnapshot &snapshot) const;
+    void merge(Assembler &masm, const RegSnapshot &snapshot, uint32 invalidationDepth) const;
     void restoreTempRegs(Assembler &masm) const;
 
   private:
     void syncType(FrameEntry *fe, Assembler &masm) const;
     void syncData(FrameEntry *fe, Assembler &masm) const;
-    void syncRegister(Assembler &masm, RegisterID reg, const RegState &state) const;
     void evictSomething();
     void invalidate(FrameEntry *fe);
+    void reset(FrameEntry *fe);
     RegisterID getDataReg(FrameEntry *vi, FrameEntry *backing);
 
   private:
     JSContext *cx;
     JSScript *script;
     Assembler &masm;
     FrameEntry *base;
     FrameEntry *locals;
     FrameEntry *args;
     FrameEntry *sp;
+
     Registers regalloc;
     RegState  regstate[MacroAssembler::TotalRegisters];
 };
 
 } /* namespace mjit */
 } /* namespace js */
 
 #endif /* jsjaeger_framestate_h__ */