Bug 1020012 - Consolidate ScriptSources with the same source, r=luke.

This commit is contained in:
Brian Hackett 2014-06-21 10:39:04 -07:00
parent 40addacbce
commit 69dc66c545
10 changed files with 171 additions and 38 deletions

View File

@ -351,7 +351,8 @@ struct RuntimeSizes
macro(_, _, temporary) \
macro(_, _, interpreterStack) \
macro(_, _, mathCache) \
macro(_, _, sourceDataCache) \
macro(_, _, uncompressedSourceCache) \
macro(_, _, compressedSourceSet) \
macro(_, _, scriptData) \
RuntimeSizes()

View File

@ -1285,7 +1285,7 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti
JS_ASSERT(lazy->source()->hasSourceData());
// Parse and compile the script from source.
SourceDataCache::AutoHoldEntry holder;
UncompressedSourceCache::AutoHoldEntry holder;
const jschar *chars = lazy->source()->chars(cx, holder);
if (!chars)
return false;

View File

@ -2949,7 +2949,7 @@ PurgeRuntime(JSRuntime *rt)
rt->scopeCoordinateNameCache.purge();
rt->newObjectCache.purge();
rt->nativeIterCache.purge();
rt->sourceDataCache.purge();
rt->uncompressedSourceCache.purge();
rt->evalCache.clear();
if (!rt->hasActiveCompilations())

View File

@ -1369,13 +1369,13 @@ JSScript::sourceData(JSContext *cx)
return scriptSource()->substring(cx, sourceStart(), sourceEnd());
}
SourceDataCache::AutoHoldEntry::AutoHoldEntry()
UncompressedSourceCache::AutoHoldEntry::AutoHoldEntry()
: cache_(nullptr), source_(nullptr), charsToFree_(nullptr)
{
}
void
SourceDataCache::AutoHoldEntry::holdEntry(SourceDataCache *cache, ScriptSource *source)
UncompressedSourceCache::AutoHoldEntry::holdEntry(UncompressedSourceCache *cache, ScriptSource *source)
{
// Initialise the holder for a specific cache and script source. This will
// hold on to the cached source chars in the event that the cache is purged.
@ -1385,7 +1385,7 @@ SourceDataCache::AutoHoldEntry::holdEntry(SourceDataCache *cache, ScriptSource *
}
void
SourceDataCache::AutoHoldEntry::deferDelete(const jschar *chars)
UncompressedSourceCache::AutoHoldEntry::deferDelete(const jschar *chars)
{
// Take ownership of source chars now the cache is being purged. Remove our
// reference to the ScriptSource which might soon be destroyed.
@ -1395,7 +1395,7 @@ SourceDataCache::AutoHoldEntry::deferDelete(const jschar *chars)
charsToFree_ = chars;
}
SourceDataCache::AutoHoldEntry::~AutoHoldEntry()
UncompressedSourceCache::AutoHoldEntry::~AutoHoldEntry()
{
// The holder is going out of scope. If it has taken ownership of cached
// chars then delete them, otherwise unregister ourself with the cache.
@ -1409,7 +1409,7 @@ SourceDataCache::AutoHoldEntry::~AutoHoldEntry()
}
void
SourceDataCache::holdEntry(AutoHoldEntry &holder, ScriptSource *ss)
UncompressedSourceCache::holdEntry(AutoHoldEntry &holder, ScriptSource *ss)
{
JS_ASSERT(!holder_);
holder.holdEntry(this, ss);
@ -1417,14 +1417,14 @@ SourceDataCache::holdEntry(AutoHoldEntry &holder, ScriptSource *ss)
}
void
SourceDataCache::releaseEntry(AutoHoldEntry &holder)
UncompressedSourceCache::releaseEntry(AutoHoldEntry &holder)
{
JS_ASSERT(holder_ == &holder);
holder_ = nullptr;
}
const jschar *
SourceDataCache::lookup(ScriptSource *ss, AutoHoldEntry &holder)
UncompressedSourceCache::lookup(ScriptSource *ss, AutoHoldEntry &holder)
{
JS_ASSERT(!holder_);
if (!map_)
@ -1437,7 +1437,7 @@ SourceDataCache::lookup(ScriptSource *ss, AutoHoldEntry &holder)
}
bool
SourceDataCache::put(ScriptSource *ss, const jschar *str, AutoHoldEntry &holder)
UncompressedSourceCache::put(ScriptSource *ss, const jschar *str, AutoHoldEntry &holder)
{
JS_ASSERT(!holder_);
@ -1461,7 +1461,7 @@ SourceDataCache::put(ScriptSource *ss, const jschar *str, AutoHoldEntry &holder)
}
void
SourceDataCache::purge()
UncompressedSourceCache::purge()
{
if (!map_)
return;
@ -1481,7 +1481,7 @@ SourceDataCache::purge()
}
size_t
SourceDataCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
UncompressedSourceCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
{
size_t n = 0;
if (map_ && !map_->empty()) {
@ -1495,7 +1495,7 @@ SourceDataCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
}
const jschar *
ScriptSource::chars(JSContext *cx, SourceDataCache::AutoHoldEntry &holder)
ScriptSource::chars(JSContext *cx, UncompressedSourceCache::AutoHoldEntry &holder)
{
switch (dataType) {
case DataUncompressed:
@ -1503,7 +1503,7 @@ ScriptSource::chars(JSContext *cx, SourceDataCache::AutoHoldEntry &holder)
case DataCompressed: {
#ifdef USE_ZLIB
if (const jschar *decompressed = cx->runtime()->sourceDataCache.lookup(this, holder))
if (const jschar *decompressed = cx->runtime()->uncompressedSourceCache.lookup(this, holder))
return decompressed;
const size_t nbytes = sizeof(jschar) * (length_ + 1);
@ -1520,7 +1520,7 @@ ScriptSource::chars(JSContext *cx, SourceDataCache::AutoHoldEntry &holder)
decompressed[length_] = 0;
if (!cx->runtime()->sourceDataCache.put(this, decompressed, holder)) {
if (!cx->runtime()->uncompressedSourceCache.put(this, decompressed, holder)) {
JS_ReportOutOfMemory(cx);
js_free(decompressed);
return nullptr;
@ -1532,6 +1532,9 @@ ScriptSource::chars(JSContext *cx, SourceDataCache::AutoHoldEntry &holder)
#endif
}
case DataParent:
return parent()->chars(cx, holder);
default:
MOZ_CRASH();
}
@ -1541,7 +1544,7 @@ JSFlatString *
ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop)
{
JS_ASSERT(start <= stop);
SourceDataCache::AutoHoldEntry holder;
UncompressedSourceCache::AutoHoldEntry holder;
const jschar *chars = this->chars(cx, holder);
if (!chars)
return nullptr;
@ -1561,7 +1564,7 @@ ScriptSource::setSource(const jschar *chars, size_t length, bool ownsChars /* =
}
void
ScriptSource::setCompressedSource(void *raw, size_t nbytes)
ScriptSource::setCompressedSource(JSRuntime *maybert, void *raw, size_t nbytes, HashNumber hash)
{
JS_ASSERT(dataType == DataMissing || dataType == DataUncompressed);
if (dataType == DataUncompressed && ownsUncompressedChars())
@ -1570,6 +1573,33 @@ ScriptSource::setCompressedSource(void *raw, size_t nbytes)
dataType = DataCompressed;
data.compressed.raw = raw;
data.compressed.nbytes = nbytes;
data.compressed.hash = hash;
if (maybert)
updateCompressedSourceSet(maybert);
}
void
ScriptSource::updateCompressedSourceSet(JSRuntime *rt)
{
JS_ASSERT(dataType == DataCompressed);
JS_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());
dataType = DataParent;
data.parent = parent;
} else {
if (rt->compressedSourceSet.add(p, this))
inCompressedSourceSet = true;
}
}
bool
@ -1688,6 +1718,7 @@ SourceCompressionTask::work()
}
}
compressedBytes = comp.outWritten();
compressedHash = CompressedSourceHasher::computeHash(compressed, compressedBytes);
#else
MOZ_CRASH();
#endif
@ -1701,6 +1732,8 @@ SourceCompressionTask::work()
ScriptSource::~ScriptSource()
{
JS_ASSERT_IF(inCompressedSourceSet, dataType == DataCompressed);
switch (dataType) {
case DataUncompressed:
if (ownsUncompressedChars())
@ -1708,9 +1741,21 @@ ScriptSource::~ScriptSource()
break;
case DataCompressed:
// Script source references are only manipulated on the main thread,
// except during off thread parsing when the source may be created
// and used exclusively by the thread doing the parse. In this case the
// ScriptSource might be destroyed while off the main thread, but it
// will not have been added to the runtime's compressed source set
// until the parse is finished on the main thread.
if (inCompressedSourceSet)
TlsPerThreadData.get()->runtimeFromMainThread()->compressedSourceSet.remove(this);
js_free(compressedData());
break;
case DataParent:
parent()->decref();
break;
default:
break;
}
@ -1753,7 +1798,22 @@ ScriptSource::performXDR(XDRState<mode> *xdr)
if (!xdr->codeUint32(&length_))
return false;
uint32_t compressedLength = (dataType == DataCompressed) ? compressedBytes() : 0;
uint32_t compressedLength;
if (mode == XDR_ENCODE) {
switch (dataType) {
case DataUncompressed:
compressedLength = 0;
break;
case DataCompressed:
compressedLength = compressedBytes();
break;
case DataParent:
compressedLength = parent()->compressedBytes();
break;
default:
MOZ_CRASH();
}
}
if (!xdr->codeUint32(&compressedLength))
return false;
@ -1776,11 +1836,25 @@ ScriptSource::performXDR(XDRState<mode> *xdr)
}
if (compressedLength)
setCompressedSource(p, compressedLength);
setCompressedSource(xdr->cx()->runtime(), p, compressedLength,
CompressedSourceHasher::computeHash(p, compressedLength));
else
setSource((const jschar *) p, length_);
} else {
void *p = compressedLength ? compressedData() : (void *) uncompressedChars();
void *p;
switch (dataType) {
case DataUncompressed:
p = (void *) uncompressedChars();
break;
case DataCompressed:
p = compressedData();
break;
case DataParent:
p = parent()->compressedData();
break;
default:
MOZ_CRASH();
}
if (!xdr->codeBytes(p, byteLen))
return false;
}
@ -3874,7 +3948,7 @@ LazyScriptHashPolicy::match(JSScript *script, const Lookup &lookup)
return false;
}
SourceDataCache::AutoHoldEntry holder;
UncompressedSourceCache::AutoHoldEntry holder;
const jschar *scriptChars = script->scriptSource()->chars(cx, holder);
if (!scriptChars)

View File

@ -339,7 +339,7 @@ typedef HashMap<JSScript *,
class ScriptSource;
class SourceDataCache
class UncompressedSourceCache
{
typedef HashMap<ScriptSource *,
const jschar *,
@ -350,17 +350,17 @@ class SourceDataCache
// Hold an entry in the source data cache and prevent it from being purged on GC.
class AutoHoldEntry
{
SourceDataCache *cache_;
UncompressedSourceCache *cache_;
ScriptSource *source_;
const jschar *charsToFree_;
public:
explicit AutoHoldEntry();
~AutoHoldEntry();
private:
void holdEntry(SourceDataCache *cache, ScriptSource *source);
void holdEntry(UncompressedSourceCache *cache, ScriptSource *source);
void deferDelete(const jschar *chars);
ScriptSource *source() const { return source_; }
friend class SourceDataCache;
friend class UncompressedSourceCache;
};
private:
@ -368,7 +368,7 @@ class SourceDataCache
AutoHoldEntry *holder_;
public:
SourceDataCache() : map_(nullptr), holder_(nullptr) {}
UncompressedSourceCache() : map_(nullptr), holder_(nullptr) {}
const jschar *lookup(ScriptSource *ss, AutoHoldEntry &asp);
bool put(ScriptSource *ss, const jschar *chars, AutoHoldEntry &asp);
@ -396,7 +396,8 @@ class ScriptSource
enum {
DataMissing,
DataUncompressed,
DataCompressed
DataCompressed,
DataParent
} dataType;
union {
@ -408,7 +409,10 @@ class ScriptSource
struct {
void *raw;
size_t nbytes;
HashNumber hash;
} compressed;
ScriptSource *parent;
} data;
uint32_t length_;
@ -450,6 +454,9 @@ 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),
@ -464,7 +471,8 @@ class ScriptSource
introductionType_(nullptr),
sourceRetrievable_(false),
argumentsNotIncluded_(false),
hasIntroductionOffset_(false)
hasIntroductionOffset_(false),
inCompressedSourceSet(false)
{
}
~ScriptSource();
@ -482,6 +490,7 @@ class ScriptSource
void setSourceRetrievable() { sourceRetrievable_ = true; }
bool sourceRetrievable() const { return sourceRetrievable_; }
bool hasSourceData() const { return dataType != DataMissing; }
bool hasCompressedSource() const { return dataType == DataCompressed; }
size_t length() const {
JS_ASSERT(hasSourceData());
return length_;
@ -490,7 +499,7 @@ class ScriptSource
JS_ASSERT(hasSourceData());
return argumentsNotIncluded_;
}
const jschar *chars(JSContext *cx, SourceDataCache::AutoHoldEntry &asp);
const jschar *chars(JSContext *cx, UncompressedSourceCache::AutoHoldEntry &asp);
JSFlatString *substring(JSContext *cx, uint32_t start, uint32_t stop);
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
JS::ScriptSourceInfo *info) const;
@ -515,8 +524,19 @@ class ScriptSource
return data.compressed.nbytes;
}
HashNumber compressedHash() const {
JS_ASSERT(dataType == DataCompressed);
return data.compressed.hash;
}
ScriptSource *parent() const {
JS_ASSERT(dataType == DataParent);
return data.parent;
}
void setSource(const jschar *chars, size_t length, bool ownsChars = true);
void setCompressedSource(void *raw, size_t nbytes);
void setCompressedSource(JSRuntime *maybert, void *raw, size_t nbytes, HashNumber hash);
void updateCompressedSourceSet(JSRuntime *rt);
bool ensureOwnsSource(ExclusiveContext *cx);
// XDR handling
@ -581,6 +601,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 JSObject
{
public:

View File

@ -702,6 +702,11 @@ GlobalHelperThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void
// The NewScript hook needs to be called for all compiled scripts.
CallNewScriptHookForAllScripts(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;
@ -990,7 +995,8 @@ SourceCompressionTask::complete()
}
if (result == Success) {
ss->setCompressedSource(compressed, compressedBytes);
ss->setCompressedSource(cx->isJSContext() ? cx->asJSContext()->runtime() : nullptr,
compressed, compressedBytes, compressedHash);
// Update memory accounting.
cx->updateMallocCounter(ss->computedSizeOfData());

View File

@ -482,11 +482,12 @@ struct SourceCompressionTask
} result;
void *compressed;
size_t compressedBytes;
HashNumber compressedHash;
public:
explicit SourceCompressionTask(ExclusiveContext *cx)
: cx(cx), ss(nullptr), abort_(false),
result(OOM), compressed(nullptr), compressedBytes(0)
result(OOM), compressed(nullptr), compressedBytes(0), compressedHash(0)
{
#ifdef JS_THREADSAFE
helperThread = nullptr;

View File

@ -312,6 +312,9 @@ JSRuntime::init(uint32_t maxbytes)
if (!evalCache.init())
return false;
if (!compressedSourceSet.init())
return false;
/* The garbage collector depends on everything before this point being initialized. */
gcInitialized = true;
@ -511,7 +514,9 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
rtSizes->mathCache += mathCache_ ? mathCache_->sizeOfIncludingThis(mallocSizeOf) : 0;
rtSizes->sourceDataCache += sourceDataCache.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())

View File

@ -1091,10 +1091,11 @@ struct JSRuntime : public JS::shadow::Runtime,
js::ScopeCoordinateNameCache scopeCoordinateNameCache;
js::NewObjectCache newObjectCache;
js::NativeIterCache nativeIterCache;
js::SourceDataCache sourceDataCache;
js::UncompressedSourceCache uncompressedSourceCache;
js::EvalCache evalCache;
js::LazyScriptCache lazyScriptCache;
js::CompressedSourceSet compressedSourceSet;
js::DateTimeInfo dateTimeInfo;
// Pool of maps used during parse/emit. This may be modified by threads

View File

@ -2374,9 +2374,13 @@ ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
KIND_HEAP, rtStats.runtime.mathCache,
"The math cache.");
RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/source-data-cache"),
KIND_HEAP, rtStats.runtime.sourceDataCache,
"The source data cache, which holds decompressed script source code.");
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,