Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "vm/HelperThreads.h"
#include "mozilla/ReverseIterator.h" // mozilla::Reversed(...)
#include "mozilla/ScopeExit.h"
#include "mozilla/Span.h" // mozilla::Span<TaggedScriptThingIndex>
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include <algorithm>
#include "frontend/BytecodeCompilation.h" // frontend::{CompileGlobalScriptToExtensibleStencil, FireOnNewScript}
#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToExtensibleStencil
#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil, ExtensibleCompilationStencil, CompilationInput, BorrowingCompilationStencil, ScriptStencilRef}
#include "frontend/FrontendContext.h"
#include "frontend/ScopeBindingCache.h" // frontend::ScopeBindingCache
#include "gc/GC.h"
#include "jit/IonCompileTask.h"
#include "jit/JitRuntime.h"
#include "jit/JitScript.h"
#include "js/CompileOptions.h" // JS::CompileOptions, JS::DecodeOptions, JS::ReadOnlyCompileOptions
#include "js/ContextOptions.h" // JS::ContextOptions
#include "js/experimental/CompileScript.h"
#include "js/experimental/JSStencil.h"
#include "js/friend/StackLimits.h" // js::ReportOverRecursed
#include "js/HelperThreadAPI.h"
#include "js/OffThreadScriptCompilation.h" // JS::OffThreadToken, JS::OffThreadCompileCallback
#include "js/SourceText.h"
#include "js/Stack.h"
#include "js/Transcoding.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "threading/CpuCount.h"
#include "util/NativeStack.h"
#include "vm/ErrorReporting.h"
#include "vm/HelperThreadState.h"
#include "vm/InternalThreadPool.h"
#include "vm/MutexIDs.h"
#include "wasm/WasmGenerator.h"
using namespace js;
using mozilla::TimeDuration;
using mozilla::TimeStamp;
using mozilla::Utf8Unit;
using JS::CompileOptions;
using JS::DispatchReason;
using JS::ReadOnlyCompileOptions;
namespace js {
Mutex gHelperThreadLock(mutexid::GlobalHelperThreadState);
GlobalHelperThreadState* gHelperThreadState = nullptr;
} // namespace js
bool js::CreateHelperThreadsState() {
MOZ_ASSERT(!gHelperThreadState);
gHelperThreadState = js_new<GlobalHelperThreadState>();
return gHelperThreadState;
}
void js::DestroyHelperThreadsState() {
AutoLockHelperThreadState lock;
if (!gHelperThreadState) {
return;
}
gHelperThreadState->finish(lock);
js_delete(gHelperThreadState);
gHelperThreadState = nullptr;
}
bool js::EnsureHelperThreadsInitialized() {
MOZ_ASSERT(gHelperThreadState);
return gHelperThreadState->ensureInitialized();
}
static size_t ClampDefaultCPUCount(size_t cpuCount) {
// It's extremely rare for SpiderMonkey to have more than a few cores worth
// of work. At higher core counts, performance can even decrease due to NUMA
// (and SpiderMonkey's lack of NUMA-awareness), contention, and general lack
// of optimization for high core counts. So to avoid wasting thread stack
// resources (and cluttering gdb and core dumps), clamp to 8 cores for now.
return std::min<size_t>(cpuCount, 8);
}
static size_t ThreadCountForCPUCount(size_t cpuCount) {
// We need at least two threads for tier-2 wasm compilations, because
// there's a master task that holds a thread while other threads do the
// compilation.
return std::max<size_t>(cpuCount, 2);
}
bool js::SetFakeCPUCount(size_t count) {
HelperThreadState().setCpuCount(count);
return true;
}
void GlobalHelperThreadState::setCpuCount(size_t count) {
// This must be called before any threads have been initialized.
AutoLockHelperThreadState lock;
MOZ_ASSERT(!isInitialized(lock));
// We can't do this if an external thread pool is in use.
MOZ_ASSERT(!dispatchTaskCallback);
cpuCount = count;
threadCount = ThreadCountForCPUCount(count);
}
size_t js::GetHelperThreadCount() { return HelperThreadState().threadCount; }
size_t js::GetHelperThreadCPUCount() { return HelperThreadState().cpuCount; }
size_t js::GetMaxWasmCompilationThreads() {
return HelperThreadState().maxWasmCompilationThreads();
}
void JS::SetProfilingThreadCallbacks(
JS::RegisterThreadCallback registerThread,
JS::UnregisterThreadCallback unregisterThread) {
HelperThreadState().registerThread = registerThread;
HelperThreadState().unregisterThread = unregisterThread;
}
static size_t ThreadStackQuotaForSize(size_t size) {
// Set the stack quota to 10% less that the actual size.
return size_t(double(size) * 0.9);
}
// error for HelperThreadTaskCallback.
JS_PUBLIC_API MOZ_NEVER_INLINE void JS::SetHelperThreadTaskCallback(
HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize) {
AutoLockHelperThreadState lock;
HelperThreadState().setDispatchTaskCallback(callback, threadCount, stackSize,
lock);
}
void GlobalHelperThreadState::setDispatchTaskCallback(
JS::HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize,
const AutoLockHelperThreadState& lock) {
MOZ_ASSERT(!isInitialized(lock));
MOZ_ASSERT(!dispatchTaskCallback);
MOZ_ASSERT(threadCount != 0);
MOZ_ASSERT(stackSize >= 16 * 1024);
dispatchTaskCallback = callback;
this->threadCount = threadCount;
this->stackQuota = ThreadStackQuotaForSize(stackSize);
}
bool js::StartOffThreadWasmCompile(wasm::CompileTask* task,
wasm::CompileMode mode) {
return HelperThreadState().submitTask(task, mode);
}
bool GlobalHelperThreadState::submitTask(wasm::CompileTask* task,
wasm::CompileMode mode) {
AutoLockHelperThreadState lock;
if (!wasmWorklist(lock, mode).pushBack(task)) {
return false;
}
dispatch(DispatchReason::NewTask, lock);
return true;
}
size_t js::RemovePendingWasmCompileTasks(
const wasm::CompileTaskState& taskState, wasm::CompileMode mode,
const AutoLockHelperThreadState& lock) {
wasm::CompileTaskPtrFifo& worklist =
HelperThreadState().wasmWorklist(lock, mode);
return worklist.eraseIf([&taskState](wasm::CompileTask* task) {
return &task->state == &taskState;
});
}
void js::StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task) {
(void)HelperThreadState().submitTask(std::move(task));
}
bool GlobalHelperThreadState::submitTask(wasm::UniqueTier2GeneratorTask task) {
AutoLockHelperThreadState lock;
MOZ_ASSERT(isInitialized(lock));
if (!wasmTier2GeneratorWorklist(lock).append(task.get())) {
return false;
}
(void)task.release();
dispatch(DispatchReason::NewTask, lock);
return true;
}
static void CancelOffThreadWasmTier2GeneratorLocked(
AutoLockHelperThreadState& lock) {
if (!HelperThreadState().isInitialized(lock)) {
return;
}
// Remove pending tasks from the tier2 generator worklist and cancel and
// delete them.
{
wasm::Tier2GeneratorTaskPtrVector& worklist =
HelperThreadState().wasmTier2GeneratorWorklist(lock);
for (size_t i = 0; i < worklist.length(); i++) {
wasm::Tier2GeneratorTask* task = worklist[i];
HelperThreadState().remove(worklist, &i);
js_delete(task);
}
}
// There is at most one running Tier2Generator task and we assume that
// below.
static_assert(GlobalHelperThreadState::MaxTier2GeneratorTasks == 1,
"code must be generalized");
// If there is a running Tier2 generator task, shut it down in a predictable
// way. The task will be deleted by the normal deletion logic.
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<wasm::Tier2GeneratorTask>()) {
// Set a flag that causes compilation to shortcut itself.
helper->as<wasm::Tier2GeneratorTask>()->cancel();
// Wait for the generator task to finish. This avoids a shutdown race
// where the shutdown code is trying to shut down helper threads and the
// ongoing tier2 compilation is trying to finish, which requires it to
// have access to helper threads.
uint32_t oldFinishedCount =
HelperThreadState().wasmTier2GeneratorsFinished(lock);
while (HelperThreadState().wasmTier2GeneratorsFinished(lock) ==
oldFinishedCount) {
HelperThreadState().wait(lock);
}
// At most one of these tasks.
break;
}
}
}
void js::CancelOffThreadWasmTier2Generator() {
AutoLockHelperThreadState lock;
CancelOffThreadWasmTier2GeneratorLocked(lock);
}
bool js::StartOffThreadIonCompile(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock) {
return HelperThreadState().submitTask(task, lock);
}
bool GlobalHelperThreadState::submitTask(
jit::IonCompileTask* task, const AutoLockHelperThreadState& locked) {
MOZ_ASSERT(isInitialized(locked));
if (!ionWorklist(locked).append(task)) {
return false;
}
// The build is moving off-thread. Freeze the LifoAlloc to prevent any
// unwanted mutations.
task->alloc().lifoAlloc()->setReadOnly();
dispatch(DispatchReason::NewTask, locked);
return true;
}
bool js::StartOffThreadIonFree(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock) {
js::UniquePtr<jit::IonFreeTask> freeTask =
js::MakeUnique<jit::IonFreeTask>(task);
if (!freeTask) {
return false;
}
return HelperThreadState().submitTask(std::move(freeTask), lock);
}
bool GlobalHelperThreadState::submitTask(
UniquePtr<jit::IonFreeTask> task, const AutoLockHelperThreadState& locked) {
MOZ_ASSERT(isInitialized(locked));
if (!ionFreeList(locked).append(std::move(task))) {
return false;
}
dispatch(DispatchReason::NewTask, locked);
return true;
}
/*
* Move an IonCompilationTask for which compilation has either finished, failed,
* or been cancelled into the global finished compilation list. All off thread
* compilations which are started must eventually be finished.
*/
void js::FinishOffThreadIonCompile(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock) {
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!HelperThreadState().ionFinishedList(lock).append(task)) {
oomUnsafe.crash("FinishOffThreadIonCompile");
}
task->script()
->runtimeFromAnyThread()
->jitRuntime()
->numFinishedOffThreadTasksRef(lock)++;
}
static JSRuntime* GetSelectorRuntime(const CompilationSelector& selector) {
struct Matcher {
JSRuntime* operator()(JSScript* script) {
return script->runtimeFromMainThread();
}
JSRuntime* operator()(Realm* realm) {
return realm->runtimeFromMainThread();
}
JSRuntime* operator()(Zone* zone) { return zone->runtimeFromMainThread(); }
JSRuntime* operator()(ZonesInState zbs) { return zbs.runtime; }
JSRuntime* operator()(JSRuntime* runtime) { return runtime; }
};
return selector.match(Matcher());
}
static bool JitDataStructuresExist(const CompilationSelector& selector) {
struct Matcher {
bool operator()(JSScript* script) { return !!script->realm()->jitRealm(); }
bool operator()(Realm* realm) { return !!realm->jitRealm(); }
bool operator()(Zone* zone) { return !!zone->jitZone(); }
bool operator()(ZonesInState zbs) { return zbs.runtime->hasJitRuntime(); }
bool operator()(JSRuntime* runtime) { return runtime->hasJitRuntime(); }
};
return selector.match(Matcher());
}
static bool IonCompileTaskMatches(const CompilationSelector& selector,
jit::IonCompileTask* task) {
struct TaskMatches {
jit::IonCompileTask* task_;
bool operator()(JSScript* script) { return script == task_->script(); }
bool operator()(Realm* realm) { return realm == task_->script()->realm(); }
bool operator()(Zone* zone) {
return zone == task_->script()->zoneFromAnyThread();
}
bool operator()(JSRuntime* runtime) {
return runtime == task_->script()->runtimeFromAnyThread();
}
bool operator()(ZonesInState zbs) {
return zbs.runtime == task_->script()->runtimeFromAnyThread() &&
zbs.state == task_->script()->zoneFromAnyThread()->gcState();
}
};
return selector.match(TaskMatches{task});
}
static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector,
AutoLockHelperThreadState& lock) {
if (!HelperThreadState().isInitialized(lock)) {
return;
}
/* Cancel any pending entries for which processing hasn't started. */
GlobalHelperThreadState::IonCompileTaskVector& worklist =
HelperThreadState().ionWorklist(lock);
for (size_t i = 0; i < worklist.length(); i++) {
jit::IonCompileTask* task = worklist[i];
if (IonCompileTaskMatches(selector, task)) {
// Once finished, tasks are added to a Linked list which is
// allocated with the IonCompileTask class. The IonCompileTask is
// allocated in the LifoAlloc so we need the LifoAlloc to be mutable.
worklist[i]->alloc().lifoAlloc()->setReadWrite();
FinishOffThreadIonCompile(task, lock);
HelperThreadState().remove(worklist, &i);
}
}
/* Wait for in progress entries to finish up. */
bool cancelled;
do {
cancelled = false;
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (!helper->is<jit::IonCompileTask>()) {
continue;
}
jit::IonCompileTask* ionCompileTask = helper->as<jit::IonCompileTask>();
if (IonCompileTaskMatches(selector, ionCompileTask)) {
ionCompileTask->mirGen().cancel();
cancelled = true;
}
}
if (cancelled) {
HelperThreadState().wait(lock);
}
} while (cancelled);
/* Cancel code generation for any completed entries. */
GlobalHelperThreadState::IonCompileTaskVector& finished =
HelperThreadState().ionFinishedList(lock);
for (size_t i = 0; i < finished.length(); i++) {
jit::IonCompileTask* task = finished[i];
if (IonCompileTaskMatches(selector, task)) {
JSRuntime* rt = task->script()->runtimeFromAnyThread();
rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--;
jit::FinishOffThreadTask(rt, task, lock);
HelperThreadState().remove(finished, &i);
}
}
/* Cancel lazy linking for pending tasks (attached to the ionScript). */
JSRuntime* runtime = GetSelectorRuntime(selector);
jit::IonCompileTask* task =
runtime->jitRuntime()->ionLazyLinkList(runtime).getFirst();
while (task) {
jit::IonCompileTask* next = task->getNext();
if (IonCompileTaskMatches(selector, task)) {
jit::FinishOffThreadTask(runtime, task, lock);
}
task = next;
}
}
void js::CancelOffThreadIonCompile(const CompilationSelector& selector) {
if (!JitDataStructuresExist(selector)) {
return;
}
AutoLockHelperThreadState lock;
CancelOffThreadIonCompileLocked(selector, lock);
}
#ifdef DEBUG
bool js::HasOffThreadIonCompile(Realm* realm) {
AutoLockHelperThreadState lock;
if (!HelperThreadState().isInitialized(lock)) {
return false;
}
GlobalHelperThreadState::IonCompileTaskVector& worklist =
HelperThreadState().ionWorklist(lock);
for (size_t i = 0; i < worklist.length(); i++) {
jit::IonCompileTask* task = worklist[i];
if (task->script()->realm() == realm) {
return true;
}
}
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<jit::IonCompileTask>() &&
helper->as<jit::IonCompileTask>()->script()->realm() == realm) {
return true;
}
}
GlobalHelperThreadState::IonCompileTaskVector& finished =
HelperThreadState().ionFinishedList(lock);
for (size_t i = 0; i < finished.length(); i++) {
jit::IonCompileTask* task = finished[i];
if (task->script()->realm() == realm) {
return true;
}
}
JSRuntime* rt = realm->runtimeFromMainThread();
jit::IonCompileTask* task = rt->jitRuntime()->ionLazyLinkList(rt).getFirst();
while (task) {
if (task->script()->realm() == realm) {
return true;
}
task = task->getNext();
}
return false;
}
#endif
struct MOZ_RAII AutoSetContextFrontendErrors {
explicit AutoSetContextFrontendErrors(FrontendContext* fc) {
fc->linkWithJSContext(TlsContext.get());
}
~AutoSetContextFrontendErrors() {
TlsContext.get()->setFrontendErrors(nullptr);
}
};
AutoSetHelperThreadContext::AutoSetHelperThreadContext(
const JS::ContextOptions& options, AutoLockHelperThreadState& lock)
: lock(lock) {
cx = HelperThreadState().getFirstUnusedContext(lock);
MOZ_ASSERT(cx);
cx->setHelperThread(options, lock);
}
AutoSetHelperThreadContext::~AutoSetHelperThreadContext() {
cx->tempLifoAlloc().releaseAll();
if (cx->shouldFreeUnusedMemory()) {
cx->tempLifoAlloc().freeAll();
cx->setFreeUnusedMemory(false);
}
cx->clearHelperThread(lock);
cx = nullptr;
}
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx,
JS::OffThreadCompileCallback callback, void* callbackData)
: kind(kind),
options(cx),
contextOptions(cx->options()),
callback(callback),
callbackData(callbackData) {
// Note that |cx| is the main thread context here but the parse task will
// run with a different, helper thread, context.
MOZ_ASSERT(!cx->isHelperThreadContext());
}
bool ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options) {
MOZ_ASSERT(!cx->isHelperThreadContext());
if (!this->options.copy(cx, options)) {
return false;
}
runtime = cx->runtime();
if (!fc_.allocateOwnedPool()) {
ReportOutOfMemory(cx);
return false;
}
// MultiStencilsDecode doesn't support JS::InstantiationStorage.
MOZ_ASSERT_IF(this->options.allocateInstantiationStorage,
kind != ParseTaskKind::MultiStencilsDecode);
return true;
}
void ParseTask::moveInstantiationStorageInto(
JS::InstantiationStorage& storage) {
storage.gcOutput_ = instantiationStorage_.gcOutput_;
instantiationStorage_.gcOutput_ = nullptr;
}
ParseTask::~ParseTask() {
// The LinkedListElement destructor will remove us from any list we are part
// of without synchronization, so ensure that doesn't happen.
MOZ_DIAGNOSTIC_ASSERT(!isInList());
}
void ParseTask::trace(JSTracer* trc) {
if (runtime != trc->runtime()) {
return;
}
compileStorage_.trace(trc);
instantiationStorage_.trace(trc);
}
size_t ParseTask::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t compileStorageSize = compileStorage_.sizeOfIncludingThis(mallocSizeOf);
size_t stencilSize =
stencil_ ? stencil_->sizeOfIncludingThis(mallocSizeOf) : 0;
size_t gcOutputSize =
instantiationStorage_.gcOutput_
? instantiationStorage_.gcOutput_->sizeOfExcludingThis(mallocSizeOf)
: 0;
// TODO: 'errors' requires adding support to `CompileError`. They are not
// common though.
return options.sizeOfExcludingThis(mallocSizeOf) + compileStorageSize +
stencilSize + gcOutputSize;
}
void ParseTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
runTask(locked);
// Schedule DelazifyTask if needed. NOTE: This should be done before adding
// this task to the finished list, as we temporarily release the lock to make
// a few large allocations.
scheduleDelazifyTask(locked);
// The callback is invoked while we are still off thread.
callback(this, callbackData);
// FinishOffThreadScript will need to be called on the script to
// migrate it into the correct compartment.
HelperThreadState().parseFinishedList(locked).insertBack(this);
}
static inline JS::NativeStackLimit GetStackLimit() {
return JS::GetNativeStackLimit(GetNativeStackBase(),
HelperThreadState().stackQuota);
}
void ParseTask::runTask(AutoLockHelperThreadState& lock) {
stackLimit = GetStackLimit();
AutoUnlockHelperThreadState unlock(lock);
parse(&fc_);
fc_.nameCollectionPool().purge();
}
void ParseTask::scheduleDelazifyTask(AutoLockHelperThreadState& lock) {
if (!stencil_) {
return;
}
// Skip delazify tasks if we parese everything on-demand or ahead.
auto strategy = options.eagerDelazificationStrategy();
if (strategy == JS::DelazificationOption::OnDemandOnly ||
strategy == JS::DelazificationOption::ParseEverythingEagerly) {
return;
}
UniquePtr<DelazifyTask> task;
{
AutoSetHelperThreadContext usesContext(contextOptions, lock);
AutoUnlockHelperThreadState unlock(lock);
AutoSetContextRuntime ascr(runtime);
task = DelazifyTask::Create(runtime, contextOptions, options, *stencil_);
if (!task) {
return;
}
}
// Schedule delazification task if there is any function to delazify.
if (!task->strategy->done()) {
HelperThreadState().submitTask(task.release(), lock);
}
}
template <typename Unit>
struct CompileToStencilTask : public ParseTask {
JS::SourceText<Unit> data;
CompileToStencilTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
JS::OffThreadCompileCallback callback,
void* callbackData);
void parse(FrontendContext* fc) override;
};
template <typename Unit>
struct CompileModuleToStencilTask : public ParseTask {
JS::SourceText<Unit> data;
CompileModuleToStencilTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
JS::OffThreadCompileCallback callback,
void* callbackData);
void parse(FrontendContext* fc) override;
};
struct DecodeStencilTask : public ParseTask {
const JS::TranscodeRange range;
DecodeStencilTask(JSContext* cx, const JS::TranscodeRange& range,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse(FrontendContext* fc) override;
};
struct MultiStencilsDecodeTask : public ParseTask {
JS::TranscodeSources* sources;
MultiStencilsDecodeTask(JSContext* cx, JS::TranscodeSources& sources,
JS::OffThreadCompileCallback callback,
void* callbackData);
void parse(FrontendContext* fc) override;
};
template <typename Unit>
CompileToStencilTask<Unit>::CompileToStencilTask(
JSContext* cx, JS::SourceText<Unit>& srcBuf,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::ScriptStencil, cx, callback, callbackData),
data(std::move(srcBuf)) {}
template <typename Unit>
void CompileToStencilTask<Unit>::parse(FrontendContext* fc) {
stencil_ = JS::CompileGlobalScriptToStencil(fc, options, stackLimit, data,
compileStorage_);
if (!stencil_) {
return;
}
if (options.allocateInstantiationStorage) {
if (!JS::PrepareForInstantiate(fc, compileStorage_, *stencil_,
instantiationStorage_)) {
stencil_ = nullptr;
}
}
}
template <typename Unit>
CompileModuleToStencilTask<Unit>::CompileModuleToStencilTask(
JSContext* cx, JS::SourceText<Unit>& srcBuf,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::ModuleStencil, cx, callback, callbackData),
data(std::move(srcBuf)) {}
template <typename Unit>
void CompileModuleToStencilTask<Unit>::parse(FrontendContext* fc) {
stencil_ = JS::CompileModuleScriptToStencil(fc, options, stackLimit, data,
compileStorage_);
if (!stencil_) {
return;
}
if (options.allocateInstantiationStorage) {
if (!JS::PrepareForInstantiate(fc, compileStorage_, *stencil_,
instantiationStorage_)) {
stencil_ = nullptr;
}
}
}
DecodeStencilTask::DecodeStencilTask(JSContext* cx,
const JS::TranscodeRange& range,
JS::OffThreadCompileCallback callback,
void* callbackData)
: ParseTask(ParseTaskKind::StencilDecode, cx, callback, callbackData),
range(range) {
MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get()));
}
void DecodeStencilTask::parse(FrontendContext* fc) {
if (!compileStorage_.allocateInput(fc, options)) {
return;
}
if (!compileStorage_.getInput().initForGlobal(fc)) {
return;
}
stencil_ = fc->getAllocator()->new_<frontend::CompilationStencil>(
compileStorage_.getInput().source);
if (!stencil_) {
return;
}
bool succeeded = false;
(void)stencil_->deserializeStencils(fc, options, range, &succeeded);
if (!succeeded) {
stencil_ = nullptr;
return;
}
if (options.allocateInstantiationStorage) {
if (!JS::PrepareForInstantiate(fc, compileStorage_, *stencil_,
instantiationStorage_)) {
stencil_ = nullptr;
}
}
}
MultiStencilsDecodeTask::MultiStencilsDecodeTask(
JSContext* cx, JS::TranscodeSources& sources,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::MultiStencilsDecode, cx, callback, callbackData),
sources(&sources) {}
void MultiStencilsDecodeTask::parse(FrontendContext* fc) {
if (!stencils.reserve(sources->length())) {
ReportOutOfMemory(fc); // This sets |outOfMemory|.
return;
}
for (auto& source : *sources) {
frontend::CompilationInput stencilInput(options);
if (!stencilInput.initForGlobal(fc)) {
break;
}
RefPtr<frontend::CompilationStencil> stencil =
fc->getAllocator()->new_<frontend::CompilationStencil>(
stencilInput.source);
if (!stencil) {
break;
}
bool succeeded = false;
(void)stencil->deserializeStencils(fc, options, source.range, &succeeded);
if (!succeeded) {
// If any decodes fail, don't process the rest. We likely are hitting OOM.
break;
}
stencils.infallibleEmplaceBack(stencil.forget());
}
}
void js::StartOffThreadDelazification(
JSContext* cx, const ReadOnlyCompileOptions& options,
const frontend::CompilationStencil& stencil) {
// Skip delazify tasks if we parse everything on-demand or ahead.
auto strategy = options.eagerDelazificationStrategy();
if (strategy == JS::DelazificationOption::OnDemandOnly ||
strategy == JS::DelazificationOption::ParseEverythingEagerly) {
return;
}
// Skip delazify task if code coverage is enabled.
if (cx->realm()->collectCoverageForDebug()) {
return;
}
if (!CanUseExtraThreads()) {
return;
}
AutoAssertNoPendingException aanpe(cx);
JSRuntime* runtime = cx->runtime();
UniquePtr<DelazifyTask> task;
task = DelazifyTask::Create(runtime, cx->options(), options, stencil);
if (!task) {
return;
}
// Schedule delazification task if there is any function to delazify.
if (!task->strategy->done()) {
AutoLockHelperThreadState lock;
HelperThreadState().submitTask(task.release(), lock);
}
}
bool DelazifyStrategy::add(FrontendContext* fc,
const frontend::CompilationStencil& stencil,
ScriptIndex index) {
using namespace js::frontend;
ScriptStencilRef scriptRef{stencil, index};
// Only functions with bytecode are allowed to be added.
MOZ_ASSERT(!scriptRef.scriptData().isGhost());
MOZ_ASSERT(scriptRef.scriptData().hasSharedData());
// Lookup the gc-things range which are referenced by this script.
size_t offset = scriptRef.scriptData().gcThingsOffset.index;
size_t length = scriptRef.scriptData().gcThingsLength;
auto gcThingData = stencil.gcThingData.Subspan(offset, length);
// Iterate over gc-things of the script and queue inner functions.
for (TaggedScriptThingIndex index : mozilla::Reversed(gcThingData)) {
if (!index.isFunction()) {
continue;
}
ScriptIndex innerScriptIndex = index.toFunction();
ScriptStencilRef innerScriptRef{stencil, innerScriptIndex};
if (innerScriptRef.scriptData().isGhost() ||
!innerScriptRef.scriptData().functionFlags.isInterpreted()) {
continue;
}
if (innerScriptRef.scriptData().hasSharedData()) {
// The top-level parse decided to eagerly parse this function, thus we
// should visit its inner function the same way.
if (!add(fc, stencil, innerScriptIndex)) {
return false;
}
continue;
}
// Maybe insert the new script index in the queue of functions to delazify.
if (!insert(innerScriptIndex, innerScriptRef)) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
DelazifyStrategy::ScriptIndex LargeFirstDelazification::next() {
std::swap(heap.back(), heap[0]);
ScriptIndex result = heap.popCopy().second;
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
// the tree of heap-sorted values which bubble up the largest values towards
// the root of the tree.
size_t len = heap.length();
size_t i = 1;
while (true) {
// NOTE: We write (n + 1) - 1, instead of n, to explicit that the
// manipualted indexes are all offseted by 1.
size_t n = 2 * i;
size_t largest;
if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) {
largest = n + 1;
} else if (n <= len) {
// The condition is n <= len in case n + 1 is out of the heap vector, but
// not n, in which case we still want to check if the last element of the
// heap vector should be swapped. Otherwise heap[n - 1] represents a
// larger function than heap[(n + 1) - 1].
largest = n;
} else {
// n is out-side the heap vector, thus our element is already in a leaf
// position and would not be moved any more.
break;
}
if (heap[i - 1].first < heap[largest - 1].first) {
// We found a function which has a larger body as a child of the current
// element. we swap it with the current element, such that the largest
// element is closer to the root of the tree.
std::swap(heap[i - 1], heap[largest - 1]);
i = largest;
} else {
// The largest function found as a child of the current node is smaller
// than the current node's function size. The heap tree is now organized
// as expected.
break;
}
}
return result;
}
bool LargeFirstDelazification::insert(ScriptIndex index,
frontend::ScriptStencilRef& ref) {
const frontend::ScriptStencilExtra& extra = ref.scriptExtra();
SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart;
if (!heap.append(std::pair(size, index))) {
return false;
}
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
// the tree of heap-sorted values which bubble up the largest values towards
// the root of the tree.
size_t i = heap.length();
while (i > 1) {
if (heap[i - 1].first <= heap[(i / 2) - 1].first) {
return true;
}
std::swap(heap[i - 1], heap[(i / 2) - 1]);
i /= 2;
}
return true;
}
UniquePtr<DelazifyTask> DelazifyTask::Create(
JSRuntime* runtime, const JS::ContextOptions& contextOptions,
const JS::ReadOnlyCompileOptions& options,
const frontend::CompilationStencil& stencil) {
UniquePtr<DelazifyTask> task;
task.reset(js_new<DelazifyTask>(runtime, contextOptions));
if (!task) {
return nullptr;
}
AutoSetContextFrontendErrors recordErrors(&task->fc_);
RefPtr<ScriptSource> source(stencil.source);
StencilCache& cache = runtime->caches().delazificationCache;
if (!cache.startCaching(std::move(source))) {
return nullptr;
}
// Clone the extensible stencil to be used for eager delazification.
auto initial = task->fc_.getAllocator()
->make_unique<frontend::ExtensibleCompilationStencil>(
options, stencil.source);
if (!initial || !initial->cloneFrom(&task->fc_, stencil)) {
// In case of errors, skip this and delazify on-demand.
return nullptr;
}
if (!task->init(options, std::move(initial))) {
// In case of errors, skip this and delazify on-demand.
return nullptr;
}
return task;
}
DelazifyTask::DelazifyTask(JSRuntime* runtime,
const JS::ContextOptions& options)
: runtime(runtime), contextOptions(options), merger() {}
DelazifyTask::~DelazifyTask() {
// The LinkedListElement destructor will remove us from any list we are part
// of without synchronization, so ensure that doesn't happen.
MOZ_DIAGNOSTIC_ASSERT(!isInList());
}
bool DelazifyTask::init(
const JS::ReadOnlyCompileOptions& options,
UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
using namespace js::frontend;
if (!fc_.allocateOwnedPool()) {
return false;
}
if (!merger.setInitial(&fc_, std::move(initial))) {
return false;
}
switch (options.eagerDelazificationStrategy()) {
case JS::DelazificationOption::OnDemandOnly:
// OnDemandOnly will parse function as they are require to continue the
// execution on the main thread.
MOZ_CRASH("OnDemandOnly should not create a DelazifyTask.");
break;
case JS::DelazificationOption::CheckConcurrentWithOnDemand:
case JS::DelazificationOption::ConcurrentDepthFirst:
// ConcurrentDepthFirst visit all functions to be delazified, visiting the
// inner functions before the siblings functions.
strategy = fc_.getAllocator()->make_unique<DepthFirstDelazification>();
break;
case JS::DelazificationOption::ConcurrentLargeFirst:
// ConcurrentLargeFirst visit all functions to be delazified, visiting the
// largest function first.
strategy = fc_.getAllocator()->make_unique<LargeFirstDelazification>();
break;
case JS::DelazificationOption::ParseEverythingEagerly:
// ParseEverythingEagerly parse all functions eagerly, thus leaving no
// functions to be parsed on demand.
MOZ_CRASH("ParseEverythingEagerly should not create a DelazifyTask");
break;
}
if (!strategy) {
return false;
}
// Queue functions from the top-level to be delazify.
BorrowingCompilationStencil borrow(merger.getResult());
ScriptIndex topLevel{0};
return strategy->add(&fc_, borrow, topLevel);
}
size_t DelazifyTask::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t mergerSize = merger.getResult().sizeOfIncludingThis(mallocSizeOf);
return mergerSize;
}
void DelazifyTask::runHelperThreadTask(AutoLockHelperThreadState& lock) {
{
AutoSetHelperThreadContext usesContext(contextOptions, lock);
AutoUnlockHelperThreadState unlock(lock);
JSContext* cx = TlsContext.get();
if (!runTask(cx)) {
// NOTE: We do not report errors beyond this scope, as there is no where
// to report these errors to. In the mean time, prevent the eager
// delazification from running after any kind of errors.
strategy->clear();
}
MOZ_ASSERT(cx->tempLifoAlloc().isEmpty());
cx->tempLifoAlloc().freeAll();
cx->frontendCollectionPool().purge();
fc_.nameCollectionPool().purge();
}
// If we should continue to delazify even more functions, then re-add this
// task to the vector of delazification tasks. This might happen when the
// DelazifyTask is interrupted by a higher priority task. (see
// mozilla::TaskController & mozilla::Task)
if (!strategy->done()) {
HelperThreadState().submitTask(this, lock);
} else {
UniquePtr<FreeDelazifyTask> freeTask(js_new<FreeDelazifyTask>(this));
if (freeTask) {
HelperThreadState().submitTask(std::move(freeTask), lock);
}
}
}
bool DelazifyTask::runTask(JSContext* cx) {
stackLimit = GetStackLimit();
AutoSetContextRuntime ascr(runtime);
AutoSetContextFrontendErrors recordErrors(&this->fc_);
using namespace js::frontend;
// Create a scope-binding cache dedicated to this Delazification task. The
// memory would be reclaimed if the task is interrupted or if all
// delazification are completed.
//
// We do not use the one from the JSContext/Runtime, as it is not thread safe
// to use it, as it could be purged by a GC in the mean time.
StencilScopeBindingCache scopeCache(merger);
while (!strategy->done() || isInterrupted()) {
RefPtr<CompilationStencil> innerStencil;
ScriptIndex scriptIndex = strategy->next();
{
BorrowingCompilationStencil borrow(merger.getResult());
// Take the next inner function to be delazified.
ScriptStencilRef scriptRef{borrow, scriptIndex};
MOZ_ASSERT(!scriptRef.scriptData().isGhost());
MOZ_ASSERT(!scriptRef.scriptData().hasSharedData());
// Parse and generate bytecode for the inner function.
innerStencil = DelazifyCanonicalScriptedFunction(
cx, &fc_, stackLimit, &scopeCache, borrow, scriptIndex);
if (!innerStencil) {
return false;
}
// Add the generated stencil to the cache, to be consumed by the main
// thread.
StencilCache& cache = runtime->caches().delazificationCache;
StencilContext key(borrow.source, scriptRef.scriptExtra().extent);
if (auto guard = cache.isSourceCached(borrow.source)) {
if (!cache.putNew(guard, key, innerStencil.get())) {
ReportOutOfMemory(&fc_);
return false;
}
} else {
// Stencils for this source are no longer accepted in the cache, thus
// there is no reason to keep our eager delazification going.
strategy->clear();
return true;
}
}
// We are merging the delazification now, while this could be post-poned
// until we have to look at inner functions, this is simpler to do it now
// than querying the cache for every enclosing script.
if (!merger.addDelazification(&this->fc_, *innerStencil)) {
return false;
}
{
BorrowingCompilationStencil borrow(merger.getResult());
if (!strategy->add(&fc_, borrow, scriptIndex)) {
return false;
}
}
}
return true;
}
void FreeDelazifyTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
{
AutoUnlockHelperThreadState unlock(locked);
js_delete(task);
task = nullptr;
}
js_delete(this);
}
static void WaitForOffThreadParses(JSRuntime* rt,
AutoLockHelperThreadState& lock) {
if (!HelperThreadState().isInitialized(lock)) {
return;
}
GlobalHelperThreadState::ParseTaskVector& worklist =
HelperThreadState().parseWorklist(lock);
while (true) {
bool pending = false;
for (const auto& task : worklist) {
if (task->runtimeMatches(rt)) {
pending = true;
break;
}
}
if (!pending) {
bool inProgress = false;
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<ParseTask>() &&
helper->as<ParseTask>()->runtimeMatches(rt)) {
inProgress = true;
break;
}
}
if (!inProgress) {
break;
}
}
HelperThreadState().wait(lock);
}
#ifdef DEBUG
for (const auto& task : worklist) {
MOZ_ASSERT(!task->runtimeMatches(rt));
}
for (auto* helper : HelperThreadState().helperTasks(lock)) {
MOZ_ASSERT_IF(helper->is<ParseTask>(),
!helper->as<ParseTask>()->runtimeMatches(rt));
}
#endif
}
void js::CancelOffThreadParses(JSRuntime* rt) {
AutoLockHelperThreadState lock;
// Instead of forcibly canceling pending parse tasks, just wait for all
// scheduled and in progress ones to complete. Otherwise the final GC may not
// collect everything due to zones being used off thread.
WaitForOffThreadParses(rt, lock);
// Clean up any parse tasks which haven't been finished by the main thread.
auto& finished = HelperThreadState().parseFinishedList(lock);
while (true) {
bool found = false;
ParseTask* next;
ParseTask* task = finished.getFirst();
while (task) {
next = task->getNext();
if (task->runtimeMatches(rt)) {
found = true;
task->remove();
HelperThreadState().destroyParseTask(rt, task);
}
task = next;
}
if (!found) {
break;
}
}
#ifdef DEBUG
for (ParseTask* task : finished) {
MOZ_ASSERT(!task->runtimeMatches(rt));
}
#endif
}
static void CancelPendingDelazifyTask(JSRuntime* rt,
AutoLockHelperThreadState& lock) {
auto& delazifyList = HelperThreadState().delazifyWorklist(lock);
auto end = delazifyList.end();
for (auto iter = delazifyList.begin(); iter != end;) {
DelazifyTask* task = *iter;
++iter;
if (task->runtimeMatches(rt)) {
task->removeFrom(delazifyList);
js_delete(task);
}
}
}
static void WaitUntilCancelledDelazifyTasks(JSRuntime* rt,
AutoLockHelperThreadState& lock) {
if (!HelperThreadState().isInitialized(lock)) {
return;
}
while (true) {
CancelPendingDelazifyTask(rt, lock);
// If running tasks are delazifying any functions, then we have to wait
// until they complete to remove them from the pending list. DelazifyTask
// are inserting themself back to be processed once more after delazifying a
// function.
bool inProgress = false;
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<DelazifyTask>() &&
helper->as<DelazifyTask>()->runtimeMatches(rt)) {
inProgress = true;
break;
}
}
if (!inProgress) {
break;
}
HelperThreadState().wait(lock);
}
#ifdef DEBUG
for (DelazifyTask* task : HelperThreadState().delazifyWorklist(lock)) {
MOZ_ASSERT(!task->runtimeMatches(rt));
}
for (auto* helper : HelperThreadState().helperTasks(lock)) {
MOZ_ASSERT_IF(helper->is<DelazifyTask>(),
!helper->as<DelazifyTask>()->runtimeMatches(rt));
}
#endif
}
static void WaitUntilEmptyFreeDelazifyTaskVector(
AutoLockHelperThreadState& lock) {
if (!HelperThreadState().isInitialized(lock)) {
return;
}
while (true) {
bool inProgress = false;
auto& freeList = HelperThreadState().freeDelazifyTaskVector(lock);
if (!freeList.empty()) {
inProgress = true;
}
// If running tasks are delazifying any functions, then we have to wait
// until they complete to remove them from the pending list. DelazifyTask
// are inserting themself back to be processed once more after delazifying a
// function.
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<FreeDelazifyTask>()) {
inProgress = true;
break;
}
}
if (!inProgress) {
break;
}
HelperThreadState().wait(lock);
}
}
void js::CancelOffThreadDelazify(JSRuntime* runtime) {
AutoLockHelperThreadState lock;
// Cancel all Delazify tasks from the given runtime, and wait if tasks are
// from the given runtime are being executed.
WaitUntilCancelledDelazifyTasks(runtime, lock);
// Empty the free list of delazify task, in case one of the delazify task
// ended and therefore did not returned to the pending list of delazify tasks.
WaitUntilEmptyFreeDelazifyTaskVector(lock);
}
static bool HasAnyDelazifyTask(JSRuntime* rt, AutoLockHelperThreadState& lock) {
auto& delazifyList = HelperThreadState().delazifyWorklist(lock);
for (auto task : delazifyList) {
if (task->runtimeMatches(rt)) {
return true;
}
}
for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (helper->is<DelazifyTask>() &&
helper->as<DelazifyTask>()->runtimeMatches(rt)) {
return true;
}
}
return false;
}
void js::WaitForAllDelazifyTasks(JSRuntime* rt) {
AutoLockHelperThreadState lock;
if (!HelperThreadState().isInitialized(lock)) {
return;
}
while (true) {
if (!HasAnyDelazifyTask(rt, lock)) {
break;
}
HelperThreadState().wait(lock);
}
}
static bool QueueOffThreadParseTask(JSContext* cx, UniquePtr<ParseTask> task) {
AutoLockHelperThreadState lock;
bool result =
HelperThreadState().submitTask(cx->runtime(), std::move(task), lock);
if (!result) {
ReportOutOfMemory(cx);
}
return result;
}
bool GlobalHelperThreadState::submitTask(
JSRuntime* rt, UniquePtr<ParseTask> task,
const AutoLockHelperThreadState& locked) {
if (!parseWorklist(locked).append(std::move(task))) {
return false;
}
dispatch(DispatchReason::NewTask, locked);
return true;
}
void GlobalHelperThreadState::submitTask(
DelazifyTask* task, const AutoLockHelperThreadState& locked) {
delazifyWorklist(locked).insertBack(task);
dispatch(DispatchReason::NewTask, locked);
}
bool GlobalHelperThreadState::submitTask(
UniquePtr<FreeDelazifyTask> task, const AutoLockHelperThreadState& locked) {
if (!freeDelazifyTaskVector(locked).append(std::move(task))) {
return false;
}
dispatch(DispatchReason::NewTask, locked);
return true;
}
static JS::OffThreadToken* StartOffThreadParseTask(
JSContext* cx, UniquePtr<ParseTask> task,
const ReadOnlyCompileOptions& options) {
// Suppress GC so that calls below do not trigger a new incremental GC
// which could require barriers on the atoms zone.
gc::AutoSuppressGC nogc(cx);
if (!task->init(cx, options)) {
return nullptr;
}
JS::OffThreadToken* token = task.get();
if (!QueueOffThreadParseTask(cx, std::move(task))) {
return nullptr;
}
// Return an opaque pointer to caller so that it may query/cancel the task
// before the callback is fired.
return token;
}
template <typename Unit>
static JS::OffThreadToken* StartOffThreadCompileToStencilInternal(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
auto task = cx->make_unique<CompileToStencilTask<Unit>>(cx, srcBuf, callback,
callbackData);
if (!task) {
return nullptr;
}
return StartOffThreadParseTask(cx, std::move(task), options);
}
JS::OffThreadToken* js::StartOffThreadCompileToStencil(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<char16_t>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
return StartOffThreadCompileToStencilInternal(cx, options, srcBuf, callback,
callbackData);
}
JS::OffThreadToken* js::StartOffThreadCompileToStencil(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<Utf8Unit>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
return StartOffThreadCompileToStencilInternal(cx, options, srcBuf, callback,
callbackData);
}
template <typename Unit>
static JS::OffThreadToken* StartOffThreadCompileModuleToStencilInternal(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
auto task = cx->make_unique<CompileModuleToStencilTask<Unit>>(
cx, srcBuf, callback, callbackData);
if (!task) {
return nullptr;
}
return StartOffThreadParseTask(cx, std::move(task), options);
}
JS::OffThreadToken* js::StartOffThreadCompileModuleToStencil(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<char16_t>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
return StartOffThreadCompileModuleToStencilInternal(cx, options, srcBuf,
callback, callbackData);
}
JS::OffThreadToken* js::StartOffThreadCompileModuleToStencil(
JSContext* cx, const ReadOnlyCompileOptions& options,
JS::SourceText<Utf8Unit>& srcBuf, JS::OffThreadCompileCallback callback,
void* callbackData) {
return StartOffThreadCompileModuleToStencilInternal(cx, options, srcBuf,
callback, callbackData);
}
JS::OffThreadToken* js::StartOffThreadDecodeStencil(
JSContext* cx, const JS::DecodeOptions& options,
const JS::TranscodeRange& range, JS::OffThreadCompileCallback callback,
void* callbackData) {
auto task =
cx->make_unique<DecodeStencilTask>(cx, range, callback, callbackData);
if (!task) {
return nullptr;
}
JS::CompileOptions compileOptions(cx);
options.copyTo(compileOptions);
return StartOffThreadParseTask(cx, std::move(task), compileOptions);
}
JS::OffThreadToken* js::StartOffThreadDecodeMultiStencils(
JSContext* cx, const JS::DecodeOptions& options,
JS::TranscodeSources& sources, JS::OffThreadCompileCallback callback,
void* callbackData) {
auto task = cx->make_unique<MultiStencilsDecodeTask>(cx, sources, callback,
callbackData);
if (!task) {
return nullptr;
}
JS::CompileOptions compileOptions(cx);
options.copyTo(compileOptions);
return StartOffThreadParseTask(cx, std::move(task), compileOptions);
}
bool js::CurrentThreadIsParseThread() {
JSContext* cx = TlsContext.get();
// Check whether this is a ParseTask or a DelazifyTask.
return cx && cx->isHelperThreadContext() && cx->frontendErrors();
}
bool GlobalHelperThreadState::ensureInitialized() {
MOZ_ASSERT(CanUseExtraThreads());
MOZ_ASSERT(this == &HelperThreadState());
AutoLockHelperThreadState lock;
if (isInitialized(lock)) {
return true;
}
for (size_t& i : runningTaskCount) {
i = 0;
}
useInternalThreadPool_ = !dispatchTaskCallback;
if (useInternalThreadPool(lock)) {
if (!InternalThreadPool::Initialize(threadCount, lock)) {
return false;
}
}
MOZ_ASSERT(dispatchTaskCallback);
if (!ensureThreadCount(threadCount, lock)) {
finishThreads(lock);
return false;
}