From 051b42edecfbcb3e6c7370a078e46bcd3103c6ea Mon Sep 17 00:00:00 2001 From: Dorel Luca Date: Fri, 11 Jun 2021 19:34:41 +0300 Subject: [PATCH] Backed out 4 changesets (bug 1714141) for Mochitest failures in builds/worker/checkouts/gecko/js/src/vm/HelperThreads.cpp. CLOSED TREE Backed out changeset b4a5d2747861 (bug 1714141) Backed out changeset ab5b2926963a (bug 1714141) Backed out changeset 49dcb1536d0a (bug 1714141) Backed out changeset a2e92a213ed2 (bug 1714141) --- js/src/gc/GCParallelTask.cpp | 5 +- js/src/moz.build | 1 - js/src/vm/HelperThreadState.h | 97 ++++++++++-- js/src/vm/HelperThreads.cpp | 240 +++++++++++++++++++++++------ js/src/vm/InternalThreadPool.cpp | 254 ------------------------------- js/src/vm/InternalThreadPool.h | 96 ------------ js/src/wasm/WasmModule.cpp | 3 +- 7 files changed, 280 insertions(+), 416 deletions(-) delete mode 100644 js/src/vm/InternalThreadPool.cpp delete mode 100644 js/src/vm/InternalThreadPool.h diff --git a/js/src/gc/GCParallelTask.cpp b/js/src/gc/GCParallelTask.cpp index 7d5303050303..a976d23fe63f 100644 --- a/js/src/gc/GCParallelTask.cpp +++ b/js/src/gc/GCParallelTask.cpp @@ -29,7 +29,8 @@ js::GCParallelTask::~GCParallelTask() { void js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock) { MOZ_ASSERT(CanUseExtraThreads()); - MOZ_ASSERT(HelperThreadState().isInitialized(lock)); + MOZ_ASSERT_IF(HelperThreadState().useInternalThreadPool(lock), + !HelperThreadState().threads(lock).empty()); assertIdle(); setDispatched(lock); @@ -101,7 +102,7 @@ void js::GCParallelTask::joinRunningOrFinishedTask( // Wait for the task to run to completion. while (!isFinished(lock)) { - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } setIdle(lock); diff --git a/js/src/moz.build b/js/src/moz.build index 1daa3fce5d19..f54ec3bcdce0 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -395,7 +395,6 @@ UNIFIED_SOURCES += [ "vm/HelperThreads.cpp", "vm/Id.cpp", "vm/Initialization.cpp", - "vm/InternalThreadPool.cpp", "vm/Iteration.cpp", "vm/JitActivation.cpp", "vm/JSAtom.cpp", diff --git a/js/src/vm/HelperThreadState.h b/js/src/vm/HelperThreadState.h index 2e315db68ab8..ebd7ffd5752a 100644 --- a/js/src/vm/HelperThreadState.h +++ b/js/src/vm/HelperThreadState.h @@ -99,6 +99,8 @@ class GlobalHelperThreadState { typedef Vector PromiseHelperTaskVector; typedef Vector ContextVector; + using HelperThreadVector = + Vector, 0, SystemAllocPolicy>; // Count of running task by each threadType. mozilla::EnumeratedArray @@ -111,6 +113,9 @@ class GlobalHelperThreadState { private: // The lists below are all protected by |lock|. + // List of available helper threads. + HelperThreadVector threads_; + // Ion compilation worklist and finished jobs. IonCompileTaskVector ionWorklist_, ionFinishedList_; IonFreeTaskVector ionFreeList_; @@ -156,14 +161,14 @@ class GlobalHelperThreadState { // This is used to get the HelperThreadTask that are currently running. HelperThreadTaskVector helperTasks_; - // Callback to dispatch a task to a thread pool. Set by + // Callback to dispatch a task using an external thread pool. Set by // JS::SetHelperThreadTaskCallback. If this is not set the internal thread // pool is used. JS::HelperThreadTaskCallback dispatchTaskCallback = nullptr; - // The number of tasks dispatched to the thread pool that have not started - // running yet. - size_t tasksPending_ = 0; + // The number of tasks dispatched to the external thread pool that have not + // started running yet. + size_t externalTasksPending_ = 0; bool isInitialized_ = false; @@ -190,15 +195,23 @@ class GlobalHelperThreadState { return isInitialized_; } + HelperThreadVector& threads(const AutoLockHelperThreadState& lock) { + return threads_; + } + const HelperThreadVector& threads( + const AutoLockHelperThreadState& lock) const { + return threads_; + } + [[nodiscard]] bool ensureInitialized(); [[nodiscard]] bool ensureThreadCount(size_t count, - AutoLockHelperThreadState& lock); + const AutoLockHelperThreadState& lock); void finish(AutoLockHelperThreadState& lock); void finishThreads(AutoLockHelperThreadState& lock); void setCpuCount(size_t count); - void setDispatchTaskCallback(JS::HelperThreadTaskCallback callback, + void setExternalTaskCallback(JS::HelperThreadTaskCallback callback, size_t threadCount); [[nodiscard]] bool ensureContextList(size_t count, @@ -210,9 +223,21 @@ class GlobalHelperThreadState { void assertIsLockedByCurrentThread() const; #endif - void wait(AutoLockHelperThreadState& locked, + enum CondVar { + // For notifying threads waiting for work that they may be able to make + // progress, ie, a work item has been completed by a helper thread and + // the thread that created the work item can now consume it. + CONSUMER, + + // For notifying helper threads doing the work that they may be able to + // make progress, ie, a work item has been enqueued and an idle helper + // thread may pick up up the work item and perform it. + PRODUCER, + }; + + void wait(AutoLockHelperThreadState& locked, CondVar which, mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever()); - void notifyAll(const AutoLockHelperThreadState&); + void notifyAll(CondVar which, const AutoLockHelperThreadState&); bool useInternalThreadPool(const AutoLockHelperThreadState& lock) const { return useInternalThreadPool_; @@ -223,7 +248,7 @@ class GlobalHelperThreadState { } private: - void notifyOne(const AutoLockHelperThreadState&); + void notifyOne(CondVar which, const AutoLockHelperThreadState&); public: // Helper method for removing items from the vectors below while iterating @@ -412,9 +437,20 @@ class GlobalHelperThreadState { void triggerFreeUnusedMemory(); private: - // Condition variable for notifiying the main thread that a helper task has - // completed some work. + /* Condvars for threads waiting/notifying each other. */ js::ConditionVariable consumerWakeup; + js::ConditionVariable producerWakeup; + + js::ConditionVariable& whichWakeup(CondVar which) { + switch (which) { + case CONSUMER: + return consumerWakeup; + case PRODUCER: + return producerWakeup; + default: + MOZ_CRASH("Invalid CondVar in |whichWakeup|"); + } + } void dispatch(const AutoLockHelperThreadState& locked); @@ -434,8 +470,8 @@ class GlobalHelperThreadState { bool submitTask(PromiseHelperTask* task); bool submitTask(GCParallelTask* task, const AutoLockHelperThreadState& locked); - void runOneTask(AutoLockHelperThreadState& lock); void runTaskLocked(HelperThreadTask* task, AutoLockHelperThreadState& lock); + void runTaskFromExternalThread(AutoLockHelperThreadState& lock); using Selector = HelperThreadTask* ( GlobalHelperThreadState::*)(const AutoLockHelperThreadState&); @@ -452,6 +488,43 @@ static inline GlobalHelperThreadState& HelperThreadState() { return *gHelperThreadState; } +/* Individual helper thread, one allocated per core. */ +class HelperThread { + Thread thread; + + /* + * The profiling thread for this helper thread, which can be used to push + * and pop label frames. + * This field being non-null indicates that this thread has been registered + * and needs to be unregistered at shutdown. + */ + ProfilingStack* profilingStack = nullptr; + + public: + HelperThread(); + [[nodiscard]] bool init(); + + ThreadId threadId() { return thread.get_id(); } + + void join(); + + static void ThreadMain(void* arg); + void threadLoop(); + + void ensureRegisteredWithProfiler(); + void unregisterWithProfilerIfNeeded(); + + private: + struct AutoProfilerLabel { + AutoProfilerLabel(HelperThread* helperThread, const char* label, + JS::ProfilingCategoryPair categoryPair); + ~AutoProfilerLabel(); + + private: + ProfilingStack* profilingStack; + }; +}; + class MOZ_RAII AutoSetHelperThreadContext { JSContext* cx; AutoLockHelperThreadState& lock; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index ae28b6540b0a..2fa7ad112d7d 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -30,7 +30,6 @@ #include "util/NativeStack.h" #include "vm/ErrorReporting.h" #include "vm/HelperThreadState.h" -#include "vm/InternalThreadPool.h" #include "vm/MutexIDs.h" #include "vm/SharedImmutableStringsCache.h" #include "vm/Time.h" @@ -63,6 +62,16 @@ GlobalHelperThreadState* gHelperThreadState = nullptr; } // namespace js +// These macros are identical in function to the same-named ones in +// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't +// use GeckoProfiler.h. +#define PROFILER_RAII_PASTE(id, line) id##line +#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) +#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) +#define AUTO_PROFILER_LABEL(label, categoryPair) \ + HelperThread::AutoProfilerLabel PROFILER_RAII( \ + this, label, JS::ProfilingCategoryPair::categoryPair) + bool js::CreateHelperThreadsState() { MOZ_ASSERT(!gHelperThreadState); gHelperThreadState = js_new(); @@ -108,7 +117,7 @@ bool js::SetFakeCPUCount(size_t count) { } void GlobalHelperThreadState::setCpuCount(size_t count) { - // This must be called before any threads have been initialized. + // This must be called before the threads have been initialized. AutoLockHelperThreadState lock; MOZ_ASSERT(!isInitialized(lock)); @@ -138,10 +147,10 @@ void JS::SetProfilingThreadCallbacks( // error for HelperThreadTaskCallback. JS_PUBLIC_API MOZ_NEVER_INLINE void JS::SetHelperThreadTaskCallback( HelperThreadTaskCallback callback, size_t threadCount) { - HelperThreadState().setDispatchTaskCallback(callback, threadCount); + HelperThreadState().setExternalTaskCallback(callback, threadCount); } -void GlobalHelperThreadState::setDispatchTaskCallback( +void GlobalHelperThreadState::setExternalTaskCallback( JS::HelperThreadTaskCallback callback, size_t threadCount) { AutoLockHelperThreadState lock; MOZ_ASSERT(!isInitialized(lock)); @@ -235,7 +244,7 @@ static void CancelOffThreadWasmTier2GeneratorLocked( HelperThreadState().wasmTier2GeneratorsFinished(lock); while (HelperThreadState().wasmTier2GeneratorsFinished(lock) == oldFinishedCount) { - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } // At most one of these tasks. @@ -398,7 +407,7 @@ static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector, } } if (cancelled) { - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } } while (cancelled); @@ -490,6 +499,32 @@ struct MOZ_RAII AutoSetContextParse { ~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); } }; +// We want our default stack size limit to be approximately 2MB, to be safe, but +// expect most threads to use much less. On Linux, however, requesting a stack +// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it +// on first access, which we do not want. To avoid this possibility, we subtract +// 2 standard VM page sizes from our default. +static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096; +static const uint32_t kDefaultHelperStackQuota = 1800 * 1024; + +// TSan enforces a minimum stack size that's just slightly larger than our +// default helper stack size. It does this to store blobs of TSan-specific +// data on each thread's stack. Unfortunately, that means that even though +// we'll actually receive a larger stack than we requested, the effective +// usable space of that stack is significantly less than what we expect. +// To offset TSan stealing our stack space from underneath us, double the +// default. +// +// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't +// require all the thread-specific state that TSan does. +#if defined(MOZ_TSAN) +static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize; +static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota; +#else +static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize; +static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota; +#endif + AutoSetHelperThreadContext::AutoSetHelperThreadContext( AutoLockHelperThreadState& lock) : lock(lock) { @@ -926,7 +961,7 @@ static void WaitForOffThreadParses(JSRuntime* rt, break; } } - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } #ifdef DEBUG @@ -1300,14 +1335,6 @@ bool GlobalHelperThreadState::ensureInitialized() { i = 0; } - if (useInternalThreadPool(lock)) { - if (!InternalThreadPool::Initialize(threadCount, lock)) { - return false; - } - - dispatchTaskCallback = InternalThreadPool::DispatchTask; - } - if (!ensureThreadCount(threadCount, lock)) { finishThreads(lock); return false; @@ -1319,7 +1346,7 @@ bool GlobalHelperThreadState::ensureInitialized() { } bool GlobalHelperThreadState::ensureThreadCount( - size_t count, AutoLockHelperThreadState& lock) { + size_t count, const AutoLockHelperThreadState& lock) { if (!ensureContextList(count, lock)) { return false; } @@ -1328,13 +1355,26 @@ bool GlobalHelperThreadState::ensureThreadCount( return false; } - if (useInternalThreadPool(lock)) { - InternalThreadPool& pool = InternalThreadPool::Get(); - if (!pool.ensureThreadCount(count, lock)) { + if (!useInternalThreadPool(lock) || threads(lock).length() >= count) { + return true; + } + + if (!threads(lock).reserve(count)) { + return false; + } + + // Update threadCount on exit so this stays consistent with how many threads + // there are. + auto updateThreadCount = + mozilla::MakeScopeExit([&] { threadCount = threads(lock).length(); }); + + while (threads(lock).length() < count) { + auto thread = js::MakeUnique(); + if (!thread || !thread->init()) { return false; } - threadCount = pool.threadCount(lock); + threads(lock).infallibleEmplaceBack(std::move(thread)); } return true; @@ -1378,11 +1418,23 @@ void GlobalHelperThreadState::finish(AutoLockHelperThreadState& lock) { } void GlobalHelperThreadState::finishThreads(AutoLockHelperThreadState& lock) { + HelperThreadVector oldThreads; + waitForAllTasksLocked(lock); + terminating_ = true; - if (InternalThreadPool::IsInitialized()) { - InternalThreadPool::ShutDown(lock); + if (!useInternalThreadPool(lock)) { + return; + } + + notifyAll(GlobalHelperThreadState::PRODUCER, lock); + + std::swap(threads_, oldThreads); + + AutoUnlockHelperThreadState unlock(lock); + for (auto& thread : oldThreads) { + thread->join(); } } @@ -1424,11 +1476,16 @@ void GlobalHelperThreadState::assertIsLockedByCurrentThread() const { void GlobalHelperThreadState::dispatch( const AutoLockHelperThreadState& locked) { - if (canStartTasks(locked) && tasksPending_ < threadCount) { + if (useInternalThreadPool(locked)) { + notifyOne(PRODUCER, locked); + return; + } + + if (canStartTasks(locked) && externalTasksPending_ < threadCount) { // This doesn't guarantee that we don't dispatch more tasks to the external // pool than necessary if tasks are taking a long time to start, but it does // limit the number. - tasksPending_++; + externalTasksPending_++; // The hazard analysis can't tell that the callback doesn't GC. JS::AutoSuppressGCAnalysis nogc; @@ -1438,17 +1495,19 @@ void GlobalHelperThreadState::dispatch( } void GlobalHelperThreadState::wait( - AutoLockHelperThreadState& locked, + AutoLockHelperThreadState& locked, CondVar which, TimeDuration timeout /* = TimeDuration::Forever() */) { - consumerWakeup.wait_for(locked, timeout); + whichWakeup(which).wait_for(locked, timeout); } -void GlobalHelperThreadState::notifyAll(const AutoLockHelperThreadState&) { - consumerWakeup.notify_all(); +void GlobalHelperThreadState::notifyAll(CondVar which, + const AutoLockHelperThreadState&) { + whichWakeup(which).notify_all(); } -void GlobalHelperThreadState::notifyOne(const AutoLockHelperThreadState&) { - consumerWakeup.notify_one(); +void GlobalHelperThreadState::notifyOne(CondVar which, + const AutoLockHelperThreadState&) { + whichWakeup(which).notify_one(); } bool GlobalHelperThreadState::hasActiveThreads( @@ -1471,9 +1530,9 @@ void GlobalHelperThreadState::waitForAllTasksLocked( AutoLockHelperThreadState& lock) { CancelOffThreadWasmTier2GeneratorLocked(lock); - while (canStartTasks(lock) || tasksPending_ || + while (canStartTasks(lock) || externalTasksPending_ || hasActiveThreads(lock)) { - wait(lock); + wait(lock, CONSUMER); } MOZ_ASSERT(gcParallelWorklist(lock).isEmpty()); @@ -1485,7 +1544,7 @@ void GlobalHelperThreadState::waitForAllTasksLocked( MOZ_ASSERT(ionFreeList(lock).empty()); MOZ_ASSERT(wasmWorklist(lock, wasm::CompileMode::Tier2).empty()); MOZ_ASSERT(wasmTier2GeneratorWorklist(lock).empty()); - MOZ_ASSERT(!tasksPending_); + MOZ_ASSERT(!externalTasksPending_); MOZ_ASSERT(!hasActiveThreads(lock)); } @@ -1576,10 +1635,7 @@ void GlobalHelperThreadState::addSizeOfIncludingThis( htStats.stateData += mallocSizeOf(this); - if (InternalThreadPool::IsInitialized()) { - htStats.stateData += - InternalThreadPool::Get().sizeOfIncludingThis(mallocSizeOf, lock); - } + htStats.stateData += threads(lock).sizeOfExcludingThis(mallocSizeOf); // Report memory used by various containers htStats.stateData += @@ -2366,7 +2422,7 @@ void GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind, break; } - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } auto& finished = HelperThreadState().parseFinishedList(lock); @@ -2404,6 +2460,56 @@ void GlobalHelperThreadState::mergeParseTaskRealm(JSContext* cx, gc::MergeRealms(parseTask->parseGlobal->as().realm(), dest); } +HelperThread::HelperThread() + : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {} + +bool HelperThread::init() { + return thread.init(HelperThread::ThreadMain, this); +} + +void HelperThread::join() { thread.join(); } + +void HelperThread::ensureRegisteredWithProfiler() { + if (profilingStack) { + return; + } + + // Note: To avoid dead locks, we should not hold on the helper thread lock + // while calling this function. This is safe because the registerThread field + // is a WriteOnceData<> type stored on the global helper tread state. + JS::RegisterThreadCallback callback = HelperThreadState().registerThread; + if (callback) { + profilingStack = + callback("JS Helper", reinterpret_cast(GetNativeStackBase())); + } +} + +void HelperThread::unregisterWithProfilerIfNeeded() { + if (!profilingStack) { + return; + } + + // Note: To avoid dead locks, we should not hold on the helper thread lock + // while calling this function. This is safe because the unregisterThread + // field is a WriteOnceData<> type stored on the global helper tread state. + JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread; + if (callback) { + callback(); + profilingStack = nullptr; + } +} + +/* static */ +void HelperThread::ThreadMain(void* arg) { + ThisThread::SetName("JS Helper"); + + auto helper = static_cast(arg); + + helper->ensureRegisteredWithProfiler(); + helper->threadLoop(); + helper->unregisterWithProfilerIfNeeded(); +} + bool JSContext::addPendingCompileError(js::CompileError** error) { auto errorPtr = make_unique(); if (!errorPtr) { @@ -2495,7 +2601,7 @@ void js::CancelOffThreadCompressions(JSRuntime* runtime) { break; } - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } // Clean up finished tasks. @@ -2536,7 +2642,7 @@ void js::RunPendingSourceCompressions(JSRuntime* runtime) { // Wait until all tasks have started compression. while (!HelperThreadState().compressionWorklist(lock).empty()) { - HelperThreadState().wait(lock); + HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER); } // Wait for all in-process compression tasks to complete. @@ -2675,6 +2781,45 @@ bool GlobalHelperThreadState::canStartTasks( canStartWasmTier2GeneratorTask(lock); } +HelperThread::AutoProfilerLabel::AutoProfilerLabel( + HelperThread* helperThread, const char* label, + JS::ProfilingCategoryPair categoryPair) + : profilingStack(helperThread->profilingStack) { + if (profilingStack) { + profilingStack->pushLabelFrame(label, nullptr, this, categoryPair); + } +} + +HelperThread::AutoProfilerLabel::~AutoProfilerLabel() { + if (profilingStack) { + profilingStack->pop(); + } +} + +void HelperThread::threadLoop() { + MOZ_ASSERT(CanUseExtraThreads()); + + AutoLockHelperThreadState lock; + + while (!HelperThreadState().isTerminating(lock)) { + // The selectors may depend on the HelperThreadState not changing + // between task selection and task execution, in particular, on new + // tasks not being added (because of the lifo structure of the work + // lists). Unlocking the HelperThreadState between task selection and + // execution is not well-defined. + + HelperThreadTask* task = HelperThreadState().findHighestPriorityTask(lock); + if (!task) { + AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE); + HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER); + continue; + } + + HelperThreadState().runTaskLocked(task, lock); + HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock); + } +} + void JS::RunHelperThreadTask() { MOZ_ASSERT(CanUseExtraThreads()); @@ -2684,24 +2829,21 @@ void JS::RunHelperThreadTask() { return; } - HelperThreadState().runOneTask(lock); + HelperThreadState().runTaskFromExternalThread(lock); } -void GlobalHelperThreadState::runOneTask(AutoLockHelperThreadState& lock) { - MOZ_ASSERT(tasksPending_ > 0); - tasksPending_--; +void GlobalHelperThreadState::runTaskFromExternalThread( + AutoLockHelperThreadState& lock) { + MOZ_ASSERT(externalTasksPending_ > 0); + externalTasksPending_--; - // The selectors may depend on the HelperThreadState not changing between task - // selection and task execution, in particular, on new tasks not being added - // (because of the lifo structure of the work lists). Unlocking the - // HelperThreadState between task selection and execution is not well-defined. HelperThreadTask* task = findHighestPriorityTask(lock); if (task) { runTaskLocked(task, lock); dispatch(lock); } - notifyAll(lock); + notifyAll(GlobalHelperThreadState::CONSUMER, lock); } HelperThreadTask* GlobalHelperThreadState::findHighestPriorityTask( diff --git a/js/src/vm/InternalThreadPool.cpp b/js/src/vm/InternalThreadPool.cpp deleted file mode 100644 index 2edcb162c2bb..000000000000 --- a/js/src/vm/InternalThreadPool.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* -*- 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 - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "vm/InternalThreadPool.h" - -#include "mozilla/ScopeExit.h" -#include "mozilla/TimeStamp.h" - -#include "js/ProfilingCategory.h" -#include "js/ProfilingStack.h" -#include "threading/Thread.h" -#include "util/NativeStack.h" -#include "vm/HelperThreadState.h" - -// These macros are identical in function to the same-named ones in -// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't -// use GeckoProfiler.h. -#define PROFILER_RAII_PASTE(id, line) id##line -#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) -#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) -#define AUTO_PROFILER_LABEL(label, categoryPair) \ - HelperThread::AutoProfilerLabel PROFILER_RAII( \ - this, label, JS::ProfilingCategoryPair::categoryPair) - -using namespace js; - -namespace js { - -class HelperThread { - Thread thread; - - /* - * The profiling thread for this helper thread, which can be used to push - * and pop label frames. - * This field being non-null indicates that this thread has been registered - * and needs to be unregistered at shutdown. - */ - ProfilingStack* profilingStack = nullptr; - - public: - HelperThread(); - [[nodiscard]] bool init(InternalThreadPool* pool); - - ThreadId threadId() { return thread.get_id(); } - - void join(); - - static void ThreadMain(InternalThreadPool* pool, HelperThread* helper); - void threadLoop(InternalThreadPool* pool); - - void ensureRegisteredWithProfiler(); - void unregisterWithProfilerIfNeeded(); - - private: - struct AutoProfilerLabel { - AutoProfilerLabel(HelperThread* helperThread, const char* label, - JS::ProfilingCategoryPair categoryPair); - ~AutoProfilerLabel(); - - private: - ProfilingStack* profilingStack; - }; -}; - -} // namespace js - -InternalThreadPool* InternalThreadPool::Instance = nullptr; - -/* static */ InternalThreadPool& InternalThreadPool::Get() { - MOZ_ASSERT(IsInitialized()); - return *Instance; -} - -/* static */ -bool InternalThreadPool::Initialize(size_t threadCount, - AutoLockHelperThreadState& lock) { - if (IsInitialized()) { - return true; - } - - auto instance = MakeUnique(); - if (!instance || !instance->ensureThreadCount(threadCount, lock)) { - return false; - } - - Instance = instance.release(); - return true; -} - -bool InternalThreadPool::ensureThreadCount(size_t threadCount, - AutoLockHelperThreadState& lock) { - if (threads(lock).length() >= threadCount) { - return true; - } - - if (!threads(lock).reserve(threadCount)) { - return false; - } - - auto shutdown = mozilla::MakeScopeExit([&] { shutDown(lock); }); - - while (threads(lock).length() < threadCount) { - auto thread = js::MakeUnique(); - if (!thread || !thread->init(this)) { - return false; - } - - threads(lock).infallibleEmplaceBack(std::move(thread)); - } - - shutdown.release(); - return true; -} - -size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) { - return threads(lock).length(); -} - -/* static */ -void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) { - MOZ_ASSERT(HelperThreadState().isTerminating(lock)); - - Get().shutDown(lock); - js_delete(Instance); - Instance = nullptr; -} - -void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) { - MOZ_ASSERT(!terminating); - terminating = true; - - notifyAll(lock); - - for (auto& thread : threads(lock)) { - AutoUnlockHelperThreadState unlock(lock); - thread->join(); - } -} - -inline HelperThreadVector& InternalThreadPool::threads( - const AutoLockHelperThreadState& lock) { - return threads_.ref(); -} -inline const HelperThreadVector& InternalThreadPool::threads( - const AutoLockHelperThreadState& lock) const { - return threads_.ref(); -} - -size_t InternalThreadPool::sizeOfIncludingThis( - mozilla::MallocSizeOf mallocSizeOf, - const AutoLockHelperThreadState& lock) const { - return sizeof(InternalThreadPool) + - threads(lock).sizeOfExcludingThis(mallocSizeOf); -} - -/* static */ -void InternalThreadPool::DispatchTask() { Get().dispatchTask(); } - -void InternalThreadPool::dispatchTask() { - gHelperThreadLock.assertOwnedByCurrentThread(); - queuedTasks++; - wakeup.notify_one(); -} - -void InternalThreadPool::notifyAll(const AutoLockHelperThreadState& lock) { - wakeup.notify_all(); -} - -void InternalThreadPool::wait(AutoLockHelperThreadState& lock) { - wakeup.wait_for(lock, mozilla::TimeDuration::Forever()); -} - -HelperThread::HelperThread() - : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {} - -bool HelperThread::init(InternalThreadPool* pool) { - return thread.init(HelperThread::ThreadMain, pool, this); -} - -void HelperThread::join() { thread.join(); } - -/* static */ -void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) { - ThisThread::SetName("JS Helper"); - - helper->ensureRegisteredWithProfiler(); - helper->threadLoop(pool); - helper->unregisterWithProfilerIfNeeded(); -} - -void HelperThread::ensureRegisteredWithProfiler() { - if (profilingStack) { - return; - } - - // Note: To avoid dead locks, we should not hold on the helper thread lock - // while calling this function. This is safe because the registerThread field - // is a WriteOnceData<> type stored on the global helper tread state. - JS::RegisterThreadCallback callback = HelperThreadState().registerThread; - if (callback) { - profilingStack = - callback("JS Helper", reinterpret_cast(GetNativeStackBase())); - } -} - -void HelperThread::unregisterWithProfilerIfNeeded() { - if (!profilingStack) { - return; - } - - // Note: To avoid dead locks, we should not hold on the helper thread lock - // while calling this function. This is safe because the unregisterThread - // field is a WriteOnceData<> type stored on the global helper tread state. - JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread; - if (callback) { - callback(); - profilingStack = nullptr; - } -} - -HelperThread::AutoProfilerLabel::AutoProfilerLabel( - HelperThread* helperThread, const char* label, - JS::ProfilingCategoryPair categoryPair) - : profilingStack(helperThread->profilingStack) { - if (profilingStack) { - profilingStack->pushLabelFrame(label, nullptr, this, categoryPair); - } -} - -HelperThread::AutoProfilerLabel::~AutoProfilerLabel() { - if (profilingStack) { - profilingStack->pop(); - } -} - -void HelperThread::threadLoop(InternalThreadPool* pool) { - MOZ_ASSERT(CanUseExtraThreads()); - - AutoLockHelperThreadState lock; - - while (!pool->terminating) { - if (pool->queuedTasks != 0) { - pool->queuedTasks--; - HelperThreadState().runOneTask(lock); - continue; - } - - AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE); - pool->wait(lock); - } -} diff --git a/js/src/vm/InternalThreadPool.h b/js/src/vm/InternalThreadPool.h deleted file mode 100644 index 889e5a146ab9..000000000000 --- a/js/src/vm/InternalThreadPool.h +++ /dev/null @@ -1,96 +0,0 @@ -/* -*- 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 - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* - * An internal thread pool, used for the shell and when - * JS::SetHelperThreadTaskCallback not called. - */ - -#ifndef vm_InternalThreadPool_h -#define vm_InternalThreadPool_h - -#include "js/AllocPolicy.h" -#include "js/UniquePtr.h" -#include "js/Vector.h" -#include "threading/ConditionVariable.h" -#include "threading/ProtectedData.h" - -// We want our default stack size limit to be approximately 2MB, to be safe, but -// expect most threads to use much less. On Linux, however, requesting a stack -// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it -// on first access, which we do not want. To avoid this possibility, we subtract -// 2 standard VM page sizes from our default. -static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096; -static const uint32_t kDefaultHelperStackQuota = 1800 * 1024; - -// TSan enforces a minimum stack size that's just slightly larger than our -// default helper stack size. It does this to store blobs of TSan-specific -// data on each thread's stack. Unfortunately, that means that even though -// we'll actually receive a larger stack than we requested, the effective -// usable space of that stack is significantly less than what we expect. -// To offset TSan stealing our stack space from underneath us, double the -// default. -// -// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't -// require all the thread-specific state that TSan does. -#if defined(MOZ_TSAN) -static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize; -static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota; -#else -static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize; -static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota; -#endif - -namespace js { - -class AutoLockHelperThreadState; -class HelperThread; - -using HelperThreadVector = - Vector, 0, SystemAllocPolicy>; - -class InternalThreadPool { - public: - static bool Initialize(size_t threadCount, AutoLockHelperThreadState& lock); - static void ShutDown(AutoLockHelperThreadState& lock); - - static bool IsInitialized() { return Instance; } - static InternalThreadPool& Get(); - - static void DispatchTask(); - - bool ensureThreadCount(size_t threadCount, AutoLockHelperThreadState& lock); - size_t threadCount(const AutoLockHelperThreadState& lock); - - size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, - const AutoLockHelperThreadState& lock) const; - - private: - void dispatchTask(); - void shutDown(AutoLockHelperThreadState& lock); - - HelperThreadVector& threads(const AutoLockHelperThreadState& lock); - const HelperThreadVector& threads( - const AutoLockHelperThreadState& lock) const; - - void notifyAll(const AutoLockHelperThreadState& lock); - void wait(AutoLockHelperThreadState& lock); - friend class HelperThread; - - static InternalThreadPool* Instance; - - HelperThreadLockData threads_; - - js::ConditionVariable wakeup; - - HelperThreadLockData queuedTasks; - - HelperThreadLockData terminating; -}; - -} // namespace js - -#endif /* vm_InternalThreadPool_h */ diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp index 1dc64425541d..c9bfed0073dc 100644 --- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -72,8 +72,7 @@ class Module::Tier2GeneratorTaskImpl : public Tier2GeneratorTask { // During shutdown the main thread will wait for any ongoing (cancelled) // tier-2 generation to shut down normally. To do so, it waits on the - // HelperThreadState's condition variable for the count of finished - // generators to rise. + // CONSUMER condition for the count of finished generators to rise. HelperThreadState().incWasmTier2GeneratorsFinished(locked); // The task is finished, release it.