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/HelperThreads.h"
8 
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/Maybe.h"
11 #include "mozilla/ScopeExit.h"
12 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
13 
14 #include <algorithm>
15 
16 #include "frontend/BytecodeCompilation.h"
17 #include "frontend/CompilationStencil.h"  // frontend::{CompilationStencil, ExtensibleCompilationStencil, CompilationInput, CompilationGCOutput, BorrowingCompilationStencil}
18 #include "frontend/ParserAtom.h"          // frontend::ParserAtomsTable
19 #include "gc/GC.h"                        // gc::MergeRealms
20 #include "jit/IonCompileTask.h"
21 #include "jit/JitRuntime.h"
22 #include "js/ContextOptions.h"      // JS::ContextOptions
23 #include "js/friend/StackLimits.h"  // js::ReportOverRecursed
24 #include "js/HelperThreadAPI.h"
25 #include "js/OffThreadScriptCompilation.h"  // JS::OffThreadToken, JS::OffThreadCompileCallback
26 #include "js/SourceText.h"
27 #include "js/UniquePtr.h"
28 #include "js/Utility.h"
29 #include "threading/CpuCount.h"
30 #include "util/NativeStack.h"
31 #include "vm/ErrorReporting.h"
32 #include "vm/HelperThreadState.h"
33 #include "vm/InternalThreadPool.h"
34 #include "vm/MutexIDs.h"
35 #include "vm/SharedImmutableStringsCache.h"
36 #include "vm/Time.h"
37 #include "vm/TraceLogging.h"
38 #include "vm/Xdr.h"
39 #include "wasm/WasmGenerator.h"
40 
41 #include "debugger/DebugAPI-inl.h"
42 #include "gc/ArenaList-inl.h"
43 #include "vm/JSContext-inl.h"
44 #include "vm/JSObject-inl.h"
45 #include "vm/JSScript-inl.h"
46 #include "vm/NativeObject-inl.h"
47 #include "vm/Realm-inl.h"
48 
49 using namespace js;
50 
51 using mozilla::Maybe;
52 using mozilla::TimeDuration;
53 using mozilla::TimeStamp;
54 using mozilla::Utf8Unit;
55 
56 using JS::CompileOptions;
57 using JS::ReadOnlyCompileOptions;
58 
59 namespace js {
60 
61 Mutex gHelperThreadLock(mutexid::GlobalHelperThreadState);
62 GlobalHelperThreadState* gHelperThreadState = nullptr;
63 
64 }  // namespace js
65 
CreateHelperThreadsState()66 bool js::CreateHelperThreadsState() {
67   MOZ_ASSERT(!gHelperThreadState);
68   gHelperThreadState = js_new<GlobalHelperThreadState>();
69   return gHelperThreadState;
70 }
71 
DestroyHelperThreadsState()72 void js::DestroyHelperThreadsState() {
73   AutoLockHelperThreadState lock;
74 
75   if (!gHelperThreadState) {
76     return;
77   }
78 
79   gHelperThreadState->finish(lock);
80   js_delete(gHelperThreadState);
81   gHelperThreadState = nullptr;
82 }
83 
EnsureHelperThreadsInitialized()84 bool js::EnsureHelperThreadsInitialized() {
85   MOZ_ASSERT(gHelperThreadState);
86   return gHelperThreadState->ensureInitialized();
87 }
88 
ClampDefaultCPUCount(size_t cpuCount)89 static size_t ClampDefaultCPUCount(size_t cpuCount) {
90   // It's extremely rare for SpiderMonkey to have more than a few cores worth
91   // of work. At higher core counts, performance can even decrease due to NUMA
92   // (and SpiderMonkey's lack of NUMA-awareness), contention, and general lack
93   // of optimization for high core counts. So to avoid wasting thread stack
94   // resources (and cluttering gdb and core dumps), clamp to 8 cores for now.
95   return std::min<size_t>(cpuCount, 8);
96 }
97 
ThreadCountForCPUCount(size_t cpuCount)98 static size_t ThreadCountForCPUCount(size_t cpuCount) {
99   // We need at least two threads for tier-2 wasm compilations, because
100   // there's a master task that holds a thread while other threads do the
101   // compilation.
102   return std::max<size_t>(cpuCount, 2);
103 }
104 
SetFakeCPUCount(size_t count)105 bool js::SetFakeCPUCount(size_t count) {
106   HelperThreadState().setCpuCount(count);
107   return true;
108 }
109 
setCpuCount(size_t count)110 void GlobalHelperThreadState::setCpuCount(size_t count) {
111   // This must be called before any threads have been initialized.
112   AutoLockHelperThreadState lock;
113   MOZ_ASSERT(!isInitialized(lock));
114 
115   // We can't do this if an external thread pool is in use.
116   MOZ_ASSERT(!dispatchTaskCallback);
117 
118   cpuCount = count;
119   threadCount = ThreadCountForCPUCount(count);
120 }
121 
GetHelperThreadCount()122 size_t js::GetHelperThreadCount() { return HelperThreadState().threadCount; }
123 
GetHelperThreadCPUCount()124 size_t js::GetHelperThreadCPUCount() { return HelperThreadState().cpuCount; }
125 
GetMaxWasmCompilationThreads()126 size_t js::GetMaxWasmCompilationThreads() {
127   return HelperThreadState().maxWasmCompilationThreads();
128 }
129 
SetProfilingThreadCallbacks(JS::RegisterThreadCallback registerThread,JS::UnregisterThreadCallback unregisterThread)130 void JS::SetProfilingThreadCallbacks(
131     JS::RegisterThreadCallback registerThread,
132     JS::UnregisterThreadCallback unregisterThread) {
133   HelperThreadState().registerThread = registerThread;
134   HelperThreadState().unregisterThread = unregisterThread;
135 }
136 
ThreadStackQuotaForSize(size_t size)137 static size_t ThreadStackQuotaForSize(size_t size) {
138   // Set the stack quota to 10% less that the actual size.
139   return size_t(double(size) * 0.9);
140 }
141 
142 // Bug 1630189: Without MOZ_NEVER_INLINE, Windows PGO builds have a linking
143 // error for HelperThreadTaskCallback.
SetHelperThreadTaskCallback(HelperThreadTaskCallback callback,size_t threadCount,size_t stackSize)144 JS_PUBLIC_API MOZ_NEVER_INLINE void JS::SetHelperThreadTaskCallback(
145     HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize) {
146   AutoLockHelperThreadState lock;
147   HelperThreadState().setDispatchTaskCallback(callback, threadCount, stackSize,
148                                               lock);
149 }
150 
setDispatchTaskCallback(JS::HelperThreadTaskCallback callback,size_t threadCount,size_t stackSize,const AutoLockHelperThreadState & lock)151 void GlobalHelperThreadState::setDispatchTaskCallback(
152     JS::HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize,
153     const AutoLockHelperThreadState& lock) {
154   MOZ_ASSERT(!isInitialized(lock));
155   MOZ_ASSERT(!dispatchTaskCallback);
156   MOZ_ASSERT(threadCount != 0);
157   MOZ_ASSERT(stackSize >= 16 * 1024);
158 
159   dispatchTaskCallback = callback;
160   this->threadCount = threadCount;
161   this->stackQuota = ThreadStackQuotaForSize(stackSize);
162 }
163 
StartOffThreadWasmCompile(wasm::CompileTask * task,wasm::CompileMode mode)164 bool js::StartOffThreadWasmCompile(wasm::CompileTask* task,
165                                    wasm::CompileMode mode) {
166   return HelperThreadState().submitTask(task, mode);
167 }
168 
submitTask(wasm::CompileTask * task,wasm::CompileMode mode)169 bool GlobalHelperThreadState::submitTask(wasm::CompileTask* task,
170                                          wasm::CompileMode mode) {
171   AutoLockHelperThreadState lock;
172   if (!wasmWorklist(lock, mode).pushBack(task)) {
173     return false;
174   }
175 
176   dispatch(lock);
177   return true;
178 }
179 
RemovePendingWasmCompileTasks(const wasm::CompileTaskState & taskState,wasm::CompileMode mode,const AutoLockHelperThreadState & lock)180 size_t js::RemovePendingWasmCompileTasks(
181     const wasm::CompileTaskState& taskState, wasm::CompileMode mode,
182     const AutoLockHelperThreadState& lock) {
183   wasm::CompileTaskPtrFifo& worklist =
184       HelperThreadState().wasmWorklist(lock, mode);
185   return worklist.eraseIf([&taskState](wasm::CompileTask* task) {
186     return &task->state == &taskState;
187   });
188 }
189 
StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task)190 void js::StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task) {
191   (void)HelperThreadState().submitTask(std::move(task));
192 }
193 
submitTask(wasm::UniqueTier2GeneratorTask task)194 bool GlobalHelperThreadState::submitTask(wasm::UniqueTier2GeneratorTask task) {
195   AutoLockHelperThreadState lock;
196 
197   MOZ_ASSERT(isInitialized(lock));
198 
199   if (!wasmTier2GeneratorWorklist(lock).append(task.get())) {
200     return false;
201   }
202   (void)task.release();
203 
204   dispatch(lock);
205   return true;
206 }
207 
CancelOffThreadWasmTier2GeneratorLocked(AutoLockHelperThreadState & lock)208 static void CancelOffThreadWasmTier2GeneratorLocked(
209     AutoLockHelperThreadState& lock) {
210   if (!HelperThreadState().isInitialized(lock)) {
211     return;
212   }
213 
214   // Remove pending tasks from the tier2 generator worklist and cancel and
215   // delete them.
216   {
217     wasm::Tier2GeneratorTaskPtrVector& worklist =
218         HelperThreadState().wasmTier2GeneratorWorklist(lock);
219     for (size_t i = 0; i < worklist.length(); i++) {
220       wasm::Tier2GeneratorTask* task = worklist[i];
221       HelperThreadState().remove(worklist, &i);
222       js_delete(task);
223     }
224   }
225 
226   // There is at most one running Tier2Generator task and we assume that
227   // below.
228   static_assert(GlobalHelperThreadState::MaxTier2GeneratorTasks == 1,
229                 "code must be generalized");
230 
231   // If there is a running Tier2 generator task, shut it down in a predictable
232   // way.  The task will be deleted by the normal deletion logic.
233   for (auto* helper : HelperThreadState().helperTasks(lock)) {
234     if (helper->is<wasm::Tier2GeneratorTask>()) {
235       // Set a flag that causes compilation to shortcut itself.
236       helper->as<wasm::Tier2GeneratorTask>()->cancel();
237 
238       // Wait for the generator task to finish.  This avoids a shutdown race
239       // where the shutdown code is trying to shut down helper threads and the
240       // ongoing tier2 compilation is trying to finish, which requires it to
241       // have access to helper threads.
242       uint32_t oldFinishedCount =
243           HelperThreadState().wasmTier2GeneratorsFinished(lock);
244       while (HelperThreadState().wasmTier2GeneratorsFinished(lock) ==
245              oldFinishedCount) {
246         HelperThreadState().wait(lock);
247       }
248 
249       // At most one of these tasks.
250       break;
251     }
252   }
253 }
254 
CancelOffThreadWasmTier2Generator()255 void js::CancelOffThreadWasmTier2Generator() {
256   AutoLockHelperThreadState lock;
257   CancelOffThreadWasmTier2GeneratorLocked(lock);
258 }
259 
StartOffThreadIonCompile(jit::IonCompileTask * task,const AutoLockHelperThreadState & lock)260 bool js::StartOffThreadIonCompile(jit::IonCompileTask* task,
261                                   const AutoLockHelperThreadState& lock) {
262   return HelperThreadState().submitTask(task, lock);
263 }
264 
submitTask(jit::IonCompileTask * task,const AutoLockHelperThreadState & locked)265 bool GlobalHelperThreadState::submitTask(
266     jit::IonCompileTask* task, const AutoLockHelperThreadState& locked) {
267   MOZ_ASSERT(isInitialized(locked));
268 
269   if (!ionWorklist(locked).append(task)) {
270     return false;
271   }
272 
273   // The build is moving off-thread. Freeze the LifoAlloc to prevent any
274   // unwanted mutations.
275   task->alloc().lifoAlloc()->setReadOnly();
276 
277   dispatch(locked);
278   return true;
279 }
280 
StartOffThreadIonFree(jit::IonCompileTask * task,const AutoLockHelperThreadState & lock)281 bool js::StartOffThreadIonFree(jit::IonCompileTask* task,
282                                const AutoLockHelperThreadState& lock) {
283   js::UniquePtr<jit::IonFreeTask> freeTask =
284       js::MakeUnique<jit::IonFreeTask>(task);
285   if (!freeTask) {
286     return false;
287   }
288 
289   return HelperThreadState().submitTask(std::move(freeTask), lock);
290 }
291 
submitTask(UniquePtr<jit::IonFreeTask> task,const AutoLockHelperThreadState & locked)292 bool GlobalHelperThreadState::submitTask(
293     UniquePtr<jit::IonFreeTask> task, const AutoLockHelperThreadState& locked) {
294   MOZ_ASSERT(isInitialized(locked));
295 
296   if (!ionFreeList(locked).append(std::move(task))) {
297     return false;
298   }
299 
300   dispatch(locked);
301   return true;
302 }
303 
304 /*
305  * Move an IonCompilationTask for which compilation has either finished, failed,
306  * or been cancelled into the global finished compilation list. All off thread
307  * compilations which are started must eventually be finished.
308  */
FinishOffThreadIonCompile(jit::IonCompileTask * task,const AutoLockHelperThreadState & lock)309 void js::FinishOffThreadIonCompile(jit::IonCompileTask* task,
310                                    const AutoLockHelperThreadState& lock) {
311   AutoEnterOOMUnsafeRegion oomUnsafe;
312   if (!HelperThreadState().ionFinishedList(lock).append(task)) {
313     oomUnsafe.crash("FinishOffThreadIonCompile");
314   }
315   task->script()
316       ->runtimeFromAnyThread()
317       ->jitRuntime()
318       ->numFinishedOffThreadTasksRef(lock)++;
319 }
320 
GetSelectorRuntime(const CompilationSelector & selector)321 static JSRuntime* GetSelectorRuntime(const CompilationSelector& selector) {
322   struct Matcher {
323     JSRuntime* operator()(JSScript* script) {
324       return script->runtimeFromMainThread();
325     }
326     JSRuntime* operator()(Realm* realm) {
327       return realm->runtimeFromMainThread();
328     }
329     JSRuntime* operator()(Zone* zone) { return zone->runtimeFromMainThread(); }
330     JSRuntime* operator()(ZonesInState zbs) { return zbs.runtime; }
331     JSRuntime* operator()(JSRuntime* runtime) { return runtime; }
332   };
333 
334   return selector.match(Matcher());
335 }
336 
JitDataStructuresExist(const CompilationSelector & selector)337 static bool JitDataStructuresExist(const CompilationSelector& selector) {
338   struct Matcher {
339     bool operator()(JSScript* script) { return !!script->realm()->jitRealm(); }
340     bool operator()(Realm* realm) { return !!realm->jitRealm(); }
341     bool operator()(Zone* zone) { return !!zone->jitZone(); }
342     bool operator()(ZonesInState zbs) { return zbs.runtime->hasJitRuntime(); }
343     bool operator()(JSRuntime* runtime) { return runtime->hasJitRuntime(); }
344   };
345 
346   return selector.match(Matcher());
347 }
348 
IonCompileTaskMatches(const CompilationSelector & selector,jit::IonCompileTask * task)349 static bool IonCompileTaskMatches(const CompilationSelector& selector,
350                                   jit::IonCompileTask* task) {
351   struct TaskMatches {
352     jit::IonCompileTask* task_;
353 
354     bool operator()(JSScript* script) { return script == task_->script(); }
355     bool operator()(Realm* realm) { return realm == task_->script()->realm(); }
356     bool operator()(Zone* zone) {
357       return zone == task_->script()->zoneFromAnyThread();
358     }
359     bool operator()(JSRuntime* runtime) {
360       return runtime == task_->script()->runtimeFromAnyThread();
361     }
362     bool operator()(ZonesInState zbs) {
363       return zbs.runtime == task_->script()->runtimeFromAnyThread() &&
364              zbs.state == task_->script()->zoneFromAnyThread()->gcState();
365     }
366   };
367 
368   return selector.match(TaskMatches{task});
369 }
370 
CancelOffThreadIonCompileLocked(const CompilationSelector & selector,AutoLockHelperThreadState & lock)371 static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector,
372                                             AutoLockHelperThreadState& lock) {
373   if (!HelperThreadState().isInitialized(lock)) {
374     return;
375   }
376 
377   /* Cancel any pending entries for which processing hasn't started. */
378   GlobalHelperThreadState::IonCompileTaskVector& worklist =
379       HelperThreadState().ionWorklist(lock);
380   for (size_t i = 0; i < worklist.length(); i++) {
381     jit::IonCompileTask* task = worklist[i];
382     if (IonCompileTaskMatches(selector, task)) {
383       // Once finished, tasks are added to a Linked list which is
384       // allocated with the IonCompileTask class. The IonCompileTask is
385       // allocated in the LifoAlloc so we need the LifoAlloc to be mutable.
386       worklist[i]->alloc().lifoAlloc()->setReadWrite();
387 
388       FinishOffThreadIonCompile(task, lock);
389       HelperThreadState().remove(worklist, &i);
390     }
391   }
392 
393   /* Wait for in progress entries to finish up. */
394   bool cancelled;
395   do {
396     cancelled = false;
397     for (auto* helper : HelperThreadState().helperTasks(lock)) {
398       if (!helper->is<jit::IonCompileTask>()) {
399         continue;
400       }
401 
402       jit::IonCompileTask* ionCompileTask = helper->as<jit::IonCompileTask>();
403       if (IonCompileTaskMatches(selector, ionCompileTask)) {
404         ionCompileTask->mirGen().cancel();
405         cancelled = true;
406       }
407     }
408     if (cancelled) {
409       HelperThreadState().wait(lock);
410     }
411   } while (cancelled);
412 
413   /* Cancel code generation for any completed entries. */
414   GlobalHelperThreadState::IonCompileTaskVector& finished =
415       HelperThreadState().ionFinishedList(lock);
416   for (size_t i = 0; i < finished.length(); i++) {
417     jit::IonCompileTask* task = finished[i];
418     if (IonCompileTaskMatches(selector, task)) {
419       JSRuntime* rt = task->script()->runtimeFromAnyThread();
420       rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--;
421       jit::FinishOffThreadTask(rt, task, lock);
422       HelperThreadState().remove(finished, &i);
423     }
424   }
425 
426   /* Cancel lazy linking for pending tasks (attached to the ionScript). */
427   JSRuntime* runtime = GetSelectorRuntime(selector);
428   jit::IonCompileTask* task =
429       runtime->jitRuntime()->ionLazyLinkList(runtime).getFirst();
430   while (task) {
431     jit::IonCompileTask* next = task->getNext();
432     if (IonCompileTaskMatches(selector, task)) {
433       jit::FinishOffThreadTask(runtime, task, lock);
434     }
435     task = next;
436   }
437 }
438 
CancelOffThreadIonCompile(const CompilationSelector & selector)439 void js::CancelOffThreadIonCompile(const CompilationSelector& selector) {
440   if (!JitDataStructuresExist(selector)) {
441     return;
442   }
443 
444   AutoLockHelperThreadState lock;
445   CancelOffThreadIonCompileLocked(selector, lock);
446 }
447 
448 #ifdef DEBUG
HasOffThreadIonCompile(Realm * realm)449 bool js::HasOffThreadIonCompile(Realm* realm) {
450   AutoLockHelperThreadState lock;
451 
452   if (!HelperThreadState().isInitialized(lock)) {
453     return false;
454   }
455 
456   GlobalHelperThreadState::IonCompileTaskVector& worklist =
457       HelperThreadState().ionWorklist(lock);
458   for (size_t i = 0; i < worklist.length(); i++) {
459     jit::IonCompileTask* task = worklist[i];
460     if (task->script()->realm() == realm) {
461       return true;
462     }
463   }
464 
465   for (auto* helper : HelperThreadState().helperTasks(lock)) {
466     if (helper->is<jit::IonCompileTask>() &&
467         helper->as<jit::IonCompileTask>()->script()->realm() == realm) {
468       return true;
469     }
470   }
471 
472   GlobalHelperThreadState::IonCompileTaskVector& finished =
473       HelperThreadState().ionFinishedList(lock);
474   for (size_t i = 0; i < finished.length(); i++) {
475     jit::IonCompileTask* task = finished[i];
476     if (task->script()->realm() == realm) {
477       return true;
478     }
479   }
480 
481   JSRuntime* rt = realm->runtimeFromMainThread();
482   jit::IonCompileTask* task = rt->jitRuntime()->ionLazyLinkList(rt).getFirst();
483   while (task) {
484     if (task->script()->realm() == realm) {
485       return true;
486     }
487     task = task->getNext();
488   }
489 
490   return false;
491 }
492 #endif
493 
494 struct MOZ_RAII AutoSetContextParse {
AutoSetContextParseAutoSetContextParse495   explicit AutoSetContextParse(ParseTask* task) {
496     TlsContext.get()->setParseTask(task);
497   }
~AutoSetContextParseAutoSetContextParse498   ~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); }
499 };
500 
AutoSetHelperThreadContext(AutoLockHelperThreadState & lock)501 AutoSetHelperThreadContext::AutoSetHelperThreadContext(
502     AutoLockHelperThreadState& lock)
503     : lock(lock) {
504   cx = HelperThreadState().getFirstUnusedContext(lock);
505   MOZ_ASSERT(cx);
506   cx->setHelperThread(lock);
507   // When we set the JSContext, we need to reset the computed stack limits for
508   // the current thread, so we also set the native stack quota.
509   JS_SetNativeStackQuota(cx, HelperThreadState().stackQuota);
510 }
511 
~AutoSetHelperThreadContext()512 AutoSetHelperThreadContext::~AutoSetHelperThreadContext() {
513   cx->tempLifoAlloc().releaseAll();
514   if (cx->shouldFreeUnusedMemory()) {
515     cx->tempLifoAlloc().freeAll();
516     cx->setFreeUnusedMemory(false);
517   }
518   cx->clearHelperThread(lock);
519   cx = nullptr;
520 }
521 
522 static const JSClass parseTaskGlobalClass = {"internal-parse-task-global",
523                                              JSCLASS_GLOBAL_FLAGS,
524                                              &JS::DefaultGlobalClassOps};
525 
ParseTask(ParseTaskKind kind,JSContext * cx,JS::OffThreadCompileCallback callback,void * callbackData)526 ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx,
527                      JS::OffThreadCompileCallback callback, void* callbackData)
528     : kind(kind),
529       options(cx),
530       parseGlobal(nullptr),
531       callback(callback),
532       callbackData(callbackData),
533       overRecursed(false),
534       outOfMemory(false) {
535   // Note that |cx| is the main thread context here but the parse task will
536   // run with a different, helper thread, context.
537   MOZ_ASSERT(!cx->isHelperThreadContext());
538 
539   MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
540   MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
541 }
542 
init(JSContext * cx,const ReadOnlyCompileOptions & options,JSObject * global)543 bool ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options,
544                      JSObject* global) {
545   MOZ_ASSERT(!cx->isHelperThreadContext());
546 
547   if (!this->options.copy(cx, options)) {
548     return false;
549   }
550 
551   runtime = cx->runtime();
552   parseGlobal = global;
553 
554   return true;
555 }
556 
activate(JSRuntime * rt)557 void ParseTask::activate(JSRuntime* rt) {
558   rt->addParseTaskRef();
559   if (parseGlobal) {
560     rt->setUsedByHelperThread(parseGlobal->zone());
561   }
562 }
563 
564 ParseTask::~ParseTask() = default;
565 
trace(JSTracer * trc)566 void ParseTask::trace(JSTracer* trc) {
567   if (runtime != trc->runtime()) {
568     return;
569   }
570 
571   if (parseGlobal) {
572     Zone* zone = MaybeForwarded(parseGlobal)->zoneFromAnyThread();
573     if (zone->usedByHelperThread()) {
574       MOZ_ASSERT(!zone->isCollecting());
575       return;
576     }
577   }
578 
579   TraceNullableRoot(trc, &parseGlobal, "ParseTask::parseGlobal");
580   scripts.trace(trc);
581   sourceObjects.trace(trc);
582 
583   if (stencilInput_) {
584     stencilInput_->trace(trc);
585   }
586 
587   gcOutput_.trace(trc);
588 }
589 
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const590 size_t ParseTask::sizeOfExcludingThis(
591     mozilla::MallocSizeOf mallocSizeOf) const {
592   size_t stencilInputSize =
593       stencilInput_ ? stencilInput_->sizeOfIncludingThis(mallocSizeOf) : 0;
594   size_t stencilSize =
595       stencil_ ? stencil_->sizeOfIncludingThis(mallocSizeOf) : 0;
596   size_t extensibleStencilSize =
597       extensibleStencil_ ? extensibleStencil_->sizeOfIncludingThis(mallocSizeOf)
598                          : 0;
599 
600   // TODO: 'errors' requires adding support to `CompileError`. They are not
601   // common though.
602 
603   return options.sizeOfExcludingThis(mallocSizeOf) +
604          scripts.sizeOfExcludingThis(mallocSizeOf) +
605          sourceObjects.sizeOfExcludingThis(mallocSizeOf) + stencilInputSize +
606          stencilSize + extensibleStencilSize +
607          gcOutput_.sizeOfExcludingThis(mallocSizeOf);
608 }
609 
runHelperThreadTask(AutoLockHelperThreadState & locked)610 void ParseTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
611 #ifdef DEBUG
612   if (parseGlobal) {
613     runtime->incOffThreadParsesRunning();
614   }
615 #endif
616 
617   runTask(locked);
618 
619   // The callback is invoked while we are still off thread.
620   callback(this, callbackData);
621 
622   // FinishOffThreadScript will need to be called on the script to
623   // migrate it into the correct compartment.
624   HelperThreadState().parseFinishedList(locked).insertBack(this);
625 
626 #ifdef DEBUG
627   if (parseGlobal) {
628     runtime->decOffThreadParsesRunning();
629   }
630 #endif
631 }
632 
runTask(AutoLockHelperThreadState & lock)633 void ParseTask::runTask(AutoLockHelperThreadState& lock) {
634   AutoSetHelperThreadContext usesContext(lock);
635 
636   AutoUnlockHelperThreadState unlock(lock);
637 
638   JSContext* cx = TlsContext.get();
639 
640   AutoSetContextRuntime ascr(runtime);
641   AutoSetContextParse parsetask(this);
642   gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx);
643 
644   Zone* zone = nullptr;
645   if (parseGlobal) {
646     zone = parseGlobal->zoneFromAnyThread();
647     zone->setHelperThreadOwnerContext(cx);
648   }
649 
650   auto resetOwnerContext = mozilla::MakeScopeExit([&] {
651     if (zone) {
652       zone->setHelperThreadOwnerContext(nullptr);
653     }
654   });
655 
656   Maybe<AutoRealm> ar;
657   if (parseGlobal) {
658     ar.emplace(cx, parseGlobal);
659   }
660 
661   parse(cx);
662 
663   MOZ_ASSERT(cx->tempLifoAlloc().isEmpty());
664   cx->tempLifoAlloc().freeAll();
665   cx->frontendCollectionPool().purge();
666   cx->atomsZoneFreeLists().clear();
667 }
668 
669 template <typename Unit>
670 struct ScriptParseTask : public ParseTask {
671   JS::SourceText<Unit> data;
672 
673   ScriptParseTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
674                   JS::OffThreadCompileCallback callback, void* callbackData);
675   void parse(JSContext* cx) override;
676 };
677 
678 template <typename Unit>
ScriptParseTask(JSContext * cx,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)679 ScriptParseTask<Unit>::ScriptParseTask(JSContext* cx,
680                                        JS::SourceText<Unit>& srcBuf,
681                                        JS::OffThreadCompileCallback callback,
682                                        void* callbackData)
683     : ParseTask(ParseTaskKind::Script, cx, callback, callbackData),
684       data(std::move(srcBuf)) {}
685 
686 template <typename Unit>
parse(JSContext * cx)687 void ScriptParseTask<Unit>::parse(JSContext* cx) {
688   MOZ_ASSERT(cx->isHelperThreadContext());
689 
690   ScopeKind scopeKind =
691       options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
692 
693   stencilInput_ = cx->make_unique<frontend::CompilationInput>(options);
694 
695   if (stencilInput_) {
696     extensibleStencil_ = frontend::CompileGlobalScriptToExtensibleStencil(
697         cx, *stencilInput_, data, scopeKind);
698   }
699 
700   if (extensibleStencil_) {
701     frontend::BorrowingCompilationStencil borrowingStencil(*extensibleStencil_);
702     if (!frontend::PrepareForInstantiate(cx, *stencilInput_, borrowingStencil,
703                                          gcOutput_)) {
704       extensibleStencil_ = nullptr;
705     }
706   }
707 
708   if (options.useOffThreadParseGlobal) {
709     (void)instantiateStencils(cx);
710   }
711 }
712 
713 template <typename Unit>
714 struct CompileToStencilTask : public ParseTask {
715   JS::SourceText<Unit> data;
716 
717   CompileToStencilTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
718                        JS::OffThreadCompileCallback callback,
719                        void* callbackData);
720   void parse(JSContext* cx) override;
721 };
722 
723 template <typename Unit>
CompileToStencilTask(JSContext * cx,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)724 CompileToStencilTask<Unit>::CompileToStencilTask(
725     JSContext* cx, JS::SourceText<Unit>& srcBuf,
726     JS::OffThreadCompileCallback callback, void* callbackData)
727     : ParseTask(ParseTaskKind::ScriptStencil, cx, callback, callbackData),
728       data(std::move(srcBuf)) {}
729 
730 template <typename Unit>
parse(JSContext * cx)731 void CompileToStencilTask<Unit>::parse(JSContext* cx) {
732   MOZ_ASSERT(cx->isHelperThreadContext());
733 
734   ScopeKind scopeKind =
735       options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
736 
737   stencilInput_ = cx->make_unique<frontend::CompilationInput>(options);
738 
739   if (stencilInput_) {
740     extensibleStencil_ = frontend::CompileGlobalScriptToExtensibleStencil(
741         cx, *stencilInput_, data, scopeKind);
742   }
743 }
744 
instantiateStencils(JSContext * cx)745 bool ParseTask::instantiateStencils(JSContext* cx) {
746   MOZ_ASSERT(kind != ParseTaskKind::ScriptStencil);
747 
748   if (!stencil_ && !extensibleStencil_) {
749     return false;
750   }
751 
752   bool result;
753   if (stencil_) {
754     result =
755         frontend::InstantiateStencils(cx, *stencilInput_, *stencil_, gcOutput_);
756   } else {
757     frontend::BorrowingCompilationStencil borrowingStencil(*extensibleStencil_);
758     result = frontend::InstantiateStencils(cx, *stencilInput_, borrowingStencil,
759                                            gcOutput_);
760   }
761 
762   // Whatever happens to the top-level script compilation (even if it fails),
763   // we must finish initializing the SSO.  This is because there may be valid
764   // inner scripts observable by the debugger which reference the partially-
765   // initialized SSO.
766   if (gcOutput_.sourceObject) {
767     sourceObjects.infallibleAppend(gcOutput_.sourceObject);
768   }
769 
770   if (result) {
771     MOZ_ASSERT(gcOutput_.script);
772     MOZ_ASSERT_IF(gcOutput_.module,
773                   gcOutput_.module->script() == gcOutput_.script);
774     scripts.infallibleAppend(gcOutput_.script);
775   }
776 
777   return result;
778 }
779 
780 template <typename Unit>
781 struct ModuleParseTask : public ParseTask {
782   JS::SourceText<Unit> data;
783 
784   ModuleParseTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
785                   JS::OffThreadCompileCallback callback, void* callbackData);
786   void parse(JSContext* cx) override;
787 };
788 
789 template <typename Unit>
ModuleParseTask(JSContext * cx,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)790 ModuleParseTask<Unit>::ModuleParseTask(JSContext* cx,
791                                        JS::SourceText<Unit>& srcBuf,
792                                        JS::OffThreadCompileCallback callback,
793                                        void* callbackData)
794     : ParseTask(ParseTaskKind::Module, cx, callback, callbackData),
795       data(std::move(srcBuf)) {}
796 
797 template <typename Unit>
parse(JSContext * cx)798 void ModuleParseTask<Unit>::parse(JSContext* cx) {
799   MOZ_ASSERT(cx->isHelperThreadContext());
800 
801   options.setModule();
802 
803   stencilInput_ = cx->make_unique<frontend::CompilationInput>(options);
804 
805   if (stencilInput_) {
806     extensibleStencil_ =
807         frontend::ParseModuleToExtensibleStencil(cx, *stencilInput_, data);
808   }
809 
810   if (extensibleStencil_) {
811     frontend::BorrowingCompilationStencil borrowingStencil(*extensibleStencil_);
812     if (!frontend::PrepareForInstantiate(cx, *stencilInput_, borrowingStencil,
813                                          gcOutput_)) {
814       extensibleStencil_ = nullptr;
815     }
816   }
817 
818   if (options.useOffThreadParseGlobal) {
819     (void)instantiateStencils(cx);
820   }
821 }
822 
ScriptDecodeTask(JSContext * cx,const JS::TranscodeRange & range,JS::OffThreadCompileCallback callback,void * callbackData)823 ScriptDecodeTask::ScriptDecodeTask(JSContext* cx,
824                                    const JS::TranscodeRange& range,
825                                    JS::OffThreadCompileCallback callback,
826                                    void* callbackData)
827     : ParseTask(ParseTaskKind::ScriptDecode, cx, callback, callbackData),
828       range(range) {
829   MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get()));
830 }
831 
parse(JSContext * cx)832 void ScriptDecodeTask::parse(JSContext* cx) {
833   MOZ_ASSERT(cx->isHelperThreadContext());
834 
835   RootedScript resultScript(cx);
836   Rooted<ScriptSourceObject*> sourceObject(cx);
837 
838   if (options.useStencilXDR) {
839     // The buffer contains stencil.
840 
841     stencilInput_ = cx->make_unique<frontend::CompilationInput>(options);
842     if (!stencilInput_) {
843       return;
844     }
845     if (!stencilInput_->initForGlobal(cx)) {
846       return;
847     }
848 
849     stencil_ =
850         cx->make_unique<frontend::CompilationStencil>(stencilInput_->source);
851     if (!stencil_) {
852       return;
853     }
854 
855     XDRStencilDecoder decoder(cx, range);
856     XDRResult res = decoder.codeStencil(*stencilInput_, *stencil_);
857     if (!res.isOk()) {
858       stencil_.reset();
859       return;
860     }
861 
862     if (!frontend::PrepareForInstantiate(cx, *stencilInput_, *stencil_,
863                                          gcOutput_)) {
864       stencil_.reset();
865     }
866 
867     if (options.useOffThreadParseGlobal) {
868       (void)instantiateStencils(cx);
869     }
870 
871     return;
872   }
873 
874   // The buffer contains JSScript.
875   auto decoder = js::MakeUnique<XDROffThreadDecoder>(
876       cx, &options, XDROffThreadDecoder::Type::Single,
877       /* sourceObjectOut = */ &sourceObject.get(), range);
878   if (!decoder) {
879     ReportOutOfMemory(cx);
880     return;
881   }
882 
883   mozilla::DebugOnly<XDRResult> res = decoder->codeScript(&resultScript);
884   MOZ_ASSERT(bool(resultScript) == static_cast<const XDRResult&>(res).isOk());
885 
886   if (sourceObject) {
887     sourceObjects.infallibleAppend(sourceObject);
888   }
889 
890   if (resultScript) {
891     scripts.infallibleAppend(resultScript);
892   }
893 }
894 
MultiScriptsDecodeTask(JSContext * cx,JS::TranscodeSources & sources,JS::OffThreadCompileCallback callback,void * callbackData)895 MultiScriptsDecodeTask::MultiScriptsDecodeTask(
896     JSContext* cx, JS::TranscodeSources& sources,
897     JS::OffThreadCompileCallback callback, void* callbackData)
898     : ParseTask(ParseTaskKind::MultiScriptsDecode, cx, callback, callbackData),
899       sources(&sources) {}
900 
parse(JSContext * cx)901 void MultiScriptsDecodeTask::parse(JSContext* cx) {
902   MOZ_ASSERT(cx->isHelperThreadContext());
903 
904   if (!scripts.reserve(sources->length()) ||
905       !sourceObjects.reserve(sources->length())) {
906     ReportOutOfMemory(cx);  // This sets |outOfMemory|.
907     return;
908   }
909 
910   for (auto& source : *sources) {
911     CompileOptions opts(cx, options);
912     opts.setFileAndLine(source.filename, source.lineno);
913 
914     RootedScript resultScript(cx);
915     Rooted<ScriptSourceObject*> sourceObject(cx);
916 
917     auto decoder = js::MakeUnique<XDROffThreadDecoder>(
918         cx, &opts, XDROffThreadDecoder::Type::Multi, &sourceObject.get(),
919         source.range);
920     if (!decoder) {
921       ReportOutOfMemory(cx);
922       return;
923     }
924 
925     mozilla::DebugOnly<XDRResult> res = decoder->codeScript(&resultScript);
926     MOZ_ASSERT(bool(resultScript) == static_cast<const XDRResult&>(res).isOk());
927 
928     if (sourceObject) {
929       sourceObjects.infallibleAppend(sourceObject);
930     }
931 
932     if (resultScript) {
933       scripts.infallibleAppend(resultScript);
934     } else {
935       // If any decodes fail, don't process the rest. We likely are hitting OOM.
936       break;
937     }
938   }
939 }
940 
WaitForOffThreadParses(JSRuntime * rt,AutoLockHelperThreadState & lock)941 static void WaitForOffThreadParses(JSRuntime* rt,
942                                    AutoLockHelperThreadState& lock) {
943   if (!HelperThreadState().isInitialized(lock)) {
944     return;
945   }
946 
947   GlobalHelperThreadState::ParseTaskVector& worklist =
948       HelperThreadState().parseWorklist(lock);
949 
950   while (true) {
951     bool pending = false;
952     for (const auto& task : worklist) {
953       if (task->runtimeMatches(rt)) {
954         pending = true;
955         break;
956       }
957     }
958     if (!pending) {
959       bool inProgress = false;
960       for (auto* helper : HelperThreadState().helperTasks(lock)) {
961         if (helper->is<ParseTask>() &&
962             helper->as<ParseTask>()->runtimeMatches(rt)) {
963           inProgress = true;
964           break;
965         }
966       }
967       if (!inProgress) {
968         break;
969       }
970     }
971     HelperThreadState().wait(lock);
972   }
973 
974 #ifdef DEBUG
975   for (const auto& task : worklist) {
976     MOZ_ASSERT(!task->runtimeMatches(rt));
977   }
978   for (auto* helper : HelperThreadState().helperTasks(lock)) {
979     MOZ_ASSERT_IF(helper->is<ParseTask>(),
980                   !helper->as<ParseTask>()->runtimeMatches(rt));
981   }
982 #endif
983 }
984 
WaitForOffThreadParses(JSRuntime * rt)985 void js::WaitForOffThreadParses(JSRuntime* rt) {
986   AutoLockHelperThreadState lock;
987   WaitForOffThreadParses(rt, lock);
988 }
989 
CancelOffThreadParses(JSRuntime * rt)990 void js::CancelOffThreadParses(JSRuntime* rt) {
991   AutoLockHelperThreadState lock;
992 
993 #ifdef DEBUG
994   for (const auto& task : HelperThreadState().parseWaitingOnGC(lock)) {
995     MOZ_ASSERT(!task->runtimeMatches(rt));
996   }
997 #endif
998 
999   // Instead of forcibly canceling pending parse tasks, just wait for all
1000   // scheduled and in progress ones to complete. Otherwise the final GC may not
1001   // collect everything due to zones being used off thread.
1002   WaitForOffThreadParses(rt, lock);
1003 
1004   // Clean up any parse tasks which haven't been finished by the main thread.
1005   auto& finished = HelperThreadState().parseFinishedList(lock);
1006   while (true) {
1007     bool found = false;
1008     ParseTask* next;
1009     ParseTask* task = finished.getFirst();
1010     while (task) {
1011       next = task->getNext();
1012       if (task->runtimeMatches(rt)) {
1013         found = true;
1014         task->remove();
1015         HelperThreadState().destroyParseTask(rt, task);
1016       }
1017       task = next;
1018     }
1019     if (!found) {
1020       break;
1021     }
1022   }
1023 
1024 #ifdef DEBUG
1025   for (ParseTask* task : finished) {
1026     MOZ_ASSERT(!task->runtimeMatches(rt));
1027   }
1028 #endif
1029 }
1030 
OffThreadParsingMustWaitForGC(JSRuntime * rt)1031 bool js::OffThreadParsingMustWaitForGC(JSRuntime* rt) {
1032   // Off thread parsing can't occur during incremental collections on the
1033   // atoms zone, to avoid triggering barriers. (Outside the atoms zone, the
1034   // compilation will use a new zone that is never collected.) If an
1035   // atoms-zone GC is in progress, hold off on executing the parse task until
1036   // the atoms-zone GC completes (see EnqueuePendingParseTasksAfterGC).
1037   return rt->activeGCInAtomsZone();
1038 }
1039 
EnsureConstructor(JSContext * cx,Handle<GlobalObject * > global,JSProtoKey key)1040 static bool EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global,
1041                               JSProtoKey key) {
1042   if (!GlobalObject::ensureConstructor(cx, global, key)) {
1043     return false;
1044   }
1045 
1046   // Set the used-as-prototype flag here because we can't GC in mergeRealms.
1047   RootedObject proto(cx, &global->getPrototype(key).toObject());
1048   return JSObject::setIsUsedAsPrototype(cx, proto);
1049 }
1050 
1051 // Initialize all classes potentially created during parsing for use in parser
1052 // data structures, template objects, &c.
EnsureParserCreatedClasses(JSContext * cx,ParseTaskKind kind)1053 static bool EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind) {
1054   Handle<GlobalObject*> global = cx->global();
1055 
1056   if (!EnsureConstructor(cx, global, JSProto_Function)) {
1057     return false;  // needed by functions, also adds object literals' proto
1058   }
1059 
1060   if (!EnsureConstructor(cx, global, JSProto_Array)) {
1061     return false;  // needed by array literals
1062   }
1063 
1064   if (!EnsureConstructor(cx, global, JSProto_RegExp)) {
1065     return false;  // needed by regular expression literals
1066   }
1067 
1068   if (!EnsureConstructor(cx, global, JSProto_GeneratorFunction)) {
1069     return false;  // needed by function*() {}
1070   }
1071 
1072   if (!EnsureConstructor(cx, global, JSProto_AsyncFunction)) {
1073     return false;  // needed by async function() {}
1074   }
1075 
1076   if (!EnsureConstructor(cx, global, JSProto_AsyncGeneratorFunction)) {
1077     return false;  // needed by async function*() {}
1078   }
1079 
1080   if (kind == ParseTaskKind::Module) {
1081     // Set the used-as-prototype flag on the prototype objects because we can't
1082     // GC in mergeRealms.
1083     bool setUsedAsPrototype = true;
1084     if (!GlobalObject::ensureModulePrototypesCreated(cx, global,
1085                                                      setUsedAsPrototype)) {
1086       return false;
1087     }
1088   }
1089 
1090   return true;
1091 }
1092 
1093 class MOZ_RAII AutoSetCreatedForHelperThread {
1094   Zone* zone;
1095 
1096  public:
AutoSetCreatedForHelperThread(JSObject * global)1097   explicit AutoSetCreatedForHelperThread(JSObject* global)
1098       : zone(global ? global->zone() : nullptr) {
1099     if (zone) {
1100       zone->setCreatedForHelperThread();
1101     }
1102   }
1103 
forget()1104   void forget() { zone = nullptr; }
1105 
~AutoSetCreatedForHelperThread()1106   ~AutoSetCreatedForHelperThread() {
1107     if (zone) {
1108       zone->clearUsedByHelperThread();
1109     }
1110   }
1111 };
1112 
CreateGlobalForOffThreadParse(JSContext * cx,const gc::AutoSuppressGC & nogc)1113 static JSObject* CreateGlobalForOffThreadParse(JSContext* cx,
1114                                                const gc::AutoSuppressGC& nogc) {
1115   JS::Realm* currentRealm = cx->realm();
1116 
1117   JS::RealmOptions realmOptions(currentRealm->creationOptions(),
1118                                 currentRealm->behaviors());
1119 
1120   auto& creationOptions = realmOptions.creationOptions();
1121 
1122   creationOptions.setInvisibleToDebugger(true)
1123       .setMergeable(true)
1124       .setNewCompartmentAndZone();
1125 
1126   // Don't falsely inherit the host's global trace hook.
1127   creationOptions.setTrace(nullptr);
1128 
1129   return JS_NewGlobalObject(cx, &parseTaskGlobalClass,
1130                             currentRealm->principals(),
1131                             JS::DontFireOnNewGlobalHook, realmOptions);
1132 }
1133 
QueueOffThreadParseTask(JSContext * cx,UniquePtr<ParseTask> task)1134 static bool QueueOffThreadParseTask(JSContext* cx, UniquePtr<ParseTask> task) {
1135   AutoLockHelperThreadState lock;
1136 
1137   bool mustWait = task->options.useOffThreadParseGlobal &&
1138                   OffThreadParsingMustWaitForGC(cx->runtime());
1139   bool result;
1140   if (mustWait) {
1141     result = HelperThreadState().parseWaitingOnGC(lock).append(std::move(task));
1142   } else {
1143     result =
1144         HelperThreadState().submitTask(cx->runtime(), std::move(task), lock);
1145   }
1146 
1147   if (!result) {
1148     ReportOutOfMemory(cx);
1149   }
1150   return result;
1151 }
1152 
submitTask(JSRuntime * rt,UniquePtr<ParseTask> task,const AutoLockHelperThreadState & locked)1153 bool GlobalHelperThreadState::submitTask(
1154     JSRuntime* rt, UniquePtr<ParseTask> task,
1155     const AutoLockHelperThreadState& locked) {
1156   if (!parseWorklist(locked).append(std::move(task))) {
1157     return false;
1158   }
1159 
1160   parseWorklist(locked).back()->activate(rt);
1161 
1162   dispatch(locked);
1163   return true;
1164 }
1165 
StartOffThreadParseTask(JSContext * cx,UniquePtr<ParseTask> task,const ReadOnlyCompileOptions & options)1166 static JS::OffThreadToken* StartOffThreadParseTask(
1167     JSContext* cx, UniquePtr<ParseTask> task,
1168     const ReadOnlyCompileOptions& options) {
1169   // Suppress GC so that calls below do not trigger a new incremental GC
1170   // which could require barriers on the atoms zone.
1171   gc::AutoSuppressGC nogc(cx);
1172   gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx);
1173   AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
1174 
1175   JSObject* global = nullptr;
1176   if (options.useOffThreadParseGlobal) {
1177     global = CreateGlobalForOffThreadParse(cx, nogc);
1178     if (!global) {
1179       return nullptr;
1180     }
1181   }
1182 
1183   // Mark the global's zone as created for a helper thread. This prevents it
1184   // from being collected until clearUsedByHelperThread() is called after
1185   // parsing is complete. If this function exits due to error this state is
1186   // cleared automatically.
1187   AutoSetCreatedForHelperThread createdForHelper(global);
1188 
1189   if (!task->init(cx, options, global)) {
1190     return nullptr;
1191   }
1192 
1193   JS::OffThreadToken* token = task.get();
1194   if (!QueueOffThreadParseTask(cx, std::move(task))) {
1195     return nullptr;
1196   }
1197 
1198   createdForHelper.forget();
1199 
1200   // Return an opaque pointer to caller so that it may query/cancel the task
1201   // before the callback is fired.
1202   return token;
1203 }
1204 
1205 template <typename Unit>
StartOffThreadParseScriptInternal(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1206 static JS::OffThreadToken* StartOffThreadParseScriptInternal(
1207     JSContext* cx, const ReadOnlyCompileOptions& options,
1208     JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1209     void* callbackData) {
1210   auto task = cx->make_unique<ScriptParseTask<Unit>>(cx, srcBuf, callback,
1211                                                      callbackData);
1212   if (!task) {
1213     return nullptr;
1214   }
1215 
1216   return StartOffThreadParseTask(cx, std::move(task), options);
1217 }
1218 
StartOffThreadParseScript(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1219 JS::OffThreadToken* js::StartOffThreadParseScript(
1220     JSContext* cx, const ReadOnlyCompileOptions& options,
1221     JS::SourceText<char16_t>& srcBuf, JS::OffThreadCompileCallback callback,
1222     void* callbackData) {
1223   return StartOffThreadParseScriptInternal(cx, options, srcBuf, callback,
1224                                            callbackData);
1225 }
1226 
StartOffThreadParseScript(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1227 JS::OffThreadToken* js::StartOffThreadParseScript(
1228     JSContext* cx, const ReadOnlyCompileOptions& options,
1229     JS::SourceText<Utf8Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1230     void* callbackData) {
1231   return StartOffThreadParseScriptInternal(cx, options, srcBuf, callback,
1232                                            callbackData);
1233 }
1234 
1235 template <typename Unit>
StartOffThreadCompileToStencilInternal(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1236 static JS::OffThreadToken* StartOffThreadCompileToStencilInternal(
1237     JSContext* cx, const ReadOnlyCompileOptions& options,
1238     JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1239     void* callbackData) {
1240   MOZ_ASSERT(!options.useOffThreadParseGlobal);
1241   auto task = cx->make_unique<CompileToStencilTask<Unit>>(cx, srcBuf, callback,
1242                                                           callbackData);
1243   if (!task) {
1244     return nullptr;
1245   }
1246 
1247   return StartOffThreadParseTask(cx, std::move(task), options);
1248 }
1249 
StartOffThreadCompileToStencil(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1250 JS::OffThreadToken* js::StartOffThreadCompileToStencil(
1251     JSContext* cx, const ReadOnlyCompileOptions& options,
1252     JS::SourceText<char16_t>& srcBuf, JS::OffThreadCompileCallback callback,
1253     void* callbackData) {
1254   return StartOffThreadCompileToStencilInternal(cx, options, srcBuf, callback,
1255                                                 callbackData);
1256 }
1257 
StartOffThreadCompileToStencil(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1258 JS::OffThreadToken* js::StartOffThreadCompileToStencil(
1259     JSContext* cx, const ReadOnlyCompileOptions& options,
1260     JS::SourceText<Utf8Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1261     void* callbackData) {
1262   return StartOffThreadCompileToStencilInternal(cx, options, srcBuf, callback,
1263                                                 callbackData);
1264 }
1265 
1266 template <typename Unit>
StartOffThreadParseModuleInternal(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1267 static JS::OffThreadToken* StartOffThreadParseModuleInternal(
1268     JSContext* cx, const ReadOnlyCompileOptions& options,
1269     JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1270     void* callbackData) {
1271   auto task = cx->make_unique<ModuleParseTask<Unit>>(cx, srcBuf, callback,
1272                                                      callbackData);
1273   if (!task) {
1274     return nullptr;
1275   }
1276 
1277   return StartOffThreadParseTask(cx, std::move(task), options);
1278 }
1279 
StartOffThreadParseModule(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1280 JS::OffThreadToken* js::StartOffThreadParseModule(
1281     JSContext* cx, const ReadOnlyCompileOptions& options,
1282     JS::SourceText<char16_t>& srcBuf, JS::OffThreadCompileCallback callback,
1283     void* callbackData) {
1284   return StartOffThreadParseModuleInternal(cx, options, srcBuf, callback,
1285                                            callbackData);
1286 }
1287 
StartOffThreadParseModule(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,JS::OffThreadCompileCallback callback,void * callbackData)1288 JS::OffThreadToken* js::StartOffThreadParseModule(
1289     JSContext* cx, const ReadOnlyCompileOptions& options,
1290     JS::SourceText<Utf8Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1291     void* callbackData) {
1292   return StartOffThreadParseModuleInternal(cx, options, srcBuf, callback,
1293                                            callbackData);
1294 }
1295 
StartOffThreadDecodeScript(JSContext * cx,const ReadOnlyCompileOptions & options,const JS::TranscodeRange & range,JS::OffThreadCompileCallback callback,void * callbackData)1296 JS::OffThreadToken* js::StartOffThreadDecodeScript(
1297     JSContext* cx, const ReadOnlyCompileOptions& options,
1298     const JS::TranscodeRange& range, JS::OffThreadCompileCallback callback,
1299     void* callbackData) {
1300   // XDR data must be Stencil format, or a parse-global must be available.
1301   MOZ_RELEASE_ASSERT(options.useStencilXDR || options.useOffThreadParseGlobal);
1302 
1303   auto task =
1304       cx->make_unique<ScriptDecodeTask>(cx, range, callback, callbackData);
1305   if (!task) {
1306     return nullptr;
1307   }
1308 
1309   return StartOffThreadParseTask(cx, std::move(task), options);
1310 }
1311 
StartOffThreadDecodeMultiScripts(JSContext * cx,const ReadOnlyCompileOptions & options,JS::TranscodeSources & sources,JS::OffThreadCompileCallback callback,void * callbackData)1312 JS::OffThreadToken* js::StartOffThreadDecodeMultiScripts(
1313     JSContext* cx, const ReadOnlyCompileOptions& options,
1314     JS::TranscodeSources& sources, JS::OffThreadCompileCallback callback,
1315     void* callbackData) {
1316   auto task = cx->make_unique<MultiScriptsDecodeTask>(cx, sources, callback,
1317                                                       callbackData);
1318   if (!task) {
1319     return nullptr;
1320   }
1321 
1322   // NOTE: All uses of DecodeMulti are currently generated by non-incremental
1323   //       XDR and therefore do not support the stencil format. As a result,
1324   //       they must continue to use the off-thread-parse-global in order to
1325   //       decode.
1326   CompileOptions optionsCopy(cx, options);
1327   optionsCopy.useStencilXDR = false;
1328   optionsCopy.useOffThreadParseGlobal = true;
1329 
1330   return StartOffThreadParseTask(cx, std::move(task), optionsCopy);
1331 }
1332 
EnqueuePendingParseTasksAfterGC(JSRuntime * rt)1333 void js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt) {
1334   MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
1335 
1336   AutoLockHelperThreadState lock;
1337 
1338   GlobalHelperThreadState::ParseTaskVector& waiting =
1339       HelperThreadState().parseWaitingOnGC(lock);
1340   for (size_t i = 0; i < waiting.length(); i++) {
1341     if (!waiting[i]->runtimeMatches(rt)) {
1342       continue;
1343     }
1344 
1345     {
1346       AutoEnterOOMUnsafeRegion oomUnsafe;
1347       if (!HelperThreadState().submitTask(rt, std::move(waiting[i]), lock)) {
1348         oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
1349       }
1350     }
1351     HelperThreadState().remove(waiting, &i);
1352   }
1353 }
1354 
1355 #ifdef DEBUG
CurrentThreadIsParseThread()1356 bool js::CurrentThreadIsParseThread() {
1357   JSContext* cx = TlsContext.get();
1358   return cx->isHelperThreadContext() && cx->parseTask();
1359 }
1360 #endif
1361 
ensureInitialized()1362 bool GlobalHelperThreadState::ensureInitialized() {
1363   MOZ_ASSERT(CanUseExtraThreads());
1364   MOZ_ASSERT(this == &HelperThreadState());
1365 
1366   AutoLockHelperThreadState lock;
1367 
1368   if (isInitialized(lock)) {
1369     return true;
1370   }
1371 
1372   for (size_t& i : runningTaskCount) {
1373     i = 0;
1374   }
1375 
1376   useInternalThreadPool_ = !dispatchTaskCallback;
1377   if (useInternalThreadPool(lock)) {
1378     if (!InternalThreadPool::Initialize(threadCount, lock)) {
1379       return false;
1380     }
1381   }
1382 
1383   MOZ_ASSERT(dispatchTaskCallback);
1384 
1385   if (!ensureThreadCount(threadCount, lock)) {
1386     finishThreads(lock);
1387     return false;
1388   }
1389 
1390   MOZ_ASSERT(threadCount != 0);
1391   isInitialized_ = true;
1392   return true;
1393 }
1394 
ensureThreadCount(size_t count,AutoLockHelperThreadState & lock)1395 bool GlobalHelperThreadState::ensureThreadCount(
1396     size_t count, AutoLockHelperThreadState& lock) {
1397   if (!ensureContextList(count, lock)) {
1398     return false;
1399   }
1400 
1401   if (!helperTasks_.reserve(count)) {
1402     return false;
1403   }
1404 
1405   if (useInternalThreadPool(lock)) {
1406     InternalThreadPool& pool = InternalThreadPool::Get();
1407     if (pool.threadCount(lock) < count) {
1408       if (!pool.ensureThreadCount(count, lock)) {
1409         return false;
1410       }
1411 
1412       threadCount = pool.threadCount(lock);
1413     }
1414   }
1415 
1416   return true;
1417 }
1418 
GlobalHelperThreadState()1419 GlobalHelperThreadState::GlobalHelperThreadState()
1420     : cpuCount(0),
1421       threadCount(0),
1422       totalCountRunningTasks(0),
1423       registerThread(nullptr),
1424       unregisterThread(nullptr),
1425       wasmTier2GeneratorsFinished_(0) {
1426   MOZ_ASSERT(!gHelperThreadState);
1427 
1428   cpuCount = ClampDefaultCPUCount(GetCPUCount());
1429   threadCount = ThreadCountForCPUCount(cpuCount);
1430   gcParallelThreadCount = threadCount;
1431 
1432   MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
1433 }
1434 
finish(AutoLockHelperThreadState & lock)1435 void GlobalHelperThreadState::finish(AutoLockHelperThreadState& lock) {
1436   if (!isInitialized(lock)) {
1437     return;
1438   }
1439 
1440   finishThreads(lock);
1441 
1442   // Make sure there are no Ion free tasks left. We check this here because,
1443   // unlike the other tasks, we don't explicitly block on this when
1444   // destroying a runtime.
1445   auto& freeList = ionFreeList(lock);
1446   while (!freeList.empty()) {
1447     UniquePtr<jit::IonFreeTask> task = std::move(freeList.back());
1448     freeList.popBack();
1449     jit::FreeIonCompileTask(task->compileTask());
1450   }
1451 
1452   destroyHelperContexts(lock);
1453 }
1454 
finishThreads(AutoLockHelperThreadState & lock)1455 void GlobalHelperThreadState::finishThreads(AutoLockHelperThreadState& lock) {
1456   waitForAllTasksLocked(lock);
1457   terminating_ = true;
1458 
1459   if (InternalThreadPool::IsInitialized()) {
1460     InternalThreadPool::ShutDown(lock);
1461   }
1462 }
1463 
ensureContextList(size_t count,const AutoLockHelperThreadState & lock)1464 bool GlobalHelperThreadState::ensureContextList(
1465     size_t count, const AutoLockHelperThreadState& lock) {
1466   while (helperContexts_.length() < count) {
1467     auto cx = js::MakeUnique<JSContext>(nullptr, JS::ContextOptions());
1468     if (!cx || !cx->init(ContextKind::HelperThread) ||
1469         !helperContexts_.append(cx.release())) {
1470       return false;
1471     }
1472   }
1473 
1474   return true;
1475 }
1476 
getFirstUnusedContext(AutoLockHelperThreadState & locked)1477 JSContext* GlobalHelperThreadState::getFirstUnusedContext(
1478     AutoLockHelperThreadState& locked) {
1479   for (auto& cx : helperContexts_) {
1480     if (cx->contextAvailable(locked)) {
1481       return cx;
1482     }
1483   }
1484   MOZ_CRASH("Expected available JSContext");
1485 }
1486 
destroyHelperContexts(AutoLockHelperThreadState & lock)1487 void GlobalHelperThreadState::destroyHelperContexts(
1488     AutoLockHelperThreadState& lock) {
1489   while (helperContexts_.length() > 0) {
1490     js_delete(helperContexts_.popCopy());
1491   }
1492 }
1493 
1494 #ifdef DEBUG
assertIsLockedByCurrentThread() const1495 void GlobalHelperThreadState::assertIsLockedByCurrentThread() const {
1496   gHelperThreadLock.assertOwnedByCurrentThread();
1497 }
1498 #endif  // DEBUG
1499 
dispatch(const AutoLockHelperThreadState & locked)1500 void GlobalHelperThreadState::dispatch(
1501     const AutoLockHelperThreadState& locked) {
1502   if (canStartTasks(locked) && tasksPending_ < threadCount) {
1503     // This doesn't guarantee that we don't dispatch more tasks to the external
1504     // pool than necessary if tasks are taking a long time to start, but it does
1505     // limit the number.
1506     tasksPending_++;
1507 
1508     // The hazard analysis can't tell that the callback doesn't GC.
1509     JS::AutoSuppressGCAnalysis nogc;
1510 
1511     dispatchTaskCallback();
1512   }
1513 }
1514 
wait(AutoLockHelperThreadState & locked,TimeDuration timeout)1515 void GlobalHelperThreadState::wait(
1516     AutoLockHelperThreadState& locked,
1517     TimeDuration timeout /* = TimeDuration::Forever() */) {
1518   consumerWakeup.wait_for(locked, timeout);
1519 }
1520 
notifyAll(const AutoLockHelperThreadState &)1521 void GlobalHelperThreadState::notifyAll(const AutoLockHelperThreadState&) {
1522   consumerWakeup.notify_all();
1523 }
1524 
notifyOne(const AutoLockHelperThreadState &)1525 void GlobalHelperThreadState::notifyOne(const AutoLockHelperThreadState&) {
1526   consumerWakeup.notify_one();
1527 }
1528 
hasActiveThreads(const AutoLockHelperThreadState & lock)1529 bool GlobalHelperThreadState::hasActiveThreads(
1530     const AutoLockHelperThreadState& lock) {
1531   return !helperTasks(lock).empty();
1532 }
1533 
WaitForAllHelperThreads()1534 void js::WaitForAllHelperThreads() { HelperThreadState().waitForAllTasks(); }
1535 
WaitForAllHelperThreads(AutoLockHelperThreadState & lock)1536 void js::WaitForAllHelperThreads(AutoLockHelperThreadState& lock) {
1537   HelperThreadState().waitForAllTasksLocked(lock);
1538 }
1539 
waitForAllTasks()1540 void GlobalHelperThreadState::waitForAllTasks() {
1541   AutoLockHelperThreadState lock;
1542   waitForAllTasksLocked(lock);
1543 }
1544 
waitForAllTasksLocked(AutoLockHelperThreadState & lock)1545 void GlobalHelperThreadState::waitForAllTasksLocked(
1546     AutoLockHelperThreadState& lock) {
1547   CancelOffThreadWasmTier2GeneratorLocked(lock);
1548 
1549   while (canStartTasks(lock) || tasksPending_ || hasActiveThreads(lock)) {
1550     wait(lock);
1551   }
1552 
1553   MOZ_ASSERT(gcParallelWorklist(lock).isEmpty());
1554   MOZ_ASSERT(ionWorklist(lock).empty());
1555   MOZ_ASSERT(wasmWorklist(lock, wasm::CompileMode::Tier1).empty());
1556   MOZ_ASSERT(promiseHelperTasks(lock).empty());
1557   MOZ_ASSERT(parseWorklist(lock).empty());
1558   MOZ_ASSERT(compressionWorklist(lock).empty());
1559   MOZ_ASSERT(ionFreeList(lock).empty());
1560   MOZ_ASSERT(wasmWorklist(lock, wasm::CompileMode::Tier2).empty());
1561   MOZ_ASSERT(wasmTier2GeneratorWorklist(lock).empty());
1562   MOZ_ASSERT(!tasksPending_);
1563   MOZ_ASSERT(!hasActiveThreads(lock));
1564 }
1565 
1566 // A task can be a "master" task, ie, it will block waiting for other worker
1567 // threads that perform work on its behalf.  If so it must not take the last
1568 // available thread; there must always be at least one worker thread able to do
1569 // the actual work.  (Or the system may deadlock.)
1570 //
1571 // If a task is a master task it *must* pass isMaster=true here, or perform a
1572 // similar calculation to avoid deadlock from starvation.
1573 //
1574 // isMaster should only be true if the thread calling checkTaskThreadLimit() is
1575 // a helper thread.
1576 //
1577 // NOTE: Calling checkTaskThreadLimit() from a helper thread in the dynamic
1578 // region after currentTask.emplace() and before currentTask.reset() may cause
1579 // it to return a different result than if it is called outside that dynamic
1580 // region, as the predicate inspects the values of the threads' currentTask
1581 // members.
1582 
checkTaskThreadLimit(ThreadType threadType,size_t maxThreads,bool isMaster,const AutoLockHelperThreadState & lock) const1583 bool GlobalHelperThreadState::checkTaskThreadLimit(
1584     ThreadType threadType, size_t maxThreads, bool isMaster,
1585     const AutoLockHelperThreadState& lock) const {
1586   MOZ_ASSERT(maxThreads > 0);
1587 
1588   if (!isMaster && maxThreads >= threadCount) {
1589     return true;
1590   }
1591 
1592   size_t count = runningTaskCount[threadType];
1593   if (count >= maxThreads) {
1594     return false;
1595   }
1596 
1597   MOZ_ASSERT(threadCount >= totalCountRunningTasks);
1598   size_t idle = threadCount - totalCountRunningTasks;
1599 
1600   // It is possible for the number of idle threads to be zero here, because
1601   // checkTaskThreadLimit() can be called from non-helper threads.  Notably,
1602   // the compression task scheduler invokes it, and runs off a helper thread.
1603   if (idle == 0) {
1604     return false;
1605   }
1606 
1607   // A master thread that's the last available thread must not be allowed to
1608   // run.
1609   if (isMaster && idle == 1) {
1610     return false;
1611   }
1612 
1613   return true;
1614 }
1615 
triggerFreeUnusedMemory()1616 void GlobalHelperThreadState::triggerFreeUnusedMemory() {
1617   if (!CanUseExtraThreads()) {
1618     return;
1619   }
1620 
1621   AutoLockHelperThreadState lock;
1622   for (auto& context : helperContexts_) {
1623     if (context->shouldFreeUnusedMemory() && context->contextAvailable(lock)) {
1624       // This context hasn't been used since the last time freeUnusedMemory
1625       // was set. Free the temp LifoAlloc from the main thread.
1626       context->tempLifoAllocNoCheck().freeAll();
1627       context->setFreeUnusedMemory(false);
1628     } else {
1629       context->setFreeUnusedMemory(true);
1630     }
1631   }
1632 }
1633 
IsHelperThreadSimulatingOOM(js::ThreadType threadType)1634 static inline bool IsHelperThreadSimulatingOOM(js::ThreadType threadType) {
1635 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1636   return js::oom::simulator.targetThread() == threadType;
1637 #else
1638   return false;
1639 #endif
1640 }
1641 
addSizeOfIncludingThis(JS::GlobalStats * stats,AutoLockHelperThreadState & lock) const1642 void GlobalHelperThreadState::addSizeOfIncludingThis(
1643     JS::GlobalStats* stats, AutoLockHelperThreadState& lock) const {
1644 #ifdef DEBUG
1645   assertIsLockedByCurrentThread();
1646 #endif
1647 
1648   mozilla::MallocSizeOf mallocSizeOf = stats->mallocSizeOf_;
1649   JS::HelperThreadStats& htStats = stats->helperThread;
1650 
1651   htStats.stateData += mallocSizeOf(this);
1652 
1653   if (InternalThreadPool::IsInitialized()) {
1654     htStats.stateData +=
1655         InternalThreadPool::Get().sizeOfIncludingThis(mallocSizeOf, lock);
1656   }
1657 
1658   // Report memory used by various containers
1659   htStats.stateData +=
1660       ionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1661       ionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1662       ionFreeList_.sizeOfExcludingThis(mallocSizeOf) +
1663       wasmWorklist_tier1_.sizeOfExcludingThis(mallocSizeOf) +
1664       wasmWorklist_tier2_.sizeOfExcludingThis(mallocSizeOf) +
1665       wasmTier2GeneratorWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1666       promiseHelperTasks_.sizeOfExcludingThis(mallocSizeOf) +
1667       parseWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1668       parseFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1669       parseWaitingOnGC_.sizeOfExcludingThis(mallocSizeOf) +
1670       compressionPendingList_.sizeOfExcludingThis(mallocSizeOf) +
1671       compressionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1672       compressionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1673       gcParallelWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1674       helperContexts_.sizeOfExcludingThis(mallocSizeOf) +
1675       helperTasks_.sizeOfExcludingThis(mallocSizeOf);
1676 
1677   // Report ParseTasks on wait lists
1678   for (const auto& task : parseWorklist_) {
1679     htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1680   }
1681   for (auto task : parseFinishedList_) {
1682     htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1683   }
1684   for (const auto& task : parseWaitingOnGC_) {
1685     htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1686   }
1687 
1688   // Report IonCompileTasks on wait lists
1689   for (auto task : ionWorklist_) {
1690     htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
1691   }
1692   for (auto task : ionFinishedList_) {
1693     htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
1694   }
1695   for (const auto& task : ionFreeList_) {
1696     htStats.ionCompileTask +=
1697         task->compileTask()->sizeOfExcludingThis(mallocSizeOf);
1698   }
1699 
1700   // Report wasm::CompileTasks on wait lists
1701   for (auto task : wasmWorklist_tier1_) {
1702     htStats.wasmCompile += task->sizeOfExcludingThis(mallocSizeOf);
1703   }
1704   for (auto task : wasmWorklist_tier2_) {
1705     htStats.wasmCompile += task->sizeOfExcludingThis(mallocSizeOf);
1706   }
1707 
1708   {
1709     // Report memory used by the JSContexts.
1710     // We're holding the helper state lock, and the JSContext memory reporter
1711     // won't do anything more substantial than traversing data structures and
1712     // getting their size, so disable ProtectedData checks.
1713     AutoNoteSingleThreadedRegion anstr;
1714     for (auto* cx : helperContexts_) {
1715       htStats.contexts += cx->sizeOfIncludingThis(mallocSizeOf);
1716     }
1717   }
1718 
1719   // Report number of helper threads.
1720   MOZ_ASSERT(htStats.idleThreadCount == 0);
1721   MOZ_ASSERT(threadCount >= totalCountRunningTasks);
1722   htStats.activeThreadCount = totalCountRunningTasks;
1723   htStats.idleThreadCount = threadCount - totalCountRunningTasks;
1724 }
1725 
maxIonCompilationThreads() const1726 size_t GlobalHelperThreadState::maxIonCompilationThreads() const {
1727   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_ION)) {
1728     return 1;
1729   }
1730   return threadCount;
1731 }
1732 
maxWasmCompilationThreads() const1733 size_t GlobalHelperThreadState::maxWasmCompilationThreads() const {
1734   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM_COMPILE_TIER1) ||
1735       IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM_COMPILE_TIER2)) {
1736     return 1;
1737   }
1738   return std::min(cpuCount, threadCount);
1739 }
1740 
maxWasmTier2GeneratorThreads() const1741 size_t GlobalHelperThreadState::maxWasmTier2GeneratorThreads() const {
1742   return MaxTier2GeneratorTasks;
1743 }
1744 
maxPromiseHelperThreads() const1745 size_t GlobalHelperThreadState::maxPromiseHelperThreads() const {
1746   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM_COMPILE_TIER1) ||
1747       IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM_COMPILE_TIER2)) {
1748     return 1;
1749   }
1750   return std::min(cpuCount, threadCount);
1751 }
1752 
maxParseThreads() const1753 size_t GlobalHelperThreadState::maxParseThreads() const {
1754   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_PARSE)) {
1755     return 1;
1756   }
1757   return std::min(cpuCount, threadCount);
1758 }
1759 
maxCompressionThreads() const1760 size_t GlobalHelperThreadState::maxCompressionThreads() const {
1761   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_COMPRESS)) {
1762     return 1;
1763   }
1764 
1765   // Compression is triggered on major GCs to compress ScriptSources. It is
1766   // considered low priority work.
1767   return 1;
1768 }
1769 
maxGCParallelThreads(const AutoLockHelperThreadState & lock) const1770 size_t GlobalHelperThreadState::maxGCParallelThreads(
1771     const AutoLockHelperThreadState& lock) const {
1772   if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_GCPARALLEL)) {
1773     return 1;
1774   }
1775   return gcParallelThreadCount;
1776 }
1777 
maybeGetWasmTier1CompileTask(const AutoLockHelperThreadState & lock)1778 HelperThreadTask* GlobalHelperThreadState::maybeGetWasmTier1CompileTask(
1779     const AutoLockHelperThreadState& lock) {
1780   return maybeGetWasmCompile(lock, wasm::CompileMode::Tier1);
1781 }
1782 
maybeGetWasmTier2CompileTask(const AutoLockHelperThreadState & lock)1783 HelperThreadTask* GlobalHelperThreadState::maybeGetWasmTier2CompileTask(
1784     const AutoLockHelperThreadState& lock) {
1785   return maybeGetWasmCompile(lock, wasm::CompileMode::Tier2);
1786 }
1787 
maybeGetWasmCompile(const AutoLockHelperThreadState & lock,wasm::CompileMode mode)1788 HelperThreadTask* GlobalHelperThreadState::maybeGetWasmCompile(
1789     const AutoLockHelperThreadState& lock, wasm::CompileMode mode) {
1790   if (!canStartWasmCompile(lock, mode)) {
1791     return nullptr;
1792   }
1793 
1794   return wasmWorklist(lock, mode).popCopyFront();
1795 }
1796 
canStartWasmTier1CompileTask(const AutoLockHelperThreadState & lock)1797 bool GlobalHelperThreadState::canStartWasmTier1CompileTask(
1798     const AutoLockHelperThreadState& lock) {
1799   return canStartWasmCompile(lock, wasm::CompileMode::Tier1);
1800 }
1801 
canStartWasmTier2CompileTask(const AutoLockHelperThreadState & lock)1802 bool GlobalHelperThreadState::canStartWasmTier2CompileTask(
1803     const AutoLockHelperThreadState& lock) {
1804   return canStartWasmCompile(lock, wasm::CompileMode::Tier2);
1805 }
1806 
canStartWasmCompile(const AutoLockHelperThreadState & lock,wasm::CompileMode mode)1807 bool GlobalHelperThreadState::canStartWasmCompile(
1808     const AutoLockHelperThreadState& lock, wasm::CompileMode mode) {
1809   if (wasmWorklist(lock, mode).empty()) {
1810     return false;
1811   }
1812 
1813   // Parallel compilation and background compilation should be disabled on
1814   // unicore systems.
1815 
1816   MOZ_RELEASE_ASSERT(cpuCount > 1);
1817 
1818   // If Tier2 is very backlogged we must give priority to it, since the Tier2
1819   // queue holds onto Tier1 tasks.  Indeed if Tier2 is backlogged we will
1820   // devote more resources to Tier2 and not start any Tier1 work at all.
1821 
1822   bool tier2oversubscribed = wasmTier2GeneratorWorklist(lock).length() > 20;
1823 
1824   // For Tier1 and Once compilation, honor the maximum allowed threads to
1825   // compile wasm jobs at once, to avoid oversaturating the machine.
1826   //
1827   // For Tier2 compilation we need to allow other things to happen too, so we
1828   // do not allow all logical cores to be used for background work; instead we
1829   // wish to use a fraction of the physical cores.  We can't directly compute
1830   // the physical cores from the logical cores, but 1/3 of the logical cores
1831   // is a safe estimate for the number of physical cores available for
1832   // background work.
1833 
1834   size_t physCoresAvailable = size_t(ceil(cpuCount / 3.0));
1835 
1836   size_t threads;
1837   ThreadType threadType;
1838   if (mode == wasm::CompileMode::Tier2) {
1839     if (tier2oversubscribed) {
1840       threads = maxWasmCompilationThreads();
1841     } else {
1842       threads = physCoresAvailable;
1843     }
1844     threadType = THREAD_TYPE_WASM_COMPILE_TIER2;
1845   } else {
1846     if (tier2oversubscribed) {
1847       threads = 0;
1848     } else {
1849       threads = maxWasmCompilationThreads();
1850     }
1851     threadType = THREAD_TYPE_WASM_COMPILE_TIER1;
1852   }
1853 
1854   return threads != 0 && checkTaskThreadLimit(threadType, threads, lock);
1855 }
1856 
maybeGetWasmTier2GeneratorTask(const AutoLockHelperThreadState & lock)1857 HelperThreadTask* GlobalHelperThreadState::maybeGetWasmTier2GeneratorTask(
1858     const AutoLockHelperThreadState& lock) {
1859   if (!canStartWasmTier2GeneratorTask(lock)) {
1860     return nullptr;
1861   }
1862 
1863   return wasmTier2GeneratorWorklist(lock).popCopy();
1864 }
1865 
canStartWasmTier2GeneratorTask(const AutoLockHelperThreadState & lock)1866 bool GlobalHelperThreadState::canStartWasmTier2GeneratorTask(
1867     const AutoLockHelperThreadState& lock) {
1868   return !wasmTier2GeneratorWorklist(lock).empty() &&
1869          checkTaskThreadLimit(THREAD_TYPE_WASM_GENERATOR_TIER2,
1870                               maxWasmTier2GeneratorThreads(),
1871                               /*isMaster=*/true, lock);
1872 }
1873 
maybeGetPromiseHelperTask(const AutoLockHelperThreadState & lock)1874 HelperThreadTask* GlobalHelperThreadState::maybeGetPromiseHelperTask(
1875     const AutoLockHelperThreadState& lock) {
1876   if (!canStartPromiseHelperTask(lock)) {
1877     return nullptr;
1878   }
1879 
1880   return promiseHelperTasks(lock).popCopy();
1881 }
1882 
canStartPromiseHelperTask(const AutoLockHelperThreadState & lock)1883 bool GlobalHelperThreadState::canStartPromiseHelperTask(
1884     const AutoLockHelperThreadState& lock) {
1885   // PromiseHelperTasks can be wasm compilation tasks that in turn block on
1886   // wasm compilation so set isMaster = true.
1887   return !promiseHelperTasks(lock).empty() &&
1888          checkTaskThreadLimit(THREAD_TYPE_PROMISE_TASK,
1889                               maxPromiseHelperThreads(),
1890                               /*isMaster=*/true, lock);
1891 }
1892 
IonCompileTaskHasHigherPriority(jit::IonCompileTask * first,jit::IonCompileTask * second)1893 static bool IonCompileTaskHasHigherPriority(jit::IonCompileTask* first,
1894                                             jit::IonCompileTask* second) {
1895   // Return true if priority(first) > priority(second).
1896   //
1897   // This method can return whatever it wants, though it really ought to be a
1898   // total order. The ordering is allowed to race (change on the fly), however.
1899 
1900   // A higher warm-up counter indicates a higher priority.
1901   jit::JitScript* firstJitScript = first->script()->jitScript();
1902   jit::JitScript* secondJitScript = second->script()->jitScript();
1903   return firstJitScript->warmUpCount() / first->script()->length() >
1904          secondJitScript->warmUpCount() / second->script()->length();
1905 }
1906 
maybeGetIonCompileTask(const AutoLockHelperThreadState & lock)1907 HelperThreadTask* GlobalHelperThreadState::maybeGetIonCompileTask(
1908     const AutoLockHelperThreadState& lock) {
1909   if (!canStartIonCompileTask(lock)) {
1910     return nullptr;
1911   }
1912 
1913   return highestPriorityPendingIonCompile(lock);
1914 }
1915 
canStartIonCompileTask(const AutoLockHelperThreadState & lock)1916 bool GlobalHelperThreadState::canStartIonCompileTask(
1917     const AutoLockHelperThreadState& lock) {
1918   return !ionWorklist(lock).empty() &&
1919          checkTaskThreadLimit(THREAD_TYPE_ION, maxIonCompilationThreads(),
1920                               lock);
1921 }
1922 
maybeGetIonFreeTask(const AutoLockHelperThreadState & lock)1923 HelperThreadTask* GlobalHelperThreadState::maybeGetIonFreeTask(
1924     const AutoLockHelperThreadState& lock) {
1925   if (!canStartIonFreeTask(lock)) {
1926     return nullptr;
1927   }
1928 
1929   UniquePtr<jit::IonFreeTask> task = std::move(ionFreeList(lock).back());
1930   ionFreeList(lock).popBack();
1931   return task.release();
1932 }
1933 
canStartIonFreeTask(const AutoLockHelperThreadState & lock)1934 bool GlobalHelperThreadState::canStartIonFreeTask(
1935     const AutoLockHelperThreadState& lock) {
1936   return !ionFreeList(lock).empty();
1937 }
1938 
highestPriorityPendingIonCompile(const AutoLockHelperThreadState & lock)1939 jit::IonCompileTask* GlobalHelperThreadState::highestPriorityPendingIonCompile(
1940     const AutoLockHelperThreadState& lock) {
1941   auto& worklist = ionWorklist(lock);
1942   MOZ_ASSERT(!worklist.empty());
1943 
1944   // Get the highest priority IonCompileTask which has not started compilation
1945   // yet.
1946   size_t index = 0;
1947   for (size_t i = 1; i < worklist.length(); i++) {
1948     if (IonCompileTaskHasHigherPriority(worklist[i], worklist[index])) {
1949       index = i;
1950     }
1951   }
1952 
1953   jit::IonCompileTask* task = worklist[index];
1954   worklist.erase(&worklist[index]);
1955   return task;
1956 }
1957 
maybeGetParseTask(const AutoLockHelperThreadState & lock)1958 HelperThreadTask* GlobalHelperThreadState::maybeGetParseTask(
1959     const AutoLockHelperThreadState& lock) {
1960   if (!canStartParseTask(lock)) {
1961     return nullptr;
1962   }
1963 
1964   auto& worklist = parseWorklist(lock);
1965   UniquePtr<ParseTask> task = std::move(worklist.back());
1966   worklist.popBack();
1967   return task.release();
1968 }
1969 
canStartParseTask(const AutoLockHelperThreadState & lock)1970 bool GlobalHelperThreadState::canStartParseTask(
1971     const AutoLockHelperThreadState& lock) {
1972   // Parse tasks that end up compiling asm.js in turn may use Wasm compilation
1973   // threads to generate machine code.  We have no way (at present) to know
1974   // ahead of time whether a parse task is going to parse asm.js content or not,
1975   // so we just assume that all parse tasks are master tasks.
1976   return !parseWorklist(lock).empty() &&
1977          checkTaskThreadLimit(THREAD_TYPE_PARSE, maxParseThreads(),
1978                               /*isMaster=*/true, lock);
1979 }
1980 
maybeGetCompressionTask(const AutoLockHelperThreadState & lock)1981 HelperThreadTask* GlobalHelperThreadState::maybeGetCompressionTask(
1982     const AutoLockHelperThreadState& lock) {
1983   if (!canStartCompressionTask(lock)) {
1984     return nullptr;
1985   }
1986 
1987   auto& worklist = compressionWorklist(lock);
1988   UniquePtr<SourceCompressionTask> task = std::move(worklist.back());
1989   worklist.popBack();
1990   return task.release();
1991 }
1992 
canStartCompressionTask(const AutoLockHelperThreadState & lock)1993 bool GlobalHelperThreadState::canStartCompressionTask(
1994     const AutoLockHelperThreadState& lock) {
1995   return !compressionWorklist(lock).empty() &&
1996          checkTaskThreadLimit(THREAD_TYPE_COMPRESS, maxCompressionThreads(),
1997                               lock);
1998 }
1999 
startHandlingCompressionTasks(ScheduleCompressionTask schedule,JSRuntime * maybeRuntime,const AutoLockHelperThreadState & lock)2000 void GlobalHelperThreadState::startHandlingCompressionTasks(
2001     ScheduleCompressionTask schedule, JSRuntime* maybeRuntime,
2002     const AutoLockHelperThreadState& lock) {
2003   MOZ_ASSERT((schedule == ScheduleCompressionTask::GC) ==
2004              (maybeRuntime != nullptr));
2005 
2006   auto& pending = compressionPendingList(lock);
2007 
2008   for (size_t i = 0; i < pending.length(); i++) {
2009     UniquePtr<SourceCompressionTask>& task = pending[i];
2010     if (schedule == ScheduleCompressionTask::API ||
2011         (task->runtimeMatches(maybeRuntime) && task->shouldStart())) {
2012       // OOMing during appending results in the task not being scheduled
2013       // and deleted.
2014       (void)submitTask(std::move(task), lock);
2015       remove(pending, &i);
2016     }
2017   }
2018 }
2019 
submitTask(UniquePtr<SourceCompressionTask> task,const AutoLockHelperThreadState & locked)2020 bool GlobalHelperThreadState::submitTask(
2021     UniquePtr<SourceCompressionTask> task,
2022     const AutoLockHelperThreadState& locked) {
2023   if (!compressionWorklist(locked).append(std::move(task))) {
2024     return false;
2025   }
2026 
2027   dispatch(locked);
2028   return true;
2029 }
2030 
submitTask(GCParallelTask * task,const AutoLockHelperThreadState & locked)2031 bool GlobalHelperThreadState::submitTask(
2032     GCParallelTask* task, const AutoLockHelperThreadState& locked) {
2033   gcParallelWorklist(locked).insertBack(task);
2034   dispatch(locked);
2035   return true;
2036 }
2037 
maybeGetGCParallelTask(const AutoLockHelperThreadState & lock)2038 HelperThreadTask* GlobalHelperThreadState::maybeGetGCParallelTask(
2039     const AutoLockHelperThreadState& lock) {
2040   if (!canStartGCParallelTask(lock)) {
2041     return nullptr;
2042   }
2043 
2044   return gcParallelWorklist(lock).popFirst();
2045 }
2046 
canStartGCParallelTask(const AutoLockHelperThreadState & lock)2047 bool GlobalHelperThreadState::canStartGCParallelTask(
2048     const AutoLockHelperThreadState& lock) {
2049   return !gcParallelWorklist(lock).isEmpty() &&
2050          checkTaskThreadLimit(THREAD_TYPE_GCPARALLEL,
2051                               maxGCParallelThreads(lock), lock);
2052 }
2053 
LeaveParseTaskZone(JSRuntime * rt,ParseTask * task)2054 static void LeaveParseTaskZone(JSRuntime* rt, ParseTask* task) {
2055   // Mark the zone as no longer in use by a helper thread, and available
2056   // to be collected by the GC.
2057   if (task->parseGlobal) {
2058     rt->clearUsedByHelperThread(task->parseGlobal->zoneFromAnyThread());
2059   }
2060   rt->decParseTaskRef();
2061 }
2062 
removeFinishedParseTask(JSContext * cx,ParseTaskKind kind,JS::OffThreadToken * token)2063 ParseTask* GlobalHelperThreadState::removeFinishedParseTask(
2064     JSContext* cx, ParseTaskKind kind, JS::OffThreadToken* token) {
2065   // The token is really a ParseTask* which should be in the finished list.
2066   auto task = static_cast<ParseTask*>(token);
2067 
2068   // The token was passed in from the browser. Check that the pointer is likely
2069   // a valid parse task of the expected kind.
2070   MOZ_RELEASE_ASSERT(task->runtime == cx->runtime());
2071   MOZ_RELEASE_ASSERT(task->kind == kind);
2072 
2073   // Remove the task from the finished list.
2074   AutoLockHelperThreadState lock;
2075   MOZ_ASSERT(parseFinishedList(lock).contains(task));
2076   task->remove();
2077   return task;
2078 }
2079 
finishParseTaskCommon(JSContext * cx,ParseTaskKind kind,JS::OffThreadToken * token)2080 UniquePtr<ParseTask> GlobalHelperThreadState::finishParseTaskCommon(
2081     JSContext* cx, ParseTaskKind kind, JS::OffThreadToken* token) {
2082   MOZ_ASSERT(!cx->isHelperThreadContext());
2083   MOZ_ASSERT(cx->realm());
2084 
2085   Rooted<UniquePtr<ParseTask>> parseTask(
2086       cx, removeFinishedParseTask(cx, kind, token));
2087 
2088   if (parseTask->options.useOffThreadParseGlobal) {
2089     // Make sure we have all the constructors we need for the prototype
2090     // remapping below, since we can't GC while that's happening.
2091     if (!EnsureParserCreatedClasses(cx, kind)) {
2092       LeaveParseTaskZone(cx->runtime(), parseTask.get().get());
2093       return nullptr;
2094     }
2095 
2096     mergeParseTaskRealm(cx, parseTask.get().get(), cx->realm());
2097 
2098     for (auto& script : parseTask->scripts) {
2099       cx->releaseCheck(script);
2100     }
2101 
2102     if (kind == ParseTaskKind::Module) {
2103       if (parseTask->scripts.length() > 0) {
2104         MOZ_ASSERT(parseTask->scripts[0]->isModule());
2105         parseTask->scripts[0]->module()->fixEnvironmentsAfterRealmMerge();
2106       }
2107     }
2108 
2109     // Finish initializing ScriptSourceObject now that we are back on
2110     // main-thread and in the correct realm.
2111     for (auto& sourceObject : parseTask->sourceObjects) {
2112       RootedScriptSourceObject sso(cx, sourceObject);
2113 
2114       if (!ScriptSourceObject::initFromOptions(cx, sso, parseTask->options)) {
2115         return nullptr;
2116       }
2117 
2118       if (!sso->source()->tryCompressOffThread(cx)) {
2119         return nullptr;
2120       }
2121     }
2122   } else {
2123     // GC things should be allocated in finishSingleParseTask, after
2124     // calling finishParseTaskCommon.
2125     MOZ_ASSERT(parseTask->scripts.length() == 0);
2126     MOZ_ASSERT(parseTask->sourceObjects.length() == 0);
2127   }
2128 
2129   // Report out of memory errors eagerly, or errors could be malformed.
2130   if (parseTask->outOfMemory) {
2131     ReportOutOfMemory(cx);
2132     return nullptr;
2133   }
2134 
2135   // Report any error or warnings generated during the parse.
2136   for (size_t i = 0; i < parseTask->errors.length(); i++) {
2137     parseTask->errors[i]->throwError(cx);
2138   }
2139   if (parseTask->overRecursed) {
2140     ReportOverRecursed(cx);
2141   }
2142   if (cx->isExceptionPending()) {
2143     return nullptr;
2144   }
2145 
2146   if (parseTask->options.useOffThreadParseGlobal) {
2147     if (coverage::IsLCovEnabled()) {
2148       if (!generateLCovSources(cx, parseTask.get().get())) {
2149         return nullptr;
2150       }
2151     }
2152   }
2153 
2154   return std::move(parseTask.get());
2155 }
2156 
2157 // Generate initial LCovSources for generated inner functions.
generateLCovSources(JSContext * cx,ParseTask * parseTask)2158 bool GlobalHelperThreadState::generateLCovSources(JSContext* cx,
2159                                                   ParseTask* parseTask) {
2160   Rooted<GCVector<JSScript*>> workList(cx, GCVector<JSScript*>(cx));
2161 
2162   if (!workList.appendAll(parseTask->scripts)) {
2163     return false;
2164   }
2165 
2166   RootedScript elem(cx);
2167   while (!workList.empty()) {
2168     elem = workList.popCopy();
2169 
2170     // Initialize LCov data for the script.
2171     if (!coverage::InitScriptCoverage(cx, elem)) {
2172       return false;
2173     }
2174 
2175     // Add inner-function scripts to the work-list.
2176     for (JS::GCCellPtr gcThing : elem->gcthings()) {
2177       if (!gcThing.is<JSObject>()) {
2178         continue;
2179       }
2180       JSObject* obj = &gcThing.as<JSObject>();
2181 
2182       if (!obj->is<JSFunction>()) {
2183         continue;
2184       }
2185       JSFunction* fun = &obj->as<JSFunction>();
2186 
2187       // Ignore asm.js functions
2188       if (!fun->isInterpreted()) {
2189         continue;
2190       }
2191 
2192       MOZ_ASSERT(fun->hasBytecode(),
2193                  "No lazy scripts exist when collecting coverage");
2194       if (!workList.append(fun->nonLazyScript())) {
2195         return false;
2196       }
2197     }
2198   }
2199 
2200   return true;
2201 }
2202 
finishSingleParseTask(JSContext * cx,ParseTaskKind kind,JS::OffThreadToken * token,StartEncoding startEncoding)2203 JSScript* GlobalHelperThreadState::finishSingleParseTask(
2204     JSContext* cx, ParseTaskKind kind, JS::OffThreadToken* token,
2205     StartEncoding startEncoding /* = StartEncoding::No */) {
2206   Rooted<UniquePtr<ParseTask>> parseTask(
2207       cx, finishParseTaskCommon(cx, kind, token));
2208   if (!parseTask) {
2209     return nullptr;
2210   }
2211 
2212   JS::RootedScript script(cx);
2213 
2214   // Finish main-thread initialization of scripts.
2215   if (parseTask->options.useOffThreadParseGlobal) {
2216     if (parseTask->scripts.length() > 0) {
2217       script = parseTask->scripts[0];
2218     }
2219 
2220     if (!script) {
2221       // No error was reported, but no script produced. Assume we hit out of
2222       // memory.
2223       MOZ_ASSERT(false, "Expected script");
2224       ReportOutOfMemory(cx);
2225       return nullptr;
2226     }
2227 
2228     if (kind == ParseTaskKind::Module) {
2229       // See: InstantiateTopLevel in frontend/Stencil.cpp.
2230       MOZ_ASSERT(script->isModule());
2231       RootedModuleObject module(cx, script->module());
2232       if (!ModuleObject::Freeze(cx, module)) {
2233         return nullptr;
2234       }
2235     }
2236 
2237     // The Debugger only needs to be told about the topmost script that was
2238     // compiled.
2239     if (!parseTask->options.hideFromNewScriptInitial()) {
2240       DebugAPI::onNewScript(cx, script);
2241     }
2242   } else {
2243     MOZ_ASSERT(parseTask->stencil_.get() ||
2244                parseTask->extensibleStencil_.get());
2245 
2246     if (!parseTask->instantiateStencils(cx)) {
2247       return nullptr;
2248     }
2249 
2250     MOZ_RELEASE_ASSERT(parseTask->scripts.length() == 1);
2251     script = parseTask->scripts[0];
2252   }
2253 
2254   // Start the incremental-XDR encoder.
2255   if (startEncoding == StartEncoding::Yes) {
2256     MOZ_DIAGNOSTIC_ASSERT(parseTask->options.useStencilXDR);
2257 
2258     if (parseTask->stencil_) {
2259       auto initial = js::MakeUnique<frontend::ExtensibleCompilationStencil>(
2260           cx, *parseTask->stencilInput_);
2261       if (!initial) {
2262         ReportOutOfMemory(cx);
2263         return nullptr;
2264       }
2265       if (!initial->steal(cx, std::move(*parseTask->stencil_))) {
2266         return nullptr;
2267       }
2268 
2269       if (!script->scriptSource()->startIncrementalEncoding(
2270               cx, parseTask->options, std::move(initial))) {
2271         return nullptr;
2272       }
2273     } else if (parseTask->extensibleStencil_) {
2274       if (!script->scriptSource()->startIncrementalEncoding(
2275               cx, parseTask->options,
2276               std::move(parseTask->extensibleStencil_))) {
2277         return nullptr;
2278       }
2279     }
2280   }
2281 
2282   return script;
2283 }
2284 
2285 UniquePtr<frontend::CompilationStencil>
finishCompileToStencilTask(JSContext * cx,ParseTaskKind kind,JS::OffThreadToken * token)2286 GlobalHelperThreadState::finishCompileToStencilTask(JSContext* cx,
2287                                                     ParseTaskKind kind,
2288                                                     JS::OffThreadToken* token) {
2289   Rooted<UniquePtr<ParseTask>> parseTask(
2290       cx, finishParseTaskCommon(cx, kind, token));
2291   if (!parseTask) {
2292     return nullptr;
2293   }
2294 
2295   MOZ_ASSERT(parseTask->stencilInput_.get());
2296   MOZ_ASSERT(parseTask->extensibleStencil_.get());
2297 
2298   auto stencil = cx->make_unique<frontend::CompilationStencil>(
2299       parseTask->stencilInput_->source);
2300   if (!stencil) {
2301     return nullptr;
2302   }
2303 
2304   if (!stencil->steal(cx, std::move(*parseTask->extensibleStencil_))) {
2305     return nullptr;
2306   }
2307 
2308   return stencil;
2309 }
2310 
finishMultiParseTask(JSContext * cx,ParseTaskKind kind,JS::OffThreadToken * token,MutableHandle<ScriptVector> scripts)2311 bool GlobalHelperThreadState::finishMultiParseTask(
2312     JSContext* cx, ParseTaskKind kind, JS::OffThreadToken* token,
2313     MutableHandle<ScriptVector> scripts) {
2314   Rooted<UniquePtr<ParseTask>> parseTask(
2315       cx, finishParseTaskCommon(cx, kind, token));
2316   if (!parseTask) {
2317     return false;
2318   }
2319 
2320   MOZ_ASSERT(parseTask->kind == ParseTaskKind::MultiScriptsDecode);
2321   auto task = static_cast<MultiScriptsDecodeTask*>(parseTask.get().get());
2322   size_t expectedLength = task->sources->length();
2323 
2324   if (!scripts.reserve(parseTask->scripts.length())) {
2325     ReportOutOfMemory(cx);
2326     return false;
2327   }
2328 
2329   for (auto& script : parseTask->scripts) {
2330     scripts.infallibleAppend(script);
2331   }
2332 
2333   if (scripts.length() != expectedLength) {
2334     // No error was reported, but fewer scripts produced than expected.
2335     // Assume we hit out of memory.
2336     MOZ_ASSERT(false, "Expected more scripts");
2337     ReportOutOfMemory(cx);
2338     return false;
2339   }
2340 
2341   // The Debugger only needs to be told about the topmost scripts that were
2342   // compiled.
2343   if (!parseTask->options.hideFromNewScriptInitial()) {
2344     JS::RootedScript rooted(cx);
2345     for (auto& script : scripts) {
2346       MOZ_ASSERT(script->isGlobalCode());
2347 
2348       rooted = script;
2349       DebugAPI::onNewScript(cx, rooted);
2350     }
2351   }
2352 
2353   return true;
2354 }
2355 
finishScriptParseTask(JSContext * cx,JS::OffThreadToken * token,StartEncoding startEncoding)2356 JSScript* GlobalHelperThreadState::finishScriptParseTask(
2357     JSContext* cx, JS::OffThreadToken* token,
2358     StartEncoding startEncoding /* = StartEncoding::No */) {
2359   JSScript* script =
2360       finishSingleParseTask(cx, ParseTaskKind::Script, token, startEncoding);
2361   MOZ_ASSERT_IF(script, script->isGlobalCode());
2362   return script;
2363 }
2364 
2365 UniquePtr<frontend::CompilationStencil>
finishCompileToStencilTask(JSContext * cx,JS::OffThreadToken * token)2366 GlobalHelperThreadState::finishCompileToStencilTask(JSContext* cx,
2367                                                     JS::OffThreadToken* token) {
2368   return finishCompileToStencilTask(cx, ParseTaskKind::ScriptStencil, token);
2369 }
2370 
finishScriptDecodeTask(JSContext * cx,JS::OffThreadToken * token)2371 JSScript* GlobalHelperThreadState::finishScriptDecodeTask(
2372     JSContext* cx, JS::OffThreadToken* token) {
2373   JSScript* script =
2374       finishSingleParseTask(cx, ParseTaskKind::ScriptDecode, token);
2375   MOZ_ASSERT_IF(script, script->isGlobalCode());
2376   return script;
2377 }
2378 
finishMultiScriptsDecodeTask(JSContext * cx,JS::OffThreadToken * token,MutableHandle<ScriptVector> scripts)2379 bool GlobalHelperThreadState::finishMultiScriptsDecodeTask(
2380     JSContext* cx, JS::OffThreadToken* token,
2381     MutableHandle<ScriptVector> scripts) {
2382   return finishMultiParseTask(cx, ParseTaskKind::MultiScriptsDecode, token,
2383                               scripts);
2384 }
2385 
finishModuleParseTask(JSContext * cx,JS::OffThreadToken * token)2386 JSObject* GlobalHelperThreadState::finishModuleParseTask(
2387     JSContext* cx, JS::OffThreadToken* token) {
2388   JSScript* script = finishSingleParseTask(cx, ParseTaskKind::Module, token);
2389   if (!script) {
2390     return nullptr;
2391   }
2392 
2393   return script->module();
2394 }
2395 
finishStencilParseTask(JSContext * cx,JS::OffThreadToken * token)2396 frontend::CompilationStencil* GlobalHelperThreadState::finishStencilParseTask(
2397     JSContext* cx, JS::OffThreadToken* token) {
2398   // TODO: The Script and Module task kinds should be combined in future since
2399   //       they both generate the same Stencil type.
2400   auto task = static_cast<ParseTask*>(token);
2401   MOZ_RELEASE_ASSERT(task->kind == ParseTaskKind::Script ||
2402                      task->kind == ParseTaskKind::Module);
2403 
2404   Rooted<UniquePtr<ParseTask>> parseTask(
2405       cx, finishParseTaskCommon(cx, task->kind, token));
2406   if (!parseTask) {
2407     return nullptr;
2408   }
2409 
2410   MOZ_ASSERT(!parseTask->options.useOffThreadParseGlobal);
2411   MOZ_ASSERT(parseTask->extensibleStencil_);
2412 
2413   UniquePtr<frontend::CompilationStencil> stencil =
2414       cx->make_unique<frontend::CompilationStencil>(
2415           parseTask->stencilInput_->source);
2416   if (!stencil) {
2417     return nullptr;
2418   }
2419 
2420   if (!stencil->steal(cx, std::move(*parseTask->extensibleStencil_))) {
2421     return nullptr;
2422   }
2423 
2424   return stencil.release();
2425 }
2426 
cancelParseTask(JSRuntime * rt,ParseTaskKind kind,JS::OffThreadToken * token)2427 void GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind,
2428                                               JS::OffThreadToken* token) {
2429   AutoLockHelperThreadState lock;
2430   MOZ_ASSERT(token);
2431 
2432   ParseTask* task = static_cast<ParseTask*>(token);
2433 
2434   // Check pending queues to see if we can simply remove the task.
2435   GlobalHelperThreadState::ParseTaskVector& waitingOnGC =
2436       HelperThreadState().parseWaitingOnGC(lock);
2437   for (size_t i = 0; i < waitingOnGC.length(); i++) {
2438     if (task == waitingOnGC[i]) {
2439       MOZ_ASSERT(task->kind == kind);
2440       MOZ_ASSERT(task->runtimeMatches(rt));
2441       task->parseGlobal->zoneFromAnyThread()->clearUsedByHelperThread();
2442       HelperThreadState().remove(waitingOnGC, &i);
2443       return;
2444     }
2445   }
2446 
2447   GlobalHelperThreadState::ParseTaskVector& worklist =
2448       HelperThreadState().parseWorklist(lock);
2449   for (size_t i = 0; i < worklist.length(); i++) {
2450     if (task == worklist[i]) {
2451       MOZ_ASSERT(task->kind == kind);
2452       MOZ_ASSERT(task->runtimeMatches(rt));
2453       LeaveParseTaskZone(rt, task);
2454       HelperThreadState().remove(worklist, &i);
2455       return;
2456     }
2457   }
2458 
2459   // If task is currently running, wait for it to complete.
2460   while (true) {
2461     bool foundTask = false;
2462     for (auto* helper : HelperThreadState().helperTasks(lock)) {
2463       if (helper->is<ParseTask>() && helper->as<ParseTask>() == task) {
2464         MOZ_ASSERT(helper->as<ParseTask>()->kind == kind);
2465         MOZ_ASSERT(helper->as<ParseTask>()->runtimeMatches(rt));
2466         foundTask = true;
2467         break;
2468       }
2469     }
2470 
2471     if (!foundTask) {
2472       break;
2473     }
2474 
2475     HelperThreadState().wait(lock);
2476   }
2477 
2478   auto& finished = HelperThreadState().parseFinishedList(lock);
2479   for (auto* t : finished) {
2480     if (task == t) {
2481       MOZ_ASSERT(task->kind == kind);
2482       MOZ_ASSERT(task->runtimeMatches(rt));
2483       task->remove();
2484       HelperThreadState().destroyParseTask(rt, task);
2485       return;
2486     }
2487   }
2488 }
2489 
destroyParseTask(JSRuntime * rt,ParseTask * parseTask)2490 void GlobalHelperThreadState::destroyParseTask(JSRuntime* rt,
2491                                                ParseTask* parseTask) {
2492   MOZ_ASSERT(!parseTask->isInList());
2493   LeaveParseTaskZone(rt, parseTask);
2494   js_delete(parseTask);
2495 }
2496 
mergeParseTaskRealm(JSContext * cx,ParseTask * parseTask,Realm * dest)2497 void GlobalHelperThreadState::mergeParseTaskRealm(JSContext* cx,
2498                                                   ParseTask* parseTask,
2499                                                   Realm* dest) {
2500   MOZ_ASSERT(parseTask->parseGlobal);
2501 
2502   // After we call LeaveParseTaskZone() it's not safe to GC until we have
2503   // finished merging the contents of the parse task's realm into the
2504   // destination realm.
2505   JS::AutoAssertNoGC nogc(cx);
2506 
2507   LeaveParseTaskZone(cx->runtime(), parseTask);
2508 
2509   // Move the parsed script and all its contents into the desired realm.
2510   gc::MergeRealms(parseTask->parseGlobal->as<GlobalObject>().realm(), dest);
2511 }
2512 
addPendingCompileError(js::CompileError ** error)2513 bool JSContext::addPendingCompileError(js::CompileError** error) {
2514   auto errorPtr = make_unique<js::CompileError>();
2515   if (!errorPtr) {
2516     return false;
2517   }
2518   if (!parseTask_->errors.append(std::move(errorPtr))) {
2519     ReportOutOfMemory(this);
2520     return false;
2521   }
2522   *error = parseTask_->errors.back().get();
2523   return true;
2524 }
2525 
isCompileErrorPending() const2526 bool JSContext::isCompileErrorPending() const {
2527   return parseTask_->errors.length() > 0;
2528 }
2529 
addPendingOverRecursed()2530 void JSContext::addPendingOverRecursed() {
2531   if (parseTask_) {
2532     parseTask_->overRecursed = true;
2533   }
2534 }
2535 
addPendingOutOfMemory()2536 void JSContext::addPendingOutOfMemory() {
2537   // Keep in sync with recoverFromOutOfMemory.
2538   if (parseTask_) {
2539     parseTask_->outOfMemory = true;
2540   }
2541 }
2542 
EnqueueOffThreadCompression(JSContext * cx,UniquePtr<SourceCompressionTask> task)2543 bool js::EnqueueOffThreadCompression(JSContext* cx,
2544                                      UniquePtr<SourceCompressionTask> task) {
2545   AutoLockHelperThreadState lock;
2546 
2547   auto& pending = HelperThreadState().compressionPendingList(lock);
2548   if (!pending.append(std::move(task))) {
2549     if (!cx->isHelperThreadContext()) {
2550       ReportOutOfMemory(cx);
2551     }
2552     return false;
2553   }
2554 
2555   return true;
2556 }
2557 
StartHandlingCompressionsOnGC(JSRuntime * runtime)2558 void js::StartHandlingCompressionsOnGC(JSRuntime* runtime) {
2559   AutoLockHelperThreadState lock;
2560   HelperThreadState().startHandlingCompressionTasks(
2561       GlobalHelperThreadState::ScheduleCompressionTask::GC, runtime, lock);
2562 }
2563 
2564 template <typename T>
ClearCompressionTaskList(T & list,JSRuntime * runtime)2565 static void ClearCompressionTaskList(T& list, JSRuntime* runtime) {
2566   for (size_t i = 0; i < list.length(); i++) {
2567     if (list[i]->runtimeMatches(runtime)) {
2568       HelperThreadState().remove(list, &i);
2569     }
2570   }
2571 }
2572 
CancelOffThreadCompressions(JSRuntime * runtime)2573 void js::CancelOffThreadCompressions(JSRuntime* runtime) {
2574   if (!CanUseExtraThreads()) {
2575     return;
2576   }
2577 
2578   AutoLockHelperThreadState lock;
2579 
2580   // Cancel all pending compression tasks.
2581   ClearCompressionTaskList(HelperThreadState().compressionPendingList(lock),
2582                            runtime);
2583   ClearCompressionTaskList(HelperThreadState().compressionWorklist(lock),
2584                            runtime);
2585 
2586   // Cancel all in-process compression tasks and wait for them to join so we
2587   // clean up the finished tasks.
2588   while (true) {
2589     bool inProgress = false;
2590     for (auto* helper : HelperThreadState().helperTasks(lock)) {
2591       if (!helper->is<SourceCompressionTask>()) {
2592         continue;
2593       }
2594 
2595       if (helper->as<SourceCompressionTask>()->runtimeMatches(runtime)) {
2596         inProgress = true;
2597       }
2598     }
2599 
2600     if (!inProgress) {
2601       break;
2602     }
2603 
2604     HelperThreadState().wait(lock);
2605   }
2606 
2607   // Clean up finished tasks.
2608   ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock),
2609                            runtime);
2610 }
2611 
AttachFinishedCompressions(JSRuntime * runtime,AutoLockHelperThreadState & lock)2612 void js::AttachFinishedCompressions(JSRuntime* runtime,
2613                                     AutoLockHelperThreadState& lock) {
2614   auto& finished = HelperThreadState().compressionFinishedList(lock);
2615   for (size_t i = 0; i < finished.length(); i++) {
2616     if (finished[i]->runtimeMatches(runtime)) {
2617       UniquePtr<SourceCompressionTask> compressionTask(std::move(finished[i]));
2618       HelperThreadState().remove(finished, &i);
2619       compressionTask->complete();
2620     }
2621   }
2622 }
2623 
SweepPendingCompressions(AutoLockHelperThreadState & lock)2624 void js::SweepPendingCompressions(AutoLockHelperThreadState& lock) {
2625   auto& pending = HelperThreadState().compressionPendingList(lock);
2626   for (size_t i = 0; i < pending.length(); i++) {
2627     if (pending[i]->shouldCancel()) {
2628       HelperThreadState().remove(pending, &i);
2629     }
2630   }
2631 }
2632 
RunPendingSourceCompressions(JSRuntime * runtime)2633 void js::RunPendingSourceCompressions(JSRuntime* runtime) {
2634   if (!CanUseExtraThreads()) {
2635     return;
2636   }
2637 
2638   AutoLockHelperThreadState lock;
2639 
2640   HelperThreadState().startHandlingCompressionTasks(
2641       GlobalHelperThreadState::ScheduleCompressionTask::API, nullptr, lock);
2642 
2643   // Wait until all tasks have started compression.
2644   while (!HelperThreadState().compressionWorklist(lock).empty()) {
2645     HelperThreadState().wait(lock);
2646   }
2647 
2648   // Wait for all in-process compression tasks to complete.
2649   HelperThreadState().waitForAllTasksLocked(lock);
2650 
2651   AttachFinishedCompressions(runtime, lock);
2652 }
2653 
executeAndResolveAndDestroy(JSContext * cx)2654 void PromiseHelperTask::executeAndResolveAndDestroy(JSContext* cx) {
2655   execute();
2656   run(cx, JS::Dispatchable::NotShuttingDown);
2657 }
2658 
runHelperThreadTask(AutoLockHelperThreadState & lock)2659 void PromiseHelperTask::runHelperThreadTask(AutoLockHelperThreadState& lock) {
2660   {
2661     AutoUnlockHelperThreadState unlock(lock);
2662     execute();
2663   }
2664 
2665   // Don't release the lock between dispatching the resolve and destroy
2666   // operation (which may start immediately on another thread) and returning
2667   // from this method.
2668 
2669   dispatchResolveAndDestroy(lock);
2670 }
2671 
StartOffThreadPromiseHelperTask(JSContext * cx,UniquePtr<PromiseHelperTask> task)2672 bool js::StartOffThreadPromiseHelperTask(JSContext* cx,
2673                                          UniquePtr<PromiseHelperTask> task) {
2674   // Execute synchronously if there are no helper threads.
2675   if (!CanUseExtraThreads()) {
2676     task.release()->executeAndResolveAndDestroy(cx);
2677     return true;
2678   }
2679 
2680   if (!HelperThreadState().submitTask(task.get())) {
2681     ReportOutOfMemory(cx);
2682     return false;
2683   }
2684 
2685   (void)task.release();
2686   return true;
2687 }
2688 
StartOffThreadPromiseHelperTask(PromiseHelperTask * task)2689 bool js::StartOffThreadPromiseHelperTask(PromiseHelperTask* task) {
2690   MOZ_ASSERT(CanUseExtraThreads());
2691 
2692   return HelperThreadState().submitTask(task);
2693 }
2694 
submitTask(PromiseHelperTask * task)2695 bool GlobalHelperThreadState::submitTask(PromiseHelperTask* task) {
2696   AutoLockHelperThreadState lock;
2697 
2698   if (!promiseHelperTasks(lock).append(task)) {
2699     return false;
2700   }
2701 
2702   dispatch(lock);
2703   return true;
2704 }
2705 
trace(JSTracer * trc)2706 void GlobalHelperThreadState::trace(JSTracer* trc) {
2707   AutoLockHelperThreadState lock;
2708 
2709 #ifdef DEBUG
2710   // Since we hold the helper thread lock here we must disable GCMarker's
2711   // checking of the atom marking bitmap since that also relies on taking the
2712   // lock.
2713   GCMarker* marker = nullptr;
2714   if (trc->isMarkingTracer()) {
2715     marker = GCMarker::fromTracer(trc);
2716     marker->setCheckAtomMarking(false);
2717   }
2718   auto reenableAtomMarkingCheck = mozilla::MakeScopeExit([marker] {
2719     if (marker) {
2720       marker->setCheckAtomMarking(true);
2721     }
2722   });
2723 #endif
2724 
2725   for (auto task : ionWorklist(lock)) {
2726     task->alloc().lifoAlloc()->setReadWrite();
2727     task->trace(trc);
2728     task->alloc().lifoAlloc()->setReadOnly();
2729   }
2730   for (auto task : ionFinishedList(lock)) {
2731     task->trace(trc);
2732   }
2733 
2734   for (auto* helper : HelperThreadState().helperTasks(lock)) {
2735     if (helper->is<jit::IonCompileTask>()) {
2736       helper->as<jit::IonCompileTask>()->trace(trc);
2737     }
2738   }
2739 
2740   JSRuntime* rt = trc->runtime();
2741   if (auto* jitRuntime = rt->jitRuntime()) {
2742     jit::IonCompileTask* task = jitRuntime->ionLazyLinkList(rt).getFirst();
2743     while (task) {
2744       task->trace(trc);
2745       task = task->getNext();
2746     }
2747   }
2748 
2749   for (auto& parseTask : parseWorklist_) {
2750     parseTask->trace(trc);
2751   }
2752   for (auto parseTask : parseFinishedList_) {
2753     parseTask->trace(trc);
2754   }
2755   for (auto& parseTask : parseWaitingOnGC_) {
2756     parseTask->trace(trc);
2757   }
2758 }
2759 
2760 // Definition of helper thread tasks.
2761 //
2762 // Priority is determined by the order they're listed here.
2763 const GlobalHelperThreadState::Selector GlobalHelperThreadState::selectors[] = {
2764     &GlobalHelperThreadState::maybeGetGCParallelTask,
2765     &GlobalHelperThreadState::maybeGetIonCompileTask,
2766     &GlobalHelperThreadState::maybeGetWasmTier1CompileTask,
2767     &GlobalHelperThreadState::maybeGetPromiseHelperTask,
2768     &GlobalHelperThreadState::maybeGetParseTask,
2769     &GlobalHelperThreadState::maybeGetCompressionTask,
2770     &GlobalHelperThreadState::maybeGetIonFreeTask,
2771     &GlobalHelperThreadState::maybeGetWasmTier2CompileTask,
2772     &GlobalHelperThreadState::maybeGetWasmTier2GeneratorTask};
2773 
canStartTasks(const AutoLockHelperThreadState & lock)2774 bool GlobalHelperThreadState::canStartTasks(
2775     const AutoLockHelperThreadState& lock) {
2776   return canStartGCParallelTask(lock) || canStartIonCompileTask(lock) ||
2777          canStartWasmTier1CompileTask(lock) ||
2778          canStartPromiseHelperTask(lock) || canStartParseTask(lock) ||
2779          canStartCompressionTask(lock) || canStartIonFreeTask(lock) ||
2780          canStartWasmTier2CompileTask(lock) ||
2781          canStartWasmTier2GeneratorTask(lock);
2782 }
2783 
RunHelperThreadTask()2784 void JS::RunHelperThreadTask() {
2785   MOZ_ASSERT(CanUseExtraThreads());
2786 
2787   AutoLockHelperThreadState lock;
2788 
2789   if (!gHelperThreadState || HelperThreadState().isTerminating(lock)) {
2790     return;
2791   }
2792 
2793   HelperThreadState().runOneTask(lock);
2794 }
2795 
runOneTask(AutoLockHelperThreadState & lock)2796 void GlobalHelperThreadState::runOneTask(AutoLockHelperThreadState& lock) {
2797   MOZ_ASSERT(tasksPending_ > 0);
2798   tasksPending_--;
2799 
2800   // The selectors may depend on the HelperThreadState not changing between task
2801   // selection and task execution, in particular, on new tasks not being added
2802   // (because of the lifo structure of the work lists). Unlocking the
2803   // HelperThreadState between task selection and execution is not well-defined.
2804   HelperThreadTask* task = findHighestPriorityTask(lock);
2805   if (task) {
2806     runTaskLocked(task, lock);
2807     dispatch(lock);
2808   }
2809 
2810   notifyAll(lock);
2811 }
2812 
findHighestPriorityTask(const AutoLockHelperThreadState & locked)2813 HelperThreadTask* GlobalHelperThreadState::findHighestPriorityTask(
2814     const AutoLockHelperThreadState& locked) {
2815   // Return the highest priority task that is ready to start, or nullptr.
2816 
2817   for (const auto& selector : selectors) {
2818     if (auto* task = (this->*(selector))(locked)) {
2819       return task;
2820     }
2821   }
2822 
2823   return nullptr;
2824 }
2825 
runTaskLocked(HelperThreadTask * task,AutoLockHelperThreadState & locked)2826 void GlobalHelperThreadState::runTaskLocked(HelperThreadTask* task,
2827                                             AutoLockHelperThreadState& locked) {
2828   JS::AutoSuppressGCAnalysis nogc;
2829 
2830   HelperThreadState().helperTasks(locked).infallibleEmplaceBack(task);
2831 
2832   ThreadType threadType = task->threadType();
2833   js::oom::SetThreadType(threadType);
2834   runningTaskCount[threadType]++;
2835   totalCountRunningTasks++;
2836 
2837   task->runHelperThreadTask(locked);
2838 
2839   // Delete task from helperTasks.
2840   HelperThreadState().helperTasks(locked).eraseIfEqual(task);
2841 
2842   totalCountRunningTasks--;
2843   runningTaskCount[threadType]--;
2844 
2845   js::oom::SetThreadType(js::THREAD_TYPE_NONE);
2846 }
2847