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)
This commit is contained in:
Dorel Luca 2021-06-11 19:34:41 +03:00
Родитель 8720353dbf
Коммит 051b42edec
7 изменённых файлов: 280 добавлений и 416 удалений

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

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

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

@ -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",

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

@ -99,6 +99,8 @@ class GlobalHelperThreadState {
typedef Vector<PromiseHelperTask*, 0, SystemAllocPolicy>
PromiseHelperTaskVector;
typedef Vector<JSContext*, 0, SystemAllocPolicy> ContextVector;
using HelperThreadVector =
Vector<UniquePtr<HelperThread>, 0, SystemAllocPolicy>;
// Count of running task by each threadType.
mozilla::EnumeratedArray<ThreadType, ThreadType::THREAD_TYPE_MAX, size_t>
@ -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;

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

@ -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<GlobalHelperThreadState>();
@ -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<HelperThread>();
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<GlobalObject>().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<void*>(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<HelperThread*>(arg);
helper->ensureRegisteredWithProfiler();
helper->threadLoop();
helper->unregisterWithProfilerIfNeeded();
}
bool JSContext::addPendingCompileError(js::CompileError** error) {
auto errorPtr = make_unique<js::CompileError>();
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(

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

@ -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<InternalThreadPool>();
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<HelperThread>();
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<void*>(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);
}
}

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

@ -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<UniquePtr<HelperThread>, 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<HelperThreadVector> threads_;
js::ConditionVariable wakeup;
HelperThreadLockData<size_t> queuedTasks;
HelperThreadLockData<bool> terminating;
};
} // namespace js
#endif /* vm_InternalThreadPool_h */

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

@ -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.