diff options
Diffstat (limited to 'js/src/vm/HelperThreads.h')
-rw-r--r-- | js/src/vm/HelperThreads.h | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h new file mode 100644 index 000000000..55a51dc8d --- /dev/null +++ b/js/src/vm/HelperThreads.h @@ -0,0 +1,655 @@ +/* -*- 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<IonCompileTask*, 0, SystemAllocPolicy> 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<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector; + typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector; + typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector; + typedef Vector<GCHelperState*, 0, SystemAllocPolicy> GCHelperStateVector; + typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector; + typedef Vector<PromiseTask*, 0, SystemAllocPolicy> PromiseTaskVector; + + // List of available threads, or null if the thread state has not been initialized. + using HelperThreadVector = Vector<HelperThread, 0, SystemAllocPolicy>; + UniquePtr<HelperThreadVector> 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<bool> 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 <typename T> + 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<GlobalObject*> 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 <typename T> + 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<PerThreadData> threadData; + mozilla::Maybe<Thread> 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<bool, mozilla::Relaxed> pause; + + /* The current task being executed by this thread, if any. */ + mozilla::Maybe<mozilla::Variant<jit::IonBuilder*, + wasm::IonCompileTask*, + PromiseTask*, + ParseTask*, + SourceCompressionTask*, + GCHelperState*, + GCParallelTask*>> currentTask; + + bool idle() const { + return currentTask.isNothing(); + } + + /* Any builder currently being compiled by Ion on this thread. */ + jit::IonBuilder* ionBuilder() { + return maybeCurrentTaskAs<jit::IonBuilder*>(); + } + + /* Any wasm data currently being optimized on this thread. */ + wasm::IonCompileTask* wasmTask() { + return maybeCurrentTaskAs<wasm::IonCompileTask*>(); + } + + /* Any source being parsed/emitted on this thread. */ + ParseTask* parseTask() { + return maybeCurrentTaskAs<ParseTask*>(); + } + + /* Any source being compressed on this thread. */ + SourceCompressionTask* compressionTask() { + return maybeCurrentTaskAs<SourceCompressionTask*>(); + } + + /* Any GC state for background sweeping or allocating being performed. */ + GCHelperState* gcHelperTask() { + return maybeCurrentTaskAs<GCHelperState*>(); + } + + /* State required to perform a GC parallel task. */ + GCParallelTask* gcParallelTask() { + return maybeCurrentTaskAs<GCParallelTask*>(); + } + + void destroy(); + + static void ThreadMain(void* arg); + void threadLoop(); + + private: + template <typename T> + T maybeCurrentTaskAs() { + if (currentTask.isSome() && currentTask->is<T>()) + return currentTask->as<T>(); + + 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<PromiseTask> 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<JSScript*, + JSCompartment*, + ZonesInState, + JSRuntime*, + AllCompilations>; + +/* + * 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<Mutex> +{ + using Base = LockGuard<Mutex>; + + 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<Mutex> +{ + using Base = UnlockGuard<Mutex>; + + 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<frontend::CompileError*> 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<bool, mozilla::Relaxed> abort_; + + // Stores the result of the compression. + enum ResultType { + OOM, + Aborted, + Success + } result; + + mozilla::Maybe<SharedImmutableString> 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 */ |