Bug 1357012 - Use fallible append for compression tasks and use UniquePtrs. (r=jonco)

This commit is contained in:
Shu-yu Guo 2017-04-19 13:58:01 -07:00
parent 8d96f69cfa
commit 0f6729b15d
7 changed files with 92 additions and 127 deletions

View File

@ -57,7 +57,6 @@ class MOZ_STACK_CLASS BytecodeCompiler
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());
@ -83,7 +82,6 @@ class MOZ_STACK_CLASS BytecodeCompiler
RootedScriptSource sourceObject;
ScriptSource* scriptSource;
SourceCompressionTask* sourceCompressionTask_;
Maybe<UsedNameTracker> usedNames;
Maybe<Parser<SyntaxParseHandler>> syntaxParser;
@ -173,7 +171,6 @@ BytecodeCompiler::BytecodeCompiler(JSContext* cx,
enclosingScope(cx, enclosingScope),
sourceObject(cx),
scriptSource(nullptr),
sourceCompressionTask_(nullptr),
directives(options.strictOption),
startPosition(keepAtoms),
script(cx)
@ -219,40 +216,6 @@ BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd)
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;
}
bool
BytecodeCompiler::canLazilyParse()
{
@ -416,7 +379,7 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
script->scriptSource()->recordParseEnded();
// Enqueue an off-thread source compression task after finishing parsing.
if (!enqueueOffThreadSourceCompression())
if (!scriptSource->tryCompressOffThread(cx))
return nullptr;
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
@ -481,7 +444,7 @@ BytecodeCompiler::compileModule()
module->setInitialEnvironment(env);
// Enqueue an off-thread source compression task after finishing parsing.
if (!enqueueOffThreadSourceCompression())
if (!scriptSource->tryCompressOffThread(cx))
return nullptr;
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
@ -537,7 +500,7 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun,
return false;
// Enqueue an off-thread source compression task after finishing parsing.
if (!enqueueOffThreadSourceCompression())
if (!scriptSource->tryCompressOffThread(cx))
return false;
return true;
@ -549,12 +512,6 @@ 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() */)
@ -606,22 +563,17 @@ class MOZ_STACK_CLASS AutoInitializeSourceObject
{
BytecodeCompiler& compiler_;
ScriptSourceObject** sourceObjectOut_;
SourceCompressionTask** sourceCompressionTaskOut_;
public:
AutoInitializeSourceObject(BytecodeCompiler& compiler,
ScriptSourceObject** sourceObjectOut,
SourceCompressionTask** sourceCompressionTaskOut)
ScriptSourceObject** sourceObjectOut)
: compiler_(compiler),
sourceObjectOut_(sourceObjectOut),
sourceCompressionTaskOut_(sourceCompressionTaskOut)
sourceObjectOut_(sourceObjectOut)
{ }
~AutoInitializeSourceObject() {
if (sourceObjectOut_)
*sourceObjectOut_ = compiler_.sourceObjectPtr();
if (sourceCompressionTaskOut_)
*sourceCompressionTaskOut_ = compiler_.sourceCompressionTask();
}
};
@ -629,12 +581,11 @@ JSScript*
frontend::CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
const ReadOnlyCompileOptions& options,
SourceBufferHolder& srcBuf,
ScriptSourceObject** sourceObjectOut,
SourceCompressionTask** sourceCompressionTaskOut)
ScriptSourceObject** sourceObjectOut)
{
MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr);
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
return compiler.compileGlobalScript(scopeKind);
}
@ -643,19 +594,17 @@ frontend::CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
HandleObject environment, HandleScope enclosingScope,
const ReadOnlyCompileOptions& options,
SourceBufferHolder& srcBuf,
ScriptSourceObject** sourceObjectOut,
SourceCompressionTask** sourceCompressionTaskOut)
ScriptSourceObject** sourceObjectOut)
{
BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope);
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
return compiler.compileEvalScript(environment, enclosingScope);
}
ModuleObject*
frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
ScriptSourceObject** sourceObjectOut,
SourceCompressionTask** sourceCompressionTaskOut)
ScriptSourceObject** sourceObjectOut)
{
MOZ_ASSERT(srcBuf.get());
MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
@ -667,7 +616,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, sourceCompressionTaskOut);
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
return compiler.compileModule();
}

View File

@ -23,7 +23,6 @@ class LazyScript;
class LifoAlloc;
class ModuleObject;
class ScriptSourceObject;
class SourceCompressionTask;
namespace frontend {
@ -35,16 +34,14 @@ JSScript*
CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
const ReadOnlyCompileOptions& options,
SourceBufferHolder& srcBuf,
ScriptSourceObject** sourceObjectOut = nullptr,
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
ScriptSourceObject** sourceObjectOut = nullptr);
JSScript*
CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
HandleObject scopeChain, HandleScope enclosingScope,
const ReadOnlyCompileOptions& options,
SourceBufferHolder& srcBuf,
ScriptSourceObject** sourceObjectOut = nullptr,
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
ScriptSourceObject** sourceObjectOut = nullptr);
ModuleObject*
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
@ -53,8 +50,7 @@ CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
ModuleObject*
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
ScriptSourceObject** sourceObjectOut = nullptr,
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
ScriptSourceObject** sourceObjectOut = nullptr);
MOZ_MUST_USE bool
CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);

View File

@ -5053,22 +5053,18 @@ SweepCompressionTasksTask::run()
// Attach finished compression tasks.
auto& finished = HelperThreadState().compressionFinishedList(lock);
for (size_t i = 0; i < finished.length(); i++) {
SourceCompressionTask* task = finished[i];
if (task->runtimeMatches(runtime())) {
if (finished[i]->runtimeMatches(runtime())) {
UniquePtr<SourceCompressionTask> task(Move(finished[i]));
HelperThreadState().remove(finished, &i);
task->complete();
js_delete(task);
}
}
// Sweep pending tasks that are holding onto should-be-dead ScriptSources.
auto& pending = HelperThreadState().compressionPendingList(lock);
for (size_t i = 0; i < pending.length(); i++) {
SourceCompressionTask* task = pending[i];
if (task->shouldCancel()) {
if (pending[i]->shouldCancel())
HelperThreadState().remove(pending, &i);
js_delete(task);
}
}
}

View File

@ -1821,6 +1821,50 @@ ScriptSource::setSource(SharedImmutableTwoByteString&& string)
data = SourceType(Uncompressed(mozilla::Move(string)));
}
bool
ScriptSource::tryCompressOffThread(JSContext* cx)
{
if (!data.is<Uncompressed>())
return true;
// 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.
bool canCompressOffThread =
HelperThreadState().cpuCount > 1 &&
HelperThreadState().threadCount >= 2 &&
CanUseExtraThreads();
const size_t TINY_SCRIPT = 256;
if (TINY_SCRIPT > length() || !canCompressOffThread)
return true;
// The SourceCompressionTask needs to record the major GC number for
// scheduling. If we're parsing off thread, this number is not safe to
// access.
//
// When parsing on the main thread, the attempts made to compress off
// thread in BytecodeCompiler will succeed.
//
// When parsing off-thread, the above attempts will fail and the attempt
// made in ParseTask::finish will succeed.
if (!CurrentThreadCanAccessRuntime(cx->runtime()))
return true;
// Heap allocate the task. It will be freed upon compression
// completing in AttachFinishedCompressedSources.
auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
if (!task) {
ReportOutOfMemory(cx);
return false;
}
return EnqueueOffThreadCompression(cx, Move(task));
}
MOZ_MUST_USE bool
ScriptSource::setCompressedSource(JSContext* cx,
mozilla::UniquePtr<char[], JS::FreePolicy>&& raw,

View File

@ -573,6 +573,8 @@ class ScriptSource
size_t length);
void setSource(SharedImmutableTwoByteString&& string);
MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
MOZ_MUST_USE bool setCompressedSource(JSContext* cx,
UniqueChars&& raw,
size_t rawLength,

View File

@ -301,7 +301,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), sourceCompressionTask(nullptr),
script(nullptr), sourceObject(nullptr),
overRecursed(false), outOfMemory(false)
{
}
@ -313,7 +313,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), sourceCompressionTask(nullptr),
script(nullptr), sourceObject(nullptr),
overRecursed(false), outOfMemory(false)
{
}
@ -340,8 +340,8 @@ ParseTask::finish(JSContext* cx)
RootedScriptSource sso(cx, sourceObject);
if (!ScriptSourceObject::initFromOptions(cx, sso, options))
return false;
if (sourceCompressionTask)
sourceCompressionTask->fixupMajorGCNumber(cx->runtime());
if (!sso->source()->tryCompressOffThread(cx))
return false;
}
return true;
@ -385,8 +385,7 @@ ScriptParseTask::parse(JSContext* cx)
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
options, srcBuf,
/* sourceObjectOut = */ &sourceObject,
/* sourceCompressionTaskOut = */ &sourceCompressionTask);
/* sourceObjectOut = */ &sourceObject);
}
ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
@ -401,8 +400,7 @@ void
ModuleParseTask::parse(JSContext* cx)
{
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject,
&sourceCompressionTask);
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
if (module)
script = module->script();
}
@ -1123,13 +1121,13 @@ GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadStat
{
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()) {
if (pending[i]->shouldStart()) {
// OOMing during appending results in the task not being scheduled
// and deleted.
Unused << worklist.append(Move(pending[i]));
remove(pending, &i);
worklist.infallibleAppend(task);
}
}
}
@ -1730,8 +1728,13 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
MOZ_ASSERT(idle());
currentTask.emplace(HelperThreadState().compressionWorklist(locked).popCopy());
SourceCompressionTask* task = compressionTask();
UniquePtr<SourceCompressionTask> task;
{
auto& worklist = HelperThreadState().compressionWorklist(locked);
task = Move(worklist.back());
worklist.popBack();
currentTask.emplace(task.get());
}
{
AutoUnlockHelperThreadState unlock(locked);
@ -1744,7 +1747,7 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
{
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!HelperThreadState().compressionFinishedList(locked).append(task))
if (!HelperThreadState().compressionFinishedList(locked).append(Move(task)))
oomUnsafe.crash("handleCompressionWorkload");
}
@ -1755,22 +1758,14 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
}
bool
js::EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task)
js::EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task)
{
AutoLockHelperThreadState lock;
auto& pending = HelperThreadState().compressionPendingList(lock);
auto& worklist = HelperThreadState().compressionWorklist(lock);
if (!pending.append(task)) {
if (!pending.append(Move(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;
}
@ -1782,11 +1777,8 @@ static void
ClearCompressionTaskList(T& list, JSRuntime* runtime)
{
for (size_t i = 0; i < list.length(); i++) {
SourceCompressionTask* task = list[i];
if (task->runtimeMatches(runtime)) {
if (list[i]->runtimeMatches(runtime))
HelperThreadState().remove(list, &i);
js_delete(task);
}
}
}

View File

@ -72,7 +72,7 @@ class GlobalHelperThreadState
typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
typedef Vector<UniquePtr<SourceCompressionTask>, 0, SystemAllocPolicy> SourceCompressionTaskVector;
typedef Vector<GCHelperState*, 0, SystemAllocPolicy> GCHelperStateVector;
typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector;
typedef Vector<PromiseTask*, 0, SystemAllocPolicy> PromiseTaskVector;
@ -165,7 +165,10 @@ class GlobalHelperThreadState
template <typename T>
void remove(T& vector, size_t* index)
{
vector[(*index)--] = vector.back();
// Self-moving is undefined behavior.
if (*index != vector.length() - 1)
vector[*index] = mozilla::Move(vector.back());
(*index)--;
vector.popBack();
}
@ -550,7 +553,7 @@ struct AutoEnqueuePendingParseTasksAfterGC {
// Enqueue a compression job to be processed if there's a major GC.
bool
EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task);
EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task);
// Cancel all scheduled, in progress, or finished compression tasks for
// runtime.
@ -623,10 +626,6 @@ 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<CompileError*, 0, SystemAllocPolicy> errors;
@ -704,7 +703,6 @@ class SourceCompressionTask
JSRuntime* runtime_;
// The major GC number of the runtime when the task was enqueued.
static const uint64_t MajorGCNumberWaitingForFixup = UINT64_MAX;
uint64_t majorGCNumber_;
// The source to be compressed.
@ -717,31 +715,19 @@ class SourceCompressionTask
mozilla::Maybe<SharedImmutableString> resultString_;
public:
// 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.
// The majorGCNumber is used for scheduling tasks.
SourceCompressionTask(JSRuntime* rt, ScriptSource* source)
: runtime_(rt),
majorGCNumber_(CurrentThreadCanAccessRuntime(rt)
? rt->gc.majorGCCount()
: MajorGCNumberWaitingForFixup),
majorGCNumber_(rt->gc.majorGCCount()),
sourceHolder_(source)
{ }
bool runtimeMatches(JSRuntime* runtime) const {
return runtime == runtime_;
}
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;
}