Bug 1348134 - Handle compression tasks with major GCs instead of eagerly. (r=sfink,jonco)

This commit is contained in:
Shu-yu Guo 2017-04-12 14:13:21 -07:00
Родитель 8f1a38f1dd
Коммит 9ca2bfcb81
8 изменённых файлов: 351 добавлений и 150 удалений

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

@ -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();