Bug 753158 - emit ALIASEDVAR ops for upvars (r=bhackett)
authorLuke Wagner <luke@mozilla.com>
Thu, 05 Jul 2012 20:35:08 -0700
changeset 101809 3923d008386d5b74926d020786afbf4253f02177
parent 101808 7221c50cb5b43f34c0aab6af24aef4c9b65d080a
child 101810 75657697cfb504161e27f7a4e1cb4406d192e076
push id1729
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 20:02:43 +0000
treeherdermozilla-aurora@f4e75e148951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs753158
milestone16.0a1
Bug 753158 - emit ALIASEDVAR ops for upvars (r=bhackett)
js/src/frontend/BytecodeEmitter.cpp
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsopcode.tbl
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/methodjit/Compiler.cpp
js/src/tests/js1_8_1/regress/regress-452498-108.js
js/src/vm/ArgumentsObject-inl.h
js/src/vm/Debugger.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -868,16 +868,17 @@ EmitAliasedVarOp(JSContext *cx, JSOp op,
         return false;
 
     jsbytecode *pc = bce->code(off);
     SET_UINT16(pc, sc.hops);
     pc += sizeof(uint16_t);
     SET_UINT16(pc, sc.slot);
     pc += sizeof(uint16_t);
     SET_UINT32_INDEX(pc, maybeBlockIndex);
+    CheckTypeSet(cx, bce, op);
     return true;
 }
 
 static unsigned
 ClonedBlockDepth(BytecodeEmitter *bce)
 {
     unsigned clonedBlockDepth = 0;
     for (StaticBlockObject *b = bce->blockChain; b; b = b->enclosingBlock()) {
@@ -886,50 +887,58 @@ ClonedBlockDepth(BytecodeEmitter *bce)
     }
 
     return clonedBlockDepth;
 }
 
 static bool
 EmitAliasedVarOp(JSContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce)
 {
-    /* This restriction will be removed shortly by bug 753158. */
-    JS_ASSERT(pn->isUsed() || pn->isDefn());
-    JS_ASSERT_IF(pn->isUsed(), pn->pn_cookie.level() == 0);
-    JS_ASSERT_IF(pn->isDefn(), pn->pn_cookie.level() == bce->script->staticLevel);
-
-    /*
-     * 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.
-     */
+    unsigned skippedScopes = 0;
+    BytecodeEmitter *bceOfDef = bce;
+    if (pn->isUsed()) {
+        /*
+         * As explained in BindNameToSlot, the 'level' of a use indicates how
+         * many function scopes (i.e., BytecodeEmitters) to skip to find the
+         * enclosing function scope of the definition being accessed.
+         */
+        for (unsigned i = pn->pn_cookie.level(); i; i--) {
+            skippedScopes += ClonedBlockDepth(bceOfDef);
+            if (bceOfDef->sc->funIsHeavyweight()) {
+                skippedScopes++;
+                if (bceOfDef->sc->fun()->isNamedLambda())
+                    skippedScopes++;
+            }
+            bceOfDef = bceOfDef->parent;
+        }
+    } else {
+        JS_ASSERT(pn->isDefn());
+        JS_ASSERT(pn->pn_cookie.level() == bce->script->staticLevel);
+    }
+
     ScopeCoordinate sc;
     if (JOF_OPTYPE(pn->getOp()) == JOF_QARG) {
-        sc.hops = ClonedBlockDepth(bce);
-        sc.slot = bce->sc->bindings.formalIndexToSlot(pn->pn_cookie.slot());
+        sc.hops = skippedScopes + ClonedBlockDepth(bceOfDef);
+        sc.slot = bceOfDef->sc->bindings.formalIndexToSlot(pn->pn_cookie.slot());
     } else {
         JS_ASSERT(JOF_OPTYPE(pn->getOp()) == JOF_LOCAL || pn->isKind(PNK_FUNCTION));
         unsigned local = pn->pn_cookie.slot();
-        if (local < bce->sc->bindings.numVars()) {
-            sc.hops = ClonedBlockDepth(bce);
-            sc.slot = bce->sc->bindings.varIndexToSlot(local);
+        if (local < bceOfDef->sc->bindings.numVars()) {
+            sc.hops = skippedScopes + ClonedBlockDepth(bceOfDef);
+            sc.slot = bceOfDef->sc->bindings.varIndexToSlot(local);
         } else {
-            unsigned depth = local - bce->sc->bindings.numVars();
-            unsigned hops = 0;
-            StaticBlockObject *b = bce->blockChain;
+            unsigned depth = local - bceOfDef->sc->bindings.numVars();
+            StaticBlockObject *b = bceOfDef->blockChain;
             while (!b->containsVarAtDepth(depth)) {
                 if (b->needsClone())
-                    hops++;
+                    skippedScopes++;
                 b = b->enclosingBlock();
             }
-            sc.hops = hops;
-            sc.slot = b->localIndexToSlot(bce->sc->bindings, local);
+            sc.hops = skippedScopes;
+            sc.slot = b->localIndexToSlot(bceOfDef->sc->bindings, local);
         }
     }
 
     return EmitAliasedVarOp(cx, op, sc, bce);
 }
 
 static bool
 EmitVarOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
@@ -1320,22 +1329,16 @@ BindNameToSlot(JSContext *cx, BytecodeEm
      *
      * x will get (level = 0, slot = 2) and y will get (level = 1, slot = 0).
      */
     JS_ASSERT(!pn->isDefn());
     JS_ASSERT(pn->isUsed());
     JS_ASSERT(pn->pn_lexdef);
     JS_ASSERT(pn->pn_cookie.isFree());
 
-    /* TODO: actually the comment lies; until bug 753158, y will stay a NAME. */
-    if (dn->pn_cookie.level() != bce->script->staticLevel) {
-        JS_ASSERT(dn->isClosed());
-        return true;
-    }
-
     /*
      * We are compiling a function body and may be able to optimize name
      * to stack slot. Look for an argument or variable in the function and
      * rewrite pn_op and update pn accordingly.
      */
     switch (dn->kind()) {
       case Definition::UNKNOWN:
         return true;
@@ -1351,16 +1354,24 @@ BindNameToSlot(JSContext *cx, BytecodeEm
           default: JS_NOT_REACHED("arg");
         }
         JS_ASSERT(!pn->isConst());
         break;
 
       case Definition::VAR:
         if (dn->isOp(JSOP_CALLEE)) {
             JS_ASSERT(op != JSOP_CALLEE);
+
+            /*
+             * Currently, the ALIASEDVAR ops do not support accessing the
+             * callee of a DeclEnvObject, so use NAME.
+             */
+            if (dn->pn_cookie.level() != bce->script->staticLevel)
+                return true;
+
             JS_ASSERT(bce->sc->fun()->flags & JSFUN_LAMBDA);
             JS_ASSERT(pn->pn_atom == bce->sc->fun()->atom);
 
             /*
              * Leave pn->isOp(JSOP_NAME) if bce->fun is heavyweight to
              * address two cases: a new binding introduced by eval, and
              * assignment to the name in strict mode.
              *
@@ -1409,19 +1420,44 @@ BindNameToSlot(JSContext *cx, BytecodeEm
           default: JS_NOT_REACHED("local");
         }
         break;
 
       default:
         JS_NOT_REACHED("unexpected dn->kind()");
     }
 
+    /*
+     * The difference between the current static level and the static level of
+     * the definition is the number of function scopes between the current
+     * scope and dn's scope.
+     */
+    unsigned skip = bce->script->staticLevel - dn->pn_cookie.level();
+    JS_ASSERT_IF(skip, dn->isClosed());
+
+    /*
+     * Explicitly disallow accessing var/let bindings in global scope from
+     * nested functions. The reason for this limitation is that, since the
+     * global script is not included in the static scope chain (1. because it
+     * has no object to stand in the static scope chain, 2. to minimize memory
+     * bloat where a single live function keeps its whole global script
+     * alive.), ScopeCoordinateToTypeSet is not able to find the var/let's
+     * associated types::TypeSet.
+     */
+    if (skip) {
+        BytecodeEmitter *bceSkipped = bce;
+        for (unsigned i = 0; i < skip; i++)
+            bceSkipped = bceSkipped->parent;
+        if (!bceSkipped->sc->inFunction())
+            return true;
+    }
+
     JS_ASSERT(!pn->isOp(op));
     pn->setOp(op);
-    if (!pn->pn_cookie.set(bce->sc->context, 0, dn->pn_cookie.slot()))
+    if (!pn->pn_cookie.set(bce->sc->context, skip, dn->pn_cookie.slot()))
         return false;
 
     pn->pn_dflags |= PND_BOUND;
     return true;
 }
 
 /*
  * If pn contains a useful expression, return true with *answer set to true.
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -309,28 +309,23 @@ ScriptAnalysis::analyzeBytecode(JSContex
           case JSOP_QNAMECONST:
             isJaegerCompileable = false;
             /* FALL THROUGH */
           case JSOP_NAME:
           case JSOP_CALLNAME:
           case JSOP_BINDNAME:
           case JSOP_SETNAME:
           case JSOP_DELNAME:
+          case JSOP_GETALIASEDVAR:
+          case JSOP_CALLALIASEDVAR:
+          case JSOP_SETALIASEDVAR:
             usesScopeChain_ = true;
             isInlineable = false;
             break;
 
-          case JSOP_GETALIASEDVAR:
-          case JSOP_CALLALIASEDVAR:
-          case JSOP_SETALIASEDVAR: {
-            JS_ASSERT(!isInlineable);
-            usesScopeChain_ = true;
-            break;
-          }
-
           case JSOP_DEFFUN:
           case JSOP_DEFVAR:
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             isInlineable = canTrackVars = false;
             break;
 
           case JSOP_EVAL:
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -354,26 +354,16 @@ static inline uint32_t GetBytecodeSlot(J
       case JSOP_CALLLOCAL:
       case JSOP_SETLOCAL:
       case JSOP_INCLOCAL:
       case JSOP_DECLOCAL:
       case JSOP_LOCALINC:
       case JSOP_LOCALDEC:
         return LocalSlot(script, GET_SLOTNO(pc));
 
-      case JSOP_GETALIASEDVAR:
-      case JSOP_CALLALIASEDVAR:
-      case JSOP_SETALIASEDVAR:
-      {
-        unsigned index;
-        return ScopeCoordinateToFrameIndex(script, pc, &index) == FrameIndex_Local
-               ? LocalSlot(script, index)
-               : ArgSlot(index);
-      }
-
       case JSOP_THIS:
         return ThisSlot();
 
       default:
         JS_NOT_REACHED("Bad slot opcode");
         return 0;
     }
 }
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3405,18 +3405,16 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
 
       case JSOP_GETXPROP: {
         TypeSet *seen = bytecodeTypes(pc);
         addTypeBarrier(cx, pc, seen, Type::UnknownType());
         seen->addSubset(cx, &pushed[0]);
         break;
       }
 
-      case JSOP_GETALIASEDVAR:
-      case JSOP_CALLALIASEDVAR:
       case JSOP_GETARG:
       case JSOP_CALLARG:
       case JSOP_GETLOCAL:
       case JSOP_CALLLOCAL: {
         uint32_t slot = GetBytecodeSlot(script, pc);
         if (trackSlot(slot)) {
             /*
              * Normally these opcodes don't pop anything, but they are given
@@ -3426,22 +3424,21 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
             poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
         } else if (slot < TotalSlots(script)) {
             TypeSet *types = TypeScript::SlotTypes(script, slot);
             types->addSubset(cx, &pushed[0]);
         } else {
             /* Local 'let' variable. Punt on types for these, for now. */
             pushed[0].addType(cx, Type::UnknownType());
         }
-        if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL || op == JSOP_CALLALIASEDVAR)
+        if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL)
             pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType());
         break;
       }
 
-      case JSOP_SETALIASEDVAR:
       case JSOP_SETARG:
       case JSOP_SETLOCAL: {
         uint32_t slot = GetBytecodeSlot(script, pc);
         if (!trackSlot(slot) && slot < TotalSlots(script)) {
             TypeSet *types = TypeScript::SlotTypes(script, slot);
             poppedTypes(pc, 0)->addSubset(cx, types);
         }
 
@@ -3449,16 +3446,34 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
          * For assignments to non-escaping locals/args, we don't need to update
          * the possible types of the var, as for each read of the var SSA gives
          * us the writes that could have produced that read.
          */
         poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
         break;
       }
 
+      case JSOP_GETALIASEDVAR:
+      case JSOP_CALLALIASEDVAR:
+        /*
+         * Every aliased variable will contain 'undefined' in addition to the
+         * type of whatever value is written to it. Thus, a dynamic barrier is
+         * necessary. Since we don't expect the to observe more than 1 type,
+         * there is little benefit to maintaining a TypeSet for the aliased
+         * variable. Instead, we monitor/barrier all reads unconditionally.
+         */
+        bytecodeTypes(pc)->addSubset(cx, &pushed[0]);
+        if (op == JSOP_CALLALIASEDVAR)
+            pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType());
+        break;
+
+      case JSOP_SETALIASEDVAR:
+        poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
+        break;
+
       case JSOP_INCARG:
       case JSOP_DECARG:
       case JSOP_ARGINC:
       case JSOP_ARGDEC:
       case JSOP_INCLOCAL:
       case JSOP_DECLOCAL:
       case JSOP_LOCALINC:
       case JSOP_LOCALDEC: {
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2760,16 +2760,17 @@ BEGIN_CASE(JSOP_REST)
 }
 END_CASE(JSOP_REST)
 
 BEGIN_CASE(JSOP_CALLALIASEDVAR)
 BEGIN_CASE(JSOP_GETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(regs.pc);
     PUSH_COPY(regs.fp()->aliasedVarScope(sc).aliasedVar(sc));
+    TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
 }
 END_CASE(JSOP_GETALIASEDVAR)
 
 BEGIN_CASE(JSOP_SETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(regs.pc);
     regs.fp()->aliasedVarScope(sc).setAliasedVar(sc, regs.sp[-1]);
 }
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -337,18 +337,18 @@ OPDEF(JSOP_FINALLY,     135,"finally",  
  * An ALIASEDVAR opcode contains the following immediates:
  *  uint16 hops:  the number of scope objects to skip to find the ScopeObject
  *                containing the variable being accessed
  *  uint16 slot:  the slot containing the variable in the ScopeObject (this
  *                'slot' does not include RESERVED_SLOTS).
  *  uint32 block: the index (into the script object table) of the block chain
  *                at the point of the variable access.
  */
-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_GETALIASEDVAR, 136,"getaliasedvar",NULL,   9,  0,  1, 19,  JOF_SCOPECOORD|JOF_NAME|JOF_TYPESET)
+OPDEF(JSOP_CALLALIASEDVAR,137,"callaliasedvar",NULL,  9,  0,  1, 19,  JOF_SCOPECOORD|JOF_NAME|JOF_TYPESET)
 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)
 
 /* Unused. */
 OPDEF(JSOP_UNUSED8,       143,"unused8",  NULL,       1,  0,  0,  0,  JOF_BYTE)
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -98,16 +98,19 @@ class Bindings
      * not be used again.
      */
     inline void transfer(Bindings *bindings);
 
     uint16_t numArgs() const { return nargs; }
     uint16_t numVars() const { return nvars; }
     unsigned count() const { return nargs + nvars; }
 
+    /* Convert a CallObject slot to either a formal or local variable index. */
+    inline BindingKind slotToFrameIndex(unsigned slot, unsigned *index);
+
     /*
      * The VM's StackFrame allocates a Value for each formal and variable.
      * A (formal|var)Index is the index passed to fp->unaliasedFormal/Var to
      * access this variable. These two functions convert between formal/var
      * indices and the corresponding slot in the CallObject.
      */
     inline uint16_t formalIndexToSlot(uint16_t i);
     inline uint16_t varIndexToSlot(uint16_t i);
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -10,29 +10,42 @@
 
 #include "jsautooplen.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 #include "jsscope.h"
 
-#include "vm/ScopeObject.h"
 #include "vm/GlobalObject.h"
 #include "vm/RegExpObject.h"
 
 #include "jsscopeinlines.h"
 
 namespace js {
 
 inline
 Bindings::Bindings()
     : lastBinding(NULL), nargs(0), nvars(0), hasDup_(false)
 {}
 
+inline BindingKind
+Bindings::slotToFrameIndex(unsigned slot, unsigned *index)
+{
+    slot -= CallObject::RESERVED_SLOTS;
+    if (slot < numArgs()) {
+        *index = slot;
+        return ARGUMENT;
+    }
+
+    *index = slot - numArgs();
+    JS_ASSERT(*index < numVars());
+    return VARIABLE;
+}
+
 inline void
 Bindings::transfer(Bindings *bindings)
 {
     JS_ASSERT(!lastBinding);
     JS_ASSERT(!bindings->lastBinding || !bindings->lastBinding->inDictionary());
 
     *this = *bindings;
 #ifdef DEBUG
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2821,49 +2821,48 @@ mjit::Compiler::generateMethod()
                 PC += JSOP_SETARG_LENGTH + JSOP_POP_LENGTH;
                 break;
             }
           }
           END_CASE(JSOP_SETARG)
 
           BEGIN_CASE(JSOP_GETLOCAL)
           BEGIN_CASE(JSOP_CALLLOCAL)
-          BEGIN_CASE(JSOP_GETALIASEDVAR)
-          BEGIN_CASE(JSOP_CALLALIASEDVAR)
           {
             /*
              * Update the var type unless we are about to pop the variable.
              * Sync is not guaranteed for types of dead locals, and GETLOCAL
              * followed by POP is not regarded as a use of the variable.
              */
             jsbytecode *next = &PC[JSOP_GETLOCAL_LENGTH];
             if (JSOp(*next) != JSOP_POP || analysis->jumpTarget(next))
                 restoreVarType();
             if (JSObject *singleton = pushedSingleton(0))
                 frame.push(ObjectValue(*singleton));
-            else if (JOF_OPTYPE(*PC) == JOF_SCOPECOORD)
-                jsop_aliasedVar(ScopeCoordinate(PC), /* get = */ true);
             else
                 frame.pushLocal(GET_SLOTNO(PC));
-
-            PC += GetBytecodeLength(PC);
-            break;
           }
           END_CASE(JSOP_GETLOCAL)
 
+          BEGIN_CASE(JSOP_GETALIASEDVAR)
+          BEGIN_CASE(JSOP_CALLALIASEDVAR)
+            jsop_aliasedVar(ScopeCoordinate(PC), /* get = */ true);
+          END_CASE(JSOP_GETALIASEDVAR);
+
           BEGIN_CASE(JSOP_SETLOCAL)
           BEGIN_CASE(JSOP_SETALIASEDVAR)
           {
             jsbytecode *next = &PC[GetBytecodeLength(PC)];
             bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next);
-            if (JOF_OPTYPE(*PC) == JOF_SCOPECOORD)
+            if (JOF_OPTYPE(*PC) == JOF_SCOPECOORD) {
                 jsop_aliasedVar(ScopeCoordinate(PC), /* get = */ false, pop);
-            else
+            } else {
                 frame.storeLocal(GET_SLOTNO(PC), pop);
-            updateVarType();
+                updateVarType();
+            }
 
             if (pop) {
                 frame.pop();
                 PC = next + JSOP_POP_LENGTH;
                 break;
             }
 
             PC = next;
@@ -5798,37 +5797,35 @@ mjit::Compiler::jsop_aliasedArg(unsigned
 void
 mjit::Compiler::jsop_aliasedVar(ScopeCoordinate sc, bool get, bool poppedAfter)
 {
     RegisterID reg = frame.allocReg(Registers::SavedRegs).reg();
     masm.loadPtr(Address(JSFrameReg, StackFrame::offsetOfScopeChain()), reg);
     for (unsigned i = 0; i < sc.hops; i++)
         masm.loadPayload(Address(reg, ScopeObject::offsetOfEnclosingScope()), reg);
 
-    Shape *shape;
-    if (StaticBlockObject *block = ScopeCoordinateBlockChain(script, PC))
-        shape = block->lastProperty();
-    else
-        shape = script->bindings.lastShape();
-
+    Shape *shape = ScopeCoordinateToStaticScope(script, PC).scopeShape();
     Address addr;
     if (shape->numFixedSlots() <= sc.slot) {
         masm.loadPtr(Address(reg, JSObject::offsetOfSlots()), reg);
         addr = Address(reg, (sc.slot - shape->numFixedSlots()) * sizeof(Value));
     } else {
         addr = Address(reg, JSObject::getFixedSlotOffset(sc.slot));
     }
 
     if (get) {
-        unsigned index;
-        FrameEntry *fe = ScopeCoordinateToFrameIndex(script, PC, &index) == FrameIndex_Local
-                         ? frame.getLocal(index)
-                         : frame.getArg(index);
-        JSValueType type = fe->isTypeKnown() ? fe->getKnownType() : JSVAL_TYPE_UNKNOWN;
-        frame.push(addr, type, true /* = reuseBase */);
+        JSValueType type = knownPushedType(0);
+        RegisterID typeReg, dataReg;
+        frame.loadIntoRegisters(addr, /* reuseBase = */ true, &typeReg, &dataReg);
+        frame.pushRegs(typeReg, dataReg, type);
+        BarrierState barrier = testBarrier(typeReg, dataReg,
+                                           /* testUndefined = */ false,
+                                           /* testReturn */ false,
+                                           /* force */ true);
+        finishBarrier(barrier, REJOIN_FALLTHROUGH, 0);
     } else {
 #ifdef JSGC_INCREMENTAL_MJ
         if (cx->compartment->needsBarrier()) {
             /* Write barrier. */
             stubcc.linkExit(masm.testGCThing(addr), Uses(0));
             stubcc.leave();
             stubcc.masm.addPtr(Imm32(addr.offset), addr.base, Registers::ArgReg1);
             OOL_STUBCALL(stubs::GCThingWriteBarrier, REJOIN_NONE);
deleted file mode 100644
--- a/js/src/tests/js1_8_1/regress/regress-452498-108.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-//-----------------------------------------------------------------------------
-var BUGNUMBER = 452498;
-var summary = 'TM: upvar2 regression tests';
-var actual = '';
-var expect = '';
-
-
-//-----------------------------------------------------------------------------
-test();
-//-----------------------------------------------------------------------------
-
-function test()
-{
-  enterFunc ('test');
-  printBugNumber(BUGNUMBER);
-  printStatus (summary);
-
-// ------- Comment #108 From Jesse Ruderman
-
-  function p(){p}
-
-  expect = 'function p(){p;}';
-  actual = p + '';
-
-  compareSource(expect, actual, summary);
-
-  exitFunc ('test');
-}
--- a/js/src/vm/ArgumentsObject-inl.h
+++ b/js/src/vm/ArgumentsObject-inl.h
@@ -67,27 +67,27 @@ ArgumentsObject::setArg(unsigned i, cons
 }
 
 inline const Value &
 ArgumentsObject::element(uint32_t i) const
 {
     JS_ASSERT(!isElementDeleted(i));
     const Value &v = data()->args[i];
     if (v.isMagic(JS_FORWARD_TO_CALL_OBJECT))
-        return getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall().arg(i);
+        return getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall().formal(i);
     return v;
 }
 
 inline void
 ArgumentsObject::setElement(uint32_t i, const Value &v)
 {
     JS_ASSERT(!isElementDeleted(i));
     HeapValue &lhs = data()->args[i];
     if (lhs.isMagic(JS_FORWARD_TO_CALL_OBJECT))
-        getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall().setArg(i, v);
+        getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall().setFormal(i, v);
     else
         lhs = v;
 }
 
 inline bool
 ArgumentsObject::isElementDeleted(uint32_t i) const
 {
     JS_ASSERT(i < data()->numArgs);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3172,17 +3172,17 @@ DebuggerArguments_getArg(JSContext *cx, 
     /*
      * Since getters can be extracted and applied to other objects,
      * there is no guarantee this object has an ith argument.
      */
     JS_ASSERT(i >= 0);
     Value arg;
     if (unsigned(i) < fp->numActualArgs()) {
         if (unsigned(i) < fp->numFormalArgs() && fp->script()->formalLivesInCallObject(i))
-            arg = fp->callObj().arg(i);
+            arg = fp->callObj().formal(i);
         else if (fp->script()->argsObjAliasesFormals() && fp->hasArgsObj())
             arg = fp->argsObj().arg(i);
         else
             arg = fp->unaliasedActual(i, DONT_CHECK_ALIASING);
     } else {
         arg.setUndefined();
     }
 
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -5,16 +5,18 @@
  * 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_inl_h___
 #define ScopeObject_inl_h___
 
 #include "ScopeObject.h"
 
+#include "jsscriptinlines.h"
+
 namespace js {
 
 inline
 ScopeCoordinate::ScopeCoordinate(jsbytecode *pc)
   : hops(GET_UINT16(pc)), slot(GET_UINT16(pc + 2))
 {
     JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
 }
@@ -67,24 +69,24 @@ CallObject::isForEval() const
 
 inline JSFunction &
 CallObject::callee() const
 {
     return *getReservedSlot(CALLEE_SLOT).toObject().toFunction();
 }
 
 inline const Value &
-CallObject::arg(unsigned i, MaybeCheckAliasing checkAliasing) const
+CallObject::formal(unsigned i, MaybeCheckAliasing checkAliasing) const
 {
     JS_ASSERT_IF(checkAliasing, callee().script()->formalLivesInCallObject(i));
     return getSlot(RESERVED_SLOTS + i);
 }
 
 inline void
-CallObject::setArg(unsigned i, const Value &v, MaybeCheckAliasing checkAliasing)
+CallObject::setFormal(unsigned i, const Value &v, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT_IF(checkAliasing, callee().script()->formalLivesInCallObject(i));
     setSlot(RESERVED_SLOTS + i, v);
 }
 
 inline const Value &
 CallObject::var(unsigned i, MaybeCheckAliasing checkAliasing) const
 {
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -53,16 +53,24 @@ StaticScopeIter::operator++(int)
 bool
 StaticScopeIter::hasDynamicScopeObject() const
 {
     return obj->isStaticBlock()
            ? obj->asStaticBlock().needsClone()
            : obj->toFunction()->isHeavyweight();
 }
 
+Shape *
+StaticScopeIter::scopeShape() const
+{
+    JS_ASSERT(hasDynamicScopeObject());
+    JS_ASSERT(type() != NAMED_LAMBDA);
+    return type() == BLOCK ? block().lastProperty() : funScript()->bindings.lastShape();
+}
+
 StaticScopeIter::Type
 StaticScopeIter::type() const
 {
     if (onNamedLambda)
         return NAMED_LAMBDA;
     return obj->isStaticBlock() ? BLOCK : FUNCTION;
 }
 
@@ -77,73 +85,57 @@ JSScript *
 StaticScopeIter::funScript() const
 {
     JS_ASSERT(type() == FUNCTION);
     return obj->toFunction()->script();
 }
 
 /*****************************************************************************/
 
-StaticBlockObject *
-js::ScopeCoordinateBlockChain(JSScript *script, jsbytecode *pc)
+StaticScopeIter
+js::ScopeCoordinateToStaticScope(JSScript *script, jsbytecode *pc)
 {
-    ScopeCoordinate sc(pc);
+    JS_ASSERT(pc >= script->code && pc < script->code + script->length);
+    JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
 
     uint32_t blockIndex = GET_UINT32_INDEX(pc + 2 * sizeof(uint16_t));
+    JSObject *innermostStaticScope;
     if (blockIndex == UINT32_MAX)
-        return NULL;
+        innermostStaticScope = script->function();
+    else
+        innermostStaticScope = &script->getObject(blockIndex)->asStaticBlock();
 
-    StaticBlockObject *block = &script->getObject(blockIndex)->asStaticBlock();
-    unsigned i = 0;
+    StaticScopeIter ssi(innermostStaticScope);
+    ScopeCoordinate sc(pc);
     while (true) {
-        while (block && !block->needsClone())
-            block = block->enclosingBlock();
-        if (i++ == sc.hops)
-            break;
-        block = block->enclosingBlock();
+        if (ssi.hasDynamicScopeObject()) {
+            if (!sc.hops)
+                break;
+            sc.hops--;
+        }
+        ssi++;
     }
-    return block;
+    return ssi;
 }
 
 PropertyName *
 js::ScopeCoordinateName(JSRuntime *rt, JSScript *script, jsbytecode *pc)
 {
-    StaticBlockObject *maybeBlock = ScopeCoordinateBlockChain(script, pc);
+    Shape::Range r = ScopeCoordinateToStaticScope(script, pc).scopeShape()->all();
     ScopeCoordinate sc(pc);
-    Shape *shape = maybeBlock ? maybeBlock->lastProperty() : script->bindings.lastShape();
-    Shape::Range r = shape->all();
     while (r.front().slot() != sc.slot)
         r.popFront();
     jsid id = r.front().propid();
+
     /* Beware nameless destructuring formal. */
     if (!JSID_IS_ATOM(id))
         return rt->atomState.emptyAtom;
     return JSID_TO_ATOM(id)->asPropertyName();
 }
 
-FrameIndexType
-js::ScopeCoordinateToFrameIndex(JSScript *script, jsbytecode *pc, unsigned *index)
-{
-    ScopeCoordinate sc(pc);
-    if (StaticBlockObject *block = ScopeCoordinateBlockChain(script, pc)) {
-        *index = block->slotToLocalIndex(script->bindings, sc.slot);
-        return FrameIndex_Local;
-    }
-
-    unsigned i = sc.slot - CallObject::RESERVED_SLOTS;
-    if (i < script->bindings.numArgs()) {
-        *index = i;
-        return FrameIndex_Arg;
-    }
-
-    *index = i - script->bindings.numArgs();
-    JS_ASSERT(*index < script->bindings.numVars());
-    return FrameIndex_Local;
-}
-
 /*****************************************************************************/
 
 /*
  * Construct a call object for the given bindings.  If this is a call object
  * for a function invocation, callee should be the function being called.
  * Otherwise it must be a call object for eval of strict mode code, and callee
  * must be null.
  */
@@ -210,22 +202,22 @@ CallObject::createForFunction(JSContext 
     CallObject *callobj = create(cx, script, scopeChain, callee);
     if (!callobj)
         return NULL;
 
     /* Copy in the closed-over formal arguments. */
     if (script->bindingsAccessedDynamically) {
         Value *formals = fp->formals();
         for (unsigned slot = 0, n = fp->fun()->nargs; slot < n; ++slot)
-            callobj->setArg(slot, formals[slot]);
+            callobj->setFormal(slot, formals[slot]);
     } else if (unsigned n = script->numClosedArgs()) {
         Value *formals = fp->formals();
         for (unsigned i = 0; i < n; ++i) {
             uint32_t slot = script->getClosedArg(i);
-            callobj->setArg(slot, formals[slot]);
+            callobj->setFormal(slot, formals[slot]);
         }
     }
 
     return callobj;
 }
 
 void
 CallObject::copyUnaliasedValues(StackFrame *fp)
@@ -236,19 +228,19 @@ CallObject::copyUnaliasedValues(StackFra
     /* If bindings are accessed dynamically, everything is aliased. */
     if (script->bindingsAccessedDynamically)
         return;
 
     /* Copy the unaliased formals. */
     for (unsigned i = 0; i < script->bindings.numArgs(); ++i) {
         if (!script->formalLivesInCallObject(i)) {
             if (script->argsObjAliasesFormals() && fp->hasArgsObj())
-                setArg(i, fp->argsObj().arg(i), DONT_CHECK_ALIASING);
+                setFormal(i, fp->argsObj().arg(i), DONT_CHECK_ALIASING);
             else
-                setArg(i, fp->unaliasedFormal(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
+                setFormal(i, fp->unaliasedFormal(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
         }
     }
 
     /* Copy the unaliased var/let bindings. */
     for (unsigned i = 0; i < script->bindings.numVars(); ++i) {
         if (!script->varIsAliased(i))
             setVar(i, fp->unaliasedLocal(i), DONT_CHECK_ALIASING);
     }
@@ -263,50 +255,30 @@ CallObject::createForStrictEval(JSContex
 
     Rooted<JSFunction*> callee(cx, NULL);
     return create(cx, fp->script(), fp->scopeChain(), callee);
 }
 
 JSBool
 CallObject::setArgOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp)
 {
-    CallObject &callobj = obj->asCall();
-
+    /* TODO: this can totally be a data property now. */
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
-
-    JSScript *script = callobj.callee().script();
-    JS_ASSERT(script->formalLivesInCallObject(i));
-
-    callobj.setArg(i, *vp);
-
-    if (!script->ensureHasTypes(cx))
-        return false;
-
-    TypeScript::SetArgument(cx, script, i, *vp);
+    obj->asCall().setFormal(i, *vp);
     return true;
 }
 
 JSBool
 CallObject::setVarOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp)
 {
-    CallObject &callobj = obj->asCall();
-
+    /* TODO: this can totally be a data property now. */
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
-
-    JSScript *script = callobj.callee().script();
-    JS_ASSERT(script->varIsAliased(i));
-
-    callobj.setVar(i, *vp);
-
-    if (!script->ensureHasTypes(cx))
-        return false;
-
-    TypeScript::SetLocal(cx, script, i, *vp);
+    obj->asCall().setVar(i, *vp);
     return true;
 }
 
 JS_PUBLIC_DATA(Class) js::CallClass = {
     "Call",
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS),
     JS_PropertyStub,         /* addProperty */
     JS_PropertyStub,         /* delProperty */
@@ -1188,19 +1160,19 @@ class DebugScopeProxy : public BaseProxy
                     } else {
                         if (action == GET)
                             *vp = maybefp->unaliasedFormal(i, DONT_CHECK_ALIASING);
                         else
                             maybefp->unaliasedFormal(i, DONT_CHECK_ALIASING) = *vp;
                     }
                 } else {
                     if (action == GET)
-                        *vp = callobj.arg(i, DONT_CHECK_ALIASING);
+                        *vp = callobj.formal(i, DONT_CHECK_ALIASING);
                     else
-                        callobj.setArg(i, *vp, DONT_CHECK_ALIASING);
+                        callobj.setFormal(i, *vp, DONT_CHECK_ALIASING);
                 }
 
                 if (action == SET)
                     TypeScript::SetArgument(cx, script, i, *vp);
 
                 return true;
             }
 
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -60,16 +60,17 @@ class StaticScopeIter
   public:
     explicit StaticScopeIter(JSObject *obj);
 
     bool done() const;
     void operator++(int);
 
     /* Return whether this static scope will be on the dynamic scope chain. */
     bool hasDynamicScopeObject() const;
+    Shape *scopeShape() const;
 
     enum Type { BLOCK, FUNCTION, NAMED_LAMBDA };
     Type type() const;
 
     StaticBlockObject &block() const;
     JSScript *funScript() const;
 };
 
@@ -88,35 +89,27 @@ struct ScopeCoordinate
 {
     uint16_t hops;
     uint16_t slot;
 
     inline ScopeCoordinate(jsbytecode *pc);
     inline ScopeCoordinate() {}
 };
 
-/* Return the static block chain (or null) accessed by *pc. */
-extern StaticBlockObject *
-ScopeCoordinateBlockChain(JSScript *script, jsbytecode *pc);
+/*
+ * Return a scope iterator pointing at the static scope containing the variable
+ * accessed by the ALIASEDVAR op at 'pc'.
+ */
+extern StaticScopeIter
+ScopeCoordinateToStaticScope(JSScript *script, jsbytecode *pc);
 
 /* Return the name being accessed by the given ALIASEDVAR op. */
 extern PropertyName *
 ScopeCoordinateName(JSRuntime *rt, JSScript *script, jsbytecode *pc);
 
-/*
- * The 'slot' of a ScopeCoordinate is relative to the scope object. Type
- * inference and jit compilation are instead relative to frame values (even if
- * these values are aliased and thus never accessed, the the index of the
- * variable is used to refer to the jit/inference information). This function
- * maps from the ScopeCoordinate space to the StackFrame variable space.
- */
-enum FrameIndexType { FrameIndex_Local, FrameIndex_Arg };
-extern FrameIndexType
-ScopeCoordinateToFrameIndex(JSScript *script, jsbytecode *pc, unsigned *index);
-
 /*****************************************************************************/
 
 /*
  * Scope objects
  *
  * Scope objects are technically real JSObjects but only belong on the scope
  * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
  * scope objects is:
@@ -196,24 +189,25 @@ class CallObject : public ScopeObject
     inline bool isForEval() const;
 
     /*
      * Returns the function for which this CallObject was created. (This may
      * only be called if !isForEval.)
      */
     inline JSFunction &callee() const;
 
-    /* Returns the formal argument at the given index. */
-    inline const Value &arg(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const;
-    inline void setArg(unsigned i, const Value &v, MaybeCheckAliasing = CHECK_ALIASING);
+    /* Get/set the formal argument at the given index. */
+    inline const Value &formal(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const;
+    inline void setFormal(unsigned i, const Value &v, MaybeCheckAliasing = CHECK_ALIASING);
 
-    /* Returns the variable at the given index. */
+    /* Get/set the variable at the given index. */
     inline const Value &var(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const;
     inline void setVar(unsigned i, const Value &v, MaybeCheckAliasing = CHECK_ALIASING);
 
+    /* Internal property ops for CallObject dynamic access via scope chain. */
     static JSBool setArgOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp);
     static JSBool setVarOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp);
 
     /* Copy in all the unaliased formals and locals. */
     void copyUnaliasedValues(StackFrame *fp);
 };
 
 class DeclEnvObject : public ScopeObject