/* -*- 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/. */ /* * Definitions for managing off-main-thread work using a process wide list * of worklist items and pool of threads. Worklist items are engine internal, * and are distinct from e.g. web workers. */ #ifndef vm_HelperThreads_h #define vm_HelperThreads_h #include "mozilla/GuardObjects.h" #include "mozilla/PodOperations.h" #include "mozilla/TimeStamp.h" #include "mozilla/Variant.h" #include "jscntxt.h" #include "frontend/TokenStream.h" #include "jit/Ion.h" #include "threading/ConditionVariable.h" #include "vm/MutexIDs.h" namespace JS { struct Zone; } // namespace JS namespace js { class AutoLockHelperThreadState; class AutoUnlockHelperThreadState; class PromiseTask; struct HelperThread; struct ParseTask; namespace jit { class IonBuilder; } // namespace jit namespace wasm { class FuncIR; class FunctionCompileResults; class IonCompileTask; typedef Vector IonCompileTaskPtrVector; } // namespace wasm enum class ParseTaskKind { Script, Module }; // Per-process state for off thread work items. class GlobalHelperThreadState { friend class AutoLockHelperThreadState; friend class AutoUnlockHelperThreadState; public: // Number of CPUs to treat this machine as having when creating threads. // May be accessed without locking. size_t cpuCount; // Number of threads to create. May be accessed without locking. size_t threadCount; typedef Vector IonBuilderVector; typedef Vector ParseTaskVector; typedef Vector SourceCompressionTaskVector; typedef Vector GCHelperStateVector; typedef Vector GCParallelTaskVector; typedef Vector PromiseTaskVector; // List of available threads, or null if the thread state has not been initialized. using HelperThreadVector = Vector; UniquePtr threads; private: // The lists below are all protected by |lock|. // Ion compilation worklist and finished jobs. IonBuilderVector ionWorklist_, ionFinishedList_; // wasm worklist and finished jobs. wasm::IonCompileTaskPtrVector wasmWorklist_, wasmFinishedList_; public: // For now, only allow a single parallel wasm compilation to happen at a // time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc. mozilla::Atomic wasmCompilationInProgress; private: // Async tasks that, upon completion, are dispatched back to the JSContext's // owner thread via embedding callbacks instead of a finished list. PromiseTaskVector promiseTasks_; // Script parsing/emitting worklist and finished jobs. ParseTaskVector parseWorklist_, parseFinishedList_; // Parse tasks waiting for an atoms-zone GC to complete. ParseTaskVector parseWaitingOnGC_; // Source compression worklist. SourceCompressionTaskVector compressionWorklist_; // Runtimes which have sweeping / allocating work to do. GCHelperStateVector gcHelperWorklist_; // GC tasks needing to be done in parallel. GCParallelTaskVector gcParallelWorklist_; ParseTask* removeFinishedParseTask(ParseTaskKind kind, void* token); public: size_t maxIonCompilationThreads() const; size_t maxUnpausedIonCompilationThreads() const; size_t maxWasmCompilationThreads() const; size_t maxParseThreads() const; size_t maxCompressionThreads() const; size_t maxGCHelperThreads() const; size_t maxGCParallelThreads() const; GlobalHelperThreadState(); bool ensureInitialized(); void finish(); void finishThreads(); void lock(); void unlock(); enum CondVar { // For notifying threads waiting for work that they may be able to make progress. CONSUMER, // For notifying threads doing work that they may be able to make progress. PRODUCER, // For notifying threads doing work which are paused that they may be // able to resume making progress. PAUSE }; void wait(AutoLockHelperThreadState& locked, CondVar which, mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever()); void notifyAll(CondVar which, const AutoLockHelperThreadState&); void notifyOne(CondVar which, const AutoLockHelperThreadState&); // Helper method for removing items from the vectors below while iterating over them. template void remove(T& vector, size_t* index) { vector[(*index)--] = vector.back(); vector.popBack(); } IonBuilderVector& ionWorklist(const AutoLockHelperThreadState&) { return ionWorklist_; } IonBuilderVector& ionFinishedList(const AutoLockHelperThreadState&) { return ionFinishedList_; } wasm::IonCompileTaskPtrVector& wasmWorklist(const AutoLockHelperThreadState&) { return wasmWorklist_; } wasm::IonCompileTaskPtrVector& wasmFinishedList(const AutoLockHelperThreadState&) { return wasmFinishedList_; } PromiseTaskVector& promiseTasks(const AutoLockHelperThreadState&) { return promiseTasks_; } ParseTaskVector& parseWorklist(const AutoLockHelperThreadState&) { return parseWorklist_; } ParseTaskVector& parseFinishedList(const AutoLockHelperThreadState&) { return parseFinishedList_; } ParseTaskVector& parseWaitingOnGC(const AutoLockHelperThreadState&) { return parseWaitingOnGC_; } SourceCompressionTaskVector& compressionWorklist(const AutoLockHelperThreadState&) { return compressionWorklist_; } GCHelperStateVector& gcHelperWorklist(const AutoLockHelperThreadState&) { return gcHelperWorklist_; } GCParallelTaskVector& gcParallelWorklist(const AutoLockHelperThreadState&) { return gcParallelWorklist_; } bool canStartWasmCompile(const AutoLockHelperThreadState& lock); bool canStartPromiseTask(const AutoLockHelperThreadState& lock); bool canStartIonCompile(const AutoLockHelperThreadState& lock); bool canStartParseTask(const AutoLockHelperThreadState& lock); bool canStartCompressionTask(const AutoLockHelperThreadState& lock); bool canStartGCHelperTask(const AutoLockHelperThreadState& lock); bool canStartGCParallelTask(const AutoLockHelperThreadState& lock); // Unlike the methods above, the value returned by this method can change // over time, even if the helper thread state lock is held throughout. bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock); jit::IonBuilder* highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock, bool remove = false); HelperThread* lowestPriorityUnpausedIonCompileAtThreshold( const AutoLockHelperThreadState& lock); HelperThread* highestPriorityPausedIonCompile(const AutoLockHelperThreadState& lock); uint32_t harvestFailedWasmJobs(const AutoLockHelperThreadState&) { uint32_t n = numWasmFailedJobs; numWasmFailedJobs = 0; return n; } void noteWasmFailure(const AutoLockHelperThreadState&) { // Be mindful to signal the main thread after calling this function. numWasmFailedJobs++; } bool wasmFailed(const AutoLockHelperThreadState&) { return bool(numWasmFailedJobs); } JSScript* finishParseTask(JSContext* cx, ParseTaskKind kind, void* token); void cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token); void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask, Handle global, JSCompartment* dest); void trace(JSTracer* trc); private: /* * Number of wasm jobs that encountered failure for the active module. * Their parent is logically the main thread, and this number serves for harvesting. */ uint32_t numWasmFailedJobs; public: JSScript* finishScriptParseTask(JSContext* cx, void* token); JSObject* finishModuleParseTask(JSContext* cx, void* token); bool compressionInProgress(SourceCompressionTask* task, const AutoLockHelperThreadState& lock); SourceCompressionTask* compressionTaskForSource(ScriptSource* ss, const AutoLockHelperThreadState& lock); bool hasActiveThreads(const AutoLockHelperThreadState&); void waitForAllThreads(); template bool checkTaskThreadLimit(size_t maxThreads) const; private: /* * Lock protecting all mutable shared state accessed by helper threads, and * used by all condition variables. */ js::Mutex helperLock; /* Condvars for threads waiting/notifying each other. */ js::ConditionVariable consumerWakeup; js::ConditionVariable producerWakeup; js::ConditionVariable pauseWakeup; js::ConditionVariable& whichWakeup(CondVar which) { switch (which) { case CONSUMER: return consumerWakeup; case PRODUCER: return producerWakeup; case PAUSE: return pauseWakeup; default: MOZ_CRASH("Invalid CondVar in |whichWakeup|"); } } }; static inline GlobalHelperThreadState& HelperThreadState() { extern GlobalHelperThreadState* gHelperThreadState; MOZ_ASSERT(gHelperThreadState); return *gHelperThreadState; } /* Individual helper thread, one allocated per core. */ struct HelperThread { mozilla::Maybe threadData; mozilla::Maybe thread; /* * Indicate to a thread that it should terminate itself. This is only read * or written with the helper thread state lock held. */ bool terminate; /* * Indicate to a thread that it should pause execution. This is only * written with the helper thread state lock held, but may be read from * without the lock held. */ mozilla::Atomic pause; /* The current task being executed by this thread, if any. */ mozilla::Maybe> currentTask; bool idle() const { return currentTask.isNothing(); } /* Any builder currently being compiled by Ion on this thread. */ jit::IonBuilder* ionBuilder() { return maybeCurrentTaskAs(); } /* Any wasm data currently being optimized on this thread. */ wasm::IonCompileTask* wasmTask() { return maybeCurrentTaskAs(); } /* Any source being parsed/emitted on this thread. */ ParseTask* parseTask() { return maybeCurrentTaskAs(); } /* Any source being compressed on this thread. */ SourceCompressionTask* compressionTask() { return maybeCurrentTaskAs(); } /* Any GC state for background sweeping or allocating being performed. */ GCHelperState* gcHelperTask() { return maybeCurrentTaskAs(); } /* State required to perform a GC parallel task. */ GCParallelTask* gcParallelTask() { return maybeCurrentTaskAs(); } void destroy(); static void ThreadMain(void* arg); void threadLoop(); private: template T maybeCurrentTaskAs() { if (currentTask.isSome() && currentTask->is()) return currentTask->as(); return nullptr; } void handleWasmWorkload(AutoLockHelperThreadState& locked); void handlePromiseTaskWorkload(AutoLockHelperThreadState& locked); void handleIonWorkload(AutoLockHelperThreadState& locked); void handleParseWorkload(AutoLockHelperThreadState& locked, uintptr_t stackLimit); void handleCompressionWorkload(AutoLockHelperThreadState& locked); void handleGCHelperWorkload(AutoLockHelperThreadState& locked); void handleGCParallelWorkload(AutoLockHelperThreadState& locked); }; /* Methods for interacting with helper threads. */ // Create data structures used by helper threads. bool CreateHelperThreadsState(); // Destroy data structures used by helper threads. void DestroyHelperThreadsState(); // Initialize helper threads unless already initialized. bool EnsureHelperThreadsInitialized(); // This allows the JS shell to override GetCPUCount() when passed the // --thread-count=N option. void SetFakeCPUCount(size_t count); // Pause the current thread until it's pause flag is unset. void PauseCurrentHelperThread(); /* Perform MIR optimization and LIR generation on a single function. */ bool StartOffThreadWasmCompile(wasm::IonCompileTask* task); /* * If helper threads are available, start executing the given PromiseTask on a * helper thread, finishing back on the originating JSContext's owner thread. If * no helper threads are available, the PromiseTask is synchronously executed * and finished. */ bool StartPromiseTask(JSContext* cx, UniquePtr task); /* * Schedule an Ion compilation for a script, given a builder which has been * generated and read everything needed from the VM state. */ bool StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder); struct AllCompilations {}; struct ZonesInState { JSRuntime* runtime; JS::Zone::GCState state; }; using CompilationSelector = mozilla::Variant; /* * Cancel scheduled or in progress Ion compilations. */ void CancelOffThreadIonCompile(CompilationSelector selector, bool discardLazyLinkList); inline void CancelOffThreadIonCompile(JSScript* script) { CancelOffThreadIonCompile(CompilationSelector(script), true); } inline void CancelOffThreadIonCompile(JSCompartment* comp) { CancelOffThreadIonCompile(CompilationSelector(comp), true); } inline void CancelOffThreadIonCompile(JSRuntime* runtime, JS::Zone::GCState state) { CancelOffThreadIonCompile(CompilationSelector(ZonesInState{runtime, state}), true); } inline void CancelOffThreadIonCompile(JSRuntime* runtime) { CancelOffThreadIonCompile(CompilationSelector(runtime), true); } inline void CancelOffThreadIonCompile() { CancelOffThreadIonCompile(CompilationSelector(AllCompilations()), false); } #ifdef DEBUG bool HasOffThreadIonCompile(JSCompartment* comp); #endif /* Cancel all scheduled, in progress or finished parses for runtime. */ void CancelOffThreadParses(JSRuntime* runtime); /* * Start a parse/emit cycle for a stream of source. The characters must stay * alive until the compilation finishes. */ bool StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); bool StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); /* * Called at the end of GC to enqueue any Parse tasks that were waiting on an * atoms-zone GC to finish. */ void EnqueuePendingParseTasksAfterGC(JSRuntime* rt); struct AutoEnqueuePendingParseTasksAfterGC { const gc::GCRuntime& gc_; explicit AutoEnqueuePendingParseTasksAfterGC(const gc::GCRuntime& gc) : gc_(gc) {} ~AutoEnqueuePendingParseTasksAfterGC(); }; /* Start a compression job for the specified token. */ bool StartOffThreadCompression(ExclusiveContext* cx, SourceCompressionTask* task); class MOZ_RAII AutoLockHelperThreadState : public LockGuard { using Base = LockGuard; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: explicit AutoLockHelperThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) : Base(HelperThreadState().helperLock) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } }; class MOZ_RAII AutoUnlockHelperThreadState : public UnlockGuard { using Base = UnlockGuard; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: explicit AutoUnlockHelperThreadState(AutoLockHelperThreadState& locked MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : Base(locked) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } }; struct ParseTask { ParseTaskKind kind; ExclusiveContext* cx; OwningCompileOptions options; const char16_t* chars; size_t length; LifoAlloc alloc; // Rooted pointer to the global object used by 'cx'. JSObject* exclusiveContextGlobal; // Callback invoked off the main thread when the parse finishes. JS::OffThreadCompileCallback callback; void* callbackData; // Holds the final script between the invocation of the callback and the // point where FinishOffThreadScript is called, which will destroy the // ParseTask. JSScript* script; // Holds the ScriptSourceObject generated for the script compilation. ScriptSourceObject* sourceObject; // Any errors or warnings produced during compilation. These are reported // when finishing the script. Vector errors; bool overRecursed; bool outOfMemory; ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); bool init(JSContext* cx, const ReadOnlyCompileOptions& options); void activate(JSRuntime* rt); virtual void parse() = 0; bool finish(JSContext* cx); bool runtimeMatches(JSRuntime* rt) { return cx->runtimeMatches(rt); } virtual ~ParseTask(); void trace(JSTracer* trc); }; struct ScriptParseTask : public ParseTask { ScriptParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); void parse() override; }; struct ModuleParseTask : public ParseTask { ModuleParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx, const char16_t* chars, size_t length, JS::OffThreadCompileCallback callback, void* callbackData); void parse() override; }; // Return whether, if a new parse task was started, it would need to wait for // an in-progress GC to complete before starting. extern bool OffThreadParsingMustWaitForGC(JSRuntime* rt); // Compression tasks are allocated on the stack by their triggering thread, // which will block on the compression completing as the task goes out of scope // to ensure it completes at the required time. struct SourceCompressionTask { friend class ScriptSource; friend struct HelperThread; // Thread performing the compression. HelperThread* helperThread; private: // Context from the triggering thread. Don't use this off thread! ExclusiveContext* cx; ScriptSource* ss; // Atomic flag to indicate to a helper thread that it should abort // compression on the source. mozilla::Atomic abort_; // Stores the result of the compression. enum ResultType { OOM, Aborted, Success } result; mozilla::Maybe resultString; public: explicit SourceCompressionTask(ExclusiveContext* cx) : helperThread(nullptr) , cx(cx) , ss(nullptr) , abort_(false) , result(OOM) {} ~SourceCompressionTask() { complete(); } ResultType work(); bool complete(); void abort() { abort_ = true; } bool active() const { return !!ss; } ScriptSource* source() { return ss; } }; } /* namespace js */ #endif /* vm_HelperThreads_h */