Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / InternalThreadPool.cpp
blob483e995254610c92b836b769dec338ef6b8282fc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "vm/InternalThreadPool.h"
9 #include "mozilla/TimeStamp.h"
11 #include "js/ProfilingCategory.h"
12 #include "js/ProfilingStack.h"
13 #include "threading/Thread.h"
14 #include "util/NativeStack.h"
15 #include "vm/HelperThreadState.h"
16 #include "vm/JSContext.h"
18 // We want our default stack size limit to be approximately 2MB, to be safe, but
19 // expect most threads to use much less. On Linux, however, requesting a stack
20 // of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
21 // on first access, which we do not want. To avoid this possibility, we subtract
22 // 2 standard VM page sizes from our default.
23 static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
25 // TSan enforces a minimum stack size that's just slightly larger than our
26 // default helper stack size. It does this to store blobs of TSan-specific
27 // data on each thread's stack. Unfortunately, that means that even though
28 // we'll actually receive a larger stack than we requested, the effective
29 // usable space of that stack is significantly less than what we expect.
30 // To offset TSan stealing our stack space from underneath us, double the
31 // default.
33 // Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
34 // require all the thread-specific state that TSan does.
35 #if defined(MOZ_TSAN)
36 static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
37 #else
38 static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
39 #endif
41 // These macros are identical in function to the same-named ones in
42 // GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
43 // use GeckoProfiler.h.
44 #define PROFILER_RAII_PASTE(id, line) id##line
45 #define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line)
46 #define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__)
47 #define AUTO_PROFILER_LABEL(label, categoryPair) \
48 HelperThread::AutoProfilerLabel PROFILER_RAII( \
49 this, label, JS::ProfilingCategoryPair::categoryPair)
51 using namespace js;
53 namespace js {
55 class HelperThread {
56 Thread thread;
59 * The profiling thread for this helper thread, which can be used to push
60 * and pop label frames.
61 * This field being non-null indicates that this thread has been registered
62 * and needs to be unregistered at shutdown.
64 ProfilingStack* profilingStack = nullptr;
66 public:
67 HelperThread();
68 [[nodiscard]] bool init(InternalThreadPool* pool);
70 ThreadId threadId() { return thread.get_id(); }
72 void join();
74 static void ThreadMain(InternalThreadPool* pool, HelperThread* helper);
75 void threadLoop(InternalThreadPool* pool);
77 void ensureRegisteredWithProfiler();
78 void unregisterWithProfilerIfNeeded();
80 private:
81 struct AutoProfilerLabel {
82 AutoProfilerLabel(HelperThread* helperThread, const char* label,
83 JS::ProfilingCategoryPair categoryPair);
84 ~AutoProfilerLabel();
86 private:
87 ProfilingStack* profilingStack;
91 } // namespace js
93 InternalThreadPool* InternalThreadPool::Instance = nullptr;
95 /* static */ InternalThreadPool& InternalThreadPool::Get() {
96 MOZ_ASSERT(IsInitialized());
97 return *Instance;
100 /* static */
101 bool InternalThreadPool::Initialize(size_t threadCount,
102 AutoLockHelperThreadState& lock) {
103 if (IsInitialized()) {
104 return true;
107 auto instance = MakeUnique<InternalThreadPool>();
108 if (!instance) {
109 return false;
112 if (!instance->ensureThreadCount(threadCount, lock)) {
113 instance->shutDown(lock);
114 return false;
117 Instance = instance.release();
118 HelperThreadState().setDispatchTaskCallback(DispatchTask, threadCount,
119 HELPER_STACK_SIZE, lock);
120 return true;
123 bool InternalThreadPool::ensureThreadCount(size_t threadCount,
124 AutoLockHelperThreadState& lock) {
125 MOZ_ASSERT(threads(lock).length() < threadCount);
127 if (!threads(lock).reserve(threadCount)) {
128 return false;
131 while (threads(lock).length() < threadCount) {
132 auto thread = js::MakeUnique<HelperThread>();
133 if (!thread || !thread->init(this)) {
134 return false;
137 threads(lock).infallibleEmplaceBack(std::move(thread));
140 return true;
143 size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) {
144 return threads(lock).length();
147 /* static */
148 void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) {
149 MOZ_ASSERT(HelperThreadState().isTerminating(lock));
151 Get().shutDown(lock);
152 js_delete(Instance);
153 Instance = nullptr;
156 void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) {
157 MOZ_ASSERT(!terminating);
158 terminating = true;
160 notifyAll(lock);
162 for (auto& thread : threads(lock)) {
163 AutoUnlockHelperThreadState unlock(lock);
164 thread->join();
168 inline HelperThreadVector& InternalThreadPool::threads(
169 const AutoLockHelperThreadState& lock) {
170 return threads_.ref();
172 inline const HelperThreadVector& InternalThreadPool::threads(
173 const AutoLockHelperThreadState& lock) const {
174 return threads_.ref();
177 size_t InternalThreadPool::sizeOfIncludingThis(
178 mozilla::MallocSizeOf mallocSizeOf,
179 const AutoLockHelperThreadState& lock) const {
180 return sizeof(InternalThreadPool) +
181 threads(lock).sizeOfExcludingThis(mallocSizeOf);
184 /* static */
185 void InternalThreadPool::DispatchTask(JS::DispatchReason reason) {
186 Get().dispatchTask(reason);
189 void InternalThreadPool::dispatchTask(JS::DispatchReason reason) {
190 gHelperThreadLock.assertOwnedByCurrentThread();
191 queuedTasks++;
192 if (reason == JS::DispatchReason::NewTask) {
193 wakeup.notify_one();
194 } else {
195 // We're called from a helper thread right before returning to
196 // HelperThread::threadLoop. There we will check queuedTasks so there's no
197 // need to wake up any threads.
198 MOZ_ASSERT(reason == JS::DispatchReason::FinishedTask);
199 MOZ_ASSERT(!TlsContext.get(), "we should be on a helper thread");
203 void InternalThreadPool::notifyAll(const AutoLockHelperThreadState& lock) {
204 wakeup.notify_all();
207 void InternalThreadPool::wait(AutoLockHelperThreadState& lock) {
208 wakeup.wait_for(lock, mozilla::TimeDuration::Forever());
211 HelperThread::HelperThread()
212 : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {}
214 bool HelperThread::init(InternalThreadPool* pool) {
215 return thread.init(HelperThread::ThreadMain, pool, this);
218 void HelperThread::join() { thread.join(); }
220 /* static */
221 void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) {
222 ThisThread::SetName("JS Helper");
224 helper->ensureRegisteredWithProfiler();
225 helper->threadLoop(pool);
226 helper->unregisterWithProfilerIfNeeded();
229 void HelperThread::ensureRegisteredWithProfiler() {
230 if (profilingStack) {
231 return;
234 // Note: To avoid dead locks, we should not hold on the helper thread lock
235 // while calling this function. This is safe because the registerThread field
236 // is a WriteOnceData<> type stored on the global helper tread state.
237 JS::RegisterThreadCallback callback = HelperThreadState().registerThread;
238 if (callback) {
239 profilingStack =
240 callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase()));
244 void HelperThread::unregisterWithProfilerIfNeeded() {
245 if (!profilingStack) {
246 return;
249 // Note: To avoid dead locks, we should not hold on the helper thread lock
250 // while calling this function. This is safe because the unregisterThread
251 // field is a WriteOnceData<> type stored on the global helper tread state.
252 JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread;
253 if (callback) {
254 callback();
255 profilingStack = nullptr;
259 HelperThread::AutoProfilerLabel::AutoProfilerLabel(
260 HelperThread* helperThread, const char* label,
261 JS::ProfilingCategoryPair categoryPair)
262 : profilingStack(helperThread->profilingStack) {
263 if (profilingStack) {
264 profilingStack->pushLabelFrame(label, nullptr, this, categoryPair);
268 HelperThread::AutoProfilerLabel::~AutoProfilerLabel() {
269 if (profilingStack) {
270 profilingStack->pop();
274 void HelperThread::threadLoop(InternalThreadPool* pool) {
275 MOZ_ASSERT(CanUseExtraThreads());
277 AutoLockHelperThreadState lock;
279 while (!pool->terminating) {
280 if (pool->queuedTasks != 0) {
281 pool->queuedTasks--;
282 HelperThreadState().runOneTask(lock);
283 continue;
286 AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE);
287 pool->wait(lock);