Bug 411722: faster implementation of js_GetLocalNames. r,a=brendan
authorigor@mir2.org
Tue, 22 Jan 2008 00:21:54 -0800
changeset 10524 5efc3c066a64f2fd9f361b5bd552c3ff408d1eff
parent 10523 73dd384359fb72f9a5b6e17460288407be59a5d5
child 10525 92c1ba245c8062ce094a3f7f1dbd3305b7ac9901
push idunknown
push userunknown
push dateunknown
reviewersbrendan
bugs411722
milestone1.9b3pre
Bug 411722: faster implementation of js_GetLocalNames. r,a=brendan
js/src/jsfun.c
js/src/jsfun.h
js/src/jsopcode.c
js/src/jsxdrapi.h
--- a/js/src/jsfun.c
+++ b/js/src/jsfun.c
@@ -65,16 +65,20 @@
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jsexn.h"
 
 #if JS_HAS_GENERATORS
 # include "jsiter.h"
 #endif
 
+#if JS_HAS_XDR
+# include "jsxdrapi.h"
+#endif
+
 /* Generic function/call/arguments tinyids -- also reflected bit numbers. */
 enum {
     CALL_ARGUMENTS  = -1,       /* predefined arguments local variable */
     ARGS_LENGTH     = -2,       /* number of actual args, arity if inactive */
     ARGS_CALLEE     = -3,       /* reference from arguments to active funobj */
     FUN_ARITY       = -4,       /* number of formal parameters; desired argc */
     FUN_NAME        = -5,       /* function name, "" if anonymous */
     FUN_CALLER      = -6        /* Function.prototype.caller, backward compat */
@@ -744,71 +748,77 @@ js_SetCallVariable(JSContext *cx, JSObje
 
 static JSBool
 call_enumerate(JSContext *cx, JSObject *obj)
 {
     JSStackFrame *fp;
     JSFunction *fun;
     uintN n, i, slot;
     void *mark;
-    JSAtom **names, *name;
+    jsuword *names;
+    JSBool ok;
+    JSAtom *name;
     JSObject *pobj;
     JSProperty *prop;
     jsval v;
 
     fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
     if (!fp)
         return JS_TRUE;
     JS_ASSERT(GET_FUNCTION_PRIVATE(cx, fp->callee) == fp->fun);
 
     /*
      * Reflect actual args from fp->argv for formal parameters, and local vars
      * and functions in fp->vars for declared variables and nested-at-top-level
      * local functions.
      */
     fun = fp->fun;
-    n = fun->nargs + fun->u.i.nvars;
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
     if (n == 0)
         return JS_TRUE;
 
     mark = JS_ARENA_MARK(&cx->tempPool);
-    names = js_GetLocalNames(cx, fun, &cx->tempPool, NULL);
-    if (!names)
+
+    /* From this point the control must flow through the label out. */
+    names = js_GetLocalNameArray(cx, fun, &cx->tempPool);
+    if (!names) {
+        ok = JS_FALSE;
         goto out;
+    }
 
     for (i = 0; i != n; ++i) {
-        name = names[i];
+        name = JS_LOCAL_NAME_TO_ATOM(names[i]);
         if (!name)
             continue;
 
         /*
          * Trigger reflection by looking up the name of the argument or
          * variable.
          */
-        if (!js_LookupProperty(cx, obj, ATOM_TO_JSID(name), &pobj, &prop)) {
-            names = NULL;
+        ok = js_LookupProperty(cx, obj, ATOM_TO_JSID(name), &pobj, &prop);
+        if (!ok)
             goto out;
-        }
 
         /*
          * At this point the call object always has a property corresponding
          * to the local name because call_resolve creates the property using
          * JSPROP_PERMANENT.
          */
         JS_ASSERT(prop && pobj == obj);
         slot = ((JSScopeProperty *) prop)->slot;
         OBJ_DROP_PROPERTY(cx, pobj, prop);
 
         v = (i < fun->nargs) ? fp->argv[i] : fp->vars[i - fun->nargs];
         LOCKED_OBJ_SET_SLOT(obj, slot, v);
     }
+    ok = JS_TRUE;
 
   out:
     JS_ARENA_RELEASE(&cx->tempPool, mark);
-    return names != NULL;
+    return ok;
 }
 
 static JSBool
 call_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
              JSObject **objp)
 {
     JSStackFrame *fp;
     JSString *str;
@@ -1154,24 +1164,27 @@ fun_convert(JSContext *cx, JSObject *obj
         return js_TryValueOf(cx, obj, type, vp);
     }
 }
 
 #if JS_HAS_XDR
 
 #include "jsxdrapi.h"
 
+static JSBool
+XDRLocalNames(JSXDRState *xdr, JSFunction *fun);
+
 /* XXX store parent and proto, if defined */
 static JSBool
 fun_xdrObject(JSXDRState *xdr, JSObject **objp)
 {
     JSContext *cx;
     JSFunction *fun;
     uint32 nullAtom;            /* flag to indicate if fun->atom is NULL */
-    uintN nargs, nvars, n;
+    uintN nargs, nvars;
     uint32 localsword;          /* word to xdr argument and variable counts */
     uint32 flagsword;           /* originally only flags was JS_XDRUint8'd */
     JSTempValueRooter tvr;
     JSBool ok;
 
     cx = xdr->cx;
     if (xdr->mode == JSXDR_ENCODE) {
         fun = GET_FUNCTION_PRIVATE(cx, *objp);
@@ -1213,91 +1226,18 @@ fun_xdrObject(JSXDRState *xdr, JSObject 
     if (xdr->mode == JSXDR_DECODE) {
         nargs = localsword >> 16;
         nvars = localsword & JS_BITMASK(16);
         JS_ASSERT(flagsword | JSFUN_INTERPRETED);
         fun->flags = (uint16) flagsword;
     }
 
     /* do arguments and local vars */
-    if (fun->object && (n = nargs + nvars) != 0) {
-        void *mark;
-        uintN i;
-        uintN bitmapLength;
-        uint32 *bitmap;
-        JSAtom **names, *name;
-        JSLocalKind localKind;
-
-        mark = JS_ARENA_MARK(&xdr->cx->tempPool);
-
-        /* From this point the control must flow through label release_mark. */
-        bitmapLength = JS_HOWMANY(n, JS_BITS_PER_UINT32);
-        if (xdr->mode == JSXDR_ENCODE) {
-            names = js_GetLocalNames(xdr->cx, fun, &xdr->cx->tempPool, &bitmap);
-            if (!names) {
-                ok = JS_FALSE;
-                goto release_mark;
-            }
-        } else {
-#ifdef __GNUC__
-            names = NULL;   /* quell GCC uninitialized warning */
-#endif
-            JS_ARENA_ALLOCATE_CAST(bitmap, uint32 *, &xdr->cx->tempPool,
-                                   bitmapLength * sizeof *bitmap);
-            if (!bitmap) {
-                js_ReportOutOfScriptQuota(xdr->cx);
-                ok = JS_FALSE;
-                goto release_mark;
-            }
-        }
-        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] &
-                  JS_BIT(i & (JS_BITS_PER_UINT32 - 1)))) {
-                if (xdr->mode == JSXDR_DECODE) {
-                    ok = js_AddLocal(xdr->cx, fun, NULL, JSLOCAL_ARG);
-                    if (!ok)
-                        goto release_mark;
-                } else {
-                    JS_ASSERT(!names[i]);
-                }
-                continue;
-            }
-            if (xdr->mode == JSXDR_ENCODE)
-                name = names[i];
-            ok = js_XDRStringAtom(xdr, &name);
-            if (!ok)
-                goto release_mark;
-            if (xdr->mode == JSXDR_DECODE) {
-                localKind = (i < nargs)
-                            ? JSLOCAL_ARG
-                            : bitmap[i / JS_BITS_PER_UINT32] &
-                              JS_BIT(i & (JS_BITS_PER_UINT32 - 1))
-                            ? JSLOCAL_CONST
-                            : JSLOCAL_VAR;
-                ok = js_AddLocal(xdr->cx, fun, name, localKind);
-                if (!ok)
-                    goto release_mark;
-            }
-        }
-        ok = JS_TRUE;
-
-      release_mark:
-        JS_ARENA_RELEASE(&xdr->cx->tempPool, mark);
-        if (!ok)
-            goto out;
-
-        if (xdr->mode == JSXDR_DECODE)
-            js_FreezeLocalNames(cx, fun);
-    }
+    if (!XDRLocalNames(xdr, fun))
+        goto bad;
 
     if (!js_XDRScript(xdr, &fun->u.i.script, NULL))
         goto bad;
 
     if (xdr->mode == JSXDR_DECODE) {
         *objp = fun->object;
         js_CallNewScriptHook(cx, fun->u.i.script, fun);
     }
@@ -2301,17 +2241,17 @@ js_AddLocal(JSContext *cx, JSFunction *f
         indexp = &fun->nargs;
     } else {
         indexp = &fun->u.i.nvars;
         if (kind == JSLOCAL_CONST)
             taggedAtom |= 1;
         else
             JS_ASSERT(kind == JSLOCAL_VAR);
     }
-    n = fun->nargs + fun->u.i.nvars;
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
     if (n == 0) {
         JS_ASSERT(fun->u.i.names.taggedAtom == 0);
         fun->u.i.names.taggedAtom = taggedAtom;
     } else if (n < MAX_ARRAY_LOCALS) {
         if (n > 1) {
             array = fun->u.i.names.array;
         } else {
             array = (jsuword *) JS_malloc(cx, MAX_ARRAY_LOCALS * sizeof *array);
@@ -2394,161 +2334,138 @@ js_AddLocal(JSContext *cx, JSFunction *f
 JSLocalKind
 js_LookupLocal(JSContext *cx, JSFunction *fun, JSAtom *atom, uintN *indexp)
 {
     uintN n, i;
     jsuword *array;
     JSLocalNameHashEntry *entry;
 
     JS_ASSERT(FUN_INTERPRETED(fun));
-    n = fun->nargs + fun->u.i.nvars;
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
     if (n == 0)
         return JSLOCAL_NONE;
     if (n <= MAX_ARRAY_LOCALS) {
         array = (n == 1) ? &fun->u.i.names.taggedAtom : fun->u.i.names.array;
 
         /* Search from the tail to pick up the last duplicated name. */
         i = n;
         do {
             --i;
-            if (atom == (JSAtom *) (array[i] & ~1)) {
+            if (atom == JS_LOCAL_NAME_TO_ATOM(array[i])) {
                 if (i < fun->nargs) {
                     if (indexp)
                         *indexp = i;
                     return JSLOCAL_ARG;
                 }
                 if (indexp)
                     *indexp = i - fun->nargs;
-                return (array[i] & 1) ? JSLOCAL_CONST : JSLOCAL_VAR;
+                return JS_LOCAL_NAME_IS_CONST(array[i])
+                       ? JSLOCAL_CONST
+                       : JSLOCAL_VAR;
             }
         } while (i != 0);
     } else {
         entry = (JSLocalNameHashEntry *)
                 JS_DHashTableOperate(&fun->u.i.names.map->names, atom,
                                      JS_DHASH_LOOKUP);
         if (JS_DHASH_ENTRY_IS_BUSY(&entry->hdr)) {
             JS_ASSERT(entry->localKind != JSLOCAL_NONE);
             if (indexp)
                 *indexp = entry->index;
             return (JSLocalKind) entry->localKind;
         }
     }
     return JSLOCAL_NONE;
 }
 
-typedef struct JSGetLocalNamesArgs {
+typedef struct JSLocalNameEnumeratorArgs {
     JSFunction      *fun;
-    JSAtom          **names;
-    uint32          *bitmap;
+    jsuword         *names;
 #ifdef DEBUG
     uintN           nCopiedArgs;
     uintN           nCopiedVars;
 #endif
-} JSGetLocalNamesArgs;
-
-#define SET_BIT32(bitmap, bit)                                                \
-    ((bitmap)[(bit) >> JS_BITS_PER_UINT32_LOG2] |=                            \
-         JS_BIT((bit) & (JS_BITS_PER_UINT32 - 1)))
+} JSLocalNameEnumeratorArgs;
 
 JS_STATIC_DLL_CALLBACK(JSDHashOperator)
 get_local_names_enumerator(JSDHashTable *table, JSDHashEntryHdr *hdr,
                            uint32 number, void *arg)
 {
     JSLocalNameHashEntry *entry;
-    JSGetLocalNamesArgs *args;
+    JSLocalNameEnumeratorArgs *args;
     uint i;
+    jsuword constFlag;
 
     entry = (JSLocalNameHashEntry *) hdr;
-    args = (JSGetLocalNamesArgs *) arg;
+    args = (JSLocalNameEnumeratorArgs *) arg;
     JS_ASSERT(entry->name);
     if (entry->localKind == JSLOCAL_ARG) {
         JS_ASSERT(entry->index < args->fun->nargs);
         JS_ASSERT(args->nCopiedArgs++ < args->fun->nargs);
         i = entry->index;
+        constFlag = 0;
     } else {
         JS_ASSERT(entry->localKind == JSLOCAL_VAR ||
                   entry->localKind == JSLOCAL_CONST);
         JS_ASSERT(entry->index < args->fun->u.i.nvars);
         JS_ASSERT(args->nCopiedVars++ < args->fun->u.i.nvars);
         i = args->fun->nargs + entry->index;
+        constFlag = (entry->localKind == JSLOCAL_CONST) ? 1 : 0;
     }
-    args->names[i] = entry->name;
-    if (args->bitmap && entry->localKind != JSLOCAL_VAR)
-        SET_BIT32(args->bitmap, i);
+    args->names[i] = (jsuword) entry->name | constFlag;
     return JS_DHASH_NEXT;
 }
 
-JSAtom **
-js_GetLocalNames(JSContext *cx, JSFunction *fun, JSArenaPool *pool,
-                 uint32 **bitmap)
+jsuword *
+js_GetLocalNameArray(JSContext *cx, JSFunction *fun, JSArenaPool *pool)
 {
-    uintN n, i;
-    size_t allocsize;
-    JSAtom **names;
-    jsuword *array;
+    uintN n;
+    jsuword *names;
     JSLocalNameMap *map;
-    JSGetLocalNamesArgs args;
+    JSLocalNameEnumeratorArgs args;
     JSNameIndexPair *dup;
 
     JS_ASSERT(FUN_INTERPRETED(fun));
-    JS_ASSERT(OBJ_IS_NATIVE(fun->object));
-    n = fun->nargs + fun->u.i.nvars;
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
     JS_ASSERT(n != 0);
-    allocsize = n * sizeof *names;
-    if (bitmap)
-        allocsize += JS_HOWMANY(n, JS_BITS_PER_UINT32) * sizeof(uint32);
-    JS_ARENA_ALLOCATE_CAST(names, JSAtom **, pool, allocsize);
+
+    if (n <= MAX_ARRAY_LOCALS)
+        return (n == 1) ? &fun->u.i.names.taggedAtom : fun->u.i.names.array;
+
+    /*
+     * 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;
     }
 
 #if JS_HAS_DESTRUCTURING
     /* Some parameter names can be NULL due to destructuring patterns. */
     memset(names, 0, fun->nargs * sizeof *names);
 #endif
-    if (bitmap) {
-        *bitmap = (uint32 *) (names + n);
-        memset(*bitmap, 0, JS_HOWMANY(n, JS_BITS_PER_UINT32) * sizeof(uint32));
+    map = fun->u.i.names.map;
+    args.fun = fun;
+    args.names = names;
+#ifdef DEBUG
+    args.nCopiedArgs = 0;
+    args.nCopiedVars = 0;
+#endif
+    JS_DHashTableEnumerate(&map->names, get_local_names_enumerator, &args);
+    for (dup = map->lastdup; dup; dup = dup->link) {
+        JS_ASSERT(dup->index < fun->nargs);
+        JS_ASSERT(args.nCopiedArgs++ < fun->nargs);
+        names[dup->index] = (jsuword) dup->name;
     }
-
-    if (n <= MAX_ARRAY_LOCALS) {
-        array = (n == 1) ? &fun->u.i.names.taggedAtom : fun->u.i.names.array;
-
-        i = n;
-        do {
-            --i;
-            names[i] = (JSAtom *) (array[i] & ~1);
-            if (bitmap &&
-                (i < fun->nargs ? array[i] != 0 : array[i] & 1)) {
-                SET_BIT32(*bitmap, i);
-            }
-        } while (i != 0);
-    } else {
-        map = fun->u.i.names.map;
-        args.fun = fun;
-        args.names = names;
-        args.bitmap = bitmap ? *bitmap : NULL;
-#ifdef DEBUG
-        args.nCopiedArgs = 0;
-        args.nCopiedVars = 0;
+#if !JS_HAS_DESTRUCTURING
+    JS_ASSERT(args.nCopiedArgs == fun->nargs);
 #endif
-        JS_DHashTableEnumerate(&map->names, get_local_names_enumerator, &args);
-        for (dup = map->lastdup; dup; dup = dup->link) {
-            JS_ASSERT(dup->index < fun->nargs);
-            JS_ASSERT(args.nCopiedArgs++ < fun->nargs);
-            names[dup->index] = dup->name;
-            if (bitmap)
-                SET_BIT32(*bitmap, dup->index);
-        }
-#if !JS_HAS_DESTRUCTURING
-        JS_ASSERT(args.nCopiedArgs == fun->nargs);
-#endif
-        JS_ASSERT(args.nCopiedVars == fun->u.i.nvars);
-    }
+    JS_ASSERT(args.nCopiedVars == fun->u.i.nvars);
 
     return names;
 }
 
 JS_STATIC_DLL_CALLBACK(JSDHashOperator)
 trace_local_names_enumerator(JSDHashTable *table, JSDHashEntryHdr *hdr,
                              uint32 number, void *arg)
 {
@@ -2568,17 +2485,17 @@ trace_local_names_enumerator(JSDHashTabl
 static void
 TraceLocalNames(JSTracer *trc, JSFunction *fun)
 {
     uintN n, i;
     JSAtom *atom;
     jsuword *array;
 
     JS_ASSERT(FUN_INTERPRETED(fun));
-    n = fun->nargs + fun->u.i.nvars;
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
     if (n == 0)
         return;
     if (n <= MAX_ARRAY_LOCALS) {
         array = (n == 1) ? &fun->u.i.names.taggedAtom : fun->u.i.names.array;
         i = n;
         do {
             --i;
             atom = (JSAtom *) (array[i] & ~1);
@@ -2626,8 +2543,112 @@ js_FreezeLocalNames(JSContext *cx, JSFun
     if (2 <= n && n < MAX_ARRAY_LOCALS) {
         /* Shrink over-allocated array ignoring realloc failures. */
         array = (jsuword *) JS_realloc(cx, fun->u.i.names.array,
                                        n * sizeof *array);
         if (array)
             fun->u.i.names.array = array;
     }
 }
+
+#if JS_HAS_XDR
+
+static JSBool
+XDRLocalNames(JSXDRState *xdr, JSFunction *fun)
+{
+    uintN n, i, jend, j;
+    void *mark;
+    jsuword *names;
+    uint32 bitmap;
+    JSAtom *name;
+    JSBool ok;
+    JSLocalKind localKind;
+
+    JS_ASSERT(FUN_INTERPRETED(fun));
+    n = JS_GET_LOCAL_NAME_COUNT(fun);
+    if (n == 0)
+        return JS_TRUE;
+
+    if (xdr->mode == JSXDR_ENCODE) {
+        mark = JS_ARENA_MARK(&xdr->cx->tempPool);
+        names = js_GetLocalNameArray(xdr->cx, fun, &xdr->cx->tempPool);
+        if (!names)
+            return JS_FALSE;
+    }
+#ifdef __GNUC__
+    else {
+        /* quell GCC uninitialized warning */
+        mark = NULL;
+        names = NULL;
+    }
+#endif
+
+    /*
+     * From this point the control must flow through the label out.
+     *
+     * We xdr the names array in chunks of 32 elements with each chunk
+     * prefixed with a bitmap. For argument names (indexes below fun->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 fun->nargs) bitmap's bit is set when the name is declared
+     * as const, not as ordinary var.
+     */
+    for (i = 0; i != n; ) {
+        JS_ASSERT(i < n && i % JS_BITS_PER_UINT32 == 0);
+        jend = JS_MIN(JS_BITS_PER_UINT32, n - i);
+        if (xdr->mode == JSXDR_ENCODE) {
+            bitmap = 0;
+            for (j = 0; j != jend; ++j, ++i) {
+                JS_ASSERT(i % JS_BITS_PER_UINT32 == j);
+                if (i < fun->nargs
+                    ? JS_LOCAL_NAME_TO_ATOM(names[i]) != NULL
+                    : JS_LOCAL_NAME_IS_CONST(names[i])) {
+                    bitmap |= JS_BIT(j);
+                }
+            }
+            i -= jend;
+        }
+        ok = JS_XDRUint32(xdr, &bitmap);
+        if (!ok)
+            goto out;
+        for (j = 0; j != jend; ++j, ++i) {
+            JS_ASSERT(i % JS_BITS_PER_UINT32 == j);
+            if (i < fun->nargs && (bitmap & JS_BIT(j)) == 0) {
+                /* Parameter is a destructuring pattern. */
+                if (xdr->mode == JSXDR_DECODE) {
+                    ok = js_AddLocal(xdr->cx, fun, NULL, JSLOCAL_ARG);
+                    if (!ok)
+                        goto out;
+                } 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_XDRStringAtom(xdr, &name);
+            if (!ok)
+                goto out;
+            if (xdr->mode == JSXDR_DECODE) {
+                localKind = i < fun->nargs
+                            ? JSLOCAL_ARG
+                            : (bitmap & JS_BIT(j))
+                            ? JSLOCAL_CONST
+                            : JSLOCAL_VAR;
+                ok = js_AddLocal(xdr->cx, fun, name, localKind);
+                if (!ok)
+                    goto out;
+            }
+        }
+    }
+    ok = JS_TRUE;
+
+  out:
+    if (xdr->mode == JSXDR_ENCODE)
+        JS_ARENA_RELEASE(&xdr->cx->tempPool, mark);
+    else if (ok)
+        js_FreezeLocalNames(xdr->cx, fun);
+
+    return ok;
+}
+
+#endif
+
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -201,43 +201,51 @@ js_XDRFunction(JSXDRState *xdr, JSObject
 
 typedef enum JSLocalKind {
     JSLOCAL_NONE,
     JSLOCAL_ARG,
     JSLOCAL_VAR,
     JSLOCAL_CONST
 } JSLocalKind;
 
+#define JS_GET_LOCAL_NAME_COUNT(fun)    ((fun)->nargs + (fun)->u.i.nvars)
+
 extern JSBool
 js_AddLocal(JSContext *cx, JSFunction *fun, 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.
  */
 extern JSLocalKind
 js_LookupLocal(JSContext *cx, JSFunction *fun, JSAtom *atom, uintN *indexp);
 
 /*
- * Get names of arguments and variables for the interpreted function.
+ * Functions to work with local names as an array of words.
  *
- * The result is an array allocated from the given pool with
- *   fun->nargs + fun->u.i.nvars
- * elements with the names of the arguments coming first. The argument
- * name is null when it corresponds to a destructive pattern.
+ * js_GetLocalNameArray returns the array or null when it cannot be allocated
+ * The function must be called only when JS_GET_LOCAL_NAME_COUNT(fun) is not
+ * zero. The function use the supplied pool to allocate the array.
  *
- * When bitmap is not null, on successful return it will contain a bit array
- * where for each index below fun->nargs the bit is set when the corresponding
- * argument name is not null. For indexes greater or equal fun->nargs the bit
- * is set when the corresponding var is really a const.
+ * The elements of the array with index below fun->nargs correspond to the
+ * names of function arguments and of function variables otherwise. Use
+ * JS_LOCAL_NAME_TO_ATOM to convert array's element into an atom. It can be
+ * null when the element is an argument corresponding to a destructuring
+ * pattern. For a variable use JS_LOCAL_NAME_IS_CONST to check if it
+ * corresponds to the const declaration.
  */
-extern JSAtom **
-js_GetLocalNames(JSContext *cx, JSFunction *fun, JSArenaPool *pool,
-                 uint32 **bitmap);
+extern jsuword *
+js_GetLocalNameArray(JSContext *cx, JSFunction *fun, JSArenaPool *pool);
+
+#define JS_LOCAL_NAME_TO_ATOM(nameWord)                                       \
+    ((JSAtom *) ((nameWord) & ~(jsuword) 1))
+
+#define JS_LOCAL_NAME_IS_CONST(nameWord)                                      \
+    ((((nameWord) & (jsuword) 1)) != 0)
 
 extern void
 js_FreezeLocalNames(JSContext *cx, JSFunction *fun);
 
 JS_END_EXTERN_C
 
 #endif /* jsfun_h___ */
--- a/js/src/jsopcode.c
+++ b/js/src/jsopcode.c
@@ -611,17 +611,17 @@ struct JSPrinter {
     Sprinter        sprinter;       /* base class state */
     JSArenaPool     pool;           /* string allocation pool */
     uintN           indent;         /* indentation in spaces */
     JSPackedBool    pretty;         /* pretty-print: indent, use newlines */
     JSPackedBool    grouped;        /* in parenthesized expression context */
     JSScript        *script;        /* script being printed */
     jsbytecode      *dvgfence;      /* js_DecompileValueGenerator fencepost */
     JSFunction      *fun;           /* interpreted function */
-    JSAtom          **localNames;   /* argument and variable names */
+    jsuword         *localNames;    /* argument and variable names */
 };
 
 /*
  * Hack another flag, a la JS_DONT_PRETTY_PRINT, into uintN indent parameters
  * to functions such as js_DecompileFunction and js_NewPrinter.  This time, as
  * opposed to JS_DONT_PRETTY_PRINT back in the dark ages, we can assume that a
  * uintN is at least 32 bits.
  */
@@ -639,20 +639,19 @@ JS_NEW_PRINTER(JSContext *cx, const char
     INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
     JS_INIT_ARENA_POOL(&jp->pool, name, 256, 1, &cx->scriptStackQuota);
     jp->indent = indent & ~JS_IN_GROUP_CONTEXT;
     jp->pretty = pretty;
     jp->grouped = (indent & JS_IN_GROUP_CONTEXT) != 0;
     jp->script = NULL;
     jp->dvgfence = NULL;
     jp->fun = fun;
-    if (!fun || !FUN_INTERPRETED(fun) || fun->nargs + fun->u.i.nvars == 0) {
-        jp->localNames = NULL;
-    } else {
-        jp->localNames = js_GetLocalNames(cx, fun, &jp->pool, NULL);
+    jp->localNames = NULL;
+    if (fun && FUN_INTERPRETED(fun) && JS_GET_LOCAL_NAME_COUNT(fun)) {
+        jp->localNames = js_GetLocalNameArray(cx, fun, &jp->pool);
         if (!jp->localNames) {
             js_DestroyPrinter(jp);
             return NULL;
         }
     }
     return jp;
 }
 
@@ -1098,25 +1097,24 @@ GetSlotAtom(JSPrinter *jp, JSBool argume
     JSFunction *fun;
     JSAtom *name;
 
     fun = jp->fun;
     LOCAL_ASSERT_RV(jp->fun, NULL);
     LOCAL_ASSERT_RV(jp->localNames, NULL);
     if (argument) {
         LOCAL_ASSERT_RV(slot < fun->nargs, NULL);
-        name = jp->localNames[slot];
-#if !JS_HAS_DESTRUCTURING
-        LOCAL_ASSERT_RV(name, NULL);
-#endif
     } else {
         LOCAL_ASSERT_RV(slot < fun->u.i.nvars, NULL);
-        name = jp->localNames[fun->nargs + slot];
-        LOCAL_ASSERT_RV(name, NULL);
+        slot += fun->nargs;
     }
+    name = JS_LOCAL_NAME_TO_ATOM(jp->localNames[slot]);
+#if !JS_HAS_DESTRUCTURING
+    LOCAL_ASSERT_RV(name, NULL);
+#endif
     return name;
 }
 
 const char *
 GetLocal(SprintStack *ss, jsint i)
 {
     ptrdiff_t off;
     JSContext *cx;
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -197,17 +197,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
  * Bytecode version number.  Decrement the second term 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 - 18)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 19)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 extern JSBool