Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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/Maybe.h"
10 : #include "mozilla/ScopeExit.h"
11 : #include "mozilla/Unused.h"
12 :
13 : #include "builtin/Promise.h"
14 : #include "frontend/BytecodeCompiler.h"
15 : #include "gc/GCInternals.h"
16 : #include "jit/IonBuilder.h"
17 : #include "js/Utility.h"
18 : #include "threading/CpuCount.h"
19 : #include "util/NativeStack.h"
20 : #include "vm/Debugger.h"
21 : #include "vm/ErrorReporting.h"
22 : #include "vm/SharedImmutableStringsCache.h"
23 : #include "vm/Time.h"
24 : #include "vm/TraceLogging.h"
25 : #include "vm/Xdr.h"
26 : #include "wasm/WasmGenerator.h"
27 :
28 : #include "gc/PrivateIterators-inl.h"
29 : #include "vm/JSContext-inl.h"
30 : #include "vm/JSObject-inl.h"
31 : #include "vm/JSScript-inl.h"
32 : #include "vm/NativeObject-inl.h"
33 : #include "vm/Realm-inl.h"
34 :
35 : using namespace js;
36 :
37 : using mozilla::Maybe;
38 : using mozilla::Unused;
39 : using mozilla::TimeDuration;
40 : using mozilla::TimeStamp;
41 :
42 : namespace js {
43 :
44 : GlobalHelperThreadState* gHelperThreadState = nullptr;
45 :
46 : } // namespace js
47 :
48 : bool
49 0 : js::CreateHelperThreadsState()
50 : {
51 0 : MOZ_ASSERT(!gHelperThreadState);
52 0 : gHelperThreadState = js_new<GlobalHelperThreadState>();
53 0 : return gHelperThreadState != nullptr;
54 : }
55 :
56 : void
57 0 : js::DestroyHelperThreadsState()
58 : {
59 0 : MOZ_ASSERT(gHelperThreadState);
60 0 : gHelperThreadState->finish();
61 0 : js_delete(gHelperThreadState);
62 0 : gHelperThreadState = nullptr;
63 0 : }
64 :
65 : bool
66 0 : js::EnsureHelperThreadsInitialized()
67 : {
68 0 : MOZ_ASSERT(gHelperThreadState);
69 0 : return gHelperThreadState->ensureInitialized();
70 : }
71 :
72 : static size_t
73 : ClampDefaultCPUCount(size_t cpuCount)
74 : {
75 : // It's extremely rare for SpiderMonkey to have more than a few cores worth
76 : // of work. At higher core counts, performance can even decrease due to NUMA
77 : // (and SpiderMonkey's lack of NUMA-awareness), contention, and general lack
78 : // of optimization for high core counts. So to avoid wasting thread stack
79 : // resources (and cluttering gdb and core dumps), clamp to 8 cores for now.
80 0 : return Min<size_t>(cpuCount, 8);
81 : }
82 :
83 : static size_t
84 : ThreadCountForCPUCount(size_t cpuCount)
85 : {
86 : // We need at least two threads for tier-2 wasm compilations, because
87 : // there's a master task that holds a thread while other threads do the
88 : // compilation.
89 0 : return Max<size_t>(cpuCount, 2);
90 : }
91 :
92 : void
93 0 : js::SetFakeCPUCount(size_t count)
94 : {
95 : // This must be called before the threads have been initialized.
96 0 : MOZ_ASSERT(!HelperThreadState().threads);
97 :
98 0 : HelperThreadState().cpuCount = count;
99 0 : HelperThreadState().threadCount = ThreadCountForCPUCount(count);
100 0 : }
101 :
102 : void
103 0 : JS::SetProfilingThreadCallbacks(JS::RegisterThreadCallback registerThread,
104 : JS::UnregisterThreadCallback unregisterThread)
105 : {
106 2 : HelperThreadState().registerThread = registerThread;
107 0 : HelperThreadState().unregisterThread = unregisterThread;
108 1 : }
109 :
110 : bool
111 0 : js::StartOffThreadWasmCompile(wasm::CompileTask* task, wasm::CompileMode mode)
112 : {
113 0 : AutoLockHelperThreadState lock;
114 :
115 0 : if (!HelperThreadState().wasmWorklist(lock, mode).pushBack(task))
116 : return false;
117 :
118 0 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
119 0 : return true;
120 : }
121 :
122 : void
123 0 : js::StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task)
124 : {
125 0 : MOZ_ASSERT(CanUseExtraThreads());
126 :
127 0 : AutoLockHelperThreadState lock;
128 :
129 0 : if (!HelperThreadState().wasmTier2GeneratorWorklist(lock).append(task.get()))
130 0 : return;
131 :
132 0 : Unused << task.release();
133 :
134 0 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
135 : }
136 :
137 : static void
138 0 : CancelOffThreadWasmTier2GeneratorLocked(AutoLockHelperThreadState& lock)
139 : {
140 0 : if (!HelperThreadState().threads)
141 : return;
142 :
143 : // Remove pending tasks from the tier2 generator worklist and cancel and
144 : // delete them.
145 : {
146 : wasm::Tier2GeneratorTaskPtrVector& worklist =
147 0 : HelperThreadState().wasmTier2GeneratorWorklist(lock);
148 0 : for (size_t i = 0; i < worklist.length(); i++) {
149 0 : wasm::Tier2GeneratorTask* task = worklist[i];
150 0 : HelperThreadState().remove(worklist, &i);
151 0 : js_delete(task);
152 : }
153 : }
154 :
155 : // There is at most one running Tier2Generator task and we assume that
156 : // below.
157 : static_assert(GlobalHelperThreadState::MaxTier2GeneratorTasks == 1,
158 : "code must be generalized");
159 :
160 : // If there is a running Tier2 generator task, shut it down in a predictable
161 : // way. The task will be deleted by the normal deletion logic.
162 0 : for (auto& helper : *HelperThreadState().threads) {
163 0 : if (helper.wasmTier2GeneratorTask()) {
164 : // Set a flag that causes compilation to shortcut itself.
165 0 : helper.wasmTier2GeneratorTask()->cancel();
166 :
167 : // Wait for the generator task to finish. This avoids a shutdown race where
168 : // the shutdown code is trying to shut down helper threads and the ongoing
169 : // tier2 compilation is trying to finish, which requires it to have access
170 : // to helper threads.
171 0 : uint32_t oldFinishedCount = HelperThreadState().wasmTier2GeneratorsFinished(lock);
172 0 : while (HelperThreadState().wasmTier2GeneratorsFinished(lock) == oldFinishedCount)
173 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
174 :
175 : // At most one of these tasks.
176 : break;
177 : }
178 : }
179 : }
180 :
181 : void
182 0 : js::CancelOffThreadWasmTier2Generator()
183 : {
184 0 : AutoLockHelperThreadState lock;
185 0 : CancelOffThreadWasmTier2GeneratorLocked(lock);
186 0 : }
187 :
188 : bool
189 50 : js::StartOffThreadIonCompile(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
190 : {
191 0 : if (!HelperThreadState().ionWorklist(lock).append(builder))
192 : return false;
193 :
194 100 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
195 0 : return true;
196 : }
197 :
198 : bool
199 0 : js::StartOffThreadIonFree(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
200 : {
201 49 : MOZ_ASSERT(CanUseExtraThreads());
202 :
203 98 : if (!HelperThreadState().ionFreeList(lock).append(builder))
204 : return false;
205 :
206 98 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
207 49 : return true;
208 : }
209 :
210 : /*
211 : * Move an IonBuilder for which compilation has either finished, failed, or
212 : * been cancelled into the global finished compilation list. All off thread
213 : * compilations which are started must eventually be finished.
214 : */
215 : static void
216 50 : FinishOffThreadIonCompile(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
217 : {
218 100 : AutoEnterOOMUnsafeRegion oomUnsafe;
219 100 : if (!HelperThreadState().ionFinishedList(lock).append(builder))
220 0 : oomUnsafe.crash("FinishOffThreadIonCompile");
221 0 : builder->script()->runtimeFromAnyThread()->jitRuntime()->numFinishedBuildersRef(lock)++;
222 0 : }
223 :
224 : static JSRuntime*
225 : GetSelectorRuntime(const CompilationSelector& selector)
226 : {
227 : struct Matcher
228 : {
229 380 : JSRuntime* match(JSScript* script) { return script->runtimeFromMainThread(); }
230 0 : JSRuntime* match(Realm* realm) { return realm->runtimeFromMainThread(); }
231 0 : JSRuntime* match(Zone* zone) { return zone->runtimeFromMainThread(); }
232 : JSRuntime* match(ZonesInState zbs) { return zbs.runtime; }
233 : JSRuntime* match(JSRuntime* runtime) { return runtime; }
234 : JSRuntime* match(AllCompilations all) { return nullptr; }
235 : JSRuntime* match(CompilationsUsingNursery cun) { return cun.runtime; }
236 : };
237 :
238 0 : return selector.match(Matcher());
239 : }
240 :
241 : static bool
242 : JitDataStructuresExist(const CompilationSelector& selector)
243 : {
244 : struct Matcher
245 : {
246 760 : bool match(JSScript* script) { return !!script->realm()->jitRealm(); }
247 0 : bool match(Realm* realm) { return !!realm->jitRealm(); }
248 0 : bool match(Zone* zone) { return !!zone->jitZone(); }
249 0 : bool match(ZonesInState zbs) { return zbs.runtime->hasJitRuntime(); }
250 0 : bool match(JSRuntime* runtime) { return runtime->hasJitRuntime(); }
251 : bool match(AllCompilations all) { return true; }
252 6 : bool match(CompilationsUsingNursery cun) { return cun.runtime->hasJitRuntime(); }
253 : };
254 :
255 766 : return selector.match(Matcher());
256 : }
257 :
258 : static bool
259 : IonBuilderMatches(const CompilationSelector& selector, jit::IonBuilder* builder)
260 : {
261 : struct BuilderMatches
262 : {
263 : jit::IonBuilder* builder_;
264 :
265 442 : bool match(JSScript* script) { return script == builder_->script(); }
266 0 : bool match(Realm* realm) { return realm == builder_->script()->realm(); }
267 0 : bool match(Zone* zone) { return zone == builder_->script()->zone(); }
268 0 : bool match(JSRuntime* runtime) { return runtime == builder_->script()->runtimeFromAnyThread(); }
269 : bool match(AllCompilations all) { return true; }
270 0 : bool match(ZonesInState zbs) {
271 0 : return zbs.runtime == builder_->script()->runtimeFromAnyThread() &&
272 0 : zbs.state == builder_->script()->zoneFromAnyThread()->gcState();
273 : }
274 : bool match(CompilationsUsingNursery cun) {
275 6 : return cun.runtime == builder_->script()->runtimeFromAnyThread() &&
276 0 : !builder_->safeForMinorGC();
277 : }
278 : };
279 :
280 888 : return selector.match(BuilderMatches{builder});
281 : }
282 :
283 : static void
284 0 : CancelOffThreadIonCompileLocked(const CompilationSelector& selector, bool discardLazyLinkList,
285 : AutoLockHelperThreadState& lock)
286 : {
287 0 : if (!HelperThreadState().threads)
288 : return;
289 :
290 : /* Cancel any pending entries for which processing hasn't started. */
291 766 : GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
292 383 : for (size_t i = 0; i < worklist.length(); i++) {
293 0 : jit::IonBuilder* builder = worklist[i];
294 0 : if (IonBuilderMatches(selector, builder)) {
295 0 : FinishOffThreadIonCompile(builder, lock);
296 0 : HelperThreadState().remove(worklist, &i);
297 : }
298 : }
299 :
300 : /* Wait for in progress entries to finish up. */
301 : bool cancelled;
302 385 : do {
303 385 : cancelled = false;
304 0 : for (auto& helper : *HelperThreadState().threads) {
305 0 : if (helper.ionBuilder() &&
306 264 : IonBuilderMatches(selector, helper.ionBuilder()))
307 : {
308 4 : helper.ionBuilder()->cancel();
309 0 : cancelled = true;
310 : }
311 : }
312 0 : if (cancelled)
313 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
314 : } while (cancelled);
315 :
316 : /* Cancel code generation for any completed entries. */
317 766 : GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
318 385 : for (size_t i = 0; i < finished.length(); i++) {
319 2 : jit::IonBuilder* builder = finished[i];
320 2 : if (IonBuilderMatches(selector, builder)) {
321 0 : JSRuntime* rt = builder->script()->runtimeFromAnyThread();
322 0 : rt->jitRuntime()->numFinishedBuildersRef(lock)--;
323 0 : jit::FinishOffThreadBuilder(rt, builder, lock);
324 0 : HelperThreadState().remove(finished, &i);
325 : }
326 : }
327 :
328 : /* Cancel lazy linking for pending builders (attached to the ionScript). */
329 383 : if (discardLazyLinkList) {
330 766 : MOZ_ASSERT(!selector.is<AllCompilations>());
331 383 : JSRuntime* runtime = GetSelectorRuntime(selector);
332 383 : jit::IonBuilder* builder = runtime->jitRuntime()->ionLazyLinkList(runtime).getFirst();
333 693 : while (builder) {
334 620 : jit::IonBuilder* next = builder->getNext();
335 0 : if (IonBuilderMatches(selector, builder))
336 2 : jit::FinishOffThreadBuilder(runtime, builder, lock);
337 : builder = next;
338 : }
339 : }
340 : }
341 :
342 : void
343 383 : js::CancelOffThreadIonCompile(const CompilationSelector& selector, bool discardLazyLinkList)
344 : {
345 383 : if (!JitDataStructuresExist(selector))
346 0 : return;
347 :
348 0 : AutoLockHelperThreadState lock;
349 383 : CancelOffThreadIonCompileLocked(selector, discardLazyLinkList, lock);
350 : }
351 :
352 : #ifdef DEBUG
353 : bool
354 0 : js::HasOffThreadIonCompile(Realm* realm)
355 : {
356 0 : AutoLockHelperThreadState lock;
357 :
358 0 : if (!HelperThreadState().threads)
359 : return false;
360 :
361 0 : GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
362 0 : for (size_t i = 0; i < worklist.length(); i++) {
363 0 : jit::IonBuilder* builder = worklist[i];
364 0 : if (builder->script()->realm() == realm)
365 : return true;
366 : }
367 :
368 0 : for (auto& helper : *HelperThreadState().threads) {
369 0 : if (helper.ionBuilder() && helper.ionBuilder()->script()->realm() == realm)
370 : return true;
371 : }
372 :
373 0 : GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
374 0 : for (size_t i = 0; i < finished.length(); i++) {
375 0 : jit::IonBuilder* builder = finished[i];
376 0 : if (builder->script()->realm() == realm)
377 : return true;
378 : }
379 :
380 0 : JSRuntime* rt = realm->runtimeFromMainThread();
381 0 : jit::IonBuilder* builder = rt->jitRuntime()->ionLazyLinkList(rt).getFirst();
382 0 : while (builder) {
383 0 : if (builder->script()->realm() == realm)
384 : return true;
385 0 : builder = builder->getNext();
386 : }
387 :
388 : return false;
389 : }
390 : #endif
391 :
392 : static const JSClassOps parseTaskGlobalClassOps = {
393 : nullptr, nullptr, nullptr, nullptr,
394 : nullptr, nullptr, nullptr, nullptr,
395 : nullptr, nullptr,
396 : JS_GlobalObjectTraceHook
397 : };
398 :
399 : static const JSClass parseTaskGlobalClass = {
400 : "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
401 : &parseTaskGlobalClassOps
402 : };
403 :
404 0 : ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx,
405 5 : JS::OffThreadCompileCallback callback, void* callbackData)
406 : : kind(kind),
407 : options(cx),
408 : alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
409 : parseGlobal(nullptr),
410 : callback(callback), callbackData(callbackData),
411 : scripts(cx), sourceObjects(cx),
412 25 : overRecursed(false), outOfMemory(false)
413 : {
414 15 : MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
415 15 : MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
416 0 : }
417 :
418 : bool
419 0 : ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options, JSObject* global)
420 : {
421 0 : if (!this->options.copy(cx, options))
422 : return false;
423 :
424 0 : parseGlobal = global;
425 0 : return true;
426 : }
427 :
428 : void
429 0 : ParseTask::activate(JSRuntime* rt)
430 : {
431 0 : rt->setUsedByHelperThread(parseGlobal->zone());
432 1 : }
433 :
434 : bool
435 5 : ParseTask::finish(JSContext* cx)
436 : {
437 15 : for (auto& sourceObject : sourceObjects) {
438 10 : RootedScriptSourceObject sso(cx, sourceObject);
439 10 : if (!ScriptSourceObject::initFromOptions(cx, sso, options))
440 0 : return false;
441 15 : if (!sso->source()->tryCompressOffThread(cx))
442 : return false;
443 : }
444 :
445 : return true;
446 : }
447 :
448 20 : ParseTask::~ParseTask()
449 : {
450 5 : for (size_t i = 0; i < errors.length(); i++)
451 0 : js_delete(errors[i]);
452 0 : }
453 :
454 : void
455 0 : ParseTask::trace(JSTracer* trc)
456 : {
457 0 : if (parseGlobal->runtimeFromAnyThread() != trc->runtime())
458 : return;
459 :
460 0 : Zone* zone = MaybeForwarded(parseGlobal)->zoneFromAnyThread();
461 0 : if (zone->usedByHelperThread()) {
462 0 : MOZ_ASSERT(!zone->isCollecting());
463 : return;
464 : }
465 :
466 0 : TraceManuallyBarrieredEdge(trc, &parseGlobal, "ParseTask::parseGlobal");
467 0 : scripts.trace(trc);
468 0 : sourceObjects.trace(trc);
469 : }
470 :
471 : size_t
472 0 : ParseTask::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
473 : {
474 0 : return options.sizeOfExcludingThis(mallocSizeOf) +
475 0 : alloc.sizeOfExcludingThis(mallocSizeOf) +
476 0 : errors.sizeOfExcludingThis(mallocSizeOf);
477 : }
478 :
479 0 : ScriptParseTask::ScriptParseTask(JSContext* cx, const char16_t* chars, size_t length,
480 0 : JS::OffThreadCompileCallback callback, void* callbackData)
481 : : ParseTask(ParseTaskKind::Script, cx, callback, callbackData),
482 10 : data(TwoByteChars(chars, length))
483 0 : {}
484 :
485 : void
486 5 : ScriptParseTask::parse(JSContext* cx)
487 : {
488 0 : SourceBufferHolder srcBuf(data.begin().get(), data.length(), SourceBufferHolder::NoOwnership);
489 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
490 :
491 0 : ScopeKind scopeKind = options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
492 :
493 5 : JSScript* script = frontend::CompileGlobalScript(cx, alloc, scopeKind,
494 : options, srcBuf,
495 0 : /* sourceObjectOut = */ &sourceObject.get());
496 5 : if (script)
497 0 : scripts.infallibleAppend(script);
498 1 : if (sourceObject)
499 5 : sourceObjects.infallibleAppend(sourceObject);
500 5 : }
501 :
502 0 : ModuleParseTask::ModuleParseTask(JSContext* cx, const char16_t* chars, size_t length,
503 0 : JS::OffThreadCompileCallback callback, void* callbackData)
504 : : ParseTask(ParseTaskKind::Module, cx, callback, callbackData),
505 0 : data(TwoByteChars(chars, length))
506 0 : {}
507 :
508 : void
509 0 : ModuleParseTask::parse(JSContext* cx)
510 : {
511 0 : SourceBufferHolder srcBuf(data.begin().get(), data.length(), SourceBufferHolder::NoOwnership);
512 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
513 :
514 0 : ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject.get());
515 0 : if (module) {
516 0 : scripts.infallibleAppend(module->script());
517 0 : if (sourceObject)
518 0 : sourceObjects.infallibleAppend(sourceObject);
519 : }
520 0 : }
521 :
522 0 : ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, const JS::TranscodeRange& range,
523 0 : JS::OffThreadCompileCallback callback, void* callbackData)
524 : : ParseTask(ParseTaskKind::ScriptDecode, cx, callback, callbackData),
525 0 : range(range)
526 0 : {}
527 :
528 : void
529 0 : ScriptDecodeTask::parse(JSContext* cx)
530 : {
531 0 : RootedScript resultScript(cx);
532 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
533 :
534 0 : XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject.get(),
535 0 : range);
536 0 : XDRResult res = decoder.codeScript(&resultScript);
537 0 : MOZ_ASSERT(bool(resultScript) == res.isOk());
538 0 : if (res.isOk()) {
539 0 : scripts.infallibleAppend(resultScript);
540 0 : if (sourceObject)
541 0 : sourceObjects.infallibleAppend(sourceObject);
542 : }
543 0 : }
544 :
545 : #if defined(JS_BUILD_BINAST)
546 :
547 0 : BinASTDecodeTask::BinASTDecodeTask(JSContext* cx, const uint8_t* buf, size_t length,
548 0 : JS::OffThreadCompileCallback callback, void* callbackData)
549 : : ParseTask(ParseTaskKind::BinAST, cx, callback, callbackData),
550 0 : data(buf, length)
551 0 : {}
552 :
553 : void
554 0 : BinASTDecodeTask::parse(JSContext* cx)
555 : {
556 0 : RootedScriptSourceObject sourceObject(cx);
557 :
558 0 : JSScript* script = frontend::CompileGlobalBinASTScript(cx, alloc, options,
559 0 : data.begin().get(), data.length(),
560 0 : &sourceObject.get());
561 0 : if (script) {
562 0 : scripts.infallibleAppend(script);
563 0 : if (sourceObject)
564 0 : sourceObjects.infallibleAppend(sourceObject);
565 : }
566 0 : }
567 :
568 : #endif /* JS_BUILD_BINAST */
569 :
570 0 : MultiScriptsDecodeTask::MultiScriptsDecodeTask(JSContext* cx, JS::TranscodeSources& sources,
571 : JS::OffThreadCompileCallback callback,
572 0 : void* callbackData)
573 : : ParseTask(ParseTaskKind::MultiScriptsDecode, cx, callback, callbackData),
574 0 : sources(&sources)
575 0 : {}
576 :
577 : void
578 0 : MultiScriptsDecodeTask::parse(JSContext* cx)
579 : {
580 0 : if (!scripts.reserve(sources->length()) ||
581 0 : !sourceObjects.reserve(sources->length()))
582 : {
583 : return;
584 : }
585 :
586 0 : for (auto& source : *sources) {
587 0 : CompileOptions opts(cx, options);
588 0 : opts.setFileAndLine(source.filename, source.lineno);
589 :
590 0 : RootedScript resultScript(cx);
591 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
592 :
593 0 : XDROffThreadDecoder decoder(cx, alloc, &opts, &sourceObject.get(), source.range);
594 0 : XDRResult res = decoder.codeScript(&resultScript);
595 0 : MOZ_ASSERT(bool(resultScript) == res.isOk());
596 :
597 0 : if (res.isErr())
598 : break;
599 0 : MOZ_ASSERT(resultScript);
600 0 : scripts.infallibleAppend(resultScript);
601 0 : sourceObjects.infallibleAppend(sourceObject);
602 : }
603 : }
604 :
605 : void
606 0 : js::CancelOffThreadParses(JSRuntime* rt)
607 : {
608 0 : AutoLockHelperThreadState lock;
609 :
610 0 : if (!HelperThreadState().threads)
611 0 : return;
612 :
613 : #ifdef DEBUG
614 : GlobalHelperThreadState::ParseTaskVector& waitingOnGC =
615 0 : HelperThreadState().parseWaitingOnGC(lock);
616 0 : for (size_t i = 0; i < waitingOnGC.length(); i++)
617 0 : MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));
618 : #endif
619 :
620 : // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
621 : // and in progress ones to complete. Otherwise the final GC may not collect
622 : // everything due to zones being used off thread.
623 : while (true) {
624 0 : bool pending = false;
625 0 : GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist(lock);
626 0 : for (size_t i = 0; i < worklist.length(); i++) {
627 0 : ParseTask* task = worklist[i];
628 0 : if (task->runtimeMatches(rt))
629 0 : pending = true;
630 : }
631 0 : if (!pending) {
632 0 : bool inProgress = false;
633 0 : for (auto& thread : *HelperThreadState().threads) {
634 0 : ParseTask* task = thread.parseTask();
635 0 : if (task && task->runtimeMatches(rt))
636 0 : inProgress = true;
637 : }
638 0 : if (!inProgress)
639 : break;
640 : }
641 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
642 : }
643 :
644 : // Clean up any parse tasks which haven't been finished by the main thread.
645 0 : auto& finished = HelperThreadState().parseFinishedList(lock);
646 : while (true) {
647 0 : bool found = false;
648 : ParseTask* next;
649 : ParseTask* task = finished.getFirst();
650 0 : while (task) {
651 0 : next = task->getNext();
652 0 : if (task->runtimeMatches(rt)) {
653 0 : found = true;
654 0 : task->remove();
655 0 : HelperThreadState().destroyParseTask(rt, task);
656 : }
657 : task = next;
658 : }
659 0 : if (!found)
660 : break;
661 : }
662 :
663 : #ifdef DEBUG
664 0 : GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist(lock);
665 0 : for (size_t i = 0; i < worklist.length(); i++) {
666 0 : ParseTask* task = worklist[i];
667 0 : MOZ_ASSERT(!task->runtimeMatches(rt));
668 : }
669 : #endif
670 : }
671 :
672 : bool
673 10 : js::OffThreadParsingMustWaitForGC(JSRuntime* rt)
674 : {
675 : // Off thread parsing can't occur during incremental collections on the
676 : // atoms zone, to avoid triggering barriers. (Outside the atoms zone, the
677 : // compilation will use a new zone that is never collected.) If an
678 : // atoms-zone GC is in progress, hold off on executing the parse task until
679 : // the atoms-zone GC completes (see EnqueuePendingParseTasksAfterGC).
680 15 : return rt->activeGCInAtomsZone();
681 : }
682 :
683 : static bool
684 15 : EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
685 : {
686 15 : if (!GlobalObject::ensureConstructor(cx, global, key))
687 : return false;
688 :
689 0 : MOZ_ASSERT(global->getPrototype(key).toObject().isDelegate(),
690 : "standard class prototype wasn't a delegate from birth");
691 : return true;
692 : }
693 :
694 : // Initialize all classes potentially created during parsing for use in parser
695 : // data structures, template objects, &c.
696 : static bool
697 5 : EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind)
698 : {
699 0 : Handle<GlobalObject*> global = cx->global();
700 :
701 5 : if (!EnsureConstructor(cx, global, JSProto_Function))
702 : return false; // needed by functions, also adds object literals' proto
703 :
704 5 : if (!EnsureConstructor(cx, global, JSProto_Array))
705 : return false; // needed by array literals
706 :
707 5 : if (!EnsureConstructor(cx, global, JSProto_RegExp))
708 : return false; // needed by regular expression literals
709 :
710 5 : if (!GlobalObject::initGenerators(cx, global))
711 : return false; // needed by function*() {}
712 :
713 5 : if (kind == ParseTaskKind::Module && !GlobalObject::ensureModulePrototypesCreated(cx, global))
714 : return false;
715 :
716 5 : return true;
717 : }
718 :
719 : class MOZ_RAII AutoSetCreatedForHelperThread
720 : {
721 : Zone* zone;
722 :
723 : public:
724 : explicit AutoSetCreatedForHelperThread(JSObject* global)
725 10 : : zone(global->zone())
726 : {
727 0 : zone->setCreatedForHelperThread();
728 : }
729 :
730 : void forget() {
731 5 : zone = nullptr;
732 : }
733 :
734 : ~AutoSetCreatedForHelperThread() {
735 0 : if (zone)
736 0 : zone->clearUsedByHelperThread();
737 : }
738 : };
739 :
740 : static JSObject*
741 5 : CreateGlobalForOffThreadParse(JSContext* cx, const gc::AutoSuppressGC& nogc)
742 : {
743 0 : JS::Realm* currentRealm = cx->realm();
744 :
745 : JS::RealmOptions realmOptions(currentRealm->creationOptions(),
746 10 : currentRealm->behaviors());
747 :
748 5 : auto& creationOptions = realmOptions.creationOptions();
749 :
750 0 : creationOptions.setInvisibleToDebugger(true)
751 0 : .setMergeable(true)
752 5 : .setNewCompartmentAndZone();
753 :
754 : // Don't falsely inherit the host's global trace hook.
755 5 : creationOptions.setTrace(nullptr);
756 :
757 5 : JSObject* obj = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
758 0 : JS::DontFireOnNewGlobalHook, realmOptions);
759 5 : if (!obj)
760 : return nullptr;
761 :
762 0 : Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
763 :
764 0 : JS::SetRealmPrincipals(global->realm(), currentRealm->principals());
765 :
766 0 : return global;
767 : }
768 :
769 : static bool
770 0 : QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
771 : {
772 0 : AutoLockHelperThreadState lock;
773 :
774 10 : bool mustWait = OffThreadParsingMustWaitForGC(cx->runtime());
775 :
776 0 : auto& queue = mustWait ? HelperThreadState().parseWaitingOnGC(lock)
777 0 : : HelperThreadState().parseWorklist(lock);
778 5 : if (!queue.append(task)) {
779 0 : ReportOutOfMemory(cx);
780 0 : return false;
781 : }
782 :
783 5 : if (!mustWait) {
784 0 : task->activate(cx->runtime());
785 5 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
786 : }
787 :
788 : return true;
789 : }
790 :
791 : bool
792 0 : StartOffThreadParseTask(JSContext* cx, ParseTask* task, const ReadOnlyCompileOptions& options)
793 : {
794 : // Suppress GC so that calls below do not trigger a new incremental GC
795 : // which could require barriers on the atoms zone.
796 10 : gc::AutoSuppressGC nogc(cx);
797 10 : gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx);
798 10 : AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
799 :
800 0 : JSObject* global = CreateGlobalForOffThreadParse(cx, nogc);
801 5 : if (!global)
802 : return false;
803 :
804 : // Mark the global's zone as created for a helper thread. This prevents it
805 : // from being collected until clearUsedByHelperThread() is called after
806 : // parsing is complete. If this function exits due to error this state is
807 : // cleared automatically.
808 0 : AutoSetCreatedForHelperThread createdForHelper(global);
809 :
810 5 : if (!task->init(cx, options, global))
811 : return false;
812 :
813 0 : if (!QueueOffThreadParseTask(cx, task))
814 : return false;
815 :
816 5 : createdForHelper.forget();
817 0 : return true;
818 : }
819 :
820 : bool
821 5 : js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
822 : const char16_t* chars, size_t length,
823 : JS::OffThreadCompileCallback callback, void* callbackData)
824 : {
825 15 : ScopedJSDeletePtr<ParseTask> task;
826 10 : task = cx->new_<ScriptParseTask>(cx, chars, length, callback, callbackData);
827 0 : if (!task || !StartOffThreadParseTask(cx, task, options))
828 : return false;
829 :
830 10 : task.forget();
831 0 : return true;
832 : }
833 :
834 : bool
835 0 : js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
836 : const char16_t* chars, size_t length,
837 : JS::OffThreadCompileCallback callback, void* callbackData)
838 : {
839 0 : ScopedJSDeletePtr<ParseTask> task;
840 0 : task = cx->new_<ModuleParseTask>(cx, chars, length, callback, callbackData);
841 0 : if (!task || !StartOffThreadParseTask(cx, task, options))
842 : return false;
843 :
844 0 : task.forget();
845 0 : return true;
846 : }
847 :
848 : bool
849 0 : js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options,
850 : const JS::TranscodeRange& range,
851 : JS::OffThreadCompileCallback callback, void* callbackData)
852 : {
853 0 : ScopedJSDeletePtr<ParseTask> task;
854 0 : task = cx->new_<ScriptDecodeTask>(cx, range, callback, callbackData);
855 0 : if (!task || !StartOffThreadParseTask(cx, task, options))
856 : return false;
857 :
858 0 : task.forget();
859 0 : return true;
860 : }
861 :
862 : bool
863 0 : js::StartOffThreadDecodeMultiScripts(JSContext* cx, const ReadOnlyCompileOptions& options,
864 : JS::TranscodeSources& sources,
865 : JS::OffThreadCompileCallback callback, void* callbackData)
866 : {
867 0 : ScopedJSDeletePtr<ParseTask> task;
868 0 : task = cx->new_<MultiScriptsDecodeTask>(cx, sources, callback, callbackData);
869 0 : if (!task || !StartOffThreadParseTask(cx, task, options))
870 : return false;
871 :
872 0 : task.forget();
873 0 : return true;
874 : }
875 :
876 : #if defined(JS_BUILD_BINAST)
877 :
878 : bool
879 0 : js::StartOffThreadDecodeBinAST(JSContext* cx, const ReadOnlyCompileOptions& options,
880 : const uint8_t* buf, size_t length,
881 : JS::OffThreadCompileCallback callback, void *callbackData)
882 : {
883 0 : ScopedJSDeletePtr<ParseTask> task;
884 0 : task = cx->new_<BinASTDecodeTask>(cx, buf, length, callback, callbackData);
885 0 : if (!task || !StartOffThreadParseTask(cx, task, options))
886 : return false;
887 :
888 0 : task.forget();
889 0 : return true;
890 : }
891 :
892 : #endif /* JS_BUILD_BINAST */
893 :
894 : void
895 0 : js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
896 : {
897 0 : MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
898 :
899 0 : GlobalHelperThreadState::ParseTaskVector newTasks;
900 : {
901 0 : AutoLockHelperThreadState lock;
902 : GlobalHelperThreadState::ParseTaskVector& waiting =
903 0 : HelperThreadState().parseWaitingOnGC(lock);
904 :
905 0 : for (size_t i = 0; i < waiting.length(); i++) {
906 0 : ParseTask* task = waiting[i];
907 0 : if (task->runtimeMatches(rt)) {
908 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
909 0 : if (!newTasks.append(task))
910 0 : oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
911 0 : HelperThreadState().remove(waiting, &i);
912 : }
913 : }
914 : }
915 :
916 0 : if (newTasks.empty())
917 0 : return;
918 :
919 : // This logic should mirror the contents of the
920 : // !OffThreadParsingMustWaitForGC() branch in QueueOffThreadParseTask:
921 :
922 0 : for (size_t i = 0; i < newTasks.length(); i++)
923 0 : newTasks[i]->activate(rt);
924 :
925 0 : AutoLockHelperThreadState lock;
926 :
927 : {
928 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
929 0 : if (!HelperThreadState().parseWorklist(lock).appendAll(newTasks))
930 0 : oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
931 : }
932 :
933 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
934 : }
935 :
936 : #ifdef DEBUG
937 : bool
938 1447 : js::CurrentThreadIsParseThread()
939 : {
940 1447 : JSContext* cx = TlsContext.get();
941 4340 : return cx->helperThread() && cx->helperThread()->parseTask();
942 : }
943 : #endif
944 :
945 : static const uint32_t kDefaultHelperStackSize = 2048 * 1024;
946 : static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
947 :
948 : // TSan enforces a minimum stack size that's just slightly larger than our
949 : // default helper stack size. It does this to store blobs of TSan-specific
950 : // data on each thread's stack. Unfortunately, that means that even though
951 : // we'll actually receive a larger stack than we requested, the effective
952 : // usable space of that stack is significantly less than what we expect.
953 : // To offset TSan stealing our stack space from underneath us, double the
954 : // default.
955 : //
956 : // Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
957 : // require all the thread-specific state that TSan does.
958 : #if defined(MOZ_TSAN)
959 : static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
960 : static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
961 : #else
962 : static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
963 : static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
964 : #endif
965 :
966 : bool
967 4 : GlobalHelperThreadState::ensureInitialized()
968 : {
969 0 : MOZ_ASSERT(CanUseExtraThreads());
970 :
971 4 : MOZ_ASSERT(this == &HelperThreadState());
972 12 : AutoLockHelperThreadState lock;
973 :
974 0 : if (threads)
975 : return true;
976 :
977 0 : threads = js::MakeUnique<HelperThreadVector>();
978 0 : if (!threads || !threads->initCapacity(threadCount))
979 : return false;
980 :
981 9 : for (size_t i = 0; i < threadCount; i++) {
982 4 : threads->infallibleEmplaceBack();
983 8 : HelperThread& helper = (*threads)[i];
984 :
985 16 : helper.thread = mozilla::Some(Thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)));
986 1 : if (!helper.thread->init(HelperThread::ThreadMain, &helper))
987 : goto error;
988 :
989 : continue;
990 :
991 : error:
992 : // Ensure that we do not leave uninitialized threads in the `threads`
993 : // vector.
994 0 : threads->popBack();
995 0 : finishThreads();
996 0 : return false;
997 : }
998 :
999 : return true;
1000 : }
1001 :
1002 0 : GlobalHelperThreadState::GlobalHelperThreadState()
1003 : : cpuCount(0),
1004 : threadCount(0),
1005 : threads(nullptr),
1006 : registerThread(nullptr),
1007 : unregisterThread(nullptr),
1008 : wasmTier2GeneratorsFinished_(0),
1009 21 : helperLock(mutexid::GlobalHelperThreadState)
1010 : {
1011 0 : cpuCount = ClampDefaultCPUCount(GetCPUCount());
1012 2 : threadCount = ThreadCountForCPUCount(cpuCount);
1013 :
1014 1 : MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
1015 1 : }
1016 :
1017 : void
1018 0 : GlobalHelperThreadState::finish()
1019 : {
1020 0 : CancelOffThreadWasmTier2Generator();
1021 0 : finishThreads();
1022 :
1023 : // Make sure there are no Ion free tasks left. We check this here because,
1024 : // unlike the other tasks, we don't explicitly block on this when
1025 : // destroying a runtime.
1026 0 : AutoLockHelperThreadState lock;
1027 0 : auto& freeList = ionFreeList(lock);
1028 0 : while (!freeList.empty())
1029 0 : jit::FreeIonBuilder(freeList.popCopy());
1030 0 : }
1031 :
1032 : void
1033 0 : GlobalHelperThreadState::finishThreads()
1034 : {
1035 0 : if (!threads)
1036 : return;
1037 :
1038 0 : MOZ_ASSERT(CanUseExtraThreads());
1039 0 : for (auto& thread : *threads)
1040 0 : thread.destroy();
1041 0 : threads.reset(nullptr);
1042 : }
1043 :
1044 : void
1045 0 : GlobalHelperThreadState::lock()
1046 : {
1047 0 : helperLock.lock();
1048 0 : }
1049 :
1050 : void
1051 0 : GlobalHelperThreadState::unlock()
1052 : {
1053 0 : helperLock.unlock();
1054 0 : }
1055 :
1056 : #ifdef DEBUG
1057 : bool
1058 0 : GlobalHelperThreadState::isLockedByCurrentThread() const
1059 : {
1060 0 : return helperLock.ownedByCurrentThread();
1061 : }
1062 : #endif // DEBUG
1063 :
1064 : void
1065 0 : GlobalHelperThreadState::wait(AutoLockHelperThreadState& locked, CondVar which,
1066 : TimeDuration timeout /* = TimeDuration::Forever() */)
1067 : {
1068 1 : whichWakeup(which).wait_for(locked, timeout);
1069 0 : }
1070 :
1071 : void
1072 0 : GlobalHelperThreadState::notifyAll(CondVar which, const AutoLockHelperThreadState&)
1073 : {
1074 0 : whichWakeup(which).notify_all();
1075 0 : }
1076 :
1077 : void
1078 0 : GlobalHelperThreadState::notifyOne(CondVar which, const AutoLockHelperThreadState&)
1079 : {
1080 0 : whichWakeup(which).notify_one();
1081 0 : }
1082 :
1083 : bool
1084 0 : GlobalHelperThreadState::hasActiveThreads(const AutoLockHelperThreadState&)
1085 : {
1086 0 : if (!threads)
1087 : return false;
1088 :
1089 0 : for (auto& thread : *threads) {
1090 0 : if (!thread.idle())
1091 : return true;
1092 : }
1093 :
1094 : return false;
1095 : }
1096 :
1097 : void
1098 0 : GlobalHelperThreadState::waitForAllThreads()
1099 : {
1100 0 : AutoLockHelperThreadState lock;
1101 0 : waitForAllThreadsLocked(lock);
1102 0 : }
1103 :
1104 : void
1105 0 : GlobalHelperThreadState::waitForAllThreadsLocked(AutoLockHelperThreadState& lock)
1106 : {
1107 0 : CancelOffThreadIonCompileLocked(CompilationSelector(AllCompilations()), false, lock);
1108 0 : CancelOffThreadWasmTier2GeneratorLocked(lock);
1109 :
1110 0 : while (hasActiveThreads(lock))
1111 0 : wait(lock, CONSUMER);
1112 0 : }
1113 :
1114 : // A task can be a "master" task, ie, it will block waiting for other worker
1115 : // threads that perform work on its behalf. If so it must not take the last
1116 : // available thread; there must always be at least one worker thread able to do
1117 : // the actual work. (Or the system may deadlock.)
1118 : //
1119 : // If a task is a master task it *must* pass isMaster=true here, or perform a
1120 : // similar calculation to avoid deadlock from starvation.
1121 : //
1122 : // isMaster should only be true if the thread calling checkTaskThreadLimit() is
1123 : // a helper thread.
1124 : //
1125 : // NOTE: Calling checkTaskThreadLimit() from a helper thread in the dynamic
1126 : // region after currentTask.emplace() and before currentTask.reset() may cause
1127 : // it to return a different result than if it is called outside that dynamic
1128 : // region, as the predicate inspects the values of the threads' currentTask
1129 : // members.
1130 :
1131 : template <typename T>
1132 : bool
1133 0 : GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads, bool isMaster) const
1134 : {
1135 0 : MOZ_ASSERT(maxThreads > 0);
1136 :
1137 0 : if (!isMaster && maxThreads >= threadCount)
1138 : return true;
1139 :
1140 10 : size_t count = 0;
1141 10 : size_t idle = 0;
1142 70 : for (auto& thread : *threads) {
1143 40 : if (thread.currentTask.isSome()) {
1144 2 : if (thread.currentTask->is<T>())
1145 2 : count++;
1146 : } else {
1147 38 : idle++;
1148 : }
1149 40 : if (count >= maxThreads)
1150 : return false;
1151 : }
1152 :
1153 : // It is possible for the number of idle threads to be zero here, because
1154 : // checkTaskThreadLimit() can be called from non-helper threads. Notably,
1155 : // the compression task scheduler invokes it, and runs off a helper thread.
1156 10 : if (idle == 0)
1157 : return false;
1158 :
1159 : // A master thread that's the last available thread must not be allowed to
1160 : // run.
1161 10 : if (isMaster && idle == 1)
1162 : return false;
1163 :
1164 10 : return true;
1165 : }
1166 :
1167 : struct MOZ_RAII AutoSetContextRuntime
1168 : {
1169 : explicit AutoSetContextRuntime(JSRuntime* rt) {
1170 76 : TlsContext.get()->setRuntime(rt);
1171 : }
1172 : ~AutoSetContextRuntime() {
1173 76 : TlsContext.get()->setRuntime(nullptr);
1174 : }
1175 : };
1176 :
1177 : static inline bool
1178 : IsHelperThreadSimulatingOOM(js::ThreadType threadType)
1179 : {
1180 : #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1181 0 : return js::oom::targetThread == threadType;
1182 : #else
1183 : return false;
1184 : #endif
1185 : }
1186 :
1187 : void
1188 0 : GlobalHelperThreadState::addSizeOfIncludingThis(JS::GlobalStats* stats,
1189 : AutoLockHelperThreadState& lock) const
1190 : {
1191 0 : MOZ_ASSERT(isLockedByCurrentThread());
1192 :
1193 0 : mozilla::MallocSizeOf mallocSizeOf = stats->mallocSizeOf_;
1194 0 : JS::HelperThreadStats& htStats = stats->helperThread;
1195 :
1196 0 : htStats.stateData += mallocSizeOf(this);
1197 :
1198 0 : if (threads)
1199 0 : htStats.stateData += threads->sizeOfIncludingThis(mallocSizeOf);
1200 :
1201 : // Report memory used by various containers
1202 0 : htStats.stateData +=
1203 0 : ionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1204 0 : ionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1205 0 : ionFreeList_.sizeOfExcludingThis(mallocSizeOf) +
1206 0 : wasmWorklist_tier1_.sizeOfExcludingThis(mallocSizeOf) +
1207 0 : wasmWorklist_tier2_.sizeOfExcludingThis(mallocSizeOf) +
1208 0 : wasmTier2GeneratorWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1209 0 : promiseHelperTasks_.sizeOfExcludingThis(mallocSizeOf) +
1210 0 : parseWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1211 0 : parseFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1212 0 : parseWaitingOnGC_.sizeOfExcludingThis(mallocSizeOf) +
1213 0 : compressionPendingList_.sizeOfExcludingThis(mallocSizeOf) +
1214 0 : compressionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1215 0 : compressionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1216 0 : gcHelperWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1217 0 : gcParallelWorklist_.sizeOfExcludingThis(mallocSizeOf);
1218 :
1219 : // Report ParseTasks on wait lists
1220 0 : for (auto task : parseWorklist_)
1221 0 : htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1222 0 : for (auto task : parseFinishedList_)
1223 0 : htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1224 0 : for (auto task : parseWaitingOnGC_)
1225 0 : htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1226 :
1227 : // Report IonBuilders on wait lists
1228 0 : for (auto builder : ionWorklist_)
1229 0 : htStats.ionBuilder += builder->sizeOfExcludingThis(mallocSizeOf);
1230 0 : for (auto builder : ionFinishedList_)
1231 0 : htStats.ionBuilder += builder->sizeOfExcludingThis(mallocSizeOf);
1232 0 : for (auto builder : ionFreeList_)
1233 0 : htStats.ionBuilder += builder->sizeOfExcludingThis(mallocSizeOf);
1234 :
1235 : // Report wasm::CompileTasks on wait lists
1236 0 : for (auto task : wasmWorklist_tier1_)
1237 0 : htStats.wasmCompile += task->sizeOfIncludingThis(mallocSizeOf);
1238 0 : for (auto task : wasmWorklist_tier2_)
1239 0 : htStats.wasmCompile += task->sizeOfIncludingThis(mallocSizeOf);
1240 :
1241 : // Report number of helper threads.
1242 0 : MOZ_ASSERT(htStats.idleThreadCount == 0);
1243 0 : if (threads) {
1244 0 : for (auto& thread : *threads) {
1245 0 : if (thread.idle())
1246 0 : htStats.idleThreadCount++;
1247 : else
1248 0 : htStats.activeThreadCount++;
1249 : }
1250 : }
1251 0 : }
1252 :
1253 : size_t
1254 0 : GlobalHelperThreadState::maxIonCompilationThreads() const
1255 : {
1256 0 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_ION))
1257 : return 1;
1258 100 : return threadCount;
1259 : }
1260 :
1261 : size_t
1262 0 : GlobalHelperThreadState::maxWasmCompilationThreads() const
1263 : {
1264 0 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM))
1265 : return 1;
1266 0 : return cpuCount;
1267 : }
1268 :
1269 : size_t
1270 0 : GlobalHelperThreadState::maxWasmTier2GeneratorThreads() const
1271 : {
1272 0 : return MaxTier2GeneratorTasks;
1273 : }
1274 :
1275 : size_t
1276 0 : GlobalHelperThreadState::maxPromiseHelperThreads() const
1277 : {
1278 0 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM))
1279 : return 1;
1280 0 : return cpuCount;
1281 : }
1282 :
1283 : size_t
1284 0 : GlobalHelperThreadState::maxParseThreads() const
1285 : {
1286 10 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_PARSE))
1287 : return 1;
1288 10 : return cpuCount;
1289 : }
1290 :
1291 : size_t
1292 0 : GlobalHelperThreadState::maxCompressionThreads() const
1293 : {
1294 0 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_COMPRESS))
1295 : return 1;
1296 :
1297 : // Compression is triggered on major GCs to compress ScriptSources. It is
1298 : // considered low priority work.
1299 : return 1;
1300 : }
1301 :
1302 : size_t
1303 0 : GlobalHelperThreadState::maxGCHelperThreads() const
1304 : {
1305 0 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_GCHELPER))
1306 : return 1;
1307 0 : return threadCount;
1308 : }
1309 :
1310 : size_t
1311 0 : GlobalHelperThreadState::maxGCParallelThreads() const
1312 : {
1313 42 : if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_GCPARALLEL))
1314 : return 1;
1315 0 : return threadCount;
1316 : }
1317 :
1318 : bool
1319 183 : GlobalHelperThreadState::canStartWasmTier1Compile(const AutoLockHelperThreadState& lock)
1320 : {
1321 0 : return canStartWasmCompile(lock, wasm::CompileMode::Tier1);
1322 : }
1323 :
1324 : bool
1325 129 : GlobalHelperThreadState::canStartWasmTier2Compile(const AutoLockHelperThreadState& lock)
1326 : {
1327 129 : return canStartWasmCompile(lock, wasm::CompileMode::Tier2);
1328 : }
1329 :
1330 : bool
1331 312 : GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock,
1332 : wasm::CompileMode mode)
1333 : {
1334 312 : if (wasmWorklist(lock, mode).empty())
1335 : return false;
1336 :
1337 : // Parallel compilation and background compilation should be disabled on
1338 : // unicore systems.
1339 :
1340 0 : MOZ_RELEASE_ASSERT(cpuCount > 1);
1341 :
1342 : // If Tier2 is very backlogged we must give priority to it, since the Tier2
1343 : // queue holds onto Tier1 tasks. Indeed if Tier2 is backlogged we will
1344 : // devote more resources to Tier2 and not start any Tier1 work at all.
1345 :
1346 0 : bool tier2oversubscribed = wasmTier2GeneratorWorklist(lock).length() > 20;
1347 :
1348 : // For Tier1 and Once compilation, honor the maximum allowed threads to
1349 : // compile wasm jobs at once, to avoid oversaturating the machine.
1350 : //
1351 : // For Tier2 compilation we need to allow other things to happen too, so we
1352 : // do not allow all logical cores to be used for background work; instead we
1353 : // wish to use a fraction of the physical cores. We can't directly compute
1354 : // the physical cores from the logical cores, but 1/3 of the logical cores
1355 : // is a safe estimate for the number of physical cores available for
1356 : // background work.
1357 :
1358 0 : size_t physCoresAvailable = size_t(ceil(cpuCount / 3.0));
1359 :
1360 : size_t threads;
1361 0 : if (mode == wasm::CompileMode::Tier2) {
1362 0 : if (tier2oversubscribed)
1363 : threads = maxWasmCompilationThreads();
1364 : else
1365 : threads = physCoresAvailable;
1366 : } else {
1367 0 : if (tier2oversubscribed)
1368 : threads = 0;
1369 : else
1370 : threads = maxWasmCompilationThreads();
1371 : }
1372 :
1373 0 : if (!threads || !checkTaskThreadLimit<wasm::CompileTask*>(threads))
1374 : return false;
1375 :
1376 0 : return true;
1377 : }
1378 :
1379 : bool
1380 129 : GlobalHelperThreadState::canStartWasmTier2Generator(const AutoLockHelperThreadState& lock)
1381 : {
1382 0 : return !wasmTier2GeneratorWorklist(lock).empty() &&
1383 0 : checkTaskThreadLimit<wasm::Tier2GeneratorTask*>(maxWasmTier2GeneratorThreads(),
1384 0 : /*isMaster=*/true);
1385 : }
1386 :
1387 : bool
1388 0 : GlobalHelperThreadState::canStartPromiseHelperTask(const AutoLockHelperThreadState& lock)
1389 : {
1390 : // PromiseHelperTasks can be wasm compilation tasks that in turn block on
1391 : // wasm compilation so set isMaster = true.
1392 183 : return !promiseHelperTasks(lock).empty() &&
1393 0 : checkTaskThreadLimit<PromiseHelperTask*>(maxPromiseHelperThreads(),
1394 183 : /*isMaster=*/true);
1395 : }
1396 :
1397 : static bool
1398 0 : IonBuilderHasHigherPriority(jit::IonBuilder* first, jit::IonBuilder* second)
1399 : {
1400 : // Return true if priority(first) > priority(second).
1401 : //
1402 : // This method can return whatever it wants, though it really ought to be a
1403 : // total order. The ordering is allowed to race (change on the fly), however.
1404 :
1405 : // A lower optimization level indicates a higher priority.
1406 0 : if (first->optimizationInfo().level() != second->optimizationInfo().level())
1407 0 : return first->optimizationInfo().level() < second->optimizationInfo().level();
1408 :
1409 : // A script without an IonScript has precedence on one with.
1410 0 : if (first->scriptHasIonScript() != second->scriptHasIonScript())
1411 0 : return !first->scriptHasIonScript();
1412 :
1413 : // A higher warm-up counter indicates a higher priority.
1414 0 : return first->script()->getWarmUpCount() / first->script()->length() >
1415 0 : second->script()->getWarmUpCount() / second->script()->length();
1416 : }
1417 :
1418 : bool
1419 283 : GlobalHelperThreadState::canStartIonCompile(const AutoLockHelperThreadState& lock)
1420 : {
1421 383 : return !ionWorklist(lock).empty() &&
1422 0 : checkTaskThreadLimit<jit::IonBuilder*>(maxIonCompilationThreads());
1423 : }
1424 :
1425 : bool
1426 178 : GlobalHelperThreadState::canStartIonFreeTask(const AutoLockHelperThreadState& lock)
1427 : {
1428 227 : return !ionFreeList(lock).empty();
1429 : }
1430 :
1431 : jit::IonBuilder*
1432 50 : GlobalHelperThreadState::highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock)
1433 : {
1434 0 : auto& worklist = ionWorklist(lock);
1435 0 : MOZ_ASSERT(!worklist.empty());
1436 :
1437 : // Get the highest priority IonBuilder which has not started compilation yet.
1438 : size_t index = 0;
1439 50 : for (size_t i = 1; i < worklist.length(); i++) {
1440 0 : if (IonBuilderHasHigherPriority(worklist[i], worklist[index]))
1441 0 : index = i;
1442 : }
1443 :
1444 50 : jit::IonBuilder* builder = worklist[index];
1445 50 : worklist.erase(&worklist[index]);
1446 0 : return builder;
1447 : }
1448 :
1449 : bool
1450 188 : GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
1451 : {
1452 : // Parse tasks that end up compiling asm.js in turn may use Wasm compilation
1453 : // threads to generate machine code. We have no way (at present) to know
1454 : // ahead of time whether a parse task is going to parse asm.js content or
1455 : // not, so we just assume that all parse tasks are master tasks.
1456 198 : return !parseWorklist(lock).empty() &&
1457 198 : checkTaskThreadLimit<ParseTask*>(maxParseThreads(), /*isMaster=*/true);
1458 : }
1459 :
1460 : bool
1461 0 : GlobalHelperThreadState::canStartCompressionTask(const AutoLockHelperThreadState& lock)
1462 : {
1463 0 : return !compressionWorklist(lock).empty() &&
1464 178 : checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
1465 : }
1466 :
1467 : void
1468 0 : GlobalHelperThreadState::startHandlingCompressionTasks(const AutoLockHelperThreadState& lock)
1469 : {
1470 0 : scheduleCompressionTasks(lock);
1471 0 : if (canStartCompressionTask(lock))
1472 : notifyOne(PRODUCER, lock);
1473 0 : }
1474 :
1475 : void
1476 0 : GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadState& lock)
1477 : {
1478 0 : auto& pending = compressionPendingList(lock);
1479 0 : auto& worklist = compressionWorklist(lock);
1480 :
1481 0 : for (size_t i = 0; i < pending.length(); i++) {
1482 0 : if (pending[i]->shouldStart()) {
1483 : // OOMing during appending results in the task not being scheduled
1484 : // and deleted.
1485 0 : Unused << worklist.append(std::move(pending[i]));
1486 0 : remove(pending, &i);
1487 : }
1488 : }
1489 0 : }
1490 :
1491 : bool
1492 0 : GlobalHelperThreadState::canStartGCHelperTask(const AutoLockHelperThreadState& lock)
1493 : {
1494 233 : return !gcHelperWorklist(lock).empty() &&
1495 0 : checkTaskThreadLimit<GCHelperState*>(maxGCHelperThreads());
1496 : }
1497 :
1498 : bool
1499 275 : GlobalHelperThreadState::canStartGCParallelTask(const AutoLockHelperThreadState& lock)
1500 : {
1501 0 : return !gcParallelWorklist(lock).empty() &&
1502 0 : checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());
1503 : }
1504 :
1505 0 : js::GCParallelTask::~GCParallelTask()
1506 : {
1507 : // Only most-derived classes' destructors may do the join: base class
1508 : // destructors run after those for derived classes' members, so a join in a
1509 : // base class can't ensure that the task is done using the members. All we
1510 : // can do now is check that someone has previously stopped the task.
1511 0 : assertNotStarted();
1512 0 : }
1513 :
1514 : bool
1515 0 : js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock)
1516 : {
1517 0 : assertNotStarted();
1518 :
1519 : // If we do the shutdown GC before running anything, we may never
1520 : // have initialized the helper threads. Just use the serial path
1521 : // since we cannot safely intialize them at this point.
1522 42 : if (!HelperThreadState().threads)
1523 : return false;
1524 :
1525 0 : if (!HelperThreadState().gcParallelWorklist(lock).append(this))
1526 : return false;
1527 0 : setDispatched(lock);
1528 :
1529 42 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
1530 :
1531 21 : return true;
1532 : }
1533 :
1534 : bool
1535 0 : js::GCParallelTask::start()
1536 : {
1537 0 : AutoLockHelperThreadState helperLock;
1538 0 : return startWithLockHeld(helperLock);
1539 : }
1540 :
1541 : void
1542 21 : js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& lock)
1543 : {
1544 42 : if (isNotStarted(lock))
1545 : return;
1546 :
1547 0 : while (!isFinished(lock))
1548 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
1549 :
1550 19 : setNotStarted(lock);
1551 19 : cancel_ = false;
1552 : }
1553 :
1554 : void
1555 0 : js::GCParallelTask::join()
1556 : {
1557 0 : AutoLockHelperThreadState helperLock;
1558 0 : joinWithLockHeld(helperLock);
1559 0 : }
1560 :
1561 : static inline
1562 : TimeDuration
1563 21 : TimeSince(TimeStamp prev)
1564 : {
1565 21 : TimeStamp now = TimeStamp::Now();
1566 : // Sadly this happens sometimes.
1567 0 : MOZ_ASSERT(now >= prev);
1568 0 : if (now < prev)
1569 0 : now = prev;
1570 0 : return now - prev;
1571 : }
1572 :
1573 : void
1574 0 : js::GCParallelTask::runFromMainThread(JSRuntime* rt)
1575 : {
1576 0 : assertNotStarted();
1577 0 : MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt));
1578 0 : TimeStamp timeStart = TimeStamp::Now();
1579 0 : runTask();
1580 0 : duration_ = TimeSince(timeStart);
1581 0 : }
1582 :
1583 : void
1584 0 : js::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState& lock)
1585 : {
1586 0 : MOZ_ASSERT(isDispatched(lock));
1587 :
1588 42 : AutoSetContextRuntime ascr(runtime());
1589 42 : gc::AutoSetThreadIsPerformingGC performingGC;
1590 :
1591 : {
1592 0 : AutoUnlockHelperThreadState parallelSection(lock);
1593 21 : TimeStamp timeStart = TimeStamp::Now();
1594 63 : runTask();
1595 0 : duration_ = TimeSince(timeStart);
1596 63 : }
1597 0 :
1598 : setFinished(lock);
1599 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock);
1600 21 : }
1601 42 :
1602 0 : bool
1603 : js::GCParallelTask::isRunning() const
1604 : {
1605 0 : AutoLockHelperThreadState lock;
1606 : return isRunningWithLockHeld(lock);
1607 0 : }
1608 0 :
1609 : void
1610 : HelperThread::handleGCParallelWorkload(AutoLockHelperThreadState& lock)
1611 : {
1612 0 : MOZ_ASSERT(HelperThreadState().canStartGCParallelTask(lock));
1613 : MOZ_ASSERT(idle());
1614 21 :
1615 21 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1616 : AutoTraceLog logCompile(logger, TraceLogger_GC);
1617 21 :
1618 63 : currentTask.emplace(HelperThreadState().gcParallelWorklist(lock).popCopy());
1619 : gcParallelTask()->runFromHelperThread(lock);
1620 0 : currentTask.reset();
1621 0 : }
1622 21 :
1623 21 : static void
1624 : LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
1625 : {
1626 5 : // Mark the zone as no longer in use by a helper thread, and available
1627 : // to be collected by the GC.
1628 : rt->clearUsedByHelperThread(task->parseGlobal->zoneFromAnyThread());
1629 : }
1630 10 :
1631 0 : ParseTask*
1632 : GlobalHelperThreadState::removeFinishedParseTask(ParseTaskKind kind, JS::OffThreadToken* token)
1633 : {
1634 0 : // The token is really a ParseTask* which should be in the finished list.
1635 : // Remove its entry.
1636 : auto task = static_cast<ParseTask*>(token);
1637 : MOZ_ASSERT(task->kind == kind);
1638 5 :
1639 5 : AutoLockHelperThreadState lock;
1640 :
1641 15 : #ifdef DEBUG
1642 : auto& finished = parseFinishedList(lock);
1643 : bool found = false;
1644 10 : for (auto t : finished) {
1645 0 : if (t == task) {
1646 0 : found = true;
1647 5 : break;
1648 : }
1649 : }
1650 : MOZ_ASSERT(found);
1651 : #endif
1652 5 :
1653 : task->remove();
1654 : return task;
1655 5 : }
1656 0 :
1657 : template <typename F, typename>
1658 : bool
1659 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind,
1660 : JS::OffThreadToken* token, F&& finishCallback)
1661 0 : {
1662 : MOZ_ASSERT(cx->realm());
1663 :
1664 5 : ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
1665 :
1666 15 : // Make sure we have all the constructors we need for the prototype
1667 : // remapping below, since we can't GC while that's happening.
1668 : if (!EnsureParserCreatedClasses(cx, kind)) {
1669 : LeaveParseTaskZone(cx->runtime(), parseTask);
1670 0 : return false;
1671 0 : }
1672 0 :
1673 : mergeParseTaskRealm(cx, parseTask, cx->realm());
1674 :
1675 5 : bool ok = finishCallback(parseTask);
1676 :
1677 0 : for (auto& script : parseTask->scripts)
1678 : releaseAssertSameCompartment(cx, script);
1679 15 :
1680 5 : if (!parseTask->finish(cx) || !ok)
1681 : return false;
1682 5 :
1683 : // Report out of memory errors eagerly, or errors could be malformed.
1684 : if (parseTask->outOfMemory) {
1685 : ReportOutOfMemory(cx);
1686 1 : return false;
1687 0 : }
1688 0 :
1689 : // Report any error or warnings generated during the parse, and inform the
1690 : // debugger about the compiled scripts.
1691 : for (size_t i = 0; i < parseTask->errors.length(); i++)
1692 : parseTask->errors[i]->throwError(cx);
1693 5 : if (parseTask->overRecursed)
1694 0 : ReportOverRecursed(cx);
1695 5 : if (cx->isExceptionPending())
1696 0 : return false;
1697 0 :
1698 : return true;
1699 : }
1700 0 :
1701 : JSScript*
1702 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind,
1703 : JS::OffThreadToken* token)
1704 5 : {
1705 : JS::RootedScript script(cx);
1706 :
1707 10 : bool ok = finishParseTask(cx, kind, token, [&script] (ParseTask* parseTask) {
1708 : MOZ_RELEASE_ASSERT(parseTask->scripts.length() <= 1);
1709 20 :
1710 10 : if (parseTask->scripts.length() > 0)
1711 : script = parseTask->scripts[0];
1712 10 :
1713 15 : return true;
1714 : });
1715 5 :
1716 5 : if (!ok)
1717 : return nullptr;
1718 5 :
1719 : if (!script) {
1720 : // No error was reported, but no script produced. Assume we hit out of
1721 5 : // memory.
1722 : MOZ_ASSERT(false, "Expected script");
1723 : ReportOutOfMemory(cx);
1724 0 : return nullptr;
1725 : }
1726 :
1727 : // The Debugger only needs to be told about the topmost script that was compiled.
1728 : Debugger::onNewScript(cx, script);
1729 :
1730 0 : return script;
1731 : }
1732 0 :
1733 : bool
1734 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind,
1735 : JS::OffThreadToken* token,
1736 0 : MutableHandle<ScriptVector> scripts)
1737 : {
1738 : size_t expectedLength = 0;
1739 :
1740 0 : bool ok = finishParseTask(cx, kind, token, [&scripts, &expectedLength] (ParseTask* parseTask) {
1741 : MOZ_ASSERT(parseTask->kind == ParseTaskKind::MultiScriptsDecode);
1742 0 : auto task = static_cast<MultiScriptsDecodeTask*>(parseTask);
1743 0 :
1744 0 : expectedLength = task->sources->length();
1745 :
1746 0 : if (!scripts.reserve(task->scripts.length()))
1747 : return false;
1748 0 :
1749 : for (auto& script : task->scripts)
1750 : scripts.infallibleAppend(script);
1751 0 : return true;
1752 0 : });
1753 :
1754 0 : if (!ok)
1755 : return false;
1756 0 :
1757 : if (scripts.length() != expectedLength) {
1758 : // No error was reported, but fewer scripts produced than expected.
1759 0 : // Assume we hit out of memory.
1760 : MOZ_ASSERT(false, "Expected more scripts");
1761 : ReportOutOfMemory(cx);
1762 0 : return false;
1763 : }
1764 :
1765 : // The Debugger only needs to be told about the topmost script that was compiled.
1766 : JS::RootedScript rooted(cx);
1767 : for (auto& script : scripts) {
1768 0 : MOZ_ASSERT(script->isGlobalCode());
1769 0 :
1770 0 : rooted = script;
1771 : Debugger::onNewScript(cx, rooted);
1772 0 : }
1773 0 :
1774 : return true;
1775 : }
1776 :
1777 : JSScript*
1778 : GlobalHelperThreadState::finishScriptParseTask(JSContext* cx, JS::OffThreadToken* token)
1779 : {
1780 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::Script, token);
1781 : MOZ_ASSERT_IF(script, script->isGlobalCode());
1782 0 : return script;
1783 10 : }
1784 5 :
1785 : JSScript*
1786 : GlobalHelperThreadState::finishScriptDecodeTask(JSContext* cx, JS::OffThreadToken* token)
1787 : {
1788 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::ScriptDecode, token);
1789 : MOZ_ASSERT_IF(script, script->isGlobalCode());
1790 0 : return script;
1791 0 : }
1792 0 :
1793 : #if defined(JS_BUILD_BINAST)
1794 :
1795 : JSScript*
1796 : GlobalHelperThreadState::finishBinASTDecodeTask(JSContext* cx, JS::OffThreadToken* token)
1797 : {
1798 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::BinAST, token);
1799 : MOZ_ASSERT_IF(script, script->isGlobalCode());
1800 0 : return script;
1801 0 : }
1802 0 :
1803 : #endif /* JS_BUILD_BINAST */
1804 :
1805 : bool
1806 : GlobalHelperThreadState::finishMultiScriptsDecodeTask(JSContext* cx, JS::OffThreadToken* token,
1807 : MutableHandle<ScriptVector> scripts)
1808 0 : {
1809 : return finishParseTask(cx, ParseTaskKind::MultiScriptsDecode, token, scripts);
1810 : }
1811 0 :
1812 : JSObject*
1813 : GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, JS::OffThreadToken* token)
1814 : {
1815 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::Module, token);
1816 : if (!script)
1817 0 : return nullptr;
1818 0 :
1819 : MOZ_ASSERT(script->module());
1820 :
1821 0 : RootedModuleObject module(cx, script->module());
1822 : module->fixEnvironmentsAfterCompartmentMerge();
1823 0 : if (!ModuleObject::Freeze(cx, module))
1824 0 : return nullptr;
1825 0 :
1826 : return module;
1827 : }
1828 0 :
1829 : void
1830 : GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind,
1831 : JS::OffThreadToken* token)
1832 0 : {
1833 : destroyParseTask(rt, removeFinishedParseTask(kind, token));
1834 : }
1835 0 :
1836 0 : void
1837 : GlobalHelperThreadState::destroyParseTask(JSRuntime* rt, ParseTask* parseTask)
1838 : {
1839 0 : MOZ_ASSERT(!parseTask->isInList());
1840 : LeaveParseTaskZone(rt, parseTask);
1841 0 : js_delete(parseTask);
1842 0 : }
1843 0 :
1844 0 : void
1845 : GlobalHelperThreadState::mergeParseTaskRealm(JSContext* cx, ParseTask* parseTask, Realm* dest)
1846 : {
1847 0 : // After we call LeaveParseTaskZone() it's not safe to GC until we have
1848 : // finished merging the contents of the parse task's realm into the
1849 : // destination realm.
1850 : JS::AutoAssertNoGC nogc(cx);
1851 :
1852 10 : LeaveParseTaskZone(cx->runtime(), parseTask);
1853 :
1854 5 : // Move the parsed script and all its contents into the desired realm.
1855 : gc::MergeRealms(parseTask->parseGlobal->realm(), dest);
1856 : }
1857 10 :
1858 5 : void
1859 : HelperThread::destroy()
1860 : {
1861 0 : if (thread.isSome()) {
1862 : {
1863 0 : AutoLockHelperThreadState lock;
1864 : terminate = true;
1865 0 :
1866 0 : /* Notify all helpers, to ensure that this thread wakes up. */
1867 : HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
1868 : }
1869 0 :
1870 : thread->join();
1871 : thread.reset();
1872 0 : }
1873 0 : }
1874 :
1875 0 : void
1876 : HelperThread::ensureRegisteredWithProfiler()
1877 : {
1878 0 : if (registered)
1879 : return;
1880 0 :
1881 : JS::RegisterThreadCallback callback = HelperThreadState().registerThread;
1882 : if (callback) {
1883 8 : callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase()));
1884 0 : registered = true;
1885 4 : }
1886 0 : }
1887 :
1888 : void
1889 : HelperThread::unregisterWithProfilerIfNeeded()
1890 : {
1891 0 : if (!registered)
1892 : return;
1893 0 :
1894 : JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread;
1895 : if (callback) {
1896 0 : callback();
1897 0 : registered = false;
1898 0 : }
1899 0 : }
1900 :
1901 : /* static */
1902 : void
1903 : HelperThread::ThreadMain(void* arg)
1904 : {
1905 4 : ThisThread::SetName("JS Helper");
1906 :
1907 0 : static_cast<HelperThread*>(arg)->threadLoop();
1908 : Mutex::ShutDown();
1909 4 : }
1910 0 :
1911 0 : void
1912 : HelperThread::handleWasmTier1Workload(AutoLockHelperThreadState& locked)
1913 : {
1914 0 : handleWasmWorkload(locked, wasm::CompileMode::Tier1);
1915 : }
1916 0 :
1917 0 : void
1918 : HelperThread::handleWasmTier2Workload(AutoLockHelperThreadState& locked)
1919 : {
1920 0 : handleWasmWorkload(locked, wasm::CompileMode::Tier2);
1921 : }
1922 0 :
1923 0 : void
1924 : HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked, wasm::CompileMode mode)
1925 : {
1926 0 : MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked, mode));
1927 : MOZ_ASSERT(idle());
1928 0 :
1929 0 : currentTask.emplace(HelperThreadState().wasmWorklist(locked, mode).popCopyFront());
1930 :
1931 0 : wasm::CompileTask* task = wasmTask();
1932 : {
1933 0 : AutoUnlockHelperThreadState unlock(locked);
1934 : wasm::ExecuteCompileTaskFromHelperThread(task);
1935 0 : }
1936 0 :
1937 : currentTask.reset();
1938 :
1939 0 : // Since currentTask is only now reset(), this could be the last active thread
1940 : // waitForAllThreads() is waiting for. No one else should be waiting, though.
1941 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1942 : }
1943 0 :
1944 0 : void
1945 : HelperThread::handleWasmTier2GeneratorWorkload(AutoLockHelperThreadState& locked)
1946 : {
1947 0 : MOZ_ASSERT(HelperThreadState().canStartWasmTier2Generator(locked));
1948 : MOZ_ASSERT(idle());
1949 0 :
1950 0 : currentTask.emplace(HelperThreadState().wasmTier2GeneratorWorklist(locked).popCopy());
1951 :
1952 0 : wasm::Tier2GeneratorTask* task = wasmTier2GeneratorTask();
1953 : {
1954 0 : AutoUnlockHelperThreadState unlock(locked);
1955 : task->execute();
1956 0 : }
1957 0 :
1958 : currentTask.reset();
1959 : js_delete(task);
1960 0 :
1961 0 : // During shutdown the main thread will wait for any ongoing (cancelled)
1962 : // tier-2 generation to shut down normally. To do so, it waits on the
1963 : // CONSUMER condition for the count of finished generators to rise.
1964 : HelperThreadState().incWasmTier2GeneratorsFinished(locked);
1965 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1966 0 : }
1967 0 :
1968 0 : void
1969 : HelperThread::handlePromiseHelperTaskWorkload(AutoLockHelperThreadState& locked)
1970 : {
1971 0 : MOZ_ASSERT(HelperThreadState().canStartPromiseHelperTask(locked));
1972 : MOZ_ASSERT(idle());
1973 0 :
1974 0 : PromiseHelperTask* task = HelperThreadState().promiseHelperTasks(locked).popCopy();
1975 : currentTask.emplace(task);
1976 0 :
1977 0 : {
1978 : AutoUnlockHelperThreadState unlock(locked);
1979 : task->execute();
1980 0 : task->dispatchResolveAndDestroy();
1981 0 : }
1982 0 :
1983 : currentTask.reset();
1984 :
1985 0 : // Since currentTask is only now reset(), this could be the last active thread
1986 : // waitForAllThreads() is waiting for. No one else should be waiting, though.
1987 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1988 : }
1989 0 :
1990 0 : void
1991 : HelperThread::handleIonWorkload(AutoLockHelperThreadState& locked)
1992 : {
1993 50 : MOZ_ASSERT(HelperThreadState().canStartIonCompile(locked));
1994 : MOZ_ASSERT(idle());
1995 50 :
1996 0 : // Find the IonBuilder in the worklist with the highest priority, and
1997 : // remove it from the worklist.
1998 : jit::IonBuilder* builder = HelperThreadState().highestPriorityPendingIonCompile(locked);
1999 :
2000 0 : currentTask.emplace(builder);
2001 :
2002 50 : JSRuntime* rt = builder->script()->compartment()->runtimeFromAnyThread();
2003 :
2004 100 : {
2005 : AutoUnlockHelperThreadState unlock(locked);
2006 :
2007 150 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
2008 : TraceLoggerEvent event(TraceLogger_AnnotateScripts, builder->script());
2009 50 : AutoTraceLog logScript(logger, event);
2010 0 : AutoTraceLog logCompile(logger, TraceLogger_IonCompilation);
2011 150 :
2012 0 : AutoSetContextRuntime ascr(rt);
2013 : jit::JitContext jctx(jit::CompileRuntime::get(rt),
2014 50 : jit::CompileRealm::get(builder->script()->realm()),
2015 : &builder->alloc());
2016 : builder->setBackgroundCodegen(jit::CompileBackEnd(builder));
2017 100 : }
2018 0 :
2019 : FinishOffThreadIonCompile(builder, locked);
2020 :
2021 50 : // Ping the main thread so that the compiled code can be incorporated at the
2022 : // next interrupt callback.
2023 : //
2024 : // This must happen before the current task is reset. DestroyContext
2025 : // cancels in progress Ion compilations before destroying its target
2026 : // context, and after we reset the current task we are no longer considered
2027 : // to be Ion compiling.
2028 : rt->mainContextFromAnyThread()->requestInterrupt(InterruptReason::AttachIonCompilations);
2029 :
2030 50 : currentTask.reset();
2031 :
2032 50 : // Notify the main thread in case it is waiting for the compilation to finish.
2033 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
2034 : }
2035 100 :
2036 0 : void
2037 : HelperThread::handleIonFreeWorkload(AutoLockHelperThreadState& locked)
2038 : {
2039 49 : MOZ_ASSERT(idle());
2040 : MOZ_ASSERT(HelperThreadState().canStartIonFreeTask(locked));
2041 0 :
2042 98 : auto& freeList = HelperThreadState().ionFreeList(locked);
2043 :
2044 98 : jit::IonBuilder* builder = freeList.popCopy();
2045 : {
2046 49 : AutoUnlockHelperThreadState unlock(locked);
2047 : FreeIonBuilder(builder);
2048 0 : }
2049 0 : }
2050 :
2051 49 : HelperThread*
2052 : js::CurrentHelperThread()
2053 : {
2054 8 : if (!HelperThreadState().threads)
2055 : return nullptr;
2056 0 : auto threadId = ThisThread::GetId();
2057 : for (auto& thisThread : *HelperThreadState().threads) {
2058 8 : if (thisThread.thread.isSome() && threadId == thisThread.thread->get_id())
2059 46 : return &thisThread;
2060 0 : }
2061 : return nullptr;
2062 : }
2063 :
2064 : bool
2065 : JSContext::addPendingCompileError(js::CompileError** error)
2066 : {
2067 0 : auto errorPtr = make_unique<js::CompileError>();
2068 : if (!errorPtr)
2069 0 : return false;
2070 0 : if (!helperThread()->parseTask()->errors.append(errorPtr.get())) {
2071 : ReportOutOfMemory(this);
2072 0 : return false;
2073 0 : }
2074 0 : *error = errorPtr.release();
2075 : return true;
2076 0 : }
2077 0 :
2078 : void
2079 : JSContext::addPendingOverRecursed()
2080 : {
2081 0 : if (helperThread()->parseTask())
2082 : helperThread()->parseTask()->overRecursed = true;
2083 0 : }
2084 0 :
2085 0 : void
2086 : JSContext::addPendingOutOfMemory()
2087 : {
2088 0 : // Keep in sync with recoverFromOutOfMemory.
2089 : if (helperThread()->parseTask())
2090 : helperThread()->parseTask()->outOfMemory = true;
2091 0 : }
2092 0 :
2093 0 : void
2094 : HelperThread::handleParseWorkload(AutoLockHelperThreadState& locked)
2095 : {
2096 5 : MOZ_ASSERT(HelperThreadState().canStartParseTask(locked));
2097 : MOZ_ASSERT(idle());
2098 0 :
2099 5 : currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());
2100 : ParseTask* task = parseTask();
2101 0 :
2102 5 : {
2103 : AutoUnlockHelperThreadState unlock(locked);
2104 : AutoSetContextRuntime ascr(task->parseGlobal->runtimeFromAnyThread());
2105 15 :
2106 0 : JSContext* cx = TlsContext.get();
2107 :
2108 0 : Zone* zone = task->parseGlobal->zoneFromAnyThread();
2109 : zone->setHelperThreadOwnerContext(cx);
2110 0 : auto resetOwnerContext = mozilla::MakeScopeExit([&] {
2111 0 : zone->setHelperThreadOwnerContext(nullptr);
2112 : });
2113 5 :
2114 15 : AutoRealm ar(cx, task->parseGlobal);
2115 :
2116 15 : task->parse(cx);
2117 :
2118 0 : cx->frontendCollectionPool().purge();
2119 : }
2120 0 :
2121 : // The callback is invoked while we are still off thread.
2122 : task->callback(task, task->callbackData);
2123 :
2124 0 : // FinishOffThreadScript will need to be called on the script to
2125 : // migrate it into the correct compartment.
2126 : HelperThreadState().parseFinishedList(locked).insertBack(task);
2127 :
2128 10 : currentTask.reset();
2129 :
2130 5 : // Notify the main thread in case it is waiting for the parse/emit to finish.
2131 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
2132 : }
2133 0 :
2134 5 : void
2135 : HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
2136 : {
2137 0 : MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
2138 : MOZ_ASSERT(idle());
2139 0 :
2140 0 : UniquePtr<SourceCompressionTask> task;
2141 : {
2142 0 : auto& worklist = HelperThreadState().compressionWorklist(locked);
2143 : task = std::move(worklist.back());
2144 0 : worklist.popBack();
2145 0 : currentTask.emplace(task.get());
2146 0 : }
2147 0 :
2148 : {
2149 : AutoUnlockHelperThreadState unlock(locked);
2150 :
2151 0 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
2152 : AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
2153 0 :
2154 0 : task->work();
2155 : }
2156 0 :
2157 : {
2158 : AutoEnterOOMUnsafeRegion oomUnsafe;
2159 : if (!HelperThreadState().compressionFinishedList(locked).append(std::move(task)))
2160 0 : oomUnsafe.crash("handleCompressionWorkload");
2161 0 : }
2162 0 :
2163 : currentTask.reset();
2164 :
2165 0 : // Notify the main thread in case it is waiting for the compression to finish.
2166 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
2167 : }
2168 0 :
2169 0 : bool
2170 : js::EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task)
2171 : {
2172 416 : AutoLockHelperThreadState lock;
2173 :
2174 1248 : auto& pending = HelperThreadState().compressionPendingList(lock);
2175 : if (!pending.append(std::move(task))) {
2176 0 : if (!cx->helperThread())
2177 0 : ReportOutOfMemory(cx);
2178 0 : return false;
2179 0 : }
2180 :
2181 : return true;
2182 : }
2183 :
2184 : template <typename T>
2185 : static void
2186 : ClearCompressionTaskList(T& list, JSRuntime* runtime)
2187 : {
2188 0 : for (size_t i = 0; i < list.length(); i++) {
2189 : if (list[i]->runtimeMatches(runtime))
2190 0 : HelperThreadState().remove(list, &i);
2191 0 : }
2192 0 : }
2193 :
2194 0 : void
2195 : js::CancelOffThreadCompressions(JSRuntime* runtime)
2196 : {
2197 0 : AutoLockHelperThreadState lock;
2198 :
2199 0 : if (!HelperThreadState().threads)
2200 : return;
2201 0 :
2202 0 : // Cancel all pending compression tasks.
2203 : ClearCompressionTaskList(HelperThreadState().compressionPendingList(lock), runtime);
2204 : ClearCompressionTaskList(HelperThreadState().compressionWorklist(lock), runtime);
2205 0 :
2206 0 : // Cancel all in-process compression tasks and wait for them to join so we
2207 : // clean up the finished tasks.
2208 : while (true) {
2209 : bool inProgress = false;
2210 : for (auto& thread : *HelperThreadState().threads) {
2211 0 : SourceCompressionTask* task = thread.compressionTask();
2212 0 : if (task && task->runtimeMatches(runtime))
2213 0 : inProgress = true;
2214 0 : }
2215 0 :
2216 : if (!inProgress)
2217 : break;
2218 0 :
2219 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
2220 : }
2221 0 :
2222 : // Clean up finished tasks.
2223 : ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock), runtime);
2224 : }
2225 0 :
2226 : void
2227 : PromiseHelperTask::executeAndResolveAndDestroy(JSContext* cx)
2228 : {
2229 0 : execute();
2230 : run(cx, JS::Dispatchable::NotShuttingDown);
2231 0 : }
2232 0 :
2233 0 : bool
2234 : js::StartOffThreadPromiseHelperTask(JSContext* cx, UniquePtr<PromiseHelperTask> task)
2235 : {
2236 0 : // Execute synchronously if there are no helper threads.
2237 : if (!CanUseExtraThreads()) {
2238 : task.release()->executeAndResolveAndDestroy(cx);
2239 0 : return true;
2240 0 : }
2241 0 :
2242 : AutoLockHelperThreadState lock;
2243 :
2244 0 : if (!HelperThreadState().promiseHelperTasks(lock).append(task.get())) {
2245 : ReportOutOfMemory(cx);
2246 0 : return false;
2247 0 : }
2248 0 :
2249 : Unused << task.release();
2250 :
2251 0 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
2252 : return true;
2253 0 : }
2254 0 :
2255 : bool
2256 : js::StartOffThreadPromiseHelperTask(PromiseHelperTask* task)
2257 : {
2258 0 : MOZ_ASSERT(CanUseExtraThreads());
2259 :
2260 0 : AutoLockHelperThreadState lock;
2261 :
2262 0 : if (!HelperThreadState().promiseHelperTasks(lock).append(task))
2263 : return false;
2264 0 :
2265 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
2266 : return true;
2267 0 : }
2268 0 :
2269 : void
2270 : GlobalHelperThreadState::trace(JSTracer* trc, gc::AutoTraceSession& session)
2271 : {
2272 0 : AutoLockHelperThreadState lock;
2273 : for (auto builder : ionWorklist(lock))
2274 0 : builder->trace(trc);
2275 0 : for (auto builder : ionFinishedList(lock))
2276 0 : builder->trace(trc);
2277 0 :
2278 0 : if (HelperThreadState().threads) {
2279 : for (auto& helper : *HelperThreadState().threads) {
2280 0 : if (auto builder = helper.ionBuilder())
2281 0 : builder->trace(trc);
2282 16 : }
2283 0 : }
2284 :
2285 : JSRuntime* rt = trc->runtime();
2286 : if (auto* jitRuntime = rt->jitRuntime()) {
2287 4 : jit::IonBuilder* builder = jitRuntime->ionLazyLinkList(rt).getFirst();
2288 4 : while (builder) {
2289 0 : builder->trace(trc);
2290 4 : builder = builder->getNext();
2291 0 : }
2292 0 : }
2293 :
2294 : for (auto parseTask : parseWorklist_)
2295 : parseTask->trace(trc);
2296 0 : for (auto parseTask : parseFinishedList_)
2297 0 : parseTask->trace(trc);
2298 0 : for (auto parseTask : parseWaitingOnGC_)
2299 0 : parseTask->trace(trc);
2300 8 : }
2301 0 :
2302 4 : void
2303 : HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked)
2304 : {
2305 0 : MOZ_ASSERT(HelperThreadState().canStartGCHelperTask(locked));
2306 : MOZ_ASSERT(idle());
2307 0 :
2308 0 : currentTask.emplace(HelperThreadState().gcHelperWorklist(locked).popCopy());
2309 : GCHelperState* task = gcHelperTask();
2310 0 :
2311 0 : AutoSetContextRuntime ascr(task->runtime());
2312 :
2313 0 : {
2314 : AutoUnlockHelperThreadState unlock(locked);
2315 : task->work();
2316 0 : }
2317 0 :
2318 : currentTask.reset();
2319 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
2320 0 : }
2321 0 :
2322 0 : void
2323 : JSContext::setHelperThread(HelperThread* thread)
2324 : {
2325 4 : if (helperThread_)
2326 : nurserySuppressions_--;
2327 8 :
2328 0 : helperThread_ = thread;
2329 :
2330 8 : if (helperThread_)
2331 : nurserySuppressions_++;
2332 8 : }
2333 4 :
2334 4 : // Definition of helper thread tasks.
2335 : //
2336 : // Priority is determined by the order they're listed here.
2337 : const HelperThread::TaskSpec HelperThread::taskSpecs[] = {
2338 : {
2339 : THREAD_TYPE_GCPARALLEL,
2340 : &GlobalHelperThreadState::canStartGCParallelTask,
2341 : &HelperThread::handleGCParallelWorkload
2342 : },
2343 : {
2344 : THREAD_TYPE_GCHELPER,
2345 : &GlobalHelperThreadState::canStartGCHelperTask,
2346 : &HelperThread::handleGCHelperWorkload
2347 : },
2348 : {
2349 : THREAD_TYPE_ION,
2350 : &GlobalHelperThreadState::canStartIonCompile,
2351 : &HelperThread::handleIonWorkload
2352 : },
2353 : {
2354 : THREAD_TYPE_WASM,
2355 : &GlobalHelperThreadState::canStartWasmTier1Compile,
2356 : &HelperThread::handleWasmTier1Workload
2357 : },
2358 : {
2359 : THREAD_TYPE_PROMISE_TASK,
2360 : &GlobalHelperThreadState::canStartPromiseHelperTask,
2361 : &HelperThread::handlePromiseHelperTaskWorkload
2362 : },
2363 : {
2364 : THREAD_TYPE_PARSE,
2365 : &GlobalHelperThreadState::canStartParseTask,
2366 : &HelperThread::handleParseWorkload
2367 : },
2368 : {
2369 : THREAD_TYPE_COMPRESS,
2370 : &GlobalHelperThreadState::canStartCompressionTask,
2371 : &HelperThread::handleCompressionWorkload
2372 : },
2373 : {
2374 : THREAD_TYPE_ION_FREE,
2375 : &GlobalHelperThreadState::canStartIonFreeTask,
2376 : &HelperThread::handleIonFreeWorkload
2377 : },
2378 : {
2379 : THREAD_TYPE_WASM,
2380 : &GlobalHelperThreadState::canStartWasmTier2Compile,
2381 : &HelperThread::handleWasmTier2Workload
2382 : },
2383 : {
2384 : THREAD_TYPE_WASM_TIER2,
2385 : &GlobalHelperThreadState::canStartWasmTier2Generator,
2386 : &HelperThread::handleWasmTier2GeneratorWorkload
2387 : }
2388 : };
2389 :
2390 : void
2391 : HelperThread::threadLoop()
2392 : {
2393 4 : MOZ_ASSERT(CanUseExtraThreads());
2394 :
2395 0 : JS::AutoSuppressGCAnalysis nogc;
2396 : AutoLockHelperThreadState lock;
2397 4 :
2398 8 : ensureRegisteredWithProfiler();
2399 :
2400 0 : JSContext cx(nullptr, JS::ContextOptions());
2401 : {
2402 : AutoEnterOOMUnsafeRegion oomUnsafe;
2403 : if (!cx.init(ContextKind::HelperThread))
2404 : oomUnsafe.crash("HelperThread cx.init()");
2405 : }
2406 : cx.setHelperThread(this);
2407 : JS_SetNativeStackQuota(&cx, HELPER_STACK_QUOTA);
2408 :
2409 : while (!terminate) {
2410 : MOZ_ASSERT(idle());
2411 :
2412 : // The selectors may depend on the HelperThreadState not changing
2413 : // between task selection and task execution, in particular, on new
2414 : // tasks not being added (because of the lifo structure of the work
2415 : // lists). Unlocking the HelperThreadState between task selection and
2416 : // execution is not well-defined.
2417 :
2418 : const TaskSpec* task = findHighestPriorityTask(lock);
2419 : if (!task) {
2420 : HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
2421 : continue;
2422 : }
2423 :
2424 : js::oom::SetThreadType(task->type);
2425 : (this->*(task->handleWorkload))(lock);
2426 : js::oom::SetThreadType(js::THREAD_TYPE_NONE);
2427 : }
2428 :
2429 : unregisterWithProfilerIfNeeded();
2430 : }
2431 :
2432 : const HelperThread::TaskSpec*
2433 : HelperThread::findHighestPriorityTask(const AutoLockHelperThreadState& locked)
2434 : {
2435 : // Return the highest priority task that is ready to start, or nullptr.
2436 :
2437 : for (const auto& task : taskSpecs) {
2438 : if ((HelperThreadState().*(task.canStart))(locked))
2439 : return &task;
2440 : }
2441 :
2442 : return nullptr;
2443 : }
|