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/DebugOnly.h"
10 : #include "mozilla/Maybe.h"
11 : #include "mozilla/Unused.h"
12 :
13 : #include "jsnativestack.h"
14 :
15 : #include "builtin/Promise.h"
16 : #include "frontend/BytecodeCompiler.h"
17 : #include "gc/GCInternals.h"
18 : #include "jit/IonBuilder.h"
19 : #include "threading/CpuCount.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 :
27 : #include "jscntxtinlines.h"
28 : #include "jscompartmentinlines.h"
29 : #include "jsobjinlines.h"
30 : #include "jsscriptinlines.h"
31 :
32 : #include "vm/NativeObject-inl.h"
33 :
34 : using namespace js;
35 :
36 : using mozilla::ArrayLength;
37 : using mozilla::DebugOnly;
38 : using mozilla::Unused;
39 : using mozilla::TimeDuration;
40 :
41 : namespace js {
42 :
43 : GlobalHelperThreadState* gHelperThreadState = nullptr;
44 :
45 : } // namespace js
46 :
47 : bool
48 3 : js::CreateHelperThreadsState()
49 : {
50 3 : MOZ_ASSERT(!gHelperThreadState);
51 3 : gHelperThreadState = js_new<GlobalHelperThreadState>();
52 3 : return gHelperThreadState != nullptr;
53 : }
54 :
55 : void
56 0 : js::DestroyHelperThreadsState()
57 : {
58 0 : MOZ_ASSERT(gHelperThreadState);
59 0 : gHelperThreadState->finish();
60 0 : js_delete(gHelperThreadState);
61 0 : gHelperThreadState = nullptr;
62 0 : }
63 :
64 : bool
65 4 : js::EnsureHelperThreadsInitialized()
66 : {
67 4 : MOZ_ASSERT(gHelperThreadState);
68 4 : return gHelperThreadState->ensureInitialized();
69 : }
70 :
71 : static size_t
72 3 : ThreadCountForCPUCount(size_t cpuCount)
73 : {
74 : // Create additional threads on top of the number of cores available, to
75 : // provide some excess capacity in case threads pause each other.
76 : static const uint32_t EXCESS_THREADS = 4;
77 3 : return cpuCount + EXCESS_THREADS;
78 : }
79 :
80 : void
81 0 : js::SetFakeCPUCount(size_t count)
82 : {
83 : // This must be called before the threads have been initialized.
84 0 : MOZ_ASSERT(!HelperThreadState().threads);
85 :
86 0 : HelperThreadState().cpuCount = count;
87 0 : HelperThreadState().threadCount = ThreadCountForCPUCount(count);
88 0 : }
89 :
90 : bool
91 0 : js::StartOffThreadWasmCompile(wasm::CompileTask* task)
92 : {
93 0 : AutoLockHelperThreadState lock;
94 :
95 0 : if (!HelperThreadState().wasmWorklist(lock).append(task))
96 0 : return false;
97 :
98 0 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
99 0 : return true;
100 : }
101 :
102 : bool
103 8 : js::StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder)
104 : {
105 16 : AutoLockHelperThreadState lock;
106 :
107 8 : if (!HelperThreadState().ionWorklist(lock).append(builder))
108 0 : return false;
109 :
110 8 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
111 8 : return true;
112 : }
113 :
114 : bool
115 8 : js::StartOffThreadIonFree(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
116 : {
117 8 : MOZ_ASSERT(CanUseExtraThreads());
118 :
119 8 : if (!HelperThreadState().ionFreeList(lock).append(builder))
120 0 : return false;
121 :
122 8 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
123 8 : return true;
124 : }
125 :
126 : /*
127 : * Move an IonBuilder for which compilation has either finished, failed, or
128 : * been cancelled into the global finished compilation list. All off thread
129 : * compilations which are started must eventually be finished.
130 : */
131 : static void
132 8 : FinishOffThreadIonCompile(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
133 : {
134 16 : AutoEnterOOMUnsafeRegion oomUnsafe;
135 8 : if (!HelperThreadState().ionFinishedList(lock).append(builder))
136 0 : oomUnsafe.crash("FinishOffThreadIonCompile");
137 8 : builder->script()->zoneFromAnyThread()->group()->numFinishedBuilders++;
138 8 : }
139 :
140 : static JSRuntime*
141 11 : GetSelectorRuntime(const CompilationSelector& selector)
142 : {
143 : struct Matcher
144 : {
145 6 : JSRuntime* match(JSScript* script) { return script->runtimeFromActiveCooperatingThread(); }
146 0 : JSRuntime* match(JSCompartment* comp) { return comp->runtimeFromActiveCooperatingThread(); }
147 1 : JSRuntime* match(ZonesInState zbs) { return zbs.runtime; }
148 4 : JSRuntime* match(JSRuntime* runtime) { return runtime; }
149 0 : JSRuntime* match(AllCompilations all) { return nullptr; }
150 : };
151 :
152 11 : return selector.match(Matcher());
153 : }
154 :
155 : static bool
156 11 : JitDataStructuresExist(const CompilationSelector& selector)
157 : {
158 : struct Matcher
159 : {
160 6 : bool match(JSScript* script) { return !!script->compartment()->jitCompartment(); }
161 0 : bool match(JSCompartment* comp) { return !!comp->jitCompartment(); }
162 1 : bool match(ZonesInState zbs) { return zbs.runtime->hasJitRuntime(); }
163 4 : bool match(JSRuntime* runtime) { return runtime->hasJitRuntime(); }
164 0 : bool match(AllCompilations all) { return true; }
165 : };
166 :
167 11 : return selector.match(Matcher());
168 : }
169 :
170 : static bool
171 1 : CompiledScriptMatches(const CompilationSelector& selector, JSScript* target)
172 : {
173 : struct ScriptMatches
174 : {
175 : JSScript* target_;
176 :
177 0 : bool match(JSScript* script) { return script == target_; }
178 0 : bool match(JSCompartment* comp) { return comp == target_->compartment(); }
179 1 : bool match(JSRuntime* runtime) { return runtime == target_->runtimeFromAnyThread(); }
180 0 : bool match(AllCompilations all) { return true; }
181 0 : bool match(ZonesInState zbs) {
182 0 : return zbs.runtime == target_->runtimeFromAnyThread() &&
183 0 : zbs.state == target_->zoneFromAnyThread()->gcState();
184 : }
185 : };
186 :
187 1 : return selector.match(ScriptMatches{target});
188 : }
189 :
190 : void
191 11 : js::CancelOffThreadIonCompile(const CompilationSelector& selector, bool discardLazyLinkList)
192 : {
193 11 : if (!JitDataStructuresExist(selector))
194 0 : return;
195 :
196 22 : AutoLockHelperThreadState lock;
197 :
198 11 : if (!HelperThreadState().threads)
199 0 : return;
200 :
201 : /* Cancel any pending entries for which processing hasn't started. */
202 11 : GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
203 11 : for (size_t i = 0; i < worklist.length(); i++) {
204 0 : jit::IonBuilder* builder = worklist[i];
205 0 : if (CompiledScriptMatches(selector, builder->script())) {
206 0 : FinishOffThreadIonCompile(builder, lock);
207 0 : HelperThreadState().remove(worklist, &i);
208 : }
209 : }
210 :
211 : /* Wait for in progress entries to finish up. */
212 : bool cancelled;
213 11 : do {
214 11 : cancelled = false;
215 11 : bool unpaused = false;
216 143 : for (auto& helper : *HelperThreadState().threads) {
217 132 : if (helper.ionBuilder() &&
218 0 : CompiledScriptMatches(selector, helper.ionBuilder()->script()))
219 : {
220 0 : helper.ionBuilder()->cancel();
221 0 : if (helper.pause) {
222 0 : helper.pause = false;
223 0 : unpaused = true;
224 : }
225 0 : cancelled = true;
226 : }
227 : }
228 11 : if (unpaused)
229 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE, lock);
230 11 : if (cancelled)
231 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
232 : } while (cancelled);
233 :
234 : /* Cancel code generation for any completed entries. */
235 11 : GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
236 11 : for (size_t i = 0; i < finished.length(); i++) {
237 0 : jit::IonBuilder* builder = finished[i];
238 0 : if (CompiledScriptMatches(selector, builder->script())) {
239 0 : builder->script()->zone()->group()->numFinishedBuilders--;
240 0 : jit::FinishOffThreadBuilder(nullptr, builder, lock);
241 0 : HelperThreadState().remove(finished, &i);
242 : }
243 : }
244 :
245 : /* Cancel lazy linking for pending builders (attached to the ionScript). */
246 11 : if (discardLazyLinkList) {
247 11 : MOZ_ASSERT(!selector.is<AllCompilations>());
248 11 : JSRuntime* runtime = GetSelectorRuntime(selector);
249 116 : for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
250 105 : jit::IonBuilder* builder = group->ionLazyLinkList().getFirst();
251 107 : while (builder) {
252 1 : jit::IonBuilder* next = builder->getNext();
253 1 : if (CompiledScriptMatches(selector, builder->script()))
254 1 : jit::FinishOffThreadBuilder(runtime, builder, lock);
255 1 : builder = next;
256 : }
257 : }
258 : }
259 : }
260 :
261 : #ifdef DEBUG
262 : bool
263 210 : js::HasOffThreadIonCompile(JSCompartment* comp)
264 : {
265 420 : AutoLockHelperThreadState lock;
266 :
267 210 : if (!HelperThreadState().threads || comp->isAtomsCompartment())
268 1 : return false;
269 :
270 209 : GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
271 209 : for (size_t i = 0; i < worklist.length(); i++) {
272 0 : jit::IonBuilder* builder = worklist[i];
273 0 : if (builder->script()->compartment() == comp)
274 0 : return true;
275 : }
276 :
277 2717 : for (auto& helper : *HelperThreadState().threads) {
278 2508 : if (helper.ionBuilder() && helper.ionBuilder()->script()->compartment() == comp)
279 0 : return true;
280 : }
281 :
282 209 : GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
283 209 : for (size_t i = 0; i < finished.length(); i++) {
284 0 : jit::IonBuilder* builder = finished[i];
285 0 : if (builder->script()->compartment() == comp)
286 0 : return true;
287 : }
288 :
289 209 : jit::IonBuilder* builder = comp->zone()->group()->ionLazyLinkList().getFirst();
290 209 : while (builder) {
291 0 : if (builder->script()->compartment() == comp)
292 0 : return true;
293 0 : builder = builder->getNext();
294 : }
295 :
296 209 : return false;
297 : }
298 : #endif
299 :
300 : static const JSClassOps parseTaskGlobalClassOps = {
301 : nullptr, nullptr, nullptr, nullptr,
302 : nullptr, nullptr, nullptr, nullptr,
303 : nullptr, nullptr, nullptr, nullptr,
304 : JS_GlobalObjectTraceHook
305 : };
306 :
307 : static const JSClass parseTaskGlobalClass = {
308 : "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
309 : &parseTaskGlobalClassOps
310 : };
311 :
312 0 : ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
313 : const char16_t* chars, size_t length,
314 0 : JS::OffThreadCompileCallback callback, void* callbackData)
315 0 : : kind(kind), options(cx), data(AsVariant(TwoByteChars(chars, length))),
316 : alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
317 : parseGlobal(parseGlobal),
318 : callback(callback), callbackData(callbackData),
319 : scripts(cx), sourceObjects(cx),
320 0 : overRecursed(false), outOfMemory(false)
321 : {
322 0 : MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
323 0 : MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
324 0 : }
325 :
326 0 : ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
327 : const JS::TranscodeRange& range,
328 0 : JS::OffThreadCompileCallback callback, void* callbackData)
329 0 : : kind(kind), options(cx), data(AsVariant(range)),
330 : alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
331 : parseGlobal(parseGlobal),
332 : callback(callback), callbackData(callbackData),
333 : scripts(cx), sourceObjects(cx),
334 0 : overRecursed(false), outOfMemory(false)
335 : {
336 0 : MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
337 0 : MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
338 0 : }
339 :
340 16 : ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
341 : JS::TranscodeSources& sources,
342 16 : JS::OffThreadCompileCallback callback, void* callbackData)
343 32 : : kind(kind), options(cx), data(AsVariant(&sources)),
344 : alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
345 : parseGlobal(parseGlobal),
346 : callback(callback), callbackData(callbackData),
347 : scripts(cx), sourceObjects(cx),
348 48 : overRecursed(false), outOfMemory(false)
349 : {
350 16 : MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
351 16 : MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
352 16 : }
353 :
354 : bool
355 16 : ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
356 : {
357 16 : if (!this->options.copy(cx, options))
358 0 : return false;
359 :
360 16 : return true;
361 : }
362 :
363 : void
364 16 : ParseTask::activate(JSRuntime* rt)
365 : {
366 16 : rt->setUsedByHelperThread(parseGlobal->zone());
367 16 : }
368 :
369 : bool
370 15 : ParseTask::finish(JSContext* cx)
371 : {
372 191 : for (auto& sourceObject : sourceObjects) {
373 352 : RootedScriptSource sso(cx, sourceObject);
374 176 : if (!ScriptSourceObject::initFromOptions(cx, sso, options))
375 0 : return false;
376 176 : if (!sso->source()->tryCompressOffThread(cx))
377 0 : return false;
378 : }
379 :
380 15 : return true;
381 : }
382 :
383 30 : ParseTask::~ParseTask()
384 : {
385 15 : for (size_t i = 0; i < errors.length(); i++)
386 0 : js_delete(errors[i]);
387 15 : }
388 :
389 : void
390 5 : ParseTask::trace(JSTracer* trc)
391 : {
392 5 : if (parseGlobal->runtimeFromAnyThread() != trc->runtime())
393 0 : return;
394 5 : Zone* zone = MaybeForwarded(parseGlobal)->zoneFromAnyThread();
395 5 : if (zone->usedByHelperThread()) {
396 5 : MOZ_ASSERT(!zone->isCollecting());
397 5 : return;
398 : }
399 :
400 0 : TraceManuallyBarrieredEdge(trc, &parseGlobal, "ParseTask::parseGlobal");
401 0 : scripts.trace(trc);
402 0 : sourceObjects.trace(trc);
403 : }
404 :
405 0 : ScriptParseTask::ScriptParseTask(JSContext* cx, JSObject* parseGlobal,
406 : const char16_t* chars, size_t length,
407 0 : JS::OffThreadCompileCallback callback, void* callbackData)
408 : : ParseTask(ParseTaskKind::Script, cx, parseGlobal, chars, length, callback,
409 0 : callbackData)
410 : {
411 0 : }
412 :
413 : void
414 0 : ScriptParseTask::parse(JSContext* cx)
415 : {
416 0 : auto& range = data.as<TwoByteChars>();
417 0 : SourceBufferHolder srcBuf(range.begin().get(), range.length(), SourceBufferHolder::NoOwnership);
418 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
419 :
420 0 : JSScript* script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
421 : options, srcBuf,
422 0 : /* sourceObjectOut = */ &sourceObject.get());
423 0 : if (script)
424 0 : scripts.infallibleAppend(script);
425 0 : if (sourceObject)
426 0 : sourceObjects.infallibleAppend(sourceObject);
427 0 : }
428 :
429 0 : ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
430 : const char16_t* chars, size_t length,
431 0 : JS::OffThreadCompileCallback callback, void* callbackData)
432 : : ParseTask(ParseTaskKind::Module, cx, parseGlobal, chars, length, callback,
433 0 : callbackData)
434 : {
435 0 : }
436 :
437 : void
438 0 : ModuleParseTask::parse(JSContext* cx)
439 : {
440 0 : auto& range = data.as<TwoByteChars>();
441 0 : SourceBufferHolder srcBuf(range.begin().get(), range.length(), SourceBufferHolder::NoOwnership);
442 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
443 :
444 0 : ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject.get());
445 0 : if (module) {
446 0 : scripts.infallibleAppend(module->script());
447 0 : if (sourceObject)
448 0 : sourceObjects.infallibleAppend(sourceObject);
449 : }
450 0 : }
451 :
452 0 : ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
453 : const JS::TranscodeRange& range,
454 0 : JS::OffThreadCompileCallback callback, void* callbackData)
455 : : ParseTask(ParseTaskKind::ScriptDecode, cx, parseGlobal,
456 0 : range, callback, callbackData)
457 : {
458 0 : }
459 :
460 : void
461 0 : ScriptDecodeTask::parse(JSContext* cx)
462 : {
463 0 : RootedScript resultScript(cx);
464 0 : Rooted<ScriptSourceObject*> sourceObject(cx);
465 :
466 0 : XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject.get(),
467 0 : data.as<const JS::TranscodeRange>());
468 0 : decoder.codeScript(&resultScript);
469 0 : MOZ_ASSERT(bool(resultScript) == (decoder.resultCode() == JS::TranscodeResult_Ok));
470 0 : if (decoder.resultCode() == JS::TranscodeResult_Ok) {
471 0 : scripts.infallibleAppend(resultScript);
472 0 : if (sourceObject)
473 0 : sourceObjects.infallibleAppend(sourceObject);
474 : }
475 0 : }
476 :
477 16 : MultiScriptsDecodeTask::MultiScriptsDecodeTask(JSContext* cx, JSObject* parseGlobal,
478 : JS::TranscodeSources& sources,
479 16 : JS::OffThreadCompileCallback callback, void* callbackData)
480 : : ParseTask(ParseTaskKind::MultiScriptsDecode, cx, parseGlobal,
481 16 : sources, callback, callbackData)
482 : {
483 16 : }
484 :
485 : void
486 16 : MultiScriptsDecodeTask::parse(JSContext* cx)
487 : {
488 16 : auto sources = data.as<JS::TranscodeSources*>();
489 :
490 32 : if (!scripts.reserve(sources->length()) ||
491 16 : !sourceObjects.reserve(sources->length()))
492 : {
493 0 : return;
494 : }
495 :
496 193 : for (auto& source : *sources) {
497 355 : CompileOptions opts(cx, options);
498 178 : opts.setFileAndLine(source.filename, source.lineno);
499 :
500 355 : RootedScript resultScript(cx);
501 355 : Rooted<ScriptSourceObject*> sourceObject(cx);
502 :
503 355 : XDROffThreadDecoder decoder(cx, alloc, &opts, &sourceObject.get(), source.range);
504 178 : decoder.codeScript(&resultScript);
505 177 : MOZ_ASSERT(bool(resultScript) == (decoder.resultCode() == JS::TranscodeResult_Ok));
506 :
507 177 : if (decoder.resultCode() != JS::TranscodeResult_Ok)
508 0 : break;
509 177 : MOZ_ASSERT(resultScript);
510 177 : scripts.infallibleAppend(resultScript);
511 177 : sourceObjects.infallibleAppend(sourceObject);
512 : }
513 : }
514 :
515 : void
516 0 : js::CancelOffThreadParses(JSRuntime* rt)
517 : {
518 0 : AutoLockHelperThreadState lock;
519 :
520 0 : if (!HelperThreadState().threads)
521 0 : return;
522 :
523 : #ifdef DEBUG
524 : GlobalHelperThreadState::ParseTaskVector& waitingOnGC =
525 0 : HelperThreadState().parseWaitingOnGC(lock);
526 0 : for (size_t i = 0; i < waitingOnGC.length(); i++)
527 0 : MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));
528 : #endif
529 :
530 : // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
531 : // and in progress ones to complete. Otherwise the final GC may not collect
532 : // everything due to zones being used off thread.
533 : while (true) {
534 0 : bool pending = false;
535 0 : GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist(lock);
536 0 : for (size_t i = 0; i < worklist.length(); i++) {
537 0 : ParseTask* task = worklist[i];
538 0 : if (task->runtimeMatches(rt))
539 0 : pending = true;
540 : }
541 0 : if (!pending) {
542 0 : bool inProgress = false;
543 0 : for (auto& thread : *HelperThreadState().threads) {
544 0 : ParseTask* task = thread.parseTask();
545 0 : if (task && task->runtimeMatches(rt))
546 0 : inProgress = true;
547 : }
548 0 : if (!inProgress)
549 0 : break;
550 : }
551 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
552 0 : }
553 :
554 : // Clean up any parse tasks which haven't been finished by the active thread.
555 0 : GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock);
556 : while (true) {
557 0 : bool found = false;
558 0 : for (size_t i = 0; i < finished.length(); i++) {
559 0 : ParseTask* task = finished[i];
560 0 : if (task->runtimeMatches(rt)) {
561 0 : found = true;
562 0 : AutoUnlockHelperThreadState unlock(lock);
563 0 : HelperThreadState().cancelParseTask(rt, task->kind, task);
564 : }
565 : }
566 0 : if (!found)
567 0 : break;
568 0 : }
569 : }
570 :
571 : bool
572 51 : js::OffThreadParsingMustWaitForGC(JSRuntime* rt)
573 : {
574 : // Off thread parsing can't occur during incremental collections on the
575 : // atoms compartment, to avoid triggering barriers. (Outside the atoms
576 : // compartment, the compilation will use a new zone that is never
577 : // collected.) If an atoms-zone GC is in progress, hold off on executing the
578 : // parse task until the atoms-zone GC completes (see
579 : // EnqueuePendingParseTasksAfterGC).
580 51 : return rt->activeGCInAtomsZone();
581 : }
582 :
583 : static bool
584 188 : EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
585 : {
586 188 : if (!GlobalObject::ensureConstructor(cx, global, key))
587 0 : return false;
588 :
589 188 : MOZ_ASSERT(global->getPrototype(key).toObject().isDelegate(),
590 : "standard class prototype wasn't a delegate from birth");
591 188 : return true;
592 : }
593 :
594 : // Initialize all classes potentially created during parsing for use in parser
595 : // data structures, template objects, &c.
596 : static bool
597 47 : EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind)
598 : {
599 47 : Handle<GlobalObject*> global = cx->global();
600 :
601 47 : if (!EnsureConstructor(cx, global, JSProto_Function))
602 0 : return false; // needed by functions, also adds object literals' proto
603 :
604 47 : if (!EnsureConstructor(cx, global, JSProto_Array))
605 0 : return false; // needed by array literals
606 :
607 47 : if (!EnsureConstructor(cx, global, JSProto_RegExp))
608 0 : return false; // needed by regular expression literals
609 :
610 47 : if (!EnsureConstructor(cx, global, JSProto_Iterator))
611 0 : return false; // needed by ???
612 :
613 47 : if (!GlobalObject::initStarGenerators(cx, global))
614 0 : return false; // needed by function*() {} and generator comprehensions
615 :
616 47 : if (kind == ParseTaskKind::Module && !GlobalObject::ensureModulePrototypesCreated(cx, global))
617 0 : return false;
618 :
619 47 : return true;
620 : }
621 :
622 : static JSObject*
623 16 : CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoSuppressGC& nogc)
624 : {
625 16 : JSCompartment* currentCompartment = cx->compartment();
626 :
627 : JS::CompartmentOptions compartmentOptions(currentCompartment->creationOptions(),
628 16 : currentCompartment->behaviors());
629 :
630 16 : auto& creationOptions = compartmentOptions.creationOptions();
631 :
632 16 : creationOptions.setInvisibleToDebugger(true)
633 16 : .setMergeable(true)
634 16 : .setNewZoneInNewZoneGroup();
635 :
636 : // Don't falsely inherit the host's global trace hook.
637 16 : creationOptions.setTrace(nullptr);
638 :
639 16 : JSObject* global = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
640 16 : JS::FireOnNewGlobalHook, compartmentOptions);
641 16 : if (!global)
642 0 : return nullptr;
643 :
644 16 : JS_SetCompartmentPrincipals(global->compartment(), currentCompartment->principals());
645 :
646 : // Initialize all classes required for parsing while still on the active
647 : // thread, for both the target and the new global so that prototype
648 : // pointers can be changed infallibly after parsing finishes.
649 16 : if (!EnsureParserCreatedClasses(cx, kind))
650 0 : return nullptr;
651 : {
652 32 : AutoCompartment ac(cx, global);
653 16 : if (!EnsureParserCreatedClasses(cx, kind))
654 0 : return nullptr;
655 : }
656 :
657 16 : return global;
658 : }
659 :
660 : static bool
661 16 : QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
662 : {
663 16 : if (OffThreadParsingMustWaitForGC(cx->runtime())) {
664 0 : AutoLockHelperThreadState lock;
665 0 : if (!HelperThreadState().parseWaitingOnGC(lock).append(task)) {
666 0 : ReportOutOfMemory(cx);
667 0 : return false;
668 : }
669 : } else {
670 32 : AutoLockHelperThreadState lock;
671 16 : if (!HelperThreadState().parseWorklist(lock).append(task)) {
672 0 : ReportOutOfMemory(cx);
673 0 : return false;
674 : }
675 :
676 16 : task->activate(cx->runtime());
677 16 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
678 : }
679 :
680 16 : return true;
681 : }
682 :
683 : template <typename TaskFunctor>
684 : bool
685 16 : StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options,
686 : ParseTaskKind kind, TaskFunctor& taskFunctor)
687 : {
688 : // Suppress GC so that calls below do not trigger a new incremental GC
689 : // which could require barriers on the atoms compartment.
690 32 : gc::AutoSuppressGC nogc(cx);
691 32 : gc::AutoAssertNoNurseryAlloc noNurseryAlloc;
692 32 : AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
693 :
694 16 : JSObject* global = CreateGlobalForOffThreadParse(cx, kind, nogc);
695 16 : if (!global)
696 0 : return false;
697 :
698 32 : ScopedJSDeletePtr<ParseTask> task(taskFunctor(global));
699 16 : if (!task)
700 0 : return false;
701 :
702 16 : if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
703 0 : return false;
704 :
705 16 : task.forget();
706 :
707 16 : return true;
708 : }
709 :
710 :
711 : bool
712 0 : js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
713 : const char16_t* chars, size_t length,
714 : JS::OffThreadCompileCallback callback, void* callbackData)
715 : {
716 0 : auto functor = [&](JSObject* global) -> ScriptParseTask* {
717 0 : return cx->new_<ScriptParseTask>(cx, global, chars, length,
718 0 : callback, callbackData);
719 0 : };
720 0 : return StartOffThreadParseTask(cx, options, ParseTaskKind::Script, functor);
721 : }
722 :
723 : bool
724 0 : js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
725 : const char16_t* chars, size_t length,
726 : JS::OffThreadCompileCallback callback, void* callbackData)
727 : {
728 0 : auto functor = [&](JSObject* global) -> ModuleParseTask* {
729 0 : return cx->new_<ModuleParseTask>(cx, global, chars, length,
730 0 : callback, callbackData);
731 0 : };
732 0 : return StartOffThreadParseTask(cx, options, ParseTaskKind::Module, functor);
733 : }
734 :
735 : bool
736 0 : js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options,
737 : const JS::TranscodeRange& range,
738 : JS::OffThreadCompileCallback callback, void* callbackData)
739 : {
740 0 : auto functor = [&](JSObject* global) -> ScriptDecodeTask* {
741 0 : return cx->new_<ScriptDecodeTask>(cx, global, range, callback, callbackData);
742 0 : };
743 0 : return StartOffThreadParseTask(cx, options, ParseTaskKind::ScriptDecode, functor);
744 : }
745 :
746 : bool
747 16 : js::StartOffThreadDecodeMultiScripts(JSContext* cx, const ReadOnlyCompileOptions& options,
748 : JS::TranscodeSources& sources,
749 : JS::OffThreadCompileCallback callback, void* callbackData)
750 : {
751 16 : auto functor = [&](JSObject* global) -> MultiScriptsDecodeTask* {
752 16 : return cx->new_<MultiScriptsDecodeTask>(cx, global, sources, callback, callbackData);
753 16 : };
754 16 : return StartOffThreadParseTask(cx, options, ParseTaskKind::MultiScriptsDecode, functor);
755 : }
756 :
757 : void
758 0 : js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
759 : {
760 0 : MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
761 :
762 0 : GlobalHelperThreadState::ParseTaskVector newTasks;
763 : {
764 0 : AutoLockHelperThreadState lock;
765 : GlobalHelperThreadState::ParseTaskVector& waiting =
766 0 : HelperThreadState().parseWaitingOnGC(lock);
767 :
768 0 : for (size_t i = 0; i < waiting.length(); i++) {
769 0 : ParseTask* task = waiting[i];
770 0 : if (task->runtimeMatches(rt) && !task->parseGlobal->zone()->wasGCStarted()) {
771 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
772 0 : if (!newTasks.append(task))
773 0 : oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
774 0 : HelperThreadState().remove(waiting, &i);
775 : }
776 : }
777 : }
778 :
779 0 : if (newTasks.empty())
780 0 : return;
781 :
782 : // This logic should mirror the contents of the !activeGCInAtomsZone()
783 : // branch in StartOffThreadParseScript:
784 :
785 0 : for (size_t i = 0; i < newTasks.length(); i++)
786 0 : newTasks[i]->activate(rt);
787 :
788 0 : AutoLockHelperThreadState lock;
789 :
790 : {
791 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
792 0 : if (!HelperThreadState().parseWorklist(lock).appendAll(newTasks))
793 0 : oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
794 : }
795 :
796 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
797 : }
798 :
799 : static const uint32_t kDefaultHelperStackSize = 2048 * 1024;
800 : static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
801 :
802 : // TSan enforces a minimum stack size that's just slightly larger than our
803 : // default helper stack size. It does this to store blobs of TSan-specific
804 : // data on each thread's stack. Unfortunately, that means that even though
805 : // we'll actually receive a larger stack than we requested, the effective
806 : // usable space of that stack is significantly less than what we expect.
807 : // To offset TSan stealing our stack space from underneath us, double the
808 : // default.
809 : //
810 : // Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
811 : // require all the thread-specific state that TSan does.
812 : #if defined(MOZ_TSAN)
813 : static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
814 : static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
815 : #else
816 : static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
817 : static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
818 : #endif
819 :
820 : bool
821 4 : GlobalHelperThreadState::ensureInitialized()
822 : {
823 4 : MOZ_ASSERT(CanUseExtraThreads());
824 :
825 4 : MOZ_ASSERT(this == &HelperThreadState());
826 8 : AutoLockHelperThreadState lock;
827 :
828 4 : if (threads)
829 1 : return true;
830 :
831 3 : threads = js::MakeUnique<HelperThreadVector>();
832 3 : if (!threads || !threads->initCapacity(threadCount))
833 0 : return false;
834 :
835 39 : for (size_t i = 0; i < threadCount; i++) {
836 36 : threads->infallibleEmplaceBack();
837 36 : HelperThread& helper = (*threads)[i];
838 :
839 36 : helper.thread = mozilla::Some(Thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)));
840 36 : if (!helper.thread->init(HelperThread::ThreadMain, &helper))
841 0 : goto error;
842 :
843 36 : continue;
844 :
845 : error:
846 : // Ensure that we do not leave uninitialized threads in the `threads`
847 : // vector.
848 0 : threads->popBack();
849 0 : finishThreads();
850 0 : return false;
851 : }
852 :
853 3 : return true;
854 : }
855 :
856 3 : GlobalHelperThreadState::GlobalHelperThreadState()
857 : : cpuCount(0),
858 : threadCount(0),
859 : threads(nullptr),
860 : wasmCompilationInProgress(false),
861 : numWasmFailedJobs(0),
862 3 : helperLock(mutexid::GlobalHelperThreadState)
863 : {
864 3 : cpuCount = GetCPUCount();
865 3 : threadCount = ThreadCountForCPUCount(cpuCount);
866 :
867 3 : MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
868 3 : }
869 :
870 : void
871 0 : GlobalHelperThreadState::finish()
872 : {
873 0 : finishThreads();
874 :
875 : // Make sure there are no Ion free tasks left. We check this here because,
876 : // unlike the other tasks, we don't explicitly block on this when
877 : // destroying a runtime.
878 0 : AutoLockHelperThreadState lock;
879 0 : auto& freeList = ionFreeList(lock);
880 0 : while (!freeList.empty())
881 0 : jit::FreeIonBuilder(freeList.popCopy());
882 0 : }
883 :
884 : void
885 0 : GlobalHelperThreadState::finishThreads()
886 : {
887 0 : if (!threads)
888 0 : return;
889 :
890 0 : MOZ_ASSERT(CanUseExtraThreads());
891 0 : for (auto& thread : *threads)
892 0 : thread.destroy();
893 0 : threads.reset(nullptr);
894 : }
895 :
896 : void
897 0 : GlobalHelperThreadState::lock()
898 : {
899 0 : helperLock.lock();
900 0 : }
901 :
902 : void
903 0 : GlobalHelperThreadState::unlock()
904 : {
905 0 : helperLock.unlock();
906 0 : }
907 :
908 : #ifdef DEBUG
909 : bool
910 554 : GlobalHelperThreadState::isLockedByCurrentThread()
911 : {
912 554 : return helperLock.ownedByCurrentThread();
913 : }
914 : #endif // DEBUG
915 :
916 : void
917 91 : GlobalHelperThreadState::wait(AutoLockHelperThreadState& locked, CondVar which,
918 : TimeDuration timeout /* = TimeDuration::Forever() */)
919 : {
920 91 : whichWakeup(which).wait_for(locked, timeout);
921 56 : }
922 :
923 : void
924 70 : GlobalHelperThreadState::notifyAll(CondVar which, const AutoLockHelperThreadState&)
925 : {
926 70 : whichWakeup(which).notify_all();
927 70 : }
928 :
929 : void
930 55 : GlobalHelperThreadState::notifyOne(CondVar which, const AutoLockHelperThreadState&)
931 : {
932 55 : whichWakeup(which).notify_one();
933 55 : }
934 :
935 : bool
936 0 : GlobalHelperThreadState::hasActiveThreads(const AutoLockHelperThreadState&)
937 : {
938 0 : if (!threads)
939 0 : return false;
940 :
941 0 : for (auto& thread : *threads) {
942 0 : if (!thread.idle())
943 0 : return true;
944 : }
945 :
946 0 : return false;
947 : }
948 :
949 : void
950 0 : GlobalHelperThreadState::waitForAllThreads()
951 : {
952 0 : CancelOffThreadIonCompile();
953 :
954 0 : AutoLockHelperThreadState lock;
955 0 : while (hasActiveThreads(lock))
956 0 : wait(lock, CONSUMER);
957 0 : }
958 :
959 : template <typename T>
960 : bool
961 134 : GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const
962 : {
963 134 : if (maxThreads >= threadCount)
964 85 : return true;
965 :
966 49 : size_t count = 0;
967 625 : for (auto& thread : *threads) {
968 577 : if (thread.currentTask.isSome() && thread.currentTask->is<T>())
969 1 : count++;
970 577 : if (count >= maxThreads)
971 1 : return false;
972 : }
973 :
974 48 : return true;
975 : }
976 :
977 : struct MOZ_RAII AutoSetContextRuntime
978 : {
979 47 : explicit AutoSetContextRuntime(JSRuntime* rt) {
980 47 : TlsContext.get()->setRuntime(rt);
981 47 : }
982 46 : ~AutoSetContextRuntime() {
983 46 : TlsContext.get()->setRuntime(nullptr);
984 46 : }
985 : };
986 :
987 : static inline bool
988 134 : IsHelperThreadSimulatingOOM(js::oom::ThreadType threadType)
989 : {
990 : #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
991 134 : return js::oom::targetThread == threadType;
992 : #else
993 : return false;
994 : #endif
995 : }
996 :
997 : size_t
998 16 : GlobalHelperThreadState::maxIonCompilationThreads() const
999 : {
1000 16 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_ION))
1001 0 : return 1;
1002 16 : return threadCount;
1003 : }
1004 :
1005 : size_t
1006 16 : GlobalHelperThreadState::maxUnpausedIonCompilationThreads() const
1007 : {
1008 16 : return 1;
1009 : }
1010 :
1011 : size_t
1012 0 : GlobalHelperThreadState::maxWasmCompilationThreads() const
1013 : {
1014 0 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_WASM))
1015 0 : return 1;
1016 0 : return cpuCount;
1017 : }
1018 :
1019 : size_t
1020 49 : GlobalHelperThreadState::maxParseThreads() const
1021 : {
1022 49 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_PARSE))
1023 0 : return 1;
1024 :
1025 : // Don't allow simultaneous off thread parses, to reduce contention on the
1026 : // atoms table. Note that wasm compilation depends on this to avoid
1027 : // stalling the helper thread, as off thread parse tasks can trigger and
1028 : // block on other off thread wasm compilation tasks.
1029 49 : return 1;
1030 : }
1031 :
1032 : size_t
1033 0 : GlobalHelperThreadState::maxCompressionThreads() const
1034 : {
1035 0 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_COMPRESS))
1036 0 : return 1;
1037 :
1038 : // Compression is triggered on major GCs to compress ScriptSources. It is
1039 : // considered low priority work.
1040 0 : return 1;
1041 : }
1042 :
1043 : size_t
1044 0 : GlobalHelperThreadState::maxGCHelperThreads() const
1045 : {
1046 0 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCHELPER))
1047 0 : return 1;
1048 0 : return threadCount;
1049 : }
1050 :
1051 : size_t
1052 69 : GlobalHelperThreadState::maxGCParallelThreads() const
1053 : {
1054 69 : if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCPARALLEL))
1055 0 : return 1;
1056 69 : return threadCount;
1057 : }
1058 :
1059 : bool
1060 161 : GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
1061 : {
1062 : // Don't execute an wasm job if an earlier one failed.
1063 161 : if (wasmWorklist(lock).empty() || numWasmFailedJobs)
1064 161 : return false;
1065 :
1066 : // Honor the maximum allowed threads to compile wasm jobs at once,
1067 : // to avoid oversaturating the machine.
1068 0 : if (!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))
1069 0 : return false;
1070 :
1071 0 : return true;
1072 : }
1073 :
1074 : bool
1075 161 : GlobalHelperThreadState::canStartPromiseTask(const AutoLockHelperThreadState& lock)
1076 : {
1077 161 : return !promiseTasks(lock).empty();
1078 : }
1079 :
1080 : static bool
1081 1 : IonBuilderHasHigherPriority(jit::IonBuilder* first, jit::IonBuilder* second)
1082 : {
1083 : // This method can return whatever it wants, though it really ought to be a
1084 : // total order. The ordering is allowed to race (change on the fly), however.
1085 :
1086 : // A lower optimization level indicates a higher priority.
1087 1 : if (first->optimizationInfo().level() != second->optimizationInfo().level())
1088 0 : return first->optimizationInfo().level() < second->optimizationInfo().level();
1089 :
1090 : // A script without an IonScript has precedence on one with.
1091 1 : if (first->scriptHasIonScript() != second->scriptHasIonScript())
1092 0 : return !first->scriptHasIonScript();
1093 :
1094 : // A higher warm-up counter indicates a higher priority.
1095 1 : return first->script()->getWarmUpCount() / first->script()->length() >
1096 1 : second->script()->getWarmUpCount() / second->script()->length();
1097 : }
1098 :
1099 : bool
1100 153 : GlobalHelperThreadState::canStartIonCompile(const AutoLockHelperThreadState& lock)
1101 : {
1102 169 : return !ionWorklist(lock).empty() &&
1103 169 : checkTaskThreadLimit<jit::IonBuilder*>(maxIonCompilationThreads());
1104 : }
1105 :
1106 : bool
1107 114 : GlobalHelperThreadState::canStartIonFreeTask(const AutoLockHelperThreadState& lock)
1108 : {
1109 114 : return !ionFreeList(lock).empty();
1110 : }
1111 :
1112 : jit::IonBuilder*
1113 10 : GlobalHelperThreadState::highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock,
1114 : bool remove /* = false */)
1115 : {
1116 10 : auto& worklist = ionWorklist(lock);
1117 10 : if (worklist.empty()) {
1118 1 : MOZ_ASSERT(!remove);
1119 1 : return nullptr;
1120 : }
1121 :
1122 : // Get the highest priority IonBuilder which has not started compilation yet.
1123 9 : size_t index = 0;
1124 9 : for (size_t i = 1; i < worklist.length(); i++) {
1125 0 : if (IonBuilderHasHigherPriority(worklist[i], worklist[index]))
1126 0 : index = i;
1127 : }
1128 9 : jit::IonBuilder* builder = worklist[index];
1129 9 : if (remove)
1130 8 : worklist.erase(&worklist[index]);
1131 9 : return builder;
1132 : }
1133 :
1134 : HelperThread*
1135 16 : GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold(
1136 : const AutoLockHelperThreadState& lock)
1137 : {
1138 : // Get the lowest priority IonBuilder which has started compilation and
1139 : // isn't paused, unless there are still fewer than the maximum number of
1140 : // such builders permitted.
1141 16 : size_t numBuilderThreads = 0;
1142 16 : HelperThread* thread = nullptr;
1143 208 : for (auto& thisThread : *threads) {
1144 192 : if (thisThread.ionBuilder() && !thisThread.pause) {
1145 2 : numBuilderThreads++;
1146 2 : if (!thread ||
1147 0 : IonBuilderHasHigherPriority(thread->ionBuilder(), thisThread.ionBuilder()))
1148 : {
1149 2 : thread = &thisThread;
1150 : }
1151 : }
1152 : }
1153 16 : if (numBuilderThreads < maxUnpausedIonCompilationThreads())
1154 14 : return nullptr;
1155 2 : return thread;
1156 : }
1157 :
1158 : HelperThread*
1159 8 : GlobalHelperThreadState::highestPriorityPausedIonCompile(const AutoLockHelperThreadState& lock)
1160 : {
1161 : // Get the highest priority IonBuilder which has started compilation but
1162 : // which was subsequently paused.
1163 8 : HelperThread* thread = nullptr;
1164 104 : for (auto& thisThread : *threads) {
1165 96 : if (thisThread.pause) {
1166 : // Currently, only threads with IonBuilders can be paused.
1167 1 : MOZ_ASSERT(thisThread.ionBuilder());
1168 1 : if (!thread ||
1169 0 : IonBuilderHasHigherPriority(thisThread.ionBuilder(), thread->ionBuilder()))
1170 : {
1171 1 : thread = &thisThread;
1172 : }
1173 : }
1174 : }
1175 8 : return thread;
1176 : }
1177 :
1178 : bool
1179 145 : GlobalHelperThreadState::pendingIonCompileHasSufficientPriority(
1180 : const AutoLockHelperThreadState& lock)
1181 : {
1182 : // Can't compile anything if there are no scripts to compile.
1183 145 : if (!canStartIonCompile(lock))
1184 137 : return false;
1185 :
1186 : // Count the number of threads currently compiling scripts, and look for
1187 : // the thread with the lowest priority.
1188 8 : HelperThread* lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold(lock);
1189 :
1190 : // If the number of threads building scripts is less than the maximum, the
1191 : // compilation can start immediately.
1192 8 : if (!lowestPriorityThread)
1193 7 : return true;
1194 :
1195 : // If there is a builder in the worklist with higher priority than some
1196 : // builder currently being compiled, then that current compilation can be
1197 : // paused, so allow the compilation.
1198 1 : if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(lock),
1199 : lowestPriorityThread->ionBuilder()))
1200 1 : return true;
1201 :
1202 : // Compilation will have to wait until one of the active compilations finishes.
1203 0 : return false;
1204 : }
1205 :
1206 : bool
1207 177 : GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
1208 : {
1209 177 : return !parseWorklist(lock).empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads());
1210 : }
1211 :
1212 : bool
1213 130 : GlobalHelperThreadState::canStartCompressionTask(const AutoLockHelperThreadState& lock)
1214 : {
1215 130 : return !compressionWorklist(lock).empty() &&
1216 130 : checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
1217 : }
1218 :
1219 : void
1220 1 : GlobalHelperThreadState::startHandlingCompressionTasks(const AutoLockHelperThreadState& lock)
1221 : {
1222 1 : scheduleCompressionTasks(lock);
1223 1 : if (canStartCompressionTask(lock))
1224 0 : notifyOne(PRODUCER, lock);
1225 1 : }
1226 :
1227 : void
1228 1 : GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadState& lock)
1229 : {
1230 1 : auto& pending = compressionPendingList(lock);
1231 1 : auto& worklist = compressionWorklist(lock);
1232 :
1233 50 : for (size_t i = 0; i < pending.length(); i++) {
1234 49 : if (pending[i]->shouldStart()) {
1235 : // OOMing during appending results in the task not being scheduled
1236 : // and deleted.
1237 0 : Unused << worklist.append(Move(pending[i]));
1238 0 : remove(pending, &i);
1239 : }
1240 : }
1241 1 : }
1242 :
1243 : bool
1244 153 : GlobalHelperThreadState::canStartGCHelperTask(const AutoLockHelperThreadState& lock)
1245 : {
1246 153 : return !gcHelperWorklist(lock).empty() &&
1247 153 : checkTaskThreadLimit<GCHelperState*>(maxGCHelperThreads());
1248 : }
1249 :
1250 : bool
1251 199 : GlobalHelperThreadState::canStartGCParallelTask(const AutoLockHelperThreadState& lock)
1252 : {
1253 268 : return !gcParallelWorklist(lock).empty() &&
1254 268 : checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());
1255 : }
1256 :
1257 4 : js::GCParallelTask::~GCParallelTask()
1258 : {
1259 : // Only most-derived classes' destructors may do the join: base class
1260 : // destructors run after those for derived classes' members, so a join in a
1261 : // base class can't ensure that the task is done using the members. All we
1262 : // can do now is check that someone has previously stopped the task.
1263 : #ifdef DEBUG
1264 4 : mozilla::Maybe<AutoLockHelperThreadState> helperLock;
1265 2 : if (!HelperThreadState().isLockedByCurrentThread())
1266 0 : helperLock.emplace();
1267 2 : MOZ_ASSERT(state == NotStarted);
1268 : #endif
1269 2 : }
1270 :
1271 : bool
1272 23 : js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock)
1273 : {
1274 : // Tasks cannot be started twice.
1275 23 : MOZ_ASSERT(state == NotStarted);
1276 :
1277 : // If we do the shutdown GC before running anything, we may never
1278 : // have initialized the helper threads. Just use the serial path
1279 : // since we cannot safely intialize them at this point.
1280 23 : if (!HelperThreadState().threads)
1281 0 : return false;
1282 :
1283 23 : if (!HelperThreadState().gcParallelWorklist(lock).append(this))
1284 0 : return false;
1285 23 : state = Dispatched;
1286 :
1287 23 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
1288 :
1289 23 : return true;
1290 : }
1291 :
1292 : bool
1293 0 : js::GCParallelTask::start()
1294 : {
1295 0 : AutoLockHelperThreadState helperLock;
1296 0 : return startWithLockHeld(helperLock);
1297 : }
1298 :
1299 : void
1300 26 : js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& locked)
1301 : {
1302 26 : if (state == NotStarted)
1303 4 : return;
1304 :
1305 0 : while (state != Finished)
1306 0 : HelperThreadState().wait(locked, GlobalHelperThreadState::CONSUMER);
1307 22 : state = NotStarted;
1308 22 : cancel_ = false;
1309 : }
1310 :
1311 : void
1312 3 : js::GCParallelTask::join()
1313 : {
1314 6 : AutoLockHelperThreadState helperLock;
1315 3 : joinWithLockHeld(helperLock);
1316 3 : }
1317 :
1318 : void
1319 0 : js::GCParallelTask::runFromActiveCooperatingThread(JSRuntime* rt)
1320 : {
1321 0 : MOZ_ASSERT(state == NotStarted);
1322 0 : MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt));
1323 0 : mozilla::TimeStamp timeStart = mozilla::TimeStamp::Now();
1324 0 : run();
1325 0 : duration_ = mozilla::TimeStamp::Now() - timeStart;
1326 0 : }
1327 :
1328 : void
1329 23 : js::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState& locked)
1330 : {
1331 46 : AutoSetContextRuntime ascr(runtime());
1332 46 : gc::AutoSetThreadIsPerformingGC performingGC;
1333 :
1334 : {
1335 46 : AutoUnlockHelperThreadState parallelSection(locked);
1336 23 : mozilla::TimeStamp timeStart = mozilla::TimeStamp::Now();
1337 23 : TlsContext.get()->heapState = JS::HeapState::MajorCollecting;
1338 23 : run();
1339 23 : TlsContext.get()->heapState = JS::HeapState::Idle;
1340 23 : duration_ = mozilla::TimeStamp::Now() - timeStart;
1341 : }
1342 :
1343 23 : state = Finished;
1344 23 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1345 23 : }
1346 :
1347 : bool
1348 22 : js::GCParallelTask::isRunningWithLockHeld(const AutoLockHelperThreadState& locked) const
1349 : {
1350 22 : return state == Dispatched;
1351 : }
1352 :
1353 : bool
1354 1 : js::GCParallelTask::isRunning() const
1355 : {
1356 2 : AutoLockHelperThreadState helperLock;
1357 2 : return isRunningWithLockHeld(helperLock);
1358 : }
1359 :
1360 : void
1361 23 : HelperThread::handleGCParallelWorkload(AutoLockHelperThreadState& locked)
1362 : {
1363 23 : MOZ_ASSERT(HelperThreadState().canStartGCParallelTask(locked));
1364 23 : MOZ_ASSERT(idle());
1365 :
1366 23 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1367 46 : AutoTraceLog logCompile(logger, TraceLogger_GC);
1368 :
1369 23 : currentTask.emplace(HelperThreadState().gcParallelWorklist(locked).popCopy());
1370 23 : gcParallelTask()->runFromHelperThread(locked);
1371 23 : currentTask.reset();
1372 23 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1373 23 : }
1374 :
1375 : static void
1376 15 : LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
1377 : {
1378 : // Mark the zone as no longer in use by a helper thread, and available
1379 : // to be collected by the GC.
1380 15 : rt->clearUsedByHelperThread(task->parseGlobal->zone());
1381 15 : }
1382 :
1383 : ParseTask*
1384 15 : GlobalHelperThreadState::removeFinishedParseTask(ParseTaskKind kind, void* token)
1385 : {
1386 : // The token is a ParseTask* which should be in the finished list.
1387 : // Find and remove its entry.
1388 :
1389 30 : AutoLockHelperThreadState lock;
1390 15 : ParseTaskVector& finished = parseFinishedList(lock);
1391 :
1392 15 : for (size_t i = 0; i < finished.length(); i++) {
1393 15 : if (finished[i] == token) {
1394 15 : ParseTask* parseTask = finished[i];
1395 15 : remove(finished, &i);
1396 15 : MOZ_ASSERT(parseTask);
1397 15 : MOZ_ASSERT(parseTask->kind == kind);
1398 30 : return parseTask;
1399 : }
1400 : }
1401 :
1402 0 : MOZ_CRASH("Invalid ParseTask token");
1403 : }
1404 :
1405 : template <typename F, typename>
1406 : bool
1407 15 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind, void* token, F&& finishCallback)
1408 : {
1409 15 : MOZ_ASSERT(cx->compartment());
1410 :
1411 30 : ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
1412 :
1413 : // Make sure we have all the constructors we need for the prototype
1414 : // remapping below, since we can't GC while that's happening.
1415 30 : Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
1416 15 : if (!EnsureParserCreatedClasses(cx, kind)) {
1417 0 : LeaveParseTaskZone(cx->runtime(), parseTask);
1418 0 : return false;
1419 : }
1420 :
1421 15 : mergeParseTaskCompartment(cx, parseTask, global, cx->compartment());
1422 :
1423 15 : bool ok = finishCallback(parseTask);
1424 :
1425 191 : for (auto& script : parseTask->scripts)
1426 176 : releaseAssertSameCompartment(cx, script);
1427 :
1428 15 : if (!parseTask->finish(cx) || !ok)
1429 0 : return false;
1430 :
1431 : // Report out of memory errors eagerly, or errors could be malformed.
1432 15 : if (parseTask->outOfMemory) {
1433 0 : ReportOutOfMemory(cx);
1434 0 : return false;
1435 : }
1436 :
1437 : // Report any error or warnings generated during the parse, and inform the
1438 : // debugger about the compiled scripts.
1439 15 : for (size_t i = 0; i < parseTask->errors.length(); i++)
1440 0 : parseTask->errors[i]->throwError(cx);
1441 15 : if (parseTask->overRecursed)
1442 0 : ReportOverRecursed(cx);
1443 15 : if (cx->isExceptionPending())
1444 0 : return false;
1445 :
1446 15 : return true;
1447 : }
1448 :
1449 : JSScript*
1450 0 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind, void* token)
1451 : {
1452 0 : JS::RootedScript script(cx);
1453 :
1454 0 : bool ok = finishParseTask(cx, kind, token, [&script] (ParseTask* parseTask) {
1455 0 : MOZ_RELEASE_ASSERT(parseTask->scripts.length() <= 1);
1456 :
1457 0 : if (parseTask->scripts.length() > 0)
1458 0 : script = parseTask->scripts[0];
1459 :
1460 0 : return true;
1461 0 : });
1462 :
1463 0 : if (!ok)
1464 0 : return nullptr;
1465 :
1466 0 : if (!script) {
1467 : // No error was reported, but no script produced. Assume we hit out of
1468 : // memory.
1469 0 : ReportOutOfMemory(cx);
1470 0 : return nullptr;
1471 : }
1472 :
1473 : // The Debugger only needs to be told about the topmost script that was compiled.
1474 0 : Debugger::onNewScript(cx, script);
1475 :
1476 0 : return script;
1477 : }
1478 :
1479 : bool
1480 15 : GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind, void* token,
1481 : MutableHandle<ScriptVector> scripts)
1482 : {
1483 15 : size_t expectedLength = 0;
1484 :
1485 236 : bool ok = finishParseTask(cx, kind, token, [&scripts, &expectedLength] (ParseTask* parseTask) {
1486 15 : expectedLength = parseTask->data.as<JS::TranscodeSources*>()->length();
1487 :
1488 30 : if (!scripts.reserve(parseTask->scripts.length()))
1489 0 : return false;
1490 :
1491 191 : for (auto& script : parseTask->scripts)
1492 176 : scripts.infallibleAppend(script);
1493 15 : return true;
1494 15 : });
1495 :
1496 15 : if (!ok)
1497 0 : return false;
1498 :
1499 15 : if (scripts.length() != expectedLength) {
1500 : // No error was reported, but fewer scripts produced than expected.
1501 : // Assume we hit out of memory.
1502 0 : ReportOutOfMemory(cx);
1503 0 : return false;
1504 : }
1505 :
1506 : // The Debugger only needs to be told about the topmost script that was compiled.
1507 30 : JS::RootedScript rooted(cx);
1508 191 : for (auto& script : scripts) {
1509 176 : MOZ_ASSERT(script->isGlobalCode());
1510 :
1511 176 : rooted = script;
1512 176 : Debugger::onNewScript(cx, rooted);
1513 : }
1514 :
1515 15 : return true;
1516 : }
1517 :
1518 : JSScript*
1519 0 : GlobalHelperThreadState::finishScriptParseTask(JSContext* cx, void* token)
1520 : {
1521 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::Script, token);
1522 0 : MOZ_ASSERT_IF(script, script->isGlobalCode());
1523 0 : return script;
1524 : }
1525 :
1526 : JSScript*
1527 0 : GlobalHelperThreadState::finishScriptDecodeTask(JSContext* cx, void* token)
1528 : {
1529 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::ScriptDecode, token);
1530 0 : MOZ_ASSERT_IF(script, script->isGlobalCode());
1531 0 : return script;
1532 : }
1533 :
1534 : bool
1535 15 : GlobalHelperThreadState::finishMultiScriptsDecodeTask(JSContext* cx, void* token, MutableHandle<ScriptVector> scripts)
1536 : {
1537 15 : return finishParseTask(cx, ParseTaskKind::MultiScriptsDecode, token, scripts);
1538 : }
1539 :
1540 : JSObject*
1541 0 : GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, void* token)
1542 : {
1543 0 : JSScript* script = finishParseTask(cx, ParseTaskKind::Module, token);
1544 0 : if (!script)
1545 0 : return nullptr;
1546 :
1547 0 : MOZ_ASSERT(script->module());
1548 :
1549 0 : RootedModuleObject module(cx, script->module());
1550 0 : module->fixEnvironmentsAfterCompartmentMerge();
1551 0 : if (!ModuleObject::Freeze(cx, module))
1552 0 : return nullptr;
1553 :
1554 0 : return module;
1555 : }
1556 :
1557 : void
1558 0 : GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token)
1559 : {
1560 0 : ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
1561 0 : LeaveParseTaskZone(rt, parseTask);
1562 0 : }
1563 :
1564 : JSObject*
1565 229 : GlobalObject::getStarGeneratorFunctionPrototype()
1566 : {
1567 229 : const Value& v = getReservedSlot(STAR_GENERATOR_FUNCTION_PROTO);
1568 229 : return v.isObject() ? &v.toObject() : nullptr;
1569 : }
1570 :
1571 : void
1572 15 : GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
1573 : Handle<GlobalObject*> global,
1574 : JSCompartment* dest)
1575 : {
1576 : // Finish any ongoing incremental GC that may affect the destination zone.
1577 15 : if (JS::IsIncrementalGCInProgress(cx) && dest->zone()->wasGCStarted())
1578 0 : JS::FinishIncrementalGC(cx, JS::gcreason::API);
1579 :
1580 : // After we call LeaveParseTaskZone() it's not safe to GC until we have
1581 : // finished merging the contents of the parse task's compartment into the
1582 : // destination compartment.
1583 30 : JS::AutoAssertNoGC nogc(cx);
1584 :
1585 15 : LeaveParseTaskZone(cx->runtime(), parseTask);
1586 30 : AutoCompartment ac(cx, parseTask->parseGlobal);
1587 :
1588 : {
1589 : // Generator functions don't have Function.prototype as prototype but a
1590 : // different function object, so the IdentifyStandardPrototype trick
1591 : // below won't work. Just special-case it.
1592 15 : GlobalObject* parseGlobal = &parseTask->parseGlobal->as<GlobalObject>();
1593 15 : JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
1594 :
1595 : // Module objects don't have standard prototypes either.
1596 15 : JSObject* moduleProto = parseGlobal->maybeGetModulePrototype();
1597 15 : JSObject* importEntryProto = parseGlobal->maybeGetImportEntryPrototype();
1598 15 : JSObject* exportEntryProto = parseGlobal->maybeGetExportEntryPrototype();
1599 :
1600 : // Point the prototypes of any objects in the script's compartment to refer
1601 : // to the corresponding prototype in the new compartment. This will briefly
1602 : // create cross compartment pointers, which will be fixed by the
1603 : // MergeCompartments call below.
1604 15 : Zone* parseZone = parseTask->parseGlobal->zone();
1605 7056 : for (auto group = parseZone->cellIter<ObjectGroup>(); !group.done(); group.next()) {
1606 7041 : TaggedProto proto(group->proto());
1607 7041 : if (!proto.isObject())
1608 300 : continue;
1609 :
1610 6921 : JSObject* protoObj = proto.toObject();
1611 :
1612 : JSObject* newProto;
1613 6921 : JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
1614 6921 : if (key != JSProto_Null) {
1615 6647 : MOZ_ASSERT(key == JSProto_Object || key == JSProto_Array ||
1616 : key == JSProto_Function || key == JSProto_RegExp ||
1617 : key == JSProto_Iterator);
1618 6647 : newProto = GetBuiltinPrototypePure(global, key);
1619 274 : } else if (protoObj == parseTaskStarGenFunctionProto) {
1620 214 : newProto = global->getStarGeneratorFunctionPrototype();
1621 60 : } else if (protoObj == moduleProto) {
1622 0 : newProto = global->getModulePrototype();
1623 60 : } else if (protoObj == importEntryProto) {
1624 0 : newProto = global->getImportEntryPrototype();
1625 60 : } else if (protoObj == exportEntryProto) {
1626 0 : newProto = global->getExportEntryPrototype();
1627 : } else {
1628 60 : continue;
1629 : }
1630 :
1631 6861 : group->setProtoUnchecked(TaggedProto(newProto));
1632 : }
1633 : }
1634 :
1635 : // Move the parsed script and all its contents into the desired compartment.
1636 15 : gc::MergeCompartments(parseTask->parseGlobal->compartment(), dest);
1637 15 : }
1638 :
1639 : void
1640 0 : HelperThread::destroy()
1641 : {
1642 0 : if (thread.isSome()) {
1643 : {
1644 0 : AutoLockHelperThreadState lock;
1645 0 : terminate = true;
1646 :
1647 : /* Notify all helpers, to ensure that this thread wakes up. */
1648 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
1649 : }
1650 :
1651 0 : thread->join();
1652 0 : thread.reset();
1653 : }
1654 0 : }
1655 :
1656 : /* static */
1657 : void
1658 34 : HelperThread::ThreadMain(void* arg)
1659 : {
1660 34 : ThisThread::SetName("JS Helper");
1661 :
1662 36 : static_cast<HelperThread*>(arg)->threadLoop();
1663 0 : Mutex::ShutDown();
1664 0 : }
1665 :
1666 : void
1667 0 : HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
1668 : {
1669 0 : MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked));
1670 0 : MOZ_ASSERT(idle());
1671 :
1672 0 : currentTask.emplace(HelperThreadState().wasmWorklist(locked).popCopy());
1673 0 : bool success = false;
1674 0 : UniqueChars error;
1675 :
1676 0 : wasm::CompileTask* task = wasmTask();
1677 : {
1678 0 : AutoUnlockHelperThreadState unlock(locked);
1679 0 : success = wasm::CompileFunction(task, &error);
1680 : }
1681 :
1682 : // On success, try to move work to the finished list.
1683 0 : if (success)
1684 0 : success = HelperThreadState().wasmFinishedList(locked).append(task);
1685 :
1686 : // On failure, note the failure for harvesting by the parent.
1687 0 : if (!success) {
1688 0 : HelperThreadState().noteWasmFailure(locked);
1689 0 : HelperThreadState().setWasmError(locked, Move(error));
1690 : }
1691 :
1692 : // Notify the active thread in case it's waiting.
1693 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1694 0 : currentTask.reset();
1695 0 : }
1696 :
1697 : void
1698 0 : HelperThread::handlePromiseTaskWorkload(AutoLockHelperThreadState& locked)
1699 : {
1700 0 : MOZ_ASSERT(HelperThreadState().canStartPromiseTask(locked));
1701 0 : MOZ_ASSERT(idle());
1702 :
1703 0 : PromiseTask* task = HelperThreadState().promiseTasks(locked).popCopy();
1704 0 : currentTask.emplace(task);
1705 :
1706 : {
1707 0 : AutoUnlockHelperThreadState unlock(locked);
1708 :
1709 0 : task->execute();
1710 :
1711 0 : if (!task->runtime()->finishAsyncTaskCallback(task)) {
1712 : // We cannot simply delete the task now because the PromiseTask must
1713 : // be destroyed on its runtime's thread. Add it to a list of tasks
1714 : // to delete before the next GC.
1715 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
1716 0 : if (!task->runtime()->promiseTasksToDestroy.lock()->append(task))
1717 0 : oomUnsafe.crash("handlePromiseTaskWorkload");
1718 : }
1719 : }
1720 :
1721 : // Notify the active thread in case it's waiting.
1722 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1723 0 : currentTask.reset();
1724 0 : }
1725 :
1726 : void
1727 8 : HelperThread::handleIonWorkload(AutoLockHelperThreadState& locked)
1728 : {
1729 8 : MOZ_ASSERT(HelperThreadState().canStartIonCompile(locked));
1730 8 : MOZ_ASSERT(idle());
1731 :
1732 : // Find the IonBuilder in the worklist with the highest priority, and
1733 : // remove it from the worklist.
1734 : jit::IonBuilder* builder =
1735 8 : HelperThreadState().highestPriorityPendingIonCompile(locked, /* remove = */ true);
1736 :
1737 : // If there are now too many threads with active IonBuilders, indicate to
1738 : // the one with the lowest priority that it should pause. Note that due to
1739 : // builder priorities changing since pendingIonCompileHasSufficientPriority
1740 : // was called, the builder we are pausing may actually be higher priority
1741 : // than the one we are about to start. Oh well.
1742 8 : HelperThread* other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold(locked);
1743 8 : if (other) {
1744 1 : MOZ_ASSERT(other->ionBuilder() && !other->pause);
1745 1 : other->pause = true;
1746 : }
1747 :
1748 8 : currentTask.emplace(builder);
1749 8 : builder->setPauseFlag(&pause);
1750 :
1751 8 : JSRuntime* rt = builder->script()->compartment()->runtimeFromAnyThread();
1752 :
1753 : {
1754 16 : AutoUnlockHelperThreadState unlock(locked);
1755 :
1756 8 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1757 16 : TraceLoggerEvent event(TraceLogger_AnnotateScripts, builder->script());
1758 16 : AutoTraceLog logScript(logger, event);
1759 16 : AutoTraceLog logCompile(logger, TraceLogger_IonCompilation);
1760 :
1761 16 : AutoSetContextRuntime ascr(rt);
1762 : jit::JitContext jctx(jit::CompileRuntime::get(rt),
1763 : jit::CompileCompartment::get(builder->script()->compartment()),
1764 16 : &builder->alloc());
1765 8 : builder->setBackgroundCodegen(jit::CompileBackEnd(builder));
1766 : }
1767 :
1768 8 : FinishOffThreadIonCompile(builder, locked);
1769 :
1770 : // Ping any thread currently operating on the compiled script's zone group
1771 : // so that the compiled code can be incorporated at the next interrupt
1772 : // callback. Don't interrupt Ion code for this, as this incorporation can
1773 : // be delayed indefinitely without affecting performance as long as the
1774 : // active thread is actually executing Ion code.
1775 : //
1776 : // This must happen before the current task is reset. DestroyContext
1777 : // cancels in progress Ion compilations before destroying its target
1778 : // context, and after we reset the current task we are no longer considered
1779 : // to be Ion compiling.
1780 8 : JSContext* target = builder->script()->zoneFromAnyThread()->group()->ownerContext().context();
1781 8 : if (target)
1782 8 : target->requestInterrupt(JSContext::RequestInterruptCanWait);
1783 :
1784 8 : currentTask.reset();
1785 8 : pause = false;
1786 :
1787 : // Notify the active thread in case it is waiting for the compilation to finish.
1788 8 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1789 :
1790 : // When finishing Ion compilation jobs, we can start unpausing compilation
1791 : // threads that were paused to restrict the number of active compilations.
1792 : // Only unpause one at a time, to make sure we don't exceed the restriction.
1793 : // Since threads are currently only paused for Ion compilations, this
1794 : // strategy will eventually unpause all paused threads, regardless of how
1795 : // many there are, since each thread we unpause will eventually finish and
1796 : // end up back here.
1797 8 : if (HelperThread* other = HelperThreadState().highestPriorityPausedIonCompile(locked)) {
1798 1 : MOZ_ASSERT(other->ionBuilder() && other->pause);
1799 :
1800 : // Only unpause the other thread if there isn't a higher priority
1801 : // builder which this thread or another can start on.
1802 1 : jit::IonBuilder* builder = HelperThreadState().highestPriorityPendingIonCompile(locked);
1803 1 : if (!builder || IonBuilderHasHigherPriority(other->ionBuilder(), builder)) {
1804 1 : other->pause = false;
1805 :
1806 : // Notify all paused threads, to make sure the one we just
1807 : // unpaused wakes up.
1808 1 : HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE, locked);
1809 : }
1810 : }
1811 8 : }
1812 :
1813 : void
1814 8 : HelperThread::handleIonFreeWorkload(AutoLockHelperThreadState& locked)
1815 : {
1816 8 : MOZ_ASSERT(idle());
1817 8 : MOZ_ASSERT(HelperThreadState().canStartIonFreeTask(locked));
1818 :
1819 8 : auto& freeList = HelperThreadState().ionFreeList(locked);
1820 :
1821 8 : jit::IonBuilder* builder = freeList.popCopy();
1822 : {
1823 16 : AutoUnlockHelperThreadState unlock(locked);
1824 8 : FreeIonBuilder(builder);
1825 : }
1826 8 : }
1827 :
1828 : HelperThread*
1829 23 : js::CurrentHelperThread()
1830 : {
1831 23 : if (!HelperThreadState().threads)
1832 0 : return nullptr;
1833 23 : auto threadId = ThisThread::GetId();
1834 175 : for (auto& thisThread : *HelperThreadState().threads) {
1835 171 : if (thisThread.thread.isSome() && threadId == thisThread.thread->get_id())
1836 19 : return &thisThread;
1837 : }
1838 4 : return nullptr;
1839 : }
1840 :
1841 : void
1842 1 : js::PauseCurrentHelperThread()
1843 : {
1844 1 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1845 2 : AutoTraceLog logPaused(logger, TraceLogger_IonCompilationPaused);
1846 :
1847 1 : HelperThread* thread = CurrentHelperThread();
1848 1 : MOZ_ASSERT(thread);
1849 :
1850 2 : AutoLockHelperThreadState lock;
1851 3 : while (thread->pause)
1852 1 : HelperThreadState().wait(lock, GlobalHelperThreadState::PAUSE);
1853 1 : }
1854 :
1855 : bool
1856 0 : JSContext::addPendingCompileError(js::CompileError** error)
1857 : {
1858 0 : auto errorPtr = make_unique<js::CompileError>();
1859 0 : if (!errorPtr)
1860 0 : return false;
1861 0 : if (!helperThread()->parseTask()->errors.append(errorPtr.get())) {
1862 0 : ReportOutOfMemory(this);
1863 0 : return false;
1864 : }
1865 0 : *error = errorPtr.release();
1866 0 : return true;
1867 : }
1868 :
1869 : void
1870 0 : JSContext::addPendingOverRecursed()
1871 : {
1872 0 : if (helperThread()->parseTask())
1873 0 : helperThread()->parseTask()->overRecursed = true;
1874 0 : }
1875 :
1876 : void
1877 0 : JSContext::addPendingOutOfMemory()
1878 : {
1879 : // Keep in sync with recoverFromOutOfMemory.
1880 0 : if (helperThread()->parseTask())
1881 0 : helperThread()->parseTask()->outOfMemory = true;
1882 0 : }
1883 :
1884 : void
1885 16 : HelperThread::handleParseWorkload(AutoLockHelperThreadState& locked)
1886 : {
1887 16 : MOZ_ASSERT(HelperThreadState().canStartParseTask(locked));
1888 16 : MOZ_ASSERT(idle());
1889 :
1890 16 : currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());
1891 16 : ParseTask* task = parseTask();
1892 :
1893 : {
1894 31 : AutoUnlockHelperThreadState unlock(locked);
1895 31 : AutoSetContextRuntime ascr(task->parseGlobal->runtimeFromAnyThread());
1896 :
1897 16 : JSContext* cx = TlsContext.get();
1898 31 : AutoCompartment ac(cx, task->parseGlobal);
1899 :
1900 16 : task->parse(cx);
1901 : }
1902 :
1903 : // The callback is invoked while we are still off thread.
1904 15 : task->callback(task, task->callbackData);
1905 :
1906 : // FinishOffThreadScript will need to be called on the script to
1907 : // migrate it into the correct compartment.
1908 : {
1909 30 : AutoEnterOOMUnsafeRegion oomUnsafe;
1910 15 : if (!HelperThreadState().parseFinishedList(locked).append(task))
1911 0 : oomUnsafe.crash("handleParseWorkload");
1912 : }
1913 :
1914 15 : currentTask.reset();
1915 :
1916 : // Notify the active thread in case it is waiting for the parse/emit to finish.
1917 15 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1918 15 : }
1919 :
1920 : void
1921 0 : HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
1922 : {
1923 0 : MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
1924 0 : MOZ_ASSERT(idle());
1925 :
1926 0 : UniquePtr<SourceCompressionTask> task;
1927 : {
1928 0 : auto& worklist = HelperThreadState().compressionWorklist(locked);
1929 0 : task = Move(worklist.back());
1930 0 : worklist.popBack();
1931 0 : currentTask.emplace(task.get());
1932 : }
1933 :
1934 : {
1935 0 : AutoUnlockHelperThreadState unlock(locked);
1936 :
1937 0 : TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1938 0 : AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
1939 :
1940 0 : task->work();
1941 : }
1942 :
1943 : {
1944 0 : AutoEnterOOMUnsafeRegion oomUnsafe;
1945 0 : if (!HelperThreadState().compressionFinishedList(locked).append(Move(task)))
1946 0 : oomUnsafe.crash("handleCompressionWorkload");
1947 : }
1948 :
1949 0 : currentTask.reset();
1950 :
1951 : // Notify the active thread in case it is waiting for the compression to finish.
1952 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
1953 0 : }
1954 :
1955 : bool
1956 154 : js::EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task)
1957 : {
1958 308 : AutoLockHelperThreadState lock;
1959 :
1960 154 : auto& pending = HelperThreadState().compressionPendingList(lock);
1961 154 : if (!pending.append(Move(task))) {
1962 0 : if (!cx->helperThread())
1963 0 : ReportOutOfMemory(cx);
1964 0 : return false;
1965 : }
1966 :
1967 154 : return true;
1968 : }
1969 :
1970 : template <typename T>
1971 : static void
1972 0 : ClearCompressionTaskList(T& list, JSRuntime* runtime)
1973 : {
1974 0 : for (size_t i = 0; i < list.length(); i++) {
1975 0 : if (list[i]->runtimeMatches(runtime))
1976 0 : HelperThreadState().remove(list, &i);
1977 : }
1978 0 : }
1979 :
1980 : void
1981 0 : js::CancelOffThreadCompressions(JSRuntime* runtime)
1982 : {
1983 0 : AutoLockHelperThreadState lock;
1984 :
1985 0 : if (!HelperThreadState().threads)
1986 0 : return;
1987 :
1988 : // Cancel all pending compression tasks.
1989 0 : ClearCompressionTaskList(HelperThreadState().compressionPendingList(lock), runtime);
1990 0 : ClearCompressionTaskList(HelperThreadState().compressionWorklist(lock), runtime);
1991 :
1992 : // Cancel all in-process compression tasks and wait for them to join so we
1993 : // clean up the finished tasks.
1994 : while (true) {
1995 0 : bool inProgress = false;
1996 0 : for (auto& thread : *HelperThreadState().threads) {
1997 0 : SourceCompressionTask* task = thread.compressionTask();
1998 0 : if (task && task->runtimeMatches(runtime))
1999 0 : inProgress = true;
2000 : }
2001 :
2002 0 : if (!inProgress)
2003 0 : break;
2004 :
2005 0 : HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
2006 0 : }
2007 :
2008 : // Clean up finished tasks.
2009 0 : ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock), runtime);
2010 : }
2011 :
2012 : bool
2013 0 : js::StartPromiseTask(JSContext* cx, UniquePtr<PromiseTask> task)
2014 : {
2015 : // Execute synchronously if there are no helper threads.
2016 0 : if (!CanUseExtraThreads())
2017 0 : return task->executeAndFinish(cx);
2018 :
2019 : // If we fail to start, by interface contract, it is because the JSContext
2020 : // is in the process of shutting down. Since promise handlers are not
2021 : // necessarily run while shutting down *anyway*, we simply ignore the error.
2022 : // This is symmetric with the handling of errors in finishAsyncTaskCallback
2023 : // which, since it is off the JSContext's owner thread, cannot report an
2024 : // error anyway.
2025 0 : if (!cx->runtime()->startAsyncTaskCallback(cx, task.get())) {
2026 0 : MOZ_ASSERT(!cx->isExceptionPending());
2027 0 : return true;
2028 : }
2029 :
2030 : // Per interface contract, after startAsyncTaskCallback succeeds,
2031 : // finishAsyncTaskCallback *must* be called on all paths.
2032 :
2033 0 : AutoLockHelperThreadState lock;
2034 :
2035 0 : if (!HelperThreadState().promiseTasks(lock).append(task.get())) {
2036 0 : Unused << cx->runtime()->finishAsyncTaskCallback(task.get());
2037 0 : ReportOutOfMemory(cx);
2038 0 : return false;
2039 : }
2040 :
2041 0 : Unused << task.release();
2042 :
2043 0 : HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
2044 0 : return true;
2045 : }
2046 :
2047 : void
2048 22 : GlobalHelperThreadState::trace(JSTracer* trc)
2049 : {
2050 44 : AutoLockHelperThreadState lock;
2051 22 : for (auto builder : ionWorklist(lock))
2052 0 : builder->trace(trc);
2053 22 : for (auto builder : ionFinishedList(lock))
2054 0 : builder->trace(trc);
2055 :
2056 22 : if (HelperThreadState().threads) {
2057 286 : for (auto& helper : *HelperThreadState().threads) {
2058 264 : if (auto builder = helper.ionBuilder())
2059 0 : builder->trace(trc);
2060 : }
2061 : }
2062 :
2063 197 : for (ZoneGroupsIter group(trc->runtime()); !group.done(); group.next()) {
2064 175 : jit::IonBuilder* builder = group->ionLazyLinkList().getFirst();
2065 179 : while (builder) {
2066 2 : builder->trace(trc);
2067 2 : builder = builder->getNext();
2068 : }
2069 : }
2070 :
2071 22 : for (auto parseTask : parseWorklist_)
2072 0 : parseTask->trace(trc);
2073 27 : for (auto parseTask : parseFinishedList_)
2074 5 : parseTask->trace(trc);
2075 22 : for (auto parseTask : parseWaitingOnGC_)
2076 0 : parseTask->trace(trc);
2077 22 : }
2078 :
2079 : void
2080 0 : HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked)
2081 : {
2082 0 : MOZ_ASSERT(HelperThreadState().canStartGCHelperTask(locked));
2083 0 : MOZ_ASSERT(idle());
2084 :
2085 0 : currentTask.emplace(HelperThreadState().gcHelperWorklist(locked).popCopy());
2086 0 : GCHelperState* task = gcHelperTask();
2087 :
2088 0 : AutoSetContextRuntime ascr(task->runtime());
2089 :
2090 : {
2091 0 : AutoUnlockHelperThreadState unlock(locked);
2092 0 : task->work();
2093 : }
2094 :
2095 0 : currentTask.reset();
2096 0 : HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
2097 0 : }
2098 :
2099 : void
2100 36 : JSContext::setHelperThread(HelperThread* thread)
2101 : {
2102 36 : helperThread_ = thread;
2103 36 : }
2104 :
2105 : void
2106 36 : HelperThread::threadLoop()
2107 : {
2108 36 : MOZ_ASSERT(CanUseExtraThreads());
2109 :
2110 35 : JS::AutoSuppressGCAnalysis nogc;
2111 36 : AutoLockHelperThreadState lock;
2112 :
2113 36 : JSContext cx(nullptr, JS::ContextOptions());
2114 : {
2115 72 : AutoEnterOOMUnsafeRegion oomUnsafe;
2116 36 : if (!cx.init(ContextKind::Background))
2117 0 : oomUnsafe.crash("HelperThread cx.init()");
2118 : }
2119 36 : cx.setHelperThread(this);
2120 36 : JS_SetNativeStackQuota(&cx, HELPER_STACK_QUOTA);
2121 :
2122 : while (true) {
2123 90 : MOZ_ASSERT(idle());
2124 :
2125 : // Block until a task is available. Save the value of whether we are
2126 : // going to do an Ion compile, in case the value returned by the method
2127 : // changes.
2128 90 : bool ionCompile = false;
2129 : while (true) {
2130 200 : if (terminate)
2131 0 : return;
2132 427 : if ((ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) ||
2133 274 : HelperThreadState().canStartWasmCompile(lock) ||
2134 274 : HelperThreadState().canStartPromiseTask(lock) ||
2135 258 : HelperThreadState().canStartParseTask(lock) ||
2136 242 : HelperThreadState().canStartCompressionTask(lock) ||
2137 242 : HelperThreadState().canStartGCHelperTask(lock) ||
2138 364 : HelperThreadState().canStartGCParallelTask(lock) ||
2139 98 : HelperThreadState().canStartIonFreeTask(lock))
2140 : {
2141 55 : break;
2142 : }
2143 90 : HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
2144 : }
2145 :
2146 55 : if (HelperThreadState().canStartGCParallelTask(lock)) {
2147 23 : js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
2148 23 : handleGCParallelWorkload(lock);
2149 32 : } else if (HelperThreadState().canStartGCHelperTask(lock)) {
2150 0 : js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
2151 0 : handleGCHelperWorkload(lock);
2152 32 : } else if (ionCompile) {
2153 8 : js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
2154 8 : handleIonWorkload(lock);
2155 24 : } else if (HelperThreadState().canStartWasmCompile(lock)) {
2156 0 : js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM);
2157 0 : handleWasmWorkload(lock);
2158 24 : } else if (HelperThreadState().canStartPromiseTask(lock)) {
2159 0 : js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK);
2160 0 : handlePromiseTaskWorkload(lock);
2161 24 : } else if (HelperThreadState().canStartParseTask(lock)) {
2162 16 : js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
2163 16 : handleParseWorkload(lock);
2164 8 : } else if (HelperThreadState().canStartCompressionTask(lock)) {
2165 0 : js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
2166 0 : handleCompressionWorkload(lock);
2167 8 : } else if (HelperThreadState().canStartIonFreeTask(lock)) {
2168 8 : js::oom::SetThreadType(js::oom::THREAD_TYPE_ION_FREE);
2169 8 : handleIonFreeWorkload(lock);
2170 : } else {
2171 0 : MOZ_CRASH("No task to perform");
2172 : }
2173 54 : }
2174 : }
|