Back out 735da799e3bb (bug 1211723) for assertion failures and crashes in SharedImmutableStringsCache
authorPhil Ringnalda <philringnalda@gmail.com>
Tue, 29 Mar 2016 21:57:51 -0700
changeset 291013 06a8c115f8fa8a253b867bf798ac376a168418b5
parent 291012 6bf6edf222a15276ca24855fd9f6bae559595524
child 291014 d926ec33189d35357876a4e7be7150ab8adec7dd
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1211723
milestone48.0a1
backs out735da799e3bbb98c087339f21599571c48ce484f
Back out 735da799e3bb (bug 1211723) for assertion failures and crashes in SharedImmutableStringsCache CLOSED TREE
js/public/MemoryMetrics.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
js/src/jsfun.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/moz.build
js/src/vm/HelperThreads.cpp
js/src/vm/MemoryMetrics.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SharedImmutableStringsCache.cpp
js/src/vm/SharedImmutableStringsCache.h
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -392,16 +392,18 @@ struct NotableStringInfo : public String
 
 /**
  * This class holds information about the memory taken up by script sources
  * from a particular file.
  */
 struct ScriptSourceInfo
 {
 #define FOR_EACH_SIZE(macro) \
+    macro(_, MallocHeap, compressed) \
+    macro(_, MallocHeap, uncompressed) \
     macro(_, MallocHeap, misc)
 
     ScriptSourceInfo()
       : FOR_EACH_SIZE(ZERO_SIZE)
         numScripts(0)
     {}
 
     void add(const ScriptSourceInfo& other) {
@@ -465,18 +467,18 @@ struct RuntimeSizes
 {
 #define FOR_EACH_SIZE(macro) \
     macro(_, MallocHeap, object) \
     macro(_, MallocHeap, atomsTable) \
     macro(_, MallocHeap, contexts) \
     macro(_, MallocHeap, temporary) \
     macro(_, MallocHeap, interpreterStack) \
     macro(_, MallocHeap, mathCache) \
-    macro(_, MallocHeap, sharedImmutableStringsCache) \
     macro(_, MallocHeap, uncompressedSourceCache) \
+    macro(_, MallocHeap, compressedSourceSet) \
     macro(_, MallocHeap, scriptData)
 
     RuntimeSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         scriptSourceInfo(),
         code(),
         gc(),
         notableScriptSources()
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -73,17 +73,16 @@ UNIFIED_SOURCES += [
     'testResolveRecursion.cpp',
     'tests.cpp',
     'testSameValue.cpp',
     'testSavedStacks.cpp',
     'testScriptInfo.cpp',
     'testScriptObject.cpp',
     'testSetProperty.cpp',
     'testSetPropertyIgnoringNamedGetter.cpp',
-    'testSharedImmutableStringsCache.cpp',
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
     'testStructuredClone.cpp',
     'testSymbol.cpp',
     'testThreadingExclusiveData.cpp',
     'testThreadingMutex.cpp',
     'testToIntWidth.cpp',
     'testTypedArrays.cpp',
deleted file mode 100644
--- a/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/IntegerRange.h"
-
-#include "js/Vector.h"
-
-#include "vm/SharedImmutableStringsCache.h"
-
-const int NUM_THREADS = 256;
-const int NUM_ITERATIONS = 256;
-
-const int NUM_STRINGS = 4;
-const char16_t* STRINGS[NUM_STRINGS] = {
-    MOZ_UTF16("uno"),
-    MOZ_UTF16("dos"),
-    MOZ_UTF16("tres"),
-    MOZ_UTF16("quattro")
-};
-
-struct CacheAndIndex
-{
-    js::SharedImmutableStringsCache* cache;
-    int index;
-
-    CacheAndIndex(js::SharedImmutableStringsCache* cache, int index)
-      : cache(cache)
-      , index(index)
-    { }
-};
-
-static void
-getString(void* data)
-{
-    auto cacheAndIndex = reinterpret_cast<CacheAndIndex*>(data);
-
-    for (int i = 0; i < NUM_ITERATIONS; i++) {
-        auto str = STRINGS[cacheAndIndex->index % NUM_STRINGS];
-
-        auto dupe = js::DuplicateString(str);
-        MOZ_RELEASE_ASSERT(dupe);
-
-        auto deduped = cacheAndIndex->cache->getOrCreate(mozilla::Move(dupe), js_strlen(str));
-        MOZ_RELEASE_ASSERT(deduped.isSome());
-        MOZ_RELEASE_ASSERT(js_strcmp(str, deduped->chars()) == 0);
-
-        {
-            auto cloned = deduped->clone();
-            // We should be de-duplicating and giving back the same string.
-            MOZ_RELEASE_ASSERT(deduped->chars() == cloned.chars());
-        }
-    }
-
-    js_delete(cacheAndIndex);
-}
-
-BEGIN_TEST(testSharedImmutableStringsCache)
-{
-    js::SharedImmutableStringsCache cache;
-
-    js::Vector<PRThread*> threads(cx);
-    CHECK(threads.reserve(NUM_THREADS));
-
-    for (auto i : mozilla::MakeRange(NUM_THREADS)) {
-        auto cacheAndIndex = js_new<CacheAndIndex>(&cache, i);
-        CHECK(cacheAndIndex);
-        auto thread = PR_CreateThread(PR_USER_THREAD,
-                                      getString,
-                                      (void *) cacheAndIndex,
-                                      PR_PRIORITY_NORMAL,
-                                      PR_LOCAL_THREAD,
-                                      PR_JOINABLE_THREAD,
-                                      0);
-        CHECK(thread);
-        threads.infallibleAppend(thread);
-    }
-
-    for (auto thread : threads) {
-        CHECK(PR_JoinThread(thread) == PR_SUCCESS);
-    }
-
-    return true;
-}
-END_TEST(testSharedImmutableStringsCache)
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -38,17 +38,16 @@
 #include "jit/Ion.h"
 #include "jit/JitFrameIterator.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Proxy.h"
 #include "vm/Debugger.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Shape.h"
-#include "vm/SharedImmutableStringsCache.h"
 #include "vm/StringBuffer.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
 #include "jsscriptinlines.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/Stack-inl.h"
@@ -763,33 +762,28 @@ CreateFunctionPrototype(JSContext* cx, J
                              SingletonObject);
     if (!functionProto_)
         return nullptr;
 
     RootedFunction functionProto(cx, &functionProto_->as<JSFunction>());
 
     const char* rawSource = "() {\n}";
     size_t sourceLen = strlen(rawSource);
-    mozilla::UniquePtr<char16_t[], JS::FreePolicy> source(InflateString(cx, rawSource, &sourceLen));
+    char16_t* source = InflateString(cx, rawSource, &sourceLen);
     if (!source)
         return nullptr;
 
-    ScriptSource* ss = cx->new_<ScriptSource>();
-    if (!ss)
-        return nullptr;
-    ScriptSourceHolder ssHolder(ss);
-
-    auto& cache = cx->runtime()->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(mozilla::Move(source), sourceLen);
-    if (!deduped) {
-        ReportOutOfMemory(cx);
+    ScriptSource* ss =
+        cx->new_<ScriptSource>();
+    if (!ss) {
+        js_free(source);
         return nullptr;
     }
-    ss->setSource(mozilla::Move(*deduped));
-
+    ScriptSourceHolder ssHolder(ss);
+    ss->setSource(source, sourceLen);
     CompileOptions options(cx);
     options.setNoScriptRval(true)
            .setVersion(JSVERSION_DEFAULT);
     RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss));
     if (!sourceObject || !ScriptSourceObject::initFromOptions(cx, sourceObject, options))
         return nullptr;
 
     RootedScript script(cx, JSScript::Create(cx,
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -42,17 +42,16 @@
 #include "js/MemoryMetrics.h"
 #include "js/Utility.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Compression.h"
 #include "vm/Debugger.h"
 #include "vm/Opcodes.h"
 #include "vm/SelfHosting.h"
 #include "vm/Shape.h"
-#include "vm/SharedImmutableStringsCache.h"
 #include "vm/Xdr.h"
 
 #include "jsfuninlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/ScopeObject-inl.h"
 #include "vm/Stack-inl.h"
 
@@ -1788,26 +1787,17 @@ JSScript::loadSource(JSContext* cx, Scri
     if (!cx->runtime()->sourceHook || !ss->sourceRetrievable())
         return true;
     char16_t* src = nullptr;
     size_t length;
     if (!cx->runtime()->sourceHook->load(cx, ss->filename(), &src, &length))
         return false;
     if (!src)
         return true;
-
-    mozilla::UniquePtr<char16_t[], JS::FreePolicy> ownedSource(src);
-    auto& cache = cx->runtime()->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(mozilla::Move(ownedSource), length);
-    if (!deduped) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-    ss->setSource(mozilla::Move(*deduped));
-
+    ss->setSource(src, length);
     *worked = true;
     return true;
 }
 
 JSFlatString*
 JSScript::sourceData(JSContext* cx)
 {
     MOZ_ASSERT(scriptSource()->hasSourceData());
@@ -1953,17 +1943,17 @@ ScriptSource::chars(JSContext* cx, Uncom
         explicit CharsMatcher(JSContext* cx, ScriptSource& ss,
                               UncompressedSourceCache::AutoHoldEntry& holder)
           : cx(cx)
           , ss(ss)
           , holder(holder)
         { }
 
         ReturnType match(Uncompressed& u) {
-            return u.string.chars();
+            return u.chars;
         }
 
         ReturnType match(Compressed& c) {
             if (const char16_t* decompressed = cx->runtime()->uncompressedSourceCache.lookup(&ss, holder))
                 return decompressed;
 
             const size_t nbytes = sizeof(char16_t) * (ss.length() + 1);
             char16_t* decompressed = static_cast<char16_t*>(js_malloc(nbytes));
@@ -1985,16 +1975,20 @@ ScriptSource::chars(JSContext* cx, Uncom
                 JS_ReportOutOfMemory(cx);
                 js_free(decompressed);
                 return nullptr;
             }
 
             return decompressed;
         }
 
+        ReturnType match(Parent& p) {
+            return p.parent->chars(cx, holder);
+        }
+
         ReturnType match(Missing&) {
             MOZ_CRASH("ScriptSource::chars() on ScriptSource with SourceType = Missing");
             return nullptr;
         }
     };
 
     CharsMatcher cm(cx, *this, holder);
     return data.match(cm);
@@ -2018,49 +2012,88 @@ ScriptSource::substringDontDeflate(JSCon
     UncompressedSourceCache::AutoHoldEntry holder;
     const char16_t* chars = this->chars(cx, holder);
     if (!chars)
         return nullptr;
     return NewStringCopyNDontDeflate<CanGC>(cx, chars + start, stop - start);
 }
 
 void
-ScriptSource::setSource(SharedImmutableTwoByteString&& string)
+ScriptSource::setSource(const char16_t* chars, size_t length, bool ownsChars /* = true */)
 {
     MOZ_ASSERT(data.is<Missing>());
-    data = SourceType(Uncompressed(mozilla::Move(string)));
+
+    data = SourceType(Uncompressed(chars, ownsChars));
+    length_ = length;
+}
+
+void
+ScriptSource::setCompressedSource(JSRuntime* maybert, void* raw, size_t nbytes, HashNumber hash)
+{
+    MOZ_ASSERT(data.is<Missing>() || data.is<Uncompressed>());
+
+    if (data.is<Uncompressed>() && data.as<Uncompressed>().ownsChars)
+        js_free(const_cast<char16_t*>(uncompressedChars()));
+
+    data = SourceType(Compressed(raw, nbytes, hash));
+
+    if (maybert)
+        updateCompressedSourceSet(maybert);
 }
 
 void
-ScriptSource::setCompressedSource(SharedImmutableString&& raw, size_t length)
+ScriptSource::updateCompressedSourceSet(JSRuntime* rt)
 {
-    MOZ_ASSERT(data.is<Missing>() || data.is<Uncompressed>());
-    MOZ_ASSERT_IF(data.is<Uncompressed>(), data.as<Uncompressed>().string.length() == length);
-
-    data = SourceType(Compressed(mozilla::Move(raw), length));
+    MOZ_ASSERT(data.is<Compressed>());
+    MOZ_ASSERT(!inCompressedSourceSet);
+
+    CompressedSourceSet::AddPtr p = rt->compressedSourceSet.lookupForAdd(this);
+    if (p) {
+        // There is another ScriptSource with the same compressed data.
+        // Mark that ScriptSource as the parent and use it for all attempts to
+        // get the source for this ScriptSource.
+        ScriptSource* parent = *p;
+        parent->incref();
+
+        js_free(compressedData());
+        data = SourceType(Parent(parent));
+    } else {
+        if (rt->compressedSourceSet.add(p, this))
+            inCompressedSourceSet = true;
+    }
+}
+
+bool
+ScriptSource::ensureOwnsSource(ExclusiveContext* cx)
+{
+    MOZ_ASSERT(data.is<Uncompressed>());
+    if (ownsUncompressedChars())
+        return true;
+
+    char16_t* uncompressed = cx->zone()->pod_malloc<char16_t>(Max<size_t>(length_, 1));
+    if (!uncompressed) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+    PodCopy(uncompressed, uncompressedChars(), length_);
+
+    data.as<Uncompressed>().chars = uncompressed;
+    data.as<Uncompressed>().ownsChars = true;
+    return true;
 }
 
 bool
 ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf,
                             bool argumentsNotIncluded, SourceCompressionTask* task)
 {
     MOZ_ASSERT(!hasSourceData());
     argumentsNotIncluded_ = argumentsNotIncluded;
 
-    auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&]() {
-        return srcBuf.ownsChars()
-            ? mozilla::UniquePtr<char16_t[], JS::FreePolicy>(srcBuf.take())
-            : DuplicateString(srcBuf.get(), srcBuf.length());
-    });
-    if (!deduped) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-    setSource(mozilla::Move(*deduped));
+    bool owns = srcBuf.ownsChars();
+    setSource(owns ? srcBuf.take() : srcBuf.get(), srcBuf.length(), owns);
 
     // There are several cases where source compression is not a good idea:
     //  - If the script is tiny, then compression will save little or no space.
     //  - If the script is enormous, then decompression can take seconds. With
     //    lazy parsing, decompression is not uncommon, so this can significantly
     //    increase latency.
     //  - If there is only one core, then compression will contend with JS
     //    execution (which hurts benchmarketing).
@@ -2083,16 +2116,18 @@ ScriptSource::setSourceCopy(ExclusiveCon
         HelperThreadState().threadCount >= 2 &&
         CanUseExtraThreads();
     const size_t TINY_SCRIPT = 256;
     const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
     if (TINY_SCRIPT <= srcBuf.length() && srcBuf.length() < HUGE_SCRIPT && canCompressOffThread) {
         task->ss = this;
         if (!StartOffThreadCompression(cx, task))
             return false;
+    } else if (!ensureOwnsSource(cx)) {
+        return false;
     }
 
     return true;
 }
 
 SourceCompressionTask::ResultType
 SourceCompressionTask::work()
 {
@@ -2135,29 +2170,71 @@ SourceCompressionTask::work()
           case Compressor::DONE:
             cont = false;
             break;
           case Compressor::OOM:
             return OOM;
         }
     }
     compressedBytes = comp.outWritten();
-    compressedHash = mozilla::HashBytes(compressed, compressedBytes);
+    compressedHash = CompressedSourceHasher::computeHash(compressed, compressedBytes);
 
     // Shrink the buffer to the size of the compressed data.
     if (void* newCompressed = js_realloc(compressed, compressedBytes))
         compressed = newCompressed;
 
     return Success;
 }
 
+ScriptSource::~ScriptSource()
+{
+    struct DestroyMatcher
+    {
+        using ReturnType = void;
+
+        ScriptSource& ss;
+
+        explicit DestroyMatcher(ScriptSource& ss)
+          : ss(ss)
+        { }
+
+        ReturnType match(Uncompressed& u) {
+            if (u.ownsChars)
+                js_free(const_cast<char16_t*>(u.chars));
+        }
+
+        ReturnType match(Compressed& c) {
+            if (ss.inCompressedSourceSet)
+                TlsPerThreadData.get()->runtimeFromMainThread()->compressedSourceSet.remove(&ss);
+            js_free(c.raw);
+        }
+
+        ReturnType match(Parent& p) {
+            p.parent->decref();
+        }
+
+        ReturnType match(Missing&) {
+            // Nothing to do here.
+        }
+    };
+
+    MOZ_ASSERT_IF(inCompressedSourceSet, data.is<Compressed>());
+
+    DestroyMatcher dm(*this);
+    data.match(dm);
+}
+
 void
 ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                      JS::ScriptSourceInfo* info) const
 {
+    if (data.is<Uncompressed>() && ownsUncompressedChars())
+        info->uncompressed += mallocSizeOf(uncompressedChars());
+    else if (data.is<Compressed>())
+        info->compressed += mallocSizeOf(compressedData());
     info->misc += mallocSizeOf(this) +
                   mallocSizeOf(filename_.get()) +
                   mallocSizeOf(introducerFilename_.get());
     info->numScripts++;
 }
 
 template<XDRMode mode>
 bool
@@ -2167,35 +2244,43 @@ ScriptSource::performXDR(XDRState<mode>*
     {
         using ReturnType = size_t;
 
         ReturnType match(Uncompressed&) {
             return 0;
         }
 
         ReturnType match(Compressed& c) {
-            return c.nbytes();
+            return c.nbytes;
+        }
+
+        ReturnType match(Parent& p) {
+            return p.parent->data.match(*this);
         }
 
         ReturnType match(Missing&) {
             MOZ_CRASH("Missing source data in ScriptSource::performXDR");
             return 0;
         }
     };
 
     struct RawDataMatcher
     {
         using ReturnType = void*;
 
         ReturnType match(Uncompressed& u) {
-            return (void*) u.string.chars();
+            return (void*) u.chars;
         }
 
         ReturnType match(Compressed& c) {
-            return (void*) c.raw.chars();
+            return c.raw;
+        }
+
+        ReturnType match(Parent& p) {
+            return p.parent->data.match(*this);
         }
 
         ReturnType match(Missing&) {
             MOZ_CRASH("Missing source data in ScriptSource::performXDR");
             return nullptr;
         }
     };
 
@@ -2204,20 +2289,17 @@ ScriptSource::performXDR(XDRState<mode>*
         return false;
 
     uint8_t retrievable = sourceRetrievable_;
     if (!xdr->codeUint8(&retrievable))
         return false;
     sourceRetrievable_ = retrievable;
 
     if (hasSource && !sourceRetrievable_) {
-        uint32_t len = 0;
-        if (mode == XDR_ENCODE)
-            len = length();
-        if (!xdr->codeUint32(&len))
+        if (!xdr->codeUint32(&length_))
             return false;
 
         uint32_t compressedLength;
         if (mode == XDR_ENCODE) {
             CompressedLengthMatcher m;
             compressedLength = data.match(m);
         }
         if (!xdr->codeUint32(&compressedLength))
@@ -2228,45 +2310,29 @@ ScriptSource::performXDR(XDRState<mode>*
             if (mode == XDR_ENCODE)
                 argumentsNotIncluded = argumentsNotIncluded_;
             if (!xdr->codeUint8(&argumentsNotIncluded))
                 return false;
             if (mode == XDR_DECODE)
                 argumentsNotIncluded_ = argumentsNotIncluded;
         }
 
-        size_t byteLen = compressedLength ? compressedLength : (len * sizeof(char16_t));
+        size_t byteLen = compressedLength ? compressedLength : (length_ * sizeof(char16_t));
         if (mode == XDR_DECODE) {
             uint8_t* p = xdr->cx()->template pod_malloc<uint8_t>(Max<size_t>(byteLen, 1));
             if (!p || !xdr->codeBytes(p, byteLen)) {
                 js_free(p);
                 return false;
             }
 
-            if (compressedLength) {
-                mozilla::UniquePtr<char[], JS::FreePolicy> compressedSource(
-                    reinterpret_cast<char*>(p));
-                auto& cache = xdr->cx()->runtime()->sharedImmutableStrings();
-                auto deduped = cache.getOrCreate(mozilla::Move(compressedSource), byteLen);
-                if (!deduped) {
-                    ReportOutOfMemory(xdr->cx());
-                    return false;
-                }
-                setCompressedSource(mozilla::Move(*deduped), len);
-            } else {
-                mozilla::UniquePtr<char16_t[], JS::FreePolicy> source(
-                    reinterpret_cast<char16_t*>(p));
-                auto& cache = xdr->cx()->runtime()->sharedImmutableStrings();
-                auto deduped = cache.getOrCreate(mozilla::Move(source), len);
-                if (!deduped) {
-                    ReportOutOfMemory(xdr->cx());
-                    return false;
-                }
-                setSource(mozilla::Move(*deduped));
-            }
+            if (compressedLength)
+                setCompressedSource(xdr->cx()->runtime(), p, compressedLength,
+                                    CompressedSourceHasher::computeHash(p, compressedLength));
+            else
+                setSource((const char16_t*) p, length_);
         } else {
             RawDataMatcher rdm;
             void* p = data.match(rdm);
             if (!xdr->codeBytes(p, byteLen))
                 return false;
         }
     }
 
@@ -2437,16 +2503,26 @@ ScriptSource::setSourceMapURL(ExclusiveC
     size_t len = js_strlen(sourceMapURL) + 1;
     if (len == 1)
         return true;
 
     sourceMapURL_ = DuplicateString(cx, sourceMapURL);
     return sourceMapURL_ != nullptr;
 }
 
+size_t
+ScriptSource::computedSizeOfData() const
+{
+    if (data.is<Uncompressed>() && ownsUncompressedChars())
+        return sizeof(char16_t) * length_;
+    if (data.is<Compressed>())
+        return compressedBytes();
+    return 0;
+}
+
 /*
  * Shared script data management.
  */
 
 SharedScriptData*
 js::SharedScriptData::new_(ExclusiveContext* cx, uint32_t codeLength,
                            uint32_t srcnotesLength, uint32_t natoms)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -21,17 +21,16 @@
 
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "vm/NativeObject.h"
 #include "vm/Shape.h"
-#include "vm/SharedImmutableStringsCache.h"
 
 namespace JS {
 struct ScriptSourceInfo;
 } // namespace JS
 
 namespace js {
 
 namespace jit {
@@ -612,39 +611,52 @@ class ScriptSource
     // on the main thread.
 
     // Indicate which field in the |data| union is active.
 
     struct Missing { };
 
     struct Uncompressed
     {
-        SharedImmutableTwoByteString string;
-
-        explicit Uncompressed(SharedImmutableTwoByteString&& str)
-          : string(mozilla::Move(str))
+        Uncompressed(const char16_t* chars, bool ownsChars)
+          : chars(chars)
+          , ownsChars(ownsChars)
         { }
+
+        const char16_t* chars;
+        bool ownsChars;
     };
 
     struct Compressed
     {
-        SharedImmutableString raw;
-        size_t length;
-
-        Compressed(SharedImmutableString&& raw, size_t length)
-          : raw(mozilla::Move(raw))
-          , length(length)
+        Compressed(void* raw, size_t nbytes, HashNumber hash)
+          : raw(raw)
+          , nbytes(nbytes)
+          , hash(hash)
         { }
 
-        size_t nbytes() const { return raw.length(); }
+        void* raw;
+        size_t nbytes;
+        HashNumber hash;
     };
 
-    using SourceType = mozilla::Variant<Missing, Uncompressed, Compressed>;
+    struct Parent
+    {
+        explicit Parent(ScriptSource* parent)
+          : parent(parent)
+        { }
+
+        ScriptSource* parent;
+    };
+
+    using SourceType = mozilla::Variant<Missing, Uncompressed, Compressed, Parent>;
     SourceType data;
 
+    uint32_t length_;
+
     // The filename of this script.
     UniqueChars filename_;
 
     UniqueTwoByteChars displayURL_;
     UniqueTwoByteChars sourceMapURL_;
     bool mutedErrors_;
 
     // bytecode offset in caller script that generated this code.
@@ -679,96 +691,95 @@ class ScriptSource
 
     // True if we can call JSRuntime::sourceHook to load the source on
     // demand. If sourceRetrievable_ and hasSourceData() are false, it is not
     // possible to get source at all.
     bool sourceRetrievable_:1;
     bool argumentsNotIncluded_:1;
     bool hasIntroductionOffset_:1;
 
+    // Whether this is in the runtime's set of compressed ScriptSources.
+    bool inCompressedSourceSet:1;
+
   public:
     explicit ScriptSource()
       : refs(0),
         data(SourceType(Missing())),
+        length_(0),
         filename_(nullptr),
         displayURL_(nullptr),
         sourceMapURL_(nullptr),
         mutedErrors_(false),
         introductionOffset_(0),
         introducerFilename_(nullptr),
         introductionType_(nullptr),
         sourceRetrievable_(false),
         argumentsNotIncluded_(false),
-        hasIntroductionOffset_(false)
+        hasIntroductionOffset_(false),
+        inCompressedSourceSet(false)
     {
     }
-
+    ~ScriptSource();
     void incref() { refs++; }
     void decref() {
         MOZ_ASSERT(refs != 0);
         if (--refs == 0)
             js_delete(this);
     }
     bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options);
     bool setSourceCopy(ExclusiveContext* cx,
                        JS::SourceBufferHolder& srcBuf,
                        bool argumentsNotIncluded,
                        SourceCompressionTask* tok);
     void setSourceRetrievable() { sourceRetrievable_ = true; }
     bool sourceRetrievable() const { return sourceRetrievable_; }
     bool hasSourceData() const { return !data.is<Missing>(); }
     bool hasCompressedSource() const { return data.is<Compressed>(); }
-
     size_t length() const {
-        struct LengthMatcher
-        {
-            using ReturnType = size_t;
-
-            ReturnType match(const Uncompressed& u) {
-                return u.string.length();
-            }
-
-            ReturnType match(const Compressed& c) {
-                return c.length;
-            }
-
-            ReturnType match(const Missing& m) {
-                MOZ_CRASH("ScriptSource::length on a missing source");
-                return 0;
-            }
-        };
-
         MOZ_ASSERT(hasSourceData());
-        return data.match(LengthMatcher());
+        return length_;
     }
-
     bool argumentsNotIncluded() const {
         MOZ_ASSERT(hasSourceData());
         return argumentsNotIncluded_;
     }
     const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp);
     JSFlatString* substring(JSContext* cx, uint32_t start, uint32_t stop);
     JSFlatString* substringDontDeflate(JSContext* cx, uint32_t start, uint32_t stop);
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 JS::ScriptSourceInfo* info) const;
 
     const char16_t* uncompressedChars() const {
-        return data.as<Uncompressed>().string.chars();
+        return data.as<Uncompressed>().chars;
     }
 
-    const void* compressedData() const {
-        return static_cast<const void*>(data.as<Compressed>().raw.chars());
+    bool ownsUncompressedChars() const {
+        return data.as<Uncompressed>().ownsChars;
+    }
+
+    void* compressedData() const {
+        return data.as<Compressed>().raw;
     }
 
     size_t compressedBytes() const {
-        return data.as<Compressed>().nbytes();
+        return data.as<Compressed>().nbytes;
+    }
+
+    HashNumber compressedHash() const {
+        return data.as<Compressed>().hash;
     }
 
-    void setSource(SharedImmutableTwoByteString&& string);
-    void setCompressedSource(SharedImmutableString&& raw, size_t length);
+    ScriptSource* parent() const {
+        return data.as<Parent>().parent;
+    }
+
+    void setSource(const char16_t* chars, size_t length, bool ownsChars = true);
+    void setCompressedSource(JSRuntime* maybert, void* raw, size_t nbytes, HashNumber hash);
+    void updateCompressedSourceSet(JSRuntime* rt);
+    bool ensureOwnsSource(ExclusiveContext* cx);
 
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode>* xdr);
 
     bool setFilename(ExclusiveContext* cx, const char* filename);
     const char* introducerFilename() const {
         return introducerFilename_ ? introducerFilename_.get() : filename_.get();
@@ -808,16 +819,19 @@ class ScriptSource
         return introductionOffset_;
     }
     void setIntroductionOffset(uint32_t offset) {
         MOZ_ASSERT(!hasIntroductionOffset());
         MOZ_ASSERT(offset <= (uint32_t)INT32_MAX);
         introductionOffset_ = offset;
         hasIntroductionOffset_ = true;
     }
+
+  private:
+    size_t computedSizeOfData() const;
 };
 
 class ScriptSourceHolder
 {
     ScriptSource* ss;
   public:
     ScriptSourceHolder()
       : ss(nullptr)
@@ -838,16 +852,37 @@ class ScriptSourceHolder
         ss = newss;
         ss->incref();
     }
     ScriptSource* get() const {
         return ss;
     }
 };
 
+struct CompressedSourceHasher
+{
+    typedef ScriptSource* Lookup;
+
+    static HashNumber computeHash(const void* data, size_t nbytes) {
+        return mozilla::HashBytes(data, nbytes);
+    }
+
+    static HashNumber hash(const ScriptSource* ss) {
+        return ss->compressedHash();
+    }
+
+    static bool match(const ScriptSource* a, const ScriptSource* b) {
+        return a->compressedBytes() == b->compressedBytes() &&
+               a->compressedHash() == b->compressedHash() &&
+               !memcmp(a->compressedData(), b->compressedData(), a->compressedBytes());
+    }
+};
+
+typedef HashSet<ScriptSource*, CompressedSourceHasher, SystemAllocPolicy> CompressedSourceSet;
+
 class ScriptSourceObject : public NativeObject
 {
   public:
     static const Class class_;
 
     static void trace(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
     static ScriptSourceObject* create(ExclusiveContext* cx, ScriptSource* source);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -4742,41 +4742,24 @@ js::DuplicateString(js::ExclusiveContext
 }
 
 UniqueChars
 js::DuplicateString(const char* s)
 {
     return UniqueChars(js_strdup(s));
 }
 
-UniqueChars
-js::DuplicateString(const char* s, size_t n)
+UniqueTwoByteChars
+js::DuplicateString(const char16_t* s)
 {
-    UniqueChars ret(js_pod_malloc<char>(n + 1));
+    size_t n = js_strlen(s) + 1;
+    UniqueTwoByteChars ret(js_pod_malloc<char16_t>(n));
     if (!ret)
         return nullptr;
     PodCopy(ret.get(), s, n);
-    ret[n] = 0;
-    return ret;
-}
-
-UniqueTwoByteChars
-js::DuplicateString(const char16_t* s)
-{
-    return DuplicateString(s, js_strlen(s));
-}
-
-UniqueTwoByteChars
-js::DuplicateString(const char16_t* s, size_t n)
-{
-    UniqueTwoByteChars ret(js_pod_malloc<char16_t>(n + 1));
-    if (!ret)
-        return nullptr;
-    PodCopy(ret.get(), s, n);
-    ret[n] = 0;
     return ret;
 }
 
 template <typename CharT>
 const CharT*
 js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)
 {
     while (s < limit) {
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -131,25 +131,19 @@ DuplicateString(ExclusiveContext* cx, co
 
 /*
  * These variants do not report OOMs, you must arrange for OOMs to be reported
  * yourself.
  */
 extern UniqueChars
 DuplicateString(const char* s);
 
-extern UniqueChars
-DuplicateString(const char* s, size_t n);
-
 extern UniqueTwoByteChars
 DuplicateString(const char16_t* s);
 
-extern UniqueTwoByteChars
-DuplicateString(const char16_t* s, size_t n);
-
 /*
  * Convert a non-string value to a string, returning null after reporting an
  * error, otherwise returning a new string reference.
  */
 template <AllowGC allowGC>
 extern JSString*
 ToStringSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg);
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -333,17 +333,16 @@ UNIFIED_SOURCES += [
     'vm/RegExpObject.cpp',
     'vm/RegExpStatics.cpp',
     'vm/Runtime.cpp',
     'vm/SavedStacks.cpp',
     'vm/ScopeObject.cpp',
     'vm/SelfHosting.cpp',
     'vm/Shape.cpp',
     'vm/SharedArrayObject.cpp',
-    'vm/SharedImmutableStringsCache.cpp',
     'vm/SPSProfiler.cpp',
     'vm/Stack.cpp',
     'vm/Stopwatch.cpp',
     'vm/String.cpp',
     'vm/StringBuffer.cpp',
     'vm/StructuredClone.cpp',
     'vm/Symbol.cpp',
     'vm/TaggedProto.cpp',
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -11,17 +11,16 @@
 #include "jsnativestack.h"
 #include "jsnum.h" // For FIX_FPU()
 
 #include "asmjs/WasmIonCompile.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "jit/IonBuilder.h"
 #include "vm/Debugger.h"
-#include "vm/SharedImmutableStringsCache.h"
 #include "vm/Time.h"
 #include "vm/TraceLogging.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
@@ -1187,16 +1186,21 @@ GlobalHelperThreadState::finishParseTask
         // memory.
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     // The Debugger only needs to be told about the topmost script that was compiled.
     Debugger::onNewScript(cx, script);
 
+    // Update the compressed source table with the result. This is normally
+    // called by setCompressedSource when compilation occurs on the main thread.
+    if (script->scriptSource()->hasCompressedSource())
+        script->scriptSource()->updateCompressedSourceSet(rt);
+
     return script;
 }
 
 JSScript*
 GlobalHelperThreadState::finishScriptParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
 {
     JSScript* script = finishParseTask(maybecx, rt, ParseTaskKind::Script, token);
     MOZ_ASSERT_IF(script, script->isGlobalCode());
@@ -1612,35 +1616,28 @@ SourceCompressionTask::complete()
 
     {
         AutoLockHelperThreadState lock;
         while (HelperThreadState().compressionInProgress(this))
             HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
     }
 
     if (result == Success) {
-        mozilla::UniquePtr<char[], JS::FreePolicy> compressedSource(
-            reinterpret_cast<char*>(compressed));
-        compressed = nullptr;
+        ss->setCompressedSource(cx->isJSContext() ? cx->asJSContext()->runtime() : nullptr,
+                                compressed, compressedBytes, compressedHash);
 
-        auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
-        auto deduped = cache.getOrCreate(mozilla::Move(compressedSource), compressedBytes);
-        if (!deduped) {
-            ReportOutOfMemory(cx);
-            result = OOM;
-            ss = nullptr;
-            return false;
-        }
-
-        ss->setCompressedSource(mozilla::Move(*deduped), ss->length());
+        // Update memory accounting.
+        cx->updateMallocCounter(ss->computedSizeOfData());
     } else {
         js_free(compressed);
 
         if (result == OOM)
             ReportOutOfMemory(cx);
+        else if (result == Aborted && !ss->ensureOwnsSource(cx))
+            result = OOM;
     }
 
     ss = nullptr;
     compressed = nullptr;
     MOZ_ASSERT(!active());
 
     return result != OOM;
 }
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -448,16 +448,17 @@ StatsCellCallback(JSRuntime* rt, void* d
         ScriptSource* ss = script->scriptSource();
         SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss);
         if (!entry) {
             bool ok = closure->seenSources.add(entry, ss);
             (void)ok; // Not much to be done on failure.
 
             JS::ScriptSourceInfo info;  // This zeroes all the sizes.
             ss->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &info);
+            MOZ_ASSERT(info.compressed == 0 || info.uncompressed == 0);
 
             rtStats->runtime.scriptSourceInfo.add(info);
 
             if (granularity == FineGrained) {
                 const char* filename = ss->filename();
                 if (!filename)
                     filename = "<no filename>";
 
@@ -920,8 +921,9 @@ AddServoSizeOf(JSRuntime *rt, MallocSize
                          sizes->gcHeapDecommitted;
     MOZ_ASSERT(rtStats.gcHeapChunkTotal == gcHeapTotal - gcHeapTotalOriginal);
 #endif
 
     return true;
 }
 
 } // namespace JS
+
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -326,16 +326,19 @@ JSRuntime::init(uint32_t maxbytes, uint3
         return false;
 
     if (!scriptDataTable_.init())
         return false;
 
     if (!evalCache.init())
         return false;
 
+    if (!compressedSourceSet.init())
+        return false;
+
     /* The garbage collector depends on everything before this point being initialized. */
     gcInitialized = true;
 
     if (!InitRuntimeNumberState(this))
         return false;
 
     JS::ResetTimeZone();
 
@@ -531,20 +534,19 @@ JSRuntime::addSizeOfIncludingThis(mozill
         rtSizes->contexts += acx->sizeOfIncludingThis(mallocSizeOf);
 
     rtSizes->temporary += tempLifoAlloc.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->interpreterStack += interpreterStack_.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->mathCache += mathCache_ ? mathCache_->sizeOfIncludingThis(mallocSizeOf) : 0;
 
-    rtSizes->sharedImmutableStringsCache +=
-        sharedImmutableStrings_.sizeOfExcludingThis(mallocSizeOf);
+    rtSizes->uncompressedSourceCache += uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
 
-    rtSizes->uncompressedSourceCache += uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
+    rtSizes->compressedSourceSet += compressedSourceSet.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->scriptData += scriptDataTable().sizeOfExcludingThis(mallocSizeOf);
     for (ScriptDataTable::Range r = scriptDataTable().all(); !r.empty(); r.popFront())
         rtSizes->scriptData += mallocSizeOf(r.front());
 
     if (jitRuntime_) {
         jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
         jitRuntime_->backedgeExecAlloc().addSizeOfCode(&rtSizes->code);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -38,17 +38,16 @@
 # include "js/Proxy.h" // For AutoEnterPolicy
 #endif
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "vm/CodeCoverage.h"
 #include "vm/CommonPropertyNames.h"
 #include "vm/DateTime.h"
 #include "vm/MallocProvider.h"
-#include "vm/SharedImmutableStringsCache.h"
 #include "vm/SPSProfiler.h"
 #include "vm/Stack.h"
 #include "vm/Stopwatch.h"
 #include "vm/Symbol.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
@@ -1261,36 +1260,34 @@ struct JSRuntime : public JS::shadow::Ru
     const char*         thousandsSeparator;
     const char*         decimalSeparator;
     const char*         numGrouping;
 #endif
 
   private:
     js::MathCache* mathCache_;
     js::MathCache* createMathCache(JSContext* cx);
-    js::SharedImmutableStringsCache sharedImmutableStrings_;
   public:
     js::MathCache* getMathCache(JSContext* cx) {
         return mathCache_ ? mathCache_ : createMathCache(cx);
     }
     js::MathCache* maybeGetMathCache() {
         return mathCache_;
     }
-    js::SharedImmutableStringsCache& sharedImmutableStrings() {
-        return parentRuntime ? parentRuntime->sharedImmutableStrings() : sharedImmutableStrings_;
-    }
 
     js::GSNCache        gsnCache;
     js::ScopeCoordinateNameCache scopeCoordinateNameCache;
     js::NewObjectCache  newObjectCache;
     js::NativeIterCache nativeIterCache;
     js::UncompressedSourceCache uncompressedSourceCache;
     js::EvalCache       evalCache;
     js::LazyScriptCache lazyScriptCache;
 
+    js::CompressedSourceSet compressedSourceSet;
+
     // Pool of maps used during parse/emit. This may be modified by threads
     // with an ExclusiveContext and requires a lock. Active compilations
     // prevent the pool from being purged during GCs.
   private:
     js::frontend::ParseMapPool parseMapPool_;
     unsigned activeCompilations_;
   public:
     js::frontend::ParseMapPool& parseMapPool() {
deleted file mode 100644
--- a/js/src/vm/SharedImmutableStringsCache.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "vm/SharedImmutableStringsCache.h"
-
-#include "jsstr.h"
-
-namespace js {
-
-SharedImmutableString::~SharedImmutableString() {
-    MOZ_ASSERT(!!cache_ == !!chars_);
-    if (!cache_)
-        return;
-
-    MOZ_ASSERT(mozilla::HashString(chars(), length()) == hash_);
-    SharedImmutableStringsCache::Hasher::Lookup lookup(chars(), length());
-
-    auto locked = cache_->set_.lock();
-    auto entry = locked->lookup(lookup);
-
-    MOZ_ASSERT(entry);
-    MOZ_ASSERT(entry->refcount > 0);
-
-    entry->refcount--;
-    if (entry->refcount == 0)
-        locked->remove(entry);
-}
-
-SharedImmutableString
-SharedImmutableString::clone() const
-{
-    auto clone = cache_->getOrCreate(chars(), length(), [&]() {
-        MOZ_CRASH("Should not need to create an owned version, as this string is already in "
-                  "the cache");
-        return nullptr;
-    });
-    MOZ_ASSERT(clone.isSome());
-    return SharedImmutableString(mozilla::Move(*clone));
-}
-
-SharedImmutableTwoByteString
-SharedImmutableTwoByteString::clone() const
-{
-    return SharedImmutableTwoByteString(string_.clone());
-}
-
-MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableString>
-SharedImmutableStringsCache::getOrCreate(OwnedChars&& chars, size_t length)
-{
-    OwnedChars owned(mozilla::Move(chars));
-    return getOrCreate(owned.get(), length, [&]() { return mozilla::Move(owned); });
-}
-
-MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableString>
-SharedImmutableStringsCache::getOrCreate(const char* chars, size_t length)
-{
-    return getOrCreate(chars, length, [&]() { return DuplicateString(chars, length); });
-}
-
-MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableTwoByteString>
-SharedImmutableStringsCache::getOrCreate(OwnedTwoByteChars&& chars, size_t length)
-{
-    OwnedTwoByteChars owned(mozilla::Move(chars));
-    return getOrCreate(owned.get(), length, [&]() { return mozilla::Move(owned); });
-}
-
-MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableTwoByteString>
-SharedImmutableStringsCache::getOrCreate(const char16_t* chars, size_t length)
-{
-    return getOrCreate(chars, length, [&]() { return DuplicateString(chars, length); });
-}
-
-} // namespace js
deleted file mode 100644
--- a/js/src/vm/SharedImmutableStringsCache.h
+++ /dev/null
@@ -1,430 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef vm_SharedImmutableStringsCache_h
-#define vm_SharedImmutableStringsCache_h
-
-#include "mozilla/Maybe.h"
-#include "mozilla/UniquePtr.h"
-
-#include <cstring>
-#include <new> // for placement new
-
-#include "jsstr.h"
-
-#include "js/HashTable.h"
-#include "js/Utility.h"
-
-#include "threading/ExclusiveData.h"
-
-namespace js {
-
-class SharedImmutableStringsCache;
-class SharedImmutableTwoByteString;
-
-/**
- * The `SharedImmutableString` class holds a reference to a `const char*` string
- * from the `SharedImmutableStringsCache` and releases the reference upon
- * destruction.
- */
-class SharedImmutableString
-{
-    friend class SharedImmutableStringsCache;
-    friend class SharedImmutableTwoByteString;
-
-    // Never nullptr in a live instance. May be nullptr if this instance has
-    // been moved from.
-    SharedImmutableStringsCache* cache_;
-    const char* chars_;
-    size_t length_;
-#ifdef DEBUG
-    HashNumber hash_;
-#endif
-
-    SharedImmutableString(SharedImmutableStringsCache* cache, const char* chars, size_t length)
-      : cache_(cache)
-      , chars_(chars)
-      , length_(length)
-#ifdef DEBUG
-      , hash_(mozilla::HashString(chars, length))
-#endif
-    {
-        MOZ_ASSERT(cache && chars);
-    }
-
-  public:
-    /**
-     * `SharedImmutableString`s are move-able. It is an error to use a
-     * `SharedImmutableString` after it has been moved.
-     */
-    SharedImmutableString(SharedImmutableString&& rhs)
-      : cache_(rhs.cache_)
-      , chars_(rhs.chars_)
-      , length_(rhs.length_)
-#ifdef DEBUG
-      , hash_(mozilla::HashString(rhs.chars_, rhs.length_))
-#endif
-    {
-        MOZ_ASSERT(this != &rhs, "self move not allowed");
-        MOZ_ASSERT(rhs.cache_ && rhs.chars_);
-        MOZ_ASSERT(rhs.hash_ == hash_);
-
-        rhs.cache_ = nullptr;
-        rhs.chars_ = nullptr;
-    }
-    SharedImmutableString& operator=(SharedImmutableString&& rhs) {
-        this->~SharedImmutableString();
-        new (this) SharedImmutableString(mozilla::Move(rhs));
-        return *this;
-    }
-
-    /**
-     * Create another shared reference to the underlying string.
-     */
-    SharedImmutableString clone() const;
-
-    ~SharedImmutableString();
-
-    /**
-     * Get a raw pointer to the underlying string. It is only safe to use the
-     * resulting pointer while this `SharedImmutableString` exists.
-     */
-    const char* chars() const {
-        MOZ_ASSERT(cache_ && chars_);
-        return chars_;
-    }
-
-    /**
-     * Get the length of the underlying string.
-     */
-    size_t length() const {
-        MOZ_ASSERT(cache_ && chars_);
-        return length_;
-    }
-};
-
-/**
- * The `SharedImmutableTwoByteString` class holds a reference to a `const
- * char16_t*` string from the `SharedImmutableStringsCache` and releases the
- * reference upon destruction.
- */
-class SharedImmutableTwoByteString
-{
-    friend class SharedImmutableStringsCache;
-
-    // If a `char*` string and `char16_t*` string happen to have the same bytes,
-    // the bytes will be shared but handed out as different types.
-    SharedImmutableString string_;
-
-    explicit SharedImmutableTwoByteString(SharedImmutableString&& string)
-      : string_(mozilla::Move(string))
-    { }
-
-    SharedImmutableTwoByteString(SharedImmutableStringsCache* cache, const char* chars, size_t length)
-      : string_(cache, chars, length)
-    {
-        MOZ_ASSERT(length % sizeof(char16_t) == 0);
-    }
-
-  public:
-    /**
-     * `SharedImmutableTwoByteString`s are move-able. It is an error to use a
-     * `SharedImmutableTwoByteString` after it has been moved.
-     */
-    SharedImmutableTwoByteString(SharedImmutableTwoByteString&& rhs)
-      : string_(mozilla::Move(rhs.string_))
-    {
-        MOZ_ASSERT(this != &rhs, "self move not allowed");
-    }
-    SharedImmutableTwoByteString& operator=(SharedImmutableTwoByteString&& rhs) {
-        this->~SharedImmutableTwoByteString();
-        new (this) SharedImmutableTwoByteString(mozilla::Move(rhs));
-        return *this;
-    }
-
-    /**
-     * Create another shared reference to the underlying string.
-     */
-    SharedImmutableTwoByteString clone() const;
-
-    /**
-     * Get a raw pointer to the underlying string. It is only safe to use the
-     * resulting pointer while this `SharedImmutableTwoByteString` exists.
-     */
-    const char16_t* chars() const { return reinterpret_cast<const char16_t*>(string_.chars()); }
-
-    /**
-     * Get the length of the underlying string.
-     */
-    size_t length() const { return string_.length() / sizeof(char16_t); }
-};
-
-/**
- * The `SharedImmutableStringsCache` allows for safely sharing and deduplicating
- * immutable strings (either `const char*` or `const char16_t*`) between
- * threads.
- *
- * The locking mechanism is dead-simple and coarse grained: a single lock guards
- * all of the internal table itself, the table's entries, and the entries'
- * reference counts. It is only safe to perform any mutation on the cache or any
- * data stored within the cache when this lock is acquired.
- */
-class SharedImmutableStringsCache
-{
-    friend class SharedImmutableString;
-    struct Hasher;
-
-  public:
-    using OwnedChars = mozilla::UniquePtr<char[], JS::FreePolicy>;
-    using OwnedTwoByteChars = mozilla::UniquePtr<char16_t[], JS::FreePolicy>;
-
-    /**
-     * Get the canonical, shared, and de-duplicated version of the given `const
-     * char*` string. If such a string does not exist, call `intoOwnedChars` and
-     * add the string it returns to the cache.
-     *
-     * `intoOwnedChars` must create an owned version of the given string, and
-     * must have one of the following types:
-     *
-     *     mozilla::UniquePtr<char[], JS::FreePolicy>   intoOwnedChars();
-     *     mozilla::UniquePtr<char[], JS::FreePolicy>&& intoOwnedChars();
-     *
-     * It can be used by callers to elide a copy of the string when it is safe
-     * to give up ownership of the lookup string to the cache. It must return a
-     * `nullptr` on failure.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    template <typename IntoOwnedChars>
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableString>
-    getOrCreate(const char* chars, size_t length, IntoOwnedChars intoOwnedChars) {
-        Hasher::Lookup lookup(chars, length);
-
-        auto locked = set_.lock();
-        if (!locked->initialized() && !locked->init())
-            return mozilla::Nothing();
-
-        auto entry = locked->lookupForAdd(lookup);
-        if (!entry) {
-            OwnedChars ownedChars(intoOwnedChars());
-            if (!ownedChars)
-                return mozilla::Nothing();
-            MOZ_ASSERT(ownedChars.get() == chars ||
-                       memcmp(ownedChars.get(), chars, length) == 0);
-            StringBox box(mozilla::Move(ownedChars), length);
-            if (!locked->add(entry, mozilla::Move(box)))
-                return mozilla::Nothing();
-        }
-
-        MOZ_ASSERT(entry);
-        entry->refcount++;
-        return mozilla::Some(SharedImmutableString(this, entry->chars(),
-                                                   entry->length()));
-    }
-
-    /**
-     * Take ownership of the given `chars` and return the canonical, shared and
-     * de-duplicated version.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableString>
-    getOrCreate(OwnedChars&& chars, size_t length);
-
-    /**
-     * Do not take ownership of the given `chars`. Return the canonical, shared
-     * and de-duplicated version. If there is no extant shared version of
-     * `chars`, make a copy and insert it into the cache.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableString>
-    getOrCreate(const char* chars, size_t length);
-
-    /**
-     * Get the canonical, shared, and de-duplicated version of the given `const
-     * char16_t*` string. If such a string does not exist, call `intoOwnedChars`
-     * and add the string it returns to the cache.
-     *
-     * `intoOwnedTwoByteChars` must create an owned version of the given string,
-     * and must have one of the following types:
-     *
-     *     mozilla::UniquePtr<char16_t[], JS::FreePolicy>   intoOwnedTwoByteChars();
-     *     mozilla::UniquePtr<char16_t[], JS::FreePolicy>&& intoOwnedTwoByteChars();
-     *
-     * It can be used by callers to elide a copy of the string when it is safe
-     * to give up ownership of the lookup string to the cache. It must return a
-     * `nullptr` on failure.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    template <typename IntoOwnedTwoByteChars>
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableTwoByteString>
-    getOrCreate(const char16_t* chars, size_t length, IntoOwnedTwoByteChars intoOwnedTwoByteChars) {
-        Hasher::Lookup lookup(chars, length);
-
-        auto locked = set_.lock();
-        if (!locked->initialized() && !locked->init())
-            return mozilla::Nothing();
-
-        auto entry = locked->lookupForAdd(lookup);
-        if (!entry) {
-            OwnedTwoByteChars ownedTwoByteChars(intoOwnedTwoByteChars());
-            if (!ownedTwoByteChars)
-                return mozilla::Nothing();
-            MOZ_ASSERT(ownedTwoByteChars.get() == chars ||
-                       memcmp(ownedTwoByteChars.get(), chars, length * sizeof(char16_t)) == 0);
-            OwnedChars ownedChars(reinterpret_cast<char*>(ownedTwoByteChars.release()));
-            StringBox box(mozilla::Move(ownedChars), length * sizeof(char16_t));
-            if (!locked->add(entry, mozilla::Move(box)))
-                return mozilla::Nothing();
-        }
-
-        MOZ_ASSERT(entry);
-        entry->refcount++;
-        return mozilla::Some(SharedImmutableTwoByteString(this, entry->chars(),
-                                                          entry->length()));
-    }
-
-    /**
-     * Take ownership of the given `chars` and return the canonical, shared and
-     * de-duplicated version.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableTwoByteString>
-    getOrCreate(OwnedTwoByteChars&& chars, size_t length);
-
-    /**
-     * Do not take ownership of the given `chars`. Return the canonical, shared
-     * and de-duplicated version. If there is no extant shared version of
-     * `chars`, then make a copy and insert it into the cache.
-     *
-     * On success, `Some` is returned. In the case of OOM failure, `Nothing` is
-     * returned.
-     */
-    MOZ_WARN_UNUSED_RESULT mozilla::Maybe<SharedImmutableTwoByteString>
-    getOrCreate(const char16_t* chars, size_t length);
-
-    SharedImmutableStringsCache()
-      : set_(Set())
-    { }
-
-    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
-        size_t n = 0;
-
-        auto locked = set_.lock();
-        if (!locked->initialized())
-            return n;
-
-        // Size of the table.
-        n += locked->sizeOfExcludingThis(mallocSizeOf);
-
-        // Sizes of the strings.
-        for (auto r = locked->all(); !r.empty(); r.popFront())
-            n += mallocSizeOf(r.front().chars());
-
-        return n;
-    }
-
-  private:
-    class StringBox
-    {
-        OwnedChars chars_;
-        size_t length_;
-
-      public:
-        mutable size_t refcount;
-
-        StringBox(OwnedChars&& chars, size_t length)
-          : chars_(mozilla::Move(chars))
-          , length_(length)
-          , refcount(0)
-        {
-            MOZ_ASSERT(chars_);
-        }
-
-        StringBox(StringBox&& rhs)
-          : chars_(mozilla::Move(rhs.chars_))
-          , length_(rhs.length_)
-          , refcount(rhs.refcount)
-        {
-            MOZ_ASSERT(this != &rhs, "self move not allowed");
-            rhs.refcount = 0;
-        }
-
-        ~StringBox() {
-            MOZ_ASSERT(refcount == 0);
-        }
-
-        const char* chars() const { return chars_.get(); }
-        size_t length() const { return length_; }
-    };
-
-    struct Hasher
-    {
-        /**
-         * A structure used when querying for a `const char*` string in the cache.
-         */
-        class Lookup
-        {
-            friend struct Hasher;
-
-            const char* chars_;
-            size_t length_;
-
-          public:
-            Lookup(const char* chars, size_t length)
-              : chars_(chars)
-              , length_(length)
-            {
-                MOZ_ASSERT(chars_);
-            }
-
-            explicit Lookup(const char* chars)
-              : Lookup(chars, strlen(chars))
-            { }
-
-            Lookup(const char16_t* chars, size_t length)
-              : Lookup(reinterpret_cast<const char*>(chars), length * sizeof(char16_t))
-            { }
-
-            explicit Lookup(const char16_t* chars)
-              : Lookup(chars, js_strlen(chars))
-            { }
-        };
-
-        static HashNumber hash(const Lookup& lookup) {
-            MOZ_ASSERT(lookup.chars_);
-            return mozilla::HashString(lookup.chars_, lookup.length_);
-        }
-
-        static bool match(const StringBox& key, const Lookup& lookup) {
-            MOZ_ASSERT(lookup.chars_);
-            MOZ_ASSERT(key.chars());
-
-            if (key.length() != lookup.length_)
-                return false;
-
-            if (key.chars() == lookup.chars_)
-                return true;
-
-            return memcmp(key.chars(), lookup.chars_, key.length()) == 0;
-        }
-    };
-
-    using Set = HashSet<StringBox, Hasher, SystemAllocPolicy>;
-    ExclusiveData<Set> set_;
-};
-
-} // namespace js
-
-#endif // vm_SharedImmutableStringsCache_h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2412,16 +2412,28 @@ ReportCompartmentStats(const JS::Compart
 }
 
 static nsresult
 ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo,
                         const nsACString& path,
                         nsIHandleReportCallback* cb, nsISupports* closure,
                         size_t& rtTotal)
 {
+    if (scriptSourceInfo.compressed > 0) {
+        RREPORT_BYTES(path + NS_LITERAL_CSTRING("compressed"),
+            KIND_HEAP, scriptSourceInfo.compressed,
+            "Compressed JavaScript source code.");
+    }
+
+    if (scriptSourceInfo.uncompressed > 0) {
+        RREPORT_BYTES(path + NS_LITERAL_CSTRING("uncompressed"),
+            KIND_HEAP, scriptSourceInfo.uncompressed,
+            "Uncompressed JavaScript source code.");
+    }
+
     if (scriptSourceInfo.misc > 0) {
         RREPORT_BYTES(path + NS_LITERAL_CSTRING("misc"),
             KIND_HEAP, scriptSourceInfo.misc,
             "Miscellaneous data relating to JavaScript source code.");
     }
 
     return NS_OK;
 }
@@ -2482,24 +2494,24 @@ ReportJSRuntimeExplicitTreeStats(const J
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"),
         KIND_HEAP, rtStats.runtime.interpreterStack,
         "JS interpreter frames.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
         KIND_HEAP, rtStats.runtime.mathCache,
         "The math cache.");
 
-    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-immutable-strings-cache"),
-        KIND_HEAP, rtStats.runtime.sharedImmutableStringsCache,
-        "Immutable strings (such as JS scripts' source text) shared across all JSRuntimes.");
-
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/uncompressed-source-cache"),
         KIND_HEAP, rtStats.runtime.uncompressedSourceCache,
         "The uncompressed source code cache.");
 
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/compressed-source-sets"),
+        KIND_HEAP, rtStats.runtime.compressedSourceSet,
+        "The table indexing compressed source code in the runtime.");
+
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"),
         KIND_HEAP, rtStats.runtime.scriptData,
         "The table holding script data shared in the runtime.");
 
     nsCString nonNotablePath =
         rtPath + nsPrintfCString("runtime/script-sources/source(scripts=%d, <non-notable files>)/",
                                  rtStats.runtime.scriptSourceInfo.numScripts);