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 id17820
push usercleary@mozilla.com
push dateTue, 04 Jan 2011 21:40:57 +0000
treeherdermozilla-central@969691cfe40e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbrendan
bugs614493
milestone2.0b9pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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());