Bug 767013 - only store aliased variables in scope objects (r=bhackett)
authorLuke Wagner <luke@mozilla.com>
Mon, 06 Aug 2012 07:56:45 -0700
changeset 107981 abc8c217f0322a277430e02f5746836505206df8
parent 107980 70a7988bfd21fc86eeaf11b4912c294bf788e97a
child 107982 36a8ff6ae00354fe4758e8cbd5a56230651f63f5
push id1490
push userakeybl@mozilla.com
push dateMon, 08 Oct 2012 18:29:50 +0000
treeherdermozilla-beta@f335e7dacdc1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs767013
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 767013 - only store aliased variables in scope objects (r=bhackett)
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/TreeContext-inl.h
js/src/frontend/TreeContext.cpp
js/src/frontend/TreeContext.h
js/src/jsanalyze.cpp
js/src/jsapi-tests/testXDR.cpp
js/src/jsdbgapi.cpp
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsopcode.cpp
js/src/jsscope.cpp
js/src/jsscope.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/vm/ArgumentsObject-inl.h
js/src/vm/ArgumentsObject.cpp
js/src/vm/Debugger.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -95,16 +95,21 @@ frontend::CompileScript(JSContext *cx, H
         return NULL;
 
     bool savedCallerFun = options.compileAndGo && callerFrame && callerFrame->isFunctionFrame();
     Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun,
                                                   options, staticLevel, ss, 0, length));
     if (!script)
         return NULL;
 
+    // Global/eval script bindings are always empty (all names are added to the
+    // scope dynamically via JSOP_DEFFUN/VAR).
+    if (!script->bindings.init(cx, 0, 0, NULL))
+        return NULL;
+
     // We can specialize a bit for the given scope chain if that scope chain is the global object.
     JSObject *globalScope = scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : NULL;
     JS_ASSERT_IF(globalScope, globalScope->isNative());
     JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass()));
 
     BytecodeEmitter bce(/* parent = */ NULL, &parser, &sc, script, callerFrame, !!globalScope,
                         options.lineno);
     if (!bce.init())
@@ -304,17 +309,17 @@ frontend::CompileFunctionBody(JSContext 
     if (!FoldConstants(cx, pn, &parser))
         return false;
 
     Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options,
                                                   staticLevel, ss, 0, length));
     if (!script)
         return false;
 
-    if (!funtc.generateBindings(cx, &script->bindings))
+    if (!funtc.generateFunctionBindings(cx, &script->bindings))
         return false;
 
     BytecodeEmitter funbce(/* parent = */ NULL, &parser, &funsc, script, /* callerFrame = */ NULL,
                            /* hasGlobalScope = */ false, options.lineno);
     if (!funbce.init())
         return false;
 
     if (!AnalyzeFunctions(&parser))
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -878,16 +878,30 @@ ClonedBlockDepth(BytecodeEmitter *bce)
     for (StaticBlockObject *b = bce->blockChain; b; b = b->enclosingBlock()) {
         if (b->needsClone())
             ++clonedBlockDepth;
     }
 
     return clonedBlockDepth;
 }
 
+static uint16_t
+AliasedNameToSlot(JSScript *script, PropertyName *name)
+{
+    unsigned slot = CallObject::RESERVED_SLOTS;
+    BindingIter bi(script->bindings);
+    for (; bi->name() != name; bi++) {
+        if (bi->aliased())
+            slot++;
+    }
+
+    JS_ASSERT(bi->aliased());
+    return slot;
+}
+
 static bool
 EmitAliasedVarOp(JSContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce)
 {
     unsigned skippedScopes = 0;
     BytecodeEmitter *bceOfDef = bce;
     if (pn->isUsed()) {
         /*
          * As explained in BindNameToSlot, the 'level' of a use indicates how
@@ -906,23 +920,23 @@ EmitAliasedVarOp(JSContext *cx, JSOp op,
     } else {
         JS_ASSERT(pn->isDefn());
         JS_ASSERT(pn->pn_cookie.level() == bce->script->staticLevel);
     }
 
     ScopeCoordinate sc;
     if (IsArgOp(pn->getOp())) {
         sc.hops = skippedScopes + ClonedBlockDepth(bceOfDef);
-        sc.slot = bceOfDef->script->bindings.formalIndexToSlot(pn->pn_cookie.slot());
+        sc.slot = AliasedNameToSlot(bceOfDef->script, pn->name());
     } else {
         JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION));
         unsigned local = pn->pn_cookie.slot();
         if (local < bceOfDef->script->bindings.numVars()) {
             sc.hops = skippedScopes + ClonedBlockDepth(bceOfDef);
-            sc.slot = bceOfDef->script->bindings.varIndexToSlot(local);
+            sc.slot = AliasedNameToSlot(bceOfDef->script, pn->name());
         } else {
             unsigned depth = local - bceOfDef->script->bindings.numVars();
             StaticBlockObject *b = bceOfDef->blockChain;
             while (!b->containsVarAtDepth(depth)) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = b->enclosingBlock();
             }
@@ -1006,22 +1020,53 @@ EmitVarIncDec(JSContext *cx, ParseNode *
 
     UpdateDecomposeLength(bce, start);
     return true;
 }
 
 bool
 BytecodeEmitter::isAliasedName(ParseNode *pn)
 {
-    if (sc->bindingsAccessedDynamically())
-        return true;
     Definition *dn = pn->resolve();
     JS_ASSERT(dn->isDefn());
     JS_ASSERT(!dn->isPlaceholder());
-    return dn->isClosed();
+    JS_ASSERT(dn->isBound());
+
+    /* If dn is in an enclosing function, it is definitely aliased. */
+    if (dn->pn_cookie.level() != script->staticLevel)
+        return true;
+
+    switch (dn->kind()) {
+      case Definition::LET:
+        /*
+         * There are two ways to alias a let variable: nested functions and
+         * dynamic scope operations. (This is overly conservative since the
+         * bindingsAccessedDynamically flag is function-wide.)
+         */
+        return dn->isClosed() || sc->bindingsAccessedDynamically();
+      case Definition::ARG:
+        /*
+         * Consult the bindings, since they already record aliasing. We might
+         * be tempted to use the same definition as VAR/CONST/LET, but there is
+         * a problem caused by duplicate arguments: only the last argument with
+         * a given name is aliased. This is necessary to avoid generating a
+         * shape for the call object with with more than one name for a given
+         * slot (which violates internal engine invariants). All this means that
+         * the '|| sc->bindingsAccessedDynamically' disjunct is incorrect since
+         * it will mark both parameters in function(x,x) as aliased.
+         */
+        return script->formalIsAliased(pn->pn_cookie.slot());
+      case Definition::VAR:
+      case Definition::CONST:
+        return script->varIsAliased(pn->pn_cookie.slot());
+      case Definition::PLACEHOLDER:
+      case Definition::NAMED_LAMBDA:
+        JS_NOT_REACHED("unexpected dn->kind");
+    }
+    return false;
 }
 
 /*
  * Adjust the slot for a block local to account for the number of variables
  * that share the same index space with locals. Due to the incremental code
  * generation for top-level script, we do the adjustment via code patching in
  * js::frontend::CompileScript; see comments there.
  *
@@ -2563,20 +2608,20 @@ 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;
         unsigned varIndex = bce->script->bindings.argumentsVarIndex(cx);
-        if (bce->sc->bindingsAccessedDynamically()) {
+        if (bce->script->varIsAliased(varIndex)) {
             ScopeCoordinate sc;
             sc.hops = 0;
-            sc.slot = bce->script->bindings.varIndexToSlot(varIndex);
+            sc.slot = AliasedNameToSlot(bce->script, cx->runtime->atomState.argumentsAtom);
             if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
                 return false;
         } else {
             if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, bce))
                 return false;
         }
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
@@ -4812,17 +4857,17 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
                .setVersion(parent->getVersion());
         Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
                                                       parent->staticLevel + 1,
                                                       bce->script->scriptSource(),
                                                       funbox->bufStart, funbox->bufEnd));
         if (!script)
             return false;
 
-        script->bindings.transferFrom(&funbox->bindings);
+        script->bindings = funbox->bindings;
 
         BytecodeEmitter bce2(bce, bce->parser, &sc, script, bce->callerFrame, bce->hasGlobalScope,
                              pn->pn_pos.begin.lineno);
         if (!bce2.init())
             return false;
 
         /* We measured the max scope depth when we parsed the function. */
         if (!EmitFunctionScript(cx, &bce2, pn->pn_body))
@@ -4860,18 +4905,20 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
             return false;
         bce->switchToMain();
 
         /* Emit NOP for the decompiler. */
         if (!EmitFunctionDefNop(cx, bce, index))
             return false;
     } else {
 #ifdef DEBUG
-        BindingIter bi(cx, bce->script->bindings.lookup(cx, fun->atom->asPropertyName()));
-        JS_ASSERT(bi->kind == VARIABLE || bi->kind == CONSTANT || bi->kind == ARGUMENT);
+        BindingIter bi(bce->script->bindings);
+        while (bi->name() != fun->atom)
+            bi++;
+        JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT);
         JS_ASSERT(bi.frameIndex() < JS_BIT(20));
 #endif
         pn->pn_index = index;
         if (NewSrcNote(cx, bce, SRC_CONTINUE) < 0)
             return false;
         if (!EmitIndexOp(cx, JSOP_LAMBDA, index, bce))
             return false;
         JS_ASSERT(pn->getOp() == JSOP_GETLOCAL || pn->getOp() == JSOP_GETARG);
@@ -5939,17 +5986,20 @@ EmitDefaults(JSContext *cx, BytecodeEmit
             if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                 return false;
             ptrdiff_t hop = bce->offset();
             if (EmitJump(cx, bce, JSOP_GOTO, 0) < 0)
                 return false;
 
             // It doesn't matter if this is correct with respect to aliasing or
             // not. Only the decompiler is going to see it.
-            BindingIter bi(cx, bce->script->bindings.lookup(cx, arg->pn_left->name()));
+            PropertyName *name = arg->pn_left->name();
+            BindingIter bi(bce->script->bindings);
+            while (bi->name() != name)
+                bi++;
             if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, bi.frameIndex(), bce))
                 return false;
             SET_JUMP_OFFSET(bce->code(hop), bce->offset() - hop);
         }
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
     JS_ASSERT(jumpoff == top + ptrdiff_t(tableSize));
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -995,17 +995,17 @@ LeaveFunction(ParseNode *fn, Parser *par
                 outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER;
             }
 
             /* Mark the outer dn as escaping. */
             outer_dn->pn_dflags |= PND_CLOSED;
         }
     }
 
-    if (!funtc->generateBindings(cx, &funbox->bindings))
+    if (!funtc->generateFunctionBindings(cx, &funbox->bindings))
         return false;
 
     funtc->lexdeps.releaseMap(cx);
     return true;
 }
 
 /*
  * DefineArg is called for both the arguments of a regular function definition
--- a/js/src/frontend/TreeContext-inl.h
+++ b/js/src/frontend/TreeContext-inl.h
@@ -76,18 +76,17 @@ TreeContext::TreeContext(Parser *prs, Sh
     parserTC(&prs->tc),
     lexdeps(prs->context),
     parent(prs->tc),
     innermostWith(NULL),
     funcStmts(NULL),
     hasReturnExpr(false),
     hasReturnVoid(false),
     inForInit(false),
-    inDeclDestructuring(false),
-    hasDuplicateArgument_(false)
+    inDeclDestructuring(false)
 {
     prs->tc = this;
 }
 
 inline bool
 TreeContext::init()
 {
     if (!frontend::GenerateBlockId(this, this->bodyid))
--- a/js/src/frontend/TreeContext.cpp
+++ b/js/src/frontend/TreeContext.cpp
@@ -124,17 +124,17 @@ TreeContext::define(JSContext *cx, Prope
     return true;
 }
 
 void
 TreeContext::prepareToAddDuplicateArg(Definition *prevDecl)
 {
     JS_ASSERT(prevDecl->kind() == Definition::ARG);
     JS_ASSERT(decls_.lookupFirst(prevDecl->name()) == prevDecl);
-    hasDuplicateArgument_ = true;
+    JS_ASSERT(!prevDecl->isClosed());
     decls_.remove(prevDecl->name());
 }
 
 void
 TreeContext::updateDecl(JSAtom *atom, ParseNode *pn)
 {
     Definition *oldDecl = decls_.lookupFirst(atom);
 
@@ -165,48 +165,69 @@ TreeContext::updateDecl(JSAtom *atom, Pa
 
 void
 TreeContext::popLetDecl(JSAtom *atom)
 {
     JS_ASSERT(decls_.lookupFirst(atom)->isLet());
     decls_.remove(atom);
 }
 
+void
+AppendPackedBindings(const TreeContext *tc, const DeclVector &vec, Binding *dst)
+{
+    for (unsigned i = 0; i < vec.length(); ++i, ++dst) {
+        Definition *dn = vec[i];
+        PropertyName *name = dn->name();
+
+        BindingKind kind;
+        switch (dn->kind()) {
+          case Definition::VAR:
+            kind = VARIABLE;
+            break;
+          case Definition::CONST:
+            kind = CONSTANT;
+            break;
+          case Definition::ARG:
+            kind = ARGUMENT;
+            break;
+          case Definition::LET:
+          case Definition::NAMED_LAMBDA:
+          case Definition::PLACEHOLDER:
+            JS_NOT_REACHED("unexpected dn->kind");
+        }
+
+        /*
+         * Bindings::init does not check for duplicates so we must ensure that
+         * only one binding with a given name is marked aliased. tc->decls
+         * maintains the canonical definition for each name, so use that.
+         */
+        JS_ASSERT_IF(dn->isClosed(), tc->decls().lookupFirst(name) == dn);
+        bool aliased = dn->isClosed() ||
+                       (tc->sc->bindingsAccessedDynamically() &&
+                        tc->decls().lookupFirst(name) == dn);
+
+        *dst = Binding(name, kind, aliased);
+    }
+}
+
 bool
-TreeContext::generateBindings(JSContext *cx, Bindings *bindings) const
+TreeContext::generateFunctionBindings(JSContext *cx, Bindings *bindings) const
 {
     JS_ASSERT(sc->inFunction());
 
-    if (hasDuplicateArgument_)
-        bindings->noteDup();
+    unsigned count = args_.length() + vars_.length();
+    Binding *packedBindings = cx->tempLifoAlloc().newArrayUninitialized<Binding>(count);
+    if (!packedBindings) {
+        js_ReportOutOfMemory(cx);
+        return false;
+    }
 
-    if (!bindings->ensureShape(cx))
+    AppendPackedBindings(this, args_, packedBindings);
+    AppendPackedBindings(this, vars_, packedBindings + args_.length());
+
+    if (!bindings->init(cx, args_.length(), vars_.length(), packedBindings))
         return false;
 
-    bool hasAnyClosedVar = false;
-
-    Rooted<JSAtom *> atom(cx);
-    for (unsigned i = 0; i < args_.length(); ++i) {
-        Definition *arg = args_[i];
-        JS_ASSERT(arg->kind() == Definition::ARG);
-        atom = arg->name();
-        if (arg->isClosed())
-            hasAnyClosedVar = true;
-        if (!bindings->add(cx, atom, ARGUMENT, arg->isClosed()))
-            return false;
-    }
-
-    for (unsigned i = 0; i < vars_.length(); ++i) {
-        Definition *var = vars_[i];
-        JS_ASSERT(var->kind() == Definition::VAR || var->kind() == Definition::CONST);
-        atom = var->name();
-        if (var->isClosed())
-            hasAnyClosedVar = true;
-        BindingKind kind = var->kind() == Definition::VAR ? VARIABLE : CONSTANT;
-        if (!bindings->add(cx, atom, kind, var->isClosed()))
-            return false;
-    }
-
-    if (hasAnyClosedVar || sc->bindingsAccessedDynamically() || sc->funHasExtensibleScope())
+    if (bindings->hasAnyAliasedBindings() || sc->funHasExtensibleScope())
         sc->fun()->flags |= JSFUN_HEAVYWEIGHT;
 
     return true;
 }
--- a/js/src/frontend/TreeContext.h
+++ b/js/src/frontend/TreeContext.h
@@ -308,17 +308,17 @@ struct TreeContext {                /* t
      *    the use/def nodes, but the emitter occasionally needs 'bindings' for
      *    various scope-related queries.
      *  - Bindings provide the initial js::Shape to use when creating a dynamic
      *    scope object (js::CallObject) for the function. This shape is used
      *    during dynamic name lookup.
      *  - Sometimes a script's bindings are accessed at runtime to retrieve the
      *    contents of the lexical scope (e.g., from the debugger).
      */
-    bool generateBindings(JSContext *cx, Bindings *bindings) const;
+    bool generateFunctionBindings(JSContext *cx, Bindings *bindings) const;
 
   public:
     ParseNode       *yieldNode;     /* parse node for a yield expression that might
                                        be an error if we turn out to be inside a
                                        generator expression */
     FunctionBox     *functionList;
 
     // A strict mode error found in this scope or one of its children. It is
@@ -357,21 +357,16 @@ struct TreeContext {                /* t
     // context's AtomList, etc. etc.  CheckDestructuring will do that work
     // later.
     //
     // The comments atop CheckDestructuring explain the distinction between
     // assignment-like and declaration-like destructuring patterns, and why
     // they need to be treated differently.
     bool            inDeclDestructuring:1;
 
-  private:
-    // Set to indicate args_ contains a duplicate
-    bool            hasDuplicateArgument_:1;
-  public:
-
     inline TreeContext(Parser *prs, SharedContext *sc, unsigned staticLevel, uint32_t bodyid);
     inline ~TreeContext();
 
     inline bool init();
 
     inline void setQueuedStrictModeError(CompileError *e);
 
     unsigned blockid();
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -93,87 +93,70 @@ ScriptAnalysis::addJump(JSContext *cx, u
 
 void
 ScriptAnalysis::analyzeBytecode(JSContext *cx)
 {
     JS_ASSERT(cx->compartment->activeAnalysis);
     JS_ASSERT(!ranBytecode());
     LifoAlloc &tla = cx->typeLifoAlloc();
 
-    unsigned length = script->length;
-    unsigned nargs = script->function() ? script->function()->nargs : 0;
-
     numSlots = TotalSlots(script);
 
+    unsigned length = script->length;
     codeArray = tla.newArray<Bytecode*>(length);
     escapedSlots = tla.newArray<bool>(numSlots);
 
     if (!codeArray || !escapedSlots) {
         setOOM(cx);
         return;
     }
 
     PodZero(codeArray, length);
 
     /*
      * Populate arg and local slots which can escape and be accessed in ways
      * other than through ARG* and LOCAL* opcodes (though arguments can still
      * be indirectly read but not written through 'arguments' properties).
      * All escaping locals are treated as having possible use-before-defs.
-     * Conservatively use 'hasArgsBinding' instead of 'needsArgsObj'
-     * (needsArgsObj requires SSA which requires escapedSlots).
+     * Conservatively use 'argumentsHasVarBinding' instead of 'needsArgsObj'
+     * (needsArgsObj requires SSA which requires escapedSlots). Lastly, the
+     * debugger can access any local at any time. Even though debugger
+     * reads/writes are monitored by the DebugScopeProxy, this monitoring
+     * updates the flow-insensitive type sets, so we cannot use SSA.
      */
 
     PodZero(escapedSlots, numSlots);
 
-    if (script->bindingsAccessedDynamically || script->compartment()->debugMode() ||
-        script->argumentsHasVarBinding())
-    {
-        for (unsigned i = 0; i < nargs; i++)
-            escapedSlots[ArgSlot(i)] = true;
-    } else {
-        for (uint32_t i = 0; i < script->numClosedArgs(); i++) {
-            unsigned arg = script->getClosedArg(i);
-            JS_ASSERT(arg < nargs);
-            escapedSlots[ArgSlot(arg)] = true;
-        }
-    }
+    bool allVarsAliased = script->compartment()->debugMode();
+    bool allArgsAliased = allVarsAliased || script->argumentsHasVarBinding();
 
-    if (script->bindingsAccessedDynamically || script->compartment()->debugMode()) {
-        for (unsigned i = 0; i < script->nfixed; i++)
-            escapedSlots[LocalSlot(script, i)] = true;
-    } else {
-        for (uint32_t i = 0; i < script->numClosedVars(); i++) {
-            unsigned local = script->getClosedVar(i);
-            JS_ASSERT(local < script->nfixed);
-            escapedSlots[LocalSlot(script, local)] = true;
-        }
+    for (BindingIter bi(script->bindings); bi; bi++) {
+        if (bi->kind() == ARGUMENT)
+            escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased();
+        else
+            escapedSlots[LocalSlot(script, bi.frameIndex())] = allVarsAliased || bi->aliased();
     }
 
     /*
      * If the script is in debug mode, JS_SetFrameReturnValue can be called at
      * any safe point.
      */
     if (cx->compartment->debugMode())
         usesReturnValue_ = true;
 
     bool heavyweight = script->function() && script->function()->isHeavyweight();
 
     isJaegerCompileable = true;
 
     isInlineable = true;
-    if (script->numClosedArgs() || script->numClosedVars() || heavyweight ||
-        script->bindingsAccessedDynamically || script->argumentsHasVarBinding() ||
-        cx->compartment->debugMode())
-    {
+    if (heavyweight || script->argumentsHasVarBinding() || cx->compartment->debugMode())
         isInlineable = false;
-    }
 
     modifiesArguments_ = false;
-    if (script->numClosedArgs() || heavyweight)
+    if (heavyweight)
         modifiesArguments_ = true;
 
     canTrackVars = true;
 
     /*
      * If we are in the middle of one or more jumps, the offset of the highest
      * target jumping over this bytecode.  Includes implicit jumps from
      * try/catch/finally blocks.
@@ -1941,21 +1924,20 @@ ScriptAnalysis::needsArgsObj(JSContext *
 
 bool
 ScriptAnalysis::needsArgsObj(JSContext *cx)
 {
     JS_ASSERT(script->argumentsHasVarBinding());
 
     /*
      * Since let variables and dynamic name access are not tracked, we cannot
-     * soundly perform this analysis in their presence. Debuggers may want to
-     * see 'arguments', so assume every arguments object escapes. Generators
-     * can be suspended when the speculation fails, so disallow it also.
+     * soundly perform this analysis in their presence. Generators can be
+     * suspended when the speculation fails, so disallow it also.
      */
-    if (script->bindingsAccessedDynamically || script->numClosedArgs() > 0 ||
+    if (script->bindingsAccessedDynamically || script->funHasAnyAliasedFormal ||
         localsAliasStack() || cx->compartment->debugMode() || script->isGenerator)
     {
         return true;
     }
 
     unsigned pcOff = script->argumentsBytecode() - script->code;
 
     SeenVector seen(cx);
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -187,16 +187,17 @@ BEGIN_TEST(testXDR_atline)
 
 END_TEST(testXDR_atline)
 
 BEGIN_TEST(testXDR_bug506491)
 {
     const char *s =
         "function makeClosure(s, name, value) {\n"
         "    eval(s);\n"
+        "    Math.sin(value);\n"
         "    return let (n = name, v = value) function () { return String(v); };\n"
         "}\n"
         "var f = makeClosure('0;', 'status', 'ok');\n";
 
     // compile
     JSScript *script = JS_CompileScript(cx, global, s, strlen(s), __FILE__, __LINE__);
     CHECK(script);
 
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -412,30 +412,30 @@ JS_FunctionHasLocalNames(JSContext *cx, 
 {
     return fun->script()->bindings.count() > 0;
 }
 
 extern JS_PUBLIC_API(uintptr_t *)
 JS_GetFunctionLocalNameArray(JSContext *cx, JSFunction *fun, void **markp)
 {
     BindingVector bindings(cx);
-    if (!GetOrderedBindings(cx, fun->script()->bindings, &bindings))
+    if (!FillBindingVector(fun->script()->bindings, &bindings))
         return NULL;
 
     /* Munge data into the API this method implements.  Avert your eyes! */
     *markp = cx->tempLifoAlloc().mark();
 
     uintptr_t *names = cx->tempLifoAlloc().newArray<uintptr_t>(bindings.length());
     if (!names) {
         js_ReportOutOfMemory(cx);
         return NULL;
     }
 
     for (size_t i = 0; i < bindings.length(); i++)
-        names[i] = reinterpret_cast<uintptr_t>(bindings[i].name);
+        names[i] = reinterpret_cast<uintptr_t>(bindings[i].name());
 
     return names;
 }
 
 extern JS_PUBLIC_API(JSAtom *)
 JS_LocalNameToAtom(uintptr_t w)
 {
     return reinterpret_cast<JSAtom *>(w);
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -647,22 +647,22 @@ js::FunctionToString(JSContext *cx, Hand
             // of bug 755821 should be cobbling the arguments passed into the
             // Function constructor into the source string.
             if (!out.append("("))
                 return NULL;
 
             // Fish out the argument names.
             BindingVector *localNames = cx->new_<BindingVector>(cx);
             js::ScopedDeletePtr<BindingVector> freeNames(localNames);
-            if (!GetOrderedBindings(cx, script->bindings, localNames))
+            if (!FillBindingVector(script->bindings, localNames))
                 return NULL;
             for (unsigned i = 0; i < fun->nargs; i++) {
                 if ((i && !out.append(", ")) ||
                     (i == unsigned(fun->nargs - 1) && fun->hasRest() && !out.append("...")) ||
-                    !out.append((*localNames)[i].name)) {
+                    !out.append((*localNames)[i].name())) {
                     return NULL;
                 }
             }
             if (!out.append(") {\n"))
                 return NULL;
         }
         if ((bodyOnly && !funCon) || addUseStrict) {
             // We need to get at the body either because we're only supposed to
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2447,19 +2447,17 @@ Shape::Range::AutoRooter::trace(JSTracer
 {
     if (r->cursor)
         MarkShapeRoot(trc, const_cast<Shape**>(&r->cursor), "Shape::Range::AutoRooter");
 }
 
 void
 Bindings::AutoRooter::trace(JSTracer *trc)
 {
-    if (bindings->lastBinding)
-        MarkShapeRoot(trc, reinterpret_cast<Shape**>(&bindings->lastBinding),
-                      "Bindings::AutoRooter lastBinding");
+    bindings->trace(trc);
 }
 
 void
 RegExpStatics::AutoRooter::trace(JSTracer *trc)
 {
     if (statics->matchPairsInput)
         MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->matchPairsInput),
                        "RegExpStatics::AutoRooter matchPairsInput");
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -927,18 +927,16 @@ class TypeScript
     /* Dynamic types generated at points within this script. */
     TypeResult *dynamicList;
 
     /* Array of type type sets for variables and JOF_TYPESET ops. */
     TypeSet *typeArray() { return (TypeSet *) (uintptr_t(this) + sizeof(TypeScript)); }
 
     static inline unsigned NumTypeSets(JSScript *script);
 
-    static bool SetScope(JSContext *cx, JSScript *script, JSObject *scope);
-
     static inline TypeSet *ReturnTypes(JSScript *script);
     static inline TypeSet *ThisTypes(JSScript *script);
     static inline TypeSet *ArgTypes(JSScript *script, unsigned i);
     static inline TypeSet *LocalTypes(JSScript *script, unsigned i);
 
     /* Follows slot layout in jsanalyze.h, can get this/arg/local type sets. */
     static inline TypeSet *SlotTypes(JSScript *script, unsigned slot);
 
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -568,24 +568,18 @@ UseNewTypeForClone(JSFunction *fun)
      * instance a singleton type and clone the underlying script.
      */
 
     JSScript *script = fun->script();
 
     if (script->length >= 50)
         return false;
 
-    if (script->hasConsts() ||
-        script->hasObjects() ||
-        script->hasRegexps() ||
-        script->hasClosedArgs() ||
-        script->hasClosedVars())
-    {
+    if (script->hasConsts() || script->hasObjects() || script->hasRegexps() || fun->isHeavyweight())
         return false;
-    }
 
     bool hasArguments = false;
     bool hasApply = false;
 
     for (jsbytecode *pc = script->code;
          pc != script->code + script->length;
          pc += GetBytecodeLength(pc))
     {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1090,17 +1090,17 @@ js_NewPrinter(JSContext *cx, const char 
     jp->script = NULL;
     jp->dvgfence = NULL;
     jp->pcstack = NULL;
     jp->fun = fun;
     jp->localNames = NULL;
     jp->decompiledOpcodes = NULL;
     if (fun && fun->isInterpreted() && fun->script()->bindings.count() > 0) {
         jp->localNames = cx->new_<BindingVector>(cx);
-        if (!jp->localNames || !GetOrderedBindings(cx, fun->script()->bindings, jp->localNames)) {
+        if (!jp->localNames || !FillBindingVector(fun->script()->bindings, jp->localNames)) {
             js_DestroyPrinter(jp);
             return NULL;
         }
     }
     return jp;
 }
 
 void
@@ -1748,17 +1748,17 @@ DecompileSwitch(SprintStack *ss, TableEn
 #define LOCAL_ASSERT_RV(expr, rv)                                             \
     LOCAL_ASSERT_CUSTOM(expr, return (rv))
 
 static JSAtom *
 GetArgOrVarAtom(JSPrinter *jp, unsigned slot)
 {
     LOCAL_ASSERT_RV(jp->fun, NULL);
     LOCAL_ASSERT_RV(slot < jp->fun->script()->bindings.count(), NULL);
-    JSAtom *name = (*jp->localNames)[slot].name;
+    JSAtom *name = (*jp->localNames)[slot].name();
 #if !JS_HAS_DESTRUCTURING
     LOCAL_ASSERT_RV(name, NULL);
 #endif
     return name;
 }
 
 #define LOCAL_ASSERT(expr)      LOCAL_ASSERT_RV(expr, "")
 
@@ -4693,17 +4693,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * more commonly, arena-allocating from cx->tempLifoAlloc
                      * Therefore after InitSprintStack succeeds, we must release
                      * to mark before returning.
                      */
                     LifoAllocScope las(&cx->tempLifoAlloc());
                     if (fun->script()->bindings.count() > 0) {
                         innerLocalNames = cx->new_<BindingVector>(cx);
                         if (!innerLocalNames ||
-                            !GetOrderedBindings(cx, fun->script()->bindings, innerLocalNames))
+                            !FillBindingVector(fun->script()->bindings, innerLocalNames))
                         {
                             return NULL;
                         }
                     } else {
                         innerLocalNames = NULL;
                     }
                     inner = fun->script();
                     if (!InitSprintStack(cx, &ss2, jp, StackDepth(inner)))
@@ -6041,17 +6041,17 @@ bool
 ExpressionDecompiler::init()
 {
     if (!sprinter.init())
         return false;
 
     localNames = cx->new_<BindingVector>(cx);
     if (!localNames)
         return false;
-    if (!GetOrderedBindings(cx, script->bindings, localNames))
+    if (!FillBindingVector(script->bindings, localNames))
         return false;
 
     return true;
 }
 
 bool
 ExpressionDecompiler::write(const char *s)
 {
@@ -6101,26 +6101,26 @@ ExpressionDecompiler::findLetVar(jsbytec
     return NULL;
 }
 
 JSAtom *
 ExpressionDecompiler::getArg(unsigned slot)
 {
     JS_ASSERT(fun);
     JS_ASSERT(slot < script->bindings.count());
-    return (*localNames)[slot].name;
+    return (*localNames)[slot].name();
 }
 
 JSAtom *
 ExpressionDecompiler::getVar(unsigned slot)
 {
     JS_ASSERT(fun);
     slot += fun->nargs;
     JS_ASSERT(slot < script->bindings.count());
-    return (*localNames)[slot].name;
+    return (*localNames)[slot].name();
 }
 
 bool
 ExpressionDecompiler::getOutput(char **res)
 {
     ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
     *res = static_cast<char *>(cx->malloc_(len + 1));
     if (!*res)
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -1171,22 +1171,20 @@ Shape::setExtensibleParents(JSContext *c
 
     /* This is only used for Block and Call objects, which have a NULL proto. */
     return replaceLastProperty(cx, base, NULL, shape);
 }
 
 bool
 Bindings::setExtensibleParents(JSContext *cx)
 {
-    if (!ensureShape(cx))
-        return false;
-    Shape *newShape = Shape::setExtensibleParents(cx, lastBinding);
+    Shape *newShape = Shape::setExtensibleParents(cx, callObjShape_);
     if (!newShape)
         return false;
-    lastBinding = newShape;
+    callObjShape_ = newShape;
     return true;
 }
 
 inline
 InitialShapeEntry::InitialShapeEntry() : shape(NULL), proto(NULL)
 {
 }
 
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -260,19 +260,18 @@ class BaseShape : public js::gc::Cell
         NOT_EXTENSIBLE     =   0x40,
         INDEXED            =   0x80,
         BOUND_FUNCTION     =  0x100,
         VAROBJ             =  0x200,
         WATCHED            =  0x400,
         ITERATED_SINGLETON =  0x800,
         NEW_TYPE_UNKNOWN   = 0x1000,
         UNCACHEABLE_PROTO  = 0x2000,
-        CLOSED_VAR         = 0x4000,  /* This bit will be removed in bug 767013 */
 
-        OBJECT_FLAG_MASK   = 0x7ff8
+        OBJECT_FLAG_MASK   = 0x3ff8
     };
 
   private:
     Class               *clasp;         /* Class of referring object. */
     HeapPtrObject       parent;         /* Parent of referring object. */
     uint32_t            flags;          /* Vector of above flags. */
     uint32_t            slotSpan_;      /* Object slot span for BaseShapes at
                                          * dictionary last properties. */
@@ -306,17 +305,16 @@ class BaseShape : public js::gc::Cell
     inline BaseShape(const StackBaseShape &base);
 
     /* Not defined: BaseShapes must not be stack allocated. */
     ~BaseShape();
 
     inline BaseShape &operator=(const BaseShape &other);
 
     bool isOwned() const { return !!(flags & OWNED_SHAPE); }
-    bool isClosedVar() const { return !!(flags & CLOSED_VAR); }
 
     inline bool matchesGetterSetter(PropertyOp rawGetter,
                                     StrictPropertyOp rawSetter) const;
 
     inline void adoptUnowned(UnownedBaseShape *other);
     inline void setOwned(UnownedBaseShape *unowned);
 
     JSObject *getObjectParent() const { return parent; }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -46,220 +46,207 @@
 #include "vm/RegExpObject-inl.h"
 
 #include "frontend/TreeContext-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::frontend;
 
-BindingIter::BindingIter(JSContext *cx, const Bindings &bindings, Shape *shape)
-  : bindings_(&bindings), shape_(shape), rooter_(cx, &shape_)
-{
-    settle();
-}
-
-BindingIter::BindingIter(JSContext *cx, Init init)
-  : bindings_(init.bindings), shape_(init.shape), rooter_(cx, &shape_)
-{
-    settle();
-}
-
-BindingIter::BindingIter(JSContext *cx, Bindings &bindings)
-  : bindings_(&bindings), shape_(bindings.lastBinding), rooter_(cx, &shape_)
-{
-    settle();
-}
-
-void
-BindingIter::operator=(Init init)
-{
-    bindings_ = init.bindings;
-    shape_ = init.shape;
-    settle();
-}
-
-void
-BindingIter::settle()
-{
-    if (shape_.empty())
-        return;
-    Shape &shape = shape_.front();
-    jsid id = shape_.front().propid();
-    binding_.name = JSID_TO_ATOM(id)->asPropertyName();
-    binding_.kind = shape.slot() - CallObject::RESERVED_SLOTS < bindings_->numArgs()
-                    ? ARGUMENT
-                    : shape.writable() ? VARIABLE : CONSTANT;
-}
-
-bool
-js::GetOrderedBindings(JSContext *cx, Bindings &bindings, BindingVector *vec)
-{
-    JS_ASSERT(vec->empty());
-    if (!vec->reserve(bindings.count()))
-        return false;
-
-    for (BindingIter bi(cx, bindings); bi; bi++)
-        vec->infallibleAppend(*bi);
-
-    /* Variables/arguments are stored in reverse order. */
-    Reverse(vec->begin(), vec->end());
-    return true;
-}
-
-BindingIter::Init
-Bindings::lookup(JSContext *cx, PropertyName *name) const
-{
-    if (!lastBinding)
-        return BindingIter::Init(this, NULL);
-
-    const Bindings *self = this;
-    SkipRoot skipSelf(cx, &self);
-    Shape **_;
-    return BindingIter::Init(self, Shape::search(cx, lastBinding, NameToId(name), &_));
-}
-
 unsigned
 Bindings::argumentsVarIndex(JSContext *cx) const
 {
-    BindingIter bi(cx, lookup(cx, cx->runtime->atomState.argumentsAtom));
+    PropertyName *arguments = cx->runtime->atomState.argumentsAtom;
+    BindingIter bi(*this);
+    while (bi->name() != arguments)
+        bi++;
     return bi.frameIndex();
 }
 
 bool
-Bindings::add(JSContext *cx, HandleAtom name, BindingKind kind, bool aliased)
+Bindings::init(JSContext *cx, unsigned numArgs, unsigned numVars, Binding *bindingArray)
 {
-    if (!ensureShape(cx))
-        return false;
+    JS_ASSERT(!callObjShape_);
+    JS_ASSERT(!bindingArray_);
 
-    if (nargs + nvars == BINDING_COUNT_LIMIT) {
+    if (numArgs > UINT16_MAX || numVars > UINT16_MAX) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             (kind == ARGUMENT)
-                             ? JSMSG_TOO_MANY_FUN_ARGS
-                             : JSMSG_TOO_MANY_LOCALS);
+                             numArgs_ > numVars_ ?
+                             JSMSG_TOO_MANY_FUN_ARGS :
+                             JSMSG_TOO_MANY_LOCALS);
         return false;
     }
 
-    /*
-     * We still follow 10.2.3 of ES3 and make argument and variable properties
-     * of the Call objects enumerable. ES5 reformulated all of its Clause 10 to
-     * avoid objects as activations, something we should do too.
-     */
-    unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
+    bindingArray_ = bindingArray;
+    numArgs_ = numArgs;
+    numVars_ = numVars;
 
-    uint16_t *indexp;
-    uint32_t slot = CallObject::RESERVED_SLOTS;
 
-    if (kind == ARGUMENT) {
-        JS_ASSERT(nvars == 0);
-        indexp = &nargs;
-        slot += nargs;
-    } else {
-        JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
+    /*
+     * Get the initial shape to use when creating CallObjects for this script.
+     * Since unaliased variables are, by definition, only accessed by local
+     * operations and never through the scope chain, only give shapes to
+     * aliased variables. While the debugger may observe any scope object at
+     * any time, such accesses are mediated by DebugScopeProxy (see
+     * DebugScopeProxy::handleUnaliasedAccess).
+     */
 
-        indexp = &nvars;
-        if (kind == CONSTANT)
-            attrs |= JSPROP_READONLY;
-        slot += nargs + nvars;
-    }
+    JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == 2);
+    gc::AllocKind allocKind = gc::FINALIZE_OBJECT2_BACKGROUND;
+    JS_ASSERT(gc::GetGCKindSlots(allocKind) == CallObject::RESERVED_SLOTS);
+    callObjShape_ = EmptyShape::getInitialShape(cx, &CallClass, NULL, cx->global(),
+                                                allocKind, BaseShape::VAROBJ);
+
+#ifdef DEBUG
+    HashSet<PropertyName *> added(cx);
+    if (!added.init())
+        return false;
+#endif
 
-    RootedId id(cx);
-    if (!name) {
-        JS_ASSERT(kind == ARGUMENT); /* destructuring */
-        id = INT_TO_JSID(nargs);
-    } else {
-        id = AtomToId(name);
-    }
+    BindingIter bi(*this);
+    unsigned slot = CallObject::RESERVED_SLOTS;
+    for (unsigned i = 0, n = count(); i < n; i++, bi++) {
+        if (!bi->aliased())
+            continue;
+
+#ifdef DEBUG
+        /* The caller ensures no duplicate aliased names. */
+        JS_ASSERT(!added.has(bi->name()));
+        if (!added.put(bi->name()))
+            return false;
+#endif
 
-    uint32_t flags = BaseShape::VAROBJ | (aliased ? BaseShape::CLOSED_VAR : 0);
-    StackBaseShape base(&CallClass, cx->global(), flags);
-    UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
-    if (!nbase)
-        return false;
-
-    StackShape child(nbase, id, slot, 0, attrs, Shape::HAS_SHORTID, *indexp);
+        StackBaseShape base(&CallClass, cx->global(), BaseShape::VAROBJ);
+        UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
+        if (!nbase)
+            return false;
 
-    /* Shapes in bindings cannot be dictionaries. */
-    Shape *shape = lastBinding->getChildBinding(cx, child);
-    if (!shape)
-        return false;
+        RootedId id(cx, NameToId(bi->name()));
+        unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE |
+                         (bi->kind() == CONSTANT ? JSPROP_READONLY : 0);
+        unsigned frameIndex = bi.frameIndex();
+        StackShape child(nbase, id, slot++, 0, attrs, Shape::HAS_SHORTID, frameIndex);
 
-    lastBinding = shape;
-    ++*indexp;
+        callObjShape_ = callObjShape_->getChildBinding(cx, child);
+        if (!callObjShape_)
+            return false;
+    }
+    JS_ASSERT(!bi);
+
     return true;
 }
 
-Shape *
-Bindings::callObjectShape(JSContext *cx) const
+uint8_t *
+Bindings::switchStorageTo(Binding *newBindingArray)
 {
-    if (!hasDup())
-        return lastBinding;
-
-    /*
-     * Build a vector of non-duplicate properties in order from last added
-     * to first (i.e., the order we normally have iterate over Shapes). Choose
-     * the last added property in each set of dups.
-     */
-    Vector<Shape *> shapes(cx);
-    HashSet<jsid> seen(cx);
-    if (!seen.init())
-        return NULL;
-
-    for (Shape::Range r = lastBinding->all(); !r.empty(); r.popFront()) {
-        Shape &s = r.front();
-        HashSet<jsid>::AddPtr p = seen.lookupForAdd(s.propid());
-        if (!p) {
-            if (!seen.add(p, s.propid()))
-                return NULL;
-            if (!shapes.append(&s))
-                return NULL;
-        }
-    }
-
-    /*
-     * Now build the Shape without duplicate properties.
-     */
-    RootedShape shape(cx, initialShape(cx));
-    for (int i = shapes.length() - 1; i >= 0; --i) {
-        shape = shape->getChildBinding(cx, shapes[i]);
-        if (!shape)
-            return NULL;
-    }
-
-    return shape;
+    PodCopy(newBindingArray, bindingArray_, count());
+    bindingArray_ = newBindingArray;
+    return reinterpret_cast<uint8_t *>(newBindingArray + count());
 }
 
 bool
-Bindings::extractClosedArgsAndVars(JSContext *cx, SlotVector *args, SlotVector *vars)
+Bindings::clone(JSContext *cx, uint8_t *dstScriptData, HandleScript srcScript)
 {
-    if (!ensureShape(cx))
-        return false;
+    /* The clone has the same bindingArray_ offset as 'src'. */
+    Bindings &src = srcScript->bindings;
+    ptrdiff_t off = (uint8_t *)src.bindingArray_ - srcScript->data;
+    JS_ASSERT(off >= 0);
+    JS_ASSERT(off <= (srcScript->code - srcScript->data));
+    Binding *dstPackedBindings = (Binding *)(dstScriptData + off);
+
+    /*
+     * Since atoms are shareable throughout the runtime, we can simply copy
+     * the source's bindingArray directly.
+     */
+    PodCopy(dstPackedBindings, src.bindingArray_, src.count());
+    return init(cx, src.numArgs(), src.numVars(), dstPackedBindings);
+}
+
+template<XDRMode mode>
+static bool
+XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, unsigned numVars,
+                  HandleScript script)
+{
+    JSContext *cx = xdr->cx();
+
+    if (mode == XDR_ENCODE) {
+        for (BindingIter bi(script->bindings); bi; bi++) {
+            JSAtom *atom = bi->name();
+            if (!XDRAtom(xdr, &atom))
+                return false;
+        }
 
-    for (Shape::Range r = lastBinding->all(); !r.empty(); r.popFront()) {
-        Shape &shape = r.front();
-        if (shape.base()->isClosedVar()) {
-            unsigned i = shape.slot() - CallObject::RESERVED_SLOTS;
-            if (i < nargs) {
-                if (!args->append(i))
-                    return false;
-            } else {
-                JS_ASSERT(i < count());
-                if (!vars->append(i - nargs))
-                    return false;
-            }
+        for (BindingIter bi(script->bindings); bi; bi++) {
+            uint8_t u8 = (uint8_t(bi->kind()) << 1) | bi->aliased();
+            if (!xdr->codeUint8(&u8))
+                return false;
+        }
+    } else {
+        unsigned nameCount = numArgs + numVars;
+
+        AutoValueVector atoms(cx);
+        if (!atoms.resize(nameCount))
+            return false;
+        for (unsigned i = 0; i < nameCount; i++) {
+            JSAtom *atom;
+            if (!XDRAtom(xdr, &atom))
+                return false;
+            atoms[i] = StringValue(atom);
         }
+
+        Binding *bindingArray = las.alloc().newArrayUninitialized<Binding>(nameCount);
+        if (!bindingArray)
+            return false;
+        for (unsigned i = 0; i < nameCount; i++) {
+            uint8_t u8;
+            if (!xdr->codeUint8(&u8))
+                return false;
+
+            PropertyName *name = atoms[i].toString()->asAtom().asPropertyName();
+            BindingKind kind = BindingKind(u8 >> 1);
+            bool aliased = bool(u8 & 1);
+
+            bindingArray[i] = Binding(name, kind, aliased);
+        }
+
+        if (!script->bindings.init(cx, numArgs, numVars, bindingArray))
+            return false;
     }
+
     return true;
 }
 
+bool
+Bindings::bindingIsAliased(unsigned bindingIndex)
+{
+    JS_ASSERT(bindingIndex < count());
+    return bindingArray_[bindingIndex].aliased();
+}
+
 void
 Bindings::trace(JSTracer *trc)
 {
-    if (lastBinding)
-        MarkShape(trc, &lastBinding, "shape");
+    if (callObjShape_)
+        MarkShape(trc, &callObjShape_, "callObjShape");
+
+    for (Binding *b = bindingArray_, *end = b + count(); b != end; b++) {
+        PropertyName *name = b->name();
+        MarkStringUnbarriered(trc, &name, "bindingArray");
+    }
+}
+
+bool
+js::FillBindingVector(Bindings &bindings, BindingVector *vec)
+{
+    for (BindingIter bi(bindings); bi; bi++) {
+        if (!vec->append(*bi))
+            return false;
+    }
+
+    return true;
 }
 
 template<XDRMode mode>
 static bool
 XDRScriptConst(XDRState<mode> *xdr, HeapValue *vp)
 {
     /*
      * A script constant can be an arbitrary primitive value as they are used
@@ -373,35 +360,36 @@ js::XDRScript(XDRState<mode> *xdr, Handl
     /* NB: Keep this in sync with CloneScript. */
 
     enum ScriptBits {
         NoScriptRval,
         SavedCallerFun,
         StrictModeCode,
         ContainsDynamicNameAccess,
         FunHasExtensibleScope,
+        FunHasAnyAliasedFormal,
         ArgumentsHasVarBinding,
         NeedsArgsObj,
         OwnFilename,
         ParentFilename,
         IsGenerator,
         IsGeneratorExp,
         OwnSource,
         ExplicitUseStrict
     };
 
     uint32_t length, lineno, nslots;
-    uint32_t natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, nClosedArgs, nClosedVars, i;
+    uint32_t natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, i;
     uint32_t prologLength, version;
     uint32_t nTypeSets = 0;
     uint32_t scriptBits = 0;
 
     JSContext *cx = xdr->cx();
     Rooted<JSScript*> script(cx);
-    nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = nClosedArgs = nClosedVars = 0;
+    nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = 0;
     jssrcnote *notes = NULL;
 
     /* XDR arguments and vars. */
     uint16_t nargs = 0, nvars = 0;
     uint32_t argsVars = 0;
     if (mode == XDR_ENCODE) {
         script = *scriptp;
         JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
@@ -411,50 +399,16 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         argsVars = (nargs << 16) | nvars;
     }
     if (!xdr->codeUint32(&argsVars))
         return false;
     if (mode == XDR_DECODE) {
         nargs = argsVars >> 16;
         nvars = argsVars & 0xFFFF;
     }
-    JS_ASSERT(nargs != Bindings::BINDING_COUNT_LIMIT);
-    JS_ASSERT(nvars != Bindings::BINDING_COUNT_LIMIT);
-
-    Bindings bindings;
-    Bindings::AutoRooter bindingsRoot(cx, &bindings);
-
-    uint32_t nameCount = nargs + nvars;
-    if (nameCount > 0) {
-        LifoAllocScope las(&cx->tempLifoAlloc());
-
-        BindingVector names(cx);
-        if (mode == XDR_ENCODE) {
-            if (!GetOrderedBindings(cx, script->bindings, &names))
-                return false;
-        }
-
-        for (unsigned i = 0; i < nameCount; i++) {
-            RootedAtom name(cx);
-            if (mode == XDR_ENCODE)
-                name = names[i].name;
-            if (!XDRAtom(xdr, name.address()))
-                return false;
-            if (mode == XDR_DECODE) {
-                BindingKind kind = (i < nargs) ? ARGUMENT : VARIABLE;
-                if (!bindings.add(cx, name, kind, /* aliased = */ false))
-                    return false;
-            }
-        }
-    }
-
-    if (mode == XDR_DECODE) {
-        if (!bindings.ensureShape(cx))
-            return false;
-    }
 
     if (mode == XDR_ENCODE)
         length = script->length;
     if (!xdr->codeUint32(&length))
         return JS_FALSE;
 
     if (mode == XDR_ENCODE) {
         prologLength = script->mainOffset;
@@ -471,33 +425,33 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         if (script->hasConsts())
             nconsts = script->consts()->length;
         if (script->hasObjects())
             nobjects = script->objects()->length;
         if (script->hasRegexps())
             nregexps = script->regexps()->length;
         if (script->hasTrynotes())
             ntrynotes = script->trynotes()->length;
-        nClosedArgs = script->numClosedArgs();
-        nClosedVars = script->numClosedVars();
 
         nTypeSets = script->nTypeSets;
 
         if (script->noScriptRval)
             scriptBits |= (1 << NoScriptRval);
         if (script->savedCallerFun)
             scriptBits |= (1 << SavedCallerFun);
         if (script->strictModeCode)
             scriptBits |= (1 << StrictModeCode);
         if (script->explicitUseStrict)
             scriptBits |= (1 << ExplicitUseStrict);
         if (script->bindingsAccessedDynamically)
             scriptBits |= (1 << ContainsDynamicNameAccess);
         if (script->funHasExtensibleScope)
             scriptBits |= (1 << FunHasExtensibleScope);
+        if (script->funHasAnyAliasedFormal)
+            scriptBits |= (1 << FunHasAnyAliasedFormal);
         if (script->argumentsHasVarBinding())
             scriptBits |= (1 << ArgumentsHasVarBinding);
         if (script->analyzedArgsUsage() && script->needsArgsObj())
             scriptBits |= (1 << NeedsArgsObj);
         if (script->filename) {
             scriptBits |= (enclosingScript && enclosingScript->filename == script->filename)
                           ? (1 << ParentFilename)
                           : (1 << OwnFilename);
@@ -529,20 +483,16 @@ js::XDRScript(XDRState<mode> *xdr, Handl
     if (!xdr->codeUint32(&ntrynotes))
         return JS_FALSE;
     if (!xdr->codeUint32(&nobjects))
         return JS_FALSE;
     if (!xdr->codeUint32(&nregexps))
         return JS_FALSE;
     if (!xdr->codeUint32(&nconsts))
         return JS_FALSE;
-    if (!xdr->codeUint32(&nClosedArgs))
-        return JS_FALSE;
-    if (!xdr->codeUint32(&nClosedVars))
-        return JS_FALSE;
     if (!xdr->codeUint32(&nTypeSets))
         return JS_FALSE;
     if (!xdr->codeUint32(&scriptBits))
         return JS_FALSE;
 
     if (mode == XDR_DECODE) {
         /* Note: version is packed into the 32b space with another 16b value. */
         JSVersion version_ = JSVersion(version & JS_BITMASK(16));
@@ -560,39 +510,48 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 return NULL;
         } else {
             JS_ASSERT(enclosingScript);
             ss = enclosingScript->scriptSource();
         }
         ScriptSourceHolder ssh(cx->runtime, ss);
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, /* staticLevel = */ 0, ss, 0, 0);
-        if (!script || !JSScript::partiallyInit(cx, script,
-                                                length, nsrcnotes, natoms, nobjects,
-                                                nregexps, ntrynotes, nconsts, nClosedArgs,
-                                                nClosedVars, nTypeSets))
-            return JS_FALSE;
+        if (!script)
+            return false;
+    }
 
-        script->bindings.transferFrom(&bindings);
+    /* JSScript::partiallyInit assumes script->bindings is fully initialized. */
+    LifoAllocScope las(&cx->tempLifoAlloc());
+    if (!XDRScriptBindings(xdr, las, nargs, nvars, script))
+        return false;
+
+    if (mode == XDR_DECODE) {
+        if (!JSScript::partiallyInit(cx, script, length, nsrcnotes, natoms, nobjects, nregexps,
+                                     ntrynotes, nconsts, nTypeSets))
+            return false;
+
         JS_ASSERT(!script->mainOffset);
         script->mainOffset = prologLength;
         script->nfixed = uint16_t(version >> 16);
 
         /* If we know nsrcnotes, we allocated space for notes in script. */
         notes = script->notes();
         *scriptp = script;
 
         if (scriptBits & (1 << StrictModeCode))
             script->strictModeCode = true;
         if (scriptBits & (1 << ExplicitUseStrict))
             script->explicitUseStrict = true;
         if (scriptBits & (1 << ContainsDynamicNameAccess))
             script->bindingsAccessedDynamically = true;
         if (scriptBits & (1 << FunHasExtensibleScope))
             script->funHasExtensibleScope = true;
+        if (scriptBits & (1 << FunHasAnyAliasedFormal))
+            script->funHasAnyAliasedFormal = true;
         if (scriptBits & (1 << ArgumentsHasVarBinding))
             script->setArgumentsHasVarBinding();
         if (scriptBits & (1 << NeedsArgsObj))
             script->setNeedsArgsObj(true);
         if (scriptBits & (1 << IsGenerator))
             script->isGenerator = true;
         if (scriptBits & (1 << IsGeneratorExp))
             script->isGeneratorExp = true;
@@ -724,24 +683,16 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 return false;
             *objp = tmp;
         }
     }
     for (i = 0; i != nregexps; ++i) {
         if (!XDRScriptRegExpObject(xdr, &script->regexps()->vector[i]))
             return false;
     }
-    for (i = 0; i != nClosedArgs; ++i) {
-        if (!xdr->codeUint32(&script->closedArgs()->vector[i]))
-            return false;
-    }
-    for (i = 0; i != nClosedVars; ++i) {
-        if (!xdr->codeUint32(&script->closedVars()->vector[i]))
-            return false;
-    }
 
     if (ntrynotes != 0) {
         /*
          * We combine tn->kind and tn->stackDepth when serializing as XDR is not
          * efficient when serializing small integer types.
          */
         JSTryNote *tn, *tnfirst;
         uint32_t kindAndDepth;
@@ -1403,36 +1354,32 @@ js::FreeScriptFilenames(JSRuntime *rt)
  * encapsulates this offset computation.
  *
  * Array type       Array elements  Accessor
  * ----------       --------------  --------
  * ConstArray       Consts          consts()
  * ObjectArray      Objects         objects()
  * ObjectArray      Regexps         regexps()
  * TryNoteArray     Try notes       trynotes()
- * ClosedSlotArray  ClosedArgs      closedArgs()
- * ClosedSlotArray  ClosedVars      closedVars()
  *
  * Then are the elements of several arrays.
  * - Most of these arrays have headers listed above (if present).  For each of
  *   these, the array pointer and the array length is stored in the header.
  * - The remaining arrays have pointers and lengths that are stored directly in
  *   JSScript.  This is because, unlike the others, they are nearly always
  *   non-zero length and so the optional-header space optimization isn't
  *   worthwhile.
  *
  * Array elements   Pointed to by         Length
  * --------------   -------------         ------
  * Consts           consts()->vector      consts()->length
  * Atoms            atoms                 natoms
  * Objects          objects()->vector     objects()->length
  * Regexps          regexps()->vector     regexps()->length
  * Try notes        trynotes()->vector    trynotes()->length
- * Closed args      closedArgs()->vector  closedArgs()->length
- * Closed vars      closedVars()->vector  closedVars()->length
  * Bytecodes        code                  length
  * Source notes     notes()               numNotes() * sizeof(jssrcnote)
  *
  * IMPORTANT: This layout has two key properties.
  * - It ensures that everything has sufficient alignment;  in particular, the
  *   consts() elements need jsval alignment.
  * - It ensures there are no gaps between elements, which saves space and makes
  *   manual layout easy.  In particular, in the second part, arrays with larger
@@ -1456,50 +1403,45 @@ js::FreeScriptFilenames(JSRuntime *rt)
  * These assertions ensure that there is no padding between the array headers,
  * and also that the consts() elements (which follow immediately afterward) are
  * jsval-aligned.  (There is an assumption that |data| itself is jsval-aligned;
  * we check this below).
  */
 JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ConstArray));
 JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ObjectArray));       /* there are two of these */
 JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(TryNoteArray));
-JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ClosedSlotArray));   /* there are two of these */
 
 /* These assertions ensure there is no padding required between array elements. */
 JS_STATIC_ASSERT(HAS_JSVAL_ALIGNMENT(HeapValue));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, JSAtom *));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSAtom *, HeapPtrObject));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, HeapPtrObject));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, JSTryNote));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, uint32_t));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, uint32_t));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, jsbytecode));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(jsbytecode, jssrcnote));
 
 static inline size_t
-ScriptDataSize(uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
-               uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts,
-               uint16_t nClosedArgs, uint16_t nClosedVars)
+ScriptDataSize(uint32_t length, uint32_t nsrcnotes, uint32_t nbindings, uint32_t natoms,
+               uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts)
 {
     size_t size = 0;
 
     if (nconsts != 0)
         size += sizeof(ConstArray) + nconsts * sizeof(Value);
     size += sizeof(JSAtom *) * natoms;
     if (nobjects != 0)
         size += sizeof(ObjectArray) + nobjects * sizeof(JSObject *);
     if (nregexps != 0)
         size += sizeof(ObjectArray) + nregexps * sizeof(JSObject *);
     if (ntrynotes != 0)
         size += sizeof(TryNoteArray) + ntrynotes * sizeof(JSTryNote);
-    if (nClosedArgs != 0)
-        size += sizeof(ClosedSlotArray) + nClosedArgs * sizeof(uint32_t);
-    if (nClosedVars != 0)
-        size += sizeof(ClosedSlotArray) + nClosedVars * sizeof(uint32_t);
 
+    size += nbindings * sizeof(Binding);
     size += length * sizeof(jsbytecode);
     size += nsrcnotes * sizeof(jssrcnote);
     return size;
 }
 
 JSScript *
 JSScript::Create(JSContext *cx, HandleObject enclosingScope, bool savedCallerFun,
                  const CompileOptions &options, unsigned staticLevel,
@@ -1560,26 +1502,29 @@ AllocScriptData(JSContext *cx, size_t si
     JS_ASSERT(size_t(data) % sizeof(Value) == 0);
     return data;
 }
 
 /* static */ bool
 JSScript::partiallyInit(JSContext *cx, Handle<JSScript*> script,
                         uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
                         uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts,
-                        uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets)
+                        uint32_t nTypeSets)
 {
-    size_t size = ScriptDataSize(length, nsrcnotes, natoms, nobjects, nregexps,
-                                 ntrynotes, nconsts, nClosedArgs, nClosedVars);
+    size_t size = ScriptDataSize(length, nsrcnotes, script->bindings.count(), natoms, nobjects,
+                                 nregexps, ntrynotes, nconsts);
     script->data = AllocScriptData(cx, size);
     if (!script->data)
         return false;
 
     script->length = length;
 
+    JS_ASSERT(nTypeSets <= UINT16_MAX);
+    script->nTypeSets = uint16_t(nTypeSets);
+
     uint8_t *cursor = script->data;
     if (nconsts != 0) {
         script->setHasArray(CONSTS);
         cursor += sizeof(ConstArray);
     }
     if (nobjects != 0) {
         script->setHasArray(OBJECTS);
         cursor += sizeof(ObjectArray);
@@ -1587,24 +1532,16 @@ JSScript::partiallyInit(JSContext *cx, H
     if (nregexps != 0) {
         script->setHasArray(REGEXPS);
         cursor += sizeof(ObjectArray);
     }
     if (ntrynotes != 0) {
         script->setHasArray(TRYNOTES);
         cursor += sizeof(TryNoteArray);
     }
-    if (nClosedArgs != 0) {
-        script->setHasArray(CLOSED_ARGS);
-        cursor += sizeof(ClosedSlotArray);
-    }
-    if (nClosedVars != 0) {
-        script->setHasArray(CLOSED_VARS);
-        cursor += sizeof(ClosedSlotArray);
-    }
 
     if (nconsts != 0) {
         JS_ASSERT(reinterpret_cast<uintptr_t>(cursor) % sizeof(jsval) == 0);
         script->consts()->length = nconsts;
         script->consts()->vector = (HeapValue *)cursor;
         cursor += nconsts * sizeof(script->consts()->vector[0]);
     }
 
@@ -1631,79 +1568,57 @@ JSScript::partiallyInit(JSContext *cx, H
         script->trynotes()->vector = reinterpret_cast<JSTryNote *>(cursor);
         size_t vectorSize = ntrynotes * sizeof(script->trynotes()->vector[0]);
 #ifdef DEBUG
         memset(cursor, 0, vectorSize);
 #endif
         cursor += vectorSize;
     }
 
-    if (nClosedArgs != 0) {
-        script->closedArgs()->length = nClosedArgs;
-        script->closedArgs()->vector = reinterpret_cast<uint32_t *>(cursor);
-        cursor += nClosedArgs * sizeof(script->closedArgs()->vector[0]);
-    }
-
-    if (nClosedVars != 0) {
-        script->closedVars()->length = nClosedVars;
-        script->closedVars()->vector = reinterpret_cast<uint32_t *>(cursor);
-        cursor += nClosedVars * sizeof(script->closedVars()->vector[0]);
-    }
-
-    JS_ASSERT(nTypeSets <= UINT16_MAX);
-    script->nTypeSets = uint16_t(nTypeSets);
+    cursor = script->bindings.switchStorageTo(reinterpret_cast<Binding *>(cursor));
 
     script->code = (jsbytecode *)cursor;
     JS_ASSERT(cursor + length * sizeof(jsbytecode) + nsrcnotes * sizeof(jssrcnote) == script->data + size);
 
     return true;
 }
 
 /* static */ bool
 JSScript::fullyInitTrivial(JSContext *cx, Handle<JSScript*> script)
 {
-    if (!partiallyInit(cx, script, /* length = */ 1, /* nsrcnotes = */ 1, 0, 0, 0, 0, 0, 0, 0, 0))
+    if (!partiallyInit(cx, script, /* length = */ 1, /* nsrcnotes = */ 1, 0, 0, 0, 0, 0, 0))
         return false;
 
     script->code[0] = JSOP_STOP;
     script->notes()[0] = SRC_NULL;
 
     return true;
 }
 
 /* static */ bool
 JSScript::fullyInitFromEmitter(JSContext *cx, Handle<JSScript*> script, BytecodeEmitter *bce)
 {
-    Bindings::SlotVector closedArgs(cx), closedVars(cx);
-    if (!script->bindings.extractClosedArgsAndVars(cx, &closedArgs, &closedVars))
-        return false;
-
     /* The counts of indexed things must be checked during code generation. */
     JS_ASSERT(bce->atomIndices->count() <= INDEX_LIMIT);
     JS_ASSERT(bce->objectList.length <= INDEX_LIMIT);
     JS_ASSERT(bce->regexpList.length <= INDEX_LIMIT);
 
     uint32_t mainLength = bce->offset();
     uint32_t prologLength = bce->prologOffset();
     uint32_t nsrcnotes = uint32_t(bce->countFinalSourceNotes());
-    uint16_t nClosedArgs = uint16_t(closedArgs.length());
-    JS_ASSERT(nClosedArgs == closedArgs.length());
-    uint16_t nClosedVars = uint16_t(closedVars.length());
-    JS_ASSERT(nClosedVars == closedVars.length());
     if (!partiallyInit(cx, script, prologLength + mainLength, nsrcnotes, bce->atomIndices->count(),
                        bce->objectList.length, bce->regexpList.length, bce->ntrynotes,
-                       bce->constList.length(), nClosedArgs, nClosedVars,
-                       bce->typesetCount))
+                       bce->constList.length(), bce->typesetCount))
         return false;
 
     JS_ASSERT(script->mainOffset == 0);
     script->mainOffset = prologLength;
     PodCopy<jsbytecode>(script->code, bce->prologBase(), prologLength);
     PodCopy<jsbytecode>(script->main(), bce->base(), mainLength);
-    uint32_t nfixed = bce->sc->inFunction() ? bce->script->bindings.numVars() : 0;
+    uint32_t nfixed = bce->sc->inFunction() ? script->bindings.numVars() : 0;
     JS_ASSERT(nfixed < SLOTNO_LIMIT);
     script->nfixed = uint16_t(nfixed);
     InitAtomMap(cx, bce->atomIndices.getMap(), script->atoms);
 
     const char *filename = bce->parser->tokenStream.getFilename();
     if (filename) {
         script->filename = SaveScriptFilename(cx, filename);
         if (!script->filename)
@@ -1742,36 +1657,38 @@ JSScript::fullyInitFromEmitter(JSContext
             script->setArgumentsHasVarBinding();
             if (bce->sc->funDefinitelyNeedsArgsObj())
                 script->setNeedsArgsObj(true);
         } else {
             JS_ASSERT(!bce->sc->funDefinitelyNeedsArgsObj());
         }
     }
 
-    if (nClosedArgs)
-        PodCopy<uint32_t>(script->closedArgs()->vector, &closedArgs[0], nClosedArgs);
-    if (nClosedVars)
-        PodCopy<uint32_t>(script->closedVars()->vector, &closedVars[0], nClosedVars);
-
     RootedFunction fun(cx, NULL);
     if (bce->sc->inFunction()) {
         JS_ASSERT(!bce->script->noScriptRval);
         script->isGenerator = bce->sc->funIsGenerator();
         script->isGeneratorExp = bce->sc->funbox() && bce->sc->funbox()->inGenexpLambda;
         script->setFunction(bce->sc->fun());
     }
 
     /*
      * initScriptCounts updates scriptCountsMap if necessary. The other script
      * maps in JSCompartment are populated lazily.
      */
     if (cx->hasRunOption(JSOPTION_PCCOUNT))
         (void) script->initScriptCounts(cx);
 
+    for (unsigned i = 0, n = script->bindings.numArgs(); i < n; ++i) {
+        if (script->formalIsAliased(i)) {
+            script->funHasAnyAliasedFormal = true;
+            break;
+        }
+    }
+
     return true;
 }
 
 size_t
 JSScript::computedSizeOfData()
 {
     uint8_t *dataEnd = code + length * sizeof(jsbytecode) + numNotes() * sizeof(jssrcnote);
     JS_ASSERT(dataEnd >= data);
@@ -2098,43 +2015,31 @@ JSScript *
 js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src)
 {
     /* NB: Keep this in sync with XDRScript. */
 
     uint32_t nconsts   = src->hasConsts()   ? src->consts()->length   : 0;
     uint32_t nobjects  = src->hasObjects()  ? src->objects()->length  : 0;
     uint32_t nregexps  = src->hasRegexps()  ? src->regexps()->length  : 0;
     uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0;
-    uint32_t nClosedArgs = src->numClosedArgs();
-    uint32_t nClosedVars = src->numClosedVars();
 
     /* Script data */
 
-    size_t size = ScriptDataSize(src->length, src->numNotes(), src->natoms,
-                                 nobjects, nregexps, ntrynotes, nconsts, nClosedArgs, nClosedVars);
+    size_t size = ScriptDataSize(src->length, src->numNotes(), src->bindings.count(), src->natoms,
+                                 nobjects, nregexps, ntrynotes, nconsts);
 
     uint8_t *data = AllocScriptData(cx, size);
     if (!data)
         return NULL;
 
     /* Bindings */
 
     Bindings bindings;
-    Bindings::AutoRooter bindingsRoot(cx, &bindings);
-    BindingVector names(cx);
-    if (!GetOrderedBindings(cx, src->bindings, &names))
-        return NULL;
-
-    for (unsigned i = 0; i < names.length(); ++i) {
-        Rooted<JSAtom*> atom(cx, names[i].name);
-        if (!bindings.add(cx, atom, names[i].kind, /* aliasedVar = */ false))
-            return NULL;
-    }
-
-    if (!bindings.ensureShape(cx))
+    Bindings::AutoRooter bindingRooter(cx, &bindings);
+    if (!bindings.clone(cx, data, src))
         return NULL;
 
     /* Objects */
 
     AutoObjectVector objects(cx);
     if (nobjects != 0) {
         HeapPtrObject *vector = src->objects()->vector;
         for (unsigned i = 0; i < nobjects; i++) {
@@ -2190,17 +2095,17 @@ js::CloneScript(JSContext *cx, HandleObj
     JSScript *dst = JSScript::Create(cx, enclosingScope, src->savedCallerFun,
                                      options, src->staticLevel,
                                      src->scriptSource(), src->sourceStart, src->sourceEnd);
     if (!dst) {
         Foreground::free_(data);
         return NULL;
     }
 
-    dst->bindings.transferFrom(&bindings);
+    dst->bindings = bindings;
 
     /* This assignment must occur before all the Rebase calls. */
     dst->data = data;
     memcpy(data, src->data, size);
 
     dst->code = Rebase<jsbytecode>(dst, src, src->code);
 
     /* Script filenames are runtime-wide. */
@@ -2222,16 +2127,17 @@ js::CloneScript(JSContext *cx, HandleObj
         if (src->analyzedArgsUsage())
             dst->setNeedsArgsObj(src->needsArgsObj());
     }
     dst->cloneHasArray(src);
     dst->strictModeCode = src->strictModeCode;
     dst->explicitUseStrict = src->explicitUseStrict;
     dst->bindingsAccessedDynamically = src->bindingsAccessedDynamically;
     dst->funHasExtensibleScope = src->funHasExtensibleScope;
+    dst->funHasAnyAliasedFormal = src->funHasAnyAliasedFormal;
     dst->hasSingletons = src->hasSingletons;
     dst->isGenerator = src->isGenerator;
     dst->isGeneratorExp = src->isGeneratorExp;
 
     /*
      * initScriptCounts updates scriptCountsMap if necessary. The other script
      * maps in JSCompartment are populated lazily.
      */
@@ -2253,20 +2159,16 @@ js::CloneScript(JSContext *cx, HandleObj
     if (nregexps != 0) {
         HeapPtrObject *vector = Rebase<HeapPtr<JSObject> >(dst, src, src->regexps()->vector);
         dst->regexps()->vector = vector;
         for (unsigned i = 0; i < nregexps; ++i)
             vector[i].init(regexps[i]);
     }
     if (ntrynotes != 0)
         dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
-    if (nClosedArgs != 0)
-        dst->closedArgs()->vector = Rebase<uint32_t>(dst, src, src->closedArgs()->vector);
-    if (nClosedVars != 0)
-        dst->closedVars()->vector = Rebase<uint32_t>(dst, src, src->closedVars()->vector);
 
     return dst;
 }
 
 DebugScript *
 JSScript::debugScript()
 {
     JS_ASSERT(hasDebugScript);
@@ -2625,48 +2527,22 @@ JSScript::argumentsOptimizationFailed(JS
     }
 
     return true;
 }
 
 bool
 JSScript::varIsAliased(unsigned varSlot)
 {
-    if (bindingsAccessedDynamically)
-        return true;
-
-    for (uint32_t i = 0; i < numClosedVars(); ++i) {
-        if (closedVars()->vector[i] == varSlot) {
-            JS_ASSERT(function()->isHeavyweight());
-            return true;
-        }
-    }
-
-    return false;
+    return bindings.bindingIsAliased(bindings.numArgs() + varSlot);
 }
 
 bool
 JSScript::formalIsAliased(unsigned argSlot)
 {
-    return formalLivesInCallObject(argSlot) || argsObjAliasesFormals();
+    return bindings.bindingIsAliased(argSlot);
 }
 
 bool
 JSScript::formalLivesInArgumentsObject(unsigned argSlot)
 {
-    return argsObjAliasesFormals() && !formalLivesInCallObject(argSlot);
+    return argsObjAliasesFormals() && !formalIsAliased(argSlot);
 }
-
-bool
-JSScript::formalLivesInCallObject(unsigned argSlot)
-{
-    if (bindingsAccessedDynamically)
-        return true;
-
-    for (uint32_t i = 0; i < numClosedArgs(); ++i) {
-        if (closedArgs()->vector[i] == argSlot) {
-            JS_ASSERT(function()->isHeavyweight());
-            return true;
-        }
-    }
-
-    return false;
-}
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -17,16 +17,17 @@
 #include "jsopcode.h"
 #include "jsscope.h"
 
 #include "gc/Barrier.h"
 
 namespace js {
 
 struct Shape;
+class BindingIter;
 
 namespace mjit {
 struct JITScript;
 class CallCompiler;
 }
 
 namespace analyze {
 class ScriptAnalysis;
@@ -68,204 +69,134 @@ struct ObjectArray {
     uint32_t        length;     /* count of indexed objects */
 };
 
 struct TryNoteArray {
     JSTryNote       *vector;    /* array of indexed try notes */
     uint32_t        length;     /* count of indexed try notes */
 };
 
-struct ClosedSlotArray {
-    uint32_t        *vector;    /* array of closed slots */
-    uint32_t        length;     /* count of closed slots */
-};
-
 /*
  * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical
  * scope is composed of these three kinds of bindings.
  */
 
 enum BindingKind { ARGUMENT, VARIABLE, CONSTANT };
 
-struct Binding
+class Binding
 {
-    PropertyName *name;
-    BindingKind kind;
-};
+    /*
+     * One JSScript stores one Binding per formal/variable so we use a
+     * packed-word representation.
+     */
+    size_t bits_;
 
-/*
- * Iterator over a script's bindings (formals and variables). Note: iteration
- * proceeds in reverse-frame-index order, vars before formals. For ascending
- * order, see GetOrderedBindings.
- */
-class BindingIter
-{
-    friend class Bindings;
-    BindingIter(JSContext *cx, const Bindings &bindings, Shape *shape);
-    void settle();
-
-    struct Init
-    {
-        Init(const Bindings *b, Shape::Range s) : bindings(b), shape(s) {}
-        const Bindings *bindings;
-        Shape::Range shape;
-    };
+    static const size_t KIND_MASK = 0x3;
+    static const size_t ALIASED_BIT = 0x4;
+    static const size_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT);
 
   public:
-    BindingIter(JSContext *cx, Bindings &bindings);
-    BindingIter(JSContext *cx, Init init);
-
-    void operator=(Init init);
+    explicit Binding() : bits_(0) {}
 
-    bool done() const { return shape_.empty(); }
-    operator bool() const { return !done(); }
-    void operator++(int) { shape_.popFront(); settle(); }
-    BindingIter &operator++() { (*this)++; return *this; }
+    Binding(PropertyName *name, BindingKind kind, bool aliased) {
+        JS_STATIC_ASSERT(CONSTANT <= KIND_MASK);
+        JS_ASSERT((size_t(name) & ~NAME_MASK) == 0);
+        JS_ASSERT((size_t(kind) & ~KIND_MASK) == 0);
+        bits_ = size_t(name) | size_t(kind) | (aliased ? ALIASED_BIT : 0);
+    }
 
-    const Binding &operator*() const { JS_ASSERT(!done()); return binding_; }
-    const Binding *operator->() const { JS_ASSERT(!done()); return &binding_; }
-    unsigned frameIndex() const { JS_ASSERT(!done()); return shape_.front().shortid(); }
+    PropertyName *name() const {
+        return (PropertyName *)(bits_ & NAME_MASK);
+    }
 
-  private:
-    const Bindings *bindings_;
-    Binding binding_;
-    Shape::Range shape_;
-    Shape::Range::AutoRooter rooter_;
+    BindingKind kind() const {
+        return BindingKind(bits_ & KIND_MASK);
+    }
+
+    bool aliased() const {
+        return bool(bits_ & ALIASED_BIT);
+    }
 };
 
-/*
- * This function fills the given BindingVector in ascending frame-index order,
- * formals before variables. Thus, for function f(x) { var y; }, *vec will
- * contain [("x",ARGUMENT),("y",VARIABLE)].
- */
-
-typedef Vector<Binding, 32> BindingVector;
-
-extern bool
-GetOrderedBindings(JSContext *cx, Bindings &bindings, BindingVector *vec);
+JS_STATIC_ASSERT(sizeof(Binding) == sizeof(size_t));
 
 /*
  * Formal parameters and local variables are stored in a shape tree
  * path encapsulated within this class.  This class represents bindings for
  * both function and top-level scripts (the latter is needed to track names in
  * strict mode eval code, to give such code its own lexical environment).
  */
 class Bindings
 {
     friend class BindingIter;
-    friend class StaticScopeIter;
+    friend class AliasedFormalIter;
 
-    HeapPtr<Shape> lastBinding;
-    uint16_t nargs;
-    uint16_t nvars;
-    bool     hasDup_:1;     // true if there are duplicate argument names
+    HeapPtr<Shape> callObjShape_;
+    Binding *bindingArray_;
+    uint16_t numArgs_;
+    uint16_t numVars_;
 
-    inline Shape *initialShape(JSContext *cx) const;
   public:
     inline Bindings();
 
     /*
-     * Transfers ownership of bindings data from bindings into this fresh
-     * Bindings instance. Once such a transfer occurs, the old bindings must
-     * not be used again.
+     * Initialize a Bindings. bindingArray must have length numArgs+numVars and
+     * must outlive Bindings. To use a temporary bindingArray, the caller may
+     * call switchStorageTo, providing new storage for Bindings to use.
      */
-    inline void transferFrom(Bindings *bindings);
-
-    uint16_t numArgs() const { return nargs; }
-    uint16_t numVars() const { return nvars; }
-    unsigned count() const { return nargs + nvars; }
+    bool init(JSContext *cx, unsigned numArgs, unsigned numVars, Binding *bindingArray);
+    uint8_t *switchStorageTo(Binding *newStorage);
 
     /*
-     * 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.
+     * Clone srcScript's bindings (as part of js::CloneScript). dstScriptData
+     * is the pointer to what will eventually be dstScript->data.
      */
-    inline uint16_t formalIndexToSlot(uint16_t i);
-    inline uint16_t varIndexToSlot(uint16_t i);
-
-    /* Ensure these bindings have a shape lineage. */
-    inline bool ensureShape(JSContext *cx);
+    bool clone(JSContext *cx, uint8_t *dstScriptData, HandleScript srcScript);
 
-    /*
-     * Return the shape to use to create a call object for these bindings.
-     * The result is guaranteed not to have duplicate property names.
-     */
-    Shape *callObjectShape(JSContext *cx) const;
+    unsigned numArgs() const { return numArgs_; }
+    unsigned numVars() const { return numVars_; }
+    unsigned count() const { return numArgs() + numVars(); }
 
-    /* Extract a list of the closed-over args and vars. */
-    typedef Vector<uint32_t, 32> SlotVector;
-    bool extractClosedArgsAndVars(JSContext *cx, SlotVector *args, SlotVector *vars);
+    /* Return the initial shape of call objects created for this scope. */
+    Shape *callObjShape() const { return callObjShape_; }
 
-    /* See Scope::extensibleParents */
+    /* See Scope::extensibleParents (TODO: remove with bug 774915). */
     inline bool extensibleParents();
     bool setExtensibleParents(JSContext *cx);
 
-    enum {
-        /* A script may have no more than this many arguments or variables. */
-        BINDING_COUNT_LIMIT = 0xFFFF
-    };
-
-    /*
-     * Add a local binding for the given name, of the given type, for the code
-     * being compiled.  If fun is non-null, this binding set is being created
-     * for that function, so adjust corresponding metadata in that function
-     * while adding.  Otherwise this set must correspond to a top-level script.
-     *
-     * A binding may be added twice with different kinds; the last one for a
-     * given name prevails.  (We preserve both bindings for the decompiler,
-     * which must deal with such cases.)  Pass null for name when indicating a
-     * destructuring argument.  Return true on success.
-     *
-     * The parser builds shape paths for functions, usable by Call objects at
-     * runtime, by calling an "add" method. All ARGUMENT bindings must be added
-     * before before any VARIABLE or CONSTANT bindings.
-     */
-    bool add(JSContext *cx, HandleAtom name, BindingKind kind, bool aliased);
-
-    void noteDup() { hasDup_ = true; }
-    bool hasDup() const { return hasDup_; }
-
-    /*
-     * Look up an argument or variable name, returning its kind when found or
-     * NONE when no such name exists. When indexp is not null and the name
-     * exists, *indexp will receive the index of the corresponding argument or
-     * variable.
-     */
-    BindingIter::Init lookup(JSContext *cx, PropertyName *name) const;
-
-    /* Convenience method to check for any binding for a name. */
-    bool hasBinding(JSContext *cx, PropertyName *name) const {
-        Shape **_;
-        return lastBinding && Shape::search(cx, lastBinding, NameToId(name), &_) != NULL;
-    }
-
     /* Convenience method to get the var index of 'arguments'. */
     unsigned argumentsVarIndex(JSContext *cx) const;
 
+    /* Return whether the binding at bindingIndex is aliased. */
+    bool bindingIsAliased(unsigned bindingIndex);
+
+    /* Return whether this scope has any aliased bindings. */
+    bool hasAnyAliasedBindings() const { return !callObjShape_->isEmptyShape(); }
+
+    void trace(JSTracer *trc);
+    class AutoRooter;
+};
+
+class Bindings::AutoRooter : private AutoGCRooter
+{
+  public:
+    explicit AutoRooter(JSContext *cx, Bindings *bindings_
+                        JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : AutoGCRooter(cx, BINDINGS), bindings(bindings_), skip(cx, bindings_)
+    {
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    friend void AutoGCRooter::trace(JSTracer *trc);
     void trace(JSTracer *trc);
 
-    class AutoRooter : private AutoGCRooter
-    {
-      public:
-        explicit AutoRooter(JSContext *cx, Bindings *bindings_
-                            JS_GUARD_OBJECT_NOTIFIER_PARAM)
-          : AutoGCRooter(cx, BINDINGS), bindings(bindings_), skip(cx, bindings_)
-        {
-            JS_GUARD_OBJECT_NOTIFIER_INIT;
-        }
-
-        friend void AutoGCRooter::trace(JSTracer *trc);
-        void trace(JSTracer *trc);
-
-      private:
-        Bindings *bindings;
-        SkipRoot skip;
-        JS_DECL_USE_GUARD_OBJECT_NOTIFIER
-    };
+  private:
+    Bindings *bindings;
+    SkipRoot skip;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class ScriptCounts
 {
     friend struct ::JSScript;
     friend struct ScriptAndCounts;
     /*
      * This points to a single block that holds an array of PCCounts followed
@@ -477,18 +408,16 @@ struct JSScript : public js::gc::Cell
 
   public:
     // The kinds of the optional arrays.
     enum ArrayKind {
         CONSTS,
         OBJECTS,
         REGEXPS,
         TRYNOTES,
-        CLOSED_ARGS,
-        CLOSED_VARS,
         LIMIT
     };
 
     typedef uint8_t ArrayBitsT;
 
   private:
     // The bits in this field indicate the presence/non-presence of several
     // optional arrays in |data|.  See the comments above Create() for details.
@@ -500,16 +429,17 @@ struct JSScript : public js::gc::Cell
     bool            noScriptRval:1; /* no need for result value of last
                                        expression statement */
     bool            savedCallerFun:1; /* can call getCallerFunction() */
     bool            strictModeCode:1; /* code is in strict mode */
     bool            explicitUseStrict:1; /* code has "use strict"; explicitly */
     bool            compileAndGo:1;   /* see Parser::compileAndGo */
     bool            bindingsAccessedDynamically:1; /* see ContextFlags' field of the same name */
     bool            funHasExtensibleScope:1;       /* see ContextFlags' field of the same name */
+    bool            funHasAnyAliasedFormal:1;      /* true if any formalIsAliased(i) */
     bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
                                                      obsolete eval(s, o) in
                                                      this script */
     bool            warnedAboutUndefinedProp:1; /* have warned about uses of
                                                    undefined properties in this
                                                    script */
     bool            hasSingletons:1;  /* script has singleton objects */
     bool            isActiveEval:1;   /* script came from eval(), and is still active */
@@ -521,17 +451,16 @@ struct JSScript : public js::gc::Cell
 #endif
     bool            callDestroyHook:1;/* need to call destroy hook */
     bool            isGenerator:1;    /* is a generator */
     bool            isGeneratorExp:1; /* is a generator expression */
     bool            hasScriptCounts:1;/* script has an entry in
                                          JSCompartment::scriptCountsMap */
     bool            hasDebugScript:1; /* script has an entry in
                                          JSCompartment::debugScriptMap */
-
   private:
     /* See comments below. */
     bool            argsHasVarBinding_:1;
     bool            needsArgsAnalysis_:1;
     bool            needsArgsObj_:1;
 
     //
     // End of fields.  Start methods.
@@ -544,17 +473,17 @@ struct JSScript : public js::gc::Cell
 
     // Three ways ways to initialize a JSScript.  Callers of partiallyInit()
     // and fullyInitTrivial() are responsible for notifying the debugger after
     // successfully creating any kind (function or other) of new JSScript.
     // However, callers of fullyInitFromEmitter() do not need to do this.
     static bool partiallyInit(JSContext *cx, JS::Handle<JSScript*> script,
                               uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
                               uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts,
-                              uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets);
+                              uint32_t nTypeSets);
     static bool fullyInitTrivial(JSContext *cx, JS::Handle<JSScript*> script);  // inits a JSOP_STOP-only script
     static bool fullyInitFromEmitter(JSContext *cx, JS::Handle<JSScript*> script,
                                      js::frontend::BytecodeEmitter *bce);
 
     void setVersion(JSVersion v) { version = v; }
 
     /* See ContextFlags::funArgumentsHasLocalBinding comment. */
     bool argumentsHasVarBinding() const { return argsHasVarBinding_; }
@@ -577,18 +506,18 @@ struct JSScript : public js::gc::Cell
     static bool argumentsOptimizationFailed(JSContext *cx, JSScript *script);
 
     /*
      * Arguments access (via JSOP_*ARG* opcodes) must access the canonical
      * location for the argument. If an arguments object exists AND this is a
      * non-strict function (where 'arguments' aliases formals), then all access
      * must go through the arguments object. Otherwise, the local slot is the
      * canonical location for the arguments. Note: if a formal is aliased
-     * through the scope chain, then script->formalLivesInCallObject and
-     * JSOP_*ARG* opcodes won't be emitted at all.
+     * through the scope chain, then script->formalIsAliased and JSOP_*ARG*
+     * opcodes won't be emitted at all.
      */
     bool argsObjAliasesFormals() const {
         return needsArgsObj() && !strictModeCode;
     }
 
     /*
      * Original compiled function for the script, if it has a function.
      * NULL for global and eval scripts.
@@ -735,27 +664,23 @@ struct JSScript : public js::gc::Cell
     bool hasArray(ArrayKind kind)           { return (hasArrayBits & (1 << kind)); }
     void setHasArray(ArrayKind kind)        { hasArrayBits |= (1 << kind); }
     void cloneHasArray(JSScript *script)    { hasArrayBits = script->hasArrayBits; }
 
     bool hasConsts()        { return hasArray(CONSTS);      }
     bool hasObjects()       { return hasArray(OBJECTS);     }
     bool hasRegexps()       { return hasArray(REGEXPS);     }
     bool hasTrynotes()      { return hasArray(TRYNOTES);    }
-    bool hasClosedArgs()    { return hasArray(CLOSED_ARGS); }
-    bool hasClosedVars()    { return hasArray(CLOSED_VARS); }
 
     #define OFF(fooOff, hasFoo, t)   (fooOff() + (hasFoo() ? sizeof(t) : 0))
 
     size_t constsOffset()     { return 0; }
     size_t objectsOffset()    { return OFF(constsOffset,     hasConsts,     js::ConstArray);      }
     size_t regexpsOffset()    { return OFF(objectsOffset,    hasObjects,    js::ObjectArray);     }
     size_t trynotesOffset()   { return OFF(regexpsOffset,    hasRegexps,    js::ObjectArray);     }
-    size_t closedArgsOffset() { return OFF(trynotesOffset,   hasTrynotes,   js::TryNoteArray);    }
-    size_t closedVarsOffset() { return OFF(closedArgsOffset, hasClosedArgs, js::ClosedSlotArray); }
 
     js::ConstArray *consts() {
         JS_ASSERT(hasConsts());
         return reinterpret_cast<js::ConstArray *>(data + constsOffset());
     }
 
     js::ObjectArray *objects() {
         JS_ASSERT(hasObjects());
@@ -767,34 +692,16 @@ struct JSScript : public js::gc::Cell
         return reinterpret_cast<js::ObjectArray *>(data + regexpsOffset());
     }
 
     js::TryNoteArray *trynotes() {
         JS_ASSERT(hasTrynotes());
         return reinterpret_cast<js::TryNoteArray *>(data + trynotesOffset());
     }
 
-    js::ClosedSlotArray *closedArgs() {
-        JS_ASSERT(hasClosedArgs());
-        return reinterpret_cast<js::ClosedSlotArray *>(data + closedArgsOffset());
-    }
-
-    js::ClosedSlotArray *closedVars() {
-        JS_ASSERT(hasClosedVars());
-        return reinterpret_cast<js::ClosedSlotArray *>(data + closedVarsOffset());
-    }
-
-    uint32_t numClosedArgs() {
-        return hasClosedArgs() ? closedArgs()->length : 0;
-    }
-
-    uint32_t numClosedVars() {
-        return hasClosedVars() ? closedVars()->length : 0;
-    }
-
     js::HeapPtrAtom &getAtom(size_t index) const {
         JS_ASSERT(index < natoms);
         return atoms[index];
     }
 
     js::HeapPtrAtom &getAtom(jsbytecode *pc) const {
         JS_ASSERT(pc >= code && pc + sizeof(uint32_t) < code + length);
         return getAtom(GET_UINT32_INDEX(pc));
@@ -837,33 +744,19 @@ struct JSScript : public js::gc::Cell
 
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     inline bool isEmpty() const;
 
-    uint32_t getClosedArg(uint32_t index) {
-        js::ClosedSlotArray *arr = closedArgs();
-        JS_ASSERT(index < arr->length);
-        return arr->vector[index];
-    }
-
-    uint32_t getClosedVar(uint32_t index) {
-        js::ClosedSlotArray *arr = closedVars();
-        JS_ASSERT(index < arr->length);
-        return arr->vector[index];
-    }
-
-
     bool varIsAliased(unsigned varSlot);
     bool formalIsAliased(unsigned argSlot);
     bool formalLivesInArgumentsObject(unsigned argSlot);
-    bool formalLivesInCallObject(unsigned argSlot);
 
   private:
     /*
      * Recompile with or without single-stepping support, as directed
      * by stepModeEnabled().
      */
     void recompileForStepMode(js::FreeOp *fop);
 
@@ -931,16 +824,89 @@ struct JSScript : public js::gc::Cell
     void markChildren(JSTracer *trc);
 };
 
 JS_STATIC_ASSERT(sizeof(JSScript::ArrayBitsT) * 8 >= JSScript::LIMIT);
 
 /* If this fails, add/remove padding within JSScript. */
 JS_STATIC_ASSERT(sizeof(JSScript) % js::gc::Cell::CellSize == 0);
 
+namespace js {
+
+/*
+ * Iterator over a script's bindings (formals and variables).
+ * The order of iteration is:
+ *  - first, formal arguments, from index 0 to numArgs
+ *  - next, variables, from index 0 to numVars
+ */
+class BindingIter
+{
+    const Bindings *bindings_;
+    unsigned i_;
+
+    friend class Bindings;
+    BindingIter(const Bindings &bindings, unsigned i) : bindings_(&bindings), i_(i) {}
+
+  public:
+    explicit BindingIter(const Bindings &bindings) : bindings_(&bindings), i_(0) {}
+
+    bool done() const { return i_ == bindings_->count(); }
+    operator bool() const { return !done(); }
+    void operator++(int) { JS_ASSERT(!done()); i_++; }
+    BindingIter &operator++() { (*this)++; return *this; }
+
+    unsigned frameIndex() const {
+        JS_ASSERT(!done());
+        return i_ < bindings_->numArgs() ? i_ : i_ - bindings_->numArgs();
+    }
+
+    const Binding &operator*() const { JS_ASSERT(!done()); return bindings_->bindingArray_[i_]; }
+    const Binding *operator->() const { JS_ASSERT(!done()); return &bindings_->bindingArray_[i_]; }
+};
+
+/*
+ * This helper function fills the given BindingVector with the sequential
+ * values of BindingIter.
+ */
+
+typedef Vector<Binding, 32> BindingVector;
+
+extern bool
+FillBindingVector(Bindings &bindings, BindingVector *vec);
+
+/*
+ * Iterator over the aliased formal bindings in ascending index order. This can
+ * be veiwed as a filtering of BindingIter with predicate
+ *   bi->aliased() && bi->kind() == ARGUMENT
+ */
+class AliasedFormalIter
+{
+    const Binding *begin_, *p_, *end_;
+    unsigned slot_;
+
+    void settle() {
+        while (p_ != end_ && !p_->aliased())
+            p_++;
+    }
+
+  public:
+    explicit inline AliasedFormalIter(JSScript *script);
+
+    bool done() const { return p_ == end_; }
+    operator bool() const { return !done(); }
+    void operator++(int) { JS_ASSERT(!done()); p_++; slot_++; settle(); }
+
+    const Binding &operator*() const { JS_ASSERT(!done()); return *p_; }
+    const Binding *operator->() const { JS_ASSERT(!done()); return p_; }
+    unsigned frameIndex() const { JS_ASSERT(!done()); return p_ - begin_; }
+    unsigned scopeSlot() const { JS_ASSERT(!done()); return slot_; }
+};
+
+}  /* namespace js */
+
 /*
  * New-script-hook calling is factored from JSScript::fullyInitFromEmitter() so
  * that it and callers of XDRScript() can share this code.  In the case of
  * callers of XDRScript(), the hook should be invoked only after successful
  * decode of any owning function (the fun parameter) or script object (null
  * fun).
  */
 extern JS_FRIEND_API(void)
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -19,71 +19,33 @@
 #include "vm/RegExpObject.h"
 
 #include "jsscopeinlines.h"
 
 namespace js {
 
 inline
 Bindings::Bindings()
-    : lastBinding(NULL), nargs(0), nvars(0), hasDup_(false)
+    : callObjShape_(NULL), bindingArray_(NULL), numArgs_(0), numVars_(0)
 {}
 
-inline void
-Bindings::transferFrom(Bindings *bindings)
-{
-    JS_ASSERT(!lastBinding);
-    JS_ASSERT(!bindings->lastBinding || !bindings->lastBinding->inDictionary());
-
-    *this = *bindings;
-#ifdef DEBUG
-    bindings->lastBinding = NULL;
-#endif
-}
-
-Shape *
-Bindings::initialShape(JSContext *cx) const
-{
-    /* Get an allocation kind to match an empty call object. */
-    gc::AllocKind kind = gc::FINALIZE_OBJECT2_BACKGROUND;
-    JS_ASSERT(gc::GetGCKindSlots(kind) == CallObject::RESERVED_SLOTS);
-
-    return EmptyShape::getInitialShape(cx, &CallClass, NULL, cx->global(),
-                                       kind, BaseShape::VAROBJ);
-}
-
-bool
-Bindings::ensureShape(JSContext *cx)
-{
-    if (!lastBinding) {
-        lastBinding = initialShape(cx);
-        if (!lastBinding)
-            return false;
-    }
-    return true;
-}
-
 bool
 Bindings::extensibleParents()
 {
-    return lastBinding && lastBinding->extensibleParents();
+    return callObjShape_->extensibleParents();
 }
 
-uint16_t
-Bindings::formalIndexToSlot(uint16_t i)
+inline
+AliasedFormalIter::AliasedFormalIter(JSScript *script)
+  : begin_(script->bindings.bindingArray_),
+    p_(begin_),
+    end_(begin_ + (script->funHasAnyAliasedFormal ? script->bindings.numArgs() : 0)),
+    slot_(CallObject::RESERVED_SLOTS)
 {
-    JS_ASSERT(i < nargs);
-    return CallObject::RESERVED_SLOTS + i;
-}
-
-uint16_t
-Bindings::varIndexToSlot(uint16_t i)
-{
-    JS_ASSERT(i < nvars);
-    return CallObject::RESERVED_SLOTS + i + nargs;
+    settle();
 }
 
 extern void
 CurrentScriptFileLineOriginSlow(JSContext *cx, const char **file, unsigned *linenop, JSPrincipals **origin);
 
 inline void
 CurrentScriptFileLineOrigin(JSContext *cx, const char **file, unsigned *linenop, JSPrincipals **origin,
                             LineOption opt = NOT_CALLED_FROM_JSOP_EVAL)
--- a/js/src/vm/ArgumentsObject-inl.h
+++ b/js/src/vm/ArgumentsObject-inl.h
@@ -66,30 +66,41 @@ ArgumentsObject::setArg(unsigned i, cons
     lhs = v;
 }
 
 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().formal(i);
+    if (v.isMagic(JS_FORWARD_TO_CALL_OBJECT)) {
+        CallObject &callobj = getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall();
+        for (AliasedFormalIter fi(callobj.callee().script()); ; fi++) {
+            if (fi.frameIndex() == i)
+                return callobj.aliasedVar(fi);
+        }
+    }
     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().setFormal(i, v);
-    else
-        lhs = v;
+    if (lhs.isMagic(JS_FORWARD_TO_CALL_OBJECT)) {
+        CallObject &callobj = getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall();
+        for (AliasedFormalIter fi(callobj.callee().script()); ; fi++) {
+            if (fi.frameIndex() == i) {
+                callobj.setAliasedVar(fi, v);
+                return;
+            }
+        }
+    }
+    lhs = v;
 }
 
 inline bool
 ArgumentsObject::isElementDeleted(uint32_t i) const
 {
     JS_ASSERT(i < data()->numArgs);
     if (i >= initialLength())
         return false;
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -81,25 +81,18 @@ ArgumentsObject::create(JSContext *cx, S
 
     /*
      * If it exists and the arguments object aliases formals, the call object
      * is the canonical location for formals.
      */
     JSScript *script = fp->script();
     if (fp->fun()->isHeavyweight() && script->argsObjAliasesFormals()) {
         obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(fp->callObj()));
-
-        /* Flag each slot that canonically lives in the callObj. */
-        if (script->bindingsAccessedDynamically) {
-            for (unsigned i = 0; i < numFormals; ++i)
-                data->args[i] = MagicValue(JS_FORWARD_TO_CALL_OBJECT);
-        } else {
-            for (unsigned i = 0; i < script->numClosedArgs(); ++i)
-                data->args[script->getClosedArg(i)] = MagicValue(JS_FORWARD_TO_CALL_OBJECT);
-        }
+        for (AliasedFormalIter fi(script); fi; fi++)
+            data->args[fi.frameIndex()] = MagicValue(JS_FORWARD_TO_CALL_OBJECT);
     }
 
     ArgumentsObject &argsobj = obj->asArguments();
     JS_ASSERT(argsobj.initialLength() == numActuals);
     JS_ASSERT(!argsobj.hasOverriddenLength());
     return &argsobj;
 }
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3182,22 +3182,29 @@ 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().formal(i);
-        else if (fp->script()->argsObjAliasesFormals() && fp->hasArgsObj())
+        JSScript *script = fp->script();
+        if (unsigned(i) < fp->numFormalArgs() && script->formalIsAliased(i)) {
+            for (AliasedFormalIter fi(script); ; fi++) {
+                if (fi.frameIndex() == unsigned(i)) {
+                    arg = fp->callObj().aliasedVar(fi);
+                    break;
+                }
+            }
+        } else if (script->argsObjAliasesFormals() && fp->hasArgsObj()) {
             arg = fp->argsObj().arg(i);
-        else
+        } else {
             arg = fp->unaliasedActual(i, DONT_CHECK_ALIASING);
+        }
     } else {
         arg.setUndefined();
     }
 
     if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &arg))
         return false;
     args.rval().set(arg);
     return true;
@@ -3705,25 +3712,25 @@ DebuggerObject_getParameterNames(JSConte
     if (!result)
         return false;
     result->ensureDenseArrayInitializedLength(cx, 0, fun->nargs);
 
     if (fun->isInterpreted()) {
         JS_ASSERT(fun->nargs == fun->script()->bindings.numArgs());
 
         if (fun->nargs > 0) {
-            BindingVector names(cx);
-            if (!GetOrderedBindings(cx, fun->script()->bindings, &names))
+            BindingVector bindings(cx);
+            if (!FillBindingVector(fun->script()->bindings, &bindings))
                 return false;
             for (size_t i = 0; i < fun->nargs; i++) {
                 Value v;
-                if (names[i].name->length() == 0)
+                if (bindings[i].name()->length() == 0)
                     v = UndefinedValue();
                 else
-                    v = StringValue(names[i].name);
+                    v = StringValue(bindings[i].name());
                 result->setDenseArrayElement(i, v);
             }
         }
     } else {
         for (size_t i = 0; i < fun->nargs; i++)
             result->setDenseArrayElement(i, UndefinedValue());
     }
 
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -69,43 +69,25 @@ CallObject::isForEval() const
 
 inline JSFunction &
 CallObject::callee() const
 {
     return *getReservedSlot(CALLEE_SLOT).toObject().toFunction();
 }
 
 inline const Value &
-CallObject::formal(unsigned i) const
+CallObject::aliasedVar(AliasedFormalIter fi)
 {
-    JS_ASSERT(callee().script()->formalLivesInCallObject(i));
-    return getSlot(RESERVED_SLOTS + i);
+    return getSlot(fi.scopeSlot());
 }
 
 inline void
-CallObject::setFormal(unsigned i, const Value &v)
-{
-    JS_ASSERT(callee().script()->formalLivesInCallObject(i));
-    setSlot(RESERVED_SLOTS + i, v);
-}
-
-inline const Value &
-CallObject::var(unsigned i) const
+CallObject::setAliasedVar(AliasedFormalIter fi, const Value &v)
 {
-    JSFunction &fun = callee();
-    JS_ASSERT(fun.script()->varIsAliased(i));
-    return getSlot(RESERVED_SLOTS + fun.nargs + i);
-}
-
-inline void
-CallObject::setVar(unsigned i, const Value &v)
-{
-    JSFunction &fun = callee();
-    JS_ASSERT(fun.script()->varIsAliased(i));
-    setSlot(RESERVED_SLOTS + fun.nargs + i, v);
+    setSlot(fi.scopeSlot(), v);
 }
 
 inline uint32_t
 NestedScopeObject::stackDepth() const
 {
     return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
 }
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -58,17 +58,17 @@ StaticScopeIter::hasDynamicScopeObject()
            : obj->toFunction()->isHeavyweight();
 }
 
 Shape *
 StaticScopeIter::scopeShape() const
 {
     JS_ASSERT(hasDynamicScopeObject());
     JS_ASSERT(type() != NAMED_LAMBDA);
-    return type() == BLOCK ? block().lastProperty() : funScript()->bindings.lastBinding;
+    return type() == BLOCK ? block().lastProperty() : funScript()->bindings.callObjShape();
 }
 
 StaticScopeIter::Type
 StaticScopeIter::type() const
 {
     if (onNamedLambda)
         return NAMED_LAMBDA;
     return obj->isStaticBlock() ? BLOCK : FUNCTION;
@@ -137,19 +137,17 @@ js::ScopeCoordinateName(JSRuntime *rt, J
  * 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.
  */
 CallObject *
 CallObject::create(JSContext *cx, JSScript *script, HandleObject enclosing, HandleFunction callee)
 {
-    RootedShape shape(cx, script->bindings.callObjectShape(cx));
-    if (shape == NULL)
-        return NULL;
+    RootedShape shape(cx, script->bindings.callObjShape());
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     JS_ASSERT(CanBeFinalizedInBackground(kind, &CallClass));
     kind = gc::GetBackgroundAllocKind(kind);
 
     RootedTypeObject type(cx, cx->compartment->getEmptyType(cx));
     if (!type)
         return NULL;
@@ -199,27 +197,18 @@ CallObject::createForFunction(JSContext 
 
     RootedScript script(cx, fp->script());
     Rooted<JSFunction*> callee(cx, &fp->callee());
     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->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->setFormal(slot, formals[slot]);
-        }
-    }
+    for (AliasedFormalIter i(script); i; i++)
+        callobj->setAliasedVar(i, fp->unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING));
 
     return callobj;
 }
 
 CallObject *
 CallObject::createForStrictEval(JSContext *cx, StackFrame *fp)
 {
     JS_ASSERT(fp->isStrictEvalFrame());
@@ -1096,23 +1085,23 @@ class DebugScopeProxy : public BaseProxy
         /* Handle unaliased formals, vars, and consts at function scope. */
         if (scope->isCall() && !scope->asCall().isForEval()) {
             CallObject &callobj = scope->asCall();
             JSScript *script = callobj.callee().script();
             if (!script->ensureHasTypes(cx))
                 return false;
 
             Bindings &bindings = script->bindings;
-            BindingIter bi(cx, script->bindings);
-            while (NameToId(bi->name) != id) {
+            BindingIter bi(script->bindings);
+            while (NameToId(bi->name()) != id) {
                 if (!++bi)
                     return false;
             }
 
-            if (bi->kind == VARIABLE || bi->kind == CONSTANT) {
+            if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) {
                 unsigned i = bi.frameIndex();
                 if (script->varIsAliased(i))
                     return false;
 
                 if (maybefp) {
                     if (action == GET)
                         *vp = maybefp->unaliasedVar(i);
                     else
@@ -1127,19 +1116,19 @@ class DebugScopeProxy : public BaseProxy
                     if (action == GET)
                         *vp = UndefinedValue();
                 }
 
                 if (action == SET)
                     TypeScript::SetLocal(cx, script, i, *vp);
 
             } else {
-                JS_ASSERT(bi->kind == ARGUMENT);
+                JS_ASSERT(bi->kind() == ARGUMENT);
                 unsigned i = bi.frameIndex();
-                if (script->formalLivesInCallObject(i))
+                if (script->formalIsAliased(i))
                     return false;
 
                 if (maybefp) {
                     if (script->argsObjAliasesFormals() && maybefp->hasArgsObj()) {
                         if (action == GET)
                             *vp = maybefp->argsObj().arg(i);
                         else
                             maybefp->argsObj().setArg(i, *vp);
@@ -1349,69 +1338,91 @@ class DebugScopeProxy : public BaseProxy
             return false;
         if (found)
             return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);
 
         return JS_DefinePropertyById(cx, scope, id, desc->value, desc->getter, desc->setter,
                                      desc->attrs);
     }
 
-    bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE
+    bool getScopePropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props, unsigned flags)
     {
         ScopeObject &scope = proxy->asDebugScope().scope();
 
         if (isMissingArgumentsBinding(scope) &&
             !props.append(NameToId(cx->runtime->atomState.argumentsAtom)))
         {
             return false;
         }
 
-        RootedObject scopeObj(cx, &scope);
-        return GetPropertyNames(cx, scopeObj, JSITER_OWNONLY, &props);
+        RootedObject rootedScope(cx, &scope);
+        if (!GetPropertyNames(cx, rootedScope, flags, &props))
+            return false;
+
+        /*
+         * Function scopes are optimized to not contain unaliased variables so
+         * they must be manually appended here.
+         */
+        if (scope.isCall() && !scope.asCall().isForEval()) {
+            for (BindingIter bi(scope.asCall().callee().script()->bindings); bi; bi++) {
+                if (!bi->aliased() && !props.append(NameToId(bi->name())))
+                    return false;
+            }
+        }
+
+        return true;
     }
 
-    bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) MOZ_OVERRIDE
+    bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE
     {
-        RootedValue val(cx, IdToValue(id));
-        return js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_CANT_DELETE,
-                                        JSDVG_IGNORE_STACK, val, NullPtr(),
-                                        NULL, NULL);
+        return getScopePropertyNames(cx, proxy, props, JSITER_OWNONLY);
     }
 
     bool enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE
     {
-        ScopeObject &scope = proxy->asDebugScope().scope();
-
-        if (isMissingArgumentsBinding(scope) &&
-            !props.append(NameToId(cx->runtime->atomState.argumentsAtom)))
-        {
-            return false;
-        }
-
-        RootedObject scopeObj(cx, &scope);
-        return GetPropertyNames(cx, scopeObj, 0, &props);
+        return getScopePropertyNames(cx, proxy, props, 0);
     }
 
     bool has(JSContext *cx, JSObject *proxy, jsid id, bool *bp) MOZ_OVERRIDE
     {
         ScopeObject &scope = proxy->asDebugScope().scope();
 
         if (isArguments(cx, id) && isFunctionScope(scope)) {
             *bp = true;
             return true;
         }
 
         JSBool found;
-        RootedObject scopeObj(cx, &scope);
-        if (!JS_HasPropertyById(cx, scopeObj, id, &found))
+        RootedObject rootedScope(cx, &scope);
+        if (!JS_HasPropertyById(cx, rootedScope, id, &found))
             return false;
 
+        /*
+         * Function scopes are optimized to not contain unaliased variables so
+         * a manual search is necessary.
+         */
+        if (!found && scope.isCall() && !scope.asCall().isForEval()) {
+            for (BindingIter bi(scope.asCall().callee().script()->bindings); bi; bi++) {
+                if (!bi->aliased() && NameToId(bi->name()) == id) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+
         *bp = found;
         return true;
     }
+
+    bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) MOZ_OVERRIDE
+    {
+        RootedValue idval(cx, IdToValue(id));
+        return js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_CANT_DELETE,
+                                        JSDVG_IGNORE_STACK, idval, NullPtr(), NULL, NULL);
+    }
 };
 
 }  /* namespace js */
 
 int DebugScopeProxy::family = 0;
 DebugScopeProxy DebugScopeProxy::singleton;
 
 /* static */ DebugScopeObject *
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -189,23 +189,19 @@ 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;
 
-    /* Get/set the formal argument at the given index. */
-    inline const Value &formal(unsigned i) const;
-    inline void setFormal(unsigned i, const Value &v);
-
-    /* Get/set the variable at the given index. */
-    inline const Value &var(unsigned i) const;
-    inline void setVar(unsigned i, const Value &v);
+    /* Get/set the aliased variable referred to by 'bi'. */
+    inline const Value &aliasedVar(AliasedFormalIter fi);
+    inline void setAliasedVar(AliasedFormalIter fi, const Value &v);
 };
 
 class DeclEnvObject : public ScopeObject
 {
   public:
     static const uint32_t RESERVED_SLOTS = 1;
     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2;
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -233,33 +233,38 @@ StackFrame::unaliasedLocal(unsigned i, M
 #endif
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT(i < numFormalArgs());
+    JS_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals());
+    if (checkAliasing && script()->formalIsAliased(i)) {
+        while (true) {}
+    }
     JS_ASSERT_IF(checkAliasing, !script()->formalIsAliased(i));
     return formals()[i];
 }
 
 inline Value &
 StackFrame::unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT(i < numActualArgs());
+    JS_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals());
     JS_ASSERT_IF(checkAliasing && i < numFormalArgs(), !script()->formalIsAliased(i));
     return i < numFormalArgs() ? formals()[i] : actuals()[i];
 }
 
 template <class Op>
 inline void
 StackFrame::forEachUnaliasedActual(Op op)
 {
-    JS_ASSERT(script()->numClosedArgs() == 0);
+    JS_ASSERT(!script()->funHasAnyAliasedFormal);
     JS_ASSERT(!script()->needsArgsObj());
 
     unsigned nformal = numFormalArgs();
     unsigned nactual = numActualArgs();
 
     const Value *formalsEnd = (const Value *)this;
     const Value *formals = formalsEnd - nformal;
 
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -20,17 +20,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 129);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 130);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(NULL), cursor(NULL), limit(NULL) { }
 
     JSContext *cx() const {
         return context;