diff -r 000000000000 -r 6474c204b198 js/src/jsworkers.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jsworkers.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1133 @@ +/* -*- 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(); + 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 workercx( + cx->new_(cx->runtime(), (PerThreadData *) nullptr, + ThreadSafeContext::Context_Exclusive)); + if (!workercx) + return false; + + ScopedJSDeletePtr task( + cx->new_(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(threadCount); + if (!threads) + CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized"); + + for (size_t i = 0; i < threadCount; i++) { + WorkerThread &helper = threads[i]; + helper.threadData.construct(static_cast(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 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 *fun = &obj->as(); + 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); +} + +static void +LeaveParseTaskZone(JSRuntime *rt, ParseTask *task) +{ + // Mark the zone as no longer in use by an ExclusiveContext, and available + // to be collected by the GC. + rt->clearUsedByExclusiveThread(task->cx->zone()); +} + +JSScript * +GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token) +{ + ScopedJSDeletePtr 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); + + if (!maybecx) { + LeaveParseTaskZone(rt, parseTask); + 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 global(cx, &cx->global()->as()); + 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)) + { + LeaveParseTaskZone(rt, parseTask); + return nullptr; + } + + LeaveParseTaskZone(rt, parseTask); + + // 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::CellIter iter(parseTask->cx->zone(), gc::FINALIZE_TYPE_OBJECT); + !iter.done(); + iter.next()) + { + types::TypeObject *object = iter.get(); + 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(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(); + 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 */