Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
7 #include "jsworkers.h"
9 #ifdef JS_THREADSAFE
11 #include "mozilla/DebugOnly.h"
13 #include "jsnativestack.h"
14 #include "prmjtime.h"
16 #include "frontend/BytecodeCompiler.h"
17 #include "jit/IonBuilder.h"
18 #include "vm/Debugger.h"
19 #include "vm/TraceLogging.h"
21 #include "jscntxtinlines.h"
22 #include "jscompartmentinlines.h"
23 #include "jsobjinlines.h"
24 #include "jsscriptinlines.h"
26 using namespace js;
28 using mozilla::ArrayLength;
29 using mozilla::DebugOnly;
31 namespace js {
33 GlobalWorkerThreadState gWorkerThreadState;
35 } // namespace js
37 void
38 js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
39 {
40 // If 'cx' is not a JSContext, we are already off the main thread and the
41 // worker threads would have already been initialized.
42 if (!cx->isJSContext())
43 return;
45 WorkerThreadState().ensureInitialized();
46 }
48 static size_t
49 ThreadCountForCPUCount(size_t cpuCount)
50 {
51 return Max(cpuCount, (size_t)2);
52 }
54 void
55 js::SetFakeCPUCount(size_t count)
56 {
57 // This must be called before the threads have been initialized.
58 JS_ASSERT(!WorkerThreadState().threads);
60 WorkerThreadState().cpuCount = count;
61 WorkerThreadState().threadCount = ThreadCountForCPUCount(count);
62 }
64 #ifdef JS_ION
66 bool
67 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
68 {
69 // Threads already initialized by the AsmJS compiler.
70 JS_ASSERT(asmData->mir);
71 JS_ASSERT(asmData->lir == nullptr);
73 AutoLockWorkerThreadState lock;
75 // Don't append this task if another failed.
76 if (WorkerThreadState().asmJSWorkerFailed())
77 return false;
79 if (!WorkerThreadState().asmJSWorklist().append(asmData))
80 return false;
82 WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
83 return true;
84 }
86 bool
87 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
88 {
89 EnsureWorkerThreadsInitialized(cx);
91 AutoLockWorkerThreadState lock;
93 if (!WorkerThreadState().ionWorklist().append(builder))
94 return false;
96 WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
97 return true;
98 }
100 /*
101 * Move an IonBuilder for which compilation has either finished, failed, or
102 * been cancelled into the global finished compilation list. All off thread
103 * compilations which are started must eventually be finished.
104 */
105 static void
106 FinishOffThreadIonCompile(jit::IonBuilder *builder)
107 {
108 WorkerThreadState().ionFinishedList().append(builder);
109 }
111 #endif // JS_ION
113 static inline bool
114 CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
115 {
116 if (script)
117 return target == script;
118 return target->compartment() == compartment;
119 }
121 void
122 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
123 {
124 #ifdef JS_ION
125 jit::JitCompartment *jitComp = compartment->jitCompartment();
126 if (!jitComp)
127 return;
129 AutoLockWorkerThreadState lock;
131 if (!WorkerThreadState().threads)
132 return;
134 /* Cancel any pending entries for which processing hasn't started. */
135 GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
136 for (size_t i = 0; i < worklist.length(); i++) {
137 jit::IonBuilder *builder = worklist[i];
138 if (CompiledScriptMatches(compartment, script, builder->script())) {
139 FinishOffThreadIonCompile(builder);
140 WorkerThreadState().remove(worklist, &i);
141 }
142 }
144 /* Wait for in progress entries to finish up. */
145 for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
146 const WorkerThread &helper = WorkerThreadState().threads[i];
147 while (helper.ionBuilder &&
148 CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
149 {
150 helper.ionBuilder->cancel();
151 WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
152 }
153 }
155 /* Cancel code generation for any completed entries. */
156 GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
157 for (size_t i = 0; i < finished.length(); i++) {
158 jit::IonBuilder *builder = finished[i];
159 if (CompiledScriptMatches(compartment, script, builder->script())) {
160 jit::FinishOffThreadBuilder(builder);
161 WorkerThreadState().remove(finished, &i);
162 }
163 }
164 #endif // JS_ION
165 }
167 static const JSClass workerGlobalClass = {
168 "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
169 JS_PropertyStub, JS_DeletePropertyStub,
170 JS_PropertyStub, JS_StrictPropertyStub,
171 JS_EnumerateStub, JS_ResolveStub,
172 JS_ConvertStub, nullptr,
173 nullptr, nullptr, nullptr,
174 JS_GlobalObjectTraceHook
175 };
177 ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
178 const jschar *chars, size_t length,
179 JS::OffThreadCompileCallback callback, void *callbackData)
180 : cx(cx), options(initCx), chars(chars), length(length),
181 alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
182 exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx),
183 optionsIntroductionScript(initCx), callback(callback), callbackData(callbackData),
184 script(nullptr), errors(cx), overRecursed(false)
185 {
186 }
188 bool
189 ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
190 {
191 if (!this->options.copy(cx, options))
192 return false;
194 // Save those compilation options that the ScriptSourceObject can't
195 // point at while it's in the compilation's temporary compartment.
196 optionsElement = this->options.element();
197 this->options.setElement(nullptr);
198 optionsIntroductionScript = this->options.introductionScript();
199 this->options.setIntroductionScript(nullptr);
201 return true;
202 }
204 void
205 ParseTask::activate(JSRuntime *rt)
206 {
207 rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
208 cx->enterCompartment(exclusiveContextGlobal->compartment());
209 }
211 void
212 ParseTask::finish()
213 {
214 if (script) {
215 // Initialize the ScriptSourceObject slots that we couldn't while the SSO
216 // was in the temporary compartment.
217 ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>();
218 sso.initElement(optionsElement);
219 sso.initIntroductionScript(optionsIntroductionScript);
220 }
221 }
223 ParseTask::~ParseTask()
224 {
225 // ParseTask takes over ownership of its input exclusive context.
226 js_delete(cx);
228 for (size_t i = 0; i < errors.length(); i++)
229 js_delete(errors[i]);
230 }
232 void
233 js::CancelOffThreadParses(JSRuntime *rt)
234 {
235 AutoLockWorkerThreadState lock;
237 if (!WorkerThreadState().threads)
238 return;
240 // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
241 // and in progress ones to complete. Otherwise the final GC may not collect
242 // everything due to zones being used off thread.
243 while (true) {
244 bool pending = false;
245 GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist();
246 for (size_t i = 0; i < worklist.length(); i++) {
247 ParseTask *task = worklist[i];
248 if (task->runtimeMatches(rt))
249 pending = true;
250 }
251 if (!pending) {
252 bool inProgress = false;
253 for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
254 ParseTask *task = WorkerThreadState().threads[i].parseTask;
255 if (task && task->runtimeMatches(rt))
256 inProgress = true;
257 }
258 if (!inProgress)
259 break;
260 }
261 WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
262 }
264 // Clean up any parse tasks which haven't been finished by the main thread.
265 GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList();
266 while (true) {
267 bool found = false;
268 for (size_t i = 0; i < finished.length(); i++) {
269 ParseTask *task = finished[i];
270 if (task->runtimeMatches(rt)) {
271 found = true;
272 AutoUnlockWorkerThreadState unlock;
273 WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
274 }
275 }
276 if (!found)
277 break;
278 }
279 }
281 bool
282 js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
283 {
284 // Off thread parsing can't occur during incremental collections on the
285 // atoms compartment, to avoid triggering barriers. (Outside the atoms
286 // compartment, the compilation will use a new zone that is never
287 // collected.) If an atoms-zone GC is in progress, hold off on executing the
288 // parse task until the atoms-zone GC completes (see
289 // EnqueuePendingParseTasksAfterGC).
290 return rt->activeGCInAtomsZone();
291 }
293 bool
294 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
295 const jschar *chars, size_t length,
296 JS::OffThreadCompileCallback callback, void *callbackData)
297 {
298 // Suppress GC so that calls below do not trigger a new incremental GC
299 // which could require barriers on the atoms compartment.
300 gc::AutoSuppressGC suppress(cx);
302 SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
303 frontend::MaybeCallSourceHandler(cx, options, srcBuf);
305 EnsureWorkerThreadsInitialized(cx);
307 JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
308 compartmentOptions.setZone(JS::FreshZone);
309 compartmentOptions.setInvisibleToDebugger(true);
310 compartmentOptions.setMergeable(true);
312 // Don't falsely inherit the host's global trace hook.
313 compartmentOptions.setTrace(nullptr);
315 JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, nullptr,
316 JS::FireOnNewGlobalHook, compartmentOptions);
317 if (!global)
318 return false;
320 JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals);
322 RootedObject obj(cx);
324 // Initialize all classes needed for parsing while we are still on the main
325 // thread. Do this for both the target and the new global so that prototype
326 // pointers can be changed infallibly after parsing finishes.
327 if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
328 !GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
329 !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
330 !GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
331 {
332 return false;
333 }
334 {
335 AutoCompartment ac(cx, global);
336 if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
337 !GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
338 !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
339 !GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
340 {
341 return false;
342 }
343 }
345 ScopedJSDeletePtr<ExclusiveContext> workercx(
346 cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) nullptr,
347 ThreadSafeContext::Context_Exclusive));
348 if (!workercx)
349 return false;
351 ScopedJSDeletePtr<ParseTask> task(
352 cx->new_<ParseTask>(workercx.get(), global, cx, chars, length,
353 callback, callbackData));
354 if (!task)
355 return false;
357 workercx.forget();
359 if (!task->init(cx, options))
360 return false;
362 if (OffThreadParsingMustWaitForGC(cx->runtime())) {
363 AutoLockWorkerThreadState lock;
364 if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
365 return false;
366 } else {
367 task->activate(cx->runtime());
369 AutoLockWorkerThreadState lock;
371 if (!WorkerThreadState().parseWorklist().append(task.get()))
372 return false;
374 WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
375 }
377 task.forget();
379 return true;
380 }
382 void
383 js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
384 {
385 JS_ASSERT(!OffThreadParsingMustWaitForGC(rt));
387 GlobalWorkerThreadState::ParseTaskVector newTasks;
388 {
389 AutoLockWorkerThreadState lock;
390 GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC();
392 for (size_t i = 0; i < waiting.length(); i++) {
393 ParseTask *task = waiting[i];
394 if (task->runtimeMatches(rt)) {
395 newTasks.append(task);
396 WorkerThreadState().remove(waiting, &i);
397 }
398 }
399 }
401 if (newTasks.empty())
402 return;
404 // This logic should mirror the contents of the !activeGCInAtomsZone()
405 // branch in StartOffThreadParseScript:
407 for (size_t i = 0; i < newTasks.length(); i++)
408 newTasks[i]->activate(rt);
410 AutoLockWorkerThreadState lock;
412 for (size_t i = 0; i < newTasks.length(); i++)
413 WorkerThreadState().parseWorklist().append(newTasks[i]);
415 WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
416 }
418 static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
419 static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
421 void
422 GlobalWorkerThreadState::ensureInitialized()
423 {
424 JS_ASSERT(this == &WorkerThreadState());
425 AutoLockWorkerThreadState lock;
427 if (threads)
428 return;
430 threads = js_pod_calloc<WorkerThread>(threadCount);
431 if (!threads)
432 CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
434 for (size_t i = 0; i < threadCount; i++) {
435 WorkerThread &helper = threads[i];
436 helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
437 helper.thread = PR_CreateThread(PR_USER_THREAD,
438 WorkerThread::ThreadMain, &helper,
439 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
440 if (!helper.thread || !helper.threadData.ref().init())
441 CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
442 }
444 resetAsmJSFailureState();
445 }
447 GlobalWorkerThreadState::GlobalWorkerThreadState()
448 {
449 mozilla::PodZero(this);
451 cpuCount = GetCPUCount();
452 threadCount = ThreadCountForCPUCount(cpuCount);
454 MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
456 workerLock = PR_NewLock();
457 consumerWakeup = PR_NewCondVar(workerLock);
458 producerWakeup = PR_NewCondVar(workerLock);
459 }
461 void
462 GlobalWorkerThreadState::finish()
463 {
464 if (threads) {
465 for (size_t i = 0; i < threadCount; i++)
466 threads[i].destroy();
467 js_free(threads);
468 }
470 PR_DestroyCondVar(consumerWakeup);
471 PR_DestroyCondVar(producerWakeup);
472 PR_DestroyLock(workerLock);
473 }
475 void
476 GlobalWorkerThreadState::lock()
477 {
478 JS_ASSERT(!isLocked());
479 AssertCurrentThreadCanLock(WorkerThreadStateLock);
480 PR_Lock(workerLock);
481 #ifdef DEBUG
482 lockOwner = PR_GetCurrentThread();
483 #endif
484 }
486 void
487 GlobalWorkerThreadState::unlock()
488 {
489 JS_ASSERT(isLocked());
490 #ifdef DEBUG
491 lockOwner = nullptr;
492 #endif
493 PR_Unlock(workerLock);
494 }
496 #ifdef DEBUG
497 bool
498 GlobalWorkerThreadState::isLocked()
499 {
500 return lockOwner == PR_GetCurrentThread();
501 }
502 #endif
504 void
505 GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
506 {
507 JS_ASSERT(isLocked());
508 #ifdef DEBUG
509 lockOwner = nullptr;
510 #endif
511 DebugOnly<PRStatus> status =
512 PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
513 millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
514 JS_ASSERT(status == PR_SUCCESS);
515 #ifdef DEBUG
516 lockOwner = PR_GetCurrentThread();
517 #endif
518 }
520 void
521 GlobalWorkerThreadState::notifyAll(CondVar which)
522 {
523 JS_ASSERT(isLocked());
524 PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
525 }
527 void
528 GlobalWorkerThreadState::notifyOne(CondVar which)
529 {
530 JS_ASSERT(isLocked());
531 PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
532 }
534 bool
535 GlobalWorkerThreadState::canStartAsmJSCompile()
536 {
537 // Don't execute an AsmJS job if an earlier one failed.
538 JS_ASSERT(isLocked());
539 return !asmJSWorklist().empty() && !numAsmJSFailedJobs;
540 }
542 bool
543 GlobalWorkerThreadState::canStartIonCompile()
544 {
545 // A worker thread can begin an Ion compilation if (a) there is some script
546 // which is waiting to be compiled, and (b) no other worker thread is
547 // currently compiling a script. The latter condition ensures that two
548 // compilations cannot simultaneously occur.
549 if (ionWorklist().empty())
550 return false;
551 for (size_t i = 0; i < threadCount; i++) {
552 if (threads[i].ionBuilder)
553 return false;
554 }
555 return true;
556 }
558 bool
559 GlobalWorkerThreadState::canStartParseTask()
560 {
561 // Don't allow simultaneous off thread parses, to reduce contention on the
562 // atoms table. Note that asm.js compilation depends on this to avoid
563 // stalling the worker thread, as off thread parse tasks can trigger and
564 // block on other off thread asm.js compilation tasks.
565 JS_ASSERT(isLocked());
566 if (parseWorklist().empty())
567 return false;
568 for (size_t i = 0; i < threadCount; i++) {
569 if (threads[i].parseTask)
570 return false;
571 }
572 return true;
573 }
575 bool
576 GlobalWorkerThreadState::canStartCompressionTask()
577 {
578 return !compressionWorklist().empty();
579 }
581 static void
582 CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
583 {
584 // We should never hit this, since nested scripts are also constructed via
585 // BytecodeEmitter instances on the stack.
586 JS_CHECK_RECURSION(cx, return);
588 // Recurse to any nested scripts.
589 if (script->hasObjects()) {
590 ObjectArray *objects = script->objects();
591 for (size_t i = 0; i < objects->length; i++) {
592 JSObject *obj = objects->vector[i];
593 if (obj->is<JSFunction>()) {
594 JSFunction *fun = &obj->as<JSFunction>();
595 if (fun->hasScript()) {
596 RootedScript nested(cx, fun->nonLazyScript());
597 CallNewScriptHookForAllScripts(cx, nested);
598 }
599 }
600 }
601 }
603 // The global new script hook is called on every script that was compiled.
604 RootedFunction function(cx, script->functionNonDelazifying());
605 CallNewScriptHook(cx, script, function);
606 }
608 static void
609 LeaveParseTaskZone(JSRuntime *rt, ParseTask *task)
610 {
611 // Mark the zone as no longer in use by an ExclusiveContext, and available
612 // to be collected by the GC.
613 rt->clearUsedByExclusiveThread(task->cx->zone());
614 }
616 JSScript *
617 GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
618 {
619 ScopedJSDeletePtr<ParseTask> parseTask;
621 // The token is a ParseTask* which should be in the finished list.
622 // Find and remove its entry.
623 {
624 AutoLockWorkerThreadState lock;
625 ParseTaskVector &finished = parseFinishedList();
626 for (size_t i = 0; i < finished.length(); i++) {
627 if (finished[i] == token) {
628 parseTask = finished[i];
629 remove(finished, &i);
630 break;
631 }
632 }
633 }
634 JS_ASSERT(parseTask);
636 if (!maybecx) {
637 LeaveParseTaskZone(rt, parseTask);
638 return nullptr;
639 }
641 JSContext *cx = maybecx;
642 JS_ASSERT(cx->compartment());
644 // Make sure we have all the constructors we need for the prototype
645 // remapping below, since we can't GC while that's happening.
646 Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
647 if (!GlobalObject::ensureConstructor(cx, global, JSProto_Object) ||
648 !GlobalObject::ensureConstructor(cx, global, JSProto_Array) ||
649 !GlobalObject::ensureConstructor(cx, global, JSProto_Function) ||
650 !GlobalObject::ensureConstructor(cx, global, JSProto_RegExp) ||
651 !GlobalObject::ensureConstructor(cx, global, JSProto_Iterator))
652 {
653 LeaveParseTaskZone(rt, parseTask);
654 return nullptr;
655 }
657 LeaveParseTaskZone(rt, parseTask);
659 // Point the prototypes of any objects in the script's compartment to refer
660 // to the corresponding prototype in the new compartment. This will briefly
661 // create cross compartment pointers, which will be fixed by the
662 // MergeCompartments call below.
663 for (gc::CellIter iter(parseTask->cx->zone(), gc::FINALIZE_TYPE_OBJECT);
664 !iter.done();
665 iter.next())
666 {
667 types::TypeObject *object = iter.get<types::TypeObject>();
668 TaggedProto proto(object->proto());
669 if (!proto.isObject())
670 continue;
672 JSProtoKey key = JS::IdentifyStandardPrototype(proto.toObject());
673 if (key == JSProto_Null)
674 continue;
675 JS_ASSERT(key == JSProto_Object || key == JSProto_Array ||
676 key == JSProto_Function || key == JSProto_RegExp ||
677 key == JSProto_Iterator);
679 JSObject *newProto = GetBuiltinPrototypePure(global, key);
680 JS_ASSERT(newProto);
682 object->setProtoUnchecked(newProto);
683 }
685 // Move the parsed script and all its contents into the desired compartment.
686 gc::MergeCompartments(parseTask->cx->compartment(), cx->compartment());
687 parseTask->finish();
689 RootedScript script(rt, parseTask->script);
690 assertSameCompartment(cx, script);
692 // Report any error or warnings generated during the parse, and inform the
693 // debugger about the compiled scripts.
694 for (size_t i = 0; i < parseTask->errors.length(); i++)
695 parseTask->errors[i]->throwError(cx);
696 if (parseTask->overRecursed)
697 js_ReportOverRecursed(cx);
699 if (script) {
700 // The Debugger only needs to be told about the topmost script that was compiled.
701 GlobalObject *compileAndGoGlobal = nullptr;
702 if (script->compileAndGo())
703 compileAndGoGlobal = &script->global();
704 Debugger::onNewScript(cx, script, compileAndGoGlobal);
706 // The NewScript hook needs to be called for all compiled scripts.
707 CallNewScriptHookForAllScripts(cx, script);
708 }
710 return script;
711 }
713 void
714 WorkerThread::destroy()
715 {
716 if (thread) {
717 {
718 AutoLockWorkerThreadState lock;
719 terminate = true;
721 /* Notify all workers, to ensure that this thread wakes up. */
722 WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
723 }
725 PR_JoinThread(thread);
726 }
728 if (!threadData.empty())
729 threadData.destroy();
730 }
732 /* static */
733 void
734 WorkerThread::ThreadMain(void *arg)
735 {
736 PR_SetCurrentThreadName("Analysis Helper");
737 static_cast<WorkerThread *>(arg)->threadLoop();
738 }
740 void
741 WorkerThread::handleAsmJSWorkload()
742 {
743 #ifdef JS_ION
744 JS_ASSERT(WorkerThreadState().isLocked());
745 JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
746 JS_ASSERT(idle());
748 asmData = WorkerThreadState().asmJSWorklist().popCopy();
749 bool success = false;
751 do {
752 AutoUnlockWorkerThreadState unlock;
753 PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime);
755 jit::IonContext icx(asmData->mir->compartment->runtime(),
756 asmData->mir->compartment,
757 &asmData->mir->alloc());
759 int64_t before = PRMJ_Now();
761 if (!OptimizeMIR(asmData->mir))
762 break;
764 asmData->lir = GenerateLIR(asmData->mir);
765 if (!asmData->lir)
766 break;
768 int64_t after = PRMJ_Now();
769 asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC;
771 success = true;
772 } while(0);
774 // On failure, signal parent for harvesting in CancelOutstandingJobs().
775 if (!success) {
776 WorkerThreadState().noteAsmJSFailure(asmData->func);
777 WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
778 asmData = nullptr;
779 return;
780 }
782 // On success, move work to the finished list.
783 WorkerThreadState().asmJSFinishedList().append(asmData);
784 asmData = nullptr;
786 // Notify the main thread in case it's blocked waiting for a LifoAlloc.
787 WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
788 #else
789 MOZ_CRASH();
790 #endif // JS_ION
791 }
793 void
794 WorkerThread::handleIonWorkload()
795 {
796 #ifdef JS_ION
797 JS_ASSERT(WorkerThreadState().isLocked());
798 JS_ASSERT(WorkerThreadState().canStartIonCompile());
799 JS_ASSERT(idle());
801 ionBuilder = WorkerThreadState().ionWorklist().popCopy();
803 TraceLogger *logger = TraceLoggerForCurrentThread();
804 AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script()));
805 AutoTraceLog logCompile(logger, TraceLogger::IonCompilation);
807 JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread();
809 {
810 AutoUnlockWorkerThreadState unlock;
811 PerThreadData::AutoEnterRuntime enter(threadData.addr(),
812 ionBuilder->script()->runtimeFromAnyThread());
813 jit::IonContext ictx(jit::CompileRuntime::get(rt),
814 jit::CompileCompartment::get(ionBuilder->script()->compartment()),
815 &ionBuilder->alloc());
816 ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
817 }
819 FinishOffThreadIonCompile(ionBuilder);
820 ionBuilder = nullptr;
822 // Ping the main thread so that the compiled code can be incorporated
823 // at the next interrupt callback. Don't interrupt Ion code for this, as
824 // this incorporation can be delayed indefinitely without affecting
825 // performance as long as the main thread is actually executing Ion code.
826 rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon);
828 // Notify the main thread in case it is waiting for the compilation to finish.
829 WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
830 #else
831 MOZ_CRASH();
832 #endif // JS_ION
833 }
835 void
836 ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
837 {
838 workerThread_ = workerThread;
839 perThreadData = workerThread->threadData.addr();
840 }
842 frontend::CompileError &
843 ExclusiveContext::addPendingCompileError()
844 {
845 frontend::CompileError *error = js_new<frontend::CompileError>();
846 if (!error)
847 MOZ_CRASH();
848 if (!workerThread()->parseTask->errors.append(error))
849 MOZ_CRASH();
850 return *error;
851 }
853 void
854 ExclusiveContext::addPendingOverRecursed()
855 {
856 if (workerThread()->parseTask)
857 workerThread()->parseTask->overRecursed = true;
858 }
860 void
861 WorkerThread::handleParseWorkload()
862 {
863 JS_ASSERT(WorkerThreadState().isLocked());
864 JS_ASSERT(WorkerThreadState().canStartParseTask());
865 JS_ASSERT(idle());
867 parseTask = WorkerThreadState().parseWorklist().popCopy();
868 parseTask->cx->setWorkerThread(this);
870 {
871 AutoUnlockWorkerThreadState unlock;
872 PerThreadData::AutoEnterRuntime enter(threadData.addr(),
873 parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
874 SourceBufferHolder srcBuf(parseTask->chars, parseTask->length,
875 SourceBufferHolder::NoOwnership);
876 parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
877 NullPtr(), NullPtr(),
878 parseTask->options,
879 srcBuf);
880 }
882 // The callback is invoked while we are still off the main thread.
883 parseTask->callback(parseTask, parseTask->callbackData);
885 // FinishOffThreadScript will need to be called on the script to
886 // migrate it into the correct compartment.
887 WorkerThreadState().parseFinishedList().append(parseTask);
889 parseTask = nullptr;
891 // Notify the main thread in case it is waiting for the parse/emit to finish.
892 WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
893 }
895 void
896 WorkerThread::handleCompressionWorkload()
897 {
898 JS_ASSERT(WorkerThreadState().isLocked());
899 JS_ASSERT(WorkerThreadState().canStartCompressionTask());
900 JS_ASSERT(idle());
902 compressionTask = WorkerThreadState().compressionWorklist().popCopy();
903 compressionTask->workerThread = this;
905 {
906 AutoUnlockWorkerThreadState unlock;
907 if (!compressionTask->work())
908 compressionTask->setOOM();
909 }
911 compressionTask->workerThread = nullptr;
912 compressionTask = nullptr;
914 // Notify the main thread in case it is waiting for the compression to finish.
915 WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
916 }
918 bool
919 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
920 {
921 EnsureWorkerThreadsInitialized(cx);
923 AutoLockWorkerThreadState lock;
925 if (!WorkerThreadState().compressionWorklist().append(task))
926 return false;
928 WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
929 return true;
930 }
932 bool
933 GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
934 {
935 JS_ASSERT(isLocked());
936 for (size_t i = 0; i < compressionWorklist().length(); i++) {
937 if (compressionWorklist()[i] == task)
938 return true;
939 }
940 for (size_t i = 0; i < threadCount; i++) {
941 if (threads[i].compressionTask == task)
942 return true;
943 }
944 return false;
945 }
947 bool
948 SourceCompressionTask::complete()
949 {
950 JS_ASSERT_IF(!ss, !chars);
951 if (active()) {
952 AutoLockWorkerThreadState lock;
954 while (WorkerThreadState().compressionInProgress(this))
955 WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
957 ss->ready_ = true;
959 // Update memory accounting.
960 if (!oom)
961 cx->updateMallocCounter(ss->computedSizeOfData());
963 ss = nullptr;
964 chars = nullptr;
965 }
966 if (oom) {
967 js_ReportOutOfMemory(cx);
968 return false;
969 }
970 return true;
971 }
973 SourceCompressionTask *
974 GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
975 {
976 JS_ASSERT(isLocked());
977 for (size_t i = 0; i < compressionWorklist().length(); i++) {
978 SourceCompressionTask *task = compressionWorklist()[i];
979 if (task->source() == ss)
980 return task;
981 }
982 for (size_t i = 0; i < threadCount; i++) {
983 SourceCompressionTask *task = threads[i].compressionTask;
984 if (task && task->source() == ss)
985 return task;
986 }
987 return nullptr;
988 }
990 const jschar *
991 ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
992 {
993 // If this is being compressed off thread, return its uncompressed chars.
995 if (ready()) {
996 // Compression has already finished on the source.
997 return nullptr;
998 }
1000 AutoLockWorkerThreadState lock;
1002 // Look for a token that hasn't finished compressing and whose source is
1003 // the given ScriptSource.
1004 if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
1005 return task->uncompressedChars();
1007 // Compressing has finished, so this ScriptSource is ready. Avoid future
1008 // queries on the worker thread state when getting the chars.
1009 ready_ = true;
1011 return nullptr;
1012 }
1014 void
1015 WorkerThread::threadLoop()
1016 {
1017 JS::AutoAssertNoGC nogc;
1018 AutoLockWorkerThreadState lock;
1020 js::TlsPerThreadData.set(threadData.addr());
1022 // Compute the thread's stack limit, for over-recursed checks.
1023 uintptr_t stackLimit = GetNativeStackBase();
1024 #if JS_STACK_GROWTH_DIRECTION > 0
1025 stackLimit += WORKER_STACK_QUOTA;
1026 #else
1027 stackLimit -= WORKER_STACK_QUOTA;
1028 #endif
1029 for (size_t i = 0; i < ArrayLength(threadData.ref().nativeStackLimit); i++)
1030 threadData.ref().nativeStackLimit[i] = stackLimit;
1032 while (true) {
1033 JS_ASSERT(!ionBuilder && !asmData);
1035 // Block until a task is available.
1036 while (true) {
1037 if (terminate)
1038 return;
1039 if (WorkerThreadState().canStartIonCompile() ||
1040 WorkerThreadState().canStartAsmJSCompile() ||
1041 WorkerThreadState().canStartParseTask() ||
1042 WorkerThreadState().canStartCompressionTask())
1043 {
1044 break;
1045 }
1046 WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
1047 }
1049 // Dispatch tasks, prioritizing AsmJS work.
1050 if (WorkerThreadState().canStartAsmJSCompile())
1051 handleAsmJSWorkload();
1052 else if (WorkerThreadState().canStartIonCompile())
1053 handleIonWorkload();
1054 else if (WorkerThreadState().canStartParseTask())
1055 handleParseWorkload();
1056 else if (WorkerThreadState().canStartCompressionTask())
1057 handleCompressionWorkload();
1058 else
1059 MOZ_ASSUME_UNREACHABLE("No task to perform");
1060 }
1061 }
1063 #else /* JS_THREADSAFE */
1065 using namespace js;
1067 #ifdef JS_ION
1069 bool
1070 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
1071 {
1072 MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
1073 }
1075 bool
1076 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
1077 {
1078 MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
1079 }
1081 #endif // JS_ION
1083 void
1084 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
1085 {
1086 }
1088 void
1089 js::CancelOffThreadParses(JSRuntime *rt)
1090 {
1091 }
1093 bool
1094 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
1095 const jschar *chars, size_t length,
1096 JS::OffThreadCompileCallback callback, void *callbackData)
1097 {
1098 MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
1099 }
1101 bool
1102 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
1103 {
1104 MOZ_ASSUME_UNREACHABLE("Off thread compression not available");
1105 }
1107 bool
1108 SourceCompressionTask::complete()
1109 {
1110 JS_ASSERT(!active() && !oom);
1111 return true;
1112 }
1114 const jschar *
1115 ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
1116 {
1117 JS_ASSERT(ready());
1118 return nullptr;
1119 }
1121 frontend::CompileError &
1122 ExclusiveContext::addPendingCompileError()
1123 {
1124 MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
1125 }
1127 void
1128 ExclusiveContext::addPendingOverRecursed()
1129 {
1130 MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
1131 }
1133 #endif /* JS_THREADSAFE */