зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1348134 - Handle compression tasks with major GCs instead of eagerly. (r=sfink,jonco)
This commit is contained in:
Родитель
8f1a38f1dd
Коммит
9ca2bfcb81
|
@ -51,11 +51,13 @@ class MOZ_STACK_CLASS BytecodeCompiler
|
|||
const Maybe<uint32_t>& parameterListEnd);
|
||||
|
||||
ScriptSourceObject* sourceObjectPtr() const;
|
||||
SourceCompressionTask* sourceCompressionTask() const;
|
||||
|
||||
private:
|
||||
JSScript* compileScript(HandleObject environment, SharedContext* sc);
|
||||
bool checkLength();
|
||||
bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
|
||||
bool enqueueOffThreadSourceCompression();
|
||||
bool canLazilyParse();
|
||||
bool createParser();
|
||||
bool createSourceAndParser(const Maybe<uint32_t>& parameterListEnd = Nothing());
|
||||
|
@ -75,6 +77,7 @@ class MOZ_STACK_CLASS BytecodeCompiler
|
|||
|
||||
RootedScriptSource sourceObject;
|
||||
ScriptSource* scriptSource;
|
||||
SourceCompressionTask* sourceCompressionTask_;
|
||||
|
||||
Maybe<UsedNameTracker> usedNames;
|
||||
Maybe<Parser<SyntaxParseHandler>> syntaxParser;
|
||||
|
@ -164,6 +167,7 @@ BytecodeCompiler::BytecodeCompiler(JSContext* cx,
|
|||
enclosingScope(cx, enclosingScope),
|
||||
sourceObject(cx),
|
||||
scriptSource(nullptr),
|
||||
sourceCompressionTask_(nullptr),
|
||||
directives(options.strictOption),
|
||||
startPosition(keepAtoms),
|
||||
script(cx)
|
||||
|
@ -199,10 +203,45 @@ BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd)
|
|||
scriptSource = sourceObject->source();
|
||||
|
||||
if (!cx->compartment()->behaviors().discardSource()) {
|
||||
if (options.sourceIsLazy)
|
||||
if (options.sourceIsLazy) {
|
||||
scriptSource->setSourceRetrievable();
|
||||
else if (!scriptSource->setSourceCopy(cx, sourceBuffer))
|
||||
} else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeCompiler::enqueueOffThreadSourceCompression()
|
||||
{
|
||||
// There are several cases where source compression is not a good idea:
|
||||
// - If the script is tiny, then compression will save little or no space.
|
||||
// - If there is only one core, then compression will contend with JS
|
||||
// execution (which hurts benchmarketing).
|
||||
//
|
||||
// Otherwise, enqueue a compression task to be processed when a major
|
||||
// GC is requested.
|
||||
|
||||
if (!scriptSource->hasUncompressedSource())
|
||||
return true;
|
||||
|
||||
bool canCompressOffThread =
|
||||
HelperThreadState().cpuCount > 1 &&
|
||||
HelperThreadState().threadCount >= 2 &&
|
||||
CanUseExtraThreads();
|
||||
const size_t TINY_SCRIPT = 256;
|
||||
if (TINY_SCRIPT <= sourceBuffer.length() && canCompressOffThread) {
|
||||
// Heap allocate the task. It will be freed upon compression
|
||||
// completing in AttachFinishedCompressedSources.
|
||||
SourceCompressionTask* task = cx->new_<SourceCompressionTask>(cx->runtime(),
|
||||
scriptSource);
|
||||
if (!task)
|
||||
return false;
|
||||
if (!EnqueueOffThreadCompression(cx, task))
|
||||
return false;
|
||||
sourceCompressionTask_ = task;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -364,6 +403,10 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
|
|||
// can compute statistics (e.g. how much time our functions remain lazy).
|
||||
script->scriptSource()->recordParseEnded();
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
|
||||
|
||||
return script;
|
||||
|
@ -425,6 +468,10 @@ BytecodeCompiler::compileModule()
|
|||
|
||||
module->setInitialEnvironment(env);
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
|
||||
return module;
|
||||
}
|
||||
|
@ -477,6 +524,10 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun,
|
|||
if (!NameFunctions(cx, fn))
|
||||
return false;
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -486,6 +537,12 @@ BytecodeCompiler::sourceObjectPtr() const
|
|||
return sourceObject.get();
|
||||
}
|
||||
|
||||
SourceCompressionTask*
|
||||
BytecodeCompiler::sourceCompressionTask() const
|
||||
{
|
||||
return sourceCompressionTask_;
|
||||
}
|
||||
|
||||
ScriptSourceObject*
|
||||
frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
|
||||
|
@ -537,16 +594,22 @@ class MOZ_STACK_CLASS AutoInitializeSourceObject
|
|||
{
|
||||
BytecodeCompiler& compiler_;
|
||||
ScriptSourceObject** sourceObjectOut_;
|
||||
SourceCompressionTask** sourceCompressionTaskOut_;
|
||||
|
||||
public:
|
||||
AutoInitializeSourceObject(BytecodeCompiler& compiler, ScriptSourceObject** sourceObjectOut)
|
||||
AutoInitializeSourceObject(BytecodeCompiler& compiler,
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
: compiler_(compiler),
|
||||
sourceObjectOut_(sourceObjectOut)
|
||||
sourceObjectOut_(sourceObjectOut),
|
||||
sourceCompressionTaskOut_(sourceCompressionTaskOut)
|
||||
{ }
|
||||
|
||||
~AutoInitializeSourceObject() {
|
||||
if (sourceObjectOut_)
|
||||
*sourceObjectOut_ = compiler_.sourceObjectPtr();
|
||||
if (sourceCompressionTaskOut_)
|
||||
*sourceCompressionTaskOut_ = compiler_.sourceCompressionTask();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -554,11 +617,12 @@ JSScript*
|
|||
frontend::CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
{
|
||||
MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
return compiler.compileGlobalScript(scopeKind);
|
||||
}
|
||||
|
||||
|
@ -567,17 +631,19 @@ frontend::CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
|
|||
HandleObject environment, HandleScope enclosingScope,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
{
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
return compiler.compileEvalScript(environment, enclosingScope);
|
||||
}
|
||||
|
||||
ModuleObject*
|
||||
frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
|
||||
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
|
||||
ScriptSourceObject** sourceObjectOut /* = nullptr */)
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
{
|
||||
MOZ_ASSERT(srcBuf.get());
|
||||
MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
|
||||
|
@ -589,7 +655,7 @@ frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInpu
|
|||
|
||||
RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
return compiler.compileModule();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class LazyScript;
|
|||
class LifoAlloc;
|
||||
class ModuleObject;
|
||||
class ScriptSourceObject;
|
||||
class SourceCompressionTask;
|
||||
|
||||
namespace frontend {
|
||||
|
||||
|
@ -34,14 +35,16 @@ JSScript*
|
|||
CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
|
||||
JSScript*
|
||||
CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
|
||||
HandleObject scopeChain, HandleScope enclosingScope,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
|
||||
ModuleObject*
|
||||
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
|
@ -50,7 +53,8 @@ CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
|||
ModuleObject*
|
||||
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
|
||||
|
|
|
@ -3941,6 +3941,15 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process any queued source compressions during the start of a major
|
||||
* GC.
|
||||
*/
|
||||
{
|
||||
AutoLockHelperThreadState helperLock;
|
||||
HelperThreadState().startHandlingCompressionTasks(helperLock);
|
||||
}
|
||||
|
||||
startNumber = number;
|
||||
|
||||
/*
|
||||
|
@ -4981,6 +4990,7 @@ MAKE_GC_SWEEP_TASK(SweepInitialShapesTask);
|
|||
MAKE_GC_SWEEP_TASK(SweepObjectGroupsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepRegExpsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepMiscTask);
|
||||
MAKE_GC_SWEEP_TASK(AttachCompressedSourcesTask);
|
||||
#undef MAKE_GC_SWEEP_TASK
|
||||
|
||||
/* virtual */ void
|
||||
|
@ -5035,6 +5045,22 @@ SweepMiscTask::run()
|
|||
}
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
AttachCompressedSourcesTask::run()
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
GlobalHelperThreadState::SourceCompressionTaskVector& finished =
|
||||
HelperThreadState().compressionFinishedList(lock);
|
||||
for (size_t i = 0; i < finished.length(); i++) {
|
||||
SourceCompressionTask* task = finished[i];
|
||||
if (task->runtimeMatches(runtime())) {
|
||||
task->complete();
|
||||
js_delete(task);
|
||||
HelperThreadState().remove(finished, &i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
|
||||
{
|
||||
|
@ -5113,6 +5139,7 @@ GCRuntime::beginSweepingSweepGroup(AutoLockForExclusiveAccess& lock)
|
|||
SweepObjectGroupsTask sweepObjectGroupsTask(rt);
|
||||
SweepRegExpsTask sweepRegExpsTask(rt);
|
||||
SweepMiscTask sweepMiscTask(rt);
|
||||
AttachCompressedSourcesTask attachCompressedSourcesTask(rt);
|
||||
WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
|
||||
|
||||
for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
|
@ -5161,12 +5188,13 @@ GCRuntime::beginSweepingSweepGroup(AutoLockForExclusiveAccess& lock)
|
|||
startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(attachCompressedSourcesTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
|
||||
// The remainder of the of the tasks run in parallel on the active
|
||||
// thread until we join, below.
|
||||
// The remainder of the tasks run in parallel on the active thread
|
||||
// until we join, below.
|
||||
{
|
||||
gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP_MISC);
|
||||
|
||||
|
@ -5242,6 +5270,7 @@ GCRuntime::beginSweepingSweepGroup(AutoLockForExclusiveAccess& lock)
|
|||
joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(attachCompressedSourcesTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
|
|
|
@ -1793,11 +1793,12 @@ ScriptSource::setSourceCopy(JSContext* cx, SourceBufferHolder& srcBuf)
|
|||
{
|
||||
MOZ_ASSERT(!hasSourceData());
|
||||
|
||||
auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
|
||||
JSRuntime* runtime = cx->zone()->runtimeFromAnyThread();
|
||||
auto& cache = runtime->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());
|
||||
? mozilla::UniquePtr<char16_t[], JS::FreePolicy>(srcBuf.take())
|
||||
: DuplicateString(srcBuf.get(), srcBuf.length());
|
||||
});
|
||||
if (!deduped) {
|
||||
ReportOutOfMemory(cx);
|
||||
|
@ -1821,42 +1822,49 @@ reallocUniquePtr(UniquePtr<char[], JS::FreePolicy>& unique, size_t size)
|
|||
return true;
|
||||
}
|
||||
|
||||
SourceCompressionTask::ResultType
|
||||
void
|
||||
SourceCompressionTask::work()
|
||||
{
|
||||
MOZ_ASSERT(ss->data.is<ScriptSource::Uncompressed>());
|
||||
if (shouldCancel())
|
||||
return;
|
||||
|
||||
ScriptSource* source = sourceHolder_.get();
|
||||
MOZ_ASSERT(source->data.is<ScriptSource::Uncompressed>());
|
||||
|
||||
// Try to keep the maximum memory usage down by only allocating half the
|
||||
// size of the string, first.
|
||||
size_t inputBytes = ss->length() * sizeof(char16_t);
|
||||
size_t inputBytes = source->length() * sizeof(char16_t);
|
||||
size_t firstSize = inputBytes / 2;
|
||||
mozilla::UniquePtr<char[], JS::FreePolicy> compressed(js_pod_malloc<char>(firstSize));
|
||||
if (!compressed)
|
||||
return OOM;
|
||||
return;
|
||||
|
||||
const char16_t* chars = ss->data.as<ScriptSource::Uncompressed>().string.chars();
|
||||
const char16_t* chars = source->data.as<ScriptSource::Uncompressed>().string.chars();
|
||||
Compressor comp(reinterpret_cast<const unsigned char*>(chars),
|
||||
inputBytes);
|
||||
if (!comp.init())
|
||||
return OOM;
|
||||
return;
|
||||
|
||||
comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
|
||||
bool cont = true;
|
||||
bool reallocated = false;
|
||||
while (cont) {
|
||||
if (shouldCancel())
|
||||
return;
|
||||
|
||||
switch (comp.compressMore()) {
|
||||
case Compressor::CONTINUE:
|
||||
break;
|
||||
case Compressor::MOREOUTPUT: {
|
||||
if (reallocated) {
|
||||
// The compressed string is longer than the original string.
|
||||
return Aborted;
|
||||
return;
|
||||
}
|
||||
|
||||
// The compressed output is greater than half the size of the
|
||||
// original string. Reallocate to the full size.
|
||||
if (!reallocUniquePtr(compressed, inputBytes))
|
||||
return OOM;
|
||||
return;
|
||||
|
||||
comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), inputBytes);
|
||||
reallocated = true;
|
||||
|
@ -1866,7 +1874,7 @@ SourceCompressionTask::work()
|
|||
cont = false;
|
||||
break;
|
||||
case Compressor::OOM:
|
||||
return OOM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1874,16 +1882,24 @@ SourceCompressionTask::work()
|
|||
|
||||
// Shrink the buffer to the size of the compressed data.
|
||||
if (!reallocUniquePtr(compressed, totalBytes))
|
||||
return OOM;
|
||||
return;
|
||||
|
||||
comp.finish(compressed.get(), totalBytes);
|
||||
|
||||
auto& strings = cx->sharedImmutableStrings();
|
||||
resultString = strings.getOrCreate(mozilla::Move(compressed), totalBytes);
|
||||
if (!resultString)
|
||||
return OOM;
|
||||
if (shouldCancel())
|
||||
return;
|
||||
|
||||
return Success;
|
||||
auto& strings = runtime_->sharedImmutableStrings();
|
||||
resultString_ = strings.getOrCreate(mozilla::Move(compressed), totalBytes);
|
||||
}
|
||||
|
||||
void
|
||||
SourceCompressionTask::complete()
|
||||
{
|
||||
if (!shouldCancel() && resultString_) {
|
||||
ScriptSource* source = sourceHolder_.get();
|
||||
source->setCompressedSource(mozilla::Move(*resultString_), source->length());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -52,7 +52,7 @@ class Debugger;
|
|||
class LazyScript;
|
||||
class ModuleObject;
|
||||
class RegExpObject;
|
||||
struct SourceCompressionTask;
|
||||
class SourceCompressionTask;
|
||||
class Shape;
|
||||
|
||||
namespace frontend {
|
||||
|
@ -355,7 +355,7 @@ class UncompressedSourceCache
|
|||
|
||||
class ScriptSource
|
||||
{
|
||||
friend struct SourceCompressionTask;
|
||||
friend class SourceCompressionTask;
|
||||
|
||||
uint32_t refs;
|
||||
|
||||
|
@ -437,6 +437,14 @@ class ScriptSource
|
|||
// function should be recorded before their first execution.
|
||||
UniquePtr<XDRIncrementalEncoder> xdrEncoder_;
|
||||
|
||||
// Instant at which the first parse of this source ended, or null
|
||||
// if the source hasn't been parsed yet.
|
||||
//
|
||||
// Used for statistics purposes, to determine how much time code spends
|
||||
// syntax parsed before being full parsed, to help determine whether
|
||||
// our syntax parse vs. full parse heuristics are correct.
|
||||
mozilla::TimeStamp parseEnded_;
|
||||
|
||||
// True if we can call JSRuntime::sourceHook to load the source on
|
||||
// demand. If sourceRetrievable_ and hasSourceData() are false, it is not
|
||||
// possible to get source at all.
|
||||
|
@ -446,14 +454,6 @@ class ScriptSource
|
|||
const char16_t* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
|
||||
size_t chunk);
|
||||
|
||||
// Instant at which the first parse of this source ended, or null
|
||||
// if the source hasn't been parsed yet.
|
||||
//
|
||||
// Used for statistics purposes, to determine how much time code spends
|
||||
// syntax parsed before being full parsed, to help determine whether
|
||||
// our syntax parse vs. full parse heuristics are correct.
|
||||
mozilla::TimeStamp parseEnded_;
|
||||
|
||||
public:
|
||||
explicit ScriptSource()
|
||||
: refs(0),
|
||||
|
@ -489,6 +489,7 @@ class ScriptSource
|
|||
void setSourceRetrievable() { sourceRetrievable_ = true; }
|
||||
bool sourceRetrievable() const { return sourceRetrievable_; }
|
||||
bool hasSourceData() const { return !data.is<Missing>(); }
|
||||
bool hasUncompressedSource() const { return data.is<Uncompressed>(); }
|
||||
bool hasCompressedSource() const { return data.is<Compressed>(); }
|
||||
|
||||
size_t length() const {
|
||||
|
@ -639,10 +640,12 @@ class ScriptSourceHolder
|
|||
ss->decref();
|
||||
}
|
||||
void reset(ScriptSource* newss) {
|
||||
// incref before decref just in case ss == newss.
|
||||
if (newss)
|
||||
newss->incref();
|
||||
if (ss)
|
||||
ss->decref();
|
||||
ss = newss;
|
||||
ss->incref();
|
||||
}
|
||||
ScriptSource* get() const {
|
||||
return ss;
|
||||
|
|
|
@ -298,7 +298,7 @@ ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
|
|||
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
||||
parseGlobal(parseGlobal),
|
||||
callback(callback), callbackData(callbackData),
|
||||
script(nullptr), sourceObject(nullptr),
|
||||
script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
|
||||
overRecursed(false), outOfMemory(false)
|
||||
{
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
|
|||
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
||||
parseGlobal(parseGlobal),
|
||||
callback(callback), callbackData(callbackData),
|
||||
script(nullptr), sourceObject(nullptr),
|
||||
script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
|
||||
overRecursed(false), outOfMemory(false)
|
||||
{
|
||||
}
|
||||
|
@ -337,6 +337,8 @@ ParseTask::finish(JSContext* cx)
|
|||
RootedScriptSource sso(cx, sourceObject);
|
||||
if (!ScriptSourceObject::initFromOptions(cx, sso, options))
|
||||
return false;
|
||||
if (sourceCompressionTask)
|
||||
sourceCompressionTask->fixupMajorGCNumber(cx->runtime());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -380,7 +382,8 @@ ScriptParseTask::parse(JSContext* cx)
|
|||
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
|
||||
script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
|
||||
options, srcBuf,
|
||||
/* sourceObjectOut = */ &sourceObject);
|
||||
/* sourceObjectOut = */ &sourceObject,
|
||||
/* sourceCompressionTaskOut = */ &sourceCompressionTask);
|
||||
}
|
||||
|
||||
ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
|
||||
|
@ -395,7 +398,8 @@ void
|
|||
ModuleParseTask::parse(JSContext* cx)
|
||||
{
|
||||
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
|
||||
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
|
||||
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject,
|
||||
&sourceCompressionTask);
|
||||
if (module)
|
||||
script = module->script();
|
||||
}
|
||||
|
@ -927,7 +931,10 @@ GlobalHelperThreadState::maxCompressionThreads() const
|
|||
{
|
||||
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_COMPRESS))
|
||||
return 1;
|
||||
return threadCount;
|
||||
|
||||
// Compression is triggered on major GCs to compress ScriptSources. It is
|
||||
// considered low priority work.
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t
|
||||
|
@ -1100,6 +1107,30 @@ GlobalHelperThreadState::canStartCompressionTask(const AutoLockHelperThreadState
|
|||
checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
|
||||
}
|
||||
|
||||
void
|
||||
GlobalHelperThreadState::startHandlingCompressionTasks(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
scheduleCompressionTasks(lock);
|
||||
if (canStartCompressionTask(lock))
|
||||
notifyOne(PRODUCER, lock);
|
||||
}
|
||||
|
||||
void
|
||||
GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
auto& pending = compressionPendingList(lock);
|
||||
auto& worklist = compressionWorklist(lock);
|
||||
MOZ_ASSERT(worklist.capacity() >= pending.length());
|
||||
|
||||
for (size_t i = 0; i < pending.length(); i++) {
|
||||
SourceCompressionTask* task = pending[i];
|
||||
if (task->shouldStart()) {
|
||||
remove(pending, &i);
|
||||
worklist.infallibleAppend(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalHelperThreadState::canStartGCHelperTask(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
|
@ -1698,7 +1729,6 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
|||
|
||||
currentTask.emplace(HelperThreadState().compressionWorklist(locked).popCopy());
|
||||
SourceCompressionTask* task = compressionTask();
|
||||
task->helperThread = this;
|
||||
|
||||
{
|
||||
AutoUnlockHelperThreadState unlock(locked);
|
||||
|
@ -1706,10 +1736,15 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
|||
TraceLoggerThread* logger = TraceLoggerForCurrentThread();
|
||||
AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
|
||||
|
||||
task->result = task->work();
|
||||
task->work();
|
||||
}
|
||||
|
||||
{
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!HelperThreadState().compressionFinishedList(locked).append(task))
|
||||
oomUnsafe.crash("handleCompressionWorkload");
|
||||
}
|
||||
|
||||
task->helperThread = nullptr;
|
||||
currentTask.reset();
|
||||
|
||||
// Notify the active thread in case it is waiting for the compression to finish.
|
||||
|
@ -1717,20 +1752,73 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
|||
}
|
||||
|
||||
bool
|
||||
js::StartOffThreadCompression(JSContext* cx, SourceCompressionTask* task)
|
||||
js::EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task)
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
|
||||
if (!HelperThreadState().compressionWorklist(lock).append(task)) {
|
||||
auto& pending = HelperThreadState().compressionPendingList(lock);
|
||||
auto& worklist = HelperThreadState().compressionWorklist(lock);
|
||||
if (!pending.append(task)) {
|
||||
if (!cx->helperThread())
|
||||
ReportOutOfMemory(cx);
|
||||
js_delete(task);
|
||||
return false;
|
||||
}
|
||||
if (!worklist.reserve(pending.length())) {
|
||||
if (!cx->helperThread())
|
||||
ReportOutOfMemory(cx);
|
||||
pending.popBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void
|
||||
ClearCompressionTaskList(T& list, JSRuntime* runtime)
|
||||
{
|
||||
for (size_t i = 0; i < list.length(); i++) {
|
||||
SourceCompressionTask* task = list[i];
|
||||
if (task->runtimeMatches(runtime)) {
|
||||
js_delete(task);
|
||||
HelperThreadState().remove(list, &i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
js::CancelOffThreadCompressions(JSRuntime* runtime)
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
|
||||
if (!HelperThreadState().threads)
|
||||
return;
|
||||
|
||||
// Cancel all pending compression tasks.
|
||||
ClearCompressionTaskList(HelperThreadState().compressionPendingList(lock), runtime);
|
||||
ClearCompressionTaskList(HelperThreadState().compressionWorklist(lock), runtime);
|
||||
|
||||
// Cancel all in-process compression tasks and wait for them to join so we
|
||||
// clean up the finished tasks.
|
||||
while (true) {
|
||||
bool inProgress = false;
|
||||
for (auto& thread : *HelperThreadState().threads) {
|
||||
SourceCompressionTask* task = thread.compressionTask();
|
||||
if (task && task->runtimeMatches(runtime))
|
||||
inProgress = true;
|
||||
}
|
||||
|
||||
if (!inProgress)
|
||||
break;
|
||||
|
||||
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
|
||||
}
|
||||
|
||||
// Clean up finished tasks.
|
||||
ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock), runtime);
|
||||
}
|
||||
|
||||
bool
|
||||
js::StartPromiseTask(JSContext* cx, UniquePtr<PromiseTask> task)
|
||||
{
|
||||
|
@ -1766,64 +1854,6 @@ js::StartPromiseTask(JSContext* cx, UniquePtr<PromiseTask> task)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalHelperThreadState::compressionInProgress(SourceCompressionTask* task,
|
||||
const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
for (size_t i = 0; i < compressionWorklist(lock).length(); i++) {
|
||||
if (compressionWorklist(lock)[i] == task)
|
||||
return true;
|
||||
}
|
||||
for (auto& thread : *threads) {
|
||||
if (thread.compressionTask() == task)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
SourceCompressionTask::complete()
|
||||
{
|
||||
if (!active())
|
||||
return true;
|
||||
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
while (HelperThreadState().compressionInProgress(this, lock))
|
||||
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
|
||||
}
|
||||
|
||||
if (result == Success) {
|
||||
MOZ_ASSERT(resultString);
|
||||
ss->setCompressedSource(mozilla::Move(*resultString), ss->length());
|
||||
} else {
|
||||
if (result == OOM)
|
||||
ReportOutOfMemory(cx);
|
||||
}
|
||||
|
||||
ss = nullptr;
|
||||
MOZ_ASSERT(!active());
|
||||
|
||||
return result != OOM;
|
||||
}
|
||||
|
||||
SourceCompressionTask*
|
||||
GlobalHelperThreadState::compressionTaskForSource(ScriptSource* ss,
|
||||
const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
for (size_t i = 0; i < compressionWorklist(lock).length(); i++) {
|
||||
SourceCompressionTask* task = compressionWorklist(lock)[i];
|
||||
if (task->source() == ss)
|
||||
return task;
|
||||
}
|
||||
for (auto& thread : *threads) {
|
||||
SourceCompressionTask* task = thread.compressionTask();
|
||||
if (task && task->source() == ss)
|
||||
return task;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
GlobalHelperThreadState::trace(JSTracer* trc)
|
||||
{
|
||||
|
|
|
@ -105,9 +105,15 @@ class GlobalHelperThreadState
|
|||
// Parse tasks waiting for an atoms-zone GC to complete.
|
||||
ParseTaskVector parseWaitingOnGC_;
|
||||
|
||||
// Source compression worklist.
|
||||
// Source compression worklist of tasks that we do not yet know can start.
|
||||
SourceCompressionTaskVector compressionPendingList_;
|
||||
|
||||
// Source compression worklist of tasks that can start.
|
||||
SourceCompressionTaskVector compressionWorklist_;
|
||||
|
||||
// Finished source compression tasks.
|
||||
SourceCompressionTaskVector compressionFinishedList_;
|
||||
|
||||
// Runtimes which have sweeping / allocating work to do.
|
||||
GCHelperStateVector gcHelperWorklist_;
|
||||
|
||||
|
@ -190,10 +196,18 @@ class GlobalHelperThreadState
|
|||
return parseWaitingOnGC_;
|
||||
}
|
||||
|
||||
SourceCompressionTaskVector& compressionPendingList(const AutoLockHelperThreadState&) {
|
||||
return compressionPendingList_;
|
||||
}
|
||||
|
||||
SourceCompressionTaskVector& compressionWorklist(const AutoLockHelperThreadState&) {
|
||||
return compressionWorklist_;
|
||||
}
|
||||
|
||||
SourceCompressionTaskVector& compressionFinishedList(const AutoLockHelperThreadState&) {
|
||||
return compressionFinishedList_;
|
||||
}
|
||||
|
||||
GCHelperStateVector& gcHelperWorklist(const AutoLockHelperThreadState&) {
|
||||
return gcHelperWorklist_;
|
||||
}
|
||||
|
@ -210,6 +224,10 @@ class GlobalHelperThreadState
|
|||
bool canStartGCHelperTask(const AutoLockHelperThreadState& lock);
|
||||
bool canStartGCParallelTask(const AutoLockHelperThreadState& lock);
|
||||
|
||||
// Used by a major GC to signal processing enqueued compression tasks.
|
||||
void startHandlingCompressionTasks(const AutoLockHelperThreadState&);
|
||||
void scheduleCompressionTasks(const AutoLockHelperThreadState&);
|
||||
|
||||
// Unlike the methods above, the value returned by this method can change
|
||||
// over time, even if the helper thread state lock is held throughout.
|
||||
bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock);
|
||||
|
@ -265,8 +283,6 @@ class GlobalHelperThreadState
|
|||
JSScript* finishScriptParseTask(JSContext* cx, void* token);
|
||||
JSScript* finishScriptDecodeTask(JSContext* cx, void* token);
|
||||
JSObject* finishModuleParseTask(JSContext* cx, void* token);
|
||||
bool compressionInProgress(SourceCompressionTask* task, const AutoLockHelperThreadState& lock);
|
||||
SourceCompressionTask* compressionTaskForSource(ScriptSource* ss, const AutoLockHelperThreadState& lock);
|
||||
|
||||
bool hasActiveThreads(const AutoLockHelperThreadState&);
|
||||
void waitForAllThreads();
|
||||
|
@ -531,9 +547,14 @@ struct AutoEnqueuePendingParseTasksAfterGC {
|
|||
~AutoEnqueuePendingParseTasksAfterGC();
|
||||
};
|
||||
|
||||
/* Start a compression job for the specified token. */
|
||||
// Enqueue a compression job to be processed if there's a major GC.
|
||||
bool
|
||||
StartOffThreadCompression(JSContext* cx, SourceCompressionTask* task);
|
||||
EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task);
|
||||
|
||||
// Cancel all scheduled, in progress, or finished compression tasks for
|
||||
// runtime.
|
||||
void
|
||||
CancelOffThreadCompressions(JSRuntime* runtime);
|
||||
|
||||
class MOZ_RAII AutoLockHelperThreadState : public LockGuard<Mutex>
|
||||
{
|
||||
|
@ -601,6 +622,10 @@ struct ParseTask
|
|||
// Holds the ScriptSourceObject generated for the script compilation.
|
||||
ScriptSourceObject* sourceObject;
|
||||
|
||||
// Holds the SourceCompressionTask, if any were enqueued for the
|
||||
// ScriptSource of sourceObject.
|
||||
SourceCompressionTask* sourceCompressionTask;
|
||||
|
||||
// Any errors or warnings produced during compilation. These are reported
|
||||
// when finishing the script.
|
||||
Vector<frontend::CompileError*, 0, SystemAllocPolicy> errors;
|
||||
|
@ -657,49 +682,76 @@ struct ScriptDecodeTask : public ParseTask
|
|||
extern bool
|
||||
OffThreadParsingMustWaitForGC(JSRuntime* rt);
|
||||
|
||||
// Compression tasks are allocated on the stack by their triggering thread,
|
||||
// which will block on the compression completing as the task goes out of scope
|
||||
// to ensure it completes at the required time.
|
||||
struct SourceCompressionTask
|
||||
// It is not desirable to eagerly compress: if lazy functions that are tied to
|
||||
// the ScriptSource were to be executed relatively soon after parsing, they
|
||||
// would need to block on decompression, which hurts responsiveness.
|
||||
//
|
||||
// To this end, compression tasks are heap allocated and enqueued in a pending
|
||||
// list by ScriptSource::setSourceCopy. When a major GC occurs, we schedule
|
||||
// pending compression tasks and move the ones that are ready to be compressed
|
||||
// to the worklist. Currently, a compression task is considered ready 2 major
|
||||
// GCs after being enqueued. Completed tasks are handled during the sweeping
|
||||
// phase by AttachCompressedSourcesTask, which runs in parallel with other GC
|
||||
// sweeping tasks.
|
||||
class SourceCompressionTask
|
||||
{
|
||||
friend class ScriptSource;
|
||||
friend struct HelperThread;
|
||||
friend class ScriptSource;
|
||||
|
||||
// Thread performing the compression.
|
||||
HelperThread* helperThread;
|
||||
// The runtime that the ScriptSource is associated with, in the sense that
|
||||
// it uses the runtime's immutable string cache.
|
||||
JSRuntime* runtime_;
|
||||
|
||||
private:
|
||||
// Context from the triggering thread. Don't use this off thread!
|
||||
JSContext* cx;
|
||||
// The major GC number of the runtime when the task was enqueued.
|
||||
static const uint64_t MajorGCNumberWaitingForFixup = UINT64_MAX;
|
||||
uint64_t majorGCNumber_;
|
||||
|
||||
ScriptSource* ss;
|
||||
// The source to be compressed.
|
||||
ScriptSourceHolder sourceHolder_;
|
||||
|
||||
// Stores the result of the compression.
|
||||
enum ResultType {
|
||||
OOM,
|
||||
Aborted,
|
||||
Success
|
||||
} result;
|
||||
|
||||
mozilla::Maybe<SharedImmutableString> resultString;
|
||||
// The resultant compressed string. If the compressed string is larger
|
||||
// than the original, or we OOM'd during compression, or nothing else
|
||||
// except the task is holding the ScriptSource alive when scheduled to
|
||||
// compress, this will remain None upon completion.
|
||||
mozilla::Maybe<SharedImmutableString> resultString_;
|
||||
|
||||
public:
|
||||
explicit SourceCompressionTask(JSContext* cx)
|
||||
: helperThread(nullptr)
|
||||
, cx(cx)
|
||||
, ss(nullptr)
|
||||
, result(OOM)
|
||||
{}
|
||||
// The majorGCNumber is used for scheduling tasks. If the task is being
|
||||
// enqueued from an off-thread parsing task, leave the GC number
|
||||
// UINT64_MAX to be fixed up when the parse task finishes.
|
||||
SourceCompressionTask(JSRuntime* rt, ScriptSource* source)
|
||||
: runtime_(rt),
|
||||
majorGCNumber_(CurrentThreadCanAccessRuntime(rt)
|
||||
? rt->gc.majorGCCount()
|
||||
: MajorGCNumberWaitingForFixup),
|
||||
sourceHolder_(source)
|
||||
{ }
|
||||
|
||||
~SourceCompressionTask()
|
||||
{
|
||||
complete();
|
||||
bool runtimeMatches(JSRuntime* runtime) const {
|
||||
return runtime == runtime_;
|
||||
}
|
||||
|
||||
ResultType work();
|
||||
bool complete();
|
||||
bool active() const { return !!ss; }
|
||||
ScriptSource* source() { return ss; }
|
||||
void fixupMajorGCNumber(JSRuntime* runtime) {
|
||||
MOZ_ASSERT(majorGCNumber_ == MajorGCNumberWaitingForFixup);
|
||||
majorGCNumber_ = runtime->gc.majorGCCount();
|
||||
}
|
||||
|
||||
bool shouldStart() const {
|
||||
// We wait 2 major GCs to start compressing, in order to avoid
|
||||
// immediate compression.
|
||||
if (majorGCNumber_ == MajorGCNumberWaitingForFixup)
|
||||
return false;
|
||||
return runtime_->gc.majorGCCount() > majorGCNumber_ + 1;
|
||||
}
|
||||
|
||||
bool shouldCancel() const {
|
||||
// If the refcount is exactly 1, then nothing else is holding on to the
|
||||
// ScriptSource, so no reason to compress it and we should cancel the task.
|
||||
return sourceHolder_.get()->refs == 1;
|
||||
}
|
||||
|
||||
void work();
|
||||
void complete();
|
||||
};
|
||||
|
||||
} /* namespace js */
|
||||
|
|
|
@ -304,6 +304,7 @@ JSRuntime::destroyRuntime()
|
|||
*/
|
||||
CancelOffThreadIonCompile(this);
|
||||
CancelOffThreadParses(this);
|
||||
CancelOffThreadCompressions(this);
|
||||
|
||||
/* Remove persistent GC roots. */
|
||||
gc.finishRoots();
|
||||
|
|
Загрузка…
Ссылка в новой задаче