bug 519476 - replacing JSSTRING_DEFLATED with scanning of the deflated cache. r=jwalden,dmandelin
authorIgor Bukanov <igor@mir2.org>
Tue, 16 Mar 2010 21:28:33 +0300
changeset 40281 636836c65832aa9b9d2ccfa22dd3f27300e8a6a5
parent 40280 bc5039752e41db77bf5ec36767898c886d0db513
child 40283 6c2d0b28dd6e00acc99df0ee4eb3c6c9074a8eec
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden, dmandelin
bugs519476
milestone1.9.3a3pre
bug 519476 - replacing JSSTRING_DEFLATED with scanning of the deflated cache. r=jwalden,dmandelin
js/src/jsapi.cpp
js/src/jsatom.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsprvtd.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/jsxml.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -560,20 +560,24 @@ JSRuntime::JSRuntime()
     JS_INIT_CLIST(&watchPointList);
 }
 
 bool
 JSRuntime::init(uint32 maxbytes)
 {
     if (!js_InitDtoa() ||
         !js_InitGC(this, maxbytes) ||
-        !js_InitAtomState(this) ||
-        !js_InitDeflatedStringCache(this)) {
+        !js_InitAtomState(this)) {
         return false;
     }
+
+    deflatedStringCache = new js::DeflatedStringCache();
+    if (!deflatedStringCache || !deflatedStringCache->init())
+        return false;
+
 #ifdef JS_THREADSAFE
     gcLock = JS_NEW_LOCK();
     if (!gcLock)
         return false;
     gcDone = JS_NEW_CONDVAR(gcLock);
     if (!gcDone)
         return false;
     requestDone = JS_NEW_CONDVAR(gcLock);
@@ -624,17 +628,17 @@ JSRuntime::~JSRuntime()
     js_FinishThreads(this);
     js_FreeRuntimeScriptState(this);
     js_FinishAtomState(this);
 
     /*
      * Finish the deflated string cache after the last GC and after
      * calling js_FinishAtomState, which finalizes strings.
      */
-    js_FinishDeflatedStringCache(this);
+    delete deflatedStringCache;
     js_FinishGC(this);
 #ifdef JS_THREADSAFE
     if (gcLock)
         JS_DESTROY_LOCK(gcLock);
     if (gcDone)
         JS_DESTROY_CONDVAR(gcDone);
     if (requestDone)
         JS_DESTROY_CONDVAR(requestDone);
@@ -5057,17 +5061,17 @@ JS_NewString(JSContext *cx, char *bytes,
     /* Free chars (but not bytes, which caller frees on error) if we fail. */
     str = js_NewString(cx, chars, length);
     if (!str) {
         cx->free(chars);
         return NULL;
     }
 
     /* Hand off bytes to the deflated string cache, if possible. */
-    if (!js_SetStringBytes(cx, str, bytes, nbytes))
+    if (!cx->runtime->deflatedStringCache->setBytes(cx, str, bytes))
         cx->free(bytes);
     return str;
 }
 
 JS_PUBLIC_API(JSString *)
 JS_NewStringCopyN(JSContext *cx, const char *s, size_t n)
 {
     jschar *js;
@@ -5186,17 +5190,17 @@ JS_GetStringChars(JSString *str)
      */
     if (str->isDependent()) {
         n = str->dependentLength();
         size = (n + 1) * sizeof(jschar);
         s = (jschar *) js_malloc(size);
         if (s) {
             memcpy(s, str->dependentChars(), n * sizeof *s);
             s[n] = 0;
-            str->reinitFlat(s, n);
+            str->initFlat(s, n);
         } else {
             s = str->dependentChars();
         }
     } else {
         str->flatClearMutable();
         s = str->flatChars();
     }
     return s;
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -494,33 +494,34 @@ js_InitCommonAtoms(JSContext *cx)
         *atoms = js_Atomize(cx, js_common_atom_names[i],
                             strlen(js_common_atom_names[i]), ATOM_PINNED);
         if (!*atoms)
             return JS_FALSE;
     }
     JS_ASSERT((uint8 *)atoms - (uint8 *)state == LAZY_ATOM_OFFSET_START);
     memset(atoms, 0, ATOM_OFFSET_LIMIT - LAZY_ATOM_OFFSET_START);
 
+    cx->runtime->emptyString = ATOM_TO_STRING(state->emptyAtom);
     return JS_TRUE;
 }
 
 static JSDHashOperator
 js_atom_unpinner(JSDHashTable *table, JSDHashEntryHdr *hdr,
                  uint32 number, void *arg)
 {
     JS_ASSERT(IS_STRING_TABLE(table));
     CLEAR_ATOM_ENTRY_FLAGS(TO_ATOM_ENTRY(hdr), ATOM_PINNED);
     return JS_DHASH_NEXT;
 }
 
 void
 js_FinishCommonAtoms(JSContext *cx)
 {
+    cx->runtime->emptyString = NULL;
     JSAtomState *state = &cx->runtime->atomState;
-
     JS_DHashTableEnumerate(&state->stringAtoms, js_atom_unpinner, NULL);
 #ifdef DEBUG
     memset(COMMON_ATOMS_START(state), JS_FREE_PATTERN,
            ATOM_OFFSET_LIMIT - ATOM_OFFSET_START);
 #endif
 }
 
 static JSDHashOperator
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -541,18 +541,16 @@ js_NewContext(JSRuntime *rt, size_t stac
          * non-zero contexts alive in rt, so don't re-init the table if it's
          * not necessary.
          */
         if (ok && !rt->scriptFilenameTable)
             ok = js_InitRuntimeScriptState(rt);
         if (ok)
             ok = js_InitRuntimeNumberState(cx);
         if (ok)
-            ok = js_InitRuntimeStringState(cx);
-        if (ok)
             ok = JSScope::initRuntimeState(cx);
 
 #ifdef JS_THREADSAFE
         JS_EndRequest(cx);
 #endif
         if (!ok) {
             js_DestroyContext(cx, JSDCM_NEW_FAILED);
             return NULL;
@@ -773,17 +771,16 @@ js_DestroyContext(JSContext *cx, JSDestr
              * will return early.
              */
             if (cx->requestDepth == 0)
                 JS_BeginRequest(cx);
 #endif
 
             JSScope::finishRuntimeState(cx);
             js_FinishRuntimeNumberState(cx);
-            js_FinishRuntimeStringState(cx);
 
             /* Unpin all common atoms before final GC. */
             js_FinishCommonAtoms(cx);
 
             /* Clear debugging state to remove GC roots. */
             JS_ClearAllTraps(cx);
             JS_ClearAllWatchPoints(cx);
         }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -815,23 +815,17 @@ struct JSRuntime {
      */
     JSSetSlotRequest    *setSlotRequests;
 
     /* Well-known numbers held for use by this runtime's contexts. */
     jsval               NaNValue;
     jsval               negativeInfinityValue;
     jsval               positiveInfinityValue;
 
-#ifdef JS_THREADSAFE
-    JSLock              *deflatedStringCacheLock;
-#endif
-    JSHashTable         *deflatedStringCache;
-#ifdef DEBUG
-    uint32              deflatedStringCacheBytes;
-#endif
+    js::DeflatedStringCache *deflatedStringCache;
 
     JSString            *emptyString;
 
     /*
      * Builtin functions, lazily created and held for use by the trace recorder.
      *
      * This field would be #ifdef JS_TRACER, but XPConnect is compiled without
      * -DJS_TRACER and includes this header.
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2617,18 +2617,16 @@ FinalizeString(JSContext *cx, JSString *
         JS_RUNTIME_UNMETER(cx->runtime, liveDependentStrings);
     } else {
         /*
          * flatChars for stillborn string is null, but cx->free would checks
          * for a null pointer on its own.
          */
         cx->free(str->flatChars());
     }
-    if (str->isDeflated())
-        js_PurgeDeflatedStringCache(cx->runtime, str);
 }
 
 inline void
 FinalizeExternalString(JSContext *cx, JSString *str, unsigned thingKind)
 {
     unsigned type = thingKind - FINALIZE_EXTERNAL_STRING0;
     JS_ASSERT(type < JS_ARRAY_LENGTH(str_finalizers));
     JS_ASSERT(!JSString::isStatic(str));
@@ -2638,18 +2636,16 @@ FinalizeExternalString(JSContext *cx, JS
 
     /* A stillborn string has null chars. */
     jschar *chars = str->flatChars();
     if (!chars)
         return;
     JSStringFinalizeOp finalizer = str_finalizers[type];
     if (finalizer)
         finalizer(cx, str);
-    if (str->isDeflated())
-        js_PurgeDeflatedStringCache(cx->runtime, str);
 }
 
 /*
  * This function is called from js_FinishAtomState to force the finalization
  * of the permanently interned strings when cx is not available.
  */
 void
 js_FinalizeStringRT(JSRuntime *rt, JSString *str)
@@ -2681,18 +2677,16 @@ js_FinalizeStringRT(JSRuntime *rt, JSStr
                 /*
                  * Assume that the finalizer for the permanently interned
                  * string knows how to deal with null context.
                  */
                 finalizer(NULL, str);
             }
         }
     }
-    if (str->isDeflated())
-        js_PurgeDeflatedStringCache(rt, str);
 }
 
 template<typename T,
          void finalizer(JSContext *cx, T *thing, unsigned thingKind)>
 static void
 FinalizeArenaList(JSContext *cx, unsigned thingKind, JSGCArena **emptyArenas)
 {
     JS_STATIC_ASSERT(!(sizeof(T) & GC_CELL_MASK));
@@ -3190,16 +3184,23 @@ js_GC(JSContext *cx, JSGCInvocationKind 
         FinalizeArenaList<JSObject, FinalizeHookedObject>
             (cx, FINALIZE_OBJECT, &emptyArenas);
         FinalizeArenaList<JSFunction, FinalizeHookedFunction>
             (cx, FINALIZE_FUNCTION, &emptyArenas);
     }
 #if JS_HAS_XML_SUPPORT
     FinalizeArenaList<JSXML, FinalizeXML>(cx, FINALIZE_XML, &emptyArenas);
 #endif
+
+    /*
+     * We sweep the deflated cache before we finalize the strings so the
+     * cache can safely use js_IsAboutToBeFinalized..
+     */
+    rt->deflatedStringCache->sweep(cx);
+
     FinalizeArenaList<JSString, FinalizeString>
         (cx, FINALIZE_STRING, &emptyArenas);
     for (unsigned i = FINALIZE_EXTERNAL_STRING0;
          i <= FINALIZE_EXTERNAL_STRING_LAST;
          ++i) {
         FinalizeArenaList<JSString, FinalizeExternalString>
             (cx, i, &emptyArenas);
     }
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -167,16 +167,18 @@ template <class Key,
           class AllocPolicy = ContextAllocPolicy>
 class HashMap;
 
 template <class T,
           class HashPolicy = DefaultHasher<T>,
           class AllocPolicy = ContextAllocPolicy>
 class HashSet;
 
+class DeflatedStringCache;
+
 } /* namespace js */
 
 /* Common instantiations. */
 typedef js::Vector<jschar, 32> JSCharBuffer;
 
 static inline JSPropertyOp
 js_CastAsPropertyOp(JSObject *object)
 {
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -99,17 +99,17 @@ MinimizeDependentStrings(JSString *str, 
             start += MinimizeDependentStrings(base, level + 1, &base);
         } else {
             do {
                 start += base->dependentStart();
                 base = base->dependentBase();
             } while (base->isDependent());
         }
         length = str->dependentLength();
-        str->reinitDependent(base, start, length);
+        str->initDependent(base, start, length);
     }
     *basep = base;
     return start;
 }
 
 jschar *
 js_GetDependentStringChars(JSString *str)
 {
@@ -182,17 +182,17 @@ js_ConcatStrings(JSContext *cx, JSString
             if (s)
                 left->mChars = s;
         }
     } else {
         str->flatSetMutable();
 
         /* Morph left into a dependent string if we realloc'd its buffer. */
         if (ldep) {
-            ldep->reinitDependent(str, 0, ln);
+            ldep->initDependent(str, 0, ln);
 #ifdef DEBUG
             {
                 JSRuntime *rt = cx->runtime;
                 JS_RUNTIME_METER(rt, liveDependentStrings);
                 JS_RUNTIME_METER(rt, totalDependentStrings);
                 JS_LOCK_RUNTIME_VOID(rt,
                     (rt->strdepLengthSum += (double)ln,
                      rt->strdepLengthSquaredSum += (double)ln * (double)ln));
@@ -214,17 +214,17 @@ js_UndependString(JSContext *cx, JSStrin
         n = str->dependentLength();
         size = (n + 1) * sizeof(jschar);
         s = (jschar *) cx->malloc(size);
         if (!s)
             return NULL;
 
         js_strncpy(s, str->dependentChars(), n);
         s[n] = 0;
-        str->reinitFlat(s, n);
+        str->initFlat(s, n);
 
 #ifdef DEBUG
         {
             JSRuntime *rt = cx->runtime;
             JS_RUNTIME_UNMETER(rt, liveDependentStrings);
             JS_RUNTIME_UNMETER(rt, totalDependentStrings);
             JS_LOCK_RUNTIME_VOID(rt,
                 (rt->strdepLengthSum -= (double)n,
@@ -3043,76 +3043,16 @@ String_fromCharCode(JSContext* cx, int32
 JS_DEFINE_TRCINFO_1(str_fromCharCode,
     (2, (static, STRING_RETRY, String_fromCharCode, CONTEXT, INT32, 1, nanojit::ACC_NONE)))
 
 static JSFunctionSpec string_static_methods[] = {
     JS_TN("fromCharCode", str_fromCharCode, 1, 0, &str_fromCharCode_trcinfo),
     JS_FS_END
 };
 
-static JSHashNumber
-js_hash_string_pointer(const void *key)
-{
-    return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS;
-}
-
-JSBool
-js_InitRuntimeStringState(JSContext *cx)
-{
-    JSRuntime *rt;
-
-    rt = cx->runtime;
-    rt->emptyString = ATOM_TO_STRING(rt->atomState.emptyAtom);
-    return JS_TRUE;
-}
-
-JSBool
-js_InitDeflatedStringCache(JSRuntime *rt)
-{
-    JSHashTable *cache;
-
-    /* Initialize string cache */
-    JS_ASSERT(!rt->deflatedStringCache);
-    cache = JS_NewHashTable(8, js_hash_string_pointer,
-                            JS_CompareValues, JS_CompareValues,
-                            NULL, NULL);
-    if (!cache)
-        return JS_FALSE;
-    rt->deflatedStringCache = cache;
-
-#ifdef JS_THREADSAFE
-    JS_ASSERT(!rt->deflatedStringCacheLock);
-    rt->deflatedStringCacheLock = JS_NEW_LOCK();
-    if (!rt->deflatedStringCacheLock)
-        return JS_FALSE;
-#endif
-    return JS_TRUE;
-}
-
-void
-js_FinishRuntimeStringState(JSContext *cx)
-{
-    cx->runtime->emptyString = NULL;
-}
-
-void
-js_FinishDeflatedStringCache(JSRuntime *rt)
-{
-    if (rt->deflatedStringCache) {
-        JS_HashTableDestroy(rt->deflatedStringCache);
-        rt->deflatedStringCache = NULL;
-    }
-#ifdef JS_THREADSAFE
-    if (rt->deflatedStringCacheLock) {
-        JS_DESTROY_LOCK(rt->deflatedStringCacheLock);
-        rt->deflatedStringCacheLock = NULL;
-    }
-#endif
-}
-
 JSObject *
 js_InitStringClass(JSContext *cx, JSObject *obj)
 {
     JSObject *proto;
 
     /* Define the escape, unescape functions in the global object. */
     if (!JS_DefineFunctions(cx, obj, string_functions))
         return NULL;
@@ -3292,36 +3232,16 @@ js_NewStringCopyZ(JSContext *cx, const j
         return NULL;
     memcpy(news, s, m);
     str = js_NewString(cx, news, n);
     if (!str)
         cx->free(news);
     return str;
 }
 
-void
-js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str)
-{
-    JSHashNumber hash;
-    JSHashEntry *he, **hep;
-
-    hash = js_hash_string_pointer(str);
-    JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
-    hep = JS_HashTableRawLookup(rt->deflatedStringCache, hash, str);
-    he = *hep;
-    if (he) {
-#ifdef DEBUG
-        rt->deflatedStringCacheBytes -= str->length();
-#endif
-        js_free(he->value);
-        JS_HashTableRawRemove(rt->deflatedStringCache, hep, he);
-    }
-    JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
-}
-
 JS_FRIEND_API(const char *)
 js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun)
 {
     JSString *str;
 
     str = v2sfun(cx, v);
     if (!str)
         return NULL;
@@ -3822,52 +3742,171 @@ bufferTooSmall:
     *dstlenp = (origDstlen - dstlen);
     if (cx) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BUFFER_TOO_SMALL);
     }
     return JS_FALSE;
 }
 
-JSBool
-js_SetStringBytes(JSContext *cx, JSString *str, char *bytes, size_t length)
+namespace js {
+
+DeflatedStringCache::DeflatedStringCache()
+{
+#ifdef JS_THREADSAFE
+    lock = NULL;
+#endif
+}
+
+bool
+DeflatedStringCache::init()
+{
+#ifdef JS_THREADSAFE
+    JS_ASSERT(!lock);
+    lock = JS_NEW_LOCK();
+    if (!lock)
+        return false;
+#endif
+
+    /*
+     * Make room for 2K deflated strings that a typical browser session
+     * creates.
+     */
+    return map.init(2048);
+}
+
+DeflatedStringCache::~DeflatedStringCache()
+{
+#ifdef JS_THREADSAFE
+    if (lock)
+        JS_DESTROY_LOCK(lock);
+#endif
+}
+
+void
+DeflatedStringCache::sweep(JSContext *cx)
 {
-    JSRuntime *rt;
-    JSHashTable *cache;
-    JSBool ok;
-    JSHashNumber hash;
-    JSHashEntry **hep;
-
-    rt = cx->runtime;
-    JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
-
-    cache = rt->deflatedStringCache;
-    hash = js_hash_string_pointer(str);
-    hep = JS_HashTableRawLookup(cache, hash, str);
-    JS_ASSERT(*hep == NULL);
-    ok = JS_HashTableRawAdd(cache, hep, hash, str, bytes) != NULL;
-    if (ok) {
-        str->setDeflated();
-#ifdef DEBUG
-        rt->deflatedStringCacheBytes += length;
-#endif
+    /*
+     * We must take a lock even during the GC as JS_GetStringBytes() can be
+     * called outside the request.
+     */
+    JS_ACQUIRE_LOCK(lock);
+
+    for (Map::Enum e(map); !e.empty(); e.popFront()) {
+        JSString *str = e.front().key;
+        if (js_IsAboutToBeFinalized(str)) {
+            char *bytes = e.front().value;
+            e.removeFront();
+
+            /*
+             * We cannot use cx->free here as bytes may come from the
+             * embedding that calls JS_NewString(cx, bytes, length). Those
+             * bytes may not be allocated via js_malloc and may not have
+             * space for the background free list.
+             */
+            js_free(bytes);
+        }
     }
 
-    JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
+    JS_RELEASE_LOCK(lock);
+}
+
+void
+DeflatedStringCache::remove(JSString *str)
+{
+    JS_ACQUIRE_LOCK(lock);
+
+    Map::Ptr p = map.lookup(str);
+    if (p) {
+        js_free(p->value);
+        map.remove(p);
+    }
+
+    JS_RELEASE_LOCK(lock);
+}
+
+bool
+DeflatedStringCache::setBytes(JSContext *cx, JSString *str, char *bytes)
+{
+    JS_ACQUIRE_LOCK(lock);
+
+    Map::AddPtr p = map.lookupForAdd(str);
+    JS_ASSERT(!p);
+    bool ok = map.add(p, str, bytes);
+
+    JS_RELEASE_LOCK(lock);
+
+    if (!ok)
+        js_ReportOutOfMemory(cx);
     return ok;
 }
 
+char *
+DeflatedStringCache::getBytes(JSContext *cx, JSString *str)
+{
+    JS_ACQUIRE_LOCK(lock);
+
+    char *bytes;
+    do {
+        Map::AddPtr p = map.lookupForAdd(str);
+        if (p) {
+            bytes = p->value;
+            break;
+        }
+#ifdef JS_THREADSAFE
+        unsigned generation = map.generation();
+        JS_RELEASE_LOCK(lock);
+#endif
+        bytes = js_DeflateString(cx, str->chars(), str->length());
+        if (!bytes)
+            return NULL;
+#ifdef JS_THREADSAFE
+        JS_ACQUIRE_LOCK(lock);
+        if (generation != map.generation()) {
+            p = map.lookupForAdd(str);
+            if (p) {
+                /* Some other thread has asked for str bytes .*/
+                if (cx)
+                    cx->free(bytes);
+                else
+                    js_free(bytes);
+                bytes = p->value;
+                break;
+            }
+        }
+#endif
+        if (!map.add(p, str, bytes)) {
+            JS_RELEASE_LOCK(lock);
+            if (cx) {
+                cx->free(bytes);
+                js_ReportOutOfMemory(cx);
+            } else {
+                js_free(bytes);
+            }
+            return NULL;
+        }
+    } while (false);
+
+    JS_ASSERT(bytes);
+
+    /* Try to catch failure to JS_ShutDown between runtime epochs. */
+    JS_ASSERT_IF(!js_CStringsAreUTF8 && *bytes != (char) str->chars()[0],
+                 *bytes == '\0' && str->empty());
+
+    JS_RELEASE_LOCK(lock);
+    return bytes;
+}
+
+} /* namespace js */
+
 const char *
 js_GetStringBytes(JSContext *cx, JSString *str)
 {
     JSRuntime *rt;
-    JSHashTable *cache;
     char *bytes;
-    JSHashNumber hash;
-    JSHashEntry *he, **hep;
 
     if (JSString::isUnitString(str)) {
 #ifdef IS_LITTLE_ENDIAN
         /* Unit string data is {c, 0, 0, 0} so we can just cast. */
         bytes = (char *)str->chars();
 #else
         /* Unit string data is {0, c, 0, 0} so we can point into the middle. */
         bytes = (char *)str->chars() + 1;
@@ -3887,60 +3926,17 @@ js_GetStringBytes(JSContext *cx, JSStrin
 
     if (cx) {
         rt = cx->runtime;
     } else {
         /* JS_GetStringBytes calls us with null cx. */
         rt = js_GetGCStringRuntime(str);
     }
 
-#ifdef JS_THREADSAFE
-    if (!rt->deflatedStringCacheLock) {
-        /*
-         * Called from last GC (see js_DestroyContext), after runtime string
-         * state has been finalized.  We have no choice but to leak here.
-         */
-        return js_DeflateString(NULL, str->chars(), str->length());
-    }
-#endif
-
-    JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
-
-    cache = rt->deflatedStringCache;
-    hash = js_hash_string_pointer(str);
-    hep = JS_HashTableRawLookup(cache, hash, str);
-    he = *hep;
-    if (he) {
-        bytes = (char *) he->value;
-
-        /* Try to catch failure to JS_ShutDown between runtime epochs. */
-        if (!js_CStringsAreUTF8) {
-            JS_ASSERT_IF(*bytes != (char) str->chars()[0],
-                         *bytes == '\0' && str->empty());
-        }
-    } else {
-        bytes = js_DeflateString(cx, str->chars(), str->length());
-        if (bytes) {
-            if (JS_HashTableRawAdd(cache, hep, hash, str, bytes)) {
-#ifdef DEBUG
-                rt->deflatedStringCacheBytes += str->length();
-#endif
-                str->setDeflated();
-            } else {
-                if (cx)
-                    cx->free(bytes);
-                else
-                    js_free(bytes);
-                bytes = NULL;
-            }
-        }
-    }
-
-    JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
-    return bytes;
+    return rt->deflatedStringCache->getBytes(cx, str);
 }
 
 /*
  * From java.lang.Character.java:
  *
  * The character properties are currently encoded into 32 bits in the
  * following manner:
  *
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -46,16 +46,17 @@
  * of API client memory, the chars are allocated separately from the length,
  * necessitating a pointer after the count, to form a separately allocated
  * string descriptor.  String descriptors are GC'ed, while their chars are
  * allocated from the malloc heap.
  */
 #include <ctype.h>
 #include "jspubtd.h"
 #include "jsprvtd.h"
+#include "jshashtable.h"
 #include "jslock.h"
 
 JS_BEGIN_EXTERN_C
 
 #define JSSTRING_BIT(n)             ((size_t)1 << (n))
 #define JSSTRING_BITMASK(n)         (JSSTRING_BIT(n) - 1)
 
 enum {
@@ -82,20 +83,16 @@ JS_STATIC_ASSERT(JS_BITS_PER_WORD >= 32)
  * from one thread and it is possible to turn it into a dependent string of the
  * same length to optimize js_ConcatStrings. It is also possible to grow such a
  * string, but extreme care must be taken to ensure that no other code relies
  * on the original length of the string.
  *
  * A flat string with the ATOMIZED flag means that the string is hashed as
  * an atom. This flag is used to avoid re-hashing the already-atomized string.
  *
- * Any string with the DEFLATED flag means that the string has an entry in the
- * deflated string cache. The GC uses this flag to optimize string finalization
- * and avoid an expensive cache lookup for strings that were never deflated.
- *
  * When the DEPENDENT flag is set, the string depends on characters of another
  * string strongly referenced by the mBase field. The base member may point to
  * another dependent string if chars() has not been called yet.
  *
  * NB: Always use the length() and chars() accessor methods.
  */
 struct JSString {
     friend class js::TraceRecorder;
@@ -119,17 +116,16 @@ struct JSString {
     /*
      * Definitions for flags stored in mFlags.
      *
      * ATOMIZED is used only with flat, immutable strings.
      */
     static const size_t DEPENDENT =     JSSTRING_BIT(1);
     static const size_t MUTABLE =       JSSTRING_BIT(2);
     static const size_t ATOMIZED =      JSSTRING_BIT(3);
-    static const size_t DEFLATED =      JSSTRING_BIT(4);
 
     inline bool hasFlag(size_t flag) const {
         return (mFlags & flag) != 0;
     }
 
   public:
     /*
      * Generous but sane length bound; the "-1" is there for comptibility with
@@ -140,24 +136,16 @@ struct JSString {
     inline bool isDependent() const {
         return hasFlag(DEPENDENT);
     }
 
     inline bool isFlat() const {
         return !isDependent();
     }
 
-    inline bool isDeflated() const {
-        return hasFlag(DEFLATED);
-    }
-
-    inline void setDeflated() {
-        JS_ATOMIC_SET_MASK(&mFlags, DEFLATED);
-    }
-
     inline bool isMutable() const {
         return !isDependent() && hasFlag(MUTABLE);
     }
 
     inline bool isAtomized() const {
         return !isDependent() && hasFlag(ATOMIZED);
     }
 
@@ -197,28 +185,16 @@ struct JSString {
     }
 
     inline size_t flatLength() const {
         JS_ASSERT(isFlat());
         return length();
     }
 
     /*
-     * Special flat string initializer that preserves the DEFLATED flag.
-     * Use this method when reinitializing an existing string which may be
-     * hashed to its deflated bytes. Newborn strings must use initFlat.
-     */
-    void reinitFlat(jschar *chars, size_t length) {
-        mLength = length;
-        mOffset = 0;
-        mFlags = mFlags & DEFLATED;
-        mChars = chars;
-    }
-
-    /*
      * Methods to manipulate atomized and mutable flags of flat strings. It is
      * safe to use these without extra locking due to the following properties:
      *
      *   * We do not have a flatClearAtomized method, as a string remains
      *     atomized until the GC collects it.
      *
      *   * A thread may call flatSetMutable only when it is the only
      *     thread accessing the string until a later call to
@@ -259,25 +235,16 @@ struct JSString {
     inline void initDependent(JSString *bstr, size_t off, size_t len) {
         JS_ASSERT(len <= MAX_LENGTH);
         mLength = len;
         mOffset = off;
         mFlags = DEPENDENT;
         mBase = bstr;
     }
 
-    /* See JSString::reinitFlat. */
-    inline void reinitDependent(JSString *bstr, size_t off, size_t len) {
-        JS_ASSERT(len <= MAX_LENGTH);
-        mLength = len;
-        mOffset = off;
-        mFlags = DEPENDENT | (mFlags & DEFLATED);
-        mBase = bstr;
-    }
-
     inline JSString *dependentBase() const {
         JS_ASSERT(isDependent());
         return mBase;
     }
 
     inline jschar *dependentChars() {
         return dependentBase()->isDependent()
                ? js_GetDependentStringChars(this)
@@ -498,29 +465,16 @@ JS_ISSPACE(jschar c)
  * Manually inline isdigit for performance; MSVC doesn't do this for us.
  */
 #define JS7_ISDEC(c)    ((((unsigned)(c)) - '0') <= 9)
 #define JS7_UNDEC(c)    ((c) - '0')
 #define JS7_ISHEX(c)    ((c) < 128 && isxdigit(c))
 #define JS7_UNHEX(c)    (uintN)(JS7_ISDEC(c) ? (c) - '0' : 10 + tolower(c) - 'a')
 #define JS7_ISLET(c)    ((c) < 128 && isalpha(c))
 
-/* Initialize per-runtime string state for the first context in the runtime. */
-extern JSBool
-js_InitRuntimeStringState(JSContext *cx);
-
-extern JSBool
-js_InitDeflatedStringCache(JSRuntime *rt);
-
-extern void
-js_FinishRuntimeStringState(JSContext *cx);
-
-extern void
-js_FinishDeflatedStringCache(JSRuntime *rt);
-
 /* Initialize the String class, returning its prototype object. */
 extern JSClass js_StringClass;
 
 extern JSObject *
 js_InitStringClass(JSContext *cx, JSObject *obj);
 
 extern const char js_escape_str[];
 extern const char js_unescape_str[];
@@ -682,33 +636,22 @@ js_GetDeflatedStringLength(JSContext *cx
  * must to be initialized with the buffer size and will contain on return the
  * number of copied bytes.
  */
 extern JSBool
 js_DeflateStringToBuffer(JSContext *cx, const jschar *chars,
                          size_t charsLength, char *bytes, size_t *length);
 
 /*
- * Associate bytes with str in the deflated string cache, returning true on
- * successful association, false on out of memory.
- */
-extern JSBool
-js_SetStringBytes(JSContext *cx, JSString *str, char *bytes, size_t length);
-
-/*
  * Find or create a deflated string cache entry for str that contains its
  * characters chopped from Unicode code points into bytes.
  */
 extern const char *
 js_GetStringBytes(JSContext *cx, JSString *str);
 
-/* Remove a deflated string cache entry associated with str if any. */
-extern void
-js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str);
-
 /* Export a few natives and a helper to other files in SpiderMonkey. */
 extern JSBool
 js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
               jsval *rval);
 
 extern JSBool
 js_str_toString(JSContext *cx, uintN argc, jsval *vp);
 
@@ -745,9 +688,62 @@ extern JS_FRIEND_API(size_t)
 js_PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp,
                         JSString *str, uint32 quote);
 
 extern JSBool
 js_String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
 
 JS_END_EXTERN_C
 
+namespace js {
+
+class DeflatedStringCache {
+  public:
+    DeflatedStringCache();
+    bool init();
+    ~DeflatedStringCache();
+
+    void sweep(JSContext *cx);
+    void remove(JSString *str);
+    bool setBytes(JSContext *cx, JSString *str, char *bytes);
+
+  private:
+    struct StringPtrHasher
+    {
+        typedef JSString *Lookup;
+
+        static uint32 hash(JSString *str) {
+            /*
+             * We hash only GC-allocated Strings. They are aligned on
+             * sizeof(JSString) boundary so we can improve hashing by stripping
+             * initial zeros.
+             */
+            const jsuword ALIGN_LOG = tl::FloorLog2<sizeof(JSString)>::result;
+            JS_STATIC_ASSERT(sizeof(JSString) == (size_t(1) << ALIGN_LOG));
+
+            jsuword ptr = reinterpret_cast<jsuword>(str);
+            jsuword key = ptr >> ALIGN_LOG;
+            JS_ASSERT((key << ALIGN_LOG) == ptr);
+            return uint32(key);
+        }
+
+        static bool match(JSString *s1, JSString *s2) {
+            return s1 == s2;
+        }
+    };
+
+    typedef HashMap<JSString *, char *, StringPtrHasher, SystemAllocPolicy> Map;
+
+    /* cx is NULL when the caller is JS_GetStringBytes(JSString *). */
+    char *getBytes(JSContext *cx, JSString *str);
+
+    friend const char *
+    ::js_GetStringBytes(JSContext *cx, JSString *str);
+
+    Map                 map;
+#ifdef JS_THREADSAFE
+    JSLock              *lock;
+#endif
+};
+
+} /* namespace js */
+
 #endif /* jsstr_h___ */
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -7490,17 +7490,17 @@ js_AddAttributePart(JSContext *cx, JSBoo
         if (!str)
             return NULL;
         chars = str->flatChars();
     } else {
         /*
          * Reallocating str (because we know it has no other references)
          * requires purging any deflated string cached for it.
          */
-        js_PurgeDeflatedStringCache(cx->runtime, str);
+        cx->runtime->deflatedStringCache->remove(str);
     }
 
     str2->getCharsAndLength(chars2, len2);
     newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
     chars = (jschar *) cx->realloc(chars, (newlen+1) * sizeof(jschar));
     if (!chars)
         return NULL;