Back out 735da799e3bb (bug 1211723) for assertion failures and crashes in SharedImmutableStringsCache

CLOSED TREE
This commit is contained in:
Phil Ringnalda 2016-03-29 21:57:51 -07:00
Родитель adc89eea34
Коммит 4204667ede
16 изменённых файлов: 259 добавлений и 760 удалений

Просмотреть файл

@ -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.");