зеркало из https://github.com/mozilla/gecko-dev.git
Back out 735da799e3bb (bug 1211723) for assertion failures and crashes in SharedImmutableStringsCache
CLOSED TREE
This commit is contained in:
Родитель
adc89eea34
Коммит
4204667ede
|
@ -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()
|
||||
|
|
|
@ -78,7 +78,6 @@ UNIFIED_SOURCES += [
|
|||
'testScriptObject.cpp',
|
||||
'testSetProperty.cpp',
|
||||
'testSetPropertyIgnoringNamedGetter.cpp',
|
||||
'testSharedImmutableStringsCache.cpp',
|
||||
'testSourcePolicy.cpp',
|
||||
'testStringBuffer.cpp',
|
||||
'testStructuredClone.cpp',
|
||||
|
|
|
@ -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)
|
|
@ -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<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);
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
|
@ -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<Missing>());
|
||||
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<Missing>() || data.is<Uncompressed>());
|
||||
MOZ_ASSERT_IF(data.is<Uncompressed>(), data.as<Uncompressed>().string.length() == length);
|
||||
|
||||
data = SourceType(Compressed(mozilla::Move(raw), length));
|
||||
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::updateCompressedSourceSet(JSRuntime* rt)
|
||||
{
|
||||
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
|
||||
|
@ -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<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.
|
||||
|
@ -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<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());
|
||||
|
@ -2172,7 +2249,11 @@ ScriptSource::performXDR(XDRState<mode>* 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<mode>* 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<mode>* 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<mode>* 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<uint8_t>(Max<size_t>(byteLen, 1));
|
||||
if (!p || !xdr->codeBytes(p, byteLen)) {
|
||||
|
@ -2241,27 +2323,11 @@ ScriptSource::performXDR(XDRState<mode>* xdr)
|
|||
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);
|
||||
|
@ -2442,6 +2508,16 @@ ScriptSource::setSourceMapURL(ExclusiveContext* cx, const char16_t* 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.
|
||||
*/
|
||||
|
|
|
@ -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<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_;
|
||||
|
||||
|
@ -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<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_;
|
||||
|
@ -751,19 +748,33 @@ class ScriptSource
|
|||
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;
|
||||
}
|
||||
|
||||
void setSource(SharedImmutableTwoByteString&& string);
|
||||
void setCompressedSource(SharedImmutableString&& raw, size_t length);
|
||||
HashNumber compressedHash() const {
|
||||
return data.as<Compressed>().hash;
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -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<ScriptSource*, CompressedSourceHasher, SystemAllocPolicy> CompressedSourceSet;
|
||||
|
||||
class ScriptSourceObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -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<char>(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<char16_t>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.");
|
||||
|
|
Загрузка…
Ссылка в новой задаче