bug 517199 - Backed out changeset 47619e6bad9a to investigate windows failures
authorIgor Bukanov <igor@mir2.org>
Thu, 01 Oct 2009 09:54:19 +0400
changeset 33574 dcbf332bfaba23d545451fb3ae548b287e7186d7
parent 33573 47619e6bad9aef45e2687a9617b50c4f78b52ad0
child 33575 31682547b6f1367b15f8dc2206310160ea72a3f7
push idunknown
push userunknown
push dateunknown
bugs517199
milestone1.9.3a1pre
bug 517199 - Backed out changeset 47619e6bad9a to investigate windows failures
js/src/jsatom.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsxml.h
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -438,26 +438,29 @@ js_InitAtomState(JSRuntime *rt)
     return JS_TRUE;
 }
 
 static JSDHashOperator
 js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr,
                      uint32 number, void *arg)
 {
     JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
-    JSRuntime *rt = (JSRuntime *) arg;
-    JSString *str = (JSString *) ATOM_ENTRY_KEY(entry);
+    JSRuntime *rt = (JSRuntime *)arg;
+    JSString *str;
 
     /*
      * Any string entry that remains at this point must be initialized, as the
      * last GC should clean any uninitialized ones.
      */
     JS_ASSERT(IS_STRING_TABLE(table));
     JS_ASSERT(entry->keyAndFlags != 0);
-    js_FinalizeStringRT(rt, str);
+    str = (JSString *)ATOM_ENTRY_KEY(entry);
+
+    /* Pass null as context. */
+    js_FinalizeStringRT(rt, str, js_GetExternalStringGCType(str), NULL);
     return JS_DHASH_NEXT;
 }
 
 void
 js_FinishAtomState(JSRuntime *rt)
 {
     JSAtomState *state = &rt->atomState;
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -358,17 +358,17 @@ struct JSThread {
     JSCList             contextList;
 
     /* Opaque thread-id, from NSPR's PR_GetCurrentThread(). */
     jsword              id;
 
     /* Indicates that the thread is waiting in ClaimTitle from jslock.cpp. */
     JSTitle             *titleToShare;
 
-    JSGCThing           *gcFreeLists[FINALIZE_LIMIT];
+    JSGCThing           *gcFreeLists[GC_NUM_FREELISTS];
 
     /* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */
     JSThreadData        data;
 };
 
 #define JS_THREAD_DATA(cx)      (&(cx)->thread->data)
 
 struct JSThreadsHashEntry {
@@ -445,17 +445,17 @@ struct JSRuntime {
      *
      * This comes early in JSRuntime to minimize the immediate format used by
      * trace-JITted code that reads it.
      */
     uint32              protoHazardShape;
 
     /* Garbage collector state, used by jsgc.c. */
     JSGCChunkInfo       *gcChunkList;
-    JSGCArenaList       gcArenaList[FINALIZE_LIMIT];
+    JSGCArenaList       gcArenaList[GC_NUM_FREELISTS];
     JSGCDoubleArenaList gcDoubleArenaList;
     JSDHashTable        gcRootsHash;
     JSDHashTable        *gcLocksHash;
     jsrefcount          gcKeepAtoms;
     size_t              gcBytes;
     size_t              gcLastBytes;
     size_t              gcMaxBytes;
     size_t              gcMaxMallocBytes;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -164,30 +164,31 @@ JS_STATIC_ASSERT(JSTRACE_XML    == 3);
 
 /*
  * JS_IS_VALID_TRACE_KIND assumes that JSTRACE_STRING is the last non-xml
  * trace kind when JS_HAS_XML_SUPPORT is false.
  */
 JS_STATIC_ASSERT(JSTRACE_STRING + 1 == JSTRACE_XML);
 
 /*
+ * The number of used GCX-types must stay within GCX_LIMIT.
+ */
+JS_STATIC_ASSERT(GCX_NTYPES <= GCX_LIMIT);
+
+
+/*
  * Check that we can reinterpret double as JSGCDoubleCell.
  */
 JS_STATIC_ASSERT(sizeof(JSGCDoubleCell) == sizeof(double));
 
 /*
  * Check that we can use memset(p, 0, ...) to implement JS_CLEAR_WEAK_ROOTS.
  */
 JS_STATIC_ASSERT(JSVAL_NULL == 0);
 
-/*
- * Check consistency of external string constants from JSFinalizeGCThingKind.
- */
-JS_STATIC_ASSERT(FINALIZE_EXTERNAL_STRING_LAST - FINALIZE_EXTERNAL_STRING0 ==
-                 JS_EXTERNAL_STRING_LIMIT - 1);
 
 /*
  * A GC arena contains a fixed number of flag bits for each thing in its heap,
  * and supports O(1) lookup of a flag given its thing's address.
  *
  * To implement this, we allocate things of the same size from a GC arena
  * containing GC_ARENA_SIZE bytes aligned on GC_ARENA_SIZE boundary. The
  * following picture shows arena's layout:
@@ -337,29 +338,16 @@ struct JSGCArenaInfo {
         JSBool      hasMarkedDoubles;   /* the arena has marked doubles */
     } u;
 
 #if JS_GC_ARENA_PAD != 0
     uint8           pad[JS_GC_ARENA_PAD];
 #endif
 };
 
-/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */
-const uint8 GCF_MARK   = JS_BIT(0);
-const uint8 GCF_FINAL  = JS_BIT(1);
-const uint8 GCF_LOCK   = JS_BIT(2);    /* lock request bit in API */
-
-/*
- * The private JSGCThing struct, which describes a JSRuntime.gcFreeList element.
- */
-struct JSGCThing {
-    JSGCThing   *next;
-    uint8       *flagp;
-};
-
 /*
  * Verify that the bit fields are indeed shared and JSGCArenaInfo is as small
  * as possible. The code does not rely on this check so if on a particular
  * platform this does not compile, then, as a workaround, comment the assert
  * out and submit a bug report.
  */
 JS_STATIC_ASSERT(offsetof(JSGCArenaInfo, u) == 3 * sizeof(jsuword));
 
@@ -673,16 +661,26 @@ JS_STATIC_ASSERT(sizeof(JSStackHeader) >
 
 JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(JSString));
 JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble));
 
 /* We want to use all the available GC thing space for object's slots. */
 JS_STATIC_ASSERT(sizeof(JSObject) % sizeof(JSGCThing) == 0);
 
 /*
+ * Ensure that JSObject is allocated from a different GC-list rather than
+ * jsdouble and JSString so we can easily finalize JSObject before these 2
+ * types of GC things. See comments in js_GC.
+ */
+JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(JSString)) !=
+                 GC_FREELIST_INDEX(sizeof(JSObject)));
+JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(jsdouble)) !=
+                 GC_FREELIST_INDEX(sizeof(JSObject)));
+
+/*
  * JSPtrTable capacity growth descriptor. The table grows by powers of two
  * starting from capacity JSPtrTableInfo.minCapacity, but switching to linear
  * growth when capacity reaches JSPtrTableInfo.linearGrowthThreshold.
  */
 typedef struct JSPtrTableInfo {
     uint16      minCapacity;
     uint16      linearGrowthThreshold;
 } JSPtrTableInfo;
@@ -1081,89 +1079,42 @@ DestroyGCArenas(JSRuntime *rt, JSGCArena
                     ci->lastFreeArena = a;
                 }
             }
         }
 # endif
     }
 }
 
-static inline size_t
-GetFinalizableThingSize(unsigned thingKind)
-{
-    JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8);
-
-    static const uint8 map[FINALIZE_LIMIT] = {
-        sizeof(JSObject),   /* FINALIZE_OBJECT */
-        sizeof(JSFunction), /* FINALIZE_FUNCTION */
-#if JS_HAS_XML_SUPPORT
-        sizeof(JSXML),      /* FINALIZE_XML */
-#endif
-        sizeof(JSString),   /* FINALIZE_STRING */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING0 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING1 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING2 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING3 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING4 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING5 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING6 */
-        sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING7 */
-    };
-
-    JS_ASSERT(thingKind < FINALIZE_LIMIT);
-    return map[thingKind];
-}
-
-static inline size_t
-GetFinalizableArenaTraceKind(JSGCArenaInfo *a)
-{
-    JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8);
-    JS_ASSERT(a->list);
-
-    static const uint8 map[FINALIZE_LIMIT] = {
-        JSTRACE_OBJECT,     /* FINALIZE_OBJECT */
-        JSTRACE_OBJECT,     /* FINALIZE_FUNCTION */
-#if JS_HAS_XML_SUPPORT      /* FINALIZE_XML */
-        JSTRACE_XML,
-#endif                      /* FINALIZE_STRING */
-        JSTRACE_STRING,
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING0 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING1 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING2 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING3 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING4 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING5 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING6 */
-        JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING7 */
-    };
-
-    JS_ASSERT(a->list->thingKind < FINALIZE_LIMIT);
-    return map[a->list->thingKind];
-}
-
 static void
 InitGCArenaLists(JSRuntime *rt)
 {
-    for (unsigned i = 0; i != FINALIZE_LIMIT; ++i) {
-        JSGCArenaList *arenaList = &rt->gcArenaList[i];
-        arenaList->thingSize = GetFinalizableThingSize(i);
+    uintN i, thingSize;
+    JSGCArenaList *arenaList;
+
+    for (i = 0; i < GC_NUM_FREELISTS; i++) {
+        arenaList = &rt->gcArenaList[i];
+        thingSize = GC_FREELIST_NBYTES(i);
         arenaList->last = NULL;
-        arenaList->lastCount = THINGS_PER_ARENA(arenaList->thingSize);
-        arenaList->thingKind = i;
+        arenaList->lastCount = THINGS_PER_ARENA(thingSize);
+        arenaList->thingSize = thingSize;
         arenaList->freeList = NULL;
     }
     rt->gcDoubleArenaList.first = NULL;
     rt->gcDoubleArenaList.nextDoubleFlags = DOUBLE_BITMAP_SENTINEL;
 }
 
 static void
 FinishGCArenaLists(JSRuntime *rt)
 {
-    for (unsigned i = 0; i < FINALIZE_LIMIT; i++) {
-        JSGCArenaList *arenaList = &rt->gcArenaList[i];
+    uintN i;
+    JSGCArenaList *arenaList;
+
+    for (i = 0; i < GC_NUM_FREELISTS; i++) {
+        arenaList = &rt->gcArenaList[i];
         DestroyGCArenas(rt, arenaList->last);
         arenaList->last = NULL;
         arenaList->lastCount = THINGS_PER_ARENA(arenaList->thingSize);
         arenaList->freeList = NULL;
     }
     DestroyGCArenas(rt, rt->gcDoubleArenaList.first);
     rt->gcDoubleArenaList.first = NULL;
     rt->gcDoubleArenaList.nextDoubleFlags = DOUBLE_BITMAP_SENTINEL;
@@ -1181,51 +1132,83 @@ GetGCThingFlags(void *thing)
     JSGCArenaInfo *a;
     uint32 index;
 
     a = THING_TO_ARENA(thing);
     index = THING_TO_INDEX(thing, a->list->thingSize);
     return THING_FLAGP(a, index);
 }
 
+/*
+ * This function returns null when thing is jsdouble.
+ */
+static uint8 *
+GetGCThingFlagsOrNull(void *thing)
+{
+    JSGCArenaInfo *a;
+    uint32 index;
+
+    if (JSString::isStatic(thing))
+        return NULL;
+    a = THING_TO_ARENA(thing);
+    if (!a->list)
+        return NULL;
+    index = THING_TO_INDEX(thing, a->list->thingSize);
+    return THING_FLAGP(a, index);
+}
+
 intN
 js_GetExternalStringGCType(JSString *str)
 {
-    JS_STATIC_ASSERT(FINALIZE_STRING + 1 == FINALIZE_EXTERNAL_STRING0);
     JS_ASSERT(!JSString::isStatic(str));
 
-    unsigned thingKind = THING_TO_ARENA(str)->list->thingKind;
-    JS_ASSERT(IsFinalizableStringKind(thingKind));
-    return intN(thingKind) - intN(FINALIZE_EXTERNAL_STRING0);
+    uintN type = (uintN) *GetGCThingFlags(str) & GCF_TYPEMASK;
+    JS_ASSERT(type == GCX_STRING || type >= GCX_EXTERNAL_STRING);
+    return (type == GCX_STRING) ? -1 : (intN) (type - GCX_EXTERNAL_STRING);
+}
+
+static uint32
+MapGCFlagsToTraceKind(uintN flags)
+{
+    uint32 type;
+
+    type = flags & GCF_TYPEMASK;
+    JS_ASSERT(type != GCX_DOUBLE);
+    JS_ASSERT(type < GCX_NTYPES);
+    return (type < GCX_EXTERNAL_STRING) ? type : JSTRACE_STRING;
 }
 
 JS_FRIEND_API(uint32)
 js_GetGCThingTraceKind(void *thing)
 {
+    JSGCArenaInfo *a;
+    uint32 index;
+
     if (JSString::isStatic(thing))
         return JSTRACE_STRING;
 
-    JSGCArenaInfo *a = THING_TO_ARENA(thing);
+    a = THING_TO_ARENA(thing);
     if (!a->list)
         return JSTRACE_DOUBLE;
-    return GetFinalizableArenaTraceKind(a);
+
+    index = THING_TO_INDEX(thing, a->list->thingSize);
+    return MapGCFlagsToTraceKind(*THING_FLAGP(a, index));
 }
 
 JSRuntime*
 js_GetGCStringRuntime(JSString *str)
 {
-    JSGCArenaList *list = THING_TO_ARENA(str)->list;
-    JS_ASSERT(list->thingSize == sizeof(JSString));
-
-    unsigned i = list->thingKind;
-    JS_ASSERT(i == FINALIZE_STRING ||
-              (FINALIZE_EXTERNAL_STRING0 <= i &&
-               i < FINALIZE_EXTERNAL_STRING0 + JS_EXTERNAL_STRING_LIMIT));
-    return (JSRuntime *)((uint8 *)(list - i) -
-                         offsetof(JSRuntime, gcArenaList));
+    JSGCArenaList *list;
+
+    list = THING_TO_ARENA(str)->list;
+
+    JS_ASSERT(list->thingSize == sizeof(JSGCThing));
+    JS_ASSERT(GC_FREELIST_INDEX(sizeof(JSGCThing)) == 0);
+
+    return (JSRuntime *)((uint8 *)list - offsetof(JSRuntime, gcArenaList));
 }
 
 JSBool
 js_IsAboutToBeFinalized(JSContext *cx, void *thing)
 {
     JSGCArenaInfo *a;
     uint32 index, flags;
 
@@ -1778,19 +1761,18 @@ IsGCThresholdReached(JSRuntime *rt)
      * zero (see the js_InitGC function) the return value is false when
      * the gcBytes value is close to zero at the JS engine start.
      */
     return rt->gcMallocBytes >= rt->gcMaxMallocBytes ||
            rt->gcBytes >= rt->gcTriggerBytes;
 }
 
 template <typename T, typename NewbornType>
-static inline T*
-NewFinalizableGCThing(JSContext *cx, unsigned thingKind,
-                      NewbornType** newbornRoot)
+static JS_INLINE T*
+NewGCThing(JSContext *cx, uintN flags, NewbornType** newbornRoot)
 {
     JSRuntime *rt;
     bool doGC;
     JSGCThing *thing;
     uint8 *flagp;
     JSGCArenaList *arenaList;
     JSGCArenaInfo *a;
     uintN thingsLimit;
@@ -1802,31 +1784,31 @@ NewFinalizableGCThing(JSContext *cx, uns
     JSBool gcLocked;
     uintN localMallocBytes;
     JSGCThing **lastptr;
     JSGCThing *tmpthing;
     uint8 *tmpflagp;
     uintN maxFreeThings;         /* max to take from the global free list */
 #endif
 
+    JS_ASSERT((flags & GCF_TYPEMASK) != GCX_DOUBLE);
     rt = cx->runtime;
-
     size_t nbytes = sizeof(T);
-    JS_ASSERT(nbytes == GetFinalizableThingSize(thingKind));
     JS_ASSERT(JS_ROUNDUP(nbytes, sizeof(JSGCThing)) == nbytes);
+    uintN flindex = GC_FREELIST_INDEX(nbytes);
 
     /* Updates of metering counters here may not be thread-safe. */
     METER(astats = &cx->runtime->gcStats.arenaStats[flindex]);
     METER(astats->alloc++);
 
 #ifdef JS_THREADSAFE
     gcLocked = JS_FALSE;
     JS_ASSERT(cx->thread);
 
-    JSGCThing *&freeList = cx->thread->gcFreeLists[thingKind];
+    JSGCThing *&freeList = cx->thread->gcFreeLists[flindex];
     thing = freeList;
     localMallocBytes = JS_THREAD_DATA(cx)->gcMallocBytes;
     if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) {
         flagp = thing->flagp;
         freeList = thing->next;
         METER(astats->localalloc++);
         goto success;
     }
@@ -1850,17 +1832,17 @@ NewFinalizableGCThing(JSContext *cx, uns
         return NULL;
     }
 
 #if defined JS_GC_ZEAL && defined JS_TRACER
     if (rt->gcZeal >= 1 && JS_TRACE_MONITOR(cx).useReservedObjects)
         goto testReservedObjects;
 #endif
 
-    arenaList = &rt->gcArenaList[thingKind];
+    arenaList = &rt->gcArenaList[flindex];
     doGC = IsGCThresholdReached(rt);
     for (;;) {
         if (doGC
 #ifdef JS_TRACER
             && !JS_ON_TRACE(cx) && !JS_TRACE_MONITOR(cx).useReservedObjects
 #endif
             ) {
             /*
@@ -2013,21 +1995,21 @@ testReservedObjects:
         /*
          * No local root scope, so we're stuck with the old, fragile model of
          * depending on a pigeon-hole newborn per type per context.
          */
         *newbornRoot = (T *) thing;
     }
 
     /* We can't fail now, so update flags. */
-    *flagp = uint8(0);
+    *flagp = (uint8)flags;
 
 #ifdef DEBUG_gchist
     gchist[gchpos].lastDitch = doGC;
-    gchist[gchpos].freeList = rt->gcArenaList[thingKind].freeList;
+    gchist[gchpos].freeList = rt->gcArenaList[flindex].freeList;
     if (++gchpos == NGCHIST)
         gchpos = 0;
 #endif
 
     /* This is not thread-safe for thread-local allocations. */
     METER_IF(flags & GCF_LOCK, rt->gcStats.lockborn++);
 
 #ifdef JS_THREADSAFE
@@ -2044,48 +2026,44 @@ fail:
     METER(astats->fail++);
     js_ReportOutOfMemory(cx);
     return NULL;
 }
 
 JSObject *
 js_NewGCObject(JSContext *cx)
 {
-    return NewFinalizableGCThing<JSObject>(cx, FINALIZE_OBJECT,
-                                           &cx->weakRoots.newbornObject);
+    return NewGCThing<JSObject>(cx, GCX_OBJECT, &cx->weakRoots.newbornObject);
 }
 
 JSString *
 js_NewGCString(JSContext *cx)
 {
-    return NewFinalizableGCThing<JSString>(cx, FINALIZE_STRING,
-                                           &cx->weakRoots.newbornString);
+    return NewGCThing<JSString>(cx, GCX_STRING, &cx->weakRoots.newbornString);
 }
 
 JSString *
 js_NewGCExternalString(JSContext *cx, uintN type)
 {
     JS_ASSERT(type < JS_EXTERNAL_STRING_LIMIT);
-    return NewFinalizableGCThing<JSString>(cx, FINALIZE_EXTERNAL_STRING0 + type,
-                                           &cx->weakRoots.newbornExternalString[type]);
+    return NewGCThing<JSString>(cx, GCX_EXTERNAL_STRING + type,
+                                &cx->weakRoots.newbornExternalString[type]);
 }
 
 JSFunction *
 js_NewGCFunction(JSContext *cx)
 {
-    return NewFinalizableGCThing<JSFunction>(cx, FINALIZE_FUNCTION,
-                                             &cx->weakRoots.newbornObject);
+    return NewGCThing<JSFunction>(cx, GCX_OBJECT, &cx->weakRoots.newbornObject);
 }
 
 #if JS_HAS_XML_SUPPORT
 JSXML *
 js_NewGCXML(JSContext *cx)
 {
-    return NewFinalizableGCThing<JSXML>(cx,FINALIZE_XML,
-                                        &cx->weakRoots.newbornXML);
+    return NewGCThing<JSXML>(cx, GCX_XML, &cx->weakRoots.newbornXML);
 }
 #endif
 
 static JSGCDoubleCell *
 RefillDoubleFreeList(JSContext *cx)
 {
     JSRuntime *rt;
     jsbitmap *doubleFlags, usedBits;
@@ -2332,57 +2310,48 @@ js_RemoveAsGCBytes(JSRuntime *rt, size_t
     rt->gcBytes -= (uint32) sz;
 }
 
 /*
  * Shallow GC-things can be locked just by setting the GCF_LOCK bit, because
  * they have no descendants to mark during the GC. Currently the optimization
  * is only used for non-dependant strings.
  */
-static uint8 *
-GetShallowGCThingFlag(void *thing)
-{
-    if (JSString::isStatic(thing))
-        return NULL;
-    JSGCArenaInfo *a = THING_TO_ARENA(thing);
-    if (!a->list || !IsFinalizableStringKind(a->list->thingKind))
-        return NULL;
-
-    JS_ASSERT(sizeof(JSString) == a->list->thingSize);
-    JSString *str = (JSString *) thing;
-    if (str->isDependent())
-        return NULL;
-
-    uint32 index = THING_TO_INDEX(thing, sizeof(JSString));
-    return THING_FLAGP(a, index);
-}
+#define GC_THING_IS_SHALLOW(flagp, thing)                                     \
+    ((flagp) &&                                                               \
+     ((*(flagp) & GCF_TYPEMASK) >= GCX_EXTERNAL_STRING ||                     \
+      ((*(flagp) & GCF_TYPEMASK) == GCX_STRING &&                             \
+       !((JSString *) (thing))->isDependent())))
 
 /* This is compatible with JSDHashEntryStub. */
 typedef struct JSGCLockHashEntry {
     JSDHashEntryHdr hdr;
     const void      *thing;
     uint32          count;
 } JSGCLockHashEntry;
 
 JSBool
 js_LockGCThingRT(JSRuntime *rt, void *thing)
 {
+    JSBool shallow, ok;
+    uint8 *flagp;
+    JSGCLockHashEntry *lhe;
+
     if (!thing)
         return JS_TRUE;
 
+    flagp = GetGCThingFlagsOrNull(thing);
     JS_LOCK_GC(rt);
+    shallow = GC_THING_IS_SHALLOW(flagp, thing);
 
     /*
      * Avoid adding a rt->gcLocksHash entry for shallow things until someone
      * nests a lock.
      */
-    uint8 *flagp = GetShallowGCThingFlag(thing);
-    JSBool ok;
-    JSGCLockHashEntry *lhe;
-    if (flagp && !(*flagp & GCF_LOCK)) {
+    if (shallow && !(*flagp & GCF_LOCK)) {
         *flagp |= GCF_LOCK;
         METER(rt->gcStats.lock++);
         ok = JS_TRUE;
         goto out;
     }
 
     if (!rt->gcLocksHash) {
         rt->gcLocksHash = JS_NewDHashTable(JS_DHashGetStubOps(), NULL,
@@ -2413,32 +2382,36 @@ js_LockGCThingRT(JSRuntime *rt, void *th
   out:
     JS_UNLOCK_GC(rt);
     return ok;
 }
 
 JSBool
 js_UnlockGCThingRT(JSRuntime *rt, void *thing)
 {
+    uint8 *flagp;
+    JSBool shallow;
+    JSGCLockHashEntry *lhe;
+
     if (!thing)
         return JS_TRUE;
 
+    flagp = GetGCThingFlagsOrNull(thing);
     JS_LOCK_GC(rt);
-
-    uint8 *flagp = GetShallowGCThingFlag(thing);
-    JSGCLockHashEntry *lhe;
-    if (flagp && !(*flagp & GCF_LOCK))
+    shallow = GC_THING_IS_SHALLOW(flagp, thing);
+
+    if (shallow && !(*flagp & GCF_LOCK))
         goto out;
     if (!rt->gcLocksHash ||
         (lhe = (JSGCLockHashEntry *)
          JS_DHashTableOperate(rt->gcLocksHash, thing,
                               JS_DHASH_LOOKUP),
              JS_DHASH_ENTRY_IS_FREE(&lhe->hdr))) {
         /* Shallow entry is not in the hash -> clear its lock bit. */
-        if (flagp)
+        if (shallow)
             *flagp &= ~GCF_LOCK;
         else
             goto out;
     } else {
         if (--lhe->count != 0)
             goto out;
         JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_REMOVE);
     }
@@ -2545,17 +2518,17 @@ DelayTracingChildren(JSRuntime *rt, uint
     JS_ASSERT(rt->gcUntracedArenaStackTop);
 }
 
 static void
 TraceDelayedChildren(JSTracer *trc)
 {
     JSRuntime *rt;
     JSGCArenaInfo *a, *aprev;
-    uint32 thingSize, traceKind;
+    uint32 thingSize;
     uint32 thingsPerUntracedBit;
     uint32 untracedBitIndex, thingIndex, indexLimit, endIndex;
     JSGCThing *thing;
     uint8 *flagp;
 
     rt = trc->context->runtime;
     a = rt->gcUntracedArenaStackTop;
     if (!a) {
@@ -2567,17 +2540,16 @@ TraceDelayedChildren(JSTracer *trc)
         /*
          * The following assert verifies that the current arena belongs to the
          * untraced stack, since DelayTracingChildren ensures that even for
          * stack's bottom prevUntracedPage != 0 but rather points to itself.
          */
         JS_ASSERT(a->prevUntracedPage != 0);
         JS_ASSERT(rt->gcUntracedArenaStackTop->prevUntracedPage != 0);
         thingSize = a->list->thingSize;
-        traceKind = GetFinalizableArenaTraceKind(a);
         indexLimit = (a == a->list->last)
                      ? a->list->lastCount
                      : THINGS_PER_ARENA(thingSize);
         thingsPerUntracedBit = THINGS_PER_UNTRACED_BIT(thingSize);
 
         /*
          * We cannot use do-while loop here as a->u.untracedThings can be zero
          * before the loop as a leftover from the previous iterations. See
@@ -2606,17 +2578,17 @@ TraceDelayedChildren(JSTracer *trc)
                 if ((*flagp & (GCF_MARK|GCF_FINAL)) != (GCF_MARK|GCF_FINAL))
                     continue;
                 *flagp &= ~GCF_FINAL;
 #ifdef DEBUG
                 JS_ASSERT(rt->gcTraceLaterCount != 0);
                 --rt->gcTraceLaterCount;
 #endif
                 thing = FLAGP_TO_THING(flagp, thingSize);
-                JS_TraceChildren(trc, thing, traceKind);
+                JS_TraceChildren(trc, thing, MapGCFlagsToTraceKind(*flagp));
             } while (++thingIndex != endIndex);
         }
 
         /*
          * We finished tracing of all things in the the arena but we can only
          * pop it from the stack if the arena is the stack's top.
          *
          * When JS_TraceChildren from the above calls JS_CallTracer that in
@@ -2683,34 +2655,33 @@ JS_CallTracer(JSTracer *trc, void *thing
         index = DOUBLE_THING_TO_INDEX(thing);
         JS_SET_BIT(DOUBLE_ARENA_BITMAP(a), index);
         goto out;
 
       case JSTRACE_STRING:
         for (;;) {
             if (JSString::isStatic(thing))
                 goto out;
-            a = THING_TO_ARENA(thing);
-            flagp = THING_FLAGP(a, THING_TO_INDEX(thing, sizeof(JSString)));
+            flagp = THING_TO_FLAGP(thing, sizeof(JSGCThing));
             JS_ASSERT((*flagp & GCF_FINAL) == 0);
-            JS_ASSERT(kind == GetFinalizableArenaTraceKind(a));
+            JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp));
             if (!((JSString *) thing)->isDependent()) {
                 *flagp |= GCF_MARK;
                 goto out;
             }
             if (*flagp & GCF_MARK)
                 goto out;
             *flagp |= GCF_MARK;
             thing = ((JSString *) thing)->dependentBase();
         }
         /* NOTREACHED */
     }
 
-    JS_ASSERT(kind == GetFinalizableArenaTraceKind(THING_TO_ARENA(thing)));
     flagp = GetGCThingFlags(thing);
+    JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp));
     if (*flagp & GCF_MARK)
         goto out;
 
     /*
      * We check for non-final flag only if mark is unset as
      * DelayTracingChildren uses the flag. See comments in the function.
      */
     JS_ASSERT(*flagp != GCF_FINAL);
@@ -2790,36 +2761,43 @@ gc_root_traversal(JSDHashTable *table, J
     jsval *rp = (jsval *)rhe->root;
     jsval v = *rp;
 
     /* Ignore null reference, scalar values, and static strings. */
     if (!JSVAL_IS_NULL(v) &&
         JSVAL_IS_GCTHING(v) &&
         !JSString::isStatic(JSVAL_TO_GCTHING(v))) {
 #ifdef DEBUG
-        bool root_points_to_gcArenaList = false;
+        JSBool root_points_to_gcArenaList = JS_FALSE;
         jsuword thing = (jsuword) JSVAL_TO_GCTHING(v);
-        JSRuntime *rt = trc->context->runtime;
-        for (unsigned i = 0; i != FINALIZE_LIMIT; i++) {
-            JSGCArenaList *arenaList = &rt->gcArenaList[i];
-            size_t thingSize = arenaList->thingSize;
-            size_t limit = arenaList->lastCount * thingSize;
-            for (JSGCArenaInfo *a = arenaList->last; a; a = a->prev) {
+        JSRuntime *rt;
+        uintN i;
+        JSGCArenaList *arenaList;
+        uint32 thingSize;
+        JSGCArenaInfo *a;
+        size_t limit;
+
+        rt = trc->context->runtime;
+        for (i = 0; i < GC_NUM_FREELISTS; i++) {
+            arenaList = &rt->gcArenaList[i];
+            thingSize = arenaList->thingSize;
+            limit = (size_t) arenaList->lastCount * thingSize;
+            for (a = arenaList->last; a; a = a->prev) {
                 if (thing - ARENA_INFO_TO_START(a) < limit) {
-                    root_points_to_gcArenaList = true;
+                    root_points_to_gcArenaList = JS_TRUE;
                     break;
                 }
-                limit = THINGS_PER_ARENA(thingSize) * thingSize;
+                limit = (size_t) THINGS_PER_ARENA(thingSize) * thingSize;
             }
         }
         if (!root_points_to_gcArenaList) {
-            for (JSGCArenaInfo *a = rt->gcDoubleArenaList.first; a; a = a->prev) {
+            for (a = rt->gcDoubleArenaList.first; a; a = a->prev) {
                 if (thing - ARENA_INFO_TO_START(a) <
                     DOUBLES_PER_ARENA * sizeof(jsdouble)) {
-                    root_points_to_gcArenaList = true;
+                    root_points_to_gcArenaList = JS_TRUE;
                     break;
                 }
             }
         }
         if (!root_points_to_gcArenaList && rhe->name) {
             fprintf(stderr,
 "JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n"
 "invalid jsval.  This is usually caused by a missing call to JS_RemoveRoot.\n"
@@ -3086,19 +3064,18 @@ js_TraceContext(JSTracer *trc, JSContext
 
 #ifdef JS_TRACER
 
 static void
 MarkReservedObjects(JSTraceMonitor *tm)
 {
     /* Keep the reserved objects. */
     for (JSObject *obj = tm->reservedObjects; obj; obj = JSVAL_TO_OBJECT(obj->fslots[0])) {
-        JS_ASSERT(js_GetGCThingTraceKind(obj) == JSTRACE_OBJECT);
-
         uint8 *flagp = GetGCThingFlags(obj);
+        JS_ASSERT((*flagp & GCF_TYPEMASK) == GCX_OBJECT);
         JS_ASSERT(*flagp != GCF_FINAL);
         *flagp |= GCF_MARK;
     }
 }
 
 #ifdef JS_THREADSAFE
 static JSDHashOperator
 reserved_objects_marker(JSDHashTable *table, JSDHashEntryHdr *hdr,
@@ -3207,88 +3184,43 @@ js_DestroyScriptsToGC(JSContext *cx, JST
         while ((script = *listp) != NULL) {
             *listp = script->u.nextToGC;
             script->u.nextToGC = NULL;
             js_DestroyScript(cx, script);
         }
     }
 }
 
-static inline void
-FinalizeGCThing(JSContext *cx, JSObject *obj, unsigned thingKind)
+static void
+FinalizeObject(JSContext *cx, JSObject *obj)
 {
-    JS_ASSERT(thingKind == FINALIZE_FUNCTION || thingKind == FINALIZE_OBJECT);
-
-    /*
-     * FIXME use a special statically allocated JSScope for objects with failed
-     * scope allocation to avoid the following map check.
-     */
+    /* Cope with stillborn objects that have no map. */
     if (!obj->map)
         return;
 
-    /*
-     * We finalize obj first, in case it needs map and slots.
-     */
+    if (JS_UNLIKELY(cx->debugHooks->objectHook != NULL)) {
+        cx->debugHooks->objectHook(cx, obj, JS_FALSE,
+                                   cx->debugHooks->objectHookData);
+    }
+
+    /* Finalize obj first, in case it needs map and slots. */
     JSClass *clasp = STOBJ_GET_CLASS(obj);
     if (clasp->finalize)
         clasp->finalize(cx, obj);
 
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_OBJECT_FINALIZE_ENABLED())
         jsdtrace_object_finalize(obj);
 #endif
 
-    /*
-     * FIXME consider allocating dense arrays using separated GC list to
-     * avoid the following check.
-     */
-    if (JS_LIKELY(OBJ_IS_NATIVE(obj))) {
+    if (JS_LIKELY(OBJ_IS_NATIVE(obj)))
         OBJ_SCOPE(obj)->drop(cx, obj);
-        js_FreeSlots(cx, obj);
-    }
-}
-
-static inline void
-FinalizeGCThing(JSContext *cx, JSFunction *fun, unsigned thingKind)
-{
-    JS_ASSERT(thingKind == FINALIZE_FUNCTION);
-    FinalizeGCThing(cx, FUN_OBJECT(fun), thingKind);
+    js_FreeSlots(cx, obj);
 }
 
-/*
- * Hack to do object hook checks only once per object and function lists
- * using specialized finalizers.
- */
-struct JSHookedObject: JSObject { };
-struct JSHookedFunction: JSFunction { };
-
-static inline void
-FinalizeGCThing(JSContext *cx, JSHookedObject *hobj, unsigned thingKind)
-{
-    JSObject *obj = static_cast<JSObject *>(hobj);
-    cx->debugHooks->objectHook(cx, obj, false, cx->debugHooks->objectHookData);
-    FinalizeGCThing(cx, obj, thingKind);
-}
-
-static inline void
-FinalizeGCThing(JSContext *cx, JSHookedFunction *hfun, unsigned thingKind)
-{
-    JSFunction *fun = static_cast<JSFunction *>(hfun);
-    cx->debugHooks->objectHook(cx, fun, false, cx->debugHooks->objectHookData);
-    FinalizeGCThing(cx, fun, thingKind);
-}
-
-#if JS_HAS_XML_SUPPORT
-static inline void
-FinalizeGCThing(JSContext *cx, JSXML *xml, unsigned thingKind)
-{
-    js_FinalizeXML(cx, xml);
-}
-#endif
-
 JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8);
 static JSStringFinalizeOp str_finalizers[JS_EXTERNAL_STRING_LIMIT] = {
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
 };
 
 intN
 js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
                                  JSStringFinalizeOp newop)
@@ -3297,181 +3229,80 @@ js_ChangeExternalStringFinalizer(JSStrin
         if (str_finalizers[i] == oldop) {
             str_finalizers[i] = newop;
             return intN(i);
         }
     }
     return -1;
 }
 
-static inline void
-FinalizeGCThing(JSContext *cx, JSString *str, unsigned thingKind)
+/*
+ * cx is NULL when we are called from js_FinishAtomState to force the
+ * finalization of the permanently interned strings.
+ */
+void
+js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx)
 {
-    JS_ASSERT(thingKind == FINALIZE_STRING);
-    JS_RUNTIME_UNMETER(cx->runtime, liveStrings);
-    JS_ASSERT(!JSString::isStatic(str));
-
-    /*
-     * FIXME consider allocating dependent strings using a separated GC list
-     * to optimize away the following check.
-     */
-    if (str->isDependent()) {
-        JS_ASSERT(str->dependentBase());
-        JS_RUNTIME_UNMETER(cx->runtime, liveDependentStrings);
-    } else {
-        jschar *chars = str->flatChars();
-        cx->free(chars);
-    }
-
-    /*
-     * FIXME consider removing the following deflated check via scanning
-     * the deflated cache for dead strings. This will be a win unless the
-     * cache is big and contains mostly long-lived strings requiring too much
-     * time to spend for continuously scanning marked strings.
-     */
-    if (str->isDeflated())
-        js_PurgeDeflatedStringCache(cx->runtime, str);
-
-}
-
-/* Hack to specialize the finalizer for external strings. */
-struct JSExternalString: JSString { };
-
-static inline void
-FinalizeGCThing(JSContext *cx, JSExternalString *extstr, unsigned thingKind)
-{
-    JS_ASSERT(FINALIZE_EXTERNAL_STRING0 <= thingKind);
-    JS_ASSERT(thingKind <= FINALIZE_EXTERNAL_STRING_LAST);
-    JS_ASSERT(extstr->isFlat());
-
-    JSString *str = static_cast<JSString *>(extstr);
-    unsigned type = thingKind - FINALIZE_EXTERNAL_STRING0;
-
-    JSStringFinalizeOp finalizer = str_finalizers[type];
-    if (finalizer)
-        finalizer(cx, str);
-
-    if (str->isDeflated())
-        js_PurgeDeflatedStringCache(cx->runtime, str);
-}
-
-void
-js_FinalizeStringRT(JSRuntime *rt, JSString *str)
-{
+    jschar *chars;
+    JSBool valid;
+    JSStringFinalizeOp finalizer;
+
     JS_RUNTIME_UNMETER(rt, liveStrings);
     JS_ASSERT(!JSString::isStatic(str));
-
     if (str->isDependent()) {
-        /* A dependent string can not be external. */
-        JS_ASSERT(js_GetExternalStringGCType(str) < 0);
+        /* A dependent string can not be external and must be valid. */
+        JS_ASSERT(type < 0);
         JS_ASSERT(str->dependentBase());
         JS_RUNTIME_UNMETER(rt, liveDependentStrings);
+        valid = JS_TRUE;
     } else {
-        intN type = js_GetExternalStringGCType(str);
-        JSStringFinalizeOp finalizer = str_finalizers[type];
-        if (finalizer) {
-            /*
-             * Assume that the finalizer for the permanently interned
-             * string knows how to deal with null context.
-             */
-            finalizer(NULL, str);
+        /* A stillborn string has null chars, so is not valid. */
+        chars = str->flatChars();
+        valid = (chars != NULL);
+        if (valid) {
+            if (type < 0) {
+                if (cx)
+                    cx->free(chars);
+                else
+                    rt->free(chars);
+            } else {
+                JS_ASSERT((uintN) type < JS_ARRAY_LENGTH(str_finalizers));
+                finalizer = str_finalizers[type];
+                if (finalizer) {
+                    /*
+                     * Assume that the finalizer for the permanently interned
+                     * string knows how to deal with null context.
+                     */
+                    finalizer(cx, str);
+                }
+            }
         }
     }
-    if (str->isDeflated())
+    if (valid && str->isDeflated())
         js_PurgeDeflatedStringCache(rt, str);
 }
 
-template<typename T>
-static void
-FinalizeArenaList(JSContext *cx, unsigned thingKind,
-                  JSGCArenaInfo **emptyArenas)
-{
-    JSGCArenaList *arenaList = &cx->runtime->gcArenaList[thingKind];
-    JS_ASSERT(sizeof(T) == arenaList->thingSize);
-
-    JSGCArenaInfo **ap = &arenaList->last;
-    JSGCArenaInfo *a = *ap;
-    if (!a)
-        return;
-
-    JS_ASSERT(arenaList->lastCount > 0);
-
-    JSGCThing *freeList = NULL;
-    arenaList->freeList = NULL;
-
-    uint32 indexLimit = THINGS_PER_ARENA(sizeof(T));
-    uint8 *flagp = THING_FLAGP(a, arenaList->lastCount - 1);
-
-#ifdef JS_GCMETER
-    uint32 nlivearenas = 0, nkilledarenas = 0, nthings = 0;
-#endif
-    for (;;) {
-        JS_ASSERT(a->prevUntracedPage == 0);
-        JS_ASSERT(a->u.untracedThings == 0);
-
-        bool allClear = true;
-        do {
-            if (*flagp & (GCF_MARK | GCF_LOCK)) {
-                *flagp &= ~GCF_MARK;
-                allClear = false;
-                METER(nthings++);
-            } else {
-                JSGCThing *thing = FLAGP_TO_THING(flagp, sizeof(T));
-                if (!(*flagp & GCF_FINAL)) {
-                    /* Call the finalizer with GCF_FINAL ORed into flags. */
-                    *flagp |= GCF_FINAL;
-                    FinalizeGCThing(cx, (T *) thing, thingKind);
-#ifdef DEBUG
-                    memset(thing, JS_FREE_PATTERN, sizeof(T));
-#endif
-                }
-                thing->flagp = flagp;
-                thing->next = freeList;
-                freeList = thing;
-            }
-        } while (++flagp != THING_FLAGS_END(a));
-
-        if (allClear) {
-            /*
-             * Forget just assembled free list head for the arena and
-             * add the arena itself to the destroy list.
-             */
-            freeList = arenaList->freeList;
-            if (a == arenaList->last)
-                arenaList->lastCount = indexLimit;
-            *ap = a->prev;
-            a->prev = *emptyArenas;
-            *emptyArenas = a;
-            METER(nkilledarenas++);
-        } else {
-            arenaList->freeList = freeList;
-            ap = &a->prev;
-            METER(nlivearenas++);
-        }
-        if (!(a = *ap))
-            break;
-        flagp = THING_FLAGP(a, indexLimit - 1);
-    }
-
-    METER(UpdateArenaStats(&rt->gcStats.arenaStats[thingKind],
-                           nlivearenas, nkilledarenas, nthings));
-}
-
 /*
  * The gckind flag bit GC_LOCK_HELD indicates a call from js_NewGCThing with
  * rt->gcLock already held, so the lock should be kept on return.
  */
 void
 js_GC(JSContext *cx, JSGCInvocationKind gckind)
 {
     JSRuntime *rt;
     JSBool keepAtoms;
     JSGCCallback callback;
+    uintN i, type;
     JSTracer trc;
-    JSGCArenaInfo *emptyArenas, *a, **ap;
+    uint32 thingSize, indexLimit;
+    JSGCArenaInfo *a, **ap, *emptyArenas;
+    uint8 flags, *flagp;
+    JSGCThing *thing, *freeList;
+    JSGCArenaList *arenaList;
+    JSBool allClear;
 #ifdef JS_THREADSAFE
     uint32 requestDebit;
 #endif
 #ifdef JS_GCMETER
     uint32 nlivearenas, nkilledarenas, nthings;
 #endif
 
     JS_ASSERT_IF(gckind == GC_LAST_DITCH, !JS_ON_TRACE(cx));
@@ -3750,40 +3581,117 @@ js_GC(JSContext *cx, JSGCInvocationKind 
     js_SweepWatchPoints(cx);
 
 #ifdef DEBUG
     /* Save the pre-sweep count of scope-mapped properties. */
     rt->liveScopePropsPreSweep = rt->liveScopeProps;
 #endif
 
     /*
-     * We finalize JSObject instances before JSString, double and other GC
-     * things to ensure that object's finalizer can access them even if they
-     * will be freed.
-     *
-     * To minimize the number of checks per each to be freed object and
-     * function we use separated list finalizers when a debug hook is
-     * installed.
+     * Here we need to ensure that JSObject instances are finalized before GC-
+     * allocated JSString and jsdouble instances so object's finalizer can
+     * access them even if they will be freed. For that we simply finalize the
+     * list containing JSObject first since the static assert at the beginning
+     * of the file guarantees that JSString and jsdouble instances are
+     * allocated from a different list.
      */
     emptyArenas = NULL;
-    if (cx->debugHooks->objectHook) {
-        FinalizeArenaList<JSHookedObject>(cx, FINALIZE_OBJECT, &emptyArenas);
-        FinalizeArenaList<JSHookedFunction>(cx, FINALIZE_FUNCTION, &emptyArenas);
-    } else {
-        FinalizeArenaList<JSObject>(cx, FINALIZE_OBJECT, &emptyArenas);
-        FinalizeArenaList<JSFunction>(cx, FINALIZE_FUNCTION, &emptyArenas);
-    }
+    for (i = 0; i < GC_NUM_FREELISTS; i++) {
+        arenaList = &rt->gcArenaList[i == 0
+                                     ? GC_FREELIST_INDEX(sizeof(JSObject))
+                                     : i == GC_FREELIST_INDEX(sizeof(JSObject))
+                                     ? 0
+                                     : i];
+        ap = &arenaList->last;
+        if (!(a = *ap))
+            continue;
+
+        JS_ASSERT(arenaList->lastCount > 0);
+        arenaList->freeList = NULL;
+        freeList = NULL;
+        thingSize = arenaList->thingSize;
+        indexLimit = THINGS_PER_ARENA(thingSize);
+        flagp = THING_FLAGP(a, arenaList->lastCount - 1);
+        METER((nlivearenas = 0, nkilledarenas = 0, nthings = 0));
+        for (;;) {
+            JS_ASSERT(a->prevUntracedPage == 0);
+            JS_ASSERT(a->u.untracedThings == 0);
+            allClear = JS_TRUE;
+            do {
+                flags = *flagp;
+                if (flags & (GCF_MARK | GCF_LOCK)) {
+                    *flagp &= ~GCF_MARK;
+                    allClear = JS_FALSE;
+                    METER(nthings++);
+                } else {
+                    thing = FLAGP_TO_THING(flagp, thingSize);
+                    if (!(flags & GCF_FINAL)) {
+                        /*
+                         * Call the finalizer with GCF_FINAL ORed into flags.
+                         */
+                        *flagp = (uint8)(flags | GCF_FINAL);
+                        type = flags & GCF_TYPEMASK;
+                        switch (type) {
+                          case GCX_OBJECT:
+                            FinalizeObject(cx, (JSObject *) thing);
+                            break;
 #if JS_HAS_XML_SUPPORT
-    FinalizeArenaList<JSXML>(cx, FINALIZE_XML, &emptyArenas);
+                          case GCX_XML:
+                            js_FinalizeXML(cx, (JSXML *) thing);
+                            break;
+#endif
+                          default:
+                            JS_ASSERT(type == GCX_STRING ||
+                                      type - GCX_EXTERNAL_STRING <
+                                      GCX_NTYPES - GCX_EXTERNAL_STRING);
+                            js_FinalizeStringRT(rt, (JSString *) thing,
+                                                (intN) (type -
+                                                        GCX_EXTERNAL_STRING),
+                                                cx);
+                            break;
+                        }
+#ifdef DEBUG
+                        memset(thing, JS_FREE_PATTERN, thingSize);
 #endif
-    FinalizeArenaList<JSString>(cx, FINALIZE_STRING, &emptyArenas);
-    for (unsigned i = FINALIZE_EXTERNAL_STRING0;
-         i <= FINALIZE_EXTERNAL_STRING_LAST;
-         ++i) {
-        FinalizeArenaList<JSExternalString>(cx, i, &emptyArenas);
+                    }
+                    thing->flagp = flagp;
+                    thing->next = freeList;
+                    freeList = thing;
+                }
+            } while (++flagp != THING_FLAGS_END(a));
+
+            if (allClear) {
+                /*
+                 * Forget just assembled free list head for the arena and
+                 * add the arena itself to the destroy list.
+                 */
+                freeList = arenaList->freeList;
+                if (a == arenaList->last)
+                    arenaList->lastCount = indexLimit;
+                *ap = a->prev;
+                a->prev = emptyArenas;
+                emptyArenas = a;
+                METER(nkilledarenas++);
+            } else {
+                arenaList->freeList = freeList;
+                ap = &a->prev;
+                METER(nlivearenas++);
+            }
+            if (!(a = *ap))
+                break;
+            flagp = THING_FLAGP(a, indexLimit - 1);
+        }
+
+        /*
+         * We use arenaList - &rt->gcArenaList[0], not i, as the stat index
+         * due to the enumeration reorder at the beginning of the loop.
+         */
+        METER(UpdateArenaStats(&rt->gcStats.arenaStats[arenaList -
+                                                       &rt->gcArenaList[0]],
+                               nlivearenas, nkilledarenas, nthings));
     }
 
     ap = &rt->gcDoubleArenaList.first;
     METER((nlivearenas = 0, nkilledarenas = 0, nthings = 0));
     while ((a = *ap) != NULL) {
         if (!a->u.hasMarkedDoubles) {
             /* No marked double values in the arena. */
             *ap = a->prev;
@@ -3818,17 +3726,17 @@ js_GC(JSContext *cx, JSGCInvocationKind 
      * Sweep script filenames after sweeping functions in the generic loop
      * above. In this way when a scripted function's finalizer destroys the
      * script and calls rt->destroyScriptHook, the hook can still access the
      * script's filename. See bug 323267.
      */
     js_SweepScriptFilenames(rt);
 
     /*
-     * Destroy arenas after we finished the sweeping so finalizers can safely
+     * Destroy arenas after we finished the sweeping sofinalizers can safely
      * use js_IsAboutToBeFinalized().
      */
     DestroyGCArenas(rt, emptyArenas);
 
 #ifdef JS_THREADSAFE
     cx->submitDeallocatorTask();
 #endif
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -53,19 +53,44 @@ JS_BEGIN_EXTERN_C
 
 #define JSTRACE_XML         3
 
 /*
  * One past the maximum trace kind.
  */
 #define JSTRACE_LIMIT       4
 
+/*
+ * We use the trace kinds as the types for all GC things except external
+ * strings.
+ */
+#define GCX_OBJECT              JSTRACE_OBJECT      /* JSObject */
+#define GCX_DOUBLE              JSTRACE_DOUBLE      /* jsdouble */
+#define GCX_STRING              JSTRACE_STRING      /* JSString */
+#define GCX_XML                 JSTRACE_XML         /* JSXML */
+#define GCX_EXTERNAL_STRING     JSTRACE_LIMIT       /* JSString with external
+                                                       chars */
 const uintN JS_EXTERNAL_STRING_LIMIT = 8;
 
 /*
+ * The number of defined GC types and the maximum limit for the number of
+ * possible GC types.
+ */
+#define GCX_NTYPES              (GCX_EXTERNAL_STRING + JS_EXTERNAL_STRING_LIMIT)
+#define GCX_LIMIT_LOG2         4           /* type index bits */
+#define GCX_LIMIT              JS_BIT(GCX_LIMIT_LOG2)
+
+/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */
+#define GCF_TYPEMASK    JS_BITMASK(GCX_LIMIT_LOG2)
+#define GCF_MARK        JS_BIT(GCX_LIMIT_LOG2)
+#define GCF_FINAL       JS_BIT(GCX_LIMIT_LOG2 + 1)
+#define GCF_LOCKSHIFT   (GCX_LIMIT_LOG2 + 2)   /* lock bit shift */
+#define GCF_LOCK        JS_BIT(GCF_LOCKSHIFT)   /* lock request bit in API */
+
+/*
  * Get the type of the external string or -1 if the string was not created
  * with JS_NewExternalString.
  */
 extern intN
 js_GetExternalStringGCType(JSString *str);
 
 extern JS_FRIEND_API(uint32)
 js_GetGCThingTraceKind(void *thing);
@@ -122,16 +147,29 @@ typedef struct JSPtrTable {
     size_t      count;
     void        **array;
 } JSPtrTable;
 
 extern JSBool
 js_RegisterCloseableIterator(JSContext *cx, JSObject *obj);
 
 /*
+ * The private JSGCThing struct, which describes a gcFreeList element.
+ */
+struct JSGCThing {
+    JSGCThing   *next;
+    uint8       *flagp;
+};
+
+#define GC_NBYTES_MAX           (10 * sizeof(JSGCThing))
+#define GC_NUM_FREELISTS        (GC_NBYTES_MAX / sizeof(JSGCThing))
+#define GC_FREELIST_NBYTES(i)   (((i) + 1) * sizeof(JSGCThing))
+#define GC_FREELIST_INDEX(n)    (((n) / sizeof(JSGCThing)) - 1)
+
+/*
  * Allocates a new GC thing of the given size. After a successful allocation
  * the caller must fully initialize the thing before calling any function that
  * can potentially trigger GC. This will ensure that GC tracing never sees junk
  * values stored in the partially initialized thing.
  */
 extern JSObject*
 js_NewGCObject(JSContext *cx);
 
@@ -246,55 +284,24 @@ typedef enum JSGCInvocationKind {
      * jsgc.c just before js_GC's definition for details.
      */
     GC_LAST_DITCH       = GC_LOCK_HELD | 2
 } JSGCInvocationKind;
 
 extern void
 js_GC(JSContext *cx, JSGCInvocationKind gckind);
 
-/*
- * The kind of GC thing with a finalizer. The external strings follow the
- * ordinary string to simplify js_GetExternalStringGCType.
- */
-enum JSFinalizeGCThingKind {
-    FINALIZE_OBJECT,
-    FINALIZE_FUNCTION,
-#if JS_HAS_XML_SUPPORT
-    FINALIZE_XML,
-#endif
-    FINALIZE_STRING,
-    FINALIZE_EXTERNAL_STRING0,
-    FINALIZE_EXTERNAL_STRING1,
-    FINALIZE_EXTERNAL_STRING2,
-    FINALIZE_EXTERNAL_STRING3,
-    FINALIZE_EXTERNAL_STRING4,
-    FINALIZE_EXTERNAL_STRING5,
-    FINALIZE_EXTERNAL_STRING6,
-    FINALIZE_EXTERNAL_STRING7,
-    FINALIZE_EXTERNAL_STRING_LAST = FINALIZE_EXTERNAL_STRING7,
-    FINALIZE_LIMIT
-};
-
-static inline bool
-IsFinalizableStringKind(unsigned thingKind)
-{
-    return unsigned(FINALIZE_STRING) <= thingKind &&
-           thingKind <= unsigned(FINALIZE_EXTERNAL_STRING_LAST);
-}
-
 typedef struct JSGCArenaInfo JSGCArenaInfo;
 typedef struct JSGCArenaList JSGCArenaList;
 typedef struct JSGCChunkInfo JSGCChunkInfo;
 
 struct JSGCArenaList {
     JSGCArenaInfo   *last;          /* last allocated GC arena */
     uint32          lastCount;      /* number of allocated things in the last
                                        arena */
-    uint32          thingKind;      /* one of JSFinalizeGCThingKind */
     uint32          thingSize;      /* size of things to allocate on this list
                                      */
     JSGCThing       *freeList;      /* list of free GC things */
 };
 
 typedef union JSGCDoubleCell JSGCDoubleCell;
 
 union JSGCDoubleCell {
@@ -347,18 +354,25 @@ class JSFreePointerListTask : public JSB
             void *next = *(void **)ptr;
             js_free(ptr);
             ptr = next;
         }
     }
 };
 #endif
 
+/*
+ * Free the chars held by str when it is finalized by the GC. When type is
+ * less then zero, it denotes an internal string. Otherwise it denotes the
+ * type of the external string allocated with JS_NewExternalString.
+ *
+ * This function always needs rt but can live with null cx.
+ */
 extern void
-js_FinalizeStringRT(JSRuntime *rt, JSString *str);
+js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx);
 
 #ifdef DEBUG_notme
 #define JS_GCMETER 1
 #endif
 
 #ifdef JS_GCMETER
 
 typedef struct JSGCArenaStats {
@@ -397,17 +411,17 @@ typedef struct JSGCStats {
     uint32  afree;      /* thing arenas freed so far */
     uint32  stackseg;   /* total extraordinary stack segments scanned */
     uint32  segslots;   /* total stack segment jsval slots scanned */
     uint32  nclose;     /* number of objects with close hooks */
     uint32  maxnclose;  /* max number of objects with close hooks */
     uint32  closelater; /* number of close hooks scheduled to run */
     uint32  maxcloselater; /* max number of close hooks scheduled to run */
 
-    JSGCArenaStats  arenaStats[FINALIZE_LIST_LIMIT];
+    JSGCArenaStats  arenaStats[GC_NUM_FREELISTS];
     JSGCArenaStats  doubleArenaStats;
 } JSGCStats;
 
 extern JS_FRIEND_API(void)
 js_DumpGCStats(JSRuntime *rt, FILE *fp);
 
 #endif /* JS_GCMETER */
 
--- a/js/src/jsxml.h
+++ b/js/src/jsxml.h
@@ -117,17 +117,17 @@ struct JSXML {
     uint32              align;
     union {
         JSXMLListVar    list;
         JSXMLElemVar    elem;
         JSString        *value;
     } u;
 };
 
-JS_STATIC_ASSERT(sizeof(JSXML) % JSVAL_ALIGN == 0);
+JS_STATIC_ASSERT(JS_ROUNDUP(sizeof(JSXML), sizeof(JSGCThing)) == sizeof(JSXML));
 
 /* union member shorthands */
 #define xml_kids        u.list.kids
 #define xml_target      u.list.target
 #define xml_targetprop  u.list.targetprop
 #define xml_namespaces  u.elem.namespaces
 #define xml_attrs       u.elem.attrs
 #define xml_value       u.value