Bug 659577 - emit ScopeCoordinate::hops (r=waldo)
authorLuke Wagner <luke@mozilla.com>
Wed, 11 Apr 2012 18:09:20 -0700
changeset 99538 ee37d2b88eeba770e3a835e618cfb9daec4ef870
parent 99537 35d64eea83855220293dea156146b5dd1f070d02
child 99539 ddc63b39ab57c25f0b4a3fa8c2637bb48bfac34f
push idunknown
push userunknown
push dateunknown
reviewerswaldo
bugs659577
milestone15.0a1
Bug 659577 - emit ScopeCoordinate::hops (r=waldo)
js/src/frontend/BytecodeEmitter.cpp
js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsinterp.cpp
js/src/jsopcode.tbl
js/src/methodjit/Compiler.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -828,53 +828,95 @@ EmitUnaliasedVarOp(JSContext *cx, JSOp o
     ptrdiff_t off = EmitN(cx, bce, op, sizeof(uint16_t));
     if (off < 0)
         return false;
     SET_UINT16(bce->code(off), slot);
     return true;
 }
 
 static bool
-EmitAliasedVarOp(JSContext *cx, JSOp op, uint16_t binding, JSAtom *atom, BytecodeEmitter *bce)
+EmitAliasedVarOp(JSContext *cx, JSOp op, ScopeCoordinate sc, JSAtom *atom, BytecodeEmitter *bce)
 {
     JS_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD);
 
-    /*
-     * XXX This is temporary: bug 659577 will need to compute the number of
-     * cloned block objects to hop over.
-     */
-    uint16_t hops = 0;
-
     jsatomid atomIndex;
     if (!bce->makeAtomIndex(atom, &atomIndex))
         return false;
 
     bool decomposed = js_CodeSpec[op].format & JOF_DECOMPOSE;
-    unsigned n = 2 * sizeof(uint16_t) + sizeof(uint32_t) + (decomposed ? 1 : 0);
+    unsigned n = 2 * sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint16_t) + (decomposed ? 1 : 0);
+    JS_ASSERT(int(n) + 1 /* op */ == js_CodeSpec[op].length);
 
     ptrdiff_t off = EmitN(cx, bce, op, n);
     if (off < 0)
         return false;
 
     jsbytecode *pc = bce->code(off);
-    SET_UINT16(pc, hops);
+    SET_UINT16(pc, sc.hops);
     pc += sizeof(uint16_t);
-    SET_UINT16(pc, binding);
+    SET_UINT16(pc, sc.binding);
     pc += sizeof(uint16_t);
     SET_UINT32_INDEX(pc, atomIndex);
+    pc += sizeof(uint32_t);
+    SET_UINT16(pc, sc.frameBinding);
     return true;
 }
 
+static unsigned
+ClonedBlockDepth(BytecodeEmitter *bce)
+{
+    unsigned clonedBlockDepth = 0;
+    for (StaticBlockObject *b = bce->sc->blockChain; b; b = b->enclosingBlock()) {
+        if (b->needsClone())
+            ++clonedBlockDepth;
+    }
+
+    return clonedBlockDepth;
+}
+
 static bool
 EmitAliasedVarOp(JSContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce)
 {
-    uint16_t binding = JOF_OPTYPE(pn->getOp()) == JOF_QARG
-                       ? bce->sc->bindings.argToBinding(pn->pn_cookie.slot())
-                       : bce->sc->bindings.localToBinding(pn->pn_cookie.slot());
-    return EmitAliasedVarOp(cx, op, binding, pn->atom(), bce);
+    /*
+     * The contents of the dynamic scope chain (fp->scopeChain) exactly reflect
+     * the needsClone-subset of the block chain. Use this to determine the
+     * number of ClonedBlockObjects on fp->scopeChain to skip to find the scope
+     * object containing the var to which pn is bound. ALIASEDVAR ops cannot
+     * reach across with scopes so ClonedBlockObjects is the only NestedScope
+     * on the scope chain.
+     */
+    ScopeCoordinate sc;
+    if (JOF_OPTYPE(pn->getOp()) == JOF_QARG) {
+        JS_ASSERT(bce->sc->funIsHeavyweight());
+        sc.hops = ClonedBlockDepth(bce);
+        sc.binding = bce->sc->bindings.argToBinding(pn->pn_cookie.slot());
+        sc.frameBinding = sc.binding;
+    } else {
+        JS_ASSERT(JOF_OPTYPE(pn->getOp()) == JOF_LOCAL || pn->isKind(PNK_FUNCTION));
+        unsigned local = pn->pn_cookie.slot();
+        sc.frameBinding = bce->sc->bindings.localToBinding(local);
+        if (local < bce->sc->bindings.numVars()) {
+            JS_ASSERT(bce->sc->funIsHeavyweight());
+            sc.hops = ClonedBlockDepth(bce);
+            sc.binding = sc.frameBinding;
+        } else {
+            unsigned depth = local - bce->sc->bindings.numVars();
+            unsigned hops = 0;
+            StaticBlockObject *b = bce->sc->blockChain;
+            while (!b->containsVarAtDepth(depth)) {
+                if (b->needsClone())
+                    hops++;
+                b = b->enclosingBlock();
+            }
+            sc.hops = hops;
+            sc.binding = depth - b->stackDepth();
+        }
+    }
+
+    return EmitAliasedVarOp(cx, op, sc, pn->atom(), bce);
 }
 
 static bool
 EmitVarOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
 {
     JS_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_NAME));
     JS_ASSERT_IF(pn->isKind(PNK_NAME), JOF_OPTYPE(op) == JOF_QARG || JOF_OPTYPE(op) == JOF_LOCAL);
     JS_ASSERT(!pn->pn_cookie.isFree());
@@ -1026,21 +1068,19 @@ EmitEnterBlock(JSContext *cx, BytecodeEm
     int depthPlusFixed = AdjustBlockSlot(cx, bce, depth);
     if (depthPlusFixed < 0)
         return false;
 
     for (unsigned i = 0; i < blockObj->slotCount(); i++) {
         Definition *dn = blockObj->maybeDefinitionParseNode(i);
 
         /* Beware the empty destructuring dummy. */
-        if (!dn) {
-            JS_ASSERT(i + 1 <= blockObj->slotCount());
+        if (!dn)
             blockObj->setAliased(i, bce->sc->bindingsAccessedDynamically());
             continue;
-        }
 
         JS_ASSERT(dn->isDefn());
         JS_ASSERT(unsigned(dn->frameSlot() + depthPlusFixed) < JS_BIT(16));
         dn->pn_cookie.set(dn->pn_cookie.level(), uint16_t(dn->frameSlot() + depthPlusFixed));
 #ifdef DEBUG
         for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
             JS_ASSERT(pnu->pn_lexdef == dn);
             JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
@@ -2586,19 +2626,22 @@ frontend::EmitFunctionScript(JSContext *
      */
 
     if (bce->sc->funArgumentsHasLocalBinding()) {
         JS_ASSERT(bce->next() == bce->base());  /* See JSScript::argumentsBytecode. */
         bce->switchToProlog();
         if (Emit1(cx, bce, JSOP_ARGUMENTS) < 0)
             return false;
         if (bce->sc->bindingsAccessedDynamically()) {
+            ScopeCoordinate sc;
+            sc.hops = 0;
+            sc.binding = bce->sc->bindings.localToBinding(bce->sc->argumentsLocalSlot());
+            sc.frameBinding = sc.binding;
             JSAtom *atom = cx->runtime->atomState.argumentsAtom;
-            uint16_t binding = bce->sc->bindings.localToBinding(bce->sc->argumentsLocalSlot());
-            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, binding, atom, bce))
+            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, atom, bce))
                 return false;
         } else {
             if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, bce->sc->argumentsLocalSlot(), bce))
                 return false;
         }
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         bce->switchToMain();
--- a/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
@@ -1,14 +1,14 @@
 // |jit-test| debug
 setDebug(true);
 x = "notset";
 function main() {
   /* The JSOP_STOP in main. */
-  a = { valueOf: function () { trap(main, 95, "success()"); } };
+  a = { valueOf: function () { trap(main, 97, "success()"); } };
   b = "";
   eval();
   a + b;
   x = "failure";
 }
 function success() { x = "success"; }
 
 main();
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -315,23 +315,29 @@ ScriptAnalysis::analyzeBytecode(JSContex
           case JSOP_SETNAME:
           case JSOP_DELNAME:
             usesScopeChain_ = true;
             isInlineable = false;
             break;
 
           case JSOP_GETALIASEDVAR:
           case JSOP_CALLALIASEDVAR:
-          case JSOP_SETALIASEDVAR:
+          case JSOP_SETALIASEDVAR: {
             JS_ASSERT(!isInlineable);
             usesScopeChain_ = true;
+
             /* XXX: this can be removed after bug 659577. */
-            if (ScopeCoordinate(pc).binding >= script->nfixed)
+            ScopeCoordinate sc(pc);
+            if (script->bindings.bindingIsLocal(sc.frameBinding) &&
+                script->bindings.bindingToLocal(sc.frameBinding) >= script->nfixed)
+            {
                 localsAliasStack_ = true;
+            }
             break;
+          }
 
           case JSOP_DEFFUN:
           case JSOP_DEFVAR:
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             extendsScope_ = true;
             isInlineable = canTrackVars = false;
             break;
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -359,19 +359,19 @@ static inline uint32_t GetBytecodeSlot(J
       case JSOP_LOCALDEC:
         return LocalSlot(script, GET_SLOTNO(pc));
 
       case JSOP_GETALIASEDVAR:
       case JSOP_CALLALIASEDVAR:
       case JSOP_SETALIASEDVAR:
       {
           ScopeCoordinate sc = ScopeCoordinate(pc);
-          return script->bindings.bindingIsArg(sc.binding)
-                 ? ArgSlot(script->bindings.bindingToArg(sc.binding))
-                 : LocalSlot(script, script->bindings.bindingToLocal(sc.binding));
+          return script->bindings.bindingIsArg(sc.frameBinding)
+                 ? ArgSlot(script->bindings.bindingToArg(sc.frameBinding))
+                 : LocalSlot(script, script->bindings.bindingToLocal(sc.frameBinding));
       }
 
 
       case JSOP_THIS:
         return ThisSlot();
 
       default:
         JS_NOT_REACHED("Bad slot opcode");
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -875,36 +875,16 @@ CheckLocalAccess(StackFrame *fp, unsigne
 
 static inline void
 CheckArgAccess(StackFrame *fp, unsigned index)
 {
     JS_ASSERT(fp->script()->formalLivesInArgumentsObject(index) ==
               fp->script()->argsObjAliasesFormals());
 }
 
-/*
- * This function is temporary. Bug 659577 will change all ALIASEDVAR
- * access to use the scope chain instead.
- */
-static inline Value &
-AliasedVar(StackFrame *fp, ScopeCoordinate sc)
-{
-    JSScript *script = fp->script();
-#ifdef DEBUG
-    JS_ASSERT(sc.hops == 0);  /* Temporary */
-    if (script->bindings.bindingIsArg(sc.binding))
-        JS_ASSERT(script->formalLivesInCallObject(script->bindings.bindingToArg(sc.binding)));
-    else
-        CheckLocalAccess(fp, script->bindings.bindingToLocal(sc.binding), true);
-#endif
-    return script->bindings.bindingIsArg(sc.binding)
-           ? fp->formalArg(script->bindings.bindingToArg(sc.binding))
-           : fp->localSlot(script->bindings.bindingToLocal(sc.binding));
-}
-
 #define PUSH_COPY(v)             do { *regs.sp++ = v; assertSameCompartment(cx, regs.sp[-1]); } while (0)
 #define PUSH_COPY_SKIP_CHECK(v)  *regs.sp++ = v
 #define PUSH_NULL()              regs.sp++->setNull()
 #define PUSH_UNDEFINED()         regs.sp++->setUndefined()
 #define PUSH_BOOLEAN(b)          regs.sp++->setBoolean(b)
 #define PUSH_DOUBLE(d)           regs.sp++->setDouble(d)
 #define PUSH_INT32(i)            regs.sp++->setInt32(i)
 #define PUSH_STRING(s)           do { regs.sp++->setString(s); assertSameCompartment(cx, regs.sp[-1]); } while (0)
@@ -1173,16 +1153,18 @@ js::Interpret(JSContext *cx, StackFrame 
 # define END_CASE_LEN3      len = 3; goto advance_pc;
 # define END_CASE_LEN4      len = 4; goto advance_pc;
 # define END_CASE_LEN5      len = 5; goto advance_pc;
 # define END_CASE_LEN6      len = 6; goto advance_pc;
 # define END_CASE_LEN7      len = 7; goto advance_pc;
 # define END_CASE_LEN8      len = 8; goto advance_pc;
 # define END_CASE_LEN9      len = 9; goto advance_pc;
 # define END_CASE_LEN10     len = 10; goto advance_pc;
+# define END_CASE_LEN11     len = 11; goto advance_pc;
+# define END_CASE_LEN12     len = 12; goto advance_pc;
 # define END_VARLEN_CASE    goto advance_pc;
 # define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP)
 # define END_EMPTY_CASES    goto advance_pc_by_one;
 
 #endif /* !JS_THREADED_INTERP */
 
 #define ENABLE_INTERRUPTS() (interruptEnabler.enableInterrupts())
 
@@ -2841,26 +2823,24 @@ BEGIN_CASE(JSOP_REST)
         goto error;
 }
 END_CASE(JSOP_REST)
 
 BEGIN_CASE(JSOP_CALLALIASEDVAR)
 BEGIN_CASE(JSOP_GETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(regs.pc);
-    Value &var = AliasedVar(regs.fp(), sc);
-    PUSH_COPY(var);
+    PUSH_COPY(regs.fp()->aliasedVarScope(sc).aliasedVar(sc));
 }
 END_CASE(JSOP_GETALIASEDVAR)
 
 BEGIN_CASE(JSOP_SETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(regs.pc);
-    Value &var = AliasedVar(regs.fp(), sc);
-    var = regs.sp[-1];
+    regs.fp()->aliasedVarScope(sc).setAliasedVar(sc, regs.sp[-1]);
 }
 END_CASE(JSOP_SETALIASEDVAR)
 
 BEGIN_CASE(JSOP_GETARG)
 BEGIN_CASE(JSOP_CALLARG)
 {
     unsigned i = GET_ARGNO(regs.pc);
     CheckArgAccess(regs.fp(), i);
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -328,24 +328,28 @@ OPDEF(JSOP_TRY,         134,"try",      
 OPDEF(JSOP_FINALLY,     135,"finally",    NULL,       1,  0,  2,  0,  JOF_BYTE)
 
 /*
  * An "aliased variable" is a var, let, or formal arg that is aliased. Sources
  * of aliasing include: nested functions accessing the vars of an enclosing
  * function, function statements that are conditionally executed, 'eval',
  * 'with', 'arguments' and E4X filters. All of these cases require creating a
  * CallObject to own the aliased variable.
+ *
+ * XXX: there is also a temporary 2-byte index (indicating the frame slot
+ * aliased by the scope chain) which will be removed with the last patch of bug
+ * 659577.
  */
-OPDEF(JSOP_GETALIASEDVAR, 136,"getaliasedvar",NULL,   9,  0,  1, 19,  JOF_SCOPECOORD|JOF_NAME)
-OPDEF(JSOP_CALLALIASEDVAR,137,"callaliasedvar",NULL,  9,  0,  1, 19,  JOF_SCOPECOORD|JOF_NAME)
-OPDEF(JSOP_SETALIASEDVAR, 138,"setaliasedvar",NULL,   9,  1,  1,  3,  JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING)
-OPDEF(JSOP_INCALIASEDVAR, 139,"incaliasedvar",NULL,   10, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_INC|JOF_TMPSLOT3|JOF_DECOMPOSE)
-OPDEF(JSOP_DECALIASEDVAR, 140,"decaliasedvar",NULL,   10, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_DEC|JOF_TMPSLOT3|JOF_DECOMPOSE)
-OPDEF(JSOP_ALIASEDVARINC, 141,"aliasedvarinc",NULL,   10, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
-OPDEF(JSOP_ALIASEDVARDEC, 142,"aliasedvardec",NULL,   10, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
+OPDEF(JSOP_GETALIASEDVAR, 136,"getaliasedvar",NULL,   11, 0,  1, 19,  JOF_SCOPECOORD|JOF_NAME)
+OPDEF(JSOP_CALLALIASEDVAR,137,"callaliasedvar",NULL,  11, 0,  1, 19,  JOF_SCOPECOORD|JOF_NAME)
+OPDEF(JSOP_SETALIASEDVAR, 138,"setaliasedvar",NULL,   11, 1,  1,  3,  JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING)
+OPDEF(JSOP_INCALIASEDVAR, 139,"incaliasedvar",NULL,   12, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_INC|JOF_TMPSLOT3|JOF_DECOMPOSE)
+OPDEF(JSOP_DECALIASEDVAR, 140,"decaliasedvar",NULL,   12, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_DEC|JOF_TMPSLOT3|JOF_DECOMPOSE)
+OPDEF(JSOP_ALIASEDVARINC, 141,"aliasedvarinc",NULL,   12, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
+OPDEF(JSOP_ALIASEDVARDEC, 142,"aliasedvardec",NULL,   12, 0,  1, 15,  JOF_SCOPECOORD|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3|JOF_DECOMPOSE)
 
 /* Unused. */
 OPDEF(JSOP_UNUSED8,       143,"unused8",  NULL,       1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED9,       144,"unused9",  NULL,       1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED10,      145,"unused10", NULL,       1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED11,      146,"unused11", NULL,       1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED12,      147,"unused12", NULL,       1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED13,      148,"unused13", NULL,       1,  0,  0,  0,  JOF_BYTE)
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2811,20 +2811,20 @@ mjit::Compiler::generateMethod()
           BEGIN_CASE(JSOP_GETALIASEDVAR)
           BEGIN_CASE(JSOP_CALLALIASEDVAR)
           {
             /* This is all temporary until bug 659577. */
             if (JSObject *singleton = pushedSingleton(0)) {
                 frame.push(ObjectValue(*singleton));
             } else {
                 ScopeCoordinate sc = ScopeCoordinate(PC);
-                if (script->bindings.bindingIsArg(sc.binding))
-                    frame.pushArg(script->bindings.bindingToArg(sc.binding));
+                if (script->bindings.bindingIsArg(sc.frameBinding))
+                    frame.pushArg(script->bindings.bindingToArg(sc.frameBinding));
                 else
-                    frame.pushLocal(script->bindings.bindingToLocal(sc.binding));
+                    frame.pushLocal(script->bindings.bindingToLocal(sc.frameBinding));
             }
           }
           END_CASE(JSOP_GETALIASEDVAR)
 
           BEGIN_CASE(JSOP_GETLOCAL)
           BEGIN_CASE(JSOP_CALLLOCAL)
           {
 
@@ -2845,20 +2845,20 @@ mjit::Compiler::generateMethod()
           END_CASE(JSOP_GETLOCAL)
 
           BEGIN_CASE(JSOP_SETALIASEDVAR)
           {
             /* This is all temporary until bug 659577. */
             jsbytecode *next = &PC[JSOP_SETALIASEDVAR_LENGTH];
             bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next);
             ScopeCoordinate sc = ScopeCoordinate(PC);
-            if (script->bindings.bindingIsArg(sc.binding))
-                frame.storeArg(script->bindings.bindingToArg(sc.binding), pop);
+            if (script->bindings.bindingIsArg(sc.frameBinding))
+                frame.storeArg(script->bindings.bindingToArg(sc.frameBinding), pop);
             else
-                frame.storeLocal(script->bindings.bindingToLocal(sc.binding), pop);
+                frame.storeLocal(script->bindings.bindingToLocal(sc.frameBinding), pop);
             updateVarType();
 
             if (pop) {
                 frame.pop();
                 PC += JSOP_SETALIASEDVAR_LENGTH + JSOP_POP_LENGTH;
                 break;
             }
           }
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -9,17 +9,18 @@
 #define ScopeObject_inl_h___
 
 #include "ScopeObject.h"
 
 namespace js {
 
 inline
 ScopeCoordinate::ScopeCoordinate(jsbytecode *pc)
-  : hops(GET_UINT16(pc)), binding(GET_UINT16(pc + 2))
+  : hops(GET_UINT16(pc)), binding(GET_UINT16(pc + 2)),
+    frameBinding(GET_UINT16(pc + 8))
 {
     JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
 }
 
 inline JSAtom *
 ScopeCoordinateAtom(JSScript *script, jsbytecode *pc)
 {
     JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
@@ -50,16 +51,68 @@ ScopeObject::maybeStackFrame() const
 }
 
 inline void
 ScopeObject::setStackFrame(StackFrame *frame)
 {
     return setPrivate(frame);
 }
 
+inline const Value &
+ScopeObject::aliasedVar(ScopeCoordinate sc)
+{
+    /* XXX: all this is temporary until the last patch of 659577 */
+    StackFrame *fp = maybeStackFrame();
+    Bindings &bindings = fp->script()->bindings;
+    if (isCall()) {
+        JS_ASSERT(sc.binding == sc.frameBinding);
+        if (bindings.bindingIsArg(sc.binding)) {
+            unsigned arg = bindings.bindingToArg(sc.binding);
+            JS_ASSERT(fp->script()->formalLivesInCallObject(arg));
+            return fp->formalArg(arg);
+        }
+
+        unsigned var = bindings.bindingToLocal(sc.binding);
+        JS_ASSERT(fp->script()->varIsAliased(var));
+        return fp->localSlot(var);
+    }
+
+    unsigned var = bindings.bindingToLocal(sc.frameBinding);
+    fp = js_LiveFrameIfGenerator(fp);
+    JS_ASSERT(var == sc.binding + asClonedBlock().staticBlock().stackDepth() + fp->numFixed());
+    JS_ASSERT(asClonedBlock().staticBlock().isAliased(sc.binding));
+    return fp->localSlot(var);
+}
+
+inline void
+ScopeObject::setAliasedVar(ScopeCoordinate sc, const Value &v)
+{
+    /* XXX: all this is temporary until the last patch of 659577 */
+    StackFrame *fp = maybeStackFrame();
+    Bindings &bindings = fp->script()->bindings;
+    if (isCall()) {
+        JS_ASSERT(sc.binding == sc.frameBinding);
+        if (bindings.bindingIsArg(sc.binding)) {
+            unsigned arg = bindings.bindingToArg(sc.binding);
+            JS_ASSERT(fp->script()->formalLivesInCallObject(arg));
+            fp->formalArg(arg) = v;
+        } else {
+            unsigned var = bindings.bindingToLocal(sc.binding);
+            JS_ASSERT(fp->script()->varIsAliased(var));
+            fp->localSlot(var) = v;
+        }
+    } else {
+        unsigned var = bindings.bindingToLocal(sc.frameBinding);
+        fp = js_LiveFrameIfGenerator(fp);
+        JS_ASSERT(var == sc.binding + asClonedBlock().staticBlock().stackDepth() + fp->numFixed());
+        JS_ASSERT(asClonedBlock().staticBlock().isAliased(sc.binding));
+        fp->localSlot(var) = v;
+    }
+}
+
 /*static*/ inline size_t
 ScopeObject::offsetOfEnclosingScope()
 {
     return getFixedSlotOffset(SCOPE_CHAIN_SLOT);
 }
 
 inline bool
 CallObject::isForEval() const
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -4,42 +4,40 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ScopeObject_h___
 #define ScopeObject_h___
 
 #include "jscntxt.h"
+#include "jsiter.h"
 #include "jsobj.h"
 #include "jsweakmap.h"
 
 namespace js {
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
  * given lexically-enclosing variable. A scope coordinate has two dimensions:
  *  - hops: the number of scope objects on the scope chain to skip
  *  - binding: which binding on the scope object
- *
- * XXX: Until bug 659577 lands, this is all for show and all ScopeCoordinates
- * have hops fixed at 0 and 'binding' is just the js::Bindings binding for args
- * and vars and the stack depth for let bindings. Thus, aliased-var access
- * touches the StackFrame like it always did and 'binding' must be first
- * converted to either an arg or local slot (using Bindings::bindingToLocal or
- * bindingToArg). With bug 659577, ScopeObject will have a 'var' function that
- * takes a ScopeCoordinate.
  */
 struct ScopeCoordinate
 {
     uint16_t hops;
     uint16_t binding;
+
+    /* XXX this will be removed with the last patch of bug 659577. */
+    uint16_t frameBinding;
+
     inline ScopeCoordinate(jsbytecode *pc);
+    inline ScopeCoordinate() {}
 };
 
 inline JSAtom *
 ScopeCoordinateAtom(JSScript *script, jsbytecode *pc);
 
 /*****************************************************************************/
 
 /*
@@ -93,16 +91,25 @@ class ScopeObject : public JSObject
      * Since every scope chain terminates with a global object and GlobalObject
      * does not derive ScopeObject (it has a completely different layout), the
      * enclosing scope of a ScopeObject is necessarily non-null.
      */
     inline JSObject &enclosingScope() const;
     inline bool setEnclosingScope(JSContext *cx, HandleObject obj);
 
     /*
+     * Get or set an aliased variable contained in this scope. Unaliased
+     * variables should instead access the StackFrame. Aliased variable access
+     * is primarily made through JOF_SCOPECOORD ops which is why these members
+     * take a ScopeCoordinate instead of just the slot index.
+     */
+    inline const Value &aliasedVar(ScopeCoordinate sc);
+    inline void setAliasedVar(ScopeCoordinate sc, const Value &v);
+
+    /*
      * The stack frame for this scope object, if the frame is still active.
      * Note: these members may not be called for a StaticBlockObject or
      * WithObject.
      */
     inline StackFrame *maybeStackFrame() const;
     inline void setStackFrame(StackFrame *frame);
 
     /* For jit access. */
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -281,16 +281,38 @@ inline Value *
 StackFrame::actualArgsEnd() const
 {
     JS_ASSERT(hasArgs());
     if (JS_UNLIKELY(flags_ & OVERFLOW_ARGS))
         return formalArgs() - 2;
     return formalArgs() + numActualArgs();
 }
 
+inline ScopeObject &
+StackFrame::aliasedVarScope(ScopeCoordinate sc) const
+{
+    JSObject *scope = &scopeChain()->asScope();
+    for (unsigned i = sc.hops; i; i--)
+        scope = &scope->asScope().enclosingScope();
+
+#ifdef DEBUG
+    if (scope->isCall()) {
+        JS_ASSERT(scope->asCall() == callObj());
+        JS_ASSERT(scope->asCall().maybeStackFrame() == this);
+    } else {
+        StaticBlockObject &target = scope->asClonedBlock().staticBlock();
+        StaticBlockObject *b = &blockChain();
+        while (b != &target)
+            b = b->enclosingBlock();
+    }
+#endif
+
+    return scope->asScope();
+}
+
 inline void
 StackFrame::setScopeChain(JSObject &obj)
 {
 #ifdef DEBUG
     JS_ASSERT(&obj != NULL);
     if (hasCallObj()) {
         JSObject *pobj = &obj;
         while (pobj && !pobj->isWith() && pobj->asScope().maybeStackFrame() != this)
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -38,16 +38,18 @@ class ExecuteFrameGuard;
 class DummyFrameGuard;
 class GeneratorFrameGuard;
 
 class CallIter;
 class ScriptFrameIter;
 class AllFramesIter;
 
 class ArgumentsObject;
+class ScopeCoordinate;
+class ScopeObject;
 class StaticBlockObject;
 
 #ifdef JS_METHODJIT
 namespace mjit {
     struct JITScript;
     jsbytecode *NativeToPC(JITScript *jit, void *ncode, CallSite **pinline);
 }
 #endif
@@ -842,16 +844,17 @@ class StackFrame
      * NB: 'fp->hasCallObj()' implies that fp->callObj() needs to be 'put' when
      * the frame is popped. Since the scope chain of a non-strict eval frame
      * contains the call object of the parent (function) frame, it is possible
      * to have:
      *   !fp->hasCall() && fp->scopeChain().isCall()
      */
 
     inline HandleObject scopeChain() const;
+    inline ScopeObject &aliasedVarScope(ScopeCoordinate sc) const;
     inline GlobalObject &global() const;
 
     bool hasCallObj() const {
         bool ret = !!(flags_ & HAS_CALL_OBJ);
         JS_ASSERT_IF(ret, !isNonStrictEvalFrame());
         return ret;
     }