mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 12:15:51 +00:00
93abbb41bc
--HG-- extra : rebase_source : 20203cad0af54bd9bea81b8ff5c2e7016e14c3c2
1124 lines
34 KiB
C++
1124 lines
34 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "jsworkers.h"
|
|
|
|
#ifdef JS_THREADSAFE
|
|
|
|
#include "mozilla/DebugOnly.h"
|
|
|
|
#include "jsnativestack.h"
|
|
#include "prmjtime.h"
|
|
|
|
#include "frontend/BytecodeCompiler.h"
|
|
#include "jit/IonBuilder.h"
|
|
#include "vm/Debugger.h"
|
|
#include "vm/TraceLogging.h"
|
|
|
|
#include "jscntxtinlines.h"
|
|
#include "jscompartmentinlines.h"
|
|
#include "jsobjinlines.h"
|
|
#include "jsscriptinlines.h"
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::ArrayLength;
|
|
using mozilla::DebugOnly;
|
|
|
|
namespace js {
|
|
|
|
GlobalWorkerThreadState gWorkerThreadState;
|
|
|
|
} // namespace js
|
|
|
|
void
|
|
js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
|
|
{
|
|
// If 'cx' is not a JSContext, we are already off the main thread and the
|
|
// worker threads would have already been initialized.
|
|
if (!cx->isJSContext())
|
|
return;
|
|
|
|
WorkerThreadState().ensureInitialized();
|
|
}
|
|
|
|
static size_t
|
|
ThreadCountForCPUCount(size_t cpuCount)
|
|
{
|
|
return Max(cpuCount, (size_t)2);
|
|
}
|
|
|
|
void
|
|
js::SetFakeCPUCount(size_t count)
|
|
{
|
|
// This must be called before the threads have been initialized.
|
|
JS_ASSERT(!WorkerThreadState().threads);
|
|
|
|
WorkerThreadState().cpuCount = count;
|
|
WorkerThreadState().threadCount = ThreadCountForCPUCount(count);
|
|
}
|
|
|
|
#ifdef JS_ION
|
|
|
|
bool
|
|
js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
|
|
{
|
|
// Threads already initialized by the AsmJS compiler.
|
|
JS_ASSERT(asmData->mir);
|
|
JS_ASSERT(asmData->lir == nullptr);
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
// Don't append this task if another failed.
|
|
if (WorkerThreadState().asmJSWorkerFailed())
|
|
return false;
|
|
|
|
if (!WorkerThreadState().asmJSWorklist().append(asmData))
|
|
return false;
|
|
|
|
WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
|
|
{
|
|
EnsureWorkerThreadsInitialized(cx);
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (!WorkerThreadState().ionWorklist().append(builder))
|
|
return false;
|
|
|
|
WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Move an IonBuilder for which compilation has either finished, failed, or
|
|
* been cancelled into the global finished compilation list. All off thread
|
|
* compilations which are started must eventually be finished.
|
|
*/
|
|
static void
|
|
FinishOffThreadIonCompile(jit::IonBuilder *builder)
|
|
{
|
|
WorkerThreadState().ionFinishedList().append(builder);
|
|
}
|
|
|
|
#endif // JS_ION
|
|
|
|
static inline bool
|
|
CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
|
|
{
|
|
if (script)
|
|
return target == script;
|
|
return target->compartment() == compartment;
|
|
}
|
|
|
|
void
|
|
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
|
|
{
|
|
#ifdef JS_ION
|
|
jit::JitCompartment *jitComp = compartment->jitCompartment();
|
|
if (!jitComp)
|
|
return;
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (!WorkerThreadState().threads)
|
|
return;
|
|
|
|
/* Cancel any pending entries for which processing hasn't started. */
|
|
GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
|
|
for (size_t i = 0; i < worklist.length(); i++) {
|
|
jit::IonBuilder *builder = worklist[i];
|
|
if (CompiledScriptMatches(compartment, script, builder->script())) {
|
|
FinishOffThreadIonCompile(builder);
|
|
WorkerThreadState().remove(worklist, &i);
|
|
}
|
|
}
|
|
|
|
/* Wait for in progress entries to finish up. */
|
|
for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
|
|
const WorkerThread &helper = WorkerThreadState().threads[i];
|
|
while (helper.ionBuilder &&
|
|
CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
|
|
{
|
|
helper.ionBuilder->cancel();
|
|
WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
|
|
}
|
|
}
|
|
|
|
/* Cancel code generation for any completed entries. */
|
|
GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
jit::IonBuilder *builder = finished[i];
|
|
if (CompiledScriptMatches(compartment, script, builder->script())) {
|
|
jit::FinishOffThreadBuilder(builder);
|
|
WorkerThreadState().remove(finished, &i);
|
|
}
|
|
}
|
|
#endif // JS_ION
|
|
}
|
|
|
|
static const JSClass workerGlobalClass = {
|
|
"internal-worker-global", JSCLASS_GLOBAL_FLAGS,
|
|
JS_PropertyStub, JS_DeletePropertyStub,
|
|
JS_PropertyStub, JS_StrictPropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub,
|
|
JS_ConvertStub, nullptr,
|
|
nullptr, nullptr, nullptr,
|
|
JS_GlobalObjectTraceHook
|
|
};
|
|
|
|
ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
|
|
const jschar *chars, size_t length,
|
|
JS::OffThreadCompileCallback callback, void *callbackData)
|
|
: cx(cx), options(initCx), chars(chars), length(length),
|
|
alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
|
exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx),
|
|
optionsIntroductionScript(initCx), callback(callback), callbackData(callbackData),
|
|
script(nullptr), errors(cx), overRecursed(false)
|
|
{
|
|
}
|
|
|
|
bool
|
|
ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
|
|
{
|
|
if (!this->options.copy(cx, options))
|
|
return false;
|
|
|
|
// Save those compilation options that the ScriptSourceObject can't
|
|
// point at while it's in the compilation's temporary compartment.
|
|
optionsElement = this->options.element();
|
|
this->options.setElement(nullptr);
|
|
optionsIntroductionScript = this->options.introductionScript();
|
|
this->options.setIntroductionScript(nullptr);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ParseTask::activate(JSRuntime *rt)
|
|
{
|
|
rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
|
|
cx->enterCompartment(exclusiveContextGlobal->compartment());
|
|
}
|
|
|
|
void
|
|
ParseTask::finish()
|
|
{
|
|
if (script) {
|
|
// Initialize the ScriptSourceObject slots that we couldn't while the SSO
|
|
// was in the temporary compartment.
|
|
ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>();
|
|
sso.initElement(optionsElement);
|
|
sso.initIntroductionScript(optionsIntroductionScript);
|
|
}
|
|
}
|
|
|
|
ParseTask::~ParseTask()
|
|
{
|
|
// ParseTask takes over ownership of its input exclusive context.
|
|
js_delete(cx);
|
|
|
|
for (size_t i = 0; i < errors.length(); i++)
|
|
js_delete(errors[i]);
|
|
}
|
|
|
|
void
|
|
js::CancelOffThreadParses(JSRuntime *rt)
|
|
{
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (!WorkerThreadState().threads)
|
|
return;
|
|
|
|
// Instead of forcibly canceling pending parse tasks, just wait for all scheduled
|
|
// and in progress ones to complete. Otherwise the final GC may not collect
|
|
// everything due to zones being used off thread.
|
|
while (true) {
|
|
bool pending = false;
|
|
GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist();
|
|
for (size_t i = 0; i < worklist.length(); i++) {
|
|
ParseTask *task = worklist[i];
|
|
if (task->runtimeMatches(rt))
|
|
pending = true;
|
|
}
|
|
if (!pending) {
|
|
bool inProgress = false;
|
|
for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
|
|
ParseTask *task = WorkerThreadState().threads[i].parseTask;
|
|
if (task && task->runtimeMatches(rt))
|
|
inProgress = true;
|
|
}
|
|
if (!inProgress)
|
|
break;
|
|
}
|
|
WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
|
|
}
|
|
|
|
// Clean up any parse tasks which haven't been finished by the main thread.
|
|
GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList();
|
|
while (true) {
|
|
bool found = false;
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
ParseTask *task = finished[i];
|
|
if (task->runtimeMatches(rt)) {
|
|
found = true;
|
|
AutoUnlockWorkerThreadState unlock;
|
|
WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
|
|
}
|
|
}
|
|
if (!found)
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool
|
|
js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
|
|
{
|
|
// Off thread parsing can't occur during incremental collections on the
|
|
// atoms compartment, to avoid triggering barriers. (Outside the atoms
|
|
// compartment, the compilation will use a new zone that is never
|
|
// collected.) If an atoms-zone GC is in progress, hold off on executing the
|
|
// parse task until the atoms-zone GC completes (see
|
|
// EnqueuePendingParseTasksAfterGC).
|
|
return rt->activeGCInAtomsZone();
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
|
|
const jschar *chars, size_t length,
|
|
JS::OffThreadCompileCallback callback, void *callbackData)
|
|
{
|
|
// Suppress GC so that calls below do not trigger a new incremental GC
|
|
// which could require barriers on the atoms compartment.
|
|
gc::AutoSuppressGC suppress(cx);
|
|
|
|
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
|
|
frontend::MaybeCallSourceHandler(cx, options, srcBuf);
|
|
|
|
EnsureWorkerThreadsInitialized(cx);
|
|
|
|
JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
|
|
compartmentOptions.setZone(JS::FreshZone);
|
|
compartmentOptions.setInvisibleToDebugger(true);
|
|
compartmentOptions.setMergeable(true);
|
|
|
|
// Don't falsely inherit the host's global trace hook.
|
|
compartmentOptions.setTrace(nullptr);
|
|
|
|
JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, nullptr,
|
|
JS::FireOnNewGlobalHook, compartmentOptions);
|
|
if (!global)
|
|
return false;
|
|
|
|
JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals);
|
|
|
|
RootedObject obj(cx);
|
|
|
|
// Initialize all classes needed for parsing while we are still on the main
|
|
// thread. Do this for both the target and the new global so that prototype
|
|
// pointers can be changed infallibly after parsing finishes.
|
|
if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
|
|
{
|
|
return false;
|
|
}
|
|
{
|
|
AutoCompartment ac(cx, global);
|
|
if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
|
|
!GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ScopedJSDeletePtr<ExclusiveContext> workercx(
|
|
cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) nullptr,
|
|
ThreadSafeContext::Context_Exclusive));
|
|
if (!workercx)
|
|
return false;
|
|
|
|
ScopedJSDeletePtr<ParseTask> task(
|
|
cx->new_<ParseTask>(workercx.get(), global, cx, chars, length,
|
|
callback, callbackData));
|
|
if (!task)
|
|
return false;
|
|
|
|
workercx.forget();
|
|
|
|
if (!task->init(cx, options))
|
|
return false;
|
|
|
|
if (OffThreadParsingMustWaitForGC(cx->runtime())) {
|
|
AutoLockWorkerThreadState lock;
|
|
if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
|
|
return false;
|
|
} else {
|
|
task->activate(cx->runtime());
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (!WorkerThreadState().parseWorklist().append(task.get()))
|
|
return false;
|
|
|
|
WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
|
|
}
|
|
|
|
task.forget();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
|
|
{
|
|
JS_ASSERT(!OffThreadParsingMustWaitForGC(rt));
|
|
|
|
GlobalWorkerThreadState::ParseTaskVector newTasks;
|
|
{
|
|
AutoLockWorkerThreadState lock;
|
|
GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC();
|
|
|
|
for (size_t i = 0; i < waiting.length(); i++) {
|
|
ParseTask *task = waiting[i];
|
|
if (task->runtimeMatches(rt)) {
|
|
newTasks.append(task);
|
|
WorkerThreadState().remove(waiting, &i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newTasks.empty())
|
|
return;
|
|
|
|
// This logic should mirror the contents of the !activeGCInAtomsZone()
|
|
// branch in StartOffThreadParseScript:
|
|
|
|
for (size_t i = 0; i < newTasks.length(); i++)
|
|
newTasks[i]->activate(rt);
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
for (size_t i = 0; i < newTasks.length(); i++)
|
|
WorkerThreadState().parseWorklist().append(newTasks[i]);
|
|
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
|
|
}
|
|
|
|
static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
|
|
static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
|
|
|
|
void
|
|
GlobalWorkerThreadState::ensureInitialized()
|
|
{
|
|
JS_ASSERT(this == &WorkerThreadState());
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (threads)
|
|
return;
|
|
|
|
threads = js_pod_calloc<WorkerThread>(threadCount);
|
|
if (!threads)
|
|
CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
|
|
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
WorkerThread &helper = threads[i];
|
|
helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
|
|
helper.thread = PR_CreateThread(PR_USER_THREAD,
|
|
WorkerThread::ThreadMain, &helper,
|
|
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
|
|
if (!helper.thread || !helper.threadData.ref().init())
|
|
CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
|
|
}
|
|
|
|
resetAsmJSFailureState();
|
|
}
|
|
|
|
GlobalWorkerThreadState::GlobalWorkerThreadState()
|
|
{
|
|
mozilla::PodZero(this);
|
|
|
|
cpuCount = GetCPUCount();
|
|
threadCount = ThreadCountForCPUCount(cpuCount);
|
|
|
|
MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
|
|
|
|
workerLock = PR_NewLock();
|
|
consumerWakeup = PR_NewCondVar(workerLock);
|
|
producerWakeup = PR_NewCondVar(workerLock);
|
|
}
|
|
|
|
void
|
|
GlobalWorkerThreadState::finish()
|
|
{
|
|
if (threads) {
|
|
for (size_t i = 0; i < threadCount; i++)
|
|
threads[i].destroy();
|
|
js_free(threads);
|
|
}
|
|
|
|
PR_DestroyCondVar(consumerWakeup);
|
|
PR_DestroyCondVar(producerWakeup);
|
|
PR_DestroyLock(workerLock);
|
|
}
|
|
|
|
void
|
|
GlobalWorkerThreadState::lock()
|
|
{
|
|
JS_ASSERT(!isLocked());
|
|
AssertCurrentThreadCanLock(WorkerThreadStateLock);
|
|
PR_Lock(workerLock);
|
|
#ifdef DEBUG
|
|
lockOwner = PR_GetCurrentThread();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
GlobalWorkerThreadState::unlock()
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
#ifdef DEBUG
|
|
lockOwner = nullptr;
|
|
#endif
|
|
PR_Unlock(workerLock);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool
|
|
GlobalWorkerThreadState::isLocked()
|
|
{
|
|
return lockOwner == PR_GetCurrentThread();
|
|
}
|
|
#endif
|
|
|
|
void
|
|
GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
#ifdef DEBUG
|
|
lockOwner = nullptr;
|
|
#endif
|
|
DebugOnly<PRStatus> status =
|
|
PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
|
|
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
|
|
JS_ASSERT(status == PR_SUCCESS);
|
|
#ifdef DEBUG
|
|
lockOwner = PR_GetCurrentThread();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
GlobalWorkerThreadState::notifyAll(CondVar which)
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
|
|
}
|
|
|
|
void
|
|
GlobalWorkerThreadState::notifyOne(CondVar which)
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
|
|
}
|
|
|
|
bool
|
|
GlobalWorkerThreadState::canStartAsmJSCompile()
|
|
{
|
|
// Don't execute an AsmJS job if an earlier one failed.
|
|
JS_ASSERT(isLocked());
|
|
return !asmJSWorklist().empty() && !numAsmJSFailedJobs;
|
|
}
|
|
|
|
bool
|
|
GlobalWorkerThreadState::canStartIonCompile()
|
|
{
|
|
// A worker thread can begin an Ion compilation if (a) there is some script
|
|
// which is waiting to be compiled, and (b) no other worker thread is
|
|
// currently compiling a script. The latter condition ensures that two
|
|
// compilations cannot simultaneously occur.
|
|
if (ionWorklist().empty())
|
|
return false;
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].ionBuilder)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GlobalWorkerThreadState::canStartParseTask()
|
|
{
|
|
// Don't allow simultaneous off thread parses, to reduce contention on the
|
|
// atoms table. Note that asm.js compilation depends on this to avoid
|
|
// stalling the worker thread, as off thread parse tasks can trigger and
|
|
// block on other off thread asm.js compilation tasks.
|
|
JS_ASSERT(isLocked());
|
|
if (parseWorklist().empty())
|
|
return false;
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].parseTask)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GlobalWorkerThreadState::canStartCompressionTask()
|
|
{
|
|
return !compressionWorklist().empty();
|
|
}
|
|
|
|
static void
|
|
CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
|
|
{
|
|
// We should never hit this, since nested scripts are also constructed via
|
|
// BytecodeEmitter instances on the stack.
|
|
JS_CHECK_RECURSION(cx, return);
|
|
|
|
// Recurse to any nested scripts.
|
|
if (script->hasObjects()) {
|
|
ObjectArray *objects = script->objects();
|
|
for (size_t i = 0; i < objects->length; i++) {
|
|
JSObject *obj = objects->vector[i];
|
|
if (obj->is<JSFunction>()) {
|
|
JSFunction *fun = &obj->as<JSFunction>();
|
|
if (fun->hasScript()) {
|
|
RootedScript nested(cx, fun->nonLazyScript());
|
|
CallNewScriptHookForAllScripts(cx, nested);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The global new script hook is called on every script that was compiled.
|
|
RootedFunction function(cx, script->functionNonDelazifying());
|
|
CallNewScriptHook(cx, script, function);
|
|
}
|
|
|
|
JSScript *
|
|
GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
|
|
{
|
|
ScopedJSDeletePtr<ParseTask> parseTask;
|
|
|
|
// The token is a ParseTask* which should be in the finished list.
|
|
// Find and remove its entry.
|
|
{
|
|
AutoLockWorkerThreadState lock;
|
|
ParseTaskVector &finished = parseFinishedList();
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
if (finished[i] == token) {
|
|
parseTask = finished[i];
|
|
remove(finished, &i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
JS_ASSERT(parseTask);
|
|
|
|
// Mark the zone as no longer in use by an ExclusiveContext, and available
|
|
// to be collected by the GC.
|
|
rt->clearUsedByExclusiveThread(parseTask->cx->zone());
|
|
if (!maybecx) {
|
|
return nullptr;
|
|
}
|
|
JSContext *cx = maybecx;
|
|
JS_ASSERT(cx->compartment());
|
|
|
|
// Make sure we have all the constructors we need for the prototype
|
|
// remapping below, since we can't GC while that's happening.
|
|
Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
|
|
if (!GlobalObject::ensureConstructor(cx, global, JSProto_Object) ||
|
|
!GlobalObject::ensureConstructor(cx, global, JSProto_Array) ||
|
|
!GlobalObject::ensureConstructor(cx, global, JSProto_Function) ||
|
|
!GlobalObject::ensureConstructor(cx, global, JSProto_RegExp) ||
|
|
!GlobalObject::ensureConstructor(cx, global, JSProto_Iterator))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Point the prototypes of any objects in the script's compartment to refer
|
|
// to the corresponding prototype in the new compartment. This will briefly
|
|
// create cross compartment pointers, which will be fixed by the
|
|
// MergeCompartments call below.
|
|
for (gc::ZoneCellIter iter(parseTask->cx->zone(), gc::FINALIZE_TYPE_OBJECT);
|
|
!iter.done();
|
|
iter.next())
|
|
{
|
|
types::TypeObject *object = iter.get<types::TypeObject>();
|
|
TaggedProto proto(object->proto());
|
|
if (!proto.isObject())
|
|
continue;
|
|
|
|
JSProtoKey key = JS::IdentifyStandardPrototype(proto.toObject());
|
|
if (key == JSProto_Null)
|
|
continue;
|
|
JS_ASSERT(key == JSProto_Object || key == JSProto_Array ||
|
|
key == JSProto_Function || key == JSProto_RegExp ||
|
|
key == JSProto_Iterator);
|
|
|
|
JSObject *newProto = GetBuiltinPrototypePure(global, key);
|
|
JS_ASSERT(newProto);
|
|
|
|
object->setProtoUnchecked(newProto);
|
|
}
|
|
|
|
// Move the parsed script and all its contents into the desired compartment.
|
|
gc::MergeCompartments(parseTask->cx->compartment(), cx->compartment());
|
|
parseTask->finish();
|
|
|
|
RootedScript script(rt, parseTask->script);
|
|
assertSameCompartment(cx, script);
|
|
|
|
// Report any error or warnings generated during the parse, and inform the
|
|
// debugger about the compiled scripts.
|
|
for (size_t i = 0; i < parseTask->errors.length(); i++)
|
|
parseTask->errors[i]->throwError(cx);
|
|
if (parseTask->overRecursed)
|
|
js_ReportOverRecursed(cx);
|
|
|
|
if (script) {
|
|
// The Debugger only needs to be told about the topmost script that was compiled.
|
|
GlobalObject *compileAndGoGlobal = nullptr;
|
|
if (script->compileAndGo())
|
|
compileAndGoGlobal = &script->global();
|
|
Debugger::onNewScript(cx, script, compileAndGoGlobal);
|
|
|
|
// The NewScript hook needs to be called for all compiled scripts.
|
|
CallNewScriptHookForAllScripts(cx, script);
|
|
}
|
|
|
|
return script;
|
|
}
|
|
|
|
void
|
|
WorkerThread::destroy()
|
|
{
|
|
if (thread) {
|
|
{
|
|
AutoLockWorkerThreadState lock;
|
|
terminate = true;
|
|
|
|
/* Notify all workers, to ensure that this thread wakes up. */
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
|
|
}
|
|
|
|
PR_JoinThread(thread);
|
|
}
|
|
|
|
if (!threadData.empty())
|
|
threadData.destroy();
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
WorkerThread::ThreadMain(void *arg)
|
|
{
|
|
PR_SetCurrentThreadName("Analysis Helper");
|
|
static_cast<WorkerThread *>(arg)->threadLoop();
|
|
}
|
|
|
|
void
|
|
WorkerThread::handleAsmJSWorkload()
|
|
{
|
|
#ifdef JS_ION
|
|
JS_ASSERT(WorkerThreadState().isLocked());
|
|
JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
|
|
JS_ASSERT(idle());
|
|
|
|
asmData = WorkerThreadState().asmJSWorklist().popCopy();
|
|
bool success = false;
|
|
|
|
do {
|
|
AutoUnlockWorkerThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime);
|
|
|
|
jit::IonContext icx(asmData->mir->compartment->runtime(),
|
|
asmData->mir->compartment,
|
|
&asmData->mir->alloc());
|
|
|
|
int64_t before = PRMJ_Now();
|
|
|
|
if (!OptimizeMIR(asmData->mir))
|
|
break;
|
|
|
|
asmData->lir = GenerateLIR(asmData->mir);
|
|
if (!asmData->lir)
|
|
break;
|
|
|
|
int64_t after = PRMJ_Now();
|
|
asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC;
|
|
|
|
success = true;
|
|
} while(0);
|
|
|
|
// On failure, signal parent for harvesting in CancelOutstandingJobs().
|
|
if (!success) {
|
|
WorkerThreadState().noteAsmJSFailure(asmData->func);
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
|
|
asmData = nullptr;
|
|
return;
|
|
}
|
|
|
|
// On success, move work to the finished list.
|
|
WorkerThreadState().asmJSFinishedList().append(asmData);
|
|
asmData = nullptr;
|
|
|
|
// Notify the main thread in case it's blocked waiting for a LifoAlloc.
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
|
|
#else
|
|
MOZ_CRASH();
|
|
#endif // JS_ION
|
|
}
|
|
|
|
void
|
|
WorkerThread::handleIonWorkload()
|
|
{
|
|
#ifdef JS_ION
|
|
JS_ASSERT(WorkerThreadState().isLocked());
|
|
JS_ASSERT(WorkerThreadState().canStartIonCompile());
|
|
JS_ASSERT(idle());
|
|
|
|
ionBuilder = WorkerThreadState().ionWorklist().popCopy();
|
|
|
|
TraceLogger *logger = TraceLoggerForCurrentThread();
|
|
AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script()));
|
|
AutoTraceLog logCompile(logger, TraceLogger::IonCompilation);
|
|
|
|
JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread();
|
|
|
|
{
|
|
AutoUnlockWorkerThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.addr(),
|
|
ionBuilder->script()->runtimeFromAnyThread());
|
|
jit::IonContext ictx(jit::CompileRuntime::get(rt),
|
|
jit::CompileCompartment::get(ionBuilder->script()->compartment()),
|
|
&ionBuilder->alloc());
|
|
ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
|
|
}
|
|
|
|
FinishOffThreadIonCompile(ionBuilder);
|
|
ionBuilder = nullptr;
|
|
|
|
// Ping the main thread so that the compiled code can be incorporated
|
|
// at the next interrupt callback. Don't interrupt Ion code for this, as
|
|
// this incorporation can be delayed indefinitely without affecting
|
|
// performance as long as the main thread is actually executing Ion code.
|
|
rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon);
|
|
|
|
// Notify the main thread in case it is waiting for the compilation to finish.
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
|
|
#else
|
|
MOZ_CRASH();
|
|
#endif // JS_ION
|
|
}
|
|
|
|
void
|
|
ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
|
|
{
|
|
workerThread_ = workerThread;
|
|
perThreadData = workerThread->threadData.addr();
|
|
}
|
|
|
|
frontend::CompileError &
|
|
ExclusiveContext::addPendingCompileError()
|
|
{
|
|
frontend::CompileError *error = js_new<frontend::CompileError>();
|
|
if (!error)
|
|
MOZ_CRASH();
|
|
if (!workerThread()->parseTask->errors.append(error))
|
|
MOZ_CRASH();
|
|
return *error;
|
|
}
|
|
|
|
void
|
|
ExclusiveContext::addPendingOverRecursed()
|
|
{
|
|
if (workerThread()->parseTask)
|
|
workerThread()->parseTask->overRecursed = true;
|
|
}
|
|
|
|
void
|
|
WorkerThread::handleParseWorkload()
|
|
{
|
|
JS_ASSERT(WorkerThreadState().isLocked());
|
|
JS_ASSERT(WorkerThreadState().canStartParseTask());
|
|
JS_ASSERT(idle());
|
|
|
|
parseTask = WorkerThreadState().parseWorklist().popCopy();
|
|
parseTask->cx->setWorkerThread(this);
|
|
|
|
{
|
|
AutoUnlockWorkerThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.addr(),
|
|
parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
|
|
SourceBufferHolder srcBuf(parseTask->chars, parseTask->length,
|
|
SourceBufferHolder::NoOwnership);
|
|
parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
|
|
NullPtr(), NullPtr(),
|
|
parseTask->options,
|
|
srcBuf);
|
|
}
|
|
|
|
// The callback is invoked while we are still off the main thread.
|
|
parseTask->callback(parseTask, parseTask->callbackData);
|
|
|
|
// FinishOffThreadScript will need to be called on the script to
|
|
// migrate it into the correct compartment.
|
|
WorkerThreadState().parseFinishedList().append(parseTask);
|
|
|
|
parseTask = nullptr;
|
|
|
|
// Notify the main thread in case it is waiting for the parse/emit to finish.
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
|
|
}
|
|
|
|
void
|
|
WorkerThread::handleCompressionWorkload()
|
|
{
|
|
JS_ASSERT(WorkerThreadState().isLocked());
|
|
JS_ASSERT(WorkerThreadState().canStartCompressionTask());
|
|
JS_ASSERT(idle());
|
|
|
|
compressionTask = WorkerThreadState().compressionWorklist().popCopy();
|
|
compressionTask->workerThread = this;
|
|
|
|
{
|
|
AutoUnlockWorkerThreadState unlock;
|
|
if (!compressionTask->work())
|
|
compressionTask->setOOM();
|
|
}
|
|
|
|
compressionTask->workerThread = nullptr;
|
|
compressionTask = nullptr;
|
|
|
|
// Notify the main thread in case it is waiting for the compression to finish.
|
|
WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
|
|
{
|
|
EnsureWorkerThreadsInitialized(cx);
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
if (!WorkerThreadState().compressionWorklist().append(task))
|
|
return false;
|
|
|
|
WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
for (size_t i = 0; i < compressionWorklist().length(); i++) {
|
|
if (compressionWorklist()[i] == task)
|
|
return true;
|
|
}
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].compressionTask == task)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SourceCompressionTask::complete()
|
|
{
|
|
JS_ASSERT_IF(!ss, !chars);
|
|
if (active()) {
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
while (WorkerThreadState().compressionInProgress(this))
|
|
WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
|
|
|
|
ss->ready_ = true;
|
|
|
|
// Update memory accounting.
|
|
if (!oom)
|
|
cx->updateMallocCounter(ss->computedSizeOfData());
|
|
|
|
ss = nullptr;
|
|
chars = nullptr;
|
|
}
|
|
if (oom) {
|
|
js_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SourceCompressionTask *
|
|
GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
|
|
{
|
|
JS_ASSERT(isLocked());
|
|
for (size_t i = 0; i < compressionWorklist().length(); i++) {
|
|
SourceCompressionTask *task = compressionWorklist()[i];
|
|
if (task->source() == ss)
|
|
return task;
|
|
}
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
SourceCompressionTask *task = threads[i].compressionTask;
|
|
if (task && task->source() == ss)
|
|
return task;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const jschar *
|
|
ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
|
|
{
|
|
// If this is being compressed off thread, return its uncompressed chars.
|
|
|
|
if (ready()) {
|
|
// Compression has already finished on the source.
|
|
return nullptr;
|
|
}
|
|
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
// Look for a token that hasn't finished compressing and whose source is
|
|
// the given ScriptSource.
|
|
if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
|
|
return task->uncompressedChars();
|
|
|
|
// Compressing has finished, so this ScriptSource is ready. Avoid future
|
|
// queries on the worker thread state when getting the chars.
|
|
ready_ = true;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
WorkerThread::threadLoop()
|
|
{
|
|
JS::AutoAssertNoGC nogc;
|
|
AutoLockWorkerThreadState lock;
|
|
|
|
js::TlsPerThreadData.set(threadData.addr());
|
|
|
|
// Compute the thread's stack limit, for over-recursed checks.
|
|
uintptr_t stackLimit = GetNativeStackBase();
|
|
#if JS_STACK_GROWTH_DIRECTION > 0
|
|
stackLimit += WORKER_STACK_QUOTA;
|
|
#else
|
|
stackLimit -= WORKER_STACK_QUOTA;
|
|
#endif
|
|
for (size_t i = 0; i < ArrayLength(threadData.ref().nativeStackLimit); i++)
|
|
threadData.ref().nativeStackLimit[i] = stackLimit;
|
|
|
|
while (true) {
|
|
JS_ASSERT(!ionBuilder && !asmData);
|
|
|
|
// Block until a task is available.
|
|
while (true) {
|
|
if (terminate)
|
|
return;
|
|
if (WorkerThreadState().canStartIonCompile() ||
|
|
WorkerThreadState().canStartAsmJSCompile() ||
|
|
WorkerThreadState().canStartParseTask() ||
|
|
WorkerThreadState().canStartCompressionTask())
|
|
{
|
|
break;
|
|
}
|
|
WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
|
|
}
|
|
|
|
// Dispatch tasks, prioritizing AsmJS work.
|
|
if (WorkerThreadState().canStartAsmJSCompile())
|
|
handleAsmJSWorkload();
|
|
else if (WorkerThreadState().canStartIonCompile())
|
|
handleIonWorkload();
|
|
else if (WorkerThreadState().canStartParseTask())
|
|
handleParseWorkload();
|
|
else if (WorkerThreadState().canStartCompressionTask())
|
|
handleCompressionWorkload();
|
|
else
|
|
MOZ_ASSUME_UNREACHABLE("No task to perform");
|
|
}
|
|
}
|
|
|
|
#else /* JS_THREADSAFE */
|
|
|
|
using namespace js;
|
|
|
|
#ifdef JS_ION
|
|
|
|
bool
|
|
js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
|
|
}
|
|
|
|
#endif // JS_ION
|
|
|
|
void
|
|
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
|
|
{
|
|
}
|
|
|
|
void
|
|
js::CancelOffThreadParses(JSRuntime *rt)
|
|
{
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
|
|
const jschar *chars, size_t length,
|
|
JS::OffThreadCompileCallback callback, void *callbackData)
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compression not available");
|
|
}
|
|
|
|
bool
|
|
SourceCompressionTask::complete()
|
|
{
|
|
JS_ASSERT(!active() && !oom);
|
|
return true;
|
|
}
|
|
|
|
const jschar *
|
|
ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
|
|
{
|
|
JS_ASSERT(ready());
|
|
return nullptr;
|
|
}
|
|
|
|
frontend::CompileError &
|
|
ExclusiveContext::addPendingCompileError()
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
|
|
}
|
|
|
|
void
|
|
ExclusiveContext::addPendingOverRecursed()
|
|
{
|
|
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
|
|
}
|
|
|
|
#endif /* JS_THREADSAFE */
|