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