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/. */
6
7 #include "vm/InternalThreadPool.h"
8
9 #include "mozilla/TimeStamp.h"
10
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
17 // We want our default stack size limit to be approximately 2MB, to be safe, but
18 // expect most threads to use much less. On Linux, however, requesting a stack
19 // of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
20 // on first access, which we do not want. To avoid this possibility, we subtract
21 // 2 standard VM page sizes from our default.
22 static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
23
24 // TSan enforces a minimum stack size that's just slightly larger than our
25 // default helper stack size. It does this to store blobs of TSan-specific
26 // data on each thread's stack. Unfortunately, that means that even though
27 // we'll actually receive a larger stack than we requested, the effective
28 // usable space of that stack is significantly less than what we expect.
29 // To offset TSan stealing our stack space from underneath us, double the
30 // default.
31 //
32 // Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
33 // require all the thread-specific state that TSan does.
34 #if defined(MOZ_TSAN)
35 static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
36 #else
37 static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
38 #endif
39
40 // These macros are identical in function to the same-named ones in
41 // GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
42 // use GeckoProfiler.h.
43 #define PROFILER_RAII_PASTE(id, line) id##line
44 #define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line)
45 #define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__)
46 #define AUTO_PROFILER_LABEL(label, categoryPair) \
47 HelperThread::AutoProfilerLabel PROFILER_RAII( \
48 this, label, JS::ProfilingCategoryPair::categoryPair)
49
50 using namespace js;
51
52 namespace js {
53
54 class HelperThread {
55 Thread thread;
56
57 /*
58 * The profiling thread for this helper thread, which can be used to push
59 * and pop label frames.
60 * This field being non-null indicates that this thread has been registered
61 * and needs to be unregistered at shutdown.
62 */
63 ProfilingStack* profilingStack = nullptr;
64
65 public:
66 HelperThread();
67 [[nodiscard]] bool init(InternalThreadPool* pool);
68
threadId()69 ThreadId threadId() { return thread.get_id(); }
70
71 void join();
72
73 static void ThreadMain(InternalThreadPool* pool, HelperThread* helper);
74 void threadLoop(InternalThreadPool* pool);
75
76 void ensureRegisteredWithProfiler();
77 void unregisterWithProfilerIfNeeded();
78
79 private:
80 struct AutoProfilerLabel {
81 AutoProfilerLabel(HelperThread* helperThread, const char* label,
82 JS::ProfilingCategoryPair categoryPair);
83 ~AutoProfilerLabel();
84
85 private:
86 ProfilingStack* profilingStack;
87 };
88 };
89
90 } // namespace js
91
92 InternalThreadPool* InternalThreadPool::Instance = nullptr;
93
Get()94 /* static */ InternalThreadPool& InternalThreadPool::Get() {
95 MOZ_ASSERT(IsInitialized());
96 return *Instance;
97 }
98
99 /* static */
Initialize(size_t threadCount,AutoLockHelperThreadState & lock)100 bool InternalThreadPool::Initialize(size_t threadCount,
101 AutoLockHelperThreadState& lock) {
102 if (IsInitialized()) {
103 return true;
104 }
105
106 auto instance = MakeUnique<InternalThreadPool>();
107 if (!instance) {
108 return false;
109 }
110
111 if (!instance->ensureThreadCount(threadCount, lock)) {
112 instance->shutDown(lock);
113 return false;
114 }
115
116 Instance = instance.release();
117 HelperThreadState().setDispatchTaskCallback(DispatchTask, threadCount,
118 HELPER_STACK_SIZE, lock);
119 return true;
120 }
121
ensureThreadCount(size_t threadCount,AutoLockHelperThreadState & lock)122 bool InternalThreadPool::ensureThreadCount(size_t threadCount,
123 AutoLockHelperThreadState& lock) {
124 MOZ_ASSERT(threads(lock).length() < threadCount);
125
126 if (!threads(lock).reserve(threadCount)) {
127 return false;
128 }
129
130 while (threads(lock).length() < threadCount) {
131 auto thread = js::MakeUnique<HelperThread>();
132 if (!thread || !thread->init(this)) {
133 return false;
134 }
135
136 threads(lock).infallibleEmplaceBack(std::move(thread));
137 }
138
139 return true;
140 }
141
threadCount(const AutoLockHelperThreadState & lock)142 size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) {
143 return threads(lock).length();
144 }
145
146 /* static */
ShutDown(AutoLockHelperThreadState & lock)147 void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) {
148 MOZ_ASSERT(HelperThreadState().isTerminating(lock));
149
150 Get().shutDown(lock);
151 js_delete(Instance);
152 Instance = nullptr;
153 }
154
shutDown(AutoLockHelperThreadState & lock)155 void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) {
156 MOZ_ASSERT(!terminating);
157 terminating = true;
158
159 notifyAll(lock);
160
161 for (auto& thread : threads(lock)) {
162 AutoUnlockHelperThreadState unlock(lock);
163 thread->join();
164 }
165 }
166
threads(const AutoLockHelperThreadState & lock)167 inline HelperThreadVector& InternalThreadPool::threads(
168 const AutoLockHelperThreadState& lock) {
169 return threads_.ref();
170 }
threads(const AutoLockHelperThreadState & lock) const171 inline const HelperThreadVector& InternalThreadPool::threads(
172 const AutoLockHelperThreadState& lock) const {
173 return threads_.ref();
174 }
175
sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,const AutoLockHelperThreadState & lock) const176 size_t InternalThreadPool::sizeOfIncludingThis(
177 mozilla::MallocSizeOf mallocSizeOf,
178 const AutoLockHelperThreadState& lock) const {
179 return sizeof(InternalThreadPool) +
180 threads(lock).sizeOfExcludingThis(mallocSizeOf);
181 }
182
183 /* static */
DispatchTask()184 void InternalThreadPool::DispatchTask() { Get().dispatchTask(); }
185
dispatchTask()186 void InternalThreadPool::dispatchTask() {
187 gHelperThreadLock.assertOwnedByCurrentThread();
188 queuedTasks++;
189 wakeup.notify_one();
190 }
191
notifyAll(const AutoLockHelperThreadState & lock)192 void InternalThreadPool::notifyAll(const AutoLockHelperThreadState& lock) {
193 wakeup.notify_all();
194 }
195
wait(AutoLockHelperThreadState & lock)196 void InternalThreadPool::wait(AutoLockHelperThreadState& lock) {
197 wakeup.wait_for(lock, mozilla::TimeDuration::Forever());
198 }
199
HelperThread()200 HelperThread::HelperThread()
201 : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {}
202
init(InternalThreadPool * pool)203 bool HelperThread::init(InternalThreadPool* pool) {
204 return thread.init(HelperThread::ThreadMain, pool, this);
205 }
206
join()207 void HelperThread::join() { thread.join(); }
208
209 /* static */
ThreadMain(InternalThreadPool * pool,HelperThread * helper)210 void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) {
211 ThisThread::SetName("JS Helper");
212
213 helper->ensureRegisteredWithProfiler();
214 helper->threadLoop(pool);
215 helper->unregisterWithProfilerIfNeeded();
216 }
217
ensureRegisteredWithProfiler()218 void HelperThread::ensureRegisteredWithProfiler() {
219 if (profilingStack) {
220 return;
221 }
222
223 // Note: To avoid dead locks, we should not hold on the helper thread lock
224 // while calling this function. This is safe because the registerThread field
225 // is a WriteOnceData<> type stored on the global helper tread state.
226 JS::RegisterThreadCallback callback = HelperThreadState().registerThread;
227 if (callback) {
228 profilingStack =
229 callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase()));
230 }
231 }
232
unregisterWithProfilerIfNeeded()233 void HelperThread::unregisterWithProfilerIfNeeded() {
234 if (!profilingStack) {
235 return;
236 }
237
238 // Note: To avoid dead locks, we should not hold on the helper thread lock
239 // while calling this function. This is safe because the unregisterThread
240 // field is a WriteOnceData<> type stored on the global helper tread state.
241 JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread;
242 if (callback) {
243 callback();
244 profilingStack = nullptr;
245 }
246 }
247
AutoProfilerLabel(HelperThread * helperThread,const char * label,JS::ProfilingCategoryPair categoryPair)248 HelperThread::AutoProfilerLabel::AutoProfilerLabel(
249 HelperThread* helperThread, const char* label,
250 JS::ProfilingCategoryPair categoryPair)
251 : profilingStack(helperThread->profilingStack) {
252 if (profilingStack) {
253 profilingStack->pushLabelFrame(label, nullptr, this, categoryPair);
254 }
255 }
256
~AutoProfilerLabel()257 HelperThread::AutoProfilerLabel::~AutoProfilerLabel() {
258 if (profilingStack) {
259 profilingStack->pop();
260 }
261 }
262
threadLoop(InternalThreadPool * pool)263 void HelperThread::threadLoop(InternalThreadPool* pool) {
264 MOZ_ASSERT(CanUseExtraThreads());
265
266 AutoLockHelperThreadState lock;
267
268 while (!pool->terminating) {
269 if (pool->queuedTasks != 0) {
270 pool->queuedTasks--;
271 HelperThreadState().runOneTask(lock);
272 continue;
273 }
274
275 AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE);
276 pool->wait(lock);
277 }
278 }
279