Bug 614493 - Move top-level binding storage (and argument, variable, and upvar counts) out of JSFunction and into js::Bindings, itself stored in JSScript, anticipating the time when strict mode eval scripts will need it. r=brendan
authorJeff Walden <jwalden@mit.edu>
Tue, 16 Nov 2010 15:34:24 -0800
changeset 59968 0d9a5752b1cf36be73c2bc2cab784fbdcb04eb20
parent 59967 6b68235ee417b4e078e691ad6bac1909dca9e5bd
child 59969 c5d43dfafcbc17707bed4c1849a68a72e2676e8b
push idunknown
push userunknown
push dateunknown
reviewersbrendan
bugs614493
milestone2.0b9pre
Bug 614493 - Move top-level binding storage (and argument, variable, and upvar counts) out of JSFunction and into js::Bindings, itself stored in JSScript, anticipating the time when strict mode eval scripts will need it. r=brendan
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsdbgapi.cpp
js/src/jsemit.cpp
js/src/jsemit.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinterp.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsreflect.cpp
js/src/jsscan.cpp
js/src/jsscope.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/jstracer.cpp
js/src/jsxdrapi.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/FastOps.cpp
js/src/shell/js.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -91,16 +91,17 @@
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 #include "jscntxtinlines.h"
 #include "jsregexpinlines.h"
+#include "jsscriptinlines.h"
 #include "jsstrinlines.h"
 #include "assembler/wtf/Platform.h"
 
 #if ENABLE_YARR_JIT
 #include "assembler/jit/ExecutableAllocator.h"
 #include "methodjit/Logging.h"
 #endif
 
@@ -4236,17 +4237,17 @@ JS_CloneFunctionObject(JSContext *cx, JS
     JSObject *clone = js_AllocFlatClosure(cx, fun, parent);
     if (!clone)
         return NULL;
 
     JSUpvarArray *uva = fun->u.i.script->upvars();
     uint32 i = uva->length;
     JS_ASSERT(i != 0);
 
-    for (Shape::Range r(fun->lastUpvar()); i-- != 0; r.popFront()) {
+    for (Shape::Range r(fun->script()->bindings.lastUpvar()); i-- != 0; r.popFront()) {
         JSObject *obj = parent;
         int skip = uva->vector[i].level();
         while (--skip > 0) {
             if (!obj) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                      JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
                 return NULL;
             }
@@ -4779,37 +4780,41 @@ JS_CompileUCFunctionForPrincipals(JSCont
         funAtom = NULL;
     } else {
         funAtom = js_Atomize(cx, name, strlen(name), 0);
         if (!funAtom) {
             fun = NULL;
             goto out2;
         }
     }
+
     fun = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, obj, funAtom);
     if (!fun)
         goto out2;
 
     {
         AutoObjectRooter tvr(cx, FUN_OBJECT(fun));
         MUST_FLOW_THROUGH("out");
 
+        Bindings bindings(cx);
         for (i = 0; i < nargs; i++) {
             argAtom = js_Atomize(cx, argnames[i], strlen(argnames[i]), 0);
             if (!argAtom) {
                 fun = NULL;
                 goto out2;
             }
-            if (!fun->addLocal(cx, argAtom, JSLOCAL_ARG)) {
+
+            uint16 dummy;
+            if (!bindings.addArgument(cx, argAtom, &dummy)) {
                 fun = NULL;
                 goto out2;
             }
         }
 
-        if (!Compiler::compileFunctionBody(cx, fun, principals,
+        if (!Compiler::compileFunctionBody(cx, fun, principals, &bindings,
                                            chars, length, filename, lineno)) {
             fun = NULL;
             goto out2;
         }
 
         if (obj && funAtom &&
             !obj->defineProperty(cx, ATOM_TO_JSID(funAtom), ObjectValue(*fun),
                                  NULL, NULL, JSPROP_ENUMERATE)) {
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2724,18 +2724,18 @@ class AutoReleaseNullablePtr {
 };
 
 class AutoLocalNameArray {
   public:
     explicit AutoLocalNameArray(JSContext *cx, JSFunction *fun
                                 JS_GUARD_OBJECT_NOTIFIER_PARAM)
       : context(cx),
         mark(JS_ARENA_MARK(&cx->tempPool)),
-        names(fun->getLocalNameArray(cx, &cx->tempPool)),
-        count(fun->countLocalNames())
+        names(fun->script()->bindings.getLocalNameArray(cx, &cx->tempPool)),
+        count(fun->script()->bindings.countLocalNames())
     {
         JS_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     ~AutoLocalNameArray() {
         JS_ARENA_RELEASE(&context->tempPool, mark);
     }
 
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -64,16 +64,17 @@
 #include "jsstr.h"
 #include "jswrapper.h"
 
 #include "jsatominlines.h"
 #include "jsdbgapiinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
+#include "jsscriptinlines.h"
 
 #include "jsautooplen.h"
 
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Retcon.h"
 
 using namespace js;
 using namespace js::gc;
@@ -1121,24 +1122,24 @@ JS_PUBLIC_API(uintN)
 JS_GetFunctionArgumentCount(JSContext *cx, JSFunction *fun)
 {
     return fun->nargs;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_FunctionHasLocalNames(JSContext *cx, JSFunction *fun)
 {
-    return fun->hasLocalNames();
+    return fun->script()->bindings.hasLocalNames();
 }
 
 extern JS_PUBLIC_API(jsuword *)
 JS_GetFunctionLocalNameArray(JSContext *cx, JSFunction *fun, void **markp)
 {
     *markp = JS_ARENA_MARK(&cx->tempPool);
-    return fun->getLocalNameArray(cx, &cx->tempPool);
+    return fun->script()->bindings.getLocalNameArray(cx, &cx->tempPool);
 }
 
 extern JS_PUBLIC_API(JSAtom *)
 JS_LocalNameToAtom(jsuword w)
 {
     return JS_LOCAL_NAME_TO_ATOM(w);
 }
 
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -67,16 +67,17 @@
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsautooplen.h"        // generated headers last
 #include "jsstaticcheck.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
+#include "jsscriptinlines.h"
 
 /* Allocation chunk counts, must be powers of two in general. */
 #define BYTECODE_CHUNK  256     /* code allocation increment */
 #define SRCNOTE_CHUNK   64      /* initial srcnote allocation increment */
 #define TRYNOTE_CHUNK   64      /* trynote allocation increment */
 
 /* Macros to compute byte sizes from typed element counts. */
 #define BYTECODE_SIZE(n)        ((n) * sizeof(jsbytecode))
@@ -91,16 +92,22 @@ NewTryNote(JSContext *cx, JSCodeGenerato
            uintN stackDepth, size_t start, size_t end);
 
 static JSBool
 EmitIndexOp(JSContext *cx, JSOp op, uintN index, JSCodeGenerator *cg);
 
 static JSBool
 EmitLeaveBlock(JSContext *cx, JSCodeGenerator *cg, JSOp op, JSObjectBox *box);
 
+void
+JSTreeContext::trace(JSTracer *trc)
+{
+    bindings.trace(trc);
+}
+
 JSCodeGenerator::JSCodeGenerator(Parser *parser,
                                  JSArenaPool *cpool, JSArenaPool *npool,
                                  uintN lineno)
   : JSTreeContext(parser),
     codePool(cpool), notePool(npool),
     codeMark(JS_ARENA_MARK(cpool)), noteMark(JS_ARENA_MARK(npool)),
     stackDepth(0), maxStackDepth(0),
     ntrynotes(0), lastTryNode(NULL),
@@ -1318,20 +1325,20 @@ JSTreeContext::ensureSharpSlots()
     JS_ASSERT(!(flags & TCF_HAS_SHARPS));
     if (inFunction()) {
         JSContext *cx = parser->context;
         JSAtom *sharpArrayAtom = js_Atomize(cx, "#array", 6, 0);
         JSAtom *sharpDepthAtom = js_Atomize(cx, "#depth", 6, 0);
         if (!sharpArrayAtom || !sharpDepthAtom)
             return false;
 
-        sharpSlotBase = fun()->u.i.nvars;
-        if (!fun()->addLocal(cx, sharpArrayAtom, JSLOCAL_VAR))
+        sharpSlotBase = bindings.countVars();
+        if (!bindings.addVariable(cx, sharpArrayAtom))
             return false;
-        if (!fun()->addLocal(cx, sharpDepthAtom, JSLOCAL_VAR))
+        if (!bindings.addVariable(cx, sharpDepthAtom))
             return false;
     } else {
         /*
          * Compiler::compileScript will rebase immediate operands indexing
          * the sharp slots to come at the end of the global script's |nfixed|
          * slots storage, after gvars and regexps.
          */
         sharpSlotBase = 0;
@@ -1709,17 +1716,17 @@ LookupCompileTimeConstant(JSContext *cx,
             /*
              * Try looking in the variable object for a direct property that
              * is readonly and permanent.  We know such a property can't be
              * shadowed by another property on obj's prototype chain, or a
              * with object or catch variable; nor can prop's value be changed,
              * nor can prop be deleted.
              */
             if (cg->inFunction()) {
-                if (cg->fun()->lookupLocal(cx, atom, NULL) != JSLOCAL_NONE)
+                if (cg->bindings.hasBinding(atom))
                     break;
             } else {
                 JS_ASSERT(cg->compileAndGo());
                 obj = cg->scopeChain();
 
                 const Shape *shape = obj->nativeLookup(ATOM_TO_JSID(atom));
                 if (shape) {
                     /*
@@ -1892,17 +1899,17 @@ JSCodeGenerator::shouldNoteClosedName(JS
  *
  * The function returns -1 on failures.
  */
 static jsint
 AdjustBlockSlot(JSContext *cx, JSCodeGenerator *cg, jsint slot)
 {
     JS_ASSERT((jsuint) slot < cg->maxStackDepth);
     if (cg->inFunction()) {
-        slot += cg->fun()->u.i.nvars;
+        slot += cg->bindings.countVars();
         if ((uintN) slot >= SLOTNO_LIMIT) {
             ReportCompileErrorNumber(cx, CG_TS(cg), NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS);
             slot = -1;
         }
     }
     return slot;
 }
 
@@ -2010,27 +2017,27 @@ MakeUpvarForEval(JSParseNode *pn, JSCode
             if (!funbox)
                 break;
         }
     }
 
     JSAtom *atom = pn->pn_atom;
 
     uintN index;
-    JSLocalKind localKind = fun->lookupLocal(cx, atom, &index);
-    if (localKind == JSLOCAL_NONE)
+    BindingKind kind = fun->script()->bindings.lookup(atom, &index);
+    if (kind == NONE)
         return true;
 
     JS_ASSERT(cg->staticLevel > upvarLevel);
     if (cg->staticLevel >= UpvarCookie::UPVAR_LEVEL_LIMIT)
         return true;
 
     JSAtomListElement *ale = cg->upvarList.lookup(atom);
     if (!ale) {
-        if (cg->inFunction() && !cg->fun()->addLocal(cx, atom, JSLOCAL_UPVAR))
+        if (cg->inFunction() && !cg->bindings.addUpvar(cx, atom))
             return false;
 
         ale = cg->upvarList.add(cg->parser, atom);
         if (!ale)
             return false;
         JS_ASSERT(ALE_INDEX(ale) == cg->upvarList.count - 1);
 
         UpvarCookie *vector = cg->upvarMap.vector;
@@ -2041,17 +2048,17 @@ MakeUpvarForEval(JSParseNode *pn, JSCode
             length = 2 * JS_MAX(2, length);
             vector = reinterpret_cast<UpvarCookie *>(cx->realloc(vector, length * sizeof *vector));
             if (!vector)
                 return false;
             cg->upvarMap.vector = vector;
             cg->upvarMap.length = length;
         }
 
-        if (localKind != JSLOCAL_ARG)
+        if (kind != ARGUMENT)
             index += fun->nargs;
         JS_ASSERT(index < JS_BIT(16));
 
         uintN skip = cg->staticLevel - upvarLevel;
         vector[ALE_INDEX(ale)].set(skip, index);
     }
 
     pn->pn_op = JSOP_GETUPVAR;
@@ -2412,17 +2419,17 @@ BindNameToSlot(JSContext *cx, JSCodeGene
 
             op = JSOP_GETUPVAR;
         }
 
         ale = cg->upvarList.lookup(atom);
         if (ale) {
             index = ALE_INDEX(ale);
         } else {
-            if (!cg->fun()->addLocal(cx, atom, JSLOCAL_UPVAR))
+            if (!cg->bindings.addUpvar(cx, atom))
                 return JS_FALSE;
 
             ale = cg->upvarList.add(cg->parser, atom);
             if (!ale)
                 return JS_FALSE;
             index = ALE_INDEX(ale);
             JS_ASSERT(index == cg->upvarList.count - 1);
 
@@ -3864,17 +3871,17 @@ MaybeEmitVarDecl(JSContext *cx, JSCodeGe
         if (!UpdateLineNumberNotes(cx, cg, pn->pn_pos.begin.lineno))
             return JS_FALSE;
         EMIT_INDEX_OP(prologOp, atomIndex);
         CG_SWITCH_TO_MAIN(cg);
     }
 
     if (cg->inFunction() &&
         JOF_OPTYPE(pn->pn_op) == JOF_LOCAL &&
-        pn->pn_cookie.slot() < cg->fun()->u.i.nvars &&
+        pn->pn_cookie.slot() < cg->bindings.countVars() &&
         cg->shouldNoteClosedName(pn))
     {
         if (!cg->closedVars.append(pn->pn_cookie.slot()))
             return JS_FALSE;
     }
 
     if (result)
         *result = atomIndex;
@@ -4735,19 +4742,20 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
                                            cg->codePool, cg->notePool,
                                            pn->pn_pos.begin.lineno);
 
         if (!cg2->init())
             return JS_FALSE;
 
         cg2->flags = pn->pn_funbox->tcflags | TCF_COMPILING | TCF_IN_FUNCTION |
                      (cg->flags & TCF_FUN_MIGHT_ALIAS_LOCALS);
+        cg2->bindings.transfer(cx, &pn->pn_funbox->bindings);
 #if JS_HAS_SHARP_VARS
         if (cg2->flags & TCF_HAS_SHARPS) {
-            cg2->sharpSlotBase = fun->sharpSlotBase(cx);
+            cg2->sharpSlotBase = cg2->bindings.sharpSlotBase(cx);
             if (cg2->sharpSlotBase < 0)
                 return JS_FALSE;
         }
 #endif
         cg2->setFunction(fun);
         cg2->funbox = pn->pn_funbox;
         cg2->parent = cg;
 
@@ -4810,23 +4818,23 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
                 CG_SWITCH_TO_MAIN(cg);
             }
 
             /* Emit NOP for the decompiler. */
             if (!EmitFunctionDefNop(cx, cg, index))
                 return JS_FALSE;
         } else {
 #ifdef DEBUG
-            JSLocalKind localKind =
+            BindingKind kind =
 #endif
-                cg->fun()->lookupLocal(cx, fun->atom, &slot);
-            JS_ASSERT(localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST);
+                cg->bindings.lookup(fun->atom, &slot);
+            JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
             JS_ASSERT(index < JS_BIT(20));
             pn->pn_index = index;
-            op = FUN_FLAT_CLOSURE(fun) ? JSOP_DEFLOCALFUN_FC : JSOP_DEFLOCALFUN;
+            op = fun->isFlatClosure() ? JSOP_DEFLOCALFUN_FC : JSOP_DEFLOCALFUN;
             if (pn->isClosed() &&
                 !cg->callsEval() &&
                 !cg->closedVars.append(pn->pn_cookie.slot())) {
                 return JS_FALSE;
             }
             if (!EmitSlotIndexOp(cx, op, slot, index, cg))
                 return JS_FALSE;
 
--- a/js/src/jsemit.h
+++ b/js/src/jsemit.h
@@ -327,26 +327,31 @@ struct JSTreeContext {              /* t
 
     JSFunctionBox   *funbox;        /* null or box for function we're compiling
                                        if (flags & TCF_IN_FUNCTION) and not in
                                        Compiler::compileFunctionBody */
     JSFunctionBox   *functionList;
 
     JSParseNode     *innermostWith; /* innermost WITH parse node */
 
+    js::Bindings    bindings;       /* bindings in this code, including
+                                       arguments if we're compiling a function */
+
 #ifdef JS_SCOPE_DEPTH_METER
     uint16          scopeDepth;     /* current lexical scope chain depth */
     uint16          maxScopeDepth;  /* maximum lexical scope chain depth */
 #endif
 
+    void trace(JSTracer *trc);
+
     JSTreeContext(js::Parser *prs)
-      : flags(0), bodyid(0), blockidGen(0),
-        topStmt(NULL), topScopeStmt(NULL), blockChainBox(NULL), blockNode(NULL),
-        parser(prs), scopeChain_(NULL), parent(prs->tc), staticLevel(0),
-        funbox(NULL), functionList(NULL), innermostWith(NULL), sharpSlotBase(-1)
+      : flags(0), bodyid(0), blockidGen(0), topStmt(NULL), topScopeStmt(NULL),
+        blockChainBox(NULL), blockNode(NULL), parser(prs), scopeChain_(NULL), parent(prs->tc),
+        staticLevel(0), funbox(NULL), functionList(NULL), innermostWith(NULL), bindings(prs->context),
+        sharpSlotBase(-1)
     {
         prs->tc = this;
         JS_SCOPE_DEPTH_METERING(scopeDepth = maxScopeDepth = 0);
     }
 
     /*
      * For functions the tree context is constructed and destructed a second
      * time during code generation. To avoid a redundant stats update in such
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -85,16 +85,17 @@
 #include "methodjit/MethodJIT.h"
 #endif
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jsfuninlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
+#include "jsscriptinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 inline JSObject *
 JSObject::getThrowTypeError() const
 {
     return &getGlobal()->getReservedSlot(JSRESERVED_GLOBAL_THROWTYPEERROR).toObject();
@@ -366,38 +367,35 @@ WrapEscapingClosure(JSContext *cx, JSSta
     if (!wfunobj)
         return NULL;
     AutoObjectRooter tvr(cx, wfunobj);
 
     JSFunction *wfun = (JSFunction *) wfunobj;
     wfunobj->setPrivate(wfun);
     wfun->nargs = fun->nargs;
     wfun->flags = fun->flags | JSFUN_HEAVYWEIGHT;
-    wfun->u.i.nvars = fun->u.i.nvars;
-    wfun->u.i.nupvars = fun->u.i.nupvars;
     wfun->u.i.skipmin = fun->u.i.skipmin;
     wfun->u.i.wrapper = true;
     wfun->u.i.script = NULL;
-    wfun->u.i.names = fun->u.i.names;
     wfun->atom = fun->atom;
 
-    JSScript *script = fun->u.i.script;
+    JSScript *script = fun->script();
     jssrcnote *snbase = script->notes();
     jssrcnote *sn = snbase;
     while (!SN_IS_TERMINATOR(sn))
         sn = SN_NEXT(sn);
     uintN nsrcnotes = (sn - snbase) + 1;
 
     /* NB: GC must not occur before wscript is homed in wfun->u.i.script. */
     JSScript *wscript = JSScript::NewScript(cx, script->length, nsrcnotes,
                                             script->atomMap.length,
                                             JSScript::isValidOffset(script->objectsOffset)
                                             ? script->objects()->length
                                             : 0,
-                                            fun->u.i.nupvars,
+                                            script->bindings.countUpvars(),
                                             JSScript::isValidOffset(script->regexpsOffset)
                                             ? script->regexps()->length
                                             : 0,
                                             JSScript::isValidOffset(script->trynotesOffset)
                                             ? script->trynotes()->length
                                             : 0,
                                             JSScript::isValidOffset(script->constOffset)
                                             ? script->consts()->length
@@ -430,20 +428,20 @@ WrapEscapingClosure(JSContext *cx, JSSta
     }
     if (JSScript::isValidOffset(script->globalsOffset)) {
         memcpy(wscript->globals()->vector, script->globals()->vector,
                wscript->globals()->length * sizeof(GlobalSlotArray::Entry));
     }
     if (script->nClosedArgs + script->nClosedVars != 0)
         script->copyClosedSlotsTo(wscript);
 
-    if (wfun->u.i.nupvars != 0) {
-        JS_ASSERT(wfun->u.i.nupvars == wscript->upvars()->length);
+    if (script->bindings.hasUpvars()) {
+        JS_ASSERT(script->bindings.countUpvars() == wscript->upvars()->length);
         memcpy(wscript->upvars()->vector, script->upvars()->vector,
-               wfun->u.i.nupvars * sizeof(uint32));
+               script->bindings.countUpvars() * sizeof(uint32));
     }
 
     jsbytecode *pc = wscript->code;
     while (*pc != JSOP_STOP) {
         /* FIXME should copy JSOP_TRAP? */
         JSOp op = js_GetOpcode(cx, wscript, pc);
         const JSCodeSpec *cs = &js_CodeSpec[op];
         ptrdiff_t oplen = cs->length;
@@ -489,16 +487,18 @@ WrapEscapingClosure(JSContext *cx, JSSta
     wscript->usesArguments = script->usesArguments;
     wscript->warnedAboutTwoArgumentEval = script->warnedAboutTwoArgumentEval;
     if (wscript->principals)
         JSPRINCIPALS_HOLD(cx, wscript->principals);
 #ifdef CHECK_SCRIPT_OWNER
     wscript->owner = script->owner;
 #endif
 
+    wscript->bindings.clone(cx, &script->bindings);
+
     /* Deoptimize wfun from FUN_{FLAT,NULL}_CLOSURE to FUN_INTERPRETED. */
     FUN_SET_KIND(wfun, JSFUN_INTERPRETED);
     wfun->u.i.script = wscript;
     return wfunobj;
 }
 
 static JSBool
 ArgGetter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
@@ -939,30 +939,32 @@ static JSBool
 CalleeGetter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     return CheckForEscapingClosure(cx, obj, vp);
 }
 
 static JSObject *
 NewCallObject(JSContext *cx, JSFunction *fun, JSObject &scopeChain, JSObject &callee)
 {
-    size_t vars = fun->countArgsAndVars();
-    size_t slots = JSObject::CALL_RESERVED_SLOTS + vars;
+    Bindings &bindings = fun->script()->bindings;
+
+    size_t argsVars = bindings.countArgsAndVars();
+    size_t slots = JSObject::CALL_RESERVED_SLOTS + argsVars;
     gc::FinalizeKind kind = gc::GetGCObjectKind(slots);
 
     JSObject *callobj = js_NewGCObject(cx, kind);
     if (!callobj)
         return NULL;
 
     /* Init immediately to avoid GC seeing a half-init'ed object. */
     callobj->init(cx, &js_CallClass, NULL, &scopeChain, NULL, false);
-    callobj->setMap(fun->u.i.names);
+    callobj->setMap(bindings.lastShape());
 
     /* This must come after callobj->lastProp has been set. */
-    if (!callobj->ensureInstanceReservedSlots(cx, vars))
+    if (!callobj->ensureInstanceReservedSlots(cx, argsVars))
         return NULL;
 
 #ifdef DEBUG
     for (Shape::Range r = callobj->lastProp; !r.empty(); r.popFront()) {
         const Shape &s = r.front();
         if (s.slot != SHAPE_INVALID_SLOT) {
             JS_ASSERT(s.slot + 1 == callobj->slotSpan());
             break;
@@ -1071,37 +1073,40 @@ js_PutCallObject(JSContext *cx, JSStackF
     if (fp->hasArgsObj()) {
         if (!fp->hasOverriddenArgs())
             callobj.setCallObjArguments(ObjectValue(fp->argsObj()));
         js_PutArgsObject(cx, fp);
     }
 
     JSFunction *fun = fp->fun();
     JS_ASSERT(fun == callobj.getCallObjCalleeFunction());
-    uintN n = fun->countArgsAndVars();
+
+    Bindings &bindings = fun->script()->bindings;
+    uintN n = bindings.countArgsAndVars();
 
     if (n != 0) {
         JS_ASSERT(JSFunction::CLASS_RESERVED_SLOTS + n <= callobj.numSlots());
 
-        uint32 nargs = fun->nargs;
-        uint32 nvars = fun->u.i.nvars;
-
-        JSScript *script = fun->u.i.script;
+        uint32 nvars = bindings.countVars();
+        uint32 nargs = bindings.countArgs();
+        JS_ASSERT(fun->nargs == nargs);
+        JS_ASSERT(nvars + nargs == n);
+
+        JSScript *script = fun->script();
         if (script->usesEval
 #ifdef JS_METHODJIT
             || script->debugMode
 #endif
             ) {
             CopyValuesToCallObject(callobj, nargs, fp->formalArgs(), nvars, fp->slots());
         } else {
             /*
              * For each arg & var that is closed over, copy it from the stack
              * into the call object.
              */
-            JSScript *script = fun->u.i.script;
             uint32 nclosed = script->nClosedArgs;
             for (uint32 i = 0; i < nclosed; i++) {
                 uint32 e = script->getClosedArg(i);
                 callobj.setSlot(JSObject::CALL_RESERVED_SLOTS + e, fp->formalArg(e));
             }
 
             nclosed = script->nClosedVars;
             for (uint32 i = 0; i < nclosed; i++) {
@@ -1160,26 +1165,28 @@ CallPropertyOp(JSContext *cx, JSObject *
         i = (uint16) JSID_TO_INT(id);
     }
 
     Value *array;
     if (kind == JSCPK_UPVAR) {
         JSObject &callee = obj->getCallObjCallee();
 
 #ifdef DEBUG
-        JSFunction *callee_fun = (JSFunction *) callee.getPrivate();
-        JS_ASSERT(FUN_FLAT_CLOSURE(callee_fun));
-        JS_ASSERT(i < callee_fun->u.i.nupvars);
+        JSFunction *calleeFun = callee.getFunctionPrivate();
+        JS_ASSERT(calleeFun->isFlatClosure());
+        JS_ASSERT(calleeFun->script()->bindings.countUpvars() == calleeFun->script()->upvars()->length);
+        JS_ASSERT(i < calleeFun->script()->bindings.countUpvars());
 #endif
 
         array = callee.getFlatClosureUpvars();
     } else {
         JSFunction *fun = obj->getCallObjCalleeFunction();
+        JS_ASSERT(fun->nargs == fun->script()->bindings.countArgs());
         JS_ASSERT_IF(kind == JSCPK_ARG, i < fun->nargs);
-        JS_ASSERT_IF(kind == JSCPK_VAR, i < fun->u.i.nvars);
+        JS_ASSERT_IF(kind == JSCPK_VAR, i < fun->script()->bindings.countVars());
 
         JSStackFrame *fp = (JSStackFrame *) obj->getPrivate();
 
         if (kind == JSCPK_ARGUMENTS) {
             if (setter) {
                 if (fp)
                     fp->setOverriddenArgs();
                 obj->setCallObjArguments(*vp);
@@ -1309,27 +1316,24 @@ call_resolve(JSContext *cx, JSObject *ob
              JSObject **objp)
 {
     JS_ASSERT(obj->isCall());
     JS_ASSERT(!obj->getProto());
 
     if (!JSID_IS_ATOM(id))
         return JS_TRUE;
 
-#ifdef DEBUG
-    JSFunction *fun = obj->getCallObjCalleeFunction();
-    JS_ASSERT(fun->lookupLocal(cx, JSID_TO_ATOM(id), NULL) == JSLOCAL_NONE);
-#endif
+    JS_ASSERT(!obj->getCallObjCalleeFunction()->script()->bindings.hasBinding(JSID_TO_ATOM(id)));
 
     /*
      * Resolve arguments so that we never store a particular Call object's
      * arguments object reference in a Call prototype's |arguments| slot.
      *
      * Include JSPROP_ENUMERATE for consistency with all other Call object
-     * properties; see JSFunction::addLocal and js::Interpret's JSOP_DEFFUN
+     * properties; see js::Bindings::add and js::Interpret's JSOP_DEFFUN
      * rebinding-Call-property logic.
      */
     if (JSID_IS_ATOM(id, cx->runtime->atomState.argumentsAtom)) {
         if (!js_DefineNativeProperty(cx, obj, id, UndefinedValue(),
                                      GetCallArguments, SetCallArguments,
                                      JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_ENUMERATE,
                                      0, 0, NULL, JSDNP_DONT_PURGE)) {
             return JS_FALSE;
@@ -1351,17 +1355,17 @@ call_trace(JSTracer *trc, JSObject *obj)
         /*
          * FIXME: Hide copies of stack values rooted by fp from the Cycle
          * Collector, which currently lacks a non-stub Unlink implementation
          * for JS objects (including Call objects), so is unable to collect
          * cycles involving Call objects whose frames are active without this
          * hiding hack.
          */
         uintN first = JSObject::CALL_RESERVED_SLOTS;
-        uintN count = fp->fun()->countArgsAndVars();
+        uintN count = fp->fun()->script()->bindings.countArgsAndVars();
 
         JS_ASSERT(obj->numSlots() >= first + count);
         SetValueRangeToUndefined(obj->getSlots() + first, count);
     }
 
     MaybeMarkGenerator(trc, obj);
 }
 
@@ -1787,19 +1791,17 @@ fun_resolve(JSContext *cx, JSObject *obj
 JSBool
 js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
 {
     JSContext *cx;
     JSFunction *fun;
     uint32 firstword;           /* flag telling whether fun->atom is non-null,
                                    plus for fun->u.i.skipmin, fun->u.i.wrapper,
                                    and 14 bits reserved for future use */
-    uintN nargs, nvars, nupvars, n;
-    uint32 localsword;          /* word for argument and variable counts */
-    uint32 flagsword;           /* word for fun->u.i.nupvars and fun->flags */
+    uint32 flagsword;           /* word for argument count and fun->flags */
 
     cx = xdr->cx;
     if (xdr->mode == JSXDR_ENCODE) {
         fun = GET_FUNCTION_PRIVATE(cx, *objp);
         if (!FUN_INTERPRETED(fun)) {
             JSAutoByteString funNameBytes;
             if (const char *name = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_SCRIPTED_FUNCTION,
@@ -1810,162 +1812,51 @@ js_XDRFunctionObject(JSXDRState *xdr, JS
         if (fun->u.i.wrapper) {
             JSAutoByteString funNameBytes;
             if (const char *name = GetFunctionNameBytes(cx, fun, &funNameBytes))
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_XDR_CLOSURE_WRAPPER, name);
             return false;
         }
         JS_ASSERT((fun->u.i.wrapper & ~1U) == 0);
         firstword = (fun->u.i.skipmin << 2) | (fun->u.i.wrapper << 1) | !!fun->atom;
-        nargs = fun->nargs;
-        nvars = fun->u.i.nvars;
-        nupvars = fun->u.i.nupvars;
-        localsword = (nargs << 16) | nvars;
-        flagsword = (nupvars << 16) | fun->flags;
+        flagsword = (fun->nargs << 16) | fun->flags;
     } else {
         fun = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, NULL, NULL);
         if (!fun)
             return false;
         FUN_OBJECT(fun)->clearParent();
         FUN_OBJECT(fun)->clearProto();
-#ifdef __GNUC__
-        nvars = nargs = nupvars = 0;    /* quell GCC uninitialized warning */
-#endif
     }
 
     AutoObjectRooter tvr(cx, FUN_OBJECT(fun));
 
     if (!JS_XDRUint32(xdr, &firstword))
         return false;
     if ((firstword & 1U) && !js_XDRAtom(xdr, &fun->atom))
         return false;
-    if (!JS_XDRUint32(xdr, &localsword) ||
-        !JS_XDRUint32(xdr, &flagsword)) {
+    if (!JS_XDRUint32(xdr, &flagsword))
         return false;
-    }
 
     if (xdr->mode == JSXDR_DECODE) {
-        nargs = localsword >> 16;
-        nvars = uint16(localsword);
+        fun->nargs = flagsword >> 16;
         JS_ASSERT((flagsword & JSFUN_KINDMASK) >= JSFUN_INTERPRETED);
-        nupvars = flagsword >> 16;
         fun->flags = uint16(flagsword);
         fun->u.i.skipmin = uint16(firstword >> 2);
         fun->u.i.wrapper = JSPackedBool((firstword >> 1) & 1);
     }
 
-    /* do arguments and local vars */
-    n = nargs + nvars + nupvars;
-    if (n != 0) {
-        void *mark;
-        uintN i;
-        uintN bitmapLength;
-        uint32 *bitmap;
-        jsuword *names;
-        JSAtom *name;
-        JSLocalKind localKind;
-
-        bool ok = true;
-        mark = JS_ARENA_MARK(&xdr->cx->tempPool);
-
-        /*
-         * From this point the control must flow via the label release_mark.
-         *
-         * To xdr the names we prefix the names with a bitmap descriptor and
-         * then xdr the names as strings. For argument names (indexes below
-         * nargs) the corresponding bit in the bitmap is unset when the name
-         * is null. Such null names are not encoded or decoded. For variable
-         * names (indexes starting from nargs) bitmap's bit is set when the
-         * name is declared as const, not as ordinary var.
-         * */
-        MUST_FLOW_THROUGH("release_mark");
-        bitmapLength = JS_HOWMANY(n, JS_BITS_PER_UINT32);
-        JS_ARENA_ALLOCATE_CAST(bitmap, uint32 *, &xdr->cx->tempPool,
-                               bitmapLength * sizeof *bitmap);
-        if (!bitmap) {
-            js_ReportOutOfScriptQuota(xdr->cx);
-            ok = false;
-            goto release_mark;
-        }
-        if (xdr->mode == JSXDR_ENCODE) {
-            names = fun->getLocalNameArray(xdr->cx, &xdr->cx->tempPool);
-            if (!names) {
-                ok = false;
-                goto release_mark;
-            }
-            PodZero(bitmap, bitmapLength);
-            for (i = 0; i != n; ++i) {
-                if (i < fun->nargs
-                    ? JS_LOCAL_NAME_TO_ATOM(names[i]) != NULL
-                    : JS_LOCAL_NAME_IS_CONST(names[i])) {
-                    bitmap[i >> JS_BITS_PER_UINT32_LOG2] |=
-                        JS_BIT(i & (JS_BITS_PER_UINT32 - 1));
-                }
-            }
-        }
-#ifdef __GNUC__
-        else {
-            names = NULL;   /* quell GCC uninitialized warning */
-        }
-#endif
-        for (i = 0; i != bitmapLength; ++i) {
-            ok = !!JS_XDRUint32(xdr, &bitmap[i]);
-            if (!ok)
-                goto release_mark;
-        }
-        for (i = 0; i != n; ++i) {
-            if (i < nargs &&
-                !(bitmap[i >> JS_BITS_PER_UINT32_LOG2] &
-                  JS_BIT(i & (JS_BITS_PER_UINT32 - 1)))) {
-                if (xdr->mode == JSXDR_DECODE) {
-                    ok = !!fun->addLocal(xdr->cx, NULL, JSLOCAL_ARG);
-                    if (!ok)
-                        goto release_mark;
-                } else {
-                    JS_ASSERT(!JS_LOCAL_NAME_TO_ATOM(names[i]));
-                }
-                continue;
-            }
-            if (xdr->mode == JSXDR_ENCODE)
-                name = JS_LOCAL_NAME_TO_ATOM(names[i]);
-            ok = !!js_XDRAtom(xdr, &name);
-            if (!ok)
-                goto release_mark;
-            if (xdr->mode == JSXDR_DECODE) {
-                localKind = (i < nargs)
-                            ? JSLOCAL_ARG
-                            : (i < nargs + nvars)
-                            ? (bitmap[i >> JS_BITS_PER_UINT32_LOG2] &
-                               JS_BIT(i & (JS_BITS_PER_UINT32 - 1))
-                               ? JSLOCAL_CONST
-                               : JSLOCAL_VAR)
-                            : JSLOCAL_UPVAR;
-                ok = !!fun->addLocal(xdr->cx, name, localKind);
-                if (!ok)
-                    goto release_mark;
-            }
-        }
-
-      release_mark:
-        JS_ARENA_RELEASE(&xdr->cx->tempPool, mark);
-        if (!ok)
-            return false;
-
-        if (xdr->mode == JSXDR_DECODE)
-            fun->freezeLocalNames(cx);
-    }
-
     if (!js_XDRScript(xdr, &fun->u.i.script, NULL))
         return false;
 
     if (xdr->mode == JSXDR_DECODE) {
         *objp = FUN_OBJECT(fun);
 #ifdef CHECK_SCRIPT_OWNER
         fun->script()->owner = NULL;
 #endif
+        JS_ASSERT(fun->nargs == fun->script()->bindings.countArgs());
         js_CallNewScriptHook(cx, fun->script(), fun);
     }
 
     return true;
 }
 
 #else  /* !JS_HAS_XDR */
 
@@ -2013,79 +1904,50 @@ fun_trace(JSTracer *trc, JSObject *obj)
     if (!fun)
         return;
 
     if (fun != obj) {
         /* obj is a cloned function object, trace the clone-parent, fun. */
         MarkObject(trc, *fun, "private");
 
         /* The function could be a flat closure with upvar copies in the clone. */
-        if (FUN_FLAT_CLOSURE(fun) && fun->u.i.nupvars)
-            MarkValueRange(trc, fun->u.i.nupvars, obj->getFlatClosureUpvars(), "upvars");
+        if (fun->isFlatClosure() && fun->script()->bindings.hasUpvars()) {
+            MarkValueRange(trc, fun->script()->bindings.countUpvars(),
+                           obj->getFlatClosureUpvars(), "upvars");
+        }
         return;
     }
 
     if (fun->atom)
         MarkString(trc, ATOM_TO_STRING(fun->atom), "atom");
 
-    if (FUN_INTERPRETED(fun)) {
-        if (fun->u.i.script)
-            js_TraceScript(trc, fun->u.i.script);
-        for (const Shape *shape = fun->u.i.names; shape; shape = shape->previous())
-            shape->trace(trc);
-    }
+    if (fun->isInterpreted() && fun->script())
+        js_TraceScript(trc, fun->script());
 }
 
 static void
 fun_finalize(JSContext *cx, JSObject *obj)
 {
     /* Ignore newborn function objects. */
-    JSFunction *fun = (JSFunction *) obj->getPrivate();
+    JSFunction *fun = obj->getFunctionPrivate();
     if (!fun)
         return;
 
     /* Cloned function objects may be flat closures with upvars to free. */
     if (fun != obj) {
-        if (FUN_FLAT_CLOSURE(fun) && fun->u.i.nupvars != 0)
+        if (fun->isFlatClosure() && fun->script()->bindings.hasUpvars())
             cx->free((void *) obj->getFlatClosureUpvars());
         return;
     }
 
     /*
-     * Null-check of u.i.script is required since the parser sets interpreted
-     * very early.
+     * Null-check fun->script() because the parser sets interpreted very early.
      */
-    if (FUN_INTERPRETED(fun) && fun->u.i.script)
-        js_DestroyScriptFromGC(cx, fun->u.i.script);
-}
-
-int
-JSFunction::sharpSlotBase(JSContext *cx)
-{
-#if JS_HAS_SHARP_VARS
-    JSAtom *name = js_Atomize(cx, "#array", 6, 0);
-    if (name) {
-        uintN index = uintN(-1);
-#ifdef DEBUG
-        JSLocalKind kind =
-#endif
-            lookupLocal(cx, name, &index);
-        JS_ASSERT(kind == JSLOCAL_VAR);
-        return int(index);
-    }
-#endif
-    return -1;
-}
-
-uint32
-JSFunction::countUpvarSlots() const
-{
-    JS_ASSERT(FUN_INTERPRETED(this));
-
-    return (u.i.nupvars == 0) ? 0 : u.i.script->upvars()->length;
+    if (fun->isInterpreted() && fun->script())
+        js_DestroyScriptFromGC(cx, fun->script());
 }
 
 /*
  * Reserve two slots in all function objects for XPConnect.  Note that this
  * does not bloat every instance, only those on which reserved slots are set,
  * and those on which ad-hoc properties are defined.
  */
 JS_PUBLIC_DATA(Class) js_FunctionClass = {
@@ -2526,16 +2388,18 @@ Function(JSContext *cx, uintN argc, Valu
      * Report errors via CSP is done in the script security manager.
      * js_CheckContentSecurityPolicy is defined in jsobj.cpp
      */
     if (!js_CheckContentSecurityPolicy(cx)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CSP_BLOCKED_FUNCTION);
         return JS_FALSE;
     }
 
+    Bindings bindings(cx);
+
     Value *argv = vp + 2;
     uintN n = argc ? argc - 1 : 0;
     if (n > 0) {
         enum { OK, BAD, BAD_FORMAL } state;
 
         /*
          * Collect the function-argument arguments into one string, separated
          * by commas, then make a tokenstream from that string, and scan it to
@@ -2630,30 +2494,35 @@ Function(JSContext *cx, uintN argc, Valu
                 /*
                  * Get the atom corresponding to the name from the token
                  * stream; we're assured at this point that it's a valid
                  * identifier.
                  */
                 JSAtom *atom = ts.currentToken().t_atom;
 
                 /* Check for a duplicate parameter name. */
-                if (fun->lookupLocal(cx, atom, NULL) != JSLOCAL_NONE) {
+                if (bindings.hasBinding(atom)) {
                     JSAutoByteString name;
                     if (!js_AtomToPrintableString(cx, atom, &name)) {
                         state = BAD;
                         goto after_args;
                     }
                     if (!ReportCompileErrorNumber(cx, &ts, NULL,
                                                   JSREPORT_WARNING | JSREPORT_STRICT,
                                                   JSMSG_DUPLICATE_FORMAL, name.ptr())) {
+                        state = BAD;
                         goto after_args;
                     }
                 }
-                if (!fun->addLocal(cx, atom, JSLOCAL_ARG))
+
+                uint16 dummy;
+                if (!bindings.addArgument(cx, atom, &dummy)) {
+                    state = BAD;
                     goto after_args;
+                }
 
                 /*
                  * Get the next token.  Stop on end of stream.  Otherwise
                  * insist on a comma, get another name, and iterate.
                  */
                 tt = ts.getToken();
                 if (tt == TOK_EOF)
                     break;
@@ -2676,30 +2545,31 @@ Function(JSContext *cx, uintN argc, Valu
         ts.close();
         JS_ARENA_RELEASE(&cx->tempPool, mark);
         if (state != OK)
             return JS_FALSE;
     }
 
     JSString *str;
     if (argc) {
-        str = js_ValueToString(cx, argv[argc-1]);
+        str = js_ValueToString(cx, argv[argc - 1]);
         if (!str)
             return JS_FALSE;
-        argv[argc-1].setString(str);
+        argv[argc - 1].setString(str);
     } else {
         str = cx->runtime->emptyString;
     }
 
     size_t length = str->length();
     const jschar *chars = str->getChars(cx);
     if (!chars)
         return JS_FALSE;
-    return Compiler::compileFunctionBody(cx, fun, principals, chars, length,
-                                         filename, lineno);
+
+    return Compiler::compileFunctionBody(cx, fun, principals, &bindings,
+                                         chars, length, filename, lineno);
 }
 
 static JSBool
 ThrowTypeError(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
                                  JSMSG_THROW_TYPE_ERROR);
     return false;
@@ -2763,22 +2633,19 @@ js_NewFunction(JSContext *cx, JSObject *
     fun = (JSFunction *) funobj;
 
     /* Initialize all function members. */
     fun->nargs = uint16(nargs);
     fun->flags = flags & (JSFUN_FLAGS_MASK | JSFUN_KINDMASK | JSFUN_TRCINFO);
     if ((flags & JSFUN_KINDMASK) >= JSFUN_INTERPRETED) {
         JS_ASSERT(!native);
         JS_ASSERT(nargs == 0);
-        fun->u.i.nvars = 0;
-        fun->u.i.nupvars = 0;
         fun->u.i.skipmin = 0;
         fun->u.i.wrapper = false;
         fun->u.i.script = NULL;
-        fun->u.i.names = cx->runtime->emptyCallShape;
     } else {
         fun->u.n.clasp = NULL;
         if (flags & JSFUN_TRCINFO) {
 #ifdef JS_TRACER
             JSNativeTraceInfo *trcinfo =
                 JS_FUNC_TO_DATA_PTR(JSNativeTraceInfo *, native);
             fun->u.n.native = (js::Native) trcinfo->native;
             fun->u.n.trcinfo = trcinfo;
@@ -2855,26 +2722,27 @@ JS_DEFINE_CALLINFO_4(extern, OBJECT, js_
 /*
  * Create a new flat closure, but don't initialize the imported upvar
  * values. The tracer calls this function and then initializes the upvar
  * slots on trace.
  */
 JSObject * JS_FASTCALL
 js_AllocFlatClosure(JSContext *cx, JSFunction *fun, JSObject *scopeChain)
 {
-    JS_ASSERT(FUN_FLAT_CLOSURE(fun));
-    JS_ASSERT((JSScript::isValidOffset(fun->u.i.script->upvarsOffset)
-               ? fun->u.i.script->upvars()->length
-               : 0) == fun->u.i.nupvars);
+    JS_ASSERT(fun->isFlatClosure());
+    JS_ASSERT(JSScript::isValidOffset(fun->script()->upvarsOffset) ==
+              fun->script()->bindings.hasUpvars());
+    JS_ASSERT_IF(JSScript::isValidOffset(fun->script()->upvarsOffset),
+                 fun->script()->upvars()->length == fun->script()->bindings.countUpvars());
 
     JSObject *closure = CloneFunctionObject(cx, fun, scopeChain);
     if (!closure)
         return closure;
 
-    uint32 nslots = fun->countUpvarSlots();
+    uint32 nslots = fun->script()->bindings.countUpvars();
     if (nslots == 0)
         return closure;
 
     Value *upvars = (Value *) cx->malloc(nslots * sizeof(Value));
     if (!upvars)
         return NULL;
 
     closure->setFlatClosureUpvars(upvars);
@@ -2891,22 +2759,22 @@ js_NewFlatClosure(JSContext *cx, JSFunct
      * Flat closures can be partial, they may need to search enclosing scope
      * objects via JSOP_NAME, etc.
      */
     JSObject *scopeChain = GetScopeChainFast(cx, cx->fp(), op, oplen);
     if (!scopeChain)
         return NULL;
 
     JSObject *closure = js_AllocFlatClosure(cx, fun, scopeChain);
-    if (!closure || fun->u.i.nupvars == 0)
+    if (!closure || !fun->script()->bindings.hasUpvars())
         return closure;
 
     Value *upvars = closure->getFlatClosureUpvars();
     uintN level = fun->u.i.script->staticLevel;
-    JSUpvarArray *uva = fun->u.i.script->upvars();
+    JSUpvarArray *uva = fun->script()->upvars();
 
     for (uint32 i = 0, n = uva->length; i < n; i++)
         upvars[i] = GetUpvar(cx, level, uva->vector[i]);
 
     return closure;
 }
 
 JSObject *
@@ -3068,220 +2936,8 @@ js_ReportIsNotFunction(JSContext *cx, co
             spindex = vp - simsp;
     }
 
     if (!spindex)
         spindex = ((flags & JSV2F_SEARCH_STACK) ? JSDVG_SEARCH_STACK : JSDVG_IGNORE_STACK);
 
     js_ReportValueError3(cx, error, spindex, *vp, NULL, name, source);
 }
-
-const Shape *
-JSFunction::lastArg() const
-{
-    const Shape *shape = lastVar();
-    if (u.i.nvars != 0) {
-        while (shape->previous() && shape->getter() != GetCallArg)
-            shape = shape->previous();
-    }
-    return shape;
-}
-
-const Shape *
-JSFunction::lastVar() const
-{
-    const Shape *shape = u.i.names;
-    if (u.i.nupvars != 0) {
-        while (shape->getter() == GetFlatUpvar)
-            shape = shape->previous();
-    }
-    return shape;
-}
-
-bool
-JSFunction::addLocal(JSContext *cx, JSAtom *atom, JSLocalKind kind)
-{
-    JS_ASSERT(FUN_INTERPRETED(this));
-    JS_ASSERT(!u.i.script);
-
-    /*
-     * 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.
-     */
-    uintN attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED;
-    uint16 *indexp;
-    PropertyOp getter, setter;
-    uint32 slot = JSObject::CALL_RESERVED_SLOTS;
-
-    if (kind == JSLOCAL_ARG) {
-        JS_ASSERT(u.i.nupvars == 0);
-
-        indexp = &nargs;
-        getter = GetCallArg;
-        setter = SetCallArg;
-        slot += nargs;
-    } else if (kind == JSLOCAL_UPVAR) {
-        indexp = &u.i.nupvars;
-        getter = GetFlatUpvar;
-        setter = SetFlatUpvar;
-        slot = SHAPE_INVALID_SLOT;
-    } else {
-        JS_ASSERT(u.i.nupvars == 0);
-
-        indexp = &u.i.nvars;
-        getter = GetCallVar;
-        setter = SetCallVar;
-        if (kind == JSLOCAL_CONST)
-            attrs |= JSPROP_READONLY;
-        else
-            JS_ASSERT(kind == JSLOCAL_VAR);
-        slot += nargs + u.i.nvars;
-    }
-
-    if (*indexp == JS_BITMASK(16)) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             (kind == JSLOCAL_ARG)
-                             ? JSMSG_TOO_MANY_FUN_ARGS
-                             : JSMSG_TOO_MANY_LOCALS);
-        return false;
-    }
-
-    jsid id;
-    if (!atom) {
-        /* Destructuring formal parameter: use argument index as id. */
-        JS_ASSERT(kind == JSLOCAL_ARG);
-        id = INT_TO_JSID(nargs);
-    } else {
-        id = ATOM_TO_JSID(atom);
-    }
-
-    Shape child(id, getter, setter, slot, attrs, Shape::HAS_SHORTID, *indexp);
-
-    Shape *shape = u.i.names->getChild(cx, child, &u.i.names);
-    if (!shape)
-        return false;
-
-    JS_ASSERT(u.i.names == shape);
-    ++*indexp;
-    return true;
-}
-
-JSLocalKind
-JSFunction::lookupLocal(JSContext *cx, JSAtom *atom, uintN *indexp)
-{
-    JS_ASSERT(FUN_INTERPRETED(this));
-
-    Shape *shape = SHAPE_FETCH(Shape::search(&u.i.names, ATOM_TO_JSID(atom)));
-    if (shape) {
-        JSLocalKind localKind;
-
-        if (shape->getter() == GetCallArg)
-            localKind = JSLOCAL_ARG;
-        else if (shape->getter() == GetFlatUpvar)
-            localKind = JSLOCAL_UPVAR;
-        else if (!shape->writable())
-            localKind = JSLOCAL_CONST;
-        else
-            localKind = JSLOCAL_VAR;
-
-        if (indexp)
-            *indexp = shape->shortid;
-        return localKind;
-    }
-    return JSLOCAL_NONE;
-}
-
-jsuword *
-JSFunction::getLocalNameArray(JSContext *cx, JSArenaPool *pool)
-{
-    JS_ASSERT(hasLocalNames());
-
-    uintN n = countLocalNames();
-    jsuword *names;
-
-    /*
-     * No need to check for overflow of the allocation size as we are making a
-     * copy of already allocated data. As such it must fit size_t.
-     */
-    JS_ARENA_ALLOCATE_CAST(names, jsuword *, pool, size_t(n) * sizeof *names);
-    if (!names) {
-        js_ReportOutOfScriptQuota(cx);
-        return NULL;
-    }
-
-#ifdef DEBUG
-    for (uintN i = 0; i != n; i++)
-        names[i] = 0xdeadbeef;
-#endif
-
-    for (Shape::Range r = u.i.names; !r.empty(); r.popFront()) {
-        const Shape &shape = r.front();
-        uintN index = uint16(shape.shortid);
-        jsuword constFlag = 0;
-
-        if (shape.getter() == GetCallArg) {
-            JS_ASSERT(index < nargs);
-        } else if (shape.getter() == GetFlatUpvar) {
-            JS_ASSERT(index < u.i.nupvars);
-            index += nargs + u.i.nvars;
-        } else {
-            JS_ASSERT(index < u.i.nvars);
-            index += nargs;
-            if (!shape.writable())
-                constFlag = 1;
-        }
-
-        JSAtom *atom;
-        if (JSID_IS_ATOM(shape.id)) {
-            atom = JSID_TO_ATOM(shape.id);
-        } else {
-            JS_ASSERT(JSID_IS_INT(shape.id));
-            JS_ASSERT(shape.getter() == GetCallArg);
-            atom = NULL;
-        }
-
-        names[index] = jsuword(atom);
-    }
-
-#ifdef DEBUG
-    for (uintN i = 0; i != n; i++)
-        JS_ASSERT(names[i] != 0xdeadbeef);
-#endif
-    return names;
-}
-
-void
-JSFunction::freezeLocalNames(JSContext *cx)
-{
-    JS_ASSERT(FUN_INTERPRETED(this));
-
-    Shape *shape = u.i.names;
-    if (shape->inDictionary()) {
-        do {
-            JS_ASSERT(!shape->frozen());
-            shape->setFrozen();
-        } while ((shape = shape->parent) != NULL);
-    }
-}
-
-/*
- * This method is called only if we parsed a duplicate formal. Let's use the
- * simplest possible algorithm, risking O(n^2) pain -- anyone dup'ing formals
- * is asking for it!
- */
-JSAtom *
-JSFunction::findDuplicateFormal() const
-{
-    JS_ASSERT(isInterpreted());
-
-    if (nargs <= 1)
-        return NULL;
-
-    for (Shape::Range r = lastArg(); !r.empty(); r.popFront()) {
-        const Shape &shape = r.front();
-        for (Shape::Range r2 = shape.previous(); !r2.empty(); r2.popFront()) {
-            if (r2.front().id == shape.id)
-                return JSID_TO_ATOM(shape.id);
-        }
-    }
-    return NULL;
-}
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -41,16 +41,17 @@
 #define jsfun_h___
 /*
  * JS function definitions.
  */
 #include "jsprvtd.h"
 #include "jspubtd.h"
 #include "jsobj.h"
 #include "jsatom.h"
+#include "jsscript.h"
 #include "jsstr.h"
 #include "jsopcode.h"
 
 /*
  * The high two bits of JSFunction.flags encode whether the function is native
  * or interpreted, and if interpreted, what kind of optimized closure form (if
  * any) it might be.
  *
@@ -108,58 +109,32 @@
 #define FUN_NULL_CLOSURE(fun)(FUN_KIND(fun) == JSFUN_NULL_CLOSURE)
 #define FUN_SCRIPT(fun)      (FUN_INTERPRETED(fun) ? (fun)->u.i.script : NULL)
 #define FUN_CLASP(fun)       (JS_ASSERT(!FUN_INTERPRETED(fun)),               \
                               fun->u.n.clasp)
 #define FUN_TRCINFO(fun)     (JS_ASSERT(!FUN_INTERPRETED(fun)),               \
                               JS_ASSERT((fun)->flags & JSFUN_TRCINFO),        \
                               fun->u.n.trcinfo)
 
-/*
- * Formal parameters, local variables, and upvars are stored in a shape tree
- * path with its latest node at fun->u.i.names. The addLocal, lookupLocal, and
- * getLocalNameArray methods abstract away this detail.
- *
- * The lastArg, lastVar, and lastUpvar JSFunction methods provide more direct
- * access to the shape path. These methods may be used to make a Shape::Range
- * for iterating over the relevant shapes from youngest to oldest (i.e., last
- * or right-most to first or left-most in source order).
- *
- * Sometimes iteration order must be from oldest to youngest, however. For such
- * cases, use getLocalNameArray. The RAII helper class js::AutoLocalNameArray,
- * defined in jscntxt.h, should be used where possible instead of direct calls
- * to getLocalNameArray.
- */
-enum JSLocalKind {
-    JSLOCAL_NONE,
-    JSLOCAL_ARG,
-    JSLOCAL_VAR,
-    JSLOCAL_CONST,
-    JSLOCAL_UPVAR
-};
-
 struct JSFunction : public JSObject_Slots2
 {
     /* Functions always have two fixed slots (FUN_CLASS_RESERVED_SLOTS). */
 
     uint16          nargs;        /* maximum number of specified arguments,
                                      reflected as f.length/f.arity */
     uint16          flags;        /* flags, see JSFUN_* below and in jsapi.h */
     union U {
         struct {
             js::Native  native;   /* native method pointer or null */
             js::Class   *clasp;   /* class of objects constructed
                                      by this function */
             JSNativeTraceInfo *trcinfo;
         } n;
         struct Scripted {
             JSScript    *script;  /* interpreted bytecode descriptor or null */
-            uint16      nvars;    /* number of local variables */
-            uint16      nupvars;  /* number of upvars (computable from script
-                                     but here for faster access) */
             uint16       skipmin; /* net skip amount up (toward zero) from
                                      script->staticLevel to nearest upvar,
                                      including upvars in nested functions */
             JSPackedBool wrapper; /* true if this function is a wrapper that
                                      rewrites bytecode optimized for a function
                                      judged non-escaping by the compiler, which
                                      then escaped via the debugger or a rogue
                                      indirect eval; if true, then this function
@@ -178,94 +153,29 @@ struct JSFunction : public JSObject_Slot
     bool isHeavyweight()     const { return JSFUN_HEAVYWEIGHT_TEST(flags); }
     bool isFlatClosure()     const { return FUN_KIND(this) == JSFUN_FLAT_CLOSURE; }
 
     bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; }
 
     /* Returns the strictness of this function, which must be interpreted. */
     inline bool inStrictMode() const;
 
-    uintN countVars() const {
-        JS_ASSERT(FUN_INTERPRETED(this));
-        return u.i.nvars;
+    void setArgCount(uint16 nargs) {
+        JS_ASSERT(this->nargs == 0);
+        this->nargs = nargs;
     }
 
     /* uint16 representation bounds number of call object dynamic slots. */
     enum { MAX_ARGS_AND_VARS = 2 * ((1U << 16) - 1) };
 
-    uintN countArgsAndVars() const {
-        JS_ASSERT(FUN_INTERPRETED(this));
-        return nargs + u.i.nvars;
-    }
-
-    uintN countLocalNames() const {
-        JS_ASSERT(FUN_INTERPRETED(this));
-        return countArgsAndVars() + u.i.nupvars;
-    }
-
-    bool hasLocalNames() const {
-        JS_ASSERT(FUN_INTERPRETED(this));
-        return countLocalNames() != 0;
-    }
-
-    int sharpSlotBase(JSContext *cx);
-
-    uint32 countUpvarSlots() const;
-
-    const js::Shape *lastArg() const;
-    const js::Shape *lastVar() const;
-    const js::Shape *lastUpvar() const { return u.i.names; }
-
-    /*
-     * The parser builds shape paths for functions, usable by Call objects at
-     * runtime, by calling addLocal. All locals of ARG kind must be addLocal'ed
-     * before any VAR kind, and VAR before UPVAR.
-     */
-    bool addLocal(JSContext *cx, JSAtom *atom, JSLocalKind kind);
-
-    /*
-     * Look up an argument or variable name returning its kind when found or
-     * JSLOCAL_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.
-     */
-    JSLocalKind lookupLocal(JSContext *cx, JSAtom *atom, uintN *indexp);
-
-    /*
-     * Function and macros to work with local names as an array of words.
-     * getLocalNameArray returns the array, or null if we are out of memory.
-     * This function must be called only when fun->hasLocalNames().
-     *
-     * The supplied pool is used to allocate the returned array, so the caller
-     * is obligated to mark and release to free it.
-     *
-     * The elements of the array with index less than fun->nargs correspond to
-     * the names of function formal parameters. An index >= fun->nargs
-     * addresses a var binding. Use JS_LOCAL_NAME_TO_ATOM to convert array's
-     * element to an atom pointer. This pointer can be null when the element is
-     * for a formal parameter corresponding to a destructuring pattern.
-     *
-     * If nameWord does not name a formal parameter, use JS_LOCAL_NAME_IS_CONST
-     * to check if nameWord corresponds to the const declaration.
-     */
-    jsuword *getLocalNameArray(JSContext *cx, struct JSArenaPool *pool);
-
-    void freezeLocalNames(JSContext *cx);
-
-    /*
-     * If fun's formal parameters include any duplicate names, return one
-     * of them (chosen arbitrarily). If they are all unique, return NULL.
-     */
-    JSAtom *findDuplicateFormal() const;
-
 #define JS_LOCAL_NAME_TO_ATOM(nameWord)  ((JSAtom *) ((nameWord) & ~(jsuword) 1))
 #define JS_LOCAL_NAME_IS_CONST(nameWord) ((((nameWord) & (jsuword) 1)) != 0)
 
     bool mightEscape() const {
-        return FUN_INTERPRETED(this) && (FUN_FLAT_CLOSURE(this) || u.i.nupvars == 0);
+        return isInterpreted() && (isFlatClosure() || !script()->bindings.hasUpvars());
     }
 
     bool joinable() const {
         return flags & JSFUN_JOINABLE;
     }
 
     JSObject &compiledFunObj() {
         return *this;
@@ -602,21 +512,27 @@ GetCallVar(JSContext *cx, JSObject *obj,
 /*
  * Slower version of js_GetCallVar used when call_resolve detects an attempt to
  * leak an optimized closure via indirect or debugger eval.
  */
 extern JSBool
 GetCallVarChecked(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
 
 extern JSBool
+GetFlatUpvar(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
+
+extern JSBool
 SetCallArg(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
 
 extern JSBool
 SetCallVar(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
 
+extern JSBool
+SetFlatUpvar(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
+
 } // namespace js
 
 extern JSBool
 js_GetArgsValue(JSContext *cx, JSStackFrame *fp, js::Value *vp);
 
 extern JSBool
 js_GetArgsProperty(JSContext *cx, JSStackFrame *fp, jsid id, js::Value *vp);
 
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -969,17 +969,17 @@ Execute(JSContext *cx, JSObject *chain, 
 #if JS_HAS_SHARP_VARS
     JS_STATIC_ASSERT(SHARP_NSLOTS == 2);
     if (script->hasSharps) {
         JS_ASSERT(script->nfixed >= SHARP_NSLOTS);
         Value *sharps = &frame.fp()->slots()[script->nfixed - SHARP_NSLOTS];
         if (prev && prev->script()->hasSharps) {
             JS_ASSERT(prev->numFixed() >= SHARP_NSLOTS);
             int base = (prev->isFunctionFrame() && !prev->isEvalOrDebuggerFrame())
-                       ? prev->fun()->sharpSlotBase(cx)
+                       ? prev->fun()->script()->bindings.sharpSlotBase(cx)
                        : prev->numFixed() - SHARP_NSLOTS;
             if (base < 0)
                 return false;
             sharps[0] = prev->slots()[base];
             sharps[1] = prev->slots()[base + 1];
         } else {
             sharps[0].setUndefined();
             sharps[1].setUndefined();
@@ -5296,17 +5296,17 @@ BEGIN_CASE(JSOP_CALLUPVAR_DBG)
     JSProperty *prop;
     jsid id;
     JSAtom *atom;
     {
         AutoLocalNameArray names(cx, fun);
         if (!names)
             goto error;
 
-        uintN index = fun->countArgsAndVars() + GET_UINT16(regs.pc);
+        uintN index = fun->script()->bindings.countArgsAndVars() + GET_UINT16(regs.pc);
         atom = JS_LOCAL_NAME_TO_ATOM(names[index]);
         id = ATOM_TO_JSID(atom);
 
         if (!js_FindProperty(cx, id, &obj, &obj2, &prop))
             goto error;
     }
 
     if (!prop) {
@@ -5327,17 +5327,17 @@ END_CASE(JSOP_GETUPVAR_DBG)
 
 BEGIN_CASE(JSOP_GETFCSLOT)
 BEGIN_CASE(JSOP_CALLFCSLOT)
 {
     JS_ASSERT(regs.fp->isFunctionFrame() && !regs.fp->isEvalFrame());
     uintN index = GET_UINT16(regs.pc);
     JSObject *obj = &argv[-2].toObject();
 
-    JS_ASSERT(index < obj->getFunctionPrivate()->u.i.nupvars);
+    JS_ASSERT(index < obj->getFunctionPrivate()->script()->bindings.countUpvars());
     PUSH_COPY(obj->getFlatClosureUpvar(index));
     if (op == JSOP_CALLFCSLOT)
         PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETFCSLOT)
 
 BEGIN_CASE(JSOP_GETGLOBAL)
 BEGIN_CASE(JSOP_CALLGLOBAL)
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -460,24 +460,24 @@ struct JSObject : js::gc::Cell {
     void setOwnShape(uint32 s)  { flags |= OWN_SHAPE; objShape = s; }
     void clearOwnShape()        { flags &= ~OWN_SHAPE; objShape = map->shape; }
 
   public:
     inline bool nativeEmpty() const;
 
     bool hasOwnShape() const    { return !!(flags & OWN_SHAPE); }
 
-    void setMap(JSObjectMap *amap) {
+    void setMap(const JSObjectMap *amap) {
         JS_ASSERT(!hasOwnShape());
-        map = amap;
+        map = const_cast<JSObjectMap *>(amap);
         objShape = map->shape;
     }
 
     void setSharedNonNativeMap() {
-        setMap(const_cast<JSObjectMap *>(&JSObjectMap::sharedNonNative));
+        setMap(&JSObjectMap::sharedNonNative);
     }
 
     void deletingShapeChange(JSContext *cx, const js::Shape &shape);
     bool methodShapeChange(JSContext *cx, const js::Shape &shape);
     bool methodShapeChange(JSContext *cx, uint32 slot);
     void protoShapeChange(JSContext *cx);
     void shadowingShapeChange(JSContext *cx, const js::Shape &shape);
     bool globalObjectOwnShapeChange(JSContext *cx);
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -488,16 +488,17 @@ JSObject::getFlatClosureUpvars() const
     JS_ASSERT(isFunction());
     JS_ASSERT(FUN_FLAT_CLOSURE(getFunctionPrivate()));
     return (js::Value *) getSlot(JSSLOT_FLAT_CLOSURE_UPVARS).toPrivate();
 }
 
 inline js::Value
 JSObject::getFlatClosureUpvar(uint32 i) const
 {
+    JS_ASSERT(i < getFunctionPrivate()->script()->bindings.countUpvars());
     return getFlatClosureUpvars()[i];
 }
 
 inline void
 JSObject::setFlatClosureUpvars(js::Value *upvars)
 {
     JS_ASSERT(isFunction());
     JS_ASSERT(FUN_FLAT_CLOSURE(getFunctionPrivate()));
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -827,18 +827,18 @@ js_NewPrinter(JSContext *cx, const char 
     jp->pretty = !!pretty;
     jp->grouped = !!grouped;
     jp->strict = !!strict;
     jp->script = NULL;
     jp->dvgfence = NULL;
     jp->pcstack = NULL;
     jp->fun = fun;
     jp->localNames = NULL;
-    if (fun && FUN_INTERPRETED(fun) && fun->hasLocalNames()) {
-        jp->localNames = fun->getLocalNameArray(cx, &jp->pool);
+    if (fun && fun->isInterpreted() && fun->script()->bindings.hasLocalNames()) {
+        jp->localNames = fun->script()->bindings.getLocalNameArray(cx, &jp->pool);
         if (!jp->localNames) {
             js_DestroyPrinter(jp);
             return NULL;
         }
     }
     return jp;
 }
 
@@ -1335,17 +1335,17 @@ DecompileSwitch(SprintStack *ss, TableEn
     LOCAL_ASSERT_CUSTOM(expr, return (rv))
 
 static JSAtom *
 GetArgOrVarAtom(JSPrinter *jp, uintN slot)
 {
     JSAtom *name;
 
     LOCAL_ASSERT_RV(jp->fun, NULL);
-    LOCAL_ASSERT_RV(slot < jp->fun->countLocalNames(), NULL);
+    LOCAL_ASSERT_RV(slot < jp->fun->script()->bindings.countLocalNames(), NULL);
     name = JS_LOCAL_NAME_TO_ATOM(jp->localNames[slot]);
 #if !JS_HAS_DESTRUCTURING
     LOCAL_ASSERT_RV(name, NULL);
 #endif
     return name;
 }
 
 const char *
@@ -2890,22 +2890,25 @@ Decompile(SprintStack *ss, jsbytecode *p
               case JSOP_GETFCSLOT:
               case JSOP_CALLFCSLOT:
               {
                 if (!jp->fun) {
                     JS_ASSERT(jp->script->savedCallerFun);
                     jp->fun = jp->script->getFunction(0);
                 }
 
-                if (!jp->localNames)
-                    jp->localNames = jp->fun->getLocalNameArray(cx, &jp->pool);
+                if (!jp->localNames) {
+                    JS_ASSERT(fun == jp->fun);
+                    jp->localNames =
+                        jp->fun->script()->bindings.getLocalNameArray(cx, &jp->pool);
+                }
 
                 uintN index = GET_UINT16(pc);
-                if (index < jp->fun->u.i.nupvars) {
-                    index += jp->fun->countArgsAndVars();
+                if (index < jp->fun->script()->bindings.countUpvars()) {
+                    index += jp->fun->script()->bindings.countArgsAndVars();
                 } else {
                     JSUpvarArray *uva;
 #ifdef DEBUG
                     /*
                      * We must be in an eval called from jp->fun, where
                      * jp->script is the eval-compiled script.
                      *
                      * However, it's possible that a js_Invoke already
@@ -2914,18 +2917,18 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * called with an intervening frame on the stack.
                      */
                     JSStackFrame *fp = js_GetTopStackFrame(cx);
                     if (fp) {
                         while (!fp->isEvalFrame())
                             fp = fp->prev();
                         JS_ASSERT(fp->script() == jp->script);
                         JS_ASSERT(fp->prev()->fun() == jp->fun);
-                        JS_ASSERT(FUN_INTERPRETED(jp->fun));
-                        JS_ASSERT(jp->script != jp->fun->u.i.script);
+                        JS_ASSERT(jp->fun->isInterpreted());
+                        JS_ASSERT(jp->script != jp->fun->script());
                         JS_ASSERT(JSScript::isValidOffset(jp->script->upvarsOffset));
                     }
 #endif
                     uva = jp->script->upvars();
                     index = uva->vector[index].slot();
                 }
                 atom = GetArgOrVarAtom(jp, index);
                 goto do_name;
@@ -4092,24 +4095,25 @@ Decompile(SprintStack *ss, jsbytecode *p
 
                     /*
                      * All allocation when decompiling is LIFO, using malloc
                      * or, more commonly, arena-allocating from cx->tempPool.
                      * Therefore after InitSprintStack succeeds, we must
                      * release to mark before returning.
                      */
                     mark = JS_ARENA_MARK(&cx->tempPool);
-                    if (!fun->hasLocalNames()) {
-                        innerLocalNames = NULL;
-                    } else {
-                        innerLocalNames = fun->getLocalNameArray(cx, &cx->tempPool);
+                    if (fun->script()->bindings.hasLocalNames()) {
+                        innerLocalNames =
+                            fun->script()->bindings.getLocalNameArray(cx, &cx->tempPool);
                         if (!innerLocalNames)
                             return NULL;
+                    } else {
+                        innerLocalNames = NULL;
                     }
-                    inner = fun->u.i.script;
+                    inner = fun->script();
                     if (!InitSprintStack(cx, &ss2, jp, StackDepth(inner))) {
                         JS_ARENA_RELEASE(&cx->tempPool, mark);
                         return NULL;
                     }
                     ss2.inGenExp = JS_TRUE;
 
                     /*
                      * Recursively decompile this generator function as an
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -87,16 +87,17 @@
 #if JS_HAS_DESTRUCTURING
 #include "jsdhash.h"
 #endif
 
 #include "jsatominlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsregexpinlines.h"
+#include "jsscriptinlines.h"
 
 // Grr, windows.h or something under it #defines CONST...
 #ifdef CONST
 #undef CONST
 #endif
 
 using namespace js;
 using namespace js::gc;
@@ -238,16 +239,17 @@ Parser::newObjectBox(JSObject *obj)
     if (!objbox) {
         js_ReportOutOfScriptQuota(context);
         return NULL;
     }
     objbox->traceLink = traceListHead;
     traceListHead = objbox;
     objbox->emitLink = NULL;
     objbox->object = obj;
+    objbox->isFunctionBox = false;
     return objbox;
 }
 
 JSFunctionBox *
 Parser::newFunctionBox(JSObject *obj, JSParseNode *fn, JSTreeContext *tc)
 {
     JS_ASSERT(obj);
     JS_ASSERT(obj->isFunction());
@@ -263,23 +265,25 @@ Parser::newFunctionBox(JSObject *obj, JS
     if (!funbox) {
         js_ReportOutOfScriptQuota(context);
         return NULL;
     }
     funbox->traceLink = traceListHead;
     traceListHead = funbox;
     funbox->emitLink = NULL;
     funbox->object = obj;
+    funbox->isFunctionBox = true;
     funbox->node = fn;
     funbox->siblings = tc->functionList;
     tc->functionList = funbox;
     ++tc->parser->functionCount;
     funbox->kids = NULL;
     funbox->parent = tc->funbox;
     funbox->methods = NULL;
+    new (&funbox->bindings) Bindings(context);
     funbox->queued = false;
     funbox->inLoop = false;
     for (JSStmtInfo *stmt = tc->topStmt; stmt; stmt = stmt->down) {
         if (STMT_IS_LOOP(stmt)) {
             funbox->inLoop = true;
             break;
         }
     }
@@ -310,18 +314,23 @@ JSFunctionBox::shouldUnbrand(uintN metho
 }
 
 void
 Parser::trace(JSTracer *trc)
 {
     JSObjectBox *objbox = traceListHead;
     while (objbox) {
         MarkObject(trc, *objbox->object, "parser.object");
+        if (objbox->isFunctionBox)
+            static_cast<JSFunctionBox *>(objbox)->bindings.trace(trc);
         objbox = objbox->traceLink;
     }
+
+    for (JSTreeContext *tc = this->tc; tc; tc = tc->parent)
+        tc->trace(trc);
 }
 
 static void
 UnlinkFunctionBoxes(JSParseNode *pn, JSTreeContext *tc);
 
 static void
 UnlinkFunctionBox(JSParseNode *pn, JSTreeContext *tc)
 {
@@ -1309,23 +1318,20 @@ CheckStrictBinding(JSContext *cx, JSTree
  * constraints to apply to the argument list, we can't report the error until
  * after we've parsed the body. And as it turns out, the function's local name
  * list makes it reasonably cheap to find duplicates after the fact.
  */
 static bool
 CheckStrictFormals(JSContext *cx, JSTreeContext *tc, JSFunction *fun,
                    JSParseNode *pn)
 {
-    JSAtom *atom;
-
     if (!tc->needStrictChecks())
         return true;
 
-    atom = fun->findDuplicateFormal();
-    if (atom) {
+    if (JSAtom *atom = tc->bindings.findDuplicateArgument()) {
         /*
          * We have found a duplicate parameter name. If we can find the
          * JSDefinition for the argument, that will have a more accurate source
          * location.
          */
         JSDefinition *dn = ALE_DEFN(tc->decls.lookup(atom));
         if (dn->pn_op == JSOP_GETARG)
             pn = dn;
@@ -1334,17 +1340,19 @@ CheckStrictFormals(JSContext *cx, JSTree
             !ReportStrictModeError(cx, TS(tc->parser), tc, pn, JSMSG_DUPLICATE_FORMAL,
                                    name.ptr())) {
             return false;
         }
     }
 
     if (tc->flags & (TCF_FUN_PARAM_ARGUMENTS | TCF_FUN_PARAM_EVAL)) {
         JSAtomState *atoms = &cx->runtime->atomState;
-        atom = (tc->flags & TCF_FUN_PARAM_ARGUMENTS) ? atoms->argumentsAtom : atoms->evalAtom;
+        JSAtom *atom = (tc->flags & TCF_FUN_PARAM_ARGUMENTS)
+                       ? atoms->argumentsAtom
+                       : atoms->evalAtom;
 
         /* The definition's source position will be more precise. */
         JSDefinition *dn = ALE_DEFN(tc->decls.lookup(atom));
         JS_ASSERT(dn->pn_atom == atom);
         JSAutoByteString name;
         if (!js_AtomToPrintableString(cx, atom, &name) ||
             !ReportStrictModeError(cx, TS(tc->parser), tc, dn, JSMSG_BAD_BINDING, name.ptr())) {
             return false;
@@ -1638,17 +1646,17 @@ DefineArg(JSParseNode *pn, JSAtom *atom,
 }
 
 /*
  * Compile a JS function body, which might appear as the value of an event
  * handler attribute in an HTML <INPUT> tag.
  */
 bool
 Compiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals,
-                              const jschar *chars, size_t length,
+                              Bindings *bindings, const jschar *chars, size_t length,
                               const char *filename, uintN lineno)
 {
     Compiler compiler(cx, principals);
 
     if (!compiler.init(chars, length, filename, lineno))
         return false;
 
     /* No early return from after here until the js_FinishArenaPool calls. */
@@ -1662,33 +1670,35 @@ Compiler::compileFunctionBody(JSContext 
     TokenStream &tokenStream = parser.tokenStream;
 
     JSCodeGenerator funcg(&parser, &codePool, &notePool, tokenStream.getLineno());
     if (!funcg.init())
         return NULL;
 
     funcg.flags |= TCF_IN_FUNCTION;
     funcg.setFunction(fun);
+    funcg.bindings.transfer(cx, bindings);
+    fun->setArgCount(funcg.bindings.countArgs());
     if (!GenerateBlockId(&funcg, funcg.bodyid))
         return NULL;
 
     /* FIXME: make Function format the source for a function definition. */
     tokenStream.mungeCurrentToken(TOK_NAME);
     JSParseNode *fn = FunctionNode::create(&funcg);
     if (fn) {
         fn->pn_body = NULL;
         fn->pn_cookie.makeFree();
 
         uintN nargs = fun->nargs;
         if (nargs) {
             /*
              * NB: do not use AutoLocalNameArray because it will release space
              * allocated from cx->tempPool by DefineArg.
              */
-            jsuword *names = fun->getLocalNameArray(cx, &cx->tempPool);
+            jsuword *names = funcg.bindings.getLocalNameArray(cx, &cx->tempPool);
             if (!names) {
                 fn = NULL;
             } else {
                 for (uintN i = 0; i < nargs; i++) {
                     JSAtom *name = JS_LOCAL_NAME_TO_ATOM(names[i]);
                     if (!DefineArg(fn, name, i, &funcg)) {
                         fn = NULL;
                         break;
@@ -1757,50 +1767,53 @@ struct BindData {
     union {
         struct {
             uintN   overflow;
         } let;
     };
     bool fresh;
 };
 
-static JSBool
-BindLocalVariable(JSContext *cx, JSFunction *fun, JSAtom *atom,
-                  JSLocalKind localKind, bool isArg)
-{
-    JS_ASSERT(localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST);
+static bool
+BindLocalVariable(JSContext *cx, JSTreeContext *tc, JSAtom *atom, BindingKind kind, bool isArg)
+{
+    JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
 
     /*
      * Don't bind a variable with the hidden name 'arguments', per ECMA-262.
      * Instead 'var arguments' always restates the predefined property of the
      * activation objects whose name is 'arguments'. Assignment to such a
      * variable must be handled specially.
      *
      * Special case: an argument named 'arguments' *does* shadow the predefined
      * arguments property.
      */
     if (atom == cx->runtime->atomState.argumentsAtom && !isArg)
-        return JS_TRUE;
-
-    return fun->addLocal(cx, atom, localKind);
+        return true;
+
+    return tc->bindings.add(cx, atom, kind);
 }
 
 #if JS_HAS_DESTRUCTURING
 static JSBool
-BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom,
-                     JSTreeContext *tc)
+BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
 {
     /* Flag tc so we don't have to lookup arguments on every use. */
     if (atom == tc->parser->context->runtime->atomState.argumentsAtom)
         tc->flags |= TCF_FUN_PARAM_ARGUMENTS;
     if (atom == tc->parser->context->runtime->atomState.evalAtom)
         tc->flags |= TCF_FUN_PARAM_EVAL;
 
     JS_ASSERT(tc->inFunction());
 
+    /*
+     * NB: Check tc->decls rather than tc->bindings, because destructuring
+     *     bindings aren't added to tc->bindings until after all arguments have
+     *     been parsed.
+     */
     if (tc->decls.lookup(atom)) {
         ReportCompileErrorNumber(cx, TS(tc->parser), NULL, JSREPORT_ERROR,
                                  JSMSG_DESTRUCT_DUP_ARG);
         return JS_FALSE;
     }
 
     JSParseNode *pn = data->pn;
 
@@ -2720,31 +2733,32 @@ LeaveFunction(JSParseNode *fn, JSTreeCon
             JSDefinition *dn = ALE_DEFN(ale);
             if (dn->kind() == JSDefinition::ARG && dn->isAssigned()) {
                 funbox->tcflags |= TCF_FUN_MUTATES_PARAMETER;
                 break;
             }
         }
     }
 
+    funbox->bindings.transfer(funtc->parser->context, &funtc->bindings);
+
     return true;
 }
 
 static bool
 DefineGlobal(JSParseNode *pn, JSCodeGenerator *cg, JSAtom *atom);
 
 /*
  * FIXME? this Parser method was factored from Parser::functionDef with minimal
- * change, hence the funtc ref param, funbox, and fun. It probably should match
- * functionBody, etc., and use tc, tc->funbox, and tc->fun() instead of taking
- * explicit parameters.
+ * change, hence the funtc ref param and funbox. It probably should match
+ * functionBody, etc., and use tc and tc->funbox instead of taking explicit
+ * parameters.
  */
 bool
-Parser::functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSFunction *fun,
-                          JSParseNode **listp)
+Parser::functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSParseNode **listp)
 {
     if (tokenStream.getToken() != TOK_LP) {
         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL);
         return false;
     }
 
     if (!tokenStream.matchToken(TOK_RP)) {
 #if JS_HAS_DESTRUCTURING
@@ -2777,18 +2791,18 @@ Parser::functionArguments(JSTreeContext 
                 JSParseNode *lhs = destructuringExpr(&data, tt);
                 if (!lhs)
                     return false;
 
                 /*
                  * Adjust fun->nargs to count the single anonymous positional
                  * parameter that is to be destructured.
                  */
-                uintN slot = fun->nargs;
-                if (!fun->addLocal(context, NULL, JSLOCAL_ARG))
+                uint16 slot;
+                if (!funtc.bindings.addDestructuring(context, &slot))
                     return false;
 
                 /*
                  * Synthesize a destructuring assignment from the single
                  * anonymous positional parameter into the destructuring
                  * left-hand-side expression and accumulate it in list.
                  */
                 JSParseNode *rhs = NameNode::create(context->runtime->atomState.emptyAtom, &funtc);
@@ -2817,36 +2831,42 @@ Parser::functionArguments(JSTreeContext 
 #endif /* JS_HAS_DESTRUCTURING */
 
               case TOK_NAME:
               {
                 JSAtom *atom = tokenStream.currentToken().t_atom;
 
 #ifdef JS_HAS_DESTRUCTURING
                 /*
-                 * ECMA-262 requires us to support duplicate parameter names, but if the
-                 * parameter list includes destructuring, we consider the code to have
-                 * "opted in" to higher standards, and forbid duplicates. We may see a
-                 * destructuring parameter later, so always note duplicates now.
+                 * ECMA-262 requires us to support duplicate parameter names,
+                 * but if the parameter list includes destructuring, we
+                 * consider the code to have "opted in" to higher standards and
+                 * forbid duplicates. We may see a destructuring parameter
+                 * later, so always note duplicates now.
                  *
-                 * Duplicates are warned about (strict option) or cause errors (strict
-                 * mode code), but we do those tests in one place below, after having
-                 * parsed the body in case it begins with a "use strict"; directive.
+                 * Duplicates are warned about (strict option) or cause errors
+                 * (strict mode code), but we do those tests in one place
+                 * below, after having parsed the body in case it begins with a
+                 * "use strict"; directive.
+                 *
+                 * NB: Check funtc.decls rather than funtc.bindings, because
+                 *     destructuring bindings aren't added to funtc.bindings
+                 *     until after all arguments have been parsed.
                  */
                 if (funtc.decls.lookup(atom)) {
                     duplicatedArg = atom;
                     if (destructuringArg)
                         goto report_dup_and_destructuring;
                 }
 #endif
 
-                if (!DefineArg(funbox->node, atom, fun->nargs, &funtc))
+                uint16 slot;
+                if (!funtc.bindings.addArgument(context, atom, &slot))
                     return false;
-
-                if (!fun->addLocal(context, atom, JSLOCAL_ARG))
+                if (!DefineArg(funbox->node, atom, slot, &funtc))
                     return false;
                 break;
               }
 
               default:
                 reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_MISSING_FORMAL);
                 /* FALL THROUGH */
               case TOK_ERROR:
@@ -2966,25 +2986,25 @@ Parser::functionDef(JSAtom *funAtom, Fun
             /*
              * Define a local in the outer function so that BindNameToSlot
              * can properly optimize accesses. Note that we need a local
              * variable, not an argument, for the function statement. Thus
              * we add a variable even if a parameter with the given name
              * already exists.
              */
             uintN index;
-            switch (tc->fun()->lookupLocal(context, funAtom, &index)) {
-              case JSLOCAL_NONE:
-              case JSLOCAL_ARG:
-                index = tc->fun()->u.i.nvars;
-                if (!tc->fun()->addLocal(context, funAtom, JSLOCAL_VAR))
+            switch (tc->bindings.lookup(funAtom, &index)) {
+              case NONE:
+              case ARGUMENT:
+                index = tc->bindings.countVars();
+                if (!tc->bindings.addVariable(context, funAtom))
                     return NULL;
                 /* FALL THROUGH */
 
-              case JSLOCAL_VAR:
+              case VARIABLE:
                 pn->pn_cookie.set(tc->staticLevel, index);
                 pn->pn_dflags |= PND_BOUND;
                 break;
 
               default:;
             }
         }
     }
@@ -2997,40 +3017,42 @@ Parser::functionDef(JSAtom *funAtom, Fun
     JSFunctionBox *funbox = EnterFunction(pn, &funtc, funAtom, lambda);
     if (!funbox)
         return NULL;
 
     JSFunction *fun = (JSFunction *) funbox->object;
 
     /* Now parse formal argument list and compute fun->nargs. */
     JSParseNode *prelude = NULL;
-    if (!functionArguments(funtc, funbox, fun, &prelude))
+    if (!functionArguments(funtc, funbox, &prelude))
         return NULL;
 
+    fun->setArgCount(funtc.bindings.countArgs());
+
 #if JS_HAS_DESTRUCTURING
     /*
      * If there were destructuring formal parameters, bind the destructured-to
      * local variables now that we've parsed all the regular and destructuring
-     * formal parameters. Because JSFunction::addLocal must be called first for
-     * all ARGs, then all VARs, finally all UPVARs, we can't bind vars induced
-     * by formal parameter destructuring until after Parser::functionArguments
-     * has returned.
+     * formal parameters. Because js::Bindings::add must be called first for
+     * all ARGUMENTs, then all VARIABLEs and CONSTANTs, and finally all UPVARs,
+     * we can't bind vars induced by formal parameter destructuring until after
+     * Parser::functionArguments has returned.
      */
     if (prelude) {
         JSAtomListIterator iter(&funtc.decls);
 
         while (JSAtomListElement *ale = iter()) {
             JSParseNode *apn = ALE_DEFN(ale);
 
             /* Filter based on pn_op -- see BindDestructuringArg, above. */
             if (apn->pn_op != JSOP_SETLOCAL)
                 continue;
 
-            uintN index = fun->u.i.nvars;
-            if (!BindLocalVariable(context, fun, apn->pn_atom, JSLOCAL_VAR, true))
+            uint16 index = funtc.bindings.countVars();
+            if (!BindLocalVariable(context, &funtc, apn->pn_atom, VARIABLE, true))
                 return NULL;
             apn->pn_cookie.set(funtc.staticLevel, index);
         }
     }
 #endif
 
     if (type == GETTER && fun->nargs > 0) {
         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ACCESSOR_WRONG_ARGS,
@@ -3797,43 +3819,43 @@ BindVarOrConst(JSContext *cx, BindData *
         return BindGvar(pn, tc);
 
     if (atom == cx->runtime->atomState.argumentsAtom) {
         pn->pn_op = JSOP_ARGUMENTS;
         pn->pn_dflags |= PND_BOUND;
         return JS_TRUE;
     }
 
-    JSLocalKind localKind = tc->fun()->lookupLocal(cx, atom, NULL);
-    if (localKind == JSLOCAL_NONE) {
+    BindingKind kind = tc->bindings.lookup(atom, NULL);
+    if (kind == NONE) {
         /*
          * Property not found in current variable scope: we have not seen this
          * variable before. Define a new local variable by adding a property to
          * the function's scope and allocating one slot in the function's vars
          * frame. Any locals declared in a with statement body are handled at
          * runtime, by script prolog JSOP_DEFVAR opcodes generated for global
          * and heavyweight-function-local vars.
          */
-        localKind = (data->op == JSOP_DEFCONST) ? JSLOCAL_CONST : JSLOCAL_VAR;
-
-        uintN index = tc->fun()->u.i.nvars;
-        if (!BindLocalVariable(cx, tc->fun(), atom, localKind, false))
+        kind = (data->op == JSOP_DEFCONST) ? CONSTANT : VARIABLE;
+
+        uintN index = tc->bindings.countVars();
+        if (!BindLocalVariable(cx, tc, atom, kind, false))
             return JS_FALSE;
         pn->pn_op = JSOP_GETLOCAL;
         pn->pn_cookie.set(tc->staticLevel, index);
         pn->pn_dflags |= PND_BOUND;
         return JS_TRUE;
     }
 
-    if (localKind == JSLOCAL_ARG) {
+    if (kind == ARGUMENT) {
         /* We checked errors and strict warnings earlier -- see above. */
         JS_ASSERT(ale && ALE_DEFN(ale)->kind() == JSDefinition::ARG);
     } else {
         /* Not an argument, must be a redeclared local var. */
-        JS_ASSERT(localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST);
+        JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
     }
     return JS_TRUE;
 }
 
 static bool
 MakeSetCall(JSContext *cx, JSParseNode *pn, JSTreeContext *tc, uintN msg)
 {
     JS_ASSERT(pn->pn_arity == PN_LIST);
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -927,33 +927,35 @@ JSParseNode::setFunArg()
         pn_lexdef->pn_dflags |= PND_FUNARG;
     pn_dflags |= PND_FUNARG;
 }
 
 struct JSObjectBox {
     JSObjectBox         *traceLink;
     JSObjectBox         *emitLink;
     JSObject            *object;
+    JSObjectBox         *parent;
     uintN               index;
-    JSObjectBox         *parent;
+    bool                isFunctionBox;
 };
 
 #define JSFB_LEVEL_BITS 14
 
 struct JSFunctionBox : public JSObjectBox
 {
     JSParseNode         *node;
     JSFunctionBox       *siblings;
     JSFunctionBox       *kids;
     JSFunctionBox       *parent;
     JSParseNode         *methods;               /* would-be methods set on this;
                                                    these nodes are linked via
                                                    pn_link, since lambdas are
                                                    neither definitions nor uses
                                                    of a binding */
+    js::Bindings        bindings;               /* bindings for this function */
     uint32              queued:1,
                         inLoop:1,               /* in a loop in parent function */
                         level:JSFB_LEVEL_BITS;
     uint32              tcflags;
 
     bool joinable() const;
 
     /*
@@ -1133,18 +1135,17 @@ private:
     JSParseNode *parenExpr(JSParseNode *pn1, JSBool *genexp);
 
     /*
      * Additional JS parsers.
      */
     bool recognizeDirectivePrologue(JSParseNode *pn, bool *isDirectivePrologueMember);
 
     enum FunctionType { GETTER, SETTER, GENERAL };
-    bool functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSFunction *fun,
-                           JSParseNode **list);
+    bool functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSParseNode **list);
     JSParseNode *functionBody();
     JSParseNode *functionDef(JSAtom *name, FunctionType type, uintN lambda);
 
     JSParseNode *condition();
     JSParseNode *comprehensionTail(JSParseNode *kid, uintN blockid,
                                    js::TokenKind type = js::TOK_SEMI, JSOp op = JSOP_NOP);
     JSParseNode *generatorExpr(JSParseNode *pn, JSParseNode *kid);
     JSBool argumentList(JSParseNode *listNode);
@@ -1194,17 +1195,17 @@ struct Compiler
     init(const jschar *base, size_t length,
          const char *filename, uintN lineno)
     {
         return parser.init(base, length, filename, lineno);
     }
 
     static bool
     compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals,
-                        const jschar *chars, size_t length,
+                        js::Bindings *bindings, const jschar *chars, size_t length,
                         const char *filename, uintN lineno);
 
     static JSScript *
     compileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame,
                   JSPrincipals *principals, uint32 tcflags,
                   const jschar *chars, size_t length,
                   const char *filename, uintN lineno,
                   JSString *source = NULL,
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -60,16 +60,18 @@
 #include "jsbool.h"
 #include "jsval.h"
 #include "jsvalue.h"
 #include "jsobjinlines.h"
 #include "jsobj.h"
 #include "jsarray.h"
 #include "jsnum.h"
 
+#include "jsscriptinlines.h"
+
 using namespace js;
 
 namespace js {
 
 char const *aopNames[] = {
     "=",    /* AOP_ASSIGN */
     "+=",   /* AOP_PLUS */
     "-=",   /* AOP_MINUS */
--- a/js/src/jsscan.cpp
+++ b/js/src/jsscan.cpp
@@ -67,16 +67,18 @@
 #include "jsopcode.h"
 #include "jsparse.h"
 #include "jsregexp.h"
 #include "jsscan.h"
 #include "jsscript.h"
 #include "jsstaticcheck.h"
 #include "jsvector.h"
 
+#include "jsscriptinlines.h"
+
 #if JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
 using namespace js;
 
 #define JS_KEYWORD(keyword, type, op, version) \
     const char js_##keyword##_str[] = #keyword;
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -290,16 +290,17 @@ CastAsPropertyOp(js::Class *clasp)
  */
 #define JSPROP_SHADOWABLE       JSPROP_INDEX
 
 struct Shape : public JSObjectMap
 {
     friend struct ::JSObject;
     friend struct ::JSFunction;
     friend class js::PropertyTree;
+    friend class js::Bindings;
     friend bool HasUnreachableGCThings(TreeFragment *f);
 
   protected:
     mutable js::PropertyTable *table;
 
   public:
     inline void freeTable(JSContext *cx);
 
@@ -489,17 +490,17 @@ struct Shape : public JSObjectMap
          * Set during a shape-regenerating GC if the shape has already been
          * regenerated.
          */
         SHAPE_REGEN     = 0x04,
 
         /* Property stored in per-object dictionary, not shared property tree. */
         IN_DICTIONARY   = 0x08,
 
-        /* Prevent unwanted mutation of shared JSFunction::u.i.names nodes. */
+        /* Prevent unwanted mutation of shared Bindings::lastBinding nodes. */
         FROZEN          = 0x10
     };
 
     Shape(jsid id, js::PropertyOp getter, js::PropertyOp setter, uint32 slot, uintN attrs,
           uintN flags, intN shortid, uint32 shape = INVALID_SHAPE, uint32 slotSpan = 0);
 
     /* Used by EmptyShape (see jsscopeinlines.h). */
     Shape(JSContext *cx, Class *aclasp);
@@ -874,16 +875,16 @@ Shape::search(js::Shape **startp, jsid i
 #undef METER
 
 inline bool
 Shape::isSharedPermanent() const
 {
     return (~attrs & (JSPROP_SHARED | JSPROP_PERMANENT)) == 0;
 }
 
-}
+} // namespace js
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 
 #endif /* jsscope_h___ */
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -68,54 +68,300 @@
 
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
+namespace js {
+
+BindingKind
+Bindings::lookup(JSAtom *name, uintN *indexp) const
+{
+    JS_ASSERT(lastBinding);
+
+    Shape *shape =
+        SHAPE_FETCH(Shape::search(const_cast<Shape **>(&lastBinding), ATOM_TO_JSID(name)));
+    if (!shape)
+        return NONE;
+
+    if (indexp)
+        *indexp = shape->shortid;
+
+    if (shape->getter() == GetCallArg)
+        return ARGUMENT;
+    if (shape->getter() == GetFlatUpvar)
+        return UPVAR;
+
+    return shape->writable() ? VARIABLE : CONSTANT;
+}
+
+bool
+Bindings::add(JSContext *cx, JSAtom *name, BindingKind kind)
+{
+    JS_ASSERT(lastBinding);
+
+    /*
+     * 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.
+     */
+    uintN attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED;
+
+    uint16 *indexp;
+    PropertyOp getter, setter;
+    uint32 slot = JSObject::CALL_RESERVED_SLOTS;
+
+    if (kind == ARGUMENT) {
+        JS_ASSERT(nvars == 0);
+        JS_ASSERT(nupvars == 0);
+        indexp = &nargs;
+        getter = GetCallArg;
+        setter = SetCallArg;
+        slot += nargs;
+    } else if (kind == UPVAR) {
+        indexp = &nupvars;
+        getter = GetFlatUpvar;
+        setter = SetFlatUpvar;
+        slot = SHAPE_INVALID_SLOT;
+    } else {
+        JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
+        JS_ASSERT(nupvars == 0);
+
+        indexp = &nvars;
+        getter = GetCallVar;
+        setter = SetCallVar;
+        if (kind == CONSTANT)
+            attrs |= JSPROP_READONLY;
+        slot += nargs + nvars;
+    }
+
+    if (*indexp == JS_BITMASK(16)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             (kind == ARGUMENT)
+                             ? JSMSG_TOO_MANY_FUN_ARGS
+                             : JSMSG_TOO_MANY_LOCALS);
+        return false;
+    }
+
+    jsid id;
+    if (!name) {
+        JS_ASSERT(kind == ARGUMENT); /* destructuring */
+        id = INT_TO_JSID(nargs);
+    } else {
+        id = ATOM_TO_JSID(name);
+    }
+
+    Shape child(id, getter, setter, slot, attrs, Shape::HAS_SHORTID, *indexp);
+
+    Shape *shape = lastBinding->getChild(cx, child, &lastBinding);
+    if (!shape)
+        return false;
+
+    JS_ASSERT(lastBinding == shape);
+    ++*indexp;
+    return true;
+}
+
+/*
+ * This algorithm is O(n^2)! But this method is only called if the function is
+ * strict mode code or if JSOPTION_STRICT is set, so for now we'll tolerate the
+ * quadratic blowup.
+ */
+JSAtom *
+Bindings::findDuplicateArgument() const
+{
+    JS_ASSERT(lastBinding);
+
+    if (nargs <= 1)
+        return NULL;
+
+    for (Shape::Range r = lastArgument(); !r.empty(); r.popFront()) {
+        const Shape &shape = r.front();
+        for (Shape::Range r2 = shape.previous(); !r2.empty(); r2.popFront()) {
+            if (r2.front().id == shape.id)
+                return JSID_TO_ATOM(shape.id);
+        }
+    }
+
+    return NULL;
+}
+
+
+jsuword *
+Bindings::getLocalNameArray(JSContext *cx, JSArenaPool *pool)
+{
+   JS_ASSERT(lastBinding);
+
+   JS_ASSERT(hasLocalNames());
+
+    uintN n = countLocalNames();
+    jsuword *names;
+
+    JS_ASSERT(SIZE_MAX / size_t(n) > sizeof *names);
+    JS_ARENA_ALLOCATE_CAST(names, jsuword *, pool, size_t(n) * sizeof *names);
+    if (!names) {
+        js_ReportOutOfScriptQuota(cx);
+        return NULL;
+    }
+
+#ifdef DEBUG
+    for (uintN i = 0; i != n; i++)
+        names[i] = 0xdeadbeef;
+#endif
+
+    for (Shape::Range r = lastBinding; !r.empty(); r.popFront()) {
+        const Shape &shape = r.front();
+        uintN index = uint16(shape.shortid);
+        jsuword constFlag = 0;
+
+        if (shape.getter() == GetCallArg) {
+            JS_ASSERT(index < nargs);
+        } else if (shape.getter() == GetFlatUpvar) {
+            JS_ASSERT(index < nupvars);
+            index += nargs + nvars;
+        } else {
+            JS_ASSERT(index < nvars);
+            index += nargs;
+            if (!shape.writable())
+                constFlag = 1;
+        }
+
+        JSAtom *atom;
+        if (JSID_IS_ATOM(shape.id)) {
+            atom = JSID_TO_ATOM(shape.id);
+        } else {
+            JS_ASSERT(JSID_IS_INT(shape.id));
+            JS_ASSERT(shape.getter() == GetCallArg);
+            atom = NULL;
+        }
+
+        names[index] = jsuword(atom);
+    }
+
+#ifdef DEBUG
+    for (uintN i = 0; i != n; i++)
+        JS_ASSERT(names[i] != 0xdeadbeef);
+#endif
+    return names;
+}
+
+const Shape *
+Bindings::lastArgument() const
+{
+    JS_ASSERT(lastBinding);
+
+    const js::Shape *shape = lastVariable();
+    if (nvars > 0) {
+        while (shape->previous() && shape->getter() != GetCallArg)
+            shape = shape->previous();
+    }
+    return shape;
+}
+
+const Shape *
+Bindings::lastVariable() const
+{
+    JS_ASSERT(lastBinding);
+
+    const js::Shape *shape = lastUpvar();
+    if (nupvars > 0) {
+        while (shape->getter() == GetFlatUpvar)
+            shape = shape->previous();
+    }
+    return shape;
+}
+
+const Shape *
+Bindings::lastUpvar() const
+{
+    JS_ASSERT(lastBinding);
+    return lastBinding;
+}
+
+int
+Bindings::sharpSlotBase(JSContext *cx)
+{
+    JS_ASSERT(lastBinding);
+#if JS_HAS_SHARP_VARS
+    if (JSAtom *name = js_Atomize(cx, "#array", 6, 0)) {
+        uintN index = uintN(-1);
+#ifdef DEBUG
+        BindingKind kind =
+#endif
+            lookup(name, &index);
+        JS_ASSERT(kind == VARIABLE);
+        return int(index);
+    }
+#endif
+    return -1;
+}
+
+void
+Bindings::makeImmutable()
+{
+    JS_ASSERT(lastBinding);
+    Shape *shape = lastBinding;
+    if (shape->inDictionary()) {
+        do {
+            JS_ASSERT(!shape->frozen());
+            shape->setFrozen();
+        } while ((shape = shape->parent) != NULL);
+    }
+}
+
+void
+Bindings::trace(JSTracer *trc)
+{
+    for (const Shape *shape = lastBinding; shape; shape = shape->previous())
+        shape->trace(trc);
+}
+
+} // namespace js
+
 #if JS_HAS_XDR
 
 enum ScriptBits {
     NoScriptRval,
     SavedCallerFun,
     HasSharps,
     StrictModeCode,
     UsesEval,
     UsesArguments
 };
 
 JSBool
 js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *hasMagic)
 {
-    JSContext *cx;
-    JSScript *script, *oldscript;
+    JSScript *oldscript;
     JSBool ok;
     jsbytecode *code;
-    uint32 length, lineno, nslots, magic;
-    uint32 natoms, nsrcnotes, ntrynotes, nobjects, nupvars, nregexps, nconsts, i;
+    uint32 length, lineno, nslots;
+    uint32 natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, i;
     uint32 prologLength, version, encodedClosedCount;
     uint16 nClosedArgs = 0, nClosedVars = 0;
     JSPrincipals *principals;
     uint32 encodeable;
     JSBool filenameWasSaved;
-    jssrcnote *notes, *sn;
+    jssrcnote *sn;
     JSSecurityCallbacks *callbacks;
     uint32 scriptBits = 0;
 
-    cx = xdr->cx;
-    script = *scriptp;
-    nsrcnotes = ntrynotes = natoms = nobjects = nupvars = nregexps = nconsts = 0;
+    JSContext *cx = xdr->cx;
+    JSScript *script = *scriptp;
+    nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = 0;
     filenameWasSaved = JS_FALSE;
-    notes = NULL;
+    jssrcnote *notes = NULL;
 
     /* Should not XDR scripts optimized for a single global object. */
     JS_ASSERT_IF(script, !JSScript::isValidOffset(script->globalsOffset));
 
+    uint32 magic;
     if (xdr->mode == JSXDR_ENCODE)
         magic = JSXDR_MAGIC_SCRIPT_CURRENT;
     if (!JS_XDRUint32(xdr, &magic))
         return JS_FALSE;
     if (magic != JSXDR_MAGIC_SCRIPT_CURRENT) {
         /* We do not provide binary compatibility with older scripts. */
         if (!hasMagic) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
@@ -123,16 +369,126 @@ js_XDRScript(JSXDRState *xdr, JSScript *
             return JS_FALSE;
         }
         *hasMagic = JS_FALSE;
         return JS_TRUE;
     }
     if (hasMagic)
         *hasMagic = JS_TRUE;
 
+    /* XDR arguments, local vars, and upvars. */
+    uint16 nargs, nvars, nupvars;
+    uint32 argsVars, paddingUpvars;
+    if (xdr->mode == JSXDR_ENCODE) {
+        nargs = script->bindings.countArgs();
+        nvars = script->bindings.countVars();
+        nupvars = script->bindings.countUpvars();
+        argsVars = (nargs << 16) | nvars;
+        paddingUpvars = nupvars;
+    }
+    if (!JS_XDRUint32(xdr, &argsVars) || !JS_XDRUint32(xdr, &paddingUpvars))
+        return false;
+    if (xdr->mode == JSXDR_DECODE) {
+        nargs = argsVars >> 16;
+        nvars = argsVars & 0xFFFF;
+        JS_ASSERT((paddingUpvars >> 16) == 0);
+        nupvars = paddingUpvars & 0xFFFF;
+    }
+
+    Bindings bindings(cx);
+    uint32 nameCount = nargs + nvars + nupvars;
+    if (nameCount > 0) {
+        struct AutoMark {
+          JSArenaPool * const pool;
+          void * const mark;
+          AutoMark(JSArenaPool *pool) : pool(pool), mark(JS_ARENA_MARK(pool)) { }
+          ~AutoMark() {
+            JS_ARENA_RELEASE(pool, mark);
+          }
+        } automark(&cx->tempPool);
+
+        /*
+         * To xdr the names we prefix the names with a bitmap descriptor and
+         * then xdr the names as strings. For argument names (indexes below
+         * nargs) the corresponding bit in the bitmap is unset when the name
+         * is null. Such null names are not encoded or decoded. For variable
+         * names (indexes starting from nargs) bitmap's bit is set when the
+         * name is declared as const, not as ordinary var.
+         * */
+        uintN bitmapLength = JS_HOWMANY(nameCount, JS_BITS_PER_UINT32);
+        uint32 *bitmap;
+        JS_ARENA_ALLOCATE_CAST(bitmap, uint32 *, &cx->tempPool,
+                               bitmapLength * sizeof *bitmap);
+        if (!bitmap) {
+            js_ReportOutOfScriptQuota(cx);
+            return false;
+        }
+
+        jsuword *names;
+        if (xdr->mode == JSXDR_ENCODE) {
+            names = script->bindings.getLocalNameArray(cx, &cx->tempPool);
+            if (!names)
+                return false;
+            PodZero(bitmap, bitmapLength);
+            for (uintN i = 0; i < nameCount; i++) {
+                if (i < nargs
+                    ? JS_LOCAL_NAME_TO_ATOM(names[i]) != NULL
+                    : JS_LOCAL_NAME_IS_CONST(names[i]))
+                {
+                    bitmap[i >> JS_BITS_PER_UINT32_LOG2] |= JS_BIT(i & (JS_BITS_PER_UINT32 - 1));
+                }
+            }
+        }
+#ifdef __GNUC__
+        else {
+            names = NULL;   /* quell GCC uninitialized warning */
+        }
+#endif
+        for (uintN i = 0; i < bitmapLength; ++i) {
+            if (!JS_XDRUint32(xdr, &bitmap[i]))
+                return false;
+        }
+
+        for (uintN i = 0; i < nameCount; i++) {
+            if (i < nargs &&
+                !(bitmap[i >> JS_BITS_PER_UINT32_LOG2] & JS_BIT(i & (JS_BITS_PER_UINT32 - 1))))
+            {
+                if (xdr->mode == JSXDR_DECODE) {
+                    uint16 dummy;
+                    if (!bindings.addDestructuring(cx, &dummy))
+                        return false;
+                } else {
+                    JS_ASSERT(!JS_LOCAL_NAME_TO_ATOM(names[i]));
+                }
+                continue;
+            }
+
+            JSAtom *name;
+            if (xdr->mode == JSXDR_ENCODE)
+                name = JS_LOCAL_NAME_TO_ATOM(names[i]);
+            if (!js_XDRAtom(xdr, &name))
+                return false;
+            if (xdr->mode == JSXDR_DECODE) {
+                BindingKind kind = (i < nargs)
+                                   ? ARGUMENT
+                                   : (i < nargs + nvars)
+                                   ? (bitmap[i >> JS_BITS_PER_UINT32_LOG2] &
+                                      JS_BIT(i & (JS_BITS_PER_UINT32 - 1))
+                                      ? CONSTANT
+                                      : VARIABLE)
+                                   : UPVAR;
+                if (!bindings.add(cx, name, kind))
+                    return false;
+            }
+        }
+
+        if (xdr->mode == JSXDR_DECODE)
+            bindings.makeImmutable();
+    }
+
     if (xdr->mode == JSXDR_ENCODE)
         length = script->length;
     if (!JS_XDRUint32(xdr, &length))
         return JS_FALSE;
 
     if (xdr->mode == JSXDR_ENCODE) {
         prologLength = script->main - script->code;
         JS_ASSERT(script->getVersion() != JSVERSION_UNKNOWN);
@@ -147,17 +503,17 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn))
             continue;
         nsrcnotes = sn - notes;
         nsrcnotes++;            /* room for the terminator */
 
         if (JSScript::isValidOffset(script->objectsOffset))
             nobjects = script->objects()->length;
         if (JSScript::isValidOffset(script->upvarsOffset))
-            nupvars = script->upvars()->length;
+            JS_ASSERT(script->bindings.countUpvars() == script->upvars()->length);
         if (JSScript::isValidOffset(script->regexpsOffset))
             nregexps = script->regexps()->length;
         if (JSScript::isValidOffset(script->trynotesOffset))
             ntrynotes = script->trynotes()->length;
         if (JSScript::isValidOffset(script->constOffset))
             nconsts = script->consts()->length;
 
         nClosedArgs = script->nClosedArgs;
@@ -181,29 +537,27 @@ js_XDRScript(JSXDRState *xdr, JSScript *
     }
 
     if (!JS_XDRUint32(xdr, &prologLength))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &version))
         return JS_FALSE;
 
     /*
-     * To fuse allocations, we need srcnote, atom, objects, upvar, regexp,
-     * and trynote counts early.
+     * To fuse allocations, we need srcnote, atom, objects, regexp, and trynote
+     * counts early.
      */
     if (!JS_XDRUint32(xdr, &natoms))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &nsrcnotes))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &ntrynotes))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &nobjects))
         return JS_FALSE;
-    if (!JS_XDRUint32(xdr, &nupvars))
-        return JS_FALSE;
     if (!JS_XDRUint32(xdr, &nregexps))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &nconsts))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &encodedClosedCount))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &scriptBits))
         return JS_FALSE;
@@ -215,16 +569,18 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         nClosedVars = encodedClosedCount & 0xFFFF;
 
         script = JSScript::NewScript(cx, length, nsrcnotes, natoms, nobjects, nupvars,
                                      nregexps, ntrynotes, nconsts, 0, nClosedArgs,
                                      nClosedVars);
         if (!script)
             return JS_FALSE;
 
+        script->bindings.transfer(cx, &bindings);
+
         script->main += prologLength;
         script->setVersion(JSVersion(version & 0xffff));
         script->nfixed = uint16(version >> 16);
 
         /* If we know nsrcnotes, we allocated space for notes in script. */
         notes = script->notes();
         *scriptp = script;
         tvr.setScript(script);
@@ -889,20 +1245,21 @@ JSScript::NewScript(JSContext *cx, uint3
 
     script = (JSScript *) cx->malloc(size);
     if (!script)
         return NULL;
 
     PodZero(script);
     script->length = length;
     script->setVersion(cx->findVersion());
+    new (&script->bindings) Bindings(cx);
 
     uint8 *scriptEnd = reinterpret_cast<uint8 *>(script + 1);
 
-    cursor = (uint8 *)script + sizeof(JSScript);
+    cursor = scriptEnd;
     if (nobjects != 0) {
         script->objectsOffset = (uint8)(cursor - scriptEnd);
         cursor += sizeof(JSObjectArray);
     } else {
         script->objectsOffset = JSScript::INVALID_OFFSET;
     }
     if (nupvars != 0) {
         script->upvarsOffset = (uint8)(cursor - scriptEnd);
@@ -923,17 +1280,17 @@ JSScript::NewScript(JSContext *cx, uint3
         script->trynotesOffset = JSScript::INVALID_OFFSET;
     }
     if (nglobals != 0) {
         script->globalsOffset = (uint8)(cursor - scriptEnd);
         cursor += sizeof(GlobalSlotArray);
     } else {
         script->globalsOffset = JSScript::INVALID_OFFSET;
     }
-    JS_ASSERT((cursor - (uint8 *)script) < 0xFF);
+    JS_ASSERT(cursor - scriptEnd < 0xFF);
     if (nconsts != 0) {
         script->constOffset = (uint8)(cursor - scriptEnd);
         cursor += sizeof(JSConstArray);
     } else {
         script->constOffset = JSScript::INVALID_OFFSET;
     }
 
     JS_STATIC_ASSERT(sizeof(JSObjectArray) +
@@ -1062,17 +1419,17 @@ JSScript::NewScriptFromCG(JSContext *cx,
     if (!script)
         return NULL;
 
     /* Now that we have script, error control flow must go to label bad. */
     script->main += prologLength;
     memcpy(script->code, CG_PROLOG_BASE(cg), prologLength * sizeof(jsbytecode));
     memcpy(script->main, CG_BASE(cg), mainLength * sizeof(jsbytecode));
     nfixed = cg->inFunction()
-             ? cg->fun()->u.i.nvars
+             ? cg->bindings.countVars()
              : cg->sharpSlots();
     JS_ASSERT(nfixed < SLOTNO_LIMIT);
     script->nfixed = (uint16) nfixed;
     js_InitAtomMap(cx, &script->atomMap, &cg->atomList);
 
     filename = cg->parser->tokenStream.getFilename();
     if (filename) {
         script->filename = js_SaveScriptFilename(cx, filename);
@@ -1131,30 +1488,34 @@ JSScript::NewScriptFromCG(JSContext *cx,
 
     if (script->nClosedArgs)
         memcpy(script->closedSlots, &cg->closedArgs[0], script->nClosedArgs * sizeof(uint32));
     if (script->nClosedVars) {
         memcpy(&script->closedSlots[script->nClosedArgs], &cg->closedVars[0],
                script->nClosedVars * sizeof(uint32));
     }
 
+    cg->bindings.makeImmutable();
+    script->bindings.transfer(cx, &cg->bindings);
+
     /*
      * We initialize fun->u.script to be the script constructed above
      * so that the debugger has a valid FUN_SCRIPT(fun).
      */
     fun = NULL;
     if (cg->inFunction()) {
         fun = cg->fun();
-        JS_ASSERT(FUN_INTERPRETED(fun) && !FUN_SCRIPT(fun));
+        JS_ASSERT(fun->isInterpreted());
+        JS_ASSERT(!fun->script());
+#ifdef DEBUG
         if (JSScript::isValidOffset(script->upvarsOffset))
-            JS_ASSERT(script->upvars()->length == fun->u.i.nupvars);
+            JS_ASSERT(script->upvars()->length == script->bindings.countUpvars());
         else
-            fun->u.i.nupvars = 0;
-
-        fun->freezeLocalNames(cx);
+            JS_ASSERT(script->bindings.countUpvars() == 0);
+#endif
         fun->u.i.script = script;
 #ifdef CHECK_SCRIPT_OWNER
         script->owner = NULL;
 #endif
         if (cg->flags & TCF_FUN_HEAVYWEIGHT)
             fun->flags |= JSFUN_HEAVYWEIGHT;
     }
 
@@ -1338,16 +1699,18 @@ js_TraceScript(JSTracer *trc, JSScript *
 
     if (script->u.object) {
         JS_SET_TRACING_NAME(trc, "object");
         Mark(trc, script->u.object);
     }
 
     if (IS_GC_MARKING_TRACER(trc) && script->filename)
         js_MarkScriptFilename(script->filename);
+
+    script->bindings.trace(trc);
 }
 
 JSBool
 js_NewScriptObject(JSContext *cx, JSScript *script)
 {
     AutoScriptRooter root(cx, script);
 
     JS_ASSERT(!script->u.object);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -140,27 +140,185 @@ typedef struct JSUpvarArray {
     uint32          length;     /* count of indexed upvar cookies */
 } JSUpvarArray;
 
 typedef struct JSConstArray {
     js::Value       *vector;    /* array of indexed constant values */
     uint32          length;
 } JSConstArray;
 
+struct JSArenaPool;
+
 namespace js {
 
 struct GlobalSlotArray {
     struct Entry {
         uint32      atomIndex;  /* index into atom table */
         uint32      slot;       /* global obj slot number */
     };
     Entry           *vector;
     uint32          length;
 };
 
+class Shape;
+
+enum BindingKind { NONE, ARGUMENT, VARIABLE, CONSTANT, UPVAR };
+
+/*
+ * Formal parameters, local variables, and upvars 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 {
+    js::Shape *lastBinding;
+    uint16 nargs;
+    uint16 nvars;
+    uint16 nupvars;
+
+  public:
+    inline Bindings(JSContext *cx);
+
+    /*
+     * 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.
+     */
+    inline void transfer(JSContext *cx, Bindings *bindings);
+
+    /*
+     * Clones bindings data from bindings, which must be immutable, into this
+     * fresh Bindings instance. A Bindings instance may be cloned multiple
+     * times.
+     */
+    inline void clone(JSContext *cx, Bindings *bindings);
+
+    uint16 countArgs() const { return nargs; }
+    uint16 countVars() const { return nvars; }
+    uint16 countUpvars() const { return nupvars; }
+
+    uintN countArgsAndVars() const { return nargs + nvars; }
+
+    uintN countLocalNames() const { return nargs + nvars + nupvars; }
+
+    bool hasUpvars() const { return nupvars > 0; }
+    bool hasLocalNames() const { return countLocalNames() > 0; }
+
+    /* Returns the shape lineage generated for these bindings. */
+    inline const js::Shape *lastShape() const;
+
+    /*
+     * 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 addLocal. All ARGUMENT bindings must be added before
+     * before any VARIABLE or CONSTANT bindings, which themselves must be added
+     * before all UPVAR bindings.
+     */
+    bool add(JSContext *cx, JSAtom *name, BindingKind kind);
+
+    /* Convenience specializations. */
+    bool addVariable(JSContext *cx, JSAtom *name) {
+        return add(cx, name, VARIABLE);
+    }
+    bool addConstant(JSContext *cx, JSAtom *name) {
+        return add(cx, name, CONSTANT);
+    }
+    bool addUpvar(JSContext *cx, JSAtom *name) {
+        return add(cx, name, UPVAR);
+    }
+    bool addArgument(JSContext *cx, JSAtom *name, uint16 *slotp) {
+        JS_ASSERT(name != NULL); /* not destructuring */
+        *slotp = nargs;
+        return add(cx, name, ARGUMENT);
+    }
+    bool addDestructuring(JSContext *cx, uint16 *slotp) {
+        *slotp = nargs;
+        return add(cx, NULL, ARGUMENT);
+    }
+
+    /*
+     * 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.
+     */
+    BindingKind lookup(JSAtom *name, uintN *indexp) const;
+
+    /* Convenience method to check for any binding for a name. */
+    bool hasBinding(JSAtom *name) const {
+        return lookup(name, NULL) != NONE;
+    }
+
+    /*
+     * If this binding set for the given function includes duplicated argument
+     * names, return an arbitrary duplicate name.  Otherwise, return NULL.
+     */
+    JSAtom *findDuplicateArgument() const;
+
+    /*
+     * Function and macros to work with local names as an array of words.
+     * getLocalNameArray returns the array, or null if we are out of memory.
+     * This function must be called only when hasLocalNames().
+     *
+     * The supplied pool is used to allocate the returned array, so the caller
+     * is obligated to mark and release to free it.
+     *
+     * The elements of the array with index less than nargs correspond to the
+     * the names of arguments. An index >= nargs addresses a var binding. Use
+     * JS_LOCAL_NAME_TO_ATOM to convert array's element to an atom pointer.
+     * This pointer can be null when the element is for an argument
+     * corresponding to a destructuring pattern.
+     *
+     * If nameWord does not name an argument, use JS_LOCAL_NAME_IS_CONST to
+     * check if nameWord corresponds to the const declaration.
+     */
+    jsuword *
+    getLocalNameArray(JSContext *cx, JSArenaPool *pool);
+
+    /*
+     * Returns the slot where the sharp array is stored, or a value < 0 if no
+     * sharps are present or in case of failure.
+     */
+    int sharpSlotBase(JSContext *cx);
+
+    /*
+     * Protect stored bindings from mutation.  Subsequent attempts to add
+     * bindings will copy the existing bindings before adding to them, allowing
+     * the original bindings to be safely shared.
+     */
+    void makeImmutable();
+
+    /*
+     * These methods provide direct access to the shape path normally
+     * encapsulated by js::Bindings. These methods may be used to make a
+     * Shape::Range for iterating over the relevant shapes from youngest to
+     * oldest (i.e., last or right-most to first or left-most in source order).
+     *
+     * Sometimes iteration order must be from oldest to youngest, however. For
+     * such cases, use js::Bindings::getLocalNameArray. The RAII class
+     * js::AutoLocalNameArray, defined in jscntxt.h, should be used where
+     * possible instead of direct calls to getLocalNameArray.
+     */
+    const js::Shape *lastArgument() const;
+    const js::Shape *lastVariable() const;
+    const js::Shape *lastUpvar() const;
+
+    void trace(JSTracer *trc);
+};
+
 } /* namespace js */
 
 #define JS_OBJECT_ARRAY_SIZE(length)                                          \
     (offsetof(JSObjectArray, vector) + sizeof(JSObject *) * (length))
 
 #if defined DEBUG && defined JS_THREADSAFE
 # define CHECK_SCRIPT_OWNER 1
 #endif
@@ -250,16 +408,18 @@ struct JSScript {
     JSAtomMap       atomMap;    /* maps immediate index to literal struct */
     JSCompartment   *compartment; /* compartment the script was compiled for */
     const char      *filename;  /* source filename or null */
     uint32          lineno;     /* base line number of script */
     uint16          nslots;     /* vars plus maximum stack depth */
     uint16          staticLevel;/* static level for display maintenance */
     uint16          nClosedArgs; /* number of args which are closed over. */
     uint16          nClosedVars; /* number of vars which are closed over. */
+    js::Bindings    bindings;   /* names of top-level variables in this script
+                                   (and arguments if this is a function script) */
     JSPrincipals    *principals;/* principals for this script */
     union {
         /*
          * A script object of class js_ScriptClass, to ensure the script is GC'd.
          * - All scripts returned by JSAPI functions (JS_CompileScript,
          *   JS_CompileFile, etc.) have these objects.
          * - Function scripts never have script objects; such scripts are owned
          *   by their function objects.
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -36,20 +36,69 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsscriptinlines_h___
 #define jsscriptinlines_h___
 
+#include "jscntxt.h"
 #include "jsfun.h"
 #include "jsopcode.h"
 #include "jsregexp.h"
 #include "jsscript.h"
+#include "jsscope.h"
+
+namespace js {
+
+inline
+Bindings::Bindings(JSContext *cx)
+  : lastBinding(cx->runtime->emptyCallShape), nargs(0), nvars(0), nupvars(0)
+{
+}
+
+inline void
+Bindings::transfer(JSContext *cx, Bindings *bindings)
+{
+    JS_ASSERT(lastBinding == cx->runtime->emptyCallShape);
+
+    *this = *bindings;
+#ifdef DEBUG
+    bindings->lastBinding = NULL;
+#endif
+
+    /* Preserve back-pointer invariants across the lastBinding transfer. */
+    if (lastBinding->inDictionary())
+        lastBinding->listp = &this->lastBinding;
+}
+
+inline void
+Bindings::clone(JSContext *cx, Bindings *bindings)
+{
+    JS_ASSERT(lastBinding == cx->runtime->emptyCallShape);
+
+    /*
+     * Non-dictionary bindings are fine to share, as are dictionary bindings if
+     * they're copy-on-modification.
+     */
+    JS_ASSERT(!bindings->lastBinding->inDictionary() || bindings->lastBinding->frozen());
+
+    *this = *bindings;
+}
+
+const Shape *
+Bindings::lastShape() const
+{
+    JS_ASSERT(lastBinding);
+    JS_ASSERT_IF(lastBinding->inDictionary(), lastBinding->frozen());
+    return lastBinding;
+}
+
+} // namespace js
 
 inline JSFunction *
 JSScript::getFunction(size_t index)
 {
     JSObject *funobj = getObject(index);
     JS_ASSERT(funobj->isFunction());
     JS_ASSERT(funobj == (JSObject *) funobj->getPrivate());
     JSFunction *fun = (JSFunction *) funobj;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -3372,31 +3372,31 @@ GetClosureArg(JSContext* cx, JSObject* c
 {
     return GetFromClosure<ArgClosureTraits>(cx, callee, cv, result);
 }
 
 struct VarClosureTraits
 {
     // See also UpvarVarTraits.
     static inline Value get_slot(JSStackFrame* fp, unsigned slot) {
-        JS_ASSERT(slot < fp->fun()->u.i.nvars);
+        JS_ASSERT(slot < fp->fun()->script()->bindings.countVars());
         return fp->slots()[slot];
     }
 
     static inline Value get_slot(JSObject* obj, unsigned slot) {
         return obj->getSlot(slot_offset(obj) + slot);
     }
 
     static inline uint32 slot_offset(JSObject* obj) {
         return JSObject::CALL_RESERVED_SLOTS +
                obj->getCallObjCalleeFunction()->nargs;
     }
 
     static inline uint16 slot_count(JSObject* obj) {
-        return obj->getCallObjCalleeFunction()->u.i.nvars;
+        return obj->getCallObjCalleeFunction()->script()->bindings.countVars();
     }
 
 private:
     VarClosureTraits();
 };
 
 uint32 JS_FASTCALL
 GetClosureVar(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* result)
@@ -3467,21 +3467,23 @@ TraceRecorder::importImpl(Address addr, 
     char name[64];
     JS_ASSERT(strlen(prefix) < 11);
     void* mark = NULL;
     jsuword* localNames = NULL;
     const char* funName = NULL;
     JSAutoByteString funNameBytes;
     if (*prefix == 'a' || *prefix == 'v') {
         mark = JS_ARENA_MARK(&cx->tempPool);
-        if (fp->fun()->hasLocalNames())
-            localNames = fp->fun()->getLocalNameArray(cx, &cx->tempPool);
-        funName = fp->fun()->atom
-                  ? js_AtomToPrintableString(cx, fp->fun()->atom, &funNameBytes)
-                : "<anonymous>";
+        JSFunction *fun = fp->fun();
+        Bindings &bindings = fun->script()->bindings;
+        if (bindings.hasLocalNames())
+            localNames = bindings.getLocalNameArray(cx, &cx->tempPool);
+        funName = fun->atom
+                  ? js_AtomToPrintableString(cx, fun->atom, &funNameBytes)
+                  : "<anonymous>";
     }
     if (!strcmp(prefix, "argv")) {
         if (index < fp->numFormalArgs()) {
             JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[index]);
             JSAutoByteString atomBytes;
             JS_snprintf(name, sizeof name, "$%s.%s", funName,
                         js_AtomToPrintableString(cx, atom, &atomBytes));
         } else {
@@ -10219,17 +10221,17 @@ TraceRecorder::putActivationObjects()
 
     if (have_args) {
         LIns* argsobj_ins = getFrameObjPtr(fp->addressOfArgs());
         LIns* args[] = { args_ins, argsobj_ins, cx_ins };
         w.call(&js_PutArgumentsOnTrace_ci, args);
     }
 
     if (have_call) {
-        int nslots = fp->fun()->countVars();
+        int nslots = fp->fun()->script()->bindings.countVars();
         LIns* slots_ins;
         if (nslots) {
             slots_ins = w.allocp(sizeof(Value) * nslots);
             for (int i = 0; i < nslots; ++i) {
                 box_value_into(fp->slots()[i], get(&fp->slots()[i]), 
                                AllocSlotsAddress(slots_ins, i));
             }
         } else {
@@ -13323,18 +13325,18 @@ TraceRecorder::guardCallee(Value& callee
      * be traced before the function has returned, and the trace then triggered
      * after, or vice versa. The function must escape, i.e., be a "funarg", or
      * else there's no need to guard callee parent at all. So once we know (by
      * static analysis) that a function may escape, we cannot avoid guarding on
      * either the private data of the Call object or the Call object itself, if
      * we wish to optimize for the particular deactivated stack frame (null
      * private data) case as noted above.
      */
-    if (FUN_INTERPRETED(callee_fun) &&
-        (!FUN_NULL_CLOSURE(callee_fun) || callee_fun->u.i.nupvars != 0)) {
+    if (callee_fun->isInterpreted() &&
+        (!FUN_NULL_CLOSURE(callee_fun) || callee_fun->script()->bindings.hasUpvars())) {
         JSObject* parent = callee_obj.getParent();
 
         if (parent != globalObj) {
             if (!parent->isCall())
                 RETURN_STOP("closure scoped by neither the global object nor a Call object");
 
             guard(true,
                   w.eqp(w.ldpObjParent(callee_ins), w.immpObjGC(parent)),
@@ -15265,24 +15267,25 @@ TraceRecorder::record_JSOP_LAMBDA_FC()
         RETURN_STOP_A("Unable to trace creating lambda in let");
 
     LIns* args[] = { scopeChain(), w.immpFunGC(fun), cx_ins };
     LIns* closure_ins = w.call(&js_AllocFlatClosure_ci, args);
     guard(false,
           w.name(w.eqp(closure_ins, w.immpNull()), "guard(js_AllocFlatClosure)"),
           OOM_EXIT);
 
-    if (fun->u.i.nupvars) {
-        JSUpvarArray *uva = fun->u.i.script->upvars();
+    JSScript *script = fun->script();
+    if (script->bindings.hasUpvars()) {
+        JSUpvarArray *uva = script->upvars();
         LIns* upvars_ins = w.getObjPrivatizedSlot(closure_ins,
                                                   JSObject::JSSLOT_FLAT_CLOSURE_UPVARS);
 
         for (uint32 i = 0, n = uva->length; i < n; i++) {
             Value v;
-            LIns* v_ins = upvar(fun->u.i.script, uva, i, v);
+            LIns* v_ins = upvar(script, uva, i, v);
             if (!v_ins)
                 return ARECORD_STOP;
 
             box_value_into(v, v_ins, FCSlotsAddress(upvars_ins, i));
         }
     }
 
     stack(0, closure_ins);
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -189,28 +189,29 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
 #define JSXDR_MAGIC_SCRIPT_3        0xdead0003
 #define JSXDR_MAGIC_SCRIPT_4        0xdead0004
 #define JSXDR_MAGIC_SCRIPT_5        0xdead0005
 #define JSXDR_MAGIC_SCRIPT_6        0xdead0006
 #define JSXDR_MAGIC_SCRIPT_7        0xdead0007
 #define JSXDR_MAGIC_SCRIPT_8        0xdead0008
 #define JSXDR_MAGIC_SCRIPT_9        0xdead0009
 #define JSXDR_MAGIC_SCRIPT_10       0xdead000a
-#define JSXDR_MAGIC_SCRIPT_CURRENT  JSXDR_MAGIC_SCRIPT_10
+#define JSXDR_MAGIC_SCRIPT_11       0xdead000b
+#define JSXDR_MAGIC_SCRIPT_CURRENT  JSXDR_MAGIC_SCRIPT_11
 
 /*
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number should be XDR'ed once near the front of any file or
  * larger storage unit containing XDR'ed bytecode and other data, and checked
  * before deserialization of bytecode.  If the saved version does not match
  * the current version, abort deserialization and invalidate the file.
  */
-#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 79)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 80)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 JS_END_EXTERN_C
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1737,26 +1737,23 @@ mjit::Compiler::generateMethod()
 
           BEGIN_CASE(JSOP_CONDSWITCH)
             /* No-op for the decompiler. */
           END_CASE(JSOP_CONDSWITCH)
 
           BEGIN_CASE(JSOP_DEFFUN)
           {
             uint32 index = fullAtomIndex(PC);
-            JSFunction *inner = script->getFunction(index);
-
-            if (fun) {
-                JSLocalKind localKind = fun->lookupLocal(cx, inner->atom, NULL);
-                if (localKind != JSLOCAL_NONE)
-                    frame.syncAndForgetEverything();
-            }
+            JSFunction *innerFun = script->getFunction(index);
+
+            if (fun && script->bindings.hasBinding(innerFun->atom))
+                frame.syncAndForgetEverything();
 
             prepareStubCall(Uses(0));
-            masm.move(ImmPtr(inner), Registers::ArgReg1);
+            masm.move(ImmPtr(innerFun), Registers::ArgReg1);
             INLINE_STUBCALL(STRICT_VARIANT(stubs::DefFun));
           }
           END_CASE(JSOP_DEFFUN)
 
           BEGIN_CASE(JSOP_DEFVAR)
           BEGIN_CASE(JSOP_DEFCONST)
           {
             uint32 index = fullAtomIndex(PC);
@@ -1768,21 +1765,18 @@ mjit::Compiler::generateMethod()
           }
           END_CASE(JSOP_DEFVAR)
 
           BEGIN_CASE(JSOP_SETCONST)
           {
             uint32 index = fullAtomIndex(PC);
             JSAtom *atom = script->getAtom(index);
 
-            if (fun) {
-                JSLocalKind localKind = fun->lookupLocal(cx, atom, NULL);
-                if (localKind != JSLOCAL_NONE)
-                    frame.syncAndForgetEverything();
-            }
+            if (fun && script->bindings.hasBinding(atom))
+                frame.syncAndForgetEverything();
 
             prepareStubCall(Uses(1));
             masm.move(ImmPtr(atom), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::SetConst);
           }
           END_CASE(JSOP_SETCONST)
 
           BEGIN_CASE(JSOP_DEFLOCALFUN_FC)
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -39,16 +39,18 @@
  * ***** END LICENSE BLOCK ***** */
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsemit.h"
 #include "jslibmath.h"
 #include "jsnum.h"
 #include "jsscope.h"
 #include "jsobjinlines.h"
+#include "jsscriptinlines.h"
+
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Compiler.h"
 #include "methodjit/StubCalls.h"
 #include "methodjit/FrameState-inl.h"
 
 #include "jsautooplen.h"
 
 using namespace js;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2028,26 +2028,27 @@ DisassembleValue(JSContext *cx, jsval v,
 #undef SHOW_FLAG
 
             if (FUN_INTERPRETED(fun)) {
                 if (FUN_NULL_CLOSURE(fun))
                     fputs(" NULL_CLOSURE", stdout);
                 else if (FUN_FLAT_CLOSURE(fun))
                     fputs(" FLAT_CLOSURE", stdout);
 
-                if (fun->u.i.nupvars) {
+                JSScript *script = fun->script();
+                if (script->bindings.hasUpvars()) {
                     fputs("\nupvars: {\n", stdout);
 
                     void *mark = JS_ARENA_MARK(&cx->tempPool);
-                    jsuword *localNames = fun->getLocalNameArray(cx, &cx->tempPool);
+                    jsuword *localNames = script->bindings.getLocalNameArray(cx, &cx->tempPool);
                     if (!localNames)
                         return false;
 
-                    JSUpvarArray *uva = fun->u.i.script->upvars();
-                    uintN upvar_base = fun->countArgsAndVars();
+                    JSUpvarArray *uva = script->upvars();
+                    uintN upvar_base = script->bindings.countArgsAndVars();
 
                     for (uint32 i = 0, n = uva->length; i < n; i++) {
                         JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[upvar_base + i]);
                         UpvarCookie cookie = uva->vector[i];
                         JSAutoByteString printable;
                         if (js_AtomToPrintableString(cx, atom, &printable)) {
                             printf("  %s: {skip:%u, slot:%u},\n",
                                    printable.ptr(), cookie.level(), cookie.slot());