diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 8b7bba54ded3..a552d5e5245a 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -397,6 +397,8 @@ struct NotableStringInfo : public StringInfo struct ScriptSourceInfo { #define FOR_EACH_SIZE(macro) \ + macro(_, MallocHeap, compressed) \ + macro(_, MallocHeap, uncompressed) \ macro(_, MallocHeap, misc) ScriptSourceInfo() @@ -470,8 +472,8 @@ struct RuntimeSizes macro(_, MallocHeap, temporary) \ macro(_, MallocHeap, interpreterStack) \ macro(_, MallocHeap, mathCache) \ - macro(_, MallocHeap, sharedImmutableStringsCache) \ macro(_, MallocHeap, uncompressedSourceCache) \ + macro(_, MallocHeap, compressedSourceSet) \ macro(_, MallocHeap, scriptData) RuntimeSizes() diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index ed1ca36fd22e..dc0b1b5eb02c 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -78,7 +78,6 @@ UNIFIED_SOURCES += [ 'testScriptObject.cpp', 'testSetProperty.cpp', 'testSetPropertyIgnoringNamedGetter.cpp', - 'testSharedImmutableStringsCache.cpp', 'testSourcePolicy.cpp', 'testStringBuffer.cpp', 'testStructuredClone.cpp', diff --git a/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp b/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp deleted file mode 100644 index b346a35b7c9a..000000000000 --- 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(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 threads(cx); - CHECK(threads.reserve(NUM_THREADS)); - - for (auto i : mozilla::MakeRange(NUM_THREADS)) { - auto cacheAndIndex = js_new(&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) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index bd2d4ef600cc..11ad95926703 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -43,7 +43,6 @@ #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" @@ -768,23 +767,18 @@ CreateFunctionPrototype(JSContext* cx, JSProtoKey key) const char* rawSource = "() {\n}"; size_t sourceLen = strlen(rawSource); - mozilla::UniquePtr source(InflateString(cx, rawSource, &sourceLen)); + char16_t* source = InflateString(cx, rawSource, &sourceLen); if (!source) return nullptr; - ScriptSource* ss = cx->new_(); - 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_(); + 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); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 637644d3ca2b..80e4a9ab621b 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -47,7 +47,6 @@ #include "vm/Opcodes.h" #include "vm/SelfHosting.h" #include "vm/Shape.h" -#include "vm/SharedImmutableStringsCache.h" #include "vm/Xdr.h" #include "jsfuninlines.h" @@ -1793,16 +1792,7 @@ JSScript::loadSource(JSContext* cx, ScriptSource* ss, bool* worked) return false; if (!src) return true; - - mozilla::UniquePtr 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; } @@ -1958,7 +1948,7 @@ ScriptSource::chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holde { } ReturnType match(Uncompressed& u) { - return u.string.chars(); + return u.chars; } ReturnType match(Compressed& c) { @@ -1990,6 +1980,10 @@ ScriptSource::chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holde 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; @@ -2023,19 +2017,67 @@ ScriptSource::substringDontDeflate(JSContext* cx, uint32_t start, uint32_t stop) } void -ScriptSource::setSource(SharedImmutableTwoByteString&& string) +ScriptSource::setSource(const char16_t* chars, size_t length, bool ownsChars /* = true */) { MOZ_ASSERT(data.is()); - data = SourceType(Uncompressed(mozilla::Move(string))); + + data = SourceType(Uncompressed(chars, ownsChars)); + length_ = length; } void -ScriptSource::setCompressedSource(SharedImmutableString&& raw, size_t length) +ScriptSource::setCompressedSource(JSRuntime* maybert, void* raw, size_t nbytes, HashNumber hash) { MOZ_ASSERT(data.is() || data.is()); - MOZ_ASSERT_IF(data.is(), data.as().string.length() == length); - data = SourceType(Compressed(mozilla::Move(raw), length)); + if (data.is() && data.as().ownsChars) + js_free(const_cast(uncompressedChars())); + + data = SourceType(Compressed(raw, nbytes, hash)); + + if (maybert) + updateCompressedSourceSet(maybert); +} + +void +ScriptSource::updateCompressedSourceSet(JSRuntime* rt) +{ + MOZ_ASSERT(data.is()); + 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()); + if (ownsUncompressedChars()) + return true; + + char16_t* uncompressed = cx->zone()->pod_malloc(Max(length_, 1)); + if (!uncompressed) { + ReportOutOfMemory(cx); + return false; + } + PodCopy(uncompressed, uncompressedChars(), length_); + + data.as().chars = uncompressed; + data.as().ownsChars = true; + return true; } bool @@ -2045,17 +2087,8 @@ ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf, MOZ_ASSERT(!hasSourceData()); argumentsNotIncluded_ = argumentsNotIncluded; - auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings(); - auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&]() { - return srcBuf.ownsChars() - ? mozilla::UniquePtr(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. @@ -2088,6 +2121,8 @@ ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf, task->ss = this; if (!StartOffThreadCompression(cx, task)) return false; + } else if (!ensureOwnsSource(cx)) { + return false; } return true; @@ -2140,7 +2175,7 @@ SourceCompressionTask::work() } } 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)) @@ -2149,10 +2184,52 @@ SourceCompressionTask::work() 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(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()); + + DestroyMatcher dm(*this); + data.match(dm); +} + void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const { + if (data.is() && ownsUncompressedChars()) + info->uncompressed += mallocSizeOf(uncompressedChars()); + else if (data.is()) + info->compressed += mallocSizeOf(compressedData()); info->misc += mallocSizeOf(this) + mallocSizeOf(filename_.get()) + mallocSizeOf(introducerFilename_.get()); @@ -2172,7 +2249,11 @@ ScriptSource::performXDR(XDRState* xdr) } ReturnType match(Compressed& c) { - return c.nbytes(); + return c.nbytes; + } + + ReturnType match(Parent& p) { + return p.parent->data.match(*this); } ReturnType match(Missing&) { @@ -2186,11 +2267,15 @@ ScriptSource::performXDR(XDRState* xdr) 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&) { @@ -2209,10 +2294,7 @@ ScriptSource::performXDR(XDRState* xdr) 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; @@ -2233,7 +2315,7 @@ ScriptSource::performXDR(XDRState* xdr) 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(Max(byteLen, 1)); if (!p || !xdr->codeBytes(p, byteLen)) { @@ -2241,27 +2323,11 @@ ScriptSource::performXDR(XDRState* xdr) return false; } - if (compressedLength) { - mozilla::UniquePtr compressedSource( - reinterpret_cast(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 source( - reinterpret_cast(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); @@ -2442,6 +2508,16 @@ ScriptSource::setSourceMapURL(ExclusiveContext* cx, const char16_t* sourceMapURL return sourceMapURL_ != nullptr; } +size_t +ScriptSource::computedSizeOfData() const +{ + if (data.is() && ownsUncompressedChars()) + return sizeof(char16_t) * length_; + if (data.is()) + return compressedBytes(); + return 0; +} + /* * Shared script data management. */ diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 159dcfc50447..6e6564a4339b 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -26,7 +26,6 @@ #include "js/UniquePtr.h" #include "vm/NativeObject.h" #include "vm/Shape.h" -#include "vm/SharedImmutableStringsCache.h" namespace JS { struct ScriptSourceInfo; @@ -617,29 +616,42 @@ class ScriptSource 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; + struct Parent + { + explicit Parent(ScriptSource* parent) + : parent(parent) + { } + + ScriptSource* parent; + }; + + using SourceType = mozilla::Variant; SourceType data; + uint32_t length_; + // The filename of this script. UniqueChars filename_; @@ -684,10 +696,14 @@ class ScriptSource 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), @@ -697,10 +713,11 @@ class ScriptSource introductionType_(nullptr), sourceRetrievable_(false), argumentsNotIncluded_(false), - hasIntroductionOffset_(false) + hasIntroductionOffset_(false), + inCompressedSourceSet(false) { } - + ~ScriptSource(); void incref() { refs++; } void decref() { MOZ_ASSERT(refs != 0); @@ -716,30 +733,10 @@ class ScriptSource bool sourceRetrievable() const { return sourceRetrievable_; } bool hasSourceData() const { return !data.is(); } bool hasCompressedSource() const { return data.is(); } - 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_; @@ -751,19 +748,33 @@ class ScriptSource JS::ScriptSourceInfo* info) const; const char16_t* uncompressedChars() const { - return data.as().string.chars(); + return data.as().chars; } - const void* compressedData() const { - return static_cast(data.as().raw.chars()); + bool ownsUncompressedChars() const { + return data.as().ownsChars; + } + + void* compressedData() const { + return data.as().raw; } size_t compressedBytes() const { - return data.as().nbytes(); + return data.as().nbytes; } - void setSource(SharedImmutableTwoByteString&& string); - void setCompressedSource(SharedImmutableString&& raw, size_t length); + HashNumber compressedHash() const { + return data.as().hash; + } + + ScriptSource* parent() const { + return data.as().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 @@ -813,6 +824,9 @@ class ScriptSource introductionOffset_ = offset; hasIntroductionOffset_ = true; } + + private: + size_t computedSizeOfData() const; }; class ScriptSourceHolder @@ -843,6 +857,27 @@ class ScriptSourceHolder } }; +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 CompressedSourceSet; + class ScriptSourceObject : public NativeObject { public: diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 137e6eae82c3..33d5eac2cab3 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -4747,31 +4747,14 @@ js::DuplicateString(const char* s) return UniqueChars(js_strdup(s)); } -UniqueChars -js::DuplicateString(const char* s, size_t n) -{ - UniqueChars ret(js_pod_malloc(n + 1)); - 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(n + 1)); + size_t n = js_strlen(s) + 1; + UniqueTwoByteChars ret(js_pod_malloc(n)); if (!ret) return nullptr; PodCopy(ret.get(), s, n); - ret[n] = 0; return ret; } diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 624456390748..71981701da0c 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -136,15 +136,9 @@ DuplicateString(ExclusiveContext* cx, const char16_t* s); 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. diff --git a/js/src/moz.build b/js/src/moz.build index 79a94e314682..8fa4e86f6182 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -338,7 +338,6 @@ UNIFIED_SOURCES += [ 'vm/SelfHosting.cpp', 'vm/Shape.cpp', 'vm/SharedArrayObject.cpp', - 'vm/SharedImmutableStringsCache.cpp', 'vm/SPSProfiler.cpp', 'vm/Stack.cpp', 'vm/Stopwatch.cpp', diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 1b403cf473ad..b5e84b287360 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -16,7 +16,6 @@ #include "gc/GCInternals.h" #include "jit/IonBuilder.h" #include "vm/Debugger.h" -#include "vm/SharedImmutableStringsCache.h" #include "vm/Time.h" #include "vm/TraceLogging.h" @@ -1192,6 +1191,11 @@ GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, Pars // 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; } @@ -1617,25 +1621,18 @@ SourceCompressionTask::complete() } if (result == Success) { - mozilla::UniquePtr compressedSource( - reinterpret_cast(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; diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index a7cea689f793..ea1190149bb2 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -453,6 +453,7 @@ StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKin 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); @@ -925,3 +926,4 @@ AddServoSizeOf(JSRuntime *rt, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor *o } } // namespace JS + diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 38033cc10a48..765d21daeee4 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -331,6 +331,9 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) if (!evalCache.init()) return false; + if (!compressedSourceSet.init()) + return false; + /* The garbage collector depends on everything before this point being initialized. */ gcInitialized = true; @@ -536,11 +539,10 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim rtSizes->mathCache += mathCache_ ? mathCache_->sizeOfIncludingThis(mallocSizeOf) : 0; - rtSizes->sharedImmutableStringsCache += - sharedImmutableStrings_.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()); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 36080af22d2d..4dbb89e0ac39 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -43,7 +43,6 @@ #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" @@ -1266,7 +1265,6 @@ struct JSRuntime : public JS::shadow::Runtime, private: js::MathCache* mathCache_; js::MathCache* createMathCache(JSContext* cx); - js::SharedImmutableStringsCache sharedImmutableStrings_; public: js::MathCache* getMathCache(JSContext* cx) { return mathCache_ ? mathCache_ : createMathCache(cx); @@ -1274,9 +1272,6 @@ struct JSRuntime : public JS::shadow::Runtime, js::MathCache* maybeGetMathCache() { return mathCache_; } - js::SharedImmutableStringsCache& sharedImmutableStrings() { - return parentRuntime ? parentRuntime->sharedImmutableStrings() : sharedImmutableStrings_; - } js::GSNCache gsnCache; js::ScopeCoordinateNameCache scopeCoordinateNameCache; @@ -1286,6 +1281,8 @@ struct JSRuntime : public JS::shadow::Runtime, 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. diff --git a/js/src/vm/SharedImmutableStringsCache.cpp b/js/src/vm/SharedImmutableStringsCache.cpp deleted file mode 100644 index 4d355bb667d7..000000000000 --- 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 -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 -SharedImmutableStringsCache::getOrCreate(const char* chars, size_t length) -{ - return getOrCreate(chars, length, [&]() { return DuplicateString(chars, length); }); -} - -MOZ_WARN_UNUSED_RESULT mozilla::Maybe -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 -SharedImmutableStringsCache::getOrCreate(const char16_t* chars, size_t length) -{ - return getOrCreate(chars, length, [&]() { return DuplicateString(chars, length); }); -} - -} // namespace js diff --git a/js/src/vm/SharedImmutableStringsCache.h b/js/src/vm/SharedImmutableStringsCache.h deleted file mode 100644 index 39f6939cb68b..000000000000 --- 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 -#include // 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(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; - using OwnedTwoByteChars = mozilla::UniquePtr; - - /** - * 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 intoOwnedChars(); - * mozilla::UniquePtr&& 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 - MOZ_WARN_UNUSED_RESULT mozilla::Maybe - 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 - 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 - 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 intoOwnedTwoByteChars(); - * mozilla::UniquePtr&& 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 - MOZ_WARN_UNUSED_RESULT mozilla::Maybe - 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(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 - 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 - 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(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; - ExclusiveData set_; -}; - -} // namespace js - -#endif // vm_SharedImmutableStringsCache_h diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index a9b8726e9d27..c9ca4d968d64 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2417,6 +2417,18 @@ ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo, 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, @@ -2487,14 +2499,14 @@ ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, 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.");