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: /* michael@0: * Definitions for managing off-main-thread work using a shared, per runtime michael@0: * worklist. Worklist items are engine internal, and are distinct from e.g. michael@0: * web workers. michael@0: */ michael@0: michael@0: #ifndef jsworkers_h michael@0: #define jsworkers_h michael@0: michael@0: #include "mozilla/GuardObjects.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jslock.h" michael@0: michael@0: #include "frontend/TokenStream.h" michael@0: #include "jit/Ion.h" michael@0: michael@0: namespace js { michael@0: michael@0: struct WorkerThread; michael@0: struct AsmJSParallelTask; michael@0: struct ParseTask; michael@0: namespace jit { michael@0: class IonBuilder; michael@0: } michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: michael@0: // Per-process state for off thread work items. michael@0: class GlobalWorkerThreadState michael@0: { michael@0: public: michael@0: // Number of CPUs to treat this machine as having when creating threads. michael@0: // May be accessed without locking. michael@0: size_t cpuCount; michael@0: michael@0: // Number of threads to create. May be accessed without locking. michael@0: size_t threadCount; michael@0: michael@0: typedef Vector IonBuilderVector; michael@0: typedef Vector AsmJSParallelTaskVector; michael@0: typedef Vector ParseTaskVector; michael@0: typedef Vector SourceCompressionTaskVector; michael@0: michael@0: // List of available threads, or null if the thread state has not been initialized. michael@0: WorkerThread *threads; michael@0: michael@0: private: michael@0: // The lists below are all protected by |lock|. michael@0: michael@0: // Ion compilation worklist and finished jobs. michael@0: IonBuilderVector ionWorklist_, ionFinishedList_; michael@0: michael@0: // AsmJS worklist and finished jobs. michael@0: // michael@0: // Simultaneous AsmJS compilations all service the same AsmJS module. michael@0: // The main thread must pick up finished optimizations and perform codegen. michael@0: // |asmJSCompilationInProgress| is used to avoid triggering compilations michael@0: // for more than one module at a time. michael@0: AsmJSParallelTaskVector asmJSWorklist_, asmJSFinishedList_; michael@0: michael@0: public: michael@0: // For now, only allow a single parallel asm.js compilation to happen at a michael@0: // time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc. michael@0: mozilla::Atomic asmJSCompilationInProgress; michael@0: michael@0: private: michael@0: // Script parsing/emitting worklist and finished jobs. michael@0: ParseTaskVector parseWorklist_, parseFinishedList_; michael@0: michael@0: // Parse tasks waiting for an atoms-zone GC to complete. michael@0: ParseTaskVector parseWaitingOnGC_; michael@0: michael@0: // Source compression worklist. michael@0: SourceCompressionTaskVector compressionWorklist_; michael@0: michael@0: public: michael@0: GlobalWorkerThreadState(); michael@0: michael@0: void ensureInitialized(); michael@0: void finish(); michael@0: michael@0: void lock(); michael@0: void unlock(); michael@0: michael@0: # ifdef DEBUG michael@0: bool isLocked(); michael@0: # endif michael@0: michael@0: enum CondVar { michael@0: // For notifying threads waiting for work that they may be able to make progress. michael@0: CONSUMER, michael@0: michael@0: // For notifying threads doing work that they may be able to make progress. michael@0: PRODUCER michael@0: }; michael@0: michael@0: void wait(CondVar which, uint32_t timeoutMillis = 0); michael@0: void notifyAll(CondVar which); michael@0: void notifyOne(CondVar which); michael@0: michael@0: // Helper method for removing items from the vectors below while iterating over them. michael@0: template michael@0: void remove(T &vector, size_t *index) michael@0: { michael@0: vector[(*index)--] = vector.back(); michael@0: vector.popBack(); michael@0: } michael@0: michael@0: IonBuilderVector &ionWorklist() { michael@0: JS_ASSERT(isLocked()); michael@0: return ionWorklist_; michael@0: } michael@0: IonBuilderVector &ionFinishedList() { michael@0: JS_ASSERT(isLocked()); michael@0: return ionFinishedList_; michael@0: } michael@0: michael@0: AsmJSParallelTaskVector &asmJSWorklist() { michael@0: JS_ASSERT(isLocked()); michael@0: return asmJSWorklist_; michael@0: } michael@0: AsmJSParallelTaskVector &asmJSFinishedList() { michael@0: JS_ASSERT(isLocked()); michael@0: return asmJSFinishedList_; michael@0: } michael@0: michael@0: ParseTaskVector &parseWorklist() { michael@0: JS_ASSERT(isLocked()); michael@0: return parseWorklist_; michael@0: } michael@0: ParseTaskVector &parseFinishedList() { michael@0: JS_ASSERT(isLocked()); michael@0: return parseFinishedList_; michael@0: } michael@0: ParseTaskVector &parseWaitingOnGC() { michael@0: JS_ASSERT(isLocked()); michael@0: return parseWaitingOnGC_; michael@0: } michael@0: michael@0: SourceCompressionTaskVector &compressionWorklist() { michael@0: JS_ASSERT(isLocked()); michael@0: return compressionWorklist_; michael@0: } michael@0: michael@0: bool canStartAsmJSCompile(); michael@0: bool canStartIonCompile(); michael@0: bool canStartParseTask(); michael@0: bool canStartCompressionTask(); michael@0: michael@0: uint32_t harvestFailedAsmJSJobs() { michael@0: JS_ASSERT(isLocked()); michael@0: uint32_t n = numAsmJSFailedJobs; michael@0: numAsmJSFailedJobs = 0; michael@0: return n; michael@0: } michael@0: void noteAsmJSFailure(void *func) { michael@0: // Be mindful to signal the main thread after calling this function. michael@0: JS_ASSERT(isLocked()); michael@0: if (!asmJSFailedFunction) michael@0: asmJSFailedFunction = func; michael@0: numAsmJSFailedJobs++; michael@0: } michael@0: bool asmJSWorkerFailed() const { michael@0: return bool(numAsmJSFailedJobs); michael@0: } michael@0: void resetAsmJSFailureState() { michael@0: numAsmJSFailedJobs = 0; michael@0: asmJSFailedFunction = nullptr; michael@0: } michael@0: void *maybeAsmJSFailedFunction() const { michael@0: return asmJSFailedFunction; michael@0: } michael@0: michael@0: JSScript *finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token); michael@0: bool compressionInProgress(SourceCompressionTask *task); michael@0: SourceCompressionTask *compressionTaskForSource(ScriptSource *ss); michael@0: michael@0: private: michael@0: michael@0: /* michael@0: * Lock protecting all mutable shared state accessed by helper threads, and michael@0: * used by all condition variables. michael@0: */ michael@0: PRLock *workerLock; michael@0: michael@0: # ifdef DEBUG michael@0: PRThread *lockOwner; michael@0: # endif michael@0: michael@0: /* Condvars for threads waiting/notifying each other. */ michael@0: PRCondVar *consumerWakeup; michael@0: PRCondVar *producerWakeup; michael@0: michael@0: /* michael@0: * Number of AsmJS workers that encountered failure for the active module. michael@0: * Their parent is logically the main thread, and this number serves for harvesting. michael@0: */ michael@0: uint32_t numAsmJSFailedJobs; michael@0: michael@0: /* michael@0: * Function index |i| in |Module.function(i)| of first failed AsmJS function. michael@0: * -1 if no function has failed. michael@0: */ michael@0: void *asmJSFailedFunction; michael@0: }; michael@0: michael@0: static inline GlobalWorkerThreadState & michael@0: WorkerThreadState() michael@0: { michael@0: extern GlobalWorkerThreadState gWorkerThreadState; michael@0: return gWorkerThreadState; michael@0: } michael@0: michael@0: /* Individual helper thread, one allocated per core. */ michael@0: struct WorkerThread michael@0: { michael@0: mozilla::Maybe threadData; michael@0: PRThread *thread; michael@0: michael@0: /* Indicate to an idle thread that it should finish executing. */ michael@0: bool terminate; michael@0: michael@0: /* Any builder currently being compiled by Ion on this thread. */ michael@0: jit::IonBuilder *ionBuilder; michael@0: michael@0: /* Any AsmJS data currently being optimized by Ion on this thread. */ michael@0: AsmJSParallelTask *asmData; michael@0: michael@0: /* Any source being parsed/emitted on this thread. */ michael@0: ParseTask *parseTask; michael@0: michael@0: /* Any source being compressed on this thread. */ michael@0: SourceCompressionTask *compressionTask; michael@0: michael@0: bool idle() const { michael@0: return !ionBuilder && !asmData && !parseTask && !compressionTask; michael@0: } michael@0: michael@0: void destroy(); michael@0: michael@0: void handleAsmJSWorkload(); michael@0: void handleIonWorkload(); michael@0: void handleParseWorkload(); michael@0: void handleCompressionWorkload(); michael@0: michael@0: static void ThreadMain(void *arg); michael@0: void threadLoop(); michael@0: }; michael@0: michael@0: #endif /* JS_THREADSAFE */ michael@0: michael@0: /* Methods for interacting with worker threads. */ michael@0: michael@0: // Initialize worker threads unless already initialized. michael@0: void michael@0: EnsureWorkerThreadsInitialized(ExclusiveContext *cx); michael@0: michael@0: // This allows the JS shell to override GetCPUCount() when passed the michael@0: // --thread-count=N option. michael@0: void michael@0: SetFakeCPUCount(size_t count); michael@0: michael@0: #ifdef JS_ION michael@0: michael@0: /* Perform MIR optimization and LIR generation on a single function. */ michael@0: bool michael@0: StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData); michael@0: michael@0: /* michael@0: * Schedule an Ion compilation for a script, given a builder which has been michael@0: * generated and read everything needed from the VM state. michael@0: */ michael@0: bool michael@0: StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder); michael@0: michael@0: #endif // JS_ION michael@0: michael@0: /* michael@0: * Cancel a scheduled or in progress Ion compilation for script. If script is michael@0: * nullptr, all compilations for the compartment are cancelled. michael@0: */ michael@0: void michael@0: CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script); michael@0: michael@0: /* Cancel all scheduled, in progress or finished parses for runtime. */ michael@0: void michael@0: CancelOffThreadParses(JSRuntime *runtime); michael@0: michael@0: /* michael@0: * Start a parse/emit cycle for a stream of source. The characters must stay michael@0: * alive until the compilation finishes. michael@0: */ michael@0: bool michael@0: 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: /* michael@0: * Called at the end of GC to enqueue any Parse tasks that were waiting on an michael@0: * atoms-zone GC to finish. michael@0: */ michael@0: void michael@0: EnqueuePendingParseTasksAfterGC(JSRuntime *rt); michael@0: michael@0: /* Start a compression job for the specified token. */ michael@0: bool michael@0: StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task); michael@0: michael@0: class AutoLockWorkerThreadState michael@0: { michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: public: michael@0: AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: WorkerThreadState().lock(); michael@0: } michael@0: michael@0: ~AutoLockWorkerThreadState() { michael@0: WorkerThreadState().unlock(); michael@0: } michael@0: #else michael@0: public: michael@0: AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: #endif michael@0: }; michael@0: michael@0: class AutoUnlockWorkerThreadState michael@0: { michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: michael@0: public: michael@0: michael@0: AutoUnlockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: #ifdef JS_THREADSAFE michael@0: WorkerThreadState().unlock(); michael@0: #endif michael@0: } michael@0: michael@0: ~AutoUnlockWorkerThreadState() michael@0: { michael@0: #ifdef JS_THREADSAFE michael@0: WorkerThreadState().lock(); michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: #ifdef JS_ION michael@0: struct AsmJSParallelTask michael@0: { michael@0: JSRuntime *runtime; // Associated runtime. michael@0: LifoAlloc lifo; // Provider of all heap memory used for compilation. michael@0: void *func; // Really, a ModuleCompiler::Func* michael@0: jit::MIRGenerator *mir; // Passed from main thread to worker. michael@0: jit::LIRGraph *lir; // Passed from worker to main thread. michael@0: unsigned compileTime; michael@0: michael@0: AsmJSParallelTask(size_t defaultChunkSize) michael@0: : runtime(nullptr), lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0) michael@0: { } michael@0: michael@0: void init(JSRuntime *rt, void *func, jit::MIRGenerator *mir) { michael@0: this->runtime = rt; michael@0: this->func = func; michael@0: this->mir = mir; michael@0: this->lir = nullptr; michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: struct ParseTask michael@0: { michael@0: ExclusiveContext *cx; michael@0: OwningCompileOptions options; michael@0: const jschar *chars; michael@0: size_t length; michael@0: LifoAlloc alloc; michael@0: michael@0: // Rooted pointer to the global object used by 'cx'. michael@0: PersistentRootedObject exclusiveContextGlobal; michael@0: michael@0: // Saved GC-managed CompileOptions fields that will populate slots in michael@0: // the ScriptSourceObject. We create the ScriptSourceObject in the michael@0: // compilation's temporary compartment, so storing these values there michael@0: // at that point would create cross-compartment references. Instead we michael@0: // hold them here, and install them after merging the compartments. michael@0: PersistentRootedObject optionsElement; michael@0: PersistentRootedScript optionsIntroductionScript; michael@0: michael@0: // Callback invoked off the main thread when the parse finishes. michael@0: JS::OffThreadCompileCallback callback; michael@0: void *callbackData; michael@0: michael@0: // Holds the final script between the invocation of the callback and the michael@0: // point where FinishOffThreadScript is called, which will destroy the michael@0: // ParseTask. michael@0: JSScript *script; michael@0: michael@0: // Any errors or warnings produced during compilation. These are reported michael@0: // when finishing the script. michael@0: Vector errors; michael@0: bool overRecursed; michael@0: michael@0: ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, michael@0: JSContext *initCx, const jschar *chars, size_t length, michael@0: JS::OffThreadCompileCallback callback, void *callbackData); michael@0: bool init(JSContext *cx, const ReadOnlyCompileOptions &options); michael@0: michael@0: void activate(JSRuntime *rt); michael@0: void finish(); michael@0: michael@0: bool runtimeMatches(JSRuntime *rt) { michael@0: return exclusiveContextGlobal->runtimeFromAnyThread() == rt; michael@0: } michael@0: michael@0: ~ParseTask(); michael@0: }; michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: // Return whether, if a new parse task was started, it would need to wait for michael@0: // an in-progress GC to complete before starting. michael@0: extern bool michael@0: OffThreadParsingMustWaitForGC(JSRuntime *rt); michael@0: #endif michael@0: michael@0: // Compression tasks are allocated on the stack by their triggering thread, michael@0: // which will block on the compression completing as the task goes out of scope michael@0: // to ensure it completes at the required time. michael@0: struct SourceCompressionTask michael@0: { michael@0: friend class ScriptSource; michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: // Thread performing the compression. michael@0: WorkerThread *workerThread; michael@0: #endif michael@0: michael@0: private: michael@0: // Context from the triggering thread. Don't use this off thread! michael@0: ExclusiveContext *cx; michael@0: michael@0: ScriptSource *ss; michael@0: const jschar *chars; michael@0: bool oom; michael@0: michael@0: // Atomic flag to indicate to a worker thread that it should abort michael@0: // compression on the source. michael@0: mozilla::Atomic abort_; michael@0: michael@0: public: michael@0: explicit SourceCompressionTask(ExclusiveContext *cx) michael@0: : cx(cx), ss(nullptr), chars(nullptr), oom(false), abort_(false) michael@0: { michael@0: #ifdef JS_THREADSAFE michael@0: workerThread = nullptr; michael@0: #endif michael@0: } michael@0: michael@0: ~SourceCompressionTask() michael@0: { michael@0: complete(); michael@0: } michael@0: michael@0: bool work(); michael@0: bool complete(); michael@0: void abort() { abort_ = true; } michael@0: bool active() const { return !!ss; } michael@0: ScriptSource *source() { return ss; } michael@0: const jschar *uncompressedChars() { return chars; } michael@0: void setOOM() { oom = true; } michael@0: }; michael@0: michael@0: } /* namespace js */ michael@0: michael@0: #endif /* jsworkers_h */