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