Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "vm/HelperThreads.h"
8
9
#include "mozilla/Maybe.h"
10
#include "mozilla/ScopeExit.h"
11
#include "mozilla/Unused.h"
12
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
13
14
#include <algorithm>
15
16
#include "frontend/BytecodeCompilation.h"
17
#include "jit/IonCompileTask.h"
18
#include "js/ContextOptions.h" // JS::ContextOptions
19
#include "js/SourceText.h"
20
#include "js/UniquePtr.h"
21
#include "js/Utility.h"
22
#include "threading/CpuCount.h"
23
#include "util/NativeStack.h"
24
#include "vm/ErrorReporting.h"
25
#include "vm/SharedImmutableStringsCache.h"
26
#include "vm/Time.h"
27
#include "vm/TraceLogging.h"
28
#include "vm/Xdr.h"
29
#include "wasm/WasmGenerator.h"
30
31
#include "debugger/DebugAPI-inl.h"
32
#include "gc/ArenaList-inl.h"
33
#include "vm/JSContext-inl.h"
34
#include "vm/JSObject-inl.h"
35
#include "vm/JSScript-inl.h"
36
#include "vm/NativeObject-inl.h"
37
#include "vm/Realm-inl.h"
38
39
using namespace js;
40
41
using mozilla::Maybe;
42
using mozilla::TimeDuration;
43
using mozilla::TimeStamp;
44
using mozilla::Unused;
45
using mozilla::Utf8Unit;
46
47
using JS::CompileOptions;
48
using JS::ReadOnlyCompileOptions;
49
50
namespace js {
51
52
GlobalHelperThreadState* gHelperThreadState = nullptr;
53
54
} // namespace js
55
56
// These macros are identical in function to the same-named ones in
57
// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
58
// use GeckoProfiler.h.
59
#define PROFILER_RAII_PASTE(id, line) id##line
60
#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line)
61
#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__)
62
#define AUTO_PROFILER_LABEL(label, categoryPair) \
63
HelperThread::AutoProfilerLabel PROFILER_RAII( \
64
this, label, JS::ProfilingCategoryPair::categoryPair)
65
66
bool js::CreateHelperThreadsState() {
67
MOZ_ASSERT(!gHelperThreadState);
68
UniquePtr<GlobalHelperThreadState> helperThreadState =
69
MakeUnique<GlobalHelperThreadState>();
70
if (!helperThreadState) {
71
return false;
72
}
73
gHelperThreadState = helperThreadState.release();
74
if (!gHelperThreadState->ensureContextListForThreadCount()) {
75
js_delete(gHelperThreadState);
76
gHelperThreadState = nullptr;
77
return false;
78
}
79
return true;
80
}
81
82
void js::DestroyHelperThreadsState() {
83
if (!gHelperThreadState) {
84
return;
85
}
86
87
gHelperThreadState->finish();
88
js_delete(gHelperThreadState);
89
gHelperThreadState = nullptr;
90
}
91
92
bool js::EnsureHelperThreadsInitialized() {
93
MOZ_ASSERT(gHelperThreadState);
94
return gHelperThreadState->ensureInitialized();
95
}
96
97
static size_t ClampDefaultCPUCount(size_t cpuCount) {
98
// It's extremely rare for SpiderMonkey to have more than a few cores worth
99
// of work. At higher core counts, performance can even decrease due to NUMA
100
// (and SpiderMonkey's lack of NUMA-awareness), contention, and general lack
101
// of optimization for high core counts. So to avoid wasting thread stack
102
// resources (and cluttering gdb and core dumps), clamp to 8 cores for now.
103
return std::min<size_t>(cpuCount, 8);
104
}
105
106
static size_t ThreadCountForCPUCount(size_t cpuCount) {
107
// We need at least two threads for tier-2 wasm compilations, because
108
// there's a master task that holds a thread while other threads do the
109
// compilation.
110
return std::max<size_t>(cpuCount, 2);
111
}
112
113
bool js::SetFakeCPUCount(size_t count) {
114
// This must be called before the threads have been initialized.
115
MOZ_ASSERT(!HelperThreadState().threads);
116
117
HelperThreadState().cpuCount = count;
118
HelperThreadState().threadCount = ThreadCountForCPUCount(count);
119
120
if (!HelperThreadState().ensureContextListForThreadCount()) {
121
return false;
122
}
123
return true;
124
}
125
126
void JS::SetProfilingThreadCallbacks(
127
JS::RegisterThreadCallback registerThread,
128
JS::UnregisterThreadCallback unregisterThread) {
129
HelperThreadState().registerThread = registerThread;
130
HelperThreadState().unregisterThread = unregisterThread;
131
}
132
133
bool js::StartOffThreadWasmCompile(wasm::CompileTask* task,
134
wasm::CompileMode mode) {
135
AutoLockHelperThreadState lock;
136
137
if (!HelperThreadState().wasmWorklist(lock, mode).pushBack(task)) {
138
return false;
139
}
140
141
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
142
return true;
143
}
144
145
void js::StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task) {
146
MOZ_ASSERT(CanUseExtraThreads());
147
148
AutoLockHelperThreadState lock;
149
150
if (!HelperThreadState().wasmTier2GeneratorWorklist(lock).append(
151
task.get())) {
152
return;
153
}
154
155
Unused << task.release();
156
157
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
158
}
159
160
static void CancelOffThreadWasmTier2GeneratorLocked(
161
AutoLockHelperThreadState& lock) {
162
if (!HelperThreadState().threads) {
163
return;
164
}
165
166
// Remove pending tasks from the tier2 generator worklist and cancel and
167
// delete them.
168
{
169
wasm::Tier2GeneratorTaskPtrVector& worklist =
170
HelperThreadState().wasmTier2GeneratorWorklist(lock);
171
for (size_t i = 0; i < worklist.length(); i++) {
172
wasm::Tier2GeneratorTask* task = worklist[i];
173
HelperThreadState().remove(worklist, &i);
174
js_delete(task);
175
}
176
}
177
178
// There is at most one running Tier2Generator task and we assume that
179
// below.
180
static_assert(GlobalHelperThreadState::MaxTier2GeneratorTasks == 1,
181
"code must be generalized");
182
183
// If there is a running Tier2 generator task, shut it down in a predictable
184
// way. The task will be deleted by the normal deletion logic.
185
for (auto& helper : *HelperThreadState().threads) {
186
if (helper.wasmTier2GeneratorTask()) {
187
// Set a flag that causes compilation to shortcut itself.
188
helper.wasmTier2GeneratorTask()->cancel();
189
190
// Wait for the generator task to finish. This avoids a shutdown race
191
// where the shutdown code is trying to shut down helper threads and the
192
// ongoing tier2 compilation is trying to finish, which requires it to
193
// have access to helper threads.
194
uint32_t oldFinishedCount =
195
HelperThreadState().wasmTier2GeneratorsFinished(lock);
196
while (HelperThreadState().wasmTier2GeneratorsFinished(lock) ==
197
oldFinishedCount) {
198
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
199
}
200
201
// At most one of these tasks.
202
break;
203
}
204
}
205
}
206
207
void js::CancelOffThreadWasmTier2Generator() {
208
AutoLockHelperThreadState lock;
209
CancelOffThreadWasmTier2GeneratorLocked(lock);
210
}
211
212
bool js::StartOffThreadIonCompile(jit::IonCompileTask* task,
213
const AutoLockHelperThreadState& lock) {
214
if (!HelperThreadState().ionWorklist(lock).append(task)) {
215
return false;
216
}
217
218
// The build is moving off-thread. Freeze the LifoAlloc to prevent any
219
// unwanted mutations.
220
task->alloc().lifoAlloc()->setReadOnly();
221
222
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
223
return true;
224
}
225
226
bool js::StartOffThreadIonFree(jit::IonCompileTask* task,
227
const AutoLockHelperThreadState& lock) {
228
MOZ_ASSERT(CanUseExtraThreads());
229
230
if (!HelperThreadState().ionFreeList(lock).append(task)) {
231
return false;
232
}
233
234
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
235
return true;
236
}
237
238
/*
239
* Move an IonCompilationTask for which compilation has either finished, failed,
240
* or been cancelled into the global finished compilation list. All off thread
241
* compilations which are started must eventually be finished.
242
*/
243
static void FinishOffThreadIonCompile(jit::IonCompileTask* task,
244
const AutoLockHelperThreadState& lock) {
245
AutoEnterOOMUnsafeRegion oomUnsafe;
246
if (!HelperThreadState().ionFinishedList(lock).append(task)) {
247
oomUnsafe.crash("FinishOffThreadIonCompile");
248
}
249
task->script()
250
->runtimeFromAnyThread()
251
->jitRuntime()
252
->numFinishedOffThreadTasksRef(lock)++;
253
}
254
255
static JSRuntime* GetSelectorRuntime(const CompilationSelector& selector) {
256
struct Matcher {
257
JSRuntime* operator()(JSScript* script) {
258
return script->runtimeFromMainThread();
259
}
260
JSRuntime* operator()(Realm* realm) {
261
return realm->runtimeFromMainThread();
262
}
263
JSRuntime* operator()(Zone* zone) { return zone->runtimeFromMainThread(); }
264
JSRuntime* operator()(ZonesInState zbs) { return zbs.runtime; }
265
JSRuntime* operator()(JSRuntime* runtime) { return runtime; }
266
JSRuntime* operator()(CompilationsUsingNursery cun) { return cun.runtime; }
267
};
268
269
return selector.match(Matcher());
270
}
271
272
static bool JitDataStructuresExist(const CompilationSelector& selector) {
273
struct Matcher {
274
bool operator()(JSScript* script) { return !!script->realm()->jitRealm(); }
275
bool operator()(Realm* realm) { return !!realm->jitRealm(); }
276
bool operator()(Zone* zone) { return !!zone->jitZone(); }
277
bool operator()(ZonesInState zbs) { return zbs.runtime->hasJitRuntime(); }
278
bool operator()(JSRuntime* runtime) { return runtime->hasJitRuntime(); }
279
bool operator()(CompilationsUsingNursery cun) {
280
return cun.runtime->hasJitRuntime();
281
}
282
};
283
284
return selector.match(Matcher());
285
}
286
287
static bool IonCompileTaskMatches(const CompilationSelector& selector,
288
jit::IonCompileTask* task) {
289
struct TaskMatches {
290
jit::IonCompileTask* task_;
291
292
bool operator()(JSScript* script) { return script == task_->script(); }
293
bool operator()(Realm* realm) { return realm == task_->script()->realm(); }
294
bool operator()(Zone* zone) {
295
return zone == task_->script()->zoneFromAnyThread();
296
}
297
bool operator()(JSRuntime* runtime) {
298
return runtime == task_->script()->runtimeFromAnyThread();
299
}
300
bool operator()(ZonesInState zbs) {
301
return zbs.runtime == task_->script()->runtimeFromAnyThread() &&
302
zbs.state == task_->script()->zoneFromAnyThread()->gcState();
303
}
304
bool operator()(CompilationsUsingNursery cun) {
305
return cun.runtime == task_->script()->runtimeFromAnyThread() &&
306
!task_->mirGen().safeForMinorGC();
307
}
308
};
309
310
return selector.match(TaskMatches{task});
311
}
312
313
static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector,
314
AutoLockHelperThreadState& lock) {
315
if (!HelperThreadState().threads) {
316
return;
317
}
318
319
/* Cancel any pending entries for which processing hasn't started. */
320
GlobalHelperThreadState::IonCompileTaskVector& worklist =
321
HelperThreadState().ionWorklist(lock);
322
for (size_t i = 0; i < worklist.length(); i++) {
323
jit::IonCompileTask* task = worklist[i];
324
if (IonCompileTaskMatches(selector, task)) {
325
// Once finished, tasks are added to a Linked list which is
326
// allocated with the IonCompileTask class. The IonCompileTask is
327
// allocated in the LifoAlloc so we need the LifoAlloc to be mutable.
328
worklist[i]->alloc().lifoAlloc()->setReadWrite();
329
330
FinishOffThreadIonCompile(task, lock);
331
HelperThreadState().remove(worklist, &i);
332
}
333
}
334
335
/* Wait for in progress entries to finish up. */
336
bool cancelled;
337
do {
338
cancelled = false;
339
for (auto& helper : *HelperThreadState().threads) {
340
if (helper.ionCompileTask() &&
341
IonCompileTaskMatches(selector, helper.ionCompileTask())) {
342
helper.ionCompileTask()->mirGen().cancel();
343
cancelled = true;
344
}
345
}
346
if (cancelled) {
347
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
348
}
349
} while (cancelled);
350
351
/* Cancel code generation for any completed entries. */
352
GlobalHelperThreadState::IonCompileTaskVector& finished =
353
HelperThreadState().ionFinishedList(lock);
354
for (size_t i = 0; i < finished.length(); i++) {
355
jit::IonCompileTask* task = finished[i];
356
if (IonCompileTaskMatches(selector, task)) {
357
JSRuntime* rt = task->script()->runtimeFromAnyThread();
358
rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--;
359
jit::FinishOffThreadTask(rt, task, lock);
360
HelperThreadState().remove(finished, &i);
361
}
362
}
363
364
/* Cancel lazy linking for pending tasks (attached to the ionScript). */
365
JSRuntime* runtime = GetSelectorRuntime(selector);
366
jit::IonCompileTask* task =
367
runtime->jitRuntime()->ionLazyLinkList(runtime).getFirst();
368
while (task) {
369
jit::IonCompileTask* next = task->getNext();
370
if (IonCompileTaskMatches(selector, task)) {
371
jit::FinishOffThreadTask(runtime, task, lock);
372
}
373
task = next;
374
}
375
}
376
377
void js::CancelOffThreadIonCompile(const CompilationSelector& selector) {
378
if (!JitDataStructuresExist(selector)) {
379
return;
380
}
381
382
AutoLockHelperThreadState lock;
383
CancelOffThreadIonCompileLocked(selector, lock);
384
}
385
386
#ifdef DEBUG
387
bool js::HasOffThreadIonCompile(Realm* realm) {
388
AutoLockHelperThreadState lock;
389
390
if (!HelperThreadState().threads) {
391
return false;
392
}
393
394
GlobalHelperThreadState::IonCompileTaskVector& worklist =
395
HelperThreadState().ionWorklist(lock);
396
for (size_t i = 0; i < worklist.length(); i++) {
397
jit::IonCompileTask* task = worklist[i];
398
if (task->script()->realm() == realm) {
399
return true;
400
}
401
}
402
403
for (auto& helper : *HelperThreadState().threads) {
404
if (helper.ionCompileTask() &&
405
helper.ionCompileTask()->script()->realm() == realm) {
406
return true;
407
}
408
}
409
410
GlobalHelperThreadState::IonCompileTaskVector& finished =
411
HelperThreadState().ionFinishedList(lock);
412
for (size_t i = 0; i < finished.length(); i++) {
413
jit::IonCompileTask* task = finished[i];
414
if (task->script()->realm() == realm) {
415
return true;
416
}
417
}
418
419
JSRuntime* rt = realm->runtimeFromMainThread();
420
jit::IonCompileTask* task = rt->jitRuntime()->ionLazyLinkList(rt).getFirst();
421
while (task) {
422
if (task->script()->realm() == realm) {
423
return true;
424
}
425
task = task->getNext();
426
}
427
428
return false;
429
}
430
#endif
431
432
struct MOZ_RAII AutoSetContextParse {
433
explicit AutoSetContextParse(ParseTask* task) {
434
TlsContext.get()->setParseTask(task);
435
}
436
~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); }
437
};
438
439
// We want our default stack size limit to be approximately 2MB, to be safe, but
440
// expect most threads to use much less. On Linux, however, requesting a stack
441
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
442
// on first access, which we do not want. To avoid this possibility, we subtract
443
// 2 standard VM page sizes from our default.
444
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
445
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
446
447
// TSan enforces a minimum stack size that's just slightly larger than our
448
// default helper stack size. It does this to store blobs of TSan-specific
449
// data on each thread's stack. Unfortunately, that means that even though
450
// we'll actually receive a larger stack than we requested, the effective
451
// usable space of that stack is significantly less than what we expect.
452
// To offset TSan stealing our stack space from underneath us, double the
453
// default.
454
//
455
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
456
// require all the thread-specific state that TSan does.
457
#if defined(MOZ_TSAN)
458
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
459
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
460
#else
461
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
462
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
463
#endif
464
465
AutoSetHelperThreadContext::AutoSetHelperThreadContext() {
466
AutoLockHelperThreadState lock;
467
cx = HelperThreadState().getFirstUnusedContext(lock);
468
MOZ_ASSERT(cx);
469
cx->setHelperThread(lock);
470
cx->nativeStackBase = GetNativeStackBase();
471
// When we set the JSContext, we need to reset the computed stack limits for
472
// the current thread, so we also set the native stack quota.
473
JS_SetNativeStackQuota(cx, HELPER_STACK_QUOTA);
474
}
475
476
static const JSClass parseTaskGlobalClass = {"internal-parse-task-global",
477
JSCLASS_GLOBAL_FLAGS,
478
&JS::DefaultGlobalClassOps};
479
480
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx,
481
JS::OffThreadCompileCallback callback, void* callbackData)
482
: kind(kind),
483
options(cx),
484
parseGlobal(nullptr),
485
callback(callback),
486
callbackData(callbackData),
487
overRecursed(false),
488
outOfMemory(false) {
489
// Note that |cx| is the main thread context here but the parse task will
490
// run with a different, helper thread, context.
491
MOZ_ASSERT(!cx->isHelperThreadContext());
492
493
MOZ_ALWAYS_TRUE(scripts.reserve(scripts.capacity()));
494
MOZ_ALWAYS_TRUE(sourceObjects.reserve(sourceObjects.capacity()));
495
}
496
497
bool ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options,
498
JSObject* global) {
499
MOZ_ASSERT(!cx->isHelperThreadContext());
500
501
if (!this->options.copy(cx, options)) {
502
return false;
503
}
504
505
parseGlobal = global;
506
return true;
507
}
508
509
void ParseTask::activate(JSRuntime* rt) {
510
rt->setUsedByHelperThread(parseGlobal->zone());
511
}
512
513
ParseTask::~ParseTask() = default;
514
515
void ParseTask::trace(JSTracer* trc) {
516
if (parseGlobal->runtimeFromAnyThread() != trc->runtime()) {
517
return;
518
}
519
520
Zone* zone = MaybeForwarded(parseGlobal)->zoneFromAnyThread();
521
if (zone->usedByHelperThread()) {
522
MOZ_ASSERT(!zone->isCollecting());
523
return;
524
}
525
526
TraceRoot(trc, &parseGlobal, "ParseTask::parseGlobal");
527
scripts.trace(trc);
528
sourceObjects.trace(trc);
529
}
530
531
size_t ParseTask::sizeOfExcludingThis(
532
mozilla::MallocSizeOf mallocSizeOf) const {
533
return options.sizeOfExcludingThis(mallocSizeOf) +
534
errors.sizeOfExcludingThis(mallocSizeOf);
535
}
536
537
void ParseTask::runTask() {
538
AutoSetHelperThreadContext usesContext;
539
540
JSContext* cx = TlsContext.get();
541
JSRuntime* runtime = parseGlobal->runtimeFromAnyThread();
542
543
AutoSetContextRuntime ascr(runtime);
544
AutoSetContextParse parsetask(this);
545
gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx);
546
547
Zone* zone = parseGlobal->zoneFromAnyThread();
548
zone->setHelperThreadOwnerContext(cx);
549
auto resetOwnerContext = mozilla::MakeScopeExit(
550
[&] { zone->setHelperThreadOwnerContext(nullptr); });
551
552
AutoRealm ar(cx, parseGlobal);
553
554
parse(cx);
555
556
MOZ_ASSERT(cx->tempLifoAlloc().isEmpty());
557
cx->tempLifoAlloc().freeAll();
558
cx->frontendCollectionPool().purge();
559
cx->atomsZoneFreeLists().clear();
560
}
561
562
template <typename Unit>
563
struct ScriptParseTask : public ParseTask {
564
JS::SourceText<Unit> data;
565
566
ScriptParseTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
567
JS::OffThreadCompileCallback callback, void* callbackData);
568
void parse(JSContext* cx) override;
569
};
570
571
template <typename Unit>
572
ScriptParseTask<Unit>::ScriptParseTask(JSContext* cx,
573
JS::SourceText<Unit>& srcBuf,
574
JS::OffThreadCompileCallback callback,
575
void* callbackData)
576
: ParseTask(ParseTaskKind::Script, cx, callback, callbackData),
577
data(std::move(srcBuf)) {}
578
579
template <typename Unit>
580
void ScriptParseTask<Unit>::parse(JSContext* cx) {
581
MOZ_ASSERT(cx->isHelperThreadContext());
582
583
ScopeKind scopeKind =
584
options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
585
LifoAllocScope allocScope(&cx->tempLifoAlloc());
586
frontend::CompilationInfo compilationInfo(cx, allocScope, options);
587
if (!compilationInfo.init(cx)) {
588
return;
589
}
590
591
// Whatever happens to the top-level script compilation (even if it fails),
592
// we must finish initializing the SSO. This is because there may be valid
593
// inner scripts observable by the debugger which reference the partially-
594
// initialized SSO.
595
sourceObjects.infallibleAppend(compilationInfo.sourceObject);
596
597
frontend::GlobalSharedContext globalsc(cx, scopeKind, compilationInfo,
598
compilationInfo.directives);
599
JSScript* script =
600
frontend::CompileGlobalScript(compilationInfo, globalsc, data);
601
602
if (script) {
603
scripts.infallibleAppend(script);
604
}
605
}
606
607
template <typename Unit>
608
struct ModuleParseTask : public ParseTask {
609
JS::SourceText<Unit> data;
610
611
ModuleParseTask(JSContext* cx, JS::SourceText<Unit>& srcBuf,
612
JS::OffThreadCompileCallback callback, void* callbackData);
613
void parse(JSContext* cx) override;
614
};
615
616
template <typename Unit>
617
ModuleParseTask<Unit>::ModuleParseTask(JSContext* cx,
618
JS::SourceText<Unit>& srcBuf,
619
JS::OffThreadCompileCallback callback,
620
void* callbackData)
621
: ParseTask(ParseTaskKind::Module, cx, callback, callbackData),
622
data(std::move(srcBuf)) {}
623
624
template <typename Unit>
625
void ModuleParseTask<Unit>::parse(JSContext* cx) {
626
MOZ_ASSERT(cx->isHelperThreadContext());
627
628
Rooted<ScriptSourceObject*> sourceObject(cx);
629
630
ModuleObject* module =
631
frontend::ParseModule(cx, options, data, &sourceObject.get());
632
if (module) {
633
scripts.infallibleAppend(module->script());
634
if (sourceObject) {
635
sourceObjects.infallibleAppend(sourceObject);
636
}
637
}
638
}
639
640
ScriptDecodeTask::ScriptDecodeTask(JSContext* cx,
641
const JS::TranscodeRange& range,
642
JS::OffThreadCompileCallback callback,
643
void* callbackData)
644
: ParseTask(ParseTaskKind::ScriptDecode, cx, callback, callbackData),
645
range(range) {}
646
647
void ScriptDecodeTask::parse(JSContext* cx) {
648
MOZ_ASSERT(cx->isHelperThreadContext());
649
650
RootedScript resultScript(cx);
651
Rooted<ScriptSourceObject*> sourceObject(cx);
652
653
Rooted<UniquePtr<XDROffThreadDecoder>> decoder(
654
cx,
655
js::MakeUnique<XDROffThreadDecoder>(
656
cx, &options, /* sourceObjectOut = */ &sourceObject.get(), range));
657
if (!decoder) {
658
ReportOutOfMemory(cx);
659
return;
660
}
661
XDRResult res = decoder->codeScript(&resultScript);
662
MOZ_ASSERT(bool(resultScript) == res.isOk());
663
if (res.isOk()) {
664
scripts.infallibleAppend(resultScript);
665
if (sourceObject) {
666
sourceObjects.infallibleAppend(sourceObject);
667
}
668
}
669
}
670
671
#if defined(JS_BUILD_BINAST)
672
673
BinASTDecodeTask::BinASTDecodeTask(JSContext* cx, const uint8_t* buf,
674
size_t length, JS::BinASTFormat format,
675
JS::OffThreadCompileCallback callback,
676
void* callbackData)
677
: ParseTask(ParseTaskKind::BinAST, cx, callback, callbackData),
678
data(buf, length),
679
format(format) {}
680
681
void BinASTDecodeTask::parse(JSContext* cx) {
682
MOZ_ASSERT(cx->isHelperThreadContext());
683
684
RootedScriptSourceObject sourceObject(cx);
685
686
JSScript* script = frontend::CompileGlobalBinASTScript(
687
cx, options, data.begin().get(), data.length(), format,
688
&sourceObject.get());
689
if (script) {
690
scripts.infallibleAppend(script);
691
if (sourceObject) {
692
sourceObjects.infallibleAppend(sourceObject);
693
}
694
}
695
}
696
697
#endif /* JS_BUILD_BINAST */
698
699
MultiScriptsDecodeTask::MultiScriptsDecodeTask(
700
JSContext* cx, JS::TranscodeSources& sources,
701
JS::OffThreadCompileCallback callback, void* callbackData)
702
: ParseTask(ParseTaskKind::MultiScriptsDecode, cx, callback, callbackData),
703
sources(&sources) {}
704
705
void MultiScriptsDecodeTask::parse(JSContext* cx) {
706
MOZ_ASSERT(cx->isHelperThreadContext());
707
708
if (!scripts.reserve(sources->length()) ||
709
!sourceObjects.reserve(sources->length())) {
710
ReportOutOfMemory(cx); // This sets |outOfMemory|.
711
return;
712
}
713
714
for (auto& source : *sources) {
715
CompileOptions opts(cx, options);
716
opts.setFileAndLine(source.filename, source.lineno);
717
718
RootedScript resultScript(cx);
719
Rooted<ScriptSourceObject*> sourceObject(cx);
720
721
Rooted<UniquePtr<XDROffThreadDecoder>> decoder(
722
cx, js::MakeUnique<XDROffThreadDecoder>(cx, &opts, &sourceObject.get(),
723
source.range));
724
if (!decoder) {
725
ReportOutOfMemory(cx);
726
return;
727
}
728
XDRResult res = decoder->codeScript(&resultScript);
729
MOZ_ASSERT(bool(resultScript) == res.isOk());
730
731
if (res.isErr()) {
732
break;
733
}
734
MOZ_ASSERT(resultScript);
735
scripts.infallibleAppend(resultScript);
736
sourceObjects.infallibleAppend(sourceObject);
737
}
738
}
739
740
void js::CancelOffThreadParses(JSRuntime* rt) {
741
AutoLockHelperThreadState lock;
742
743
if (!HelperThreadState().threads) {
744
return;
745
}
746
747
#ifdef DEBUG
748
GlobalHelperThreadState::ParseTaskVector& waitingOnGC =
749
HelperThreadState().parseWaitingOnGC(lock);
750
for (size_t i = 0; i < waitingOnGC.length(); i++) {
751
MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));
752
}
753
#endif
754
755
// Instead of forcibly canceling pending parse tasks, just wait for all
756
// scheduled and in progress ones to complete. Otherwise the final GC may not
757
// collect everything due to zones being used off thread.
758
while (true) {
759
bool pending = false;
760
GlobalHelperThreadState::ParseTaskVector& worklist =
761
HelperThreadState().parseWorklist(lock);
762
for (size_t i = 0; i < worklist.length(); i++) {
763
ParseTask* task = worklist[i];
764
if (task->runtimeMatches(rt)) {
765
pending = true;
766
}
767
}
768
if (!pending) {
769
bool inProgress = false;
770
for (auto& thread : *HelperThreadState().threads) {
771
ParseTask* task = thread.parseTask();
772
if (task && task->runtimeMatches(rt)) {
773
inProgress = true;
774
}
775
}
776
if (!inProgress) {
777
break;
778
}
779
}
780
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
781
}
782
783
// Clean up any parse tasks which haven't been finished by the main thread.
784
auto& finished = HelperThreadState().parseFinishedList(lock);
785
while (true) {
786
bool found = false;
787
ParseTask* next;
788
ParseTask* task = finished.getFirst();
789
while (task) {
790
next = task->getNext();
791
if (task->runtimeMatches(rt)) {
792
found = true;
793
task->remove();
794
HelperThreadState().destroyParseTask(rt, task);
795
}
796
task = next;
797
}
798
if (!found) {
799
break;
800
}
801
}
802
803
#ifdef DEBUG
804
GlobalHelperThreadState::ParseTaskVector& worklist =
805
HelperThreadState().parseWorklist(lock);
806
for (size_t i = 0; i < worklist.length(); i++) {
807
ParseTask* task = worklist[i];
808
MOZ_ASSERT(!task->runtimeMatches(rt));
809
}
810
#endif
811
}
812
813
bool js::OffThreadParsingMustWaitForGC(JSRuntime* rt) {
814
// Off thread parsing can't occur during incremental collections on the
815
// atoms zone, to avoid triggering barriers. (Outside the atoms zone, the
816
// compilation will use a new zone that is never collected.) If an
817
// atoms-zone GC is in progress, hold off on executing the parse task until
818
// the atoms-zone GC completes (see EnqueuePendingParseTasksAfterGC).
819
return rt->activeGCInAtomsZone();
820
}
821
822
static bool EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global,
823
JSProtoKey key) {
824
if (!GlobalObject::ensureConstructor(cx, global, key)) {
825
return false;
826
}
827
828
MOZ_ASSERT(global->getPrototype(key).toObject().isDelegate(),
829
"standard class prototype wasn't a delegate from birth");
830
return true;
831
}
832
833
// Initialize all classes potentially created during parsing for use in parser
834
// data structures, template objects, &c.
835
static bool EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind) {
836
Handle<GlobalObject*> global = cx->global();
837
838
if (!EnsureConstructor(cx, global, JSProto_Function)) {
839
return false; // needed by functions, also adds object literals' proto
840
}
841
842
if (!EnsureConstructor(cx, global, JSProto_Array)) {
843
return false; // needed by array literals
844
}
845
846
if (!EnsureConstructor(cx, global, JSProto_RegExp)) {
847
return false; // needed by regular expression literals
848
}
849
850
if (!EnsureConstructor(cx, global, JSProto_GeneratorFunction)) {
851
return false; // needed by function*() {}
852
}
853
854
if (!EnsureConstructor(cx, global, JSProto_AsyncFunction)) {
855
return false; // needed by async function() {}
856
}
857
858
if (!EnsureConstructor(cx, global, JSProto_AsyncGeneratorFunction)) {
859
return false; // needed by async function*() {}
860
}
861
862
if (kind == ParseTaskKind::Module &&
863
!GlobalObject::ensureModulePrototypesCreated(cx, global)) {
864
return false;
865
}
866
867
return true;
868
}
869
870
class MOZ_RAII AutoSetCreatedForHelperThread {
871
Zone* zone;
872
873
public:
874
explicit AutoSetCreatedForHelperThread(JSObject* global)
875
: zone(global->zone()) {
876
zone->setCreatedForHelperThread();
877
}
878
879
void forget() { zone = nullptr; }
880
881
~AutoSetCreatedForHelperThread() {
882
if (zone) {
883
zone->clearUsedByHelperThread();
884
}
885
}
886
};
887
888
static JSObject* CreateGlobalForOffThreadParse(JSContext* cx,
889
const gc::AutoSuppressGC& nogc) {
890
JS::Realm* currentRealm = cx->realm();
891
892
JS::RealmOptions realmOptions(currentRealm->creationOptions(),
893
currentRealm->behaviors());
894
895
auto& creationOptions = realmOptions.creationOptions();
896
897
creationOptions.setInvisibleToDebugger(true)
898
.setMergeable(true)
899
.setNewCompartmentAndZone();
900
901
// Don't falsely inherit the host's global trace hook.
902
creationOptions.setTrace(nullptr);
903
904
return JS_NewGlobalObject(cx, &parseTaskGlobalClass,
905
currentRealm->principals(),
906
JS::DontFireOnNewGlobalHook, realmOptions);
907
}
908
909
static bool QueueOffThreadParseTask(JSContext* cx, UniquePtr<ParseTask> task) {
910
AutoLockHelperThreadState lock;
911
912
bool mustWait = OffThreadParsingMustWaitForGC(cx->runtime());
913
914
// Append null first, then overwrite it on success, to avoid having two
915
// |task| pointers (one ostensibly "unique") in flight at once. (Obviously it
916
// would be better if these vectors stored unique pointers themselves....)
917
auto& queue = mustWait ? HelperThreadState().parseWaitingOnGC(lock)
918
: HelperThreadState().parseWorklist(lock);
919
if (!queue.append(nullptr)) {
920
ReportOutOfMemory(cx);
921
return false;
922
}
923
924
queue.back() = task.release();
925
926
if (!mustWait) {
927
queue.back()->activate(cx->runtime());
928
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
929
}
930
931
return true;
932
}
933
934
static bool StartOffThreadParseTask(JSContext* cx, UniquePtr<ParseTask> task,
935
const ReadOnlyCompileOptions& options) {
936
// Suppress GC so that calls below do not trigger a new incremental GC
937
// which could require barriers on the atoms zone.
938
gc::AutoSuppressGC nogc(cx);
939
gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx);
940
AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
941
942
JSObject* global = CreateGlobalForOffThreadParse(cx, nogc);
943
if (!global) {
944
return false;
945
}
946
947
// Mark the global's zone as created for a helper thread. This prevents it
948
// from being collected until clearUsedByHelperThread() is called after
949
// parsing is complete. If this function exits due to error this state is
950
// cleared automatically.
951
AutoSetCreatedForHelperThread createdForHelper(global);
952
953
if (!task->init(cx, options, global)) {
954
return false;
955
}
956
957
if (!QueueOffThreadParseTask(cx, std::move(task))) {
958
return false;
959
}
960
961
createdForHelper.forget();
962
return true;
963
}
964
965
template <typename Unit>
966
static bool StartOffThreadParseScriptInternal(
967
JSContext* cx, const ReadOnlyCompileOptions& options,
968
JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
969
void* callbackData) {
970
auto task = cx->make_unique<ScriptParseTask<Unit>>(cx, srcBuf, callback,
971
callbackData);
972
if (!task) {
973
return false;
974
}
975
976
return StartOffThreadParseTask(cx, std::move(task), options);
977
}
978
979
bool js::StartOffThreadParseScript(JSContext* cx,
980
const ReadOnlyCompileOptions& options,
981
JS::SourceText<char16_t>& srcBuf,
982
JS::OffThreadCompileCallback callback,
983
void* callbackData) {
984
return StartOffThreadParseScriptInternal(cx, options, srcBuf, callback,
985
callbackData);
986
}
987
988
bool js::StartOffThreadParseScript(JSContext* cx,
989
const ReadOnlyCompileOptions& options,
990
JS::SourceText<Utf8Unit>& srcBuf,
991
JS::OffThreadCompileCallback callback,
992
void* callbackData) {
993
return StartOffThreadParseScriptInternal(cx, options, srcBuf, callback,
994
callbackData);
995
}
996
997
template <typename Unit>
998
static bool StartOffThreadParseModuleInternal(
999
JSContext* cx, const ReadOnlyCompileOptions& options,
1000
JS::SourceText<Unit>& srcBuf, JS::OffThreadCompileCallback callback,
1001
void* callbackData) {
1002
auto task = cx->make_unique<ModuleParseTask<Unit>>(cx, srcBuf, callback,
1003
callbackData);
1004
if (!task) {
1005
return false;
1006
}
1007
1008
return StartOffThreadParseTask(cx, std::move(task), options);
1009
}
1010
1011
bool js::StartOffThreadParseModule(JSContext* cx,
1012
const ReadOnlyCompileOptions& options,
1013
JS::SourceText<char16_t>& srcBuf,
1014
JS::OffThreadCompileCallback callback,
1015
void* callbackData) {
1016
return StartOffThreadParseModuleInternal(cx, options, srcBuf, callback,
1017
callbackData);
1018
}
1019
1020
bool js::StartOffThreadParseModule(JSContext* cx,
1021
const ReadOnlyCompileOptions& options,
1022
JS::SourceText<Utf8Unit>& srcBuf,
1023
JS::OffThreadCompileCallback callback,
1024
void* callbackData) {
1025
return StartOffThreadParseModuleInternal(cx, options, srcBuf, callback,
1026
callbackData);
1027
}
1028
1029
bool js::StartOffThreadDecodeScript(JSContext* cx,
1030
const ReadOnlyCompileOptions& options,
1031
const JS::TranscodeRange& range,
1032
JS::OffThreadCompileCallback callback,
1033
void* callbackData) {
1034
auto task =
1035
cx->make_unique<ScriptDecodeTask>(cx, range, callback, callbackData);
1036
if (!task) {
1037
return false;
1038
}
1039
1040
return StartOffThreadParseTask(cx, std::move(task), options);
1041
}
1042
1043
bool js::StartOffThreadDecodeMultiScripts(JSContext* cx,
1044
const ReadOnlyCompileOptions& options,
1045
JS::TranscodeSources& sources,
1046
JS::OffThreadCompileCallback callback,
1047
void* callbackData) {
1048
auto task = cx->make_unique<MultiScriptsDecodeTask>(cx, sources, callback,
1049
callbackData);
1050
if (!task) {
1051
return false;
1052
}
1053
1054
return StartOffThreadParseTask(cx, std::move(task), options);
1055
}
1056
1057
#if defined(JS_BUILD_BINAST)
1058
1059
bool js::StartOffThreadDecodeBinAST(JSContext* cx,
1060
const ReadOnlyCompileOptions& options,
1061
const uint8_t* buf, size_t length,
1062
JS::BinASTFormat format,
1063
JS::OffThreadCompileCallback callback,
1064
void* callbackData) {
1065
if (!cx->runtime()->binast().ensureBinTablesInitialized(cx)) {
1066
return false;
1067
}
1068
1069
auto task = cx->make_unique<BinASTDecodeTask>(cx, buf, length, format,
1070
callback, callbackData);
1071
if (!task) {
1072
return false;
1073
}
1074
1075
return StartOffThreadParseTask(cx, std::move(task), options);
1076
}
1077
1078
#endif /* JS_BUILD_BINAST */
1079
1080
void js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt) {
1081
MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
1082
1083
GlobalHelperThreadState::ParseTaskVector newTasks;
1084
{
1085
AutoLockHelperThreadState lock;
1086
GlobalHelperThreadState::ParseTaskVector& waiting =
1087
HelperThreadState().parseWaitingOnGC(lock);
1088
1089
for (size_t i = 0; i < waiting.length(); i++) {
1090
ParseTask* task = waiting[i];
1091
if (task->runtimeMatches(rt)) {
1092
AutoEnterOOMUnsafeRegion oomUnsafe;
1093
if (!newTasks.append(task)) {
1094
oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
1095
}
1096
HelperThreadState().remove(waiting, &i);
1097
}
1098
}
1099
}
1100
1101
if (newTasks.empty()) {
1102
return;
1103
}
1104
1105
// This logic should mirror the contents of the
1106
// !OffThreadParsingMustWaitForGC() branch in QueueOffThreadParseTask:
1107
1108
for (size_t i = 0; i < newTasks.length(); i++) {
1109
newTasks[i]->activate(rt);
1110
}
1111
1112
AutoLockHelperThreadState lock;
1113
1114
{
1115
AutoEnterOOMUnsafeRegion oomUnsafe;
1116
if (!HelperThreadState().parseWorklist(lock).appendAll(newTasks)) {
1117
oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
1118
}
1119
}
1120
1121
HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
1122
}
1123
1124
#ifdef DEBUG
1125
bool js::CurrentThreadIsParseThread() {
1126
JSContext* cx = TlsContext.get();
1127
return cx->isHelperThreadContext() && cx->parseTask();
1128
}
1129
#endif
1130
1131
bool GlobalHelperThreadState::ensureInitialized() {
1132
MOZ_ASSERT(CanUseExtraThreads());
1133
1134
MOZ_ASSERT(this == &HelperThreadState());
1135
1136
{
1137
// We must not hold this lock during the error handling code below.
1138
AutoLockHelperThreadState lock;
1139
1140
if (threads) {
1141
return true;
1142
}
1143
1144
threads = js::MakeUnique<HelperThreadVector>();
1145
if (!threads) {
1146
return false;
1147
}
1148
if (!threads->initCapacity(threadCount)) {
1149
goto error;
1150
}
1151
1152
for (size_t i = 0; i < threadCount; i++) {
1153
threads->infallibleEmplaceBack();
1154
HelperThread& helper = (*threads)[i];
1155
1156
helper.thread = mozilla::Some(
1157
Thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)));
1158
if (!helper.thread->init(HelperThread::ThreadMain, &helper)) {
1159
// Ensure that we do not leave uninitialized threads in the `threads`
1160
// vector.
1161
threads->popBack();
1162
goto error;
1163
}
1164
}
1165
}
1166
1167
return true;
1168
1169
error:
1170
finishThreads();
1171
return false;
1172
}
1173
1174
GlobalHelperThreadState::GlobalHelperThreadState()
1175
: cpuCount(0),
1176
threadCount(0),
1177
threads(nullptr),
1178
registerThread(nullptr),
1179
unregisterThread(nullptr),
1180
wasmTier2GeneratorsFinished_(0),
1181
helperLock(mutexid::GlobalHelperThreadState) {
1182
cpuCount = ClampDefaultCPUCount(GetCPUCount());
1183
threadCount = ThreadCountForCPUCount(cpuCount);
1184
1185
MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
1186
}
1187
1188
void GlobalHelperThreadState::finish() {
1189
CancelOffThreadWasmTier2Generator();
1190
finishThreads();
1191
1192
// Make sure there are no Ion free tasks left. We check this here because,
1193
// unlike the other tasks, we don't explicitly block on this when
1194
// destroying a runtime.
1195
AutoLockHelperThreadState lock;
1196
auto& freeList = ionFreeList(lock);
1197
while (!freeList.empty()) {
1198
jit::FreeIonCompileTask(freeList.popCopy());
1199
}
1200
destroyHelperContexts(lock);
1201
}
1202
1203
void GlobalHelperThreadState::finishThreads() {
1204
if (!threads) {
1205
return;
1206
}
1207
1208
MOZ_ASSERT(CanUseExtraThreads());
1209
for (auto& thread : *threads) {
1210
thread.destroy();
1211
}
1212
threads.reset(nullptr);
1213
}
1214
1215
bool GlobalHelperThreadState::ensureContextListForThreadCount() {
1216
if (helperContexts_.length() >= threadCount) {
1217
return true;
1218
}
1219
AutoLockHelperThreadState lock;
1220
1221
// SetFakeCPUCount() may cause the context list to contain less contexts
1222
// than there are helper threads, which could potentially lead to a crash.
1223
// Append more initialized contexts to the list until there are enough.
1224
while (helperContexts_.length() < threadCount) {
1225
UniquePtr<JSContext> cx =
1226
js::MakeUnique<JSContext>(nullptr, JS::ContextOptions());
1227
if (!cx) {
1228
return false;
1229
}
1230
1231
// To initialize context-specific protected data, the context must
1232
// temporarily set itself to the main thread. After initialization,
1233
// cx can clear itself from the thread.
1234
cx->setHelperThread(lock);
1235
if (!cx->init(ContextKind::HelperThread)) {
1236
return false;
1237
}
1238
cx->clearHelperThread(lock);
1239
if (!helperContexts_.append(cx.release())) {
1240
return false;
1241
}
1242
}
1243
return true;
1244
}
1245
1246
JSContext* GlobalHelperThreadState::getFirstUnusedContext(
1247
AutoLockHelperThreadState& locked) {
1248
for (auto& cx : helperContexts_) {
1249
if (cx->contextAvailable(locked)) {
1250
return cx;
1251
}
1252
}
1253
MOZ_CRASH("Expected available JSContext");
1254
}
1255
1256
void GlobalHelperThreadState::destroyHelperContexts(
1257
AutoLockHelperThreadState& lock) {
1258
while (helperContexts_.length() > 0) {
1259
JSContext* cx = helperContexts_.popCopy();
1260
// Before cx can be destroyed, it has to set itself to the main thread.
1261
// This enables it to pass its context-specific data checks.
1262
cx->setHelperThread(lock);
1263
js_delete(cx);
1264
}
1265
}
1266
1267
#ifdef DEBUG
1268
bool GlobalHelperThreadState::isLockedByCurrentThread() const {
1269
return helperLock.ownedByCurrentThread();
1270
}
1271
#endif // DEBUG
1272
1273
void GlobalHelperThreadState::wait(
1274
AutoLockHelperThreadState& locked, CondVar which,
1275
TimeDuration timeout /* = TimeDuration::Forever() */) {
1276
whichWakeup(which).wait_for(locked, timeout);
1277
}
1278
1279
void GlobalHelperThreadState::notifyAll(CondVar which,
1280
const AutoLockHelperThreadState&) {
1281
whichWakeup(which).notify_all();
1282
}
1283
1284
void GlobalHelperThreadState::notifyOne(CondVar which,
1285
const AutoLockHelperThreadState&) {
1286
whichWakeup(which).notify_one();
1287
}
1288
1289
bool GlobalHelperThreadState::hasActiveThreads(
1290
const AutoLockHelperThreadState&) {
1291
if (!threads) {
1292
return false;
1293
}
1294
1295
for (auto& thread : *threads) {
1296
if (!thread.idle()) {
1297
return true;
1298
}
1299
}
1300
1301
return false;
1302
}
1303
1304
void GlobalHelperThreadState::waitForAllThreads() {
1305
AutoLockHelperThreadState lock;
1306
waitForAllThreadsLocked(lock);
1307
}
1308
1309
void GlobalHelperThreadState::waitForAllThreadsLocked(
1310
AutoLockHelperThreadState& lock) {
1311
CancelOffThreadWasmTier2GeneratorLocked(lock);
1312
1313
while (hasActiveThreads(lock)) {
1314
wait(lock, CONSUMER);
1315
}
1316
}
1317
1318
// A task can be a "master" task, ie, it will block waiting for other worker
1319
// threads that perform work on its behalf. If so it must not take the last
1320
// available thread; there must always be at least one worker thread able to do
1321
// the actual work. (Or the system may deadlock.)
1322
//
1323
// If a task is a master task it *must* pass isMaster=true here, or perform a
1324
// similar calculation to avoid deadlock from starvation.
1325
//
1326
// isMaster should only be true if the thread calling checkTaskThreadLimit() is
1327
// a helper thread.
1328
//
1329
// NOTE: Calling checkTaskThreadLimit() from a helper thread in the dynamic
1330
// region after currentTask.emplace() and before currentTask.reset() may cause
1331
// it to return a different result than if it is called outside that dynamic
1332
// region, as the predicate inspects the values of the threads' currentTask
1333
// members.
1334
1335
template <typename T>
1336
bool GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads,
1337
bool isMaster) const {
1338
MOZ_ASSERT(maxThreads > 0);
1339
1340
if (!isMaster && maxThreads >= threadCount) {
1341
return true;
1342
}
1343
1344
size_t count = 0;
1345
size_t idle = 0;
1346
for (auto& thread : *threads) {
1347
if (thread.currentTask.isSome()) {
1348
if (thread.currentTask->is<T>()) {
1349
count++;
1350
}
1351
} else {
1352
idle++;
1353
}
1354
if (count >= maxThreads) {
1355
return false;
1356
}
1357
}
1358
1359
// It is possible for the number of idle threads to be zero here, because
1360
// checkTaskThreadLimit() can be called from non-helper threads. Notably,
1361
// the compression task scheduler invokes it, and runs off a helper thread.
1362
if (idle == 0) {
1363
return false;
1364
}
1365
1366
// A master thread that's the last available thread must not be allowed to
1367
// run.
1368
if (isMaster && idle == 1) {
1369
return false;
1370
}
1371
1372
return true;
1373
}
1374
1375
void GlobalHelperThreadState::triggerFreeUnusedMemory() {
1376
if (!CanUseExtraThreads()) {
1377
return;
1378
}
1379
1380
AutoLockHelperThreadState lock;
1381
for (auto& context : helperContexts_) {
1382
if (context->shouldFreeUnusedMemory() && context->contextAvailable(lock)) {
1383
// This context hasn't been used since the last time freeUnusedMemory
1384
// was set. Free the temp LifoAlloc from the main thread.
1385
context->tempLifoAllocNoCheck().freeAll();
1386
context->setFreeUnusedMemory(false);
1387
} else {
1388
context->setFreeUnusedMemory(true);
1389
}
1390
}
1391
}
1392
1393
static inline bool IsHelperThreadSimulatingOOM(js::ThreadType threadType) {
1394
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1395
return js::oom::simulator.targetThread() == threadType;
1396
#else
1397
return false;
1398
#endif
1399
}
1400
1401
void GlobalHelperThreadState::addSizeOfIncludingThis(
1402
JS::GlobalStats* stats, AutoLockHelperThreadState& lock) const {
1403
MOZ_ASSERT(isLockedByCurrentThread());
1404
1405
mozilla::MallocSizeOf mallocSizeOf = stats->mallocSizeOf_;
1406
JS::HelperThreadStats& htStats = stats->helperThread;
1407
1408
htStats.stateData += mallocSizeOf(this);
1409
1410
if (threads) {
1411
htStats.stateData += threads->sizeOfIncludingThis(mallocSizeOf);
1412
}
1413
1414
// Report memory used by various containers
1415
htStats.stateData +=
1416
ionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1417
ionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1418
ionFreeList_.sizeOfExcludingThis(mallocSizeOf) +
1419
wasmWorklist_tier1_.sizeOfExcludingThis(mallocSizeOf) +
1420
wasmWorklist_tier2_.sizeOfExcludingThis(mallocSizeOf) +
1421
wasmTier2GeneratorWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1422
promiseHelperTasks_.sizeOfExcludingThis(mallocSizeOf) +
1423
parseWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1424
parseFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1425
parseWaitingOnGC_.sizeOfExcludingThis(mallocSizeOf) +
1426
compressionPendingList_.sizeOfExcludingThis(mallocSizeOf) +
1427
compressionWorklist_.sizeOfExcludingThis(mallocSizeOf) +
1428
compressionFinishedList_.sizeOfExcludingThis(mallocSizeOf) +
1429
gcParallelWorklist_.sizeOfExcludingThis(mallocSizeOf);
1430
1431
// Report ParseTasks on wait lists
1432
for (auto task : parseWorklist_) {
1433
htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1434
}
1435
for (auto task : parseFinishedList_) {
1436
htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1437
}
1438
for (auto task : parseWaitingOnGC_) {
1439
htStats.parseTask += task->sizeOfIncludingThis(mallocSizeOf);
1440
}
1441
1442
// Report IonCompileTasks on wait lists
1443
for (auto task : ionWorklist_) {
1444
htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
1445
}
1446
for (auto task : ionFinishedList_) {
1447
htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
1448
}
1449
for (auto task : ionFreeList_) {
1450
htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
1451
}
1452
1453
// Report wasm::CompileTasks on wait lists
1454
for (auto task : wasmWorklist_tier1_) {
1455
htStats.wasmCompile += task->sizeOfExcludingThis(mallocSizeOf);
1456
}
1457
for (auto task : wasmWorklist_tier2_) {
1458
htStats.wasmCompile += task->sizeOfExcludingThis(mallocSizeOf);
1459
}
1460
1461
// Report number of helper threads.
1462
MOZ_ASSERT(htStats.idleThreadCount == 0);
1463
if (threads) {
1464
for (auto& thread : *threads) {
1465
if (thread.idle()) {
1466
htStats.idleThreadCount++;
1467
} else {
1468
htStats.activeThreadCount++;
1469
}
1470
}
1471
}
1472
}
1473
1474
size_t GlobalHelperThreadState::maxIonCompilationThreads() const {
1475
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_ION)) {
1476
return 1;
1477
}
1478
return threadCount;
1479
}
1480
1481
size_t GlobalHelperThreadState::maxWasmCompilationThreads() const {
1482
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM)) {
1483
return 1;
1484
}
1485
return cpuCount;
1486
}
1487
1488
size_t GlobalHelperThreadState::maxWasmTier2GeneratorThreads() const {
1489
return MaxTier2GeneratorTasks;
1490
}
1491
1492
size_t GlobalHelperThreadState::maxPromiseHelperThreads() const {
1493
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM)) {
1494
return 1;
1495
}
1496
return cpuCount;
1497
}
1498
1499
size_t GlobalHelperThreadState::maxParseThreads() const {
1500
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_PARSE)) {
1501
return 1;
1502
}
1503
return cpuCount;
1504
}
1505
1506
size_t GlobalHelperThreadState::maxCompressionThreads() const {
1507
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_COMPRESS)) {
1508
return 1;
1509
}
1510
1511
// Compression is triggered on major GCs to compress ScriptSources. It is
1512
// considered low priority work.
1513
return 1;
1514
}
1515
1516
size_t GlobalHelperThreadState::maxGCParallelThreads() const {
1517
if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_GCPARALLEL)) {
1518
return 1;
1519
}
1520
return threadCount;
1521
}
1522
1523
bool GlobalHelperThreadState::canStartWasmTier1Compile(
1524
const AutoLockHelperThreadState& lock) {
1525
return canStartWasmCompile(lock, wasm::CompileMode::Tier1);
1526
}
1527
1528
bool GlobalHelperThreadState::canStartWasmTier2Compile(
1529
const AutoLockHelperThreadState& lock) {
1530
return canStartWasmCompile(lock, wasm::CompileMode::Tier2);
1531
}
1532
1533
bool GlobalHelperThreadState::canStartWasmCompile(
1534
const AutoLockHelperThreadState& lock, wasm::CompileMode mode) {
1535
if (wasmWorklist(lock, mode).empty()) {
1536
return false;
1537
}
1538
1539
// Parallel compilation and background compilation should be disabled on
1540
// unicore systems.
1541
1542
MOZ_RELEASE_ASSERT(cpuCount > 1);
1543
1544
// If Tier2 is very backlogged we must give priority to it, since the Tier2
1545
// queue holds onto Tier1 tasks. Indeed if Tier2 is backlogged we will
1546
// devote more resources to Tier2 and not start any Tier1 work at all.
1547
1548
bool tier2oversubscribed = wasmTier2GeneratorWorklist(lock).length() > 20;
1549
1550
// For Tier1 and Once compilation, honor the maximum allowed threads to
1551
// compile wasm jobs at once, to avoid oversaturating the machine.
1552
//
1553
// For Tier2 compilation we need to allow other things to happen too, so we
1554
// do not allow all logical cores to be used for background work; instead we
1555
// wish to use a fraction of the physical cores. We can't directly compute
1556
// the physical cores from the logical cores, but 1/3 of the logical cores
1557
// is a safe estimate for the number of physical cores available for
1558
// background work.
1559
1560
size_t physCoresAvailable = size_t(ceil(cpuCount / 3.0));
1561
1562
size_t threads;
1563
if (mode == wasm::CompileMode::Tier2) {
1564
if (tier2oversubscribed) {
1565
threads = maxWasmCompilationThreads();
1566
} else {
1567
threads = physCoresAvailable;
1568
}
1569
} else {
1570
if (tier2oversubscribed) {
1571
threads = 0;
1572
} else {
1573
threads = maxWasmCompilationThreads();
1574
}
1575
}
1576
1577
if (!threads || !checkTaskThreadLimit<wasm::CompileTask*>(threads)) {
1578
return false;
1579
}
1580
1581
return true;
1582
}
1583
1584
bool GlobalHelperThreadState::canStartWasmTier2Generator(
1585
const AutoLockHelperThreadState& lock) {
1586
return !wasmTier2GeneratorWorklist(lock).empty() &&
1587
checkTaskThreadLimit<wasm::Tier2GeneratorTask*>(
1588
maxWasmTier2GeneratorThreads(),
1589
/*isMaster=*/true);
1590
}
1591
1592
bool GlobalHelperThreadState::canStartPromiseHelperTask(
1593
const AutoLockHelperThreadState& lock) {
1594
// PromiseHelperTasks can be wasm compilation tasks that in turn block on
1595
// wasm compilation so set isMaster = true.
1596
return !promiseHelperTasks(lock).empty() &&
1597
checkTaskThreadLimit<PromiseHelperTask*>(maxPromiseHelperThreads(),
1598
/*isMaster=*/true);
1599
}
1600
1601
static bool IonCompileTaskHasHigherPriority(jit::IonCompileTask* first,
1602
jit::IonCompileTask* second) {
1603
// Return true if priority(first) > priority(second).
1604
//
1605
// This method can return whatever it wants, though it really ought to be a
1606
// total order. The ordering is allowed to race (change on the fly), however.
1607
1608
// A lower optimization level indicates a higher priority.
1609
jit::OptimizationLevel firstLevel =
1610
first->mirGen().optimizationInfo().level();
1611
jit::OptimizationLevel secondLevel =
1612
second->mirGen().optimizationInfo().level();
1613
if (firstLevel != secondLevel) {
1614
return firstLevel < secondLevel;
1615
}
1616
1617
// A script without an IonScript has precedence on one with.
1618
if (first->scriptHasIonScript() != second->scriptHasIonScript()) {
1619
return !first->scriptHasIonScript();
1620
}
1621
1622
// A higher warm-up counter indicates a higher priority.
1623
jit::JitScript* firstJitScript = first->script()->jitScript();
1624
jit::JitScript* secondJitScript = second->script()->jitScript();
1625
return firstJitScript->warmUpCount() / first->script()->length() >
1626
secondJitScript->warmUpCount() / second->script()->length();
1627
}
1628
1629
bool GlobalHelperThreadState::canStartIonCompile(
1630
const AutoLockHelperThreadState& lock) {
1631
return !ionWorklist(lock).empty() &&
1632
checkTaskThreadLimit<jit::IonCompileTask*>(maxIonCompilationThreads());
1633
}
1634
1635
bool GlobalHelperThreadState::canStartIonFreeTask(
1636
const AutoLockHelperThreadState& lock) {
1637
return !ionFreeList(lock).empty();
1638
}
1639
1640
jit::IonCompileTask* GlobalHelperThreadState::highestPriorityPendingIonCompile(
1641
const AutoLockHelperThreadState& lock) {
1642
auto& worklist = ionWorklist(lock);
1643
MOZ_ASSERT(!worklist.empty());
1644
1645
// Get the highest priority IonCompileTask which has not started compilation
1646
// yet.
1647
size_t index = 0;
1648
for (size_t i = 1; i < worklist.length(); i++) {
1649
if (IonCompileTaskHasHigherPriority(worklist[i], worklist[index])) {
1650
index = i;
1651
}
1652
}
1653
1654
jit::IonCompileTask* task = worklist[index];
1655
worklist.erase(&worklist[index]);
1656
return task;
1657
}
1658
1659
bool GlobalHelperThreadState::canStartParseTask(
1660
const AutoLockHelperThreadState& lock) {
1661
// Parse tasks that end up compiling asm.js in turn may use Wasm compilation
1662
// threads to generate machine code. We have no way (at present) to know
1663
// ahead of time whether a parse task is going to parse asm.js content or
1664
// not, so we just assume that all parse tasks are master tasks.
1665
return !parseWorklist(lock).empty() &&
1666
checkTaskThreadLimit<ParseTask*>(maxParseThreads(), /*isMaster=*/true);
1667
}
1668
1669
bool GlobalHelperThreadState::canStartCompressionTask(
1670
const AutoLockHelperThreadState& lock) {
1671
return !compressionWorklist(lock).empty() &&
1672
checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
1673
}
1674
1675
void GlobalHelperThreadState::startHandlingCompressionTasks(
1676
const AutoLockHelperThreadState& lock) {
1677
scheduleCompressionTasks(lock);
1678
if (canStartCompressionTask(lock)) {
1679
notifyOne(PRODUCER, lock);
1680
}
1681
}
1682
1683
void GlobalHelperThreadState::scheduleCompressionTasks(
1684
const AutoLockHelperThreadState& lock) {
1685
auto& pending = compressionPendingList(lock);
1686
auto& worklist = compressionWorklist(lock);
1687
1688
for (size_t i = 0; i < pending.length(); i++) {
1689
if (pending[i]->shouldStart()) {
1690
// OOMing during appending results in the task not being scheduled
1691
// and deleted.
1692
Unused << worklist.append(std::move(pending[i]));
1693
remove(pending, &i);
1694
}
1695
}
1696
}
1697
1698
bool GlobalHelperThreadState::canStartGCParallelTask(
1699
const AutoLockHelperThreadState& lock) {
1700
return !gcParallelWorklist(lock).isEmpty() &&
1701
checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());
1702
}
1703
1704
void HelperThread::handleGCParallelWorkload(AutoLockHelperThreadState& lock) {
1705
MOZ_ASSERT(HelperThreadState().canStartGCParallelTask(lock));
1706
MOZ_ASSERT(idle());
1707
1708
TraceLoggerThread* logger = TraceLoggerForCurrentThread();
1709
AutoTraceLog logCompile(logger, TraceLogger_GC);
1710
1711
currentTask.emplace(HelperThreadState().gcParallelWorklist(lock).popFirst());
1712
gcParallelTask()->runFromHelperThread(lock);
1713
currentTask.reset();
1714
}
1715
1716
static void LeaveParseTaskZone(JSRuntime* rt, ParseTask* task) {
1717
// Mark the zone as no longer in use by a helper thread, and available
1718
// to be collected by the GC.
1719
rt->clearUsedByHelperThread(task->parseGlobal->zoneFromAnyThread());
1720
}
1721
1722
ParseTask* GlobalHelperThreadState::removeFinishedParseTask(
1723
ParseTaskKind kind, JS::OffThreadToken* token) {
1724
// The token is really a ParseTask* which should be in the finished list.
1725
// Remove its entry.
1726
auto task = static_cast<ParseTask*>(token);
1727
MOZ_ASSERT(task->kind == kind);
1728
1729
AutoLockHelperThreadState lock;
1730
1731
#ifdef DEBUG
1732
auto& finished = parseFinishedList(lock);
1733
bool found = false;
1734
for (auto t : finished) {
1735
if (t == task) {
1736
found = true;
1737
break;
1738
}
1739
}
1740
MOZ_ASSERT(found);
1741
#endif
1742
1743
task->remove();
1744
return task;
1745
}
1746
1747
UniquePtr<ParseTask> GlobalHelperThreadState::finishParseTaskCommon(
1748
JSContext* cx, ParseTaskKind kind, JS::OffThreadToken* token) {
1749
MOZ_ASSERT(!cx->isHelperThreadContext());
1750
MOZ_ASSERT(cx->realm());
1751
1752
Rooted<UniquePtr<ParseTask>> parseTask(cx,
1753
removeFinishedParseTask(kind, token));
1754
1755
// Make sure we have all the constructors we need for the prototype
1756
// remapping below, since we can't GC while that's happening.
1757
if (!EnsureParserCreatedClasses(cx, kind)) {
1758
LeaveParseTaskZone(cx->runtime(), parseTask.get().get());
1759
return nullptr;
1760
}
1761
1762
mergeParseTaskRealm(cx, parseTask.get().get(), cx->realm());
1763
1764
for (auto& script : parseTask->scripts) {
1765
cx->releaseCheck(script);
1766
}
1767
1768
for (auto& sourceObject : parseTask->sourceObjects) {
1769
RootedScriptSourceObject sso(cx, sourceObject);
1770
if (!ScriptSourceObject::initFromOptions(cx, sso, parseTask->options)) {
1771
return nullptr;