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