Raw File
HelperThreads.h
/* -*- 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 */
back to top